diff --git a/packages/api/cli/spec/util/check-system.spec.ts b/packages/api/cli/spec/util/check-system.spec.ts index cb75c91aa5..d46c06ca20 100644 --- a/packages/api/cli/spec/util/check-system.spec.ts +++ b/packages/api/cli/spec/util/check-system.spec.ts @@ -66,7 +66,7 @@ describe('checkPackageManager', () => { dev: '--dev', exact: '--exact', }); - vi.mocked(spawnPackageManager).mockImplementation((args) => { + vi.mocked(spawnPackageManager).mockImplementation((_pm, args) => { if (args?.join(' ') === 'config get node-linker') { return Promise.resolve('isolated'); } else if (args?.join(' ') === 'config get hoist-pattern') { @@ -91,7 +91,7 @@ describe('checkPackageManager', () => { dev: '--dev', exact: '--exact', }); - vi.mocked(spawnPackageManager).mockImplementation((args) => { + vi.mocked(spawnPackageManager).mockImplementation((_pm, args) => { if (args?.join(' ') === 'config get node-linker') { return Promise.resolve('isolated'); } else if (args?.join(' ') === `config get ${cfg}`) { diff --git a/packages/api/cli/src/util/check-system.ts b/packages/api/cli/src/util/check-system.ts index 9c694c53fc..4a980992d8 100644 --- a/packages/api/cli/src/util/check-system.ts +++ b/packages/api/cli/src/util/check-system.ts @@ -2,7 +2,7 @@ import { exec } from 'node:child_process'; import os from 'node:os'; import path from 'node:path'; -import { 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'; @@ -36,8 +36,9 @@ async function checkNodeVersion() { * configuration purposes. */ async function checkPnpmConfig() { - const hoistPattern = await spawnPackageManager(['config', 'get', 'hoist-pattern']); - const publicHoistPattern = await spawnPackageManager(['config', 'get', 'public-hoist-pattern']); + const { pnpm } = PACKAGE_MANAGERS; + 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( @@ -49,7 +50,7 @@ async function checkPnpmConfig() { return; } - const nodeLinker = await spawnPackageManager(['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.' @@ -74,7 +75,7 @@ const ALLOWLISTED_VERSIONS: Record { const mod = await importOriginal(); return { ...mod, - resolvePackageManager: vi.fn(), spawnPackageManager: vi.fn(), }; }); describe('installDependencies', () => { it('should immediately resolve if no deps are provided', async () => { - vi.mocked(resolvePackageManager).mockResolvedValue({ executable: 'npm', install: 'install', dev: '--save-dev', exact: '--save-exact' }); - await installDependencies('mydir', []); + await installDependencies(PACKAGE_MANAGERS['npm'], 'mydir', []); expect(spawnPackageManager).not.toHaveBeenCalled(); }); it('should reject if the package manager fails to spawn', async () => { - vi.mocked(resolvePackageManager).mockResolvedValue({ executable: 'npm', install: 'install', dev: '--save-dev', exact: '--save-exact' }); vi.mocked(spawnPackageManager).mockRejectedValueOnce('fail'); - await expect(installDependencies('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(resolvePackageManager).mockResolvedValue({ executable: 'npm', install: 'install', dev: '--save-dev', exact: '--save-exact' }); vi.mocked(spawnPackageManager).mockResolvedValueOnce('pass'); - await expect(installDependencies('void', ['electron'])).resolves.toBe(undefined); + await expect(installDependencies(PACKAGE_MANAGERS['npm'], 'void', ['electron'])).resolves.toBe(undefined); }); - describe.each([ - { executable: 'npm' as const, install: 'install', exact: '--save-exact', dev: '--save-dev' }, - { executable: 'yarn' as const, install: 'add', exact: '--exact', dev: '--dev' }, - { executable: 'pnpm' as const, install: 'install', exact: '--save-exact', dev: '--save-dev' }, - ])('$executable', (args) => { - beforeEach(() => { - vi.mocked(resolvePackageManager).mockResolvedValue(args); - }); - + describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGERS['pnpm']])('$executable', (pm) => { it('should install deps', async () => { - await installDependencies('mydir', ['react']); - expect(spawnPackageManager).toHaveBeenCalledWith([args.install, 'react'], expect.anything()); + await installDependencies(pm, 'mydir', ['react']); + expect(spawnPackageManager).toHaveBeenCalledWith(pm, [pm.install, 'react'], expect.anything()); }); it('should install dev deps', async () => { - await installDependencies('mydir', ['eslint'], DepType.DEV); - expect(spawnPackageManager).toHaveBeenCalledWith([args.install, 'eslint', args.dev], expect.anything()); + await installDependencies(pm, 'mydir', ['eslint'], DepType.DEV); + expect(spawnPackageManager).toHaveBeenCalledWith(pm, [pm.install, 'eslint', pm.dev], expect.anything()); }); it('should install exact deps', async () => { - await installDependencies('mydir', ['react'], DepType.PROD, DepVersionRestriction.EXACT); - expect(spawnPackageManager).toHaveBeenCalledWith([args.install, 'react', args.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('mydir', ['eslint'], DepType.DEV, DepVersionRestriction.EXACT); - expect(spawnPackageManager).toHaveBeenCalledWith([args.install, 'eslint', args.dev, args.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/slow/api.slow.spec.ts b/packages/api/core/spec/slow/api.slow.spec.ts index ff78287366..67f61fb287 100644 --- a/packages/api/core/spec/slow/api.slow.spec.ts +++ b/packages/api/core/spec/slow/api.slow.spec.ts @@ -3,12 +3,12 @@ import { execSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; -import { 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 { readMetadata } from 'electron-installer-common'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest'; // eslint-disable-next-line n/no-missing-import import { api, InitOptions } from '../../src/api'; @@ -29,19 +29,18 @@ async function updatePackageJSON(dir: string, packageJSONUpdater: (packageJSON: await fs.promises.writeFile(path.resolve(dir, 'package.json'), JSON.stringify(packageJSON), 'utf-8'); } -describe.each([{ pm: 'npm' }, { pm: 'yarn' }, { pm: 'pnpm' }])(`init (with $pm)`, ({ pm }) => { +describe.each([PACKAGE_MANAGERS['npm'], PACKAGE_MANAGERS['yarn'], PACKAGE_MANAGERS['pnpm']])(`init (with $executable)`, (pm) => { let dir: string; beforeAll(async () => { - await spawnPackageManager(['run', 'link:prepare']); - process.env.NODE_INSTALLER = pm; + await spawnPackageManager(pm, ['run', 'link:prepare']); - if (pm === 'pnpm') { - await spawnPackageManager('config set node-linker hoisted'.split(' ')); + if (pm.executable === 'pnpm') { + await spawnPackageManager(pm, 'config set node-linker hoisted'.split(' ')); } return async () => { - await spawnPackageManager(['run', 'link:remove']); + await spawnPackageManager(pm, ['run', 'link:remove']); delete process.env.NODE_INSTALLER; }; }); @@ -187,7 +186,7 @@ describe.each([{ pm: 'npm' }, { pm: 'yarn' }, { pm: 'pnpm' }])(`init (with $pm)` }); describe('import', () => { - beforeAll(async () => { + beforeEach(async () => { dir = await ensureTestDirIsNonexistent(); await fs.promises.mkdir(dir); execSync(`git clone https://github.com/electron/electron-quick-start.git . --quiet`, { @@ -209,9 +208,7 @@ describe.each([{ pm: 'npm' }, { pm: 'yarn' }, { pm: 'pnpm' }])(`init (with $pm)` expect(fs.existsSync(path.join(dir, 'forge.config.js'))).toEqual(true); - execSync(`${pm} install`, { - cwd: dir, - }); + await spawnPackageManager(pm, ['install'], { cwd: dir }); await api.package({ dir }); @@ -297,7 +294,7 @@ describe.each([{ pm: 'npm' }, { pm: 'yarn' }, { pm: 'pnpm' }])(`init (with $pm)` describe('with prebuilt native module deps installed', () => { beforeAll(async () => { - await installDeps(dir, ['ref-napi']); + await installDeps(pm, dir, ['ref-napi']); return async () => { await fs.promises.rm(path.resolve(dir, 'node_modules/ref-napi'), { recursive: true, force: true }); 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 de761889e1..0809aafac9 100644 --- a/packages/api/core/spec/slow/install-dependencies.slow.spec.ts +++ b/packages/api/core/spec/slow/install-dependencies.slow.spec.ts @@ -2,6 +2,7 @@ import fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; +import { PACKAGE_MANAGERS } from '@electron-forge/core-utils'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import installDeps from '../../src/util/install-dependencies'; @@ -16,7 +17,7 @@ describe.runIf(!(process.platform === 'linux' && process.env.CI))('install-depen }); it('should install the latest minor version when the dependency has a caret', async () => { - await installDeps(installDir, ['debug@^2.0.0']); + 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'); diff --git a/packages/api/core/src/api/import.ts b/packages/api/core/src/api/import.ts index 321f3c2afb..e1a816aecb 100644 --- a/packages/api/core/src/api/import.ts +++ b/packages/api/core/src/api/import.ts @@ -1,6 +1,6 @@ import path from 'node:path'; -import { resolvePackageManager, updateElectronDependency } from '@electron-forge/core-utils'; +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'; @@ -58,7 +58,7 @@ export default autoTrace( childTrace, { dir = process.cwd(), interactive = false, confirmImport, shouldContinueOnExisting, shouldRemoveDependency, shouldUpdateScript, outDir }: ImportOptions ): Promise => { - const listrOptions: ForgeListrOptions = { + const listrOptions: ForgeListrOptions<{ pm: PMDetails }> = { concurrent: false, rendererOptions: { collapseSubtasks: false, @@ -188,12 +188,18 @@ export default autoTrace( await fs.writeJson(path.resolve(dir, 'package.json'), packageJSON, { spaces: 2 }); }; - return task.newListr( + 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 (_, task) => { - const pm = await resolvePackageManager(); + task: async ({ pm }, task) => { await writeChanges(); d('deleting old dependencies forcefully'); @@ -202,15 +208,15 @@ export default autoTrace( d('installing dependencies'); task.output = `${pm.executable} ${pm.install} ${importDeps.join(' ')}`; - await installDepList(dir, importDeps); + await installDepList(pm, dir, importDeps); d('installing devDependencies'); task.output = `${pm.executable} ${pm.install} ${pm.dev} ${importDevDeps.join(' ')}`; - await installDepList(dir, importDevDeps, DepType.DEV); + 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(dir, importExactDevDeps, DepType.DEV, DepVersionRestriction.EXACT); + await installDepList(pm, dir, importExactDevDeps, DepType.DEV, DepVersionRestriction.EXACT); }, }, { 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 ec1e1805b2..f61b916c62 100644 --- a/packages/api/core/src/api/init-scripts/init-link.ts +++ b/packages/api/core/src/api/init-scripts/init-link.ts @@ -1,6 +1,6 @@ import path from 'node:path'; -import { resolvePackageManager, spawnPackageManager } from '@electron-forge/core-utils'; +import { PMDetails, spawnPackageManager } from '@electron-forge/core-utils'; import { ForgeListrTask } from '@electron-forge/shared-types'; import debug from 'debug'; @@ -17,12 +17,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(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'); const packageJson = await readRawPackageJson(dir); - const pm = await resolvePackageManager(); // 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. @@ -30,7 +29,7 @@ export async function initLink(dir: string, task?: ForgeListrTask) { 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(['link', '--link-folder', linkFolder, packageName], { + await spawnPackageManager(pm, ['link', '--link-folder', linkFolder, packageName], { cwd: dir, }); } 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 98f9f2e2f3..edf1219dfa 100644 --- a/packages/api/core/src/api/init-scripts/init-npm.ts +++ b/packages/api/core/src/api/init-scripts/init-npm.ts @@ -1,6 +1,6 @@ import path from 'node:path'; -import { resolvePackageManager } from '@electron-forge/core-utils'; +import { PMDetails } from '@electron-forge/core-utils'; import { ForgeListrTask } from '@electron-forge/shared-types'; import debug from 'debug'; import fs from 'fs-extra'; @@ -27,19 +27,18 @@ export const devDeps = [ ]; export const exactDevDeps = ['electron']; -export const initNPM = async (dir: string, task: ForgeListrTask): Promise => { +export const initNPM = async (pm: PMDetails, dir: string, task: ForgeListrTask): Promise => { d('installing dependencies'); - const pm = await resolvePackageManager(); task.output = `${pm.executable} ${pm.install} ${deps.join(' ')}`; - await installDepList(dir, deps); + await installDepList(pm, dir, deps); d('installing devDependencies'); task.output = `${pm.executable} ${pm.install} ${pm.dev} ${deps.join(' ')}`; - await installDepList(dir, devDeps, DepType.DEV); + await installDepList(pm, dir, devDeps, DepType.DEV); d('installing exact devDependencies'); for (const packageName of exactDevDeps) { task.output = `${pm.executable} ${pm.install} ${pm.dev} ${pm.exact} ${packageName}`; - await installDepList(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 5d357295e6..5b5128cccb 100644 --- a/packages/api/core/src/api/init.ts +++ b/packages/api/core/src/api/init.ts @@ -1,7 +1,8 @@ import path from 'node:path'; -import { resolvePackageManager } from '@electron-forge/core-utils'; +import { PMDetails, resolvePackageManager } from '@electron-forge/core-utils'; import { ForgeTemplate } from '@electron-forge/shared-types'; +import chalk from 'chalk'; import debug from 'debug'; import { Listr } from 'listr2'; import semver from 'semver'; @@ -56,12 +57,18 @@ async function validateTemplate(template: string, templateModule: ForgeTemplate) export default async ({ dir = process.cwd(), interactive = false, copyCIFiles = false, force = false, template = 'base' }: InitOptions): Promise => { d(`Initializing in: ${dir}`); - const pm = await resolvePackageManager(); - const runner = new Listr<{ templateModule: ForgeTemplate; + 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: `Locating custom template: "${template}"`, task: async (ctx) => { @@ -83,7 +90,7 @@ export default async ({ dir = process.cwd(), interactive = false, copyCIFiles = }, }, { - title: 'Initializing template', + title: `Initializing template`, task: async ({ templateModule }, task) => { if (typeof templateModule.initializeTemplate === 'function') { const tasks = await templateModule.initializeTemplate(dir, { copyCIFiles, force }); @@ -100,23 +107,23 @@ export default async ({ dir = process.cwd(), interactive = false, copyCIFiles = [ { title: 'Installing production dependencies', - task: async (_, task) => { + task: async ({ pm }, task) => { d('installing dependencies'); if (templateModule.dependencies?.length) { task.output = `${pm.executable} ${pm.install} ${pm.dev} ${templateModule.dependencies.join(' ')}`; } - return await installDepList(dir, templateModule.dependencies || [], DepType.PROD, DepVersionRestriction.RANGE); + return await installDepList(pm, dir, templateModule.dependencies || [], DepType.PROD, DepVersionRestriction.RANGE); }, exitOnError: false, }, { title: 'Installing development dependencies', - task: async (_, task) => { + task: async ({ pm }, task) => { d('installing devDependencies'); if (templateModule.devDependencies?.length) { task.output = `${pm.executable} ${pm.install} ${pm.dev} ${templateModule.devDependencies.join(' ')}`; } - await installDepList(dir, templateModule.devDependencies || [], DepType.DEV); + await installDepList(pm, dir, templateModule.devDependencies || [], DepType.DEV); }, exitOnError: false, }, @@ -126,15 +133,16 @@ export default async ({ dir = process.cwd(), interactive = false, copyCIFiles = return task.newListr([ { title: 'Installing common dependencies', - task: async (_, task) => { - await initNPM(dir, task); + task: async ({ pm }, task) => { + await initNPM(pm, dir, task); }, exitOnError: false, }, { - title: process.env.LINK_FORGE_DEPENDENCIES_ON_INIT ? 'Linking forge dependencies' : 'Skip linking forge dependencies', - task: async (_, task) => { - await initLink(dir, task); + title: 'Linking Forge dependencies to local build', + enabled: !!process.env.LINK_FORGE_DEPENDENCIES_ON_INIT, + task: async ({ pm }, task) => { + await initLink(pm, dir, task); }, exitOnError: true, }, diff --git a/packages/api/core/src/api/make.ts b/packages/api/core/src/api/make.ts index 0e046d2fb0..f76650e67b 100644 --- a/packages/api/core/src/api/make.ts +++ b/packages/api/core/src/api/make.ts @@ -338,7 +338,7 @@ export const listrMake = ( } receiveMakeResults?.(ctx.outputs); - task.output = `Artifacts available at: ${chalk.green(outputLocations.join(', '))})}`; + task.output = `Artifacts available at: ${chalk.green(outputLocations.join(', '))}`; }), rendererOptions: { persistentOutput: true, diff --git a/packages/api/core/src/util/install-dependencies.ts b/packages/api/core/src/util/install-dependencies.ts index 77f08d73ce..b7031911a6 100644 --- a/packages/api/core/src/util/install-dependencies.ts +++ b/packages/api/core/src/util/install-dependencies.ts @@ -1,4 +1,4 @@ -import { resolvePackageManager, spawnPackageManager } from '@electron-forge/core-utils'; +import { PMDetails, spawnPackageManager } from '@electron-forge/core-utils'; import { ExitError } from '@malept/cross-spawn-promise'; import debug from 'debug'; @@ -14,8 +14,7 @@ export enum DepVersionRestriction { RANGE = 'RANGE', } -export default async (dir: string, deps: string[], depType = DepType.PROD, versionRestriction = DepVersionRestriction.RANGE): Promise => { - const pm = await resolvePackageManager(); +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'); @@ -27,7 +26,7 @@ export default async (dir: string, deps: string[], depType = DepType.PROD, versi d('executing', JSON.stringify(cmd), 'in:', dir); try { - await spawnPackageManager(cmd, { + await spawnPackageManager(pm, cmd, { cwd: dir, stdio: 'pipe', }); diff --git a/packages/plugin/webpack/spec/fixtures/apps/native-modules/package.json b/packages/plugin/webpack/spec/fixtures/apps/native-modules/package.json index 1cd85b849c..462678a86d 100644 --- a/packages/plugin/webpack/spec/fixtures/apps/native-modules/package.json +++ b/packages/plugin/webpack/spec/fixtures/apps/native-modules/package.json @@ -7,7 +7,7 @@ "start": "npx electron ." }, "devDependencies": { - "@vercel/webpack-asset-relocator-loader": "1.7.0", + "@vercel/webpack-asset-relocator-loader": "1.7.3", "electron": "^33.3.1" }, "dependencies": { diff --git a/packages/template/vite-typescript/spec/ViteTypeScriptTemplate.slow.spec.ts b/packages/template/vite-typescript/spec/ViteTypeScriptTemplate.slow.spec.ts index 9dd6aaa27c..05eb9ea878 100644 --- a/packages/template/vite-typescript/spec/ViteTypeScriptTemplate.slow.spec.ts +++ b/packages/template/vite-typescript/spec/ViteTypeScriptTemplate.slow.spec.ts @@ -2,7 +2,7 @@ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; -import { 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,12 +15,12 @@ describe('ViteTypeScriptTemplate', () => { let dir: string; beforeAll(async () => { - await spawnPackageManager(['run', 'link:prepare']); + await spawnPackageManager(PACKAGE_MANAGERS['yarn'], ['run', 'link:prepare']); dir = await testUtils.ensureTestDirIsNonexistent(); }); afterAll(async () => { - await spawnPackageManager(['run', 'link:remove']); + await spawnPackageManager(PACKAGE_MANAGERS['yarn'], ['run', 'link:remove']); if (os.platform() !== 'win32') { // Windows platform `fs.remove(dir)` logic using `npm run test:clear`. await fs.promises.rm(dir, { force: true, recursive: true }); @@ -81,14 +81,14 @@ describe('ViteTypeScriptTemplate', () => { vite: `${require('../../../../node_modules/vite/package.json').version}`, }; await fs.promises.writeFile(path.resolve(dir, 'package.json'), JSON.stringify(pj)); - await spawnPackageManager(['install'], { + await spawnPackageManager(PACKAGE_MANAGERS['yarn'], ['install'], { cwd: dir, }); // Installing deps removes symlinks that were added at the start of this // spec via `api.init`. So we should re-link local forge dependencies // again. - await initLink(dir); + await initLink(PACKAGE_MANAGERS['yarn'], dir); }); afterAll(() => { diff --git a/packages/template/webpack-typescript/spec/WebpackTypeScript.slow.spec.ts b/packages/template/webpack-typescript/spec/WebpackTypeScript.slow.spec.ts index 200dc029e4..f814c796ba 100644 --- a/packages/template/webpack-typescript/spec/WebpackTypeScript.slow.spec.ts +++ b/packages/template/webpack-typescript/spec/WebpackTypeScript.slow.spec.ts @@ -1,7 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; -import { 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 +14,7 @@ describe('WebpackTypeScriptTemplate', () => { let dir: string; beforeAll(async () => { - await spawnPackageManager(['run', 'link:prepare']); + await spawnPackageManager(PACKAGE_MANAGERS['yarn'], ['run', 'link:prepare']); dir = await testUtils.ensureTestDirIsNonexistent(); }); @@ -71,14 +71,14 @@ describe('WebpackTypeScriptTemplate', () => { webpack: `${require('../../../../node_modules/webpack/package.json').version}`, }; await fs.promises.writeFile(path.resolve(dir, 'package.json'), JSON.stringify(pj)); - await spawnPackageManager(['install'], { + await spawnPackageManager(PACKAGE_MANAGERS['yarn'], ['install'], { cwd: dir, }); // Installing deps removes symlinks that were added at the start of this // spec via `api.init`. So we should re-link local forge dependencies // again. - await initLink(dir); + await initLink(PACKAGE_MANAGERS['yarn'], dir); }); afterAll(() => { @@ -94,7 +94,7 @@ describe('WebpackTypeScriptTemplate', () => { }); afterAll(async () => { - await spawnPackageManager(['link:remove']); + await spawnPackageManager(PACKAGE_MANAGERS['yarn'], ['link:remove']); await fs.promises.rm(dir, { recursive: true, force: true }); }); }); diff --git a/packages/utils/core-utils/src/package-manager.ts b/packages/utils/core-utils/src/package-manager.ts index a873a8a97c..2f30012e95 100644 --- a/packages/utils/core-utils/src/package-manager.ts +++ b/packages/utils/core-utils/src/package-manager.ts @@ -9,7 +9,12 @@ 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 }; -const MANAGERS: Record = { +let hasWarned = false; + +/** + * Supported package managers and the commands and flags they need to install dependencies. + */ +export const PACKAGE_MANAGERS: Record = { yarn: { executable: 'yarn', install: 'add', @@ -75,8 +80,9 @@ export const resolvePackageManager: () => Promise = async () => { 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') { + 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`)); + hasWarned = true; } switch (installer) { @@ -86,7 +92,7 @@ export const resolvePackageManager: () => Promise = async () => { d( `Resolved package manager to ${installer}. (Derived from NODE_INSTALLER: ${process.env.NODE_INSTALLER}, npm_config_user_agent: ${executingPM}, lockfile: ${lockfilePM}.)` ); - return { ...MANAGERS[installer], version: executingPM?.version }; + return { ...PACKAGE_MANAGERS[installer], version: executingPM?.version }; default: if (installer !== undefined) { console.warn( @@ -96,9 +102,10 @@ export const resolvePackageManager: () => Promise = async () => { } else { d(`No package manager detected. Falling back to npm.`); } - return MANAGERS['npm']; + return PACKAGE_MANAGERS['npm']; } }; -export const spawnPackageManager = async (args?: CrossSpawnArgs, opts?: CrossSpawnOptions): Promise => - spawn((await resolvePackageManager()).executable, args, opts); +export const spawnPackageManager = async (pm: PMDetails, args?: CrossSpawnArgs, opts?: CrossSpawnOptions): Promise => { + return spawn(pm.executable, args, opts); +};