@@ -5,8 +5,6 @@ import fsp from 'node:fs/promises';
55import path from 'node:path' ;
66import { spawnSync } from 'node:child_process' ;
77import { 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' ;
108import { getMsixPaths } from './msix-config.js' ;
119import { loadStorePackageConfig } from './store-package-config.js' ;
1210import { buildStorePurchaseAddon } from './build-store-purchase-addon.js' ;
@@ -23,8 +21,10 @@ const packageDir = configuredPackageOutputDir
2321 : path . resolve ( projectRoot , configuredPackageOutputDir ) )
2422 : path . join ( projectRoot , 'pkg' ) ;
2523const 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
2929function 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+
143221async 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+
161304async 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
326482async 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+
342518async 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+
351532async 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