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"