diff --git a/.gitignore b/.gitignore index b0a36c5fe..39db8f1fe 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ _ur_addons/_public .npmignore /.parcel-cache /public* +/dist_* # the code-workspace file also hides node_modules from file list **/node_modules diff --git a/@build-turbo360.js b/@build-turbo360.js new file mode 100644 index 000000000..23ee8f884 --- /dev/null +++ b/@build-turbo360.js @@ -0,0 +1,225 @@ +const process = require('process'); +const fs = require('fs-extra'); +const child_process = require('child_process'); +const shell = require('shelljs'); +const argv = require('minimist')(process.argv.slice(1)); +const dotenv = require('dotenv'); + +const PR = 'BUILD\t'; +const TR = '\x1b[0m'; +const CY = '\x1b[33m'; +const CR = '\x1b[41m'; + +const DIST_FOLDER = './dist_turbo360'; + +if (!shell.which('git')) { + shell.echo( + `\x1b[30;41m You must have git installed to run the net.create/Turbo360 devtool \x1b[0m` + ); + shell.exit(0); +} + +const param1 = argv._[1]; + +switch (param1) { + case 'package-turbo360': + f_PackageWebTurbo360(argv._[2]); + break; + case 'deploy-turbo360': + f_DeployWebTurbo360(); + break; + + default: + console.log(`${PR}\n- unknown command '${param1}'\n`); +} + +function f_PackageWebTurbo360(template = '_blank') { + console.log(`\n`); + console.log(PR, `packaging for ${CY}Turbo-360${TR}`); + console.log(PR, `erasing ./public and ${DIST_FOLDER} directories`); + shell.rm('-rf', DIST_FOLDER, './public'); + console.log(PR, `compiling web into ./public`); + + // Package the net.create application in "standalone" mode + let res = shell.exec(`npx brunch build -e package`, { silent: true }); + u_checkError(res); + + // Prepare a local copy of the Turbo360 NodeJS/Express base template + // See: https://github.com/Vertex-Labs/base-template-netcreate + console.log( + PR, + `cloning latest ${CY}Turbo-360${TR} net.create base template into ${DIST_FOLDER}` + ); + res = shell.exec( + `git clone https://github.com/Vertex-Labs/base-template-netcreate.git ${DIST_FOLDER}`, + { silent: true } + ); + if (res.code !== 0) { + console.error( + PR, + `${CR}Unable to clone Turbo 360 Base Template - do you have access?${TR}:` + ); + process.exit(1); + } + + console.log(PR, `installing ${CY}Turbo-360${TR} Node dependencies...`); + shell.cd(DIST_FOLDER); + res = shell.exec('npm i --omit=dev', { silent: true }); + if (res.code !== 0) { + console.error( + PR, + `${CR}Unable to install Turbo 360 Base Template NodeJS dependencies${TR}` + ); + console.error(PR, `\t${res.stderr}`); + process.exit(1); + } + shell.cd(__dirname); + + console.log(PR, `Copying web-app...`); + // Copy the created net.create web-app bundle into the template + fs.copySync('./public', `${DIST_FOLDER}/public`); + fs.moveSync(`${DIST_FOLDER}/public/index.ejs`, `${DIST_FOLDER}/views/home.html`); + fs.removeSync(`${DIST_FOLDER}/public/index.html`); + + // TODO: Consider copying TOML and other files + // Rename TOML/DB files to general name "db" and "template.toml" so they can be located without + // having a dynamic configuration in URSYS Turbo360 backend + // console.log(PR, `Copying resources (using template: ${template})`); + // fs.copySync(`./templates/${template}/resources`, './dist/public/resources'); + + console.log(PR, `${CY}Turbo-360 packaging complete${TR}`); + console.log( + PR, + `To deploy, type ${CY}npm run deploy-turbo360${TR} and follow the prompts` + ); +} + +function f_DeployWebTurbo360() { + let res = 0; + + console.log(PR, `Welcome to the Turbo-360 Deployment Tool!`); + console.log(PR, `Please select the ${CY}Turbo-360${TR} project to deploy to:`); + + // First, connect the local project: + shell.cd(DIST_FOLDER); + + try { + child_process.execFileSync('npx turbo', ['connect'], { + stdio: 'inherit', + shell: true + }); + } catch (err) { + if (err.status !== 0) { + f_HandleDeployError(res.code); + process.exit(1); + } + } + + // .env file contains the slug + if (!fs.existsSync('.env')) { + console.log( + PR, + `You must connect your local project to a Turbo-360 project by selecting an option` + ); + process.exit(1); + } + + const { TURBO_PROJECT = null, TURBO_PROJECT_SLUG = null } = dotenv.parse( + fs.readFileSync('.env') ?? '' + ); + + // Second, do the two deployment steps: + console.log( + PR, + `Deploying to ${CY}Turbo-360${TR} Project ${CY}${TURBO_PROJECT}${TR}` + ); + + console.log(PR, `Beginning Turbo-360 deployment...`); + console.log(PR, `Please wait, this process may take several minutes....`); + try { + res = shell.exec('npx turbo deploy', { silent: true }); + if (res.code !== 0) { + f_HandleDeployError(res.code); + process.exit(1); + } + + res = shell.exec('npx turbo deploy -t static', { silent: true }); + if (res.code !== 0) { + f_HandleDeployError(res.code); + process.exit(1); + } + + const url = `https://${TURBO_PROJECT_SLUG}.turbo360-staging.com`; + console.log( + '\nDeployment complete, you can access the site using the following URLs:' + ); + console.log(`\tApplication: ${url}/`); + } catch (err) { + // Unexpected errors + console.log(PR, `unexpected error during Turbo-360 deployment: ${err}`); + process.exit(-1); + } finally { + shell.cd(__dirname); + } + + // Local function + function f_HandleDeployError(exitCode) { + if (exitCode) { + // FUTURE: This should ideally be exported from the CLI tool, or alternatively, the CLI tool + // should expose a programmatic interface rather than mediate this through the shell + const TURBO360_ERRORS = { + // General errors + UNSPECIFIED: { exitCode: 1 }, + INVALID_PARAMS: { exitCode: 2 }, + NOT_LOGGED_IN: { exitCode: 3 }, + + // Deploy-specific errors + PROJECT_NOT_CONNECTED: { exitCode: 100 }, + PROJECT_NOT_FOUND: { exitCode: 101 }, + NOT_AUTHORIZED: { exitCode: 102 } + }; + + // Non-zero exit code, interpret it + switch (exitCode) { + case TURBO360_ERRORS.NOT_LOGGED_IN: + console.log(PR, `You must log in to ${CY}Turbo-360${TR} to deploy.`); + break; + + case TURBO360_ERRORS.PROJECT_NOT_CONNECTED: + console.log( + PR, + `Your local codebase must be connected to a ${CY}Turbo-360${TR} project to continue.` + ); + break; + + case TURBO360_ERRORS.PROJECT_NOT_FOUND: + case TURBO360_ERRORS.NOT_AUTHORIZED: + console.log( + PR, + `The specified ${CY}Turbo-360${TR} project does not exist or you do not have access to it.` + ); + break; + + default: + // All other errors + console.log( + PR, + `Unexpected error while performing the ${CY}Turbo-360${TR} deployment: ${exitCode}.` + ); + break; + } + + console.log( + PR, + `\tPlease review the ${CY}Turbo-360${TR} deployment notes in ${CY}README-Turbo360.md${TR}` + ); + } + } +} + +function u_checkError(execResults) { + if (!execResults.stderr) return; + console.log(`${CR}*** ERROR IN NETCREATE EXEC ***${TR}`); + console.log(execResults.stderr); + process.exit(0); +} diff --git a/app/assets/index.ejs b/app/assets/index.ejs index 044cfcf5e..d795ab584 100644 --- a/app/assets/index.ejs +++ b/app/assets/index.ejs @@ -41,7 +41,10 @@ }, socket: { uaddr: '<%=uaddr%>', - uport: '<%=uport%>' + uport: '<%=uport%>', + wss: <%=wss%>, + preamble: '<%-preamble%>', + heartbeat: <%=heartbeat%> } }; // diff --git a/app/unisys/client-network.js b/app/unisys/client-network.js index 691cb2398..f174ab348 100644 --- a/app/unisys/client-network.js +++ b/app/unisys/client-network.js @@ -85,7 +85,9 @@ NETWORK.Connect = function (datalink, opt) { // create websocket // uses values that were embedded in index.ejs on load - let wsURI = `ws://${NETSOCK.uaddr}:${NETSOCK.uport}`; + const wsHost = `${NETSOCK.uaddr}${NETSOCK.uport ? `:${NETSOCK.uport}` : ''}`; + let wsURI = `${NETSOCK.wss ? 'wss' : 'ws'}://${wsHost}`; + NETSOCK.ws = new WebSocket(wsURI); if (DBG.connect) console.log(PR, 'OPEN SOCKET TO', wsURI); @@ -93,6 +95,12 @@ NETWORK.Connect = function (datalink, opt) { NETWORK.AddListener('open', function (event) { if (DBG.connect) console.log(PR, '..OPEN', event.target.url); m_status = M2_CONNECTED; + + // If a preamble has been defined, send it upon opening the connection + if (NETSOCK.preamble) { + NETSOCK.ws.send(NETSOCK.preamble); + } + // message handling continues in 'message' handler // the first message is assumed to be registration data }); @@ -153,7 +161,7 @@ function m_HandleRegistrationMessage(msgEvent) { // (4) network is initialized if (typeof m_options.success === 'function') m_options.success(); // (5) initialize heartbeat timer - m_ResetHearbeatTimer(); + if (NETSOCK.ws.heartbeat) m_ResetHearbeatTimer(); } /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /** When a heartbeat ping is received, respond with a pong to let the server diff --git a/brunch-server.js b/brunch-server.js index 231b2687f..12c5b0f9c 100644 --- a/brunch-server.js +++ b/brunch-server.js @@ -111,7 +111,10 @@ module.exports = (config, callback) => { ukey, // socket address uaddr, - uport + uport, + wss: false, + preamble: null, + heartbeat: true }; res.render(indexFile, templateProps); // adding next() causes 'headers already sent' error diff --git a/package-lock.json b/package-lock.json index f99e4c85b..0271ccef2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,6 +71,7 @@ "chokidar": "^3.5.3", "clean-css-brunch": "^2.10.0", "copycat-brunch": "^1.1.1", + "dotenv": "^16.4.5", "ejs": "^3.1.9", "eslint": "^8.17.0", "eslint-config-prettier": "^8.5.0", @@ -7307,6 +7308,18 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", diff --git a/package.json b/package.json index 8243dce7b..2ccde12ba 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,9 @@ "package:debug": "brunch watch -e package_debug -s", "clean": "rm -rf ./public ./node_modules && npm exec --workspaces -- npm run clean", "debug": "LOGGY_STACKS=true BRUNCH_DEVTOOLS=true ./brunch-debug watch --server", - "log": "tail -f -n100 \"$(ls -at ./runtime/logs/* | head -n 1)\"" + "log": "tail -f -n100 \"$(ls -at ./runtime/logs/* | head -n 1)\"", + "package-turbo360": "node @build-turbo360.js package-turbo360", + "deploy-turbo360": "node @build-turbo360.js deploy-turbo360" }, "repository": { "type": "git", @@ -45,6 +47,7 @@ "chokidar": "^3.5.3", "clean-css-brunch": "^2.10.0", "copycat-brunch": "^1.1.1", + "dotenv": "^16.4.5", "ejs": "^3.1.9", "eslint": "^8.17.0", "eslint-config-prettier": "^8.5.0",