From b4e064d46906935dc09c5f22eba44e73e8ef6e94 Mon Sep 17 00:00:00 2001 From: James Prevett Date: Sat, 11 Jul 2026 10:36:54 -0500 Subject: [PATCH] Replaced yargs with commander --- .ncurc.js | 3 +- CLAUDE.md | 2 +- bin/mailauth.js | 585 +++++++++++++++------------------------------- package-lock.json | 144 ++++++++---- package.json | 4 +- 5 files changed, 281 insertions(+), 457 deletions(-) diff --git a/.ncurc.js b/.ncurc.js index 2444b85..d5ef847 100644 --- a/.ncurc.js +++ b/.ncurc.js @@ -2,7 +2,6 @@ module.exports = { upgrade: true, reject: [ // only works as ESM - 'chai', - 'yargs' + 'chai' ] }; diff --git a/CLAUDE.md b/CLAUDE.md index 56389f9..f7e363b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,7 +47,7 @@ Each protocol lives in its own directory under `lib/`: ### CLI -`bin/mailauth.js` — yargs-based CLI with subcommands: `report`, `sign`, `seal`, `spf`, `vmc`, `bodyhash`. Command implementations in `lib/commands/`. +`bin/mailauth.js` — commander-based CLI with subcommands: `report`, `sign`, `seal`, `spf`, `vmc`, `bodyhash`. Command implementations in `lib/commands/`. ### Tests diff --git a/bin/mailauth.js b/bin/mailauth.js index 3425ce9..14ae73f 100755 --- a/bin/mailauth.js +++ b/bin/mailauth.js @@ -2,10 +2,8 @@ 'use strict'; -const yargs = require('yargs/yargs'); -const { hideBin } = require('yargs/helpers'); +const { Command } = require('commander'); const os = require('node:os'); -const assert = require('node:assert'); const commandReport = require('../lib/commands/report'); const commandSign = require('../lib/commands/sign'); @@ -17,419 +15,202 @@ const commandBodyhash = require('../lib/commands/bodyhash'); const fs = require('node:fs'); const pathlib = require('node:path'); -const argv = yargs(hideBin(process.argv)) - .command( - ['report [email]', '$0 [email]'], - 'Validate an email message and return a detailed JSON report', - yargs => { - yargs - .option('client-ip', { - alias: 'i', - type: 'string', - description: 'IP address of the remote client (used for SPF checks). If not provided, it is parsed from the latest Received header.' - }) - .option('mta', { - alias: 'm', - type: 'string', - description: - 'Hostname of the server performing the validation (used in the Authentication-Results header). Defaults to the local hostname.', - default: os.hostname() - }) - .option('helo', { - alias: 'e', - type: 'string', - description: 'Client hostname from the HELO/EHLO command (used in some SPF checks).' - }) - .option('sender', { - alias: 'f', - type: 'string', - description: 'Email address from the MAIL FROM command. If not provided, the address from the latest Return-Path header is used.' - }) - .option('dns-cache', { - alias: 'n', - type: 'string', - description: - 'Path to a JSON file with cached DNS responses. When provided, DNS queries use these cached responses instead of performing actual DNS lookups.' - }) - .option('max-lookups', { - alias: 'x', - type: 'number', - description: 'Maximum allowed DNS lookups during SPF checks. Defaults to 10.', - default: 10 - }) - .option('max-void-lookups', { - alias: 'z', - type: 'number', - description: 'Maximum allowed DNS lookups that return no data (void lookups) during SPF checks. Defaults to 2.', - default: 2 - }); - yargs.positional('email', { - describe: 'Path to the email message file in EML format. If not specified, the content is read from standard input.' +// Coerce numeric option values the same way yargs `type: 'number'` used to. +const numberArg = value => { + let num = Number(value); + return isNaN(num) ? value : num; +}; + +// Wrap an async command implementation with the shared promise handling used by +// every subcommand: merge the positional `email` argument with the parsed +// options (including the global --verbose flag) into a single `argv` object. +const runCommand = (fn, failMessage) => + function () { + let command = arguments[arguments.length - 1]; + let positional = Array.prototype.slice.call(arguments, 0, arguments.length - 2); + + let argv = Object.assign({}, command.optsWithGlobals()); + if (command.registeredArguments && command.registeredArguments.length) { + command.registeredArguments.forEach((arg, i) => { + argv[arg.name()] = positional[i]; }); - }, - argv => { - commandReport(argv) - .then(() => { - process.exit(); - }) - .catch(err => { - console.error('Failed to generate report for the input message.'); - console.error(err); - process.exit(1); - }); } - ) - .command( - ['sign [email]'], - 'Sign an email with a DKIM digital signature', - yargs => { - yargs - .option('private-key', { - alias: 'k', - type: 'string', - description: 'Path to the private key file used for signing.', - demandOption: true - }) - .option('domain', { - alias: 'd', - type: 'string', - description: 'Domain name to use in the DKIM signature (d= tag).', - demandOption: true - }) - .option('selector', { - alias: 's', - type: 'string', - description: 'Selector to use in the DKIM signature (s= tag).', - demandOption: true - }) - .option('algo', { - alias: 'a', - type: 'string', - description: 'Signing algorithm. Defaults to "rsa-sha256" or "ed25519-sha256" depending on the private key type.', - default: 'rsa-sha256' - }) - .option('canonicalization', { - alias: 'c', - type: 'string', - description: 'Canonicalization method (c= tag). Defaults to "relaxed/relaxed".', - default: 'relaxed/relaxed' - }) - .option('time', { - alias: 't', - type: 'number', - description: 'Signing time as a UNIX timestamp (t= tag). Defaults to the current time.' - }) - .option('body-length', { - alias: 'l', - type: 'number', - description: 'Maximum length of the canonicalized body to include in the signature (l= tag). Not recommended for general use.' - }) - .option('header-fields', { - alias: 'h', - type: 'string', - description: 'Colon-separated list of header field names to include in the signature (h= tag).' - }) - .option('headers-only', { - alias: 'o', - type: 'boolean', - description: 'If set, outputs only the DKIM signature headers without the message body.' - }); - yargs.positional('email', { - describe: 'Path to the email message file in EML format. If not specified, the content is read from standard input.' + + fn(argv) + .then(() => { + process.exit(); + }) + .catch(err => { + if (!err || !err.suppress) { + console.error(failMessage); + console.error(err); + } + process.exit(1); }); - }, - argv => { - commandSign(argv) - .then(() => { - process.exit(); - }) - .catch(err => { - if (!err.suppress) { - console.error('Failed to sign the input message.'); - console.error(err); - } - process.exit(1); - }); - } + }; + +const emailArgDescription = 'Path to the email message file in EML format. If not specified, the content is read from standard input.'; + +const program = new Command(); + +program + .name('mailauth') + .description('Email authentication tools for Node.js') + .option('-v, --verbose', 'Enable verbose logging for debugging purposes.') + // sign/seal use -h as an alias for --header-fields, so free it up from the + // built-in help option and expose help via --help only. + .helpOption('--help', 'Show help.'); + +program + .command('report', { isDefault: true }) + .description('Validate an email message and return a detailed JSON report') + .argument('[email]', emailArgDescription) + .helpOption('--help', 'Show help.') + .option('-i, --client-ip ', 'IP address of the remote client (used for SPF checks). If not provided, it is parsed from the latest Received header.') + .option( + '-m, --mta ', + 'Hostname of the server performing the validation (used in the Authentication-Results header). Defaults to the local hostname.', + os.hostname() ) - .command( - ['seal [email]'], - 'Authenticate and seal an email with an ARC digital signature', - yargs => { - yargs - .option('private-key', { - alias: 'k', - type: 'string', - description: 'Path to the private key file used for sealing.', - demandOption: true - }) - .option('domain', { - alias: 'd', - type: 'string', - description: 'Domain name to use in the ARC seal (d= tag).', - demandOption: true - }) - .option('selector', { - alias: 's', - type: 'string', - description: 'Selector to use in the ARC seal (s= tag).', - demandOption: true - }) - .option('algo', { - alias: 'a', - type: 'string', - description: - 'Sealing algorithm. Defaults to "rsa-sha256" or "ed25519-sha256" depending on the private key type. Note: RFC8617 only allows "rsa-sha256" (a= tag).', - default: 'rsa-sha256' - }) - .option('canonicalization', { - alias: 'c', - type: 'string', - description: 'Canonicalization method. Note: RFC8617 only allows "relaxed/relaxed" (c= tag).', - default: 'relaxed/relaxed' - }) - .option('time', { - alias: 't', - type: 'number', - description: 'Sealing time as a UNIX timestamp (t= tag). Defaults to the current time.' - }) - .option('header-fields', { - alias: 'h', - type: 'string', - description: 'Colon-separated list of header field names to include in the seal (h= tag).' - }) - .option('client-ip', { - alias: 'i', - type: 'string', - description: 'IP address of the remote client (used for SPF checks). If not provided, it is parsed from the latest Received header.' - }) - .option('mta', { - alias: 'm', - type: 'string', - description: - 'Hostname of the server performing the validation (used in the Authentication-Results header). Defaults to the local hostname.', - default: os.hostname() - }) - .option('helo', { - alias: 'e', - type: 'string', - description: 'Client hostname from the HELO/EHLO command (used in some SPF checks).' - }) - .option('sender', { - alias: 'f', - type: 'string', - description: 'Email address from the MAIL FROM command. If not provided, the address from the latest Return-Path header is used.' - }) - .option('dns-cache', { - alias: 'n', - type: 'string', - description: - 'Path to a JSON file with cached DNS responses. When provided, DNS queries use these cached responses instead of performing actual DNS lookups.' - }) - .option('headers-only', { - alias: 'o', - type: 'boolean', - description: 'If set, outputs only the ARC seal headers without the message body.' - }); - yargs.positional('email', { - describe: 'Path to the email message file in EML format. If not specified, the content is read from standard input.' - }); - }, - argv => { - commandSeal(argv) - .then(() => { - process.exit(); - }) - .catch(err => { - if (!err.suppress) { - console.error('Failed to seal the input message.'); - console.error(err); - } - process.exit(1); - }); - } + .option('-e, --helo ', 'Client hostname from the HELO/EHLO command (used in some SPF checks).') + .option('-f, --sender
', 'Email address from the MAIL FROM command. If not provided, the address from the latest Return-Path header is used.') + .option( + '-n, --dns-cache ', + 'Path to a JSON file with cached DNS responses. When provided, DNS queries use these cached responses instead of performing actual DNS lookups.' ) - .command( - ['spf'], - 'Validate SPF for an email address and MTA IP address', - yargs => { - yargs - .option('sender', { - alias: 'f', - type: 'string', - description: 'Email address from the MAIL FROM command.', - demandOption: true - }) - .option('client-ip', { - alias: 'i', - type: 'string', - description: 'IP address of the remote client (used for SPF checks).', - demandOption: true - }) - .option('helo', { - alias: 'e', - type: 'string', - description: 'Client hostname from the HELO/EHLO command (used in some SPF checks).' - }) - .option('mta', { - alias: 'm', - type: 'string', - description: 'Hostname of the server performing the SPF check (used in the Authentication-Results header). Defaults to the local hostname.', - default: os.hostname() - }) - .option('dns-cache', { - alias: 'n', - type: 'string', - description: - 'Path to a JSON file with cached DNS responses. When provided, DNS queries use these cached responses instead of performing actual DNS lookups.' - }) - .option('headers-only', { - alias: 'o', - type: 'boolean', - description: 'If set, outputs only the SPF authentication header.' - }) - .option('max-lookups', { - alias: 'x', - type: 'number', - description: 'Maximum allowed DNS lookups during SPF checks. Defaults to 10.', - default: 10 - }) - .option('max-void-lookups', { - alias: 'z', - type: 'number', - description: 'Maximum allowed DNS lookups that return no data (void lookups) during SPF checks. Defaults to 2.', - default: 2 - }); - }, - argv => { - commandSpf(argv) - .then(() => { - process.exit(); - }) - .catch(err => { - console.error('Failed to verify SPF for the email address.'); - console.error(err); - process.exit(1); - }); - } + .option('-x, --max-lookups ', 'Maximum allowed DNS lookups during SPF checks. Defaults to 10.', numberArg, 10) + .option('-z, --max-void-lookups ', 'Maximum allowed DNS lookups that return no data (void lookups) during SPF checks. Defaults to 2.', numberArg, 2) + .action(runCommand(commandReport, 'Failed to generate report for the input message.')); + +program + .command('sign') + .description('Sign an email with a DKIM digital signature') + .argument('[email]', emailArgDescription) + .helpOption('--help', 'Show help.') + .requiredOption('-k, --private-key ', 'Path to the private key file used for signing.') + .requiredOption('-d, --domain ', 'Domain name to use in the DKIM signature (d= tag).') + .requiredOption('-s, --selector ', 'Selector to use in the DKIM signature (s= tag).') + .option('-a, --algo ', 'Signing algorithm. Defaults to "rsa-sha256" or "ed25519-sha256" depending on the private key type.', 'rsa-sha256') + .option('-c, --canonicalization ', 'Canonicalization method (c= tag). Defaults to "relaxed/relaxed".', 'relaxed/relaxed') + .option('-t, --time ', 'Signing time as a UNIX timestamp (t= tag). Defaults to the current time.', numberArg) + .option( + '-l, --body-length ', + 'Maximum length of the canonicalized body to include in the signature (l= tag). Not recommended for general use.', + numberArg ) - .command( - ['vmc'], - 'Validate a Verified Mark Certificate (VMC) logo file', - yargs => { - yargs - .option('authorityPath', { - alias: 'p', - type: 'string', - description: 'Path to a local VMC file.' - }) - .option('authority', { - alias: 'a', - type: 'string', - description: 'URL of the VMC file.' - }) - .option('domain', { - alias: 'd', - type: 'string', - description: 'Sending domain to validate against the VMC.' - }) - .option('date', { - alias: 't', - type: 'string', - description: 'ISO-formatted timestamp to use for certificate expiration checks.' - }); - }, - argv => { - commandVmc(argv) - .then(() => { - process.exit(); - }) - .catch(err => { - console.error('Failed to verify the VMC file.'); - console.error(err); - process.exit(1); - }); - } + .option('-h, --header-fields ', 'Colon-separated list of header field names to include in the signature (h= tag).') + .option('-o, --headers-only', 'If set, outputs only the DKIM signature headers without the message body.') + .action(runCommand(commandSign, 'Failed to sign the input message.')); + +program + .command('seal') + .description('Authenticate and seal an email with an ARC digital signature') + .argument('[email]', emailArgDescription) + .helpOption('--help', 'Show help.') + .requiredOption('-k, --private-key ', 'Path to the private key file used for sealing.') + .requiredOption('-d, --domain ', 'Domain name to use in the ARC seal (d= tag).') + .requiredOption('-s, --selector ', 'Selector to use in the ARC seal (s= tag).') + .option( + '-a, --algo ', + 'Sealing algorithm. Defaults to "rsa-sha256" or "ed25519-sha256" depending on the private key type. Note: RFC8617 only allows "rsa-sha256" (a= tag).', + 'rsa-sha256' ) - .command( - ['bodyhash [email]'], - 'Generate a DKIM body hash for an email message', - yargs => { - yargs - .option('algo', { - alias: 'a', - type: 'string', - description: 'Hashing algorithm to use. Defaults to "sha256". Can also use DKIM-style algorithms like "rsa-sha256".', - default: 'sha256' - }) - .option('canonicalization', { - alias: 'c', - type: 'string', - description: 'Body canonicalization method (c= tag). Defaults to "relaxed". Can use DKIM-style formats like "relaxed/relaxed".', - default: 'relaxed' - }) - .option('body-length', { - alias: 'l', - type: 'number', - description: 'Maximum length of the canonicalized body to include in the hash (l= tag).' - }); - yargs.positional('email', { - describe: 'Path to the email message file in EML format. If not specified, the content is read from standard input.' - }); - }, - argv => { - commandBodyhash(argv) - .then(() => { - process.exit(); - }) - .catch(err => { - if (!err.suppress) { - console.error('Failed to calculate the body hash for the input message.'); - console.error(err); - } - process.exit(1); - }); - } + .option('-c, --canonicalization ', 'Canonicalization method. Note: RFC8617 only allows "relaxed/relaxed" (c= tag).', 'relaxed/relaxed') + .option('-t, --time ', 'Sealing time as a UNIX timestamp (t= tag). Defaults to the current time.', numberArg) + .option('-h, --header-fields ', 'Colon-separated list of header field names to include in the seal (h= tag).') + .option('-i, --client-ip ', 'IP address of the remote client (used for SPF checks). If not provided, it is parsed from the latest Received header.') + .option( + '-m, --mta ', + 'Hostname of the server performing the validation (used in the Authentication-Results header). Defaults to the local hostname.', + os.hostname() + ) + .option('-e, --helo ', 'Client hostname from the HELO/EHLO command (used in some SPF checks).') + .option('-f, --sender
', 'Email address from the MAIL FROM command. If not provided, the address from the latest Return-Path header is used.') + .option( + '-n, --dns-cache ', + 'Path to a JSON file with cached DNS responses. When provided, DNS queries use these cached responses instead of performing actual DNS lookups.' + ) + .option('-o, --headers-only', 'If set, outputs only the ARC seal headers without the message body.') + .action(runCommand(commandSeal, 'Failed to seal the input message.')); + +program + .command('spf') + .description('Validate SPF for an email address and MTA IP address') + .helpOption('--help', 'Show help.') + .requiredOption('-f, --sender
', 'Email address from the MAIL FROM command.') + .requiredOption('-i, --client-ip ', 'IP address of the remote client (used for SPF checks).') + .option('-e, --helo ', 'Client hostname from the HELO/EHLO command (used in some SPF checks).') + .option( + '-m, --mta ', + 'Hostname of the server performing the SPF check (used in the Authentication-Results header). Defaults to the local hostname.', + os.hostname() ) - .command( - ['license'], - 'Display license information for mailauth and included modules', - () => false, - () => { - fs.readFile(pathlib.join(__dirname, '..', 'LICENSE.txt'), (err, license) => { + .option( + '-n, --dns-cache ', + 'Path to a JSON file with cached DNS responses. When provided, DNS queries use these cached responses instead of performing actual DNS lookups.' + ) + .option('-o, --headers-only', 'If set, outputs only the SPF authentication header.') + .option('-x, --max-lookups ', 'Maximum allowed DNS lookups during SPF checks. Defaults to 10.', numberArg, 10) + .option('-z, --max-void-lookups ', 'Maximum allowed DNS lookups that return no data (void lookups) during SPF checks. Defaults to 2.', numberArg, 2) + .action(runCommand(commandSpf, 'Failed to verify SPF for the email address.')); + +program + .command('vmc') + .description('Validate a Verified Mark Certificate (VMC) logo file') + .helpOption('--help', 'Show help.') + .option('-p, --authorityPath ', 'Path to a local VMC file.') + .option('-a, --authority ', 'URL of the VMC file.') + .option('-d, --domain ', 'Sending domain to validate against the VMC.') + .option('-t, --date ', 'ISO-formatted timestamp to use for certificate expiration checks.') + .action(runCommand(commandVmc, 'Failed to verify the VMC file.')); + +program + .command('bodyhash') + .description('Generate a DKIM body hash for an email message') + .argument('[email]', emailArgDescription) + .helpOption('--help', 'Show help.') + .option('-a, --algo ', 'Hashing algorithm to use. Defaults to "sha256". Can also use DKIM-style algorithms like "rsa-sha256".', 'sha256') + .option( + '-c, --canonicalization ', + 'Body canonicalization method (c= tag). Defaults to "relaxed". Can use DKIM-style formats like "relaxed/relaxed".', + 'relaxed' + ) + .option('-l, --body-length ', 'Maximum length of the canonicalized body to include in the hash (l= tag).', numberArg) + .action(runCommand(commandBodyhash, 'Failed to calculate the body hash for the input message.')); + +program + .command('license') + .description('Display license information for mailauth and included modules') + .helpOption('--help', 'Show help.') + .action(() => { + fs.readFile(pathlib.join(__dirname, '..', 'LICENSE.txt'), (err, license) => { + if (err) { + console.error('Failed to load license information.'); + console.error(err); + return process.exit(1); + } + + console.error('mailauth License'); + console.error('================'); + + console.error(license.toString().trim()); + + console.error(''); + + fs.readFile(pathlib.join(__dirname, '..', 'licenses.txt'), (err, data) => { if (err) { - console.error('Failed to load license information.'); + console.error('Failed to load included modules license information.'); console.error(err); return process.exit(1); } - console.error('mailauth License'); + console.error('Included Modules'); console.error('================'); - console.error(license.toString().trim()); - - console.error(''); - - fs.readFile(pathlib.join(__dirname, '..', 'licenses.txt'), (err, data) => { - if (err) { - console.error('Failed to load included modules license information.'); - console.error(err); - return process.exit(1); - } - - console.error('Included Modules'); - console.error('================'); - - console.error(data.toString().trim()); - process.exit(); - }); + console.error(data.toString().trim()); + process.exit(); }); - } - ) - .option('verbose', { - alias: 'v', - type: 'boolean', - description: 'Enable verbose logging for debugging purposes.' - }).argv; + }); + }); -assert.ok(argv); +program.parse(process.argv); diff --git a/package-lock.json b/package-lock.json index 69e894d..0f6f971 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@postalsys/vmc": "1.1.4", + "commander": "15.0.0", "fast-xml-parser": "5.7.3", "ipaddr.js": "2.4.0", "joi": "18.2.1", @@ -17,8 +18,7 @@ "nodemailer": "8.0.7", "punycode.js": "2.3.1", "tldts": "7.0.30", - "undici": "7.25.0", - "yargs": "17.7.2" + "undici": "7.25.0" }, "bin": { "mailauth": "bin/mailauth.js" @@ -133,9 +133,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", - "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -177,9 +177,9 @@ "license": "BSD-3-Clause" }, "node_modules/@hapi/tlds": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.6.tgz", - "integrity": "sha512-xdi7A/4NZokvV0ewovme3aUO5kQhW9pQ2YD1hRqZGhhSi5rBv4usHYidVocXSi9eihYsznZxLtAiEYYUL6VBGw==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.7.tgz", + "integrity": "sha512-MgNjRwy9Ti92yVAixLmDc8dd1bJIKwO9qlWCfFQRwRmUEDPQHYn4G6hwPFvFGUTzAa0FsS+inMjLin7GnyBRhA==", "license": "BSD-3-Clause", "engines": { "node": ">=14.0.0" @@ -293,9 +293,9 @@ "license": "MIT" }, "node_modules/@nodable/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==", "funding": [ { "type": "github", @@ -416,9 +416,9 @@ "license": "MIT" }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", "bin": { @@ -472,6 +472,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -483,6 +484,18 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anynum": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -525,9 +538,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "dev": true, "license": "MIT", "dependencies": { @@ -698,6 +711,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -712,6 +726,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -721,12 +736,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -741,6 +758,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -753,6 +771,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -770,6 +789,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -782,8 +802,18 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, + "node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -923,6 +953,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -1138,9 +1169,9 @@ "license": "MIT" }, "node_modules/fast-xml-builder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", - "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.0.tgz", + "integrity": "sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==", "funding": [ { "type": "github", @@ -1149,8 +1180,8 @@ ], "license": "MIT", "dependencies": { - "path-expression-matcher": "^1.5.0", - "xml-naming": "^0.1.0" + "path-expression-matcher": "^1.6.2", + "xml-naming": "^0.3.0" } }, "node_modules/fast-xml-parser": { @@ -1279,6 +1310,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -1367,9 +1399,9 @@ "license": "MIT" }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -1539,6 +1571,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -1962,9 +1995,9 @@ "license": "MIT" }, "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -2101,9 +2134,9 @@ } }, "node_modules/path-expression-matcher": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", - "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", + "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", "funding": [ { "type": "github", @@ -2310,6 +2343,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2383,9 +2417,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -2559,16 +2593,19 @@ } }, "node_modules/strnum": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", - "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz", + "integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "anynum": "^1.0.1" + } }, "node_modules/supports-color": { "version": "8.1.1", @@ -2624,9 +2661,9 @@ "license": "MIT" }, "node_modules/tablemark/node_modules/string-width": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", - "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", "dev": true, "license": "MIT", "dependencies": { @@ -2696,9 +2733,9 @@ } }, "node_modules/tldts-core": { - "version": "7.0.30", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.30.tgz", - "integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==", + "version": "7.4.8", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.8.tgz", + "integrity": "sha512-c1P7u0EhACHj7lPy4MJm8iTFEU8+nB0LCtddH0fhP7noaVoXAqafMtOOeX+ulpuPBqnrRgRhw494RICT3mbhnw==", "license": "MIT" }, "node_modules/tslib": { @@ -2908,9 +2945,9 @@ } }, "node_modules/xml-naming": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", - "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", + "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", "funding": [ { "type": "github", @@ -2926,15 +2963,17 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -2953,6 +2992,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -2978,6 +3018,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2987,12 +3028,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -3007,6 +3050,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" diff --git a/package.json b/package.json index 662cd39..1f96888 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ }, "dependencies": { "@postalsys/vmc": "1.1.4", + "commander": "15.0.0", "fast-xml-parser": "5.7.3", "ipaddr.js": "2.4.0", "joi": "18.2.1", @@ -54,8 +55,7 @@ "nodemailer": "8.0.7", "punycode.js": "2.3.1", "tldts": "7.0.30", - "undici": "7.25.0", - "yargs": "17.7.2" + "undici": "7.25.0" }, "engines": { "node": ">=20.18.1"