From 1d9844a58704957e3d1b920f52c6f5955bab4e56 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Mar 2026 08:18:01 +0000 Subject: [PATCH] refactor: replace commander with node:util parseArgs Removes the commander dependency in favor of Node's built-in util.parseArgs for CLI argument parsing. Since the minimum supported Node version is 22.12.0, parseArgs is fully available and stable. This reduces the dependency footprint while maintaining the same CLI interface including subcommands, aliases, options, and help output. --- bin/asar.mjs | 214 ++++++++++++++++++++++++++++++++++++--------------- package.json | 1 - yarn.lock | 8 -- 3 files changed, 151 insertions(+), 72 deletions(-) diff --git a/bin/asar.mjs b/bin/asar.mjs index dca335e..26265b6 100755 --- a/bin/asar.mjs +++ b/bin/asar.mjs @@ -2,7 +2,7 @@ import packageJSON from '../package.json' with { type: 'json' }; import { createPackageWithOptions, listPackage, extractFile, extractAll } from '../lib/asar.js'; -import { program } from 'commander'; +import { parseArgs } from 'node:util'; import fs from 'node:fs'; import path from 'node:path'; @@ -23,72 +23,160 @@ if ( process.exit(1); } -program.version('v' + packageJSON.version).description('Manipulate asar archive files'); - -program - .command('pack ') - .alias('p') - .description('create asar archive') - .option('--ordering ', 'path to a text file for ordering contents') - .option('--unpack ', 'do not pack files matching glob ') - .option( - '--unpack-dir ', - 'do not pack dirs matching glob or starting with literal ', - ) - .option('--exclude-hidden', 'exclude hidden files') - .action(function (dir, output, options) { - options = { - unpack: options.unpack, - unpackDir: options.unpackDir, - ordering: options.ordering, - version: options.sv, - arch: options.sa, - builddir: options.sb, - dot: !options.excludeHidden, - }; - createPackageWithOptions(dir, output, options).catch((error) => { - console.error(error); - process.exit(1); - }); - }); +const commands = { + pack: { + aliases: ['p'], + usage: 'pack|p [options] ', + description: 'create asar archive', + args: ['dir', 'output'], + options: { + ordering: { type: 'string', description: 'path to a text file for ordering contents' }, + unpack: { type: 'string', description: 'do not pack files matching glob ' }, + 'unpack-dir': { + type: 'string', + description: + 'do not pack dirs matching glob or starting with literal ', + }, + 'exclude-hidden': { type: 'boolean', description: 'exclude hidden files' }, + }, + action: (positionals, values) => { + const [dir, output] = positionals; + const options = { + unpack: values.unpack, + unpackDir: values['unpack-dir'], + ordering: values.ordering, + dot: !values['exclude-hidden'], + }; + return createPackageWithOptions(dir, output, options); + }, + }, + list: { + aliases: ['l'], + usage: 'list|l [options] ', + description: 'list files of asar archive', + args: ['archive'], + options: { + 'is-pack': { + type: 'boolean', + short: 'i', + description: 'each file in the asar is pack or unpack', + }, + }, + action: (positionals, values) => { + const [archive] = positionals; + const files = listPackage(archive, { isPack: values['is-pack'] }); + for (const i in files) { + console.log(files[i]); + } + }, + }, + 'extract-file': { + aliases: ['ef'], + usage: 'extract-file|ef ', + description: 'extract one file from archive', + args: ['archive', 'filename'], + options: {}, + action: (positionals) => { + const [archive, filename] = positionals; + fs.writeFileSync(path.basename(filename), extractFile(archive, filename)); + }, + }, + extract: { + aliases: ['e'], + usage: 'extract|e ', + description: 'extract archive', + args: ['archive', 'dest'], + options: {}, + action: (positionals) => { + const [archive, dest] = positionals; + extractAll(archive, dest); + }, + }, +}; -program - .command('list ') - .alias('l') - .description('list files of asar archive') - .option('-i, --is-pack', 'each file in the asar is pack or unpack') - .action(function (archive, options) { - options = { - isPack: options.isPack, - }; - const files = listPackage(archive, options); - for (const i in files) { - console.log(files[i]); - } - }); +function printHelp() { + console.log('Usage: asar [options] [command]'); + console.log(); + console.log('Manipulate asar archive files'); + console.log(); + console.log('Options:'); + console.log(' -V, --version output the version number'); + console.log(' -h, --help display help for command'); + console.log(); + console.log('Commands:'); + for (const [name, cmd] of Object.entries(commands)) { + const label = `${name}|${cmd.aliases[0]}`; + console.log(` ${label.padEnd(32)} ${cmd.description}`); + } +} -program - .command('extract-file ') - .alias('ef') - .description('extract one file from archive') - .action(function (archive, filename) { - fs.writeFileSync(path.basename(filename), extractFile(archive, filename)); - }); +function printCommandHelp(cmd) { + console.log(`Usage: asar ${cmd.usage}`); + console.log(); + console.log(cmd.description); + console.log(); + console.log('Options:'); + for (const [opt, spec] of Object.entries(cmd.options)) { + const prefix = spec.short ? `-${spec.short}, ` : ''; + const suffix = spec.type === 'string' ? ' ' : ''; + const label = `${prefix}--${opt}${suffix}`; + console.log(` ${label.padEnd(32)} ${spec.description}`); + } + console.log(` ${'-h, --help'.padEnd(32)} display help for command`); +} -program - .command('extract ') - .alias('e') - .description('extract archive') - .action(function (archive, dest) { - extractAll(archive, dest); - }); +const args = process.argv.slice(2); -program.command('*', { hidden: true }).action(function (_cmd, args) { - console.log("asar: '%s' is not an asar command. See 'asar --help'.", args[0]); -}); +if (args.length === 0 || args[0] === '--help' || args[0] === '-h') { + printHelp(); + process.exit(0); +} + +if (args[0] === '--version' || args[0] === '-V') { + console.log('v' + packageJSON.version); + process.exit(0); +} -program.parse(process.argv); +const commandName = args[0]; +const commandArgs = args.slice(1); -if (program.args.length === 0) { - program.help(); +let command = commands[commandName]; +if (!command) { + command = Object.values(commands).find((cmd) => cmd.aliases.includes(commandName)); } + +if (!command) { + console.log("asar: '%s' is not an asar command. See 'asar --help'.", commandName); + process.exit(1); +} + +let values, positionals; +try { + ({ values, positionals } = parseArgs({ + args: commandArgs, + options: { + ...command.options, + help: { type: 'boolean', short: 'h' }, + }, + allowPositionals: true, + })); +} catch (error) { + console.error(`error: ${error.message}`); + process.exit(1); +} + +if (values.help) { + printCommandHelp(command); + process.exit(0); +} + +if (positionals.length < command.args.length) { + const missing = command.args[positionals.length]; + console.error(`error: missing required argument '${missing}'`); + process.exit(1); +} + +Promise.resolve(command.action(positionals, values)).catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/package.json b/package.json index 769a206..79f8db4 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "prepare": "husky" }, "dependencies": { - "commander": "^13.1.0", "glob": "^13.0.2", "minimatch": "^10.0.1" }, diff --git a/yarn.lock b/yarn.lock index 4e75d28..1f2b92e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11,7 +11,6 @@ __metadata: dependencies: "@tsconfig/node22": "npm:^22.0.1" "@types/node": "npm:^22.12.0" - commander: "npm:^13.1.0" electron: "npm:^39.8.4" glob: "npm:^13.0.2" husky: "npm:^9.1.7" @@ -1433,13 +1432,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:^13.1.0": - version: 13.1.0 - resolution: "commander@npm:13.1.0" - checksum: 10c0/7b8c5544bba704fbe84b7cab2e043df8586d5c114a4c5b607f83ae5060708940ed0b5bd5838cf8ce27539cde265c1cbd59ce3c8c6b017ed3eec8943e3a415164 - languageName: node - linkType: hard - "commander@npm:^14.0.2": version: 14.0.3 resolution: "commander@npm:14.0.3"