import type { PlayArtifactKind, PlayBundleArtifact } from './artifact-types'; /** * Loud guard that stops a historical or malformed non-CJS bundle from * reaching a Node/Daytona (`cjs_node20`) runner. * * The Node runner loads play code with `Module._compile`, which cannot resolve * `cloudflare:workers` scheme imports and dies with the opaque * `ERR_UNSUPPORTED_ESM_URL_SCHEME: Received protocol 'cloudflare:'`. Those * imports can remain in historical `esm_workers` artifacts. If one reaches an * active launch seam, this guard names the mismatch instead of surfacing the * opaque ESM scheme error. */ /** * Legacy artifacts predate the `artifactKind` field; they are always * `cjs_node20` (the only kind that existed before the workers split). Mirror the * default documented on `PlayBundleArtifact.artifactKind`. */ export function artifactKindOf(artifact: PlayBundleArtifact): string { const rawKind = (artifact as { artifactKind?: unknown }).artifactKind; return typeof rawKind === 'string' && rawKind.trim() ? rawKind : 'cjs_node20'; } /** * Detect `cloudflare:` scheme imports in bundled code. Matches static imports, * dynamic imports, and require calls with single or double quotes. */ const CLOUDFLARE_SCHEME_IMPORT = /(?:\bfrom\s*|\bimport\s*(?:\(\s*)?|\brequire\s*\(\s*)['"]cloudflare:[^'"]*['"]/; export function bundledCodeHasCloudflareSchemeImport( bundledCode: string, ): boolean { return CLOUDFLARE_SCHEME_IMPORT.test(bundledCode); } export class PlayArtifactKindMismatchError extends Error { readonly expectedKind: PlayArtifactKind; readonly actualKind: string; readonly reason: | 'kind_mismatch' | 'code_format_mismatch' | 'cloudflare_scheme_import'; constructor(input: { expectedKind: PlayArtifactKind; actualKind: string; reason: | 'kind_mismatch' | 'code_format_mismatch' | 'cloudflare_scheme_import'; message: string; }) { super(input.message); this.name = 'PlayArtifactKindMismatchError'; this.expectedKind = input.expectedKind; this.actualKind = input.actualKind; this.reason = input.reason; } } /** * Assert an artifact is loadable by the Node (`cjs_node20`) runner. Throws a * `PlayArtifactKindMismatchError` naming the run/profile and expected vs actual * kind if the artifact is an `esm_workers` bundle or carries `cloudflare:` * scheme imports. * * `context` is a short human-readable locator (run id, play name, submit path) * woven into the error so the failure points at the wrong targeting decision, * not the downstream ESM scheme error. */ export function assertNodeRunnableArtifact(input: { artifact: PlayBundleArtifact; expectedKind: PlayArtifactKind; context: string; }): void { const { artifact, expectedKind, context } = input; const actualKind = artifactKindOf(artifact); if (actualKind !== 'cjs_node20') { throw new PlayArtifactKindMismatchError({ expectedKind, actualKind, reason: 'kind_mismatch', message: `Node runner (${context}) received unsupported play artifact kind ` + `"${actualKind}"; only "cjs_node20" is executable. Rebuild and ` + `republish the play with the current SDK.`, }); } if (artifact.codeFormat !== 'cjs_module') { throw new PlayArtifactKindMismatchError({ expectedKind, actualKind, reason: 'code_format_mismatch', message: `Node runner (${context}) received a "${actualKind}" play artifact ` + `with codeFormat "${artifact.codeFormat}" but requires "cjs_module". ` + `The artifact metadata is inconsistent; rebuild it for the node runner ` + `before launch.`, }); } if (bundledCodeHasCloudflareSchemeImport(artifact.bundledCode)) { throw new PlayArtifactKindMismatchError({ expectedKind, actualKind, reason: 'cloudflare_scheme_import', message: `Node runner (${context}) received a play artifact tagged ` + `"cjs_node20" whose bundledCode still contains a cloudflare: scheme ` + `import. This bundle would die with ERR_UNSUPPORTED_ESM_URL_SCHEME ` + `under Module._compile. The artifact was mis-tagged or mis-targeted; ` + `rebundle for the node runner.`, }); } }