@electron-forge/test-utils 8.0.0-alpha.9 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -5
- package/dist/template-tests.d.ts +42 -0
- package/dist/template-tests.d.ts.map +1 -0
- package/dist/template-tests.js +265 -0
- package/package.json +4 -5
- package/src/index.ts +24 -4
- package/src/template-tests.ts +366 -0
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,11 @@ export declare function ensureModulesInstalled(dir: string, deps: string[], devD
|
|
|
10
10
|
export declare function ensureTestDirIsNonexistent(): Promise<string>;
|
|
11
11
|
export declare function expectLintToPass(dir: string): Promise<void>;
|
|
12
12
|
export declare function expectTypecheckToPass(dir: string): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Runs the `typecheck` script and expects it to fail.
|
|
15
|
+
* Returns the script's stdout, where `tsc` reports its diagnostics.
|
|
16
|
+
*/
|
|
17
|
+
export declare function expectTypecheckToFail(dir: string): Promise<string>;
|
|
13
18
|
/**
|
|
14
19
|
* Mutates the `package.json` file in a directory.
|
|
15
20
|
* Use the return value to later restore the original `package.json` value
|
|
@@ -20,4 +25,5 @@ export declare function expectTypecheckToPass(dir: string): Promise<void>;
|
|
|
20
25
|
* @returns The original `package.json` prior to mutation
|
|
21
26
|
*/
|
|
22
27
|
export declare function updatePackageJSON(dir: string, callback: (packageJSON: PackageJSON) => Promise<PackageJSON>): Promise<any>;
|
|
23
|
-
|
|
28
|
+
export * from './template-tests.js';
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAClD,MAAM,CAAC,EAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACjC,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C,CAAC;AAMF,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,iBAEjE;AAED,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CAGf;AAID,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,MAAM,CAAC,CAMlE;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUjE;AAED,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUtE;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAClD,MAAM,CAAC,EAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACjC,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C,CAAC;AAMF,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,iBAEjE;AAED,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CAGf;AAID,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,MAAM,CAAC,CAMlE;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUjE;AAED,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUtE;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAUxE;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,WAAW,CAAC,gBAY7D;AAED,cAAc,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
1
2
|
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { ExitError, spawn } from '@malept/cross-spawn-promise';
|
|
4
|
-
import fs from 'fs-extra';
|
|
5
5
|
async function runNPM(dir, ...args) {
|
|
6
6
|
await spawn('npm', args, { cwd: dir });
|
|
7
7
|
}
|
|
@@ -16,7 +16,7 @@ let dirID = Date.now();
|
|
|
16
16
|
export async function ensureTestDirIsNonexistent() {
|
|
17
17
|
const dir = path.resolve(os.tmpdir(), `electron-forge-test-${dirID}`);
|
|
18
18
|
dirID += 1;
|
|
19
|
-
await fs.
|
|
19
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
20
20
|
return dir;
|
|
21
21
|
}
|
|
22
22
|
export async function expectLintToPass(dir) {
|
|
@@ -43,6 +43,22 @@ export async function expectTypecheckToPass(dir) {
|
|
|
43
43
|
throw err;
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Runs the `typecheck` script and expects it to fail.
|
|
48
|
+
* Returns the script's stdout, where `tsc` reports its diagnostics.
|
|
49
|
+
*/
|
|
50
|
+
export async function expectTypecheckToFail(dir) {
|
|
51
|
+
try {
|
|
52
|
+
await runNPM(dir, 'run', 'typecheck');
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
if (err instanceof ExitError) {
|
|
56
|
+
return err.stdout.toString();
|
|
57
|
+
}
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
throw new Error('Expected typecheck to fail, but it passed');
|
|
61
|
+
}
|
|
46
62
|
/**
|
|
47
63
|
* Mutates the `package.json` file in a directory.
|
|
48
64
|
* Use the return value to later restore the original `package.json` value
|
|
@@ -53,9 +69,10 @@ export async function expectTypecheckToPass(dir) {
|
|
|
53
69
|
* @returns The original `package.json` prior to mutation
|
|
54
70
|
*/
|
|
55
71
|
export async function updatePackageJSON(dir, callback) {
|
|
56
|
-
const packageJSON = await fs.
|
|
72
|
+
const packageJSON = JSON.parse(await fs.readFile(path.resolve(dir, 'package.json'), 'utf8'));
|
|
57
73
|
const mutated = await callback(JSON.parse(JSON.stringify(packageJSON)));
|
|
58
|
-
await fs.
|
|
74
|
+
await fs.writeFile(path.resolve(dir, 'package.json'), JSON.stringify(mutated, null, 2), 'utf-8');
|
|
59
75
|
return packageJSON;
|
|
60
76
|
}
|
|
61
|
-
|
|
77
|
+
export * from './template-tests.js';
|
|
78
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDbEMsT0FBTyxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBQ3pCLE9BQU8sSUFBSSxNQUFNLFdBQVcsQ0FBQztBQUU3QixPQUFPLEVBQUUsU0FBUyxFQUFFLEtBQUssRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBVS9ELEtBQUssVUFBVSxNQUFNLENBQUMsR0FBVyxFQUFFLEdBQUcsSUFBYztJQUNsRCxNQUFNLEtBQUssQ0FBQyxLQUFLLEVBQUUsSUFBSSxFQUFFLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUM7QUFDekMsQ0FBQztBQUVELE1BQU0sQ0FBQyxLQUFLLFVBQVUsYUFBYSxDQUFDLEdBQVcsRUFBRSxHQUFHLElBQWM7SUFDaEUsTUFBTSxNQUFNLENBQUMsR0FBRyxFQUFFLFNBQVMsRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDO0FBQ3hDLENBQUM7QUFFRCxNQUFNLENBQUMsS0FBSyxVQUFVLHNCQUFzQixDQUMxQyxHQUFXLEVBQ1gsSUFBYyxFQUNkLE9BQWlCO0lBRWpCLE1BQU0sYUFBYSxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDO0lBQ2xDLE1BQU0sYUFBYSxDQUFDLEdBQUcsRUFBRSxZQUFZLEVBQUUsR0FBRyxPQUFPLENBQUMsQ0FBQztBQUNyRCxDQUFDO0FBRUQsSUFBSSxLQUFLLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO0FBRXZCLE1BQU0sQ0FBQyxLQUFLLFVBQVUsMEJBQTBCO0lBQzlDLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxFQUFFLHVCQUF1QixLQUFLLEVBQUUsQ0FBQyxDQUFDO0lBQ3RFLEtBQUssSUFBSSxDQUFDLENBQUM7SUFDWCxNQUFN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
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
declare const supportedTemplates: readonly ['base', 'vite', 'webpack'];
|
|
2
|
+
type ModuleFormat = 'es' | 'cjs';
|
|
3
|
+
declare const supportedModuleFormats: ModuleFormat[];
|
|
4
|
+
type SupportedModuleFormats = typeof supportedModuleFormats;
|
|
5
|
+
export type TestForgeTemplateOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* Note: `create-electron-app` always creates CommonJS projects; this option
|
|
8
|
+
* is only here to allow us to test projects that have been manually changed
|
|
9
|
+
* to ESM (either by programmatically updating the relevant project files in
|
|
10
|
+
* the temporary test directory or by manually updating them in the Forge
|
|
11
|
+
* checkout for local testing).
|
|
12
|
+
*/
|
|
13
|
+
moduleFormats: SupportedModuleFormats;
|
|
14
|
+
templateName: (typeof supportedTemplates)[number];
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Runs the local version of `create-electron-app` to create a project based on
|
|
18
|
+
* a given Forge template using all supported package managers. Because this
|
|
19
|
+
* test suite runs under Verdaccio, all ´@electron-forge/*` packages installed
|
|
20
|
+
* by the templates are served from the file system and rebuilt before every
|
|
21
|
+
* test run, which makes it easy to test local changes in the templates.
|
|
22
|
+
*
|
|
23
|
+
* At a high level, for each different package manager, this test suite:
|
|
24
|
+
*
|
|
25
|
+
* - creates a project with `create-electron-app`;
|
|
26
|
+
*
|
|
27
|
+
* - modifies the project's preload file so that it sends a "preload file ok"
|
|
28
|
+
* IPC message to the main process once it has successfully loaded;
|
|
29
|
+
*
|
|
30
|
+
* - modifies the project's main process entrypoint so that it logs a "main
|
|
31
|
+
* process ok" message. It also sets up a listener for the IPC message sent by
|
|
32
|
+
* the preload file that logs a "preload file ok" when that message is received;
|
|
33
|
+
*
|
|
34
|
+
* - runs the project's `start` script and checks whether its output contains
|
|
35
|
+
* the expected log messages;
|
|
36
|
+
*
|
|
37
|
+
* - checks whether the project includes a lock file that is consistent with the
|
|
38
|
+
* package-manager used by `create-electron-app`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function testForgeTemplate({ moduleFormats, templateName, }: TestForgeTemplateOptions): void;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=template-tests.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-tests.d.ts","sourceRoot":"","sources":["../src/template-tests.ts"],"names":[],"mappings":"AAUA,QAAA,MAAM,kBAAkB,YAAI,MAAM,EAAE,MAAM,EAAE,SAAS,CAAU,CAAC;AAEhE,KAAK,YAAY,GAAG,IAAI,GAAG,KAAK,CAAC;AAEjC,QAAA,MAAM,sBAAsB,EAAE,YAAY,EAAkB,CAAC;AAC7D,KAAK,sBAAsB,GAAG,OAAO,sBAAsB,CAAC;AAE5D,MAAM,MAAM,wBAAwB,GAAG;IACrC;;;;;;OAMG;IACH,aAAa,EAAE,sBAAsB,CAAC;IAEtC,YAAY,EAAE,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;CACnD,CAAC;AAyDF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,aAAa,EACb,YAAY,GACb,EAAE,wBAAwB,QA6P1B"}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { spawn } from '@malept/cross-spawn-promise';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import debug from 'debug';
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
const supportedTemplates = ['base', 'vite', 'webpack'];
|
|
9
|
+
const supportedModuleFormats = ['cjs', 'es'];
|
|
10
|
+
const d = debug('electron-forge:testForgeTemplate');
|
|
11
|
+
/**
|
|
12
|
+
* Summarizes the layout a package manager installed into a project, which is
|
|
13
|
+
* what tells a flat `node_modules` (npm, Yarn, pnpm with `nodeLinker: hoisted`)
|
|
14
|
+
* apart from one where every package is a link into a store, and shows which
|
|
15
|
+
* packages a failing app could have resolved.
|
|
16
|
+
*/
|
|
17
|
+
function describeDependencyTree(projectDir) {
|
|
18
|
+
const nodeModules = path.join(projectDir, 'node_modules');
|
|
19
|
+
let entries;
|
|
20
|
+
try {
|
|
21
|
+
entries = fs.readdirSync(nodeModules, { withFileTypes: true });
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
return `no readable \`node_modules\` in ${projectDir} (${error})`;
|
|
25
|
+
}
|
|
26
|
+
const names = entries
|
|
27
|
+
.flatMap((entry) =>
|
|
28
|
+
// Scopes hold the packages we care about rather than being one themselves.
|
|
29
|
+
entry.name.startsWith('@')
|
|
30
|
+
? fs
|
|
31
|
+
.readdirSync(path.join(nodeModules, entry.name))
|
|
32
|
+
.map((scoped) => `${entry.name}/${scoped}`)
|
|
33
|
+
: [entry.name])
|
|
34
|
+
.sort();
|
|
35
|
+
return `${names.length} packages into ${nodeModules}: ${names.join(' ')}`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Everything the Verdaccio harness' pnpm shim recorded about the installs into
|
|
39
|
+
* one project, which is the only account there is of them: pnpm's own output.
|
|
40
|
+
*/
|
|
41
|
+
function describePnpmInstalls(projectDir) {
|
|
42
|
+
const invocationLog = process.env.FORGE_PNPM_INVOCATION_LOG;
|
|
43
|
+
if (!invocationLog)
|
|
44
|
+
return 'no record of the pnpm runs was kept';
|
|
45
|
+
let records;
|
|
46
|
+
try {
|
|
47
|
+
records = fs.readFileSync(invocationLog, 'utf8').split(/^(?==== pnpm )/m);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
return `no readable record of the pnpm runs in ${invocationLog} (${error})`;
|
|
51
|
+
}
|
|
52
|
+
const forThisProject = records.filter((record) => record.includes(`in ${projectDir}`));
|
|
53
|
+
return forThisProject.length
|
|
54
|
+
? forThisProject.join('')
|
|
55
|
+
: `nothing in ${invocationLog} mentions ${projectDir}`;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Runs the local version of `create-electron-app` to create a project based on
|
|
59
|
+
* a given Forge template using all supported package managers. Because this
|
|
60
|
+
* test suite runs under Verdaccio, all ´@electron-forge/*` packages installed
|
|
61
|
+
* by the templates are served from the file system and rebuilt before every
|
|
62
|
+
* test run, which makes it easy to test local changes in the templates.
|
|
63
|
+
*
|
|
64
|
+
* At a high level, for each different package manager, this test suite:
|
|
65
|
+
*
|
|
66
|
+
* - creates a project with `create-electron-app`;
|
|
67
|
+
*
|
|
68
|
+
* - modifies the project's preload file so that it sends a "preload file ok"
|
|
69
|
+
* IPC message to the main process once it has successfully loaded;
|
|
70
|
+
*
|
|
71
|
+
* - modifies the project's main process entrypoint so that it logs a "main
|
|
72
|
+
* process ok" message. It also sets up a listener for the IPC message sent by
|
|
73
|
+
* the preload file that logs a "preload file ok" when that message is received;
|
|
74
|
+
*
|
|
75
|
+
* - runs the project's `start` script and checks whether its output contains
|
|
76
|
+
* the expected log messages;
|
|
77
|
+
*
|
|
78
|
+
* - checks whether the project includes a lock file that is consistent with the
|
|
79
|
+
* package-manager used by `create-electron-app`.
|
|
80
|
+
*/
|
|
81
|
+
export function testForgeTemplate({ moduleFormats, templateName, }) {
|
|
82
|
+
describe(`${templateName} template`, () => {
|
|
83
|
+
if (!moduleFormats.length) {
|
|
84
|
+
d('`moduleFormats` is empty, defaulting to `cjs` only');
|
|
85
|
+
moduleFormats = moduleFormats.concat('cjs');
|
|
86
|
+
}
|
|
87
|
+
let tmpDir;
|
|
88
|
+
beforeEach(async () => {
|
|
89
|
+
tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'electron-forge-test-'));
|
|
90
|
+
});
|
|
91
|
+
const testCases = moduleFormats.reduce((acc, moduleFormat) => {
|
|
92
|
+
const packageManagers = [
|
|
93
|
+
'npm',
|
|
94
|
+
'pnpm',
|
|
95
|
+
'yarn',
|
|
96
|
+
];
|
|
97
|
+
packageManagers.forEach((packageManager) => {
|
|
98
|
+
acc.push([moduleFormat, packageManager]);
|
|
99
|
+
});
|
|
100
|
+
return acc;
|
|
101
|
+
}, []);
|
|
102
|
+
test.each(testCases)(`can launch a \`%s\` project created from \`template-${templateName}\` with \`%s\``, async (moduleFormat, packageManager) => {
|
|
103
|
+
if (!supportedTemplates.includes(templateName)) {
|
|
104
|
+
throw new Error(`unknown template ${templateName}`);
|
|
105
|
+
}
|
|
106
|
+
const createOutput = await spawn('node', [
|
|
107
|
+
path.resolve(__dirname, '../../../external/create-electron-app/dist/create-electron-app.js'),
|
|
108
|
+
tmpDir,
|
|
109
|
+
`--template=${templateName}`,
|
|
110
|
+
`--package-manager=${packageManager}`,
|
|
111
|
+
// Electron 41 is the last version that downloads its binary from a
|
|
112
|
+
// `postinstall` script. Yarn 4.18 disables install scripts by
|
|
113
|
+
// default (`enableScripts`), so on 41 the binary never gets
|
|
114
|
+
// downloaded and `electron-forge start` fails with "Electron failed
|
|
115
|
+
// to install correctly". Electron 42+ downloads the binary on demand
|
|
116
|
+
// the first time it's needed instead, so no install script is
|
|
117
|
+
// involved.
|
|
118
|
+
`--electron-version=43.4.0`,
|
|
119
|
+
]);
|
|
120
|
+
d('tmpdir: ', pathToFileURL(tmpDir).toString());
|
|
121
|
+
const possiblePreloadFiles = ['preload.ts', 'preload.js'].map((item) => path.resolve(tmpDir, `src/${item}`));
|
|
122
|
+
const possibleMainProcessEntrypoints = [
|
|
123
|
+
'main.ts',
|
|
124
|
+
'main.js',
|
|
125
|
+
'index.ts',
|
|
126
|
+
'index.js',
|
|
127
|
+
].map((item) => path.resolve(tmpDir, `src/${item}`));
|
|
128
|
+
const preloadPath = possiblePreloadFiles.find((item) => fs.existsSync(item));
|
|
129
|
+
const mainProcessEntrypoint = possibleMainProcessEntrypoints.find((item) => fs.existsSync(item));
|
|
130
|
+
let missingPreloadFileError = null;
|
|
131
|
+
let missingMainProcessEntrypointError = null;
|
|
132
|
+
if (!preloadPath) {
|
|
133
|
+
missingPreloadFileError = new Error(`"preload file not found in the following locations: ${JSON.stringify(possiblePreloadFiles, null, 2)}`);
|
|
134
|
+
}
|
|
135
|
+
if (!mainProcessEntrypoint) {
|
|
136
|
+
missingMainProcessEntrypointError = new Error(`"main process entrypoint not found in the following locations: ${JSON.stringify(possibleMainProcessEntrypoints, null, 2)}`);
|
|
137
|
+
}
|
|
138
|
+
if (missingPreloadFileError || missingMainProcessEntrypointError) {
|
|
139
|
+
throw new AggregateError([missingPreloadFileError, missingMainProcessEntrypointError], 'one or more files are missing');
|
|
140
|
+
}
|
|
141
|
+
const preloadOkMessage = '__FORGE_INTERNAL_PRELOAD_PROCESS_OK__';
|
|
142
|
+
const mainProcessOkMessage = '__FORGE_INTERNAL_MAIN_PROCESS_OK__';
|
|
143
|
+
await fs.promises.appendFile(preloadPath, [
|
|
144
|
+
'\n',
|
|
145
|
+
moduleFormat === 'es'
|
|
146
|
+
? `import { ipcRenderer } from 'electron';`
|
|
147
|
+
: `const { ipcRenderer } = require('electron');`,
|
|
148
|
+
// If the preload file loads correctly, it will send this message to
|
|
149
|
+
// the main process, which will in turn log it. Once the app exits
|
|
150
|
+
// (either by calling `app.exit()` in our injected code upon
|
|
151
|
+
// receiving this message or after the test times out), we check if
|
|
152
|
+
// `electronForgeStartOutput` includes this string; if it doesn't,
|
|
153
|
+
// it means the preload file failed to load for some reason.
|
|
154
|
+
`ipcRenderer.send('${preloadOkMessage}')`,
|
|
155
|
+
].join('\n'));
|
|
156
|
+
await fs.promises.appendFile(mainProcessEntrypoint, [
|
|
157
|
+
'\n',
|
|
158
|
+
moduleFormat === 'es'
|
|
159
|
+
? `import { ipcMain } from 'electron';`
|
|
160
|
+
: `const { ipcMain } = require('electron');`,
|
|
161
|
+
// This runs at the top-level and below all other template code, so
|
|
162
|
+
// its presence in `electronForgeStartOutput` indicates that the
|
|
163
|
+
// main process entrypoint must have been correctly parsed /
|
|
164
|
+
// transpiled.
|
|
165
|
+
`console.log('${mainProcessOkMessage}');`,
|
|
166
|
+
// Logs the message that indicates that the preload file has been
|
|
167
|
+
// correctly loaded.
|
|
168
|
+
`
|
|
169
|
+
ipcMain.on('${preloadOkMessage}', () => {
|
|
170
|
+
console.log('${preloadOkMessage}');
|
|
171
|
+
app.exit(0);
|
|
172
|
+
});
|
|
173
|
+
`,
|
|
174
|
+
].join('\n'));
|
|
175
|
+
const startApp = () => spawn(packageManager, ['run', 'start'], {
|
|
176
|
+
cwd: tmpDir,
|
|
177
|
+
env: {
|
|
178
|
+
PATH: process.env.PATH,
|
|
179
|
+
/**
|
|
180
|
+
* `start` makes the package manager check the lockfile it just
|
|
181
|
+
* wrote, and `XDG_CONFIG_HOME` is where the Verdaccio test harness
|
|
182
|
+
* puts the config that tells pnpm which registry to use, how old a
|
|
183
|
+
* release has to be, which packages are exempt, and to warn rather
|
|
184
|
+
* than fail when the check finds a difference. Dropping it would
|
|
185
|
+
* leave the check looking at the public registry under a policy
|
|
186
|
+
* the install never ran under.
|
|
187
|
+
*/
|
|
188
|
+
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME,
|
|
189
|
+
...(process.platform === 'linux' && {
|
|
190
|
+
DISPLAY: process.env.DISPLAY,
|
|
191
|
+
XAUTHORITY: process.env.XAUTHORITY,
|
|
192
|
+
}),
|
|
193
|
+
...(packageManager !== 'yarn' && {
|
|
194
|
+
/**
|
|
195
|
+
* HACK: when running the test script with Yarn on a npm/pnpm
|
|
196
|
+
* project created by `create-electron-app`,
|
|
197
|
+
* `process.env.npm_config_user_agent` can be something like
|
|
198
|
+
* `yarn/4.10.3 npm/? node/v24.14.1 win32 x64`, and Forge's
|
|
199
|
+
* `checkPackageManager` function takes this value to mean that
|
|
200
|
+
* the project _also_ uses Yarn, so `electron-forge start` ends
|
|
201
|
+
* up failing because there's no `yarn.lock` ([relevant
|
|
202
|
+
* code](https://github.com/electron/forge/blob/001f41befe2c049b6f54ce7d6c55e83435141055/packages/api/cli/src/util/check-system.ts#L108-L135)).
|
|
203
|
+
*
|
|
204
|
+
* Removing the `yarn/4.10.3` user agent causes Forge to
|
|
205
|
+
* correctly identify the project's package manager, but since
|
|
206
|
+
* the version information can be missing for npm in `npm/?`, it
|
|
207
|
+
* fails semver validation and Forge treats it like an
|
|
208
|
+
* unsupported npm version, so we also have to spoof a supported
|
|
209
|
+
* npm version number to work around that behavior.
|
|
210
|
+
*/
|
|
211
|
+
npm_config_user_agent: process.env
|
|
212
|
+
.npm_config_user_agent.replace(/^yarn\/\d+\.\d+\.\d+ /i, '')
|
|
213
|
+
.replace(/\bnpm\/\?/, 'npm/99.99.99'),
|
|
214
|
+
}),
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
let electronForgeStartOutput;
|
|
218
|
+
try {
|
|
219
|
+
electronForgeStartOutput = await startApp();
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
/**
|
|
223
|
+
* When `start` fails, it is usually because the package manager
|
|
224
|
+
* installed a dependency tree the app cannot resolve its own
|
|
225
|
+
* configuration from, and the failure alone doesn't say which tree it
|
|
226
|
+
* ended up with. `create-electron-app` runs its steps with listr2's
|
|
227
|
+
* `exitOnError: false`, so a failed install leaves a broken project
|
|
228
|
+
* behind and still exits 0; its output is the only place that failure
|
|
229
|
+
* is reported at all.
|
|
230
|
+
*/
|
|
231
|
+
console.error([
|
|
232
|
+
`[template-tests] ${packageManager} installed ${describeDependencyTree(tmpDir)}`,
|
|
233
|
+
`[template-tests] from this package.json:\n${fs.readFileSync(path.join(tmpDir, 'package.json'), 'utf8')}`,
|
|
234
|
+
...(packageManager === 'pnpm'
|
|
235
|
+
? [
|
|
236
|
+
`[template-tests] pnpm was run like this:\n${describePnpmInstalls(tmpDir)}`,
|
|
237
|
+
]
|
|
238
|
+
: []),
|
|
239
|
+
`[template-tests] create-electron-app said:\n${createOutput}`,
|
|
240
|
+
].join('\n'));
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
d({ electronForgeStartOutput });
|
|
244
|
+
const mainProcessOk = electronForgeStartOutput.includes(mainProcessOkMessage);
|
|
245
|
+
const preloadProcessOk = electronForgeStartOutput.includes(preloadOkMessage);
|
|
246
|
+
expect(mainProcessOk).toBe(true);
|
|
247
|
+
expect(preloadProcessOk).toBe(true);
|
|
248
|
+
const lockFile = {
|
|
249
|
+
npm: 'package-lock.json',
|
|
250
|
+
pnpm: 'pnpm-lock.yaml',
|
|
251
|
+
yarn: 'yarn.lock',
|
|
252
|
+
}[packageManager];
|
|
253
|
+
expect(fs.existsSync(path.resolve(tmpDir, lockFile))).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
afterEach(async () => {
|
|
256
|
+
await fs.promises.rm(tmpDir, {
|
|
257
|
+
recursive: true,
|
|
258
|
+
force: true,
|
|
259
|
+
maxRetries: 5,
|
|
260
|
+
retryDelay: 500,
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVtcGxhdGUtdGVzdHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvdGVtcGxhdGUtdGVzdHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQ3BELE9BQU8sSUFBSSxNQUFNLFdBQVcsQ0FBQztBQUM3QixPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sVUFBVSxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUN6QixPQUFPLEtBQUssTUFBTSxPQUFPLENBQUM7QUFDMUIsT0FBTyxFQUFFLFNBQVMsRUFBRSxVQUFVLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFDdkUsT0FBTyxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBSXpCLE1BQU0sa0JBQWtCLEdBQUcsQ0FBQyxNQUFNLEVBQUUsTUFBTSxFQUFFLFNBQVMsQ0FBVSxDQUFDO0FBSWhFLE1BQU0sc0JBQXNCLEdBQW1CLENBQUMsS0FBSyxFQUFFLElBQUksQ0FBQyxDQUFDO0FBZ0I3RCxNQUFNLENBQUMsR0FBRyxLQUFLLENBQUMsa0NBQWtDLENBQUMsQ0FBQztBQUVwRDs7Ozs7R0FLRztBQUNILFNBQVMsc0JBQXNCLENBQUMsVUFBa0I7SUFDaEQsTUFBTSxXQUFXLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLEVBQUUsY0FBYyxDQUFDLENBQUM7SUFFMUQsSUFBSSxPQUFPLENBQUM7SUFDWixJQUFJLENBQUM7UUFDSCxPQUFPLEdBQUcsRUFBRSxDQUFDLFdBQVcsQ0FBQyxXQUFXLEVBQUUsRUFBRSxhQUFhLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUNqRSxDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNmLE9BQU8sbUNBQW1DLFVBQVUsS0FBSyxLQUFLLEdBQUcsQ0FBQztJQUNwRSxDQUFDO0lBRUQsTUFBTSxLQUFL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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@electron-forge/test-utils",
|
|
3
|
-
"version": "8.0.0
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Helper utilities for the Electron Forge testsuite",
|
|
6
6
|
"repository": "https://github.com/electron/forge",
|
|
@@ -9,11 +9,10 @@
|
|
|
9
9
|
"exports": "./dist/index.js",
|
|
10
10
|
"typings": "dist/index.d.ts",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@malept/cross-spawn-promise": "^2.0.0"
|
|
13
|
-
"fs-extra": "^10.0.0"
|
|
12
|
+
"@malept/cross-spawn-promise": "^2.0.0"
|
|
14
13
|
},
|
|
15
14
|
"engines": {
|
|
16
|
-
"node": ">= 22.
|
|
15
|
+
"node": ">= 22.13.0"
|
|
17
16
|
},
|
|
18
17
|
"publishConfig": {
|
|
19
18
|
"access": "public"
|
|
@@ -22,5 +21,5 @@
|
|
|
22
21
|
"dist",
|
|
23
22
|
"src"
|
|
24
23
|
],
|
|
25
|
-
"gitHead": "
|
|
24
|
+
"gitHead": "40b4adf8ebbbb09d4349079c65a0b7682bbb8565"
|
|
26
25
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
1
2
|
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
|
|
4
5
|
import { ExitError, spawn } from '@malept/cross-spawn-promise';
|
|
5
|
-
import fs from 'fs-extra';
|
|
6
6
|
|
|
7
7
|
export type PackageJSON = Record<string, unknown> & {
|
|
8
8
|
config?: {
|
|
@@ -34,7 +34,7 @@ let dirID = Date.now();
|
|
|
34
34
|
export async function ensureTestDirIsNonexistent(): Promise<string> {
|
|
35
35
|
const dir = path.resolve(os.tmpdir(), `electron-forge-test-${dirID}`);
|
|
36
36
|
dirID += 1;
|
|
37
|
-
await fs.
|
|
37
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
38
38
|
|
|
39
39
|
return dir;
|
|
40
40
|
}
|
|
@@ -63,6 +63,22 @@ export async function expectTypecheckToPass(dir: string): Promise<void> {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Runs the `typecheck` script and expects it to fail.
|
|
68
|
+
* Returns the script's stdout, where `tsc` reports its diagnostics.
|
|
69
|
+
*/
|
|
70
|
+
export async function expectTypecheckToFail(dir: string): Promise<string> {
|
|
71
|
+
try {
|
|
72
|
+
await runNPM(dir, 'run', 'typecheck');
|
|
73
|
+
} catch (err) {
|
|
74
|
+
if (err instanceof ExitError) {
|
|
75
|
+
return err.stdout.toString();
|
|
76
|
+
}
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
throw new Error('Expected typecheck to fail, but it passed');
|
|
80
|
+
}
|
|
81
|
+
|
|
66
82
|
/**
|
|
67
83
|
* Mutates the `package.json` file in a directory.
|
|
68
84
|
* Use the return value to later restore the original `package.json` value
|
|
@@ -76,12 +92,16 @@ export async function updatePackageJSON(
|
|
|
76
92
|
dir: string,
|
|
77
93
|
callback: (packageJSON: PackageJSON) => Promise<PackageJSON>,
|
|
78
94
|
) {
|
|
79
|
-
const packageJSON =
|
|
95
|
+
const packageJSON = JSON.parse(
|
|
96
|
+
await fs.readFile(path.resolve(dir, 'package.json'), 'utf8'),
|
|
97
|
+
);
|
|
80
98
|
const mutated = await callback(JSON.parse(JSON.stringify(packageJSON)));
|
|
81
|
-
await fs.
|
|
99
|
+
await fs.writeFile(
|
|
82
100
|
path.resolve(dir, 'package.json'),
|
|
83
101
|
JSON.stringify(mutated, null, 2),
|
|
84
102
|
'utf-8',
|
|
85
103
|
);
|
|
86
104
|
return packageJSON;
|
|
87
105
|
}
|
|
106
|
+
|
|
107
|
+
export * from './template-tests.js';
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { spawn } from '@malept/cross-spawn-promise';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import debug from 'debug';
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
|
|
9
|
+
type SupportedPackageManager = 'npm' | 'pnpm' | 'yarn';
|
|
10
|
+
|
|
11
|
+
const supportedTemplates = ['base', 'vite', 'webpack'] as const;
|
|
12
|
+
|
|
13
|
+
type ModuleFormat = 'es' | 'cjs';
|
|
14
|
+
|
|
15
|
+
const supportedModuleFormats: ModuleFormat[] = ['cjs', 'es'];
|
|
16
|
+
type SupportedModuleFormats = typeof supportedModuleFormats;
|
|
17
|
+
|
|
18
|
+
export type TestForgeTemplateOptions = {
|
|
19
|
+
/**
|
|
20
|
+
* Note: `create-electron-app` always creates CommonJS projects; this option
|
|
21
|
+
* is only here to allow us to test projects that have been manually changed
|
|
22
|
+
* to ESM (either by programmatically updating the relevant project files in
|
|
23
|
+
* the temporary test directory or by manually updating them in the Forge
|
|
24
|
+
* checkout for local testing).
|
|
25
|
+
*/
|
|
26
|
+
moduleFormats: SupportedModuleFormats;
|
|
27
|
+
|
|
28
|
+
templateName: (typeof supportedTemplates)[number];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const d = debug('electron-forge:testForgeTemplate');
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Summarizes the layout a package manager installed into a project, which is
|
|
35
|
+
* what tells a flat `node_modules` (npm, Yarn, pnpm with `nodeLinker: hoisted`)
|
|
36
|
+
* apart from one where every package is a link into a store, and shows which
|
|
37
|
+
* packages a failing app could have resolved.
|
|
38
|
+
*/
|
|
39
|
+
function describeDependencyTree(projectDir: string) {
|
|
40
|
+
const nodeModules = path.join(projectDir, 'node_modules');
|
|
41
|
+
|
|
42
|
+
let entries;
|
|
43
|
+
try {
|
|
44
|
+
entries = fs.readdirSync(nodeModules, { withFileTypes: true });
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return `no readable \`node_modules\` in ${projectDir} (${error})`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const names = entries
|
|
50
|
+
.flatMap((entry) =>
|
|
51
|
+
// Scopes hold the packages we care about rather than being one themselves.
|
|
52
|
+
entry.name.startsWith('@')
|
|
53
|
+
? fs
|
|
54
|
+
.readdirSync(path.join(nodeModules, entry.name))
|
|
55
|
+
.map((scoped) => `${entry.name}/${scoped}`)
|
|
56
|
+
: [entry.name],
|
|
57
|
+
)
|
|
58
|
+
.sort();
|
|
59
|
+
|
|
60
|
+
return `${names.length} packages into ${nodeModules}: ${names.join(' ')}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Everything the Verdaccio harness' pnpm shim recorded about the installs into
|
|
65
|
+
* one project, which is the only account there is of them: pnpm's own output.
|
|
66
|
+
*/
|
|
67
|
+
function describePnpmInstalls(projectDir: string) {
|
|
68
|
+
const invocationLog = process.env.FORGE_PNPM_INVOCATION_LOG;
|
|
69
|
+
if (!invocationLog) return 'no record of the pnpm runs was kept';
|
|
70
|
+
|
|
71
|
+
let records;
|
|
72
|
+
try {
|
|
73
|
+
records = fs.readFileSync(invocationLog, 'utf8').split(/^(?==== pnpm )/m);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return `no readable record of the pnpm runs in ${invocationLog} (${error})`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const forThisProject = records.filter((record) =>
|
|
79
|
+
record.includes(`in ${projectDir}`),
|
|
80
|
+
);
|
|
81
|
+
return forThisProject.length
|
|
82
|
+
? forThisProject.join('')
|
|
83
|
+
: `nothing in ${invocationLog} mentions ${projectDir}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Runs the local version of `create-electron-app` to create a project based on
|
|
88
|
+
* a given Forge template using all supported package managers. Because this
|
|
89
|
+
* test suite runs under Verdaccio, all ´@electron-forge/*` packages installed
|
|
90
|
+
* by the templates are served from the file system and rebuilt before every
|
|
91
|
+
* test run, which makes it easy to test local changes in the templates.
|
|
92
|
+
*
|
|
93
|
+
* At a high level, for each different package manager, this test suite:
|
|
94
|
+
*
|
|
95
|
+
* - creates a project with `create-electron-app`;
|
|
96
|
+
*
|
|
97
|
+
* - modifies the project's preload file so that it sends a "preload file ok"
|
|
98
|
+
* IPC message to the main process once it has successfully loaded;
|
|
99
|
+
*
|
|
100
|
+
* - modifies the project's main process entrypoint so that it logs a "main
|
|
101
|
+
* process ok" message. It also sets up a listener for the IPC message sent by
|
|
102
|
+
* the preload file that logs a "preload file ok" when that message is received;
|
|
103
|
+
*
|
|
104
|
+
* - runs the project's `start` script and checks whether its output contains
|
|
105
|
+
* the expected log messages;
|
|
106
|
+
*
|
|
107
|
+
* - checks whether the project includes a lock file that is consistent with the
|
|
108
|
+
* package-manager used by `create-electron-app`.
|
|
109
|
+
*/
|
|
110
|
+
export function testForgeTemplate({
|
|
111
|
+
moduleFormats,
|
|
112
|
+
templateName,
|
|
113
|
+
}: TestForgeTemplateOptions) {
|
|
114
|
+
describe(`${templateName} template`, () => {
|
|
115
|
+
if (!moduleFormats.length) {
|
|
116
|
+
d('`moduleFormats` is empty, defaulting to `cjs` only');
|
|
117
|
+
|
|
118
|
+
moduleFormats = moduleFormats.concat('cjs');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let tmpDir: string;
|
|
122
|
+
|
|
123
|
+
beforeEach(async () => {
|
|
124
|
+
tmpDir = await fs.promises.mkdtemp(
|
|
125
|
+
path.join(os.tmpdir(), 'electron-forge-test-'),
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const testCases: Array<
|
|
130
|
+
[SupportedModuleFormats[number], SupportedPackageManager]
|
|
131
|
+
> = moduleFormats.reduce(
|
|
132
|
+
(acc, moduleFormat) => {
|
|
133
|
+
const packageManagers: SupportedPackageManager[] = [
|
|
134
|
+
'npm',
|
|
135
|
+
'pnpm',
|
|
136
|
+
'yarn',
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
packageManagers.forEach((packageManager) => {
|
|
140
|
+
acc.push([moduleFormat, packageManager]);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return acc;
|
|
144
|
+
},
|
|
145
|
+
[] as typeof testCases,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
test.each(testCases)(
|
|
149
|
+
`can launch a \`%s\` project created from \`template-${templateName}\` with \`%s\``,
|
|
150
|
+
async (moduleFormat, packageManager) => {
|
|
151
|
+
if (!supportedTemplates.includes(templateName)) {
|
|
152
|
+
throw new Error(`unknown template ${templateName}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const createOutput = await spawn('node', [
|
|
156
|
+
path.resolve(
|
|
157
|
+
__dirname,
|
|
158
|
+
'../../../external/create-electron-app/dist/create-electron-app.js',
|
|
159
|
+
),
|
|
160
|
+
tmpDir,
|
|
161
|
+
`--template=${templateName}`,
|
|
162
|
+
`--package-manager=${packageManager}`,
|
|
163
|
+
|
|
164
|
+
// Electron 41 is the last version that downloads its binary from a
|
|
165
|
+
// `postinstall` script. Yarn 4.18 disables install scripts by
|
|
166
|
+
// default (`enableScripts`), so on 41 the binary never gets
|
|
167
|
+
// downloaded and `electron-forge start` fails with "Electron failed
|
|
168
|
+
// to install correctly". Electron 42+ downloads the binary on demand
|
|
169
|
+
// the first time it's needed instead, so no install script is
|
|
170
|
+
// involved.
|
|
171
|
+
`--electron-version=43.4.0`,
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
d('tmpdir: ', pathToFileURL(tmpDir).toString());
|
|
175
|
+
|
|
176
|
+
const possiblePreloadFiles = ['preload.ts', 'preload.js'].map((item) =>
|
|
177
|
+
path.resolve(tmpDir, `src/${item}`),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const possibleMainProcessEntrypoints = [
|
|
181
|
+
'main.ts',
|
|
182
|
+
'main.js',
|
|
183
|
+
'index.ts',
|
|
184
|
+
'index.js',
|
|
185
|
+
].map((item) => path.resolve(tmpDir, `src/${item}`));
|
|
186
|
+
|
|
187
|
+
const preloadPath = possiblePreloadFiles.find((item) =>
|
|
188
|
+
fs.existsSync(item),
|
|
189
|
+
)!;
|
|
190
|
+
const mainProcessEntrypoint = possibleMainProcessEntrypoints.find(
|
|
191
|
+
(item) => fs.existsSync(item),
|
|
192
|
+
)!;
|
|
193
|
+
|
|
194
|
+
let missingPreloadFileError: Error | null = null;
|
|
195
|
+
let missingMainProcessEntrypointError: Error | null = null;
|
|
196
|
+
|
|
197
|
+
if (!preloadPath) {
|
|
198
|
+
missingPreloadFileError = new Error(
|
|
199
|
+
`"preload file not found in the following locations: ${JSON.stringify(possiblePreloadFiles, null, 2)}`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!mainProcessEntrypoint) {
|
|
204
|
+
missingMainProcessEntrypointError = new Error(
|
|
205
|
+
`"main process entrypoint not found in the following locations: ${JSON.stringify(possibleMainProcessEntrypoints, null, 2)}`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (missingPreloadFileError || missingMainProcessEntrypointError) {
|
|
210
|
+
throw new AggregateError(
|
|
211
|
+
[missingPreloadFileError, missingMainProcessEntrypointError],
|
|
212
|
+
'one or more files are missing',
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const preloadOkMessage = '__FORGE_INTERNAL_PRELOAD_PROCESS_OK__';
|
|
217
|
+
const mainProcessOkMessage = '__FORGE_INTERNAL_MAIN_PROCESS_OK__';
|
|
218
|
+
|
|
219
|
+
await fs.promises.appendFile(
|
|
220
|
+
preloadPath,
|
|
221
|
+
[
|
|
222
|
+
'\n',
|
|
223
|
+
moduleFormat === 'es'
|
|
224
|
+
? `import { ipcRenderer } from 'electron';`
|
|
225
|
+
: `const { ipcRenderer } = require('electron');`,
|
|
226
|
+
|
|
227
|
+
// If the preload file loads correctly, it will send this message to
|
|
228
|
+
// the main process, which will in turn log it. Once the app exits
|
|
229
|
+
// (either by calling `app.exit()` in our injected code upon
|
|
230
|
+
// receiving this message or after the test times out), we check if
|
|
231
|
+
// `electronForgeStartOutput` includes this string; if it doesn't,
|
|
232
|
+
// it means the preload file failed to load for some reason.
|
|
233
|
+
`ipcRenderer.send('${preloadOkMessage}')`,
|
|
234
|
+
].join('\n'),
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
await fs.promises.appendFile(
|
|
238
|
+
mainProcessEntrypoint,
|
|
239
|
+
[
|
|
240
|
+
'\n',
|
|
241
|
+
moduleFormat === 'es'
|
|
242
|
+
? `import { ipcMain } from 'electron';`
|
|
243
|
+
: `const { ipcMain } = require('electron');`,
|
|
244
|
+
|
|
245
|
+
// This runs at the top-level and below all other template code, so
|
|
246
|
+
// its presence in `electronForgeStartOutput` indicates that the
|
|
247
|
+
// main process entrypoint must have been correctly parsed /
|
|
248
|
+
// transpiled.
|
|
249
|
+
`console.log('${mainProcessOkMessage}');`,
|
|
250
|
+
|
|
251
|
+
// Logs the message that indicates that the preload file has been
|
|
252
|
+
// correctly loaded.
|
|
253
|
+
`
|
|
254
|
+
ipcMain.on('${preloadOkMessage}', () => {
|
|
255
|
+
console.log('${preloadOkMessage}');
|
|
256
|
+
app.exit(0);
|
|
257
|
+
});
|
|
258
|
+
`,
|
|
259
|
+
].join('\n'),
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const startApp = () =>
|
|
263
|
+
spawn(packageManager, ['run', 'start'], {
|
|
264
|
+
cwd: tmpDir,
|
|
265
|
+
env: {
|
|
266
|
+
PATH: process.env.PATH,
|
|
267
|
+
/**
|
|
268
|
+
* `start` makes the package manager check the lockfile it just
|
|
269
|
+
* wrote, and `XDG_CONFIG_HOME` is where the Verdaccio test harness
|
|
270
|
+
* puts the config that tells pnpm which registry to use, how old a
|
|
271
|
+
* release has to be, which packages are exempt, and to warn rather
|
|
272
|
+
* than fail when the check finds a difference. Dropping it would
|
|
273
|
+
* leave the check looking at the public registry under a policy
|
|
274
|
+
* the install never ran under.
|
|
275
|
+
*/
|
|
276
|
+
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME,
|
|
277
|
+
...(process.platform === 'linux' && {
|
|
278
|
+
DISPLAY: process.env.DISPLAY,
|
|
279
|
+
XAUTHORITY: process.env.XAUTHORITY,
|
|
280
|
+
}),
|
|
281
|
+
...(packageManager !== 'yarn' && {
|
|
282
|
+
/**
|
|
283
|
+
* HACK: when running the test script with Yarn on a npm/pnpm
|
|
284
|
+
* project created by `create-electron-app`,
|
|
285
|
+
* `process.env.npm_config_user_agent` can be something like
|
|
286
|
+
* `yarn/4.10.3 npm/? node/v24.14.1 win32 x64`, and Forge's
|
|
287
|
+
* `checkPackageManager` function takes this value to mean that
|
|
288
|
+
* the project _also_ uses Yarn, so `electron-forge start` ends
|
|
289
|
+
* up failing because there's no `yarn.lock` ([relevant
|
|
290
|
+
* code](https://github.com/electron/forge/blob/001f41befe2c049b6f54ce7d6c55e83435141055/packages/api/cli/src/util/check-system.ts#L108-L135)).
|
|
291
|
+
*
|
|
292
|
+
* Removing the `yarn/4.10.3` user agent causes Forge to
|
|
293
|
+
* correctly identify the project's package manager, but since
|
|
294
|
+
* the version information can be missing for npm in `npm/?`, it
|
|
295
|
+
* fails semver validation and Forge treats it like an
|
|
296
|
+
* unsupported npm version, so we also have to spoof a supported
|
|
297
|
+
* npm version number to work around that behavior.
|
|
298
|
+
*/
|
|
299
|
+
npm_config_user_agent: process.env
|
|
300
|
+
.npm_config_user_agent!.replace(/^yarn\/\d+\.\d+\.\d+ /i, '')
|
|
301
|
+
.replace(/\bnpm\/\?/, 'npm/99.99.99'),
|
|
302
|
+
}),
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
let electronForgeStartOutput: string;
|
|
307
|
+
try {
|
|
308
|
+
electronForgeStartOutput = await startApp();
|
|
309
|
+
} catch (error) {
|
|
310
|
+
/**
|
|
311
|
+
* When `start` fails, it is usually because the package manager
|
|
312
|
+
* installed a dependency tree the app cannot resolve its own
|
|
313
|
+
* configuration from, and the failure alone doesn't say which tree it
|
|
314
|
+
* ended up with. `create-electron-app` runs its steps with listr2's
|
|
315
|
+
* `exitOnError: false`, so a failed install leaves a broken project
|
|
316
|
+
* behind and still exits 0; its output is the only place that failure
|
|
317
|
+
* is reported at all.
|
|
318
|
+
*/
|
|
319
|
+
console.error(
|
|
320
|
+
[
|
|
321
|
+
`[template-tests] ${packageManager} installed ${describeDependencyTree(tmpDir)}`,
|
|
322
|
+
`[template-tests] from this package.json:\n${fs.readFileSync(path.join(tmpDir, 'package.json'), 'utf8')}`,
|
|
323
|
+
...(packageManager === 'pnpm'
|
|
324
|
+
? [
|
|
325
|
+
`[template-tests] pnpm was run like this:\n${describePnpmInstalls(tmpDir)}`,
|
|
326
|
+
]
|
|
327
|
+
: []),
|
|
328
|
+
`[template-tests] create-electron-app said:\n${createOutput}`,
|
|
329
|
+
].join('\n'),
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
throw error;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
d({ electronForgeStartOutput });
|
|
336
|
+
|
|
337
|
+
const mainProcessOk =
|
|
338
|
+
electronForgeStartOutput.includes(mainProcessOkMessage);
|
|
339
|
+
const preloadProcessOk =
|
|
340
|
+
electronForgeStartOutput.includes(preloadOkMessage);
|
|
341
|
+
|
|
342
|
+
expect(mainProcessOk).toBe(true);
|
|
343
|
+
expect(preloadProcessOk).toBe(true);
|
|
344
|
+
|
|
345
|
+
const lockFile = (
|
|
346
|
+
{
|
|
347
|
+
npm: 'package-lock.json',
|
|
348
|
+
pnpm: 'pnpm-lock.yaml',
|
|
349
|
+
yarn: 'yarn.lock',
|
|
350
|
+
} as Record<SupportedPackageManager, string>
|
|
351
|
+
)[packageManager];
|
|
352
|
+
|
|
353
|
+
expect(fs.existsSync(path.resolve(tmpDir, lockFile))).toBe(true);
|
|
354
|
+
},
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
afterEach(async () => {
|
|
358
|
+
await fs.promises.rm(tmpDir, {
|
|
359
|
+
recursive: true,
|
|
360
|
+
force: true,
|
|
361
|
+
maxRetries: 5,
|
|
362
|
+
retryDelay: 500,
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
}
|