diff --git a/package.json b/package.json index ee78617955..b290476054 100644 --- a/package.json +++ b/package.json @@ -51,12 +51,10 @@ "@sentry/electron": "^7.12.0", "algoliasearch": "^4.12.0", "classnames": "^2.2.6", - "commander": "^7.1.0", "electron-default-menu": "^1.0.2", "electron-squirrel-startup": "^1.0.0", "extract-zip": "^2.0.1", "fs-extra": "^9.1.0", - "getos": "^3.2.1", "mobx": "^6.5.0", "mobx-react": "^7.3.0", "monaco-editor": "^0.22.0", @@ -97,7 +95,6 @@ "@tsconfig/node22": "^22.0.2", "@types/classnames": "^2.2.11", "@types/fs-extra": "^9.0.7", - "@types/getos": "^3.0.1", "@types/node": "^22.19.1", "@types/parse-env-string": "^1.0.2", "@types/react": "^16.14.0", diff --git a/src/main/command-line.ts b/src/main/command-line.ts deleted file mode 100644 index ad521a5dcd..0000000000 --- a/src/main/command-line.ts +++ /dev/null @@ -1,193 +0,0 @@ -import * as os from 'node:os'; - -import * as commander from 'commander'; -import fs from 'fs-extra'; -import getos from 'getos'; - -import { openFiddle } from './files'; -import { ipcMainManager } from './ipc'; -import { findProtocolArg } from './protocol'; -import { - ElectronReleaseChannel, - OutputEntry, - RunResult, - SetupRequest, -} from '../interfaces'; -import { IpcEvents } from '../ipc-events'; -import { getGistId } from '../utils/gist'; - -async function getSetup(opts: commander.OptionValues): Promise { - const config: SetupRequest = { - showChannels: [], - hideChannels: [], - }; - - const { betas, fiddle, full, nightlies, obsolete, version } = opts; - - if (fs.existsSync(fiddle)) { - const files = await openFiddle(fiddle); - config.fiddle = { localFiddle: { filePath: fiddle, files } }; - } else { - const gistId = getGistId(fiddle); - if (gistId) { - config.fiddle = { gistId }; - } - } - if (!config.fiddle) { - throw `Unrecognized Fiddle "${fiddle}"`; - } - - if (version) { - config.version = version; - } - - if (typeof obsolete === 'boolean') { - config.useObsolete = obsolete; - } - - if (betas) { - config.showChannels.push(ElectronReleaseChannel.beta); - } else if (betas === false) { - config.hideChannels.push(ElectronReleaseChannel.beta); - } - - if (nightlies) { - config.showChannels.push(ElectronReleaseChannel.nightly); - } else if (nightlies === false) { - config.hideChannels.push(ElectronReleaseChannel.nightly); - } - - if (full) { - config.useObsolete = true; - config.hideChannels = []; - config.showChannels = [ - ElectronReleaseChannel.beta, - ElectronReleaseChannel.nightly, - ElectronReleaseChannel.stable, - ]; - } - - return config; -} - -const exitCodes = Object.freeze({ - [RunResult.SUCCESS]: 0, - [RunResult.FAILURE]: 1, - [RunResult.INVALID]: 2, -}); - -async function sendTask(type: IpcEvents, task: any) { - const onOutputEntry = (_: any, msg: OutputEntry) => { - console.log(`[${msg.timeString}] ${msg.text}`); - }; - const onTaskDone = (_: any, r: RunResult) => exitWithCode(exitCodes[r]); - ipcMainManager.on(IpcEvents.OUTPUT_ENTRY, onOutputEntry); - ipcMainManager.once(IpcEvents.TASK_DONE, onTaskDone); - ipcMainManager.send(type, [task]); -} - -async function logConfig() { - const { default: packageJson } = await import('../../package.json', { - with: { type: 'json' }, - }); - const osinfo = await new Promise((resolve) => - getos((err, result) => resolve(err || result)), - ); - - console.log(`${packageJson.name} started - argv: ${JSON.stringify(process.argv)} - date: ${new Date()} - fiddle.version: ${packageJson.version} - os.arch: ${os.arch()} - os.platform: ${os.platform()} - os.release: ${os.release()} - os.version: ${os.version()} - platform: ${JSON.stringify(osinfo)}`); -} - -async function exitWithCode(code: number) { - console.log(`Electron Fiddle is exiting with code ${code}`); - process.exit(code); -} - -async function bisect(good: string, bad: string, opts: commander.OptionValues) { - try { - if (opts.logConfig) await logConfig(); - await sendTask(IpcEvents.TASK_BISECT, { - setup: await getSetup(opts), - goodVersion: good, - badVersion: bad, - }); - } catch (err) { - console.error(err); - exitWithCode(exitCodes[RunResult.INVALID]); - } -} - -async function test(opts: commander.OptionValues) { - try { - if (opts.logConfig) await logConfig(); - await sendTask(IpcEvents.TASK_TEST, { - setup: await getSetup(opts), - }); - } catch (err) { - console.error(err); - exitWithCode(exitCodes[RunResult.INVALID]); - } -} - -export async function processCommandLine(argv: string[]) { - const program = new commander.Command(); - program.exitOverride(); - - program - .command('start', { isDefault: true }) - .description('Start Fiddle') - .allowUnknownOption(); - - program - .command('bisect ') - .description('Find where regressions were introduced') - .option('--fiddle ', 'Open a fiddle', process.cwd()) - .option('--log-config', 'Log the fiddle version, platform, and args', false) - .option('--full', 'Shorthand --betas --nightlies --obsolete', false) - .option('--nightlies', 'Include nightly releases') - .option('--no-nightlies', 'Omit nightly releases') - .option('--betas', 'Include beta releases') - .option('--no-betas', 'Omit beta releases') - .option('--obsolete', 'Include obsolete releases') - .option('--no-obsolete', 'Omit obsolete releases') - .action(bisect); - - program - .command('test') - .description('Test a fiddle') - .option('--version ', 'Use Electron version') - .option('--fiddle ', 'Open a fiddle', process.cwd()) - .option('--log-config', 'Log the fiddle version, platform, and args', false) - .action(test); - - program.addHelpText( - 'after', - ` - -Example calls: - $ electron-fiddle bisect 10.0.0 11.2.0 --fiddle /path/to/fiddle - $ electron-fiddle bisect 10.0.0 11.2.0 --fiddle /path/to/fiddle --full - $ electron-fiddle test --version 11.2.0 --fiddle /path/to/fiddle - $ electron-fiddle test --version 11.2.0 --fiddle 8c5fc0c6a5153d49b5a4a56d3ed9da8f - $ electron-fiddle test --version 11.2.0 --fiddle https://gist.github.com/ckerr/8c5fc0c6a5153d49b5a4a56d3ed9da8f/ -`, - ); - - // do nothing if argv holds no commands/options - if (argv.length > (process.defaultApp ? 2 : 1)) { - try { - if (findProtocolArg(argv)) return; - await program.parseAsync(argv, { from: 'electron' }); - } catch (err) { - console.error(err); - exitWithCode(exitCodes[RunResult.INVALID]); - } - } -} diff --git a/src/main/main.ts b/src/main/main.ts index 53494f8e20..5986050cca 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -13,7 +13,6 @@ import { } from 'electron'; import { setupAboutPanel } from './about-panel'; -import { processCommandLine } from './command-line'; import { setupContent } from './content'; import { setupDevTools } from './devtools'; import { setupDialogs } from './dialogs'; @@ -34,8 +33,6 @@ import { setupVersions } from './versions'; import { getOrCreateMainWindow, mainIsReady } from './windows'; import { IpcEvents } from '../ipc-events'; -let argv: string[] = []; - /** * Handle the app's "ready" event. This is essentially * the method that takes care of booting the application. @@ -73,8 +70,6 @@ export async function onReady() { // any IPC listeners are set up before they're used mainIsReady(); await getOrCreateMainWindow(); - - processCommandLine(argv); } /** @@ -200,9 +195,7 @@ export function onWindowsAllClosed() { * * Exported for testing purposes. */ -export function main(argv_in: string[]) { - argv = argv_in; - +export function main() { // Handle creating/removing shortcuts on Windows when // installing/uninstalling. if (shouldQuit()) { @@ -225,4 +218,4 @@ export function main(argv_in: string[]) { }); } -main(process.argv); +main(); diff --git a/tests/main/command-line.spec.ts b/tests/main/command-line.spec.ts deleted file mode 100644 index b00cac72cb..0000000000 --- a/tests/main/command-line.spec.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; - -import { - ElectronReleaseChannel, - OutputEntry, - RunResult, -} from '../../src/interfaces'; -import { IpcEvents } from '../../src/ipc-events'; -import { processCommandLine } from '../../src/main/command-line'; -import { ipcMainManager } from '../../src/main/ipc'; - -vi.unmock('fs-extra'); - -describe('processCommandLine()', () => { - // when no fiddle specified, cwd is the default - const DEFAULT_FIDDLE = { - localFiddle: { - filePath: process.cwd(), - files: expect.anything(), - }, - }; - const ARGV_PREFIX = process.defaultApp - ? ['/path/to/electron', 'main.ts'] - : ['main.ts']; - - beforeEach(() => { - ipcMainManager.removeAllListeners(); - ipcMainManager.send = vi.fn(); - }); - - it('does nothing when passed no arguments', async () => { - await processCommandLine(ARGV_PREFIX); - expect(ipcMainManager.send).not.toHaveBeenCalled(); - }); - - it('does nothing when passed flags for electron binary', async () => { - await processCommandLine([...ARGV_PREFIX, '--no-sandbox']); - expect(ipcMainManager.send).not.toHaveBeenCalled(); - }); - - it('exits with 2 if called with invalid parameters', async () => { - const argv = [...ARGV_PREFIX, 'test', '--this-option-is-unknown=true']; - const exitCode = 2; - const exitSpy = vi - .spyOn(process, 'exit') - .mockImplementation((() => {}) as () => never); - await processCommandLine(argv); - expect(exitSpy).toHaveBeenCalledWith(exitCode); - exitSpy.mockReset(); - }); - - function expectSendCalledOnceWith( - event: IpcEvents, - payload: Record, - ) { - const send = vi.mocked(ipcMainManager.send); - expect(send).toHaveBeenCalledTimes(1); - const [call] = send.mock.calls; - expect(call.length).toEqual(2); - const [ev, params] = call; - expect(ev).toBe(event); - expect(params?.length).toBe(1); - const [request] = params!; - expect(request).toEqual(payload); - } - - async function expectLogConfigOptionWorks(argv: string[]) { - argv = [...argv, '--log-config']; - const consoleSpy = vi.spyOn(console, 'log').mockReset(); - await processCommandLine(argv); - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringMatching('electron-fiddle started'), - ); - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringMatching(`platform: ${process.platform}`), - ); - consoleSpy.mockReset(); - } - - describe('test', () => { - const ARGV = [...ARGV_PREFIX, 'test']; - - function expectTestCalledOnceWith(payload: Record) { - expectSendCalledOnceWith(IpcEvents.TASK_TEST, payload); - } - - it('uses cwd as the default fiddle location', async () => { - const argv = ARGV; - const expected = { - setup: { fiddle: DEFAULT_FIDDLE, hideChannels: [], showChannels: [] }, - }; - await processCommandLine(argv); - expectTestCalledOnceWith(expected); - }); - - it('handles a --fiddle that is a hex gist id', async () => { - const GIST_ID = 'af3e1a018f5dcce4a2ff40004ef5bab5'; - const argv = [...ARGV, '--fiddle', GIST_ID]; - const expected = { - setup: { - fiddle: { gistId: GIST_ID }, - hideChannels: [], - showChannels: [], - }, - }; - await processCommandLine(argv); - expectTestCalledOnceWith(expected); - }); - - it('handles a --fiddle option that is unrecognizable', async () => { - const FIDDLE = '✨🤪💎'; - const argv = [...ARGV, '--fiddle', FIDDLE]; - const consoleExpected = `Unrecognized Fiddle "${FIDDLE}"`; - const consoleSpy = vi - .spyOn(console, 'error') - .mockImplementation(() => {}); - const exitExpected = 2; - const exitSpy = vi - .spyOn(process, 'exit') - .mockImplementation((() => {}) as () => never); - await processCommandLine(argv); - expect(ipcMainManager.send).not.toHaveBeenCalled(); - expect(consoleSpy).toHaveBeenCalledWith(consoleExpected); - expect(exitSpy).toHaveBeenCalledWith(exitExpected); - consoleSpy.mockReset(); - exitSpy.mockReset(); - }); - - it('handles a --version option', async () => { - const VERSION = '12.0.0'; - const argv = [...ARGV, '--version', VERSION]; - const expected = { - setup: { - fiddle: DEFAULT_FIDDLE, - hideChannels: [], - showChannels: [], - version: VERSION, - }, - }; - await processCommandLine(argv); - expectTestCalledOnceWith(expected); - }); - - it('handles a --log-config option', async () => { - await expectLogConfigOptionWorks([...ARGV, '--log-config']); - }); - }); - - describe('bisect', () => { - const ARGV = [...ARGV_PREFIX, 'bisect']; - const GOOD = '10.0.0'; - const BAD = '11.2.0'; - - function expectBisectCalledOnceWith(payload: Record) { - expectSendCalledOnceWith(IpcEvents.TASK_BISECT, payload); - } - - it('sends a bisect request', async () => { - const argv = [...ARGV, GOOD, BAD]; - const expected = { - badVersion: BAD, - goodVersion: GOOD, - setup: { fiddle: DEFAULT_FIDDLE, hideChannels: [], showChannels: [] }, - }; - await processCommandLine(argv); - expectBisectCalledOnceWith(expected); - }); - - it('handles a --full option', async () => { - const argv = [...ARGV, GOOD, BAD, '--full']; - const expected = { - badVersion: BAD, - goodVersion: GOOD, - setup: { - fiddle: DEFAULT_FIDDLE, - hideChannels: [], - showChannels: [ - ElectronReleaseChannel.beta, - ElectronReleaseChannel.nightly, - ElectronReleaseChannel.stable, - ], - useObsolete: true, - }, - }; - await processCommandLine(argv); - expectBisectCalledOnceWith(expected); - }); - - it('handles a --nightlies option', async () => { - const argv = [...ARGV, GOOD, BAD, '--nightlies']; - const expected = { - badVersion: BAD, - goodVersion: GOOD, - setup: { - fiddle: DEFAULT_FIDDLE, - hideChannels: [], - showChannels: [ElectronReleaseChannel.nightly], - }, - }; - await processCommandLine(argv); - expectBisectCalledOnceWith(expected); - }); - - it('handles a --no-nightlies option', async () => { - const argv = [...ARGV, GOOD, BAD, '--no-nightlies']; - const expected = { - badVersion: BAD, - goodVersion: GOOD, - setup: { - fiddle: DEFAULT_FIDDLE, - hideChannels: [ElectronReleaseChannel.nightly], - showChannels: [], - }, - }; - await processCommandLine(argv); - expectBisectCalledOnceWith(expected); - }); - - it('handles a --betas option', async () => { - const argv = [...ARGV, GOOD, BAD, '--betas']; - const expected = { - badVersion: BAD, - goodVersion: GOOD, - setup: { - fiddle: DEFAULT_FIDDLE, - hideChannels: [], - showChannels: [ElectronReleaseChannel.beta], - }, - }; - await processCommandLine(argv); - expectBisectCalledOnceWith(expected); - }); - - it('handles a --no-betas option', async () => { - const argv = [...ARGV, GOOD, BAD, '--no-betas']; - const expected = { - badVersion: BAD, - goodVersion: GOOD, - setup: { - fiddle: DEFAULT_FIDDLE, - hideChannels: [ElectronReleaseChannel.beta], - showChannels: [], - }, - }; - await processCommandLine(argv); - expectBisectCalledOnceWith(expected); - }); - - it('handles a --obsolete option', async () => { - const argv = [...ARGV, GOOD, BAD, '--obsolete']; - const expected = { - badVersion: BAD, - goodVersion: GOOD, - setup: { - fiddle: DEFAULT_FIDDLE, - hideChannels: [], - showChannels: [], - useObsolete: true, - }, - }; - await processCommandLine(argv); - expectBisectCalledOnceWith(expected); - }); - - it('handles a --no-obsolete option', async () => { - const argv = [...ARGV, GOOD, BAD, '--no-obsolete']; - const expected = { - badVersion: BAD, - goodVersion: GOOD, - setup: { - fiddle: DEFAULT_FIDDLE, - hideChannels: [], - showChannels: [], - useObsolete: false, - }, - }; - await processCommandLine(argv); - expectBisectCalledOnceWith(expected); - }); - - it('handles a --fiddle option that is unrecognizable', async () => { - const FIDDLE = '✨🤪💎'; - const argv = [...ARGV, GOOD, BAD, '--fiddle', FIDDLE]; - const consoleExpected = `Unrecognized Fiddle "${FIDDLE}"`; - const consoleSpy = vi - .spyOn(console, 'error') - .mockImplementation(() => {}); - const exitExpected = 2; - const exitSpy = vi - .spyOn(process, 'exit') - .mockImplementation((() => {}) as () => never); - await processCommandLine(argv); - expect(ipcMainManager.send).not.toHaveBeenCalled(); - expect(consoleSpy).toHaveBeenCalledWith(consoleExpected); - expect(exitSpy).toHaveBeenCalledWith(exitExpected); - consoleSpy.mockReset(); - exitSpy.mockReset(); - }); - - it('handles a --log-config option', async () => { - await expectLogConfigOptionWorks([...ARGV, GOOD, BAD, '--log-config']); - }); - - describe(`watches for ${IpcEvents.TASK_DONE} events`, () => { - async function expectDoneCausesExit(result: RunResult, exitCode: number) { - const argv = [...ARGV, GOOD, BAD]; - vi.mocked(ipcMainManager.send).mockImplementationOnce(() => { - const fakeEvent = {}; - ipcMainManager.emit(IpcEvents.TASK_DONE, fakeEvent, result); - }); - const exitSpy = vi - .spyOn(process, 'exit') - .mockImplementation((() => {}) as () => never); - await processCommandLine(argv); - expect(exitSpy).toHaveBeenCalledWith(exitCode); - exitSpy.mockReset(); - } - - it(`exits with 0 on ${RunResult.SUCCESS}`, async () => { - await expectDoneCausesExit(RunResult.SUCCESS, 0); - }); - - it(`exits with 1 on ${RunResult.FAILURE}`, async () => { - await expectDoneCausesExit(RunResult.FAILURE, 1); - }); - - it(`exits with 2 on ${RunResult.INVALID}`, async () => { - await expectDoneCausesExit(RunResult.INVALID, 2); - }); - - it('sends output messages to the console', async () => { - const timeString = new Date().toLocaleTimeString(); - const text = 'asieoniezi'; - const expected = `[${timeString}] ${text}`; - const spy = vi.spyOn(console, 'log').mockReturnValue(); - - const fakeEvent = {}; - const entry: OutputEntry = { text, timeString }; - vi.mocked(ipcMainManager.send).mockImplementationOnce(() => { - ipcMainManager.emit(IpcEvents.OUTPUT_ENTRY, fakeEvent, entry); - }); - - const argv = [...ARGV, GOOD, BAD]; - await processCommandLine(argv); - - expect(spy).toHaveBeenCalledWith(expected); - - spy.mockRestore(); - }); - }); - }); -}); diff --git a/tests/main/main.spec.ts b/tests/main/main.spec.ts index a91bccbb6d..3d1fd37024 100644 --- a/tests/main/main.spec.ts +++ b/tests/main/main.spec.ts @@ -31,12 +31,6 @@ import { getOrCreateMainWindow } from '../../src/main/windows'; import { BrowserWindowMock } from '../mocks/browser-window'; import { overridePlatform, resetPlatform } from '../utils'; -// Need to mock this out or CI will hit an error due to -// code being run async continuing after the test ends: -// > ReferenceError: You are trying to `import` a file -// > after the test environment has been torn down. -vi.mock('getos'); - vi.mock('../../src/main/windows', () => ({ getOrCreateMainWindow: vi.fn(), mainIsReady: vi.fn(), @@ -91,12 +85,12 @@ describe('main', () => { it('quits during Squirrel events', () => { vi.mocked(shouldQuit).mockReturnValueOnce(true); - main([]); + main(); expect(app.quit).toHaveBeenCalledTimes(1); }); it('listens to core events', async () => { - main([]); + main(); expect(app.on).toHaveBeenCalledTimes(6); }); }); diff --git a/yarn.lock b/yarn.lock index 680d315d4b..4869252d10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2831,13 +2831,6 @@ __metadata: languageName: node linkType: hard -"@types/getos@npm:^3.0.1": - version: 3.0.1 - resolution: "@types/getos@npm:3.0.1" - checksum: 10c0/5037e8344365014e18f92a7611bdabe6debaf244e636b320d886c0bf174444c2d9fc77dd968c333c009ea320bc8cb8c159bfe32d758ad7a7059deeeb75a23987 - languageName: node - linkType: hard - "@types/glob@npm:^7.1.1": version: 7.2.0 resolution: "@types/glob@npm:7.2.0" @@ -4842,13 +4835,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:^7.1.0": - version: 7.2.0 - resolution: "commander@npm:7.2.0" - checksum: 10c0/8d690ff13b0356df7e0ebbe6c59b4712f754f4b724d4f473d3cc5b3fdcf978e3a5dc3078717858a2ceb50b0f84d0660a7f22a96cdc50fb877d0c9bb31593d23a - languageName: node - linkType: hard - "commander@npm:^8.3.0": version: 8.3.0 resolution: "commander@npm:8.3.0" @@ -5640,7 +5626,6 @@ __metadata: "@tsconfig/node22": "npm:^22.0.2" "@types/classnames": "npm:^2.2.11" "@types/fs-extra": "npm:^9.0.7" - "@types/getos": "npm:^3.0.1" "@types/node": "npm:^22.19.1" "@types/parse-env-string": "npm:^1.0.2" "@types/react": "npm:^16.14.0" @@ -5654,7 +5639,6 @@ __metadata: "@vitest/coverage-v8": "npm:4.1.0" algoliasearch: "npm:^4.12.0" classnames: "npm:^2.2.6" - commander: "npm:^7.1.0" copy-webpack-plugin: "npm:^11.0.0" css-loader: "npm:^6.7.1" electron: "npm:^40.8.4" @@ -5670,7 +5654,6 @@ __metadata: extract-zip: "npm:^2.0.1" fork-ts-checker-webpack-plugin: "npm:^8.0.0" fs-extra: "npm:^9.1.0" - getos: "npm:^3.2.1" husky: "npm:^9.0.11" jsdom: "npm:^26.1.0" less: "npm:^4.1.1"