Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(core): pnpm support #3822

Merged
merged 37 commits into from
Feb 11, 2025
Merged
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
7c77355
chore: rename yarn-or-npm -> package-manager
erickzhao Jan 24, 2025
2f6ef3d
refactor: remove hasYarn function and rename spawn
erickzhao Jan 24, 2025
c70224d
pnpm support take 1
erickzhao Jan 28, 2025
0acb1f3
install pnpm in CI
erickzhao Jan 28, 2025
284e219
reverse order of link:prepare
erickzhao Jan 28, 2025
1d85d64
attempt to remove corepack for now
erickzhao Jan 28, 2025
431116d
hoist it!
erickzhao Jan 28, 2025
cafacc3
downgrade to pnpm 9
erickzhao Jan 28, 2025
a19a6ea
adjustments
erickzhao Jan 28, 2025
15db452
copyFile instead of rename
erickzhao Jan 28, 2025
89a0fd1
add additional checks
erickzhao Jan 29, 2025
d1547a8
Merge branch 'main' into pnpmpnpmpnpmpnpm
erickzhao Jan 29, 2025
e23518f
use default reporter in CI
erickzhao Jan 29, 2025
dacb4e5
Merge branch 'pnpmpnpmpnpmpnpm' of github.com:electron/forge into pnp…
erickzhao Jan 29, 2025
93c0ef8
fix [object Object]
erickzhao Jan 29, 2025
3dc34f3
Merge branch 'main' into pnpmpnpmpnpmpnpm
erickzhao Jan 29, 2025
dccf638
Merge branch 'pnpmpnpmpnpmpnpm' of github.com:electron/forge into pnp…
erickzhao Jan 29, 2025
8b0e22f
add back `packageManager` and work around with `.npmrc` setting
erickzhao Jan 29, 2025
1592d85
add missing mock :)
erickzhao Jan 29, 2025
d5a43a2
fix CLI output for initNPM commands
erickzhao Jan 30, 2025
64ac631
Merge branch 'main' into pnpmpnpmpnpmpnpm
erickzhao Feb 3, 2025
d695b26
use npm_config_user_agent
erickzhao Feb 4, 2025
7a5ab70
Update .npmrc
erickzhao Feb 5, 2025
3350dfe
Update packages/template/base/tmpl/.npmrc
erickzhao Feb 5, 2025
65458b3
Update packages/utils/core-utils/src/package-manager.ts
erickzhao Feb 5, 2025
1b084df
clarify todo with my name
erickzhao Feb 5, 2025
336662c
Update packages/api/core/spec/slow/api.slow.spec.ts
erickzhao Feb 5, 2025
33454a5
Update packages/utils/core-utils/spec/package-manager.spec.ts
erickzhao Feb 5, 2025
514f2a1
Merge branch 'pnpmpnpmpnpmpnpm' of github.com:electron/forge into pnp…
erickzhao Feb 5, 2025
f3ff623
afterall
erickzhao Feb 5, 2025
519aa9f
clarify cleanup function
erickzhao Feb 5, 2025
8fb93be
Update packages/utils/core-utils/spec/package-manager.spec.ts
erickzhao Feb 6, 2025
12168ac
Revert "Update packages/utils/core-utils/spec/package-manager.spec.ts"
erickzhao Feb 6, 2025
f19e1cb
Update packages/utils/core-utils/spec/package-manager.spec.ts
erickzhao Feb 6, 2025
67ff824
Merge branch 'pnpmpnpmpnpmpnpm' of github.com:electron/forge into pnp…
erickzhao Feb 6, 2025
2e96421
adjust tests and findUp
erickzhao Feb 6, 2025
7825022
support `hoist-pattern` and `public-hoist-pattern` as well
erickzhao Feb 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor: remove hasYarn function and rename spawn
erickzhao committed Jan 24, 2025
commit 2f6ef3d9d53a5509a2b4f284316e8eb119e9ddbd
1 change: 1 addition & 0 deletions packages/api/cli/package.json
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@
},
"dependencies": {
"@electron-forge/core": "7.6.1",
"@electron-forge/core-utils": "7.6.1",
"@electron-forge/shared-types": "7.6.1",
"@electron/get": "^3.0.0",
"chalk": "^4.0.0",
6 changes: 3 additions & 3 deletions packages/api/cli/src/util/check-system.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import { exec } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';

import { utils as forgeUtils } from '@electron-forge/core';
import { resolvePackageManager, spawnPackageManager } from '@electron-forge/core-utils';
import { ForgeListrTask } from '@electron-forge/shared-types';
import debug from 'debug';
import fs from 'fs-extra';
@@ -55,9 +55,9 @@ function warnIfPackageManagerIsntAKnownGoodVersion(packageManager: string, versi
}

async function checkPackageManagerVersion() {
const version = await forgeUtils.yarnOrNpmSpawn(['--version']);
const version = await spawnPackageManager(['--version']);
const versionString = version.toString().trim();
if (await forgeUtils.hasYarn()) {
if ((await resolvePackageManager()) === 'yarn') {
warnIfPackageManagerIsntAKnownGoodVersion('Yarn', versionString, YARN_ALLOWLISTED_VERSIONS);
return `yarn@${versionString}`;
} else {
26 changes: 13 additions & 13 deletions packages/api/core/spec/fast/util/install-dependencies.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { hasYarn, yarnOrNpmSpawn } from '@electron-forge/core-utils';
import { resolvePackageManager, spawnPackageManager } from '@electron-forge/core-utils';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import installDependencies, { DepType, DepVersionRestriction } from '../../../src/util/install-dependencies';
@@ -7,53 +7,53 @@ vi.mock(import('@electron-forge/core-utils'), async (importOriginal) => {
const mod = await importOriginal();
return {
...mod,
hasYarn: vi.fn(),
yarnOrNpmSpawn: vi.fn(),
resolvePackageManager: vi.fn(),
spawnPackageManager: vi.fn(),
};
});

describe('installDependencies', () => {
it('should immediately resolve if no deps are provided', async () => {
await installDependencies('mydir', []);
expect(yarnOrNpmSpawn).not.toHaveBeenCalled();
expect(spawnPackageManager).not.toHaveBeenCalled();
});

it('should reject if reject the promise if exit code is not 0', async () => {
vi.mocked(yarnOrNpmSpawn).mockRejectedValueOnce('fail');
vi.mocked(spawnPackageManager).mockRejectedValueOnce('fail');
await expect(installDependencies('void', ['electron'])).rejects.toThrow('fail');
});

it('should resolve if reject the promise if exit code is 0', async () => {
vi.mocked(yarnOrNpmSpawn).mockResolvedValueOnce('pass');
vi.mocked(spawnPackageManager).mockResolvedValueOnce('pass');
await expect(installDependencies('void', ['electron'])).resolves.toBe(undefined);
});

describe.each([
{ pm: 'npm', install: 'install', flags: { exact: '--save-exact', dev: '--save-dev' } },
{ pm: 'yarn', install: 'add', flags: { exact: '--exact', dev: '--dev' } },
{ pm: 'npm' as const, install: 'install', flags: { exact: '--save-exact', dev: '--save-dev' } },
{ pm: 'yarn' as const, install: 'add', flags: { exact: '--exact', dev: '--dev' } },
])('$pm', ({ pm, install, flags }) => {
beforeEach(() => {
vi.mocked(hasYarn).mockResolvedValue(pm === 'yarn');
vi.mocked(resolvePackageManager).mockResolvedValue(pm);
});

it('should install deps', async () => {
await installDependencies('mydir', ['react']);
expect(yarnOrNpmSpawn).toHaveBeenCalledWith([install, 'react'], expect.anything());
expect(spawnPackageManager).toHaveBeenCalledWith([install, 'react'], expect.anything());
});

it('should install dev deps', async () => {
await installDependencies('mydir', ['eslint'], DepType.DEV);
expect(yarnOrNpmSpawn).toHaveBeenCalledWith([install, 'eslint', flags.dev], expect.anything());
expect(spawnPackageManager).toHaveBeenCalledWith([install, 'eslint', flags.dev], expect.anything());
});

it('should install exact deps', async () => {
await installDependencies('mydir', ['react'], DepType.PROD, DepVersionRestriction.EXACT);
expect(yarnOrNpmSpawn).toHaveBeenCalledWith([install, 'react', flags.exact], expect.anything());
expect(spawnPackageManager).toHaveBeenCalledWith([install, 'react', flags.exact], expect.anything());
});

it('should install exact dev deps', async () => {
await installDependencies('mydir', ['eslint'], DepType.DEV, DepVersionRestriction.EXACT);
expect(yarnOrNpmSpawn).toHaveBeenCalledWith([install, 'eslint', flags.dev, flags.exact], expect.anything());
expect(spawnPackageManager).toHaveBeenCalledWith([install, 'eslint', flags.dev, flags.exact], expect.anything());
});
});
});
4 changes: 2 additions & 2 deletions packages/api/core/src/api/import.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'node:path';

import { safeYarnOrNpm, updateElectronDependency } from '@electron-forge/core-utils';
import { 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';
@@ -193,7 +193,7 @@ export default autoTrace(
{
title: 'Installing dependencies',
task: async (_, task) => {
const packageManager = await safeYarnOrNpm();
const packageManager = await resolvePackageManager();
await writeChanges();

d('deleting old dependencies forcefully');
6 changes: 3 additions & 3 deletions packages/api/core/src/api/init-scripts/init-link.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'node:path';

import { safeYarnOrNpm, yarnOrNpmSpawn } from '@electron-forge/core-utils';
import { resolvePackageManager, spawnPackageManager } from '@electron-forge/core-utils';
import { ForgeListrTask } from '@electron-forge/shared-types';
import debug from 'debug';

@@ -22,12 +22,12 @@ export async function initLink<T>(dir: string, task?: ForgeListrTask<T>) {
if (shouldLink) {
d('Linking forge dependencies');
const packageJson = await readRawPackageJson(dir);
const packageManager = await safeYarnOrNpm();
const packageManager = await resolvePackageManager();
const linkFolder = path.resolve(__dirname, '..', '..', '..', '..', '..', '..', '.links');
for (const packageName of Object.keys(packageJson.devDependencies)) {
if (packageName.startsWith('@electron-forge/')) {
if (task) task.output = `${packageManager} link --link-folder ${linkFolder} ${packageName}`;
await yarnOrNpmSpawn(['link', '--link-folder', linkFolder, packageName], {
await spawnPackageManager(['link', '--link-folder', linkFolder, packageName], {
cwd: dir,
});
}
4 changes: 2 additions & 2 deletions packages/api/core/src/api/init-scripts/init-npm.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'node:path';

import { safeYarnOrNpm } from '@electron-forge/core-utils';
import { resolvePackageManager } from '@electron-forge/core-utils';
import { ForgeListrTask } from '@electron-forge/shared-types';
import debug from 'debug';
import fs from 'fs-extra';
@@ -29,7 +29,7 @@ export const exactDevDeps = ['electron'];

export const initNPM = async <T>(dir: string, task: ForgeListrTask<T>): Promise<void> => {
d('installing dependencies');
const packageManager = await safeYarnOrNpm();
const packageManager = await resolvePackageManager();
task.output = `${packageManager} install ${deps.join(' ')}`;
await installDepList(dir, deps);

4 changes: 2 additions & 2 deletions packages/api/core/src/api/init.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'node:path';

import { safeYarnOrNpm } from '@electron-forge/core-utils';
import { resolvePackageManager } from '@electron-forge/core-utils';
import { ForgeTemplate } from '@electron-forge/shared-types';
import debug from 'debug';
import { Listr } from 'listr2';
@@ -56,7 +56,7 @@ async function validateTemplate(template: string, templateModule: ForgeTemplate)
export default async ({ dir = process.cwd(), interactive = false, copyCIFiles = false, force = false, template = 'base' }: InitOptions): Promise<void> => {
d(`Initializing in: ${dir}`);

const packageManager = await safeYarnOrNpm();
const packageManager = await resolvePackageManager();

const runner = new Listr<{
templateModule: ForgeTemplate;
6 changes: 2 additions & 4 deletions packages/api/core/src/util/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getElectronVersion, hasYarn, yarnOrNpmSpawn } from '@electron-forge/core-utils';
import { getElectronVersion, spawnPackageManager } from '@electron-forge/core-utils';

import {
BuildIdentifierConfig,
@@ -24,9 +24,7 @@ export default class ForgeUtils {

getElectronVersion = getElectronVersion;

hasYarn = hasYarn;

yarnOrNpmSpawn = yarnOrNpmSpawn;
spawnPackageManager = spawnPackageManager;
Copy link
Member Author

Choose a reason for hiding this comment

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

Is a change in API signatures for @electron-forge/core-utils a breaking change? If it is, I can add a temporary shim for these utils and deprecate them.


/**
* Register a virtual config file for forge to find.
9 changes: 5 additions & 4 deletions packages/api/core/src/util/install-dependencies.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { hasYarn, yarnOrNpmSpawn } from '@electron-forge/core-utils';
import { resolvePackageManager, spawnPackageManager } from '@electron-forge/core-utils';
import { ExitError } from '@malept/cross-spawn-promise';
import debug from 'debug';

@@ -15,13 +15,14 @@ export enum DepVersionRestriction {
}

export default async (dir: string, deps: string[], depType = DepType.PROD, versionRestriction = DepVersionRestriction.RANGE): Promise<void> => {
d('installing', JSON.stringify(deps), 'in:', dir, `depType=${depType},versionRestriction=${versionRestriction},withYarn=${await hasYarn()}`);
const pm = await resolvePackageManager();
d('installing', JSON.stringify(deps), 'in:', dir, `depType=${depType},versionRestriction=${versionRestriction},withPackageManager=${pm}`);
if (deps.length === 0) {
d('nothing to install, stopping immediately');
return Promise.resolve();
}
let cmd = ['install'].concat(deps);
if (await hasYarn()) {
if (pm === 'yarn') {
cmd = ['add'].concat(deps);
if (depType === DepType.DEV) cmd.push('--dev');
if (versionRestriction === DepVersionRestriction.EXACT) cmd.push('--exact');
@@ -31,7 +32,7 @@ export default async (dir: string, deps: string[], depType = DepType.PROD, versi
}
d('executing', JSON.stringify(cmd), 'in:', dir);
try {
await yarnOrNpmSpawn(cmd, {
await spawnPackageManager(cmd, {
cwd: dir,
stdio: 'pipe',
});
10 changes: 5 additions & 5 deletions packages/utils/core-utils/spec/package-manager.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { detect } from 'detect-package-manager';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { safeYarnOrNpm } from '../src/package-manager';
import { resolvePackageManager } from '../src/package-manager';

describe('package-manager', () => {
let nodeInstaller: string | undefined;
@@ -21,24 +21,24 @@ describe('package-manager', () => {

it('should by default equal the system package manager value', async () => {
const pm = await detect();
await expect(safeYarnOrNpm()).resolves.toEqual(pm);
await expect(resolvePackageManager()).resolves.toEqual(pm);
});

it('should return yarn if NODE_INSTALLER=yarn', async () => {
process.env.NODE_INSTALLER = 'yarn';
await expect(safeYarnOrNpm()).resolves.toEqual('yarn');
await expect(resolvePackageManager()).resolves.toEqual('yarn');
});

it('should return npm if NODE_INSTALLER=npm', async () => {
process.env.NODE_INSTALLER = 'npm';
await expect(safeYarnOrNpm()).resolves.toEqual('npm');
await expect(resolvePackageManager()).resolves.toEqual('npm');
});

it('should return system value if NODE_INSTALLER is an unrecognized installer', async () => {
process.env.NODE_INSTALLER = 'magical_unicorn';
console.warn = vi.fn();
const pm = await detect();
await expect(safeYarnOrNpm()).resolves.toEqual(pm);
await expect(resolvePackageManager()).resolves.toEqual(pm);
expect(console.warn).toHaveBeenCalledWith('⚠', expect.stringContaining('Unknown NODE_INSTALLER'));
});
});
6 changes: 2 additions & 4 deletions packages/utils/core-utils/src/package-manager.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import chalk from 'chalk';
import { detect } from 'detect-package-manager';
import logSymbols from 'log-symbols';

export const safeYarnOrNpm = async () => {
export const resolvePackageManager = async () => {
const system = await detect();
switch (process.env.NODE_INSTALLER) {
case 'yarn':
@@ -17,6 +17,4 @@ export const safeYarnOrNpm = async () => {
}
};

export const yarnOrNpmSpawn = async (args?: CrossSpawnArgs, opts?: CrossSpawnOptions): Promise<string> => spawn(await safeYarnOrNpm(), args, opts);

export const hasYarn = async () => (await safeYarnOrNpm()) === 'yarn';
export const spawnPackageManager = async (args?: CrossSpawnArgs, opts?: CrossSpawnOptions): Promise<string> => spawn(await resolvePackageManager(), args, opts);
Loading