leo / args

Toolkit for building command line interfaces
https://npmjs.com/args
MIT License
462 stars 30 forks source link

Default `version` command does not work for esmodule packages #165

Open JakeStanger opened 2 years ago

JakeStanger commented 2 years ago

If you use the now stable esmodule resolution (by including "type": "module" in your package.json), the default version command throws an error trying to get the main module.

λ fe version
/home/jake/Programming/filebrowser2/node_modules/args/lib/version.js:26
  const pkg = findPackage(path.dirname(process.mainModule.filename))
                                                          ^

TypeError: Cannot read properties of undefined (reading 'filename')
    at module.exports [as showVersion] (/home/jake/Programming/filebrowser2/node_modules/args/lib/version.js:26:59)
    at Args.runCommand (/home/jake/Programming/filebrowser2/node_modules/args/lib/utils.js:306:37)
    at module.exports [as parse] (/home/jake/Programming/filebrowser2/node_modules/args/lib/parse.js:63:10)
    at file:///home/jake/Programming/filebrowser2/lib/cli.js:9:20
    at ModuleJob.run (node:internal/modules/esm/module_job:198:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:409:24)
    at async loadESM (node:internal/process/esm_loader:85:5)
    at async handleMainPromise (node:internal/modules/run_main:61:12)

Node.js v18.3.0

I don't think there's going to be a very nice way around this unfortunately, as the concept of a "main" module is kinda thrown out the window. You can use meta.import.url to get the current module's URL, and could possibly do a walk up the filetree, but I'm not sure how reliable that'll be.

If the command can't be easily fixed, I propose args does a check to see if process.mainModule is defined, and if not just disables the version command.

imme-emosol commented 1 month ago

May be ESM patch.

import { Args } from 'args';
import { showVersion } from './args-patches.js';

const args = new Args();
args.showVersion = showVersion.bind(args);

File args-pathches.js :

import path from 'node:path';
import { pathToFileURL } from 'node:url';
import fs from 'node:fs';
import { createRequire } from 'node:module';

/**
 * Retrieves the main module package.json information.
 *
 * @param {string} current
 *   The directory to start looking in.
 *
 * @return {Object|null}
 *   An object containing the package.json contents or NULL if it could not be found.
 */
const findPackage = function(current) {
    let file;
    let container;
    do
    {
        container = current;
        current = path.resolve(container, '..')
        file = path.resolve(container, 'package.json');
        file = fs.existsSync(file) && fs.statSync(file).isFile()
            ? file : null;
    }
    while (null == file && current != container);
    // now either file exists or current ran out of containers
    if (null != file)
    {
        const require = createRequire(import.meta.url);
        file = require(file);
    }

    return file;
}
const showVersion = function() {
    const pkg = findPackage(process.argv[1])
    const version = pkg?.version ?? '-/-'
    console.log(version)
    if (this.config.exit && this.config.exit.version) {
        // eslint-disable-next-line unicorn/no-process-exit
        process.exit()
    }
};

export { showVersion };