Skip to content

Commit b601062

Browse files
committed
Run Forge packaging through CLI again
1 parent cd9a41b commit b601062

1 file changed

Lines changed: 208 additions & 35 deletions

File tree

scripts/run-electron-forge-lib.js

Lines changed: 208 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ 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';
108
import { getMsixPaths } from './msix-config.js';
119
import { loadStorePackageConfig } from './store-package-config.js';
1210
import { buildStorePurchaseAddon } from './build-store-purchase-addon.js';
@@ -23,8 +21,10 @@ const packageDir = configuredPackageOutputDir
2321
: path.resolve(projectRoot, configuredPackageOutputDir))
2422
: path.join(projectRoot, 'pkg');
2523
const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'));
26-
const makeDistributables = makeDistributablesModule.default ?? makeDistributablesModule;
27-
const packageApplication = packageApplicationModule.default ?? packageApplicationModule;
24+
25+
function resolveForgeCliScriptPath() {
26+
return path.join(projectRoot, 'node_modules', '@electron-forge', 'cli', 'dist', 'electron-forge.js');
27+
}
2828

2929
function run(command, args, options = {}) {
3030
const result = spawnSync(command, args, {
@@ -140,6 +140,84 @@ 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+
143221
async function resetOutputDirectories() {
144222
await fsp.rm(outDir, { recursive: true, force: true });
145223
await fsp.rm(packageDir, { recursive: true, force: true });
@@ -158,16 +236,94 @@ function resolveUnpackedDestination(platform, arch) {
158236
return path.join(packageDir, arch === 'arm64' ? 'mac-arm64' : 'mac');
159237
}
160238

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+
161304
async function stagePackagedApplication(platform, arch, packagedPath) {
162-
if (!packagedPath) {
163-
throw new Error(`Forge package() did not return a packagedPath for ${platform}/${arch}`);
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}`);
164320
}
165321

166322
const destination = resolveUnpackedDestination(platform, arch);
167323
await fsp.rm(destination, { recursive: true, force: true });
168324
await fsp.mkdir(path.dirname(destination), { recursive: true });
169325

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

326482
async function collectArtifacts(makeResults) {
327-
const artifactPaths = unique(makeResults.flatMap(result => result.artifacts));
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+
}
328490

329491
for (const artifactPath of artifactPaths) {
330492
const stats = await fsp.stat(artifactPath);
@@ -339,6 +501,20 @@ async function collectArtifacts(makeResults) {
339501
}
340502
}
341503

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+
342518
async function createTarGzArtifact(unpackedDir, platform, arch) {
343519
const artifactName = `${sanitizeArtifactNameSegment(packageJson.productName || packageJson.name)}-${packageJson.version}-${platform}-${arch}.tar.gz`;
344520
const artifactPath = path.join(packageDir, artifactName);
@@ -348,6 +524,11 @@ async function createTarGzArtifact(unpackedDir, platform, arch) {
348524
console.log(`[electron-forge] collected ${path.relative(projectRoot, artifactPath)}`);
349525
}
350526

527+
function runForgeCli(subcommand, args) {
528+
const forgeCliScriptPath = resolveForgeCliScriptPath();
529+
run(process.execPath, [forgeCliScriptPath, subcommand, ...args, projectRoot]);
530+
}
531+
351532
async function main() {
352533
ensureDarwinFileLimit();
353534

@@ -358,19 +539,14 @@ async function main() {
358539
await buildStorePurchaseAddon({ arch: options.arch });
359540
}
360541

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-
}
542+
runForgeCli('package', [
543+
'--platform',
544+
options.platform,
545+
'--arch',
546+
options.arch,
547+
]);
372548

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

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

389565
const forgeTargets = mapForgeTargets(options.platform, options.targets);
390566
if (forgeTargets.length > 0) {
391-
let makeResults;
392567
try {
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-
});
568+
runForgeCli('make', [
569+
'--platform',
570+
options.platform,
571+
'--arch',
572+
options.arch,
573+
'--skip-package',
574+
'--targets',
575+
forgeTargets.join(','),
576+
]);
402577
} catch (error) {
403-
const recoveredResults = await recoverMacDmgDetachRace(error, options);
404-
if (!recoveredResults) {
578+
const recoveredArtifacts = await recoverMacDmgDetachRace(error, options);
579+
if (!recoveredArtifacts) {
405580
throw error;
406581
}
407-
408-
makeResults = recoveredResults;
409582
}
410583

411-
await collectArtifacts(makeResults);
584+
await collectArtifacts([]);
412585
}
413586

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

0 commit comments

Comments
 (0)