UNPKG

profoundjs

Version:

Profound.js Framework and Server

728 lines (654 loc) 26.2 kB
"use strict"; /* This is NPM script: npm run setup * Performs additional installation. * Kicked off from 'complete_install.js' which is manually run by user. * Can also be run directly for advanced/automated installation. */ // Platform-specific dependency versions. const idb_connector_version = "1.2.16"; const node_pty_version = "^2"; const child_process = require("child_process"); const compareVersions = require("compare-versions"); const crypto = require("crypto"); const fs = require("fs-extra"); const iutils = require("./install_utils.js"); const os = require("os"); const minimist = require("minimist"); const path = require("path"); const util = require("util"); const uuidv4 = require("uuid").v4; const IBMi = iutils.isIBMi(); let RED = "\x1b[31m"; let CYAN = "\x1b[36m"; let RESET = "\x1b[0m"; const logDir = path.join(os.tmpdir(), "profoundjs-" + uuidv4()); const logPath = path.join(logDir, "setup.log"); let logFile; let SILENT_MODE = false; (async() => { try { // Parse arguments. const args = minimist( process.argv.slice(2), { alias: { "c": "config-file", "h": "help", "s": "silent" }, boolean: [ "h", "ibmi-connector-library", "ibmi-instance-autostart", "nodegit", "silent", "installSamples" ], string: [ "c", "ibmi-instance", "ibmi-instance-ccsid", "ibmi-instance-node-path", "nodegit-version" ], unknown: function(arg) { console.error("Unknown argument:", arg); process.exit(1); } } ); // Show help and quit, if requested. if (args["help"]) { const HELP = `Usage: npm run setup -- [OPTION] -c, --config-file=<file> Alternate config file. -h, --help Print this help and exit. --nodegit Install nodegit. --nodegit-version=version Override nodegit version. --ibmi-connector-library Install IBM i Connector library. --ibmi-instance=<name> Create/update STRTCPSVR instance config. --ibmi-instance-autostart Autostart flag for STRTCPSVR instance. --ibmi-instance-ccsid=<ccsid> CCSID for STRTCPSVR instance. --ibmi-instance-node-path=<path> Node.js binary path for STRTCPSVR instance. --samples Install sample code. Performs additional installation using config values and CLI options. Default config file is <INSTALL_DIRECTORY>/config.js. --config-file can be an absolute path or path relative to working directory. --nodegit-version is ignored if --nodegit is not used. --nodegit-version can accept an NPM package version, tag, or version range. IBM i-related values are invalid when not installing on IBM i. IBM i instance-related values are ignored unless --ibmi-instance is specified. --ibmi-connector-library installs to library specified in config file. --ibmi-instance creates or replaces STRTCPSVR config. --ibmi-instance-autostart sets "autostart=1" in STRTCPSVR config. --ibmi-instance-ccsid sets "ccsid" in STRTCPSVR config. --ibmi-instance-node-path sets "nodePath" in STRTCPSVR config. `; console.log(HELP); process.exit(0); } if (args.silent === true) { SILENT_MODE = true; RED = CYAN = RESET = ""; } if (!SILENT_MODE) { // Initialize log file. fs.mkdirSync(logDir); logFile = fs.openSync(logPath, "w"); } // Validate arguments. if (args._.length > 0) { logError("Unknown argument:", args._[0]); die(); } const deployDir = iutils.getDeployDir(); if (!deployDir) { logError("Can't find deployment directory."); die(); } const configPath = args["config-file"] !== undefined ? path.resolve(args["config-file"]) : iutils.getConfigPath(); if (!fileExists(configPath)) { logError(`Configuration file ${configPath} not found.`); die(); } const config = require(configPath); if (args["ibmi-connector-library"]) { if (!IBMi) { logError(`--ibmi-connector-library is not valid on ${os.platform()}`); die(); } if (typeof config.connectorLibrary !== "string") { logError("connectorLibrary is missing from config or is invalid."); die(); } let error = iutils.validateIBMiName(config.connectorLibrary); if (error) { logError("connectorLibrary is invalid: " + error); die(); } if (config.connectorIASP !== undefined) { error = iutils.validateIBMiIASP(config.connectorIASP); if (error) { logError("connectorIASP is invalid: " + error); die(); } } } if (args["ibmi-instance"] !== undefined) { if (!IBMi) { logError(`--ibmi-instance is not valid on ${os.platform()}`); die(); } let error = iutils.validateIBMiName(args["ibmi-instance"]); if (error) { logError("--ibmi-instance is invalid: " + error); die(); } if (args["ibmi-instance-ccsid"] !== undefined) { error = iutils.validateIBMiCCSID(parseInt(args["ibmi-instance-ccsid"], 10)); if (error) { logError("--ibmi-instance-ccsid is invalid: " + error); die(); } } if (args["ibmi-instance-node-path"] !== undefined) { const nodePath = args["ibmi-instance-node-path"].trim(); if (!fileExists(nodePath)) { logError("--ibmi-instance-node-path is invalid: " + nodePath + " does not exist."); die(); } } } // Install platform-specific dependencies. if (IBMi) { log(`Installing idb-connector...`); child_process.execSync( `npm install idb-connector@"${idb_connector_version}"`, { cwd: iutils.getDeployDir(), stdio: [ "ignore", SILENT_MODE ? process.stdout : logFile, SILENT_MODE ? process.stderr : logFile ] } ); } else { log(`Installing profoundjs-node-pty...`); child_process.execSync( `npm install --save-optional profoundjs-node-pty@"${node_pty_version}"`, { cwd: iutils.getDeployDir(), stdio: [ "ignore", SILENT_MODE ? process.stdout : logFile, SILENT_MODE ? process.stderr : logFile ] } ); } // Install nodegit, if necessary. if (args["nodegit"]) { let version; if (args["nodegit-version"]) { version = args["nodegit-version"]; } else { // Current stable version of nodegit doesn't have pre-built binaries for Node > 14. // If installing on Node > 14, use alpha version with pre-built binaries. version = "0.27.0"; // Current stable version. if (compareVersions("14.*.*", process.versions.node) === -1) { version = "0.28.0-alpha.18"; } } log(`Installing nodegit...`); try { child_process.execSync( `npm install nodegit@"${version}"`, { cwd: iutils.getDeployDir(), stdio: [ "ignore", SILENT_MODE ? process.stdout : logFile, SILENT_MODE ? process.stderr : logFile ] } ); } catch (error) { logError("nodegit installation failed."); die(); } } // Create modules directory. if (directoryExists(path.join(deployDir, "modules"))) { log("modules directory exists."); } else { log("Creating modules directory."); fs.mkdirSync(path.join(deployDir, "modules")); } // Create plugins directory. if (directoryExists(path.join(deployDir, "plugins"))) { log("plugins directory exists."); } else { log("Creating plugins directory."); fs.mkdirSync(path.join(deployDir, "plugins")); } // Grant PROFOUNDJS user all permissions to modules directory tree. if (IBMi) { runCommand("CHGAUT OBJ('" + deployDir + "') USER(PROFOUNDJS) DTAAUT(*RWX) OBJAUT(*ALL)"); runCommand("CHGAUT OBJ('" + path.join(deployDir, "modules") + "') USER(PROFOUNDJS) DTAAUT(*RWX) OBJAUT(*ALL) SUBTREE(*ALL)"); } // Create puiscreens.json. createPuiscreens(deployDir); // Create puiuplexit.js. if (fileExists(path.join(deployDir, "modules", "puiuplexit.js"))) { log("puiuplexit.js file exists."); } else { log("Creating puiuplexit.js."); copyFile(path.join(__dirname, "modules", "puiuplexit.js"), path.join(deployDir, "modules"), "utf8"); } // Create puidnlexit.js. if (fileExists(path.join(deployDir, "modules", "puidnlexit.js"))) { log("puidnlexit.js file exists."); } else { log("Creating puidnlexit.js."); copyFile(path.join(__dirname, "modules", "puidnlexit.js"), path.join(deployDir, "modules"), "utf8"); } if (args["installSamples"]) { // Create samples directory. if (directoryExists(path.join(deployDir, "modules", "pjssamples"))) { fs.removeSync(path.join(deployDir, "modules", "pjssamples")); } log("Copying pjssamples."); copyDir(path.join(__dirname, "modules", "pjssamples"), path.join(deployDir, "modules")); const copyWSMsg = "Copying sample workspace into "; // Create PAPI samples directory let wsPath = path.join(deployDir, "modules", "papisamples"); if (directoryExists(wsPath)) { log(`Skipping sample workspace, ${wsPath} -- directory exists.`); } else { log(copyWSMsg + wsPath); copyDir(path.join(__dirname, "modules", "papisamples"), path.join(deployDir, "modules")); } // Create oauth2sample directory. wsPath = path.join(deployDir, "modules", "oauth2sample"); if (directoryExists(wsPath)) { log(`Skipping sample workspace, ${wsPath} -- directory exists.`); } else { log(copyWSMsg + wsPath); copyDir(path.join(__dirname, "modules", "oauth2sample"), path.join(deployDir, "modules")); } // Create mathoperation.js. const fPath = path.join(deployDir, "plugins", "mathoperation.js"); if (fileExists(fPath)) { log(`Skipping sample Low-code plugin: ${fPath} -- file exists.`); } else { log(`Creating sample Low-code plugin: ${fPath}`); copyFile(path.join(__dirname, "plugins", "mathoperation.js"), path.join(deployDir, "plugins"), "utf8"); } } // Create store_credentials.js. if (fileExists(path.join(deployDir, "store_credentials.js"))) { log("store_credentials.js file exists."); } else { copyFile(path.join(__dirname, "store_credentials.js"), deployDir, "utf8"); log("store_credentials.js created."); } // Create store_options.js. if (fileExists(path.join(deployDir, "store_options.js"))) { log("store_options.js file exists."); } else { copyFile(path.join(__dirname, "store_options.js"), deployDir, "utf8"); log("store_options.js created."); } // Create call.js. if (fileExists(path.join(deployDir, "call.js"))) { log("call.js file exists."); } else { copyFile(path.join(__dirname, "call.js"), deployDir, "utf8"); log("call.js created."); } // Create lic_client.js copyFile(path.join(__dirname, "lic_client.js"), deployDir, "utf8"); log("lic_client.js created."); // Create gen_key.js. if (fileExists(path.join(deployDir, "gen_key.js"))) { log("gen_key.js file exists."); } else { copyFile(path.join(__dirname, "gen_key.js"), deployDir, "utf8"); log("gen_key.js created."); } // Create get_pjscall_key.js. if (fileExists(path.join(deployDir, "get_pjscall_key.js"))) { log("get_pjscall_key.js file exists."); } else { copyFile(path.join(__dirname, "get_pjscall_key.js"), deployDir, "utf8"); log("get_pjscall_key.js created."); } // Convert start.js to Promises, if necessary. const convertStartJS = require("./convertStartJS.js"); if (convertStartJS(path.join(deployDir, "start.js"))) { log("start.js file converted to Promises."); } // Install native IBM i components, if necessary. if (IBMi) { log(""); if (args["ibmi-connector-library"] || args["ibmi-instance"] !== undefined) { const portNumber = config.port || 8081; let connectorLibrary = "*NONE"; let connectorIASP; if (args["ibmi-connector-library"]) { connectorLibrary = config.connectorLibrary; connectorIASP = config.connectorIASP || "*SYSBAS"; } let svrname = "*NONE"; let autostart = true; let ccsid = 37; let nodePath = process.argv[0]; if (args["ibmi-instance"] !== undefined) { svrname = args["ibmi-instance"].toUpperCase(); if (process.argv.find(arg => arg.includes("ibmi-instance-autostart"))) { // minimist sets booleans to false if not passed. autostart = args["ibmi-instance-autostart"]; } if (args["ibmi-instance-ccsid"] !== undefined) { ccsid = args["ibmi-instance-ccsid"]; } if (args["ibmi-instance-node-path"] !== undefined) { nodePath = args["ibmi-instance-node-path"]; } } // First, try copying the save file let success = runCommand("CPYFRMSTMF FROMSTMF('" + __dirname + "/pjsdist.savf') TOMBR('/QSYS.LIB/QGPL.LIB/PJSDIST.FILE') MBROPT(*REPLACE)"); if (!success) { logError("Unable to create installer save file."); die(); } // Now, try restoring the PJSINSTALL program success = runCommand("RSTOBJ OBJ(PJSINSTALL) SAVLIB(QTEMP) DEV(*SAVF) OBJTYPE(*ALL) SAVF(QGPL/PJSDIST) RSTLIB(QGPL)"); if (!success) { // Clean up save file log(""); log("Cleaning up..."); runCommand("DLTF FILE(QGPL/PJSDIST)"); logError("Unable to restore installer save file."); die(); } // Now, try running PJSINSTALL let command = "QGPL/PJSINSTALL CONNLIB(" + connectorLibrary + ")"; if (connectorLibrary != "*NONE") { command += " CONNIASP(" + connectorIASP + ") CONNHOST('localhost') CONNPORT(" + portNumber + ")"; } command += " SVRNAME(" + svrname + ")"; if (svrname != "*NONE") { command += " SVRDIR('" + deployDir + "') " + "SVRAUTO(" + ((autostart) ? "*YES" : "*NO") + ") CCSID(" + ccsid + ") NODEPATH('" + nodePath + "')"; } success = runCommand(command, "-Ke"); // Clean up program and save file log(""); log("Cleaning up..."); runCommand("DLTPGM PGM(QGPL/PJSINSTALL)"); runCommand("DLTCMD CMD(QGPL/PJSINSTALL)"); runCommand("DLTPNLGRP PNLGRP(QGPL/PJSINSTALL)"); runCommand("DLTMSGF MSGF(QGPL/PJSINSTALL)"); runCommand("DLTF FILE(QGPL/PJSDIST)"); if (!success) { logError("PJSINSTALL command failed."); die(); } } if (args["ibmi-instance"] === undefined) { // Instance is restarted by PJSINSTALL command only when config is created/replaced. const instances = iutils.getIBMiInstances(deployDir); if (instances.length > 0) { const instance = instances[0]; log(""); runCommand(`ENDTCPSVR SERVER(*PJS) INSTANCE(${instance.name})`); log("Waiting for instance to end..."); // This should generally work as shutdown is relatively fast. await new Promise(resolve => setTimeout(resolve, 10000)); runCommand(`STRTCPSVR SERVER(*PJS) INSTANCE(${instance.name})`); } } } log(`\n${CYAN}Profound.js installation complete.${RESET}\n`); } catch (error) { logError(error); die(); } })(); function log(...args) { console.log(...args); logToFile(...args); } function logError(...args) { console.error(...args); logToFile(...args); } function logToFile(...args) { if (SILENT_MODE) { return; } let output = `${util.format(...args)}\n`; // Mimic console.log(). output = output.replace(/\x1b\[[0-9]+m/g, ""); // Strip terminal escape codes for colors. fs.writeFileSync(logFile, output, { flag: "a" }); } function die() { logError(`\n${RED}Profound.js installation failed.${RESET}`); if (!SILENT_MODE) { console.error(`${RED}See installation log:${RESET} ${logPath}\n`); } process.exit(1); } function copyDir(dir, destinationDir) { const dirName = path.basename(dir); fs.mkdirSync(path.join(destinationDir, dirName)); const files = fs.readdirSync(dir); files.forEach(function(file, index) { if (fs.lstatSync(path.join(dir, file)).isDirectory()) { // recurse copyDir(path.join(dir, file), path.join(destinationDir, dirName)); } else { copyFile(path.join(dir, file), path.join(destinationDir, dirName), null, null); } }); } function copyFile(file, destination, type) { if (type == null) type = "binary"; let toFile; if (directoryExists(destination)) { toFile = path.join(destination, path.basename(file)); } else { toFile = destination; } const content = fs.readFileSync(file, type); fs.writeFileSync(toFile, content, type); } function createPuiscreens(deployDir) { const targetPuiScreensFile = path.join(deployDir, "modules", "puiscreens.json"); function copyFileOldVersionIs(version, compareStr) { compareStr = compareStr || "="; log("Found Standard puiscreens.json file version " + compareStr + " " + version + ", replacing with current version..."); copyFile(path.join(__dirname, "modules", "puiscreens.json"), targetPuiScreensFile, "utf8"); log("puiscreens.json file was replaced."); } // Check which version of puiscreens.json is installed. // Version 4.7.0 ships with a new version of puiscreens where the bound fields (SSUSER, SSPASSWORD, SSSUBMIT, SSERROR, etc) are now lower case, // so this would be a breaking change. So our install logic is now as follows: - // 1. Check the SHA key of existing puiscreens.json. // 2. If it's an official PJS screen, no customizations have been made, so it's safe to replace with this version. // 3. If it's not an official version, check if we have lower case User & Password. If not, then backup existing version to puiscreens_bak.json and install this official version. // 4. Otherwise it's not an official version but should work OK. So leave existing puiscreens.json, and check if we already have a puiscreens_orig.json, and // create it if not. if (fileExists(targetPuiScreensFile)) { const data = fs.readFileSync(targetPuiScreensFile); const checksum = crypto.createHash("sha512").update(data).digest("hex"); // sha512 best for 64-bit let format = null; let puiScreens; let goodUser = false; let goodPassword = false; let goodSubmit = false; let goodError = false; switch (checksum) { case "4fb38daa851aacd21cf3aeaa92304df4e0353cae2b6dab3a5fd53c0cf4d42875b5efadbb4bb29642cbff07bfdd8e1c7a9c107f582905f7fc798c3841e888ee61": copyFileOldVersionIs("4.6.1", "<="); break; case "2e63c0e9816f52b515c45479ada102af806bc73b79d14109567500c529bfaa166fb981ddb6062a1ba0a7f8e36a11d674a955d2f003806f21165cb26ad0fbdf33": copyFileOldVersionIs("4.7.0"); break; case "b5eadc2df96e97de184cc331f133933c5f2945ab6c84997cfb30f2e6b478dfa6c8753bdb328b8257382f16a6d8e85b469af09f6d0eb872642300225590031c83": copyFileOldVersionIs("4.8.0"); break; case "05e7b8a2a69dd12f5dafecdd032be8e42bfe07d02dc0bce2c90d8586135130dcfa134e8851dd1df00fe52021ef4d81dab09be419711837b4a4cddf536019ccfe": case "b5e4d420d49d48c1dd08d0f50b88018fb64ee77248edcd9a656aee9f8ba723d0a91b18751f34cfc2efcd905f71834d5c2ac5cbff552fd85d164b710c3c2e079d": copyFileOldVersionIs("4.9.1"); break; case "1d469f6d2e4549dc259155a4b00d8f278ea89dc3566e18860ed9f6ccd406db8a47b0b3cbc359b397c59feac6faeb6a7fdc6ce2edbd3f120e295464dd41dd28f8": copyFileOldVersionIs("4.11.1"); break; case "313d7e363fc9f2df66f2803116487bc75d14e647be6da33fb7be8ac886a96a58c970eecc5b147fd1a07af3c559a0de61a32893875ec0be26e6840e1945a441c3": copyFileOldVersionIs("4.12.0"); break; case "ea10b5e751b736f4164d23e45399e6364dd95e074b36c4c996a5824fc5ba1a0436be6811c0f421510eecc57b6125e388150e5f0664dbaea681cd42e73855eb34": copyFileOldVersionIs("4.13.0"); break; case "9768e7f3df273d809845b93390a832ed6e5b77ccbd1b51bc7ad681c8261dba0b75bf9c3f802e9784ce01a1812e45bbbf95dc8a99bd6d3b3ea43f6e5a4c1a878e": case "4353f5ae5067b91821825ec20190e0adf96bc87493141fba3cf1da7cea8863af2e4358cd24019223a2704eee4170c75cb4037843ca7c1f289416555ee146a851": copyFileOldVersionIs("5.0.0"); break; case "2becc968e9db1acc1ffd6e3885d95c153d678468e69a4fdef0df725c06897b7f6ddcff82d703cb76724774729b18b56307445e728521311dbf256b97e17538d8": copyFileOldVersionIs("5.2.0"); break; case "af04803ac52709fd25f6fc87ff3ea309ad03ee6a9631f32511a5aa32c5ae50f34fd0e38e0d47dea223ac0c5dfb595199c623a83772af830edd21378055403e5c": copyFileOldVersionIs("6.0.0-beta.0"); break; case "cd3e32138fbf1dac7755c233e1b5fc0d14f800694184c92989ddc5e62bacf86306013bcf423bf837183c979f61d6b323e0581363297ca6cf7f1b15435e0fbff5": copyFileOldVersionIs("6.0.0-beta.5"); break; case "3aaab88d4cf922f01a09bab3424ecf81460c7679a6d51ecb0f187fdce97e627499128e9b8c46d83075098e7e8f7150b73936056b1accedff55a79c69d65cb20b": // Note: Betas were considered pre-release versions. 6.0.0 is the release version; so 6.0.0 is newer than 6.0.0-beta.X copyFileOldVersionIs("6.0.0"); break; case "41a350ae45c7616fe1824fb236b48ab64ca6d0de1023946e16ef9de4eea804649ff13123fa4ce630bb93308c8312f75e12448bbe58e91802dc8b12cc27bfd051": log("Found Standard puiscreens.json file version > 6.0.0, no need for update."); break; default: puiScreens = JSON.parse(data); log("Found Customized puiscreens.json file, analyzing contents..."); // We need to check the signon screen and make sure we have lower case bound fields if (Array.isArray(puiScreens.formats)) { format = puiScreens.formats.find(format => typeof format.screen["record format name"] === "string" && format.screen["record format name"].toLowerCase() === "signonscrn"); } if (format) { for (const j in format.items) { const itemValue = format.items[j].value; if (itemValue && typeof itemValue === "object") { if (itemValue.fieldName === "ssuser") goodUser = true; else if (itemValue.fieldName === "sspassword") goodPassword = true; } const itemResponse = format.items[j].response; if (itemResponse && typeof itemResponse === "object") { if (itemResponse.fieldName === "sssubmit") goodSubmit = true; } const itemHtml = format.items[j].html; if (itemHtml && typeof itemHtml === "object") { if (itemHtml.fieldName === "sserror") goodError = true; } if (goodUser && goodPassword && goodSubmit && goodError) break; } } if (goodUser && goodPassword && goodSubmit && goodError) { log("The Customized puiscreens.json file is compatible, so leaving it in place. Creating puiscreens_orig.json"); copyFile(path.join(__dirname, "modules", "puiscreens.json"), path.join(deployDir, "modules", "puiscreens_orig.json"), "utf8"); log("puiscreens_orig.json created. For latest functionality, please migrate standard fields into your customized version."); } else { log("The Customized puiscreens.json file is NOT compatible, backing it up to puiscreens_bak.json..."); copyFile(path.join(deployDir, "modules", "puiscreens.json"), path.join(deployDir, "modules", "puiscreens_bak.json"), "utf8"); log("puiscreens_bak.json created. Creating puiscreens.json ..."); copyFile(path.join(__dirname, "modules", "puiscreens.json"), targetPuiScreensFile, "utf8"); log("puiscreens.json created. Please migrate standard fields into your customized version."); } } } else { log("Creating puiscreens.json."); copyFile(path.join(__dirname, "modules", "puiscreens.json"), targetPuiScreensFile, "utf8"); } } function directoryExists(dir) { let exists = false; try { const stat = fs.statSync(dir); if (stat && stat.isDirectory()) exists = true; } catch (err) { exists = false; } return exists; } function fileExists(file) { let exists = false; try { const stat = fs.statSync(file); if (stat && stat.isFile()) exists = true; } catch (err) { exists = false; } return exists; } function runCommand(command, switches) { log("Executing IBM i command: " + command); const args = [command]; if (typeof switches == "string") { args.unshift(switches); } else { args.unshift("-e"); } const options = { stdio: [ "ignore", SILENT_MODE ? process.stdout : logFile, SILENT_MODE ? process.stderr : logFile ], cwd: __dirname, env: { QIBM_USE_DESCRIPTOR_STDIO: "Y", QIBM_MULTI_THREADED: "N" // Many IBM commands cannot run in multi-threaded mode... } }; const results = child_process.spawnSync("system", args, options); const code = results.status; const signal = results.signal; if (code != null) { if (code === 0) { return true; } else { logError("Process exited, code %d", code); } } else { logError("Process ended due to signal %s", signal); logError(results); // show full results including detailed error } return false; }