Skip to content

Commit bd82484

Browse files
committed
Restore Forge core API packaging flow
1 parent c09b600 commit bd82484

1 file changed

Lines changed: 35 additions & 208 deletions

File tree

scripts/run-electron-forge-lib.js

Lines changed: 35 additions & 208 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import fsp from 'node:fs/promises';
55
import path from 'node:path';
66
import { spawnSync } from 'node:child_process';
77
import { fileURLToPath } from 'node:url';
8+
import makeDistributablesModule from '@electron-forge/core/dist/api/make.js';
9+
import packageApplicationModule from '@electron-forge/core/dist/api/package.js';
810
import { getMsixPaths } from './msix-config.js';
911
import { loadStorePackageConfig } from './store-package-config.js';
1012
import { buildStorePurchaseAddon } from './build-store-purchase-addon.js';
@@ -21,10 +23,8 @@ const packageDir = configuredPackageOutputDir
2123
: path.resolve(projectRoot, configuredPackageOutputDir))
2224
: path.join(projectRoot, 'pkg');
2325
const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'));
24-
25-
function resolveForgeCliScriptPath() {
26-
return path.join(projectRoot, 'node_modules', '@electron-forge', 'cli', 'dist', 'electron-forge.js');
27-
}
26+
const makeDistributables = makeDistributablesModule.default ?? makeDistributablesModule;
27+
const packageApplication = packageApplicationModule.default ?? packageApplicationModule;
2828

