From 71a887d91d58eb59d979982de14b1bdec92ff298 Mon Sep 17 00:00:00 2001 From: danrahn Date: Sat, 23 Mar 2024 13:15:51 -0700 Subject: [PATCH] Add basic CLI options Add -v/--version and -h/--help CLI options that prints out version info/help, then exits the process. Also modify the node source so that {binary} -v indicates that '--' must be added to pass arguments directly to MarkerEditor. --- Build/build.cjs | 59 +++++++++++++++++++++---- Server/MarkerEditor.js | 97 +++++++++++++++++++++++++++++++++++------- 2 files changed, 132 insertions(+), 24 deletions(-) diff --git a/Build/build.cjs b/Build/build.cjs index 5b141d7..f9dfd05 100644 --- a/Build/build.cjs +++ b/Build/build.cjs @@ -121,11 +121,42 @@ async function toExe() { const arch = getArch(); let nodeVersion = fallbackNodeVersion; + + // nexe doesn't appear to take into account that the currently cached build output is a different + // target architecture. To get around that, ignore the standard 'out' folder and check for + // architecture-specific output folders. If it doesn't exist, do a full build and rename the + // output to an architecture-specific folder, and link that to the standard 'out' folder. This + // relies on internal nexe behavior, but since it's dev-only, nothing user-facing should break if + // nexe changes, this section will just have to be updated. + const temp = process.env.NEXE_TEMP || join(homedir(), '.nexe'); + const oldOut = join(temp, nodeVersion, 'out'); + if (args.includes('version')) { const idx = args.indexOf('version'); if (idx < args.length - 1) { nodeVersion = args[idx + 1]; } + } else if (args.includes('clean')) { + const tryRm = out => { + try { + fs.rmSync(out, { recursive : true, force : true }); + } catch (ex) { + console.warn(`\tUnable to clear output ${out}`); + } + }; + + console.log('\nCleaning existing cached output'); + if (fs.existsSync(oldOut)) { + console.log('\tClearing old output directory'); + tryRm(oldOut); + } + + for (const cachedOut of ['arm64', 'ia32', 'x64']) { + if (fs.existsSync(oldOut + cachedOut)) { + console.log(`\tClearing out ${cachedOut} cache`); + tryRm(oldOut + cachedOut); + } + } } else { // Find the latest LTS version try { @@ -142,14 +173,6 @@ async function toExe() { console.log(`Attempting to build ${platform}-${arch}-${nodeVersion}`); - // nexe doesn't appear to take into account that the currently cached build output is a different - // target architecture. To get around that, ignore the standard 'out' folder and check for - // architecture-specific output folders. If it doesn't exist, do a full build and rename the - // output to an architecture-specific folder, and link that to the standard 'out' folder. This - // relies on internal nexe behavior, but since it's dev-only, nothing user-facing should break if - // nexe changes, this section will just have to be updated. - const temp = process.env.NEXE_TEMP || join(homedir(), '.nexe'); - const oldOut = join(temp, nodeVersion, 'out'); const archOut = oldOut + arch; const hadCache = fs.existsSync(archOut); if (hadCache) { @@ -189,6 +212,26 @@ async function toExe() { resolve(__dirname, '../dist/built.cjs'), ], patches : [ + async (compiler, next) => { + const isWin = process.platform === 'win32'; + const bin = isWin ? '.\\\\MarkerEditor.exe' : './MarkerEditor'; + await compiler.replaceInFileAsync( + 'src/node.cc', + /\bNODE_VERSION\b/, + `"Marker Editor: v${version}\\n` + + `Node.js: " NODE_VERSION ` + + `"\\n\\nUse '--' to pass arguments directly to Marker Editor (e.g. ${bin} -- -v)\\n"` + ); + + await compiler.replaceInFileAsync( + 'lib/internal/main/print_help.js', + /^ {4}'Usage: node/, + ` 'NOTE: Printing Node help use \\'--\\' to pass arguments directly to Marker Editor\\n` + + ` e.g. ${bin} -- --help\\n\\n' +\n 'Usage: node` + ); + + return next(); + }, async (compiler, next) => { if (process.platform !== 'win32') { return next(); diff --git a/Server/MarkerEditor.js b/Server/MarkerEditor.js index d198ffb..8f41d7c 100644 --- a/Server/MarkerEditor.js +++ b/Server/MarkerEditor.js @@ -1,5 +1,5 @@ /** External dependencies */ -import { existsSync, mkdirSync, writeFileSync } from 'fs'; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; import { createServer } from 'http'; import { join } from 'path'; import Open from 'open'; @@ -25,6 +25,14 @@ import ServerCommands from './ServerCommands.js'; import ServerError from './ServerError.js'; import { ThumbnailManager } from './ThumbnailManager.js'; +/** + * @typedef {Object} CLIArguments + * @property {boolean} isTest Indicates this is a test run + * @property {string?} configOverride The path to a config file to override the existing one + * @property {boolean} version The user passed `-v`/`--version` to the command line + * @property {boolean} help The user passed `-h`/`--help`/`--?` to the command line + */ + const Log = new ContextualLog('ServerCore'); /** @@ -38,14 +46,18 @@ let IsTest = false; /** Initializes and starts the server */ async function run() { setupTerminateHandlers(); - const testData = checkTestData(); + const argInfo = checkArgs(); + if (shouldExitEarly(argInfo)) { + process.exit(0); + } + // In docker, the location of the config and backup data files are not the project root. const dataRoot = process.env.IS_DOCKER ? '/Data' : ProjectRoot(); - if (!testData.isTest) { + if (!argInfo.isTest) { await FirstRunConfig(dataRoot); } - const config = MarkerEditorConfig.Create(testData, dataRoot); + const config = MarkerEditorConfig.Create(argInfo, dataRoot); // Set up the database, and make sure it's the right one. const queryManager = await PlexQueryManager.CreateInstance(config.databasePath()); @@ -418,34 +430,87 @@ async function handlePost(req, res) { } /** - * Returns test override data specified in the command line, if any. - * @returns {{isTest: boolean, configOverride : string?}} */ -function checkTestData() { - const testData = { + * Parse command line arguments. + * @returns {CLIArguments} */ +function checkArgs() { + const argInfo = { isTest : false, configOverride : null, + version : false, + help : false, }; - if (process.argv.indexOf('--test') !== -1) { - testData.isTest = true; + const argsLower = process.argv.map(x => x.toLowerCase()); + if (argsLower.includes('-v') || argsLower.includes('--version')) { + argInfo.version = true; + } + + if (argsLower.includes('-h') || argsLower.includes('--help') || argsLower.includes('-?') || argsLower.includes('/?')) { + argInfo.help = true; + } + + if (argsLower.includes('--test')) { + argInfo.isTest = true; IsTest = true; // Tests default to testConfig.json, but it can be overridden below - testData.configOverride = 'testConfig.json'; + argInfo.configOverride = 'testConfig.json'; } - const configIndex = process.argv.indexOf('--config_override'); - if (configIndex !== -1) { - if (process.argv.length <= configIndex - 1) { + const coi = argsLower.indexOf('--config_override'); + if (coi !== -1) { + if (process.argv.length <= coi - 1) { Log.critical('Invalid config override file detected, aborting...'); // We're very early into boot. Just get out of here. process.exit(1); } - testData.configOverride = process.argv[configIndex + 1]; + argInfo.configOverride = process.argv[coi + 1]; } - return testData; + return argInfo; +} + +/** + * Process command line arguments, spits out info if needed, and returns + * whether we should exit the program early. + * @param {CLIArguments} args */ +function shouldExitEarly(args) { + let version; + if (args.version || args.help) { + const packagePath = join(ProjectRoot(), 'package.json'); + if (existsSync(packagePath)) { + try { + version = JSON.parse(readFileSync(packagePath).toString()).version; + console.log(`Marker Editor version ${version}`); + } catch (err) { + console.log('Error retrieving version info.'); + } + } else { + console.log('Error retrieving version info.'); + } + + if (args.version) { + return true; + } + } + + if (args.help) { + const isBin = process.argv[1]?.includes('built.cjs'); + const isWin = process.platform === 'win32'; + const invoke = (isBin ? (isWin ? '.\\MarkerEditor.exe' : './MarkerEditor') : 'node app.js'); + console.log(`Usage: ${invoke} [options]`); + console.log(); + console.log(` OPTIONS`); + console.log(` -v | --version Print out the current version of MarkerEditor.`); + console.log(` -h | --help Print out this help text.`); + console.log(` --config_override [config] Use the given config file instead of the standard config.json`); + console.log(` --test Indicates we're launching MarkerEditor for tests. Do not set manually.`); + console.log('\n For setup and usage instructions, visit https://github.com/danrahn/MarkerEditorForPlex/wiki.'); + return true; + } + + return false; } export { run };