Replace nopt with commander.js for argument parsing (#7673)

- Replace nopt with commander.js for better command-line argument parsing
- Add automatic help generation with detailed descriptions
- Maintain backward compatibility with existing arguments
- Remove unused nopt dependency from package.json

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Matt Godbolt
2025-05-12 12:49:28 -05:00
committed by GitHub
parent 1e07e16758
commit 54c942ba76
9 changed files with 144 additions and 201 deletions

View File

@@ -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

View File

@@ -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

225
app.ts
View File

@@ -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 <environments...>', 'Environment(s) to use', ['dev'])
.option('--root-dir <dir>', 'Root directory for config files', './etc')
.option('--host <hostname>', 'Hostname to listen on')
.option('--port <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 <dir>', 'Directory to use for temporary files')
.option('--wsl', 'Running under Windows Subsystem for Linux')
.option('--language <languages...>', '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>', 'Hostname for remote logging')
.option('--logPort, --log-port <port>', 'Port for remote logging', parseNumberForOptions)
.option('--hostname-for-logging <hostname>', 'Hostname to use in logs')
.option('--suppressConsoleLog, --suppress-console-log', 'Disable console logging')
.option('--metricsPort, --metrics-port <port>', 'Port to serve metrics on', parseNumberForOptions)
.option('--loki <url>', 'URL for Loki logging')
.option('--discoveryonly, --discovery-only <file>', 'Output discovery info to file and exit')
.option('--prediscovered <file>', 'Input discovery info from file')
.option('--webpack-content <dir>', 'Path to webpack content')
.option('--no-local', 'Disable local config')
.option('--version', 'Show version information');
program.parse();
const opts = program.opts<CompilerExplorerOptions>();
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<string>('restrictToLanguages');
if (restrictToLanguages) {
defArgs.wantedLanguages = restrictToLanguages.split(',');
appArgs.wantedLanguages = restrictToLanguages.split(',');
}
const languages = (() => {
if (defArgs.wantedLanguages) {
if (appArgs.wantedLanguages) {
const filteredLangs: Partial<Record<LanguageKey, Language>> = {};
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<boolean>('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();

View File

@@ -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=<compilertype> --exe=<path> [--padding=<number>]')
.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 <type>', 'Compiler parser type')
.requiredOption('--exe <path>', 'Path to compiler executable')
.option('--padding <number>', '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=<compilertype> --exe=<path> [--padding=<number>]\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));

View File

@@ -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

View File

@@ -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.

View File

@@ -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;

63
package-lock.json generated
View File

@@ -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",

View File

@@ -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",