From aef075440fa5236c9ce1225a1971aa41e5dd4b2e 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 | 254 ++++++++++++++++++++++++++++++++++----------------- package.json | 1 - yarn.lock | 8 -- 3 files changed, 171 insertions(+), 92 deletions(-) diff --git a/bin/asar.mjs b/bin/asar.mjs index 67ddd51..5938fdb 100755 --- a/bin/asar.mjs +++ b/bin/asar.mjs @@ -3,7 +3,7 @@ import packageJSON from '../package.json' with { type: 'json' }; import { createPackageWithOptions, listPackage, extractFile, extractAll } from '../lib/asar.js'; import { enableIntegrityDigestForApp, disableIntegrityDigestForApp, verifyIntegrityDigestForApp, printStoredIntegrityDigestForApp } from '../lib/integrity-digest.js'; -import { program } from 'commander'; +import { parseArgs } from 'node:util'; import fs from 'node:fs'; import path from 'node:path'; @@ -17,93 +17,181 @@ if (actualNodeVersion[0] < requiredNodeVersion[0] || (actualNodeVersion[0] === r 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 +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) } - createPackageWithOptions(dir, output, options).catch(error => { - console.error(error) - process.exit(1) - }) - }) - -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 + }, + 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]) + } } - const files = listPackage(archive, options) - 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)) } - }) - -program.command('extract-file ') - .alias('ef') - .description('extract one file from archive') - .action(function (archive, filename) { - fs.writeFileSync(path.basename(filename), - extractFile(archive, filename)) - }) - -program.command('extract ') - .alias('e') - .description('extract archive') - .action(function (archive, dest) { - extractAll(archive, dest) - }) - -program.command('integrity-digest ') - .alias('id') - .description('manage integrity digest in app binary (macOS only)') - .action(async function (command, app) { - // No platform guard just in case users want to run this on other platforms - const allowedCommands = ['on', 'off', 'status', 'verify'] - switch (command) { - case 'on': - await enableIntegrityDigestForApp(app) - break - case 'off': - await disableIntegrityDigestForApp(app) - break - case 'status': - await printStoredIntegrityDigestForApp(app) - break - case 'verify': - await verifyIntegrityDigestForApp(app) - break - default: - console.log('Unknown integrity digest command: %s. Allowed commands are: %s', command, allowedCommands.join(', ')) - process.exit(1) + }, + extract: { + aliases: ['e'], + usage: 'extract|e ', + description: 'extract archive', + args: ['archive', 'dest'], + options: {}, + action: (positionals) => { + const [archive, dest] = positionals + extractAll(archive, dest) } - }) + }, + 'integrity-digest': { + aliases: ['id'], + usage: 'integrity-digest|id ', + description: 'manage integrity digest in app binary (macOS only)', + args: ['command', 'app'], + options: {}, + action: async (positionals) => { + const [command, app] = positionals + // No platform guard just in case users want to run this on other platforms + const allowedCommands = ['on', 'off', 'status', 'verify'] + switch (command) { + case 'on': + await enableIntegrityDigestForApp(app) + break + case 'off': + await disableIntegrityDigestForApp(app) + break + case 'status': + await printStoredIntegrityDigestForApp(app) + break + case 'verify': + await verifyIntegrityDigestForApp(app) + break + default: + console.log('Unknown integrity digest command: %s. Allowed commands are: %s', command, allowedCommands.join(', ')) + process.exit(1) + } + } + } +} + +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}`) + } +} + +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('*', { hidden: true}) - .action(function (_cmd, args) { - console.log('asar: \'%s\' is not an asar command. See \'asar --help\'.', args[0]) - }) +const args = process.argv.slice(2) -program.parse(process.argv) +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) +} -if (program.args.length === 0) { - program.help() +const commandName = args[0] +const commandArgs = args.slice(1) + +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 5694dce..9236c5e 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", "plist": "^3.1.0" diff --git a/yarn.lock b/yarn.lock index 672cf7a..9b05c5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,7 +15,6 @@ __metadata: "@types/node": "npm:^22.12.0" "@types/plist": "npm:^3.0.5" "@types/semver": "npm:^7.7.1" - commander: "npm:^13.1.0" electron: "npm:^35.7.5" glob: "npm:^13.0.2" husky: "npm:^9.1.7" @@ -1036,13 +1035,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"