From 19d279c9b5a82af9c6d8bba07eb91c6f77279ca6 Mon Sep 17 00:00:00 2001 From: Slayer95 Date: Thu, 17 Jul 2025 07:06:26 -0500 Subject: [PATCH] CLI: Update to use util.parseArgs (#11245) --------- Co-authored-by: Guangcong Luo --- build | 15 ++-- pokemon-showdown | 198 ++++++++++++++++++++++++++--------------------- server/index.ts | 21 +++-- 3 files changed, 132 insertions(+), 102 deletions(-) diff --git a/build b/build index 9ed148e12b..c62617a428 100755 --- a/build +++ b/build @@ -2,10 +2,12 @@ "use strict"; try { - // technically this was introduced in Node 17, but we'll ask for the most recent LTS with it to be safe - structuredClone({}); + // fetch was introduced in Node 18, which is EOL, + // so we'll ask for the most recent "Active LTS" with it to be safe + // https://nodejs.org/en/about/previous-releases + fetch; } catch (e) { - console.log("We require Node.js version 18 or later; you're using " + process.version); + console.error("We require Node.js version 22 or later; you're using " + process.version); process.exit(1); } @@ -22,13 +24,10 @@ function shell(cmd) { // Check to make sure the most recently added or updated dependency is installed at the correct version try { - var version = require('esbuild').version.split('.'); - if (parseInt(version[1]) < 16) { - throw new Error("esbuild version too old"); - } + require.resolve('ts-chacha20'); } catch (e) { console.log('Installing dependencies...'); - shell('npm install'); + shell('npm ci'); } // Make sure config.js exists. If not, copy it over synchronously from diff --git a/pokemon-showdown b/pokemon-showdown index a02321057d..0c1e3140fc 100755 --- a/pokemon-showdown +++ b/pokemon-showdown @@ -22,8 +22,11 @@ // on the side of caution and run `node build` to ensure we're always running // with the latest code. var fs = require('fs'); +var util = require('util'); +var cli = null; + function build() { - if (process.argv.includes('--skip-build')) return; + if (cli.values['skip-build']) return; require('child_process').execSync('node build', {stdio: 'inherit', cwd: __dirname}); } @@ -40,65 +43,116 @@ function ensureBuilt() { } } -if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) { +function showHelp() { + console.log('pokemon-showdown start [--skip-build] [PORT]'); + console.log(''); + console.log(' Starts a PS server on the specified port'); + console.log(' (Defaults to the port setting in config/config.js)'); + console.log(' (The port setting in config/config.js defaults to 8000)'); + console.log(''); + console.log('pokemon-showdown generate-team [FORMAT-ID] [RANDOM-SEED]'); + console.log(''); + console.log(' Generates a random team, and writes it to stdout in packed team format'); + console.log(' (Format defaults to the latest Random Battles format)'); + console.log(''); + console.log('pokemon-showdown validate-team [FORMAT-ID]'); + console.log(''); + console.log(' Reads a team from stdin, and validates it'); + console.log(' If valid: exits with code 0'); + console.log(' If invalid: writes errors to stderr, exits with code 1'); + console.log(''); + console.log('pokemon-showdown simulate-battle'); + console.log(''); + console.log(' Simulates a battle, taking input to stdin and writing output to stdout'); + console.log(' Protocol is documented in ./dist/sim/README.md'); + console.log(''); + console.log('pokemon-showdown json-team'); + console.log(''); + console.log(' Reads a team in any format from stdin, writes the unpacked JSON to stdout'); + console.log(''); + console.log('pokemon-showdown pack-team'); + console.log(''); + console.log(' Reads a team in any format from stdin, writes the packed team to stdout'); + console.log(''); + console.log('pokemon-showdown export-team'); + console.log(''); + console.log(' Reads a team in any format from stdin, writes the exported (human-readable) team to stdout'); + console.log(''); + console.log('pokemon-showdown help'); + console.log(''); + console.log(' Displays this reference'); +} + +try { + fetch; +} catch (e) { + // fetch was introduced in Node 18, which is EOL, + // so we'll ask for the most recent "Active LTS" with it to be safe + // https://nodejs.org/en/about/previous-releases + console.error("We require Node.js version 22 or later; you're using " + process.version); + process.exit(1); +} + +var cliOpts = { + help: { + type: 'boolean', + short: 'h', + default: false + }, + 'skip-build': { + type: 'boolean', + default: false + }, + debug: { + type: 'boolean', + short: 'D', + default: false + }, + replay: { + type: 'boolean', + short: 'R', + default: false + }, + spectate: { + type: 'boolean', + short: 'S', + default: false + } +}; + +var parserOpts = { + options: cliOpts, + strict: false, + allowPositionals: true, + allowNegative: false +}; + +cli = util.parseArgs(parserOpts); + +if (cli.values['help']) { + showHelp(); + process.exit(0); +} + +if (!cli.positionals.length || /^[0-9]+$/.test(cli.positionals[0])) { // Start the server. // - // The port the server should host on can be passed using the second argument - // when launching with this file the same way app.js normally allows, e.g. to + // The port the server should host on can be passed as an argument, e.g. to // host on port 9000: // $ ./pokemon-showdown 9000 build(); require('module')._load('./dist/server/index.js', module, true); } else { - switch (process.argv[2]) { + switch (cli.positionals[0]) { case 'help': case 'h': case '?': - case '-h': - case '--help': - case '-?': - console.log('pokemon-showdown start [--skip-build] [PORT]'); - console.log(''); - console.log(' Starts a PS server on the specified port'); - console.log(' (Defaults to the port setting in config/config.js)'); - console.log(' (The port setting in config/config.js defaults to 8000)'); - console.log(''); - console.log('pokemon-showdown generate-team [FORMAT-ID [RANDOM-SEED]]'); - console.log(''); - console.log(' Generates a random team, and writes it to stdout in packed team format'); - console.log(' (Format defaults to the latest Random Battles format)'); - console.log(''); - console.log('pokemon-showdown validate-team [FORMAT-ID]'); - console.log(''); - console.log(' Reads a team from stdin, and validates it'); - console.log(' If valid: exits with code 0'); - console.log(' If invalid: writes errors to stderr, exits with code 1'); - console.log(''); - console.log('pokemon-showdown simulate-battle'); - console.log(''); - console.log(' Simulates a battle, taking input to stdin and writing output to stdout'); - console.log(' Protocol is documented in ./dist/sim/README.md'); - console.log(''); - console.log('pokemon-showdown json-team'); - console.log(''); - console.log(' Reads a team in any format from stdin, writes the unpacked JSON to stdout'); - console.log(''); - console.log('pokemon-showdown pack-team'); - console.log(''); - console.log(' Reads a team in any format from stdin, writes the packed team to stdout'); - console.log(''); - console.log('pokemon-showdown export-team'); - console.log(''); - console.log(' Reads a team in any format from stdin, writes the exported (human-readable) team to stdout'); - console.log(''); - console.log('pokemon-showdown help'); - console.log(''); - console.log(' Displays this reference'); - break; - case 'start': - case '--skip-build': { - process.argv.splice(2, 1); + showHelp(); + break; + } + case 'start': + { build(); require('module')._load('./dist/server/index.js', module, true); break; @@ -107,8 +161,8 @@ if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) { { ensureBuilt(); var Teams = require('./dist/sim/teams.js').Teams; - var seed = process.argv[4] || undefined; - console.log(Teams.pack(Teams.generate(process.argv[3], {seed}))); + var seed = cli.positionals[2] || undefined; + console.log(Teams.pack(Teams.generate(cli.positionals[1], {seed}))); } break; case 'validate-team': @@ -117,7 +171,7 @@ if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) { require('source-map-support/register'); var Teams = require('./dist/sim/teams.js').Teams; var TeamValidator = require('./dist/sim/team-validator.js').TeamValidator; - var validator = TeamValidator.get(process.argv[3]); + var validator = TeamValidator.get(cli.positionals[1]); var Streams = require('./dist/lib/streams.js'); var stdin = Streams.stdin(); @@ -145,44 +199,10 @@ if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) { var stdin = Streams.stdin(); var stdout = Streams.stdout(); - var args = process.argv.slice(3); - - var options = args.flatMap(function (arg) { - if (arg.charAt(0) !== '-') { - if (arg) console.error("Invalid parameter: " + arg); - return []; - } else if (arg.charAt(1) === '-') { - return arg.slice(2); - } else { - return Array.from(arg.slice(1)); - } - }); - - var debug = false; - var replay = false; - var spectate = false; - for (var i = 0; i < options.length; i++) { - switch (options[i]) { - case 'debug': case 'D': - debug = true; - break; - case 'replay': case 'R': - replay = true; - break; - case 'spectate': case 'spectator': case 'S': - replay = true; - spectate = true; - break; - default: - console.error("Invalid option: " + options[i]); - break; - } - } - var battleStream = new BattleTextStream({ noCatch: true, - debug: debug, - replay: spectate ? 'spectator' : replay, + debug: cli.values['debug'], + replay: cli.values['spectate'] ? 'spectator' : cli.values['replay'] }); stdin.pipeTo(battleStream); battleStream.pipeTo(stdout); @@ -247,7 +267,7 @@ if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) { } break; default: - console.error('Unrecognized command: ' + process.argv[2]); + console.error('Unrecognized command: ' + cli.positionals[0]); console.error('Use `pokemon-showdown help` for help'); process.exit(1); } diff --git a/server/index.ts b/server/index.ts index 211c586cce..f015c9de90 100644 --- a/server/index.ts +++ b/server/index.ts @@ -50,12 +50,23 @@ try { } // NOTE: This file intentionally doesn't use too many modern JavaScript // features, so that it doesn't crash old versions of Node.js, so we -// can successfully print the "We require Node.js 18+" message. +// can successfully print the "We require Node.js 22+" message. -// Check for version -const nodeVersion = parseInt(process.versions.node); -if (isNaN(nodeVersion) || nodeVersion < 18) { - throw new Error("We require Node.js version 18 or later; you're using " + process.version); +// I've gotten enough reports by people who don't use the launch +// script that this is worth repeating here +try { +// eslint-disable-next-line @typescript-eslint/no-unused-expressions + fetch; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +} catch (e) { + throw new Error("We require Node.js version 22 or later; you're using " + process.version); +} + +try { + require.resolve('ts-chacha20'); +// eslint-disable-next-line @typescript-eslint/no-unused-vars +} catch (e) { + throw new Error("Dependencies are unmet; run `npm ci` before launching Pokemon Showdown again."); } import { FS, Repl } from '../lib';