2929
function run(command, args, options = {}) {
3030
const result = spawnSync(command, args, {
@@ -140,84 +140,6 @@ function unique(values) {
140140
return [...new Set(values)];
141141
}
142142

143-
function inferPackagedRootFromAsar(asarPath, platform) {
144-
const asarDirectory = path.dirname(asarPath);
145-
146-
if (platform === 'darwin') {
147-
const resourcesDirectory = path.basename(asarDirectory);
148-
const contentsDirectory = path.basename(path.dirname(asarDirectory));
149-
const bundlePath = path.dirname(path.dirname(asarDirectory));
150-
151-
if (resourcesDirectory === 'Resources' && contentsDirectory === 'Contents' && bundlePath.endsWith('.app')) {
152-
return bundlePath;
153-
}
154-
155-
return null;
156-
}
157-
158-
if (path.basename(asarDirectory) !== 'resources') {
159-
return null;
160-
}
161-
162-
return path.dirname(asarDirectory);
163-
}
164-
165-
async function pathExists(targetPath) {
166-
try {
167-
await fsp.access(targetPath);
168-
return true;
169-
} catch {
170-
return false;
171-
}
172-
}
173-
174-
async function findPaths(rootPath, predicate, maxDepth = 4, depth = 0) {
175-
const matches = [];
176-
let entries = [];
177-
178-
try {
179-
entries = await fsp.readdir(rootPath, { withFileTypes: true });
180-
} catch {
181-
return matches;
182-
}
183-
184-
for (const entry of entries) {
185-
const candidatePath = path.join(rootPath, entry.name);
186-
if (await predicate(candidatePath, entry, depth)) {
187-
matches.push(candidatePath);
188-
}
189-
190-
if (entry.isDirectory() && depth < maxDepth) {
191-
matches.push(...await findPaths(candidatePath, predicate, maxDepth, depth + 1));
192-
}
193-
}
194-
195-
return matches;
196-
}
197-
198-
async function describeDirectoryTree(rootPath, maxDepth = 3, depth = 0) {
199-
let entries = [];
200-
201-
try {
202-
entries = await fsp.readdir(rootPath, { withFileTypes: true });
203-
} catch {
204-
return [];
205-
}
206-
207-
const lines = [];
208-
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
209-
const candidatePath = path.join(rootPath, entry.name);
210-
const relativePath = path.relative(projectRoot, candidatePath) || path.basename(candidatePath);
211-
lines.push(`${' '.repeat(depth)}- ${relativePath}${entry.isDirectory() ? '/' : ''}`);
212-
213-
if (entry.isDirectory() && depth < maxDepth) {
214-
lines.push(...await describeDirectoryTree(candidatePath, maxDepth, depth + 1));
215-
}
216-
}
217-
218-
return lines;
219-
}
220-
221143
async function resetOutputDirectories() {
222144
await fsp.rm(outDir, { recursive: true, force: true });
223145
await fsp.rm(packageDir, { recursive: true, force: true });
@@ -236,94 +158,16 @@ function resolveUnpackedDestination(platform, arch) {
236158
return path.join(packageDir, arch === 'arm64' ? 'mac-arm64' : 'mac');
237159
}
238160

239-
async function findFallbackPackagedPath(platform, arch) {
240-
const executableName = platform === 'win32' ? `${packageJson.productName || packageJson.name}.exe` : null;
241-
const fallbackExecutableName = platform === 'linux' ? `${packageJson.productName || packageJson.name}` : null;
242-
const asarPaths = await findPaths(outDir, async (candidatePath, entry) => (
243-
entry.isFile() && entry.name === 'app.asar'
244-
), 8);
245-
const candidateRoots = unique(
246-
asarPaths
247-
.map(asarPath => inferPackagedRootFromAsar(asarPath, platform))
248-
.filter(Boolean),
249-
).sort((left, right) => left.localeCompare(right));
250-
251-
for (const candidateRoot of candidateRoots) {
252-
if (platform === 'win32') {
253-
if (await pathExists(path.join(candidateRoot, executableName))) {
254-
return candidateRoot;
255-
}
256-
continue;
257-
}
258-
259-
if (platform === 'linux' && fallbackExecutableName) {
260-
if (await pathExists(path.join(candidateRoot, fallbackExecutableName))) {
261-
return candidateRoot;
262-
}
263-
}
264-
265-
return candidateRoot;
266-
}
267-
268-
if (platform === 'darwin') {
269-
const bundles = await findPaths(outDir, async (candidatePath, entry) => entry.isDirectory() && entry.name.endsWith('.app'), 8);
270-
return bundles[0] || null;
271-
}
272-
273-
return null;
274-
}
275-
276-
async function resolvePackagedApplicationPath(platform, arch, packagedPath) {
277-
if (packagedPath && await pathExists(packagedPath)) {
278-
return packagedPath;
279-
}
280-
281-
const fallbackPath = await findFallbackPackagedPath(platform, arch);
282-
if (fallbackPath) {
283-
console.warn(`[electron-forge] Using fallback packaged application path from ${path.relative(projectRoot, fallbackPath)}`);
284-
}
285-
286-
return fallbackPath;
287-
}
288-
289-
async function waitForPackagedApplicationPath(platform, arch, packagedPath, timeoutMs = 30000) {
290-
const startedAt = Date.now();
291-
292-
while ((Date.now() - startedAt) < timeoutMs) {
293-
const resolvedPackagedPath = await resolvePackagedApplicationPath(platform, arch, packagedPath);
294-
if (resolvedPackagedPath) {
295-
return resolvedPackagedPath;
296-
}
297-
298-
await new Promise(resolve => setTimeout(resolve, 500));
299-
}
300-
301-
return null;
302-
}
303-
304161
async function stagePackagedApplication(platform, arch, packagedPath) {
305-
const resolvedPackagedPath = await waitForPackagedApplicationPath(platform, arch, packagedPath);
306-
if (!resolvedPackagedPath) {
307-
const outEntries = await describeDirectoryTree(outDir, 4);
308-
const asarPaths = await findPaths(outDir, async (candidatePath, entry) => (
309-
entry.isFile() && entry.name === 'app.asar'
310-
), 8);
311-
if (outEntries.length > 0) {
312-
console.warn(`[electron-forge] out directory contents:\n${outEntries.join('\n')}`);
313-
} else {
314-
console.warn('[electron-forge] out directory is empty or missing after packaging.');
315-
}
316-
if (asarPaths.length > 0) {
317-
console.warn(`[electron-forge] discovered app.asar files:\n${asarPaths.map(candidatePath => `- ${path.relative(projectRoot, candidatePath)}`).join('\n')}`);
318-
}
319-
throw new Error(`Unable to locate packaged application output for ${platform}/${arch} under ${outDir}`);
162+
if (!packagedPath) {
163+
throw new Error(`Forge package() did not return a packagedPath for ${platform}/${arch}`);
320164
}
321165

322166
const destination = resolveUnpackedDestination(platform, arch);
323167
await fsp.rm(destination, { recursive: true, force: true });
324168
await fsp.mkdir(path.dirname(destination), { recursive: true });
325169

326-
await fsp.cp(resolvedPackagedPath, destination, { recursive: true });
170+
await fsp.cp(packagedPath, destination, { recursive: true });
327171
await materializeForgePackagingResources(destination, platform);
328172
console.log(`[electron-forge] staged unpacked application ${path.relative(projectRoot, destination)}`);
329173
return destination;
@@ -480,13 +324,7 @@ async function recoverMacDmgDetachRace(error, options) {
480324
}
481325

482326
async function collectArtifacts(makeResults) {
483-
let artifactPaths = unique(makeResults.flatMap(result => result.artifacts));
484-
if (artifactPaths.length === 0) {
485-
artifactPaths = await findFallbackMakeArtifacts();
486-
if (artifactPaths.length > 0) {
487-
console.warn(`[electron-forge] Recovered Forge artifacts from ${path.relative(projectRoot, path.join(outDir, 'make'))}`);
488-
}
489-
}
327+
const artifactPaths = unique(makeResults.flatMap(result => result.artifacts));
490328

491329
for (const artifactPath of artifactPaths) {
492330
const stats = await fsp.stat(artifactPath);
@@ -501,20 +339,6 @@ async function collectArtifacts(makeResults) {
501339
}
502340
}
503341

504-
async function findFallbackMakeArtifacts() {
505-
const makeRoot = path.join(outDir, 'make');
506-
const supportedExtensions = new Set(['.appimage', '.dmg', '.exe', '.msix', '.zip']);
507-
const artifactPaths = await findPaths(makeRoot, async (candidatePath, entry) => {
508-
if (!entry.isFile()) {
509-
return false;
510-
}
511-
512-
return supportedExtensions.has(path.extname(candidatePath).toLowerCase());
513-
}, 5);
514-
515-
return artifactPaths.sort((left, right) => left.localeCompare(right));
516-
}
517-
518342
async function createTarGzArtifact(unpackedDir, platform, arch) {
519343
const artifactName = `${sanitizeArtifactNameSegment(packageJson.productName || packageJson.name)}-${packageJson.version}-${platform}-${arch}.tar.gz`;
520344
const artifactPath = path.join(packageDir, artifactName);
@@ -524,11 +348,6 @@ async function createTarGzArtifact(unpackedDir, platform, arch) {
524348
console.log(`[electron-forge] collected ${path.relative(projectRoot, artifactPath)}`);
525349
}
526350

527-
function runForgeCli(subcommand, args) {
528-
const forgeCliScriptPath = resolveForgeCliScriptPath();
529-
run(process.execPath, [forgeCliScriptPath, subcommand, ...args, projectRoot]);
530-
}
531-
532351
async function main() {
533352
ensureDarwinFileLimit();
534353

@@ -539,14 +358,19 @@ async function main() {
539358
await buildStorePurchaseAddon({ arch: options.arch });
540359
}
541360

542-
runForgeCli('package', [
543-
'--platform',
544-
options.platform,
545-
'--arch',
546-
options.arch,
547-
]);
361+
const packageResults = await packageApplication({
362+
dir: projectRoot,
363+
platform: options.platform,
364+
arch: options.arch,
365+
outDir,
366+
interactive: false,
367+
});
368+
369+
if (packageResults.length !== 1) {
370+
throw new Error(`Expected one packaged application, received ${packageResults.length}`);
371+
}
548372

549-
const unpackedDir = await stagePackagedApplication(options.platform, options.arch, null);
373+
const unpackedDir = await stagePackagedApplication(options.platform, options.arch, packageResults[0].packagedPath);
550374

551375
if (options.platform === 'win32' && options.targets.includes('msix')) {
552376
const { storeConfig } = await loadStorePackageConfig();
@@ -564,24 +388,27 @@ async function main() {
564388

565389
const forgeTargets = mapForgeTargets(options.platform, options.targets);
566390
if (forgeTargets.length > 0) {
391+
let makeResults;
567392
try {
568-
runForgeCli('make', [
569-
'--platform',
570-
options.platform,
571-
'--arch',
572-
options.arch,
573-
'--skip-package',
574-
'--targets',
575-
forgeTargets.join(','),
576-
]);
393+
makeResults = await makeDistributables({
394+
dir: projectRoot,
395+
platform: options.platform,
396+
arch: options.arch,
397+
outDir,
398+
skipPackage: true,
399+
overrideTargets: forgeTargets,
400+
interactive: false,
401+
});
577402
} catch (error) {
578-
const recoveredArtifacts = await recoverMacDmgDetachRace(error, options);
579-
if (!recoveredArtifacts) {
403+
const recoveredResults = await recoverMacDmgDetachRace(error, options);
404+
if (!recoveredResults) {
580405
throw error;
581406
}
407+
408+
makeResults = recoveredResults;
582409
}
583410

584-
await collectArtifacts([]);
411+
await collectArtifacts(makeResults);
585412
}
586413

587414
if (options.targets.includes('tar.gz')) {

0 commit comments

Comments
 (0)