import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; import { mkdtemp, readFile, realpath, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const root=resolve(dirname(fileURLToPath(import.meta.url)),'../..'); const temporary=await realpath(await mkdtemp(join(tmpdir(),'endoleap-headless-package-'))); const run=(program,args,options={})=>execFileSync(program,args,{cwd:root,encoding:'utf8',stdio:['ignore','pipe','pipe'],...options}); try { run(process.execPath,['scripts/build-headless.mjs']); const packed=JSON.parse(run('npm',['pack','--offline','--ignore-scripts','--json','--pack-destination',temporary],{cwd:join(root,'.headless-dist'),env:{...process.env,npm_config_cache:join(temporary,'npm-cache')}})); assert.equal(packed.length,1); run('tar',['-xzf',join(temporary,packed[0].filename),'-C',temporary]); const packageRoot=join(temporary,'package'); const metadata=JSON.parse(await readFile(join(packageRoot,'package.json'),'utf8')); assert.equal(metadata.private,true);assert.equal(metadata.type,'module'); assert.equal(Object.keys(metadata.dependencies??{}).length,0); assert.equal(Object.keys(metadata.peerDependencies??{}).length,0); // Resolve every import through the packed artifact, never checkout node_modules. await writeFile(join(temporary,'guard-loader.mjs'),` import {fileURLToPath} from 'node:url'; import {sep} from 'node:path'; const root=${JSON.stringify(packageRoot)}; export async function resolve(specifier,context,next){ if(specifier.startsWith('node:'))return next(specifier,context); const result=await next(specifier,context); if(!result.url.startsWith('file:')||!fileURLToPath(result.url).startsWith(root+sep))throw Error('External dependency: '+specifier); return result; } `); await writeFile(join(temporary,'guard.mjs'),` import {register} from 'node:module'; register('./guard-loader.mjs',import.meta.url); for(const name of ['window','document','navigator','indexedDB','Worker'])Object.defineProperty(globalThis,name,{configurable:true,get(){throw Error('Browser global: '+name);}}); `); const report=JSON.parse(run(process.execPath,['--import',join(temporary,'guard.mjs'),join(packageRoot,'consumer.mjs')],{cwd:temporary})); assert.equal(report.status,'ok');assert.equal(report.reopened,true);assert.equal(report.elements,2); // No DOM library: browser adapter declarations must not leak into portable ports. await writeFile(join(packageRoot,'consumer-types.mts'),` import {createRegistry,builtinComponents,createDocument} from '@endoleap/headless-canvas/core'; import {openProjectEngine,type StoragePort,type StorageScope,type LoadingState} from '@endoleap/headless-canvas/runtime'; import {createFoundryPersistence,type ServiceTransport} from '@endoleap/headless-canvas/service'; const registry=createRegistry(builtinComponents()); export async function completePartial(engine:Awaited>,document:Parameters[0]['document'],versions:ReadonlyMap,state:LoadingState){ const accepted=engine.loading.accept(document,versions,state); return accepted.status==='applied'?engine.loading.complete(document,versions,{sourceToken:state.sourceToken}):accepted; } export const certified:LoadingState={version:2,sourceToken:'consumer-read',declaredCanvasIds:['root'],receivedElements:0,totalElements:1,canvasElementCounts:{root:1}}; export const scoped:LoadingState={...certified,version:3}; export function inspectPartial(engine:Awaited>){ const queries=engine.editor.getQueries(),lookup=queries.lookupElement('node'); const id:string|undefined=lookup.status==='present'?lookup.element.id:undefined; const complete:boolean=queries.membersResult(engine.getSnapshot().document.projectId).complete; return {id,complete,projectComplete:queries.coverage.projectComplete}; } export function open(scope:StorageScope,storage:StoragePort,transport:ServiceTransport){ return openProjectEngine({scope,storage,registry,document:createDocument(scope.projectId,[{id:scope.projectId,parent:null}],[]),versions:new Map([[scope.projectId,1]]),persistence:createFoundryPersistence({registry,transport,rootId:scope.projectId,workspaceId:scope.workspaceId})}); } `); run(process.execPath,[join(root,'node_modules/typescript/bin/tsc'),'--noEmit','--strict','--target','ES2022','--module','NodeNext','--moduleResolution','NodeNext','--lib','ES2022','--types','node','--typeRoots',join(root,'node_modules/@types'),join(packageRoot,'consumer-types.mts')],{cwd:temporary}); const browser=run(process.execPath,['tests/headless/browser-storage.mjs','--package']); process.stdout.write(browser); console.log(JSON.stringify({compiledBrowserAdapters:true,status:'ok',packedFiles:packed[0].files.length,consumer:report,nodeTypes:true,browserGlobalsBlocked:true,externalRuntimeDependenciesBlocked:true})); } catch(error){ if(error.stdout)process.stderr.write(error.stdout); if(error.stderr)process.stderr.write(error.stderr); throw error; } finally { await rm(temporary,{recursive:true,force:true}); }