1+ import { fileURLToPath } from 'node:url' ;
2+ import { cwd , stdout , platform } from 'node:process' ;
3+ import { freemem } from 'node:os' ;
4+ import { spawnSync } from 'node:child_process' ;
5+ import { tmpdir } from 'node:os' ;
6+ import { resolve , join , dirname } from 'node:path' ;
7+ import { readFile , writeFile , mkdir , rm } from 'node:fs/promises' ;
8+ import { rmSync , existsSync } from 'node:fs' ;
9+ import { createHash } from 'node:crypto' ;
10+
111import wizer from '@bytecodealliance/wizer' ;
212import getWeval from '@bytecodealliance/weval' ;
313import {
414 componentNew ,
515 metadataAdd ,
616 preview1AdapterReactorPath ,
717} from '@bytecodealliance/jco' ;
8- import { spawnSync } from 'node:child_process' ;
9- import { tmpdir } from 'node:os' ;
10- import { resolve , join , dirname } from 'node:path' ;
11- import { readFile , writeFile , mkdir , rm } from 'node:fs/promises' ;
12- import { rmSync , existsSync } from 'node:fs' ;
13- import { createHash } from 'node:crypto' ;
18+
1419import {
1520 spliceBindings ,
1621 stubWasi ,
1722} from '../lib/spidermonkey-embedding-splicer.js' ;
18- import { fileURLToPath } from 'node:url' ;
19- import { cwd , stdout , platform } from 'node:process' ;
23+
2024export const { version } = JSON . parse (
2125 await readFile ( new URL ( '../package.json' , import . meta. url ) , 'utf8' )
2226) ;
23- const isWindows = platform === 'win32' ;
2427
25- function maybeWindowsPath ( path ) {
26- if ( ! path ) return path ;
27- if ( ! isWindows ) return resolve ( path ) ;
28- return '//?/' + resolve ( path ) . replace ( / \\ / g, '/' ) ;
29- }
30-
31- /**
32- * Clean up the given input string by removing the given patterns if
33- * found as line prefixes.
34- */
35- function stripLinesPrefixes ( input , prefixPatterns ) {
36- return input . split ( '\n' )
37- . map ( line => prefixPatterns . reduce ( ( line , n ) => line . replace ( n , '' ) , line ) )
38- . join ( '\n' ) . trim ( ) ;
39- }
28+ /** Whether the current platform is windows */
29+ const IS_WINDOWS = platform === 'win32' ;
4030
41- const WizerErrorCause = `Error: the \`componentize.wizer\` function trapped
31+ /** Prefix into wizer error output that indicates a error/trap */
32+ const WIZER_ERROR_CAUSE_PREFIX = `Error: the \`componentize.wizer\` function trapped
4233
4334Caused by:` ;
4435
45- const WizerExitCode = "Exited with i32 exit status" ;
46-
47- function parseWizerStderr ( stderr ) {
48- let output = `${ stderr } ` ;
49- let causeStart = output . indexOf ( WizerErrorCause ) ;
50- let exitCodeStart = output . indexOf ( WizerExitCode ) ;
51- if ( causeStart === - 1 || exitCodeStart === - 1 ) {
52- return output ;
53- }
54-
55- let causeEnd = output . indexOf ( '\n' , exitCodeStart + 1 ) ;
56- return `${ output . substring ( 0 , causeStart ) } ${ output . substring ( causeEnd ) } ` . trim ( ) ;
57- }
36+ /** Prefix into wizer error output that indicates exit status */
37+ const WIZER_EXIT_CODE_PREFIX = 'Exited with i32 exit status' ;
5838
59- export async function componentize ( opts ,
60- _deprecatedWitWorldOrOpts = undefined ,
61- _deprecatedOpts = undefined ) {
39+ export async function componentize (
40+ opts ,
41+ _deprecatedWitWorldOrOpts = undefined ,
42+ _deprecatedOpts = undefined ,
43+ ) {
6244 let useOriginalSourceFile = true ;
6345 let jsSource ;
6446
@@ -74,27 +56,19 @@ export async function componentize(opts,
7456 } else {
7557 if ( typeof _deprecatedWitWorldOrOpts !== 'object' ) {
7658 throw new Error (
77- `componentize: second argument must be an object or a string, but is ${ typeof _deprecatedWitWorldOrOpts } `
59+ `componentize: second argument must be an object or a string, but is ${ typeof _deprecatedWitWorldOrOpts } ` ,
7860 ) ;
7961 }
8062 opts = _deprecatedWitWorldOrOpts ;
8163 }
8264 }
8365
84- const tmpDir = join (
85- tmpdir ( ) ,
86- createHash ( 'sha256' )
87- . update ( Math . random ( ) . toString ( ) )
88- . digest ( 'hex' )
89- . slice ( 0 , 12 )
90- ) ;
91- await mkdir ( tmpDir ) ;
92- const sourceDir = join ( tmpDir , 'sources' ) ;
93- await mkdir ( sourceDir ) ;
66+ // Prepare a working diretory for use during componentization
67+ const { sourcesDir, baseDir : tmpDir } = prepWorkDir ( ) ;
9468
9569 let {
9670 sourceName = 'source.js' ,
97- sourcePath = join ( sourceDir , sourceName ) ,
71+ sourcePath = join ( sourcesDir , sourceName ) ,
9872 preview2Adapter = preview1AdapterReactorPath ( ) ,
9973 witPath,
10074 witWorld,
@@ -110,40 +84,30 @@ export async function componentize(opts,
11084 ) ,
11185 } = opts ;
11286
113- const engine =
114- opts . engine ||
115- fileURLToPath (
116- new URL (
117- opts . enableAot
118- ? `../lib/starlingmonkey_embedding_weval.wasm`
119- : `../lib/starlingmonkey_embedding${
120- debugBuild ? '.debug' : ''
121- } .wasm`,
122- import . meta. url
123- )
124- ) ;
87+ // Determine the path to the StarlingMonkey binary
88+ const engine = getEnginePath ( opts ) ;
12589
12690 let { wasm, jsBindings, exports, imports } = spliceBindings (
12791 await readFile ( engine ) ,
12892 witWorld ,
12993 maybeWindowsPath ( witPath ) ,
13094 worldName ,
131- false
95+ false ,
13296 ) ;
13397
13498 const input = join ( tmpDir , 'in.wasm' ) ;
13599 const output = join ( tmpDir , 'out.wasm' ) ;
136100
137101 await writeFile ( input , Buffer . from ( wasm ) ) ;
138- await writeFile ( join ( sourceDir , " initializer.js" ) , jsBindings ) ;
102+ await writeFile ( join ( sourcesDir , ' initializer.js' ) , jsBindings ) ;
139103
140104 if ( debugBindings ) {
141105 console . log ( '--- JS Bindings ---' ) ;
142106 console . log (
143107 jsBindings
144108 . split ( '\n' )
145109 . map ( ( ln , idx ) => `${ ( idx + 1 ) . toString ( ) . padStart ( 4 , ' ' ) } | ${ ln } ` )
146- . join ( '\n' )
110+ . join ( '\n' ) ,
147111 ) ;
148112 console . log ( '--- JS Imports ---' ) ;
149113 console . log ( imports ) ;
@@ -206,7 +170,7 @@ export async function componentize(opts,
206170 console . log ( env ) ;
207171 }
208172
209- let initializerPath = join ( sourceDir , 'initializer.js' ) ;
173+ let initializerPath = join ( sourcesDir , 'initializer.js' ) ;
210174 sourcePath = maybeWindowsPath ( sourcePath ) ;
211175 let workspacePrefix = dirname ( sourcePath ) ;
212176
@@ -221,7 +185,7 @@ export async function componentize(opts,
221185 }
222186 let args = `--initializer-script-path ${ initializerPath } ${ sourcePath } ` ;
223187 runtimeArgs = runtimeArgs ? `${ runtimeArgs } ${ args } ` : args ;
224- let preopens = [ `--dir ${ sourceDir } ` ] ;
188+ let preopens = [ `--dir ${ sourcesDir } ` ] ;
225189 if ( opts . enableAot ) {
226190 preopens . push ( `--dir ${ workspacePrefix } ` ) ;
227191 } else {
@@ -252,12 +216,12 @@ export async function componentize(opts,
252216 `-o ${ output } ` ,
253217 ] ,
254218 {
255- stdio : [ null , stdout , " pipe" ] ,
219+ stdio : [ null , stdout , ' pipe' ] ,
256220 env,
257221 input : runtimeArgs ,
258222 shell : true ,
259223 encoding : 'utf-8' ,
260- }
224+ } ,
261225 ) ;
262226 } else {
263227 wizerProcess = spawnSync (
@@ -273,15 +237,16 @@ export async function componentize(opts,
273237 input ,
274238 ] ,
275239 {
276- stdio : [ null , stdout , " pipe" ] ,
240+ stdio : [ null , stdout , ' pipe' ] ,
277241 env,
278242 input : runtimeArgs ,
279243 shell : true ,
280244 encoding : 'utf-8' ,
281- }
245+ } ,
282246 ) ;
283247 }
284248
249+ // If the wizer (or weval) process failed, parse the output and display to the user
285250 if ( wizerProcess . status !== 0 ) {
286251 let wizerErr = parseWizerStderr ( wizerProcess . stderr ) ;
287252 let err = `Failed to initialize component:\n${ wizerErr } ` ;
@@ -413,11 +378,111 @@ export async function componentize(opts,
413378
414379 // convert CABI import conventions to ESM import conventions
415380 imports = imports . map ( ( [ specifier , impt ] ) =>
416- specifier === '$root' ? [ impt , 'default' ] : [ specifier , impt ]
381+ specifier === '$root' ? [ impt , 'default' ] : [ specifier , impt ] ,
417382 ) ;
418383
419384 return {
420385 component,
421386 imports,
422387 } ;
423388}
389+
390+ /**
391+ * Calculate the min stack size depending on free memory
392+ *
393+ * @param {number } freeMemoryBytes - Amount of free memory in the system, in bytes (if not provided, os.freemem() is used)
394+ * @returns {number } The minimum stack size that should be used as a default.
395+ */
396+ function defaultMinStackSize ( freeMemoryBytes ) {
397+ freeMemoryBytes = freeMemoryBytes ?? freemem ( ) ;
398+ return Math . max ( 8 * 1024 * 1024 , Math . floor ( freeMemoryBytes * 0.1 ) ) ;
399+ }
400+
401+ /** Determine the correct path for the engine */
402+ function getEnginePath ( opts ) {
403+ if ( opts . engine ) {
404+ return opts . engine ;
405+ }
406+ let engineBinaryRelPath = '../lib/starlingmonkey_embedding_weval.wasm' ;
407+ if ( opts . enableAot ) {
408+ engineBinaryRelPath = `../lib/starlingmonkey_embedding${ debugBuild ? '.debug' : '' } .wasm` ;
409+ }
410+ return fileURLToPath ( new URL ( engineBinaryRelPath , import . meta. url ) ) ;
411+ }
412+
413+ /**
414+ * Parse output of post-processing step (whether Wizer or Weval)
415+ *
416+ * @param {Stream } stderr
417+ * @returns {string } String that can be printed to describe error output
418+ */
419+ function parseWizerStderr ( stderr ) {
420+ let output = `${ stderr } ` ;
421+ let causeStart = output . indexOf ( WIZER_ERROR_CAUSE_PREFIX ) ;
422+ let exitCodeStart = output . indexOf ( WIZER_EXIT_CODE_PREFIX ) ;
423+ if ( causeStart === - 1 || exitCodeStart === - 1 ) {
424+ return output ;
425+ }
426+
427+ let causeEnd = output . indexOf ( '\n' , exitCodeStart + 1 ) ;
428+ return `${ output . substring ( 0 , causeStart ) } ${ output . substring ( causeEnd ) } ` . trim ( ) ;
429+ }
430+
431+ /**
432+ * Check whether a value is numeric (including BigInt)
433+ *
434+ * @param {any } n
435+ * @returns {boolean } whether the value is numeric
436+ */
437+ function isNumeric ( n ) {
438+ switch ( typeof n ) {
439+ case 'bigint' :
440+ case 'number' :
441+ return true ;
442+ case 'object' :
443+ return n . constructor == BigInt || n . constructor == Number ;
444+ default :
445+ return false ;
446+ }
447+ }
448+
449+ /**
450+ * Convert a given path to one that also works on windows
451+ *
452+ * @param {string } path - A path that may or may not be windows-compatible
453+ * @returns {string } The Windows-compatible (if necessary) path
454+ */
455+ function maybeWindowsPath ( path ) {
456+ if ( ! path ) return path ;
457+ if ( ! IS_WINDOWS ) return resolve ( path ) ;
458+ return '//?/' + resolve ( path ) . replace ( / \\ / g, '/' ) ;
459+ }
460+
461+ /**
462+ * Clean up the given input string by removing the given patterns if
463+ * found as line prefixes.
464+ */
465+ function stripLinesPrefixes ( input , prefixPatterns ) {
466+ return input
467+ . split ( '\n' )
468+ . map ( ( line ) =>
469+ prefixPatterns . reduce ( ( line , n ) => line . replace ( n , '' ) , line ) ,
470+ )
471+ . join ( '\n' )
472+ . trim ( ) ;
473+ }
474+
475+ /** Prepare a work directory for use with componentization */
476+ async function prepWorkDir ( ) {
477+ const tmpDir = join (
478+ tmpdir ( ) ,
479+ createHash ( 'sha256' )
480+ . update ( Math . random ( ) . toString ( ) )
481+ . digest ( 'hex' )
482+ . slice ( 0 , 12 ) ,
483+ ) ;
484+ await mkdir ( tmpDir ) ;
485+ const sourcesDir = join ( tmpDir , 'sources' ) ;
486+ await mkdir ( sourceDir ) ;
487+ return { baseDir, sourcesDir } ;
488+ }
0 commit comments