Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 28 additions & 0 deletions packages/api/cli/src/electron-forge-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
'--skip-git',
'Skip initializing a git repository in the initialized project.',
)
.option(
'--electron-version [version]',
'Set a specific Electron version for your Forge project. Can take in a version string (e.g. `38.3.0`) or `latest`, `beta`, or `nightly` tags.',
)
.action(async (dir) => {
const options = program.opts();
const tasks = new Listr<InitOptions>(
Expand All @@ -41,6 +45,7 @@
initOpts.force = Boolean(options.force);
initOpts.skipGit = Boolean(options.skipGit);
initOpts.dir = resolveWorkingDir(dir, false);
initOpts.electronVersion = options.electronVersion ?? 'latest';
},
},
{
Expand Down Expand Up @@ -74,7 +79,7 @@
}
}

const bundler: string = await prompt.run<Prompt<string, any>>(

Check warning on line 82 in packages/api/cli/src/electron-forge-init.ts

View workflow job for this annotation

GitHub Actions / lint-and-build

Unexpected any. Specify a different type
select,
{
message: 'Select a bundler',
Expand All @@ -98,7 +103,7 @@
let language: string | undefined;

if (bundler !== 'base') {
language = await prompt.run<Prompt<string | undefined, any>>(

Check warning on line 106 in packages/api/cli/src/electron-forge-init.ts

View workflow job for this annotation

GitHub Actions / lint-and-build

Unexpected any. Specify a different type
select,
{
message: 'Select a programming language',
Expand All @@ -117,6 +122,29 @@
}

initOpts.template = `${bundler}${language ? `-${language}` : ''}`;

// TODO: add prompt for passing in an exact version as well
initOpts.electronVersion = await prompt.run<Prompt<string, any>>(

Check warning on line 127 in packages/api/cli/src/electron-forge-init.ts

View workflow job for this annotation

GitHub Actions / lint-and-build

Unexpected any. Specify a different type
select,
{
message: 'Select an Electron release',
choices: [
{
name: 'electron@latest',
value: 'latest',
},
{
name: 'electron@beta',
value: 'beta',
},
{
name: 'electron-nightly@latest',
value: 'nightly',
},
],
},
);

initOpts.skipGit = !(await prompt.run(confirm, {
message: `Would you like to initialize Git in your new project?`,
default: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import path from 'node:path';

import { beforeEach, describe, expect, it } from 'vitest';

import { initGit } from '../../src/api/init-scripts/init-git';
import { initGit } from '../../../src/api/init-scripts/init-git';

let dir: string;
let dirID = Date.now();
Expand Down
75 changes: 75 additions & 0 deletions packages/api/core/spec/fast/init-scripts/init-npm.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { PACKAGE_MANAGERS } from '@electron-forge/core-utils';
import { ForgeListrTask } from '@electron-forge/shared-types';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { deps, devDeps, initNPM } from '../../../src/api/init-scripts/init-npm';
import {
DepType,
DepVersionRestriction,
installDependencies,
} from '../../../src/util/install-dependencies';

vi.mock('../../../src/util/install-dependencies', async (importOriginal) => ({
...(await importOriginal()),
installDependencies: vi.fn(),
}));

describe('init-npm', () => {
const mockTask = {
output: '',
} as ForgeListrTask<unknown>;

beforeEach(() => {
vi.clearAllMocks();
});

describe('with regular electron version', () => {
it('should call installDependencies three times with correct parameters', async () => {
const pm = PACKAGE_MANAGERS['npm'];
const dir = '/test/dir';

await initNPM(pm, dir, 'latest', mockTask);
expect(vi.mocked(installDependencies)).toHaveBeenNthCalledWith(
1,
pm,
dir,
deps,
);
expect(vi.mocked(installDependencies)).toHaveBeenNthCalledWith(
2,
pm,
dir,
devDeps,
DepType.DEV,
);
expect(vi.mocked(installDependencies)).toHaveBeenNthCalledWith(
3,
pm,
dir,
['electron@latest'],
DepType.DEV,
DepVersionRestriction.EXACT,
);
});
});

describe('with `nightly`', () => {
it('should install electron-nightly@latest instead of electron', async () => {
const pm = PACKAGE_MANAGERS['npm'];
const dir = '/test/dir';
const electronVersion = 'nightly';

await initNPM(pm, dir, electronVersion, mockTask);

expect(installDependencies).toHaveBeenCalledTimes(3);
expect(vi.mocked(installDependencies)).toHaveBeenNthCalledWith(
3,
pm,
dir,
['electron-nightly@latest'],
DepType.DEV,
DepVersionRestriction.EXACT,
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import {
} from '@electron-forge/core-utils';
import { describe, expect, it, vi } from 'vitest';

import installDependencies, {
import {
DepType,
DepVersionRestriction,
installDependencies,
} from '../../../src/util/install-dependencies';

vi.mock(import('@electron-forge/core-utils'), async (importOriginal) => {
Expand Down
70 changes: 66 additions & 4 deletions packages/api/core/spec/slow/api.slow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ import {
expectLintToPass,
} from '@electron-forge/test-utils';
import { readMetadata } from 'electron-installer-common';
import semver from 'semver';
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';

// eslint-disable-next-line n/no-missing-import
import { api, InitOptions } from '../../src/api';
import installDeps from '../../src/util/install-dependencies';
import { api, InitOptions } from '../../src/api/index';
import { installDependencies } from '../../src/util/install-dependencies';
import { readRawPackageJson } from '../../src/util/read-package-json';

type BeforeInitFunction = () => void;
Expand All @@ -45,6 +45,68 @@ async function updatePackageJSON(
);
}

// TODO: move more tests outside of the describe.each block
// if the actual package manager doesn't matter for the test
describe('init params', () => {
let dir: string;
describe('init (with electronVersion)', () => {
beforeEach(async () => {
dir = await ensureTestDirIsNonexistent();

return async () => {
await fs.promises.rm(dir, { recursive: true, force: true });
};
});

it('can define a specific Electron version with a version number', async () => {
await api.init({
dir,
electronVersion: 'v38.0.0',
});
const packageJSON = await import(path.resolve(dir, 'package.json'));
expect(packageJSON.devDependencies.electron).toEqual('38.0.0');
});

it('can define a specific Electron nightly version with a version number', async () => {
await api.init({
dir,
electronVersion: '40.0.0-nightly.20251020',
});
const packageJSON = await import(path.resolve(dir, 'package.json'));
expect(
semver.valid(packageJSON.devDependencies['electron-nightly']),
).not.toBeNull();
expect(packageJSON.devDependencies.electron).not.toBeDefined();
});

it('can define a specific Electron prerelease version with the beta tag', async () => {
await api.init({
dir,
electronVersion: 'beta',
});
const packageJSON = await import(path.resolve(dir, 'package.json'));
const prereleaseTag = semver.prerelease(
packageJSON.devDependencies.electron,
);
expect(prereleaseTag).toEqual(
expect.arrayContaining([expect.stringMatching(/alpha|beta/)]),
);
});

it('can define a specific Electron nightly version with the nightly tag', async () => {
await api.init({
dir,
electronVersion: 'nightly',
});
const packageJSON = await import(path.resolve(dir, 'package.json'));
expect(
semver.valid(packageJSON.devDependencies['electron-nightly']),
).not.toBeNull();
expect(packageJSON.devDependencies.electron).not.toBeDefined();
});
});
});

describe.each([
PACKAGE_MANAGERS['npm'],
PACKAGE_MANAGERS['yarn'],
Expand Down Expand Up @@ -400,7 +462,7 @@ describe.each([

describe('with prebuilt native module deps installed', () => {
beforeAll(async () => {
await installDeps(pm, dir, ['ref-napi']);
await installDependencies(pm, dir, ['ref-napi']);

return async () => {
await fs.promises.rm(path.resolve(dir, 'node_modules/ref-napi'), {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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';
import { installDependencies } from '../../src/util/install-dependencies';

describe.runIf(!(process.platform === 'linux' && process.env.CI))(
'install-dependencies',
Expand All @@ -19,7 +19,9 @@ describe.runIf(!(process.platform === 'linux' && process.env.CI))(
});

it('should install the latest minor version when the dependency has a caret', async () => {
await installDeps(PACKAGE_MANAGERS['npm'], installDir, ['debug@^2.0.0']);
await installDependencies(PACKAGE_MANAGERS['npm'], installDir, [
'debug@^2.0.0',
]);

const packageJSON = await import(
path.resolve(installDir, 'node_modules', 'debug', 'package.json')
Expand Down
14 changes: 10 additions & 4 deletions packages/api/core/src/api/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import fs from 'fs-extra';
import { Listr } from 'listr2';
import { merge } from 'lodash';

import installDepList, {
import {
DepType,
DepVersionRestriction,
installDependencies,
} from '../util/install-dependencies';
import { readRawPackageJson } from '../util/read-package-json';
import upgradeForgeConfig, {
Expand Down Expand Up @@ -306,15 +307,20 @@ export default autoTrace(

d('installing dependencies');
task.output = `${pm.executable} ${pm.install} ${importDeps.join(' ')}`;
await installDepList(pm, dir, importDeps);
await installDependencies(pm, dir, importDeps);

d('installing devDependencies');
task.output = `${pm.executable} ${pm.install} ${pm.dev} ${importDevDeps.join(' ')}`;
await installDepList(pm, dir, importDevDeps, DepType.DEV);
await installDependencies(
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(
await installDependencies(
pm,
dir,
importExactDevDeps,
Expand Down
29 changes: 21 additions & 8 deletions packages/api/core/src/api/init-scripts/init-npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { PMDetails } from '@electron-forge/core-utils';
import { ForgeListrTask } from '@electron-forge/shared-types';
import debug from 'debug';
import fs from 'fs-extra';
import semver from 'semver';

import installDepList, {
import {
DepType,
DepVersionRestriction,
installDependencies,
} from '../../util/install-dependencies';

const d = debug('electron-forge:init:npm');
Expand Down Expand Up @@ -35,23 +37,34 @@ export const exactDevDeps = ['electron'];
export const initNPM = async <T>(
pm: PMDetails,
dir: string,
electronVersion: string,
task: ForgeListrTask<T>,
): Promise<void> => {
d('installing dependencies');
task.output = `${pm.executable} ${pm.install} ${deps.join(' ')}`;
await installDepList(pm, dir, deps);
await installDependencies(pm, dir, deps);

d('installing devDependencies');
task.output = `${pm.executable} ${pm.install} ${pm.dev} ${deps.join(' ')}`;
await installDepList(pm, dir, devDeps, DepType.DEV);
d(`installing devDependencies`);
task.output = `${pm.executable} ${pm.install} ${pm.dev} ${devDeps.join(' ')}`;
await installDependencies(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(
let packageInstallString = packageName;
if (packageName === 'electron') {
if (electronVersion === 'nightly') {
packageInstallString = `electron-nightly@latest`;
} else if (semver.prerelease(electronVersion)?.includes('nightly')) {
packageInstallString = `electron-nightly@${electronVersion}`;
} else {
packageInstallString += `@${electronVersion}`;
}
}
task.output = `${pm.executable} ${pm.install} ${pm.dev} ${pm.exact} ${packageInstallString}`;
await installDependencies(
pm,
dir,
[packageName],
[packageInstallString],
DepType.DEV,
DepVersionRestriction.EXACT,
);
Expand Down
Loading
Loading