@@ -12,11 +12,11 @@ import type { Kernel } from '../../../core/src/kernel/index.ts';
1212import { InMemoryFileSystem } from '../../../browser/src/os-filesystem.ts' ;
1313import { createNodeRuntime } from '../../../nodejs/src/kernel-runtime.ts' ;
1414
15- async function createNodeKernel ( ) : Promise < { kernel : Kernel ; dispose : ( ) => Promise < void > } > {
15+ async function createNodeKernel ( ) : Promise < { kernel : Kernel ; vfs : InMemoryFileSystem ; dispose : ( ) => Promise < void > } > {
1616 const vfs = new InMemoryFileSystem ( ) ;
1717 const kernel = createKernel ( { filesystem : vfs } ) ;
1818 await kernel . mount ( createNodeRuntime ( ) ) ;
19- return { kernel, dispose : ( ) => kernel . dispose ( ) } ;
19+ return { kernel, vfs , dispose : ( ) => kernel . dispose ( ) } ;
2020}
2121
2222/** Collect all output from a PTY-backed process spawned via openShell. */
@@ -140,3 +140,104 @@ describe('bridge gap: setRawMode via PTY', () => {
140140 expect ( output ) . toContain ( 'not a TTY' ) ;
141141 } , 15_000 ) ;
142142} ) ;
143+
144+ // ---------------------------------------------------------------------------
145+ // Native ESM mode (V8 module system)
146+ // ---------------------------------------------------------------------------
147+
148+ describe ( 'native ESM execution via V8 module system' , ( ) => {
149+ let ctx : { kernel : Kernel ; vfs : InMemoryFileSystem ; dispose : ( ) => Promise < void > } ;
150+
151+ afterEach ( async ( ) => {
152+ await ctx ?. dispose ( ) ;
153+ } ) ;
154+
155+ it ( 'ESM module with import/export runs correctly via kernel.spawn()' , async ( ) => {
156+ ctx = await createNodeKernel ( ) ;
157+ // Write an ESM file to VFS
158+ await ctx . vfs . writeFile ( '/app/main.mjs' , `
159+ const msg = 'ESM_OK';
160+ console.log(msg);
161+ ` ) ;
162+
163+ const stdout : string [ ] = [ ] ;
164+ const proc = ctx . kernel . spawn ( 'node' , [ '/app/main.mjs' ] , {
165+ onStdout : ( data ) => stdout . push ( new TextDecoder ( ) . decode ( data ) ) ,
166+ } ) ;
167+ const exitCode = await proc . wait ( ) ;
168+
169+ expect ( exitCode ) . toBe ( 0 ) ;
170+ expect ( stdout . join ( '' ) ) . toContain ( 'ESM_OK' ) ;
171+ } , 15_000 ) ;
172+
173+ it ( 'CJS module with require() still runs correctly via kernel.spawn()' , async ( ) => {
174+ ctx = await createNodeKernel ( ) ;
175+ // CJS code — no import/export syntax, uses require
176+ const stdout : string [ ] = [ ] ;
177+ const proc = ctx . kernel . spawn ( 'node' , [ '-e' , "const os = require('os'); console.log('CJS_OK:' + os.platform())" ] , {
178+ onStdout : ( data ) => stdout . push ( new TextDecoder ( ) . decode ( data ) ) ,
179+ } ) ;
180+ const exitCode = await proc . wait ( ) ;
181+
182+ expect ( exitCode ) . toBe ( 0 ) ;
183+ expect ( stdout . join ( '' ) ) . toContain ( 'CJS_OK:' ) ;
184+ } , 15_000 ) ;
185+
186+ it ( 'ESM file with static import resolves via V8 module_resolve_callback' , async ( ) => {
187+ ctx = await createNodeKernel ( ) ;
188+ // Write two ESM files — main imports from helper
189+ await ctx . vfs . writeFile ( '/app/helper.mjs' , `
190+ export const greeting = 'HELLO_FROM_ESM';
191+ ` ) ;
192+ await ctx . vfs . writeFile ( '/app/main.mjs' , `
193+ import { greeting } from './helper.mjs';
194+ console.log(greeting);
195+ ` ) ;
196+
197+ const stdout : string [ ] = [ ] ;
198+ const proc = ctx . kernel . spawn ( 'node' , [ '/app/main.mjs' ] , {
199+ onStdout : ( data ) => stdout . push ( new TextDecoder ( ) . decode ( data ) ) ,
200+ } ) ;
201+ const exitCode = await proc . wait ( ) ;
202+
203+ expect ( exitCode ) . toBe ( 0 ) ;
204+ expect ( stdout . join ( '' ) ) . toContain ( 'HELLO_FROM_ESM' ) ;
205+ } , 15_000 ) ;
206+
207+ it ( 'import.meta.url is populated for ESM modules' , async ( ) => {
208+ ctx = await createNodeKernel ( ) ;
209+ await ctx . vfs . writeFile ( '/app/meta.mjs' , `
210+ console.log('META_URL:' + import.meta.url);
211+ ` ) ;
212+
213+ const stdout : string [ ] = [ ] ;
214+ const proc = ctx . kernel . spawn ( 'node' , [ '/app/meta.mjs' ] , {
215+ onStdout : ( data ) => stdout . push ( new TextDecoder ( ) . decode ( data ) ) ,
216+ } ) ;
217+ const exitCode = await proc . wait ( ) ;
218+
219+ expect ( exitCode ) . toBe ( 0 ) ;
220+ const output = stdout . join ( '' ) ;
221+ expect ( output ) . toContain ( 'META_URL:file:///app/meta.mjs' ) ;
222+ } , 15_000 ) ;
223+
224+ it ( 'dynamic import() works in ESM via V8 native callback' , async ( ) => {
225+ ctx = await createNodeKernel ( ) ;
226+ await ctx . vfs . writeFile ( '/app/dynamic-dep.mjs' , `
227+ export const value = 'DYNAMIC_IMPORT_OK';
228+ ` ) ;
229+ await ctx . vfs . writeFile ( '/app/dynamic-main.mjs' , `
230+ const mod = await import('./dynamic-dep.mjs');
231+ console.log(mod.value);
232+ ` ) ;
233+
234+ const stdout : string [ ] = [ ] ;
235+ const proc = ctx . kernel . spawn ( 'node' , [ '/app/dynamic-main.mjs' ] , {
236+ onStdout : ( data ) => stdout . push ( new TextDecoder ( ) . decode ( data ) ) ,
237+ } ) ;
238+ const exitCode = await proc . wait ( ) ;
239+
240+ expect ( exitCode ) . toBe ( 0 ) ;
241+ expect ( stdout . join ( '' ) ) . toContain ( 'DYNAMIC_IMPORT_OK' ) ;
242+ } , 15_000 ) ;
243+ } ) ;
0 commit comments