Files
Endoleap/tests/headless/package.integration.mjs
T

61 lines
4.2 KiB
JavaScript

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} from '@endoleap/headless-canvas/runtime';
import {createFoundryPersistence,type ServiceTransport} from '@endoleap/headless-canvas/service';
const registry=createRegistry(builtinComponents());
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});
}