diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml index 977b35499..319befcee 100644 --- a/.github/workflows/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -28,7 +28,7 @@ jobs: - name: Cypress run uses: cypress-io/github-action@v6 with: - start: npm run dev -- --language c++ --noLocal + start: npm run dev -- --language c++ --no-local wait-on: 'http://localhost:10240' wait-on-timeout: 120 config: screenshotOnRunFailure=true,video=false diff --git a/Makefile b/Makefile index 996ddd392..e005165b8 100644 --- a/Makefile +++ b/Makefile @@ -84,7 +84,7 @@ prebuild: prereqs scripts .PHONY: run-only run-only: node-installed ## Runs the site like it runs in production without building it - env NODE_ENV=production $(NODE) $(NODE_ARGS) ./out/dist/app.js --webpackContent ./out/webpack/static $(EXTRA_ARGS) + env NODE_ENV=production $(NODE) $(NODE_ARGS) ./out/dist/app.js --webpack-content ./out/webpack/static $(EXTRA_ARGS) .PHONY: run run: ## Runs the site like it runs in production diff --git a/app.ts b/app.ts index 93570f64d..5a100ffd6 100755 --- a/app.ts +++ b/app.ts @@ -35,12 +35,12 @@ import url from 'node:url'; import * as fsSync from 'node:fs'; import fs from 'node:fs/promises'; import * as Sentry from '@sentry/node'; +import {Command, OptionValues} from 'commander'; import compression from 'compression'; import express from 'express'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import morgan from 'morgan'; -import nopt from 'nopt'; import PromClient from 'prom-client'; import responseTime from 'response-time'; import sanitize from 'sanitize-filename'; @@ -92,73 +92,73 @@ import type {Language, LanguageKey} from './types/languages.interfaces.js'; setBaseDirectory(new URL('.', import.meta.url)); -(nopt as any).invalidHandler = (key: string, val: unknown, types: unknown[]) => { - logger.error( - `Command line argument type error for "--${key}=${val}", - expected ${types.map((t: unknown) => typeof t).join(' | ')}`, - ); -}; +function parseNumberForOptions(value: string): number { + const parsedValue = Number.parseInt(value, 10); + if (Number.isNaN(parsedValue)) { + throw new Error(`Invalid number: "${value}"`); + } + return parsedValue; +} -type CompilerExplorerOptions = Partial<{ +interface CompilerExplorerOptions extends OptionValues { env: string[]; rootDir: string; - host: string; + host?: string; port: number; - propDebug: boolean; - debug: boolean; - dist: boolean; - archivedVersions: string; - noRemoteFetch: boolean; - tmpDir: string; - wsl: boolean; - language: string[]; - noCache: boolean; - ensureNoIdClash: boolean; - logHost: string; - logPort: number; - hostnameForLogging: string; + propDebug?: boolean; + debug?: boolean; + dist?: boolean; + remoteFetch: boolean; + tmpDir?: string; + wsl?: boolean; + language?: string[]; + cache: boolean; + ensureNoIdClash?: boolean; + logHost?: string; + logPort?: number; + hostnameForLogging?: string; suppressConsoleLog: boolean; - metricsPort: number; - loki: string; - discoveryonly: string; - prediscovered: string; + metricsPort?: number; + loki?: string; + discoveryOnly?: string; + prediscovered?: string; + webpackContent?: string; + local: boolean; version: boolean; - webpackContent: string; - noLocal: boolean; -}>; +} -// Parse arguments from command line 'node ./app.js args...' -const opts = nopt({ - env: [String, Array], - rootDir: [String], - host: [String], - port: [Number], - propDebug: [Boolean], - debug: [Boolean], - dist: [Boolean], - archivedVersions: [String], - // Ignore fetch marks and assume every compiler is found locally - noRemoteFetch: [Boolean], - tmpDir: [String], - wsl: [Boolean], - // If specified, only loads the specified languages, resulting in faster loadup/iteration times - language: [String, Array], - // Do not use caching for compilation results (Requests might still be cached by the client's browser) - noCache: [Boolean], - // Don't cleanly run if two or more compilers have clashing ids - ensureNoIdClash: [Boolean], - logHost: [String], - logPort: [Number], - hostnameForLogging: [String], - suppressConsoleLog: [Boolean], - metricsPort: [Number], - loki: [String], - discoveryonly: [String], - prediscovered: [String], - version: [Boolean], - webpackContent: [String], - noLocal: [Boolean], -}) as CompilerExplorerOptions; +const program = new Command(); +program + .name('compiler-explorer') + .description('Interactively investigate compiler output') + .option('--env ', 'Environment(s) to use', ['dev']) + .option('--root-dir ', 'Root directory for config files', './etc') + .option('--host ', 'Hostname to listen on') + .option('--port ', 'Port to listen on', parseNumberForOptions, 10240) + .option('--prop-debug', 'Debug properties') + .option('--debug', 'Enable debug output') + .option('--dist', 'Running in dist mode') + .option('--no-remote-fetch', 'Ignore fetch marks and assume every compiler is found locally') + .option('--tmpDir, --tmp-dir ', 'Directory to use for temporary files') + .option('--wsl', 'Running under Windows Subsystem for Linux') + .option('--language ', 'Only load specified languages for faster startup') + .option('--no-cache', 'Do not use caching for compilation results') + .option('--ensure-no-id-clash', "Don't run if compilers have clashing ids") + .option('--logHost, --log-host ', 'Hostname for remote logging') + .option('--logPort, --log-port ', 'Port for remote logging', parseNumberForOptions) + .option('--hostname-for-logging ', 'Hostname to use in logs') + .option('--suppressConsoleLog, --suppress-console-log', 'Disable console logging') + .option('--metricsPort, --metrics-port ', 'Port to serve metrics on', parseNumberForOptions) + .option('--loki ', 'URL for Loki logging') + .option('--discoveryonly, --discovery-only ', 'Output discovery info to file and exit') + .option('--prediscovered ', 'Input discovery info from file') + .option('--webpack-content ', 'Path to webpack content') + .option('--no-local', 'Disable local config') + .option('--version', 'Show version information'); + +program.parse(); + +const opts = program.opts(); if (opts.debug) logger.level = 'debug'; @@ -222,39 +222,29 @@ const releaseBuildNumber = (() => { return ''; })(); -function patchUpLanguageArg(languages: string[] | undefined): string[] | null { - if (!languages) return null; - if (languages.length === 1) { - // Support old style comma-separated language args. - return languages[0].split(','); - } - return languages; -} - -// Set default values for omitted arguments -const defArgs: AppArguments = { - rootDir: opts.rootDir || './etc', - env: opts.env || ['dev'], +const appArgs: AppArguments = { + rootDir: opts.rootDir, + env: opts.env, hostname: opts.host, - port: opts.port || 10240, + port: opts.port, gitReleaseName: gitReleaseName, releaseBuildNumber: releaseBuildNumber, - wantedLanguages: patchUpLanguageArg(opts.language), - doCache: !opts.noCache, - fetchCompilersFromRemote: !opts.noRemoteFetch, + wantedLanguages: opts.language, + doCache: opts.cache, + fetchCompilersFromRemote: opts.remoteFetch, ensureNoCompilerClash: opts.ensureNoIdClash, - suppressConsoleLog: opts.suppressConsoleLog || false, + suppressConsoleLog: opts.suppressConsoleLog, }; if (opts.logHost && opts.logPort) { - logToPapertrail(opts.logHost, opts.logPort, defArgs.env.join('.'), opts.hostnameForLogging); + logToPapertrail(opts.logHost, opts.logPort, appArgs.env.join('.'), opts.hostnameForLogging); } if (opts.loki) { logToLoki(opts.loki); } -if (defArgs.suppressConsoleLog) { +if (appArgs.suppressConsoleLog) { logger.info('Disabling further console logging'); suppressConsoleLog(); } @@ -276,12 +266,12 @@ function getFaviconFilename() { const propHierarchy = [ 'defaults', - defArgs.env, - defArgs.env.map(e => `${e}.${process.platform}`), + appArgs.env, + appArgs.env.map(e => `${e}.${process.platform}`), process.platform, os.hostname(), ].flat(); -if (!opts.noLocal) { +if (opts.local) { propHierarchy.push('local'); } logger.info(`properties hierarchy: ${propHierarchy.join(', ')}`); @@ -290,20 +280,20 @@ logger.info(`properties hierarchy: ${propHierarchy.join(', ')}`); if (opts.propDebug) props.setDebug(true); // *All* files in config dir are parsed -const configDir = path.join(defArgs.rootDir, 'config'); +const configDir = path.join(appArgs.rootDir, 'config'); props.initialize(configDir, propHierarchy); // Instantiate a function to access records concerning "compiler-explorer" // in hidden object props.properties const ceProps = props.propsFor('compiler-explorer'); const restrictToLanguages = ceProps('restrictToLanguages'); if (restrictToLanguages) { - defArgs.wantedLanguages = restrictToLanguages.split(','); + appArgs.wantedLanguages = restrictToLanguages.split(','); } const languages = (() => { - if (defArgs.wantedLanguages) { + if (appArgs.wantedLanguages) { const filteredLangs: Partial> = {}; - for (const wantedLang of defArgs.wantedLanguages) { + for (const wantedLang of appArgs.wantedLanguages) { for (const lang of Object.values(allLanguages)) { if (lang.id === wantedLang || lang.name === wantedLang || lang.alias.includes(wantedLang)) { filteredLangs[lang.id] = lang; @@ -459,8 +449,7 @@ function oldGoogleUrlHandler(req: express.Request, res: express.Response, next: } function startListening(server: express.Express) { - const ss = systemdSocket(); - let _port; + const ss: {fd: number} | null = systemdSocket(); // TODO: I'm not sure this works any more if (ss) { // ms (5 min default) const idleTimeout = process.env.IDLE_TIMEOUT; @@ -478,9 +467,15 @@ function startListening(server: express.Express) { server.all('*', reset); logger.info(` IDLE_TIMEOUT: ${idleTimeout}`); } - _port = ss; + logger.info(` Listening on systemd socket: ${JSON.stringify(ss)}`); + server.listen(ss); } else { - _port = defArgs.port; + logger.info(` Listening on http://${appArgs.hostname || 'localhost'}:${appArgs.port}/`); + if (appArgs.hostname) { + server.listen(appArgs.port, appArgs.hostname); + } else { + server.listen(appArgs.port); + } } const startupGauge = new PromClient.Gauge({ @@ -489,24 +484,8 @@ function startListening(server: express.Express) { }); startupGauge.set(process.uptime()); const startupDurationMs = Math.floor(process.uptime() * 1000); - if (Number.isNaN(Number.parseInt(_port))) { - // unix socket, not a port number... - logger.info(` Listening on socket: //${_port}/`); - logger.info(` Startup duration: ${startupDurationMs}ms`); - logger.info('======================================='); - server.listen(_port); - } else { - // normal port number - logger.info(` Listening on http://${defArgs.hostname || 'localhost'}:${_port}/`); - logger.info(` Startup duration: ${startupDurationMs}ms`); - logger.info('======================================='); - // silly express typing, passing undefined is fine but - if (defArgs.hostname) { - server.listen(_port, defArgs.hostname); - } else { - server.listen(_port); - } - } + logger.info(` Startup duration: ${startupDurationMs}ms`); + logger.info('======================================='); } const awsProps = props.propsFor('aws'); @@ -514,31 +493,31 @@ const awsProps = props.propsFor('aws'); // eslint-disable-next-line max-statements async function main() { await aws.initConfig(awsProps); - SetupSentry(aws.getConfig('sentryDsn'), ceProps, releaseBuildNumber, gitReleaseName, defArgs); + SetupSentry(aws.getConfig('sentryDsn'), ceProps, releaseBuildNumber, gitReleaseName, appArgs); const webServer = express(); const router = express.Router(); startWineInit(); - RemoteExecutionQuery.initRemoteExecutionArchs(ceProps, defArgs.env); + RemoteExecutionQuery.initRemoteExecutionArchs(ceProps, appArgs.env); const formattingService = new FormattingService(); await formattingService.initialize(ceProps); - const clientOptionsHandler = new ClientOptionsHandler(sources, compilerProps, defArgs); + const clientOptionsHandler = new ClientOptionsHandler(sources, compilerProps, appArgs); const compilationQueue = CompilationQueue.fromProps(compilerProps.ceProps); const compilationEnvironment = new CompilationEnvironment( compilerProps, awsProps, compilationQueue, formattingService, - defArgs.doCache, + appArgs.doCache, ); const compileHandler = new CompileHandler(compilationEnvironment, awsProps); compilationEnvironment.setCompilerFinder(compileHandler.findCompiler.bind(compileHandler)); const storageType = getStorageTypeByKey(storageSolution); const storageHandler = new storageType(httpRoot, compilerProps, awsProps); - const compilerFinder = new CompilerFinder(compileHandler, compilerProps, defArgs, clientOptionsHandler); + const compilerFinder = new CompilerFinder(compileHandler, compilerProps, appArgs, clientOptionsHandler); const isExecutionWorker = ceProps('execqueue.is_worker', false); const healthCheckFilePath = ceProps('healthCheckFilePath', null) as string | null; @@ -576,7 +555,7 @@ async function main() { if (!isExecutionWorker && initialCompilers.length === 0) { throw new Error('Unexpected failure, no compilers found!'); } - if (defArgs.ensureNoCompilerClash) { + if (appArgs.ensureNoCompilerClash) { logger.warn('Ensuring no compiler ids clash'); if (initialFindResults.foundClash) { // If we are forced to have no clashes, throw an error with some explanation @@ -586,7 +565,7 @@ async function main() { } } - if (opts.discoveryonly) { + if (opts.discoveryOnly) { for (const compiler of initialCompilers) { if (compiler.buildenvsetup && compiler.buildenvsetup.id === '') delete compiler.buildenvsetup; @@ -597,8 +576,8 @@ async function main() { compiler.cachedPossibleArguments = compilerInstance.possibleArguments.possibleArguments; } } - await fs.writeFile(opts.discoveryonly, JSON.stringify(initialCompilers)); - logger.info(`Discovered compilers saved to ${opts.discoveryonly}`); + await fs.writeFile(opts.discoveryOnly, JSON.stringify(initialCompilers)); + logger.info(`Discovered compilers saved to ${opts.discoveryOnly}`); process.exit(0); } @@ -607,7 +586,7 @@ async function main() { clientOptionsHandler, renderConfig, storageHandler, - defArgs.wantedLanguages?.[0], + appArgs.wantedLanguages?.[0], ); const routeApi = new RouteAPI(router, { compileHandler, @@ -615,7 +594,7 @@ async function main() { storageHandler, compilationEnvironment, ceProps, - defArgs, + defArgs: appArgs, renderConfig, renderGoldenLayout, }); @@ -649,7 +628,7 @@ async function main() { if (opts.metricsPort) { logger.info(`Running metrics server on port ${opts.metricsPort}`); - setupMetricsServer(opts.metricsPort, defArgs.hostname); + setupMetricsServer(opts.metricsPort, appArgs.hostname); } webServer @@ -851,7 +830,7 @@ async function main() { noscriptHandler.initializeRoutes(); routeApi.initializeRoutes(); - if (!defArgs.doCache) { + if (!appArgs.doCache) { logger.info(' with disabled caching'); } setupEventLoopLagLogging(); diff --git a/compiler-args-app.ts b/compiler-args-app.ts index e7a621215..4475fcbba 100644 --- a/compiler-args-app.ts +++ b/compiler-args-app.ts @@ -22,7 +22,7 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. -import nopt from 'nopt'; +import {Command} from 'commander'; import _ from 'underscore'; import {CompilerArguments} from './lib/compiler-arguments.js'; @@ -30,12 +30,20 @@ import * as Parsers from './lib/compilers/argument-parsers.js'; import {executeDirect} from './lib/exec.js'; import {logger} from './lib/logger.js'; -const opts = nopt({ - parser: [String], - exe: [String], - padding: [Number], - debug: [Boolean], -}); +const program = new Command(); +program + .name('compiler-args-app.ts') + .usage('--parser= --exe= [--padding=]') + .description( + 'Extracts compiler arguments\nFor example: node --no-warnings=ExperimentalWarning --import=tsx compiler-args-app.ts --parser=clang --exe=/opt/compiler-explorer/clang-15.0.0/bin/clang++ --padding=50', + ) + .requiredOption('--parser ', 'Compiler parser type') + .requiredOption('--exe ', 'Path to compiler executable') + .option('--padding ', 'Padding for output formatting', '40') + .option('--debug', 'Enable debug output'); + +program.parse(); +const opts = program.opts(); if (opts.debug) logger.level = 'debug'; @@ -73,7 +81,7 @@ class CompilerArgsApp { constructor() { this.parserName = opts.parser; this.executable = opts.exe; - this.pad = opts.padding || 40; + this.pad = Number.parseInt(opts.padding, 10); this.compiler = { compiler: { exe: this.executable, @@ -145,17 +153,7 @@ class CompilerArgsApp { } } -if (!opts.parser || !opts.exe) { - console.error( - 'Usage: ' + - 'node --no-warnings=ExperimentalWarning --import=tsx compiler-args-app.ts ' + - '--parser= --exe= [--padding=]\n' + - 'for example: --parser=clang --exe=/opt/compiler-explorer/clang-15.0.0/bin/clang++ --padding=50', - ); - process.exit(1); -} else { - const app = new CompilerArgsApp(); - app.doTheParsing() - .then(() => app.print()) - .catch(e => console.error(e)); -} +const app = new CompilerArgsApp(); +app.doTheParsing() + .then(() => app.print()) + .catch(e => console.error(e)); diff --git a/docs/Configuration.md b/docs/Configuration.md index ebb93ca0c..57f2a3897 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -223,7 +223,7 @@ Note that path-style properties often support variable substitution as shown abo ### Property Debugging -You can enable detailed debugging of property resolution by using the `--propDebug` flag when starting Compiler +You can enable detailed debugging of property resolution by using the `--prop-debug` flag when starting Compiler Explorer. This shows every property lookup, including where properties are being overridden and which configuration source they come from. @@ -257,7 +257,7 @@ The configuration system is implemented primarily in the following files: If you need to troubleshoot configuration issues, you can run Compiler Explorer with debug output: ``` -make EXTRA_ARGS='--propDebug' dev +make EXTRA_ARGS='--prop-debug' dev ``` This will show detailed logs about property resolution, including which properties are being overridden and from which diff --git a/docs/UsingCypress.md b/docs/UsingCypress.md index 6ff0c9bf3..9caa4b545 100644 --- a/docs/UsingCypress.md +++ b/docs/UsingCypress.md @@ -4,7 +4,7 @@ Our frontend testing is done with cypress. To run the tests locally: -- start a server with `npm run dev -- --language c++ --noLocal` - this configuration ensures your setup is clean of any +- start a server with `npm run dev -- --language c++ --no-local` - this configuration ensures your setup is clean of any local properties. - in another terminal run `npx cypress open`, then choose "end to end" and then you should be able to run tests interactively. diff --git a/lib/app.interfaces.ts b/lib/app.interfaces.ts index 670571902..6faa344cf 100644 --- a/lib/app.interfaces.ts +++ b/lib/app.interfaces.ts @@ -29,7 +29,7 @@ export type AppArguments = { port: number; gitReleaseName: string; releaseBuildNumber: string; - wantedLanguages: string[] | null; + wantedLanguages: string[] | undefined; doCache: boolean; fetchCompilersFromRemote: boolean; ensureNoCompilerClash: boolean | undefined; diff --git a/package-lock.json b/package-lock.json index 6850b3dfc..6a31d717e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "buffer": "^6.0.3", "chart.js": "^4.4.9", "clipboard": "^2.0.11", + "commander": "^13.1.0", "compression": "^1.8.0", "copy-webpack-plugin": "^13.0.0", "cross-env": "^7.0.3", @@ -46,7 +47,6 @@ "monaco-vim": "^0.4.2", "morgan": "^1.10.0", "node-targz": "^0.2.0", - "nopt": "^8.1.0", "p-queue": "^8.1.0", "path-browserify": "^1.0.1", "profanities": "^3.0.1", @@ -87,7 +87,6 @@ "@types/jquery": "^3.5.32", "@types/js-cookie": "^3.0.6", "@types/node-targz": "^0.2.4", - "@types/nopt": "^3.0.32", "@types/request": "^2.48.12", "@types/response-time": "^2.3.8", "@types/serve-favicon": "^2.5.7", @@ -5233,13 +5232,6 @@ "@types/tar-fs": "*" } }, - "node_modules/@types/nopt": { - "version": "3.0.32", - "resolved": "https://registry.npmjs.org/@types/nopt/-/nopt-3.0.32.tgz", - "integrity": "sha512-UP8QR0GaR8AE3l4wDmyCx4lEkX5o/1YVeDBqb1UqmfHefZqFNedQLTsobfzw56CvlXVVbt6kAwJJbN1yDTp16Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/pg": { "version": "8.6.1", "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", @@ -5843,15 +5835,6 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "license": "Apache-2.0" }, - "node_modules/abbrev": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", - "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -6943,13 +6926,12 @@ } }, "node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=18" } }, "node_modules/common-tags": { @@ -7534,6 +7516,16 @@ "ieee754": "^1.1.13" } }, + "node_modules/cypress/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -9864,16 +9856,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lint-staged/node_modules/commander": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/lint-staged/node_modules/emoji-regex": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", @@ -10899,21 +10881,6 @@ "node": ">= 0.10.0" } }, - "node_modules/nopt": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", - "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", - "license": "ISC", - "dependencies": { - "abbrev": "^3.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", diff --git a/package.json b/package.json index 8da3e2f5a..930047566 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "buffer": "^6.0.3", "chart.js": "^4.4.9", "clipboard": "^2.0.11", + "commander": "^13.1.0", "compression": "^1.8.0", "copy-webpack-plugin": "^13.0.0", "cross-env": "^7.0.3", @@ -55,7 +56,6 @@ "monaco-vim": "^0.4.2", "morgan": "^1.10.0", "node-targz": "^0.2.0", - "nopt": "^8.1.0", "p-queue": "^8.1.0", "path-browserify": "^1.0.1", "profanities": "^3.0.1", @@ -96,7 +96,6 @@ "@types/jquery": "^3.5.32", "@types/js-cookie": "^3.0.6", "@types/node-targz": "^0.2.4", - "@types/nopt": "^3.0.32", "@types/request": "^2.48.12", "@types/response-time": "^2.3.8", "@types/serve-favicon": "^2.5.7",