From 133a8b49e8566cb371679bfc93c9b8d95652f771 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 11 Aug 2025 15:52:34 -0700 Subject: [PATCH 01/26] fix: wait for previous app to exit before restarting (#3973) --- packages/api/core/src/api/start.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/api/core/src/api/start.ts b/packages/api/core/src/api/start.ts index ea482b6ff0..a0c1ae709e 100644 --- a/packages/api/core/src/api/start.ts +++ b/packages/api/core/src/api/start.ts @@ -238,15 +238,17 @@ export default autoTrace( }; if (interactive) { - process.stdin.on('data', async (data) => { + process.stdin.on('data', (data) => { if (data.toString().trim() === 'rs' && lastSpawned) { readline.moveCursor(process.stdout, 0, -1); readline.clearLine(process.stdout, 0); readline.cursorTo(process.stdout, 0); console.info(`${chalk.green('✔ ')}${chalk.dim('Restarting Electron app')}`); lastSpawned.restarted = true; + lastSpawned.on('exit', async () => { + lastSpawned!.emit('restarted', await forgeSpawnWrapper()); + }); lastSpawned.kill('SIGTERM'); - lastSpawned.emit('restarted', await forgeSpawnWrapper()); } }); process.stdin.resume(); From 8eb38761d04aaffcd1a7ffdcb0add26bc7536aca Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Mon, 11 Aug 2025 15:52:43 -0700 Subject: [PATCH 02/26] fix(plugin-vite): only copy `/public` in the renderer (#3971) --- packages/plugin/vite/src/config/vite.main.config.ts | 1 + packages/plugin/vite/src/config/vite.preload.config.ts | 1 + packages/plugin/vite/src/config/vite.renderer.config.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/packages/plugin/vite/src/config/vite.main.config.ts b/packages/plugin/vite/src/config/vite.main.config.ts index 5f5939094f..564f6992fd 100644 --- a/packages/plugin/vite/src/config/vite.main.config.ts +++ b/packages/plugin/vite/src/config/vite.main.config.ts @@ -7,6 +7,7 @@ export function getConfig(forgeEnv: ConfigEnv<'build'>, userConfig: UserConfig = const define = getBuildDefine(forgeEnv); const config: UserConfig = { build: { + copyPublicDir: false, rollupOptions: { external, }, diff --git a/packages/plugin/vite/src/config/vite.preload.config.ts b/packages/plugin/vite/src/config/vite.preload.config.ts index 3f4f06f65e..5b743ea6fe 100644 --- a/packages/plugin/vite/src/config/vite.preload.config.ts +++ b/packages/plugin/vite/src/config/vite.preload.config.ts @@ -6,6 +6,7 @@ export function getConfig(forgeEnv: ConfigEnv<'build'>, userConfig: UserConfig = const { forgeConfigSelf } = forgeEnv; const config: UserConfig = { build: { + copyPublicDir: false, rollupOptions: { external, // Preload scripts may contain Web assets, so use the `build.rollupOptions.input` instead `build.lib.entry`. diff --git a/packages/plugin/vite/src/config/vite.renderer.config.ts b/packages/plugin/vite/src/config/vite.renderer.config.ts index cf022ad56f..85863f17b3 100644 --- a/packages/plugin/vite/src/config/vite.renderer.config.ts +++ b/packages/plugin/vite/src/config/vite.renderer.config.ts @@ -12,6 +12,7 @@ export function getConfig(forgeEnv: ConfigEnv<'renderer'>, userConfig: UserConfi mode, base: './', build: { + copyPublicDir: true, outDir: `.vite/renderer/${name}`, }, plugins: [pluginExposeRenderer(name)], From 717faf963b103b55f7041dc26e4fd825215aaf1d Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Tue, 26 Aug 2025 15:33:47 -0700 Subject: [PATCH 03/26] chore: use Prettier 3.6 with experimental CLI (#3977) --- .eslintrc.json | 46 +- .vscode/extensions.json | 6 +- ci/fix-changelog.js | 11 +- nx.json | 8 +- package.json | 10 +- packages/api/cli/spec/cli.slow.spec.ts | 6 +- .../api/cli/spec/util/check-system.spec.ts | 50 +- packages/api/cli/src/electron-forge-import.ts | 11 +- packages/api/cli/src/electron-forge-init.ts | 17 +- packages/api/cli/src/electron-forge-make.ts | 27 +- .../api/cli/src/electron-forge-package.ts | 5 +- .../api/cli/src/electron-forge-publish.ts | 15 +- packages/api/cli/src/electron-forge-start.ts | 32 +- packages/api/cli/src/electron-forge.ts | 17 +- packages/api/cli/src/util/check-system.ts | 66 +- .../api/cli/src/util/resolve-working-dir.ts | 4 +- packages/api/cli/src/util/terminate.ts | 29 +- .../api/core/spec/fast/find-template.spec.ts | 46 +- packages/api/core/spec/fast/init-git.spec.ts | 10 +- packages/api/core/spec/fast/make.spec.ts | 26 +- packages/api/core/spec/fast/publish.spec.ts | 18 +- packages/api/core/spec/fast/start.spec.ts | 57 +- .../fast/util/electron-executable.spec.ts | 12 +- .../core/spec/fast/util/forge-config.spec.ts | 125 +++- packages/api/core/spec/fast/util/hook.spec.ts | 26 +- .../core/spec/fast/util/import-search.spec.ts | 12 +- .../fast/util/install-dependencies.spec.ts | 64 +- .../api/core/spec/fast/util/out-dir.spec.ts | 14 +- .../core/spec/fast/util/parse-archs.spec.ts | 6 +- .../spec/fast/util/read-package-json.spec.ts | 12 +- .../core/spec/fast/util/resolve-dir.spec.ts | 18 +- .../fast/util/upgrade-forge-config.spec.ts | 41 +- .../core/spec/fixture/custom_init/index.js | 10 +- packages/api/core/spec/slow/api.slow.spec.ts | 231 +++++-- .../slow/install-dependencies.slow.spec.ts | 33 +- packages/api/core/src/api/import.ts | 413 +++++++----- packages/api/core/src/api/index.ts | 14 +- .../src/api/init-scripts/find-template.ts | 8 +- .../src/api/init-scripts/init-directory.ts | 10 +- .../api/core/src/api/init-scripts/init-git.ts | 6 +- .../core/src/api/init-scripts/init-link.ts | 35 +- .../api/core/src/api/init-scripts/init-npm.ts | 23 +- packages/api/core/src/api/init.ts | 47 +- packages/api/core/src/api/make.ts | 289 ++++++--- packages/api/core/src/api/package.ts | 581 ++++++++++++----- packages/api/core/src/api/publish.ts | 226 ++++--- packages/api/core/src/api/start.ts | 134 ++-- packages/api/core/src/util/deprecate.ts | 7 +- .../api/core/src/util/electron-executable.ts | 20 +- packages/api/core/src/util/forge-config.ts | 109 +++- packages/api/core/src/util/hook.ts | 37 +- packages/api/core/src/util/import-search.ts | 39 +- packages/api/core/src/util/index.ts | 5 +- .../api/core/src/util/install-dependencies.ts | 20 +- packages/api/core/src/util/linux-installer.ts | 10 +- packages/api/core/src/util/parse-archs.ts | 18 +- .../api/core/src/util/plugin-interface.ts | 67 +- packages/api/core/src/util/publish-state.ts | 37 +- .../api/core/src/util/read-package-json.ts | 14 +- packages/api/core/src/util/resolve-dir.ts | 10 +- .../api/core/src/util/upgrade-forge-config.ts | 43 +- packages/maker/appx/spec/MakerAppX.spec.ts | 9 +- .../maker/appx/spec/util/author-name.spec.ts | 4 +- packages/maker/appx/src/MakerAppX.ts | 50 +- packages/maker/appx/src/util/author-name.ts | 6 +- .../maker/base/spec/config-fetcher.spec.ts | 2 +- .../maker/base/spec/ensure-output.spec.ts | 16 +- packages/maker/base/spec/support.spec.ts | 4 +- packages/maker/base/spec/version.spec.ts | 14 +- packages/maker/base/src/Maker.ts | 33 +- packages/maker/deb/spec/MakerDeb.spec.ts | 5 +- packages/maker/dmg/spec/MakerDMG.spec.ts | 5 +- packages/maker/dmg/src/Config.ts | 5 +- packages/maker/dmg/src/MakerDMG.ts | 13 +- .../maker/flatpak/spec/MakerFlatpak.spec.ts | 2 +- packages/maker/flatpak/src/MakerFlatpak.ts | 4 +- packages/maker/pkg/spec/MakerPKG.spec.ts | 4 +- packages/maker/pkg/src/MakerPKG.ts | 20 +- packages/maker/rpm/spec/MakerRpm.spec.ts | 7 +- packages/maker/rpm/src/MakerRpm.ts | 5 +- packages/maker/snap/spec/MakerSnap.spec.ts | 2 +- packages/maker/snap/src/Config.ts | 3 +- packages/maker/squirrel/src/MakerSquirrel.ts | 51 +- packages/maker/wix/spec/author-name.spec.ts | 4 +- packages/maker/wix/src/Config.ts | 11 +- packages/maker/wix/src/MakerWix.ts | 17 +- packages/maker/wix/src/util/author-name.ts | 6 +- packages/maker/zip/spec/MakerZip.spec.ts | 161 +++-- packages/maker/zip/src/MakerZIP.ts | 33 +- .../src/AutoUnpackNativesPlugin.ts | 8 +- packages/plugin/base/src/Plugin.ts | 12 +- .../src/ElectronegativityPlugin.ts | 7 +- .../fuses/spec/FusesPlugin.slow.spec.ts | 32 +- packages/plugin/fuses/src/FusesPlugin.ts | 60 +- .../src/util/getElectronExecutablePath.ts | 6 +- .../spec/LocalElectronPlugin.spec.ts | 59 +- .../local-electron/src/LocalElectronPlugin.ts | 14 +- packages/plugin/vite/forge-vite-env.d.ts | 4 +- packages/plugin/vite/spec/ViteConfig.spec.ts | 25 +- packages/plugin/vite/spec/VitePlugin.spec.ts | 124 +++- .../vite/spec/config/vite.base.config.spec.ts | 14 +- packages/plugin/vite/src/ViteConfig.ts | 38 +- packages/plugin/vite/src/VitePlugin.ts | 94 ++- .../vite/src/config/vite.base.config.ts | 34 +- .../vite/src/config/vite.main.config.ts | 12 +- .../vite/src/config/vite.preload.config.ts | 5 +- .../vite/src/config/vite.renderer.config.ts | 5 +- .../webpack/spec/AssetRelocatorPatch.spec.ts | 64 +- .../plugin/webpack/spec/WebpackConfig.spec.ts | 317 +++++++-- .../plugin/webpack/spec/WebpackPlugin.spec.ts | 124 +++- .../apps/native-modules/src/index.html | 2 +- .../webpack/spec/util/processConfig.spec.ts | 32 +- packages/plugin/webpack/src/Config.ts | 26 +- packages/plugin/webpack/src/WebpackConfig.ts | 225 +++++-- packages/plugin/webpack/src/WebpackPlugin.ts | 607 ++++++++++++------ .../webpack/src/util/AssetRelocatorPatch.ts | 87 +-- .../webpack/src/util/ElectronForgeLogging.ts | 2 +- .../plugin/webpack/src/util/processConfig.ts | 12 +- .../webpack/src/util/rendererTypeUtils.ts | 34 +- .../base-static/spec/StaticPublisher.spec.ts | 4 +- .../base-static/src/PublisherStatic.ts | 15 +- packages/publisher/base/src/Publisher.ts | 22 +- .../bitbucket/src/PublisherBitbucket.ts | 36 +- .../spec/PublisherERS.spec.ts | 129 +++- .../src/PublisherERS.ts | 68 +- packages/publisher/gcs/src/PublisherGCS.ts | 34 +- .../publisher/github/spec/util/github.spec.ts | 14 +- .../publisher/github/src/PublisherGithub.ts | 95 ++- packages/publisher/github/src/util/github.ts | 10 +- .../publisher/nucleus/src/PublisherNucleus.ts | 38 +- packages/publisher/s3/src/PublisherS3.ts | 30 +- .../snapcraft/src/PublisherSnapcraft.ts | 21 +- .../base/spec/determine-author.spec.ts | 47 +- packages/template/base/src/BaseTemplate.ts | 60 +- .../template/base/src/determine-author.ts | 3 +- packages/template/base/tmpl/index.css | 5 +- packages/template/base/tmpl/index.html | 2 +- .../spec/ViteTypeScriptTemplate.slow.spec.ts | 19 +- .../src/ViteTypeScriptTemplate.ts | 43 +- .../vite-typescript/tmpl/forge.config.ts | 7 +- .../template/vite-typescript/tmpl/main.ts | 4 +- .../template/vite-typescript/tmpl/renderer.ts | 4 +- .../template/vite/spec/ViteTemplate.spec.ts | 19 +- packages/template/vite/src/ViteTemplate.ts | 42 +- packages/template/vite/tmpl/renderer.js | 4 +- .../spec/WebpackTypeScript.slow.spec.ts | 19 +- .../src/WebpackTypeScriptTemplate.ts | 34 +- .../webpack-typescript/tmpl/forge.config.ts | 7 +- .../webpack-typescript/tmpl/renderer.ts | 4 +- .../webpack/spec/WebpackTemplate.spec.ts | 13 +- .../template/webpack/src/WebpackTemplate.ts | 39 +- packages/template/webpack/tmpl/renderer.js | 4 +- .../core-utils/spec/electron-version.spec.ts | 127 +++- .../core-utils/spec/package-manager.spec.ts | 96 ++- .../utils/core-utils/src/electron-version.ts | 69 +- .../utils/core-utils/src/package-manager.ts | 41 +- packages/utils/core-utils/src/rebuild.ts | 74 ++- .../utils/core-utils/src/remote-rebuild.ts | 12 +- packages/utils/test-utils/src/index.ts | 6 +- packages/utils/tracer/src/index.ts | 28 +- packages/utils/types/src/index.ts | 92 ++- packages/utils/web-multi-logger/src/Log.ts | 5 +- packages/utils/web-multi-logger/src/Logger.ts | 15 +- packages/utils/web-multi-logger/src/Tab.ts | 7 +- .../utils/web-multi-logger/static/index.html | 10 +- .../utils/web-multi-logger/static/main.js | 4 +- tools/doc-plugin/src/index.tsx | 8 +- tools/fix-deps.ts | 10 +- tools/gen-ts-glue.ts | 14 +- tools/gen-tsconfigs.ts | 34 +- tools/position-docs.ts | 26 +- tools/sync-readmes.ts | 58 +- tools/test-clear.ts | 6 +- tools/test-dist.ts | 10 +- tools/update-dependencies.js | 51 +- tools/utils.ts | 4 +- typedoc.json | 9 +- typings/sudo-prompt/index.d.ts | 8 +- yarn.lock | 8 +- 179 files changed, 5576 insertions(+), 2075 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index f0d88e46b1..43d0d3ed1b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,7 +9,14 @@ "globals": { "NodeJS": "readonly" }, - "extends": ["eslint:recommended", "plugin:import/errors", "plugin:import/warnings", "plugin:n/recommended", "plugin:promise/recommended", "prettier"], + "extends": [ + "eslint:recommended", + "plugin:import/errors", + "plugin:import/warnings", + "plugin:n/recommended", + "plugin:promise/recommended", + "prettier" + ], "rules": { "no-unused-vars": [ "error", @@ -23,7 +30,16 @@ "import/order": [ "error", { - "groups": ["builtin", "external", "internal", "parent", "sibling", "index", "object", "type"], + "groups": [ + "builtin", + "external", + "internal", + "parent", + "sibling", + "index", + "object", + "type" + ], "newlines-between": "always", "alphabetize": { "order": "asc", @@ -53,7 +69,10 @@ }, "project": ["./tsconfig.base.json"] }, - "extends": ["plugin:@typescript-eslint/recommended", "plugin:import/typescript"], + "extends": [ + "plugin:@typescript-eslint/recommended", + "plugin:import/typescript" + ], "rules": { // There is no "recommended" ruleset for tsdoc yet. "tsdoc/syntax": "warn", @@ -86,7 +105,10 @@ } }, { - "files": ["packages/*/*/spec/**/*.spec.ts", "packages/*/*/spec/fixture/**/*.ts"], + "files": [ + "packages/*/*/spec/**/*.spec.ts", + "packages/*/*/spec/fixture/**/*.ts" + ], "rules": { "global-require": "off", "import/no-dynamic-require": "off", @@ -97,7 +119,11 @@ } }, { - "files": ["packages/api/cli/src/**/*.ts", "packages/external/create-electron-app/src/**/*.ts", "tools/*.{js,ts}"], + "files": [ + "packages/api/cli/src/**/*.ts", + "packages/external/create-electron-app/src/**/*.ts", + "tools/*.{js,ts}" + ], "rules": { "no-process-exit": "off", "n/hashbang": [ @@ -111,13 +137,19 @@ } }, { - "files": ["packages/api/core/spec/**/*.ts", "packages/maker/*/src/Maker*.ts"], + "files": [ + "packages/api/core/spec/**/*.ts", + "packages/maker/*/src/Maker*.ts" + ], "rules": { "@typescript-eslint/no-require-imports": "off" } }, { - "files": ["packages/*/*/spec/fixture/**/*.js", "packages/*/*/spec/fixtures/**/*.js"], + "files": [ + "packages/*/*/spec/fixture/**/*.js", + "packages/*/*/spec/fixtures/**/*.js" + ], "rules": { "n/no-extraneous-require": "off", "n/no-missing-require": "off", diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 3e6cc1fd54..0f4f85db2a 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,7 @@ { - "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "vitest.explorer"] + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "vitest.explorer" + ] } diff --git a/ci/fix-changelog.js b/ci/fix-changelog.js index 39391b3d9f..f5a0fc886b 100644 --- a/ci/fix-changelog.js +++ b/ci/fix-changelog.js @@ -8,15 +8,20 @@ const changelogPath = path.resolve(__dirname, '..', 'CHANGELOG.md'); const changelog = fs.readFileSync(changelogPath, 'utf8'); const fixedChangelog = changelog - .replace(/\(([A-Za-z0-9]{8})\)/g, (match, commitID) => `([${commitID}](https://github.com/electron/forge/commit/${commitID}))`) + .replace( + /\(([A-Za-z0-9]{8})\)/g, + (match, commitID) => + `([${commitID}](https://github.com/electron/forge/commit/${commitID}))`, + ) .replace( /# ([0-9]+\.[0-9]+\.[0-9]+(?:-[a-z]+.[0-9]+)?) /g, - (match, version) => `# [${version}](https://github.com/electron/forge/releases/tag/v${version}) ` + (match, version) => + `# [${version}](https://github.com/electron/forge/releases/tag/v${version}) `, ); fs.writeFileSync( changelogPath, prettier.format(fixedChangelog, { parser: 'markdown', - }) + }), ); diff --git a/nx.json b/nx.json index 0d529cc3cc..0698c4a660 100644 --- a/nx.json +++ b/nx.json @@ -3,7 +3,13 @@ "default": { "runner": "nx/tasks-runners/default", "options": { - "cacheableOperations": ["coverage:base", "test", "test:base", "test:fast", "test:slow"] + "cacheableOperations": [ + "coverage:base", + "test", + "test:base", + "test:fast", + "test:slow" + ] } } }, diff --git a/package.json b/package.json index ad8d64fe67..1eb4a78ab4 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,12 @@ "postbuild": "ts-node tools/test-dist", "docs": "yarn build && npx typedoc", "lerna:publish": "lerna publish --force-publish --conventional-commits --no-changelog --exact", - "lint:js": "prettier --check . && eslint . --cache", + "lint:js": "prettier --check . --experimental-cli && eslint . --cache", "lint:markdown": "electron-markdownlint \"**/*.md\"", "lint:markdown-js": "electron-lint-markdown-standard --root . --ignore-path .markdownlintignore --semi \"**/*.md\"", "lint:markdown-links": "electron-lint-markdown-links --root . --ignore-path .markdownlintignore \"**/*.md\"", "lint": "npm run lint:js && npm run lint:markdown && npm run lint:markdown-js && npm run lint:markdown-links", - "lint:fix": "prettier --write . && eslint --fix . --cache", + "lint:fix": "prettier --write . --experimental-cli && eslint --fix . --cache", "link:prepare": "lerna exec -- node ../../../tools/silent.js yarn link --silent --no-bin-links --link-folder ../../../.links", "link:remove": "lerna exec -- node ../../../tools/silent.js yarn unlink --silent --no-bin-links --link-folder ../../../.links", "test": "xvfb-maybe vitest run --project fast --project slow", @@ -118,7 +118,7 @@ "lint-staged": "^12.1.7", "minimist": "^1.2.6", "msw": "^2.7.0", - "prettier": "^2.4.0", + "prettier": "^3.6.2", "rimraf": "^3.0.1", "syncpack": "^11.2.1", "ts-node": "^10.0.0", @@ -142,9 +142,9 @@ "@electron/fuses": "^1.0.0" }, "lint-staged": { - "*.{html,json,md,yml}": "prettier --write", + "*.{html,json,md,yml}": "prettier --write --experimental-cli", "*.{js,ts}": [ - "prettier --write", + "prettier --write --experimental-cli", "eslint --fix --cache" ], "*.{json,lock}": "syncpack" diff --git a/packages/api/cli/spec/cli.slow.spec.ts b/packages/api/cli/spec/cli.slow.spec.ts index 1d92067eb7..335d62301a 100644 --- a/packages/api/cli/spec/cli.slow.spec.ts +++ b/packages/api/cli/spec/cli.slow.spec.ts @@ -4,7 +4,11 @@ import { spawn } from '@malept/cross-spawn-promise'; import { describe, expect, it } from 'vitest'; function runForgeCLI(...extraArgs: string[]): Promise { - const args = ['ts-node', path.resolve(__dirname, '../src/electron-forge.ts'), ...extraArgs]; + const args = [ + 'ts-node', + path.resolve(__dirname, '../src/electron-forge.ts'), + ...extraArgs, + ]; return spawn('npx', args); } diff --git a/packages/api/cli/spec/util/check-system.spec.ts b/packages/api/cli/spec/util/check-system.spec.ts index d46c06ca20..dd839347b1 100644 --- a/packages/api/cli/spec/util/check-system.spec.ts +++ b/packages/api/cli/spec/util/check-system.spec.ts @@ -1,4 +1,7 @@ -import { resolvePackageManager, spawnPackageManager } from '@electron-forge/core-utils'; +import { + resolvePackageManager, + spawnPackageManager, +} from '@electron-forge/core-utils'; import { describe, expect, it, vi } from 'vitest'; import { checkPackageManager } from '../../src/util/check-system'; @@ -80,30 +83,33 @@ describe('checkPackageManager', () => { } }); await expect(checkPackageManager()).rejects.toThrow( - 'When using pnpm, `node-linker` must be set to "hoisted" (or a custom `hoist-pattern` or `public-hoist-pattern` must be defined). Run `pnpm config set node-linker hoisted` to set this config value, or add it to your project\'s `.npmrc` file.' + 'When using pnpm, `node-linker` must be set to "hoisted" (or a custom `hoist-pattern` or `public-hoist-pattern` must be defined). Run `pnpm config set node-linker hoisted` to set this config value, or add it to your project\'s `.npmrc` file.', ); }); - it.each(['hoist-pattern', 'public-hoist-pattern'])('should pass without validation if user has set %s in their pnpm config', async (cfg) => { - vi.mocked(resolvePackageManager).mockResolvedValue({ - executable: 'pnpm', - install: 'add', - dev: '--dev', - exact: '--exact', - }); - vi.mocked(spawnPackageManager).mockImplementation((_pm, args) => { - if (args?.join(' ') === 'config get node-linker') { - return Promise.resolve('isolated'); - } else if (args?.join(' ') === `config get ${cfg}`) { - return Promise.resolve('["*eslint*","*babel*"]'); - } else if (args?.join(' ') === '--version') { - return Promise.resolve('10.0.0'); - } else { - return Promise.resolve('undefined'); - } - }); - await expect(checkPackageManager()).resolves.not.toThrow(); - }); + it.each(['hoist-pattern', 'public-hoist-pattern'])( + 'should pass without validation if user has set %s in their pnpm config', + async (cfg) => { + vi.mocked(resolvePackageManager).mockResolvedValue({ + executable: 'pnpm', + install: 'add', + dev: '--dev', + exact: '--exact', + }); + vi.mocked(spawnPackageManager).mockImplementation((_pm, args) => { + if (args?.join(' ') === 'config get node-linker') { + return Promise.resolve('isolated'); + } else if (args?.join(' ') === `config get ${cfg}`) { + return Promise.resolve('["*eslint*","*babel*"]'); + } else if (args?.join(' ') === '--version') { + return Promise.resolve('10.0.0'); + } else { + return Promise.resolve('undefined'); + } + }); + await expect(checkPackageManager()).resolves.not.toThrow(); + }, + ); // resolvePackageManager optionally returns a `version` if `npm_config_user_agent` was used to // resolve the package manager being used. diff --git a/packages/api/cli/src/electron-forge-import.ts b/packages/api/cli/src/electron-forge-import.ts index 6b625474e1..fbde84eedf 100644 --- a/packages/api/cli/src/electron-forge-import.ts +++ b/packages/api/cli/src/electron-forge-import.ts @@ -9,8 +9,15 @@ import { resolveWorkingDir } from './util/resolve-working-dir'; program .version(packageJSON.version, '-V, --version', 'Output the current version.') .helpOption('-h, --help', 'Output usage information.') - .argument('[dir]', 'Directory of the project to import. (default: current directory)') - .option('--skip-git', 'Skip initializing a git repository in the imported project.', false) + .argument( + '[dir]', + 'Directory of the project to import. (default: current directory)', + ) + .option( + '--skip-git', + 'Skip initializing a git repository in the imported project.', + false, + ) .action(async (dir: string) => { const workingDir = resolveWorkingDir(dir, false); diff --git a/packages/api/cli/src/electron-forge-init.ts b/packages/api/cli/src/electron-forge-init.ts index abd7d18b83..8db81d0b5a 100644 --- a/packages/api/cli/src/electron-forge-init.ts +++ b/packages/api/cli/src/electron-forge-init.ts @@ -9,11 +9,22 @@ import { resolveWorkingDir } from './util/resolve-working-dir'; program .version(packageJSON.version, '-V, --version', 'Output the current version.') .helpOption('-h, --help', 'Output usage information.') - .argument('[dir]', 'Directory to initialize the project in. (default: current directory)') + .argument( + '[dir]', + 'Directory to initialize the project in. (default: current directory)', + ) .option('-t, --template [name]', 'Name of the Forge template to use.', 'base') - .option('-c, --copy-ci-files', 'Whether to copy the templated CI files.', false) + .option( + '-c, --copy-ci-files', + 'Whether to copy the templated CI files.', + false, + ) .option('-f, --force', 'Whether to overwrite an existing directory.', false) - .option('--skip-git', 'Skip initializing a git repository in the initialized project.', false) + .option( + '--skip-git', + 'Skip initializing a git repository in the initialized project.', + false, + ) .action(async (dir) => { const workingDir = resolveWorkingDir(dir, false); diff --git a/packages/api/cli/src/electron-forge-make.ts b/packages/api/cli/src/electron-forge-make.ts index 34a24d5bb7..2da4ae0bc3 100644 --- a/packages/api/cli/src/electron-forge-make.ts +++ b/packages/api/cli/src/electron-forge-make.ts @@ -11,13 +11,30 @@ import { resolveWorkingDir } from './util/resolve-working-dir'; export async function getMakeOptions(): Promise { let workingDir: string; program - .version(packageJSON.version, '-V, --version', 'Output the current version.') + .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('--skip-package', `Skip packaging the Electron application, and use the output from a previous ${chalk.green('package')} run instead.`) + .argument( + '[dir]', + 'Directory to run the command in. (default: current directory)', + ) + .option( + '--skip-package', + `Skip packaging the Electron application, and use the output from a previous ${chalk.green('package')} run instead.`, + ) .option('-a, --arch [arch]', 'Target build architecture.', process.arch) - .option('-p, --platform [platform]', 'Target build platform.', process.platform) - .option('--targets [targets]', `Override your ${chalk.green('make')} targets for this run.`) + .option( + '-p, --platform [platform]', + 'Target build platform.', + process.platform, + ) + .option( + '--targets [targets]', + `Override your ${chalk.green('make')} targets for this run.`, + ) .allowUnknownOption(true) .action((dir) => { workingDir = resolveWorkingDir(dir, false); diff --git a/packages/api/cli/src/electron-forge-package.ts b/packages/api/cli/src/electron-forge-package.ts index a4b9d5e003..8e6e273566 100644 --- a/packages/api/cli/src/electron-forge-package.ts +++ b/packages/api/cli/src/electron-forge-package.ts @@ -10,7 +10,10 @@ import { resolveWorkingDir } from './util/resolve-working-dir'; 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)') + .argument( + '[dir]', + 'Directory to run the command in. (default: current directory)', + ) .option('-a, --arch [arch]', 'Target build architecture') .option('-p, --platform [platform]', 'Target build platform') .action(async (dir) => { diff --git a/packages/api/cli/src/electron-forge-publish.ts b/packages/api/cli/src/electron-forge-publish.ts index fddbc0fd10..e331c7becd 100644 --- a/packages/api/cli/src/electron-forge-publish.ts +++ b/packages/api/cli/src/electron-forge-publish.ts @@ -12,9 +12,18 @@ import { resolveWorkingDir } from './util/resolve-working-dir'; 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 ${chalk.green('make')} command and save publish metadata without uploading anything.`) + .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 ${chalk.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) => { diff --git a/packages/api/cli/src/electron-forge-start.ts b/packages/api/cli/src/electron-forge-start.ts index 7c371fd979..1af1cb3e71 100644 --- a/packages/api/cli/src/electron-forge-start.ts +++ b/packages/api/cli/src/electron-forge-start.ts @@ -19,15 +19,31 @@ import { resolveWorkingDir } from './util/resolve-working-dir'; let dir; program - .version(packageJSON.version, '-V, --version', 'Output the current version.') + .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('-p, --app-path ', 'Path to the Electron app to launch. (default: current directory)') + .argument( + '[dir]', + 'Directory to run the command in. (default: current directory)', + ) + .option( + '-p, --app-path ', + 'Path to the Electron app to launch. (default: current directory)', + ) .option('-l, --enable-logging', 'Enable internal Electron logging.') .option('-n, --run-as-node', 'Run the Electron app as a Node.JS script.') .addOption(new Option('--vscode').hideHelp()) // Used to enable arg transformation for debugging Electron through VSCode. Hidden from users. - .option('-i, --inspect-electron', 'Run Electron in inspect mode to allow debugging the main process.') - .option('--inspect-brk-electron', 'Run Electron in inspect-brk mode to allow debugging the main process.') + .option( + '-i, --inspect-electron', + 'Run Electron in inspect mode to allow debugging the main process.', + ) + .option( + '--inspect-brk-electron', + 'Run Electron in inspect-brk mode to allow debugging the main process.', + ) .addHelpText( 'after', ` @@ -35,7 +51,7 @@ import { resolveWorkingDir } from './util/resolve-working-dir'; $ 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.` + ...will pass the arguments "-d -f foo.txt" to the Electron app.`, ) .action((targetDir: string) => { dir = resolveWorkingDir(targetDir); @@ -55,7 +71,9 @@ import { resolveWorkingDir } from './util/resolve-working-dir'; if (options.vscode && appArgs) { // Args are in the format ~arg~ so we need to strip the "~" - appArgs = appArgs.map((arg) => arg.substr(1, arg.length - 2)).filter((arg) => arg.length > 0); + appArgs = appArgs + .map((arg) => arg.substr(1, arg.length - 2)) + .filter((arg) => arg.length > 0); } if (options.appPath) opts.appPath = options.appPath; diff --git a/packages/api/cli/src/electron-forge.ts b/packages/api/cli/src/electron-forge.ts index 90dcda7896..89a0f1d9e1 100755 --- a/packages/api/cli/src/electron-forge.ts +++ b/packages/api/cli/src/electron-forge.ts @@ -14,7 +14,7 @@ import { checkSystem, SystemCheckContext } from './util/check-system'; if (!semver.satisfies(process.versions.node, packageJSON.engines.node)) { console.error( logSymbols.error, - `You are running Node.js version ${chalk.red(process.versions.node)}, but Electron Forge requires Node.js ${chalk.red(packageJSON.engines.node)}. \n` + `You are running Node.js version ${chalk.red(process.versions.node)}, but Electron Forge requires Node.js ${chalk.red(packageJSON.engines.node)}. \n`, ); process.exit(1); } @@ -27,9 +27,15 @@ program .helpOption('-h, --help', 'Output usage information.') .command('init', 'Initialize a new Electron application.') .command('import', 'Import an existing Electron project to Forge.') - .command('start', 'Start the current Electron application in development mode.') + .command( + 'start', + 'Start the current Electron application in development mode.', + ) .command('package', 'Package the current Electron application.') - .command('make', 'Generate distributables for the current Electron application.') + .command( + 'make', + 'Generate distributables for the current Electron application.', + ) .command('publish', 'Publish the current Electron application.') .passThroughOptions(true) .hook('preSubcommand', async (_command, subcommand) => { @@ -48,8 +54,9 @@ program { concurrent: false, exitOnError: true, - fallbackRendererCondition: Boolean(process.env.DEBUG) || Boolean(process.env.CI), - } + fallbackRendererCondition: + Boolean(process.env.DEBUG) || Boolean(process.env.CI), + }, ); try { diff --git a/packages/api/cli/src/util/check-system.ts b/packages/api/cli/src/util/check-system.ts index a244d0f63e..c5ba467a65 100644 --- a/packages/api/cli/src/util/check-system.ts +++ b/packages/api/cli/src/util/check-system.ts @@ -2,7 +2,12 @@ import { exec } from 'node:child_process'; import os from 'node:os'; import path from 'node:path'; -import { PACKAGE_MANAGERS, resolvePackageManager, spawnPackageManager, SupportedPackageManager } from '@electron-forge/core-utils'; +import { + PACKAGE_MANAGERS, + resolvePackageManager, + spawnPackageManager, + SupportedPackageManager, +} from '@electron-forge/core-utils'; import { ForgeListrTask } from '@electron-forge/shared-types'; import debug from 'debug'; import fs from 'fs-extra'; @@ -12,7 +17,11 @@ const d = debug('electron-forge:check-system'); async function getGitVersion(): Promise { return new Promise((resolve) => { - exec('git --version', (err, output) => (err ? resolve(null) : resolve(output.toString().trim().split(' ').reverse()[0]))); + exec('git --version', (err, output) => + err + ? resolve(null) + : resolve(output.toString().trim().split(' ').reverse()[0]), + ); }); } @@ -26,29 +35,44 @@ async function getGitVersion(): Promise { */ async function checkPnpmConfig() { const { pnpm } = PACKAGE_MANAGERS; - const hoistPattern = await spawnPackageManager(pnpm, ['config', 'get', 'hoist-pattern']); - const publicHoistPattern = await spawnPackageManager(pnpm, ['config', 'get', 'public-hoist-pattern']); + const hoistPattern = await spawnPackageManager(pnpm, [ + 'config', + 'get', + 'hoist-pattern', + ]); + const publicHoistPattern = await spawnPackageManager(pnpm, [ + 'config', + 'get', + 'public-hoist-pattern', + ]); if (hoistPattern !== 'undefined' || publicHoistPattern !== 'undefined') { d( `Custom hoist pattern detected ${JSON.stringify({ hoistPattern, publicHoistPattern, - })}, assuming that the user has configured pnpm to package dependencies.` + })}, assuming that the user has configured pnpm to package dependencies.`, ); return; } - const nodeLinker = await spawnPackageManager(pnpm, ['config', 'get', 'node-linker']); + const nodeLinker = await spawnPackageManager(pnpm, [ + 'config', + 'get', + 'node-linker', + ]); if (nodeLinker !== 'hoisted') { throw new Error( - 'When using pnpm, `node-linker` must be set to "hoisted" (or a custom `hoist-pattern` or `public-hoist-pattern` must be defined). Run `pnpm config set node-linker hoisted` to set this config value, or add it to your project\'s `.npmrc` file.' + 'When using pnpm, `node-linker` must be set to "hoisted" (or a custom `hoist-pattern` or `public-hoist-pattern` must be defined). Run `pnpm config set node-linker hoisted` to set this config value, or add it to your project\'s `.npmrc` file.', ); } } // TODO(erickzhao): Drop antiquated versions of npm for Forge v8 -const ALLOWLISTED_VERSIONS: Record> = { +const ALLOWLISTED_VERSIONS: Record< + SupportedPackageManager, + Record +> = { npm: { all: '^3.0.0 || ^4.0.0 || ~5.1.0 || ~5.2.0 || >= 5.4.2', darwin: '>= 5.4.0', @@ -67,13 +91,19 @@ export async function checkPackageManager() { const version = pm.version ?? (await spawnPackageManager(pm, ['--version'])); const versionString = version.toString().trim(); - const range = ALLOWLISTED_VERSIONS[pm.executable][process.platform] ?? ALLOWLISTED_VERSIONS[pm.executable].all; + const range = + ALLOWLISTED_VERSIONS[pm.executable][process.platform] ?? + ALLOWLISTED_VERSIONS[pm.executable].all; if (!semver.valid(version)) { d(`Invalid semver-string while checking version: ${version}`); - throw new Error(`Could not check ${pm.executable} version "${version}", assuming incompatible`); + throw new Error( + `Could not check ${pm.executable} version "${version}", assuming incompatible`, + ); } if (!semver.satisfies(version, range)) { - throw new Error(`Incompatible version of ${pm.executable} detected: "${version}" must be in range ${range}`); + throw new Error( + `Incompatible version of ${pm.executable} detected: "${version}" must be in range ${range}`, + ); } if (pm.executable === 'pnpm') { @@ -92,7 +122,10 @@ export async function checkPackageManager() { * * This is specifically not documented or everyone would make it. */ -const SKIP_SYSTEM_CHECK = path.resolve(os.homedir(), '.skip-forge-system-check'); +const SKIP_SYSTEM_CHECK = path.resolve( + os.homedir(), + '.skip-forge-system-check', +); export type SystemCheckContext = { command: string; @@ -101,7 +134,9 @@ export type SystemCheckContext = { packageManager: boolean; }; -export async function checkSystem(callerTask: ForgeListrTask) { +export async function checkSystem( + callerTask: ForgeListrTask, +) { if (!(await fs.pathExists(SKIP_SYSTEM_CHECK))) { d('checking system, create ~/.skip-forge-system-check to stop doing this'); return callerTask.newListr( @@ -109,7 +144,8 @@ export async function checkSystem(callerTask: ForgeListrTask { title: 'Checking git exists', // We only call the `initGit` helper in the `init` and `import` commands - enabled: (ctx): boolean => (ctx.command === 'init' || ctx.command === 'import') && ctx.git, + enabled: (ctx): boolean => + (ctx.command === 'init' || ctx.command === 'import') && ctx.git, task: async (_, task) => { const gitVersion = await getGitVersion(); if (gitVersion) { @@ -133,7 +169,7 @@ export async function checkSystem(callerTask: ForgeListrTask rendererOptions: { collapseSubtasks: true, }, - } + }, ); } d('skipping system check'); diff --git a/packages/api/cli/src/util/resolve-working-dir.ts b/packages/api/cli/src/util/resolve-working-dir.ts index e279b7a6a2..a622543d15 100644 --- a/packages/api/cli/src/util/resolve-working-dir.ts +++ b/packages/api/cli/src/util/resolve-working-dir.ts @@ -13,7 +13,9 @@ export function resolveWorkingDir(dir: string, checkExisting = true): string { return process.cwd(); } - const resolved = path.isAbsolute(dir) ? dir : path.resolve(process.cwd(), dir); + const resolved = path.isAbsolute(dir) + ? dir + : path.resolve(process.cwd(), dir); if (checkExisting && !fs.existsSync(resolved)) { return process.cwd(); diff --git a/packages/api/cli/src/util/terminate.ts b/packages/api/cli/src/util/terminate.ts index 142f6bc192..fa5caa527c 100644 --- a/packages/api/cli/src/util/terminate.ts +++ b/packages/api/cli/src/util/terminate.ts @@ -4,19 +4,24 @@ function redConsoleError(msg: string) { console.error(chalk.red(msg)); } -process.on('unhandledRejection', (reason: string, promise: Promise) => { - redConsoleError('\nAn unhandled rejection has occurred inside Forge:'); - redConsoleError(reason.toString().trim()); - promise.catch((err: Error) => { - if ('stack' in err) { - const usefulStack = err.stack; - if (usefulStack?.startsWith(reason.toString().trim())) { - redConsoleError(usefulStack.substring(reason.toString().trim().length + 1).trim()); +process.on( + 'unhandledRejection', + (reason: string, promise: Promise) => { + redConsoleError('\nAn unhandled rejection has occurred inside Forge:'); + redConsoleError(reason.toString().trim()); + promise.catch((err: Error) => { + if ('stack' in err) { + const usefulStack = err.stack; + if (usefulStack?.startsWith(reason.toString().trim())) { + redConsoleError( + usefulStack.substring(reason.toString().trim().length + 1).trim(), + ); + } } - } - process.exit(1); - }); -}); + process.exit(1); + }); + }, +); process.on('uncaughtException', (err) => { if (err && err.message && err.stack) { diff --git a/packages/api/core/spec/fast/find-template.spec.ts b/packages/api/core/spec/fast/find-template.spec.ts index 7e03091355..a1d0eb8921 100644 --- a/packages/api/core/spec/fast/find-template.spec.ts +++ b/packages/api/core/spec/fast/find-template.spec.ts @@ -16,18 +16,32 @@ describe('findTemplate', () => { */ describe('local modules', () => { it('should find an @electron-forge/template based on partial name', async () => { - await expect(findTemplate('fixture')).resolves.toEqual(expect.objectContaining({ name: '@electron-forge/template-fixture' })); + await expect(findTemplate('fixture')).resolves.toEqual( + expect.objectContaining({ name: '@electron-forge/template-fixture' }), + ); }); it('should find an @electron-forge/template based on full name', async () => { - await expect(findTemplate('@electron-forge/template-fixture')).resolves.toEqual(expect.objectContaining({ name: '@electron-forge/template-fixture' })); + await expect( + findTemplate('@electron-forge/template-fixture'), + ).resolves.toEqual( + expect.objectContaining({ name: '@electron-forge/template-fixture' }), + ); }); it('should find an electron-forge-template based on partial name', async () => { - await expect(findTemplate('fixture-two')).resolves.toEqual(expect.objectContaining({ name: 'electron-forge-template-fixture-two' })); + await expect(findTemplate('fixture-two')).resolves.toEqual( + expect.objectContaining({ + name: 'electron-forge-template-fixture-two', + }), + ); }); it('should find an @electron-forge-template based on full name', async () => { - await expect(findTemplate('electron-forge-template-fixture-two')).resolves.toEqual( - expect.objectContaining({ name: 'electron-forge-template-fixture-two' }) + await expect( + findTemplate('electron-forge-template-fixture-two'), + ).resolves.toEqual( + expect.objectContaining({ + name: 'electron-forge-template-fixture-two', + }), ); }); }); @@ -42,22 +56,36 @@ describe('findTemplate', () => { vi.spyOn(globalDirs, 'npm', 'get').mockReturnValue({ binaries: '', prefix: '', - packages: path.resolve(__dirname, '..', 'fixture', 'global-stub', 'node_modules'), + packages: path.resolve( + __dirname, + '..', + 'fixture', + 'global-stub', + 'node_modules', + ), }); }); it('should find an @electron-forge/template based on name', async () => { await expect(findTemplate('global')).resolves.toEqual( - expect.objectContaining({ template: { name: 'electron-forge-template-fixture-global' }, type: 'global' }) + expect.objectContaining({ + template: { name: 'electron-forge-template-fixture-global' }, + type: 'global', + }), ); }); it('should find an electron-forge-template based on name', async () => { await expect(findTemplate('global-two')).resolves.toEqual( - expect.objectContaining({ template: { name: 'electron-forge-template-fixture-global' }, type: 'global' }) + expect.objectContaining({ + template: { name: 'electron-forge-template-fixture-global' }, + type: 'global', + }), ); }); }); it('should error if there are no valid templates', async () => { - await expect(findTemplate('non-existent-template')).rejects.toThrowError('Failed to locate custom template: "non-existent-template".'); + await expect(findTemplate('non-existent-template')).rejects.toThrowError( + 'Failed to locate custom template: "non-existent-template".', + ); }); }); diff --git a/packages/api/core/spec/fast/init-git.spec.ts b/packages/api/core/spec/fast/init-git.spec.ts index ffe390a2ce..a20c3ec0e8 100644 --- a/packages/api/core/spec/fast/init-git.spec.ts +++ b/packages/api/core/spec/fast/init-git.spec.ts @@ -25,7 +25,10 @@ describe('init-git', () => { it('creates Git repository when run inside non-Git directory', async () => { await initGit(dir); const gitDir = path.join(dir, '.git'); - expect(fs.existsSync(gitDir), 'the .git directory inside the folder').toEqual(true); + expect( + fs.existsSync(gitDir), + 'the .git directory inside the folder', + ).toEqual(true); }); it('skips when run at root of Git repository', async () => { @@ -63,6 +66,9 @@ describe('init-git', () => { const after = statAfter.mtimeMs; expect(after, 'the config file in the repository').toEqual(before); - expect(fs.existsSync(innerGitDir), 'a nested .git directory inside the repository').toEqual(false); + expect( + fs.existsSync(innerGitDir), + 'a nested .git directory inside the repository', + ).toEqual(false); }); }); diff --git a/packages/api/core/spec/fast/make.spec.ts b/packages/api/core/spec/fast/make.spec.ts index 86dd193346..a3934737b2 100644 --- a/packages/api/core/spec/fast/make.spec.ts +++ b/packages/api/core/spec/fast/make.spec.ts @@ -25,7 +25,9 @@ describe('make', () => { skipPackage: true, }); expect(result).toHaveLength(1); - expect(result[0].artifacts).toEqual([expect.stringContaining('@scope-package-linux-x64-1.0.0.zip')]); + expect(result[0].artifacts).toEqual([ + expect.stringContaining('@scope-package-linux-x64-1.0.0.zip'), + ]); }); it('can override targets', async () => { @@ -47,8 +49,10 @@ describe('make', () => { dir: path.join(fixtureDir, 'maker-name-wrong-type'), platform: 'linux', skipPackage: true, - }) - ).rejects.toThrowError(/^The following maker config has a maker name that is not a string:/); + }), + ).rejects.toThrowError( + /^The following maker config has a maker name that is not a string:/, + ); }); it('throws an error if the name is missing', async () => { @@ -58,8 +62,10 @@ describe('make', () => { dir: path.join(fixtureDir, 'maker-sans-name'), platform: 'linux', skipPackage: true, - }) - ).rejects.toThrowError(/^The following maker config is missing a maker name:/); + }), + ).rejects.toThrowError( + /^The following maker config is missing a maker name:/, + ); }); it('can skip makers via config', async () => { @@ -69,8 +75,10 @@ describe('make', () => { dir: path.join(fixtureDir, 'app-with-maker-disable'), platform: 'linux', skipPackage: true, - }) - ).rejects.toThrowError(/Could not find any make targets configured for the "linux" platform./); + }), + ).rejects.toThrowError( + /Could not find any make targets configured for the "linux" platform./, + ); }); it('throws if maker cannot be resolved', async () => { @@ -81,6 +89,8 @@ describe('make', () => { skipPackage: true, }; - await expect(make(opts)).rejects.toThrowError("Could not find module with name '@electron-forge/non-existent-forge-maker'"); + await expect(make(opts)).rejects.toThrowError( + "Could not find module with name '@electron-forge/non-existent-forge-maker'", + ); }); }); diff --git a/packages/api/core/spec/fast/publish.spec.ts b/packages/api/core/spec/fast/publish.spec.ts index 37a5898d8e..860cccd13d 100644 --- a/packages/api/core/spec/fast/publish.spec.ts +++ b/packages/api/core/spec/fast/publish.spec.ts @@ -2,7 +2,10 @@ import fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; -import { ForgeMakeResult, ResolvedForgeConfig } from '@electron-forge/shared-types'; +import { + ForgeMakeResult, + ResolvedForgeConfig, +} from '@electron-forge/shared-types'; import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { listrMake } from '../../src/api/make'; @@ -61,7 +64,9 @@ describe('publish', () => { publishers: [new MockPublisher()], }; - vi.mocked(findConfig).mockResolvedValue(config as unknown as ResolvedForgeConfig); + vi.mocked(findConfig).mockResolvedValue( + config as unknown as ResolvedForgeConfig, + ); vi.mocked(listrMake).mockImplementationOnce((_childTrace, _opts, cb) => { cb!([ { @@ -100,7 +105,9 @@ describe('publish', () => { MockPublisher.prototype.publish = mockPublish; MockPublisher.prototype.__isElectronForgePublisher = true; - vi.mocked(findConfig).mockResolvedValue({} as unknown as ResolvedForgeConfig); + vi.mocked(findConfig).mockResolvedValue( + {} as unknown as ResolvedForgeConfig, + ); await publish({ dir: __dirname, @@ -176,7 +183,10 @@ describe('publish', () => { expect(dryRunFolder).toHaveLength(1); folder.push(dryRunFolder.pop() as string); const hashFolder = await fs.readdir(path.join(...folder)); - expect(hashFolder).toEqual([expect.stringContaining('.forge.publish'), expect.stringContaining('.forge.publish')]); + expect(hashFolder).toEqual([ + expect.stringContaining('.forge.publish'), + expect.stringContaining('.forge.publish'), + ]); for (const file of hashFolder) { const hashFile = await fs.readFile(path.join(...folder, file), 'utf8'); diff --git a/packages/api/core/spec/fast/start.spec.ts b/packages/api/core/spec/fast/start.spec.ts index f16047646b..50286067b9 100644 --- a/packages/api/core/spec/fast/start.spec.ts +++ b/packages/api/core/spec/fast/start.spec.ts @@ -77,7 +77,11 @@ describe('start', () => { interactive: false, }); expect(vi.mocked(spawn)).toHaveBeenCalledOnce(); - expect(vi.mocked(spawn)).toHaveBeenCalledWith('electron', expect.anything(), expect.anything()); + expect(vi.mocked(spawn)).toHaveBeenCalledWith( + 'electron', + expect.anything(), + expect.anything(), + ); }); it('allows plugin to override the start command with its own child process', async () => { @@ -107,7 +111,9 @@ describe('start', () => { interactive: false, }); expect(vi.mocked(spawn)).toHaveBeenCalledOnce(); - expect(vi.mocked(spawn).mock.calls[0]).toEqual(expect.arrayContaining(['electron', expect.arrayContaining(['.'])])); + expect(vi.mocked(spawn).mock.calls[0]).toEqual( + expect.arrayContaining(['electron', expect.arrayContaining(['.'])]), + ); }); it('should pass electron the app path if specified', async () => { @@ -117,7 +123,12 @@ describe('start', () => { appPath: './path/to/app.js', }); expect(vi.mocked(spawn)).toHaveBeenCalledOnce(); - expect(vi.mocked(spawn).mock.calls[0]).toEqual(expect.arrayContaining(['electron', expect.arrayContaining(['./path/to/app.js'])])); + expect(vi.mocked(spawn).mock.calls[0]).toEqual( + expect.arrayContaining([ + 'electron', + expect.arrayContaining(['./path/to/app.js']), + ]), + ); }); it('should enable electron logging if enableLogging=true', async () => { @@ -127,7 +138,10 @@ describe('start', () => { enableLogging: true, }); expect(vi.mocked(spawn)).toHaveBeenCalledOnce(); - expect(vi.mocked(spawn).mock.calls[0][2]).toHaveProperty('env', expect.objectContaining({ ELECTRON_ENABLE_LOGGING: 'true' })); + expect(vi.mocked(spawn).mock.calls[0][2]).toHaveProperty( + 'env', + expect.objectContaining({ ELECTRON_ENABLE_LOGGING: 'true' }), + ); }); it('should enable RUN_AS_NODE if runAsNode=true', async () => { @@ -137,7 +151,10 @@ describe('start', () => { runAsNode: true, }); expect(vi.mocked(spawn)).toHaveBeenCalledOnce(); - expect(vi.mocked(spawn).mock.calls[0][2]).toHaveProperty('env', expect.objectContaining({ ELECTRON_RUN_AS_NODE: 'true' })); + expect(vi.mocked(spawn).mock.calls[0][2]).toHaveProperty( + 'env', + expect.objectContaining({ ELECTRON_RUN_AS_NODE: 'true' }), + ); }); it('should disable RUN_AS_NODE if runAsNode=false', async () => { @@ -147,7 +164,9 @@ describe('start', () => { runAsNode: false, }); expect(vi.mocked(spawn)).toHaveBeenCalledOnce(); - expect(vi.mocked(spawn).mock.calls[0][2].env).not.toHaveProperty('ELECTRON_RUN_AS_NODE'); + expect(vi.mocked(spawn).mock.calls[0][2].env).not.toHaveProperty( + 'ELECTRON_RUN_AS_NODE', + ); }); it('should pass all args through to the spawned Electron instance', async () => { @@ -170,7 +189,11 @@ describe('start', () => { inspect: true, }); expect(vi.mocked(spawn)).toHaveBeenCalledOnce(); - expect(vi.mocked(spawn).mock.calls[0][1]).toEqual(['.', '--inspect', ...args]); + expect(vi.mocked(spawn).mock.calls[0][1]).toEqual([ + '.', + '--inspect', + ...args, + ]); }); it('should pass --inspect-brk at the start of the args if inspectBrk is set', async () => { @@ -182,7 +205,11 @@ describe('start', () => { inspectBrk: true, }); expect(vi.mocked(spawn)).toHaveBeenCalledOnce(); - expect(vi.mocked(spawn).mock.calls[0][1]).toEqual(['.', '--inspect-brk', ...args]); + expect(vi.mocked(spawn).mock.calls[0][1]).toEqual([ + '.', + '--inspect-brk', + ...args, + ]); }); it('should resolve with a handle to the spawned instance', async () => { @@ -192,7 +219,7 @@ describe('start', () => { start({ dir: __dirname, interactive: false, - }) + }), ).resolves.toEqual(child); }); @@ -202,7 +229,7 @@ describe('start', () => { start({ dir: __dirname, interactive: false, - }) + }), ).rejects.toThrowError('Failed to locate startable Electron application'); }); @@ -212,13 +239,17 @@ describe('start', () => { start({ dir: __dirname, interactive: false, - }) + }), ).rejects.toThrowError("Please set your application's 'version' in"); }); // TODO(erickzhao): improve test coverage - it.todo('allows plugin to override the start command with a custom spawn string'); - it.todo('allows plugin to override the start command with a custom spawn string with args'); + it.todo( + 'allows plugin to override the start command with a custom spawn string', + ); + it.todo( + 'allows plugin to override the start command with a custom spawn string with args', + ); it.todo('runs the preStart hook'); it.todo('runs the generateAssets hook'); }); diff --git a/packages/api/core/spec/fast/util/electron-executable.spec.ts b/packages/api/core/spec/fast/util/electron-executable.spec.ts index fffb61be05..8cc01070b1 100644 --- a/packages/api/core/spec/fast/util/electron-executable.spec.ts +++ b/packages/api/core/spec/fast/util/electron-executable.spec.ts @@ -4,7 +4,13 @@ import { describe, expect, it } from 'vitest'; import locateElectronExecutable from '../../../src/util/electron-executable'; -const fixtureDir = path.resolve(__dirname, '..', '..', 'fixture', 'electron-executable'); +const fixtureDir = path.resolve( + __dirname, + '..', + '..', + 'fixture', + 'electron-executable', +); describe('locateElectronExecutable', () => { it('returns the correct path to electron', async () => { @@ -13,6 +19,8 @@ describe('locateElectronExecutable', () => { devDependencies: { electron: '^100.0.0' }, }; - await expect(locateElectronExecutable(appFixture, packageJSON)).resolves.toEqual('execPath'); + await expect( + locateElectronExecutable(appFixture, packageJSON), + ).resolves.toEqual('execPath'); }); }); diff --git a/packages/api/core/spec/fast/util/forge-config.spec.ts b/packages/api/core/spec/fast/util/forge-config.spec.ts index d4ff846af0..1e82889105 100644 --- a/packages/api/core/spec/fast/util/forge-config.spec.ts +++ b/packages/api/core/spec/fast/util/forge-config.spec.ts @@ -20,20 +20,30 @@ const DEFAULTS = { describe('findConfig', () => { it('falls back to default if no config exists', async () => { - const fixturePath = path.resolve(__dirname, '../../fixture/no_forge_config'); + const fixturePath = path.resolve( + __dirname, + '../../fixture/no_forge_config', + ); const config = await findConfig(fixturePath); - expect(config).toEqual({ ...DEFAULTS, pluginInterface: expect.objectContaining({}) }); + expect(config).toEqual({ + ...DEFAULTS, + pluginInterface: expect.objectContaining({}), + }); }); it('sets a pluginInterface property', async () => { const fixturePath = path.resolve(__dirname, '../../fixture/dummy_app'); const config = await findConfig(fixturePath); - expect(config).toEqual(expect.objectContaining({ pluginInterface: expect.objectContaining({}) })); + expect(config).toEqual( + expect.objectContaining({ pluginInterface: expect.objectContaining({}) }), + ); }); it('should resolve undefined from fromBuildIdentifier if no value is provided', async () => { type ResolveUndefConfig = ResolvedForgeConfig & { topLevelUndef?: string }; - const conf = (await findConfig(path.resolve(__dirname, '../../fixture/dummy_js_conf'))) as ResolveUndefConfig; + const conf = (await findConfig( + path.resolve(__dirname, '../../fixture/dummy_js_conf'), + )) as ResolveUndefConfig; expect(conf.topLevelUndef).toEqual(undefined); }); @@ -45,13 +55,17 @@ describe('findConfig', () => { }; }; }; - const conf = (await findConfig(path.resolve(__dirname, '../../fixture/dummy_js_conf'))) as NestedConfig; + const conf = (await findConfig( + path.resolve(__dirname, '../../fixture/dummy_js_conf'), + )) as NestedConfig; expect(Array.isArray(conf.sub.prop.inArray)).toEqual(true); }); it('should leave regexps intact', async () => { type RegExpConfig = ResolvedForgeConfig & { regexp: RegExp }; - const conf = (await findConfig(path.resolve(__dirname, '../../fixture/dummy_js_conf'))) as RegExpConfig; + const conf = (await findConfig( + path.resolve(__dirname, '../../fixture/dummy_js_conf'), + )) as RegExpConfig; expect(conf.regexp).toBeInstanceOf(RegExp); expect(conf.regexp.test('foo')).toEqual(true); expect(conf.regexp.test('bar')).toEqual(false); @@ -59,14 +73,23 @@ describe('findConfig', () => { describe('from package.json', () => { it('throws if the "config.forge" property is not an object or requirable path', async () => { - const fixturePath = path.resolve(__dirname, '../../fixture/bad_forge_config'); - const err = 'Expected packageJSON.config.forge to be an object or point to a requirable JS file'; + const fixturePath = path.resolve( + __dirname, + '../../fixture/bad_forge_config', + ); + const err = + 'Expected packageJSON.config.forge to be an object or point to a requirable JS file'; await expect(findConfig(fixturePath)).rejects.toThrow(err); }); it('throws if the "config.forge" property is not parseable', async () => { - const spy = vi.spyOn(console, 'error').mockImplementation(() => undefined); - const fixturePath = path.resolve(__dirname, '../../fixture/bad_external_forge_config'); + const spy = vi + .spyOn(console, 'error') + .mockImplementation(() => undefined); + const fixturePath = path.resolve( + __dirname, + '../../fixture/bad_external_forge_config', + ); const err = /Unexpected token/; await expect(findConfig(fixturePath)).rejects.toThrow(err); spy.mockRestore(); @@ -88,7 +111,10 @@ describe('findConfig', () => { describe('from forge.config.js', () => { it('resolves when "config.forge" points to a JS file', async () => { - const fixturePath = path.resolve(__dirname, '../../fixture/dummy_js_conf'); + const fixturePath = path.resolve( + __dirname, + '../../fixture/dummy_js_conf', + ); const config = await findConfig(fixturePath); expect(config).toEqual( expect.objectContaining({ @@ -97,18 +123,24 @@ describe('findConfig', () => { packagerConfig: { foo: 'bar', baz: {} }, s3: {}, electronReleaseServer: {}, - }) + }), ); }); it('falls back to forge.config.js if "config.forge" does not exist', async () => { - const fixturePath = path.resolve(__dirname, '../../fixture/dummy_default_js_conf'); + const fixturePath = path.resolve( + __dirname, + '../../fixture/dummy_default_js_conf', + ); const conf = await findConfig(fixturePath); expect(conf.buildIdentifier).toEqual('default'); }); it('maintains functions from the JS export', async () => { - const fixturePath = path.resolve(__dirname, '../../fixture/dummy_js_conf'); + const fixturePath = path.resolve( + __dirname, + '../../fixture/dummy_js_conf', + ); const conf = await findConfig(fixturePath); const preStart = conf.hooks?.preStart; expect(preStart).not.toBeUndefined(); @@ -117,7 +149,10 @@ describe('findConfig', () => { }); it('should support async configs', async () => { - const fixturePath = path.resolve(__dirname, '../../fixture/async_forge_config'); + const fixturePath = path.resolve( + __dirname, + '../../fixture/async_forge_config', + ); const config = await findConfig(fixturePath); expect(config).toEqual({ ...DEFAULTS, @@ -133,8 +168,12 @@ describe('findConfig', () => { }); it('should support ESM configs', async () => { - type DefaultResolvedConfig = ResolvedForgeConfig & { defaultResolved: boolean }; - const conf = (await findConfig(path.resolve(__dirname, '../../fixture/dummy_default_esm_conf'))) as DefaultResolvedConfig; + type DefaultResolvedConfig = ResolvedForgeConfig & { + defaultResolved: boolean; + }; + const conf = (await findConfig( + path.resolve(__dirname, '../../fixture/dummy_default_esm_conf'), + )) as DefaultResolvedConfig; expect(conf.buildIdentifier).toEqual('esm'); expect(conf.defaultResolved).toEqual(true); }); @@ -156,7 +195,9 @@ describe('findConfig', () => { it('allows overwrite of properties', async () => { // Why: This needs to get refactored anyway. // eslint-disable-next-line @typescript-eslint/no-explicit-any - const conf: any = await findConfig(path.resolve(__dirname, '../../fixture/dummy_js_conf')); + const conf: any = await findConfig( + path.resolve(__dirname, '../../fixture/dummy_js_conf'), + ); expect(conf.packagerConfig.baz.hasOwnProperty).toBeTypeOf('function'); expect(() => { conf.packagerConfig.baz = 'bar'; @@ -169,7 +210,9 @@ describe('findConfig', () => { configurable: true, value: 'SecretyThing', }; - expect(Object.getOwnPropertyDescriptor(conf.s3, 'secretAccessKey')).toEqual(descriptor); + expect( + Object.getOwnPropertyDescriptor(conf.s3, 'secretAccessKey'), + ).toEqual(descriptor); expect(() => { conf.s3.secretAccessKey = 'bar'; }).not.toThrow(); @@ -186,11 +229,14 @@ describe('findConfig', () => { baseUrl: string; }; }; - const conf = (await findConfig(path.resolve(__dirname, '../../fixture/dummy_js_conf'))) as MappedConfig; + const conf = (await findConfig( + path.resolve(__dirname, '../../fixture/dummy_js_conf'), + )) as MappedConfig; expect(conf.s3.secretAccessKey).toBe(undefined); process.env.ELECTRON_FORGE_S3_SECRET_ACCESS_KEY = 'SecretyThing'; - process.env.ELECTRON_FORGE_ELECTRON_RELEASE_SERVER_BASE_URL = 'http://example.com'; + process.env.ELECTRON_FORGE_ELECTRON_RELEASE_SERVER_BASE_URL = + 'http://example.com'; expect(conf.s3.secretAccessKey).toEqual('SecretyThing'); expect(conf.electronReleaseServer.baseUrl).toEqual('http://example.com'); delete process.env.ELECTRON_FORGE_S3_SECRET_ACCESS_KEY; @@ -230,7 +276,10 @@ describe('findConfig', () => { }); it('should prioritize virtual config over forge.config.js', async () => { - const fixturePath = path.resolve(__dirname, '../../fixture/async_forge_config'); + const fixturePath = path.resolve( + __dirname, + '../../fixture/async_forge_config', + ); try { registerForgeConfigForDirectory(fixturePath, { outDir: 'magic' }); const config = await findConfig(fixturePath); @@ -247,26 +296,38 @@ describe('findConfig', () => { describe('alternate config formats', () => { it('should resolve the yml config from forge.config.yml specified in config.forge', async () => { - const fixturePath = path.resolve(__dirname, '../../fixture/dummy_ts_conf'); + const fixturePath = path.resolve( + __dirname, + '../../fixture/dummy_ts_conf', + ); const conf = await findConfig(fixturePath); expect(conf.buildIdentifier).toEqual('yml'); }); describe('TypeScript', () => { it('should resolve forge.config.ts', async () => { - const fixturePath = path.resolve(__dirname, '../../fixture/dummy_default_ts_conf'); + const fixturePath = path.resolve( + __dirname, + '../../fixture/dummy_default_ts_conf', + ); const conf = await findConfig(fixturePath); expect(conf.buildIdentifier).toEqual('typescript'); }); it('should resolve forge.config.cts', async () => { - const fixturePath = path.resolve(__dirname, '../../fixture/dummy_default_cts_conf'); + const fixturePath = path.resolve( + __dirname, + '../../fixture/dummy_default_cts_conf', + ); const conf = await findConfig(fixturePath); expect(conf.buildIdentifier).toEqual('typescript-commonjs'); }); it('should resolve forge.config.mts', async () => { - const fixturePath = path.resolve(__dirname, '../../fixture/dummy_default_mts_conf'); + const fixturePath = path.resolve( + __dirname, + '../../fixture/dummy_default_mts_conf', + ); const conf = await findConfig(fixturePath); expect(conf.buildIdentifier).toEqual('typescript-esm'); }); @@ -286,7 +347,9 @@ it('should resolve values fromBuildIdentifier', async () => { }; }; }; - const conf = (await findConfig(path.resolve(__dirname, '../../fixture/dummy_js_conf'))) as ResolveBIConfig; + const conf = (await findConfig( + path.resolve(__dirname, '../../fixture/dummy_js_conf'), + )) as ResolveBIConfig; expect(conf.topLevelProp).toEqual('foo'); expect(conf.sub).toEqual({ prop: { @@ -301,12 +364,16 @@ it('should resolve values fromBuildIdentifier', async () => { describe('forgeConfigIsValidFilePath', () => { it('succeeds for a file extension-less path', async () => { const fixturePath = path.resolve(__dirname, '../../fixture/dummy_js_conf/'); - await expect(forgeConfigIsValidFilePath(fixturePath, 'forge.different.config')).resolves.toEqual(true); + await expect( + forgeConfigIsValidFilePath(fixturePath, 'forge.different.config'), + ).resolves.toEqual(true); }); it('fails when a file is nonexistent', async () => { const fixturePath = path.resolve(__dirname, '../../fixture/dummy_js_conf/'); - await expect(forgeConfigIsValidFilePath(fixturePath, 'forge.nonexistent.config')).resolves.toEqual(false); + await expect( + forgeConfigIsValidFilePath(fixturePath, 'forge.nonexistent.config'), + ).resolves.toEqual(false); }); }); diff --git a/packages/api/core/spec/fast/util/hook.spec.ts b/packages/api/core/spec/fast/util/hook.spec.ts index 1d7644b77c..8fc4248dad 100644 --- a/packages/api/core/spec/fast/util/hook.spec.ts +++ b/packages/api/core/spec/fast/util/hook.spec.ts @@ -10,7 +10,9 @@ const fakeConfig = { }, } as unknown as ResolvedForgeConfig; -vi.mocked(fakeConfig.pluginInterface.triggerMutatingHook).mockImplementation((_, arg1) => Promise.resolve(arg1)); +vi.mocked(fakeConfig.pluginInterface.triggerMutatingHook).mockImplementation( + (_, arg1) => Promise.resolve(arg1), +); describe('runHook', () => { it('should not error when running non existent hooks', async () => { @@ -18,7 +20,13 @@ describe('runHook', () => { }); it('should not error when running a hook that is not a function', async () => { - await runHook({ hooks: { preMake: 'abc' as unknown as ForgeHookFn<'preMake'> }, ...fakeConfig }, 'preMake'); + await runHook( + { + hooks: { preMake: 'abc' as unknown as ForgeHookFn<'preMake'> }, + ...fakeConfig, + }, + 'preMake', + ); }); it('should run the hook if it is provided as a function', async () => { @@ -34,7 +42,9 @@ describe('runMutatingHook', () => { const info = { foo: 'bar', }; - expect(await runMutatingHook({ ...fakeConfig }, 'readPackageJson', info)).toEqual(info); + expect( + await runMutatingHook({ ...fakeConfig }, 'readPackageJson', info), + ).toEqual(info); }); it('should return the mutated input when returned from a hook', async () => { @@ -45,11 +55,17 @@ describe('runMutatingHook', () => { const info = { foo: 'bar', }; - const output = await runMutatingHook({ hooks: { readPackageJson: fn }, ...fakeConfig }, 'readPackageJson', info); + const output = await runMutatingHook( + { hooks: { readPackageJson: fn }, ...fakeConfig }, + 'readPackageJson', + info, + ); expect(output).toEqual({ mutated: 'foo', }); - expect(vi.mocked(fakeConfig.pluginInterface.triggerMutatingHook).mock.lastCall).toEqual([ + expect( + vi.mocked(fakeConfig.pluginInterface.triggerMutatingHook).mock.lastCall, + ).toEqual([ 'readPackageJson', { mutated: 'foo', diff --git a/packages/api/core/spec/fast/util/import-search.spec.ts b/packages/api/core/spec/fast/util/import-search.spec.ts index 4f3462dbe1..f7fb93f094 100644 --- a/packages/api/core/spec/fast/util/import-search.spec.ts +++ b/packages/api/core/spec/fast/util/import-search.spec.ts @@ -5,16 +5,22 @@ import importSearch from '../../../src/util/import-search'; describe('import-search', () => { it('should resolve null if no file exists', async () => { - const resolved = await importSearch(__dirname, ['../../../src/util/wizard-secrets']); + const resolved = await importSearch(__dirname, [ + '../../../src/util/wizard-secrets', + ]); expect(resolved).toEqual(null); }); it('should resolve a file if it exists', async () => { - const resolved = await importSearch(__dirname, ['../../../src/util/forge-config']); + const resolved = await importSearch(__dirname, [ + '../../../src/util/forge-config', + ]); expect(resolved).toEqual(findConfig); }); it('should throw if file exists but fails to load', async () => { - await expect(importSearch(__dirname, ['../../fixture/require-search/throw-error'])).rejects.toThrowError('test'); + await expect( + importSearch(__dirname, ['../../fixture/require-search/throw-error']), + ).rejects.toThrowError('test'); }); }); diff --git a/packages/api/core/spec/fast/util/install-dependencies.spec.ts b/packages/api/core/spec/fast/util/install-dependencies.spec.ts index af34052dd6..fc029e7369 100644 --- a/packages/api/core/spec/fast/util/install-dependencies.spec.ts +++ b/packages/api/core/spec/fast/util/install-dependencies.spec.ts @@ -1,7 +1,13 @@ -import { PACKAGE_MANAGERS, spawnPackageManager } from '@electron-forge/core-utils'; +import { + PACKAGE_MANAGERS, + spawnPackageManager, +} from '@electron-forge/core-utils'; import { describe, expect, it, vi } from 'vitest'; -import installDependencies, { DepType, DepVersionRestriction } from '../../../src/util/install-dependencies'; +import installDependencies, { + DepType, + DepVersionRestriction, +} from '../../../src/util/install-dependencies'; vi.mock(import('@electron-forge/core-utils'), async (importOriginal) => { const mod = await importOriginal(); @@ -19,33 +25,69 @@ describe('installDependencies', () => { it('should reject if the package manager fails to spawn', async () => { vi.mocked(spawnPackageManager).mockRejectedValueOnce('fail'); - await expect(installDependencies(PACKAGE_MANAGERS['npm'], 'void', ['electron'])).rejects.toThrow('fail'); + await expect( + installDependencies(PACKAGE_MANAGERS['npm'], 'void', ['electron']), + ).rejects.toThrow('fail'); }); it('should resolve if the package manager command succeeds', async () => { vi.mocked(spawnPackageManager).mockResolvedValueOnce('pass'); - await expect(installDependencies(PACKAGE_MANAGERS['npm'], 'void', ['electron'])).resolves.toBe(undefined); + await expect( + installDependencies(PACKAGE_MANAGERS['npm'], 'void', ['electron']), + ).resolves.toBe(undefined); }); - describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGERS['pnpm']])('$executable', (pm) => { + describe.each([ + PACKAGE_MANAGERS['npm'], + PACKAGE_MANAGERS['yarn'], + PACKAGE_MANAGERS['pnpm'], + ])('$executable', (pm) => { it('should install deps', async () => { await installDependencies(pm, 'mydir', ['react']); - expect(spawnPackageManager).toHaveBeenCalledWith(pm, [pm.install, 'react'], expect.anything()); + expect(spawnPackageManager).toHaveBeenCalledWith( + pm, + [pm.install, 'react'], + expect.anything(), + ); }); it('should install dev deps', async () => { await installDependencies(pm, 'mydir', ['eslint'], DepType.DEV); - expect(spawnPackageManager).toHaveBeenCalledWith(pm, [pm.install, 'eslint', pm.dev], expect.anything()); + expect(spawnPackageManager).toHaveBeenCalledWith( + pm, + [pm.install, 'eslint', pm.dev], + expect.anything(), + ); }); it('should install exact deps', async () => { - await installDependencies(pm, 'mydir', ['react'], DepType.PROD, DepVersionRestriction.EXACT); - expect(spawnPackageManager).toHaveBeenCalledWith(pm, [pm.install, 'react', pm.exact], expect.anything()); + await installDependencies( + pm, + 'mydir', + ['react'], + DepType.PROD, + DepVersionRestriction.EXACT, + ); + expect(spawnPackageManager).toHaveBeenCalledWith( + pm, + [pm.install, 'react', pm.exact], + expect.anything(), + ); }); it('should install exact dev deps', async () => { - await installDependencies(pm, 'mydir', ['eslint'], DepType.DEV, DepVersionRestriction.EXACT); - expect(spawnPackageManager).toHaveBeenCalledWith(pm, [pm.install, 'eslint', pm.dev, pm.exact], expect.anything()); + await installDependencies( + pm, + 'mydir', + ['eslint'], + DepType.DEV, + DepVersionRestriction.EXACT, + ); + expect(spawnPackageManager).toHaveBeenCalledWith( + pm, + [pm.install, 'eslint', pm.dev, pm.exact], + expect.anything(), + ); }); }); }); diff --git a/packages/api/core/spec/fast/util/out-dir.spec.ts b/packages/api/core/spec/fast/util/out-dir.spec.ts index 5fb1158bf2..39cbf96bb8 100644 --- a/packages/api/core/spec/fast/util/out-dir.spec.ts +++ b/packages/api/core/spec/fast/util/out-dir.spec.ts @@ -18,14 +18,18 @@ describe('getCurrentOutDir', () => { const config = { buildIdentifier: 'bar', } as const; - expect(getCurrentOutDir(DIR, config as ResolvedForgeConfig)).toEqual(path.join(DIR, 'out', config.buildIdentifier)); + expect(getCurrentOutDir(DIR, config as ResolvedForgeConfig)).toEqual( + path.join(DIR, 'out', config.buildIdentifier), + ); }); it('resolves to the return value of provided identifier getter', () => { const config = { buildIdentifier: () => 'thing', } as ResolvedForgeConfig; - expect(getCurrentOutDir(DIR, config)).toEqual(path.join(DIR, 'out', 'thing')); + expect(getCurrentOutDir(DIR, config)).toEqual( + path.join(DIR, 'out', 'thing'), + ); }); }); @@ -34,7 +38,7 @@ describe('getCurrentOutDir', () => { expect( getCurrentOutDir(DIR, { outDir: 'dist', - } as ResolvedForgeConfig) + } as ResolvedForgeConfig), ).toEqual(path.join(DIR, 'dist')); }); @@ -43,7 +47,7 @@ describe('getCurrentOutDir', () => { getCurrentOutDir(DIR, { buildIdentifier: 'bar', outDir: 'dist', - } as ResolvedForgeConfig) + } as ResolvedForgeConfig), ).toEqual(path.join(DIR, 'dist', 'bar')); }); @@ -52,7 +56,7 @@ describe('getCurrentOutDir', () => { getCurrentOutDir(DIR, { buildIdentifier: () => 'thing', outDir: 'dist', - } as ResolvedForgeConfig) + } as ResolvedForgeConfig), ).toEqual(path.join(DIR, 'dist', 'thing')); }); }); diff --git a/packages/api/core/spec/fast/util/parse-archs.spec.ts b/packages/api/core/spec/fast/util/parse-archs.spec.ts index 15ba9b5c82..6d94a6f7e5 100644 --- a/packages/api/core/spec/fast/util/parse-archs.spec.ts +++ b/packages/api/core/spec/fast/util/parse-archs.spec.ts @@ -13,7 +13,11 @@ describe('parse-archs', () => { it('should use the official Electron arch list when arch is "all"', () => { expect(parseArchs('win32', 'all', '1.7.0')).toEqual(['ia32', 'x64']); - expect(parseArchs('win32', 'all', '33.0.0')).toEqual(['ia32', 'x64', 'arm64']); + expect(parseArchs('win32', 'all', '33.0.0')).toEqual([ + 'ia32', + 'x64', + 'arm64', + ]); }); it('should default to [x64] when the platform is unknown', () => { diff --git a/packages/api/core/spec/fast/util/read-package-json.spec.ts b/packages/api/core/spec/fast/util/read-package-json.spec.ts index e9f1a0b5e6..e599c81bfa 100644 --- a/packages/api/core/spec/fast/util/read-package-json.spec.ts +++ b/packages/api/core/spec/fast/util/read-package-json.spec.ts @@ -4,7 +4,10 @@ import { ResolvedForgeConfig } from '@electron-forge/shared-types'; import { describe, expect, it } from 'vitest'; import packageJSON from '../../../package.json'; -import { readMutatedPackageJson, readRawPackageJson } from '../../../src/util/read-package-json'; +import { + readMutatedPackageJson, + readRawPackageJson, +} from '../../../src/util/read-package-json'; describe('readRawPackageJson', () => { it('should find a package.json file from the given directory', async () => { @@ -20,9 +23,10 @@ describe('readMutatedPackageJson', () => { await readMutatedPackageJson(path.resolve(__dirname, '../../../'), { pluginInterface: { // eslint-disable-next-line @typescript-eslint/no-explicit-any - triggerMutatingHook: (_hookName: string, pj: any) => Promise.resolve(pj), + triggerMutatingHook: (_hookName: string, pj: any) => + Promise.resolve(pj), }, - } as unknown as ResolvedForgeConfig) + } as unknown as ResolvedForgeConfig), ).toEqual(packageJSON); }); @@ -32,7 +36,7 @@ describe('readMutatedPackageJson', () => { pluginInterface: { triggerMutatingHook: () => Promise.resolve('test_mutation'), }, - } as unknown as ResolvedForgeConfig) + } as unknown as ResolvedForgeConfig), ).toEqual('test_mutation'); }); }); diff --git a/packages/api/core/spec/fast/util/resolve-dir.spec.ts b/packages/api/core/spec/fast/util/resolve-dir.spec.ts index 32c5236a7c..71911597db 100644 --- a/packages/api/core/spec/fast/util/resolve-dir.spec.ts +++ b/packages/api/core/spec/fast/util/resolve-dir.spec.ts @@ -2,7 +2,10 @@ import path from 'node:path'; import { describe, expect, it } from 'vitest'; -import { registerForgeConfigForDirectory, unregisterForgeConfigForDirectory } from '../../../src/util/forge-config'; +import { + registerForgeConfigForDirectory, + unregisterForgeConfigForDirectory, +} from '../../../src/util/forge-config'; import resolveDir from '../../../src/util/resolve-dir'; describe('resolve-dir', () => { @@ -11,7 +14,10 @@ describe('resolve-dir', () => { }); it('should return a directory if a forge config is found, but no package.json.forge.config', async () => { - const dir = path.resolve(__dirname, '../../fixture/forge-config-no-package-json-config/'); + const dir = path.resolve( + __dirname, + '../../fixture/forge-config-no-package-json-config/', + ); const resolved = await resolveDir(dir); expect(resolved).not.toBeNull(); expect(resolved).toEqual(dir); @@ -21,14 +27,18 @@ describe('resolve-dir', () => { const dir = path.resolve(__dirname, '../../fixture/dummy_app/foo'); const resolved = await resolveDir(dir); expect(resolved).not.toBeNull(); - expect(await resolveDir(path.resolve(__dirname, '../../fixture/dummy_app/foo'))).toEqual(path.resolve(__dirname, '../../fixture/dummy_app')); + expect( + await resolveDir(path.resolve(__dirname, '../../fixture/dummy_app/foo')), + ).toEqual(path.resolve(__dirname, '../../fixture/dummy_app')); }); it('should return a directory if it finds a virtual config', async () => { try { registerForgeConfigForDirectory('/foo/var/virtual', {}); expect(await resolveDir('/foo/var/virtual')).not.toEqual(null); - expect(await resolveDir(path.resolve(__dirname, '/foo/var/virtual'))).toEqual(path.resolve(__dirname, '/foo/var/virtual')); + expect( + await resolveDir(path.resolve(__dirname, '/foo/var/virtual')), + ).toEqual(path.resolve(__dirname, '/foo/var/virtual')); } finally { unregisterForgeConfigForDirectory('/foo/var/virtual'); } diff --git a/packages/api/core/spec/fast/util/upgrade-forge-config.spec.ts b/packages/api/core/spec/fast/util/upgrade-forge-config.spec.ts index aeb8af4023..12891d03ae 100644 --- a/packages/api/core/spec/fast/util/upgrade-forge-config.spec.ts +++ b/packages/api/core/spec/fast/util/upgrade-forge-config.spec.ts @@ -1,10 +1,16 @@ import assert from 'node:assert'; -import { ForgeConfig, IForgeResolvableMaker, IForgeResolvablePublisher } from '@electron-forge/shared-types'; +import { + ForgeConfig, + IForgeResolvableMaker, + IForgeResolvablePublisher, +} from '@electron-forge/shared-types'; import { merge } from 'lodash'; import { describe, expect, it } from 'vitest'; -import upgradeForgeConfig, { updateUpgradedForgeDevDeps } from '../../../src/util/upgrade-forge-config'; +import upgradeForgeConfig, { + updateUpgradedForgeDevDeps, +} from '../../../src/util/upgrade-forge-config'; describe('upgradeForgeConfig', () => { it('converts Electron Packager config', () => { @@ -107,7 +113,9 @@ describe('upgradeForgeConfig', () => { const newConfig = upgradeForgeConfig(oldConfig); expect(newConfig.publishers).toHaveLength(1); assert(newConfig.publishers); - const publisherConfig = (newConfig.publishers[0] as IForgeResolvablePublisher).config; + const publisherConfig = ( + newConfig.publishers[0] as IForgeResolvablePublisher + ).config; expect(publisherConfig.repository).toEqual(repo); expect(publisherConfig.octokitOptions).toEqual(octokitOptions); expect(publisherConfig.draft).toEqual(true); @@ -135,7 +143,9 @@ describe('updateUpgradedForgeDevDeps', () => { it('removes unused makers from devDependencies', () => { const packageJSON = merge({}, skeletonPackageJSON); - const devDeps = updateUpgradedForgeDevDeps(packageJSON, ['@electron-forge/maker-squirrel']); + const devDeps = updateUpgradedForgeDevDeps(packageJSON, [ + '@electron-forge/maker-squirrel', + ]); expect(devDeps).toEqual([]); }); @@ -155,17 +165,30 @@ describe('updateUpgradedForgeDevDeps', () => { const actual = updateUpgradedForgeDevDeps(packageJSON, []); expect(actual).toHaveLength(2); - expect(actual.find((dep) => dep.startsWith('@electron-forge/maker-zip'))).not.toEqual(undefined); - expect(actual.find((dep) => dep.startsWith('@electron-forge/maker-squirrel'))).not.toEqual(undefined); + expect( + actual.find((dep) => dep.startsWith('@electron-forge/maker-zip')), + ).not.toEqual(undefined); + expect( + actual.find((dep) => dep.startsWith('@electron-forge/maker-squirrel')), + ).not.toEqual(undefined); }); it('adds publishers to devDependencies', () => { const packageJSON = merge({}, skeletonPackageJSON); - packageJSON.config.forge.publishers = [{ name: '@electron-forge/publisher-github' }, { name: '@electron-forge/publisher-snapcraft' }]; + packageJSON.config.forge.publishers = [ + { name: '@electron-forge/publisher-github' }, + { name: '@electron-forge/publisher-snapcraft' }, + ]; const actual = updateUpgradedForgeDevDeps(packageJSON, []); expect(actual).toHaveLength(2); - expect(actual.find((dep) => dep.startsWith('@electron-forge/publisher-github'))).not.toEqual(undefined); - expect(actual.find((dep) => dep.startsWith('@electron-forge/publisher-snapcraft'))).not.toEqual(undefined); + expect( + actual.find((dep) => dep.startsWith('@electron-forge/publisher-github')), + ).not.toEqual(undefined); + expect( + actual.find((dep) => + dep.startsWith('@electron-forge/publisher-snapcraft'), + ), + ).not.toEqual(undefined); }); }); diff --git a/packages/api/core/spec/fixture/custom_init/index.js b/packages/api/core/spec/fixture/custom_init/index.js index 7043c0d983..16f847f7e0 100644 --- a/packages/api/core/spec/fixture/custom_init/index.js +++ b/packages/api/core/spec/fixture/custom_init/index.js @@ -14,8 +14,14 @@ module.exports = { { title: 'Adding custom template files', task: async () => { - await fs.copy(path.resolve(__dirname, 'tmpl', '_bar'), path.resolve(directory, '.bar')); - await fs.copy(path.resolve(__dirname, 'tmpl', 'src'), path.resolve(directory, 'src')); + await fs.copy( + path.resolve(__dirname, 'tmpl', '_bar'), + path.resolve(directory, '.bar'), + ); + await fs.copy( + path.resolve(__dirname, 'tmpl', 'src'), + path.resolve(directory, 'src'), + ); }, }, ]; diff --git a/packages/api/core/spec/slow/api.slow.spec.ts b/packages/api/core/spec/slow/api.slow.spec.ts index 1d3746da86..be85e3c991 100644 --- a/packages/api/core/spec/slow/api.slow.spec.ts +++ b/packages/api/core/spec/slow/api.slow.spec.ts @@ -3,10 +3,19 @@ import { execSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; -import { PACKAGE_MANAGERS, spawnPackageManager } from '@electron-forge/core-utils'; +import { + PACKAGE_MANAGERS, + spawnPackageManager, +} from '@electron-forge/core-utils'; import { createDefaultCertificate } from '@electron-forge/maker-appx'; -import { ForgeConfig, IForgeResolvableMaker } from '@electron-forge/shared-types'; -import { ensureTestDirIsNonexistent, expectLintToPass } from '@electron-forge/test-utils'; +import { + ForgeConfig, + IForgeResolvableMaker, +} from '@electron-forge/shared-types'; +import { + ensureTestDirIsNonexistent, + expectLintToPass, +} from '@electron-forge/test-utils'; import { readMetadata } from 'electron-installer-common'; import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest'; @@ -23,20 +32,34 @@ type PackageJSON = Record & { dependencies: Record; }; -async function updatePackageJSON(dir: string, packageJSONUpdater: (packageJSON: PackageJSON) => Promise) { +async function updatePackageJSON( + dir: string, + packageJSONUpdater: (packageJSON: PackageJSON) => Promise, +) { const packageJSON = await readRawPackageJson(dir); await packageJSONUpdater(packageJSON); - await fs.promises.writeFile(path.resolve(dir, 'package.json'), JSON.stringify(packageJSON), 'utf-8'); + await fs.promises.writeFile( + path.resolve(dir, 'package.json'), + JSON.stringify(packageJSON), + 'utf-8', + ); } -describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGERS['pnpm']])(`init (with $executable)`, (pm) => { +describe.each([ + PACKAGE_MANAGERS['npm'], + PACKAGE_MANAGERS['yarn'], + PACKAGE_MANAGERS['pnpm'], +])(`init (with $executable)`, (pm) => { let dir: string; beforeAll(async () => { await spawnPackageManager(pm, ['run', 'link:prepare']); if (pm.executable === 'pnpm') { - await spawnPackageManager(pm, 'config set node-linker hoisted'.split(' ')); + await spawnPackageManager( + pm, + 'config set node-linker hoisted'.split(' '), + ); } return async () => { @@ -45,7 +68,10 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE }; }); - const beforeInitTest = (params?: Partial, beforeInit?: BeforeInitFunction) => { + const beforeInitTest = ( + params?: Partial, + beforeInit?: BeforeInitFunction, + ) => { beforeAll(async () => { dir = await ensureTestDirIsNonexistent(); if (beforeInit) { @@ -80,7 +106,7 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE it('should fail in initializing an already initialized directory', async () => { await expect(api.init({ dir })).rejects.toThrow( - `The specified path: "${dir}" is not empty. Please ensure it is empty before initializing a new project` + `The specified path: "${dir}" is not empty. Please ensure it is empty before initializing a new project`, ); }); @@ -92,17 +118,32 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE }); it('should create a new folder with a npm module inside', async () => { - expect(fs.existsSync(dir), 'the target dir should have been created').toEqual(true); + expect( + fs.existsSync(dir), + 'the target dir should have been created', + ).toEqual(true); expect(fs.existsSync(path.join(dir, 'package.json'))).toEqual(true); expect(fs.existsSync(path.join(dir, '.git'))).toEqual(true); - expect(fs.existsSync(path.resolve(dir, 'node_modules/electron')), 'electron should exist').toEqual(true); - expect(fs.existsSync(path.resolve(dir, 'node_modules/electron-squirrel-startup')), 'electron-squirrel-startup should exist').toEqual(true); - expect(fs.existsSync(path.resolve(dir, 'node_modules/@electron-forge/cli')), '@electron-forge/cli should exist').toEqual(true); + expect( + fs.existsSync(path.resolve(dir, 'node_modules/electron')), + 'electron should exist', + ).toEqual(true); + expect( + fs.existsSync( + path.resolve(dir, 'node_modules/electron-squirrel-startup'), + ), + 'electron-squirrel-startup should exist', + ).toEqual(true); + expect( + fs.existsSync(path.resolve(dir, 'node_modules/@electron-forge/cli')), + '@electron-forge/cli should exist', + ).toEqual(true); expect(fs.existsSync(path.join(dir, 'forge.config.js'))).toEqual(true); }); describe('lint', () => { - it('should initially pass the linting process', () => expectLintToPass(dir)); + it('should initially pass the linting process', () => + expectLintToPass(dir)); }); }); @@ -112,7 +153,9 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE }); describe('init (with custom templater)', () => { - beforeInitTest({ template: path.resolve(__dirname, '../fixture/custom_init') }); + beforeInitTest({ + template: path.resolve(__dirname, '../fixture/custom_init'), + }); it('should add custom dependencies', async () => { const packageJSON = await import(path.resolve(dir, 'package.json')); @@ -125,7 +168,10 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE }); it('should create dot files correctly', async () => { - expect(fs.existsSync(dir), 'the target dir should have been created').toEqual(true); + expect( + fs.existsSync(dir), + 'the target dir should have been created', + ).toEqual(true); expect(fs.existsSync(path.join(dir, '.bar'))).toEqual(true); }); @@ -135,7 +181,8 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE }); describe('lint', () => { - it('should initially pass the linting process', () => expectLintToPass(dir)); + it('should initially pass the linting process', () => + expectLintToPass(dir)); }); afterAll(async () => { @@ -159,8 +206,11 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE await expect( api.init({ dir, - template: path.resolve(__dirname, '../fixture/template-sans-forge-version'), - }) + template: path.resolve( + __dirname, + '../fixture/template-sans-forge-version', + ), + }), ).rejects.toThrow(/it does not specify its required Forge version\.$/); }); }); @@ -178,9 +228,14 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE await expect( api.init({ dir, - template: path.resolve(__dirname, '../fixture/template-nonmatching-forge-version'), - }) - ).rejects.toThrow(/is not compatible with this version of Electron Forge/); + template: path.resolve( + __dirname, + '../fixture/template-nonmatching-forge-version', + ), + }), + ).rejects.toThrow( + /is not compatible with this version of Electron Forge/, + ); }); }); @@ -198,7 +253,7 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE api.init({ dir, template: 'does-not-exist', - }) + }), ).rejects.toThrow('Failed to locate custom template'); }); }); @@ -207,9 +262,12 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE beforeEach(async () => { dir = await ensureTestDirIsNonexistent(); await fs.promises.mkdir(dir); - execSync(`git clone https://github.com/electron/minimal-repro.git . --quiet`, { - cwd: dir, - }); + execSync( + `git clone https://github.com/electron/minimal-repro.git . --quiet`, + { + cwd: dir, + }, + ); return async () => { await fs.promises.rm(dir, { recursive: true, force: true }); @@ -232,7 +290,9 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE const outDirContents = fs.readdirSync(path.join(dir, 'out')); expect(outDirContents).toHaveLength(1); - expect(outDirContents[0]).toEqual(`ProductName-${process.platform}-${process.arch}`); + expect(outDirContents[0]).toEqual( + `ProductName-${process.platform}-${process.arch}`, + ); }); }); @@ -243,7 +303,10 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE let devCert: string; beforeAll(async () => { - dir = path.join(await ensureTestDirIsNonexistent(), 'electron-forge-test'); + dir = path.join( + await ensureTestDirIsNonexistent(), + 'electron-forge-test', + ); await api.init({ dir }); await updatePackageJSON(dir, async (packageJSON) => { @@ -258,8 +321,13 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE }, }; if (process.platform === 'win32') { - await fs.promises.copyFile(path.join(__dirname, '..', 'fixture', 'bogus-private-key.pvk'), path.join(dir, 'default.pvk')); - devCert = await createDefaultCertificate('CN=Test Author', { certFilePath: dir }); + await fs.promises.copyFile( + path.join(__dirname, '..', 'fixture', 'bogus-private-key.pvk'), + path.join(dir, 'default.pvk'), + ); + devCert = await createDefaultCertificate('CN=Test Author', { + certFilePath: dir, + }); } else if (process.platform === 'linux') { packageJSON.config.forge.packagerConfig = { ...packageJSON.config.forge.packagerConfig, @@ -280,7 +348,9 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE assert(packageJSON.config.forge.packagerConfig); packageJSON.config.forge.packagerConfig.all = true; }); - await expect(api.package({ dir })).rejects.toThrow(/packagerConfig\.all is not supported by Electron Forge/); + await expect(api.package({ dir })).rejects.toThrow( + /packagerConfig\.all is not supported by Electron Forge/, + ); await updatePackageJSON(dir, async (packageJSON) => { assert(packageJSON.config.forge.packagerConfig); delete packageJSON.config.forge.packagerConfig.all; @@ -300,14 +370,24 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE it('can make from custom outDir without errors', async () => { await updatePackageJSON(dir, async (packageJSON) => { // eslint-disable-next-line n/no-missing-require - packageJSON.config.forge.makers = [{ name: require.resolve('@electron-forge/maker-zip') } as IForgeResolvableMaker]; + packageJSON.config.forge.makers = [ + { + name: require.resolve('@electron-forge/maker-zip'), + } as IForgeResolvableMaker, + ]; }); await api.make({ dir, skipPackage: true, outDir: `${dir}/foo` }); // Cleanup here to ensure things dont break in the make tests - await fs.promises.rm(path.resolve(dir, 'foo'), { recursive: true, force: true }); - await fs.promises.rm(path.resolve(dir, 'out'), { recursive: true, force: true }); + await fs.promises.rm(path.resolve(dir, 'foo'), { + recursive: true, + force: true, + }); + await fs.promises.rm(path.resolve(dir, 'out'), { + recursive: true, + force: true, + }); }); describe('with prebuilt native module deps installed', () => { @@ -315,7 +395,10 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE await installDeps(pm, dir, ['ref-napi']); return async () => { - await fs.promises.rm(path.resolve(dir, 'node_modules/ref-napi'), { recursive: true, force: true }); + await fs.promises.rm(path.resolve(dir, 'node_modules/ref-napi'), { + recursive: true, + force: true, + }); await updatePackageJSON(dir, async (packageJSON) => { delete packageJSON.dependencies['ref-napi']; }); @@ -339,7 +422,11 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE describe('after package', () => { it('should have deleted the forge config from the packaged app', async () => { const cleanPackageJSON = await readMetadata({ - src: path.resolve(dir, 'out', `Test-App-${process.platform}-${process.arch}`), + src: path.resolve( + dir, + 'out', + `Test-App-${process.platform}-${process.arch}`, + ), logger: console.error, }); expect(cleanPackageJSON).not.toHaveProperty('config.forge'); @@ -372,7 +459,10 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE // eslint-disable-next-line @typescript-eslint/no-require-imports const MakerClass = require(makerPath).default; const maker = new MakerClass(); - return maker.isSupportedOnCurrentPlatform() === good && maker.externalBinariesExist() === good; + return ( + maker.isSupportedOnCurrentPlatform() === good && + maker.externalBinariesExist() === good + ); }) .map((makerPath) => () => { const makerDefinition = { @@ -384,7 +474,9 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE }; if (process.platform === 'win32') { - (makerDefinition.config as Record).makeVersionWinStoreCompatible = true; + ( + makerDefinition.config as Record + ).makeVersionWinStoreCompatible = true; } return makerDefinition; @@ -403,7 +495,9 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE describe(`make (with target=${target().name})`, async () => { beforeAll(async () => { await updatePackageJSON(dir, async (packageJSON) => { - packageJSON.config.forge.makers = [target() as IForgeResolvableMaker]; + packageJSON.config.forge.makers = [ + target() as IForgeResolvableMaker, + ]; }); }); @@ -414,7 +508,9 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE for (const outputResult of outputs) { for (const output of outputResult.artifacts) { expect(fs.existsSync(output)).toEqual(true); - expect(output.startsWith(path.resolve(dir, 'out', 'make'))).toEqual(true); + expect( + output.startsWith(path.resolve(dir, 'out', 'make')), + ).toEqual(true); } } }); @@ -438,11 +534,16 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE describe('make', () => { it('throws an error when given an unrecognized platform', async () => { - await expect(api.make({ dir, platform: 'dos' })).rejects.toThrow(/invalid platform/); + await expect(api.make({ dir, platform: 'dos' })).rejects.toThrow( + /invalid platform/, + ); }); it("throws an error when the specified maker doesn't support the current platform", async () => { - const makerPath = path.resolve(__dirname, '../fixture/maker-unsupported'); + const makerPath = path.resolve( + __dirname, + '../fixture/maker-unsupported', + ); await expect( api.make({ dir, @@ -452,12 +553,15 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE } as IForgeResolvableMaker, ], skipPackage: true, - }) + }), ).rejects.toThrow(/the maker declared that it cannot run/); }); it("throws an error when the specified maker doesn't implement isSupportedOnCurrentPlatform()", async () => { - const makerPath = path.resolve(__dirname, '../fixture/maker-incompatible'); + const makerPath = path.resolve( + __dirname, + '../fixture/maker-incompatible', + ); await expect( api.make({ dir, @@ -467,7 +571,7 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE } as IForgeResolvableMaker, ], skipPackage: true, - }) + }), ).rejects.toThrow(/incompatible with this version/); }); @@ -477,25 +581,36 @@ describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGE dir, overrideTargets: [ { - name: path.resolve(__dirname, '../fixture/maker-wrong-platform'), + name: path.resolve( + __dirname, + '../fixture/maker-wrong-platform', + ), } as IForgeResolvableMaker, ], platform: 'linux', skipPackage: true, - }) - ).rejects.toThrow('Could not find any make targets configured for the "linux" platform.'); + }), + ).rejects.toThrow( + 'Could not find any make targets configured for the "linux" platform.', + ); }); - it.runIf(process.platform === 'darwin')('can make for the MAS platform successfully', async () => { - await expect( - api.make({ - dir, - // eslint-disable-next-line n/no-missing-require - overrideTargets: [require.resolve('@electron-forge/maker-zip'), require.resolve('@electron-forge/maker-dmg')], - platform: 'mas', - }) - ).resolves.toHaveLength(2); - }); + it.runIf(process.platform === 'darwin')( + 'can make for the MAS platform successfully', + async () => { + await expect( + api.make({ + dir, + // eslint-disable-next-line n/no-missing-require + overrideTargets: [ + require.resolve('@electron-forge/maker-zip'), + require.resolve('@electron-forge/maker-dmg'), + ], + platform: 'mas', + }), + ).resolves.toHaveLength(2); + }, + ); }); }); }); diff --git a/packages/api/core/spec/slow/install-dependencies.slow.spec.ts b/packages/api/core/spec/slow/install-dependencies.slow.spec.ts index 0809aafac9..ff10d8d4b6 100644 --- a/packages/api/core/spec/slow/install-dependencies.slow.spec.ts +++ b/packages/api/core/spec/slow/install-dependencies.slow.spec.ts @@ -7,21 +7,26 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import installDeps from '../../src/util/install-dependencies'; -describe.runIf(!(process.platform === 'linux' && process.env.CI))('install-dependencies', () => { - let installDir: string; +describe.runIf(!(process.platform === 'linux' && process.env.CI))( + 'install-dependencies', + () => { + let installDir: string; - beforeAll(async () => { - const tmp = os.tmpdir(); - const tmpdir = path.join(tmp, 'electron-forge-test-'); - installDir = await fs.mkdtemp(tmpdir); - }); + beforeAll(async () => { + const tmp = os.tmpdir(); + const tmpdir = path.join(tmp, 'electron-forge-test-'); + installDir = await fs.mkdtemp(tmpdir); + }); - it('should install the latest minor version when the dependency has a caret', async () => { - await installDeps(PACKAGE_MANAGERS['npm'], installDir, ['debug@^2.0.0']); + it('should install the latest minor version when the dependency has a caret', async () => { + await installDeps(PACKAGE_MANAGERS['npm'], installDir, ['debug@^2.0.0']); - const packageJSON = await import(path.resolve(installDir, 'node_modules', 'debug', 'package.json')); - expect(packageJSON.version).not.toEqual('2.0.0'); - }); + const packageJSON = await import( + path.resolve(installDir, 'node_modules', 'debug', 'package.json') + ); + expect(packageJSON.version).not.toEqual('2.0.0'); + }); - afterAll(async () => fs.rm(installDir, { recursive: true, force: true })); -}); + afterAll(async () => fs.rm(installDir, { recursive: true, force: true })); + }, +); diff --git a/packages/api/core/src/api/import.ts b/packages/api/core/src/api/import.ts index 76ae8c62f5..7bdc5b34df 100644 --- a/packages/api/core/src/api/import.ts +++ b/packages/api/core/src/api/import.ts @@ -1,7 +1,14 @@ import path from 'node:path'; -import { PMDetails, resolvePackageManager, updateElectronDependency } from '@electron-forge/core-utils'; -import { ForgeListrOptions, ForgeListrTaskFn } from '@electron-forge/shared-types'; +import { + PMDetails, + resolvePackageManager, + updateElectronDependency, +} from '@electron-forge/core-utils'; +import { + ForgeListrOptions, + ForgeListrTaskFn, +} from '@electron-forge/shared-types'; import baseTemplate from '@electron-forge/template-base'; import { autoTrace } from '@electron-forge/tracer'; import chalk from 'chalk'; @@ -10,9 +17,14 @@ import fs from 'fs-extra'; import { Listr } from 'listr2'; import { merge } from 'lodash'; -import installDepList, { DepType, DepVersionRestriction } from '../util/install-dependencies'; +import installDepList, { + DepType, + DepVersionRestriction, +} from '../util/install-dependencies'; import { readRawPackageJson } from '../util/read-package-json'; -import upgradeForgeConfig, { updateUpgradedForgeDevDeps } from '../util/upgrade-forge-config'; +import upgradeForgeConfig, { + updateUpgradedForgeDevDeps, +} from '../util/upgrade-forge-config'; import { initGit } from './init-scripts/init-git'; import { deps, devDeps, exactDevDeps } from './init-scripts/init-npm'; @@ -41,11 +53,17 @@ export interface ImportOptions { /** * An async function that returns whether the given dependency should be removed */ - shouldRemoveDependency?: (dependency: string, explanation: string) => Promise; + shouldRemoveDependency?: ( + dependency: string, + explanation: string, + ) => Promise; /** * An async function that returns whether the given script should be overridden with a forge one */ - shouldUpdateScript?: (scriptName: string, newValue: string) => Promise; + shouldUpdateScript?: ( + scriptName: string, + newValue: string, + ) => Promise; /** * The path to the directory containing generated distributables */ @@ -69,7 +87,7 @@ export default autoTrace( shouldUpdateScript, outDir, skipGit = false, - }: ImportOptions + }: ImportOptions, ): Promise => { const listrOptions: ForgeListrOptions<{ pm: PMDetails }> = { concurrent: false, @@ -78,31 +96,40 @@ export default autoTrace( collapseErrors: false, }, silentRendererCondition: !interactive, - fallbackRendererCondition: Boolean(process.env.DEBUG) || Boolean(process.env.CI), + fallbackRendererCondition: + Boolean(process.env.DEBUG) || Boolean(process.env.CI), }; const runner = new Listr( [ { title: 'Locating importable project', - task: childTrace({ name: 'locate-project', category: '@electron-forge/core' }, async () => { - d(`Attempting to import project in: ${dir}`); - if (!(await fs.pathExists(dir)) || !(await fs.pathExists(path.resolve(dir, 'package.json')))) { - throw new Error(`We couldn't find a project with a package.json file in: ${dir}`); - } + task: childTrace( + { name: 'locate-project', category: '@electron-forge/core' }, + async () => { + d(`Attempting to import project in: ${dir}`); + if ( + !(await fs.pathExists(dir)) || + !(await fs.pathExists(path.resolve(dir, 'package.json'))) + ) { + throw new Error( + `We couldn't find a project with a package.json file in: ${dir}`, + ); + } - if (typeof confirmImport === 'function') { - if (!(await confirmImport())) { - // TODO: figure out if we can just return early here - // eslint-disable-next-line no-process-exit - process.exit(0); + if (typeof confirmImport === 'function') { + if (!(await confirmImport())) { + // TODO: figure out if we can just return early here + // eslint-disable-next-line no-process-exit + process.exit(0); + } } - } - if (!skipGit) { - await initGit(dir); - } - }), + if (!skipGit) { + await initGit(dir); + } + }, + ), }, { title: 'Processing configuration and dependencies', @@ -110,165 +137,232 @@ export default autoTrace( persistentOutput: true, bottomBar: Infinity, }, - task: childTrace>({ name: 'string', category: 'foo' }, async (_, ctx, task) => { - const calculatedOutDir = outDir || 'out'; + task: childTrace>( + { name: 'string', category: 'foo' }, + async (_, ctx, task) => { + const calculatedOutDir = outDir || 'out'; - const importDeps = ([] as string[]).concat(deps); - let importDevDeps = ([] as string[]).concat(devDeps); - let importExactDevDeps = ([] as string[]).concat(exactDevDeps); + const importDeps = ([] as string[]).concat(deps); + let importDevDeps = ([] as string[]).concat(devDeps); + let importExactDevDeps = ([] as string[]).concat(exactDevDeps); - let packageJSON = await readRawPackageJson(dir); - if (!packageJSON.version) { - task.output = chalk.yellow(`Please set the ${chalk.green('"version"')} in your application's package.json`); - } - if (packageJSON.config && packageJSON.config.forge) { - if (packageJSON.config.forge.makers) { - task.output = chalk.green('Existing Electron Forge configuration detected'); - if (typeof shouldContinueOnExisting === 'function') { - if (!(await shouldContinueOnExisting())) { - // TODO: figure out if we can just return early here - // eslint-disable-next-line no-process-exit - process.exit(0); - } - } - } else if (!(typeof packageJSON.config.forge === 'object')) { + let packageJSON = await readRawPackageJson(dir); + if (!packageJSON.version) { task.output = chalk.yellow( - "We can't tell if the Electron Forge config is compatible because it's in an external JavaScript file, not trying to convert it and continuing anyway" + `Please set the ${chalk.green('"version"')} in your application's package.json`, ); - } else { - d('Upgrading an Electron Forge < 6 project'); - packageJSON.config.forge = upgradeForgeConfig(packageJSON.config.forge); - importDevDeps = updateUpgradedForgeDevDeps(packageJSON, importDevDeps); } - } + if (packageJSON.config && packageJSON.config.forge) { + if (packageJSON.config.forge.makers) { + task.output = chalk.green( + 'Existing Electron Forge configuration detected', + ); + if (typeof shouldContinueOnExisting === 'function') { + if (!(await shouldContinueOnExisting())) { + // TODO: figure out if we can just return early here + // eslint-disable-next-line no-process-exit + process.exit(0); + } + } + } else if (!(typeof packageJSON.config.forge === 'object')) { + task.output = chalk.yellow( + "We can't tell if the Electron Forge config is compatible because it's in an external JavaScript file, not trying to convert it and continuing anyway", + ); + } else { + d('Upgrading an Electron Forge < 6 project'); + packageJSON.config.forge = upgradeForgeConfig( + packageJSON.config.forge, + ); + importDevDeps = updateUpgradedForgeDevDeps( + packageJSON, + importDevDeps, + ); + } + } - packageJSON.dependencies = packageJSON.dependencies || {}; - packageJSON.devDependencies = packageJSON.devDependencies || {}; + packageJSON.dependencies = packageJSON.dependencies || {}; + packageJSON.devDependencies = packageJSON.devDependencies || {}; - [importDevDeps, importExactDevDeps] = updateElectronDependency(packageJSON, importDevDeps, importExactDevDeps); + [importDevDeps, importExactDevDeps] = updateElectronDependency( + packageJSON, + importDevDeps, + importExactDevDeps, + ); - const keys = Object.keys(packageJSON.dependencies).concat(Object.keys(packageJSON.devDependencies)); - const buildToolPackages: Record = { - '@electron/get': 'already uses this module as a transitive dependency', - '@electron/osx-sign': 'already uses this module as a transitive dependency', - '@electron/packager': 'already uses this module as a transitive dependency', - 'electron-builder': 'provides mostly equivalent functionality', - 'electron-download': 'already uses this module as a transitive dependency', - 'electron-forge': 'replaced with @electron-forge/cli', - 'electron-installer-debian': 'already uses this module as a transitive dependency', - 'electron-installer-dmg': 'already uses this module as a transitive dependency', - 'electron-installer-flatpak': 'already uses this module as a transitive dependency', - 'electron-installer-redhat': 'already uses this module as a transitive dependency', - 'electron-winstaller': 'already uses this module as a transitive dependency', - }; + const keys = Object.keys(packageJSON.dependencies).concat( + Object.keys(packageJSON.devDependencies), + ); + const buildToolPackages: Record = { + '@electron/get': + 'already uses this module as a transitive dependency', + '@electron/osx-sign': + 'already uses this module as a transitive dependency', + '@electron/packager': + 'already uses this module as a transitive dependency', + 'electron-builder': 'provides mostly equivalent functionality', + 'electron-download': + 'already uses this module as a transitive dependency', + 'electron-forge': 'replaced with @electron-forge/cli', + 'electron-installer-debian': + 'already uses this module as a transitive dependency', + 'electron-installer-dmg': + 'already uses this module as a transitive dependency', + 'electron-installer-flatpak': + 'already uses this module as a transitive dependency', + 'electron-installer-redhat': + 'already uses this module as a transitive dependency', + 'electron-winstaller': + 'already uses this module as a transitive dependency', + }; - for (const key of keys) { - if (buildToolPackages[key]) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const explanation = buildToolPackages[key]!; - let remove = true; - if (typeof shouldRemoveDependency === 'function') { - remove = await shouldRemoveDependency(key, explanation); - } + for (const key of keys) { + if (buildToolPackages[key]) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const explanation = buildToolPackages[key]!; + let remove = true; + if (typeof shouldRemoveDependency === 'function') { + remove = await shouldRemoveDependency(key, explanation); + } - if (remove) { - delete packageJSON.dependencies[key]; - delete packageJSON.devDependencies[key]; + if (remove) { + delete packageJSON.dependencies[key]; + delete packageJSON.devDependencies[key]; + } } } - } - packageJSON.scripts = packageJSON.scripts || {}; - d('reading current scripts object:', packageJSON.scripts); + packageJSON.scripts = packageJSON.scripts || {}; + d('reading current scripts object:', packageJSON.scripts); - const updatePackageScript = async (scriptName: string, newValue: string) => { - if (packageJSON.scripts[scriptName] !== newValue) { - let update = true; - if (typeof shouldUpdateScript === 'function') { - update = await shouldUpdateScript(scriptName, newValue); - } - if (update) { - packageJSON.scripts[scriptName] = newValue; + const updatePackageScript = async ( + scriptName: string, + newValue: string, + ) => { + if (packageJSON.scripts[scriptName] !== newValue) { + let update = true; + if (typeof shouldUpdateScript === 'function') { + update = await shouldUpdateScript(scriptName, newValue); + } + if (update) { + packageJSON.scripts[scriptName] = newValue; + } } - } - }; + }; - await updatePackageScript('start', 'electron-forge start'); - await updatePackageScript('package', 'electron-forge package'); - await updatePackageScript('make', 'electron-forge make'); + await updatePackageScript('start', 'electron-forge start'); + await updatePackageScript('package', 'electron-forge package'); + await updatePackageScript('make', 'electron-forge make'); - d('forgified scripts object:', packageJSON.scripts); + d('forgified scripts object:', packageJSON.scripts); - const writeChanges = async () => { - await fs.writeJson(path.resolve(dir, 'package.json'), packageJSON, { spaces: 2 }); - }; + const writeChanges = async () => { + await fs.writeJson( + path.resolve(dir, 'package.json'), + packageJSON, + { spaces: 2 }, + ); + }; - return task.newListr<{ pm: PMDetails }>( - [ - { - title: `Resolving package manager`, - task: async (ctx, task) => { - ctx.pm = await resolvePackageManager(); - task.title = `Resolving package manager: ${chalk.cyan(ctx.pm.executable)}`; + return task.newListr<{ pm: PMDetails }>( + [ + { + title: `Resolving package manager`, + task: async (ctx, task) => { + ctx.pm = await resolvePackageManager(); + task.title = `Resolving package manager: ${chalk.cyan(ctx.pm.executable)}`; + }, }, - }, - { - title: 'Installing dependencies', - task: async ({ pm }, task) => { - await writeChanges(); + { + title: 'Installing dependencies', + task: async ({ pm }, task) => { + await writeChanges(); - d('deleting old dependencies forcefully'); - await fs.remove(path.resolve(dir, 'node_modules/.bin/electron')); - await fs.remove(path.resolve(dir, 'node_modules/.bin/electron.cmd')); + d('deleting old dependencies forcefully'); + await fs.remove( + path.resolve(dir, 'node_modules/.bin/electron'), + ); + await fs.remove( + path.resolve(dir, 'node_modules/.bin/electron.cmd'), + ); - d('installing dependencies'); - task.output = `${pm.executable} ${pm.install} ${importDeps.join(' ')}`; - await installDepList(pm, dir, importDeps); + d('installing dependencies'); + task.output = `${pm.executable} ${pm.install} ${importDeps.join(' ')}`; + await installDepList(pm, dir, importDeps); - d('installing devDependencies'); - task.output = `${pm.executable} ${pm.install} ${pm.dev} ${importDevDeps.join(' ')}`; - await installDepList(pm, dir, importDevDeps, DepType.DEV); + d('installing devDependencies'); + task.output = `${pm.executable} ${pm.install} ${pm.dev} ${importDevDeps.join(' ')}`; + await installDepList(pm, dir, importDevDeps, DepType.DEV); - d('installing devDependencies with exact versions'); - task.output = `${pm.executable} ${pm.install} ${pm.dev} ${pm.exact} ${importExactDevDeps.join(' ')}`; - await installDepList(pm, dir, importExactDevDeps, DepType.DEV, DepVersionRestriction.EXACT); + d('installing devDependencies with exact versions'); + task.output = `${pm.executable} ${pm.install} ${pm.dev} ${pm.exact} ${importExactDevDeps.join(' ')}`; + await installDepList( + pm, + dir, + importExactDevDeps, + DepType.DEV, + DepVersionRestriction.EXACT, + ); + }, }, - }, - { - title: 'Copying base template Forge configuration', - task: async () => { - const pathToTemplateConfig = path.resolve(baseTemplate.templateDir, 'forge.config.js'); + { + title: 'Copying base template Forge configuration', + task: async () => { + const pathToTemplateConfig = path.resolve( + baseTemplate.templateDir, + 'forge.config.js', + ); - // if there's an existing config.forge object in package.json - if (packageJSON?.config?.forge && typeof packageJSON.config.forge === 'object') { - d('detected existing Forge config in package.json, merging with base template Forge config'); - // eslint-disable-next-line @typescript-eslint/no-require-imports - const templateConfig = require(path.resolve(baseTemplate.templateDir, 'forge.config.js')); - packageJSON = await readRawPackageJson(dir); - merge(templateConfig, packageJSON.config.forge); // mutates the templateConfig object - await writeChanges(); - // otherwise, write to forge.config.js - } else { - d('writing new forge.config.js'); - await fs.copyFile(pathToTemplateConfig, path.resolve(dir, 'forge.config.js')); - } + // if there's an existing config.forge object in package.json + if ( + packageJSON?.config?.forge && + typeof packageJSON.config.forge === 'object' + ) { + d( + 'detected existing Forge config in package.json, merging with base template Forge config', + ); + // eslint-disable-next-line @typescript-eslint/no-require-imports + const templateConfig = require( + path.resolve( + baseTemplate.templateDir, + 'forge.config.js', + ), + ); + packageJSON = await readRawPackageJson(dir); + merge(templateConfig, packageJSON.config.forge); // mutates the templateConfig object + await writeChanges(); + // otherwise, write to forge.config.js + } else { + d('writing new forge.config.js'); + await fs.copyFile( + pathToTemplateConfig, + path.resolve(dir, 'forge.config.js'), + ); + } + }, }, - }, - { - title: 'Fixing .gitignore', - task: async () => { - if (await fs.pathExists(path.resolve(dir, '.gitignore'))) { - const gitignore = await fs.readFile(path.resolve(dir, '.gitignore')); - if (!gitignore.includes(calculatedOutDir)) { - await fs.writeFile(path.resolve(dir, '.gitignore'), `${gitignore}\n${calculatedOutDir}/`); + { + title: 'Fixing .gitignore', + task: async () => { + if ( + await fs.pathExists(path.resolve(dir, '.gitignore')) + ) { + const gitignore = await fs.readFile( + path.resolve(dir, '.gitignore'), + ); + if (!gitignore.includes(calculatedOutDir)) { + await fs.writeFile( + path.resolve(dir, '.gitignore'), + `${gitignore}\n${calculatedOutDir}/`, + ); + } } - } + }, }, - }, - ], - listrOptions - ); - }), + ], + listrOptions, + ); + }, + ), }, { title: 'Finalizing import', @@ -276,16 +370,19 @@ export default autoTrace( persistentOutput: true, bottomBar: Infinity, }, - task: childTrace>({ name: 'finalize-import', category: '@electron-forge/core' }, (_, __, task) => { - task.output = `We have attempted to convert your app to be in a format that Electron Forge understands. + task: childTrace>( + { name: 'finalize-import', category: '@electron-forge/core' }, + (_, __, task) => { + task.output = `We have attempted to convert your app to be in a format that Electron Forge understands. Thanks for using ${chalk.green('Electron Forge')}!`; - }), + }, + ), }, ], - listrOptions + listrOptions, ); await runner.run(); - } + }, ); diff --git a/packages/api/core/src/api/index.ts b/packages/api/core/src/api/index.ts index e1c5824cc2..5a358595ac 100644 --- a/packages/api/core/src/api/index.ts +++ b/packages/api/core/src/api/index.ts @@ -62,4 +62,16 @@ export class ForgeAPI { const api = new ForgeAPI(); const utils = new ForgeUtils(); -export { ForgeMakeResult, ElectronProcess, ForgeUtils, ImportOptions, InitOptions, MakeOptions, PackageOptions, PublishOptions, StartOptions, api, utils }; +export { + ForgeMakeResult, + ElectronProcess, + ForgeUtils, + ImportOptions, + InitOptions, + MakeOptions, + PackageOptions, + PublishOptions, + StartOptions, + api, + utils, +}; diff --git a/packages/api/core/src/api/init-scripts/find-template.ts b/packages/api/core/src/api/init-scripts/find-template.ts index e751c1c15a..a80473faf4 100644 --- a/packages/api/core/src/api/init-scripts/find-template.ts +++ b/packages/api/core/src/api/init-scripts/find-template.ts @@ -18,7 +18,9 @@ export interface ForgeTemplateDetails { type: TemplateType; } -export const findTemplate = async (template: string): Promise => { +export const findTemplate = async ( + template: string, +): Promise => { let foundTemplate: Omit | null = null; const resolveTemplateTypes = [ @@ -55,7 +57,9 @@ export const findTemplate = async (template: string): Promise = await import(foundTemplate.path); + const templateModule: PossibleModule = await import( + foundTemplate.path + ); const tmpl = templateModule.default ?? templateModule; return { diff --git a/packages/api/core/src/api/init-scripts/init-directory.ts b/packages/api/core/src/api/init-scripts/init-directory.ts index cc35bddf93..4ecf51d275 100644 --- a/packages/api/core/src/api/init-scripts/init-directory.ts +++ b/packages/api/core/src/api/init-scripts/init-directory.ts @@ -5,7 +5,11 @@ import logSymbols from 'log-symbols'; const d = debug('electron-forge:init:directory'); -export const initDirectory = async (dir: string, task: ForgeListrTask, force = false): Promise => { +export const initDirectory = async ( + dir: string, + task: ForgeListrTask, + force = false, +): Promise => { d('creating directory:', dir); await fs.mkdirs(dir); @@ -16,7 +20,9 @@ export const initDirectory = async (dir: string, task: ForgeListrTask, forc if (force) { task.output = `${logSymbols.warning} The specified path "${dir}" is not empty. "force" was set to true, so proceeding to initialize. Files may be overwritten`; } else { - throw new Error(`The specified path: "${dir}" is not empty. Please ensure it is empty before initializing a new project`); + throw new Error( + `The specified path: "${dir}" is not empty. Please ensure it is empty before initializing a new project`, + ); } } }; diff --git a/packages/api/core/src/api/init-scripts/init-git.ts b/packages/api/core/src/api/init-scripts/init-git.ts index 930de91430..849289066e 100644 --- a/packages/api/core/src/api/init-scripts/init-git.ts +++ b/packages/api/core/src/api/init-scripts/init-git.ts @@ -15,12 +15,14 @@ export const initGit = async (dir: string): Promise => { if (err) { // not run within a Git repository d('executing "git init" in directory:', dir); - exec('git init', { cwd: dir }, (initErr) => (initErr ? reject(initErr) : resolve())); + exec('git init', { cwd: dir }, (initErr) => + initErr ? reject(initErr) : resolve(), + ); } else { d('.git directory already exists, skipping git initialization'); resolve(); } - } + }, ); }); }; diff --git a/packages/api/core/src/api/init-scripts/init-link.ts b/packages/api/core/src/api/init-scripts/init-link.ts index e24cfd8e23..92fc1b9408 100644 --- a/packages/api/core/src/api/init-scripts/init-link.ts +++ b/packages/api/core/src/api/init-scripts/init-link.ts @@ -18,7 +18,11 @@ const d = debug('electron-forge:init:link'); * Note: `yarn link:prepare` needs to run first before dependencies can be * linked. */ -export async function initLink(pm: PMDetails, dir: string, task?: ForgeListrTask) { +export async function initLink( + pm: PMDetails, + dir: string, + task?: ForgeListrTask, +) { const shouldLink = process.env.LINK_FORGE_DEPENDENCIES_ON_INIT; if (shouldLink) { d('Linking forge dependencies'); @@ -26,16 +30,33 @@ export async function initLink(pm: PMDetails, dir: string, task?: ForgeListrT // TODO(erickzhao): the `--link-folder` argument only works for `yarn`. Since this command is // only made for Forge contributors, it isn't a big deal if it doesn't work for other package managers, // but we should make it cleaner. - const linkFolder = path.resolve(__dirname, '..', '..', '..', '..', '..', '..', '.links'); + const linkFolder = path.resolve( + __dirname, + '..', + '..', + '..', + '..', + '..', + '..', + '.links', + ); for (const packageName of Object.keys(packageJson.devDependencies)) { if (packageName.startsWith('@electron-forge/')) { - if (task) task.output = `${pm.executable} link --link-folder ${linkFolder} ${packageName}`; - await spawnPackageManager(pm, ['link', '--link-folder', linkFolder, packageName], { - cwd: dir, - }); + if (task) + task.output = `${pm.executable} link --link-folder ${linkFolder} ${packageName}`; + await spawnPackageManager( + pm, + ['link', '--link-folder', linkFolder, packageName], + { + cwd: dir, + }, + ); } } - await fs.promises.chmod(path.resolve(dir, 'node_modules', '.bin', 'electron-forge'), 0o755); + await fs.promises.chmod( + path.resolve(dir, 'node_modules', '.bin', 'electron-forge'), + 0o755, + ); } else { d('LINK_FORGE_DEPENDENCIES_ON_INIT is falsy. Skipping.'); } diff --git a/packages/api/core/src/api/init-scripts/init-npm.ts b/packages/api/core/src/api/init-scripts/init-npm.ts index d2c0368765..1c51f29274 100644 --- a/packages/api/core/src/api/init-scripts/init-npm.ts +++ b/packages/api/core/src/api/init-scripts/init-npm.ts @@ -5,10 +5,15 @@ import { ForgeListrTask } from '@electron-forge/shared-types'; import debug from 'debug'; import fs from 'fs-extra'; -import installDepList, { DepType, DepVersionRestriction } from '../../util/install-dependencies'; +import installDepList, { + DepType, + DepVersionRestriction, +} from '../../util/install-dependencies'; const d = debug('electron-forge:init:npm'); -const corePackage = fs.readJsonSync(path.resolve(__dirname, '../../../package.json')); +const corePackage = fs.readJsonSync( + path.resolve(__dirname, '../../../package.json'), +); export function siblingDep(name: string): string { return `@electron-forge/${name}@^${corePackage.version}`; @@ -27,7 +32,11 @@ export const devDeps = [ ]; export const exactDevDeps = ['electron']; -export const initNPM = async (pm: PMDetails, dir: string, task: ForgeListrTask): Promise => { +export const initNPM = async ( + pm: PMDetails, + dir: string, + task: ForgeListrTask, +): Promise => { d('installing dependencies'); task.output = `${pm.executable} ${pm.install} ${deps.join(' ')}`; await installDepList(pm, dir, deps); @@ -39,6 +48,12 @@ export const initNPM = async (pm: PMDetails, dir: string, task: ForgeListrTas d('installing exact devDependencies'); for (const packageName of exactDevDeps) { task.output = `${pm.executable} ${pm.install} ${pm.dev} ${pm.exact} ${packageName}`; - await installDepList(pm, dir, [packageName], DepType.DEV, DepVersionRestriction.EXACT); + await installDepList( + pm, + dir, + [packageName], + DepType.DEV, + DepVersionRestriction.EXACT, + ); } }; diff --git a/packages/api/core/src/api/init.ts b/packages/api/core/src/api/init.ts index ce00a7565d..eb532f4c57 100644 --- a/packages/api/core/src/api/init.ts +++ b/packages/api/core/src/api/init.ts @@ -7,7 +7,10 @@ import debug from 'debug'; import { Listr } from 'listr2'; import semver from 'semver'; -import installDepList, { DepType, DepVersionRestriction } from '../util/install-dependencies'; +import installDepList, { + DepType, + DepVersionRestriction, +} from '../util/install-dependencies'; import { readRawPackageJson } from '../util/read-package-json'; import { findTemplate } from './init-scripts/find-template'; @@ -45,15 +48,22 @@ export interface InitOptions { skipGit?: boolean; } -async function validateTemplate(template: string, templateModule: ForgeTemplate): Promise { +async function validateTemplate( + template: string, + templateModule: ForgeTemplate, +): Promise { if (!templateModule.requiredForgeVersion) { - throw new Error(`Cannot use a template (${template}) with this version of Electron Forge, as it does not specify its required Forge version.`); + throw new Error( + `Cannot use a template (${template}) with this version of Electron Forge, as it does not specify its required Forge version.`, + ); } - const forgeVersion = (await readRawPackageJson(path.join(__dirname, '..', '..'))).version; + const forgeVersion = ( + await readRawPackageJson(path.join(__dirname, '..', '..')) + ).version; if (!semver.satisfies(forgeVersion, templateModule.requiredForgeVersion)) { throw new Error( - `Template (${template}) is not compatible with this version of Electron Forge (${forgeVersion}), it requires ${templateModule.requiredForgeVersion}` + `Template (${template}) is not compatible with this version of Electron Forge (${forgeVersion}), it requires ${templateModule.requiredForgeVersion}`, ); } } @@ -113,7 +123,10 @@ export default async ({ title: `Initializing template`, task: async ({ templateModule }, task) => { if (typeof templateModule.initializeTemplate === 'function') { - const tasks = await templateModule.initializeTemplate(dir, { copyCIFiles, force }); + const tasks = await templateModule.initializeTemplate(dir, { + copyCIFiles, + force, + }); if (tasks) { return task.newListr(tasks, { concurrent: false }); } @@ -132,7 +145,13 @@ export default async ({ if (templateModule.dependencies?.length) { task.output = `${pm.executable} ${pm.install} ${pm.dev} ${templateModule.dependencies.join(' ')}`; } - return await installDepList(pm, dir, templateModule.dependencies || [], DepType.PROD, DepVersionRestriction.RANGE); + return await installDepList( + pm, + dir, + templateModule.dependencies || [], + DepType.PROD, + DepVersionRestriction.RANGE, + ); }, exitOnError: false, }, @@ -143,7 +162,12 @@ export default async ({ if (templateModule.devDependencies?.length) { task.output = `${pm.executable} ${pm.install} ${pm.dev} ${templateModule.devDependencies.join(' ')}`; } - await installDepList(pm, dir, templateModule.devDependencies || [], DepType.DEV); + await installDepList( + pm, + dir, + templateModule.devDependencies || [], + DepType.DEV, + ); }, exitOnError: false, }, @@ -172,7 +196,7 @@ export default async ({ ], { concurrent: false, - } + }, ); }, }, @@ -180,8 +204,9 @@ export default async ({ { concurrent: false, silentRendererCondition: !interactive, - fallbackRendererCondition: Boolean(process.env.DEBUG) || Boolean(process.env.CI), - } + fallbackRendererCondition: + Boolean(process.env.DEBUG) || Boolean(process.env.CI), + }, ); await runner.run(); diff --git a/packages/api/core/src/api/make.ts b/packages/api/core/src/api/make.ts index f76650e67b..f9f17bb623 100644 --- a/packages/api/core/src/api/make.ts +++ b/packages/api/core/src/api/make.ts @@ -37,11 +37,18 @@ type MakerImpl = { type MakeTargets = ForgeConfigMaker[] | string[]; -function generateTargets(forgeConfig: ResolvedForgeConfig, overrideTargets?: MakeTargets) { +function generateTargets( + forgeConfig: ResolvedForgeConfig, + overrideTargets?: MakeTargets, +) { if (overrideTargets) { return overrideTargets.map((target) => { if (typeof target === 'string') { - return forgeConfig.makers.find((maker) => (maker as IForgeResolvableMaker).name === target) || ({ name: target } as IForgeResolvableMaker); + return ( + forgeConfig.makers.find( + (maker) => (maker as IForgeResolvableMaker).name === target, + ) || ({ name: target } as IForgeResolvableMaker) + ); } return target; @@ -51,7 +58,9 @@ function generateTargets(forgeConfig: ResolvedForgeConfig, overrideTargets?: Mak } // eslint-disable-next-line @typescript-eslint/no-explicit-any -function isElectronForgeMaker(target: MakerBase | unknown): target is MakerBase { +function isElectronForgeMaker( + target: MakerBase | unknown, +): target is MakerBase { // eslint-disable-next-line @typescript-eslint/no-explicit-any return (target as MakerBase).__isElectronForgeMaker; } @@ -106,7 +115,7 @@ export const listrMake = ( overrideTargets, outDir, }: MakeOptions, - receiveMakeResults?: (results: ForgeMakeResult[]) => void + receiveMakeResults?: (results: ForgeMakeResult[]) => void, ) => { const listrOptions: ForgeListrOptions = { concurrent: false, @@ -115,22 +124,28 @@ export const listrMake = ( collapseErrors: false, }, silentRendererCondition: !interactive, - fallbackRendererCondition: Boolean(process.env.DEBUG) || Boolean(process.env.CI), + fallbackRendererCondition: + Boolean(process.env.DEBUG) || Boolean(process.env.CI), }; const runner = new Listr( [ { title: 'Loading configuration', - task: childTrace>>({ name: 'load-forge-config', category: '@electron-forge/core' }, async (_, ctx) => { - const resolvedDir = await resolveDir(providedDir); - if (!resolvedDir) { - throw new Error('Failed to locate startable Electron application'); - } - - ctx.dir = resolvedDir; - ctx.forgeConfig = await getForgeConfig(resolvedDir); - }), + task: childTrace>>( + { name: 'load-forge-config', category: '@electron-forge/core' }, + async (_, ctx) => { + const resolvedDir = await resolveDir(providedDir); + if (!resolvedDir) { + throw new Error( + 'Failed to locate startable Electron application', + ); + } + + ctx.dir = resolvedDir; + ctx.forgeConfig = await getForgeConfig(resolvedDir); + }, + ), }, { title: 'Resolving make targets', @@ -141,13 +156,18 @@ export const listrMake = ( ctx.actualOutDir = outDir || getCurrentOutDir(dir, forgeConfig); if (!['darwin', 'win32', 'linux', 'mas'].includes(platform)) { - throw new Error(`'${platform}' is an invalid platform. Choices are 'darwin', 'mas', 'win32' or 'linux'.`); + throw new Error( + `'${platform}' is an invalid platform. Choices are 'darwin', 'mas', 'win32' or 'linux'.`, + ); } // eslint-disable-next-line @typescript-eslint/no-explicit-any const makers: Array<() => MakerBase> = []; - const possibleMakers = generateTargets(forgeConfig, overrideTargets); + const possibleMakers = generateTargets( + forgeConfig, + overrideTargets, + ); for (const possibleMaker of possibleMakers) { /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ @@ -161,19 +181,28 @@ export const listrMake = ( if (resolvableTarget.enabled === false) continue; if (!resolvableTarget.name) { - throw new Error(`The following maker config is missing a maker name: ${JSON.stringify(resolvableTarget)}`); + throw new Error( + `The following maker config is missing a maker name: ${JSON.stringify(resolvableTarget)}`, + ); } else if (typeof resolvableTarget.name !== 'string') { - throw new Error(`The following maker config has a maker name that is not a string: ${JSON.stringify(resolvableTarget)}`); + throw new Error( + `The following maker config has a maker name that is not a string: ${JSON.stringify(resolvableTarget)}`, + ); } - const MakerClass = await importSearch(dir, [resolvableTarget.name]); + const MakerClass = await importSearch(dir, [ + resolvableTarget.name, + ]); if (!MakerClass) { throw new Error( - `Could not find module with name '${resolvableTarget.name}'. If this is a package from NPM, make sure it's listed in the devDependencies of your package.json. If this is a local module, make sure you have the correct path to its entry point. Try using the DEBUG="electron-forge:require-search" environment variable for more information.` + `Could not find module with name '${resolvableTarget.name}'. If this is a package from NPM, make sure it's listed in the devDependencies of your package.json. If this is a local module, make sure you have the correct path to its entry point. Try using the DEBUG="electron-forge:require-search" environment variable for more information.`, ); } - maker = new MakerClass(resolvableTarget.config, resolvableTarget.platforms || undefined); + maker = new MakerClass( + resolvableTarget.config, + resolvableTarget.platforms || undefined, + ); if (!maker.platforms.includes(platform)) continue; } @@ -183,12 +212,14 @@ export const listrMake = ( `Maker for target ${maker.name} is incompatible with this version of `, 'Electron Forge, please upgrade or contact the maintainer ', "(needs to implement 'isSupportedOnCurrentPlatform)')", - ].join('') + ].join(''), ); } if (!maker.isSupportedOnCurrentPlatform()) { - throw new Error(`Cannot make for ${platform} and target ${maker.name}: the maker declared that it cannot run on ${process.platform}.`); + throw new Error( + `Cannot make for ${platform} and target ${maker.name}: the maker declared that it cannot run on ${process.platform}.`, + ); } maker.ensureExternalBinariesExist(); @@ -197,13 +228,15 @@ export const listrMake = ( } if (makers.length === 0) { - throw new Error(`Could not find any make targets configured for the "${platform}" platform.`); + throw new Error( + `Could not find any make targets configured for the "${platform}" platform.`, + ); } ctx.makers = makers; task.output = `Making for the following targets: ${chalk.magenta(`${makers.map((maker) => maker.name).join(', ')}`)}`; - } + }, ), rendererOptions: { persistentOutput: true, @@ -211,24 +244,29 @@ export const listrMake = ( }, { title: `Running ${chalk.yellow('package')} command`, - task: childTrace>>({ name: 'package()', category: '@electron-forge/core' }, async (childTrace, ctx, task) => { - if (!skipPackage) { - return delayTraceTillSignal( - childTrace, - listrPackage(childTrace, { - dir: ctx.dir, - interactive, - arch, - outDir: ctx.actualOutDir, - platform, - }), - 'run' - ); - } else { - task.output = chalk.yellow(`${logSymbols.warning} Skipping could result in an out of date build`); - task.skip(); - } - }), + task: childTrace>>( + { name: 'package()', category: '@electron-forge/core' }, + async (childTrace, ctx, task) => { + if (!skipPackage) { + return delayTraceTillSignal( + childTrace, + listrPackage(childTrace, { + dir: ctx.dir, + interactive, + arch, + outDir: ctx.actualOutDir, + platform, + }), + 'run', + ); + } else { + task.output = chalk.yellow( + `${logSymbols.warning} Skipping could result in an out of date build`, + ); + task.skip(); + } + }, + ), rendererOptions: { persistentOutput: true, }, @@ -238,8 +276,14 @@ export const listrMake = ( task: childTrace>>( { name: 'run-preMake-hook', category: '@electron-forge/core' }, async (childTrace, ctx, task) => { - return delayTraceTillSignal(childTrace, task.newListr(await getHookListrTasks(childTrace, ctx.forgeConfig, 'preMake')), 'run'); - } + return delayTraceTillSignal( + childTrace, + task.newListr( + await getHookListrTasks(childTrace, ctx.forgeConfig, 'preMake'), + ), + 'run', + ); + }, ), }, { @@ -249,7 +293,12 @@ export const listrMake = ( async (childTrace, ctx, task) => { const { actualOutDir, dir, forgeConfig, makers } = ctx; const packageJSON = await readMutatedPackageJson(dir, forgeConfig); - const appName = filenamify(forgeConfig.packagerConfig.name || packageJSON.productName || packageJSON.name, { replacement: '-' }); + const appName = filenamify( + forgeConfig.packagerConfig.name || + packageJSON.productName || + packageJSON.name, + { replacement: '-' }, + ); const outputs: ForgeMakeResult[] = []; ctx.outputs = outputs; @@ -262,8 +311,15 @@ export const listrMake = ( }, }); - for (const targetArch of parseArchs(platform, arch, await getElectronVersion(dir, packageJSON))) { - const packageDir = path.resolve(actualOutDir, `${appName}-${platform}-${targetArch}`); + for (const targetArch of parseArchs( + platform, + arch, + await getElectronVersion(dir, packageJSON), + )) { + const packageDir = path.resolve( + actualOutDir, + `${appName}-${platform}-${targetArch}`, + ); if (!(await fs.pathExists(packageDir))) { throw new Error(`Couldn't find packaged app at: ${packageDir}`); } @@ -272,35 +328,46 @@ export const listrMake = ( const uniqMaker = maker(); subRunner.add({ title: `Making a ${chalk.magenta(uniqMaker.name)} distributable for ${chalk.cyan(`${platform}/${targetArch}`)}`, - task: childTrace<[]>({ name: `make-${maker.name}`, category: '@electron-forge/core', newRoot: true }, async () => { - try { - await Promise.resolve(uniqMaker.prepareConfig(targetArch)); - const artifacts = await uniqMaker.make({ - appName, - forgeConfig, - packageJSON, - targetArch, - dir: packageDir, - makeDir: path.resolve(actualOutDir, 'make'), - targetPlatform: platform, - }); - - outputs.push({ - artifacts, - packageJSON, - platform, - arch: targetArch, - }); - } catch (err) { - if (err instanceof Error) { - throw err; - } else if (typeof err === 'string') { - throw new Error(err); - } else { - throw new Error(`An unknown error occurred while making for target: ${uniqMaker.name}`); + task: childTrace<[]>( + { + name: `make-${maker.name}`, + category: '@electron-forge/core', + newRoot: true, + }, + async () => { + try { + await Promise.resolve( + uniqMaker.prepareConfig(targetArch), + ); + const artifacts = await uniqMaker.make({ + appName, + forgeConfig, + packageJSON, + targetArch, + dir: packageDir, + makeDir: path.resolve(actualOutDir, 'make'), + targetPlatform: platform, + }); + + outputs.push({ + artifacts, + packageJSON, + platform, + arch: targetArch, + }); + } catch (err) { + if (err instanceof Error) { + throw err; + } else if (typeof err === 'string') { + throw new Error(err); + } else { + throw new Error( + `An unknown error occurred while making for target: ${uniqMaker.name}`, + ); + } } - } - }), + }, + ), rendererOptions: { timer: { ...PRESET_TIMER }, }, @@ -309,37 +376,44 @@ export const listrMake = ( } return delayTraceTillSignal(childTrace, subRunner, 'run'); - } + }, ), }, { title: `Running ${chalk.yellow('postMake')} hook`, - task: childTrace>>({ name: 'run-postMake-hook', category: '@electron-forge/core' }, async (_, ctx, task) => { - // If the postMake hooks modifies the locations / names of the outputs it must return - // the new locations so that the publish step knows where to look - const originalOutputs = JSON.stringify(ctx.outputs); - ctx.outputs = await runMutatingHook(ctx.forgeConfig, 'postMake', ctx.outputs); - - let outputLocations = [path.resolve(ctx.actualOutDir, 'make')]; - if (originalOutputs !== JSON.stringify(ctx.outputs)) { - const newDirs = new Set(); - const artifactPaths = []; - for (const result of ctx.outputs) { - for (const artifact of result.artifacts) { - newDirs.add(path.dirname(artifact)); - artifactPaths.push(artifact); + task: childTrace>>( + { name: 'run-postMake-hook', category: '@electron-forge/core' }, + async (_, ctx, task) => { + // If the postMake hooks modifies the locations / names of the outputs it must return + // the new locations so that the publish step knows where to look + const originalOutputs = JSON.stringify(ctx.outputs); + ctx.outputs = await runMutatingHook( + ctx.forgeConfig, + 'postMake', + ctx.outputs, + ); + + let outputLocations = [path.resolve(ctx.actualOutDir, 'make')]; + if (originalOutputs !== JSON.stringify(ctx.outputs)) { + const newDirs = new Set(); + const artifactPaths = []; + for (const result of ctx.outputs) { + for (const artifact of result.artifacts) { + newDirs.add(path.dirname(artifact)); + artifactPaths.push(artifact); + } + } + if (newDirs.size <= ctx.outputs.length) { + outputLocations = [...newDirs]; + } else { + outputLocations = artifactPaths; } } - if (newDirs.size <= ctx.outputs.length) { - outputLocations = [...newDirs]; - } else { - outputLocations = artifactPaths; - } - } - receiveMakeResults?.(ctx.outputs); + receiveMakeResults?.(ctx.outputs); - task.output = `Artifacts available at: ${chalk.green(outputLocations.join(', '))}`; - }), + task.output = `Artifacts available at: ${chalk.green(outputLocations.join(', '))}`; + }, + ), rendererOptions: { persistentOutput: true, }, @@ -348,16 +422,19 @@ export const listrMake = ( { ...listrOptions, ctx: {} as MakeContext, - } + }, ); return runner; }; -export default autoTrace({ name: 'make()', category: '@electron-forge/core' }, async (childTrace, opts: MakeOptions): Promise => { - const runner = listrMake(childTrace, opts); +export default autoTrace( + { name: 'make()', category: '@electron-forge/core' }, + async (childTrace, opts: MakeOptions): Promise => { + const runner = listrMake(childTrace, opts); - await runner.run(); + await runner.run(); - return runner.ctx.outputs; -}); + return runner.ctx.outputs; + }, +); diff --git a/packages/api/core/src/api/package.ts b/packages/api/core/src/api/package.ts index 5805f3d6cc..0dd30927c1 100644 --- a/packages/api/core/src/api/package.ts +++ b/packages/api/core/src/api/package.ts @@ -2,9 +2,25 @@ import path from 'node:path'; import { promisify } from 'node:util'; import { getHostArch } from '@electron/get'; -import { FinalizePackageTargetsHookFunction, HookFunction, Options, packager, TargetDefinition } from '@electron/packager'; -import { getElectronVersion, listrCompatibleRebuildHook } from '@electron-forge/core-utils'; -import { ForgeArch, ForgeListrTask, ForgeListrTaskDefinition, ForgeListrTaskFn, ForgePlatform, ResolvedForgeConfig } from '@electron-forge/shared-types'; +import { + FinalizePackageTargetsHookFunction, + HookFunction, + Options, + packager, + TargetDefinition, +} from '@electron/packager'; +import { + getElectronVersion, + listrCompatibleRebuildHook, +} from '@electron-forge/core-utils'; +import { + ForgeArch, + ForgeListrTask, + ForgeListrTaskDefinition, + ForgeListrTaskFn, + ForgePlatform, + ResolvedForgeConfig, +} from '@electron-forge/shared-types'; import { autoTrace, delayTraceTillSignal } from '@electron-forge/tracer'; import chalk from 'chalk'; import debug from 'debug'; @@ -25,22 +41,40 @@ const d = debug('electron-forge:packager'); /** * Resolves hooks if they are a path to a file (instead of a `Function`). */ -async function resolveHooks(hooks: (string | F)[] | undefined, dir: string) { +async function resolveHooks( + hooks: (string | F)[] | undefined, + dir: string, +) { if (hooks) { - return await Promise.all(hooks.map(async (hook) => (typeof hook === 'string' ? ((await importSearch(dir, [hook])) as F) : hook))); + return await Promise.all( + hooks.map(async (hook) => + typeof hook === 'string' + ? ((await importSearch(dir, [hook])) as F) + : hook, + ), + ); } return []; } type DoneFunction = (err?: Error) => void; -type PromisifiedHookFunction = (buildPath: string, electronVersion: string, platform: string, arch: string) => Promise; -type PromisifiedFinalizePackageTargetsHookFunction = (targets: TargetDefinition[]) => Promise; +type PromisifiedHookFunction = ( + buildPath: string, + electronVersion: string, + platform: string, + arch: string, +) => Promise; +type PromisifiedFinalizePackageTargetsHookFunction = ( + targets: TargetDefinition[], +) => Promise; /** * @deprecated Only use until \@electron/packager publishes a new major version with promise based hooks */ -function hidePromiseFromPromisify

(fn: (...args: P) => Promise): (...args: P) => void { +function hidePromiseFromPromisify

( + fn: (...args: P) => Promise, +): (...args: P) => void { return (...args: P) => { void fn(...args); }; @@ -52,31 +86,43 @@ function hidePromiseFromPromisify

(fn: (...args: P) => Promi */ function sequentialHooks(hooks: HookFunction[]): PromisifiedHookFunction[] { return [ - hidePromiseFromPromisify(async (buildPath: string, electronVersion: string, platform: string, arch: string, done: DoneFunction) => { - for (const hook of hooks) { - try { - await promisify(hook)(buildPath, electronVersion, platform, arch); - } catch (err) { - d('hook failed:', hook.toString(), err); - return done(err as Error); + hidePromiseFromPromisify( + async ( + buildPath: string, + electronVersion: string, + platform: string, + arch: string, + done: DoneFunction, + ) => { + for (const hook of hooks) { + try { + await promisify(hook)(buildPath, electronVersion, platform, arch); + } catch (err) { + d('hook failed:', hook.toString(), err); + return done(err as Error); + } } - } - done(); - }), + done(); + }, + ), ] as PromisifiedHookFunction[]; } -function sequentialFinalizePackageTargetsHooks(hooks: FinalizePackageTargetsHookFunction[]): PromisifiedFinalizePackageTargetsHookFunction[] { +function sequentialFinalizePackageTargetsHooks( + hooks: FinalizePackageTargetsHookFunction[], +): PromisifiedFinalizePackageTargetsHookFunction[] { return [ - hidePromiseFromPromisify(async (targets: TargetDefinition[], done: DoneFunction) => { - for (const hook of hooks) { - try { - await promisify(hook)(targets); - } catch (err) { - return done(err as Error); + hidePromiseFromPromisify( + async (targets: TargetDefinition[], done: DoneFunction) => { + for (const hook of hooks) { + try { + await promisify(hook)(targets); + } catch (err) { + return done(err as Error); + } } - } - done(); - }), + done(); + }, + ), ] as PromisifiedFinalizePackageTargetsHookFunction[]; } @@ -128,28 +174,39 @@ export const listrPackage = ( arch = getHostArch() as ForgeArch, platform = process.platform as ForgePlatform, outDir, - }: PackageOptions + }: PackageOptions, ) => { const runner = new Listr( [ { title: 'Preparing to package application', - task: childTrace>>({ name: 'package-prepare', category: '@electron-forge/core' }, async (_, ctx) => { - const resolvedDir = await resolveDir(providedDir); - if (!resolvedDir) { - throw new Error('Failed to locate compilable Electron application'); - } - ctx.dir = resolvedDir; + task: childTrace>>( + { name: 'package-prepare', category: '@electron-forge/core' }, + async (_, ctx) => { + const resolvedDir = await resolveDir(providedDir); + if (!resolvedDir) { + throw new Error( + 'Failed to locate compilable Electron application', + ); + } + ctx.dir = resolvedDir; - ctx.forgeConfig = await getForgeConfig(resolvedDir); - ctx.packageJSON = await readMutatedPackageJson(resolvedDir, ctx.forgeConfig); + ctx.forgeConfig = await getForgeConfig(resolvedDir); + ctx.packageJSON = await readMutatedPackageJson( + resolvedDir, + ctx.forgeConfig, + ); - if (!ctx.packageJSON.main) { - throw new Error('packageJSON.main must be set to a valid entry point for your Electron app'); - } + if (!ctx.packageJSON.main) { + throw new Error( + 'packageJSON.main must be set to a valid entry point for your Electron app', + ); + } - ctx.calculatedOutDir = outDir || getCurrentOutDir(resolvedDir, ctx.forgeConfig); - }), + ctx.calculatedOutDir = + outDir || getCurrentOutDir(resolvedDir, ctx.forgeConfig); + }, + ), }, { title: 'Running packaging hooks', @@ -162,33 +219,55 @@ export const listrPackage = ( { title: `Running ${chalk.yellow('generateAssets')} hook`, task: childTrace>( - { name: 'run-generateAssets-hook', category: '@electron-forge/core' }, + { + name: 'run-generateAssets-hook', + category: '@electron-forge/core', + }, async (childTrace, _, task) => { return delayTraceTillSignal( childTrace, - task.newListr(await getHookListrTasks(childTrace, forgeConfig, 'generateAssets', platform, arch)), - 'run' + task.newListr( + await getHookListrTasks( + childTrace, + forgeConfig, + 'generateAssets', + platform, + arch, + ), + ), + 'run', ); - } + }, ), }, { title: `Running ${chalk.yellow('prePackage')} hook`, task: childTrace>( - { name: 'run-prePackage-hook', category: '@electron-forge/core' }, + { + name: 'run-prePackage-hook', + category: '@electron-forge/core', + }, async (childTrace, _, task) => { return delayTraceTillSignal( childTrace, - task.newListr(await getHookListrTasks(childTrace, forgeConfig, 'prePackage', platform, arch)), - 'run' + task.newListr( + await getHookListrTasks( + childTrace, + forgeConfig, + 'prePackage', + platform, + arch, + ), + ), + 'run', ); - } + }, ), }, ]), - 'run' + 'run', ); - } + }, ), }, { @@ -197,7 +276,8 @@ export const listrPackage = ( { name: 'packaging-application', category: '@electron-forge/core' }, async (childTrace, ctx, task) => { const { calculatedOutDir, forgeConfig, packageJSON } = ctx; - const getTargetKey = (target: TargetDefinition) => `${target.platform}/${target.arch}`; + const getTargetKey = (target: TargetDefinition) => + `${target.platform}/${target.arch}`; task.output = 'Determining targets...'; @@ -206,104 +286,195 @@ export const listrPackage = ( const signalRebuildDone: StepDoneSignalMap = new Map(); const signalPackageDone: StepDoneSignalMap = new Map(); const rejects: ((err: any) => void)[] = []; - const signalDone = (map: StepDoneSignalMap, target: TargetDefinition) => { + const signalDone = ( + map: StepDoneSignalMap, + target: TargetDefinition, + ) => { map.get(getTargetKey(target))?.pop()?.(); }; - const addSignalAndWait = async (map: StepDoneSignalMap, target: TargetDefinition) => { + const addSignalAndWait = async ( + map: StepDoneSignalMap, + target: TargetDefinition, + ) => { const targetKey = getTargetKey(target); await new Promise((resolve, reject) => { rejects.push(reject); - map.set(targetKey, (map.get(targetKey) || []).concat([resolve])); + map.set( + targetKey, + (map.get(targetKey) || []).concat([resolve]), + ); }); }; let provideTargets: (targets: TargetDefinition[]) => void; - const targetsPromise = new Promise((resolve, reject) => { - provideTargets = resolve; - rejects.push(reject); - }); - - const rebuildTasks = new Map>[]>(); - const signalRebuildStart = new Map) => void)[]>(); - const afterFinalizePackageTargetsHooks: FinalizePackageTargetsHookFunction[] = [ - (targets, done) => { - provideTargets(targets); - done(); + const targetsPromise = new Promise( + (resolve, reject) => { + provideTargets = resolve; + rejects.push(reject); }, - ...(await resolveHooks(forgeConfig.packagerConfig.afterFinalizePackageTargets, ctx.dir)), - ]; + ); + + const rebuildTasks = new Map< + string, + Promise>[] + >(); + const signalRebuildStart = new Map< + string, + ((task: ForgeListrTask) => void)[] + >(); + const afterFinalizePackageTargetsHooks: FinalizePackageTargetsHookFunction[] = + [ + (targets, done) => { + provideTargets(targets); + done(); + }, + ...(await resolveHooks( + forgeConfig.packagerConfig.afterFinalizePackageTargets, + ctx.dir, + )), + ]; - const pruneEnabled = !('prune' in forgeConfig.packagerConfig) || forgeConfig.packagerConfig.prune; + const pruneEnabled = + !('prune' in forgeConfig.packagerConfig) || + forgeConfig.packagerConfig.prune; const afterCopyHooks: HookFunction[] = [ - hidePromiseFromPromisify(async (buildPath, electronVersion, platform, arch, done) => { - signalDone(signalCopyDone, { platform, arch }); - done(); - }), - hidePromiseFromPromisify(async (buildPath, electronVersion, pPlatform, pArch, done) => { - const bins = await glob(path.join(buildPath, '**/.bin/**/*')); - for (const bin of bins) { - await fs.remove(bin); - } - done(); - }), - hidePromiseFromPromisify(async (buildPath, electronVersion, pPlatform, pArch, done) => { - await runHook(forgeConfig, 'packageAfterCopy', buildPath, electronVersion, pPlatform, pArch); - done(); - }), - hidePromiseFromPromisify(async (buildPath, electronVersion, pPlatform, pArch, done) => { - const targetKey = getTargetKey({ platform: pPlatform, arch: pArch }); - await listrCompatibleRebuildHook( - buildPath, - electronVersion, - pPlatform, - pArch, - forgeConfig.rebuildConfig, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await rebuildTasks.get(targetKey)!.pop()! - ); - signalRebuildDone.get(targetKey)?.pop()?.(); - done(); - }), - hidePromiseFromPromisify(async (buildPath, electronVersion, pPlatform, pArch, done) => { - const copiedPackageJSON = await readMutatedPackageJson(buildPath, forgeConfig); - if (copiedPackageJSON.config && copiedPackageJSON.config.forge) { - delete copiedPackageJSON.config.forge; - } - await fs.writeJson(path.resolve(buildPath, 'package.json'), copiedPackageJSON, { spaces: 2 }); - done(); - }), - ...(await resolveHooks(forgeConfig.packagerConfig.afterCopy, ctx.dir)), + hidePromiseFromPromisify( + async (buildPath, electronVersion, platform, arch, done) => { + signalDone(signalCopyDone, { platform, arch }); + done(); + }, + ), + hidePromiseFromPromisify( + async (buildPath, electronVersion, pPlatform, pArch, done) => { + const bins = await glob(path.join(buildPath, '**/.bin/**/*')); + for (const bin of bins) { + await fs.remove(bin); + } + done(); + }, + ), + hidePromiseFromPromisify( + async (buildPath, electronVersion, pPlatform, pArch, done) => { + await runHook( + forgeConfig, + 'packageAfterCopy', + buildPath, + electronVersion, + pPlatform, + pArch, + ); + done(); + }, + ), + hidePromiseFromPromisify( + async (buildPath, electronVersion, pPlatform, pArch, done) => { + const targetKey = getTargetKey({ + platform: pPlatform, + arch: pArch, + }); + await listrCompatibleRebuildHook( + buildPath, + electronVersion, + pPlatform, + pArch, + forgeConfig.rebuildConfig, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await rebuildTasks.get(targetKey)!.pop()!, + ); + signalRebuildDone.get(targetKey)?.pop()?.(); + done(); + }, + ), + hidePromiseFromPromisify( + async (buildPath, electronVersion, pPlatform, pArch, done) => { + const copiedPackageJSON = await readMutatedPackageJson( + buildPath, + forgeConfig, + ); + if ( + copiedPackageJSON.config && + copiedPackageJSON.config.forge + ) { + delete copiedPackageJSON.config.forge; + } + await fs.writeJson( + path.resolve(buildPath, 'package.json'), + copiedPackageJSON, + { spaces: 2 }, + ); + done(); + }, + ), + ...(await resolveHooks( + forgeConfig.packagerConfig.afterCopy, + ctx.dir, + )), ]; const afterCompleteHooks: HookFunction[] = [ - hidePromiseFromPromisify(async (buildPath, electronVersion, pPlatform, pArch, done) => { - signalPackageDone.get(getTargetKey({ platform: pPlatform, arch: pArch }))?.pop()?.(); - done(); - }), - ...(await resolveHooks(forgeConfig.packagerConfig.afterComplete, ctx.dir)), + hidePromiseFromPromisify( + async (buildPath, electronVersion, pPlatform, pArch, done) => { + signalPackageDone + .get(getTargetKey({ platform: pPlatform, arch: pArch })) + ?.pop()?.(); + done(); + }, + ), + ...(await resolveHooks( + forgeConfig.packagerConfig.afterComplete, + ctx.dir, + )), ]; const afterPruneHooks = []; if (pruneEnabled) { - afterPruneHooks.push(...(await resolveHooks(forgeConfig.packagerConfig.afterPrune, ctx.dir))); + afterPruneHooks.push( + ...(await resolveHooks( + forgeConfig.packagerConfig.afterPrune, + ctx.dir, + )), + ); } afterPruneHooks.push( - hidePromiseFromPromisify(async (buildPath, electronVersion, pPlatform, pArch, done) => { - await runHook(forgeConfig, 'packageAfterPrune', buildPath, electronVersion, pPlatform, pArch); - done(); - }) as HookFunction + hidePromiseFromPromisify( + async (buildPath, electronVersion, pPlatform, pArch, done) => { + await runHook( + forgeConfig, + 'packageAfterPrune', + buildPath, + electronVersion, + pPlatform, + pArch, + ); + done(); + }, + ) as HookFunction, ); const afterExtractHooks = [ - hidePromiseFromPromisify(async (buildPath, electronVersion, pPlatform, pArch, done) => { - await runHook(forgeConfig, 'packageAfterExtract', buildPath, electronVersion, pPlatform, pArch); - done(); - }) as HookFunction, + hidePromiseFromPromisify( + async (buildPath, electronVersion, pPlatform, pArch, done) => { + await runHook( + forgeConfig, + 'packageAfterExtract', + buildPath, + electronVersion, + pPlatform, + pArch, + ); + done(); + }, + ) as HookFunction, ]; - afterExtractHooks.push(...(await resolveHooks(forgeConfig.packagerConfig.afterExtract, ctx.dir))); + afterExtractHooks.push( + ...(await resolveHooks( + forgeConfig.packagerConfig.afterExtract, + ctx.dir, + )), + ); type PackagerArch = Exclude; @@ -316,7 +487,10 @@ export const listrPackage = ( dir: ctx.dir, arch: arch as PackagerArch, platform, - afterFinalizePackageTargets: sequentialFinalizePackageTargetsHooks(afterFinalizePackageTargetsHooks), + afterFinalizePackageTargets: + sequentialFinalizePackageTargetsHooks( + afterFinalizePackageTargetsHooks, + ), afterComplete: sequentialHooks(afterCompleteHooks), afterCopy: sequentialHooks(afterCopyHooks), afterExtract: sequentialHooks(afterExtractHooks), @@ -326,20 +500,24 @@ export const listrPackage = ( }; if (packageOpts.all) { - throw new Error('config.forge.packagerConfig.all is not supported by Electron Forge'); + throw new Error( + 'config.forge.packagerConfig.all is not supported by Electron Forge', + ); } if (!packageJSON.version && !packageOpts.appVersion) { warn( interactive, chalk.yellow( - 'Please set "version" or "config.forge.packagerConfig.appVersion" in your application\'s package.json so auto-updates work properly' - ) + 'Please set "version" or "config.forge.packagerConfig.appVersion" in your application\'s package.json so auto-updates work properly', + ), ); } if (packageOpts.prebuiltAsar) { - throw new Error('config.forge.packagerConfig.prebuiltAsar is not supported by Electron Forge'); + throw new Error( + 'config.forge.packagerConfig.prebuiltAsar is not supported by Electron Forge', + ); } d('packaging with options', packageOpts); @@ -372,7 +550,7 @@ export const listrPackage = ( platform: target.platform, arch: 'arm64', forUniversal: true, - } + }, ); } } @@ -388,9 +566,14 @@ export const listrPackage = ( targetKey, (rebuildTasks.get(targetKey) || []).concat([ new Promise((resolve) => { - signalRebuildStart.set(targetKey, (signalRebuildStart.get(targetKey) || []).concat([resolve])); + signalRebuildStart.set( + targetKey, + (signalRebuildStart.get(targetKey) || []).concat([ + resolve, + ]), + ); }), - ]) + ]), ); } d('targets:', targets); @@ -403,7 +586,7 @@ export const listrPackage = ( target.arch === 'universal' ? { title: `Stitching ${chalk.cyan(`${target.platform}/x64`)} and ${chalk.cyan(`${target.platform}/arm64`)} into a ${chalk.green( - `${target.platform}/universal` + `${target.platform}/universal`, )} package`, task: async () => { await addSignalAndWait(signalPackageDone, target); @@ -414,13 +597,18 @@ export const listrPackage = ( } : { title: `Packaging for ${chalk.cyan(target.arch)} on ${chalk.cyan(target.platform)}${ - target.forUniversal ? chalk.italic(' (for universal package)') : '' + target.forUniversal + ? chalk.italic(' (for universal package)') + : '' }`, task: childTrace>>( { name: `package-app-${target.platform}-${target.arch}${target.forUniversal ? '-universal-tmp' : ''}`, category: '@electron-forge/core', - extraDetails: { arch: target.arch, platform: target.platform }, + extraDetails: { + arch: target.arch, + platform: target.platform, + }, newRoot: true, }, async (childTrace, _, task) => { @@ -430,16 +618,36 @@ export const listrPackage = ( [ { title: 'Copying files', - task: childTrace({ name: 'copy-files', category: '@electron-forge/core' }, async () => { - await addSignalAndWait(signalCopyDone, target); - }), + task: childTrace( + { + name: 'copy-files', + category: '@electron-forge/core', + }, + async () => { + await addSignalAndWait( + signalCopyDone, + target, + ); + }, + ), }, { title: 'Preparing native dependencies', - task: childTrace({ name: 'prepare-native-dependencies', category: '@electron-forge/core' }, async (_, __, task) => { - signalRebuildStart.get(getTargetKey(target))?.pop()?.(task); - await addSignalAndWait(signalRebuildDone, target); - }), + task: childTrace( + { + name: 'prepare-native-dependencies', + category: '@electron-forge/core', + }, + async (_, __, task) => { + signalRebuildStart + .get(getTargetKey(target)) + ?.pop()?.(task); + await addSignalAndWait( + signalRebuildDone, + target, + ); + }, + ), rendererOptions: { persistentOutput: true, bottomBar: Infinity, @@ -448,27 +656,47 @@ export const listrPackage = ( }, { title: 'Finalizing package', - task: childTrace({ name: 'finalize-package', category: '@electron-forge/core' }, async () => { - await addSignalAndWait(signalPackageDone, target); - }), + task: childTrace( + { + name: 'finalize-package', + category: '@electron-forge/core', + }, + async () => { + await addSignalAndWait( + signalPackageDone, + target, + ); + }, + ), }, ], - { rendererOptions: { collapseSubtasks: true, collapseErrors: false } } + { + rendererOptions: { + collapseSubtasks: true, + collapseErrors: false, + }, + }, ), - 'run' + 'run', ); - } + }, ), rendererOptions: { timer: { ...PRESET_TIMER }, }, - } + }, ), - { concurrent: true, rendererOptions: { collapseSubtasks: false, collapseErrors: false } } + { + concurrent: true, + rendererOptions: { + collapseSubtasks: false, + collapseErrors: false, + }, + }, ), - 'run' + 'run', ); - } + }, ), }, { @@ -481,42 +709,51 @@ export const listrPackage = ( return delayTraceTillSignal( childTrace, task.newListr( - await getHookListrTasks(childTrace, forgeConfig, 'postPackage', { - arch, - outputPaths, - platform, - }) + await getHookListrTasks( + childTrace, + forgeConfig, + 'postPackage', + { + arch, + outputPaths, + platform, + }, + ), ), - 'run' + 'run', ); - } + }, ), }, ], { concurrent: false, silentRendererCondition: !interactive, - fallbackRendererCondition: Boolean(process.env.DEBUG) || Boolean(process.env.CI), + fallbackRendererCondition: + Boolean(process.env.DEBUG) || Boolean(process.env.CI), rendererOptions: { collapseSubtasks: false, collapseErrors: false, }, ctx: {} as PackageContext, - } + }, ); return runner; }; -export default autoTrace({ name: 'package()', category: '@electron-forge/core' }, async (childTrace, opts: PackageOptions): Promise => { - const runner = listrPackage(childTrace, opts); - - await runner.run(); - - const outputPaths = await runner.ctx.packagerPromise; - return runner.ctx.targets.map((target, index) => ({ - platform: target.platform, - arch: target.arch, - packagedPath: outputPaths[index], - })); -}); +export default autoTrace( + { name: 'package()', category: '@electron-forge/core' }, + async (childTrace, opts: PackageOptions): Promise => { + const runner = listrPackage(childTrace, opts); + + await runner.run(); + + const outputPaths = await runner.ctx.packagerPromise; + return runner.ctx.targets.map((target, index) => ({ + platform: target.platform, + arch: target.arch, + packagedPath: outputPaths[index], + })); + }, +); diff --git a/packages/api/core/src/api/publish.ts b/packages/api/core/src/api/publish.ts index fffe178714..30e621284d 100644 --- a/packages/api/core/src/api/publish.ts +++ b/packages/api/core/src/api/publish.ts @@ -81,7 +81,7 @@ export default autoTrace( dryRun = false, dryRunResume = false, outDir, - }: PublishOptions + }: PublishOptions, ): Promise => { if (dryRun && dryRunResume) { throw new Error("Can't dry run and resume a dry run at the same time"); @@ -93,7 +93,8 @@ export default autoTrace( collapseErrors: false, }, silentRendererCondition: !interactive, - fallbackRendererCondition: Boolean(process.env.DEBUG) || Boolean(process.env.CI), + fallbackRendererCondition: + Boolean(process.env.DEBUG) || Boolean(process.env.CI), }; const publishDistributablesTasks = (childTrace: typeof autoTrace) => [ @@ -101,7 +102,11 @@ export default autoTrace( title: 'Publishing distributables', task: childTrace>>( { name: 'publish-distributables', category: '@electron-forge/core' }, - async (childTrace, { dir, forgeConfig, makeResults, publishers }, task: ForgeListrTask) => { + async ( + childTrace, + { dir, forgeConfig, makeResults, publishers }, + task: ForgeListrTask, + ) => { if (publishers.length === 0) { task.output = 'No publishers configured'; task.skip(); @@ -114,7 +119,10 @@ export default autoTrace( publishers.map((publisher) => ({ title: `${chalk.cyan(`[publisher-${publisher.name}]`)} Running the ${chalk.yellow('publish')} command`, task: childTrace>( - { name: `publish-${publisher.name}`, category: '@electron-forge/core' }, + { + name: `publish-${publisher.name}`, + category: '@electron-forge/core', + }, async (childTrace, _, task) => { const setStatusLine = (s: string) => { task.output = s; @@ -125,7 +133,7 @@ export default autoTrace( forgeConfig, setStatusLine, }); - } + }, ), rendererOptions: { persistentOutput: true, @@ -136,11 +144,11 @@ export default autoTrace( collapseSubtasks: false, collapseErrors: false, }, - } + }, ), - 'run' + 'run', ); - } + }, ), rendererOptions: { persistentOutput: true, @@ -157,55 +165,75 @@ export default autoTrace( async (childTrace, ctx) => { const resolvedDir = await resolveDir(providedDir); if (!resolvedDir) { - throw new Error('Failed to locate publishable Electron application'); + throw new Error( + 'Failed to locate publishable Electron application', + ); } ctx.dir = resolvedDir; ctx.forgeConfig = await getForgeConfig(resolvedDir); - } + }, ), }, { title: 'Resolving publish targets', task: childTrace>>( - { name: 'resolve-publish-targets', category: '@electron-forge/core' }, + { + name: 'resolve-publish-targets', + category: '@electron-forge/core', + }, async (childTrace, ctx, task) => { const { dir, forgeConfig } = ctx; if (!publishTargets) { publishTargets = forgeConfig.publishers || []; } - publishTargets = (publishTargets as ForgeConfigPublisher[]).map((target) => { - if (typeof target === 'string') { - return ( - (forgeConfig.publishers || []).find((p: ForgeConfigPublisher) => { - if (typeof p === 'string') return false; - if ((p as IForgePublisher).__isElectronForgePublisher) return false; - return (p as IForgeResolvablePublisher).name === target; - }) || { name: target } - ); - } - return target; - }); + publishTargets = (publishTargets as ForgeConfigPublisher[]).map( + (target) => { + if (typeof target === 'string') { + return ( + (forgeConfig.publishers || []).find( + (p: ForgeConfigPublisher) => { + if (typeof p === 'string') return false; + if ((p as IForgePublisher).__isElectronForgePublisher) + return false; + return ( + (p as IForgeResolvablePublisher).name === target + ); + }, + ) || { name: target } + ); + } + return target; + }, + ); ctx.publishers = []; for (const publishTarget of publishTargets) { // eslint-disable-next-line @typescript-eslint/no-explicit-any let publisher: PublisherBase; - if ((publishTarget as IForgePublisher).__isElectronForgePublisher) { + if ( + (publishTarget as IForgePublisher).__isElectronForgePublisher + ) { // eslint-disable-next-line @typescript-eslint/no-explicit-any publisher = publishTarget as PublisherBase; } else { - const resolvablePublishTarget = publishTarget as IForgeResolvablePublisher; + const resolvablePublishTarget = + publishTarget as IForgeResolvablePublisher; // eslint-disable-next-line @typescript-eslint/no-explicit-any - const PublisherClass: any = await importSearch(dir, [resolvablePublishTarget.name]); + const PublisherClass: any = await importSearch(dir, [ + resolvablePublishTarget.name, + ]); if (!PublisherClass) { throw new Error( - `Could not find a publish target with the name: ${resolvablePublishTarget.name}. Make sure it's listed in the devDependencies of your package.json` + `Could not find a publish target with the name: ${resolvablePublishTarget.name}. Make sure it's listed in the devDependencies of your package.json`, ); } - publisher = new PublisherClass(resolvablePublishTarget.config || {}, resolvablePublishTarget.platforms); + publisher = new PublisherClass( + resolvablePublishTarget.config || {}, + resolvablePublishTarget.platforms, + ); } ctx.publishers.push(publisher); @@ -214,24 +242,36 @@ export default autoTrace( if (ctx.publishers.length) { task.output = `Publishing to the following targets: ${chalk.magenta(`${ctx.publishers.map((publisher) => publisher.name).join(', ')}`)}`; } - } + }, ), rendererOptions: { persistentOutput: true, }, }, { - title: dryRunResume ? 'Resuming from dry run...' : `Running ${chalk.yellow('make')} command`, + title: dryRunResume + ? 'Resuming from dry run...' + : `Running ${chalk.yellow('make')} command`, task: childTrace>>( - { name: dryRunResume ? 'resume-dry-run' : 'make()', category: '@electron-forge/core' }, + { + name: dryRunResume ? 'resume-dry-run' : 'make()', + category: '@electron-forge/core', + }, async (childTrace, ctx, task) => { const { dir, forgeConfig } = ctx; - const calculatedOutDir = outDir || getCurrentOutDir(dir, forgeConfig); - const dryRunDir = path.resolve(calculatedOutDir, 'publish-dry-run'); + const calculatedOutDir = + outDir || getCurrentOutDir(dir, forgeConfig); + const dryRunDir = path.resolve( + calculatedOutDir, + 'publish-dry-run', + ); if (dryRunResume) { d('attempting to resume from dry run'); - const publishes = await PublishState.loadFromDirectory(dryRunDir, dir); + const publishes = await PublishState.loadFromDirectory( + dryRunDir, + dir, + ); task.title = `Resuming ${publishes.length} found dry runs...`; return delayTraceTillSignal( @@ -240,41 +280,59 @@ export default autoTrace( publishes.map((publishStates, index) => { return { title: `Publishing dry-run ${chalk.blue(`#${index + 1}`)}`, - task: childTrace>>( - { name: `publish-dry-run-${index + 1}`, category: '@electron-forge/core' }, + task: childTrace< + Parameters> + >( + { + name: `publish-dry-run-${index + 1}`, + category: '@electron-forge/core', + }, async (childTrace, ctx, task) => { - const restoredMakeResults = publishStates.map(({ state }) => state); + const restoredMakeResults = publishStates.map( + ({ state }) => state, + ); d('restoring publish settings from dry run'); for (const makeResult of restoredMakeResults) { makeResult.artifacts = await Promise.all( - makeResult.artifacts.map(async (makePath: string) => { - // standardize the path to artifacts across platforms - const normalizedPath = makePath.split(/\/|\\/).join(path.sep); - if (!(await fs.pathExists(normalizedPath))) { - throw new Error(`Attempted to resume a dry run, but an artifact (${normalizedPath}) could not be found`); - } - return normalizedPath; - }) + makeResult.artifacts.map( + async (makePath: string) => { + // standardize the path to artifacts across platforms + const normalizedPath = makePath + .split(/\/|\\/) + .join(path.sep); + if ( + !(await fs.pathExists(normalizedPath)) + ) { + throw new Error( + `Attempted to resume a dry run, but an artifact (${normalizedPath}) could not be found`, + ); + } + return normalizedPath; + }, + ), ); } d('publishing for given state set'); return delayTraceTillSignal( childTrace, - task.newListr(publishDistributablesTasks(childTrace), { - ctx: { - ...ctx, - makeResults: restoredMakeResults, + task.newListr( + publishDistributablesTasks(childTrace), + { + ctx: { + ...ctx, + makeResults: restoredMakeResults, + }, + rendererOptions: { + collapseSubtasks: false, + collapseErrors: false, + }, }, - rendererOptions: { - collapseSubtasks: false, - collapseErrors: false, - }, - }), - 'run' + ), + 'run', ); - } + }, ), }; }), @@ -283,9 +341,9 @@ export default autoTrace( collapseSubtasks: false, collapseErrors: false, }, - } + }, ), - 'run' + 'run', ); } @@ -301,37 +359,47 @@ export default autoTrace( }, (results) => { ctx.makeResults = results; - } + }, ), - 'run' + 'run', ); - } + }, ), }, ...(dryRunResume ? [] : dryRun - ? [ - { - title: 'Saving dry-run state', - task: childTrace>>( - { name: 'save-dry-run', category: '@electron-forge/core' }, - async (childTrace, { dir, forgeConfig, makeResults }) => { - d('saving results of make in dry run state', makeResults); - const calculatedOutDir = outDir || getCurrentOutDir(dir, forgeConfig); - const dryRunDir = path.resolve(calculatedOutDir, 'publish-dry-run'); + ? [ + { + title: 'Saving dry-run state', + task: childTrace< + Parameters> + >( + { name: 'save-dry-run', category: '@electron-forge/core' }, + async (childTrace, { dir, forgeConfig, makeResults }) => { + d('saving results of make in dry run state', makeResults); + const calculatedOutDir = + outDir || getCurrentOutDir(dir, forgeConfig); + const dryRunDir = path.resolve( + calculatedOutDir, + 'publish-dry-run', + ); - await fs.remove(dryRunDir); - await PublishState.saveToDirectory(dryRunDir, makeResults!, dir); - } - ), - }, - ] - : publishDistributablesTasks(childTrace)), + await fs.remove(dryRunDir); + await PublishState.saveToDirectory( + dryRunDir, + makeResults!, + dir, + ); + }, + ), + }, + ] + : publishDistributablesTasks(childTrace)), ], - listrOptions + listrOptions, ); await runner.run(); - } + }, ); diff --git a/packages/api/core/src/api/start.ts b/packages/api/core/src/api/start.ts index a0c1ae709e..11461497ed 100644 --- a/packages/api/core/src/api/start.ts +++ b/packages/api/core/src/api/start.ts @@ -1,7 +1,10 @@ import { spawn, SpawnOptions } from 'node:child_process'; import readline from 'node:readline'; -import { getElectronVersion, listrCompatibleRebuildHook } from '@electron-forge/core-utils'; +import { + getElectronVersion, + listrCompatibleRebuildHook, +} from '@electron-forge/core-utils'; import { ElectronProcess, ForgeArch, @@ -46,7 +49,7 @@ export default autoTrace( runAsNode = false, inspect = false, inspectBrk = false, - }: StartOptions + }: StartOptions, ): Promise => { const platform = process.env.npm_config_platform || process.platform; const arch = process.env.npm_config_arch || process.arch; @@ -58,37 +61,54 @@ export default autoTrace( collapseSubtasks: false, }, silentRendererCondition: !interactive, - fallbackRendererCondition: Boolean(process.env.DEBUG) || Boolean(process.env.CI), + fallbackRendererCondition: + Boolean(process.env.DEBUG) || Boolean(process.env.CI), }; const runner = new Listr( [ { title: 'Locating application', - task: childTrace>>({ name: 'locate-application', category: '@electron-forge/core' }, async (_, ctx) => { - const resolvedDir = await resolveDir(providedDir); - if (!resolvedDir) { - throw new Error('Failed to locate startable Electron application'); - } - ctx.dir = resolvedDir; - }), + task: childTrace>>( + { name: 'locate-application', category: '@electron-forge/core' }, + async (_, ctx) => { + const resolvedDir = await resolveDir(providedDir); + if (!resolvedDir) { + throw new Error( + 'Failed to locate startable Electron application', + ); + } + ctx.dir = resolvedDir; + }, + ), }, { title: 'Loading configuration', - task: childTrace>>({ name: 'load-forge-config', category: '@electron-forge/core' }, async (_, ctx) => { - const { dir } = ctx; - ctx.forgeConfig = await getForgeConfig(dir); - ctx.packageJSON = await readMutatedPackageJson(dir, ctx.forgeConfig); + task: childTrace>>( + { name: 'load-forge-config', category: '@electron-forge/core' }, + async (_, ctx) => { + const { dir } = ctx; + ctx.forgeConfig = await getForgeConfig(dir); + ctx.packageJSON = await readMutatedPackageJson( + dir, + ctx.forgeConfig, + ); - if (!ctx.packageJSON.version) { - throw new Error(`Please set your application's 'version' in '${dir}/package.json'.`); - } - }), + if (!ctx.packageJSON.version) { + throw new Error( + `Please set your application's 'version' in '${dir}/package.json'.`, + ); + } + }, + ), }, { title: 'Preparing native dependencies', task: childTrace>>( - { name: 'prepare-native-dependencies', category: '@electron-forge/core' }, + { + name: 'prepare-native-dependencies', + category: '@electron-forge/core', + }, async (_, { dir, forgeConfig, packageJSON }, task) => { await listrCompatibleRebuildHook( dir, @@ -96,9 +116,9 @@ export default autoTrace( platform as ForgePlatform, arch as ForgeArch, forgeConfig.rebuildConfig, - task as any + task as any, ); - } + }, ), rendererOptions: { persistentOutput: true, @@ -109,10 +129,25 @@ export default autoTrace( { title: `Running ${chalk.yellow('generateAssets')} hook`, task: childTrace>>( - { name: 'run-generateAssets-hook', category: '@electron-forge/core' }, + { + name: 'run-generateAssets-hook', + category: '@electron-forge/core', + }, async (childTrace, { forgeConfig }, task) => { - return delayTraceTillSignal(childTrace, task.newListr(await getHookListrTasks(childTrace, forgeConfig, 'generateAssets', platform, arch)), 'run'); - } + return delayTraceTillSignal( + childTrace, + task.newListr( + await getHookListrTasks( + childTrace, + forgeConfig, + 'generateAssets', + platform, + arch, + ), + ), + 'run', + ); + }, ), }, { @@ -120,8 +155,14 @@ export default autoTrace( task: childTrace>>( { name: 'run-preStart-hook', category: '@electron-forge/core' }, async (childTrace, { forgeConfig }, task) => { - return delayTraceTillSignal(childTrace, task.newListr(await getHookListrTasks(childTrace, forgeConfig, 'preStart')), 'run'); - } + return delayTraceTillSignal( + childTrace, + task.newListr( + await getHookListrTasks(childTrace, forgeConfig, 'preStart'), + ), + 'run', + ); + }, ), }, { @@ -130,7 +171,7 @@ export default autoTrace( }, }, ], - listrOptions + listrOptions, ); await runner.run(); @@ -142,18 +183,25 @@ export default autoTrace( let electronExecPath: string | null = null; // If a plugin has taken over the start command let's stop here - let spawnedPluginChild = await forgeConfig.pluginInterface.overrideStartLogic({ - dir, - appPath, - interactive, - enableLogging, - args, - runAsNode, - inspect, - inspectBrk, - }); - if (typeof spawnedPluginChild === 'object' && 'tasks' in spawnedPluginChild) { - const innerRunner = new Listr([], listrOptions as ForgeListrOptions); + let spawnedPluginChild = + await forgeConfig.pluginInterface.overrideStartLogic({ + dir, + appPath, + interactive, + enableLogging, + args, + runAsNode, + inspect, + inspectBrk, + }); + if ( + typeof spawnedPluginChild === 'object' && + 'tasks' in spawnedPluginChild + ) { + const innerRunner = new Listr( + [], + listrOptions as ForgeListrOptions, + ); for (const task of spawnedPluginChild.tasks) { innerRunner.add(task); } @@ -206,7 +254,7 @@ export default autoTrace( const spawned = spawn( electronExecPath!, // eslint-disable-line @typescript-eslint/no-non-null-assertion prefixArgs.concat([appPath]).concat(args as string[]), - spawnOpts as SpawnOptions + spawnOpts as SpawnOptions, ) as ElectronProcess; await runHook(forgeConfig, 'postStart', spawned); @@ -243,7 +291,9 @@ export default autoTrace( readline.moveCursor(process.stdout, 0, -1); readline.clearLine(process.stdout, 0); readline.cursorTo(process.stdout, 0); - console.info(`${chalk.green('✔ ')}${chalk.dim('Restarting Electron app')}`); + console.info( + `${chalk.green('✔ ')}${chalk.dim('Restarting Electron app')}`, + ); lastSpawned.restarted = true; lastSpawned.on('exit', async () => { lastSpawned!.emit('restarted', await forgeSpawnWrapper()); @@ -259,5 +309,5 @@ export default autoTrace( if (interactive) console.log(''); return spawned; - } + }, ); diff --git a/packages/api/core/src/util/deprecate.ts b/packages/api/core/src/util/deprecate.ts index 6e7dca5c36..9ecdd4fe24 100644 --- a/packages/api/core/src/util/deprecate.ts +++ b/packages/api/core/src/util/deprecate.ts @@ -7,6 +7,11 @@ type Deprecation = { export default (what: string): Deprecation => ({ replaceWith: (replacement: string): void => { - console.warn(logSymbols.warning, chalk.yellow(`WARNING: ${what} is deprecated, please use ${replacement} instead`)); + console.warn( + logSymbols.warning, + chalk.yellow( + `WARNING: ${what} is deprecated, please use ${replacement} instead`, + ), + ); }, }); diff --git a/packages/api/core/src/util/electron-executable.ts b/packages/api/core/src/util/electron-executable.ts index 252cb2e8f0..50e75e1b06 100644 --- a/packages/api/core/src/util/electron-executable.ts +++ b/packages/api/core/src/util/electron-executable.ts @@ -5,14 +5,26 @@ import logSymbols from 'log-symbols'; type PackageJSON = Record; -export default async function locateElectronExecutable(dir: string, packageJSON: PackageJSON): Promise { - const electronModulePath: string | undefined = await getElectronModulePath(dir, packageJSON); +export default async function locateElectronExecutable( + dir: string, + packageJSON: PackageJSON, +): Promise { + const electronModulePath: string | undefined = await getElectronModulePath( + dir, + packageJSON, + ); // eslint-disable-next-line @typescript-eslint/no-require-imports - let electronExecPath = require(electronModulePath || path.resolve(dir, 'node_modules/electron')); + let electronExecPath = require( + electronModulePath || path.resolve(dir, 'node_modules/electron'), + ); if (typeof electronExecPath !== 'string') { - console.warn(logSymbols.warning, 'Returned Electron executable path is not a string, defaulting to a hardcoded location. Value:', electronExecPath); + console.warn( + logSymbols.warning, + 'Returned Electron executable path is not a string, defaulting to a hardcoded location. Value:', + electronExecPath, + ); // eslint-disable-next-line @typescript-eslint/no-require-imports electronExecPath = require(path.resolve(dir, 'node_modules/electron')); } diff --git a/packages/api/core/src/util/forge-config.ts b/packages/api/core/src/util/forge-config.ts index 7720736d2c..91608300fb 100644 --- a/packages/api/core/src/util/forge-config.ts +++ b/packages/api/core/src/util/forge-config.ts @@ -24,19 +24,36 @@ const underscoreCase = (str: string) => type ProxiedObject = object; /* eslint-disable @typescript-eslint/no-explicit-any */ -function isBuildIdentifierConfig(value: any): value is BuildIdentifierConfig { - return value && typeof value === 'object' && value.__isMagicBuildIdentifierMap; +function isBuildIdentifierConfig( + value: any, +): value is BuildIdentifierConfig { + return ( + value && typeof value === 'object' && value.__isMagicBuildIdentifierMap + ); } -const proxify = (buildIdentifier: string | (() => string), proxifiedObject: T, envPrefix: string): T => { +const proxify = ( + buildIdentifier: string | (() => string), + proxifiedObject: T, + envPrefix: string, +): T => { let newObject: T = {} as any; if (Array.isArray(proxifiedObject)) { newObject = [] as any; } for (const [key, val] of Object.entries(proxifiedObject)) { - if (typeof val === 'object' && (val.constructor === Object || val.constructor === Array) && key !== 'pluginInterface' && !(val instanceof RegExp)) { - (newObject as any)[key] = proxify(buildIdentifier, (proxifiedObject as any)[key], `${envPrefix}_${underscoreCase(key)}`); + if ( + typeof val === 'object' && + (val.constructor === Object || val.constructor === Array) && + key !== 'pluginInterface' && + !(val instanceof RegExp) + ) { + (newObject as any)[key] = proxify( + buildIdentifier, + (proxifiedObject as any)[key], + `${envPrefix}_${underscoreCase(key)}`, + ); } else { (newObject as any)[key] = (proxifiedObject as any)[key]; } @@ -52,13 +69,17 @@ const proxify = (buildIdentifier: string | (() => strin const value = Reflect.get(target, name, receiver); if (isBuildIdentifierConfig(value)) { - const identifier = typeof buildIdentifier === 'function' ? buildIdentifier() : buildIdentifier; + const identifier = + typeof buildIdentifier === 'function' + ? buildIdentifier() + : buildIdentifier; return value.map[identifier]; } return value; }, getOwnPropertyDescriptor(target, name) { - const envValue = process.env[`${envPrefix}_${underscoreCase(name as string)}`]; + const envValue = + process.env[`${envPrefix}_${underscoreCase(name as string)}`]; // eslint-disable-next-line no-prototype-builtins if (target.hasOwnProperty(name)) { return Reflect.getOwnPropertyDescriptor(target, name); @@ -80,7 +101,10 @@ const proxify = (buildIdentifier: string | (() => strin /* eslint-enable @typescript-eslint/no-explicit-any */ export const registeredForgeConfigs: Map = new Map(); -export function registerForgeConfigForDirectory(dir: string, config: ForgeConfig): void { +export function registerForgeConfigForDirectory( + dir: string, + config: ForgeConfig, +): void { registeredForgeConfigs.set(path.resolve(dir), config); } export function unregisterForgeConfigForDirectory(dir: string): void { @@ -93,19 +117,32 @@ export type BuildIdentifierConfig = { __isMagicBuildIdentifierMap: true; }; -export function fromBuildIdentifier(map: BuildIdentifierMap): BuildIdentifierConfig { +export function fromBuildIdentifier( + map: BuildIdentifierMap, +): BuildIdentifierConfig { return { map, __isMagicBuildIdentifierMap: true, }; } -export async function forgeConfigIsValidFilePath(dir: string, forgeConfig: string | ForgeConfig): Promise { - return typeof forgeConfig === 'string' && ((await fs.pathExists(path.resolve(dir, forgeConfig))) || fs.pathExists(path.resolve(dir, `${forgeConfig}.js`))); +export async function forgeConfigIsValidFilePath( + dir: string, + forgeConfig: string | ForgeConfig, +): Promise { + return ( + typeof forgeConfig === 'string' && + ((await fs.pathExists(path.resolve(dir, forgeConfig))) || + fs.pathExists(path.resolve(dir, `${forgeConfig}.js`))) + ); } // eslint-disable-next-line @typescript-eslint/no-explicit-any -export function renderConfigTemplate(dir: string, templateObj: any, obj: any): void { +export function renderConfigTemplate( + dir: string, + templateObj: any, + obj: any, +): void { for (const [key, value] of Object.entries(obj)) { if (typeof value === 'object' && value !== null) { renderConfigTemplate(dir, templateObj, value); @@ -123,16 +160,24 @@ type MaybeESM = T | { default: T }; type AsyncForgeConfigGenerator = () => Promise; export default async (dir: string): Promise => { - let forgeConfig: ForgeConfig | string | null | undefined = registeredForgeConfigs.get(dir); + let forgeConfig: ForgeConfig | string | null | undefined = + registeredForgeConfigs.get(dir); const packageJSON = await readRawPackageJson(dir); if (forgeConfig === undefined) { - forgeConfig = packageJSON.config && packageJSON.config.forge ? packageJSON.config.forge : null; + forgeConfig = + packageJSON.config && packageJSON.config.forge + ? packageJSON.config.forge + : null; } if (!forgeConfig || typeof forgeConfig === 'string') { // interpret.extensions doesn't support `.mts` files - for (const extension of ['.js', '.mts', ...Object.keys(interpret.extensions)]) { + for (const extension of [ + '.js', + '.mts', + ...Object.keys(interpret.extensions), + ]) { const pathToConfig = path.resolve(dir, `forge.config${extension}`); if (await fs.pathExists(pathToConfig)) { // Use rechoir to parse alternative syntaxes (except for TypeScript where we use jiti) @@ -157,15 +202,22 @@ export default async (dir: string): Promise => { loadFn = dynamicImportMaybe; } // The loaded "config" could potentially be a static forge config, ESM module or async function - const loaded = (await loadFn(forgeConfigPath)) as MaybeESM; + const loaded = (await loadFn(forgeConfigPath)) as MaybeESM< + ForgeConfig | AsyncForgeConfigGenerator + >; const maybeForgeConfig = 'default' in loaded ? loaded.default : loaded; - forgeConfig = typeof maybeForgeConfig === 'function' ? await maybeForgeConfig() : maybeForgeConfig; + forgeConfig = + typeof maybeForgeConfig === 'function' + ? await maybeForgeConfig() + : maybeForgeConfig; } catch (err) { console.error(`Failed to load: ${forgeConfigPath}`); throw err; } } else if (typeof forgeConfig !== 'object') { - throw new Error('Expected packageJSON.config.forge to be an object or point to a requirable JS file'); + throw new Error( + 'Expected packageJSON.config.forge to be an object or point to a requirable JS file', + ); } const defaultForgeConfig = { rebuildConfig: {}, @@ -184,9 +236,20 @@ export default async (dir: string): Promise => { const templateObj = { ...packageJSON, year: new Date().getFullYear() }; renderConfigTemplate(dir, templateObj, resolvedForgeConfig); - resolvedForgeConfig.pluginInterface = await PluginInterface.create(dir, resolvedForgeConfig); - - resolvedForgeConfig = await runMutatingHook(resolvedForgeConfig, 'resolveForgeConfig', resolvedForgeConfig); - - return proxify(resolvedForgeConfig.buildIdentifier || '', resolvedForgeConfig, 'ELECTRON_FORGE'); + resolvedForgeConfig.pluginInterface = await PluginInterface.create( + dir, + resolvedForgeConfig, + ); + + resolvedForgeConfig = await runMutatingHook( + resolvedForgeConfig, + 'resolveForgeConfig', + resolvedForgeConfig, + ); + + return proxify( + resolvedForgeConfig.buildIdentifier || '', + resolvedForgeConfig, + 'ELECTRON_FORGE', + ); }; diff --git a/packages/api/core/src/util/hook.ts b/packages/api/core/src/util/hook.ts index d5bfeda2ec..7b80664ee5 100644 --- a/packages/api/core/src/util/hook.ts +++ b/packages/api/core/src/util/hook.ts @@ -23,13 +23,18 @@ export const runHook = async ( d(`hook triggered: ${hookName}`); if (typeof hooks[hookName] === 'function') { d('calling hook:', hookName, 'with args:', hookArgs); - await (hooks[hookName] as ForgeSimpleHookFn)(forgeConfig, ...hookArgs); + await (hooks[hookName] as ForgeSimpleHookFn)( + forgeConfig, + ...hookArgs, + ); } } await forgeConfig.pluginInterface.triggerHook(hookName, hookArgs); }; -export const getHookListrTasks = async ( +export const getHookListrTasks = async < + Hook extends keyof ForgeSimpleHookSignatures, +>( childTrace: typeof autoTrace, forgeConfig: ResolvedForgeConfig, hookName: Hook, @@ -43,17 +48,35 @@ export const getHookListrTasks = async { - await (hooks[hookName] as ForgeSimpleHookFn)(forgeConfig, ...hookArgs); - }), + task: childTrace( + { + name: 'forge-config-hook', + category: '@electron-forge/hooks', + extraDetails: { hook: hookName }, + }, + async () => { + await (hooks[hookName] as ForgeSimpleHookFn)( + forgeConfig, + ...hookArgs, + ); + }, + ), }); } } - tasks.push(...(await forgeConfig.pluginInterface.getHookListrTasks(childTrace, hookName, hookArgs))); + tasks.push( + ...(await forgeConfig.pluginInterface.getHookListrTasks( + childTrace, + hookName, + hookArgs, + )), + ); return tasks; }; -export async function runMutatingHook( +export async function runMutatingHook< + Hook extends keyof ForgeMutatingHookSignatures, +>( forgeConfig: ResolvedForgeConfig, hookName: Hook, ...item: ForgeMutatingHookSignatures[Hook] diff --git a/packages/api/core/src/util/import-search.ts b/packages/api/core/src/util/import-search.ts index cea3102770..ee8e975b3e 100644 --- a/packages/api/core/src/util/import-search.ts +++ b/packages/api/core/src/util/import-search.ts @@ -14,12 +14,27 @@ type RequireError = Error & { requestPath: string | undefined; }; -export async function importSearchRaw(relativeTo: string, paths: string[]): Promise { +export async function importSearchRaw( + relativeTo: string, + paths: string[], +): Promise { // Attempt to locally short-circuit if we're running from a checkout of forge - if (__dirname.includes('forge/packages/api/core/') && paths.length === 1 && paths[0].startsWith('@electron-forge/')) { + if ( + __dirname.includes('forge/packages/api/core/') && + paths.length === 1 && + paths[0].startsWith('@electron-forge/') + ) { const [moduleType, moduleName] = paths[0].split('/')[1].split('-'); try { - const localPath = path.resolve(__dirname, '..', '..', '..', '..', moduleType, moduleName); + const localPath = path.resolve( + __dirname, + '..', + '..', + '..', + '..', + moduleType, + moduleName, + ); d('testing local forge build', { moduleType, moduleName, localPath }); return await dynamicImportMaybe(localPath); } catch { @@ -30,7 +45,9 @@ export async function importSearchRaw(relativeTo: string, paths: string[]): P // Load via normal search paths const testPaths = paths .concat(paths.map((mapPath) => path.resolve(relativeTo, mapPath))) - .concat(paths.map((mapPath) => path.resolve(relativeTo, 'node_modules', mapPath))); + .concat( + paths.map((mapPath) => path.resolve(relativeTo, 'node_modules', mapPath)), + ); d('searching', testPaths, 'relative to', relativeTo); for (const testPath of testPaths) { try { @@ -40,7 +57,10 @@ export async function importSearchRaw(relativeTo: string, paths: string[]): P if (err instanceof Error) { const requireErr = err as RequireError; // Ignore require-related errors - if (requireErr.code !== 'MODULE_NOT_FOUND' || ![undefined, testPath].includes(requireErr.requestPath)) { + if ( + requireErr.code !== 'MODULE_NOT_FOUND' || + ![undefined, testPath].includes(requireErr.requestPath) + ) { throw err; } } @@ -54,7 +74,12 @@ export type PossibleModule = { default?: T; } & T; -export default async (relativeTo: string, paths: string[]): Promise => { +export default async ( + relativeTo: string, + paths: string[], +): Promise => { const result = await importSearchRaw>(relativeTo, paths); - return typeof result === 'object' && result && result.default ? result.default : (result as T | null); + return typeof result === 'object' && result && result.default + ? result.default + : (result as T | null); }; diff --git a/packages/api/core/src/util/index.ts b/packages/api/core/src/util/index.ts index b8f7752dcf..f1d6c9e79c 100644 --- a/packages/api/core/src/util/index.ts +++ b/packages/api/core/src/util/index.ts @@ -1,4 +1,7 @@ -import { getElectronVersion, spawnPackageManager } from '@electron-forge/core-utils'; +import { + getElectronVersion, + spawnPackageManager, +} from '@electron-forge/core-utils'; import { BuildIdentifierConfig, diff --git a/packages/api/core/src/util/install-dependencies.ts b/packages/api/core/src/util/install-dependencies.ts index b7031911a6..e966b7f575 100644 --- a/packages/api/core/src/util/install-dependencies.ts +++ b/packages/api/core/src/util/install-dependencies.ts @@ -14,8 +14,20 @@ export enum DepVersionRestriction { RANGE = 'RANGE', } -export default async (pm: PMDetails, dir: string, deps: string[], depType = DepType.PROD, versionRestriction = DepVersionRestriction.RANGE): Promise => { - d('installing', JSON.stringify(deps), 'in:', dir, `depType=${depType},versionRestriction=${versionRestriction},withPackageManager=${pm.executable}`); +export default async ( + pm: PMDetails, + dir: string, + deps: string[], + depType = DepType.PROD, + versionRestriction = DepVersionRestriction.RANGE, +): Promise => { + d( + 'installing', + JSON.stringify(deps), + 'in:', + dir, + `depType=${depType},versionRestriction=${versionRestriction},withPackageManager=${pm.executable}`, + ); if (deps.length === 0) { d('nothing to install, stopping immediately'); return Promise.resolve(); @@ -32,7 +44,9 @@ export default async (pm: PMDetails, dir: string, deps: string[], depType = DepT }); } catch (err) { if (err instanceof ExitError) { - throw new Error(`Failed to install modules: ${JSON.stringify(deps)}\n\nWith output: ${err.message}\n${err.stderr ? err.stderr.toString() : ''}`); + throw new Error( + `Failed to install modules: ${JSON.stringify(deps)}\n\nWith output: ${err.message}\n${err.stderr ? err.stderr.toString() : ''}`, + ); } else { throw err; } diff --git a/packages/api/core/src/util/linux-installer.ts b/packages/api/core/src/util/linux-installer.ts index 52e0837fbe..eb07559ae6 100644 --- a/packages/api/core/src/util/linux-installer.ts +++ b/packages/api/core/src/util/linux-installer.ts @@ -4,7 +4,11 @@ import { promisify } from 'node:util'; import sudoPrompt from 'sudo-prompt'; // eslint-disable-next-line @typescript-eslint/no-explicit-any -const which = async (type: string, prog: string, promise: () => Promise): Promise => { +const which = async ( + type: string, + prog: string, + promise: () => Promise, +): Promise => { if (spawnSync('which', [prog]).status === 0) { await promise(); } else { @@ -13,6 +17,8 @@ const which = async (type: string, prog: string, promise: () => Promise): P }; export const sudo = (type: string, prog: string, args: string): Promise => - which(type, prog, () => promisify(sudoPrompt.exec)(`${prog} ${args}`, { name: 'Electron Forge' })); + which(type, prog, () => + promisify(sudoPrompt.exec)(`${prog} ${args}`, { name: 'Electron Forge' }), + ); export default which; diff --git a/packages/api/core/src/util/parse-archs.ts b/packages/api/core/src/util/parse-archs.ts index d1116c1171..f08e9e74ac 100644 --- a/packages/api/core/src/util/parse-archs.ts +++ b/packages/api/core/src/util/parse-archs.ts @@ -1,9 +1,21 @@ -import { allOfficialArchsForPlatformAndVersion, SupportedPlatform } from '@electron/packager'; +import { + allOfficialArchsForPlatformAndVersion, + SupportedPlatform, +} from '@electron/packager'; import { ForgeArch, ForgePlatform } from '@electron-forge/shared-types'; -export default function parseArchs(platform: ForgePlatform | string, declaredArch: ForgeArch | 'all' | string, electronVersion: string): ForgeArch[] { +export default function parseArchs( + platform: ForgePlatform | string, + declaredArch: ForgeArch | 'all' | string, + electronVersion: string, +): ForgeArch[] { if (declaredArch === 'all') { - return allOfficialArchsForPlatformAndVersion(platform as SupportedPlatform, electronVersion) || ['x64']; + return ( + allOfficialArchsForPlatformAndVersion( + platform as SupportedPlatform, + electronVersion, + ) || ['x64'] + ); } return declaredArch.split(',') as ForgeArch[]; diff --git a/packages/api/core/src/util/plugin-interface.ts b/packages/api/core/src/util/plugin-interface.ts index b8a07769a0..31a59b4be8 100644 --- a/packages/api/core/src/util/plugin-interface.ts +++ b/packages/api/core/src/util/plugin-interface.ts @@ -31,7 +31,10 @@ export default class PluginInterface implements IForgePluginInterface { private config: ResolvedForgeConfig; - static async create(dir: string, forgeConfig: ResolvedForgeConfig): Promise { + static async create( + dir: string, + forgeConfig: ResolvedForgeConfig, + ): Promise { const int = new PluginInterface(dir, forgeConfig); await int._pluginPromise; return int; @@ -44,21 +47,31 @@ export default class PluginInterface implements IForgePluginInterface { return plugin; } - if (typeof plugin === 'object' && 'name' in plugin && 'config' in plugin) { + if ( + typeof plugin === 'object' && + 'name' in plugin && + 'config' in plugin + ) { const { name: pluginName, config: opts } = plugin; if (typeof pluginName !== 'string') { - throw new Error(`Expected plugin[0] to be a string but found ${pluginName}`); + throw new Error( + `Expected plugin[0] to be a string but found ${pluginName}`, + ); } // eslint-disable-next-line @typescript-eslint/no-explicit-any const Plugin = await importSearch(dir, [pluginName]); if (!Plugin) { - throw new Error(`Could not find module with name: ${pluginName}. Make sure it's listed in the devDependencies of your package.json`); + throw new Error( + `Could not find module with name: ${pluginName}. Make sure it's listed in the devDependencies of your package.json`, + ); } return new Plugin(opts); } - throw new Error(`Expected plugin to either be a plugin instance or a { name, config } object but found ${JSON.stringify(plugin)}`); - }) + throw new Error( + `Expected plugin to either be a plugin instance or a { name, config } object but found ${JSON.stringify(plugin)}`, + ); + }), ).then((plugins) => { this.plugins = plugins; for (const plugin of this.plugins) { @@ -79,10 +92,15 @@ export default class PluginInterface implements IForgePluginInterface { this.overrideStartLogic = this.overrideStartLogic.bind(this); } - async triggerHook(hookName: Hook, hookArgs: ForgeSimpleHookSignatures[Hook]): Promise { + async triggerHook( + hookName: Hook, + hookArgs: ForgeSimpleHookSignatures[Hook], + ): Promise { for (const plugin of this.plugins) { if (typeof plugin.getHooks === 'function') { - let hooks = plugin.getHooks()[hookName] as ForgeSimpleHookFn[] | ForgeSimpleHookFn; + let hooks = plugin.getHooks()[hookName] as + | ForgeSimpleHookFn[] + | ForgeSimpleHookFn; if (hooks) { if (typeof hooks === 'function') hooks = [hooks]; for (const hook of hooks) { @@ -96,28 +114,38 @@ export default class PluginInterface implements IForgePluginInterface { async getHookListrTasks( childTrace: typeof autoTrace, hookName: Hook, - hookArgs: ForgeSimpleHookSignatures[Hook] + hookArgs: ForgeSimpleHookSignatures[Hook], ): Promise { const tasks: ForgeListrTaskDefinition[] = []; for (const plugin of this.plugins) { if (typeof plugin.getHooks === 'function') { - let hooks = plugin.getHooks()[hookName] as ForgeSimpleHookFn[] | ForgeSimpleHookFn; + let hooks = plugin.getHooks()[hookName] as + | ForgeSimpleHookFn[] + | ForgeSimpleHookFn; if (hooks) { if (typeof hooks === 'function') hooks = [hooks]; for (const hook of hooks) { tasks.push({ title: `${chalk.cyan(`[plugin-${plugin.name}]`)} ${(hook as any).__hookName || `Running ${chalk.yellow(hookName)} hook`}`, task: childTrace( - { name: 'forge-plugin-hook', category: '@electron-forge/hooks', extraDetails: { plugin: plugin.name, hook: hookName } }, + { + name: 'forge-plugin-hook', + category: '@electron-forge/hooks', + extraDetails: { plugin: plugin.name, hook: hookName }, + }, async (_, __, task) => { if ((hook as any).__hookName) { // Also give it the task - return await (hook as any).call(task, this.config, ...(hookArgs as any[])); + return await (hook as any).call( + task, + this.config, + ...(hookArgs as any[]), + ); } else { await hook(this.config, ...hookArgs); } - } + }, ), rendererOptions: {}, }); @@ -136,7 +164,9 @@ export default class PluginInterface implements IForgePluginInterface { let result: ForgeMutatingHookSignatures[Hook][0] = item[0]; for (const plugin of this.plugins) { if (typeof plugin.getHooks === 'function') { - let hooks = plugin.getHooks()[hookName] as ForgeMutatingHookFn[] | ForgeMutatingHookFn; + let hooks = plugin.getHooks()[hookName] as + | ForgeMutatingHookFn[] + | ForgeMutatingHookFn; if (hooks) { if (typeof hooks === 'function') hooks = [hooks]; for (const hook of hooks) { @@ -152,13 +182,18 @@ export default class PluginInterface implements IForgePluginInterface { let newStartFn; const claimed: string[] = []; for (const plugin of this.plugins) { - if (typeof plugin.startLogic === 'function' && plugin.startLogic !== PluginBase.prototype.startLogic) { + if ( + typeof plugin.startLogic === 'function' && + plugin.startLogic !== PluginBase.prototype.startLogic + ) { claimed.push(plugin.name); newStartFn = plugin.startLogic; } } if (claimed.length > 1) { - throw new Error(`Multiple plugins tried to take control of the start command, please remove one of them\n --> ${claimed.join(', ')}`); + throw new Error( + `Multiple plugins tried to take control of the start command, please remove one of them\n --> ${claimed.join(', ')}`, + ); } if (claimed.length === 1 && newStartFn) { d(`plugin: "${claimed[0]}" has taken control of the start command`); diff --git a/packages/api/core/src/util/publish-state.ts b/packages/api/core/src/util/publish-state.ts index 6cc5c3e477..28339f0371 100644 --- a/packages/api/core/src/util/publish-state.ts +++ b/packages/api/core/src/util/publish-state.ts @@ -7,9 +7,14 @@ import fs from 'fs-extra'; const EXTENSION = '.forge.publish'; export default class PublishState { - static async loadFromDirectory(directory: string, rootDir: string): Promise { + static async loadFromDirectory( + directory: string, + rootDir: string, + ): Promise { if (!(await fs.pathExists(directory))) { - throw new Error(`Attempted to load publish state from a missing directory: ${directory}`); + throw new Error( + `Attempted to load publish state from a missing directory: ${directory}`, + ); } const publishes: PublishState[][] = []; @@ -18,12 +23,16 @@ export default class PublishState { const states: PublishState[] = []; if ((await fs.stat(subDir)).isDirectory()) { - const filePaths = (await fs.readdir(subDir)).filter((fileName) => fileName.endsWith(EXTENSION)).map((fileName) => path.resolve(subDir, fileName)); + const filePaths = (await fs.readdir(subDir)) + .filter((fileName) => fileName.endsWith(EXTENSION)) + .map((fileName) => path.resolve(subDir, fileName)); for (const filePath of filePaths) { const state = new PublishState(filePath); await state.load(); - state.state.artifacts = state.state.artifacts.map((artifactPath) => path.resolve(rootDir, artifactPath)); + state.state.artifacts = state.state.artifacts.map((artifactPath) => + path.resolve(rootDir, artifactPath), + ); states.push(state); } } @@ -32,11 +41,23 @@ export default class PublishState { return publishes; } - static async saveToDirectory(directory: string, artifacts: ForgeMakeResult[], rootDir: string): Promise { - const id = crypto.createHash('SHA256').update(JSON.stringify(artifacts)).digest('hex'); + static async saveToDirectory( + directory: string, + artifacts: ForgeMakeResult[], + rootDir: string, + ): Promise { + const id = crypto + .createHash('SHA256') + .update(JSON.stringify(artifacts)) + .digest('hex'); for (const artifact of artifacts) { - artifact.artifacts = artifact.artifacts.map((artifactPath) => path.relative(rootDir, artifactPath)); - const publishState = new PublishState(path.resolve(directory, id, 'null'), false); + artifact.artifacts = artifact.artifacts.map((artifactPath) => + path.relative(rootDir, artifactPath), + ); + const publishState = new PublishState( + path.resolve(directory, id, 'null'), + false, + ); publishState.state = artifact; await publishState.saveToDisk(); } diff --git a/packages/api/core/src/util/read-package-json.ts b/packages/api/core/src/util/read-package-json.ts index af6f5da363..21f646991d 100644 --- a/packages/api/core/src/util/read-package-json.ts +++ b/packages/api/core/src/util/read-package-json.ts @@ -6,8 +6,16 @@ import fs from 'fs-extra'; import { runMutatingHook } from './hook'; // eslint-disable-next-line @typescript-eslint/no-explicit-any -export const readRawPackageJson = async (dir: string): Promise => fs.readJson(path.resolve(dir, 'package.json')); +export const readRawPackageJson = async (dir: string): Promise => + fs.readJson(path.resolve(dir, 'package.json')); // eslint-disable-next-line @typescript-eslint/no-explicit-any -export const readMutatedPackageJson = async (dir: string, forgeConfig: ResolvedForgeConfig): Promise => - runMutatingHook(forgeConfig, 'readPackageJson', await readRawPackageJson(dir)); +export const readMutatedPackageJson = async ( + dir: string, + forgeConfig: ResolvedForgeConfig, +): Promise => + runMutatingHook( + forgeConfig, + 'readPackageJson', + await readRawPackageJson(dir), + ); diff --git a/packages/api/core/src/util/resolve-dir.ts b/packages/api/core/src/util/resolve-dir.ts index 7c9e4ba112..69a8880408 100644 --- a/packages/api/core/src/util/resolve-dir.ts +++ b/packages/api/core/src/util/resolve-dir.ts @@ -44,7 +44,10 @@ export default async (dir: string): Promise => { return mDir; } - if (packageJSON.devDependencies?.['@electron-forge/cli'] || packageJSON.devDependencies?.['@electron-forge/core']) { + if ( + packageJSON.devDependencies?.['@electron-forge/cli'] || + packageJSON.devDependencies?.['@electron-forge/core'] + ) { d('package.json with forge dependency found in', testPath); return mDir; } @@ -54,7 +57,10 @@ export default async (dir: string): Promise => { mDir = path.dirname(mDir); } if (bestGuessDir) { - d('guessing on the best electron-forge package.json found in', bestGuessDir); + d( + 'guessing on the best electron-forge package.json found in', + bestGuessDir, + ); return bestGuessDir; } if (lastError) { diff --git a/packages/api/core/src/util/upgrade-forge-config.ts b/packages/api/core/src/util/upgrade-forge-config.ts index b690ca3095..5b1385aa18 100644 --- a/packages/api/core/src/util/upgrade-forge-config.ts +++ b/packages/api/core/src/util/upgrade-forge-config.ts @@ -1,6 +1,11 @@ import path from 'node:path'; -import { ForgeConfig, ForgePlatform, IForgeResolvableMaker, IForgeResolvablePublisher } from '@electron-forge/shared-types'; +import { + ForgeConfig, + ForgePlatform, + IForgeResolvableMaker, + IForgeResolvablePublisher, +} from '@electron-forge/shared-types'; import { siblingDep } from '../api/init-scripts/init-npm'; @@ -40,10 +45,14 @@ type ForgePackageJSON = Record & { devDependencies: Record; }; -function mapMakeTargets(forge5Config: Forge5Config): Map { +function mapMakeTargets( + forge5Config: Forge5Config, +): Map { const makeTargets = new Map(); if (forge5Config.make_targets) { - for (const [platform, targets] of Object.entries(forge5Config.make_targets as MakeTargets)) { + for (const [platform, targets] of Object.entries( + forge5Config.make_targets as MakeTargets, + )) { for (const target of targets) { let platforms = makeTargets.get(target); if (platforms === undefined) { @@ -72,7 +81,9 @@ const forge5MakerMappings = new Map([ /** * Converts Forge v5 maker config to v6. */ -function generateForgeMakerConfig(forge5Config: Forge5Config): IForgeResolvableMaker[] { +function generateForgeMakerConfig( + forge5Config: Forge5Config, +): IForgeResolvableMaker[] { const makeTargets = mapMakeTargets(forge5Config); const makers: IForgeResolvableMaker[] = []; @@ -121,7 +132,9 @@ function transformGitHubPublisherConfig(config: GitHub5Config) { /** * Converts Forge v5 publisher config to v6. */ -function generateForgePublisherConfig(forge5Config: Forge5Config): IForgeResolvablePublisher[] { +function generateForgePublisherConfig( + forge5Config: Forge5Config, +): IForgeResolvablePublisher[] { const publishers: IForgeResolvablePublisher[] = []; for (const [forge5Key, publisherType] of forge5PublisherMappings) { @@ -144,7 +157,9 @@ function generateForgePublisherConfig(forge5Config: Forge5Config): IForgeResolva /** * Upgrades Forge v5 config to v6. */ -export default function upgradeForgeConfig(forge5Config: Forge5Config): ForgeConfig { +export default function upgradeForgeConfig( + forge5Config: Forge5Config, +): ForgeConfig { const forgeConfig: ForgeConfig = {} as ForgeConfig; if (forge5Config.electronPackagerConfig) { @@ -160,12 +175,22 @@ export default function upgradeForgeConfig(forge5Config: Forge5Config): ForgeCon return forgeConfig; } -export function updateUpgradedForgeDevDeps(packageJSON: ForgePackageJSON, devDeps: string[]): string[] { +export function updateUpgradedForgeDevDeps( + packageJSON: ForgePackageJSON, + devDeps: string[], +): string[] { const forgeConfig = packageJSON.config.forge; devDeps = devDeps.filter((dep) => !dep.startsWith('@electron-forge/maker-')); - devDeps = devDeps.concat((forgeConfig.makers as IForgeResolvableMaker[]).map((maker: IForgeResolvableMaker) => siblingDep(path.basename(maker.name)))); devDeps = devDeps.concat( - (forgeConfig.publishers as IForgeResolvablePublisher[]).map((publisher: IForgeResolvablePublisher) => siblingDep(path.basename(publisher.name))) + (forgeConfig.makers as IForgeResolvableMaker[]).map( + (maker: IForgeResolvableMaker) => siblingDep(path.basename(maker.name)), + ), + ); + devDeps = devDeps.concat( + (forgeConfig.publishers as IForgeResolvablePublisher[]).map( + (publisher: IForgeResolvablePublisher) => + siblingDep(path.basename(publisher.name)), + ), ); return devDeps; diff --git a/packages/maker/appx/spec/MakerAppX.spec.ts b/packages/maker/appx/spec/MakerAppX.spec.ts index 62307b85ed..2455e7add7 100644 --- a/packages/maker/appx/spec/MakerAppX.spec.ts +++ b/packages/maker/appx/spec/MakerAppX.spec.ts @@ -23,7 +23,14 @@ describe.runIf(process.platform === 'win32')('MakerAppX', function () { const def = process.platform === 'win32' ? it : it.skip; def('should create a .pfx file', async () => { - await fs.copyFile(path.join(__dirname, '../../../api/core/spec/fixture', 'bogus-private-key.pvk'), path.join(tmpDir, 'dummy.pvk')); + await fs.copyFile( + path.join( + __dirname, + '../../../api/core/spec/fixture', + 'bogus-private-key.pvk', + ), + path.join(tmpDir, 'dummy.pvk'), + ); const outputCertPath = await createDefaultCertificate('CN=Test', { certFilePath: tmpDir, certFileName: 'dummy', diff --git a/packages/maker/appx/spec/util/author-name.spec.ts b/packages/maker/appx/spec/util/author-name.spec.ts index 0b77684c5d..8f6a517e1c 100644 --- a/packages/maker/appx/spec/util/author-name.spec.ts +++ b/packages/maker/appx/spec/util/author-name.spec.ts @@ -28,7 +28,9 @@ describe('getNameFromAuthor', () => { }, ].forEach((scenario) => { it(`${JSON.stringify(scenario.author)} -> "${scenario.expectedReturnValue}"`, () => { - expect(getNameFromAuthor(scenario.author)).toBe(scenario.expectedReturnValue); + expect(getNameFromAuthor(scenario.author)).toBe( + scenario.expectedReturnValue, + ); }); }); }); diff --git a/packages/maker/appx/src/MakerAppX.ts b/packages/maker/appx/src/MakerAppX.ts index ae09dd7dd3..86f62f5ccb 100644 --- a/packages/maker/appx/src/MakerAppX.ts +++ b/packages/maker/appx/src/MakerAppX.ts @@ -4,7 +4,10 @@ import { MakerBase, MakerOptions } from '@electron-forge/maker-base'; import { ForgePlatform } from '@electron-forge/shared-types'; import resolveCommand from 'cross-spawn/lib/util/resolveCommand'; import windowsStore from 'electron-windows-store'; -import { isValidPublisherName, makeCert } from 'electron-windows-store/lib/sign'; +import { + isValidPublisherName, + makeCert, +} from 'electron-windows-store/lib/sign'; import fs from 'fs-extra'; import { MakerAppXConfig } from './Config'; @@ -13,7 +16,10 @@ import getNameFromAuthor from './util/author-name'; // NB: This is not a typo, we require AppXs to be built on 64-bit // but if we're running in a 32-bit node.js process, we're going to // be Wow64 redirected -const windowsSdkPaths = ['C:\\Program Files\\Windows Kits\\10\\bin\\x64', 'C:\\Program Files (x86)\\Windows Kits\\10\\bin\\x64']; +const windowsSdkPaths = [ + 'C:\\Program Files\\Windows Kits\\10\\bin\\x64', + 'C:\\Program Files (x86)\\Windows Kits\\10\\bin\\x64', +]; async function findSdkTool(exe: string) { let sdkTool: string | undefined; @@ -26,7 +32,8 @@ async function findSdkTool(exe: string) { } const topDir = path.dirname(testPath); for (const subVersion of await fs.readdir(topDir)) { - if (!(await fs.stat(path.resolve(topDir, subVersion))).isDirectory()) continue; + if (!(await fs.stat(path.resolve(topDir, subVersion))).isDirectory()) + continue; if (subVersion.substr(0, 2) !== '10') continue; testExe = path.resolve(topDir, subVersion, 'x64', 'makecert.exe'); @@ -42,7 +49,9 @@ async function findSdkTool(exe: string) { } if (!sdkTool || !(await fs.pathExists(sdkTool))) { - throw new Error(`Can't find ${exe} in PATH. You probably need to install the Windows SDK.`); + throw new Error( + `Can't find ${exe} in PATH. You probably need to install the Windows SDK.`, + ); } return sdkTool; @@ -57,18 +66,22 @@ export interface CreateDefaultCertOpts { export async function createDefaultCertificate( publisherName: string, - { certFilePath, certFileName, install, program }: CreateDefaultCertOpts + { certFilePath, certFileName, install, program }: CreateDefaultCertOpts, ): Promise { const makeCertOptions = { publisherName, certFilePath: certFilePath || process.cwd(), certFileName: certFileName || 'default', install: typeof install === 'boolean' ? install : false, - program: program || { windowsKit: path.dirname(await findSdkTool('makecert.exe')) }, + program: program || { + windowsKit: path.dirname(await findSdkTool('makecert.exe')), + }, }; if (!isValidPublisherName(publisherName)) { - throw new Error(`Received invalid publisher name: '${publisherName}' did not conform to X.500 distinguished name syntax for MakeCert.`); + throw new Error( + `Received invalid publisher name: '${publisherName}' did not conform to X.500 distinguished name syntax for MakeCert.`, + ); } return makeCert(makeCertOptions); @@ -83,7 +96,13 @@ export default class MakerAppX extends MakerBase { return process.platform === 'win32'; } - async make({ dir, makeDir, appName, packageJSON, targetArch }: MakerOptions): Promise { + async make({ + dir, + makeDir, + appName, + packageJSON, + targetArch, + }: MakerOptions): Promise { const outPath = path.resolve(makeDir, `appx/${targetArch}`); await this.ensureDirectory(outPath); @@ -96,18 +115,25 @@ export default class MakerAppX extends MakerBase { packageDisplayName: appName, packageDescription: packageJSON.description || appName, packageExecutable: `app\\${appName}.exe`, - windowsKit: this.config.windowsKit || path.dirname(await findSdkTool('makeappx.exe')), + windowsKit: + this.config.windowsKit || + path.dirname(await findSdkTool('makeappx.exe')), ...this.config, inputDirectory: dir, outputDirectory: outPath, }; if (!opts.publisher) { - throw new Error('Please set config.forge.windowsStoreConfig.publisher or author.name in package.json for the appx target'); + throw new Error( + 'Please set config.forge.windowsStoreConfig.publisher or author.name in package.json for the appx target', + ); } if (!opts.devCert) { - opts.devCert = await createDefaultCertificate(opts.publisher, { certFilePath: outPath, program: opts }); + opts.devCert = await createDefaultCertificate(opts.publisher, { + certFilePath: outPath, + program: opts, + }); } if (/[-+]/.test(opts.packageVersion)) { @@ -117,7 +143,7 @@ export default class MakerAppX extends MakerBase { throw new Error( "Windows Store version numbers don't support semver beta tags. To " + 'automatically fix this, set makeVersionWinStoreCompatible to true or ' + - 'explicitly set packageVersion to a version of the format X.Y.Z.A' + 'explicitly set packageVersion to a version of the format X.Y.Z.A', ); } } diff --git a/packages/maker/appx/src/util/author-name.ts b/packages/maker/appx/src/util/author-name.ts index 6f00be6d74..2a884147af 100644 --- a/packages/maker/appx/src/util/author-name.ts +++ b/packages/maker/appx/src/util/author-name.ts @@ -8,7 +8,11 @@ export default function getNameFromAuthor(author: PackagePerson): string { publisher = parseAuthor(publisher); } - if (typeof publisher !== 'string' && publisher && typeof publisher.name === 'string') { + if ( + typeof publisher !== 'string' && + publisher && + typeof publisher.name === 'string' + ) { publisher = publisher.name; } diff --git a/packages/maker/base/spec/config-fetcher.spec.ts b/packages/maker/base/spec/config-fetcher.spec.ts index e90e48335a..dac6143411 100644 --- a/packages/maker/base/spec/config-fetcher.spec.ts +++ b/packages/maker/base/spec/config-fetcher.spec.ts @@ -41,7 +41,7 @@ describe('prepareConfig', () => { { a: 234, }, - [] + [], ); expect(maker.config).toBeUndefined(); await maker.prepareConfig('x64'); diff --git a/packages/maker/base/spec/ensure-output.spec.ts b/packages/maker/base/spec/ensure-output.spec.ts index 987b73f80b..48de69fda2 100644 --- a/packages/maker/base/spec/ensure-output.spec.ts +++ b/packages/maker/base/spec/ensure-output.spec.ts @@ -30,11 +30,15 @@ describe('ensure-output', () => { it('should delete the directory contents if it exists', async () => { fs.mkdirSync(path.resolve(tmpDir, 'foo')); fs.writeFileSync(path.resolve(tmpDir, 'foo', 'touchedFile'), ''); - expect(fs.existsSync(path.resolve(tmpDir, 'foo', 'touchedFile'))).toEqual(true); + expect(fs.existsSync(path.resolve(tmpDir, 'foo', 'touchedFile'))).toEqual( + true, + ); await maker.ensureDirectory(path.resolve(tmpDir, 'foo')); - expect(fs.existsSync(path.resolve(tmpDir, 'foo', 'touchedFile'))).toEqual(false); + expect(fs.existsSync(path.resolve(tmpDir, 'foo', 'touchedFile'))).toEqual( + false, + ); }); it('should create the directory if it does not exist', async () => { @@ -48,11 +52,15 @@ describe('ensure-output', () => { it('should delete the file if it exists', async () => { fs.mkdirSync(path.resolve(tmpDir, 'foo')); fs.writeFileSync(path.resolve(tmpDir, 'foo', 'touchedFile'), ''); - expect(fs.existsSync(path.resolve(tmpDir, 'foo', 'touchedFile'))).toEqual(true); + expect(fs.existsSync(path.resolve(tmpDir, 'foo', 'touchedFile'))).toEqual( + true, + ); await maker.ensureFile(path.resolve(tmpDir, 'foo')); - expect(fs.existsSync(path.resolve(tmpDir, 'foo', 'touchedFile'))).toEqual(false); + expect(fs.existsSync(path.resolve(tmpDir, 'foo', 'touchedFile'))).toEqual( + false, + ); }); it('should create the containing directory if it does not exist', async () => { diff --git a/packages/maker/base/spec/support.spec.ts b/packages/maker/base/spec/support.spec.ts index 394e21bd46..0c95a341b4 100644 --- a/packages/maker/base/spec/support.spec.ts +++ b/packages/maker/base/spec/support.spec.ts @@ -14,6 +14,8 @@ describe('ensureExternalBinariesExist', () => { const maker = new MakerImpl({}, []); it('throws an error when one of the binaries does not exist', () => { - expect(() => maker.ensureExternalBinariesExist()).toThrow(/the following external binaries need to be installed: bash, nonexistent/); + expect(() => maker.ensureExternalBinariesExist()).toThrow( + /the following external binaries need to be installed: bash, nonexistent/, + ); }); }); diff --git a/packages/maker/base/spec/version.spec.ts b/packages/maker/base/spec/version.spec.ts index b4b9ade1fc..0ced250d8e 100644 --- a/packages/maker/base/spec/version.spec.ts +++ b/packages/maker/base/spec/version.spec.ts @@ -12,12 +12,22 @@ describe('normalizeWindowsVersion', () => { const maker = new MakerImpl({}, []); it('removes everything after the dash', () => { - for (const version of ['1.0.0-alpha', '1.0.0-alpha.1', '1.0.0-0.3.7', '1.0.0-x.7.z.92']) { + for (const version of [ + '1.0.0-alpha', + '1.0.0-alpha.1', + '1.0.0-0.3.7', + '1.0.0-x.7.z.92', + ]) { expect(maker.normalizeWindowsVersion(version)).toEqual('1.0.0.0'); } }); it('removes everything after the plus sign', () => { - for (const version of ['1.0.0-alpha+001', '1.0.0+20130313144700', '1.0.0-beta+exp.sha.5114f85', '1.0.0+21AF26D3----117B344092BD']) { + for (const version of [ + '1.0.0-alpha+001', + '1.0.0+20130313144700', + '1.0.0-beta+exp.sha.5114f85', + '1.0.0+21AF26D3----117B344092BD', + ]) { expect(maker.normalizeWindowsVersion(version)).toEqual('1.0.0.0'); } }); diff --git a/packages/maker/base/src/Maker.ts b/packages/maker/base/src/Maker.ts index c6ad7fec1a..efb97fd8b3 100644 --- a/packages/maker/base/src/Maker.ts +++ b/packages/maker/base/src/Maker.ts @@ -1,6 +1,11 @@ import path from 'node:path'; -import { ForgeArch, ForgePlatform, IForgeMaker, ResolvedForgeConfig } from '@electron-forge/shared-types'; +import { + ForgeArch, + ForgePlatform, + IForgeMaker, + ResolvedForgeConfig, +} from '@electron-forge/shared-types'; import fs from 'fs-extra'; import which from 'which'; @@ -54,7 +59,10 @@ export default abstract class Maker implements IForgeMaker { * @param configOrConfigFetcher - Either a configuration object for this maker or a simple method that returns such a configuration for a given target architecture * @param platformsToMakeOn - If you want this maker to run on platforms different from `defaultPlatforms` you can provide those platforms here */ - constructor(private configOrConfigFetcher: C | ((arch: ForgeArch) => C) = {} as C, protected platformsToMakeOn?: ForgePlatform[]) { + constructor( + private configOrConfigFetcher: C | ((arch: ForgeArch) => C) = {} as C, + protected platformsToMakeOn?: ForgePlatform[], + ) { Object.defineProperty(this, '__isElectronForgeMaker', { value: true, enumerable: false, @@ -71,7 +79,9 @@ export default abstract class Maker implements IForgeMaker { // v5 style functionality in the new API async prepareConfig(targetArch: ForgeArch): Promise { if (typeof this.configOrConfigFetcher === 'function') { - this.config = await Promise.resolve((this.configOrConfigFetcher as (arch: ForgeArch) => C)(targetArch)); + this.config = await Promise.resolve( + (this.configOrConfigFetcher as (arch: ForgeArch) => C)(targetArch), + ); } else { this.config = this.configOrConfigFetcher as C; } @@ -87,8 +97,13 @@ export default abstract class Maker implements IForgeMaker { * telling the developer exactly what is missing and if possible how to get it. */ isSupportedOnCurrentPlatform(): boolean { - if (this.isSupportedOnCurrentPlatform === Maker.prototype.isSupportedOnCurrentPlatform) { - throw new Error(`Maker ${this.name} did not implement the isSupportedOnCurrentPlatform method`); + if ( + this.isSupportedOnCurrentPlatform === + Maker.prototype.isSupportedOnCurrentPlatform + ) { + throw new Error( + `Maker ${this.name} did not implement the isSupportedOnCurrentPlatform method`, + ); } return true; } @@ -143,7 +158,9 @@ export default abstract class Maker implements IForgeMaker { * Checks if the specified binaries exist, which are required for the maker to be used. */ externalBinariesExist(): boolean { - return this.requiredExternalBinaries.every((binary) => which.sync(binary, { nothrow: true }) !== null); + return this.requiredExternalBinaries.every( + (binary) => which.sync(binary, { nothrow: true }) !== null, + ); } /** @@ -151,7 +168,9 @@ export default abstract class Maker implements IForgeMaker { */ ensureExternalBinariesExist(): void { if (!this.externalBinariesExist()) { - throw new Error(`Cannot make for ${this.name}, the following external binaries need to be installed: ${this.requiredExternalBinaries.join(', ')}`); + throw new Error( + `Cannot make for ${this.name}, the following external binaries need to be installed: ${this.requiredExternalBinaries.join(', ')}`, + ); } } diff --git a/packages/maker/deb/spec/MakerDeb.spec.ts b/packages/maker/deb/spec/MakerDeb.spec.ts index 7c9c655561..42ea232761 100644 --- a/packages/maker/deb/spec/MakerDeb.spec.ts +++ b/packages/maker/deb/spec/MakerDeb.spec.ts @@ -19,7 +19,10 @@ const packageJSON = { version: '1.2.3' }; vi.hoisted(async () => { const { mockRequire } = await import('@electron-forge/test-utils'); - void mockRequire('electron-installer-debian', vi.fn().mockResolvedValue({ packagePaths: ['/foo/bar.deb'] })); + void mockRequire( + 'electron-installer-debian', + vi.fn().mockResolvedValue({ packagePaths: ['/foo/bar.deb'] }), + ); }); describe('MakerDeb', () => { diff --git a/packages/maker/dmg/spec/MakerDMG.spec.ts b/packages/maker/dmg/spec/MakerDMG.spec.ts index f87f744bca..d0c89372ea 100644 --- a/packages/maker/dmg/spec/MakerDMG.spec.ts +++ b/packages/maker/dmg/spec/MakerDMG.spec.ts @@ -60,7 +60,10 @@ describe('MakerDMG', () => { packageJSON, }); expect(vi.mocked(fs.rename)).toHaveBeenCalledOnce(); - expect(vi.mocked(fs.rename)).toHaveBeenCalledWith(expect.anything(), expect.stringContaining(`1.2.3-${targetArch}`)); + expect(vi.mocked(fs.rename)).toHaveBeenCalledWith( + expect.anything(), + expect.stringContaining(`1.2.3-${targetArch}`), + ); }); it('should not attempt to rename the DMG file if a custom name is set', async () => { diff --git a/packages/maker/dmg/src/Config.ts b/packages/maker/dmg/src/Config.ts index 9a8e290816..d6a1298cd4 100644 --- a/packages/maker/dmg/src/Config.ts +++ b/packages/maker/dmg/src/Config.ts @@ -35,4 +35,7 @@ export interface AdditionalDMGOptions { 'code-sign'?: CodeSignOptions; } -export type MakerDMGConfig = Omit & { name?: string }; +export type MakerDMGConfig = Omit< + ElectronInstallerDMGOptions, + 'name' | 'appPath' | 'out' +> & { name?: string }; diff --git a/packages/maker/dmg/src/MakerDMG.ts b/packages/maker/dmg/src/MakerDMG.ts index 60c8a15dcc..eeb4d158ae 100644 --- a/packages/maker/dmg/src/MakerDMG.ts +++ b/packages/maker/dmg/src/MakerDMG.ts @@ -17,11 +17,20 @@ export default class MakerDMG extends MakerBase { return process.platform === 'darwin'; } - async make({ dir, makeDir, appName, packageJSON, targetArch }: MakerOptions): Promise { + async make({ + dir, + makeDir, + appName, + packageJSON, + targetArch, + }: MakerOptions): Promise { const { createDMG } = require('electron-installer-dmg'); const outPath = path.resolve(makeDir, `${this.config.name || appName}.dmg`); - const forgeDefaultOutPath = path.resolve(makeDir, `${appName}-${packageJSON.version}-${targetArch}.dmg`); + const forgeDefaultOutPath = path.resolve( + makeDir, + `${appName}-${packageJSON.version}-${targetArch}.dmg`, + ); await this.ensureFile(outPath); const dmgConfig: ElectronInstallerDMGOptions = { diff --git a/packages/maker/flatpak/spec/MakerFlatpak.spec.ts b/packages/maker/flatpak/spec/MakerFlatpak.spec.ts index 101f2c8581..911864cefd 100644 --- a/packages/maker/flatpak/spec/MakerFlatpak.spec.ts +++ b/packages/maker/flatpak/spec/MakerFlatpak.spec.ts @@ -64,7 +64,7 @@ describe('MakerFlatpak', () => { files: [], }, }, - [] + [], ); maker.ensureDirectory = vi.fn(); await maker.prepareConfig(targetArch); diff --git a/packages/maker/flatpak/src/MakerFlatpak.ts b/packages/maker/flatpak/src/MakerFlatpak.ts index e83b88be32..86125af50d 100644 --- a/packages/maker/flatpak/src/MakerFlatpak.ts +++ b/packages/maker/flatpak/src/MakerFlatpak.ts @@ -50,7 +50,9 @@ export default class MakerFlatpak extends MakerBase { await installer(flatpakConfig); - return (await fs.readdir(outDir)).filter((basename) => basename.endsWith('.flatpak')).map((basename) => path.join(outDir, basename)); + return (await fs.readdir(outDir)) + .filter((basename) => basename.endsWith('.flatpak')) + .map((basename) => path.join(outDir, basename)); } } diff --git a/packages/maker/pkg/spec/MakerPKG.spec.ts b/packages/maker/pkg/spec/MakerPKG.spec.ts index 13933e15d8..7960a9614a 100644 --- a/packages/maker/pkg/spec/MakerPKG.spec.ts +++ b/packages/maker/pkg/spec/MakerPKG.spec.ts @@ -57,6 +57,8 @@ describe('MakerPKG', () => { targetPlatform: 'win32', }); - await expect(promise).rejects.toThrow('The pkg maker only supports targeting "mas" and "darwin" builds. You provided "win32".'); + await expect(promise).rejects.toThrow( + 'The pkg maker only supports targeting "mas" and "darwin" builds. You provided "win32".', + ); }); }); diff --git a/packages/maker/pkg/src/MakerPKG.ts b/packages/maker/pkg/src/MakerPKG.ts index 2ff8380a0e..28615e77b9 100644 --- a/packages/maker/pkg/src/MakerPKG.ts +++ b/packages/maker/pkg/src/MakerPKG.ts @@ -15,12 +15,22 @@ export default class MakerPKG extends MakerBase { return process.platform === 'darwin'; } - async make({ dir, makeDir, appName, packageJSON, targetPlatform, targetArch }: MakerOptions): Promise { + async make({ + dir, + makeDir, + appName, + packageJSON, + targetPlatform, + targetArch, + }: MakerOptions): Promise { if (!this.isValidTargetPlatform(targetPlatform)) { - throw new Error(`The pkg maker only supports targeting "mas" and "darwin" builds. You provided "${targetPlatform}".`); + throw new Error( + `The pkg maker only supports targeting "mas" and "darwin" builds. You provided "${targetPlatform}".`, + ); } - const name = this.config.name || `${appName}-${packageJSON.version}-${targetArch}`; + const name = + this.config.name || `${appName}-${packageJSON.version}-${targetArch}`; const outPath = path.resolve(makeDir, `${name}.pkg`); await this.ensureFile(outPath); @@ -36,7 +46,9 @@ export default class MakerPKG extends MakerBase { return [outPath]; } - private isValidTargetPlatform(platform: string): platform is 'darwin' | 'mas' { + private isValidTargetPlatform( + platform: string, + ): platform is 'darwin' | 'mas' { return this.defaultPlatforms.includes(platform); } } diff --git a/packages/maker/rpm/spec/MakerRpm.spec.ts b/packages/maker/rpm/spec/MakerRpm.spec.ts index b73b9c159b..7ae51d364f 100644 --- a/packages/maker/rpm/spec/MakerRpm.spec.ts +++ b/packages/maker/rpm/spec/MakerRpm.spec.ts @@ -13,7 +13,10 @@ type MakeFunction = (opts: Partial) => Promise; vi.hoisted(async () => { const { mockRequire } = await import('@electron-forge/test-utils'); - void mockRequire('electron-installer-redhat', vi.fn().mockResolvedValue({ packagePaths: ['/foo/bar.rpm'] })); + void mockRequire( + 'electron-installer-redhat', + vi.fn().mockResolvedValue({ packagePaths: ['/foo/bar.rpm'] }), + ); }); describe('MakerRpm', () => { @@ -52,7 +55,7 @@ describe('MakerRpm', () => { productName: 'Redhat', }, }, - [] + [], ); maker.ensureDirectory = vi.fn(); await maker.prepareConfig(targetArch); diff --git a/packages/maker/rpm/src/MakerRpm.ts b/packages/maker/rpm/src/MakerRpm.ts index 6f45317a35..eda59c6f78 100644 --- a/packages/maker/rpm/src/MakerRpm.ts +++ b/packages/maker/rpm/src/MakerRpm.ts @@ -6,7 +6,10 @@ import { ForgeArch, ForgePlatform } from '@electron-forge/shared-types'; import { MakerRpmConfig } from './Config'; function renameRpm(dest: string, _src: string): string { - return path.join(dest, '<%= name %>-<%= version %>-<%= revision %>.<%= arch === "aarch64" ? "arm64" : arch %>.rpm'); + return path.join( + dest, + '<%= name %>-<%= version %>-<%= revision %>.<%= arch === "aarch64" ? "arm64" : arch %>.rpm', + ); } export function rpmArch(nodeArch: ForgeArch): string { diff --git a/packages/maker/snap/spec/MakerSnap.spec.ts b/packages/maker/snap/spec/MakerSnap.spec.ts index d3d640b153..43b1fb2a88 100644 --- a/packages/maker/snap/spec/MakerSnap.spec.ts +++ b/packages/maker/snap/spec/MakerSnap.spec.ts @@ -46,7 +46,7 @@ describe('MakerSnap', () => { arch: 'overridden', description: 'Snap description', }, - [] + [], ); maker.ensureDirectory = vi.fn(); await maker.prepareConfig(targetArch); diff --git a/packages/maker/snap/src/Config.ts b/packages/maker/snap/src/Config.ts index 4788496e51..ff6250f282 100644 --- a/packages/maker/snap/src/Config.ts +++ b/packages/maker/snap/src/Config.ts @@ -1,3 +1,4 @@ import { Options, SnapcraftConfig } from 'electron-installer-snap'; -export type MakerSnapConfig = Omit & SnapcraftConfig; +export type MakerSnapConfig = Omit & + SnapcraftConfig; diff --git a/packages/maker/squirrel/src/MakerSquirrel.ts b/packages/maker/squirrel/src/MakerSquirrel.ts index d19de3a4d5..05882f27cc 100644 --- a/packages/maker/squirrel/src/MakerSquirrel.ts +++ b/packages/maker/squirrel/src/MakerSquirrel.ts @@ -2,10 +2,17 @@ import path from 'node:path'; import { MakerBase, MakerOptions } from '@electron-forge/maker-base'; import { ForgePlatform } from '@electron-forge/shared-types'; -import { convertVersion, createWindowsInstaller, Options as ElectronWinstallerOptions } from 'electron-winstaller'; +import { + convertVersion, + createWindowsInstaller, + Options as ElectronWinstallerOptions, +} from 'electron-winstaller'; import fs from 'fs-extra'; -export type MakerSquirrelConfig = Omit; +export type MakerSquirrelConfig = Omit< + ElectronWinstallerOptions, + 'appDirectory' | 'outputDirectory' +>; export default class MakerSquirrel extends MakerBase { name = 'squirrel'; @@ -13,15 +20,28 @@ export default class MakerSquirrel extends MakerBase { defaultPlatforms: ForgePlatform[] = ['win32']; isSupportedOnCurrentPlatform(): boolean { - return this.isInstalled('electron-winstaller') && !process.env.DISABLE_SQUIRREL_TEST; + return ( + this.isInstalled('electron-winstaller') && + !process.env.DISABLE_SQUIRREL_TEST + ); } - async make({ dir, makeDir, targetArch, packageJSON, appName, forgeConfig }: MakerOptions): Promise { + async make({ + dir, + makeDir, + targetArch, + packageJSON, + appName, + forgeConfig, + }: MakerOptions): Promise { const outPath = path.resolve(makeDir, `squirrel.windows/${targetArch}`); await this.ensureDirectory(outPath); const winstallerConfig: ElectronWinstallerOptions = { - name: typeof packageJSON.name === 'string' ? packageJSON.name.replace(/-/g, '_') : undefined, // squirrel hates hyphens + name: + typeof packageJSON.name === 'string' + ? packageJSON.name.replace(/-/g, '_') + : undefined, // squirrel hates hyphens title: appName, noMsi: true, exe: `${forgeConfig.packagerConfig.executableName || appName}.exe`, @@ -38,13 +58,26 @@ export default class MakerSquirrel extends MakerBase { const artifacts = [ path.resolve(outPath, 'RELEASES'), path.resolve(outPath, winstallerConfig.setupExe || `${appName}Setup.exe`), - path.resolve(outPath, `${winstallerConfig.name}-${nupkgVersion}-full.nupkg`), + path.resolve( + outPath, + `${winstallerConfig.name}-${nupkgVersion}-full.nupkg`, + ), ]; - const deltaPath = path.resolve(outPath, `${winstallerConfig.name}-${nupkgVersion}-delta.nupkg`); - if (winstallerConfig.remoteReleases && !winstallerConfig.noDelta && (await fs.pathExists(deltaPath))) { + const deltaPath = path.resolve( + outPath, + `${winstallerConfig.name}-${nupkgVersion}-delta.nupkg`, + ); + if ( + winstallerConfig.remoteReleases && + !winstallerConfig.noDelta && + (await fs.pathExists(deltaPath)) + ) { artifacts.push(deltaPath); } - const msiPath = path.resolve(outPath, winstallerConfig.setupMsi || `${appName}Setup.msi`); + const msiPath = path.resolve( + outPath, + winstallerConfig.setupMsi || `${appName}Setup.msi`, + ); if (!winstallerConfig.noMsi && (await fs.pathExists(msiPath))) { artifacts.push(msiPath); } diff --git a/packages/maker/wix/spec/author-name.spec.ts b/packages/maker/wix/spec/author-name.spec.ts index 21d8013772..b5af161d33 100644 --- a/packages/maker/wix/spec/author-name.spec.ts +++ b/packages/maker/wix/spec/author-name.spec.ts @@ -29,7 +29,9 @@ describe('author-name', () => { }, ].forEach((scenario) => { it(`${JSON.stringify(scenario.author)} -> "${scenario.expectedReturnValue}"`, () => { - expect(getNameFromAuthor(scenario.author)).toEqual(scenario.expectedReturnValue); + expect(getNameFromAuthor(scenario.author)).toEqual( + scenario.expectedReturnValue, + ); }); }); }); diff --git a/packages/maker/wix/src/Config.ts b/packages/maker/wix/src/Config.ts index 7e8df6fbee..e8b6183b4e 100644 --- a/packages/maker/wix/src/Config.ts +++ b/packages/maker/wix/src/Config.ts @@ -1,6 +1,15 @@ import { MSICreator, MSICreatorOptions } from 'electron-wix-msi/lib/creator'; -export type MakerWixConfig = Omit & { +export type MakerWixConfig = Omit< + MSICreatorOptions, + | 'appDirectory' + | 'outputDirectory' + | 'description' + | 'name' + | 'version' + | 'manufacturer' + | 'exe' +> & { /** * The app's description * diff --git a/packages/maker/wix/src/MakerWix.ts b/packages/maker/wix/src/MakerWix.ts index 9e560aa2a5..a195349292 100644 --- a/packages/maker/wix/src/MakerWix.ts +++ b/packages/maker/wix/src/MakerWix.ts @@ -19,19 +19,28 @@ export default class MakerWix extends MakerBase { return process.platform === 'win32'; } - async make({ dir, makeDir, targetArch, packageJSON, appName }: MakerOptions): Promise { + async make({ + dir, + makeDir, + targetArch, + packageJSON, + appName, + }: MakerOptions): Promise { const outPath = path.resolve(makeDir, `wix/${targetArch}`); await this.ensureDirectory(outPath); const { version } = packageJSON; const parsed = semver.parse(version); - if ((Array.isArray(parsed?.prerelease) && parsed.prerelease.length > 0) || (Array.isArray(parsed?.build) && parsed.build.length > 0)) { + if ( + (Array.isArray(parsed?.prerelease) && parsed.prerelease.length > 0) || + (Array.isArray(parsed?.build) && parsed.build.length > 0) + ) { console.warn( logSymbols.warning, chalk.yellow( 'WARNING: MSI packages follow Windows version format "major.minor.build.revision".\n' + - `The provided semantic version "${version}" will be transformed to Windows version format. Prerelease component will not be retained.` - ) + `The provided semantic version "${version}" will be transformed to Windows version format. Prerelease component will not be retained.`, + ), ); } diff --git a/packages/maker/wix/src/util/author-name.ts b/packages/maker/wix/src/util/author-name.ts index 6f00be6d74..2a884147af 100644 --- a/packages/maker/wix/src/util/author-name.ts +++ b/packages/maker/wix/src/util/author-name.ts @@ -8,7 +8,11 @@ export default function getNameFromAuthor(author: PackagePerson): string { publisher = parseAuthor(publisher); } - if (typeof publisher !== 'string' && publisher && typeof publisher.name === 'string') { + if ( + typeof publisher !== 'string' && + publisher && + typeof publisher.name === 'string' + ) { publisher = publisher.name; } diff --git a/packages/maker/zip/spec/MakerZip.spec.ts b/packages/maker/zip/spec/MakerZip.spec.ts index d6bb2ae5e6..2573fc8d25 100644 --- a/packages/maker/zip/spec/MakerZip.spec.ts +++ b/packages/maker/zip/spec/MakerZip.spec.ts @@ -49,59 +49,39 @@ describe('MakerZip', () => { const targetArch = process.arch; const packageJSON = { version: '1.2.3' }; - it.each([['win32', 'linux']])(`should generate a zip file for a %s app`, async (platform) => { - const maker = new MakerZIP({}, []); - maker.ensureFile = vi.fn(); - const output = await maker.make({ - dir, - makeDir, - appName, - targetArch, - targetPlatform: platform, - packageJSON, - forgeConfig: null as any, - }); - - expect(output).toHaveLength(1); - expect(zip).toHaveBeenCalledOnce(); - expect(zip).toHaveBeenCalledWith(dir, path.join(makeDir, 'zip', platform, targetArch, 'fake-app-1.2.3.zip'), expect.anything()); - }); - - it.each([['darwin', 'mas']])(`should generate a zip file for a %s app`, async (platform) => { - const maker = new MakerZIP( - { - macUpdateManifestBaseUrl: undefined, - }, - [] - ); - maker.prepareConfig(targetArch); - maker.ensureFile = vi.fn(); - const output = await maker.make({ - dir: darwinDir, - makeDir, - appName, - targetArch, - targetPlatform: platform, - packageJSON, - forgeConfig: null as any, - }); + it.each([['win32', 'linux']])( + `should generate a zip file for a %s app`, + async (platform) => { + const maker = new MakerZIP({}, []); + maker.ensureFile = vi.fn(); + const output = await maker.make({ + dir, + makeDir, + appName, + targetArch, + targetPlatform: platform, + packageJSON, + forgeConfig: null as any, + }); - expect(output).toHaveLength(1); - expect(zip).toHaveBeenCalledOnce(); - expect(zip).toHaveBeenCalledWith( - path.join(darwinDir, 'My Test App.app'), - path.join(makeDir, 'zip', platform, targetArch, 'fake-darwin-app-1.2.3.zip'), - expect.anything() - ); - }); + expect(output).toHaveLength(1); + expect(zip).toHaveBeenCalledOnce(); + expect(zip).toHaveBeenCalledWith( + dir, + path.join(makeDir, 'zip', platform, targetArch, 'fake-app-1.2.3.zip'), + expect.anything(), + ); + }, + ); - describe('macUpdateManifestBaseUrl', () => { - it.each([['win32', 'mas', 'linux']])('should not make a network request on $platform', async (platform) => { + it.each([['darwin', 'mas']])( + `should generate a zip file for a %s app`, + async (platform) => { const maker = new MakerZIP( { - macUpdateManifestBaseUrl: 'https://electronjs.org', + macUpdateManifestBaseUrl: undefined, }, - [] + [], ); maker.prepareConfig(targetArch); maker.ensureFile = vi.fn(); @@ -116,8 +96,47 @@ describe('MakerZip', () => { }); expect(output).toHaveLength(1); - expect(got.get).not.toHaveBeenCalled(); - }); + expect(zip).toHaveBeenCalledOnce(); + expect(zip).toHaveBeenCalledWith( + path.join(darwinDir, 'My Test App.app'), + path.join( + makeDir, + 'zip', + platform, + targetArch, + 'fake-darwin-app-1.2.3.zip', + ), + expect.anything(), + ); + }, + ); + + describe('macUpdateManifestBaseUrl', () => { + it.each([['win32', 'mas', 'linux']])( + 'should not make a network request on $platform', + async (platform) => { + const maker = new MakerZIP( + { + macUpdateManifestBaseUrl: 'https://electronjs.org', + }, + [], + ); + maker.prepareConfig(targetArch); + maker.ensureFile = vi.fn(); + const output = await maker.make({ + dir: darwinDir, + makeDir, + appName, + targetArch, + targetPlatform: platform, + packageJSON, + forgeConfig: null as any, + }); + + expect(output).toHaveLength(1); + expect(got.get).not.toHaveBeenCalled(); + }, + ); describe('when making for the darwin platform', () => { it('should fetch the current RELEASES.json and write it to disk', async () => { @@ -125,7 +144,7 @@ describe('MakerZip', () => { { macUpdateManifestBaseUrl: 'fake://test/foo', }, - [] + [], ); maker.prepareConfig(targetArch); maker.ensureFile = vi.fn(); @@ -163,11 +182,14 @@ describe('MakerZip', () => { { macUpdateManifestBaseUrl: 'fake://test/foo', }, - [] + [], ); maker.prepareConfig(targetArch); maker.ensureFile = vi.fn(); - vi.mocked(got.get).mockResolvedValue({ statusCode: 404, body: undefined }); + vi.mocked(got.get).mockResolvedValue({ + statusCode: 404, + body: undefined, + }); await maker.make({ dir: darwinDir, makeDir, @@ -202,7 +224,7 @@ describe('MakerZip', () => { macUpdateManifestBaseUrl: 'fake://test/foo', macUpdateReleaseNotes: 'my-notes', }, - [] + [], ); maker.prepareConfig(targetArch); maker.ensureFile = vi.fn(); @@ -231,22 +253,25 @@ describe('MakerZip', () => { forgeConfig: null as any, }); - expect(vi.mocked(fs.writeJson)).toHaveBeenCalledWith(expect.anything(), { - currentRelease: '1.2.3', - releases: [ - oneOneOneRelease, - { - version: '1.2.3', - updateTo: { + expect(vi.mocked(fs.writeJson)).toHaveBeenCalledWith( + expect.anything(), + { + currentRelease: '1.2.3', + releases: [ + oneOneOneRelease, + { version: '1.2.3', - name: 'My Test App v1.2.3', - url: 'fake://test/foo/fake-darwin-app-1.2.3.zip', - notes: 'my-notes', - pub_date: expect.anything(), + updateTo: { + version: '1.2.3', + name: 'My Test App v1.2.3', + url: 'fake://test/foo/fake-darwin-app-1.2.3.zip', + notes: 'my-notes', + pub_date: expect.anything(), + }, }, - }, - ], - }); + ], + }, + ); }); }); }); diff --git a/packages/maker/zip/src/MakerZIP.ts b/packages/maker/zip/src/MakerZIP.ts index 91433fd044..8d5da563a6 100644 --- a/packages/maker/zip/src/MakerZIP.ts +++ b/packages/maker/zip/src/MakerZIP.ts @@ -33,13 +33,28 @@ export default class MakerZIP extends MakerBase { return true; } - async make({ dir, makeDir, appName, packageJSON, targetArch, targetPlatform }: MakerOptions): Promise { + async make({ + dir, + makeDir, + appName, + packageJSON, + targetArch, + targetPlatform, + }: MakerOptions): Promise { const { zip } = require('cross-zip'); - const zipDir = ['darwin', 'mas'].includes(targetPlatform) ? path.resolve(dir, `${appName}.app`) : dir; + const zipDir = ['darwin', 'mas'].includes(targetPlatform) + ? path.resolve(dir, `${appName}.app`) + : dir; const zipName = `${path.basename(dir)}-${packageJSON.version}.zip`; - const zipPath = path.resolve(makeDir, 'zip', targetPlatform, targetArch, zipName); + const zipPath = path.resolve( + makeDir, + 'zip', + targetPlatform, + targetArch, + zipName, + ); await this.ensureFile(zipPath); await promisify(zip)(zipDir, zipPath); @@ -62,7 +77,9 @@ export default class MakerZIP extends MakerBase { updateUrl.pathname += `/${zipName}`; // Remove existing release if it is already in the manifest currentValue.releases = currentValue.releases || []; - currentValue.releases = currentValue.releases.filter((release) => release.version !== packageJSON.version); + currentValue.releases = currentValue.releases.filter( + (release) => release.version !== packageJSON.version, + ); // Add the current version as the current release currentValue.currentRelease = packageJSON.version; currentValue.releases.push({ @@ -76,7 +93,13 @@ export default class MakerZIP extends MakerBase { }, }); - const releasesPath = path.resolve(makeDir, 'zip', targetPlatform, targetArch, 'RELEASES.json'); + const releasesPath = path.resolve( + makeDir, + 'zip', + targetPlatform, + targetArch, + 'RELEASES.json', + ); await this.ensureFile(releasesPath); await fs.writeJson(releasesPath, currentValue); diff --git a/packages/plugin/auto-unpack-natives/src/AutoUnpackNativesPlugin.ts b/packages/plugin/auto-unpack-natives/src/AutoUnpackNativesPlugin.ts index 3c4ffd72c5..aed54e54b5 100644 --- a/packages/plugin/auto-unpack-natives/src/AutoUnpackNativesPlugin.ts +++ b/packages/plugin/auto-unpack-natives/src/AutoUnpackNativesPlugin.ts @@ -12,12 +12,16 @@ export default class AutoUnpackNativesPlugin extends PluginBase = async (forgeConfig) => { + resolveForgeConfig: ForgeHookFn<'resolveForgeConfig'> = async ( + forgeConfig, + ) => { if (!forgeConfig.packagerConfig) { forgeConfig.packagerConfig = {}; } if (!forgeConfig.packagerConfig.asar) { - throw new Error('The AutoUnpackNatives plugin requires asar to be truthy or an object'); + throw new Error( + 'The AutoUnpackNatives plugin requires asar to be truthy or an object', + ); } if (forgeConfig.packagerConfig.asar === true) { forgeConfig.packagerConfig.asar = {}; diff --git a/packages/plugin/base/src/Plugin.ts b/packages/plugin/base/src/Plugin.ts index 4efcbc1a1d..defb3d5716 100644 --- a/packages/plugin/base/src/Plugin.ts +++ b/packages/plugin/base/src/Plugin.ts @@ -55,10 +55,16 @@ export default abstract class Plugin implements IForgePlugin { * @internal */ export const namedHookWithTaskFn = ( - hookFn: (task: ForgeListrTask | null, ...args: Parameters>) => ReturnType>, - name: string + hookFn: ( + task: ForgeListrTask | null, + ...args: Parameters> + ) => ReturnType>, + name: string, ): ForgeHookFn => { - function namedHookWithTaskInner(this: ForgeListrTask | null, ...args: any[]) { + function namedHookWithTaskInner( + this: ForgeListrTask | null, + ...args: any[] + ) { return (hookFn as any)(this, ...args); } const fn = namedHookWithTaskInner as any; diff --git a/packages/plugin/electronegativity/src/ElectronegativityPlugin.ts b/packages/plugin/electronegativity/src/ElectronegativityPlugin.ts index 32b25e36c9..42c6305dbd 100644 --- a/packages/plugin/electronegativity/src/ElectronegativityPlugin.ts +++ b/packages/plugin/electronegativity/src/ElectronegativityPlugin.ts @@ -61,7 +61,10 @@ export default class ElectronegativityPlugin extends PluginBase = async (_forgeConfig, options): Promise => { + postPackage: ForgeHookFn<'postPackage'> = async ( + _forgeConfig, + options, + ): Promise => { await runElectronegativity( { ...this.config, @@ -69,7 +72,7 @@ export default class ElectronegativityPlugin extends PluginBase { const packageJSON = JSON.parse( fs.readFileSync(path.join(appPath, 'package.json.tmpl'), { encoding: 'utf-8', - }) + }), ); const { name: appName } = packageJSON; @@ -27,9 +30,15 @@ describe('FusesPlugin', () => { const outDir = path.join(appPath, 'out', 'fuses-test-app'); beforeAll(async () => { - await spawnPackageManager(PACKAGE_MANAGERS['yarn'], ['run', 'link:prepare']); + await spawnPackageManager(PACKAGE_MANAGERS['yarn'], [ + 'run', + 'link:prepare', + ]); delete process.env.TS_NODE_PROJECT; - await fs.promises.copyFile(path.join(appPath, 'package.json.tmpl'), path.join(appPath, 'package.json')); + await fs.promises.copyFile( + path.join(appPath, 'package.json.tmpl'), + path.join(appPath, 'package.json'), + ); await spawn('yarn', ['install'], spawnOptions); // Installing deps removes symlinks that were added at the start of this @@ -41,7 +50,10 @@ describe('FusesPlugin', () => { }); afterAll(async () => { - await fs.promises.rm(path.resolve(outDir, '../'), { recursive: true, force: true }); + await fs.promises.rm(path.resolve(outDir, '../'), { + recursive: true, + force: true, + }); }); it('should flip Fuses', async () => { @@ -49,11 +61,17 @@ describe('FusesPlugin', () => { const electronExecutablePath = getElectronExecutablePath({ appName, - basePath: path.join(outDir, ...(process.platform === 'darwin' ? [`${appName}.app`, 'Contents'] : [])), + basePath: path.join( + outDir, + ...(process.platform === 'darwin' + ? [`${appName}.app`, 'Contents'] + : []), + ), platform: process.platform, }); - const args: string[] = process.platform === 'linux' ? ['-v', '--no-sandbox'] : ['-v']; + const args: string[] = + process.platform === 'linux' ? ['-v', '--no-sandbox'] : ['-v']; /** * If the `RunAsNode` fuse had not been flipped, diff --git a/packages/plugin/fuses/src/FusesPlugin.ts b/packages/plugin/fuses/src/FusesPlugin.ts index a94d349e6c..9244992e1b 100644 --- a/packages/plugin/fuses/src/FusesPlugin.ts +++ b/packages/plugin/fuses/src/FusesPlugin.ts @@ -19,27 +19,45 @@ export default class FusesPlugin extends PluginBase { getHooks(): ForgeMultiHookMap { return { - packageAfterCopy: namedHookWithTaskFn<'packageAfterCopy'>(async (listrTask, resolvedForgeConfig, resourcesPath, electronVersion, platform, arch) => { - const { fusesConfig } = this; - - const applePlatforms: ForgePlatform[] = ['darwin', 'mas']; - - if (Object.keys(fusesConfig).length) { - const pathToElectronExecutable = getElectronExecutablePath({ - appName: applePlatforms.includes(platform) ? 'Electron' : 'electron', - basePath: path.resolve(resourcesPath, '../..'), - platform, - }); - - const osxSignConfig = resolvedForgeConfig.packagerConfig.osxSign; - const hasOSXSignConfig = (typeof osxSignConfig === 'object' && Boolean(Object.keys(osxSignConfig).length)) || Boolean(osxSignConfig); - - await flipFuses(pathToElectronExecutable, { - resetAdHocDarwinSignature: !hasOSXSignConfig && applePlatforms.includes(platform) && arch === 'arm64', - ...this.fusesConfig, - }); - } - }, 'Flipping Fuses'), + packageAfterCopy: namedHookWithTaskFn<'packageAfterCopy'>( + async ( + listrTask, + resolvedForgeConfig, + resourcesPath, + electronVersion, + platform, + arch, + ) => { + const { fusesConfig } = this; + + const applePlatforms: ForgePlatform[] = ['darwin', 'mas']; + + if (Object.keys(fusesConfig).length) { + const pathToElectronExecutable = getElectronExecutablePath({ + appName: applePlatforms.includes(platform) + ? 'Electron' + : 'electron', + basePath: path.resolve(resourcesPath, '../..'), + platform, + }); + + const osxSignConfig = resolvedForgeConfig.packagerConfig.osxSign; + const hasOSXSignConfig = + (typeof osxSignConfig === 'object' && + Boolean(Object.keys(osxSignConfig).length)) || + Boolean(osxSignConfig); + + await flipFuses(pathToElectronExecutable, { + resetAdHocDarwinSignature: + !hasOSXSignConfig && + applePlatforms.includes(platform) && + arch === 'arm64', + ...this.fusesConfig, + }); + } + }, + 'Flipping Fuses', + ), }; } } diff --git a/packages/plugin/fuses/src/util/getElectronExecutablePath.ts b/packages/plugin/fuses/src/util/getElectronExecutablePath.ts index fcf9f0cabf..d3f8bc1dd3 100644 --- a/packages/plugin/fuses/src/util/getElectronExecutablePath.ts +++ b/packages/plugin/fuses/src/util/getElectronExecutablePath.ts @@ -8,7 +8,11 @@ type GetElectronExecutablePathParams = { platform: ForgePlatform; }; -export function getElectronExecutablePath({ appName, basePath, platform }: GetElectronExecutablePathParams): string { +export function getElectronExecutablePath({ + appName, + basePath, + platform, +}: GetElectronExecutablePathParams): string { if (['darwin', 'mas'].includes(platform)) { return path.join(basePath, 'MacOS', appName); } diff --git a/packages/plugin/local-electron/spec/LocalElectronPlugin.spec.ts b/packages/plugin/local-electron/spec/LocalElectronPlugin.spec.ts index d280ea879a..155675640a 100644 --- a/packages/plugin/local-electron/spec/LocalElectronPlugin.spec.ts +++ b/packages/plugin/local-electron/spec/LocalElectronPlugin.spec.ts @@ -28,15 +28,21 @@ describe('LocalElectronPlugin', () => { it('should not set ELECTRON_OVERRIDE_DIST_PATH when disabled', async () => { expect(process.env.ELECTRON_OVERRIDE_DIST_PATH).toEqual(undefined); - const p = new LocalElectronPlugin({ enabled: false, electronPath: 'test/foo' }); + const p = new LocalElectronPlugin({ + enabled: false, + electronPath: 'test/foo', + }); await p.getHooks().preStart?.(fakeForgeConfig); expect(process.env.ELECTRON_OVERRIDE_DIST_PATH).toEqual(undefined); }); it("should throw an error if platforms don't match", async () => { - const p = new LocalElectronPlugin({ electronPath: 'test/bar', electronPlatform: 'wut' }); + const p = new LocalElectronPlugin({ + electronPath: 'test/bar', + electronPlatform: 'wut', + }); await expect(p.getHooks().preStart?.(fakeForgeConfig)).rejects.toThrow( - `Can not use local Electron version, required platform "${process.platform}" but local platform is "wut"` + `Can not use local Electron version, required platform "${process.platform}" but local platform is "wut"`, ); }); }); @@ -71,7 +77,13 @@ describe('LocalElectronPlugin', () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const fn = p.getHooks().packageAfterExtract!; - await fn({} as ResolvedForgeConfig, tmpDir, 'null', process.platform, process.arch); + await fn( + {} as ResolvedForgeConfig, + tmpDir, + 'null', + process.platform, + process.arch, + ); expect(fs.existsSync(tmpDir)).toEqual(true); expect(fs.existsSync(path.resolve(tmpDir, 'touch'))).toEqual(true); @@ -81,8 +93,10 @@ describe('LocalElectronPlugin', () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const fn = p.getHooks().packageAfterExtract!; - await expect(fn({} as ResolvedForgeConfig, tmpDir, 'null', 'bad', process.arch)).rejects.toThrow( - `Can not use local Electron version, required platform "bad" but local platform is "${process.platform}"` + await expect( + fn({} as ResolvedForgeConfig, tmpDir, 'null', 'bad', process.arch), + ).rejects.toThrow( + `Can not use local Electron version, required platform "bad" but local platform is "${process.platform}"`, ); }); @@ -90,23 +104,44 @@ describe('LocalElectronPlugin', () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const fn = p.getHooks().packageAfterExtract!; - await expect(fn({} as ResolvedForgeConfig, tmpDir, 'null', process.platform, 'bad')).rejects.toThrow( - `Can not use local Electron version, required arch "bad" but local arch is "${process.arch}"` + await expect( + fn( + {} as ResolvedForgeConfig, + tmpDir, + 'null', + process.platform, + 'bad', + ), + ).rejects.toThrow( + `Can not use local Electron version, required arch "bad" but local arch is "${process.arch}"`, ); }); it('should copy the electron dir to the build dir if everything is ok and enabled', async () => { - const electronDir = await fs.promises.mkdtemp(path.resolve(os.tmpdir(), 'electron-tmp-')); - await fs.promises.writeFile(path.resolve(electronDir, 'electron'), 'hi i am electron I swear'); + const electronDir = await fs.promises.mkdtemp( + path.resolve(os.tmpdir(), 'electron-tmp-'), + ); + await fs.promises.writeFile( + path.resolve(electronDir, 'electron'), + 'hi i am electron I swear', + ); p.config.electronPath = electronDir; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const fn = p.getHooks().packageAfterExtract!; - await fn({} as ResolvedForgeConfig, tmpDir, 'null', process.platform, process.arch); + await fn( + {} as ResolvedForgeConfig, + tmpDir, + 'null', + process.platform, + process.arch, + ); expect(fs.existsSync(path.resolve(tmpDir, 'touch'))).toEqual(false); expect(fs.existsSync(path.resolve(tmpDir, 'electron'))).toEqual(true); - expect(await fs.promises.readFile(path.resolve(tmpDir, 'electron'), 'utf8')).toEqual('hi i am electron I swear'); + expect( + await fs.promises.readFile(path.resolve(tmpDir, 'electron'), 'utf8'), + ).toEqual('hi i am electron I swear'); }); }); }); diff --git a/packages/plugin/local-electron/src/LocalElectronPlugin.ts b/packages/plugin/local-electron/src/LocalElectronPlugin.ts index 98f09c21c8..5911a5e35a 100644 --- a/packages/plugin/local-electron/src/LocalElectronPlugin.ts +++ b/packages/plugin/local-electron/src/LocalElectronPlugin.ts @@ -30,14 +30,16 @@ export default class LocalElectronPlugin extends PluginBase { if ((this.config.electronPlatform || process.platform) !== platform) { throw new Error( - `Can not use local Electron version, required platform "${platform}" but local platform is "${this.config.electronPlatform || process.platform}"` + `Can not use local Electron version, required platform "${platform}" but local platform is "${this.config.electronPlatform || process.platform}"`, ); } }; private checkArch = (arch: string) => { if ((this.config.electronArch || process.arch) !== arch) { - throw new Error(`Can not use local Electron version, required arch "${arch}" but local arch is "${this.config.electronArch || process.arch}"`); + throw new Error( + `Can not use local Electron version, required arch "${arch}" but local arch is "${this.config.electronArch || process.arch}"`, + ); } }; @@ -48,7 +50,13 @@ export default class LocalElectronPlugin extends PluginBase = async (_config, buildPath, _electronVersion, platform, arch) => { + private afterExtract: ForgeHookFn<'packageAfterExtract'> = async ( + _config, + buildPath, + _electronVersion, + platform, + arch, + ) => { if (!this.enabled) return; this.checkPlatform(platform); diff --git a/packages/plugin/vite/forge-vite-env.d.ts b/packages/plugin/vite/forge-vite-env.d.ts index 6fef99673e..c99aab1ee6 100644 --- a/packages/plugin/vite/forge-vite-env.d.ts +++ b/packages/plugin/vite/forge-vite-env.d.ts @@ -15,7 +15,9 @@ declare global { } declare module 'vite' { - interface ConfigEnv { + interface ConfigEnv< + K extends keyof VitePluginConfig = keyof VitePluginConfig, + > { root: string; forgeConfig: VitePluginConfig; forgeConfigSelf: VitePluginConfig[K][number]; diff --git a/packages/plugin/vite/spec/ViteConfig.spec.ts b/packages/plugin/vite/spec/ViteConfig.spec.ts index bea9d18396..04dfc07c69 100644 --- a/packages/plugin/vite/spec/ViteConfig.spec.ts +++ b/packages/plugin/vite/spec/ViteConfig.spec.ts @@ -31,12 +31,21 @@ describe('ViteConfigGenerator', () => { expect(buildConfig.build?.outDir).toEqual('.vite/build'); expect(buildConfig.build?.watch).toBeNull(); expect(buildConfig.build?.minify).toBe(true); - expect(buildConfig.build?.lib && buildConfig.build.lib.entry).toEqual('src/main.js'); - expect(buildConfig.build?.lib && (buildConfig.build.lib.fileName as () => string)()).toEqual('[name].js'); - expect(buildConfig.build?.lib && buildConfig.build.lib.formats).toEqual(['cjs']); + expect(buildConfig.build?.lib && buildConfig.build.lib.entry).toEqual( + 'src/main.js', + ); + expect( + buildConfig.build?.lib && + (buildConfig.build.lib.fileName as () => string)(), + ).toEqual('[name].js'); + expect(buildConfig.build?.lib && buildConfig.build.lib.formats).toEqual([ + 'cjs', + ]); expect(buildConfig.build?.rollupOptions?.external).toEqual(external); expect(buildConfig.clearScreen).toBe(false); - expect(buildConfig.plugins?.map((plugin) => (plugin as Plugin).name)).toEqual(['@electron-forge/plugin-vite:hot-restart']); + expect( + buildConfig.plugins?.map((plugin) => (plugin as Plugin).name), + ).toEqual(['@electron-forge/plugin-vite:hot-restart']); expect(buildConfig.define).toEqual({}); expect(buildConfig.resolve).toEqual({ conditions: ['node'], @@ -74,7 +83,9 @@ describe('ViteConfigGenerator', () => { assetFileNames: '[name].[ext]', }); expect(buildConfig.clearScreen).toBe(false); - expect(buildConfig.plugins?.map((plugin) => (plugin as Plugin).name)).toEqual(['@electron-forge/plugin-vite:hot-restart']); + expect( + buildConfig.plugins?.map((plugin) => (plugin as Plugin).name), + ).toEqual(['@electron-forge/plugin-vite:hot-restart']); }); it('getRendererConfig:renderer', async () => { @@ -94,7 +105,9 @@ describe('ViteConfigGenerator', () => { expect(rendererConfig.mode).toEqual('production'); expect(rendererConfig.base).toEqual('./'); expect(rendererConfig.build?.outDir).toEqual('.vite/renderer/main_window'); - expect(rendererConfig.plugins?.map((plugin) => (plugin as Plugin).name)).toEqual(['@electron-forge/plugin-vite:expose-renderer']); + expect( + rendererConfig.plugins?.map((plugin) => (plugin as Plugin).name), + ).toEqual(['@electron-forge/plugin-vite:expose-renderer']); expect(rendererConfig.resolve).toEqual({ preserveSymlinks: true }); expect(rendererConfig.clearScreen).toBe(false); }); diff --git a/packages/plugin/vite/spec/VitePlugin.spec.ts b/packages/plugin/vite/spec/VitePlugin.spec.ts index 16c552bb7a..a67d91ea61 100644 --- a/packages/plugin/vite/spec/VitePlugin.spec.ts +++ b/packages/plugin/vite/spec/VitePlugin.spec.ts @@ -32,31 +32,61 @@ describe('VitePlugin', async () => { }); it('should remove config.forge from package.json', async () => { - const packageJSON = { main: './.vite/build/main.js', config: { forge: 'config.js' } }; - await fs.promises.writeFile(packageJSONPath, JSON.stringify(packageJSON), 'utf-8'); + const packageJSON = { + main: './.vite/build/main.js', + config: { forge: 'config.js' }, + }; + await fs.promises.writeFile( + packageJSONPath, + JSON.stringify(packageJSON), + 'utf-8', + ); await plugin.packageAfterCopy({} as ResolvedForgeConfig, packagedPath); expect(fs.existsSync(packagedPackageJSONPath)).toEqual(true); - expect(JSON.parse(await fs.promises.readFile(packagedPackageJSONPath, 'utf-8')).config).not.toHaveProperty('forge'); + expect( + JSON.parse(await fs.promises.readFile(packagedPackageJSONPath, 'utf-8')) + .config, + ).not.toHaveProperty('forge'); }); it('should succeed if there is no config.forge', async () => { const packageJSON = { main: '.vite/build/main.js' }; - await fs.promises.writeFile(packageJSONPath, JSON.stringify(packageJSON), 'utf-8'); + await fs.promises.writeFile( + packageJSONPath, + JSON.stringify(packageJSON), + 'utf-8', + ); await plugin.packageAfterCopy({} as ResolvedForgeConfig, packagedPath); expect(fs.existsSync(packagedPackageJSONPath)).toEqual(true); - expect(JSON.parse(await fs.promises.readFile(packagedPackageJSONPath, 'utf-8'))).not.toHaveProperty('config'); + expect( + JSON.parse( + await fs.promises.readFile(packagedPackageJSONPath, 'utf-8'), + ), + ).not.toHaveProperty('config'); }); it('should fail if there is no main key in package.json', async () => { const packageJSON = {}; - await fs.promises.writeFile(packageJSONPath, JSON.stringify(packageJSON), 'utf-8'); - await expect(plugin.packageAfterCopy({} as ResolvedForgeConfig, packagedPath)).rejects.toThrow(/entry point/); + await fs.promises.writeFile( + packageJSONPath, + JSON.stringify(packageJSON), + 'utf-8', + ); + await expect( + plugin.packageAfterCopy({} as ResolvedForgeConfig, packagedPath), + ).rejects.toThrow(/entry point/); }); it('should fail if main in package.json does not starts with .vite/', async () => { const packageJSON = { main: 'src/main.js' }; - await fs.promises.writeFile(packageJSONPath, JSON.stringify(packageJSON), 'utf-8'); - await expect(plugin.packageAfterCopy({} as ResolvedForgeConfig, packagedPath)).rejects.toThrow(/entry point/); + await fs.promises.writeFile( + packageJSONPath, + JSON.stringify(packageJSON), + 'utf-8', + ); + await expect( + plugin.packageAfterCopy({} as ResolvedForgeConfig, packagedPath), + ).rejects.toThrow(/entry point/); }); afterAll(async () => { @@ -89,7 +119,9 @@ describe('VitePlugin', async () => { }); it('ignores everything but files in .vite', async () => { - const config = await plugin.resolveForgeConfig({} as ResolvedForgeConfig); + const config = await plugin.resolveForgeConfig( + {} as ResolvedForgeConfig, + ); const ignore = config.packagerConfig.ignore as IgnoreFunction; expect(ignore('')).toEqual(false); @@ -101,25 +133,77 @@ describe('VitePlugin', async () => { it('ignores source map files by default', async () => { const viteConfig = { ...baseConfig }; plugin = new VitePlugin(viteConfig); - const config = await plugin.resolveForgeConfig({} as ResolvedForgeConfig); + const config = await plugin.resolveForgeConfig( + {} as ResolvedForgeConfig, + ); const ignore = config.packagerConfig.ignore as IgnoreFunction; - expect(ignore(path.posix.join('/.vite', 'build', 'main.js'))).toEqual(false); - expect(ignore(path.posix.join('/.vite', 'build', 'main.js.map'))).toEqual(false); - expect(ignore(path.posix.join('/.vite', 'renderer', 'main_window', 'assets', 'index.js'))).toEqual(false); - expect(ignore(path.posix.join('/.vite', 'renderer', 'main_window', 'assets', 'index.js.map'))).toEqual(false); + expect(ignore(path.posix.join('/.vite', 'build', 'main.js'))).toEqual( + false, + ); + expect( + ignore(path.posix.join('/.vite', 'build', 'main.js.map')), + ).toEqual(false); + expect( + ignore( + path.posix.join( + '/.vite', + 'renderer', + 'main_window', + 'assets', + 'index.js', + ), + ), + ).toEqual(false); + expect( + ignore( + path.posix.join( + '/.vite', + 'renderer', + 'main_window', + 'assets', + 'index.js.map', + ), + ), + ).toEqual(false); }); it('includes source map files when specified by config', async () => { const viteConfig = { ...baseConfig, packageSourceMaps: true }; plugin = new VitePlugin(viteConfig); - const config = await plugin.resolveForgeConfig({} as ResolvedForgeConfig); + const config = await plugin.resolveForgeConfig( + {} as ResolvedForgeConfig, + ); const ignore = config.packagerConfig.ignore as IgnoreFunction; - expect(ignore(path.posix.join('/.vite', 'build', 'main.js'))).toEqual(false); - expect(ignore(path.posix.join('/.vite', 'build', 'main.js.map'))).toEqual(false); - expect(ignore(path.posix.join('/.vite', 'renderer', 'main_window', 'assets', 'index.js'))).toEqual(false); - expect(ignore(path.posix.join('/.vite', 'renderer', 'main_window', 'assets', 'index.js.map'))).toEqual(false); + expect(ignore(path.posix.join('/.vite', 'build', 'main.js'))).toEqual( + false, + ); + expect( + ignore(path.posix.join('/.vite', 'build', 'main.js.map')), + ).toEqual(false); + expect( + ignore( + path.posix.join( + '/.vite', + 'renderer', + 'main_window', + 'assets', + 'index.js', + ), + ), + ).toEqual(false); + expect( + ignore( + path.posix.join( + '/.vite', + 'renderer', + 'main_window', + 'assets', + 'index.js.map', + ), + ), + ).toEqual(false); }); }); }); diff --git a/packages/plugin/vite/spec/config/vite.base.config.spec.ts b/packages/plugin/vite/spec/config/vite.base.config.spec.ts index cc15d658b7..ba5d0c087c 100644 --- a/packages/plugin/vite/spec/config/vite.base.config.spec.ts +++ b/packages/plugin/vite/spec/config/vite.base.config.spec.ts @@ -3,7 +3,11 @@ import path from 'node:path'; import { createServer } from 'vite'; import { describe, expect, it } from 'vitest'; -import { getBuildDefine, getDefineKeys, pluginExposeRenderer } from '../../src/config/vite.base.config'; +import { + getBuildDefine, + getDefineKeys, + pluginExposeRenderer, +} from '../../src/config/vite.base.config'; import type { VitePluginConfig } from '../../src/Config'; @@ -35,7 +39,9 @@ const forgeConfig: VitePluginConfig = { describe('vite.base.config', () => { it('getDefineKeys', () => { - const defineKeys1 = getDefineKeys(forgeConfig.renderer.map(({ name }) => name)); + const defineKeys1 = getDefineKeys( + forgeConfig.renderer.map(({ name }) => name), + ); const defineKeys2 = { main_window: { VITE_DEV_SERVER_URL: 'MAIN_WINDOW_VITE_DEV_SERVER_URL', @@ -74,8 +80,8 @@ describe('vite.base.config', () => { createServer({ publicDir: false, plugins: [pluginExposeRenderer(name)], - }) - ) + }), + ), ); let port = 5173; diff --git a/packages/plugin/vite/src/ViteConfig.ts b/packages/plugin/vite/src/ViteConfig.ts index 8d24c3cd1c..8673f7879a 100644 --- a/packages/plugin/vite/src/ViteConfig.ts +++ b/packages/plugin/vite/src/ViteConfig.ts @@ -5,7 +5,11 @@ import { getConfig as getMainViteConfig } from './config/vite.main.config'; import { getConfig as getPreloadViteConfig } from './config/vite.preload.config'; import { getConfig as getRendererViteConfig } from './config/vite.renderer.config'; -import type { VitePluginBuildConfig, VitePluginConfig, VitePluginRendererConfig } from './Config'; +import type { + VitePluginBuildConfig, + VitePluginConfig, + VitePluginRendererConfig, +} from './Config'; import type { ConfigEnv, UserConfig } from 'vite'; const d = debug('@electron-forge/plugin-vite:ViteConfig'); @@ -13,11 +17,18 @@ const d = debug('@electron-forge/plugin-vite:ViteConfig'); type Target = NonNullable | 'renderer'; export default class ViteConfigGenerator { - constructor(private readonly pluginConfig: VitePluginConfig, private readonly projectDir: string, private readonly isProd: boolean) { + constructor( + private readonly pluginConfig: VitePluginConfig, + private readonly projectDir: string, + private readonly isProd: boolean, + ) { d('Config mode:', this.mode); } - async resolveConfig(buildConfig: VitePluginBuildConfig | VitePluginRendererConfig, target: Target): Promise { + async resolveConfig( + buildConfig: VitePluginBuildConfig | VitePluginRendererConfig, + target: Target, + ): Promise { const configEnv: ConfigEnv = { // @see - https://vitejs.dev/config/#conditional-config command: this.isProd ? 'build' : 'serve', @@ -31,16 +42,25 @@ export default class ViteConfigGenerator { }; // `configEnv` is to be passed as an arguments when the user export a function in `vite.config.js`. - const userConfig = (await loadConfigFromFile(configEnv, buildConfig.config))?.config; + const userConfig = (await loadConfigFromFile(configEnv, buildConfig.config)) + ?.config; switch (target) { case 'main': return getMainViteConfig(configEnv as ConfigEnv<'build'>, userConfig); case 'preload': - return getPreloadViteConfig(configEnv as ConfigEnv<'build'>, userConfig); + return getPreloadViteConfig( + configEnv as ConfigEnv<'build'>, + userConfig, + ); case 'renderer': - return getRendererViteConfig(configEnv as ConfigEnv<'renderer'>, userConfig); + return getRendererViteConfig( + configEnv as ConfigEnv<'renderer'>, + userConfig, + ); default: - throw new Error(`Unknown target: ${target}, expected 'main', 'preload' or 'renderer'`); + throw new Error( + `Unknown target: ${target}, expected 'main', 'preload' or 'renderer'`, + ); } } @@ -59,7 +79,9 @@ export default class ViteConfigGenerator { const configs = this.pluginConfig.build // Prevent load the default `vite.config.js` file. .filter(({ config }) => config) - .map((buildConfig) => this.resolveConfig(buildConfig, buildConfig.target ?? 'main')); + .map((buildConfig) => + this.resolveConfig(buildConfig, buildConfig.target ?? 'main'), + ); return await Promise.all(configs); } diff --git a/packages/plugin/vite/src/VitePlugin.ts b/packages/plugin/vite/src/VitePlugin.ts index f95b45debe..cbd1f4edde 100644 --- a/packages/plugin/vite/src/VitePlugin.ts +++ b/packages/plugin/vite/src/VitePlugin.ts @@ -14,7 +14,11 @@ import { onBuildDone } from './util/plugins'; import ViteConfigGenerator from './ViteConfig'; import type { VitePluginConfig } from './Config'; -import type { ForgeListrTask, ForgeMultiHookMap, ResolvedForgeConfig } from '@electron-forge/shared-types'; +import type { + ForgeListrTask, + ForgeMultiHookMap, + ResolvedForgeConfig, +} from '@electron-forge/shared-types'; import type { AddressInfo } from 'node:net'; const d = debug('electron-forge:plugin:vite'); @@ -67,7 +71,11 @@ export default class VitePlugin extends PluginBase { } private get configGenerator(): ViteConfigGenerator { - return (this.configGeneratorCache ??= new ViteConfigGenerator(this.config, this.projectDir, this.isProd)); + return (this.configGeneratorCache ??= new ViteConfigGenerator( + this.config, + this.projectDir, + this.isProd, + )); } getHooks = (): ForgeMultiHookMap => { @@ -83,10 +91,12 @@ export default class VitePlugin extends PluginBase { return task?.newListr( [ { - title: 'Launching Vite dev servers for renderer process code...', + title: + 'Launching Vite dev servers for renderer process code...', task: async (_ctx, task) => { const result = await this.launchRendererDevServers(task); - task.title = 'Launched Vite dev servers for renderer process code'; + task.title = + 'Launched Vite dev servers for renderer process code'; return result; }, rendererOptions: { @@ -108,7 +118,7 @@ export default class VitePlugin extends PluginBase { }, }, ], - { concurrent: false } + { concurrent: false }, ); }, 'Preparing Vite bundles'), ], @@ -134,7 +144,7 @@ export default class VitePlugin extends PluginBase { }, }, ], - { concurrent: true } + { concurrent: true }, ); }, 'Building production Vite bundles'), ], @@ -150,7 +160,9 @@ export default class VitePlugin extends PluginBase { }; }; - resolveForgeConfig = async (forgeConfig: ResolvedForgeConfig): Promise => { + resolveForgeConfig = async ( + forgeConfig: ResolvedForgeConfig, + ): Promise => { forgeConfig.packagerConfig ??= {}; if (forgeConfig.packagerConfig.ignore) { @@ -158,7 +170,7 @@ export default class VitePlugin extends PluginBase { console.error( chalk.yellow(`You have set packagerConfig.ignore, the Electron Forge Vite plugin normally sets this automatically. -Your packaged app may be larger than expected if you dont ignore everything other than the '.vite' folder`) +Your packaged app may be larger than expected if you dont ignore everything other than the '.vite' folder`), ); } return forgeConfig; @@ -176,7 +188,10 @@ Your packaged app may be larger than expected if you dont ignore everything othe return forgeConfig; }; - packageAfterCopy = async (_forgeConfig: ResolvedForgeConfig, buildPath: string): Promise => { + packageAfterCopy = async ( + _forgeConfig: ResolvedForgeConfig, + buildPath: string, + ): Promise => { const pj = await fs.readJson(path.resolve(this.projectDir, 'package.json')); if (!pj.main?.includes('.vite/')) { @@ -189,18 +204,34 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}.`); delete pj.config.forge; } - await fs.writeJson(path.resolve(buildPath, 'package.json'), pj, { spaces: 2 }); + await fs.writeJson(path.resolve(buildPath, 'package.json'), pj, { + spaces: 2, + }); }; // Main process, Preload scripts and Worker process, etc. build = async (task?: ForgeListrTask): Promise => { const configs = await this.configGenerator.getBuildConfigs(); - const isRollupWatcher = (x: vite.Rollup.RollupWatcher | vite.Rollup.RollupOutput | vite.Rollup.RollupOutput[]): x is vite.Rollup.RollupWatcher => - x && typeof x === 'object' && 'on' in x && typeof x.on === 'function' && 'close' in x && typeof x.close === 'function'; + const isRollupWatcher = ( + x: + | vite.Rollup.RollupWatcher + | vite.Rollup.RollupOutput + | vite.Rollup.RollupOutput[], + ): x is vite.Rollup.RollupWatcher => + x && + typeof x === 'object' && + 'on' in x && + typeof x.on === 'function' && + 'close' in x && + typeof x.close === 'function'; return task?.newListr( configs.map((userConfig) => { - const target = (userConfig.build?.rollupOptions?.input || (typeof userConfig.build?.lib !== 'boolean' && userConfig.build?.lib?.entry)) ?? ''; + const target = + (userConfig.build?.rollupOptions?.input || + (typeof userConfig.build?.lib !== 'boolean' && + userConfig.build?.lib?.entry)) ?? + ''; return { title: `Building ${chalk.green(target)} target`, task: async (_ctx, subtask) => { @@ -214,20 +245,25 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}.`); configFile: false, logLevel: 'silent', // We suppress Vite output and instead log lines using RollupWatcher events ...userConfig, - plugins: [onBuildDone(resolve), ...(userConfig.plugins ?? [])], + plugins: [ + onBuildDone(resolve), + ...(userConfig.plugins ?? []), + ], clearScreen: false, }) .then((result) => { if (isRollupWatcher(result)) { result.on('event', (event) => { if (event.code === 'ERROR') { - console.error(`\n${this.timeFormatter.format(new Date())} ${event.error.message}`); + console.error( + `\n${this.timeFormatter.format(new Date())} ${event.error.message}`, + ); reject(event.error); } else if (event.code === 'BUNDLE_END') { console.log( `${chalk.dim(this.timeFormatter.format(new Date()))} ${chalk.cyan.bold('[@electron-forge/plugin-vite]')} ${chalk.green( - 'target built' - )} ${chalk.dim(target)}` + 'target built', + )} ${chalk.dim(target)}`, ); } }); @@ -248,7 +284,7 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}.`); }), { concurrent: true, - } + }, ); }; @@ -265,7 +301,7 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}.`); }); subtask.title = `Built target ${chalk.dim(path.basename(userConfig.build?.outDir ?? ''))}`; }, - })) + })), ); }; @@ -289,7 +325,10 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}.`); if (viteDevServer.httpServer) { // Make sure that `getDefines` in VitePlugin.ts gets the correct `server.port`. (#3198) const addressInfo = viteDevServer.httpServer.address(); - const isAddressInfo = (x: AddressInfo | string | null): x is AddressInfo => (typeof x === 'object' ? typeof x?.address === 'string' : false); + const isAddressInfo = ( + x: AddressInfo | string | null, + ): x is AddressInfo => + typeof x === 'object' ? typeof x?.address === 'string' : false; if (isAddressInfo(addressInfo)) { userConfig.server ??= {}; @@ -300,11 +339,14 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}.`); rendererOptions: { persistentOutput: true, }, - })) + })), ); }; - exitHandler = (options: { cleanup?: boolean; exit?: boolean }, err?: Error): void => { + exitHandler = ( + options: { cleanup?: boolean; exit?: boolean }, + err?: Error, + ): void => { d('handling process exit with:', options); if (options.cleanup) { for (const watcher of this.watchers) { @@ -331,7 +373,8 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}.`); */ function getServerURLs(urls: vite.ResolvedServerUrls) { let output = ''; - const colorUrl = (url: string) => chalk.cyan(url.replace(/:(\d+)\//, (_, port) => `:${chalk.bold(port)}/`)); + const colorUrl = (url: string) => + chalk.cyan(url.replace(/:(\d+)\//, (_, port) => `:${chalk.bold(port)}/`)); for (const url of urls.local) { output += ` ${chalk.green('➜')} ${chalk.bold('Local')}: ${colorUrl(url)}`; } @@ -339,7 +382,10 @@ function getServerURLs(urls: vite.ResolvedServerUrls) { output += ` \n${chalk.green('➜')} ${chalk.bold('Network')}: ${colorUrl(url)}`; } if (urls.network.length === 0) { - output += chalk.dim(` \n${chalk.green('➜')} ${chalk.bold('Network')}: use `) + chalk.bold('--host') + chalk.dim(' to expose'); + output += + chalk.dim(` \n${chalk.green('➜')} ${chalk.bold('Network')}: use `) + + chalk.bold('--host') + + chalk.dim(' to expose'); } return output; diff --git a/packages/plugin/vite/src/config/vite.base.config.ts b/packages/plugin/vite/src/config/vite.base.config.ts index 10fb0d4c02..cdeb18b524 100644 --- a/packages/plugin/vite/src/config/vite.base.config.ts +++ b/packages/plugin/vite/src/config/vite.base.config.ts @@ -3,7 +3,10 @@ import { builtinModules } from 'node:module'; import type { AddressInfo } from 'node:net'; import type { ConfigEnv, Plugin, UserConfig, ViteDevServer } from 'vite'; -export const builtins = ['electron', ...builtinModules.map((m) => [m, `node:${m}`]).flat()]; +export const builtins = [ + 'electron', + ...builtinModules.map((m) => [m, `node:${m}`]).flat(), +]; export const external = [...builtins]; @@ -48,16 +51,24 @@ export function getDefineKeys(names: string[]) { export function getBuildDefine(env: ConfigEnv<'build'>) { const { command, forgeConfig } = env; - const names = forgeConfig.renderer.filter(({ name }) => name != null).map(({ name }) => name!); + const names = forgeConfig.renderer + .filter(({ name }) => name != null) + .map(({ name }) => name!); const defineKeys = getDefineKeys(names); - const define = Object.entries(defineKeys).reduce((acc, [name, keys]) => { - const { VITE_DEV_SERVER_URL, VITE_NAME } = keys; - const def = { - [VITE_DEV_SERVER_URL]: command === 'serve' ? JSON.stringify(viteDevServerUrls[VITE_DEV_SERVER_URL]) : undefined, - [VITE_NAME]: JSON.stringify(name), - }; - return { ...acc, ...def }; - }, {} as Record); + const define = Object.entries(defineKeys).reduce( + (acc, [name, keys]) => { + const { VITE_DEV_SERVER_URL, VITE_NAME } = keys; + const def = { + [VITE_DEV_SERVER_URL]: + command === 'serve' + ? JSON.stringify(viteDevServerUrls[VITE_DEV_SERVER_URL]) + : undefined, + [VITE_NAME]: JSON.stringify(name), + }; + return { ...acc, ...def }; + }, + {} as Record, + ); return define; } @@ -74,7 +85,8 @@ export function pluginExposeRenderer(name: string): Plugin { server.httpServer?.once('listening', () => { const addressInfo = server.httpServer?.address() as AddressInfo; // Expose env constant for main process use. - viteDevServerUrls[VITE_DEV_SERVER_URL] = `http://localhost:${addressInfo?.port}`; + viteDevServerUrls[VITE_DEV_SERVER_URL] = + `http://localhost:${addressInfo?.port}`; }); }, }; diff --git a/packages/plugin/vite/src/config/vite.main.config.ts b/packages/plugin/vite/src/config/vite.main.config.ts index 564f6992fd..b6fd30e195 100644 --- a/packages/plugin/vite/src/config/vite.main.config.ts +++ b/packages/plugin/vite/src/config/vite.main.config.ts @@ -1,8 +1,16 @@ import { type ConfigEnv, mergeConfig, type UserConfig } from 'vite'; -import { external, getBuildConfig, getBuildDefine, pluginHotRestart } from './vite.base.config'; +import { + external, + getBuildConfig, + getBuildDefine, + pluginHotRestart, +} from './vite.base.config'; -export function getConfig(forgeEnv: ConfigEnv<'build'>, userConfig: UserConfig = {}): UserConfig { +export function getConfig( + forgeEnv: ConfigEnv<'build'>, + userConfig: UserConfig = {}, +): UserConfig { const { forgeConfigSelf } = forgeEnv; const define = getBuildDefine(forgeEnv); const config: UserConfig = { diff --git a/packages/plugin/vite/src/config/vite.preload.config.ts b/packages/plugin/vite/src/config/vite.preload.config.ts index 5b743ea6fe..c5b219eb59 100644 --- a/packages/plugin/vite/src/config/vite.preload.config.ts +++ b/packages/plugin/vite/src/config/vite.preload.config.ts @@ -2,7 +2,10 @@ import { type ConfigEnv, mergeConfig, type UserConfig } from 'vite'; import { external, getBuildConfig, pluginHotRestart } from './vite.base.config'; -export function getConfig(forgeEnv: ConfigEnv<'build'>, userConfig: UserConfig = {}): UserConfig { +export function getConfig( + forgeEnv: ConfigEnv<'build'>, + userConfig: UserConfig = {}, +): UserConfig { const { forgeConfigSelf } = forgeEnv; const config: UserConfig = { build: { diff --git a/packages/plugin/vite/src/config/vite.renderer.config.ts b/packages/plugin/vite/src/config/vite.renderer.config.ts index 85863f17b3..2a05cbc8a9 100644 --- a/packages/plugin/vite/src/config/vite.renderer.config.ts +++ b/packages/plugin/vite/src/config/vite.renderer.config.ts @@ -3,7 +3,10 @@ import { type ConfigEnv, mergeConfig, type UserConfig } from 'vite'; import { pluginExposeRenderer } from './vite.base.config'; // https://vitejs.dev/config -export function getConfig(forgeEnv: ConfigEnv<'renderer'>, userConfig: UserConfig = {}) { +export function getConfig( + forgeEnv: ConfigEnv<'renderer'>, + userConfig: UserConfig = {}, +) { const { root, mode, forgeConfigSelf } = forgeEnv; const name = forgeConfigSelf.name ?? ''; diff --git a/packages/plugin/webpack/spec/AssetRelocatorPatch.spec.ts b/packages/plugin/webpack/spec/AssetRelocatorPatch.spec.ts index bcb12cd413..97c7611c17 100644 --- a/packages/plugin/webpack/spec/AssetRelocatorPatch.spec.ts +++ b/packages/plugin/webpack/spec/AssetRelocatorPatch.spec.ts @@ -7,7 +7,10 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { Configuration, webpack } from 'webpack'; import which from 'which'; -import { WebpackPluginConfig, WebpackPluginEntryPointLocalWindow } from '../src/Config'; +import { + WebpackPluginConfig, + WebpackPluginEntryPointLocalWindow, +} from '../src/Config'; import WebpackConfigGenerator from '../src/WebpackConfig'; type Closeable = { @@ -18,7 +21,8 @@ let servers: Closeable[] = []; const nativePathSuffix = 'build/Release/hello_world.node'; const appPath = path.join(__dirname, 'fixtures', 'apps', 'native-modules'); -const pmCmd = process.platform === 'win32' ? `"${which.sync('npm.cmd')}"` : 'npm'; +const pmCmd = + process.platform === 'win32' ? `"${which.sync('npm.cmd')}"` : 'npm'; async function asyncWebpack(config: Configuration): Promise { return new Promise((resolve, reject) => { @@ -51,7 +55,9 @@ function createSimpleDevServer(rendererOut: string): http.Server { return http .createServer(async (req, res) => { const url = req.url || ''; - const file = url.endsWith('main_window') ? path.join(url, '/index.html') : url; + const file = url.endsWith('main_window') + ? path.join(url, '/index.html') + : url; const fullPath = path.join(rendererOut, file); try { const data = await fs.promises.readFile(fullPath, 'utf-8'); @@ -148,13 +154,16 @@ describe('AssetRelocatorPatch', () => { await expectOutputFileToHaveTheCorrectNativeModulePath({ outDir: mainOut, jsPath: path.join(mainOut, 'index.js'), - nativeModulesString: '__webpack_require__.ab = __dirname + "/native_modules/"', + nativeModulesString: + '__webpack_require__.ab = __dirname + "/native_modules/"', nativePathString: `require(__webpack_require__.ab + "${nativePathSuffix}")`, }); }); it('builds preload', async () => { - const preloadConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const preloadConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); await asyncWebpack(preloadConfig[0]); await expectOutputFileToHaveTheCorrectNativeModulePath({ @@ -166,7 +175,9 @@ describe('AssetRelocatorPatch', () => { }); it('builds renderer', async () => { - const rendererConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const rendererConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); for (const rendererEntryConfig of rendererConfig) { await asyncWebpack(rendererEntryConfig); } @@ -184,9 +195,15 @@ describe('AssetRelocatorPatch', () => { const output = await runApp(); - expect(output).toEqual(expect.stringContaining('Hello, world! from the main')); - expect(output).toEqual(expect.stringContaining('Hello, world! from the preload')); - expect(output).toEqual(expect.stringContaining('Hello, world! from the renderer')); + expect(output).toEqual( + expect.stringContaining('Hello, world! from the main'), + ); + expect(output).toEqual( + expect.stringContaining('Hello, world! from the preload'), + ); + expect(output).toEqual( + expect.stringContaining('Hello, world! from the renderer'), + ); }); }); @@ -206,7 +223,8 @@ describe('AssetRelocatorPatch', () => { }); it('builds preload', async () => { - const entryPoint = safeFirstRendererConfig(config.renderer).entryPoints[0] as WebpackPluginEntryPointLocalWindow; + const entryPoint = safeFirstRendererConfig(config.renderer) + .entryPoints[0] as WebpackPluginEntryPointLocalWindow; const preloadConfig = await generator.getRendererConfig({ ...safeFirstRendererConfig(config.renderer), entryPoints: [entryPoint], @@ -216,13 +234,16 @@ describe('AssetRelocatorPatch', () => { await expectOutputFileToHaveTheCorrectNativeModulePath({ outDir: rendererOut, jsPath: path.join(rendererOut, 'main_window/preload.js'), - nativeModulesString: '.ab=require("path").resolve(__dirname,"..")+"/native_modules/"', + nativeModulesString: + '.ab=require("path").resolve(__dirname,"..")+"/native_modules/"', nativePathString: `.ab+"${nativePathSuffix}"`, }); }); it('builds renderer', async () => { - const rendererConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const rendererConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); for (const rendererEntryConfig of rendererConfig) { await asyncWebpack(rendererEntryConfig); } @@ -230,7 +251,8 @@ describe('AssetRelocatorPatch', () => { await expectOutputFileToHaveTheCorrectNativeModulePath({ outDir: rendererOut, jsPath: path.join(rendererOut, 'main_window/index.js'), - nativeModulesString: '.ab=require("path").resolve(__dirname,"..")+"/native_modules/"', + nativeModulesString: + '.ab=require("path").resolve(__dirname,"..")+"/native_modules/"', nativePathString: `.ab+"${nativePathSuffix}"`, }); }); @@ -238,16 +260,24 @@ describe('AssetRelocatorPatch', () => { it('runs the app with the native module', async () => { const output = await runApp(); - expect(output).toEqual(expect.stringContaining('Hello, world! from the main')); - expect(output).toEqual(expect.stringContaining('Hello, world! from the preload')); - expect(output).toEqual(expect.stringContaining('Hello, world! from the renderer')); + expect(output).toEqual( + expect.stringContaining('Hello, world! from the main'), + ); + expect(output).toEqual( + expect.stringContaining('Hello, world! from the preload'), + ); + expect(output).toEqual( + expect.stringContaining('Hello, world! from the renderer'), + ); }); it('builds renderer with nodeIntegration = false', async () => { safeFirstRendererConfig(config.renderer).nodeIntegration = false; generator = new WebpackConfigGenerator(config, appPath, true, 3000); - const rendererConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const rendererConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); for (const rendererEntryConfig of rendererConfig) { await asyncWebpack(rendererEntryConfig); } diff --git a/packages/plugin/webpack/spec/WebpackConfig.spec.ts b/packages/plugin/webpack/spec/WebpackConfig.spec.ts index 68824ec39e..f15d7150d9 100644 --- a/packages/plugin/webpack/spec/WebpackConfig.spec.ts +++ b/packages/plugin/webpack/spec/WebpackConfig.spec.ts @@ -3,14 +3,27 @@ import path from 'node:path'; import { describe, expect, it } from 'vitest'; import { Configuration, Entry } from 'webpack'; -import { WebpackConfiguration, WebpackPluginConfig, WebpackPluginEntryPoint } from '../src/Config'; +import { + WebpackConfiguration, + WebpackPluginConfig, + WebpackPluginEntryPoint, +} from '../src/Config'; import AssetRelocatorPatch from '../src/util/AssetRelocatorPatch'; -import WebpackConfigGenerator, { ConfigurationFactory } from '../src/WebpackConfig'; +import WebpackConfigGenerator, { + ConfigurationFactory, +} from '../src/WebpackConfig'; const mockProjectDir = process.platform === 'win32' ? 'C:\\path' : '/path'; -function hasAssetRelocatorPatchPlugin(plugins?: Required['plugins']): boolean { - return (plugins || []).some((plugin) => plugin && typeof plugin === 'object' && plugin instanceof AssetRelocatorPatch); +function hasAssetRelocatorPatchPlugin( + plugins?: Required['plugins'], +): boolean { + return (plugins || []).some( + (plugin) => + plugin && + typeof plugin === 'object' && + plugin instanceof AssetRelocatorPatch, + ); } const sampleWebpackConfig = { @@ -38,7 +51,9 @@ describe('WebpackConfigGenerator', () => { }, } as WebpackPluginConfig; const generator = new WebpackConfigGenerator(config, '/', false, 3000); - const webpackConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const webpackConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); expect(webpackConfig[0].target).toEqual('web'); }); @@ -50,7 +65,9 @@ describe('WebpackConfigGenerator', () => { }, } as WebpackPluginConfig; const generator = new WebpackConfigGenerator(config, '/', false, 3000); - const webpackConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const webpackConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); expect(webpackConfig[0].target).toEqual('web'); }); @@ -62,19 +79,25 @@ describe('WebpackConfigGenerator', () => { }, } as WebpackPluginConfig; const generator = new WebpackConfigGenerator(config, '/', false, 3000); - const webpackConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const webpackConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); expect(webpackConfig[0].target).toEqual('electron-renderer'); }); it('is web if entry nodeIntegration is false', async () => { const config = { renderer: { - entryPoints: [{ name: 'foo', js: 'foo/index.js', nodeIntegration: false }], + entryPoints: [ + { name: 'foo', js: 'foo/index.js', nodeIntegration: false }, + ], nodeIntegration: true, }, } as WebpackPluginConfig; const generator = new WebpackConfigGenerator(config, '/', false, 3000); - const webpackConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const webpackConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); expect(webpackConfig[0].target).toEqual('web'); }); }); @@ -85,7 +108,9 @@ describe('WebpackConfigGenerator', () => { renderer: {}, } as WebpackPluginConfig; const generator = new WebpackConfigGenerator(config, '/', false, 3000); - expect(() => generator.getDefines()).toThrow(/renderer.entryPoints.* has not been defined/); + expect(() => generator.getDefines()).toThrow( + /renderer.entryPoints.* has not been defined/, + ); }); it('throws an error if renderer.entryPoints is not an array', () => { @@ -95,7 +120,9 @@ describe('WebpackConfigGenerator', () => { }, } as WebpackPluginConfig; const generator = new WebpackConfigGenerator(config, '/', false, 3000); - expect(() => generator.getDefines()).toThrow(/renderer.entryPoints.* has not been defined/); + expect(() => generator.getDefines()).toThrow( + /renderer.entryPoints.* has not been defined/, + ); }); it('sets the renderer entry point to a JS file in development', () => { @@ -112,7 +139,9 @@ describe('WebpackConfigGenerator', () => { const generator = new WebpackConfigGenerator(config, '/', false, 3000); const defines = generator.getDefines(); - expect(defines.HELLO_WEBPACK_ENTRY).toEqual("'http://localhost:3000/hello/index.js'"); + expect(defines.HELLO_WEBPACK_ENTRY).toEqual( + "'http://localhost:3000/hello/index.js'", + ); }); it('sets the renderer entry point to a JS file in production', () => { @@ -129,7 +158,9 @@ describe('WebpackConfigGenerator', () => { const generator = new WebpackConfigGenerator(config, '/', true, 3000); const defines = generator.getDefines(); - expect(defines.HELLO_WEBPACK_ENTRY).toEqual("`file://${require('path').resolve(__dirname, '..', 'renderer', 'hello', 'index.js')}`"); + expect(defines.HELLO_WEBPACK_ENTRY).toEqual( + "`file://${require('path').resolve(__dirname, '..', 'renderer', 'hello', 'index.js')}`", + ); }); it('sets the renderer entry point to an HTML file if both an HTML & JS file are specified', () => { @@ -147,7 +178,9 @@ describe('WebpackConfigGenerator', () => { const generator = new WebpackConfigGenerator(config, '/', false, 3000); const defines = generator.getDefines(); - expect(defines.HELLO_WEBPACK_ENTRY).toEqual("'http://localhost:3000/hello/index.html'"); + expect(defines.HELLO_WEBPACK_ENTRY).toEqual( + "'http://localhost:3000/hello/index.html'", + ); }); describe('PRELOAD_WEBPACK_ENTRY', () => { @@ -169,20 +202,36 @@ describe('WebpackConfigGenerator', () => { }; it('should assign absolute preload script path in development', () => { - const generator = new WebpackConfigGenerator(config, mockProjectDir, false, 3000); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + false, + 3000, + ); const defines = generator.getDefines(); if (process.platform === 'win32') { - expect(defines.WINDOW_PRELOAD_WEBPACK_ENTRY).toEqual(String.raw`'C:\\path\\.webpack\\renderer\\window\\preload.js'`); + expect(defines.WINDOW_PRELOAD_WEBPACK_ENTRY).toEqual( + String.raw`'C:\\path\\.webpack\\renderer\\window\\preload.js'`, + ); } else { - expect(defines.WINDOW_PRELOAD_WEBPACK_ENTRY).toEqual(`'${mockProjectDir}/.webpack/renderer/window/preload.js'`); + expect(defines.WINDOW_PRELOAD_WEBPACK_ENTRY).toEqual( + `'${mockProjectDir}/.webpack/renderer/window/preload.js'`, + ); } }); it('should assign an expression to resolve the preload script in production', () => { - const generator = new WebpackConfigGenerator(config, mockProjectDir, true, 3000); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + true, + 3000, + ); const defines = generator.getDefines(); - expect(defines.WINDOW_PRELOAD_WEBPACK_ENTRY).toEqual("require('path').resolve(__dirname, '../renderer', 'window', 'preload.js')"); + expect(defines.WINDOW_PRELOAD_WEBPACK_ENTRY).toEqual( + "require('path').resolve(__dirname, '../renderer', 'window', 'preload.js')", + ); }); }); }); @@ -193,7 +242,9 @@ describe('WebpackConfigGenerator', () => { mainConfig: {}, } as WebpackPluginConfig; const generator = new WebpackConfigGenerator(config, '/', false, 3000); - await expect(generator.getMainConfig()).rejects.toThrow('Required option "mainConfig.entry" has not been defined'); + await expect(generator.getMainConfig()).rejects.toThrow( + 'Required option "mainConfig.entry" has not been defined', + ); }); it('generates a development config', async () => { @@ -205,7 +256,12 @@ describe('WebpackConfigGenerator', () => { entryPoints: [] as WebpackPluginEntryPoint[], }, } as WebpackPluginConfig; - const generator = new WebpackConfigGenerator(config, mockProjectDir, false, 3000); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + false, + 3000, + ); const webpackConfig = await generator.getMainConfig(); expect(webpackConfig.target).toEqual('electron-main'); expect(webpackConfig.mode).toEqual('development'); @@ -215,7 +271,9 @@ describe('WebpackConfigGenerator', () => { filename: 'index.js', libraryTarget: 'commonjs2', }); - expect(hasAssetRelocatorPatchPlugin(webpackConfig.plugins)).toEqual(false); + expect(hasAssetRelocatorPatchPlugin(webpackConfig.plugins)).toEqual( + false, + ); }); it('generates a production config', async () => { @@ -227,10 +285,17 @@ describe('WebpackConfigGenerator', () => { entryPoints: [] as WebpackPluginEntryPoint[], }, } as WebpackPluginConfig; - const generator = new WebpackConfigGenerator(config, mockProjectDir, true, 3000); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + true, + 3000, + ); const webpackConfig = await generator.getMainConfig(); expect(webpackConfig.mode).toEqual('production'); - expect(hasAssetRelocatorPatchPlugin(webpackConfig.plugins)).toEqual(false); + expect(hasAssetRelocatorPatchPlugin(webpackConfig.plugins)).toEqual( + false, + ); }); it('generates a config with a relative entry path', async () => { @@ -242,9 +307,16 @@ describe('WebpackConfigGenerator', () => { entryPoints: [] as WebpackPluginEntryPoint[], }, } as WebpackPluginConfig; - const generator = new WebpackConfigGenerator(config, mockProjectDir, true, 3000); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + true, + 3000, + ); const webpackConfig = await generator.getMainConfig(); - expect(webpackConfig.entry).toEqual(path.join(mockProjectDir, 'foo', 'main.js')); + expect(webpackConfig.entry).toEqual( + path.join(mockProjectDir, 'foo', 'main.js'), + ); }); it('generates a config with multiple entries', async () => { @@ -259,7 +331,12 @@ describe('WebpackConfigGenerator', () => { entryPoints: [] as WebpackPluginEntryPoint[], }, } as WebpackPluginConfig; - const generator = new WebpackConfigGenerator(config, mockProjectDir, true, 3000); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + true, + 3000, + ); const webpackConfig = await generator.getMainConfig(); expect(webpackConfig.entry).toEqual({ foo: path.join(mockProjectDir, 'foo', 'main.js'), @@ -301,7 +378,12 @@ describe('WebpackConfigGenerator', () => { entryPoints: [] as WebpackPluginEntryPoint[], }, } as WebpackPluginConfig; - const generator = new WebpackConfigGenerator(config, mockProjectDir, false, 3000); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + false, + 3000, + ); return generator.getMainConfig(); }; @@ -315,7 +397,7 @@ describe('WebpackConfigGenerator', () => { await generateWebpackConfig(() => ({ entry: 'main.js', ...sampleWebpackConfig, - })) + })), ).toEqual(modelWebpackConfig); // Check promise form @@ -323,7 +405,7 @@ describe('WebpackConfigGenerator', () => { await generateWebpackConfig(async () => ({ entry: 'main.js', ...sampleWebpackConfig, - })) + })), ).toEqual(modelWebpackConfig); }); }); @@ -341,8 +423,15 @@ describe('WebpackConfigGenerator', () => { nodeIntegration: true, }, } as WebpackPluginConfig; - const generator = new WebpackConfigGenerator(config, mockProjectDir, false, 3000); - const webpackConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + false, + 3000, + ); + const webpackConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); expect(webpackConfig[0].target).toEqual('electron-renderer'); expect(webpackConfig[0].mode).toEqual('development'); expect(webpackConfig[0].entry).toEqual({ @@ -356,7 +445,9 @@ describe('WebpackConfigGenerator', () => { }); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(webpackConfig[0].plugins!.length).toEqual(1); - expect(hasAssetRelocatorPatchPlugin(webpackConfig[0].plugins)).toEqual(true); + expect(hasAssetRelocatorPatchPlugin(webpackConfig[0].plugins)).toEqual( + true, + ); }); it('generates a development config with an HTML endpoint', async () => { @@ -371,14 +462,23 @@ describe('WebpackConfigGenerator', () => { ], }, } as WebpackPluginConfig; - const generator = new WebpackConfigGenerator(config, mockProjectDir, false, 3000); - const webpackConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + false, + 3000, + ); + const webpackConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); expect(webpackConfig[0].entry).toEqual({ main: ['rendererScript.js'], }); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(webpackConfig[0].plugins!.length).toEqual(2); - expect(hasAssetRelocatorPatchPlugin(webpackConfig[0].plugins)).toEqual(true); + expect(hasAssetRelocatorPatchPlugin(webpackConfig[0].plugins)).toEqual( + true, + ); }); it('generates a preload-only development config', async () => { @@ -394,8 +494,15 @@ describe('WebpackConfigGenerator', () => { ], }, } as WebpackPluginConfig; - const generator = new WebpackConfigGenerator(config, mockProjectDir, false, 3000); - const webpackConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + false, + 3000, + ); + const webpackConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); expect(webpackConfig[0].target).toEqual('web'); expect(webpackConfig[0].mode).toEqual('development'); expect(webpackConfig[0].entry).toEqual({ @@ -409,7 +516,9 @@ describe('WebpackConfigGenerator', () => { }); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(webpackConfig[0].plugins!.length).toEqual(2); - expect(hasAssetRelocatorPatchPlugin(webpackConfig[0].plugins)).toEqual(true); + expect(hasAssetRelocatorPatchPlugin(webpackConfig[0].plugins)).toEqual( + true, + ); }); it('generates a production config', async () => { @@ -423,8 +532,15 @@ describe('WebpackConfigGenerator', () => { ], }, } as WebpackPluginConfig; - const generator = new WebpackConfigGenerator(config, mockProjectDir, true, 3000); - const webpackConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + true, + 3000, + ); + const webpackConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); expect(webpackConfig[0].target).toEqual('web'); expect(webpackConfig[0].mode).toEqual('production'); expect(webpackConfig[0].entry).toEqual({ @@ -437,7 +553,9 @@ describe('WebpackConfigGenerator', () => { }); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(webpackConfig[0].plugins!.length).toEqual(1); - expect(hasAssetRelocatorPatchPlugin(webpackConfig[0].plugins)).toEqual(true); + expect(hasAssetRelocatorPatchPlugin(webpackConfig[0].plugins)).toEqual( + true, + ); }); it('generates a production config with entryPoint preload', async () => { @@ -453,8 +571,15 @@ describe('WebpackConfigGenerator', () => { ], }, } as WebpackPluginConfig; - const generator = new WebpackConfigGenerator(config, mockProjectDir, true, 3000); - const webpackConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + true, + 3000, + ); + const webpackConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); expect(webpackConfig[0].target).toEqual('web'); expect(webpackConfig[0].mode).toEqual('production'); expect(webpackConfig[0].entry).toEqual({ main: ['preload.js'] }); @@ -466,7 +591,9 @@ describe('WebpackConfigGenerator', () => { }); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(webpackConfig[0].plugins!.length).toEqual(2); - expect(hasAssetRelocatorPatchPlugin(webpackConfig[0].plugins)).toEqual(true); + expect(hasAssetRelocatorPatchPlugin(webpackConfig[0].plugins)).toEqual( + true, + ); }); it('generates a preload-only production config', async () => { @@ -482,8 +609,15 @@ describe('WebpackConfigGenerator', () => { ], }, } as WebpackPluginConfig; - const generator = new WebpackConfigGenerator(config, mockProjectDir, true, 3000); - const webpackConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + true, + 3000, + ); + const webpackConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); expect(webpackConfig[0].target).toEqual('web'); expect(webpackConfig[0].mode).toEqual('production'); expect(webpackConfig[0].entry).toEqual({ @@ -497,7 +631,9 @@ describe('WebpackConfigGenerator', () => { }); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(webpackConfig[0].plugins!.length).toEqual(2); - expect(hasAssetRelocatorPatchPlugin(webpackConfig[0].plugins)).toEqual(true); + expect(hasAssetRelocatorPatchPlugin(webpackConfig[0].plugins)).toEqual( + true, + ); }); it('can override the renderer target', async () => { @@ -514,8 +650,15 @@ describe('WebpackConfigGenerator', () => { ], }, } as WebpackPluginConfig; - const generator = new WebpackConfigGenerator(config, mockProjectDir, true, 3000); - const webpackConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + true, + 3000, + ); + const webpackConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); expect(webpackConfig[0].target).toEqual('web'); }); @@ -536,8 +679,15 @@ describe('WebpackConfigGenerator', () => { ], }, } as WebpackPluginConfig; - const generator = new WebpackConfigGenerator(config, mockProjectDir, true, 3000); - const webpackConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + true, + 3000, + ); + const webpackConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); expect(webpackConfig[0].target).toEqual('electron-preload'); }); @@ -564,8 +714,15 @@ describe('WebpackConfigGenerator', () => { ], }, } as WebpackPluginConfig; - const generator = new WebpackConfigGenerator(config, mockProjectDir, true, 3000); - const webpackConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + true, + 3000, + ); + const webpackConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); expect(webpackConfig[0].target).toEqual('web'); expect(webpackConfig[0].name).toEqual('preload'); }); @@ -613,8 +770,15 @@ describe('WebpackConfigGenerator', () => { ], }, } as WebpackPluginConfig; - const generator = new WebpackConfigGenerator(config, mockProjectDir, true, 3000); - const webpackConfig = await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + true, + 3000, + ); + const webpackConfig = await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); expect(webpackConfig.length).toEqual(4); }); @@ -631,8 +795,15 @@ describe('WebpackConfigGenerator', () => { ], }, } as WebpackPluginConfig; - const generator = new WebpackConfigGenerator(config, mockProjectDir, false, 3000); - return generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + const generator = new WebpackConfigGenerator( + config, + mockProjectDir, + false, + 3000, + ); + return generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); }; const modelWebpackConfig = await generateWebpackConfig({ @@ -643,14 +814,14 @@ describe('WebpackConfigGenerator', () => { expect( await generateWebpackConfig(() => ({ ...sampleWebpackConfig, - })) + })), ).toEqual(modelWebpackConfig); // Check promise form expect( await generateWebpackConfig(async () => ({ ...sampleWebpackConfig, - })) + })), ).toEqual(modelWebpackConfig); }); }); @@ -661,7 +832,9 @@ describe('WebpackConfigGenerator', () => { let invoked = 0; class MyWebpackConfigGenerator extends WebpackConfigGenerator { - preprocessConfig = async (config: ConfigurationFactory): Promise => { + preprocessConfig = async ( + config: ConfigurationFactory, + ): Promise => { invoked += 1; return config({ hello: 'world' }, {}); }; @@ -692,14 +865,21 @@ describe('WebpackConfigGenerator', () => { }, } as WebpackPluginConfig; - const generator = new MyWebpackConfigGenerator(config, mockProjectDir, false, 3000); + const generator = new MyWebpackConfigGenerator( + config, + mockProjectDir, + false, + 3000, + ); expect(getInvokedCounter()).toEqual(0); await generator.getMainConfig(); expect(getInvokedCounter()).toEqual(1); - await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); expect(getInvokedCounter()).toEqual(2); }); @@ -722,14 +902,21 @@ describe('WebpackConfigGenerator', () => { }, } as WebpackPluginConfig; - const generator = new MyWebpackConfigGenerator(config, mockProjectDir, false, 3000); + const generator = new MyWebpackConfigGenerator( + config, + mockProjectDir, + false, + 3000, + ); expect(getInvokedCounter()).toEqual(0); await generator.getMainConfig(); expect(getInvokedCounter()).toEqual(1); - await generator.getRendererConfig(safeFirstRendererConfig(config.renderer)); + await generator.getRendererConfig( + safeFirstRendererConfig(config.renderer), + ); expect(getInvokedCounter()).toEqual(2); }); }); diff --git a/packages/plugin/webpack/spec/WebpackPlugin.spec.ts b/packages/plugin/webpack/spec/WebpackPlugin.spec.ts index a70ec3c4b6..a0733a9ec5 100644 --- a/packages/plugin/webpack/spec/WebpackPlugin.spec.ts +++ b/packages/plugin/webpack/spec/WebpackPlugin.spec.ts @@ -6,7 +6,10 @@ import { IgnoreFunction } from '@electron/packager'; import { ResolvedForgeConfig } from '@electron-forge/shared-types'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import { WebpackPluginConfig, WebpackPluginRendererConfig } from '../src/Config'; +import { + WebpackPluginConfig, + WebpackPluginRendererConfig, +} from '../src/Config'; import { WebpackPlugin } from '../src/WebpackPlugin'; describe('WebpackPlugin', async () => { @@ -28,11 +31,15 @@ describe('WebpackPlugin', async () => { describe('TCP port', () => { it('should fail for privileged ports', () => { - expect(() => new WebpackPlugin({ ...baseConfig, loggerPort: 80 })).toThrow(/privileged$/); + expect( + () => new WebpackPlugin({ ...baseConfig, loggerPort: 80 }), + ).toThrow(/privileged$/); }); it('should fail for too-large port numbers', () => { - expect(() => new WebpackPlugin({ ...baseConfig, loggerPort: 99999 })).toThrow(/not a valid TCP port/); + expect( + () => new WebpackPlugin({ ...baseConfig, loggerPort: 99999 }), + ).toThrow(/not a valid TCP port/); }); }); @@ -49,31 +56,61 @@ describe('WebpackPlugin', async () => { }); it('should remove config.forge from package.json', async () => { - const packageJSON = { main: './.webpack/main', config: { forge: 'config.js' } }; - await fs.promises.writeFile(packageJSONPath, JSON.stringify(packageJSON), 'utf-8'); + const packageJSON = { + main: './.webpack/main', + config: { forge: 'config.js' }, + }; + await fs.promises.writeFile( + packageJSONPath, + JSON.stringify(packageJSON), + 'utf-8', + ); await plugin.packageAfterCopy({} as ResolvedForgeConfig, packagedPath); expect(fs.existsSync(packagedPackageJSONPath)).toEqual(true); - expect(JSON.parse(await fs.promises.readFile(packagedPackageJSONPath, 'utf-8')).config).not.toHaveProperty('forge'); + expect( + JSON.parse(await fs.promises.readFile(packagedPackageJSONPath, 'utf-8')) + .config, + ).not.toHaveProperty('forge'); }); it('should succeed if there is no config.forge', async () => { const packageJSON = { main: '.webpack/main' }; - await fs.promises.writeFile(packageJSONPath, JSON.stringify(packageJSON), 'utf-8'); + await fs.promises.writeFile( + packageJSONPath, + JSON.stringify(packageJSON), + 'utf-8', + ); await plugin.packageAfterCopy({} as ResolvedForgeConfig, packagedPath); expect(fs.existsSync(packagedPackageJSONPath)).toEqual(true); - expect(JSON.parse(await fs.promises.readFile(packagedPackageJSONPath, 'utf-8'))).not.toHaveProperty('config'); + expect( + JSON.parse( + await fs.promises.readFile(packagedPackageJSONPath, 'utf-8'), + ), + ).not.toHaveProperty('config'); }); it('should fail if there is no main key in package.json', async () => { const packageJSON = {}; - await fs.promises.writeFile(packageJSONPath, JSON.stringify(packageJSON), 'utf-8'); - await expect(plugin.packageAfterCopy({} as ResolvedForgeConfig, packagedPath)).rejects.toThrow(/entry point/); + await fs.promises.writeFile( + packageJSONPath, + JSON.stringify(packageJSON), + 'utf-8', + ); + await expect( + plugin.packageAfterCopy({} as ResolvedForgeConfig, packagedPath), + ).rejects.toThrow(/entry point/); }); it('should fail if main in package.json does not end with .webpack/main', async () => { const packageJSON = { main: 'src/main.js' }; - await fs.promises.writeFile(packageJSONPath, JSON.stringify(packageJSON), 'utf-8'); - await expect(plugin.packageAfterCopy({} as ResolvedForgeConfig, packagedPath)).rejects.toThrow(/entry point/); + await fs.promises.writeFile( + packageJSONPath, + JSON.stringify(packageJSON), + 'utf-8', + ); + await expect( + plugin.packageAfterCopy({} as ResolvedForgeConfig, packagedPath), + ).rejects.toThrow(/entry point/); }); }); @@ -102,7 +139,9 @@ describe('WebpackPlugin', async () => { }); it('ignores everything but files in .webpack', async () => { - const config = await plugin.resolveForgeConfig({} as ResolvedForgeConfig); + const config = await plugin.resolveForgeConfig( + {} as ResolvedForgeConfig, + ); const ignore = config.packagerConfig.ignore as IgnoreFunction; expect(ignore('')).toEqual(false); @@ -113,37 +152,68 @@ describe('WebpackPlugin', async () => { it('ignores files generated by jsonStats config', async () => { const webpackConfig = { ...baseConfig, jsonStats: true }; - (webpackConfig.renderer as WebpackPluginRendererConfig).jsonStats = true; + (webpackConfig.renderer as WebpackPluginRendererConfig).jsonStats = + true; plugin = new WebpackPlugin(webpackConfig); - const config = await plugin.resolveForgeConfig({} as ResolvedForgeConfig); + const config = await plugin.resolveForgeConfig( + {} as ResolvedForgeConfig, + ); const ignore = config.packagerConfig.ignore as IgnoreFunction; - expect(ignore(path.join('foo', 'bar', '.webpack', 'main', 'stats.json'))).toEqual(true); - expect(ignore(path.join('foo', 'bar', '.webpack', 'renderer', 'stats.json'))).toEqual(true); + expect( + ignore(path.join('foo', 'bar', '.webpack', 'main', 'stats.json')), + ).toEqual(true); + expect( + ignore(path.join('foo', 'bar', '.webpack', 'renderer', 'stats.json')), + ).toEqual(true); }); it('ignores source map files by default', async () => { const webpackConfig = { ...baseConfig }; plugin = new WebpackPlugin(webpackConfig); - const config = await plugin.resolveForgeConfig({} as ResolvedForgeConfig); + const config = await plugin.resolveForgeConfig( + {} as ResolvedForgeConfig, + ); const ignore = config.packagerConfig.ignore as IgnoreFunction; - expect(ignore(path.join('/.webpack', 'main', 'index.js'))).toEqual(false); - expect(ignore(path.join('/.webpack', 'main', 'index.js.map'))).toEqual(true); - expect(ignore(path.join('/.webpack', 'renderer', 'main_window', 'index.js'))).toEqual(false); - expect(ignore(path.join('/.webpack', 'renderer', 'main_window', 'index.js.map'))).toEqual(true); + expect(ignore(path.join('/.webpack', 'main', 'index.js'))).toEqual( + false, + ); + expect(ignore(path.join('/.webpack', 'main', 'index.js.map'))).toEqual( + true, + ); + expect( + ignore(path.join('/.webpack', 'renderer', 'main_window', 'index.js')), + ).toEqual(false); + expect( + ignore( + path.join('/.webpack', 'renderer', 'main_window', 'index.js.map'), + ), + ).toEqual(true); }); it('includes source map files when specified by config', async () => { const webpackConfig = { ...baseConfig, packageSourceMaps: true }; plugin = new WebpackPlugin(webpackConfig); - const config = await plugin.resolveForgeConfig({} as ResolvedForgeConfig); + const config = await plugin.resolveForgeConfig( + {} as ResolvedForgeConfig, + ); const ignore = config.packagerConfig.ignore as IgnoreFunction; - expect(ignore(path.join('/.webpack', 'main', 'index.js'))).toEqual(false); - expect(ignore(path.join('/.webpack', 'main', 'index.js.map'))).toEqual(false); - expect(ignore(path.join('/.webpack', 'renderer', 'main_window', 'index.js'))).toEqual(false); - expect(ignore(path.join('/.webpack', 'renderer', 'main_window', 'index.js.map'))).toEqual(false); + expect(ignore(path.join('/.webpack', 'main', 'index.js'))).toEqual( + false, + ); + expect(ignore(path.join('/.webpack', 'main', 'index.js.map'))).toEqual( + false, + ); + expect( + ignore(path.join('/.webpack', 'renderer', 'main_window', 'index.js')), + ).toEqual(false); + expect( + ignore( + path.join('/.webpack', 'renderer', 'main_window', 'index.js.map'), + ), + ).toEqual(false); }); }); }); diff --git a/packages/plugin/webpack/spec/fixtures/apps/native-modules/src/index.html b/packages/plugin/webpack/spec/fixtures/apps/native-modules/src/index.html index d4b04306a6..e7e71fbb72 100644 --- a/packages/plugin/webpack/spec/fixtures/apps/native-modules/src/index.html +++ b/packages/plugin/webpack/spec/fixtures/apps/native-modules/src/index.html @@ -1,4 +1,4 @@ - + Test diff --git a/packages/plugin/webpack/spec/util/processConfig.spec.ts b/packages/plugin/webpack/spec/util/processConfig.spec.ts index 4b524e0e31..cd0d76f854 100644 --- a/packages/plugin/webpack/spec/util/processConfig.spec.ts +++ b/packages/plugin/webpack/spec/util/processConfig.spec.ts @@ -14,17 +14,25 @@ const sampleWebpackConfig = { }, }; -const sampleConfigFactoryParams: Parameters = [{}, { mode: 'production' }]; +const sampleConfigFactoryParams: Parameters = [ + {}, + { mode: 'production' }, +]; describe('processConfig', () => { it('works for object config', async () => { let invoked = 0; const processor: ConfigProcessor = async (configFactory) => { invoked += 1; - return configFactory(sampleConfigFactoryParams[0], sampleConfigFactoryParams[1]); + return configFactory( + sampleConfigFactoryParams[0], + sampleConfigFactoryParams[1], + ); }; - expect(await processConfig(processor, sampleWebpackConfig)).toEqual(sampleWebpackConfig); + expect(await processConfig(processor, sampleWebpackConfig)).toEqual( + sampleWebpackConfig, + ); expect(invoked).toEqual(1); }); @@ -32,7 +40,10 @@ describe('processConfig', () => { let invoked = 0; const processor: ConfigProcessor = async (configFactory) => { invoked += 1; - return configFactory(sampleConfigFactoryParams[0], sampleConfigFactoryParams[1]); + return configFactory( + sampleConfigFactoryParams[0], + sampleConfigFactoryParams[1], + ); }; const fnConfig: ConfigurationFactory = (arg0, arg1) => { @@ -41,7 +52,9 @@ describe('processConfig', () => { return sampleWebpackConfig; }; - expect(await processConfig(processor, fnConfig)).toEqual(sampleWebpackConfig); + expect(await processConfig(processor, fnConfig)).toEqual( + sampleWebpackConfig, + ); expect(invoked).toEqual(1); }); @@ -49,7 +62,10 @@ describe('processConfig', () => { let invoked = 0; const processor: ConfigProcessor = async (configFactory) => { invoked += 1; - return configFactory(sampleConfigFactoryParams[0], sampleConfigFactoryParams[1]); + return configFactory( + sampleConfigFactoryParams[0], + sampleConfigFactoryParams[1], + ); }; const promiseConfig: ConfigurationFactory = (arg0, arg1) => { @@ -58,7 +74,9 @@ describe('processConfig', () => { return sampleWebpackConfig; }; - expect(await processConfig(processor, promiseConfig)).toEqual(sampleWebpackConfig); + expect(await processConfig(processor, promiseConfig)).toEqual( + sampleWebpackConfig, + ); expect(invoked).toEqual(1); }); }); diff --git a/packages/plugin/webpack/src/Config.ts b/packages/plugin/webpack/src/Config.ts index 2a201af825..154d76d139 100644 --- a/packages/plugin/webpack/src/Config.ts +++ b/packages/plugin/webpack/src/Config.ts @@ -35,7 +35,8 @@ export interface WebpackPluginEntryPointBase { nodeIntegration?: boolean; } -export interface WebpackPluginEntryPointLocalWindow extends WebpackPluginEntryPointBase { +export interface WebpackPluginEntryPointLocalWindow + extends WebpackPluginEntryPointBase { /** * Relative or absolute path to the HTML template file for this entry point. */ @@ -51,21 +52,26 @@ export interface WebpackPluginEntryPointLocalWindow extends WebpackPluginEntryPo preload?: WebpackPreloadEntryPoint; } -export interface WebpackPluginEntryPointPreloadOnly extends WebpackPluginEntryPointBase { +export interface WebpackPluginEntryPointPreloadOnly + extends WebpackPluginEntryPointBase { /** * Information about the preload script for this entry point. */ preload: WebpackPreloadEntryPoint; } -export interface WebpackPluginEntryPointNoWindow extends WebpackPluginEntryPointBase { +export interface WebpackPluginEntryPointNoWindow + extends WebpackPluginEntryPointBase { /** * Relative or absolute path to the main JS file for this entry point. */ js: string; } -export type WebpackPluginEntryPoint = WebpackPluginEntryPointLocalWindow | WebpackPluginEntryPointNoWindow | WebpackPluginEntryPointPreloadOnly; +export type WebpackPluginEntryPoint = + | WebpackPluginEntryPointLocalWindow + | WebpackPluginEntryPointNoWindow + | WebpackPluginEntryPointPreloadOnly; export interface WebpackPreloadEntryPoint { /** @@ -85,7 +91,8 @@ export interface WebpackPreloadEntryPoint { config?: WebpackConfiguration | string; } -export interface StandaloneWebpackPreloadEntryPoint extends WebpackPreloadEntryPoint { +export interface StandaloneWebpackPreloadEntryPoint + extends WebpackPreloadEntryPoint { name: string; } @@ -183,7 +190,12 @@ export interface WebpackPluginConfig { * * `setupExitSignals` * * `headers.Content-Security-Policy` (use the `devContentSecurityPolicy` config option) */ - devServer?: Omit; + devServer?: Omit< + WebpackDevServer.Configuration, + 'port' | 'static' | 'setupExitSignals' | 'Content-Security-Policy' + >; } -export type WebpackConfiguration = RawWebpackConfiguration | WebpackConfigurationFactory; +export type WebpackConfiguration = + | RawWebpackConfiguration + | WebpackConfigurationFactory; diff --git a/packages/plugin/webpack/src/WebpackConfig.ts b/packages/plugin/webpack/src/WebpackConfig.ts index 16e8dbeec0..6b6c7bfd84 100644 --- a/packages/plugin/webpack/src/WebpackConfig.ts +++ b/packages/plugin/webpack/src/WebpackConfig.ts @@ -14,7 +14,13 @@ import { } from './Config'; import AssetRelocatorPatch from './util/AssetRelocatorPatch'; import processConfig from './util/processConfig'; -import { isLocalOrNoWindowEntries, isLocalWindow, isNoWindow, isPreloadOnly, isPreloadOnlyEntries } from './util/rendererTypeUtils'; +import { + isLocalOrNoWindowEntries, + isLocalWindow, + isNoWindow, + isPreloadOnly, + isPreloadOnlyEntries, +} from './util/rendererTypeUtils'; type EntryType = string | string[] | Record; type WebpackMode = 'production' | 'development'; @@ -23,7 +29,7 @@ const d = debug('electron-forge:plugin:webpack:webpackconfig'); export type ConfigurationFactory = ( env: string | Record | unknown, - args: Record + args: Record, ) => Configuration | Promise; enum RendererTarget { @@ -66,7 +72,12 @@ export default class WebpackConfigGenerator { private webpackDir: string; - constructor(pluginConfig: WebpackPluginConfig, projectDir: string, isProd: boolean, port: number) { + constructor( + pluginConfig: WebpackPluginConfig, + projectDir: string, + isProd: boolean, + port: number, + ) { this.pluginConfig = pluginConfig; this.projectDir = projectDir; this.webpackDir = path.resolve(projectDir, '.webpack'); @@ -76,13 +87,17 @@ export default class WebpackConfigGenerator { d('Config mode:', this.mode); } - async resolveConfig(config: Configuration | ConfigurationFactory | string): Promise { + async resolveConfig( + config: Configuration | ConfigurationFactory | string, + ): Promise { type MaybeESM = T | { default: T }; let rawConfig = typeof config === 'string' ? // eslint-disable-next-line @typescript-eslint/no-require-imports - (require(path.resolve(this.projectDir, config)) as MaybeESM) + (require(path.resolve(this.projectDir, config)) as MaybeESM< + Configuration | ConfigurationFactory + >) : config; if (rawConfig && typeof rawConfig === 'object' && 'default' in rawConfig) { @@ -94,12 +109,14 @@ export default class WebpackConfigGenerator { // Users can override this method in a subclass to provide custom logic or // configuration parameters. - preprocessConfig = async (config: ConfigurationFactory): Promise => + preprocessConfig = async ( + config: ConfigurationFactory, + ): Promise => config( {}, { mode: this.mode, - } + }, ); get mode(): WebpackMode { @@ -110,16 +127,23 @@ export default class WebpackConfigGenerator { return this.isProd ? 'source-map' : 'eval-source-map'; } - rendererEntryPoint(entryPoint: WebpackPluginEntryPoint, basename: string): string { + rendererEntryPoint( + entryPoint: WebpackPluginEntryPoint, + basename: string, + ): string { if (this.isProd) { return `\`file://$\{require('path').resolve(__dirname, '..', 'renderer', '${entryPoint.name}', '${basename}')}\``; } - const protocol = this.pluginConfig.devServer?.server === 'https' ? 'https' : 'http'; + const protocol = + this.pluginConfig.devServer?.server === 'https' ? 'https' : 'http'; const baseUrl = `${protocol}://localhost:${this.port}/${entryPoint.name}`; return `'${baseUrl}/${basename}'`; } - toEnvironmentVariable(entryPoint: WebpackPluginEntryPoint, preload = false): string { + toEnvironmentVariable( + entryPoint: WebpackPluginEntryPoint, + preload = false, + ): string { const suffix = preload ? '_PRELOAD_WEBPACK_ENTRY' : '_WEBPACK_ENTRY'; return `${entryPoint.name.toUpperCase().replace(/ /g, '_')}${suffix}`; } @@ -138,15 +162,22 @@ export default class WebpackConfigGenerator { } private get allPluginRendererOptions() { - return Array.isArray(this.pluginConfig.renderer) ? this.pluginConfig.renderer : [this.pluginConfig.renderer]; + return Array.isArray(this.pluginConfig.renderer) + ? this.pluginConfig.renderer + : [this.pluginConfig.renderer]; } getDefines(): Record { const defines: Record = {}; for (const pluginRendererOptions of this.allPluginRendererOptions) { - if (!pluginRendererOptions.entryPoints || !Array.isArray(pluginRendererOptions.entryPoints)) { - throw new Error('Required config option "renderer.entryPoints" has not been defined'); + if ( + !pluginRendererOptions.entryPoints || + !Array.isArray(pluginRendererOptions.entryPoints) + ) { + throw new Error( + 'Required config option "renderer.entryPoints" has not been defined', + ); } for (const entryPoint of pluginRendererOptions.entryPoints) { const entryKey = this.toEnvironmentVariable(entryPoint); @@ -170,12 +201,16 @@ export default class WebpackConfigGenerator { const mainConfig = await this.resolveConfig(this.pluginConfig.mainConfig); if (!mainConfig.entry) { - throw new Error('Required option "mainConfig.entry" has not been defined'); + throw new Error( + 'Required option "mainConfig.entry" has not been defined', + ); } const fix = (item: EntryType): EntryType => { if (typeof item === 'string') return (fix([item]) as string[])[0]; if (Array.isArray(item)) { - return item.map((val) => (val.startsWith('./') ? path.resolve(this.projectDir, val) : val)); + return item.map((val) => + val.startsWith('./') ? path.resolve(this.projectDir, val) : val, + ); } const ret: Record = {}; for (const key of Object.keys(item)) { @@ -201,39 +236,72 @@ export default class WebpackConfigGenerator { __filename: false, }, }, - mainConfig || {} + mainConfig || {}, ); } - async getRendererConfig(rendererOptions: WebpackPluginRendererConfig): Promise { + async getRendererConfig( + rendererOptions: WebpackPluginRendererConfig, + ): Promise { const entryPointsForTarget = { - web: [] as (WebpackPluginEntryPointLocalWindow | WebpackPluginEntryPoint)[], - electronRenderer: [] as (WebpackPluginEntryPointLocalWindow | WebpackPluginEntryPoint)[], + web: [] as ( + | WebpackPluginEntryPointLocalWindow + | WebpackPluginEntryPoint + )[], + electronRenderer: [] as ( + | WebpackPluginEntryPointLocalWindow + | WebpackPluginEntryPoint + )[], electronPreload: [] as WebpackPluginEntryPointPreloadOnly[], sandboxedPreload: [] as WebpackPluginEntryPointPreloadOnly[], }; for (const entry of rendererOptions.entryPoints) { - const target = entry.nodeIntegration ?? rendererOptions.nodeIntegration ? 'electronRenderer' : 'web'; - const preloadTarget = entry.nodeIntegration ?? rendererOptions.nodeIntegration ? 'electronPreload' : 'sandboxedPreload'; + const target = + (entry.nodeIntegration ?? rendererOptions.nodeIntegration) + ? 'electronRenderer' + : 'web'; + const preloadTarget = + (entry.nodeIntegration ?? rendererOptions.nodeIntegration) + ? 'electronPreload' + : 'sandboxedPreload'; if (isPreloadOnly(entry)) { entryPointsForTarget[preloadTarget].push(entry); } else { entryPointsForTarget[target].push(entry); if (isLocalWindow(entry) && entry.preload) { - entryPointsForTarget[preloadTarget].push({ ...entry, preload: entry.preload }); + entryPointsForTarget[preloadTarget].push({ + ...entry, + preload: entry.preload, + }); } } } const rendererConfigs = await Promise.all( [ - await this.buildRendererConfigs(rendererOptions, entryPointsForTarget.web, RendererTarget.Web), - await this.buildRendererConfigs(rendererOptions, entryPointsForTarget.electronRenderer, RendererTarget.ElectronRenderer), - await this.buildRendererConfigs(rendererOptions, entryPointsForTarget.electronPreload, RendererTarget.ElectronPreload), - await this.buildRendererConfigs(rendererOptions, entryPointsForTarget.sandboxedPreload, RendererTarget.SandboxedPreload), - ].reduce((configs, allConfigs) => allConfigs.concat(configs)) + await this.buildRendererConfigs( + rendererOptions, + entryPointsForTarget.web, + RendererTarget.Web, + ), + await this.buildRendererConfigs( + rendererOptions, + entryPointsForTarget.electronRenderer, + RendererTarget.ElectronRenderer, + ), + await this.buildRendererConfigs( + rendererOptions, + entryPointsForTarget.electronPreload, + RendererTarget.ElectronPreload, + ), + await this.buildRendererConfigs( + rendererOptions, + entryPointsForTarget.sandboxedPreload, + RendererTarget.SandboxedPreload, + ), + ].reduce((configs, allConfigs) => allConfigs.concat(configs)), ); return rendererConfigs.filter(isNotNull); @@ -254,21 +322,28 @@ export default class WebpackConfigGenerator { __dirname: false, __filename: false, }, - plugins: [new AssetRelocatorPatch(this.isProd, target === RendererTarget.ElectronRenderer || target === RendererTarget.ElectronPreload)], + plugins: [ + new AssetRelocatorPatch( + this.isProd, + target === RendererTarget.ElectronRenderer || + target === RendererTarget.ElectronPreload, + ), + ], }; } async buildRendererConfigForWebOrRendererTarget( rendererOptions: WebpackPluginRendererConfig, entryPoints: WebpackPluginEntryPoint[], - target: RendererTarget.Web | RendererTarget.ElectronRenderer + target: RendererTarget.Web | RendererTarget.ElectronRenderer, ): Promise { if (!isLocalOrNoWindowEntries(entryPoints)) { throw new Error('Invalid renderer entry point detected.'); } const entry: webpack.Entry = {}; - const baseConfig: webpack.Configuration = this.buildRendererBaseConfig(target); + const baseConfig: webpack.Configuration = + this.buildRendererBaseConfig(target); const rendererConfig = await this.resolveConfig(rendererOptions.config); const output = { @@ -280,7 +355,9 @@ export default class WebpackConfigGenerator { const plugins: webpack.WebpackPluginInstance[] = []; for (const entryPoint of entryPoints) { - entry[entryPoint.name] = (entryPoint.prefixedEntries || []).concat([entryPoint.js]); + entry[entryPoint.name] = (entryPoint.prefixedEntries || []).concat([ + entryPoint.js, + ]); if (isLocalWindow(entryPoint)) { plugins.push( @@ -289,30 +366,46 @@ export default class WebpackConfigGenerator { template: entryPoint.html, filename: `${entryPoint.name}/index.html`, chunks: [entryPoint.name].concat(entryPoint.additionalChunks || []), - }) as WebpackPluginInstance + }) as WebpackPluginInstance, ); } } - return webpackMerge(baseConfig, rendererConfig || {}, { entry, output, plugins }); + return webpackMerge(baseConfig, rendererConfig || {}, { + entry, + output, + plugins, + }); } async buildRendererConfigForPreloadOrSandboxedPreloadTarget( rendererOptions: WebpackPluginRendererConfig, entryPoints: WebpackPluginEntryPointPreloadOnly[], - target: RendererTarget.ElectronPreload | RendererTarget.SandboxedPreload + target: RendererTarget.ElectronPreload | RendererTarget.SandboxedPreload, ): Promise { if (entryPoints.length === 0) { return null; } - const externals = ['electron', 'electron/renderer', 'electron/common', 'events', 'timers', 'url']; + const externals = [ + 'electron', + 'electron/renderer', + 'electron/common', + 'events', + 'timers', + 'url', + ]; const entry: webpack.Entry = {}; - const baseConfig: webpack.Configuration = this.buildRendererBaseConfig(target); - const rendererConfig = await this.resolveConfig(entryPoints[0].preload?.config || rendererOptions.config); + const baseConfig: webpack.Configuration = + this.buildRendererBaseConfig(target); + const rendererConfig = await this.resolveConfig( + entryPoints[0].preload?.config || rendererOptions.config, + ); for (const entryPoint of entryPoints) { - entry[entryPoint.name] = (entryPoint.prefixedEntries || []).concat([entryPoint.preload.js]); + entry[entryPoint.name] = (entryPoint.prefixedEntries || []).concat([ + entryPoint.preload.js, + ]); } const config: Configuration = { target: rendererTargetToWebpackTarget(target), @@ -323,7 +416,10 @@ export default class WebpackConfigGenerator { globalObject: 'self', ...(this.isProd ? { publicPath: '' } : { publicPath: '/' }), }, - plugins: target === RendererTarget.ElectronPreload ? [] : [new webpack.ExternalsPlugin('commonjs2', externals)], + plugins: + target === RendererTarget.ElectronPreload + ? [] + : [new webpack.ExternalsPlugin('commonjs2', externals)], }; return webpackMerge(baseConfig, rendererConfig || {}, config); } @@ -331,27 +427,58 @@ export default class WebpackConfigGenerator { async buildRendererConfigs( rendererOptions: WebpackPluginRendererConfig, entryPoints: WebpackPluginEntryPoint[], - target: RendererTarget + target: RendererTarget, ): Promise[]> { if (entryPoints.length === 0) { return []; } const rendererConfigs = []; - if (target === RendererTarget.Web || target === RendererTarget.ElectronRenderer) { - rendererConfigs.push(this.buildRendererConfigForWebOrRendererTarget(rendererOptions, entryPoints, target)); + if ( + target === RendererTarget.Web || + target === RendererTarget.ElectronRenderer + ) { + rendererConfigs.push( + this.buildRendererConfigForWebOrRendererTarget( + rendererOptions, + entryPoints, + target, + ), + ); return rendererConfigs; - } else if (target === RendererTarget.ElectronPreload || target === RendererTarget.SandboxedPreload) { + } else if ( + target === RendererTarget.ElectronPreload || + target === RendererTarget.SandboxedPreload + ) { if (!isPreloadOnlyEntries(entryPoints)) { throw new Error('Invalid renderer entry point detected.'); } - const entryPointsWithPreloadConfig: WebpackPluginEntryPointPreloadOnly[] = [], - entryPointsWithoutPreloadConfig: WebpackPluginEntryPointPreloadOnly[] = []; - entryPoints.forEach((entryPoint) => (entryPoint.preload.config ? entryPointsWithPreloadConfig : entryPointsWithoutPreloadConfig).push(entryPoint)); - - rendererConfigs.push(this.buildRendererConfigForPreloadOrSandboxedPreloadTarget(rendererOptions, entryPointsWithoutPreloadConfig, target)); + const entryPointsWithPreloadConfig: WebpackPluginEntryPointPreloadOnly[] = + [], + entryPointsWithoutPreloadConfig: WebpackPluginEntryPointPreloadOnly[] = + []; + entryPoints.forEach((entryPoint) => + (entryPoint.preload.config + ? entryPointsWithPreloadConfig + : entryPointsWithoutPreloadConfig + ).push(entryPoint), + ); + + rendererConfigs.push( + this.buildRendererConfigForPreloadOrSandboxedPreloadTarget( + rendererOptions, + entryPointsWithoutPreloadConfig, + target, + ), + ); entryPointsWithPreloadConfig.forEach((entryPoint) => { - rendererConfigs.push(this.buildRendererConfigForPreloadOrSandboxedPreloadTarget(rendererOptions, [entryPoint], target)); + rendererConfigs.push( + this.buildRendererConfigForPreloadOrSandboxedPreloadTarget( + rendererOptions, + [entryPoint], + target, + ), + ); }); return rendererConfigs; } else { diff --git a/packages/plugin/webpack/src/WebpackPlugin.ts b/packages/plugin/webpack/src/WebpackPlugin.ts index f98e1296d1..90dfc7084c 100644 --- a/packages/plugin/webpack/src/WebpackPlugin.ts +++ b/packages/plugin/webpack/src/WebpackPlugin.ts @@ -3,9 +3,16 @@ import http from 'node:http'; import path from 'node:path'; import { pipeline } from 'stream/promises'; -import { getElectronVersion, listrCompatibleRebuildHook } from '@electron-forge/core-utils'; +import { + getElectronVersion, + listrCompatibleRebuildHook, +} from '@electron-forge/core-utils'; import { namedHookWithTaskFn, PluginBase } from '@electron-forge/plugin-base'; -import { ForgeMultiHookMap, ListrTask, ResolvedForgeConfig } from '@electron-forge/shared-types'; +import { + ForgeMultiHookMap, + ListrTask, + ResolvedForgeConfig, +} from '@electron-forge/shared-types'; import Logger, { Tab } from '@electron-forge/web-multi-logger'; import chalk from 'chalk'; import debug from 'debug'; @@ -75,7 +82,9 @@ export default class WebpackPlugin extends PluginBase { private isValidPort = (port: number) => { if (port < 1024) { - throw new Error(`Cannot specify port (${port}) below 1024, as they are privileged`); + throw new Error( + `Cannot specify port (${port}) below 1024, as they are privileged`, + ); } else if (port > 65535) { throw new Error(`Port specified (${port}) is not a valid TCP port.`); } else { @@ -83,7 +92,10 @@ export default class WebpackPlugin extends PluginBase { } }; - exitHandler = (options: { cleanup?: boolean; exit?: boolean }, err?: Error): void => { + exitHandler = ( + options: { cleanup?: boolean; exit?: boolean }, + err?: Error, + ): void => { d('handling process exit with:', options); if (options.cleanup) { for (const watcher of this.watchers) { @@ -110,21 +122,38 @@ export default class WebpackPlugin extends PluginBase { if (options.exit) process.exit(); }; - async writeJSONStats(type: string, stats: webpack.Stats | undefined, statsOptions: WebpackToJsonOptions, suffix: string): Promise { + async writeJSONStats( + type: string, + stats: webpack.Stats | undefined, + statsOptions: WebpackToJsonOptions, + suffix: string, + ): Promise { if (!stats) return; d(`Writing JSON stats for ${type} config`); const jsonStats = stats.toJson(statsOptions); - const jsonStatsFilename = path.resolve(this.baseDir, type, `stats-${suffix}.json`); + const jsonStatsFilename = path.resolve( + this.baseDir, + type, + `stats-${suffix}.json`, + ); await fs.writeJson(jsonStatsFilename, jsonStats, { spaces: 2 }); } - private runWebpack = async (options: Configuration[], rendererOptions: WebpackPluginRendererConfig | null): Promise => + private runWebpack = async ( + options: Configuration[], + rendererOptions: WebpackPluginRendererConfig | null, + ): Promise => new Promise((resolve, reject) => { webpack(options).run(async (err, stats) => { if (rendererOptions && rendererOptions.jsonStats) { for (const [index, entryStats] of (stats?.stats ?? []).entries()) { const name = rendererOptions.entryPoints[index].name; - await this.writeJSONStats('renderer', entryStats, options[index].stats as WebpackToJsonOptions, name); + await this.writeJSONStats( + 'renderer', + entryStats, + options[index].stats as WebpackToJsonOptions, + name, + ); } } if (err) { @@ -139,7 +168,9 @@ export default class WebpackPlugin extends PluginBase { d('hooking process events'); process.on('exit', (_code) => this.exitHandler({ cleanup: true })); - process.on('SIGINT' as NodeJS.Signals, (_signal) => this.exitHandler({ exit: true })); + process.on('SIGINT' as NodeJS.Signals, (_signal) => + this.exitHandler({ exit: true }), + ); }; setDirectories = (dir: string): void => { @@ -149,7 +180,12 @@ export default class WebpackPlugin extends PluginBase { get configGenerator(): WebpackConfigGenerator { if (!this._configGenerator) { - this._configGenerator = new WebpackConfigGenerator(this.config, this.projectDir, this.isProd, this.port); + this._configGenerator = new WebpackConfigGenerator( + this.config, + this.projectDir, + this.isProd, + this.port, + ); } return this._configGenerator; @@ -193,198 +229,301 @@ export default class WebpackPlugin extends PluginBase { }, 'Preparing webpack bundles'), ], prePackage: [ - namedHookWithTaskFn<'prePackage'>(async (task, config, platform, arch) => { - if (!task) { - throw new Error('Incompatible usage of webpack-plugin prePackage hook'); - } - - this.isProd = true; - await fs.remove(this.baseDir); - - // TODO: Figure out how to get matrix from packager - const arches: string[] = Array.from( - new Set(arch.split(',').reduce((all, pArch) => (pArch === 'universal' ? all.concat(['arm64', 'x64']) : all.concat([pArch])), [])) - ); - - const firstArch = arches[0]; - const otherArches = arches.slice(1); - - const multiArchTasks: ListrTask[] = - otherArches.length === 0 - ? [] - : [ - { - title: 'Mapping native dependencies', - task: async (ctx: NativeDepsCtx) => { - const firstArchDir = path.resolve(this.baseDir, firstArch); - const nodeModulesDir = path.resolve(this.projectDir, 'node_modules'); - const mapping: Record = Object.create(null); - - const webpackNodeFiles = await glob('**/*.node', { - cwd: firstArchDir, - }); - const nodeModulesNodeFiles = await glob('**/*.node', { - cwd: nodeModulesDir, - }); - const hashToNodeModules: Record = Object.create(null); - - for (const nodeModulesNodeFile of nodeModulesNodeFiles) { - const hash = crypto.createHash('sha256'); - const resolvedNodeFile = path.resolve(nodeModulesDir, nodeModulesNodeFile); - await pipeline(fs.createReadStream(resolvedNodeFile), hash); - const digest = hash.digest('hex'); - - hashToNodeModules[digest] = hashToNodeModules[digest] || []; - hashToNodeModules[digest].push(resolvedNodeFile); - } - - for (const webpackNodeFile of webpackNodeFiles) { - const hash = crypto.createHash('sha256'); - await pipeline(fs.createReadStream(path.resolve(firstArchDir, webpackNodeFile)), hash); - const matchedNodeModule = hashToNodeModules[hash.digest('hex')]; - if (!matchedNodeModule || !matchedNodeModule.length) { - throw new Error(`Could not find originating native module for "${webpackNodeFile}"`); + namedHookWithTaskFn<'prePackage'>( + async (task, config, platform, arch) => { + if (!task) { + throw new Error( + 'Incompatible usage of webpack-plugin prePackage hook', + ); + } + + this.isProd = true; + await fs.remove(this.baseDir); + + // TODO: Figure out how to get matrix from packager + const arches: string[] = Array.from( + new Set( + arch + .split(',') + .reduce< + string[] + >((all, pArch) => (pArch === 'universal' ? all.concat(['arm64', 'x64']) : all.concat([pArch])), []), + ), + ); + + const firstArch = arches[0]; + const otherArches = arches.slice(1); + + const multiArchTasks: ListrTask[] = + otherArches.length === 0 + ? [] + : [ + { + title: 'Mapping native dependencies', + task: async (ctx: NativeDepsCtx) => { + const firstArchDir = path.resolve( + this.baseDir, + firstArch, + ); + const nodeModulesDir = path.resolve( + this.projectDir, + 'node_modules', + ); + const mapping: Record = + Object.create(null); + + const webpackNodeFiles = await glob('**/*.node', { + cwd: firstArchDir, + }); + const nodeModulesNodeFiles = await glob('**/*.node', { + cwd: nodeModulesDir, + }); + const hashToNodeModules: Record = + Object.create(null); + + for (const nodeModulesNodeFile of nodeModulesNodeFiles) { + const hash = crypto.createHash('sha256'); + const resolvedNodeFile = path.resolve( + nodeModulesDir, + nodeModulesNodeFile, + ); + await pipeline( + fs.createReadStream(resolvedNodeFile), + hash, + ); + const digest = hash.digest('hex'); + + hashToNodeModules[digest] = + hashToNodeModules[digest] || []; + hashToNodeModules[digest].push(resolvedNodeFile); } - mapping[webpackNodeFile] = matchedNodeModule; - } + for (const webpackNodeFile of webpackNodeFiles) { + const hash = crypto.createHash('sha256'); + await pipeline( + fs.createReadStream( + path.resolve(firstArchDir, webpackNodeFile), + ), + hash, + ); + const matchedNodeModule = + hashToNodeModules[hash.digest('hex')]; + if (!matchedNodeModule || !matchedNodeModule.length) { + throw new Error( + `Could not find originating native module for "${webpackNodeFile}"`, + ); + } + + mapping[webpackNodeFile] = matchedNodeModule; + } - ctx.nativeDeps = mapping; + ctx.nativeDeps = mapping; + }, }, - }, - { - title: `Generating multi-arch bundles`, - task: async (_, task) => { - return task.newListr( - otherArches.map( - (pArch): ListrTask => ({ - title: `Generating ${chalk.magenta(pArch)} bundle`, - task: async (_, innerTask) => { - return innerTask.newListr( - [ - { - title: 'Preparing native dependencies', - task: async (_, innerTask) => { - await listrCompatibleRebuildHook( - this.projectDir, - await getElectronVersion(this.projectDir, await fs.readJson(path.join(this.projectDir, 'package.json'))), - platform, - pArch, - config.rebuildConfig, - innerTask - ); - }, - rendererOptions: { - persistentOutput: true, - bottomBar: Infinity, - showTimer: true, + { + title: `Generating multi-arch bundles`, + task: async (_, task) => { + return task.newListr( + otherArches.map( + (pArch): ListrTask => ({ + title: `Generating ${chalk.magenta(pArch)} bundle`, + task: async (_, innerTask) => { + return innerTask.newListr( + [ + { + title: 'Preparing native dependencies', + task: async (_, innerTask) => { + await listrCompatibleRebuildHook( + this.projectDir, + await getElectronVersion( + this.projectDir, + await fs.readJson( + path.join( + this.projectDir, + 'package.json', + ), + ), + ), + platform, + pArch, + config.rebuildConfig, + innerTask, + ); + }, + rendererOptions: { + persistentOutput: true, + bottomBar: Infinity, + showTimer: true, + }, }, - }, - { - title: 'Mapping native dependencies', - task: async (ctx) => { - const nodeModulesDir = path.resolve(this.projectDir, 'node_modules'); - - // Duplicate the firstArch build - const firstDir = path.resolve(this.baseDir, firstArch); - const targetDir = path.resolve(this.baseDir, pArch); - await fs.mkdirp(targetDir); - for (const child of await fs.readdir(firstDir)) { - await fs.promises.cp(path.resolve(firstDir, child), path.resolve(targetDir, child), { - recursive: true, - }); - } - - const nodeModulesNodeFiles = await glob('**/*.node', { - cwd: nodeModulesDir, - }); - const nodeModuleToHash: Record = Object.create(null); - - for (const nodeModulesNodeFile of nodeModulesNodeFiles) { - const hash = crypto.createHash('sha256'); - const resolvedNodeFile = path.resolve(nodeModulesDir, nodeModulesNodeFile); - await pipeline(fs.createReadStream(resolvedNodeFile), hash); - - nodeModuleToHash[resolvedNodeFile] = hash.digest('hex'); - } - - // Use the native module map to find the newly built native modules - for (const nativeDep of Object.keys(ctx.nativeDeps)) { - const archPath = path.resolve(targetDir, nativeDep); - await fs.remove(archPath); - - const mappedPaths = ctx.nativeDeps[nativeDep]; - if (!mappedPaths || !mappedPaths.length) { - throw new Error(`The "${nativeDep}" module could not be mapped to any native modules on disk`); + { + title: 'Mapping native dependencies', + task: async (ctx) => { + const nodeModulesDir = path.resolve( + this.projectDir, + 'node_modules', + ); + + // Duplicate the firstArch build + const firstDir = path.resolve( + this.baseDir, + firstArch, + ); + const targetDir = path.resolve( + this.baseDir, + pArch, + ); + await fs.mkdirp(targetDir); + for (const child of await fs.readdir( + firstDir, + )) { + await fs.promises.cp( + path.resolve(firstDir, child), + path.resolve(targetDir, child), + { + recursive: true, + }, + ); } - if (!mappedPaths.every((mappedPath) => nodeModuleToHash[mappedPath] === nodeModuleToHash[mappedPaths[0]])) { - throw new Error( - `The "${nativeDep}" mapped to multiple modules "${mappedPaths.join( - ', ' - )}" but the same modules post rebuild did not map to the same native code` + const nodeModulesNodeFiles = await glob( + '**/*.node', + { + cwd: nodeModulesDir, + }, + ); + const nodeModuleToHash: Record< + string, + string + > = Object.create(null); + + for (const nodeModulesNodeFile of nodeModulesNodeFiles) { + const hash = + crypto.createHash('sha256'); + const resolvedNodeFile = path.resolve( + nodeModulesDir, + nodeModulesNodeFile, + ); + await pipeline( + fs.createReadStream( + resolvedNodeFile, + ), + hash, ); + + nodeModuleToHash[resolvedNodeFile] = + hash.digest('hex'); } - await fs.promises.cp(mappedPaths[0], archPath); - } + // Use the native module map to find the newly built native modules + for (const nativeDep of Object.keys( + ctx.nativeDeps, + )) { + const archPath = path.resolve( + targetDir, + nativeDep, + ); + await fs.remove(archPath); + + const mappedPaths = + ctx.nativeDeps[nativeDep]; + if ( + !mappedPaths || + !mappedPaths.length + ) { + throw new Error( + `The "${nativeDep}" module could not be mapped to any native modules on disk`, + ); + } + + if ( + !mappedPaths.every( + (mappedPath) => + nodeModuleToHash[mappedPath] === + nodeModuleToHash[ + mappedPaths[0] + ], + ) + ) { + throw new Error( + `The "${nativeDep}" mapped to multiple modules "${mappedPaths.join( + ', ', + )}" but the same modules post rebuild did not map to the same native code`, + ); + } + + await fs.promises.cp( + mappedPaths[0], + archPath, + ); + } + }, }, - }, - ], - { concurrent: false } - ); - }, - }) - ) - ); + ], + { concurrent: false }, + ); + }, + }), + ), + ); + }, }, + ]; + + return task.newListr( + [ + { + title: `Preparing native dependencies for ${chalk.magenta(firstArch)}`, + task: async (_, innerTask) => { + await listrCompatibleRebuildHook( + this.projectDir, + await getElectronVersion( + this.projectDir, + await fs.readJson( + path.join(this.projectDir, 'package.json'), + ), + ), + platform, + firstArch, + config.rebuildConfig, + innerTask, + ); + }, + rendererOptions: { + persistentOutput: true, + bottomBar: Infinity, + timer: { ...PRESET_TIMER }, }, - ]; - - return task.newListr( - [ - { - title: `Preparing native dependencies for ${chalk.magenta(firstArch)}`, - task: async (_, innerTask) => { - await listrCompatibleRebuildHook( - this.projectDir, - await getElectronVersion(this.projectDir, await fs.readJson(path.join(this.projectDir, 'package.json'))), - platform, - firstArch, - config.rebuildConfig, - innerTask - ); - }, - rendererOptions: { - persistentOutput: true, - bottomBar: Infinity, - timer: { ...PRESET_TIMER }, - }, - }, - { - title: 'Building webpack bundles', - task: async () => { - await this.compileMain(); - await this.compileRenderers(); - // Store it in a place that won't get messed with - // We'll restore the right "arch" in the afterCopy hook further down - const preExistingChildren = await fs.readdir(this.baseDir); - const targetDir = path.resolve(this.baseDir, firstArch); - await fs.mkdirp(targetDir); - for (const child of preExistingChildren) { - await fs.move(path.resolve(this.baseDir, child), path.resolve(targetDir, child)); - } }, - rendererOptions: { - timer: { ...PRESET_TIMER }, + { + title: 'Building webpack bundles', + task: async () => { + await this.compileMain(); + await this.compileRenderers(); + // Store it in a place that won't get messed with + // We'll restore the right "arch" in the afterCopy hook further down + const preExistingChildren = await fs.readdir(this.baseDir); + const targetDir = path.resolve(this.baseDir, firstArch); + await fs.mkdirp(targetDir); + for (const child of preExistingChildren) { + await fs.move( + path.resolve(this.baseDir, child), + path.resolve(targetDir, child), + ); + } + }, + rendererOptions: { + timer: { ...PRESET_TIMER }, + }, }, - }, - ...multiArchTasks, - ], - { concurrent: false } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ) as any; - }, 'Preparing webpack bundles'), + ...multiArchTasks, + ], + { concurrent: false }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ) as any; + }, + 'Preparing webpack bundles', + ), ], postStart: async (_config, child) => { d('hooking electron process exit'); @@ -395,11 +534,20 @@ export default class WebpackPlugin extends PluginBase { }, resolveForgeConfig: this.resolveForgeConfig, packageAfterCopy: [ - async (_forgeConfig: ResolvedForgeConfig, buildPath: string, _electronVersion: string, _platform: string, pArch: string): Promise => { + async ( + _forgeConfig: ResolvedForgeConfig, + buildPath: string, + _electronVersion: string, + _platform: string, + pArch: string, + ): Promise => { // Restore the correct 'arch' build of webpack // Steal the correct arch, wipe the folder, move it back to pretend to be ".webpack" root const tmpWebpackDir = path.resolve(buildPath, '.webpack.tmp'); - await fs.move(path.resolve(buildPath, '.webpack', pArch), tmpWebpackDir); + await fs.move( + path.resolve(buildPath, '.webpack', pArch), + tmpWebpackDir, + ); await fs.remove(path.resolve(buildPath, '.webpack')); await fs.move(tmpWebpackDir, path.resolve(buildPath, '.webpack')); }, @@ -408,7 +556,9 @@ export default class WebpackPlugin extends PluginBase { }; } - resolveForgeConfig = async (forgeConfig: ResolvedForgeConfig): Promise => { + resolveForgeConfig = async ( + forgeConfig: ResolvedForgeConfig, + ): Promise => { if (!forgeConfig.packagerConfig) { forgeConfig.packagerConfig = {}; } @@ -417,7 +567,7 @@ export default class WebpackPlugin extends PluginBase { console.error( chalk.red(`You have set packagerConfig.ignore, the Electron Forge webpack plugin normally sets this automatically. -Your packaged app may be larger than expected if you dont ignore everything other than the '.webpack' folder`) +Your packaged app may be larger than expected if you dont ignore everything other than the '.webpack' folder`), ); } return forgeConfig; @@ -425,11 +575,17 @@ Your packaged app may be larger than expected if you dont ignore everything othe forgeConfig.packagerConfig.ignore = (file: string) => { if (!file) return false; - if (this.config.jsonStats && file.endsWith(path.join('.webpack', 'main', 'stats.json'))) { + if ( + this.config.jsonStats && + file.endsWith(path.join('.webpack', 'main', 'stats.json')) + ) { return true; } - if (this.allRendererOptions.some((r) => r.jsonStats) && file.endsWith(path.join('.webpack', 'renderer', 'stats.json'))) { + if ( + this.allRendererOptions.some((r) => r.jsonStats) && + file.endsWith(path.join('.webpack', 'renderer', 'stats.json')) + ) { return true; } @@ -443,10 +599,15 @@ Your packaged app may be larger than expected if you dont ignore everything othe }; private get allRendererOptions() { - return Array.isArray(this.config.renderer) ? this.config.renderer : [this.config.renderer]; + return Array.isArray(this.config.renderer) + ? this.config.renderer + : [this.config.renderer]; } - packageAfterCopy = async (_forgeConfig: ResolvedForgeConfig, buildPath: string): Promise => { + packageAfterCopy = async ( + _forgeConfig: ResolvedForgeConfig, + buildPath: string, + ): Promise => { const pj = await fs.readJson(path.resolve(this.projectDir, 'package.json')); if (!pj.main?.endsWith('.webpack/main')) { @@ -481,16 +642,25 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}`); tab.log( stats.toString({ colors: true, - }) + }), ); } if (this.config.jsonStats) { - await this.writeJSONStats('main', stats, mainConfig.stats as WebpackToJsonOptions, 'main'); + await this.writeJSONStats( + 'main', + stats, + mainConfig.stats as WebpackToJsonOptions, + 'main', + ); } if (err) return onceReject(err); if (!watch && stats?.hasErrors()) { - return onceReject(new Error(`Compilation errors in the main process: ${stats.toString()}`)); + return onceReject( + new Error( + `Compilation errors in the main process: ${stats.toString()}`, + ), + ); } return onceResolve(undefined); @@ -505,9 +675,14 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}`); compileRenderers = async (watch = false): Promise => { for (const rendererOptions of this.allRendererOptions) { - const stats = await this.runWebpack(await this.configGenerator.getRendererConfig(rendererOptions), rendererOptions); + const stats = await this.runWebpack( + await this.configGenerator.getRendererConfig(rendererOptions), + rendererOptions, + ); if (!watch && stats?.hasErrors()) { - throw new Error(`Compilation errors in the renderer: ${stats.toString()}`); + throw new Error( + `Compilation errors in the renderer: ${stats.toString()}`, + ); } } }; @@ -518,11 +693,13 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}`); for (const [i, rendererOptions] of this.allRendererOptions.entries()) { const groupName = `group_${i}`; configs.push( - ...(await this.configGenerator.getRendererConfig(rendererOptions)).map((config) => ({ - ...config, - name: groupName, - dependencies: [...rollingDependencies], - })) + ...(await this.configGenerator.getRendererConfig(rendererOptions)).map( + (config) => ({ + ...config, + name: groupName, + dependencies: [...rollingDependencies], + }), + ), ); rollingDependencies.push(groupName); } @@ -535,7 +712,11 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}`); let numPreloadEntriesWithConfig = 0; for (const entryConfig of configs) { if (!entryConfig.plugins) entryConfig.plugins = []; - entryConfig.plugins.push(new ElectronForgeLoggingPlugin(logger.createTab(`Renderer Target Bundle (${entryConfig.target})`))); + entryConfig.plugins.push( + new ElectronForgeLoggingPlugin( + logger.createTab(`Renderer Target Bundle (${entryConfig.target})`), + ), + ); const filename = entryConfig.output?.filename as string; if (filename?.endsWith('preload.js')) { @@ -559,14 +740,21 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}`); return new Promise((resolve, reject) => { compiler.hooks.done.tap(preloadPlugin, (stats) => { if (stats.hasErrors()) { - return reject(new Error(`Compilation errors in the preload: ${stats.toString()}`)); + return reject( + new Error( + `Compilation errors in the preload: ${stats.toString()}`, + ), + ); } return resolve(undefined); }); }); }); - const webpackDevServer = new WebpackDevServer(this.devServerOptions(), compiler); + const webpackDevServer = new WebpackDevServer( + this.devServerOptions(), + compiler, + ); await webpackDevServer.start(); this.servers.push(webpackDevServer.server!); await Promise.all(promises); @@ -574,7 +762,8 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}`); devServerOptions(): WebpackDevServer.Configuration { const cspDirectives = - this.config.devContentSecurityPolicy ?? "default-src 'self' 'unsafe-inline' data:; script-src 'self' 'unsafe-eval' 'unsafe-inline' data:"; + this.config.devContentSecurityPolicy ?? + "default-src 'self' 'unsafe-inline' data:; script-src 'self' 'unsafe-eval' 'unsafe-inline' data:"; const defaults: Partial = { hot: true, diff --git a/packages/plugin/webpack/src/util/AssetRelocatorPatch.ts b/packages/plugin/webpack/src/util/AssetRelocatorPatch.ts index 4b838ae23d..9c7ee476fb 100644 --- a/packages/plugin/webpack/src/util/AssetRelocatorPatch.ts +++ b/packages/plugin/webpack/src/util/AssetRelocatorPatch.ts @@ -23,46 +23,61 @@ export default class AssetRelocatorPatch { } public apply(compiler: Compiler): void { - compiler.hooks.compilation.tap('asset-relocator-forge-patch', (compilation) => { - // We intercept the Vercel loader code injection and replace __dirname with - // code that works with Electron Forge - // - // Here is where the injection occurs: - // https://github.com/vercel/webpack-asset-relocator-loader/blob/4710a018fc8fb64ad51efb368759616fb273618f/src/asset-relocator.js#L331-L339 - compilation.mainTemplate.hooks.requireExtensions.intercept({ - register: (tapInfo) => { - if (tapInfo.name === 'asset-relocator-loader') { - const originalFn = tapInfo.fn as (source: string, chunk: Chunk) => string; + compiler.hooks.compilation.tap( + 'asset-relocator-forge-patch', + (compilation) => { + // We intercept the Vercel loader code injection and replace __dirname with + // code that works with Electron Forge + // + // Here is where the injection occurs: + // https://github.com/vercel/webpack-asset-relocator-loader/blob/4710a018fc8fb64ad51efb368759616fb273618f/src/asset-relocator.js#L331-L339 + compilation.mainTemplate.hooks.requireExtensions.intercept({ + register: (tapInfo) => { + if (tapInfo.name === 'asset-relocator-loader') { + const originalFn = tapInfo.fn as ( + source: string, + chunk: Chunk, + ) => string; - tapInfo.fn = (source: string, chunk: Chunk) => { - const originalInjectCode = originalFn(source, chunk); + tapInfo.fn = (source: string, chunk: Chunk) => { + const originalInjectCode = originalFn(source, chunk); - // Since this is not a public API of the Vercel loader, it could - // change on patch versions and break things. - // - // If the injected code changes substantially, we throw an error - if (!originalInjectCode.includes('__webpack_require__.ab = __dirname + ')) { - throw new Error('The installed version of @vercel/webpack-asset-relocator-loader does not appear to be compatible with Forge'); - } + // Since this is not a public API of the Vercel loader, it could + // change on patch versions and break things. + // + // If the injected code changes substantially, we throw an error + if ( + !originalInjectCode.includes( + '__webpack_require__.ab = __dirname + ', + ) + ) { + throw new Error( + 'The installed version of @vercel/webpack-asset-relocator-loader does not appear to be compatible with Forge', + ); + } - if (this.isProd) { - return originalInjectCode.replace('__dirname', this.injectedProductionDirnameCode()); - } + if (this.isProd) { + return originalInjectCode.replace( + '__dirname', + this.injectedProductionDirnameCode(), + ); + } - return originalInjectCode.replace( - '__dirname', - // In development, the app is loaded via webpack-dev-server - // so __dirname is useless because it points to Electron - // internal code. Instead we hard-code the absolute path to - // the webpack output. - JSON.stringify(compiler.options.output.path) - ); - }; - } + return originalInjectCode.replace( + '__dirname', + // In development, the app is loaded via webpack-dev-server + // so __dirname is useless because it points to Electron + // internal code. Instead we hard-code the absolute path to + // the webpack output. + JSON.stringify(compiler.options.output.path), + ); + }; + } - return tapInfo; - }, - }); - }); + return tapInfo; + }, + }); + }, + ); } } diff --git a/packages/plugin/webpack/src/util/ElectronForgeLogging.ts b/packages/plugin/webpack/src/util/ElectronForgeLogging.ts index 6e7a471dda..cb3feb7092 100644 --- a/packages/plugin/webpack/src/util/ElectronForgeLogging.ts +++ b/packages/plugin/webpack/src/util/ElectronForgeLogging.ts @@ -16,7 +16,7 @@ export default class LoggingPlugin { this.tab.log( stats.toString({ colors: true, - }) + }), ); } }); diff --git a/packages/plugin/webpack/src/util/processConfig.ts b/packages/plugin/webpack/src/util/processConfig.ts index 53722cd482..5567b660d6 100644 --- a/packages/plugin/webpack/src/util/processConfig.ts +++ b/packages/plugin/webpack/src/util/processConfig.ts @@ -7,12 +7,18 @@ const trivialConfigurationFactory = () => config; -export type ConfigProcessor = (config: ConfigurationFactory) => Promise; +export type ConfigProcessor = ( + config: ConfigurationFactory, +) => Promise; // Ensure processing logic is run for both `Configuration` and // `ConfigurationFactory` config variants. -const processConfig = async (processor: ConfigProcessor, config: Configuration | ConfigurationFactory): Promise => { - const configFactory = typeof config === 'function' ? config : trivialConfigurationFactory(config); +const processConfig = async ( + processor: ConfigProcessor, + config: Configuration | ConfigurationFactory, +): Promise => { + const configFactory = + typeof config === 'function' ? config : trivialConfigurationFactory(config); return processor(configFactory); }; diff --git a/packages/plugin/webpack/src/util/rendererTypeUtils.ts b/packages/plugin/webpack/src/util/rendererTypeUtils.ts index 2b952a5ec5..a78c3a4b4a 100644 --- a/packages/plugin/webpack/src/util/rendererTypeUtils.ts +++ b/packages/plugin/webpack/src/util/rendererTypeUtils.ts @@ -1,30 +1,46 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { WebpackPluginEntryPoint, WebpackPluginEntryPointLocalWindow, WebpackPluginEntryPointNoWindow, WebpackPluginEntryPointPreloadOnly } from '../Config'; +import { + WebpackPluginEntryPoint, + WebpackPluginEntryPointLocalWindow, + WebpackPluginEntryPointNoWindow, + WebpackPluginEntryPointPreloadOnly, +} from '../Config'; /** * Reusable type predicate functions to narrow down the type of the WebpackPluginEntryPoint */ -export const isLocalWindow = (entry: WebpackPluginEntryPoint): entry is WebpackPluginEntryPointLocalWindow => { +export const isLocalWindow = ( + entry: WebpackPluginEntryPoint, +): entry is WebpackPluginEntryPointLocalWindow => { return !!(entry as any).html; }; -export const isPreloadOnly = (entry: WebpackPluginEntryPoint): entry is WebpackPluginEntryPointPreloadOnly => { +export const isPreloadOnly = ( + entry: WebpackPluginEntryPoint, +): entry is WebpackPluginEntryPointPreloadOnly => { return !(entry as any).html && !(entry as any).js && !!(entry as any).preload; }; -export const isNoWindow = (entry: WebpackPluginEntryPoint): entry is WebpackPluginEntryPointNoWindow => { +export const isNoWindow = ( + entry: WebpackPluginEntryPoint, +): entry is WebpackPluginEntryPointNoWindow => { return !(entry as any).html && !!(entry as any).js; }; -export const hasPreloadScript = (entry: WebpackPluginEntryPoint): entry is WebpackPluginEntryPointPreloadOnly => { +export const hasPreloadScript = ( + entry: WebpackPluginEntryPoint, +): entry is WebpackPluginEntryPointPreloadOnly => { return 'preload' in entry; }; export const isLocalOrNoWindowEntries = ( - entries: WebpackPluginEntryPoint[] -): entries is (WebpackPluginEntryPointLocalWindow | WebpackPluginEntryPointNoWindow)[] => { + entries: WebpackPluginEntryPoint[], +): entries is ( + | WebpackPluginEntryPointLocalWindow + | WebpackPluginEntryPointNoWindow +)[] => { for (const entry of entries) { if (!isLocalWindow(entry) && !isNoWindow(entry)) { return false; @@ -33,7 +49,9 @@ export const isLocalOrNoWindowEntries = ( return true; }; -export const isPreloadOnlyEntries = (entries: WebpackPluginEntryPoint[]): entries is WebpackPluginEntryPointPreloadOnly[] => { +export const isPreloadOnlyEntries = ( + entries: WebpackPluginEntryPoint[], +): entries is WebpackPluginEntryPointPreloadOnly[] => { for (const entry of entries) { if (!hasPreloadScript(entry)) { return false; diff --git a/packages/publisher/base-static/spec/StaticPublisher.spec.ts b/packages/publisher/base-static/spec/StaticPublisher.spec.ts index cf84dc5cd0..576a19fda5 100644 --- a/packages/publisher/base-static/spec/StaticPublisher.spec.ts +++ b/packages/publisher/base-static/spec/StaticPublisher.spec.ts @@ -20,7 +20,7 @@ describe('PublisherStatic', () => { arch: 'arch', keyPrefix: 'stuff', path: __filename, - }) + }), ).toEqual('stuff/plat/arch/StaticPublisher.spec.ts'); }); @@ -34,7 +34,7 @@ describe('PublisherStatic', () => { arch: 'arch', keyPrefix: 'stuff', path: __filename, - }) + }), ).toEqual('lololol'); }); }); diff --git a/packages/publisher/base-static/src/PublisherStatic.ts b/packages/publisher/base-static/src/PublisherStatic.ts index 4dfc247682..fa76281962 100644 --- a/packages/publisher/base-static/src/PublisherStatic.ts +++ b/packages/publisher/base-static/src/PublisherStatic.ts @@ -1,6 +1,9 @@ import path from 'node:path'; -import { PublisherBase, PublisherOptions } from '@electron-forge/publisher-base'; +import { + PublisherBase, + PublisherOptions, +} from '@electron-forge/publisher-base'; import { ForgeArch, ForgePlatform } from '@electron-forge/shared-types'; interface StaticArtifact { @@ -17,10 +20,16 @@ interface StaticPublisherConfig { keyResolver?: (fileName: string, platform: string, arch: string) => string; } -export default abstract class PublisherStatic extends PublisherBase { +export default abstract class PublisherStatic< + C extends StaticPublisherConfig, +> extends PublisherBase { protected keyForArtifact(artifact: StaticArtifact): string { if (this.config.keyResolver) { - return this.config.keyResolver(path.basename(artifact.path), artifact.platform, artifact.arch); + return this.config.keyResolver( + path.basename(artifact.path), + artifact.platform, + artifact.arch, + ); } return `${artifact.keyPrefix}/${artifact.platform}/${artifact.arch}/${path.basename(artifact.path)}`; diff --git a/packages/publisher/base/src/Publisher.ts b/packages/publisher/base/src/Publisher.ts index cb00c65194..512b3fb706 100644 --- a/packages/publisher/base/src/Publisher.ts +++ b/packages/publisher/base/src/Publisher.ts @@ -1,4 +1,10 @@ -import { ForgeListrTaskDefinition, ForgeMakeResult, ForgePlatform, IForgePublisher, ResolvedForgeConfig } from '@electron-forge/shared-types'; +import { + ForgeListrTaskDefinition, + ForgeMakeResult, + ForgePlatform, + IForgePublisher, + ResolvedForgeConfig, +} from '@electron-forge/shared-types'; export interface PublisherOptions { /** @@ -35,7 +41,10 @@ export default abstract class Publisher implements IForgePublisher { * @param config - A configuration object for this publisher * @param platformsToPublishOn - If you want this maker to run on platforms different from `defaultPlatforms` you can provide those platforms here */ - constructor(public config: C, protected platformsToPublishOn?: ForgePlatform[]) { + constructor( + public config: C, + protected platformsToPublishOn?: ForgePlatform[], + ) { this.config = config; Object.defineProperty(this, '__isElectronForgePublisher', { value: true, @@ -61,9 +70,12 @@ export default abstract class Publisher implements IForgePublisher { * you will have to create the version on GitHub and the second call will just * be appending files to the existing version. */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async publish(opts: PublisherOptions): Promise { - throw new Error(`Publisher ${this.name} did not implement the publish method`); + async publish( + _opts: PublisherOptions, + ): Promise { + throw new Error( + `Publisher ${this.name} did not implement the publish method`, + ); } } diff --git a/packages/publisher/bitbucket/src/PublisherBitbucket.ts b/packages/publisher/bitbucket/src/PublisherBitbucket.ts index 522eccaf43..0565706666 100644 --- a/packages/publisher/bitbucket/src/PublisherBitbucket.ts +++ b/packages/publisher/bitbucket/src/PublisherBitbucket.ts @@ -1,6 +1,9 @@ import path from 'node:path'; -import { PublisherBase, PublisherOptions } from '@electron-forge/publisher-base'; +import { + PublisherBase, + PublisherOptions, +} from '@electron-forge/publisher-base'; import FormData from 'form-data'; import fs from 'fs-extra'; import fetch from 'node-fetch'; @@ -10,7 +13,10 @@ import { PublisherBitbucketConfig } from './Config'; export default class PublisherBitbucket extends PublisherBase { name = 'bitbucket'; - async publish({ makeResults, setStatusLine }: PublisherOptions): Promise { + async publish({ + makeResults, + setStatusLine, + }: PublisherOptions): Promise { const { config } = this; const hasRepositoryConfig = config.repository && typeof config.repository; const replaceExistingFiles = Boolean(config.replaceExistingFiles); @@ -18,17 +24,25 @@ export default class PublisherBitbucket extends PublisherBase { }); const server = setupServer( - http.post('https://example.com/api/auth/login', () => HttpResponse.json({ token })), + http.post('https://example.com/api/auth/login', () => + HttpResponse.json({ token }), + ), http.get('https://example.com/versions/sorted', () => - HttpResponse.json({ total: 0, offset: 0, page: 0, items: [{ name: '2.0.0', assets: [], flavor: { name: 'default' } }] }) + HttpResponse.json({ + total: 0, + offset: 0, + page: 0, + items: [{ name: '2.0.0', assets: [], flavor: { name: 'default' } }], + }), ), http.post('https://example.com/api/version', () => { return HttpResponse.json({}); }), - http.post('https://example.com/api/asset', () => HttpResponse.json({})) + http.post('https://example.com/api/asset', () => HttpResponse.json({})), ); describe('PublisherERS', () => { @@ -66,12 +84,23 @@ describe('PublisherERS', () => { requests.push(request); }); - await publisher.publish({ makeResults, dir: '', forgeConfig: {} as ResolvedForgeConfig, setStatusLine: noop }); + await publisher.publish({ + makeResults, + dir: '', + forgeConfig: {} as ResolvedForgeConfig, + setStatusLine: noop, + }); expect(requests).toHaveLength(4); // creates a new version with the correct flavor, name, and channel expect(requests[2].url).toEqual(`${baseUrl}/api/version`); - await expect(requests[2].json()).resolves.toEqual({ channel: 'stable', flavor, name: version, notes: '', id: `${version}_stable` }); + await expect(requests[2].json()).resolves.toEqual({ + channel: 'stable', + flavor, + name: version, + notes: '', + id: `${version}_stable`, + }); // uploads asset successfully expect(requests[3].url).toEqual(`${baseUrl}/api/asset`); }); @@ -86,8 +115,13 @@ describe('PublisherERS', () => { // mock fetch all existing versions server.use( http.get('https://example.com/versions/sorted', () => - HttpResponse.json({ total: 1, offset: 0, page: 0, items: [{ name: '2.0.0', assets: [], flavor: { name: 'lite' } }] }) - ) + HttpResponse.json({ + total: 1, + offset: 0, + page: 0, + items: [{ name: '2.0.0', assets: [], flavor: { name: 'lite' } }], + }), + ), ); const publisher = new PublisherERS({ @@ -114,7 +148,12 @@ describe('PublisherERS', () => { requests.push(request); }); - await publisher.publish({ makeResults, dir: '', forgeConfig: {} as ResolvedForgeConfig, setStatusLine: noop }); + await publisher.publish({ + makeResults, + dir: '', + forgeConfig: {} as ResolvedForgeConfig, + setStatusLine: noop, + }); expect(requests).toHaveLength(3); // uploads asset successfully expect(requests[2].url).toEqual(`${baseUrl}/api/asset`); @@ -131,9 +170,15 @@ describe('PublisherERS', () => { total: 1, offset: 0, page: 0, - items: [{ name: '2.0.0', assets: [{ name: 'existing-artifact', platform: 'linux_64' }], flavor: { name: 'default' } }], - }) - ) + items: [ + { + name: '2.0.0', + assets: [{ name: 'existing-artifact', platform: 'linux_64' }], + flavor: { name: 'default' }, + }, + ], + }), + ), ); const publisher = new PublisherERS({ @@ -159,9 +204,16 @@ describe('PublisherERS', () => { requests.push(request); }); - await publisher.publish({ makeResults, dir: '', forgeConfig: {} as ResolvedForgeConfig, setStatusLine: noop }); + await publisher.publish({ + makeResults, + dir: '', + forgeConfig: {} as ResolvedForgeConfig, + setStatusLine: noop, + }); - expect(requests).not.toContainEqual(expect.objectContaining({ url: `${baseUrl}/api/asset` })); + expect(requests).not.toContainEqual( + expect.objectContaining({ url: `${baseUrl}/api/asset` }), + ); }); it('can upload a new flavor for an existing version', async () => { @@ -175,9 +227,15 @@ describe('PublisherERS', () => { total: 1, offset: 0, page: 0, - items: [{ name: '2.0.0', assets: [{ name: 'existing-artifact', platform: 'linux_64' }], flavor: { name: 'default' } }], - }) - ) + items: [ + { + name: '2.0.0', + assets: [{ name: 'existing-artifact', platform: 'linux_64' }], + flavor: { name: 'default' }, + }, + ], + }), + ), ); const publisher = new PublisherERS({ @@ -203,7 +261,12 @@ describe('PublisherERS', () => { requests.push(request); }); - await publisher.publish({ makeResults, dir: '', forgeConfig: {} as ResolvedForgeConfig, setStatusLine: noop }); + await publisher.publish({ + makeResults, + dir: '', + forgeConfig: {} as ResolvedForgeConfig, + setStatusLine: noop, + }); expect(requests).toHaveLength(4); @@ -230,21 +293,39 @@ describe('PublisherERS', () => { // @ts-expect-error testing invalid options const publisher = new PublisherERS({}); - await expect(publisher.publish({ makeResults: [], dir: '', forgeConfig: {} as ResolvedForgeConfig, setStatusLine: noop })).rejects.toThrow( - 'In order to publish to ERS you must set the "electronReleaseServer.baseUrl", "electronReleaseServer.username" and "electronReleaseServer.password" properties in your Forge config. See the docs for more info' + await expect( + publisher.publish({ + makeResults: [], + dir: '', + forgeConfig: {} as ResolvedForgeConfig, + setStatusLine: noop, + }), + ).rejects.toThrow( + 'In order to publish to ERS you must set the "electronReleaseServer.baseUrl", "electronReleaseServer.username" and "electronReleaseServer.password" properties in your Forge config. See the docs for more info', ); }); it('fails if the server returns 4xx', async () => { - server.use(http.post('https://example.com/api/auth/login', () => HttpResponse.json({ error: 'Not Authorized' }, { status: 401 }))); + server.use( + http.post('https://example.com/api/auth/login', () => + HttpResponse.json({ error: 'Not Authorized' }, { status: 401 }), + ), + ); const publisher = new PublisherERS({ baseUrl, username: 'test', password: 'test', }); - return expect(publisher.publish({ makeResults: [], dir: '', forgeConfig: {} as ResolvedForgeConfig, setStatusLine: noop })).rejects.toThrow( - 'ERS publish failed with status code: 401 (https://example.com/api/auth/login)' + return expect( + publisher.publish({ + makeResults: [], + dir: '', + forgeConfig: {} as ResolvedForgeConfig, + setStatusLine: noop, + }), + ).rejects.toThrow( + 'ERS publish failed with status code: 401 (https://example.com/api/auth/login)', ); }); }); diff --git a/packages/publisher/electron-release-server/src/PublisherERS.ts b/packages/publisher/electron-release-server/src/PublisherERS.ts index d88e49325d..e681b11afd 100644 --- a/packages/publisher/electron-release-server/src/PublisherERS.ts +++ b/packages/publisher/electron-release-server/src/PublisherERS.ts @@ -1,6 +1,9 @@ import path from 'node:path'; -import { PublisherBase, PublisherOptions } from '@electron-forge/publisher-base'; +import { + PublisherBase, + PublisherOptions, +} from '@electron-forge/publisher-base'; import { ForgeArch, ForgePlatform } from '@electron-forge/shared-types'; import debug from 'debug'; import FormData from 'form-data'; @@ -33,16 +36,24 @@ interface ERSVersionSorted { items: ERSVersion[]; } -const fetchAndCheckStatus = async (url: RequestInfo, init?: RequestInit): Promise => { +const fetchAndCheckStatus = async ( + url: RequestInfo, + init?: RequestInit, +): Promise => { const result = await fetch(url, init); if (result.ok) { // res.status >= 200 && res.status < 300 return result; } - throw new Error(`ERS publish failed with status code: ${result.status} (${result.url})`); + throw new Error( + `ERS publish failed with status code: ${result.status} (${result.url})`, + ); }; -export const ersPlatform = (platform: ForgePlatform, arch: ForgeArch): string => { +export const ersPlatform = ( + platform: ForgePlatform, + arch: ForgeArch, +): string => { switch (platform) { case 'darwin': return arch === 'arm64' ? 'osx_arm64' : 'osx_64'; @@ -58,12 +69,15 @@ export const ersPlatform = (platform: ForgePlatform, arch: ForgeArch): string => export default class PublisherERS extends PublisherBase { name = 'electron-release-server'; - async publish({ makeResults, setStatusLine }: PublisherOptions): Promise { + async publish({ + makeResults, + setStatusLine, + }: PublisherOptions): Promise { const { config } = this; if (!(config.baseUrl && config.username && config.password)) { throw new Error( - 'In order to publish to ERS you must set the "electronReleaseServer.baseUrl", "electronReleaseServer.username" and "electronReleaseServer.password" properties in your Forge config. See the docs for more info' + 'In order to publish to ERS you must set the "electronReleaseServer.baseUrl", "electronReleaseServer.username" and "electronReleaseServer.password" properties in your Forge config. See the docs for more info', ); } @@ -85,18 +99,33 @@ export default class PublisherERS extends PublisherBase { ).json(); const authFetch = (apiPath: string, options?: RequestInit) => - fetchAndCheckStatus(api(apiPath), { ...(options || {}), headers: { ...(options || {}).headers, Authorization: `Bearer ${token}` } }); + fetchAndCheckStatus(api(apiPath), { + ...(options || {}), + headers: { + ...(options || {}).headers, + Authorization: `Bearer ${token}`, + }, + }); const flavor = config.flavor || 'default'; for (const makeResult of makeResults) { const { packageJSON } = makeResult; - const artifacts = makeResult.artifacts.filter((artifactPath) => path.basename(artifactPath).toLowerCase() !== 'releases'); + const artifacts = makeResult.artifacts.filter( + (artifactPath) => + path.basename(artifactPath).toLowerCase() !== 'releases', + ); - const versions: ERSVersionSorted = await (await authFetch('versions/sorted')).json(); + const versions: ERSVersionSorted = await ( + await authFetch('versions/sorted') + ).json(); // Find the version with the same name and flavor - const existingVersion = versions['items'].find((version) => version.name === packageJSON.version && version.flavor.name === flavor); + const existingVersion = versions['items'].find( + (version) => + version.name === packageJSON.version && + version.flavor.name === flavor, + ); let channel = 'stable'; if (config.channel) { @@ -126,14 +155,21 @@ export default class PublisherERS extends PublisherBase { } let uploaded = 0; - const updateStatusLine = () => setStatusLine(`Uploading distributable (${uploaded}/${artifacts.length})`); + const updateStatusLine = () => + setStatusLine( + `Uploading distributable (${uploaded}/${artifacts.length})`, + ); updateStatusLine(); await Promise.all( artifacts.map(async (artifactPath: string) => { const platform = ersPlatform(makeResult.platform, makeResult.arch); if (existingVersion) { - const existingAsset = existingVersion.assets.find((asset) => asset.name === path.basename(artifactPath) && asset.platform === platform); + const existingAsset = existingVersion.assets.find( + (asset) => + asset.name === path.basename(artifactPath) && + asset.platform === platform, + ); if (existingAsset) { d('asset at path:', artifactPath, 'already exists on server'); uploaded += 1; @@ -150,7 +186,11 @@ export default class PublisherERS extends PublisherBase { const fileOptions = { knownLength: fs.statSync(artifactPath).size, }; - artifactForm.append('file', fs.createReadStream(artifactPath), fileOptions); + artifactForm.append( + 'file', + fs.createReadStream(artifactPath), + fileOptions, + ); await authFetch('api/asset', { method: 'POST', @@ -160,7 +200,7 @@ export default class PublisherERS extends PublisherBase { d('upload successful for asset:', artifactPath); uploaded += 1; updateStatusLine(); - }) + }), ); } } diff --git a/packages/publisher/gcs/src/PublisherGCS.ts b/packages/publisher/gcs/src/PublisherGCS.ts index e36e0d9284..39dcdcf048 100644 --- a/packages/publisher/gcs/src/PublisherGCS.ts +++ b/packages/publisher/gcs/src/PublisherGCS.ts @@ -1,4 +1,7 @@ -import { PublisherOptions, PublisherStatic } from '@electron-forge/publisher-static'; +import { + PublisherOptions, + PublisherStatic, +} from '@electron-forge/publisher-static'; import { Storage } from '@google-cloud/storage'; import debug from 'debug'; @@ -20,13 +23,23 @@ export default class PublisherGCS extends PublisherStatic { return key.replace(/@/g, '_').replace(/\//g, '_'); }; - async publish({ makeResults, setStatusLine }: PublisherOptions): Promise { + async publish({ + makeResults, + setStatusLine, + }: PublisherOptions): Promise { const artifacts: GCSArtifact[] = []; - const { storageOptions, bucket: configBucket, folder, ...uploadOptions } = this.config; + const { + storageOptions, + bucket: configBucket, + folder, + ...uploadOptions + } = this.config; if (!configBucket) { - throw new Error('In order to publish to Google Cloud Storage you must set the "bucket" property in your Forge config.'); + throw new Error( + 'In order to publish to Google Cloud Storage you must set the "bucket" property in your Forge config.', + ); } for (const makeResult of makeResults) { @@ -36,7 +49,7 @@ export default class PublisherGCS extends PublisherStatic { keyPrefix: folder || this.GCSKeySafe(makeResult.packageJSON.name), platform: makeResult.platform, arch: makeResult.arch, - })) + })), ); } @@ -47,14 +60,19 @@ export default class PublisherGCS extends PublisherStatic { d('creating Google Cloud Storage client with options:', this.config); let uploaded = 0; - const updateStatusLine = () => setStatusLine(`Uploading distributable (${uploaded}/${artifacts.length})`); + const updateStatusLine = () => + setStatusLine( + `Uploading distributable (${uploaded}/${artifacts.length})`, + ); updateStatusLine(); await Promise.all( artifacts.map(async (artifact) => { d('uploading:', artifact.path); await bucket.upload(artifact.path, { - metadata: this.config.metadataGenerator ? this.config.metadataGenerator(artifact) : {}, + metadata: this.config.metadataGenerator + ? this.config.metadataGenerator(artifact) + : {}, gzip: true, destination: this.keyForArtifact(artifact), ...uploadOptions, @@ -62,7 +80,7 @@ export default class PublisherGCS extends PublisherStatic { uploaded += 1; updateStatusLine(); - }) + }), ); } } diff --git a/packages/publisher/github/spec/util/github.spec.ts b/packages/publisher/github/spec/util/github.spec.ts index 3684cfe4f4..895e71f798 100644 --- a/packages/publisher/github/spec/util/github.spec.ts +++ b/packages/publisher/github/spec/util/github.spec.ts @@ -63,7 +63,9 @@ describe('GitHub', () => { it('should not override the user agent', () => { const gh = new GitHub('1234', true, { userAgent: 'Something' }); gh.getGitHub(); - expect(gitHubSpy).toHaveBeenCalledWith(expect.objectContaining({ userAgent: 'Electron Forge' })); + expect(gitHubSpy).toHaveBeenCalledWith( + expect.objectContaining({ userAgent: 'Electron Forge' }), + ); }); it('should authenticate if a token is present', () => { @@ -73,7 +75,7 @@ describe('GitHub', () => { expect.objectContaining({ auth: 'token', userAgent: 'Electron Forge', - }) + }), ); }); @@ -89,7 +91,9 @@ describe('GitHub', () => { expect(() => { const gh = new GitHub(undefined, true); gh.getGitHub(); - }).toThrow('Please set GITHUB_TOKEN in your environment to access these features'); + }).toThrow( + 'Please set GITHUB_TOKEN in your environment to access these features', + ); }); }); @@ -103,7 +107,9 @@ describe('GitHub', () => { }); it('should replace non-alphanumeric, non-hyphen characters with periods', () => { - expect(GitHub.sanitizeName('path/to/foo%$bar baz.')).toEqual('foo.bar.baz'); + expect(GitHub.sanitizeName('path/to/foo%$bar baz.')).toEqual( + 'foo.bar.baz', + ); }); it('should preserve special symbols', () => { diff --git a/packages/publisher/github/src/PublisherGithub.ts b/packages/publisher/github/src/PublisherGithub.ts index a219924eba..87984f1e54 100644 --- a/packages/publisher/github/src/PublisherGithub.ts +++ b/packages/publisher/github/src/PublisherGithub.ts @@ -1,6 +1,9 @@ import path from 'node:path'; -import { PublisherBase, PublisherOptions } from '@electron-forge/publisher-base'; +import { + PublisherBase, + PublisherOptions, +} from '@electron-forge/publisher-base'; import { ForgeMakeResult } from '@electron-forge/shared-types'; import { RequestError } from '@octokit/request-error'; import { GetResponseDataTypeFromEndpointMethod } from '@octokit/types'; @@ -26,7 +29,10 @@ interface GitHubRelease { export default class PublisherGithub extends PublisherBase { name = 'github'; - async publish({ makeResults, setStatusLine }: PublisherOptions): Promise { + async publish({ + makeResults, + setStatusLine, + }: PublisherOptions): Promise { const { config } = this; const perReleaseArtifacts: { @@ -41,17 +47,28 @@ export default class PublisherGithub extends PublisherBase; - type OctokitReleaseAsset = GetResponseDataTypeFromEndpointMethod; + type OctokitRelease = GetResponseDataTypeFromEndpointMethod< + Octokit['repos']['getRelease'] + >; + type OctokitReleaseAsset = GetResponseDataTypeFromEndpointMethod< + Octokit['repos']['updateReleaseAsset'] + >; for (const releaseVersion of Object.keys(perReleaseArtifacts)) { let release: OctokitRelease | undefined; @@ -66,7 +83,9 @@ export default class PublisherGithub extends PublisherBase testRelease.tag_name === releaseName); + ).data.find( + (testRelease: GitHubRelease) => testRelease.tag_name === releaseName, + ); if (!release) { throw new NoReleaseError(404); } @@ -92,7 +111,9 @@ export default class PublisherGithub extends PublisherBase { - setStatusLine(`Uploading distributable (${uploaded}/${artifacts.length} to ${releaseName})`); + setStatusLine( + `Uploading distributable (${uploaded}/${artifacts.length} to ${releaseName})`, + ); }; updateUploadStatus(); @@ -107,7 +128,10 @@ export default class PublisherGithub extends PublisherBase item.name === sanitizedArtifactName); + const asset = release!.assets.find( + (item: OctokitReleaseAsset) => + item.name === sanitizedArtifactName, + ); if (asset !== undefined) { if (config.force === true) { await github.getGitHub().repos.deleteReleaseAsset({ @@ -120,37 +144,52 @@ export default class PublisherGithub extends PublisherBase { /* Intentionally does nothing */ }; @@ -34,7 +38,9 @@ export default class GitHub { } else if (process.env.GITHUB_TOKEN) { this.token = process.env.GITHUB_TOKEN; } else if (requireAuth) { - throw new Error('Please set GITHUB_TOKEN in your environment to access these features'); + throw new Error( + 'Please set GITHUB_TOKEN in your environment to access these features', + ); } } diff --git a/packages/publisher/nucleus/src/PublisherNucleus.ts b/packages/publisher/nucleus/src/PublisherNucleus.ts index fca6fbce07..9a860ac146 100644 --- a/packages/publisher/nucleus/src/PublisherNucleus.ts +++ b/packages/publisher/nucleus/src/PublisherNucleus.ts @@ -1,7 +1,10 @@ import fs from 'node:fs'; import path from 'node:path'; -import { PublisherBase, PublisherOptions } from '@electron-forge/publisher-base'; +import { + PublisherBase, + PublisherOptions, +} from '@electron-forge/publisher-base'; import debug from 'debug'; import FormData from 'form-data'; import fetch from 'node-fetch'; @@ -13,11 +16,16 @@ const d = debug('electron-forge:publish:nucleus'); export default class PublisherNucleus extends PublisherBase { name = 'nucleus'; - private collapseMakeResults = (makeResults: PublisherOptions['makeResults']) => { + private collapseMakeResults = ( + makeResults: PublisherOptions['makeResults'], + ) => { const newMakeResults: typeof makeResults = []; for (const result of makeResults) { const existingResult = newMakeResults.find( - (nResult) => nResult.arch === result.arch && nResult.platform === result.platform && nResult.packageJSON.version === result.packageJSON.version + (nResult) => + nResult.arch === result.arch && + nResult.platform === result.platform && + nResult.packageJSON.version === result.packageJSON.version, ); if (existingResult) { existingResult.artifacts.push(...result.artifacts); @@ -28,7 +36,10 @@ export default class PublisherNucleus extends PublisherBase { + async publish({ + makeResults, + setStatusLine, + }: PublisherOptions): Promise { const { config } = this; const collapsedResults = this.collapseMakeResults(makeResults); @@ -51,16 +62,21 @@ export default class PublisherNucleus extends PublisherBase { return key.replace(/@/g, '_').replace(/\//g, '_'); }; - async publish({ makeResults, setStatusLine }: PublisherOptions): Promise { + async publish({ + makeResults, + setStatusLine, + }: PublisherOptions): Promise { const artifacts: S3Artifact[] = []; if (!this.config.bucket) { - throw new Error('In order to publish to S3, you must set the "bucket" property in your Forge publisher config. See the docs for more info'); + throw new Error( + 'In order to publish to S3, you must set the "bucket" property in your Forge publisher config. See the docs for more info', + ); } for (const makeResult of makeResults) { artifacts.push( ...makeResult.artifacts.map((artifact) => ({ path: artifact, - keyPrefix: this.config.folder || this.s3KeySafe(makeResult.packageJSON.name), + keyPrefix: + this.config.folder || this.s3KeySafe(makeResult.packageJSON.name), platform: makeResult.platform, arch: makeResult.arch, - })) + })), ); } @@ -53,7 +62,10 @@ export default class PublisherS3 extends PublisherStatic { d('creating s3 client with options:', this.config); let uploaded = 0; - const updateStatusLine = () => setStatusLine(`Uploading distributable (${uploaded}/${artifacts.length})`); + const updateStatusLine = () => + setStatusLine( + `Uploading distributable (${uploaded}/${artifacts.length})`, + ); updateStatusLine(); await Promise.all( @@ -76,14 +88,16 @@ export default class PublisherS3 extends PublisherStatic { uploader.on('httpUploadProgress', (progress: Progress) => { if (progress.total) { const percentage = `${Math.round(((progress.loaded || 0) / progress.total) * 100)}%`; - d(`Upload Progress (${path.basename(artifact.path)}) ${percentage}`); + d( + `Upload Progress (${path.basename(artifact.path)}) ${percentage}`, + ); } }); await uploader.done(); uploaded += 1; updateStatusLine(); - }) + }), ); } diff --git a/packages/publisher/snapcraft/src/PublisherSnapcraft.ts b/packages/publisher/snapcraft/src/PublisherSnapcraft.ts index 20146eda5e..8677acd7fb 100644 --- a/packages/publisher/snapcraft/src/PublisherSnapcraft.ts +++ b/packages/publisher/snapcraft/src/PublisherSnapcraft.ts @@ -1,6 +1,9 @@ import path from 'node:path'; -import { PublisherBase, PublisherOptions } from '@electron-forge/publisher-base'; +import { + PublisherBase, + PublisherOptions, +} from '@electron-forge/publisher-base'; import fs from 'fs-extra'; import { PublisherSnapcraftConfig } from './Config'; @@ -12,16 +15,24 @@ const Snapcraft = require('electron-installer-snap/src/snapcraft'); export default class PublisherSnapcraft extends PublisherBase { name = 'snapcraft'; - async publish({ dir, makeResults, setStatusLine }: PublisherOptions): Promise { + async publish({ + dir, + makeResults, + setStatusLine, + }: PublisherOptions): Promise { const artifacts = makeResults.reduce((flat, makeResult) => { flat.push(...makeResult.artifacts); return flat; }, [] as string[]); - const snapArtifacts = artifacts.filter((artifact) => artifact.endsWith('.snap')); + const snapArtifacts = artifacts.filter((artifact) => + artifact.endsWith('.snap'), + ); if (snapArtifacts.length === 0) { - throw new Error('No snap files to upload. Please ensure that "snap" is listed in the "make_targets" in Forge config.'); + throw new Error( + 'No snap files to upload. Please ensure that "snap" is listed in the "make_targets" in Forge config.', + ); } const snapcraftCfgPath = path.join(dir, '.snapcraft', 'snapcraft.cfg'); @@ -29,7 +40,7 @@ export default class PublisherSnapcraft extends PublisherBase { return { - spawn: vi.fn().mockImplementation(async (cmd: string, args: string[], _options: { cwd: string }): Promise => { - if (args.includes('user.name')) { - if (returnGitUsername) { - return Promise.resolve('Foo Bar\n'); - } - - throw new Error('Not returning username'); - } else if (args.includes('user.email')) { - if (returnGitEmail) { - return Promise.resolve('foo@example.com\n'); - } - - throw new Error('Not returning email'); - } - - throw new Error(`Unknown command: ${cmd} ${args.join(' ')}`); - }), + spawn: vi + .fn() + .mockImplementation( + async ( + cmd: string, + args: string[], + _options: { cwd: string }, + ): Promise => { + if (args.includes('user.name')) { + if (returnGitUsername) { + return Promise.resolve('Foo Bar\n'); + } + + throw new Error('Not returning username'); + } else if (args.includes('user.email')) { + if (returnGitEmail) { + return Promise.resolve('foo@example.com\n'); + } + + throw new Error('Not returning email'); + } + + throw new Error(`Unknown command: ${cmd} ${args.join(' ')}`); + }, + ), }; }); @@ -39,7 +47,10 @@ describe('determineAuthor', () => { it('returns git config if both name and email are set', async () => { returnGitUsername = true; returnGitEmail = true; - await expect(determineAuthor('foo')).resolves.toEqual({ name: 'Foo Bar', email: 'foo@example.com' }); + await expect(determineAuthor('foo')).resolves.toEqual({ + name: 'Foo Bar', + email: 'foo@example.com', + }); }); it('returns username if only name is set', async () => { diff --git a/packages/template/base/src/BaseTemplate.ts b/packages/template/base/src/BaseTemplate.ts index 2517155def..ace88f3eb5 100644 --- a/packages/template/base/src/BaseTemplate.ts +++ b/packages/template/base/src/BaseTemplate.ts @@ -1,7 +1,11 @@ import path from 'node:path'; import { resolvePackageManager } from '@electron-forge/core-utils'; -import { ForgeListrTaskDefinition, ForgeTemplate, InitTemplateOptions } from '@electron-forge/shared-types'; +import { + ForgeListrTaskDefinition, + ForgeTemplate, + InitTemplateOptions, +} from '@electron-forge/shared-types'; import debug from 'debug'; import fs from 'fs-extra'; @@ -52,7 +56,10 @@ export class BaseTemplate implements ForgeTemplate { return []; } - public async initializeTemplate(directory: string, { copyCIFiles }: InitTemplateOptions): Promise { + public async initializeTemplate( + directory: string, + { copyCIFiles }: InitTemplateOptions, + ): Promise { return [ { title: 'Copying starter files', @@ -67,16 +74,29 @@ export class BaseTemplate implements ForgeTemplate { } if (copyCIFiles) { - d(`Copying CI files is currently not supported - this will be updated in a later version of Forge`); + d( + `Copying CI files is currently not supported - this will be updated in a later version of Forge`, + ); } - const srcFiles = ['index.css', 'index.js', 'index.html', 'preload.js']; + const srcFiles = [ + 'index.css', + 'index.js', + 'index.html', + 'preload.js', + ]; for (const file of rootFiles) { - await this.copy(path.resolve(tmplDir, file), path.resolve(directory, file.replace(/^_/, '.'))); + await this.copy( + path.resolve(tmplDir, file), + path.resolve(directory, file.replace(/^_/, '.')), + ); } for (const file of srcFiles) { - await this.copy(path.resolve(tmplDir, file), path.resolve(directory, 'src', file)); + await this.copy( + path.resolve(tmplDir, file), + path.resolve(directory, 'src', file), + ); } }, }, @@ -95,12 +115,19 @@ export class BaseTemplate implements ForgeTemplate { } async copyTemplateFile(destDir: string, basename: string): Promise { - await this.copy(path.join(this.templateDir, basename), path.resolve(destDir, basename)); + await this.copy( + path.join(this.templateDir, basename), + path.resolve(destDir, basename), + ); } async initializePackageJSON(directory: string): Promise { - const packageJSON = await fs.readJson(path.resolve(__dirname, '../tmpl/package.json')); - packageJSON.productName = packageJSON.name = path.basename(directory).toLowerCase(); + const packageJSON = await fs.readJson( + path.resolve(__dirname, '../tmpl/package.json'), + ); + packageJSON.productName = packageJSON.name = path + .basename(directory) + .toLowerCase(); packageJSON.author = await determineAuthor(directory); const pm = await resolvePackageManager(); @@ -115,11 +142,20 @@ export class BaseTemplate implements ForgeTemplate { packageJSON.scripts.lint = 'echo "No linting configured"'; d('writing package.json to:', directory); - await fs.writeJson(path.resolve(directory, 'package.json'), packageJSON, { spaces: 2 }); + await fs.writeJson(path.resolve(directory, 'package.json'), packageJSON, { + spaces: 2, + }); } - async updateFileByLine(inputPath: string, lineHandler: (line: string) => string, outputPath?: string | undefined): Promise { - const fileContents = (await fs.readFile(inputPath, 'utf8')).split('\n').map(lineHandler).join('\n'); + async updateFileByLine( + inputPath: string, + lineHandler: (line: string) => string, + outputPath?: string | undefined, + ): Promise { + const fileContents = (await fs.readFile(inputPath, 'utf8')) + .split('\n') + .map(lineHandler) + .join('\n'); await fs.writeFile(outputPath || inputPath, fileContents); if (outputPath !== undefined) { await fs.remove(inputPath); diff --git a/packages/template/base/src/determine-author.ts b/packages/template/base/src/determine-author.ts index afeb62e154..4359e19aab 100644 --- a/packages/template/base/src/determine-author.ts +++ b/packages/template/base/src/determine-author.ts @@ -21,4 +21,5 @@ const getAuthorFromGitConfig = async (dir: string): Promise => { } }; -export default async (dir: string): Promise => (await getAuthorFromGitConfig(dir)) || username(); +export default async (dir: string): Promise => + (await getAuthorFromGitConfig(dir)) || username(); diff --git a/packages/template/base/tmpl/index.css b/packages/template/base/tmpl/index.css index 8856f90b3f..3a16b1c779 100644 --- a/packages/template/base/tmpl/index.css +++ b/packages/template/base/tmpl/index.css @@ -1,6 +1,7 @@ body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, - Arial, sans-serif; + font-family: + -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, + sans-serif; margin: auto; max-width: 38rem; padding: 2rem; diff --git a/packages/template/base/tmpl/index.html b/packages/template/base/tmpl/index.html index 660955fe96..e656603e74 100644 --- a/packages/template/base/tmpl/index.html +++ b/packages/template/base/tmpl/index.html @@ -1,4 +1,4 @@ - + diff --git a/packages/template/vite-typescript/spec/ViteTypeScriptTemplate.slow.spec.ts b/packages/template/vite-typescript/spec/ViteTypeScriptTemplate.slow.spec.ts index 05eb9ea878..ff514c9ae9 100644 --- a/packages/template/vite-typescript/spec/ViteTypeScriptTemplate.slow.spec.ts +++ b/packages/template/vite-typescript/spec/ViteTypeScriptTemplate.slow.spec.ts @@ -2,7 +2,10 @@ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; -import { PACKAGE_MANAGERS, spawnPackageManager } from '@electron-forge/core-utils'; +import { + PACKAGE_MANAGERS, + spawnPackageManager, +} from '@electron-forge/core-utils'; import testUtils from '@electron-forge/test-utils'; import glob from 'fast-glob'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; @@ -15,7 +18,10 @@ describe('ViteTypeScriptTemplate', () => { let dir: string; beforeAll(async () => { - await spawnPackageManager(PACKAGE_MANAGERS['yarn'], ['run', 'link:prepare']); + await spawnPackageManager(PACKAGE_MANAGERS['yarn'], [ + 'run', + 'link:prepare', + ]); dir = await testUtils.ensureTestDirIsNonexistent(); }); @@ -75,12 +81,17 @@ describe('ViteTypeScriptTemplate', () => { process.chdir(dir); // We need the version of vite to match exactly during development due to a quirk in // typescript type-resolution. In prod no one has to worry about things like this - const pj = JSON.parse(await fs.promises.readFile(path.resolve(dir, 'package.json'), 'utf-8')); + const pj = JSON.parse( + await fs.promises.readFile(path.resolve(dir, 'package.json'), 'utf-8'), + ); pj.resolutions = { // eslint-disable-next-line @typescript-eslint/no-require-imports vite: `${require('../../../../node_modules/vite/package.json').version}`, }; - await fs.promises.writeFile(path.resolve(dir, 'package.json'), JSON.stringify(pj)); + await fs.promises.writeFile( + path.resolve(dir, 'package.json'), + JSON.stringify(pj), + ); await spawnPackageManager(PACKAGE_MANAGERS['yarn'], ['install'], { cwd: dir, }); diff --git a/packages/template/vite-typescript/src/ViteTypeScriptTemplate.ts b/packages/template/vite-typescript/src/ViteTypeScriptTemplate.ts index 3c23f07562..055f968a0f 100644 --- a/packages/template/vite-typescript/src/ViteTypeScriptTemplate.ts +++ b/packages/template/vite-typescript/src/ViteTypeScriptTemplate.ts @@ -1,13 +1,19 @@ import path from 'node:path'; -import { ForgeListrTaskDefinition, InitTemplateOptions } from '@electron-forge/shared-types'; +import { + ForgeListrTaskDefinition, + InitTemplateOptions, +} from '@electron-forge/shared-types'; import { BaseTemplate } from '@electron-forge/template-base'; import fs from 'fs-extra'; class ViteTypeScriptTemplate extends BaseTemplate { public templateDir = path.resolve(__dirname, '..', 'tmpl'); - public async initializeTemplate(directory: string, options: InitTemplateOptions): Promise { + public async initializeTemplate( + directory: string, + options: InitTemplateOptions, + ): Promise { const superTasks = await super.initializeTemplate(directory, options); return [ ...superTasks, @@ -22,7 +28,8 @@ class ViteTypeScriptTemplate extends BaseTemplate { { title: 'Preparing TypeScript files and configuration', task: async () => { - const filePath = (fileName: string) => path.join(directory, 'src', fileName); + const filePath = (fileName: string) => + path.join(directory, 'src', fileName); // Copy Vite files await this.copyTemplateFile(directory, 'vite.main.config.ts'); @@ -39,20 +46,34 @@ class ViteTypeScriptTemplate extends BaseTemplate { await fs.remove(filePath('index.js')); await this.copyTemplateFile(path.join(directory, 'src'), 'main.ts'); - await this.copyTemplateFile(path.join(directory, 'src'), 'renderer.ts'); + await this.copyTemplateFile( + path.join(directory, 'src'), + 'renderer.ts', + ); // Remove preload.js and replace with preload.ts await fs.remove(filePath('preload.js')); - await this.copyTemplateFile(path.join(directory, 'src'), 'preload.ts'); + await this.copyTemplateFile( + path.join(directory, 'src'), + 'preload.ts', + ); // TODO: Compatible with any path entry. // Vite uses index.html under the root path as the entry point. - await fs.move(filePath('index.html'), path.join(directory, 'index.html'), { overwrite: options.force }); - await this.updateFileByLine(path.join(directory, 'index.html'), (line) => { - if (line.includes('link rel="stylesheet"')) return ''; - if (line.includes('')) return ' \n '; - return line; - }); + await fs.move( + filePath('index.html'), + path.join(directory, 'index.html'), + { overwrite: options.force }, + ); + await this.updateFileByLine( + path.join(directory, 'index.html'), + (line) => { + if (line.includes('link rel="stylesheet"')) return ''; + if (line.includes('')) + return ' \n '; + return line; + }, + ); // update package.json const packageJSONPath = path.resolve(directory, 'package.json'); diff --git a/packages/template/vite-typescript/tmpl/forge.config.ts b/packages/template/vite-typescript/tmpl/forge.config.ts index 42181d2f2d..c59a670121 100644 --- a/packages/template/vite-typescript/tmpl/forge.config.ts +++ b/packages/template/vite-typescript/tmpl/forge.config.ts @@ -12,7 +12,12 @@ const config: ForgeConfig = { asar: true, }, rebuildConfig: {}, - makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})], + makers: [ + new MakerSquirrel({}), + new MakerZIP({}, ['darwin']), + new MakerRpm({}), + new MakerDeb({}), + ], plugins: [ new VitePlugin({ // `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc. diff --git a/packages/template/vite-typescript/tmpl/main.ts b/packages/template/vite-typescript/tmpl/main.ts index 352f594c0b..f4f001e6e5 100644 --- a/packages/template/vite-typescript/tmpl/main.ts +++ b/packages/template/vite-typescript/tmpl/main.ts @@ -21,7 +21,9 @@ const createWindow = () => { if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL); } else { - mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)); + mainWindow.loadFile( + path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`), + ); } // Open the DevTools. diff --git a/packages/template/vite-typescript/tmpl/renderer.ts b/packages/template/vite-typescript/tmpl/renderer.ts index 0a1ff1966a..fd1662ff48 100644 --- a/packages/template/vite-typescript/tmpl/renderer.ts +++ b/packages/template/vite-typescript/tmpl/renderer.ts @@ -28,4 +28,6 @@ import './index.css'; -console.log('👋 This message is being logged by "renderer.ts", included via Vite'); +console.log( + '👋 This message is being logged by "renderer.ts", included via Vite', +); diff --git a/packages/template/vite/spec/ViteTemplate.spec.ts b/packages/template/vite/spec/ViteTemplate.spec.ts index 0417137ef3..3ad02ab0e1 100644 --- a/packages/template/vite/spec/ViteTemplate.spec.ts +++ b/packages/template/vite/spec/ViteTemplate.spec.ts @@ -23,7 +23,8 @@ describe('ViteTemplate', () => { const runner = new Listr(tasks, { concurrent: false, exitOnError: false, - fallbackRendererCondition: Boolean(process.env.DEBUG) || Boolean(process.env.CI), + fallbackRendererCondition: + Boolean(process.env.DEBUG) || Boolean(process.env.CI), }); await runner.run(); expect(runner.errors).toHaveLength(0); @@ -48,16 +49,24 @@ describe('ViteTemplate', () => { it('should move and rewrite the main process file', async () => { expect(fs.existsSync(path.join(dir, 'src', 'index.js'))).toBe(false); expect(fs.existsSync(path.join(dir, 'src', 'main.js'))).toBe(true); - const mainFile = (await fs.promises.readFile(path.join(dir, 'src', 'main.js'))).toString(); + const mainFile = ( + await fs.promises.readFile(path.join(dir, 'src', 'main.js')) + ).toString(); expect(mainFile).toMatch(/MAIN_WINDOW_VITE_DEV_SERVER_URL/); - expect(mainFile).toMatch(/\.\.\/renderer\/\${MAIN_WINDOW_VITE_NAME}\/index\.html/); + expect(mainFile).toMatch( + /\.\.\/renderer\/\${MAIN_WINDOW_VITE_NAME}\/index\.html/, + ); }); it('should remove the stylesheet link from the HTML file', async () => { - expect((await fs.promises.readFile(path.join(dir, 'index.html'))).toString()).not.toMatch(/link rel="stylesheet"/); + expect( + (await fs.promises.readFile(path.join(dir, 'index.html'))).toString(), + ).not.toMatch(/link rel="stylesheet"/); }); it('should inject script into the HTML file', async () => { - expect((await fs.promises.readFile(path.join(dir, 'index.html'))).toString()).toMatch(/src="\/src\/renderer\.js"/); + expect( + (await fs.promises.readFile(path.join(dir, 'index.html'))).toString(), + ).toMatch(/src="\/src\/renderer\.js"/); }); }); diff --git a/packages/template/vite/src/ViteTemplate.ts b/packages/template/vite/src/ViteTemplate.ts index a2aba76e5f..62de050271 100644 --- a/packages/template/vite/src/ViteTemplate.ts +++ b/packages/template/vite/src/ViteTemplate.ts @@ -1,13 +1,19 @@ import path from 'node:path'; -import { ForgeListrTaskDefinition, InitTemplateOptions } from '@electron-forge/shared-types'; +import { + ForgeListrTaskDefinition, + InitTemplateOptions, +} from '@electron-forge/shared-types'; import { BaseTemplate } from '@electron-forge/template-base'; import fs from 'fs-extra'; class ViteTemplate extends BaseTemplate { public templateDir = path.resolve(__dirname, '..', 'tmpl'); - public async initializeTemplate(directory: string, options: InitTemplateOptions): Promise { + public async initializeTemplate( + directory: string, + options: InitTemplateOptions, + ): Promise { const superTasks = await super.initializeTemplate(directory, options); return [ ...superTasks, @@ -23,8 +29,14 @@ class ViteTemplate extends BaseTemplate { await this.copyTemplateFile(directory, 'vite.main.config.mjs'); await this.copyTemplateFile(directory, 'vite.preload.config.mjs'); await this.copyTemplateFile(directory, 'vite.renderer.config.mjs'); - await this.copyTemplateFile(path.join(directory, 'src'), 'renderer.js'); - await this.copyTemplateFile(path.join(directory, 'src'), 'preload.js'); + await this.copyTemplateFile( + path.join(directory, 'src'), + 'renderer.js', + ); + await this.copyTemplateFile( + path.join(directory, 'src'), + 'preload.js', + ); await this.copyTemplateFile(path.join(directory, 'src'), 'index.js'); await this.updateFileByLine( @@ -38,17 +50,25 @@ class ViteTemplate extends BaseTemplate { }`; return line; }, - path.resolve(directory, 'src', 'main.js') + path.resolve(directory, 'src', 'main.js'), ); // TODO: Compatible with any path entry. // Vite uses index.html under the root path as the entry point. - fs.moveSync(path.join(directory, 'src', 'index.html'), path.join(directory, 'index.html'), { overwrite: options.force }); - await this.updateFileByLine(path.join(directory, 'index.html'), (line) => { - if (line.includes('link rel="stylesheet"')) return ''; - if (line.includes('')) return ' \n '; - return line; - }); + fs.moveSync( + path.join(directory, 'src', 'index.html'), + path.join(directory, 'index.html'), + { overwrite: options.force }, + ); + await this.updateFileByLine( + path.join(directory, 'index.html'), + (line) => { + if (line.includes('link rel="stylesheet"')) return ''; + if (line.includes('')) + return ' \n '; + return line; + }, + ); // update package.json entry point const pjPath = path.resolve(directory, 'package.json'); diff --git a/packages/template/vite/tmpl/renderer.js b/packages/template/vite/tmpl/renderer.js index 3c3a498fdb..757e88f5f0 100644 --- a/packages/template/vite/tmpl/renderer.js +++ b/packages/template/vite/tmpl/renderer.js @@ -28,4 +28,6 @@ import './index.css'; -console.log('👋 This message is being logged by "renderer.js", included via Vite'); +console.log( + '👋 This message is being logged by "renderer.js", included via Vite', +); diff --git a/packages/template/webpack-typescript/spec/WebpackTypeScript.slow.spec.ts b/packages/template/webpack-typescript/spec/WebpackTypeScript.slow.spec.ts index f814c796ba..b711b85de9 100644 --- a/packages/template/webpack-typescript/spec/WebpackTypeScript.slow.spec.ts +++ b/packages/template/webpack-typescript/spec/WebpackTypeScript.slow.spec.ts @@ -1,7 +1,10 @@ import fs from 'node:fs'; import path from 'node:path'; -import { PACKAGE_MANAGERS, spawnPackageManager } from '@electron-forge/core-utils'; +import { + PACKAGE_MANAGERS, + spawnPackageManager, +} from '@electron-forge/core-utils'; import testUtils from '@electron-forge/test-utils'; import glob from 'fast-glob'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; @@ -14,7 +17,10 @@ describe('WebpackTypeScriptTemplate', () => { let dir: string; beforeAll(async () => { - await spawnPackageManager(PACKAGE_MANAGERS['yarn'], ['run', 'link:prepare']); + await spawnPackageManager(PACKAGE_MANAGERS['yarn'], [ + 'run', + 'link:prepare', + ]); dir = await testUtils.ensureTestDirIsNonexistent(); }); @@ -65,12 +71,17 @@ describe('WebpackTypeScriptTemplate', () => { process.chdir(dir); // We need the version of webpack to match exactly during development due to a quirk in // typescript type-resolution. In prod no one has to worry about things like this - const pj = JSON.parse(await fs.promises.readFile(path.resolve(dir, 'package.json'), 'utf-8')); + const pj = JSON.parse( + await fs.promises.readFile(path.resolve(dir, 'package.json'), 'utf-8'), + ); pj.resolutions = { // eslint-disable-next-line @typescript-eslint/no-require-imports webpack: `${require('../../../../node_modules/webpack/package.json').version}`, }; - await fs.promises.writeFile(path.resolve(dir, 'package.json'), JSON.stringify(pj)); + await fs.promises.writeFile( + path.resolve(dir, 'package.json'), + JSON.stringify(pj), + ); await spawnPackageManager(PACKAGE_MANAGERS['yarn'], ['install'], { cwd: dir, }); diff --git a/packages/template/webpack-typescript/src/WebpackTypeScriptTemplate.ts b/packages/template/webpack-typescript/src/WebpackTypeScriptTemplate.ts index 7d2e61881c..433d3f1585 100644 --- a/packages/template/webpack-typescript/src/WebpackTypeScriptTemplate.ts +++ b/packages/template/webpack-typescript/src/WebpackTypeScriptTemplate.ts @@ -1,13 +1,19 @@ import path from 'node:path'; -import { ForgeListrTaskDefinition, InitTemplateOptions } from '@electron-forge/shared-types'; +import { + ForgeListrTaskDefinition, + InitTemplateOptions, +} from '@electron-forge/shared-types'; import { BaseTemplate } from '@electron-forge/template-base'; import fs from 'fs-extra'; class WebpackTypeScriptTemplate extends BaseTemplate { public templateDir = path.resolve(__dirname, '..', 'tmpl'); - async initializeTemplate(directory: string, options: InitTemplateOptions): Promise { + async initializeTemplate( + directory: string, + options: InitTemplateOptions, + ): Promise { const superTasks = await super.initializeTemplate(directory, options); return [ ...superTasks, @@ -21,7 +27,8 @@ class WebpackTypeScriptTemplate extends BaseTemplate { { title: 'Preparing TypeScript files and configuration', task: async () => { - const filePath = (fileName: string) => path.join(directory, 'src', fileName); + const filePath = (fileName: string) => + path.join(directory, 'src', fileName); // Copy Webpack files await this.copyTemplateFile(directory, 'webpack.main.config.ts'); @@ -29,10 +36,13 @@ class WebpackTypeScriptTemplate extends BaseTemplate { await this.copyTemplateFile(directory, 'webpack.rules.ts'); await this.copyTemplateFile(directory, 'webpack.plugins.ts'); - await this.updateFileByLine(path.resolve(directory, 'src', 'index.html'), (line) => { - if (line.includes('link rel="stylesheet"')) return ''; - return line; - }); + await this.updateFileByLine( + path.resolve(directory, 'src', 'index.html'), + (line) => { + if (line.includes('link rel="stylesheet"')) return ''; + return line; + }, + ); // Copy tsconfig with a small set of presets await this.copyTemplateFile(directory, 'tsconfig.json'); @@ -44,11 +54,17 @@ class WebpackTypeScriptTemplate extends BaseTemplate { await fs.remove(filePath('index.js')); await this.copyTemplateFile(path.join(directory, 'src'), 'index.ts'); - await this.copyTemplateFile(path.join(directory, 'src'), 'renderer.ts'); + await this.copyTemplateFile( + path.join(directory, 'src'), + 'renderer.ts', + ); // Remove preload.js and replace with preload.ts await fs.remove(filePath('preload.js')); - await this.copyTemplateFile(path.join(directory, 'src'), 'preload.ts'); + await this.copyTemplateFile( + path.join(directory, 'src'), + 'preload.ts', + ); // update package.json const packageJSONPath = path.resolve(directory, 'package.json'); diff --git a/packages/template/webpack-typescript/tmpl/forge.config.ts b/packages/template/webpack-typescript/tmpl/forge.config.ts index 1dcc889870..46e40caaca 100644 --- a/packages/template/webpack-typescript/tmpl/forge.config.ts +++ b/packages/template/webpack-typescript/tmpl/forge.config.ts @@ -16,7 +16,12 @@ const config: ForgeConfig = { asar: true, }, rebuildConfig: {}, - makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})], + makers: [ + new MakerSquirrel({}), + new MakerZIP({}, ['darwin']), + new MakerRpm({}), + new MakerDeb({}), + ], plugins: [ new AutoUnpackNativesPlugin({}), new WebpackPlugin({ diff --git a/packages/template/webpack-typescript/tmpl/renderer.ts b/packages/template/webpack-typescript/tmpl/renderer.ts index a91994ff79..c518b8092f 100644 --- a/packages/template/webpack-typescript/tmpl/renderer.ts +++ b/packages/template/webpack-typescript/tmpl/renderer.ts @@ -28,4 +28,6 @@ import './index.css'; -console.log('👋 This message is being logged by "renderer.js", included via webpack'); +console.log( + '👋 This message is being logged by "renderer.js", included via webpack', +); diff --git a/packages/template/webpack/spec/WebpackTemplate.spec.ts b/packages/template/webpack/spec/WebpackTemplate.spec.ts index a977683208..259e2933a6 100644 --- a/packages/template/webpack/spec/WebpackTemplate.spec.ts +++ b/packages/template/webpack/spec/WebpackTemplate.spec.ts @@ -23,7 +23,8 @@ describe('WebpackTemplate', () => { const runner = new Listr(tasks, { concurrent: false, exitOnError: false, - fallbackRendererCondition: Boolean(process.env.DEBUG) || Boolean(process.env.CI), + fallbackRendererCondition: + Boolean(process.env.DEBUG) || Boolean(process.env.CI), }); await runner.run(); expect(runner.errors).toHaveLength(0); @@ -46,12 +47,18 @@ describe('WebpackTemplate', () => { it('should move and rewrite the main process file', async () => { expect(fs.existsSync(path.join(dir, 'src', 'index.js'))).toBe(false); expect(fs.existsSync(path.join(dir, 'src', 'main.js'))).toBe(true); - const mainFile = (await fs.promises.readFile(path.join(dir, 'src', 'main.js'))).toString(); + const mainFile = ( + await fs.promises.readFile(path.join(dir, 'src', 'main.js')) + ).toString(); expect(mainFile).toMatch(/MAIN_WINDOW_WEBPACK_ENTRY/); expect(mainFile).toMatch(/MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY/); }); it('should remove the stylesheet link from the HTML file', async () => { - expect((await fs.promises.readFile(path.join(dir, 'src', 'index.html'))).toString()).not.toMatch(/link rel="stylesheet"/); + expect( + ( + await fs.promises.readFile(path.join(dir, 'src', 'index.html')) + ).toString(), + ).not.toMatch(/link rel="stylesheet"/); }); }); diff --git a/packages/template/webpack/src/WebpackTemplate.ts b/packages/template/webpack/src/WebpackTemplate.ts index d9ac8d2aee..7d90045bf2 100644 --- a/packages/template/webpack/src/WebpackTemplate.ts +++ b/packages/template/webpack/src/WebpackTemplate.ts @@ -1,13 +1,19 @@ import path from 'node:path'; -import { ForgeListrTaskDefinition, InitTemplateOptions } from '@electron-forge/shared-types'; +import { + ForgeListrTaskDefinition, + InitTemplateOptions, +} from '@electron-forge/shared-types'; import { BaseTemplate } from '@electron-forge/template-base'; import fs from 'fs-extra'; class WebpackTemplate extends BaseTemplate { public templateDir = path.resolve(__dirname, '..', 'tmpl'); - public async initializeTemplate(directory: string, options: InitTemplateOptions): Promise { + public async initializeTemplate( + directory: string, + options: InitTemplateOptions, + ): Promise { const superTasks = await super.initializeTemplate(directory, options); return [ ...superTasks, @@ -23,23 +29,34 @@ class WebpackTemplate extends BaseTemplate { await this.copyTemplateFile(directory, 'webpack.main.config.js'); await this.copyTemplateFile(directory, 'webpack.renderer.config.js'); await this.copyTemplateFile(directory, 'webpack.rules.js'); - await this.copyTemplateFile(path.join(directory, 'src'), 'renderer.js'); - await this.copyTemplateFile(path.join(directory, 'src'), 'preload.js'); + await this.copyTemplateFile( + path.join(directory, 'src'), + 'renderer.js', + ); + await this.copyTemplateFile( + path.join(directory, 'src'), + 'preload.js', + ); await this.updateFileByLine( path.resolve(directory, 'src', 'index.js'), (line) => { - if (line.includes('mainWindow.loadFile')) return ' mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);'; - if (line.includes('preload: ')) return ' preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,'; + if (line.includes('mainWindow.loadFile')) + return ' mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);'; + if (line.includes('preload: ')) + return ' preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,'; return line; }, - path.resolve(directory, 'src', 'main.js') + path.resolve(directory, 'src', 'main.js'), ); - await this.updateFileByLine(path.resolve(directory, 'src', 'index.html'), (line) => { - if (line.includes('link rel="stylesheet"')) return ''; - return line; - }); + await this.updateFileByLine( + path.resolve(directory, 'src', 'index.html'), + (line) => { + if (line.includes('link rel="stylesheet"')) return ''; + return line; + }, + ); // update package.json entry point const pjPath = path.resolve(directory, 'package.json'); diff --git a/packages/template/webpack/tmpl/renderer.js b/packages/template/webpack/tmpl/renderer.js index 1d5082aae3..80985d62fa 100644 --- a/packages/template/webpack/tmpl/renderer.js +++ b/packages/template/webpack/tmpl/renderer.js @@ -28,4 +28,6 @@ import './index.css'; -console.log('👋 This message is being logged by "renderer.js", included via webpack'); +console.log( + '👋 This message is being logged by "renderer.js", included via webpack', +); diff --git a/packages/utils/core-utils/spec/electron-version.spec.ts b/packages/utils/core-utils/spec/electron-version.spec.ts index f60fcb99d4..0d3edc79e6 100644 --- a/packages/utils/core-utils/spec/electron-version.spec.ts +++ b/packages/utils/core-utils/spec/electron-version.spec.ts @@ -4,15 +4,26 @@ import path from 'node:path'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import { devDeps, exactDevDeps } from '../../../api/core/src/api/init-scripts/init-npm'; -import { getElectronModulePath, getElectronVersion, updateElectronDependency } from '../src/electron-version'; +import { + devDeps, + exactDevDeps, +} from '../../../api/core/src/api/init-scripts/init-npm'; +import { + getElectronModulePath, + getElectronVersion, + updateElectronDependency, +} from '../src/electron-version'; const fixturePath = path.resolve(__dirname, 'fixture'); describe('updateElectronDependency', () => { it('adds an Electron dep if one does not already exist', () => { const packageJSON = { dependencies: {}, devDependencies: {} }; - const [dev, exact] = updateElectronDependency(packageJSON, devDeps, exactDevDeps); + const [dev, exact] = updateElectronDependency( + packageJSON, + devDeps, + exactDevDeps, + ); expect(dev).toEqual(devDeps); expect(exact).toEqual(exactDevDeps); }); @@ -22,7 +33,11 @@ describe('updateElectronDependency', () => { dependencies: {}, devDependencies: { electron: '0.37.0' }, }; - const [dev, exact] = updateElectronDependency(packageJSON, devDeps, exactDevDeps); + const [dev, exact] = updateElectronDependency( + packageJSON, + devDeps, + exactDevDeps, + ); expect(dev).toEqual(devDeps); expect(exact).toEqual([]); }); @@ -32,7 +47,11 @@ describe('updateElectronDependency', () => { dependencies: { electron: '0.37.0' }, devDependencies: {}, }; - const [dev, exact] = updateElectronDependency(packageJSON, devDeps, exactDevDeps); + const [dev, exact] = updateElectronDependency( + packageJSON, + devDeps, + exactDevDeps, + ); expect(dev.includes('electron@0.37.0')).toEqual(true); expect(exact).toEqual([]); }); @@ -40,27 +59,41 @@ describe('updateElectronDependency', () => { describe('getElectronVersion', () => { it('fails without devDependencies', async () => { - await expect(getElectronVersion('', {})).rejects.toThrow('does not have any devDependencies'); + await expect(getElectronVersion('', {})).rejects.toThrow( + 'does not have any devDependencies', + ); }); it('fails without electron devDependencies', async () => - expect(getElectronVersion('', { devDependencies: {} })).rejects.toThrow('Electron packages in devDependencies')); + expect(getElectronVersion('', { devDependencies: {} })).rejects.toThrow( + 'Electron packages in devDependencies', + )); it('fails with a non-exact version and no electron installed', async () => { const fixtureDir = path.resolve(fixturePath, 'dummy_app'); - await expect(getElectronVersion(fixtureDir, { devDependencies: { electron: '^4.0.2' } })).rejects.toThrow('Cannot find the package'); + await expect( + getElectronVersion(fixtureDir, { + devDependencies: { electron: '^4.0.2' }, + }), + ).rejects.toThrow('Cannot find the package'); }); it('works with a non-exact version with electron installed', async () => { const fixtureDir = path.resolve(fixturePath, 'non-exact'); - await expect(getElectronVersion(fixtureDir, { devDependencies: { electron: '^4.0.2' } })).resolves.toEqual('4.0.9'); + await expect( + getElectronVersion(fixtureDir, { + devDependencies: { electron: '^4.0.2' }, + }), + ).resolves.toEqual('4.0.9'); }); it('works with electron-nightly', async () => { const packageJSON = { devDependencies: { 'electron-nightly': '5.0.0-nightly.20190107' }, }; - await expect(getElectronVersion('', packageJSON)).resolves.toEqual('5.0.0-nightly.20190107'); + await expect(getElectronVersion('', packageJSON)).resolves.toEqual( + '5.0.0-nightly.20190107', + ); }); it('works with electron', async () => { @@ -76,12 +109,19 @@ describe('getElectronVersion', () => { }); it('works with a non-exact version', async () => { - const fixtureDir = path.resolve(fixturePath, 'yarn-workspace', 'packages', 'subpackage'); + const fixtureDir = path.resolve( + fixturePath, + 'yarn-workspace', + 'packages', + 'subpackage', + ); const packageJSON = { devDependencies: { electron: '^4.0.4' }, }; - await expect(getElectronVersion(fixtureDir, packageJSON)).resolves.toEqual('4.0.9'); + await expect( + getElectronVersion(fixtureDir, packageJSON), + ).resolves.toEqual('4.0.9'); }); afterAll(() => { @@ -92,17 +132,23 @@ describe('getElectronVersion', () => { describe('getElectronModulePath', () => { it('fails without devDependencies', async () => { - await expect(getElectronModulePath('', {})).rejects.toThrow('does not have any devDependencies'); + await expect(getElectronModulePath('', {})).rejects.toThrow( + 'does not have any devDependencies', + ); }); it('fails without electron devDependencies', async () => { - await expect(getElectronModulePath('', { devDependencies: {} })).rejects.toThrow('Electron packages in devDependencies'); + await expect( + getElectronModulePath('', { devDependencies: {} }), + ).rejects.toThrow('Electron packages in devDependencies'); }); describe('with no electron installed', () => { let tempDir: string; beforeAll(async () => { - tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'electron-forge-test-')); + tempDir = await fs.mkdtemp( + path.join(os.tmpdir(), 'electron-forge-test-'), + ); }); afterAll(async () => { @@ -112,20 +158,27 @@ describe('getElectronModulePath', () => { it('throws an error saying it cannot find electron', async () => { const fixtureDir = path.resolve(fixturePath, 'dummy_app'); await fs.cp(fixtureDir, tempDir, { recursive: true }); - await expect(getElectronModulePath(tempDir, { devDependencies: { electron: '^4.0.2' } })).rejects.toThrow('Cannot find the package'); + await expect( + getElectronModulePath(tempDir, { + devDependencies: { electron: '^4.0.2' }, + }), + ).rejects.toThrow('Cannot find the package'); }); }); it('works with electron', async () => { const fixtureDir = path.resolve(fixturePath, 'non-exact'); - await expect(getElectronModulePath(fixtureDir, { devDependencies: { electron: '^4.0.2' } })).resolves.toEqual( - path.join(fixtureDir, 'node_modules', 'electron') - ); + await expect( + getElectronModulePath(fixtureDir, { + devDependencies: { electron: '^4.0.2' }, + }), + ).resolves.toEqual(path.join(fixtureDir, 'node_modules', 'electron')); }); describe('with npm workspaces', () => { beforeAll(() => { - process.env.npm_config_user_agent = 'npm/10.9.2 node/v22.13.0 darwin arm64 workspaces/false'; + process.env.npm_config_user_agent = + 'npm/10.9.2 node/v22.13.0 darwin arm64 workspaces/false'; }); afterAll(() => { @@ -139,7 +192,9 @@ describe('getElectronModulePath', () => { devDependencies: { electron: '^4.0.4' }, }; - await expect(getElectronModulePath(fixtureDir, packageJSON)).resolves.toEqual(path.join(workspaceDir, 'node_modules', 'electron')); + await expect( + getElectronModulePath(fixtureDir, packageJSON), + ).resolves.toEqual(path.join(workspaceDir, 'node_modules', 'electron')); }); }); @@ -159,28 +214,46 @@ describe('getElectronModulePath', () => { devDependencies: { electron: '^4.0.4' }, }; - await expect(getElectronModulePath(fixtureDir, packageJSON)).resolves.toEqual(path.join(workspaceDir, 'node_modules', 'electron')); + await expect( + getElectronModulePath(fixtureDir, packageJSON), + ).resolves.toEqual(path.join(workspaceDir, 'node_modules', 'electron')); }); it('finds the top-level electron module despite the additional node_modules folder inside the package', async () => { const workspaceDir = path.resolve(fixturePath, 'yarn-workspace'); - const fixtureDir = path.join(workspaceDir, 'packages', 'with-node-modules'); + const fixtureDir = path.join( + workspaceDir, + 'packages', + 'with-node-modules', + ); const packageJSON = { devDependencies: { electron: '^4.0.4' }, }; - await expect(getElectronModulePath(fixtureDir, packageJSON)).resolves.toEqual(path.join(workspaceDir, 'node_modules', 'electron')); + await expect( + getElectronModulePath(fixtureDir, packageJSON), + ).resolves.toEqual(path.join(workspaceDir, 'node_modules', 'electron')); }); it('finds the correct electron module in nohoist mode', async () => { const workspaceDir = path.resolve(fixturePath, 'yarn-workspace'); - const fixtureDir = path.join(workspaceDir, 'packages', 'electron-folder-in-node-modules'); + const fixtureDir = path.join( + workspaceDir, + 'packages', + 'electron-folder-in-node-modules', + ); const packageJSON = { devDependencies: { electron: '^13.0.0' }, }; - await expect(getElectronModulePath(fixtureDir, packageJSON)).resolves.toEqual(path.join(fixtureDir, 'node_modules', 'electron')); - await expect(getElectronModulePath(fixtureDir, packageJSON)).resolves.not.toEqual(path.join(workspaceDir, 'node_modules', 'electron')); + await expect( + getElectronModulePath(fixtureDir, packageJSON), + ).resolves.toEqual(path.join(fixtureDir, 'node_modules', 'electron')); + await expect( + getElectronModulePath(fixtureDir, packageJSON), + ).resolves.not.toEqual( + path.join(workspaceDir, 'node_modules', 'electron'), + ); }); }); }); diff --git a/packages/utils/core-utils/spec/package-manager.spec.ts b/packages/utils/core-utils/spec/package-manager.spec.ts index 232528597a..00126863d6 100644 --- a/packages/utils/core-utils/spec/package-manager.spec.ts +++ b/packages/utils/core-utils/spec/package-manager.spec.ts @@ -2,7 +2,10 @@ import { spawn } from '@malept/cross-spawn-promise'; import findUp from 'find-up'; import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; -import { resolvePackageManager, spawnPackageManager } from '../src/package-manager'; +import { + resolvePackageManager, + spawnPackageManager, +} from '../src/package-manager'; vi.mock('@malept/cross-spawn-promise'); vi.mock('find-up', async (importOriginal) => { @@ -24,29 +27,62 @@ describe('package-manager', () => { }); it.each([ - { ua: 'yarn/1.22.22 npm/? node/v22.13.0 darwin arm64', pm: 'yarn', version: '1.22.22' }, - { ua: 'pnpm/10.0.0 npm/? node/v20.11.1 darwin arm64', pm: 'pnpm', version: '10.0.0' }, - { ua: 'npm/10.9.2 node/v22.13.0 darwin arm64 workspaces/false', pm: 'npm', version: '10.9.2' }, + { + ua: 'yarn/1.22.22 npm/? node/v22.13.0 darwin arm64', + pm: 'yarn', + version: '1.22.22', + }, + { + ua: 'pnpm/10.0.0 npm/? node/v20.11.1 darwin arm64', + pm: 'pnpm', + version: '10.0.0', + }, + { + ua: 'npm/10.9.2 node/v22.13.0 darwin arm64 workspaces/false', + pm: 'npm', + version: '10.9.2', + }, ])('with $ua', async ({ ua, pm, version }) => { process.env.npm_config_user_agent = ua; - await expect(resolvePackageManager()).resolves.toHaveProperty('executable', pm); - await expect(resolvePackageManager()).resolves.toHaveProperty('version', version); + await expect(resolvePackageManager()).resolves.toHaveProperty( + 'executable', + pm, + ); + await expect(resolvePackageManager()).resolves.toHaveProperty( + 'version', + version, + ); }); it('should return yarn if npm_config_user_agent=yarn', async () => { - process.env.npm_config_user_agent = 'yarn/1.22.22 npm/? node/v22.13.0 darwin arm64'; - await expect(resolvePackageManager()).resolves.toHaveProperty('executable', 'yarn'); - await expect(resolvePackageManager()).resolves.toHaveProperty('version', '1.22.22'); + process.env.npm_config_user_agent = + 'yarn/1.22.22 npm/? node/v22.13.0 darwin arm64'; + await expect(resolvePackageManager()).resolves.toHaveProperty( + 'executable', + 'yarn', + ); + await expect(resolvePackageManager()).resolves.toHaveProperty( + 'version', + '1.22.22', + ); }); it('should return pnpm if npm_config_user_agent=pnpm', async () => { - process.env.npm_config_user_agent = 'pnpm/10.0.0 npm/? node/v20.11.1 darwin arm64'; - await expect(resolvePackageManager()).resolves.toHaveProperty('executable', 'pnpm'); + process.env.npm_config_user_agent = + 'pnpm/10.0.0 npm/? node/v20.11.1 darwin arm64'; + await expect(resolvePackageManager()).resolves.toHaveProperty( + 'executable', + 'pnpm', + ); }); it('should return npm if npm_config_user_agent=npm', async () => { - process.env.npm_config_user_agent = 'npm/10.9.2 node/v22.13.0 darwin arm64 workspaces/false'; - await expect(resolvePackageManager()).resolves.toHaveProperty('executable', 'npm'); + process.env.npm_config_user_agent = + 'npm/10.9.2 node/v22.13.0 darwin arm64 workspaces/false'; + await expect(resolvePackageManager()).resolves.toHaveProperty( + 'executable', + 'npm', + ); }); }); @@ -72,16 +108,28 @@ describe('package-manager', () => { }; }); - it.each([{ pm: 'yarn' }, { pm: 'npm' }, { pm: 'pnpm' }])('should return $pm if NODE_INSTALLER=$pm', async ({ pm }) => { - process.env.NODE_INSTALLER = pm; - await expect(resolvePackageManager()).resolves.toHaveProperty('executable', pm); - }); + it.each([{ pm: 'yarn' }, { pm: 'npm' }, { pm: 'pnpm' }])( + 'should return $pm if NODE_INSTALLER=$pm', + async ({ pm }) => { + process.env.NODE_INSTALLER = pm; + await expect(resolvePackageManager()).resolves.toHaveProperty( + 'executable', + pm, + ); + }, + ); it('should return npm if package manager is unsupported', async () => { process.env.NODE_INSTALLER = 'bun'; console.warn = vi.fn(); - await expect(resolvePackageManager()).resolves.toHaveProperty('executable', 'npm'); - expect(console.warn).toHaveBeenCalledWith('⚠', expect.stringContaining('Package manager bun is unsupported')); + await expect(resolvePackageManager()).resolves.toHaveProperty( + 'executable', + 'npm', + ); + expect(console.warn).toHaveBeenCalledWith( + '⚠', + expect.stringContaining('Package manager bun is unsupported'), + ); }); }); @@ -100,12 +148,18 @@ describe('package-manager', () => { it('should use the package manager for the nearest ancestor lockfile if detected', async () => { vi.mocked(findUp).mockResolvedValue('/Users/foo/bar/yarn.lock'); - await expect(resolvePackageManager()).resolves.toHaveProperty('executable', 'yarn'); + await expect(resolvePackageManager()).resolves.toHaveProperty( + 'executable', + 'yarn', + ); }); it('should fall back to npm if no other strategy worked', async () => { process.env.npm_config_user_agent = undefined; vi.mocked(findUp).mockResolvedValue(undefined); - await expect(resolvePackageManager()).resolves.toHaveProperty('executable', 'npm'); + await expect(resolvePackageManager()).resolves.toHaveProperty( + 'executable', + 'npm', + ); }); }); diff --git a/packages/utils/core-utils/src/electron-version.ts b/packages/utils/core-utils/src/electron-version.ts index cc117795f3..f7a595564c 100644 --- a/packages/utils/core-utils/src/electron-version.ts +++ b/packages/utils/core-utils/src/electron-version.ts @@ -18,12 +18,22 @@ function findElectronDep(dep: string): boolean { return electronPackageNames.includes(dep); } -async function findAncestorNodeModulesPath(dir: string, packageName: string): Promise { +async function findAncestorNodeModulesPath( + dir: string, + packageName: string, +): Promise { d('Looking for a lock file to indicate the root of the repo'); - const lockPath = await findUp(['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml'], { cwd: dir, type: 'file' }); + const lockPath = await findUp( + ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml'], + { cwd: dir, type: 'file' }, + ); if (lockPath) { d(`Found lock file: ${lockPath}`); - const nodeModulesPath = path.join(path.dirname(lockPath), 'node_modules', packageName); + const nodeModulesPath = path.join( + path.dirname(lockPath), + 'node_modules', + packageName, + ); if (await fs.pathExists(nodeModulesPath)) { return nodeModulesPath; } @@ -32,8 +42,15 @@ async function findAncestorNodeModulesPath(dir: string, packageName: string): Pr return Promise.resolve(undefined); } -async function determineNodeModulesPath(dir: string, packageName: string): Promise { - const nodeModulesPath: string | undefined = path.join(dir, 'node_modules', packageName); +async function determineNodeModulesPath( + dir: string, + packageName: string, +): Promise { + const nodeModulesPath: string | undefined = path.join( + dir, + 'node_modules', + packageName, + ); if (await fs.pathExists(nodeModulesPath)) { return nodeModulesPath; } @@ -42,7 +59,9 @@ async function determineNodeModulesPath(dir: string, packageName: string): Promi export class PackageNotFoundError extends Error { constructor(packageName: string, dir: string) { - super(`Cannot find the package "${packageName}". Perhaps you need to run install it in "${dir}"?`); + super( + `Cannot find the package "${packageName}". Perhaps you need to run install it in "${dir}"?`, + ); } } @@ -53,7 +72,9 @@ function getElectronModuleName(packageJSON: PackageJSONWithDeps): string { // Why: checked above // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const packageName = electronPackageNames.find((pkg) => packageJSON.devDependencies![pkg]); + const packageName = electronPackageNames.find( + (pkg) => packageJSON.devDependencies![pkg], + ); if (packageName === undefined) { throw new Error('Could not find any Electron packages in devDependencies'); } @@ -61,7 +82,10 @@ function getElectronModuleName(packageJSON: PackageJSONWithDeps): string { return packageName; } -async function getElectronPackageJSONPath(dir: string, packageName: string): Promise { +async function getElectronPackageJSONPath( + dir: string, + packageName: string, +): Promise { const nodeModulesPath = await determineNodeModulesPath(dir, packageName); if (!nodeModulesPath) { throw new PackageNotFoundError(packageName, dir); @@ -75,7 +99,10 @@ async function getElectronPackageJSONPath(dir: string, packageName: string): Pro return undefined; } -export async function getElectronModulePath(dir: string, packageJSON: PackageJSONWithDeps): Promise { +export async function getElectronModulePath( + dir: string, + packageJSON: PackageJSONWithDeps, +): Promise { const moduleName = getElectronModuleName(packageJSON); const packageJSONPath = await getElectronPackageJSONPath(dir, moduleName); if (packageJSONPath) { @@ -85,7 +112,10 @@ export async function getElectronModulePath(dir: string, packageJSON: PackageJSO return undefined; } -export async function getElectronVersion(dir: string, packageJSON: PackageJSONWithDeps): Promise { +export async function getElectronVersion( + dir: string, + packageJSON: PackageJSONWithDeps, +): Promise { const packageName = getElectronModuleName(packageJSON); // Why: checked in getElectronModuleName @@ -93,7 +123,10 @@ export async function getElectronVersion(dir: string, packageJSON: PackageJSONWi let version = packageJSON.devDependencies![packageName]; if (!semver.valid(version)) { // It's not an exact version, find it in the actual module - const electronPackageJSONPath = await getElectronPackageJSONPath(dir, packageName); + const electronPackageJSONPath = await getElectronPackageJSONPath( + dir, + packageName, + ); if (electronPackageJSONPath) { const electronPackageJSON = await fs.readJson(electronPackageJSONPath); version = electronPackageJSON.version; @@ -105,7 +138,11 @@ export async function getElectronVersion(dir: string, packageJSON: PackageJSONWi return version; } -export function updateElectronDependency(packageJSON: PackageJSONWithDeps, dev: string[], exact: string[]): [string[], string[]] { +export function updateElectronDependency( + packageJSON: PackageJSONWithDeps, + dev: string[], + exact: string[], +): [string[], string[]] { const alteredDev = ([] as string[]).concat(dev); let alteredExact = ([] as string[]).concat(exact); // Why: checked in getElectronModuleName @@ -113,11 +150,15 @@ export function updateElectronDependency(packageJSON: PackageJSONWithDeps, dev: if (Object.keys(packageJSON.devDependencies!).find(findElectronDep)) { alteredExact = alteredExact.filter((dep) => dep !== 'electron'); } else if (packageJSON.dependencies) { - const electronKey = Object.keys(packageJSON.dependencies).find(findElectronDep); + const electronKey = Object.keys(packageJSON.dependencies).find( + findElectronDep, + ); if (electronKey) { alteredExact = alteredExact.filter((dep) => dep !== 'electron'); d(`Moving ${electronKey} from dependencies to devDependencies`); - alteredDev.push(`${electronKey}@${packageJSON.dependencies[electronKey]}`); + alteredDev.push( + `${electronKey}@${packageJSON.dependencies[electronKey]}`, + ); delete packageJSON.dependencies[electronKey]; } } diff --git a/packages/utils/core-utils/src/package-manager.ts b/packages/utils/core-utils/src/package-manager.ts index 62526a1b1b..5bbeff83e2 100644 --- a/packages/utils/core-utils/src/package-manager.ts +++ b/packages/utils/core-utils/src/package-manager.ts @@ -1,6 +1,10 @@ import path from 'node:path'; -import { CrossSpawnArgs, CrossSpawnOptions, spawn } from '@malept/cross-spawn-promise'; +import { + CrossSpawnArgs, + CrossSpawnOptions, + spawn, +} from '@malept/cross-spawn-promise'; import chalk from 'chalk'; import debug from 'debug'; import findUp from 'find-up'; @@ -9,7 +13,13 @@ import logSymbols from 'log-symbols'; const d = debug('electron-forge:package-manager'); export type SupportedPackageManager = 'yarn' | 'npm' | 'pnpm'; -export type PMDetails = { executable: SupportedPackageManager; version?: string; install: string; dev: string; exact: string }; +export type PMDetails = { + executable: SupportedPackageManager; + version?: string; + install: string; + dev: string; + exact: string; +}; let hasWarned = false; @@ -78,16 +88,25 @@ function pmFromUserAgent() { export const resolvePackageManager: () => Promise = async () => { const executingPM = pmFromUserAgent(); let lockfilePM; - const lockfile = await findUp(['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'pnpm-workspace.yaml'], { type: 'file' }); + const lockfile = await findUp( + ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'pnpm-workspace.yaml'], + { type: 'file' }, + ); if (lockfile) { const lockfileName = path.basename(lockfile); lockfilePM = PM_FROM_LOCKFILE[lockfileName]; } - const installer = process.env.NODE_INSTALLER || executingPM?.name || lockfilePM; + const installer = + process.env.NODE_INSTALLER || executingPM?.name || lockfilePM; // TODO(erickzhao): Remove NODE_INSTALLER environment variable for Forge 8 if (typeof process.env.NODE_INSTALLER === 'string' && !hasWarned) { - console.warn(logSymbols.warning, chalk.yellow(`The NODE_INSTALLER environment variable is deprecated and will be removed in Electron Forge v8`)); + console.warn( + logSymbols.warning, + chalk.yellow( + `The NODE_INSTALLER environment variable is deprecated and will be removed in Electron Forge v8`, + ), + ); hasWarned = true; } @@ -96,14 +115,16 @@ export const resolvePackageManager: () => Promise = async () => { case 'npm': case 'pnpm': d( - `Resolved package manager to ${installer}. (Derived from NODE_INSTALLER: ${process.env.NODE_INSTALLER}, npm_config_user_agent: ${process.env.npm_config_user_agent}, lockfile: ${lockfilePM})` + `Resolved package manager to ${installer}. (Derived from NODE_INSTALLER: ${process.env.NODE_INSTALLER}, npm_config_user_agent: ${process.env.npm_config_user_agent}, lockfile: ${lockfilePM})`, ); return { ...PACKAGE_MANAGERS[installer], version: executingPM?.version }; default: if (installer !== undefined) { console.warn( logSymbols.warning, - chalk.yellow(`Package manager ${chalk.red(installer)} is unsupported. Falling back to ${chalk.green('npm')} instead.`) + chalk.yellow( + `Package manager ${chalk.red(installer)} is unsupported. Falling back to ${chalk.green('npm')} instead.`, + ), ); } else { d(`No package manager detected. Falling back to npm.`); @@ -112,6 +133,10 @@ export const resolvePackageManager: () => Promise = async () => { } }; -export const spawnPackageManager = async (pm: PMDetails, args?: CrossSpawnArgs, opts?: CrossSpawnOptions): Promise => { +export const spawnPackageManager = async ( + pm: PMDetails, + args?: CrossSpawnArgs, + opts?: CrossSpawnOptions, +): Promise => { return (await spawn(pm.executable, args, opts)).trim(); }; diff --git a/packages/utils/core-utils/src/rebuild.ts b/packages/utils/core-utils/src/rebuild.ts index b18edb2ad5..dd659955a4 100644 --- a/packages/utils/core-utils/src/rebuild.ts +++ b/packages/utils/core-utils/src/rebuild.ts @@ -2,7 +2,11 @@ import * as cp from 'node:child_process'; import * as path from 'node:path'; import { RebuildOptions } from '@electron/rebuild'; -import { ForgeArch, ForgeListrTask, ForgePlatform } from '@electron-forge/shared-types'; +import { + ForgeArch, + ForgeListrTask, + ForgePlatform, +} from '@electron-forge/shared-types'; export const listrCompatibleRebuildHook = async ( buildPath: string, @@ -11,7 +15,7 @@ export const listrCompatibleRebuildHook = async ( arch: ForgeArch, config: Partial = {}, task: ForgeListrTask, - taskTitlePrefix = '' + taskTitlePrefix = '', ): Promise => { task.title = `${taskTitlePrefix}Preparing native dependencies`; @@ -22,9 +26,13 @@ export const listrCompatibleRebuildHook = async ( arch, }; - const child = cp.fork(path.resolve(__dirname, 'remote-rebuild.js'), [JSON.stringify(options)], { - stdio: ['pipe', 'pipe', 'pipe', 'ipc'], - }); + const child = cp.fork( + path.resolve(__dirname, 'remote-rebuild.js'), + [JSON.stringify(options)], + { + stdio: ['pipe', 'pipe', 'pipe', 'ipc'], + }, + ); let pendingError: Error; let found = 0; @@ -41,38 +49,46 @@ export const listrCompatibleRebuildHook = async ( task.output = chunk.toString(); }); - child.on('message', (message: { msg: string; err: { message: string; stack: string } }) => { - switch (message.msg) { - case 'module-found': { - found += 1; - redraw(); - break; - } - case 'module-done': { - done += 1; - redraw(); - break; - } - case 'rebuild-error': { - pendingError = new Error(message.err.message); - pendingError.stack = message.err.stack; - break; - } - case 'rebuild-done': { - if (task.task.rendererTaskOptions && 'persistentOutput' in task.task.rendererTaskOptions) { - task.task.rendererTaskOptions.persistentOutput = false; + child.on( + 'message', + (message: { msg: string; err: { message: string; stack: string } }) => { + switch (message.msg) { + case 'module-found': { + found += 1; + redraw(); + break; + } + case 'module-done': { + done += 1; + redraw(); + break; + } + case 'rebuild-error': { + pendingError = new Error(message.err.message); + pendingError.stack = message.err.stack; + break; + } + case 'rebuild-done': { + if ( + task.task.rendererTaskOptions && + 'persistentOutput' in task.task.rendererTaskOptions + ) { + task.task.rendererTaskOptions.persistentOutput = false; + } + break; } - break; } - } - }); + }, + ); await new Promise((resolve, reject) => { child.on('exit', (code) => { if (code === 0 && !pendingError) { resolve(); } else { - reject(pendingError || new Error(`Rebuilder failed with exit code: ${code}`)); + reject( + pendingError || new Error(`Rebuilder failed with exit code: ${code}`), + ); } }); }); diff --git a/packages/utils/core-utils/src/remote-rebuild.ts b/packages/utils/core-utils/src/remote-rebuild.ts index bf10955d35..73614d5075 100644 --- a/packages/utils/core-utils/src/remote-rebuild.ts +++ b/packages/utils/core-utils/src/remote-rebuild.ts @@ -1,7 +1,9 @@ import { rebuild, RebuildOptions } from '@electron/rebuild'; if (!process.send) { - console.error('The remote rebuilder expects to be spawned with an IPC channel'); + console.error( + 'The remote rebuilder expects to be spawned with an IPC channel', + ); // eslint-disable-next-line no-process-exit process.exit(1); } @@ -10,8 +12,12 @@ const options: RebuildOptions = JSON.parse(process.argv[2]); const rebuilder = rebuild(options); -rebuilder.lifecycle.on('module-found', () => process.send?.({ msg: 'module-found' })); -rebuilder.lifecycle.on('module-done', () => process.send?.({ msg: 'module-done' })); +rebuilder.lifecycle.on('module-found', () => + process.send?.({ msg: 'module-found' }), +); +rebuilder.lifecycle.on('module-done', () => + process.send?.({ msg: 'module-done' }), +); rebuilder .then(() => { diff --git a/packages/utils/test-utils/src/index.ts b/packages/utils/test-utils/src/index.ts index 1594452fb2..85f894d0e0 100644 --- a/packages/utils/test-utils/src/index.ts +++ b/packages/utils/test-utils/src/index.ts @@ -12,7 +12,11 @@ export async function runNPMInstall(dir: string, ...args: string[]) { await runNPM(dir, 'install', ...args); } -export async function ensureModulesInstalled(dir: string, deps: string[], devDeps: string[]): Promise { +export async function ensureModulesInstalled( + dir: string, + deps: string[], + devDeps: string[], +): Promise { await runNPMInstall(dir, ...deps); await runNPMInstall(dir, '--save-dev', ...devDeps); } diff --git a/packages/utils/tracer/src/index.ts b/packages/utils/tracer/src/index.ts index 2a27c40bf9..3302bc4391 100644 --- a/packages/utils/tracer/src/index.ts +++ b/packages/utils/tracer/src/index.ts @@ -13,7 +13,9 @@ const forgeTracer: { } = store._forgeTracer; if (process.env.ELECTRON_FORGE_TRACE_FILE) { - store._forgeTracer.pipe(fs.createWriteStream(process.env.ELECTRON_FORGE_TRACE_FILE)); + store._forgeTracer.pipe( + fs.createWriteStream(process.env.ELECTRON_FORGE_TRACE_FILE), + ); } else { store._forgeTracer = null; } @@ -31,7 +33,7 @@ function _autoTrace( tracer: Tracer | null, autoTraceId: string, opts: TraceOptions, - fn: (childTrace: typeof autoTrace, ...args: Args) => R + fn: (childTrace: typeof autoTrace, ...args: Args) => R, ): (...args: Args) => R { return (async (...args: Args) => { const traceArgs: Fields = { @@ -43,7 +45,12 @@ function _autoTrace( }; tracer?.begin(traceArgs); const childTrace = (opts: TraceOptions, fn: any) => { - return _autoTrace(tracer?.child(traceArgs) ?? null, opts.newRoot ? nextRoot() : autoTraceId, opts, fn); + return _autoTrace( + tracer?.child(traceArgs) ?? null, + opts.newRoot ? nextRoot() : autoTraceId, + opts, + fn, + ); }; (childTrace as any)._autoEnd = true; (childTrace as any)._end = () => tracer?.end(traceArgs); @@ -57,13 +64,19 @@ function _autoTrace( }) as any; } -export function delayTraceTillSignal(trace: typeof autoTrace, signaller: O, signal: K) { +export function delayTraceTillSignal( + trace: typeof autoTrace, + signaller: O, + signal: K, +) { const original: any = signaller[signal]; (trace as any)._autoEnd = false; signaller[signal] = function (...args: any[]) { const result = original.call(signaller, ...args); if (typeof result === 'object' && result.then && result.catch) { - result.then(() => (trace as any)._end()).catch(() => (trace as any)._end()); + result + .then(() => (trace as any)._end()) + .catch(() => (trace as any)._end()); } else { (trace as any)._end(); } @@ -72,6 +85,9 @@ export function delayTraceTillSignal(trace: return signaller; } -export function autoTrace(opts: TraceOptions, fn: (childTrace: typeof autoTrace, ...args: Args) => R): (...args: Args) => R { +export function autoTrace( + opts: TraceOptions, + fn: (childTrace: typeof autoTrace, ...args: Args) => R, +): (...args: Args) => R { return _autoTrace(forgeTracer.tracer, nextRoot(), opts, fn as any); } diff --git a/packages/utils/types/src/index.ts b/packages/utils/types/src/index.ts index beb61f1811..52ce10fd66 100644 --- a/packages/utils/types/src/index.ts +++ b/packages/utils/types/src/index.ts @@ -1,6 +1,10 @@ import { ChildProcess } from 'node:child_process'; -import { ArchOption, Options as ElectronPackagerOptions, TargetPlatform } from '@electron/packager'; +import { + ArchOption, + Options as ElectronPackagerOptions, + TargetPlatform, +} from '@electron/packager'; import { RebuildOptions } from '@electron/rebuild'; import { autoTrace } from '@electron-forge/tracer'; import { @@ -14,9 +18,20 @@ import { ListrTaskWrapper, } from 'listr2'; -export type ForgeListrOptions = ListrBaseClassOptions; -export type ForgeListrTask = ListrTaskWrapper; -export type ForgeListrTaskFn = ListrTask['task']; +export type ForgeListrOptions = ListrBaseClassOptions< + T, + ListrDefaultRendererValue, + ListrSimpleRendererValue +>; +export type ForgeListrTask = ListrTaskWrapper< + T, + ListrDefaultRenderer, + ListrDefaultRenderer | ListrSimpleRenderer +>; +export type ForgeListrTaskFn = ListrTask< + Ctx, + ListrDefaultRenderer +>['task']; export type ElectronProcess = ChildProcess & { restarted: boolean }; export type ForgePlatform = TargetPlatform; @@ -30,15 +45,30 @@ export interface ForgeSimpleHookSignatures { preStart: []; postStart: [appProcess: ElectronProcess]; prePackage: [platform: ForgePlatform, version: ForgeArch]; - packageAfterCopy: [buildPath: string, electronVersion: string, platform: ForgePlatform, arch: ForgeArch]; - packageAfterPrune: [buildPath: string, electronVersion: string, platform: ForgePlatform, arch: ForgeArch]; - packageAfterExtract: [buildPath: string, electronVersion: string, platform: ForgePlatform, arch: ForgeArch]; + packageAfterCopy: [ + buildPath: string, + electronVersion: string, + platform: ForgePlatform, + arch: ForgeArch, + ]; + packageAfterPrune: [ + buildPath: string, + electronVersion: string, + platform: ForgePlatform, + arch: ForgeArch, + ]; + packageAfterExtract: [ + buildPath: string, + electronVersion: string, + platform: ForgePlatform, + arch: ForgeArch, + ]; postPackage: [ packageResult: { platform: ForgePlatform; arch: ForgeArch; outputPaths: string[]; - } + }, ]; preMake: []; } @@ -50,20 +80,24 @@ export interface ForgeMutatingHookSignatures { readPackageJson: [packageJson: Record]; } -export type ForgeHookName = keyof (ForgeSimpleHookSignatures & ForgeMutatingHookSignatures); +export type ForgeHookName = keyof (ForgeSimpleHookSignatures & + ForgeMutatingHookSignatures); export type ForgeSimpleHookFn = ( forgeConfig: ResolvedForgeConfig, ...args: ForgeSimpleHookSignatures[Hook] ) => Promise; -export type ForgeMutatingHookFn = ( +export type ForgeMutatingHookFn< + Hook extends keyof ForgeMutatingHookSignatures, +> = ( forgeConfig: ResolvedForgeConfig, ...args: ForgeMutatingHookSignatures[Hook] ) => Promise; -export type ForgeHookFn = Hook extends keyof ForgeSimpleHookSignatures - ? ForgeSimpleHookFn - : Hook extends keyof ForgeMutatingHookSignatures - ? ForgeMutatingHookFn - : never; +export type ForgeHookFn = + Hook extends keyof ForgeSimpleHookSignatures + ? ForgeSimpleHookFn + : Hook extends keyof ForgeMutatingHookSignatures + ? ForgeMutatingHookFn + : never; export type ForgeHookMap = { [S in ForgeHookName]?: ForgeHookFn; }; @@ -72,22 +106,31 @@ export type ForgeMultiHookMap = { }; export interface IForgePluginInterface { - triggerHook(hookName: Hook, hookArgs: ForgeSimpleHookSignatures[Hook]): Promise; + triggerHook( + hookName: Hook, + hookArgs: ForgeSimpleHookSignatures[Hook], + ): Promise; getHookListrTasks( childTrace: typeof autoTrace, hookName: Hook, - hookArgs: ForgeSimpleHookSignatures[Hook] + hookArgs: ForgeSimpleHookSignatures[Hook], ): Promise; triggerMutatingHook( hookName: Hook, - item: ForgeMutatingHookSignatures[Hook][0] + item: ForgeMutatingHookSignatures[Hook][0], ): Promise; overrideStartLogic(opts: StartOptions): Promise; } /* eslint-enable @typescript-eslint/no-explicit-any */ -export type ForgeRebuildOptions = Omit; -export type ForgePackagerOptions = Omit; +export type ForgeRebuildOptions = Omit< + RebuildOptions, + 'buildPath' | 'electronVersion' | 'arch' +>; +export type ForgePackagerOptions = Omit< + ElectronPackagerOptions, + 'dir' | 'arch' | 'platform' | 'out' | 'electronVersion' +>; export interface ResolvedForgeConfig { /** * A string to uniquely identify artifacts of this build, will be appended @@ -211,7 +254,9 @@ export interface StartOptions { } export type InnerStartResult = ElectronProcess | string | string[] | false; -export type StartResult = InnerStartResult | { tasks: ForgeListrTaskDefinition[]; result: InnerStartResult }; +export type StartResult = + | InnerStartResult + | { tasks: ForgeListrTaskDefinition[]; result: InnerStartResult }; export interface InitTemplateOptions { copyCIFiles?: boolean; @@ -226,7 +271,10 @@ export interface ForgeTemplate { requiredForgeVersion?: string; dependencies?: string[]; devDependencies?: string[]; - initializeTemplate?: (dir: string, options: InitTemplateOptions) => Promise; + initializeTemplate?: ( + dir: string, + options: InitTemplateOptions, + ) => Promise; } export type PackagePerson = diff --git a/packages/utils/web-multi-logger/src/Log.ts b/packages/utils/web-multi-logger/src/Log.ts index 6ed4907119..e9dec8654c 100644 --- a/packages/utils/web-multi-logger/src/Log.ts +++ b/packages/utils/web-multi-logger/src/Log.ts @@ -1,3 +1,6 @@ export default class Log { - constructor(public line: string, public timestamp: Date) {} + constructor( + public line: string, + public timestamp: Date, + ) {} } diff --git a/packages/utils/web-multi-logger/src/Logger.ts b/packages/utils/web-multi-logger/src/Logger.ts index cc215312da..5547f6ae5f 100644 --- a/packages/utils/web-multi-logger/src/Logger.ts +++ b/packages/utils/web-multi-logger/src/Logger.ts @@ -25,9 +25,18 @@ export default class Logger { this.ws = ews(this.app); this.app.get('/rest/tabs', (_req, res) => res.json(this.tabs)); - this.app.use('/xterm/addons/fit', express.static(path.dirname(require.resolve('xterm-addon-fit')))); - this.app.use('/xterm/addons/search', express.static(path.dirname(require.resolve('xterm-addon-search')))); - this.app.use('/xterm', express.static(path.resolve(require.resolve('xterm'), '../..'))); + this.app.use( + '/xterm/addons/fit', + express.static(path.dirname(require.resolve('xterm-addon-fit'))), + ); + this.app.use( + '/xterm/addons/search', + express.static(path.dirname(require.resolve('xterm-addon-search'))), + ); + this.app.use( + '/xterm', + express.static(path.resolve(require.resolve('xterm'), '../..')), + ); this.app.use(express.static(path.resolve(__dirname, '..', 'static'))); this.ws.app.ws('/sub', () => { // I assume this endpoint is just a no-op needed for some reason. diff --git a/packages/utils/web-multi-logger/src/Tab.ts b/packages/utils/web-multi-logger/src/Tab.ts index 6469ee2a70..f10e75b2ed 100644 --- a/packages/utils/web-multi-logger/src/Tab.ts +++ b/packages/utils/web-multi-logger/src/Tab.ts @@ -9,7 +9,10 @@ export default class Tab { private id: number; - constructor(public name: string, private ws: ews.Instance) { + constructor( + public name: string, + private ws: ews.Instance, + ) { this.id = idCounter; idCounter += 1; } @@ -26,7 +29,7 @@ export default class Tab { JSON.stringify({ tab: this.id, payload: log, - }) + }), ); } } diff --git a/packages/utils/web-multi-logger/static/index.html b/packages/utils/web-multi-logger/static/index.html index 962e031c6b..8817943ff0 100644 --- a/packages/utils/web-multi-logger/static/index.html +++ b/packages/utils/web-multi-logger/static/index.html @@ -1,4 +1,4 @@ - + @@ -11,7 +11,9 @@ padding: 0; margin: 0; background: #002b36; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + font-family: + -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, + 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; } .tabs { @@ -26,7 +28,9 @@ align-items: center; justify-content: center; display: flex; - transition: background 0.4s ease-in-out, color 0.4s ease-in-out; + transition: + background 0.4s ease-in-out, + color 0.4s ease-in-out; background: #eee8d5; color: #073642; cursor: pointer; diff --git a/packages/utils/web-multi-logger/static/main.js b/packages/utils/web-multi-logger/static/main.js index 488046df72..a55650d30b 100644 --- a/packages/utils/web-multi-logger/static/main.js +++ b/packages/utils/web-multi-logger/static/main.js @@ -94,7 +94,9 @@ class Renderer { selectTab(tab) { const selected = document.querySelector('.selected-tab'); if (selected) selected.classList.remove('selected-tab'); - document.querySelector(`[data-id="${tab.id}"]`).classList.add('selected-tab'); + document + .querySelector(`[data-id="${tab.id}"]`) + .classList.add('selected-tab'); this.currentTab = tab; this.initialRender(tab); } diff --git a/tools/doc-plugin/src/index.tsx b/tools/doc-plugin/src/index.tsx index c5d4ba5e64..7450277b43 100644 --- a/tools/doc-plugin/src/index.tsx +++ b/tools/doc-plugin/src/index.tsx @@ -27,7 +27,7 @@ export class NavigationOverrideTheme extends DefaultTheme { override getRenderContext(): NavigationOverrideThemeContext { this._contextCache ||= new NavigationOverrideThemeContext( this, - this.application.options + this.application.options, ); return this._contextCache; } @@ -36,7 +36,7 @@ export class NavigationOverrideTheme extends DefaultTheme { // Replicated from the TypeDoc codebase. This should be exported by TypeDoc. function classNames( names: Record, - extraCss?: string + extraCss?: string, ) { const css = Object.keys(names) .filter((key) => names[key]) @@ -49,10 +49,10 @@ function classNames( function overridePrimaryNavigation( context: DefaultThemeRenderContext, - props: PageEvent + props: PageEvent, ) { const modules = props.model.project.getChildrenByKind( - ReflectionKind.SomeModule + ReflectionKind.SomeModule, ); const projectLinkName = modules.some((m) => m.kindOf(ReflectionKind.Module)) ? 'All Modules' diff --git a/tools/fix-deps.ts b/tools/fix-deps.ts index 6e2e9040df..a1f31e3389 100644 --- a/tools/fix-deps.ts +++ b/tools/fix-deps.ts @@ -7,7 +7,9 @@ import { getPackageInfo } from './utils'; (async () => { const packages = await getPackageInfo(); - const baseJson = await fs.readJson(path.resolve(__dirname, '..', 'package.json')); + const baseJson = await fs.readJson( + path.resolve(__dirname, '..', 'package.json'), + ); const allDeps = { ...baseJson.dependencies, @@ -18,7 +20,11 @@ import { getPackageInfo } from './utils'; for (const p of packages) { const json = await fs.readJson(path.resolve(p.path, 'package.json')); - for (const key of ['dependencies', 'devDependencies', 'optionalDependencies']) { + for (const key of [ + 'dependencies', + 'devDependencies', + 'optionalDependencies', + ]) { const deps = json[key]; if (!deps) continue; diff --git a/tools/gen-ts-glue.ts b/tools/gen-ts-glue.ts index 53207858ab..5400018d7f 100644 --- a/tools/gen-ts-glue.ts +++ b/tools/gen-ts-glue.ts @@ -49,7 +49,9 @@ function convertMainToSrc(main: string): string { // Read the main file const srcMain = convertMainToSrc(main); const srcMainFull = path.resolve(pkg.path, srcMain); - const srcMainContents = await fs.readFile(srcMainFull, { encoding: 'utf8' }); + const srcMainContents = await fs.readFile(srcMainFull, { + encoding: 'utf8', + }); // Detect if the package has a default export const hasDefault = /export\s+default/i.test(srcMainContents); @@ -67,17 +69,19 @@ function convertMainToSrc(main: string): string { entryPoints: [srcMain], }, null, - 2 - ) + 2, + ), ); let facadeFileContents = '// ⚠️ AUTOGENERATED ⚠️ AUTOGENERATED ⚠️ AUTOGENERATED ⚠️\n' + '// This file was automatically generated by `tools/gen-ts-glue.ts`. Do not modify directly if you want to keep your changes.\n' + `export * from "${importTarget}";\n`; if (hasDefault) { - facadeFileContents += `import defaultExport from "${importTarget}";\n` + `export default defaultExport;\n`; + facadeFileContents += + `import defaultExport from "${importTarget}";\n` + + `export default defaultExport;\n`; } await fs.writeFile(facadeFilePath, facadeFileContents); - }) + }), ); })().catch(console.error); diff --git a/tools/gen-tsconfigs.ts b/tools/gen-tsconfigs.ts index 46d397ae9d..bd26964372 100644 --- a/tools/gen-tsconfigs.ts +++ b/tools/gen-tsconfigs.ts @@ -11,7 +11,12 @@ function filterDupes(arr: readonly T[]): T[] { } (async () => { - const BASE_TS_CONFIG = JSON.parse(await fs.readFile(path.resolve(__dirname, '../tsconfig.base.json'), 'utf-8')); + const BASE_TS_CONFIG = JSON.parse( + await fs.readFile( + path.resolve(__dirname, '../tsconfig.base.json'), + 'utf-8', + ), + ); const packages = await getPackageInfo(); // Generate a root tsconfig.json for project references @@ -19,14 +24,19 @@ function filterDupes(arr: readonly T[]): T[] { files: [], references: packages.map((p) => { const pathArr = p.path.split(path.sep); - const pathFromPackages = pathArr.slice(pathArr.indexOf('packages') + 1).join(path.sep); + const pathFromPackages = pathArr + .slice(pathArr.indexOf('packages') + 1) + .join(path.sep); return { path: pathFromPackages, }; }), }; - fs.writeFile(path.resolve(__dirname, '../packages/tsconfig.json'), JSON.stringify(rootPackagesConfig, null, 2)); + fs.writeFile( + path.resolve(__dirname, '../packages/tsconfig.json'), + JSON.stringify(rootPackagesConfig, null, 2), + ); // Do each package in parallel await Promise.all( @@ -38,12 +48,17 @@ function filterDupes(arr: readonly T[]): T[] { }; // Figure out which other local packages this package references - const pkgDeps = [pkgManifest.dependencies, pkgManifest.devDependencies].flatMap((deps) => (deps === undefined ? [] : Object.keys(deps))); + const pkgDeps = [ + pkgManifest.dependencies, + pkgManifest.devDependencies, + ].flatMap((deps) => (deps === undefined ? [] : Object.keys(deps))); const refs = filterDupes( pkgDeps.flatMap((depName) => { - const depPkg = packages.find((maybeDepPkg) => maybeDepPkg.name === depName); + const depPkg = packages.find( + (maybeDepPkg) => maybeDepPkg.name === depName, + ); return depPkg === undefined ? [] : [depPkg]; - }) + }), ); // Map each package this package references to a typescript project reference @@ -58,13 +73,16 @@ function filterDupes(arr: readonly T[]): T[] { Object.assign(tsConfig.compilerOptions, { typeRoots: [ path.relative(pkg.path, path.resolve(__dirname, '..', 'typings')), - path.relative(pkg.path, path.resolve(__dirname, '..', 'node_modules', '@types')), + path.relative( + pkg.path, + path.resolve(__dirname, '..', 'node_modules', '@types'), + ), ], }); // Write the typescript config to the package dir const tsConfigPath = path.join(pkg.path, 'tsconfig.json'); return fs.writeFile(tsConfigPath, JSON.stringify(tsConfig, undefined, 2)); - }) + }), ); })().catch(console.error); diff --git a/tools/position-docs.ts b/tools/position-docs.ts index 1ad1dfc497..bc895244d0 100644 --- a/tools/position-docs.ts +++ b/tools/position-docs.ts @@ -7,12 +7,22 @@ import { getPackageInfo } from './utils'; const DOCS_PATH = path.resolve(__dirname, '..', 'docs'); -async function normalizeLinks(htmlFile: string, subPath: string): Promise { +async function normalizeLinks( + htmlFile: string, + subPath: string, +): Promise { const content: string = await fs.readFile(htmlFile, 'utf8'); - const relative: string = path.relative(path.resolve(DOCS_PATH, subPath), path.dirname(htmlFile)); + const relative: string = path.relative( + path.resolve(DOCS_PATH, subPath), + path.dirname(htmlFile), + ); return content .replace(/="[^"]*assets\//gi, '="/assets/') - .replace(/( `${m1}/${path.posix.join(subPath, relative, m2)}"`); + .replace( + /( + `${m1}/${path.posix.join(subPath, relative, m2)}"`, + ); } (async () => { @@ -23,10 +33,16 @@ async function normalizeLinks(htmlFile: string, subPath: string): Promise { for (const workspace of Object.keys(workspaceMappings)) { const workspaceDir = path.resolve(BASE_DIR, 'packages', workspace); - for (const packageName of await fs.readdir(path.resolve(workspaceDir))) { - const packageKey = workspaceMappings[workspace][packageName] || packageName; + for (const packageName of await fs.readdir( + path.resolve(workspaceDir), + )) { + const packageKey = + workspaceMappings[workspace][packageName] || packageName; - ctx.packageKeys.push([workspace, workspaceDir, packageKey, packageName]); + ctx.packageKeys.push([ + workspace, + workspaceDir, + packageKey, + packageName, + ]); } } }, @@ -63,23 +74,28 @@ function sync(): Listr { title: 'Fetching READMEs', task: (ctx: SyncContext) => new Listr( - ctx.packageKeys.map(([workspace, workspaceDir, packageKey, packageName]) => ({ - title: `Fetching README for ${path.basename(workspaceDir)}/${packageKey}`, - task: async () => { - let rp: ReturnType; - if (workspace !== 'api') { - rp = fetch(`${DOCS_BASE}/${workspace}s/${packageKey}.md`); - } else { - rp = fetch(`${DOCS_BASE}/${packageKey}.md`); - } - const r = await rp; - if (r.status !== 200) return; + ctx.packageKeys.map( + ([workspace, workspaceDir, packageKey, packageName]) => ({ + title: `Fetching README for ${path.basename(workspaceDir)}/${packageKey}`, + task: async () => { + let rp: ReturnType; + if (workspace !== 'api') { + rp = fetch(`${DOCS_BASE}/${workspace}s/${packageKey}.md`); + } else { + rp = fetch(`${DOCS_BASE}/${packageKey}.md`); + } + const r = await rp; + if (r.status !== 200) return; - const md = sanitize(await r.text()); - await fs.writeFile(path.resolve(workspaceDir, packageName, 'README.md'), md); - }, - })), - { concurrent: 3 } + const md = sanitize(await r.text()); + await fs.writeFile( + path.resolve(workspaceDir, packageName, 'README.md'), + md, + ); + }, + }), + ), + { concurrent: 3 }, ), }, ]); diff --git a/tools/test-clear.ts b/tools/test-clear.ts index 9e2aaf5267..0825997211 100644 --- a/tools/test-clear.ts +++ b/tools/test-clear.ts @@ -18,6 +18,10 @@ import fs from 'fs-extra'; await fs.remove(dir); } } else { - console.log(chalk.gray('There is no "electron-forge-test-*" dir that needs to be cleaned.')); + console.log( + chalk.gray( + 'There is no "electron-forge-test-*" dir that needs to be cleaned.', + ), + ); } })(); diff --git a/tools/test-dist.ts b/tools/test-dist.ts index 4751525b3d..f7f782cc02 100644 --- a/tools/test-dist.ts +++ b/tools/test-dist.ts @@ -28,11 +28,17 @@ const PACKAGES_DIR = path.resolve(BASE_DIR, 'packages'); const pj = await fs.readJson(path.resolve(dir, 'package.json')); if (pj.name === '@electron-forge/cli') continue; if (!(await fs.pathExists(path.resolve(dir, pj.main)))) { - console.error(`${chalk.cyan(`[${pj.name}]`)}:`, chalk.red(`Main entry not found (${pj.main})`)); + console.error( + `${chalk.cyan(`[${pj.name}]`)}:`, + chalk.red(`Main entry not found (${pj.main})`), + ); bad = true; } if (!pj.typings || !(await fs.pathExists(path.resolve(dir, pj.typings)))) { - console.error(`${chalk.cyan(`[${pj.name}]`)}:`, chalk.red(`Typings entry not found (${pj.typings})`)); + console.error( + `${chalk.cyan(`[${pj.name}]`)}:`, + chalk.red(`Typings entry not found (${pj.typings})`), + ); bad = true; } } diff --git a/tools/update-dependencies.js b/tools/update-dependencies.js index 1b807729f7..cc0290d45d 100755 --- a/tools/update-dependencies.js +++ b/tools/update-dependencies.js @@ -73,7 +73,10 @@ class Package { } isMinorVersionBump() { - return this.minorVersionLocked && !satisfies(this.latestVersion, `~${this.wantedVersion}`); + return ( + this.minorVersionLocked && + !satisfies(this.latestVersion, `~${this.wantedVersion}`) + ); } async smoketestAndCommit(packageName = null) { @@ -81,7 +84,11 @@ class Package { await yarn('lint'); await yarn('build'); await git('add', 'package.json', 'yarn.lock', ...packageJSONs); - await git('commit', '-m', `build(${this.commitType}): upgrade ${packageName || this.name} to ${this.commitVersion}`); + await git( + 'commit', + '-m', + `build(${this.commitType}): upgrade ${packageName || this.name} to ${this.commitVersion}`, + ); } async upgrade() { @@ -93,12 +100,16 @@ class Package { } async yarn_upgrade_and_update_packageJSON() { - console.log(`Upgrading ${this.name} from ${this.wantedVersion} to ^${this.latestVersion} (and updating package.json)...`); + console.log( + `Upgrading ${this.name} from ${this.wantedVersion} to ^${this.latestVersion} (and updating package.json)...`, + ); await yarn('upgrade', `${this.name}@^${this.latestVersion}`); } async yarn_upgrade_in_yarn_lock() { - console.log(`Upgrading ${this.name} from ${this.currentVersion} to ${this.latestVersion} in yarn.lock...`); + console.log( + `Upgrading ${this.name} from ${this.currentVersion} to ${this.latestVersion} in yarn.lock...`, + ); await yarn('upgrade', this.name); } } @@ -113,21 +124,43 @@ async function main() { console.log('No packages to update.'); } catch (error) { const table = JSON.parse(error.stdout.split('\n')[1]); - for (const [packageName, currentVersion, wantedVersion, latestVersion, packageType /*, _url */] of table.data.body) { + for (const [ + packageName, + currentVersion, + wantedVersion, + latestVersion, + packageType /*, _url */, + ] of table.data.body) { if (DO_NOT_UPGRADE.includes(packageName)) { - console.log(`Skipping "${packageName} from update as it is in the denylist`); + console.log( + `Skipping "${packageName} from update as it is in the denylist`, + ); continue; } if (onlyModules.length > 0 && !onlyModules.includes(packageName)) { - console.log(`Skipping "${packageName}" from update as it was not specified on the command line`); + console.log( + `Skipping "${packageName}" from update as it was not specified on the command line`, + ); continue; } let commitPackageName = null; - const nodePackage = new Package(packageName, currentVersion, wantedVersion, latestVersion, packageType); + const nodePackage = new Package( + packageName, + currentVersion, + wantedVersion, + latestVersion, + packageType, + ); await nodePackage.upgrade(); if (packageName === '@typescript-eslint/parser') { - const eslintPlugin = new Package('@typescript-eslint/eslint-plugin', currentVersion, wantedVersion, latestVersion, packageType); + const eslintPlugin = new Package( + '@typescript-eslint/eslint-plugin', + currentVersion, + wantedVersion, + latestVersion, + packageType, + ); await eslintPlugin.upgrade(); commitPackageName = '@typescript-eslint/{parser,eslint-plugin}'; } diff --git a/tools/utils.ts b/tools/utils.ts index 55df642b1e..4bf0a0f28b 100644 --- a/tools/utils.ts +++ b/tools/utils.ts @@ -21,7 +21,9 @@ export const getPackageInfo = async (): Promise => { if (stat.isDirectory()) { for (const packageDir of await fs.readdir(subDirPath)) { const packagePath = path.resolve(subDirPath, packageDir); - const pkg = await fs.readJson(path.resolve(packagePath, 'package.json')); + const pkg = await fs.readJson( + path.resolve(packagePath, 'package.json'), + ); packages.push({ path: packagePath, name: pkg.name, diff --git a/typedoc.json b/typedoc.json index 452ab73699..fb0b0085ad 100644 --- a/typedoc.json +++ b/typedoc.json @@ -1,5 +1,12 @@ { "$schema": "https://typedoc.org/schema.json", "entryPointStrategy": "packages", - "entryPoints": ["packages/api/core", "packages/maker/*", "packages/plugin/*", "packages/plugin/*", "packages/publisher/*", "packages/utils/types"] + "entryPoints": [ + "packages/api/core", + "packages/maker/*", + "packages/plugin/*", + "packages/plugin/*", + "packages/publisher/*", + "packages/utils/types" + ] } diff --git a/typings/sudo-prompt/index.d.ts b/typings/sudo-prompt/index.d.ts index 1917dbf542..f0e371d618 100644 --- a/typings/sudo-prompt/index.d.ts +++ b/typings/sudo-prompt/index.d.ts @@ -4,10 +4,14 @@ import { PromiseWithChild } from 'node:child_process'; // TODO: Remove this if/when that PR gets merged/released declare module 'sudo-prompt' { namespace exec { - function __promisify__(command: string): PromiseWithChild<{ stdout: string; stderr: string }>; + function __promisify__( + command: string, + ): PromiseWithChild<{ stdout: string; stderr: string }>; function __promisify__( command: string, - options: ((error?: Error, stdout?: TBuffer, stderr?: TBuffer) => void) | { name?: string; icns?: string; env?: Record } + options: + | ((error?: Error, stdout?: TBuffer, stderr?: TBuffer) => void) + | { name?: string; icns?: string; env?: Record }, ): PromiseWithChild<{ stdout: TBuffer; stderr: TBuffer }>; } } diff --git a/yarn.lock b/yarn.lock index b62941e3a2..239eee1f77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11224,10 +11224,10 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prettier@^2.4.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" - integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== +prettier@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.6.2.tgz#ccda02a1003ebbb2bfda6f83a074978f608b9393" + integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ== pretty-error@^4.0.0: version "4.0.0" From 73df6f15fcfd8f9261920b80e904867d409a1680 Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Wed, 27 Aug 2025 10:42:40 -0700 Subject: [PATCH 04/26] chore: add `.git-blame-ignore-revs` (#3980) --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..ec5351657e --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Prettier 3.6 (https://github.com/electron/forge/pull/3977) +717faf963b103b55f7041dc26e4fd825215aaf1d \ No newline at end of file From 46989c1c5130d6a85ec46eef9094e61d3d690386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zhong=20Lufan=20=28=E9=92=9F=E8=B7=AF=E5=B8=86=29?= Date: Thu, 28 Aug 2025 03:54:49 +0800 Subject: [PATCH 05/26] fix(core-utils): startLogic was called in incorrect context (#3809) fix(core-utils): startLogic was called in incorrect context. Co-authored-by: Erick Zhao --- packages/api/core/src/util/plugin-interface.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/core/src/util/plugin-interface.ts b/packages/api/core/src/util/plugin-interface.ts index 31a59b4be8..d56cbf647b 100644 --- a/packages/api/core/src/util/plugin-interface.ts +++ b/packages/api/core/src/util/plugin-interface.ts @@ -187,7 +187,7 @@ export default class PluginInterface implements IForgePluginInterface { plugin.startLogic !== PluginBase.prototype.startLogic ) { claimed.push(plugin.name); - newStartFn = plugin.startLogic; + newStartFn = plugin.startLogic.bind(plugin); } } if (claimed.length > 1) { From d6781532fc0c290b351a2115c603440126af59aa Mon Sep 17 00:00:00 2001 From: Seth <8952745+sethcg@users.noreply.github.com> Date: Wed, 27 Aug 2025 21:24:14 +0000 Subject: [PATCH 06/26] feat: added inquirer as an alternative to --template cli (#3933) * added inquirer as an alternative to --template cli * added inquirer as an alternative to --template cli * implemented prompt-adapter-inquirer * removed commented code, related to another pull request * chore: update lockfile * fixed default parameters, and fixed comments * simplified options * UX changes * revert all lockfile changes * reinstall deps --------- Co-authored-by: Erick Zhao --- packages/api/cli/package.json | 2 + packages/api/cli/src/electron-forge-init.ts | 131 +++++++++++--- yarn.lock | 178 +++++++++++++++++++- 3 files changed, 289 insertions(+), 22 deletions(-) diff --git a/packages/api/cli/package.json b/packages/api/cli/package.json index 6b8509ecda..6891e4638f 100644 --- a/packages/api/cli/package.json +++ b/packages/api/cli/package.json @@ -19,6 +19,8 @@ "@electron-forge/core-utils": "7.8.3", "@electron-forge/shared-types": "7.8.3", "@electron/get": "^3.0.0", + "@inquirer/prompts": "^6.0.1", + "@listr2/prompt-adapter-inquirer": "^2.0.22", "chalk": "^4.0.0", "commander": "^11.1.0", "debug": "^4.3.1", diff --git a/packages/api/cli/src/electron-forge-init.ts b/packages/api/cli/src/electron-forge-init.ts index 8db81d0b5a..29c3ee86cd 100644 --- a/packages/api/cli/src/electron-forge-init.ts +++ b/packages/api/cli/src/electron-forge-init.ts @@ -1,44 +1,133 @@ +import fs from 'node:fs/promises'; + import { api, InitOptions } from '@electron-forge/core'; +import { confirm, select } from '@inquirer/prompts'; +import { ListrInquirerPromptAdapter } from '@listr2/prompt-adapter-inquirer'; +import chalk from 'chalk'; import { program } from 'commander'; +import { Listr } from 'listr2'; import './util/terminate'; import packageJSON from '../package.json'; import { resolveWorkingDir } from './util/resolve-working-dir'; +// eslint-disable-next-line n/no-extraneous-import -- we get this from `@inquirer/prompts` +import type { Prompt } from '@inquirer/type'; + program .version(packageJSON.version, '-V, --version', 'Output the current version.') .helpOption('-h, --help', 'Output usage information.') .argument( '[dir]', - 'Directory to initialize the project in. (default: current directory)', - ) - .option('-t, --template [name]', 'Name of the Forge template to use.', 'base') - .option( - '-c, --copy-ci-files', - 'Whether to copy the templated CI files.', - false, + 'Directory to initialize the project in. Defaults to the current directory.', ) - .option('-f, --force', 'Whether to overwrite an existing directory.', false) + .option('-t, --template [name]', 'Name of the Forge template to use.') + .option('-c, --copy-ci-files', 'Whether to copy the templated CI files.') + .option('-f, --force', 'Whether to overwrite an existing directory.') .option( '--skip-git', 'Skip initializing a git repository in the initialized project.', - false, ) .action(async (dir) => { - const workingDir = resolveWorkingDir(dir, false); - const options = program.opts(); + const tasks = new Listr( + [ + { + task: async (initOpts): Promise => { + initOpts.interactive = true; + initOpts.template = options.template ?? 'base'; + initOpts.copyCIFiles = Boolean(options.copyCiFiles); + initOpts.force = Boolean(options.force); + initOpts.skipGit = Boolean(options.skipGit); + initOpts.dir = resolveWorkingDir(dir, false); + }, + }, + { + task: async (initOpts, task): Promise => { + // only run interactive prompts if no args passed and not in CI environment + if ( + Object.keys(options).length > 0 || + process.env.CI || + !process.stdout.isTTY + ) { + return; + } + + const prompt = task.prompt(ListrInquirerPromptAdapter); - const initOpts: InitOptions = { - dir: workingDir, - interactive: true, - copyCIFiles: !!options.copyCiFiles, - force: !!options.force, - skipGit: !!options.skipGit, - }; - if (options.template) initOpts.template = options.template; + if ( + typeof initOpts.dir === 'string' && + (await fs.readdir(initOpts.dir)).length > 0 + ) { + const confirmResult = await prompt.run(confirm, { + message: `${chalk.cyan(initOpts.dir)} is not empty. Would you like to continue and overwrite existing files?`, + default: false, + }); + if (confirmResult) { + initOpts.force = true; + } else { + task.output = 'Directory is not empty. Exiting.'; + process.exit(0); + } + } + + const bundler: string = await prompt.run>( + select, + { + message: 'Select a bundler', + choices: [ + { + name: 'None', + value: 'base', + }, + { + name: 'Vite', + value: 'vite', + }, + { + name: 'webpack', + value: 'webpack', + }, + ], + }, + ); + + let language: string | undefined; + + if (bundler !== 'base') { + language = await prompt.run>( + select, + { + message: 'Select a programming language', + choices: [ + { + name: 'JavaScript', + value: undefined, + }, + { + name: 'TypeScript', + value: 'typescript', + }, + ], + }, + ); + } + + initOpts.template = `${bundler}${language ? `-${language}` : ''}`; + initOpts.skipGit = !(await prompt.run(confirm, { + message: `Would you like to initialize Git in your new project?`, + default: true, + })); + }, + }, + ], + { concurrent: false }, + ); + + const initOpts: InitOptions = await tasks.run(); await api.init(initOpts); - }) - .parse(process.argv); + }); + +program.parse(process.argv); diff --git a/yarn.lock b/yarn.lock index 239eee1f77..0ffc7ab33d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1437,6 +1437,25 @@ resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340" integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q== +"@inquirer/checkbox@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/checkbox/-/checkbox-3.0.1.tgz#0a57f704265f78c36e17f07e421b98efb4b9867b" + integrity sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + yoctocolors-cjs "^2.1.2" + +"@inquirer/confirm@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-4.0.1.tgz#9106d6bffa0b2fdd0e4f60319b6f04f2e06e6e25" + integrity sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + "@inquirer/confirm@^5.0.0": version "5.1.1" resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-5.1.1.tgz#18385064b8275eb79fdba505ce527801804eea04" @@ -1460,11 +1479,137 @@ wrap-ansi "^6.2.0" yoctocolors-cjs "^2.1.2" +"@inquirer/core@^9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-9.2.1.tgz#677c49dee399c9063f31e0c93f0f37bddc67add1" + integrity sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg== + dependencies: + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + "@types/mute-stream" "^0.0.4" + "@types/node" "^22.5.5" + "@types/wrap-ansi" "^3.0.0" + ansi-escapes "^4.3.2" + cli-width "^4.1.0" + mute-stream "^1.0.0" + signal-exit "^4.1.0" + strip-ansi "^6.0.1" + wrap-ansi "^6.2.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/editor@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/editor/-/editor-3.0.1.tgz#d109f21e050af6b960725388cb1c04214ed7c7bc" + integrity sha512-VA96GPFaSOVudjKFraokEEmUQg/Lub6OXvbIEZU1SDCmBzRkHGhxoFAVaF30nyiB4m5cEbDgiI2QRacXZ2hw9Q== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + external-editor "^3.1.0" + +"@inquirer/expand@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/expand/-/expand-3.0.1.tgz#aed9183cac4d12811be47a4a895ea8e82a17e22c" + integrity sha512-ToG8d6RIbnVpbdPdiN7BCxZGiHOTomOX94C2FaT5KOHupV40tKEDozp12res6cMIfRKrXLJyexAZhWVHgbALSQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/figures@^1.0.6": + version "1.0.13" + resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.13.tgz#ad0afd62baab1c23175115a9b62f511b6a751e45" + integrity sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw== + "@inquirer/figures@^1.0.9": version "1.0.9" resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.9.tgz#9d8128f8274cde4ca009ca8547337cab3f37a4a3" integrity sha512-BXvGj0ehzrngHTPTDqUoDT3NXL8U0RxUk2zJm2A66RhCEIWdtU1v6GuUqNAgArW4PQ9CinqIWyHdQgdwOj06zQ== +"@inquirer/input@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/input/-/input-3.0.1.tgz#de63d49e516487388508d42049deb70f2cb5f28e" + integrity sha512-BDuPBmpvi8eMCxqC5iacloWqv+5tQSJlUafYWUe31ow1BVXjW2a5qe3dh4X/Z25Wp22RwvcaLCc2siHobEOfzg== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + +"@inquirer/number@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/number/-/number-2.0.1.tgz#b9863080d02ab7dc2e56e16433d83abea0f2a980" + integrity sha512-QpR8jPhRjSmlr/mD2cw3IR8HRO7lSVOnqUvQa8scv1Lsr3xoAMMworcYW3J13z3ppjBFBD2ef1Ci6AE5Qn8goQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + +"@inquirer/password@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/password/-/password-3.0.1.tgz#2a9a9143591088336bbd573bcb05d5bf080dbf87" + integrity sha512-haoeEPUisD1NeE2IanLOiFr4wcTXGWrBOyAyPZi1FfLJuXOzNmxCJPgUrGYKVh+Y8hfGJenIfz5Wb/DkE9KkMQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + +"@inquirer/prompts@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/prompts/-/prompts-6.0.1.tgz#43f5c0ed35c5ebfe52f1d43d46da2d363d950071" + integrity sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A== + dependencies: + "@inquirer/checkbox" "^3.0.1" + "@inquirer/confirm" "^4.0.1" + "@inquirer/editor" "^3.0.1" + "@inquirer/expand" "^3.0.1" + "@inquirer/input" "^3.0.1" + "@inquirer/number" "^2.0.1" + "@inquirer/password" "^3.0.1" + "@inquirer/rawlist" "^3.0.1" + "@inquirer/search" "^2.0.1" + "@inquirer/select" "^3.0.1" + +"@inquirer/rawlist@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/rawlist/-/rawlist-3.0.1.tgz#729def358419cc929045f264131878ed379e0af3" + integrity sha512-VgRtFIwZInUzTiPLSfDXK5jLrnpkuSOh1ctfaoygKAdPqjcjKYmGh6sCY1pb0aGnCGsmhUxoqLDUAU0ud+lGXQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/search@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/search/-/search-2.0.1.tgz#69b774a0a826de2e27b48981d01bc5ad81e73721" + integrity sha512-r5hBKZk3g5MkIzLVoSgE4evypGqtOannnB3PKTG9NRZxyFRKcfzrdxXXPcoJQsxJPzvdSU2Rn7pB7lw0GCmGAg== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/select@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/select/-/select-3.0.1.tgz#1df9ed27fb85a5f526d559ac5ce7cc4e9dc4e7ec" + integrity sha512-lUDGUxPhdWMkN/fHy1Lk7pF3nK1fh/gqeyWXmctefhxLYxlDsc7vsPBEpxrfVGDsVdyYJsiJoD4bJ1b623cV1Q== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + yoctocolors-cjs "^2.1.2" + +"@inquirer/type@^1.5.5": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-1.5.5.tgz#303ea04ce7ad2e585b921b662b3be36ef7b4f09b" + integrity sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA== + dependencies: + mute-stream "^1.0.0" + +"@inquirer/type@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-2.0.0.tgz#08fa513dca2cb6264fe1b0a2fabade051444e3f6" + integrity sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag== + dependencies: + mute-stream "^1.0.0" + "@inquirer/type@^3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-3.0.2.tgz#baff9f8d70947181deb36772cd9a5b6876d3e60c" @@ -1653,6 +1798,13 @@ yargs "16.2.0" yargs-parser "20.2.4" +"@listr2/prompt-adapter-inquirer@^2.0.22": + version "2.0.22" + resolved "https://registry.yarnpkg.com/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.22.tgz#95f7730de62089be79a87a80aa333f5f4644f3c5" + integrity sha512-hV36ZoY+xKL6pYOt1nPNnkciFkn89KZwqLhAFzJvYysAvL5uBQdiADZx/8bIDXIukzzwG0QlPYolgMzQUtKgpQ== + dependencies: + "@inquirer/type" "^1.5.5" + "@malept/cross-spawn-promise@^1.0.0", "@malept/cross-spawn-promise@^1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz#504af200af6b98e198bce768bc1730c6936ae01d" @@ -3131,6 +3283,13 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== +"@types/mute-stream@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@types/mute-stream/-/mute-stream-0.0.4.tgz#77208e56a08767af6c5e1237be8888e2f255c478" + integrity sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow== + dependencies: + "@types/node" "*" + "@types/node-fetch@^2.5.5": version "2.5.12" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.12.tgz#8a6f779b1d4e60b7a57fb6fd48d84fb545b9cc66" @@ -3153,6 +3312,13 @@ dependencies: undici-types "~5.26.4" +"@types/node@^22.5.5": + version "22.17.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.17.2.tgz#47a93d6f4b79327da63af727e7c54e8cab8c4d33" + integrity sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w== + dependencies: + undici-types "~6.21.0" + "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -3261,6 +3427,11 @@ resolved "https://registry.yarnpkg.com/@types/which/-/which-2.0.1.tgz#27ecd67f915b7c3d6ba552135bb1eecd66e63501" integrity sha512-Jjakcv8Roqtio6w1gr0D7y6twbhx6gGgFGF5BLwajPpnOIOxFkakFhCq+LmyyeAz7BX6ULrjBOxdKaCDy+4+dQ== +"@types/wrap-ansi@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" + integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g== + "@types/ws@*": version "8.2.2" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21" @@ -6751,7 +6922,7 @@ extend@^3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -external-editor@^3.0.3: +external-editor@^3.0.3, external-editor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== @@ -13373,6 +13544,11 @@ undici-types@~6.20.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + unique-filename@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" From cc3dbadc8f9a8e815edc34f6fec7ee1fa8ab4e2f Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Wed, 27 Aug 2025 17:41:29 -0700 Subject: [PATCH 07/26] build(deps): use `@vscode/sudo-prompt` fork (#3983) --- package.json | 2 +- packages/api/core/package.json | 2 +- packages/api/core/src/util/linux-installer.ts | 4 ++-- typings/sudo-prompt/index.d.ts | 2 +- yarn.lock | 5 +++++ 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1eb4a78ab4..cd3cd07c43 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@octokit/request-error": "^5.1.1", "@octokit/rest": "^20.1.2", "@octokit/types": "^6.1.2", + "@vscode/sudo-prompt": "^9.3.1", "chalk": "^4.0.0", "commander": "^11.1.0", "cross-spawn": "^7.0.3", @@ -71,7 +72,6 @@ "rechoir": "^0.8.0", "semver": "^7.2.1", "source-map-support": "^0.5.13", - "sudo-prompt": "^9.1.1", "username": "^5.1.0", "vite": "^5.0.12", "webpack": "^5.69.1", diff --git a/packages/api/core/package.json b/packages/api/core/package.json index f3a04679f5..7c80679513 100644 --- a/packages/api/core/package.json +++ b/packages/api/core/package.json @@ -60,7 +60,7 @@ "rechoir": "^0.8.0", "semver": "^7.2.1", "source-map-support": "^0.5.13", - "sudo-prompt": "^9.1.1", + "@vscode/sudo-prompt": "^9.3.1", "username": "^5.1.0" }, "engines": { diff --git a/packages/api/core/src/util/linux-installer.ts b/packages/api/core/src/util/linux-installer.ts index eb07559ae6..0d5ac7b091 100644 --- a/packages/api/core/src/util/linux-installer.ts +++ b/packages/api/core/src/util/linux-installer.ts @@ -1,12 +1,12 @@ import { spawnSync } from 'node:child_process'; import { promisify } from 'node:util'; -import sudoPrompt from 'sudo-prompt'; +import sudoPrompt from '@vscode/sudo-prompt'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any const which = async ( type: string, prog: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any promise: () => Promise, ): Promise => { if (spawnSync('which', [prog]).status === 0) { diff --git a/typings/sudo-prompt/index.d.ts b/typings/sudo-prompt/index.d.ts index f0e371d618..2f8074e049 100644 --- a/typings/sudo-prompt/index.d.ts +++ b/typings/sudo-prompt/index.d.ts @@ -2,7 +2,7 @@ import { PromiseWithChild } from 'node:child_process'; // Copied from https://github.com/jorangreef/sudo-prompt/pull/124 // TODO: Remove this if/when that PR gets merged/released -declare module 'sudo-prompt' { +declare module '@vscode/sudo-prompt' { namespace exec { function __promisify__( command: string, diff --git a/yarn.lock b/yarn.lock index 0ffc7ab33d..6a9ffbad10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3629,6 +3629,11 @@ resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.10.tgz#9c513107c690c0dd16e3ec61e453743de15ebdb0" integrity sha512-E1OCmDcDWa0Ya7vtSjp/XfHFGqYJfh+YPC1RkATU71fTac+j1JjCcB3qwSzmlKAighx2WxhLlfhS0RwAN++PFQ== +"@vscode/sudo-prompt@^9.3.1": + version "9.3.1" + resolved "https://registry.yarnpkg.com/@vscode/sudo-prompt/-/sudo-prompt-9.3.1.tgz#c562334bc6647733649fd42afc96c0eea8de3b65" + integrity sha512-9ORTwwS74VaTn38tNbQhsA5U44zkJfcb0BdTSyyG6frP4e8KMtHuTXYmwefe5dpL8XB1aGSIVTaLjD3BbWb5iA== + "@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": version "1.12.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" From 3149688c553cc28f1577b9ec50976b25ea8e6f0b Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Wed, 27 Aug 2025 17:41:34 -0700 Subject: [PATCH 08/26] chore: remove `.editorconfig` (#3981) --- .editorconfig | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 261157616f..0000000000 --- a/.editorconfig +++ /dev/null @@ -1,14 +0,0 @@ -root = true - -[{*.html,*.js,*.json,*.md,*.ts,*.yml,*eslintrc}] -indent_style = space -indent_size = 2 -insert_final_newline = true -max_line_length = 160 -trim_trailing_whitespace = true - -[*.sh] -indent_style = space -indent_size = 4 -insert_final_newline = true -trim_trailing_whitespace = true From 46ac369408f6c4f69b0aa218fd479191a0bed363 Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Thu, 28 Aug 2025 12:06:01 -0700 Subject: [PATCH 09/26] fix(plugin-vite): handle object-like inputs from rollup options in CLI output (#3982) fix(plugin-vite): parse object-like inputs from rollup options --- packages/plugin/vite/src/VitePlugin.ts | 32 ++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/plugin/vite/src/VitePlugin.ts b/packages/plugin/vite/src/VitePlugin.ts index cbd1f4edde..cb00e374cb 100644 --- a/packages/plugin/vite/src/VitePlugin.ts +++ b/packages/plugin/vite/src/VitePlugin.ts @@ -225,13 +225,35 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}.`); 'close' in x && typeof x.close === 'function'; + /** + * Rollup's `input` can be a string, an array of strings, or an object. + * This function converts the input to a string for the Forge CLI to consume. + * + * @see https://rollupjs.org/configuration-options/#input + */ + const parseInputOptionToString = (input: vite.Rollup.InputOption) => { + if (typeof input === 'string') { + return input; + } else if (Array.isArray(input)) { + return input.join(' '); + } else { + return Object.keys(input).join(' '); + } + }; + return task?.newListr( configs.map((userConfig) => { - const target = - (userConfig.build?.rollupOptions?.input || - (typeof userConfig.build?.lib !== 'boolean' && - userConfig.build?.lib?.entry)) ?? - ''; + let target = ''; + const input = userConfig.build?.rollupOptions?.input; + if (input) { + target = parseInputOptionToString(input); + } else if ( + typeof userConfig.build?.lib !== 'boolean' && + userConfig.build?.lib?.entry + ) { + target = parseInputOptionToString(userConfig.build.lib.entry); + } + return { title: `Building ${chalk.green(target)} target`, task: async (_ctx, subtask) => { From d862ecafe6a33d9096bcbc61208b65180c745bf4 Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Thu, 28 Aug 2025 16:05:45 -0700 Subject: [PATCH 10/26] test: increase test timeout (#3984) spec: increase test timeout --- vitest.workspace.mts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vitest.workspace.mts b/vitest.workspace.mts index 11b25a5b03..83454678c2 100644 --- a/vitest.workspace.mts +++ b/vitest.workspace.mts @@ -14,8 +14,8 @@ export default defineWorkspace([ test: { include: ['**/spec/**/*.slow.spec.ts'], name: 'slow', - hookTimeout: 120000, - testTimeout: 120000, + hookTimeout: 160000, + testTimeout: 160000, }, }, ]); From 780db79d6580bbe1e0ba64087ceaba17760dd7a9 Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Fri, 29 Aug 2025 18:20:33 -0700 Subject: [PATCH 11/26] chore: remove stale lockfile entry (#3986) --- yarn.lock | 5 ----- 1 file changed, 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6a9ffbad10..36eb31772b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12941,11 +12941,6 @@ stubs@^3.0.0: resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b" integrity sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw== -sudo-prompt@^9.1.1: - version "9.2.1" - resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.2.1.tgz#77efb84309c9ca489527a4e749f287e6bdd52afd" - integrity sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw== - sumchecker@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" From f7433c9f3939e789cffd14bbf817f0d3b69a4d7e Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Wed, 3 Sep 2025 14:24:36 -0700 Subject: [PATCH 12/26] build(deps): update `form-data` (#3991) * build(deps): update `form-data` * kick ci --- yarn.lock | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/yarn.lock b/yarn.lock index 36eb31772b..81a1d0c5ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4970,7 +4970,7 @@ columnify@1.6.0: strip-ansi "^6.0.1" wcwidth "^1.0.0" -combined-stream@^1.0.6, combined-stream@^1.0.8: +combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -7218,22 +7218,27 @@ fork-ts-checker-webpack-plugin@^7.2.13: tapable "^2.2.1" form-data@^2.5.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" - integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== + version "2.5.5" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.5.tgz#a5f6364ad7e4e67e95b4a07e2d8c6f711c74f624" + integrity sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A== dependencies: asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.35" + safe-buffer "^5.2.1" form-data@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" - integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + version "3.0.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.4.tgz#938273171d3f999286a4557528ce022dc2c98df1" + integrity sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" - mime-types "^2.1.12" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.35" form-data@^4.0.0: version "4.0.4" @@ -10016,7 +10021,7 @@ mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.25, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.25, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@^2.1.35, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -12108,7 +12113,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== From 7a0a62f45df13c047bca5532f0d24952ffae88d4 Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Thu, 4 Sep 2025 13:13:38 -0700 Subject: [PATCH 13/26] ci: migrate back to GHA (#3992) * GHA * don't cache yarn * python 3.11 * update libasound2t64 * attempt some fixes * zzz * can i get rid of this? * swap out ubuntu-latest * Revert "update libasound2t64" This reverts commit 474335ef2e326c2a8853ba6ddeefc90485e246c8. * back to libasound2 * node-abi upgrade * slight cleanup * remove circleci config * needs --- .circleci/config.yml | 208 ------------------------------------- .github/workflows/ci.yml | 218 +++++++++++++++++++++++++++++++++++++++ yarn.lock | 6 +- 3 files changed, 221 insertions(+), 211 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .github/workflows/ci.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 1fdd399ce8..0000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,208 +0,0 @@ -version: 2.1 - -orbs: - node: electronjs/node@2.3.1 - -executors: - linux-medium-plus: - docker: - - image: cimg/base:stable - resource_class: medium+ - -commands: - install: - steps: - - run: git config --global core.autocrlf input - - node/install: - node-version: 20.17.0 - - checkout - run-lint-and-build: - steps: - - node/install-packages - - run: - name: 'Build Electron Forge' - command: | - yarn build - - restore_cache: - name: Restore ESLint Cache - keys: - - v1-lint-dependencies-{{ checksum "yarn.lock" }}-{{ checksum ".eslintrc.json" }} - - run: - name: 'Lint codebase' - command: | - yarn lint - yarn syncpack - - save_cache: - name: Save ESlint Cache - paths: - - .eslintcache - key: v1-lint-dependencies-{{ checksum "yarn.lock" }}-{{ checksum ".eslintrc.json" }} - run-fast-tests: - steps: - - node/install-packages: - with-cache: false - - attach_workspace: - at: . - - run: - name: 'Run fast tests' - command: | - yarn test:fast --reporter=default --reporter=junit --outputFile="./reports/out/test_output.xml" - - run-slow-tests: - steps: - - node/install-packages: - with-cache: false - - attach_workspace: - at: . - - run: - name: 'Run slow tests' - command: | - yarn test:slow --reporter=default --reporter=junit --outputFile="./reports/out/test_output.xml" - -jobs: - lint-and-build: - executor: node/linux - steps: - - install - - run-lint-and-build - - persist_to_workspace: - root: . - paths: - - packages/*/*/dist/* - - fast-tests: - parameters: - executor: - type: string - arch: - type: enum - enum: ['x64', 'arm64'] - executor: << parameters.executor >> - steps: - - install - - when: - condition: - equal: [node/windows, << parameters.executor >>] - steps: - - run: - name: Windows Setup - shell: bash - command: | - pip install setuptools - cd 'C:\Program Files\nodejs\node_modules\npm\node_modules\@npmcli\run-script' - npm install node-gyp@9.4.0 - - when: - condition: - equal: [node/linux, << parameters.executor >>] - steps: - - run: - name: Linux specific setup - command: | - sudo apt-get update - sudo apt-get install -y \ - --no-install-recommends \ - xvfb \ - libnss3 \ - libasound2 \ - libatk1.0-0 \ - libatk-bridge2.0-0 \ - libgdk-pixbuf2.0-dev \ - libgtk-3-0 \ - libgbm1 - sudo add-apt-repository -y ppa:alexlarsson/flatpak - - run: - name: 'Install pnpm' - command: | - npm install -g pnpm@10.0.0 - - run-fast-tests - - store_test_results: - path: ./reports/ - slow-tests: - parameters: - executor: - type: string - arch: - type: enum - enum: ['x64', 'arm64'] - executor: << parameters.executor >> - parallelism: 1 # disabled due to being on CircleCI's free plan - steps: - - install - - when: - condition: - equal: [node/windows, << parameters.executor >>] - steps: - - run: - name: Windows Setup - shell: bash - command: | - pip install setuptools - choco install --no-progress -y wixtoolset --version=3.14.1 - echo 'export PATH=$PATH:"/C/Program Files (x86)/WiX Toolset v3.14/bin"' >> "$BASH_ENV" - cd 'C:\Program Files\nodejs\node_modules\npm\node_modules\@npmcli\run-script' - npm install node-gyp@9.4.0 - - when: - condition: - equal: [linux-medium-plus, << parameters.executor >>] - steps: - - run: - name: Linux specific setup - command: | - sudo apt-get update - sudo apt-get install -y \ - --no-install-recommends \ - xvfb \ - libnss3 \ - libasound2 \ - libatk1.0-0 \ - libatk-bridge2.0-0 \ - libgdk-pixbuf2.0-dev \ - libgtk-3-0 \ - libgbm1 - - run: - name: 'Install pnpm' - command: | - npm install -g pnpm@10.0.0 - - run-slow-tests - - run: - when: always # the report is generated on pass or fail - name: Make test report paths relative - command: | - if [ -e ./reports/out/test_output.xml ]; then - sed -i.bak 's/"[^"]*packages/"packages/' ./reports/out/test_output.xml - fi - - store_test_results: - path: ./reports/ - -workflows: - tests: - jobs: - - lint-and-build - - fast-tests: - requires: - - lint-and-build - matrix: - parameters: - executor: [node/windows, node/linux, node/macos] - arch: [x64, arm64] - exclude: - - executor: node/windows - arch: arm64 - - executor: node/linux - arch: arm64 - - executor: node/macos - arch: x64 - - slow-tests: - requires: - - lint-and-build - matrix: - parameters: - executor: [node/windows, linux-medium-plus, node/macos] - arch: [x64, arm64] - exclude: - - executor: node/windows - arch: arm64 - - executor: linux-medium-plus - arch: arm64 - - executor: node/macos - arch: x64 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..60b5afe078 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,218 @@ +name: CI + +on: + push: + branches: + - main + tags: + - v[0-9]+.[0-9]+.[0-9]+* + pull_request: + +env: + NODE_VERSION: 20.17.0 + +jobs: + lint-and-build: + runs-on: ubuntu-22.04 + steps: + - name: Configure git + run: git config --global core.autocrlf input + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: ${{ env.NODE_VERSION }} + # cache: yarn + + # TODO(Forge 8): remove this once we can upgrade to `@electron/rebuild` v4 + - name: Set up Python 3.11 (with distutils) + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: 3.11 + + - name: Checkout code + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Build Electron Forge + run: yarn build + + - name: Cache ESLint + uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + with: + path: .eslintcache + key: v1-lint-dependencies-${{ hashFiles('yarn.lock') }}-${{ hashFiles('.eslintrc.json') }} + + - name: Lint codebase + run: | + yarn lint + yarn syncpack + + - name: Upload build artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: dist-files + path: packages/*/*/dist/* + + fast-tests: + needs: lint-and-build + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-22.04, macos-latest] + arch: [x64, arm64] + exclude: + - os: windows-latest + arch: arm64 + - os: ubuntu-22.04 + arch: arm64 + - os: macos-latest + arch: x64 + + runs-on: ${{ matrix.os }} + + steps: + - name: Configure git + run: git config --global core.autocrlf input + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: ${{ env.NODE_VERSION }} + # cache: yarn + + # TODO(Forge 8): remove this once we can upgrade to `@electron/rebuild` v4 + - name: Set up Python 3.11 (with distutils) + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: 3.11 + + - name: Checkout code + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Download build artifacts + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + name: dist-files + path: packages + + - name: Windows setup + if: runner.os == 'Windows' + shell: bash + run: | + cd "$PROGRAMFILES/nodejs/node_modules/npm/node_modules/@npmcli/run-script" + npm install node-gyp@9.4.0 + + - name: Linux setup + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y \ + --no-install-recommends \ + xvfb \ + libnss3 \ + libasound2 \ + libatk1.0-0 \ + libatk-bridge2.0-0 \ + libgdk-pixbuf2.0-dev \ + libgtk-3-0 \ + libgbm1 + sudo add-apt-repository -y ppa:alexlarsson/flatpak + + - name: Install pnpm + run: npm install -g pnpm@10.0.0 + + - name: Run fast tests + run: | + mkdir -p ./reports/out + yarn test:fast --reporter=default --reporter=junit --outputFile="./reports/out/test_output.xml" + + slow-tests: + needs: lint-and-build + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-22.04, macos-latest] + arch: [x64, arm64] + exclude: + - os: windows-latest + arch: arm64 + - os: ubuntu-22.04 + arch: arm64 + - os: macos-latest + arch: x64 + + runs-on: ${{ matrix.os }} + + steps: + - name: Configure git + run: git config --global core.autocrlf input + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: ${{ env.NODE_VERSION }} + # cache: yarn + + # TODO(Forge 8): remove this once we can upgrade to `@electron/rebuild` v4 + - name: Set up Python 3.11 (with distutils) + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: 3.11 + + - name: Checkout code + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Download build artifacts + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + name: dist-files + path: packages + + - name: Windows setup + if: runner.os == 'Windows' + shell: bash + run: | + cd "$PROGRAMFILES/nodejs/node_modules/npm/node_modules/@npmcli/run-script" + npm install node-gyp@9.4.0 + + - name: Linux setup + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y \ + --no-install-recommends \ + xvfb \ + libnss3 \ + libasound2 \ + libatk1.0-0 \ + libatk-bridge2.0-0 \ + libgdk-pixbuf2.0-dev \ + libgtk-3-0 \ + libgbm1 + + - name: Install pnpm + run: npm install -g pnpm@10.0.0 + + - name: Run slow tests + run: | + mkdir -p ./reports/out + yarn test:slow --reporter=default --reporter=junit --outputFile="./reports/out/test_output.xml" + + required-ci: + needs: + - lint-and-build + - fast-tests + - slow-tests + runs-on: ubuntu-latest + steps: + - name: We are done + run: echo "Hello World" diff --git a/yarn.lock b/yarn.lock index 81a1d0c5ff..abf443c9db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10401,9 +10401,9 @@ no-case@^3.0.4: tslib "^2.0.3" node-abi@^3.45.0: - version "3.75.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.75.0.tgz#2f929a91a90a0d02b325c43731314802357ed764" - integrity sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg== + version "3.76.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.76.0.tgz#b6113facd47ba4ad41e9f7e320f4d23338039948" + integrity sha512-vxPAUdXYJ2Vv8bUWSpbC/A4ltEdMYO4ruzMXrb99BbZz0IMmrt6xLhYirTIhOMk2dpsbjby+PZEhUkHIeMXM4g== dependencies: semver "^7.3.5" From f3919035773590779f5848187114da18d014762d Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Thu, 4 Sep 2025 16:20:41 -0700 Subject: [PATCH 14/26] ci: add tests to merge queue (#3996) --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60b5afe078..3414bd3d00 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,8 @@ on: tags: - v[0-9]+.[0-9]+.[0-9]+* pull_request: + merge_group: + types: [checks_requested] env: NODE_VERSION: 20.17.0 From 7adb9adb2947a5651af48bc519955a83f1111755 Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Thu, 4 Sep 2025 17:09:36 -0700 Subject: [PATCH 15/26] docs: deprecate CHANGELOG.md (#3995) --- CHANGELOG.md | 6 ++++++ README.md | 3 +-- ci/fix-changelog.js | 27 --------------------------- package.json | 1 - yarn.lock | 18 ++---------------- 5 files changed, 9 insertions(+), 46 deletions(-) delete mode 100644 ci/fix-changelog.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 518e6acb1a..56ddbf5522 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# Changelog + +The changelog for subsequent updates is available for each release independently on this repo's +[GitHub Releases](https://github.com/electron/forge/releases) page. This document is preserved +for historical purposes. + #### [6.0.2](https://github.com/electron/forge/releases/tag/v6.0.2) (2022-11-14) #### [6.0.1](https://github.com/electron/forge/releases/tag/v6.0.1) (2022-11-08) diff --git a/README.md b/README.md index 6fc24bd2fa..b5365989aa 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,7 @@ jump right in to Electron development. [Docs and Usage](#docs-and-usage) | [Configuration](https://www.electronforge.io/configuration) | [Support](https://github.com/electron/forge/blob/main/SUPPORT.md) | -[Contributing](https://github.com/electron/forge/blob/main/CONTRIBUTING.md) | -[Changelog](https://github.com/electron/forge/blob/main/CHANGELOG.md) +[Contributing](https://github.com/electron/forge/blob/main/CONTRIBUTING.md) --- diff --git a/ci/fix-changelog.js b/ci/fix-changelog.js deleted file mode 100644 index f5a0fc886b..0000000000 --- a/ci/fix-changelog.js +++ /dev/null @@ -1,27 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const prettier = require('prettier'); - -const changelogPath = path.resolve(__dirname, '..', 'CHANGELOG.md'); - -const changelog = fs.readFileSync(changelogPath, 'utf8'); - -const fixedChangelog = changelog - .replace( - /\(([A-Za-z0-9]{8})\)/g, - (match, commitID) => - `([${commitID}](https://github.com/electron/forge/commit/${commitID}))`, - ) - .replace( - /# ([0-9]+\.[0-9]+\.[0-9]+(?:-[a-z]+.[0-9]+)?) /g, - (match, version) => - `# [${version}](https://github.com/electron/forge/releases/tag/v${version}) `, - ); - -fs.writeFileSync( - changelogPath, - prettier.format(fixedChangelog, { - parser: 'markdown', - }), -); diff --git a/package.json b/package.json index cd3cd07c43..f2c155da8e 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,6 @@ "eslint-plugin-promise": "^7.2.1", "eslint-plugin-tsdoc": "^0.4.0", "fork-ts-checker-webpack-plugin": "^7.2.13", - "generate-changelog": "^1.8.0", "husky": "^7.0.1", "inquirer": "^8.0.0", "lerna": "^7.4.2", diff --git a/yarn.lock b/yarn.lock index abf443c9db..e96e7d9402 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4371,7 +4371,7 @@ bl@^4.0.3, bl@^4.1.0: inherits "^2.0.4" readable-stream "^3.4.0" -bluebird@^3.0.6, bluebird@^3.1.1: +bluebird@^3.1.1: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -4987,7 +4987,7 @@ commander@^11.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906" integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ== -commander@^2.19.0, commander@^2.20.0, commander@^2.9.0: +commander@^2.19.0, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -7493,15 +7493,6 @@ gcp-metadata@^6.0.0: gaxios "^6.0.0" json-bigint "^1.0.0" -generate-changelog@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/generate-changelog/-/generate-changelog-1.8.0.tgz#1d788cdef5a13a649da7eb2a1f3b02674850e4a8" - integrity sha512-msgpxeB75Ziyg3wGsZuPNl7c5RxChMKmYcAX5obnhUow90dBZW3nLic6nxGtst7Bpx453oS6zAIHcX7F3QVasw== - dependencies: - bluebird "^3.0.6" - commander "^2.9.0" - github-url-from-git "^1.4.0" - generate-function@^2.0.0: version "2.3.1" resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" @@ -7705,11 +7696,6 @@ gitconfiglocal@^1.0.0: dependencies: ini "^1.3.2" -github-url-from-git@^1.4.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/github-url-from-git/-/github-url-from-git-1.5.0.tgz#f985fedcc0a9aa579dc88d7aff068d55cc6251a0" - integrity sha1-+YX+3MCpqledyI16/waNVcxiUaA= - glob-parent@5.1.2, glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" From a1d893239c5698c4cd0aa92d638fdd0625b8df9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 17:09:43 -0700 Subject: [PATCH 16/26] build(deps-dev): bump electron from 34.0.0 to 35.7.5 in /packages/plugin/fuses/spec/fixture/app (#3994) build(deps-dev): bump electron Bumps [electron](https://github.com/electron/electron) from 34.0.0 to 35.7.5. - [Release notes](https://github.com/electron/electron/releases) - [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md) - [Commits](https://github.com/electron/electron/compare/v34.0.0...v35.7.5) --- updated-dependencies: - dependency-name: electron dependency-version: 35.7.5 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Erick Zhao --- packages/plugin/fuses/spec/fixture/app/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin/fuses/spec/fixture/app/package.json b/packages/plugin/fuses/spec/fixture/app/package.json index e8821a5ce4..4dd149db47 100644 --- a/packages/plugin/fuses/spec/fixture/app/package.json +++ b/packages/plugin/fuses/spec/fixture/app/package.json @@ -13,6 +13,6 @@ "@electron-forge/cli": "6.4.0", "@electron-forge/plugin-fuses": "6.4.0", "@electron-forge/shared-types": "6.4.0", - "electron": "34.0.0" + "electron": "35.7.5" } } From 7e99ef9fa846729526cd8a7d73413c9282c581e8 Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Thu, 4 Sep 2025 17:09:53 -0700 Subject: [PATCH 17/26] feat(plugin-vite): add `concurrency` option (#3990) * feat(plugin-vite): allow users to limit build concurrency * fix config env --- packages/plugin/vite/forge-vite-env.d.ts | 4 +--- packages/plugin/vite/src/Config.ts | 9 +++++++++ packages/plugin/vite/src/VitePlugin.ts | 5 ++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/plugin/vite/forge-vite-env.d.ts b/packages/plugin/vite/forge-vite-env.d.ts index c99aab1ee6..59074dc18a 100644 --- a/packages/plugin/vite/forge-vite-env.d.ts +++ b/packages/plugin/vite/forge-vite-env.d.ts @@ -15,9 +15,7 @@ declare global { } declare module 'vite' { - interface ConfigEnv< - K extends keyof VitePluginConfig = keyof VitePluginConfig, - > { + interface ConfigEnv { root: string; forgeConfig: VitePluginConfig; forgeConfigSelf: VitePluginConfig[K][number]; diff --git a/packages/plugin/vite/src/Config.ts b/packages/plugin/vite/src/Config.ts index 8aba1b9299..b7cd6847be 100644 --- a/packages/plugin/vite/src/Config.ts +++ b/packages/plugin/vite/src/Config.ts @@ -40,4 +40,13 @@ export interface VitePluginConfig { * Renderer process Vite configs. */ renderer: VitePluginRendererConfig[]; + + /** + * Run builds concurrently. If a boolean is provided, targets specified in the {@link build} and {@link renderer} + * configurations will be run concurrently. If a number is provided, it will limit the number of concurrent builds. + * + * Limit concurrency if you are running into memory constraints when packaging. + * @defaultValue `true` + */ + concurrent?: boolean | number; } diff --git a/packages/plugin/vite/src/VitePlugin.ts b/packages/plugin/vite/src/VitePlugin.ts index cb00e374cb..fd88d542a8 100644 --- a/packages/plugin/vite/src/VitePlugin.ts +++ b/packages/plugin/vite/src/VitePlugin.ts @@ -305,7 +305,7 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}.`); }; }), { - concurrent: true, + concurrent: this.config.concurrent ?? true, }, ); }; @@ -324,6 +324,9 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}.`); subtask.title = `Built target ${chalk.dim(path.basename(userConfig.build?.outDir ?? ''))}`; }, })), + { + concurrent: this.config.concurrent ?? true, + }, ); }; From ffe7712c4249371ea0c7399157aa9ec1d71a10c8 Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Thu, 4 Sep 2025 17:09:59 -0700 Subject: [PATCH 18/26] fix(plugin-vite): support `electron/*` subpath imports (#3988) * fix(plugin-vite): support `electron/main` and `electron/renderer` import paths * common too! * tests * kick ci --- packages/plugin/vite/spec/ViteConfig.spec.ts | 10 ++++++++-- packages/plugin/vite/src/config/vite.base.config.ts | 1 + packages/plugin/vite/src/config/vite.main.config.ts | 2 +- packages/plugin/vite/src/config/vite.preload.config.ts | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/plugin/vite/spec/ViteConfig.spec.ts b/packages/plugin/vite/spec/ViteConfig.spec.ts index 04dfc07c69..76069df91d 100644 --- a/packages/plugin/vite/spec/ViteConfig.spec.ts +++ b/packages/plugin/vite/spec/ViteConfig.spec.ts @@ -41,7 +41,10 @@ describe('ViteConfigGenerator', () => { expect(buildConfig.build?.lib && buildConfig.build.lib.formats).toEqual([ 'cjs', ]); - expect(buildConfig.build?.rollupOptions?.external).toEqual(external); + expect(buildConfig.build?.rollupOptions?.external).toEqual([ + ...external, + 'electron/main', + ]); expect(buildConfig.clearScreen).toBe(false); expect( buildConfig.plugins?.map((plugin) => (plugin as Plugin).name), @@ -73,7 +76,10 @@ describe('ViteConfigGenerator', () => { expect(buildConfig.build?.outDir).toEqual('.vite/build'); expect(buildConfig.build?.watch).toBeNull(); expect(buildConfig.build?.minify).toBe(true); - expect(buildConfig.build?.rollupOptions?.external).toEqual(external); + expect(buildConfig.build?.rollupOptions?.external).toEqual([ + ...external, + 'electron/renderer', + ]); expect(buildConfig.build?.rollupOptions?.input).toEqual('src/preload.js'); expect(buildConfig.build?.rollupOptions?.output).toEqual({ format: 'cjs', diff --git a/packages/plugin/vite/src/config/vite.base.config.ts b/packages/plugin/vite/src/config/vite.base.config.ts index cdeb18b524..53f0933e78 100644 --- a/packages/plugin/vite/src/config/vite.base.config.ts +++ b/packages/plugin/vite/src/config/vite.base.config.ts @@ -5,6 +5,7 @@ import type { ConfigEnv, Plugin, UserConfig, ViteDevServer } from 'vite'; export const builtins = [ 'electron', + 'electron/common', ...builtinModules.map((m) => [m, `node:${m}`]).flat(), ]; diff --git a/packages/plugin/vite/src/config/vite.main.config.ts b/packages/plugin/vite/src/config/vite.main.config.ts index b6fd30e195..04ad52527f 100644 --- a/packages/plugin/vite/src/config/vite.main.config.ts +++ b/packages/plugin/vite/src/config/vite.main.config.ts @@ -17,7 +17,7 @@ export function getConfig( build: { copyPublicDir: false, rollupOptions: { - external, + external: [...external, 'electron/main'], }, }, plugins: [pluginHotRestart('restart')], diff --git a/packages/plugin/vite/src/config/vite.preload.config.ts b/packages/plugin/vite/src/config/vite.preload.config.ts index c5b219eb59..6b81930e8b 100644 --- a/packages/plugin/vite/src/config/vite.preload.config.ts +++ b/packages/plugin/vite/src/config/vite.preload.config.ts @@ -11,7 +11,7 @@ export function getConfig( build: { copyPublicDir: false, rollupOptions: { - external, + external: [...external, 'electron/renderer'], // Preload scripts may contain Web assets, so use the `build.rollupOptions.input` instead `build.lib.entry`. input: forgeConfigSelf.entry, output: { From 4f2ba7d14f425b472462cfb23b8cfdb0992c055b Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Thu, 4 Sep 2025 18:49:37 -0700 Subject: [PATCH 19/26] feat: improve Yarn Berry support (#3997) --- .../api/cli/spec/util/check-system.spec.ts | 21 ++++++ packages/api/cli/src/util/check-system.ts | 21 ++++++ packages/template/base/package.json | 1 + packages/template/base/src/BaseTemplate.ts | 8 +++ packages/template/base/tmpl/_yarnrc | 1 + .../core-utils/spec/package-manager.spec.ts | 56 +++++++++------ .../utils/core-utils/src/package-manager.ts | 68 +++++++++++++------ 7 files changed, 132 insertions(+), 44 deletions(-) create mode 100644 packages/template/base/tmpl/_yarnrc diff --git a/packages/api/cli/spec/util/check-system.spec.ts b/packages/api/cli/spec/util/check-system.spec.ts index dd839347b1..8d57d1731c 100644 --- a/packages/api/cli/spec/util/check-system.spec.ts +++ b/packages/api/cli/spec/util/check-system.spec.ts @@ -87,6 +87,27 @@ describe('checkPackageManager', () => { ); }); + it('should throw if using yarn without node-linker=node-modules', async () => { + vi.mocked(resolvePackageManager).mockResolvedValue({ + executable: 'yarn', + install: 'add', + dev: '--dev', + exact: '--exact', + }); + vi.mocked(spawnPackageManager).mockImplementation((_pm, args) => { + if (args?.join(' ') === 'config get nodeLinker') { + return Promise.resolve('isolated'); + } else if (args?.join(' ') === '--version') { + return Promise.resolve('4.1.0'); + } else { + throw new Error('Unexpected command'); + } + }); + await expect(checkPackageManager()).rejects.toThrow( + 'When using Yarn 2+, `nodeLinker` must be set to "node-modules". Run `yarn config set nodeLinker node-modules` to set this config value, or add it to your project\'s `.yarnrc` file.', + ); + }); + it.each(['hoist-pattern', 'public-hoist-pattern'])( 'should pass without validation if user has set %s in their pnpm config', async (cfg) => { diff --git a/packages/api/cli/src/util/check-system.ts b/packages/api/cli/src/util/check-system.ts index c5ba467a65..c63982bc83 100644 --- a/packages/api/cli/src/util/check-system.ts +++ b/packages/api/cli/src/util/check-system.ts @@ -68,6 +68,25 @@ async function checkPnpmConfig() { } } +async function checkYarnConfig() { + const { yarn } = PACKAGE_MANAGERS; + const yarnVersion = await spawnPackageManager(yarn, ['--version']); + const nodeLinker = await spawnPackageManager(yarn, [ + 'config', + 'get', + 'nodeLinker', + ]); + if ( + yarnVersion && + semver.gte(yarnVersion, '2.0.0') && + nodeLinker !== 'node-modules' + ) { + throw new Error( + 'When using Yarn 2+, `nodeLinker` must be set to "node-modules". Run `yarn config set nodeLinker node-modules` to set this config value, or add it to your project\'s `.yarnrc` file.', + ); + } +} + // TODO(erickzhao): Drop antiquated versions of npm for Forge v8 const ALLOWLISTED_VERSIONS: Record< SupportedPackageManager, @@ -108,6 +127,8 @@ export async function checkPackageManager() { if (pm.executable === 'pnpm') { await checkPnpmConfig(); + } else if (pm.executable === 'yarn') { + await checkYarnConfig(); } return `${pm.executable}@${versionString}`; diff --git a/packages/template/base/package.json b/packages/template/base/package.json index 1b0031a298..1f88525c58 100644 --- a/packages/template/base/package.json +++ b/packages/template/base/package.json @@ -16,6 +16,7 @@ "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.3.1", "fs-extra": "^10.0.0", + "semver": "^7.2.1", "username": "^5.1.0" }, "devDependencies": { diff --git a/packages/template/base/src/BaseTemplate.ts b/packages/template/base/src/BaseTemplate.ts index ace88f3eb5..8783b5246b 100644 --- a/packages/template/base/src/BaseTemplate.ts +++ b/packages/template/base/src/BaseTemplate.ts @@ -8,6 +8,7 @@ import { } from '@electron-forge/shared-types'; import debug from 'debug'; import fs from 'fs-extra'; +import semver from 'semver'; import determineAuthor from './determine-author'; @@ -71,6 +72,13 @@ export class BaseTemplate implements ForgeTemplate { if (pm.executable === 'pnpm') { rootFiles.push('_npmrc'); + } else if ( + // Support Yarn 2+ by default by initializing with nodeLinker: node-modules + pm.executable === 'yarn' && + pm.version && + semver.gte(pm.version, '2.0.0') + ) { + rootFiles.push('_yarnrc'); } if (copyCIFiles) { diff --git a/packages/template/base/tmpl/_yarnrc b/packages/template/base/tmpl/_yarnrc new file mode 100644 index 0000000000..8b757b29a1 --- /dev/null +++ b/packages/template/base/tmpl/_yarnrc @@ -0,0 +1 @@ +nodeLinker: node-modules \ No newline at end of file diff --git a/packages/utils/core-utils/spec/package-manager.spec.ts b/packages/utils/core-utils/spec/package-manager.spec.ts index 00126863d6..408ee7ae6d 100644 --- a/packages/utils/core-utils/spec/package-manager.spec.ts +++ b/packages/utils/core-utils/spec/package-manager.spec.ts @@ -17,15 +17,14 @@ vi.mock('find-up', async (importOriginal) => { }); describe('package-manager', () => { - describe('npm_config_user_agent', () => { - beforeAll(() => { - const originalUa = process.env.npm_config_user_agent; - - return () => { - process.env.npm_config_user_agent = originalUa; - }; - }); + beforeAll(() => { + const originalUa = process.env.npm_config_user_agent; + return () => { + process.env.npm_config_user_agent = originalUa; + }; + }); + describe('npm_config_user_agent', () => { it.each([ { ua: 'yarn/1.22.22 npm/? node/v22.13.0 darwin arm64', @@ -112,16 +111,22 @@ describe('package-manager', () => { 'should return $pm if NODE_INSTALLER=$pm', async ({ pm }) => { process.env.NODE_INSTALLER = pm; + vi.mocked(spawn).mockResolvedValue('9.9.9'); await expect(resolvePackageManager()).resolves.toHaveProperty( 'executable', pm, ); + await expect(resolvePackageManager()).resolves.toHaveProperty( + 'version', + '9.9.9', + ); }, ); it('should return npm if package manager is unsupported', async () => { process.env.NODE_INSTALLER = 'bun'; console.warn = vi.fn(); + vi.mocked(spawn).mockResolvedValue('1.22.22'); await expect(resolvePackageManager()).resolves.toHaveProperty( 'executable', 'npm', @@ -133,21 +138,10 @@ describe('package-manager', () => { }); }); - describe('spawnPackageManager', () => { - it('should trim the output', async () => { - vi.mocked(spawn).mockResolvedValue(' foo \n'); - const result = await spawnPackageManager({ - executable: 'npm', - install: 'install', - dev: '--save-dev', - exact: '--save-exact', - }); - expect(result).toBe('foo'); - }); - }); - it('should use the package manager for the nearest ancestor lockfile if detected', async () => { + delete process.env.npm_config_user_agent; vi.mocked(findUp).mockResolvedValue('/Users/foo/bar/yarn.lock'); + vi.mocked(spawn).mockResolvedValue('1.22.22'); await expect(resolvePackageManager()).resolves.toHaveProperty( 'executable', 'yarn', @@ -155,11 +149,29 @@ describe('package-manager', () => { }); it('should fall back to npm if no other strategy worked', async () => { - process.env.npm_config_user_agent = undefined; + delete process.env.npm_config_user_agent; vi.mocked(findUp).mockResolvedValue(undefined); + vi.mocked(spawn).mockResolvedValue('9.99.99'); await expect(resolvePackageManager()).resolves.toHaveProperty( 'executable', 'npm', ); + await expect(resolvePackageManager()).resolves.toHaveProperty( + 'version', + '9.99.99', + ); + }); + + describe('spawnPackageManager', () => { + it('should trim the output', async () => { + vi.mocked(spawn).mockResolvedValue(' foo \n'); + const result = await spawnPackageManager({ + executable: 'npm', + install: 'install', + dev: '--save-dev', + exact: '--save-exact', + }); + expect(result).toBe('foo'); + }); }); }); diff --git a/packages/utils/core-utils/src/package-manager.ts b/packages/utils/core-utils/src/package-manager.ts index 5bbeff83e2..660ccbf916 100644 --- a/packages/utils/core-utils/src/package-manager.ts +++ b/packages/utils/core-utils/src/package-manager.ts @@ -96,18 +96,43 @@ export const resolvePackageManager: () => Promise = async () => { const lockfileName = path.basename(lockfile); lockfilePM = PM_FROM_LOCKFILE[lockfileName]; } - const installer = - process.env.NODE_INSTALLER || executingPM?.name || lockfilePM; - // TODO(erickzhao): Remove NODE_INSTALLER environment variable for Forge 8 - if (typeof process.env.NODE_INSTALLER === 'string' && !hasWarned) { - console.warn( - logSymbols.warning, - chalk.yellow( - `The NODE_INSTALLER environment variable is deprecated and will be removed in Electron Forge v8`, - ), + let installer; + let installerVersion; + + if (typeof process.env.NODE_INSTALLER === 'string') { + if (Object.keys(PACKAGE_MANAGERS).includes(process.env.NODE_INSTALLER)) { + installer = process.env.NODE_INSTALLER; + installerVersion = await spawnPackageManager( + PACKAGE_MANAGERS[installer as SupportedPackageManager], + ['--version'], + ); + if (!hasWarned) { + console.warn( + logSymbols.warning, + chalk.yellow( + `The NODE_INSTALLER environment variable is deprecated and will be removed in Electron Forge v8`, + ), + ); + hasWarned = true; + } + } else { + console.warn( + logSymbols.warning, + chalk.yellow( + `Package manager ${chalk.red(process.env.NODE_INSTALLER)} is unsupported. Falling back to ${chalk.green('npm')} instead.`, + ), + ); + } + } else if (executingPM) { + installer = executingPM.name; + installerVersion = executingPM.version; + } else if (lockfilePM) { + installer = lockfilePM; + installerVersion = await spawnPackageManager( + PACKAGE_MANAGERS[installer as SupportedPackageManager], + ['--version'], ); - hasWarned = true; } switch (installer) { @@ -117,19 +142,18 @@ export const resolvePackageManager: () => Promise = async () => { d( `Resolved package manager to ${installer}. (Derived from NODE_INSTALLER: ${process.env.NODE_INSTALLER}, npm_config_user_agent: ${process.env.npm_config_user_agent}, lockfile: ${lockfilePM})`, ); - return { ...PACKAGE_MANAGERS[installer], version: executingPM?.version }; + return { + ...PACKAGE_MANAGERS[installer], + version: installerVersion, + }; default: - if (installer !== undefined) { - console.warn( - logSymbols.warning, - chalk.yellow( - `Package manager ${chalk.red(installer)} is unsupported. Falling back to ${chalk.green('npm')} instead.`, - ), - ); - } else { - d(`No package manager detected. Falling back to npm.`); - } - return PACKAGE_MANAGERS['npm']; + d(`No valid package manager detected. Falling back to npm.`); + return { + ...PACKAGE_MANAGERS['npm'], + version: await spawnPackageManager(PACKAGE_MANAGERS['npm'], [ + '--version', + ]), + }; } }; From ac87b6b845692219b75cda4baa54be714d56beb1 Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Fri, 5 Sep 2025 11:25:23 -0700 Subject: [PATCH 20/26] docs: update CI badge for GitHub Actions (#3999) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b5365989aa..f1b12d33a3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## Electron Forge -[![CircleCI](https://dl.circleci.com/status-badge/img/gh/electron/forge/tree/main.svg?style=shield)](https://dl.circleci.com/status-badge/redirect/gh/electron/forge/tree/main) +[![CI](https://github.com/electron/forge/actions/workflows/ci.yml/badge.svg)](https://github.com/electron/forge/actions/workflows/ci.yml) [![Discord](https://img.shields.io/discord/745037351163527189?color=blueviolet&logo=discord)](https://discord.com/invite/APGC3k5yaH) [![npm version](https://img.shields.io/npm/v/@electron-forge/cli)](https://npm.im/@electron-forge/cli) [![license](https://img.shields.io/github/license/electron/forge.svg)](https://github.com/electron/forge/blob/main/LICENSE) From 001f41befe2c049b6f54ce7d6c55e83435141055 Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Fri, 5 Sep 2025 15:12:54 -0700 Subject: [PATCH 21/26] fix(template-base): add `.yml` extension to `.yarnrc` (#3998) --- packages/api/cli/spec/util/check-system.spec.ts | 2 +- packages/api/cli/src/util/check-system.ts | 2 +- packages/template/base/src/BaseTemplate.ts | 2 +- packages/template/base/tmpl/_yarnrc | 1 - packages/template/base/tmpl/_yarnrc.yml | 1 + 5 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 packages/template/base/tmpl/_yarnrc create mode 100644 packages/template/base/tmpl/_yarnrc.yml diff --git a/packages/api/cli/spec/util/check-system.spec.ts b/packages/api/cli/spec/util/check-system.spec.ts index 8d57d1731c..c31f15d27e 100644 --- a/packages/api/cli/spec/util/check-system.spec.ts +++ b/packages/api/cli/spec/util/check-system.spec.ts @@ -104,7 +104,7 @@ describe('checkPackageManager', () => { } }); await expect(checkPackageManager()).rejects.toThrow( - 'When using Yarn 2+, `nodeLinker` must be set to "node-modules". Run `yarn config set nodeLinker node-modules` to set this config value, or add it to your project\'s `.yarnrc` file.', + 'When using Yarn 2+, `nodeLinker` must be set to "node-modules". Run `yarn config set nodeLinker node-modules` to set this config value, or add it to your project\'s `.yarnrc.yml` file.', ); }); diff --git a/packages/api/cli/src/util/check-system.ts b/packages/api/cli/src/util/check-system.ts index c63982bc83..e2c70493f5 100644 --- a/packages/api/cli/src/util/check-system.ts +++ b/packages/api/cli/src/util/check-system.ts @@ -82,7 +82,7 @@ async function checkYarnConfig() { nodeLinker !== 'node-modules' ) { throw new Error( - 'When using Yarn 2+, `nodeLinker` must be set to "node-modules". Run `yarn config set nodeLinker node-modules` to set this config value, or add it to your project\'s `.yarnrc` file.', + 'When using Yarn 2+, `nodeLinker` must be set to "node-modules". Run `yarn config set nodeLinker node-modules` to set this config value, or add it to your project\'s `.yarnrc.yml` file.', ); } } diff --git a/packages/template/base/src/BaseTemplate.ts b/packages/template/base/src/BaseTemplate.ts index 8783b5246b..06b5550034 100644 --- a/packages/template/base/src/BaseTemplate.ts +++ b/packages/template/base/src/BaseTemplate.ts @@ -78,7 +78,7 @@ export class BaseTemplate implements ForgeTemplate { pm.version && semver.gte(pm.version, '2.0.0') ) { - rootFiles.push('_yarnrc'); + rootFiles.push('_yarnrc.yml'); } if (copyCIFiles) { diff --git a/packages/template/base/tmpl/_yarnrc b/packages/template/base/tmpl/_yarnrc deleted file mode 100644 index 8b757b29a1..0000000000 --- a/packages/template/base/tmpl/_yarnrc +++ /dev/null @@ -1 +0,0 @@ -nodeLinker: node-modules \ No newline at end of file diff --git a/packages/template/base/tmpl/_yarnrc.yml b/packages/template/base/tmpl/_yarnrc.yml new file mode 100644 index 0000000000..3186f3f079 --- /dev/null +++ b/packages/template/base/tmpl/_yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules From 1631b7cd113177a2031cdc72a10bc0f98c379ebe Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 9 Sep 2025 02:07:58 -0700 Subject: [PATCH 22/26] build: ignore fixture package.json in socket (#4000) --- .../api/core/spec/fixture/async_forge_config/package.json | 1 + .../fixture/forge-config-no-package-json-config/package.json | 1 + socket.yml | 5 +++++ 3 files changed, 7 insertions(+) create mode 100644 socket.yml diff --git a/packages/api/core/spec/fixture/async_forge_config/package.json b/packages/api/core/spec/fixture/async_forge_config/package.json index 403ba98984..c26caa1366 100644 --- a/packages/api/core/spec/fixture/async_forge_config/package.json +++ b/packages/api/core/spec/fixture/async_forge_config/package.json @@ -2,6 +2,7 @@ "name": "async-test", "devDependencies": { "electron": "^1000.0.0", + "@electron/package-does-not-exist": "1.0.0", "@electron-forge/cli": "6.0.0", "@electron-forge/core": "6.0.0" } diff --git a/packages/api/core/spec/fixture/forge-config-no-package-json-config/package.json b/packages/api/core/spec/fixture/forge-config-no-package-json-config/package.json index db8ec52f37..1bb8eca0cc 100644 --- a/packages/api/core/spec/fixture/forge-config-no-package-json-config/package.json +++ b/packages/api/core/spec/fixture/forge-config-no-package-json-config/package.json @@ -2,6 +2,7 @@ "name": "test", "devDependencies": { "electron": "^1000.0.0", + "@electron/package-does-not-exist": "1.0.0", "@electron-forge/cli": "6.0.0" } } diff --git a/socket.yml b/socket.yml new file mode 100644 index 0000000000..f6d72dd004 --- /dev/null +++ b/socket.yml @@ -0,0 +1,5 @@ +version: 2 + +projectIgnorePaths: + - 'packages/api/core/spec/fixture/async_forge_config' + - 'packages/api/core/spec/fixture/forge-config-no-package-json-config' From a064bb966b6df27552fe562089930f5532d22c2d Mon Sep 17 00:00:00 2001 From: Ollie Hayman Date: Wed, 10 Sep 2025 00:13:56 +0100 Subject: [PATCH 23/26] feat: updated publisher-s3 to support cache max-age for release files (#3968) * feat: updated S3 publisher to support cache max-age AWS best practice for hosting content in S3 is to use a CloudFront distribution on-top of the S3 bucket. When doing this, it's preferrable to have an agressive cache policy in place. To ensure releases become available in a timely fashion, this change exposes a config item to set the Cache-Control max-age property against the RELEASES files used by electron's autoupdate. The change is made backwards compatible and also introduces unit tests for the S3 publisher # Conflicts: # packages/publisher/s3/src/PublisherS3.ts * fix: lint Signed-off-by: Kevin Cui --------- Signed-off-by: Kevin Cui Co-authored-by: Kevin Cui --- .../publisher/s3/spec/PublisherS3.spec.ts | 497 ++++++++++++++++++ packages/publisher/s3/src/Config.ts | 6 + packages/publisher/s3/src/PublisherS3.ts | 12 + 3 files changed, 515 insertions(+) create mode 100644 packages/publisher/s3/spec/PublisherS3.spec.ts diff --git a/packages/publisher/s3/spec/PublisherS3.spec.ts b/packages/publisher/s3/spec/PublisherS3.spec.ts new file mode 100644 index 0000000000..ca0622348e --- /dev/null +++ b/packages/publisher/s3/spec/PublisherS3.spec.ts @@ -0,0 +1,497 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +import { S3Client } from '@aws-sdk/client-s3'; +import { Upload } from '@aws-sdk/lib-storage'; +import { + ForgeMakeResult, + ResolvedForgeConfig, +} from '@electron-forge/shared-types'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { PublisherS3, PublisherS3Config } from '../src/PublisherS3'; + +// Mock AWS SDK modules +vi.mock('@aws-sdk/client-s3'); +vi.mock('@aws-sdk/lib-storage'); +vi.mock('node:fs'); + +describe('PublisherS3', () => { + let publisher: PublisherS3; + let mockS3Client: vi.Mocked; + let mockUpload: vi.Mocked; + let tmpDir: string; + + beforeEach(async () => { + // Create temporary directory for test artifacts + const tmp = os.tmpdir(); + const tmpdir = path.join(tmp, 'electron-forge-test-'); + tmpDir = await fs.promises.mkdtemp(tmpdir); + + // Create test artifact files + await fs.promises.writeFile( + path.join(tmpDir, 'test-app-1.0.0.dmg'), + 'fake-dmg-content', + ); + await fs.promises.writeFile( + path.join(tmpDir, 'test-app-1.0.0.exe'), + 'fake-exe-content', + ); + await fs.promises.writeFile( + path.join(tmpDir, 'RELEASES'), + 'fake-releases-content', + ); + await fs.promises.writeFile( + path.join(tmpDir, 'RELEASES.json'), + 'fake-releases-json-content', + ); + + // Mock S3Client constructor + mockS3Client = { + send: vi.fn(), + } as any; + vi.mocked(S3Client).mockImplementation(() => mockS3Client); + + // Mock Upload class + mockUpload = { + on: vi.fn().mockReturnThis(), + done: vi.fn().mockResolvedValue(undefined), + } as any; + vi.mocked(Upload).mockImplementation(() => mockUpload); + + // Mock fs.createReadStream + vi.mocked(fs.createReadStream).mockReturnValue('fake-stream' as any); + }); + + afterEach(async () => { + // Clean up temporary directory + await fs.promises.rm(tmpDir, { recursive: true, force: true }); + vi.clearAllMocks(); + }); + + describe('constructor', () => { + it('should create a PublisherS3 instance with correct name', () => { + const config: PublisherS3Config = { + bucket: 'test-bucket', + }; + publisher = new PublisherS3(config); + expect(publisher.name).toBe('s3'); + }); + }); + + describe('generateCredentials', () => { + it('should return credentials when accessKeyId and secretAccessKey are provided', () => { + const config: PublisherS3Config = { + bucket: 'test-bucket', + accessKeyId: 'test-access-key', + secretAccessKey: 'test-secret-key', + sessionToken: 'test-session-token', + }; + publisher = new PublisherS3(config); + + const credentials = publisher.generateCredentials(); + expect(credentials).toEqual({ + accessKeyId: 'test-access-key', + secretAccessKey: 'test-secret-key', + sessionToken: 'test-session-token', + }); + }); + + it('should return undefined when credentials are not provided', () => { + const config: PublisherS3Config = { + bucket: 'test-bucket', + }; + publisher = new PublisherS3(config); + + const credentials = publisher.generateCredentials(); + expect(credentials).toBeUndefined(); + }); + }); + + describe('publish', () => { + let mockMakeResults: ForgeMakeResult[]; + const mockForgeConfig = {} as ResolvedForgeConfig; + const mockSetStatusLine = vi.fn(); + + beforeEach(() => { + mockMakeResults = [ + { + artifacts: [path.join(tmpDir, 'test-app-1.0.0.dmg')], + packageJSON: { + name: 'test-app', + version: '1.0.0', + }, + platform: 'darwin', + arch: 'x64', + }, + { + artifacts: [path.join(tmpDir, 'test-app-1.0.0.exe')], + packageJSON: { + name: 'test-app', + version: '1.0.0', + }, + platform: 'win32', + arch: 'x64', + }, + ]; + }); + + it('should throw error when bucket is not configured', async () => { + const config: PublisherS3Config = {}; + publisher = new PublisherS3(config); + + await expect( + publisher.publish({ + makeResults: mockMakeResults, + dir: tmpDir, + forgeConfig: mockForgeConfig, + setStatusLine: mockSetStatusLine, + }), + ).rejects.toThrow( + 'In order to publish to S3, you must set the "bucket" property', + ); + }); + + it('should upload artifacts successfully with basic configuration', async () => { + const config: PublisherS3Config = { + bucket: 'test-bucket', + region: 'us-east-1', + }; + publisher = new PublisherS3(config); + + await publisher.publish({ + makeResults: mockMakeResults, + dir: tmpDir, + forgeConfig: mockForgeConfig, + setStatusLine: mockSetStatusLine, + }); + + // Verify S3Client was created with correct options + expect(S3Client).toHaveBeenCalledWith({ + credentials: undefined, + region: 'us-east-1', + endpoint: undefined, + forcePathStyle: false, + }); + + // Verify Upload was called for each artifact + expect(Upload).toHaveBeenCalledTimes(2); + expect(mockUpload.done).toHaveBeenCalledTimes(2); + + // Verify status line updates + expect(mockSetStatusLine).toHaveBeenCalledWith( + 'Uploading distributable (0/2)', + ); + expect(mockSetStatusLine).toHaveBeenCalledWith( + 'Uploading distributable (1/2)', + ); + expect(mockSetStatusLine).toHaveBeenCalledWith( + 'Uploading distributable (2/2)', + ); + }); + + it('should upload artifacts with custom folder and credentials', async () => { + const config: PublisherS3Config = { + bucket: 'test-bucket', + folder: 'custom-folder', + accessKeyId: 'test-key', + secretAccessKey: 'test-secret', + region: 'eu-west-1', + }; + publisher = new PublisherS3(config); + + await publisher.publish({ + makeResults: mockMakeResults, + dir: tmpDir, + forgeConfig: mockForgeConfig, + setStatusLine: mockSetStatusLine, + }); + + // Verify S3Client was created with credentials + expect(S3Client).toHaveBeenCalledWith({ + credentials: { + accessKeyId: 'test-key', + secretAccessKey: 'test-secret', + sessionToken: undefined, + }, + region: 'eu-west-1', + endpoint: undefined, + forcePathStyle: false, + }); + + // Verify Upload parameters + expect(Upload).toHaveBeenCalledWith({ + client: mockS3Client, + leavePartsOnError: true, + params: { + Body: 'fake-stream', + Bucket: 'test-bucket', + Key: expect.stringContaining('custom-folder/'), + ACL: 'private', + }, + }); + }); + + it('should upload artifacts with public ACL when public is true', async () => { + const config: PublisherS3Config = { + bucket: 'test-bucket', + public: true, + }; + publisher = new PublisherS3(config); + + await publisher.publish({ + makeResults: mockMakeResults, + dir: tmpDir, + forgeConfig: mockForgeConfig, + setStatusLine: mockSetStatusLine, + }); + + // Verify Upload parameters include public ACL + expect(Upload).toHaveBeenCalledWith({ + client: mockS3Client, + leavePartsOnError: true, + params: { + Body: 'fake-stream', + Bucket: 'test-bucket', + Key: expect.any(String), + ACL: 'public-read', + }, + }); + }); + + it('should omit ACL when omitAcl is true', async () => { + const config: PublisherS3Config = { + bucket: 'test-bucket', + omitAcl: true, + public: true, // This should be ignored when omitAcl is true + }; + publisher = new PublisherS3(config); + + await publisher.publish({ + makeResults: mockMakeResults, + dir: tmpDir, + forgeConfig: mockForgeConfig, + setStatusLine: mockSetStatusLine, + }); + + // Verify Upload parameters don't include ACL + expect(Upload).toHaveBeenCalledWith({ + client: mockS3Client, + leavePartsOnError: true, + params: { + Body: 'fake-stream', + Bucket: 'test-bucket', + Key: expect.any(String), + // ACL should not be present + }, + }); + }); + + it('should set Cache-Control metadata for RELEASES file', async () => { + const config: PublisherS3Config = { + bucket: 'test-bucket', + releaseFileCacheControlMaxAge: 3600, + }; + publisher = new PublisherS3(config); + + const makeResultsWithReleases: ForgeMakeResult[] = [ + { + artifacts: [path.join(tmpDir, 'RELEASES')], + packageJSON: { + name: 'test-app', + version: '1.0.0', + }, + platform: 'win32', + arch: 'x64', + }, + ]; + + await publisher.publish({ + makeResults: makeResultsWithReleases, + dir: tmpDir, + forgeConfig: mockForgeConfig, + setStatusLine: mockSetStatusLine, + }); + + // Verify Upload parameters include Cache-Control header + expect(Upload).toHaveBeenCalledWith({ + client: mockS3Client, + leavePartsOnError: true, + params: { + Body: 'fake-stream', + Bucket: 'test-bucket', + Key: expect.any(String), + ACL: 'private', + CacheControl: 'max-age=3600', + }, + }); + }); + + it('should set Cache-Control metadata for RELEASES.json file', async () => { + const config: PublisherS3Config = { + bucket: 'test-bucket', + releaseFileCacheControlMaxAge: 3600, + }; + publisher = new PublisherS3(config); + + const makeResultsWithReleasesJson: ForgeMakeResult[] = [ + { + artifacts: [path.join(tmpDir, 'RELEASES.json')], + packageJSON: { + name: 'test-app', + version: '1.0.0', + }, + platform: 'win32', + arch: 'x64', + }, + ]; + + await publisher.publish({ + makeResults: makeResultsWithReleasesJson, + dir: tmpDir, + forgeConfig: mockForgeConfig, + setStatusLine: mockSetStatusLine, + }); + + // Verify Upload parameters include Cache-Control header for RELEASES.json + expect(Upload).toHaveBeenCalledWith({ + client: mockS3Client, + leavePartsOnError: true, + params: { + Body: 'fake-stream', + Bucket: 'test-bucket', + Key: expect.any(String), + ACL: 'private', + CacheControl: 'max-age=3600', + }, + }); + }); + + it('should set Cache-Control metadata for both RELEASES and RELEASES.json files in same upload', async () => { + const config: PublisherS3Config = { + bucket: 'test-bucket', + releaseFileCacheControlMaxAge: 3600, + }; + publisher = new PublisherS3(config); + + const makeResultsWithBothReleases: ForgeMakeResult[] = [ + { + artifacts: [ + path.join(tmpDir, 'RELEASES'), + path.join(tmpDir, 'RELEASES.json'), + ], + packageJSON: { + name: 'test-app', + version: '1.0.0', + }, + platform: 'win32', + arch: 'x64', + }, + ]; + + await publisher.publish({ + makeResults: makeResultsWithBothReleases, + dir: tmpDir, + forgeConfig: mockForgeConfig, + setStatusLine: mockSetStatusLine, + }); + + // Verify Upload was called twice (once for each RELEASES file) + expect(Upload).toHaveBeenCalledTimes(2); + expect(mockUpload.done).toHaveBeenCalledTimes(2); + + // Verify both uploads include Cache-Control header + const uploadCalls = vi.mocked(Upload).mock.calls; + expect(uploadCalls[0][0].params.CacheControl).toEqual('max-age=3600'); + expect(uploadCalls[1][0].params.CacheControl).toEqual('max-age=3600'); + }); + + it('should not set Cache-Control metadata for non-RELEASES files', async () => { + const config: PublisherS3Config = { + bucket: 'test-bucket', + releaseFileCacheControlMaxAge: 3600, + }; + publisher = new PublisherS3(config); + + await publisher.publish({ + makeResults: mockMakeResults, + dir: tmpDir, + forgeConfig: mockForgeConfig, + setStatusLine: mockSetStatusLine, + }); + + // Verify Upload parameters don't include Cache-Control metadata for non-RELEASES files + expect(Upload).toHaveBeenCalledWith({ + client: mockS3Client, + leavePartsOnError: true, + params: { + Body: 'fake-stream', + Bucket: 'test-bucket', + Key: expect.any(String), + ACL: 'private', + // Metadata should not be present for non-RELEASES files + }, + }); + }); + + it('should handle upload progress events', async () => { + const config: PublisherS3Config = { + bucket: 'test-bucket', + }; + publisher = new PublisherS3(config); + + await publisher.publish({ + makeResults: mockMakeResults, + dir: tmpDir, + forgeConfig: mockForgeConfig, + setStatusLine: mockSetStatusLine, + }); + + // Verify progress event handler was set up + expect(mockUpload.on).toHaveBeenCalledWith( + 'httpUploadProgress', + expect.any(Function), + ); + }); + + it('should handle custom endpoint and forcePathStyle', async () => { + const config: PublisherS3Config = { + bucket: 'test-bucket', + endpoint: 'https://s3.example.com', + s3ForcePathStyle: true, + }; + publisher = new PublisherS3(config); + + await publisher.publish({ + makeResults: mockMakeResults, + dir: tmpDir, + forgeConfig: mockForgeConfig, + setStatusLine: mockSetStatusLine, + }); + + // Verify S3Client was created with custom endpoint and forcePathStyle + expect(S3Client).toHaveBeenCalledWith({ + credentials: undefined, + region: undefined, + endpoint: 'https://s3.example.com', + forcePathStyle: true, + }); + }); + }); + + describe('s3KeySafe', () => { + it('should replace @ and / characters in keys', () => { + const config: PublisherS3Config = { + bucket: 'test-bucket', + }; + publisher = new PublisherS3(config); + + // Access the private method through the class instance + const result = (publisher as any).s3KeySafe( + 'test@example.com/path/to/file', + ); + expect(result).toBe('test_example.com_path_to_file'); + }); + }); +}); diff --git a/packages/publisher/s3/src/Config.ts b/packages/publisher/s3/src/Config.ts index 52abd50a30..05a20c9c54 100644 --- a/packages/publisher/s3/src/Config.ts +++ b/packages/publisher/s3/src/Config.ts @@ -66,4 +66,10 @@ export interface PublisherS3Config { * Custom function to provide the key to upload a given file to */ keyResolver?: (fileName: string, platform: string, arch: string) => string; + /** + * Set the Cache-Control max-age metadata in S3 for the RELEASES file + * + * Default: Cache-Control metadata is not set + */ + releaseFileCacheControlMaxAge?: number; } diff --git a/packages/publisher/s3/src/PublisherS3.ts b/packages/publisher/s3/src/PublisherS3.ts index 41d5783919..62f2534015 100644 --- a/packages/publisher/s3/src/PublisherS3.ts +++ b/packages/publisher/s3/src/PublisherS3.ts @@ -19,6 +19,7 @@ type S3Artifact = { keyPrefix: string; platform: string; arch: string; + isReleaseFile: boolean; }; export default class PublisherS3 extends PublisherStatic { @@ -48,6 +49,8 @@ export default class PublisherS3 extends PublisherStatic { this.config.folder || this.s3KeySafe(makeResult.packageJSON.name), platform: makeResult.platform, arch: makeResult.arch, + isReleaseFile: + path.basename(artifact, path.extname(artifact)) === 'RELEASES', })), ); } @@ -79,6 +82,15 @@ export default class PublisherS3 extends PublisherStatic { if (!this.config.omitAcl) { params.ACL = this.config.public ? 'public-read' : 'private'; } + // Cache-Control must be an integer number of seconds to cache and should not be negative. + if ( + artifact.isReleaseFile && + typeof this.config.releaseFileCacheControlMaxAge !== 'undefined' && + Number.isInteger(this.config.releaseFileCacheControlMaxAge) && + this.config.releaseFileCacheControlMaxAge >= 0 + ) { + params.CacheControl = `max-age=${this.config.releaseFileCacheControlMaxAge}`; + } const uploader = new Upload({ client: s3Client, leavePartsOnError: true, From 1631a3eca1ea2897c6c893c68b6e746e7298e2c3 Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Tue, 9 Sep 2025 16:14:12 -0700 Subject: [PATCH 24/26] fix(plugin-vite): clearer bundle error state (#3987) * fix(plugin-vite): clearer bundle error state * only log rollup messages depending on vite `logLevel` in userConfig --- packages/plugin/vite/src/VitePlugin.ts | 49 ++++++++++++++++++------ packages/plugin/vite/src/util/plugins.ts | 10 ----- 2 files changed, 38 insertions(+), 21 deletions(-) delete mode 100644 packages/plugin/vite/src/util/plugins.ts diff --git a/packages/plugin/vite/src/VitePlugin.ts b/packages/plugin/vite/src/VitePlugin.ts index fd88d542a8..8b72f5f960 100644 --- a/packages/plugin/vite/src/VitePlugin.ts +++ b/packages/plugin/vite/src/VitePlugin.ts @@ -10,7 +10,6 @@ import fs from 'fs-extra'; import { Listr, PRESET_TIMER } from 'listr2'; import { default as vite } from 'vite'; -import { onBuildDone } from './util/plugins'; import ViteConfigGenerator from './ViteConfig'; import type { VitePluginConfig } from './Config'; @@ -212,6 +211,10 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}.`); // Main process, Preload scripts and Worker process, etc. build = async (task?: ForgeListrTask): Promise => { const configs = await this.configGenerator.getBuildConfigs(); + /** + * Checks if the result of the Vite build is a Rollup watcher. + * This should happen iff we're running `electron-forge start`. + */ const isRollupWatcher = ( x: | vite.Rollup.RollupWatcher @@ -265,23 +268,50 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}.`); .build({ // Avoid recursive builds caused by users configuring @electron-forge/plugin-vite in Vite config file. configFile: false, - logLevel: 'silent', // We suppress Vite output and instead log lines using RollupWatcher events + // We suppress Vite output and instead log lines using RollupWatcher events + logLevel: 'silent', ...userConfig, plugins: [ - onBuildDone(resolve), + // This plugin controls the output of the first-time Vite build that happens. + // `buildEnd` and `closeBundle` are Rollup output generation hooks. + // See https://rollupjs.org/plugin-development/#output-generation-hooks + { + name: '@electron-forge/plugin-vite:build-done', + buildEnd(err) { + if (err instanceof Error) { + d( + 'buildEnd rollup hook called with error so build failed', + ); + reject(err); + } + }, + closeBundle() { + d( + 'no error in buildEnd and reached closeBundle so build succeeded', + ); + resolve(); + }, + }, ...(userConfig.plugins ?? []), ], clearScreen: false, }) .then((result) => { + // When running `start` and enabling watch mode in Vite, the Rollup watcher + // emits events for subsequent builds. if (isRollupWatcher(result)) { result.on('event', (event) => { - if (event.code === 'ERROR') { + if ( + event.code === 'ERROR' && + userConfig.logLevel !== 'silent' + ) { console.error( - `\n${this.timeFormatter.format(new Date())} ${event.error.message}`, + `\n${chalk.dim(this.timeFormatter.format(new Date()))} ${event.error.message}`, ); - reject(event.error); - } else if (event.code === 'BUNDLE_END') { + } else if ( + event.code === 'BUNDLE_END' && + (!userConfig.logLevel || userConfig.logLevel === 'info') + ) { console.log( `${chalk.dim(this.timeFormatter.format(new Date()))} ${chalk.cyan.bold('[@electron-forge/plugin-vite]')} ${chalk.green( 'target built', @@ -298,14 +328,11 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}.`); .catch(reject); }); }, - rendererOptions: { - persistentOutput: true, - }, - exitOnError: true, }; }), { concurrent: this.config.concurrent ?? true, + exitOnError: this.isProd, }, ); }; diff --git a/packages/plugin/vite/src/util/plugins.ts b/packages/plugin/vite/src/util/plugins.ts deleted file mode 100644 index 5bb0e0b256..0000000000 --- a/packages/plugin/vite/src/util/plugins.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Plugin } from 'vite'; - -export function onBuildDone(callback: () => void) { - return { - name: '@electron-forge/plugin-vite:build-done', - closeBundle() { - callback(); - }, - } as Plugin; -} From e3352f363ebbf63a2e50e090d9124657728b6b6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:14:33 -0700 Subject: [PATCH 25/26] build(deps): bump vite from 5.4.19 to 5.4.20 (#4001) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.19 to 5.4.20. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.4.20/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.4.20/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 5.4.20 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index e96e7d9402..45e84b040b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13772,9 +13772,9 @@ vite-node@3.1.3: fsevents "~2.3.3" vite@^5.0.12: - version "5.4.19" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.19.tgz#20efd060410044b3ed555049418a5e7d1998f959" - integrity sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA== + version "5.4.20" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.20.tgz#3267a5e03f21212f44edfd72758138e8fcecd76a" + integrity sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g== dependencies: esbuild "^0.21.3" postcss "^8.4.43" From cd63f57bd6870af2ad847076a183456221b30269 Mon Sep 17 00:00:00 2001 From: Keeley Hammond Date: Tue, 9 Sep 2025 17:46:05 -0700 Subject: [PATCH 26/26] v7.9.0 --- lerna.json | 2 +- packages/api/cli/package.json | 8 ++-- packages/api/core/package.json | 46 +++++++++---------- .../external/create-electron-app/package.json | 4 +- packages/maker/appx/package.json | 6 +-- packages/maker/base/package.json | 4 +- packages/maker/deb/package.json | 8 ++-- packages/maker/dmg/package.json | 8 ++-- packages/maker/flatpak/package.json | 8 ++-- packages/maker/pkg/package.json | 8 ++-- packages/maker/rpm/package.json | 8 ++-- packages/maker/snap/package.json | 8 ++-- packages/maker/squirrel/package.json | 6 +-- packages/maker/wix/package.json | 6 +-- packages/maker/zip/package.json | 8 ++-- .../plugin/auto-unpack-natives/package.json | 6 +-- packages/plugin/base/package.json | 4 +- .../plugin/electronegativity/package.json | 6 +-- packages/plugin/fuses/package.json | 6 +-- packages/plugin/local-electron/package.json | 6 +-- packages/plugin/vite/package.json | 6 +-- packages/plugin/webpack/package.json | 10 ++-- packages/publisher/base-static/package.json | 6 +-- packages/publisher/base/package.json | 4 +- packages/publisher/bitbucket/package.json | 4 +- .../electron-release-server/package.json | 6 +-- packages/publisher/gcs/package.json | 6 +-- packages/publisher/github/package.json | 6 +-- packages/publisher/nucleus/package.json | 6 +-- packages/publisher/s3/package.json | 6 +-- packages/publisher/snapcraft/package.json | 4 +- packages/template/base/package.json | 8 ++-- .../template/vite-typescript/package.json | 10 ++-- packages/template/vite/package.json | 8 ++-- .../template/webpack-typescript/package.json | 20 ++++---- packages/template/webpack/package.json | 8 ++-- packages/utils/core-utils/package.json | 4 +- packages/utils/test-utils/package.json | 2 +- packages/utils/tracer/package.json | 2 +- packages/utils/types/package.json | 4 +- packages/utils/web-multi-logger/package.json | 2 +- 41 files changed, 149 insertions(+), 149 deletions(-) diff --git a/lerna.json b/lerna.json index 4045dd466a..592171c885 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "7.8.3", + "version": "7.9.0", "npmClient": "yarn" } diff --git a/packages/api/cli/package.json b/packages/api/cli/package.json index 6891e4638f..3c02bd73f4 100644 --- a/packages/api/cli/package.json +++ b/packages/api/cli/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/cli", - "version": "7.8.3", + "version": "7.9.0", "description": "A complete tool for building modern Electron applications", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -15,9 +15,9 @@ "vitest": "^3.1.3" }, "dependencies": { - "@electron-forge/core": "7.8.3", - "@electron-forge/core-utils": "7.8.3", - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/core": "7.9.0", + "@electron-forge/core-utils": "7.9.0", + "@electron-forge/shared-types": "7.9.0", "@electron/get": "^3.0.0", "@inquirer/prompts": "^6.0.1", "@listr2/prompt-adapter-inquirer": "^2.0.22", diff --git a/packages/api/core/package.json b/packages/api/core/package.json index 7c80679513..ff76060361 100644 --- a/packages/api/core/package.json +++ b/packages/api/core/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/core", - "version": "7.8.3", + "version": "7.9.0", "description": "A complete tool for building modern Electron applications", "repository": "https://github.com/electron/forge", "main": "dist/api/index.js", @@ -8,17 +8,17 @@ "author": "Samuel Attard", "license": "MIT", "devDependencies": { - "@electron-forge/maker-appx": "7.8.3", - "@electron-forge/maker-deb": "7.8.3", - "@electron-forge/maker-dmg": "7.8.3", - "@electron-forge/maker-flatpak": "7.8.3", - "@electron-forge/maker-rpm": "7.8.3", - "@electron-forge/maker-snap": "7.8.3", - "@electron-forge/maker-squirrel": "7.8.3", - "@electron-forge/maker-wix": "7.8.3", - "@electron-forge/maker-zip": "7.8.3", + "@electron-forge/maker-appx": "7.9.0", + "@electron-forge/maker-deb": "7.9.0", + "@electron-forge/maker-dmg": "7.9.0", + "@electron-forge/maker-flatpak": "7.9.0", + "@electron-forge/maker-rpm": "7.9.0", + "@electron-forge/maker-snap": "7.9.0", + "@electron-forge/maker-squirrel": "7.9.0", + "@electron-forge/maker-wix": "7.9.0", + "@electron-forge/maker-zip": "7.9.0", "@electron-forge/template-fixture": "file:./spec/fixture/electron-forge-template-fixture", - "@electron-forge/test-utils": "7.8.3", + "@electron-forge/test-utils": "7.9.0", "@types/interpret": "^1.1.1", "@types/progress": "^2.0.5", "@types/rechoir": "^0.6.1", @@ -28,21 +28,22 @@ "yaml-hook": "^1.0.0" }, "dependencies": { - "@electron-forge/core-utils": "7.8.3", - "@electron-forge/maker-base": "7.8.3", - "@electron-forge/plugin-base": "7.8.3", - "@electron-forge/publisher-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3", - "@electron-forge/template-base": "7.8.3", - "@electron-forge/template-vite": "7.8.3", - "@electron-forge/template-vite-typescript": "7.8.3", - "@electron-forge/template-webpack": "7.8.3", - "@electron-forge/template-webpack-typescript": "7.8.3", - "@electron-forge/tracer": "7.8.3", + "@electron-forge/core-utils": "7.9.0", + "@electron-forge/maker-base": "7.9.0", + "@electron-forge/plugin-base": "7.9.0", + "@electron-forge/publisher-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0", + "@electron-forge/template-base": "7.9.0", + "@electron-forge/template-vite": "7.9.0", + "@electron-forge/template-vite-typescript": "7.9.0", + "@electron-forge/template-webpack": "7.9.0", + "@electron-forge/template-webpack-typescript": "7.9.0", + "@electron-forge/tracer": "7.9.0", "@electron/get": "^3.0.0", "@electron/packager": "^18.3.5", "@electron/rebuild": "^3.7.0", "@malept/cross-spawn-promise": "^2.0.0", + "@vscode/sudo-prompt": "^9.3.1", "chalk": "^4.0.0", "debug": "^4.3.1", "fast-glob": "^3.2.7", @@ -60,7 +61,6 @@ "rechoir": "^0.8.0", "semver": "^7.2.1", "source-map-support": "^0.5.13", - "@vscode/sudo-prompt": "^9.3.1", "username": "^5.1.0" }, "engines": { diff --git a/packages/external/create-electron-app/package.json b/packages/external/create-electron-app/package.json index 040a827f35..616f2e822b 100644 --- a/packages/external/create-electron-app/package.json +++ b/packages/external/create-electron-app/package.json @@ -1,13 +1,13 @@ { "name": "create-electron-app", - "version": "7.8.3", + "version": "7.9.0", "description": "Create Electron App", "main": "dist/index.js", "typings": "dist/index.d.ts", "author": "Samuel Attard", "license": "MIT", "dependencies": { - "@electron-forge/cli": "7.8.3" + "@electron-forge/cli": "7.9.0" }, "bin": { "create-electron-app": "dist/index.js" diff --git a/packages/maker/appx/package.json b/packages/maker/appx/package.json index f4b9a63080..a7affd6182 100644 --- a/packages/maker/appx/package.json +++ b/packages/maker/appx/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/maker-appx", - "version": "7.8.3", + "version": "7.9.0", "description": "AppX maker for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -14,8 +14,8 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/maker-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/maker-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0", "cross-spawn": "^7.0.3", "fs-extra": "^10.0.0", "parse-author": "^2.0.0" diff --git a/packages/maker/base/package.json b/packages/maker/base/package.json index c938cf31ef..df693af939 100644 --- a/packages/maker/base/package.json +++ b/packages/maker/base/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/maker-base", - "version": "7.8.3", + "version": "7.9.0", "description": "Base maker for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -14,7 +14,7 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/shared-types": "7.9.0", "fs-extra": "^10.0.0", "which": "^2.0.2" }, diff --git a/packages/maker/deb/package.json b/packages/maker/deb/package.json index 5f1818666f..5a1cdf6494 100644 --- a/packages/maker/deb/package.json +++ b/packages/maker/deb/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/maker-deb", - "version": "7.8.3", + "version": "7.9.0", "description": "Deb maker for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -8,15 +8,15 @@ "main": "dist/MakerDeb.js", "typings": "dist/MakerDeb.d.ts", "devDependencies": { - "@electron-forge/test-utils": "7.8.3", + "@electron-forge/test-utils": "7.9.0", "vitest": "^3.1.3" }, "engines": { "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/maker-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3" + "@electron-forge/maker-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0" }, "optionalDependencies": { "electron-installer-debian": "^3.2.0" diff --git a/packages/maker/dmg/package.json b/packages/maker/dmg/package.json index 945b4612e1..b60f4f7f2c 100644 --- a/packages/maker/dmg/package.json +++ b/packages/maker/dmg/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/maker-dmg", - "version": "7.8.3", + "version": "7.9.0", "description": "DMG maker for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -8,15 +8,15 @@ "main": "dist/MakerDMG.js", "typings": "dist/MakerDMG.d.ts", "devDependencies": { - "@electron-forge/test-utils": "7.8.3", + "@electron-forge/test-utils": "7.9.0", "vitest": "^3.1.3" }, "engines": { "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/maker-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/maker-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0", "fs-extra": "^10.0.0" }, "optionalDependencies": { diff --git a/packages/maker/flatpak/package.json b/packages/maker/flatpak/package.json index 2f3bbe7eb5..c38ae4f3db 100644 --- a/packages/maker/flatpak/package.json +++ b/packages/maker/flatpak/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/maker-flatpak", - "version": "7.8.3", + "version": "7.9.0", "description": "Flatpak maker for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -8,15 +8,15 @@ "main": "dist/MakerFlatpak.js", "typings": "dist/MakerFlatpak.d.ts", "devDependencies": { - "@electron-forge/test-utils": "7.8.3", + "@electron-forge/test-utils": "7.9.0", "vitest": "^3.1.3" }, "engines": { "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/maker-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/maker-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0", "fs-extra": "^10.0.0" }, "optionalDependencies": { diff --git a/packages/maker/pkg/package.json b/packages/maker/pkg/package.json index d08c229958..7319824f8a 100644 --- a/packages/maker/pkg/package.json +++ b/packages/maker/pkg/package.json @@ -1,21 +1,21 @@ { "name": "@electron-forge/maker-pkg", - "version": "7.8.3", + "version": "7.9.0", "description": "PKG maker for Electron Forge", "repository": "https://github.com/electron/forge", "license": "MIT", "main": "dist/MakerPKG.js", "typings": "dist/MakerPKG.d.ts", "devDependencies": { - "@electron-forge/test-utils": "7.8.3", + "@electron-forge/test-utils": "7.9.0", "vitest": "^3.1.3" }, "engines": { "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/maker-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/maker-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0", "@electron/osx-sign": "^1.0.5" }, "publishConfig": { diff --git a/packages/maker/rpm/package.json b/packages/maker/rpm/package.json index bb67d6bfe5..331db3f5d8 100644 --- a/packages/maker/rpm/package.json +++ b/packages/maker/rpm/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/maker-rpm", - "version": "7.8.3", + "version": "7.9.0", "description": "Rpm maker for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -8,15 +8,15 @@ "main": "dist/MakerRpm.js", "typings": "dist/MakerRpm.d.ts", "devDependencies": { - "@electron-forge/test-utils": "7.8.3", + "@electron-forge/test-utils": "7.9.0", "vitest": "^3.1.3" }, "engines": { "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/maker-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3" + "@electron-forge/maker-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0" }, "optionalDependencies": { "electron-installer-redhat": "^3.2.0" diff --git a/packages/maker/snap/package.json b/packages/maker/snap/package.json index 1707c657af..39e5552496 100644 --- a/packages/maker/snap/package.json +++ b/packages/maker/snap/package.json @@ -1,21 +1,21 @@ { "name": "@electron-forge/maker-snap", - "version": "7.8.3", + "version": "7.9.0", "description": "Snap maker for Electron Forge", "repository": "https://github.com/electron/forge", "license": "MIT", "main": "dist/MakerSnap.js", "typings": "dist/MakerSnap.d.ts", "devDependencies": { - "@electron-forge/test-utils": "7.8.3", + "@electron-forge/test-utils": "7.9.0", "vitest": "^3.1.3" }, "engines": { "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/maker-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3" + "@electron-forge/maker-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0" }, "optionalDependencies": { "electron-installer-snap": "^5.2.0" diff --git a/packages/maker/squirrel/package.json b/packages/maker/squirrel/package.json index fce2d69231..0ff556c1ba 100644 --- a/packages/maker/squirrel/package.json +++ b/packages/maker/squirrel/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/maker-squirrel", - "version": "7.8.3", + "version": "7.9.0", "description": "Squirrel maker for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -11,8 +11,8 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/maker-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/maker-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0", "fs-extra": "^10.0.0" }, "optionalDependencies": { diff --git a/packages/maker/wix/package.json b/packages/maker/wix/package.json index cd844832e7..8111aa0672 100644 --- a/packages/maker/wix/package.json +++ b/packages/maker/wix/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/maker-wix", - "version": "7.8.3", + "version": "7.9.0", "description": "Wix maker for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -14,8 +14,8 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/maker-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/maker-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0", "chalk": "^4.0.0", "electron-wix-msi": "^5.1.3", "log-symbols": "^4.0.0", diff --git a/packages/maker/zip/package.json b/packages/maker/zip/package.json index aedb957800..9dec60afea 100644 --- a/packages/maker/zip/package.json +++ b/packages/maker/zip/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/maker-zip", - "version": "7.8.3", + "version": "7.9.0", "description": "ZIP maker for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -8,15 +8,15 @@ "main": "dist/MakerZIP.js", "typings": "dist/MakerZIP.d.ts", "devDependencies": { - "@electron-forge/test-utils": "7.8.3", + "@electron-forge/test-utils": "7.9.0", "vitest": "^3.1.3" }, "engines": { "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/maker-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/maker-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0", "cross-zip": "^4.0.0", "fs-extra": "^10.0.0", "got": "^11.8.5" diff --git a/packages/plugin/auto-unpack-natives/package.json b/packages/plugin/auto-unpack-natives/package.json index e46c0f1751..7d7a500e5c 100644 --- a/packages/plugin/auto-unpack-natives/package.json +++ b/packages/plugin/auto-unpack-natives/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/plugin-auto-unpack-natives", - "version": "7.8.3", + "version": "7.9.0", "description": "Auto Unpack Natives plugin for Electron Forge, automatically adds native node modules to asar.unpacked", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -11,8 +11,8 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/plugin-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3" + "@electron-forge/plugin-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0" }, "publishConfig": { "access": "public" diff --git a/packages/plugin/base/package.json b/packages/plugin/base/package.json index 4bd6353cd8..cb68fd5878 100644 --- a/packages/plugin/base/package.json +++ b/packages/plugin/base/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/plugin-base", - "version": "7.8.3", + "version": "7.9.0", "description": "Base plugin for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -11,7 +11,7 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/shared-types": "7.8.3" + "@electron-forge/shared-types": "7.9.0" }, "publishConfig": { "access": "public" diff --git a/packages/plugin/electronegativity/package.json b/packages/plugin/electronegativity/package.json index 6fbd4a51c8..119cafc311 100644 --- a/packages/plugin/electronegativity/package.json +++ b/packages/plugin/electronegativity/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/plugin-electronegativity", - "version": "7.8.3", + "version": "7.9.0", "description": "Integrate Electronegativity into the Electron Forge workflow", "repository": { "type": "git", @@ -16,8 +16,8 @@ }, "dependencies": { "@doyensec/electronegativity": "^1.9.1", - "@electron-forge/plugin-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3" + "@electron-forge/plugin-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0" }, "publishConfig": { "access": "public" diff --git a/packages/plugin/fuses/package.json b/packages/plugin/fuses/package.json index efb1ba5a7d..fc6e0bd0e6 100644 --- a/packages/plugin/fuses/package.json +++ b/packages/plugin/fuses/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/plugin-fuses", - "version": "7.8.3", + "version": "7.9.0", "description": "A plugin for flipping Electron Fuses in Electron Forge", "repository": "https://github.com/electron/forge", "author": "Erik Moura ", @@ -25,8 +25,8 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/plugin-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3" + "@electron-forge/plugin-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0" }, "publishConfig": { "access": "public" diff --git a/packages/plugin/local-electron/package.json b/packages/plugin/local-electron/package.json index bb5b90edfa..1ef6900632 100644 --- a/packages/plugin/local-electron/package.json +++ b/packages/plugin/local-electron/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/plugin-local-electron", - "version": "7.8.3", + "version": "7.9.0", "description": "Local Electron plugin for Electron Forge, let's you use a local build of Electron", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -11,8 +11,8 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/plugin-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/plugin-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0", "fs-extra": "^10.0.0" }, "devDependencies": { diff --git a/packages/plugin/vite/package.json b/packages/plugin/vite/package.json index de8ee33cf6..7727336746 100644 --- a/packages/plugin/vite/package.json +++ b/packages/plugin/vite/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/plugin-vite", - "version": "7.8.3", + "version": "7.9.0", "description": "Vite plugin for Electron Forge, lets you use Vite directly in your tooling", "repository": { "type": "git", @@ -12,8 +12,8 @@ "main": "dist/VitePlugin.js", "typings": "dist/VitePlugin.d.ts", "dependencies": { - "@electron-forge/plugin-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/plugin-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0", "chalk": "^4.0.0", "debug": "^4.3.1", "fs-extra": "^10.0.0", diff --git a/packages/plugin/webpack/package.json b/packages/plugin/webpack/package.json index 5bd48235f6..d1e7174659 100644 --- a/packages/plugin/webpack/package.json +++ b/packages/plugin/webpack/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/plugin-webpack", - "version": "7.8.3", + "version": "7.9.0", "description": "Webpack plugin for Electron Forge, lets you use Webpack directly in your tooling", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -19,10 +19,10 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/core-utils": "7.8.3", - "@electron-forge/plugin-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3", - "@electron-forge/web-multi-logger": "7.8.3", + "@electron-forge/core-utils": "7.9.0", + "@electron-forge/plugin-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0", + "@electron-forge/web-multi-logger": "7.9.0", "chalk": "^4.0.0", "debug": "^4.3.1", "fast-glob": "^3.2.7", diff --git a/packages/publisher/base-static/package.json b/packages/publisher/base-static/package.json index e986cdac0a..fdd6e9916a 100644 --- a/packages/publisher/base-static/package.json +++ b/packages/publisher/base-static/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/publisher-static", - "version": "7.8.3", + "version": "7.9.0", "description": "Base publisher for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -8,8 +8,8 @@ "main": "dist/PublisherStatic.js", "typings": "dist/PublisherStatic.d.ts", "dependencies": { - "@electron-forge/publisher-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3" + "@electron-forge/publisher-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0" }, "devDependencies": { "vitest": "^3.1.3" diff --git a/packages/publisher/base/package.json b/packages/publisher/base/package.json index 00e4fce5fa..552d297fd3 100644 --- a/packages/publisher/base/package.json +++ b/packages/publisher/base/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/publisher-base", - "version": "7.8.3", + "version": "7.9.0", "description": "Base publisher for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -8,7 +8,7 @@ "main": "dist/Publisher.js", "typings": "dist/Publisher.d.ts", "dependencies": { - "@electron-forge/shared-types": "7.8.3" + "@electron-forge/shared-types": "7.9.0" }, "devDependencies": { "vitest": "^3.1.3" diff --git a/packages/publisher/bitbucket/package.json b/packages/publisher/bitbucket/package.json index 177a1b4156..26fe19b167 100644 --- a/packages/publisher/bitbucket/package.json +++ b/packages/publisher/bitbucket/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/publisher-bitbucket", - "version": "7.8.3", + "version": "7.9.0", "description": "Bitbucket publisher for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Luke Batchelor", @@ -11,7 +11,7 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/publisher-base": "7.8.3", + "@electron-forge/publisher-base": "7.9.0", "form-data": "^4.0.0", "fs-extra": "^10.0.0", "node-fetch": "^2.6.7" diff --git a/packages/publisher/electron-release-server/package.json b/packages/publisher/electron-release-server/package.json index 65182097b5..4c63e4fe7f 100644 --- a/packages/publisher/electron-release-server/package.json +++ b/packages/publisher/electron-release-server/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/publisher-electron-release-server", - "version": "7.8.3", + "version": "7.9.0", "description": "Electron release server publisher for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -15,8 +15,8 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/publisher-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/publisher-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0", "debug": "^4.3.1", "form-data": "^4.0.0", "fs-extra": "^10.0.0", diff --git a/packages/publisher/gcs/package.json b/packages/publisher/gcs/package.json index 61dfb368f9..a0b0d3af00 100644 --- a/packages/publisher/gcs/package.json +++ b/packages/publisher/gcs/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/publisher-gcs", - "version": "7.8.3", + "version": "7.9.0", "description": "Google Cloud Storage publisher for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Evgeny Vlasenko", @@ -11,8 +11,8 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/publisher-static": "7.8.3", - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/publisher-static": "7.9.0", + "@electron-forge/shared-types": "7.9.0", "@google-cloud/storage": "^7.5.0", "debug": "^4.3.1" }, diff --git a/packages/publisher/github/package.json b/packages/publisher/github/package.json index aed6fce7fd..9ca2ffddeb 100644 --- a/packages/publisher/github/package.json +++ b/packages/publisher/github/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/publisher-github", - "version": "7.8.3", + "version": "7.9.0", "description": "Github publisher for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -14,8 +14,8 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/publisher-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/publisher-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0", "@octokit/core": "^5.2.1", "@octokit/plugin-retry": "^6.1.0", "@octokit/request-error": "^5.1.1", diff --git a/packages/publisher/nucleus/package.json b/packages/publisher/nucleus/package.json index b55385e956..e1d277865e 100644 --- a/packages/publisher/nucleus/package.json +++ b/packages/publisher/nucleus/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/publisher-nucleus", - "version": "7.8.3", + "version": "7.9.0", "description": "Nucleus publisher for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -11,8 +11,8 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/publisher-base": "7.8.3", - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/publisher-base": "7.9.0", + "@electron-forge/shared-types": "7.9.0", "debug": "^4.3.1", "form-data": "^4.0.0", "node-fetch": "^2.6.7" diff --git a/packages/publisher/s3/package.json b/packages/publisher/s3/package.json index d0df9de343..5b9333c4d2 100644 --- a/packages/publisher/s3/package.json +++ b/packages/publisher/s3/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/publisher-s3", - "version": "7.8.3", + "version": "7.9.0", "description": "S3 publisher for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -14,8 +14,8 @@ "@aws-sdk/client-s3": "^3.654.0", "@aws-sdk/lib-storage": "^3.654.0", "@aws-sdk/types": "^3.654.0", - "@electron-forge/publisher-static": "7.8.3", - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/publisher-static": "7.9.0", + "@electron-forge/shared-types": "7.9.0", "debug": "^4.3.1" }, "publishConfig": { diff --git a/packages/publisher/snapcraft/package.json b/packages/publisher/snapcraft/package.json index 44bee20976..f0fe5a0298 100644 --- a/packages/publisher/snapcraft/package.json +++ b/packages/publisher/snapcraft/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/publisher-snapcraft", - "version": "7.8.3", + "version": "7.9.0", "description": "Snapcraft publisher for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -11,7 +11,7 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/publisher-base": "7.8.3", + "@electron-forge/publisher-base": "7.9.0", "fs-extra": "^10.0.0" }, "optionalDependencies": { diff --git a/packages/template/base/package.json b/packages/template/base/package.json index 1f88525c58..9d5653e50c 100644 --- a/packages/template/base/package.json +++ b/packages/template/base/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/template-base", - "version": "7.8.3", + "version": "7.9.0", "description": "Base template for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -11,8 +11,8 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/core-utils": "7.8.3", - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/core-utils": "7.9.0", + "@electron-forge/shared-types": "7.9.0", "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.3.1", "fs-extra": "^10.0.0", @@ -20,7 +20,7 @@ "username": "^5.1.0" }, "devDependencies": { - "@electron-forge/test-utils": "7.8.3", + "@electron-forge/test-utils": "7.9.0", "vitest": "^3.1.3" }, "publishConfig": { diff --git a/packages/template/vite-typescript/package.json b/packages/template/vite-typescript/package.json index aba5999b94..742bfac931 100644 --- a/packages/template/vite-typescript/package.json +++ b/packages/template/vite-typescript/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/template-vite-typescript", - "version": "7.8.3", + "version": "7.9.0", "description": "Vite-TypeScript template for Electron Forge, gets you started with Vite really quickly", "repository": { "type": "git", @@ -15,13 +15,13 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/shared-types": "7.8.3", - "@electron-forge/template-base": "7.8.3", + "@electron-forge/shared-types": "7.9.0", + "@electron-forge/template-base": "7.9.0", "fs-extra": "^10.0.0" }, "devDependencies": { - "@electron-forge/core-utils": "7.8.3", - "@electron-forge/test-utils": "7.8.3", + "@electron-forge/core-utils": "7.9.0", + "@electron-forge/test-utils": "7.9.0", "fast-glob": "^3.2.7", "vitest": "^3.1.3" }, diff --git a/packages/template/vite/package.json b/packages/template/vite/package.json index 25c509ecb0..b162a618e6 100644 --- a/packages/template/vite/package.json +++ b/packages/template/vite/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/template-vite", - "version": "7.8.3", + "version": "7.9.0", "description": "Vite template for Electron Forge, gets you started with Vite really quickly", "repository": { "type": "git", @@ -15,12 +15,12 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/shared-types": "7.8.3", - "@electron-forge/template-base": "7.8.3", + "@electron-forge/shared-types": "7.9.0", + "@electron-forge/template-base": "7.9.0", "fs-extra": "^10.0.0" }, "devDependencies": { - "@electron-forge/test-utils": "7.8.3", + "@electron-forge/test-utils": "7.9.0", "listr2": "^7.0.2", "vitest": "^3.1.3" }, diff --git a/packages/template/webpack-typescript/package.json b/packages/template/webpack-typescript/package.json index 93167c8528..158e28e0b7 100644 --- a/packages/template/webpack-typescript/package.json +++ b/packages/template/webpack-typescript/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/template-webpack-typescript", - "version": "7.8.3", + "version": "7.9.0", "description": "Webpack-TypeScript template for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Shelley Vohr ", @@ -11,18 +11,18 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/shared-types": "7.8.3", - "@electron-forge/template-base": "7.8.3", + "@electron-forge/shared-types": "7.9.0", + "@electron-forge/template-base": "7.9.0", "fs-extra": "^10.0.0" }, "devDependencies": { - "@electron-forge/core-utils": "7.8.3", - "@electron-forge/maker-deb": "7.8.3", - "@electron-forge/maker-rpm": "7.8.3", - "@electron-forge/maker-squirrel": "7.8.3", - "@electron-forge/maker-zip": "7.8.3", - "@electron-forge/plugin-webpack": "7.8.3", - "@electron-forge/test-utils": "7.8.3", + "@electron-forge/core-utils": "7.9.0", + "@electron-forge/maker-deb": "7.9.0", + "@electron-forge/maker-rpm": "7.9.0", + "@electron-forge/maker-squirrel": "7.9.0", + "@electron-forge/maker-zip": "7.9.0", + "@electron-forge/plugin-webpack": "7.9.0", + "@electron-forge/test-utils": "7.9.0", "fast-glob": "^3.2.7", "fork-ts-checker-webpack-plugin": "^7.2.13", "listr2": "^7.0.2", diff --git a/packages/template/webpack/package.json b/packages/template/webpack/package.json index 67e8e9c0c9..592bab5aaf 100644 --- a/packages/template/webpack/package.json +++ b/packages/template/webpack/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/template-webpack", - "version": "7.8.3", + "version": "7.9.0", "description": "Webpack template for Electron Forge, gets you started with Webpack really quickly", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -11,12 +11,12 @@ "node": ">= 16.4.0" }, "dependencies": { - "@electron-forge/shared-types": "7.8.3", - "@electron-forge/template-base": "7.8.3", + "@electron-forge/shared-types": "7.9.0", + "@electron-forge/template-base": "7.9.0", "fs-extra": "^10.0.0" }, "devDependencies": { - "@electron-forge/test-utils": "7.8.3", + "@electron-forge/test-utils": "7.9.0", "listr2": "^7.0.2", "vitest": "^3.1.3" }, diff --git a/packages/utils/core-utils/package.json b/packages/utils/core-utils/package.json index fc04b4cbf3..5a45237bb8 100644 --- a/packages/utils/core-utils/package.json +++ b/packages/utils/core-utils/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/core-utils", - "version": "7.8.3", + "version": "7.9.0", "description": "Core utilities for the Electron Forge packages", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -8,7 +8,7 @@ "main": "dist/index.js", "typings": "dist/index.d.ts", "dependencies": { - "@electron-forge/shared-types": "7.8.3", + "@electron-forge/shared-types": "7.9.0", "@electron/rebuild": "^3.7.0", "@malept/cross-spawn-promise": "^2.0.0", "chalk": "^4.0.0", diff --git a/packages/utils/test-utils/package.json b/packages/utils/test-utils/package.json index bc6fb0751d..778f866310 100644 --- a/packages/utils/test-utils/package.json +++ b/packages/utils/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/test-utils", - "version": "7.8.3", + "version": "7.9.0", "description": "Helper utilities for the Electron Forge testsuite", "repository": "https://github.com/electron/forge", "author": "Mark Lee", diff --git a/packages/utils/tracer/package.json b/packages/utils/tracer/package.json index fe94a06cf1..a410928c57 100644 --- a/packages/utils/tracer/package.json +++ b/packages/utils/tracer/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/tracer", - "version": "7.8.3", + "version": "7.9.0", "description": "Tracing helpers for Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", diff --git a/packages/utils/types/package.json b/packages/utils/types/package.json index 4334bbc3dd..efce9db124 100644 --- a/packages/utils/types/package.json +++ b/packages/utils/types/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/shared-types", - "version": "7.8.3", + "version": "7.9.0", "description": "Shared types across Electron Forge", "repository": "https://github.com/electron/forge", "author": "Samuel Attard", @@ -8,7 +8,7 @@ "main": "dist/index.js", "typings": "dist/index.d.ts", "dependencies": { - "@electron-forge/tracer": "7.8.3", + "@electron-forge/tracer": "7.9.0", "@electron/packager": "^18.3.5", "@electron/rebuild": "^3.7.0", "listr2": "^7.0.2" diff --git a/packages/utils/web-multi-logger/package.json b/packages/utils/web-multi-logger/package.json index f5241c13bf..a2e2edc873 100644 --- a/packages/utils/web-multi-logger/package.json +++ b/packages/utils/web-multi-logger/package.json @@ -1,6 +1,6 @@ { "name": "@electron-forge/web-multi-logger", - "version": "7.8.3", + "version": "7.9.0", "description": "Display multiple streams of logs in one window", "repository": "https://github.com/electron/forge", "author": "Samuel Attard",