From 871c4f0a5356d3887d72b49c16a7ae1c53247a19 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 15:39:48 +0000 Subject: [PATCH] feat: rename `publish` command to `release` Renames the primary Electron Forge `publish` command to `release` while keeping `publish` working as a hidden, deprecated alias. - CLI: register `release` as the primary command and add `publish` as a hidden alias that prints a deprecation warning to stderr before delegating to the exact same behavior via `runRelease()`. - Core API: rename the `publish()` API to `release()` (src/api/publish.ts -> release.ts, `PublishOptions` -> `ReleaseOptions`). Keep `ForgeAPI.publish()` and the `PublishOptions` type as `@deprecated` aliases for backward compatibility. - Templates: base template `package.json` now uses a `release` script (`electron-forge release`); update template verdaccio specs. - Tests: rename core spec to release.spec.ts; add CLI help assertions that `release` is listed and `publish` is hidden. Publisher plugin classes/interfaces and the `Publisher` concept are intentionally left unchanged. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01KNJA1Q3Airp39YMZ4k46E8 --- packages/api/cli/spec/cli.slow.spec.ts | 8 +++ .../api/cli/src/electron-forge-publish.ts | 61 +++++------------ .../api/cli/src/electron-forge-release.ts | 67 +++++++++++++++++++ packages/api/cli/src/electron-forge.ts | 8 ++- .../fast/{publish.spec.ts => release.spec.ts} | 16 ++--- packages/api/core/src/api/index.ts | 24 +++++-- .../core/src/api/{publish.ts => release.ts} | 30 ++++----- packages/template/base/tmpl/package.json | 2 +- ...eTypeScriptTemplate.slow.verdaccio.spec.ts | 2 +- .../WebpackTypeScript.slow.verdaccio.spec.ts | 2 +- 10 files changed, 145 insertions(+), 75 deletions(-) create mode 100644 packages/api/cli/src/electron-forge-release.ts rename packages/api/core/spec/fast/{publish.spec.ts => release.spec.ts} (96%) rename packages/api/core/src/api/{publish.ts => release.ts} (94%) diff --git a/packages/api/cli/spec/cli.slow.spec.ts b/packages/api/cli/spec/cli.slow.spec.ts index 4ff4383bcd..7e482ff905 100644 --- a/packages/api/cli/spec/cli.slow.spec.ts +++ b/packages/api/cli/spec/cli.slow.spec.ts @@ -20,4 +20,12 @@ describe('cli', () => { it('should fail on unknown subcommands', async () => { await expect(runForgeCLI('nonexistent')).rejects.toThrow(Error); }); + + it('should list the release command in help output', async () => { + await expect(runForgeCLI('help')).resolves.toMatch(/\brelease\b/); + }); + + it('should hide the deprecated publish alias from help output', async () => { + await expect(runForgeCLI('help')).resolves.not.toMatch(/\bpublish\b/); + }); }); diff --git a/packages/api/cli/src/electron-forge-publish.ts b/packages/api/cli/src/electron-forge-publish.ts index f504cd57d7..1b172d842e 100644 --- a/packages/api/cli/src/electron-forge-publish.ts +++ b/packages/api/cli/src/electron-forge-publish.ts @@ -1,48 +1,21 @@ +import url from 'node:url'; import { styleText } from 'node:util'; -import { initializeProxy } from '@electron/get'; -import { api, PublishOptions } from '@electron-forge/core'; -import { resolveWorkingDir } from '@electron-forge/core-utils'; -import { program } from 'commander'; - import './util/terminate.js'; -import packageJSON from '../package.json' with { type: 'json' }; - -import { getMakeOptions } from './electron-forge-make.js'; - -program - .version(packageJSON.version, '-V, --version', 'Output the current version.') - .helpOption('-h, --help', 'Output usage information.') - .argument( - '[dir]', - 'Directory to run the command in. (default: current directory)', - ) - .option( - '--target [target[,target...]]', - 'A comma-separated list of deployment targets. (default: all publishers in your Forge config)', - ) - .option( - '--dry-run', - `Run the ${styleText('green', 'make')} command and save publish metadata without uploading anything.`, - ) - .option('--from-dry-run', 'Publish artifacts from the last saved dry run.') - .allowUnknownOption(true) - .action(async (targetDir) => { - const dir = resolveWorkingDir(targetDir); - const options = program.opts(); - - initializeProxy(); - - const publishOpts: PublishOptions = { - dir, - interactive: true, - dryRun: options.dryRun, - dryRunResume: options.fromDryRun, - }; - if (options.target) publishOpts.publishTargets = options.target.split(','); - - publishOpts.makeOptions = await getMakeOptions(); - await api.publish(publishOpts); - }) - .parse(process.argv); +import { runRelease } from './electron-forge-release.js'; + +// `electron-forge publish` is a deprecated alias for `electron-forge release`. +// It keeps working for now but prints a deprecation warning to stderr and +// delegates to the exact same behavior as `release`. +if (import.meta.url.startsWith('file:')) { + const modulePath = url.fileURLToPath(import.meta.url); + if (process.argv[1] === modulePath) { + console.error( + styleText('yellow', '⚠'), + '`electron-forge publish` is deprecated and will be removed in a future major version; use `electron-forge release` instead.', + ); + + await runRelease(); + } +} diff --git a/packages/api/cli/src/electron-forge-release.ts b/packages/api/cli/src/electron-forge-release.ts new file mode 100644 index 0000000000..ad4a5ac740 --- /dev/null +++ b/packages/api/cli/src/electron-forge-release.ts @@ -0,0 +1,67 @@ +import url from 'node:url'; +import { styleText } from 'node:util'; + +import { initializeProxy } from '@electron/get'; +import { api, ReleaseOptions } from '@electron-forge/core'; +import { resolveWorkingDir } from '@electron-forge/core-utils'; +import { program } from 'commander'; + +import './util/terminate.js'; +import packageJSON from '../package.json' with { type: 'json' }; + +import { getMakeOptions } from './electron-forge-make.js'; + +export async function runRelease(): Promise { + program + .version( + packageJSON.version, + '-V, --version', + 'Output the current version.', + ) + .helpOption('-h, --help', 'Output usage information.') + .argument( + '[dir]', + 'Directory to run the command in. (default: current directory)', + ) + .option( + '--target [target[,target...]]', + 'A comma-separated list of deployment targets. (default: all publishers in your Forge config)', + ) + .option( + '--dry-run', + `Run the ${styleText('green', 'make')} command and save release metadata without uploading anything.`, + ) + .option('--from-dry-run', 'Release artifacts from the last saved dry run.') + .allowUnknownOption(true) + .action(async (targetDir) => { + const dir = resolveWorkingDir(targetDir); + const options = program.opts(); + + initializeProxy(); + + const releaseOpts: ReleaseOptions = { + dir, + interactive: true, + dryRun: options.dryRun, + dryRunResume: options.fromDryRun, + }; + if (options.target) + releaseOpts.publishTargets = options.target.split(','); + + releaseOpts.makeOptions = await getMakeOptions(); + + await api.release(releaseOpts); + }) + .parse(process.argv); +} + +// NOTE: this is a hack that exists because Node.js didn't add import.meta.main +// support until 22.18.0. We should bump up the engines and get that fix before +// we go to stable. +// ref https://2ality.com/2022/07/nodejs-esm-main.html +if (import.meta.url.startsWith('file:')) { + const modulePath = url.fileURLToPath(import.meta.url); + if (process.argv[1] === modulePath) { + await runRelease(); + } +} diff --git a/packages/api/cli/src/electron-forge.ts b/packages/api/cli/src/electron-forge.ts index 85f08d0de5..6711e27280 100755 --- a/packages/api/cli/src/electron-forge.ts +++ b/packages/api/cli/src/electron-forge.ts @@ -35,7 +35,13 @@ program 'make', 'Generate distributables for the current Electron application.', ) - .command('publish', 'Publish the current Electron application.') + .command('release', 'Release the current Electron application.') + // `publish` is a deprecated alias for `release`. It is hidden from the help + // output and prints a deprecation warning when invoked (see + // electron-forge-publish.ts). + .command('publish', 'Publish the current Electron application.', { + hidden: true, + }) .passThroughOptions(true) .hook('preSubcommand', async (_command, subcommand) => { if (!process.argv.includes('--help') && !process.argv.includes('-h')) { diff --git a/packages/api/core/spec/fast/publish.spec.ts b/packages/api/core/spec/fast/release.spec.ts similarity index 96% rename from packages/api/core/spec/fast/publish.spec.ts rename to packages/api/core/spec/fast/release.spec.ts index 8f9540944c..5d4c9dfdc8 100644 --- a/packages/api/core/spec/fast/publish.spec.ts +++ b/packages/api/core/spec/fast/release.spec.ts @@ -9,7 +9,7 @@ import { import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { listrMake } from '../../src/api/make'; -import publish from '../../src/api/publish'; +import release from '../../src/api/release'; import findConfig from '../../src/util/forge-config.js'; import { importSearch } from '../../src/util/import-search.js'; @@ -45,9 +45,9 @@ vi.mock(import('../../src/util/import-search'), async (importOriginal) => { }; }); -describe('publish', () => { +describe('release', () => { it('calls "make"', async () => { - await publish({ + await release({ dir: import.meta.dirname, interactive: false, }); @@ -79,7 +79,7 @@ describe('publish', () => { } as any; }); - await publish({ + await release({ dir: import.meta.dirname, interactive: false, }); @@ -109,7 +109,7 @@ describe('publish', () => { {} as unknown as ResolvedForgeConfig, ); - await publish({ + await release({ dir: import.meta.dirname, interactive: false, publishTargets: [new MockPublisher()], @@ -134,7 +134,7 @@ describe('publish', () => { vi.mocked(importSearch).mockResolvedValue(MockPublisher); - await publish({ + await release({ dir: import.meta.dirname, interactive: false, }); @@ -172,7 +172,7 @@ describe('publish', () => { }); it('dryRun creates hash JSON files', async () => { - await publish({ + await release({ dir: import.meta.dirname, outDir: tmpDir, interactive: false, @@ -203,7 +203,7 @@ describe('publish', () => { MockPublisher.prototype.publish = mockPublish; MockPublisher.prototype.__isElectronForgePublisher = true; - await publish({ + await release({ dir: import.meta.dirname, outDir: tmpDir, interactive: false, diff --git a/packages/api/core/src/api/index.ts b/packages/api/core/src/api/index.ts index eefb7b8042..da3cf53025 100644 --- a/packages/api/core/src/api/index.ts +++ b/packages/api/core/src/api/index.ts @@ -8,9 +8,15 @@ import ForgeUtils from '../util/index.js'; import make, { MakeOptions } from './make.js'; import _package, { PackageOptions } from './package.js'; -import publish, { PublishOptions } from './publish.js'; +import release, { ReleaseOptions } from './release.js'; import start, { StartOptions } from './start.js'; +/** + * @deprecated Use {@link ReleaseOptions} instead. `PublishOptions` is a + * deprecated alias that will be removed in a future major version. + */ +export type PublishOptions = ReleaseOptions; + export class ForgeAPI { /** * Make distributables for an Electron application @@ -26,11 +32,21 @@ export class ForgeAPI { return _package(opts); } + /** + * Release an Electron application into the given target service + */ + release(opts: ReleaseOptions): Promise { + return release(opts); + } + /** * Publish an Electron application into the given target service + * + * @deprecated Use {@link ForgeAPI.release} instead. `publish` is a deprecated + * alias that will be removed in a future major version. */ - publish(opts: PublishOptions): Promise { - return publish(opts); + publish(opts: ReleaseOptions): Promise { + return release(opts); } /** @@ -53,7 +69,7 @@ export { ForgeUtils, MakeOptions, PackageOptions, - PublishOptions, + ReleaseOptions, StartOptions, api, utils, diff --git a/packages/api/core/src/api/publish.ts b/packages/api/core/src/api/release.ts similarity index 94% rename from packages/api/core/src/api/publish.ts rename to packages/api/core/src/api/release.ts index fa1adead49..df4943f09f 100644 --- a/packages/api/core/src/api/publish.ts +++ b/packages/api/core/src/api/release.ts @@ -26,16 +26,16 @@ import resolveDir from '../util/resolve-dir.js'; import { listrMake, MakeOptions } from './make.js'; -const d = debug('electron-forge:publish'); +const d = debug('electron-forge:release'); -type PublishContext = { +type ReleaseContext = { dir: string; forgeConfig: ResolvedForgeConfig; publishers: PublisherBase[]; makeResults: ForgeMakeResult[]; }; -export interface PublishOptions { +export interface ReleaseOptions { /** * The path to the app to be published */ @@ -70,7 +70,7 @@ export interface PublishOptions { } export default autoTrace( - { name: 'publish()', category: '@electron-forge/core' }, + { name: 'release()', category: '@electron-forge/core' }, async ( childTrace, { @@ -81,13 +81,13 @@ export default autoTrace( dryRun = false, dryRunResume = false, outDir, - }: PublishOptions, + }: ReleaseOptions, ): Promise => { if (dryRun && dryRunResume) { throw new Error("Can't dry run and resume a dry run at the same time"); } - const listrOptions: ForgeListrOptions = { + const listrOptions: ForgeListrOptions = { concurrent: false, rendererOptions: { collapseErrors: false, @@ -100,12 +100,12 @@ export default autoTrace( const publishDistributablesTasks = (childTrace: typeof autoTrace) => [ { title: 'Publishing distributables', - task: childTrace>>( + task: childTrace>>( { name: 'publish-distributables', category: '@electron-forge/core' }, async ( childTrace, { dir, forgeConfig, makeResults, publishers }, - task: ForgeListrTask, + task: ForgeListrTask, ) => { if (publishers.length === 0) { task.output = 'No publishers configured'; @@ -156,11 +156,11 @@ export default autoTrace( }, ]; - const runner = new Listr( + const runner = new Listr( [ { title: 'Loading configuration', - task: childTrace>>( + task: childTrace>>( { name: 'load-forge-config', category: '@electron-forge/core' }, async (childTrace, ctx) => { const resolvedDir = await resolveDir(providedDir); @@ -177,7 +177,7 @@ export default autoTrace( }, { title: 'Resolving publish targets', - task: childTrace>>( + task: childTrace>>( { name: 'resolve-publish-targets', category: '@electron-forge/core', @@ -252,7 +252,7 @@ export default autoTrace( title: dryRunResume ? 'Resuming from dry run...' : `Running ${styleText('yellow', 'make')} command`, - task: childTrace>>( + task: childTrace>>( { name: dryRunResume ? 'resume-dry-run' : 'make()', category: '@electron-forge/core', @@ -276,12 +276,12 @@ export default autoTrace( return delayTraceTillSignal( childTrace, - task.newListr( + task.newListr( publishes.map((publishStates, index) => { return { title: `Publishing dry-run ${styleText('blue', `#${index + 1}`)}`, task: childTrace< - Parameters> + Parameters> >( { name: `publish-dry-run-${index + 1}`, @@ -371,7 +371,7 @@ export default autoTrace( { title: 'Saving dry-run state', task: childTrace< - Parameters> + Parameters> >( { name: 'save-dry-run', category: '@electron-forge/core' }, async (childTrace, { dir, forgeConfig, makeResults }) => { diff --git a/packages/template/base/tmpl/package.json b/packages/template/base/tmpl/package.json index 99db188360..562a039a8a 100644 --- a/packages/template/base/tmpl/package.json +++ b/packages/template/base/tmpl/package.json @@ -12,6 +12,6 @@ "start": "electron-forge start", "package": "electron-forge package", "make": "electron-forge make", - "publish": "electron-forge publish" + "release": "electron-forge release" } } diff --git a/packages/template/vite-typescript/spec/ViteTypeScriptTemplate.slow.verdaccio.spec.ts b/packages/template/vite-typescript/spec/ViteTypeScriptTemplate.slow.verdaccio.spec.ts index 4fd6fe26f7..89477248d2 100644 --- a/packages/template/vite-typescript/spec/ViteTypeScriptTemplate.slow.verdaccio.spec.ts +++ b/packages/template/vite-typescript/spec/ViteTypeScriptTemplate.slow.verdaccio.spec.ts @@ -73,7 +73,7 @@ describe('ViteTypeScriptTemplate', () => { expect(packageJSON.scripts.start).toBe('electron-forge start'); expect(packageJSON.scripts.package).toBe('electron-forge package'); expect(packageJSON.scripts.make).toBe('electron-forge make'); - expect(packageJSON.scripts.publish).toBe('electron-forge publish'); + expect(packageJSON.scripts.release).toBe('electron-forge release'); }); }); diff --git a/packages/template/webpack-typescript/spec/WebpackTypeScript.slow.verdaccio.spec.ts b/packages/template/webpack-typescript/spec/WebpackTypeScript.slow.verdaccio.spec.ts index 65a930fdd7..42b5962d5a 100644 --- a/packages/template/webpack-typescript/spec/WebpackTypeScript.slow.verdaccio.spec.ts +++ b/packages/template/webpack-typescript/spec/WebpackTypeScript.slow.verdaccio.spec.ts @@ -65,7 +65,7 @@ describe('WebpackTypeScriptTemplate', () => { expect(packageJSON.scripts.start).toBe('electron-forge start'); expect(packageJSON.scripts.package).toBe('electron-forge package'); expect(packageJSON.scripts.make).toBe('electron-forge make'); - expect(packageJSON.scripts.publish).toBe('electron-forge publish'); + expect(packageJSON.scripts.release).toBe('electron-forge release'); }); describe('lint', () => {