@@ -493,79 +493,201 @@ export function assertVercelSandboxAuthAvailable(
493493}
494494
495495/**
496- * Resolve the pure-ESM entry of `@vercel/sandbox` as a file:// URL .
496+ * Locate the installed `@vercel/sandbox` package root .
497497 *
498- * Package root resolution under Next/CJS serverless can load `dist/index.cjs`
499- * → `command.cjs`, which does `require('@workflow/serde')`. That package is
500- * pure ESM, so Node throws ERR_REQUIRE_ESM on Vercel `/var/task`. The ESM
501- * graph (`dist/index.js`) imports serde correctly.
498+ * Avoids `createRequire(...).resolve` as the only strategy: Next/webpack can
499+ * leave `createRequire` unusable on Vercel (`undefined.resolve` → immediate
500+ * "Cannot read properties of undefined (reading 'resolve')"). Prefer
501+ * filesystem discovery under cwd `node_modules` (and pnpm layout), then a
502+ * guarded createRequire fallback for local/Jest.
502503 */
504+ export function resolveVercelSandboxPackageRoot (
505+ resolveId ?: ( id : string ) => string ,
506+ ) : string {
507+ if ( resolveId ) {
508+ try {
509+ return path . dirname ( resolveId ( '@vercel/sandbox/package.json' ) ) ;
510+ } catch {
511+ let dir = path . dirname ( resolveId ( '@vercel/sandbox' ) ) ;
512+ for ( let i = 0 ; i < 8 ; i ++ ) {
513+ const candidate = path . join ( dir , 'package.json' ) ;
514+ if ( readPackageName ( candidate ) === '@vercel/sandbox' ) return dir ;
515+ const parent = path . dirname ( dir ) ;
516+ if ( parent === dir ) break ;
517+ dir = parent ;
518+ }
519+ }
520+ }
521+
522+ const cwd = process . cwd ( ) ;
523+ const directCandidates = [
524+ path . join ( cwd , 'node_modules' , '@vercel' , 'sandbox' ) ,
525+ path . join ( cwd , '..' , 'node_modules' , '@vercel' , 'sandbox' ) ,
526+ // Next standalone / monorepo traces sometimes nest under .next
527+ path . join ( cwd , '.next' , 'standalone' , 'node_modules' , '@vercel' , 'sandbox' ) ,
528+ path . join ( cwd , '.next' , 'server' , 'node_modules' , '@vercel' , 'sandbox' ) ,
529+ ] ;
530+ for ( const dir of directCandidates ) {
531+ if ( readPackageName ( path . join ( dir , 'package.json' ) ) === '@vercel/sandbox' ) {
532+ return dir ;
533+ }
534+ }
535+
536+ // pnpm virtual store: node_modules/.pnpm/@vercel +sandbox@*/node_modules/@vercel /sandbox
537+ for ( const base of [ cwd , path . join ( cwd , '..' ) ] ) {
538+ const pnpmRoot = path . join ( base , 'node_modules' , '.pnpm' ) ;
539+ if ( ! fs . existsSync ( pnpmRoot ) ) continue ;
540+ let entries : string [ ] = [ ] ;
541+ try {
542+ entries = fs . readdirSync ( pnpmRoot ) ;
543+ } catch {
544+ continue ;
545+ }
546+ const matches = entries
547+ . filter ( ( name ) => name . startsWith ( '@vercel+sandbox@' ) )
548+ . sort ( )
549+ . reverse ( ) ;
550+ for ( const name of matches ) {
551+ const dir = path . join ( pnpmRoot , name , 'node_modules' , '@vercel' , 'sandbox' ) ;
552+ if ( readPackageName ( path . join ( dir , 'package.json' ) ) === '@vercel/sandbox' ) {
553+ return dir ;
554+ }
555+ }
556+ }
557+
558+ const fromRequire = tryCreateRequireResolve ( '@vercel/sandbox/package.json' ) ;
559+ if ( fromRequire ) return path . dirname ( fromRequire ) ;
560+
561+ const fromRuntime = tryCreateRequireResolve ( '@vercel/sandbox' ) ;
562+ if ( fromRuntime ) {
563+ let dir = path . dirname ( fromRuntime ) ;
564+ for ( let i = 0 ; i < 8 ; i ++ ) {
565+ if ( readPackageName ( path . join ( dir , 'package.json' ) ) === '@vercel/sandbox' ) {
566+ return dir ;
567+ }
568+ const parent = path . dirname ( dir ) ;
569+ if ( parent === dir ) break ;
570+ dir = parent ;
571+ }
572+ }
573+
574+ throw new Error (
575+ 'Could not resolve @vercel/sandbox package root for ESM load ' +
576+ `(cwd=${ cwd } ). Ensure @vercel/sandbox is installed for the server runtime.` ,
577+ ) ;
578+ }
579+
503580export function resolveVercelSandboxEsmEntryHref (
504- resolveId : ( id : string ) => string = defaultResolvePackageId ,
581+ resolveId ? : ( id : string ) => string ,
505582) : string {
506583 const root = resolveVercelSandboxPackageRoot ( resolveId ) ;
507584 const esmIndex = path . join ( root , 'dist' , 'index.js' ) ;
585+ // Only enforce on-disk presence for live discovery (not unit-test injectors).
586+ if ( ! resolveId && ! fs . existsSync ( esmIndex ) ) {
587+ throw new Error (
588+ `@vercel/sandbox ESM entry missing at ${ esmIndex } (package root ${ root } ).` ,
589+ ) ;
590+ }
508591 return pathToFileURL ( esmIndex ) . href ;
509592}
510593
511- export function resolveVercelSandboxPackageRoot (
512- resolveId : ( id : string ) => string = defaultResolvePackageId ,
513- ) : string {
594+ function readPackageName ( packageJsonPath : string ) : string | null {
514595 try {
515- return path . dirname ( resolveId ( '@vercel/sandbox/package.json' ) ) ;
596+ const pkg = JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf8' ) ) as { name ?: string } ;
597+ return typeof pkg . name === 'string' ? pkg . name : null ;
516598 } catch {
517- // Incomplete exports / hoisting: resolve a runtime file and walk up until
518- // package.json name is @vercel /sandbox.
519- let dir = path . dirname ( resolveId ( '@vercel/sandbox' ) ) ;
520- for ( let i = 0 ; i < 8 ; i ++ ) {
521- const candidate = path . join ( dir , 'package.json' ) ;
599+ return null ;
600+ }
601+ }
602+
603+ function tryCreateRequireResolve ( id : string ) : string | null {
604+ try {
605+ const createReq = createRequire as unknown as
606+ | ( ( filename : string ) => NodeRequire )
607+ | undefined ;
608+ if ( typeof createReq !== 'function' ) return null ;
609+
610+ const anchors = [
611+ path . join ( process . cwd ( ) , 'package.json' ) ,
612+ path . join ( process . cwd ( ) , 'node_modules' , '@vercel' , 'sandbox' , 'package.json' ) ,
613+ // Prefer a real file when present so createRequire is valid under Next.
614+ path . join ( process . cwd ( ) , 'node_modules' , '@vercel' , 'sandbox' , 'dist' , 'index.js' ) ,
615+ ] ;
616+ for ( const anchor of anchors ) {
522617 try {
523- const pkg = JSON . parse ( fs . readFileSync ( candidate , 'utf8' ) ) as { name ?: string } ;
524- if ( pkg . name === '@vercel/sandbox' ) return dir ;
618+ const req = createReq ( anchor ) ;
619+ if ( ! req || typeof req . resolve !== 'function' ) continue ;
620+ return req . resolve ( id ) ;
525621 } catch {
526- // keep walking
622+ // try next anchor
527623 }
528- const parent = path . dirname ( dir ) ;
529- if ( parent === dir ) break ;
530- dir = parent ;
531624 }
532- throw new Error ( 'Could not resolve @vercel/sandbox package root for ESM load.' ) ;
625+ } catch {
626+ // createRequire unavailable in this runtime
533627 }
628+ return null ;
534629}
535630
536- function defaultResolvePackageId ( id : string ) : string {
537- // createRequire needs a filepath anchor; cwd package.json works under Next,
538- // Jest, and monorepo package tests without import.meta.
539- const req = createRequire ( path . join ( process . cwd ( ) , 'package.json' ) ) ;
540- return req . resolve ( id ) ;
631+ function extractSandboxFactory ( loaded : {
632+ Sandbox ?: SandboxFactory ;
633+ default ?: unknown ;
634+ } ) : SandboxFactory | null {
635+ if ( loaded . Sandbox ?. create ) return loaded . Sandbox ;
636+ if ( loaded . default && typeof loaded . default === 'object' ) {
637+ const defaultExport = loaded . default as {
638+ Sandbox ?: SandboxFactory ;
639+ create ?: SandboxFactory [ 'create' ] ;
640+ } ;
641+ if ( defaultExport . Sandbox ?. create ) return defaultExport . Sandbox ;
642+ if ( typeof defaultExport . create === 'function' ) {
643+ return defaultExport as SandboxFactory ;
644+ }
645+ }
646+ return null ;
541647}
542648
649+ /**
650+ * Load Sandbox.create without hitting the dual-package CJS hazard.
651+ *
652+ * 1) webpackIgnore import of package name (Node uses the ESM "import" export)
653+ * 2) file:// import of dist/index.js after filesystem root discovery
654+ *
655+ * Plain `import('@vercel/sandbox')` without webpackIgnore can be rewritten to
656+ * CJS require → command.cjs → require(@workflow/serde) → ERR_REQUIRE_ESM.
657+ */
543658export async function loadVercelSandboxFactory ( ) : Promise < SandboxFactory > {
544- const esmHref = resolveVercelSandboxEsmEntryHref ( ) ;
545- // webpackIgnore: do not rewrite to a CJS interop chunk; load Node-native ESM.
546- const loaded = ( await import (
547- /* webpackIgnore: true */
548- esmHref
549- ) ) as {
550- Sandbox ?: SandboxFactory ;
551- default ?: unknown ;
552- } ;
659+ const errors : string [ ] = [ ] ;
553660
554- let Sandbox = loaded . Sandbox ;
555- if ( ! Sandbox ?. create && loaded . default && typeof loaded . default === 'object' ) {
556- const defaultExport = loaded . default as { Sandbox ?: SandboxFactory ; create ?: SandboxFactory [ 'create' ] } ;
557- if ( defaultExport . Sandbox ?. create ) {
558- Sandbox = defaultExport . Sandbox ;
559- } else if ( typeof defaultExport . create === 'function' ) {
560- Sandbox = defaultExport as SandboxFactory ;
561- }
661+ try {
662+ const loaded = ( await import (
663+ /* webpackIgnore: true */
664+ '@vercel/sandbox'
665+ ) ) as { Sandbox ?: SandboxFactory ; default ?: unknown } ;
666+ const factory = extractSandboxFactory ( loaded ) ;
667+ if ( factory ) return factory ;
668+ errors . push ( 'package import missing Sandbox.create' ) ;
669+ } catch ( error ) {
670+ const message = error instanceof Error ? error . message : String ( error ) ;
671+ errors . push ( `package import: ${ message . slice ( 0 , 240 ) } ` ) ;
562672 }
563- if ( ! Sandbox ?. create ) {
564- throw new Error (
565- `@vercel/sandbox did not expose Sandbox.create() (ESM entry ${ esmHref } ).` ,
566- ) ;
673+
674+ try {
675+ const esmHref = resolveVercelSandboxEsmEntryHref ( ) ;
676+ const loaded = ( await import (
677+ /* webpackIgnore: true */
678+ esmHref
679+ ) ) as { Sandbox ?: SandboxFactory ; default ?: unknown } ;
680+ const factory = extractSandboxFactory ( loaded ) ;
681+ if ( factory ) return factory ;
682+ errors . push ( `file import missing Sandbox.create (${ esmHref } )` ) ;
683+ } catch ( error ) {
684+ const message = error instanceof Error ? error . message : String ( error ) ;
685+ errors . push ( `file import: ${ message . slice ( 0 , 240 ) } ` ) ;
567686 }
568- return Sandbox ;
687+
688+ throw new Error (
689+ `@vercel/sandbox could not be loaded for host dispatch. ${ errors . join ( ' | ' ) } ` ,
690+ ) ;
569691}
570692
571693async function readCommandOutput (
0 commit comments