Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: upgrade commander 4 -> 11 #3848

Merged
merged 2 commits into from
Feb 12, 2025
Merged

fix: upgrade commander 4 -> 11 #3848

merged 2 commits into from
Feb 12, 2025

Conversation

erickzhao
Copy link
Member

@erickzhao erickzhao commented Feb 11, 2025

Motivation

This PR bumps up our version of commander, which is woefully out of date. The motivation here is to reduce friction to make further improvements to our CLI, since commander's API has changed a lot in the last few years.

Note that commander@11 was the latest major version that supports Node 16, while 13 is the current latest major version.

Changes

This migration was done by reading the CHANGELOG.md for commander and ensuring that everything compiled in TypeScript.

Here's a brief explanation of all the changes.

Named exports

The main program object is now a named export.

- import program from 'commander';
+ import { program } from 'commander';

Options syntax

Options for each command are now accessed via the program.opts() function rather than being extended on the program object itself.

- console.log(program.myOption);
+ const options = program.opts();
+ console.log(options.myOption);

Reading from package.json

Getting the Forge version is necessary to get the --version flag to work. We did this inconsistently across our various CLI commands (either via fs.readJson or require call). TypeScript has the resolveJsonModule config flag as of TS 2.9.

import packageJSON from '../package.json';

program
  .version(packageJSON.version, '-V, --version', 'Output the current version')

Unknown command handling

We previously handled unknown commands with a custom command:* event listener, but newer versions of commander automatically exit with an error when given an unknown subcommand.

if (!program._execs.has(commands[0])) {
console.error();
console.error(chalk.red(`Unknown command "${program.args.join(' ')}".`));
console.error('See --help for a list of available commands.');
process.exit(1);

Note that I had to move from a command:* listener to a preSubcommand hook because command:* would swallow any unknown command errors.

Pass through arguments

Forge supports passing args through to the Electron executable app via CLI. This was previously implemented by monkeypatching the program.executeSubCommand helper function, but that function is no longer part of commander's public API.

Instead, the program.passThroughOptions helper enables this behaviour directly. Previously, the monkeypatching was done in the root electron-forge command, but this PR changes it to only pass through options on electron-forge start.

I tested this with a minimal tester app that just printed out process.argv in the main process:

yarn start -- --inspect-electron -- --arg1 --arg2
yarn run v1.22.22
$ electron-forge start --inspect-electron -- --arg1 --arg2
✔ Locating application
✔ Loading configuration
✔ Preparing native dependencies [0.2s]
✔ Running generateAssets hook
✔ Running preStart hook

argv [
  '/Users/erick.zhao/Developer/dump/20250210-commander-11/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron',
  '.',
  '--inspect',
  '--arg1',
  '--arg2'
]
npm start -- --inspect-electron -- --arg1 --arg2

> [email protected] start
> electron-forge start --inspect-electron -- --arg1 --arg2

✔ Locating application
✔ Loading configuration
✔ Preparing native dependencies [0.1s]
✔ Running generateAssets hook
✔ Running preStart hook

argv [
  '/Users/erick.zhao/Developer/dump/20250210-commander-11/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron',
  '.',
  '--inspect',
  '--arg1',
  '--arg2'
]

Adding help text

In theory, we're supposed to add a bit of helper text to instruct users how to pass through args to their Electron application.

program.on('--help', () => {
console.log(' Any arguments found after "--" will be passed to the Electron app, e.g.');
console.log('');
console.log(' $ electron-forge /path/to/project -l -- -d -f foo.txt');
console.log('');
console.log(' will pass the arguments "-d -f foo.txt" to the Electron app');
});

However, this doesn't work as of Forge 7.6.1:

yarn run v1.22.22
$ electron-forge start --help
✔ Checking your system
Usage: electron-forge-start [options] [cwd]

Options:
  -V, --version           Output the current version
  -p, --app-path <path>   Override the path to the Electron app to launch (defaults to '.')
  -l, --enable-logging    Enable advanced logging.  This will log internal Electron things
  -n, --run-as-node       Run the Electron app as a Node.JS script
  --vscode                Used to enable arg transformation for debugging Electron through VSCode.  Do not use yourself.
  -i, --inspect-electron  Triggers inspect mode on Electron to allow debugging the main process.  Electron >1.7 only
  --inspect-brk-electron  Triggers inspect-brk mode on Electron to allow debugging the main process.  Electron >1.7 only
  -h, --help              Output usage information
✨  Done in 1.08s.

This PR fixes that by calling the addHelpText() helper function.

program
  .addHelpText(
      'after',
      `
      Any arguments found after "--" will be passed to the Electron app. For example...
      
          $ npx electron-forge start /path/to/project --enable-logging -- -d -f foo.txt
                                    
      ...will pass the arguments "-d -f foo.txt" to the Electron app.`
    )

@erickzhao erickzhao requested a review from a team as a code owner February 11, 2025 19:53
@erickzhao erickzhao requested a review from a team February 11, 2025 21:01
Copy link
Member

@dsanders11 dsanders11 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

I don't love using import to read package.json, just because I've seen problems in the past with that approach since module caching means mutating the imported JSON will be reflected everywhere (which can be a problem if you're loading JSON test fixtures using import, for example), but it's low risk in this case and will be more palatable in the future with import attributes making things more explicit.


import './util/terminate';
import packageJSON from '../package.json';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to future us, once we bump the minimum Node.js version in Forge, we should be able to use import attributes to change this to import packageJSON from '../package.json' with { type: 'json' };

@erickzhao erickzhao added this pull request to the merge queue Feb 12, 2025
Merged via the queue into main with commit 0802c42 Feb 12, 2025
12 checks passed
@erickzhao erickzhao deleted the commander-11 branch February 12, 2025 05:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants