rushstack icon indicating copy to clipboard operation
rushstack copied to clipboard

[ts-command-line] Investigate replacing argparse with built-in util.parseArgs

Open dmichon-msft opened this issue 9 months ago • 1 comments
trafficstars

Summary

Node's built-in util.parseArgs functionality appears to be sufficiently feature-complete that we should be able to remove our dependency on argparse in favor of it: https://nodejs.org/docs/latest-v22.x/api/util.html#utilparseargsconfig

Details

The integer and choice parameter types would need to be implemented by layering additional parameter validation on top of underlying string parameter types, but that's pretty much how it works anyway.

The concept of actions is not built in, but a sample of how that can be accomplished:

const { parseArgs } = require('node:util');

const rushOptions = {
    debug: {
        type: 'boolean',
        default: false
    },
    quiet: {
        type: 'boolean',
        default: false
    }
};

const phasedOptions = {
    to: {
        type: 'string',
        multiple: true,
        short: 't'
    },
    'to-except': {
        type: 'string',
        multiple: true,
        short: 'T'
    },
    from: {
        type: 'string',
        multiple: true,
        short: 'f'
    },
    'from-except': {
        type: 'string',
        multiple: true,
        short: 'F'
    },
    only: {
        type: 'string',
        multiple: true,
        short: 'o'
    },
    'impacted-by': {
        type: 'string',
        multiple: true,
        short: 'i'
    },
    'impacted-by-except': {
        type: 'string',
        multiple: true,
        short: 'I'
    },
    verbose: {
        type: 'boolean',
        default: false,
        short: 'v'
    }
}

const buildOptions = {
    ...phasedOptions,
    production: {
        type: 'boolean',
        default: false
    }
}

const testOptions = {
    ...phasedOptions,
    'disable-code-coverage': {
        type: 'boolean',
        default: false
    },
    'update-snapshots': {
        type: 'boolean',
        default: false
    }
}

const actions = {
    build: buildOptions,
    test: testOptions
}

function parseStrict(args, options) {
    const { tokens } = parseArgs({ args, options, tokens: true, strict: false, allowPositionals: true });

    const firstPositional = tokens.findIndex(token => token.kind === 'positional');
    const { values } = parseArgs({ args: args.slice(0, firstPositional), options, strict: true, allowPositionals: false });

    return {
        options: values,
        action: tokens[firstPositional]?.value,
        remainder: args.slice(firstPositional + 1)
    };
}

function parseRushArgs(args) {
    const {
        options,
        action,
        remainder
    } = parseStrict(args, rushOptions);

    const actionOptions = actions[action];
    if (!actionOptions) {
        throw new Error(`Unknown action: ${action}`);
    }

    const {
        values
    } = parseArgs({ args: remainder, options: actionOptions, strict: true, allowPositionals: false });

    return {
        rushOptions: options,
        action,
        actionOptions: values
    };
}

console.log(parseRushArgs(process.argv.slice(2)));
node .\parser.js --debug build --only sp-loader -T spfx-heft-plugins --verbose --
{
  rushOptions: [Object: null prototype] { debug: true, quiet: false },      
  action: 'build',
  actionOptions: [Object: null prototype] {
    only: [ 'sp-loader' ],
    'to-except': [ 'spfx-heft-plugins' ],
    verbose: true,
    production: false
  }
}

dmichon-msft avatar Jan 29 '25 23:01 dmichon-msft