test(row333 R2): release fixtures carry the plug-in files business imports (#1831)

Row 333 made packages/business/src/launch.mjs and organization.mjs import
packages/plugins/src/model-quota.mjs. The stack-release fixtures copy
packages/business/src and added only plugins/src/kinds.mjs, so their copies
failed with 'Cannot find module .../model-quota.mjs'. Whole cli runs on the
row 333 tip gave stack-release 56 pass 168 fail, stack-release-external
4/21 and stack-upgrade 7/14.

business-imports.mjs reads the imports of packages/business/src and returns
the files outside it that they reach, so both fixture builders copy what
business needs instead of a hard-coded name. No product code, schema or
assertion changes.
This commit is contained in:
jason.woltje committed 2026-11-02 16:27:43 -06:00
1 parent ddb3e961fb
commit 12eba4b599
3 files changed
+33

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
// Row 333 R2 (#1831): the files outside packages/business/src that business code imports, found by
// reading its imports. A copy fixture carries these beside packages/business/src, so a new import of
// a plug-in source file (row 333's model-quota.mjs) does not break the release tests the way it did
// when each fixture named kinds.mjs alone. A real copy ships all of packages/plugins (scripts/
// stack-copy.sh COPY_PATHS); a fixture models a smaller one and takes only what the loader reads.
import {existsSync,readdirSync,readFileSync} from 'node:fs';
import {dirname,join,relative,resolve,sep} from 'node:path';
const IMPORT=/(?:import|export)\s[^'"`;]*?\bfrom\s*['"](\.{1,2}\/[^'"]+)['"]|\bimport\s*['"](\.{1,2}\/[^'"]+)['"]|\bimport\(\s*['"](\.{1,2}\/[^'"]+)['"]\s*\)/g;
const sources=(dir)=>readdirSync(dir,{withFileTypes:true}).flatMap((e)=>e.isDirectory()?sources(join(dir,e.name)):e.name.endsWith('.mjs')?[join(dir,e.name)]:[]);
// Repo-relative paths, sorted: every file that a relative import from packages/business/src reaches
// outside it, following the imports of those files in turn. An import of a missing file throws.
export function businessImportedFiles(repo){
const own=join(repo,'packages','business','src')+sep,found=new Set(),todo=sources(own);
for(let file;(file=todo.pop())!==undefined;){
for(const m of readFileSync(file,'utf8').matchAll(IMPORT)){
const spec=m[1]??m[2]??m[3],target=resolve(dirname(file),spec);
if(target.startsWith(own)||found.has(target))continue;
if(!existsSync(target))throw new Error(`${relative(repo,file)} imports ${spec}, which does not exist`);
found.add(target);
if(target.endsWith('.mjs'))todo.push(target);
}
}
return [...found].map((f)=>relative(repo,f)).sort();
}
@@ -12,6 +12,7 @@ import {tmpdir} from 'node:os';
import {join,dirname,basename} from 'node:path';
import {fileURLToPath,pathToFileURL} from 'node:url';
import {once} from 'node:events';
import {businessImportedFiles} from './business-imports.mjs';
export const ROOT=fileURLToPath(new URL('../../..',import.meta.url)).replace(/\/$/,'');
export const NODE=process.execPath;
@@ -308,6 +309,9 @@ for(const [id,store] of [[F,'// a copy from before SCHEMA_VERSION\n'],[A,storeTe
mkdirSync(dirname(join(d,f)),{recursive:true});writeFileSync(join(d,f),text,{mode});
}
for(const p of ['packages/business/src','packages/library/src','roles'])cpSync(join(repo,p),join(d,p),{recursive:true});
// Row 333 R2 (#1831): what the business loader imports from outside its directory (kinds.mjs, which is
// already here in its OLD or NEW form, and model-quota.mjs).
for(const p of businessImportedFiles(repo))if(!existsSync(join(d,p)))cpSync(join(repo,p),join(d,p));
execFileSync('chmod',['-R','a-w',d]);
}
for(const [name,fn] of [['systemctl',systemctl],['journalctl',journalctl],['systemd-analyze',systemdAnalyze]])writeFileSync(join(SBIN,name),script(fn),{mode:0o755});
@@ -15,6 +15,7 @@ import {join,dirname,basename} from 'node:path';
import {fileURLToPath,pathToFileURL} from 'node:url';
import {once} from 'node:events';
import {ALGS} from '../../webui/src/sso-config.mjs';
import {businessImportedFiles} from './business-imports.mjs';
import {A,B,BUS,C,C1,CON,COPIES,EXAMPLE,F,KINDS_NEW,KINDS_OLD,M,MARKER,NODE,ROOT,S,SBIN,SR,backupSum,base,biz,calls,commit,commitLines,edit,fault,fp,git,healthz,holder,journalctl,kept,lines,logLines,migrateStub,mk,mosaicStub,named,pkg,planBackup,planNoBackup,planTail,pointer,posts,repo,rolledBack,run,script,seed,sha,snaps,startsStops,storeText,storeVersion,sysctl,systemctl,systemdAnalyze,tracked,tree,U,units} from './stack-release-fixture.mjs';
// --- The T table ---
@@ -1989,6 +1990,8 @@ const RCOPIES=join(base,'rcopies');mkdirSync(RCOPIES);
for(const [id,v,froms] of [[B,'3g',['3f']],[C,'3h',['3f','3g']],[A,'3g',['3f']],[C1,'3g',['3f']]]){
const d=join(RCOPIES,id);
for(const p of ['scripts/mosaic','scripts/mosaic-config.mjs','packages/cli/src/config.mjs','packages/cli/src/errors.mjs','packages/cli/src/access.mjs','packages/cli/src/transport.mjs','packages/bus/src/business.mjs','packages/plugins/src/kinds.mjs','packages/business/src','packages/library/src','roles'])cpSync(join(repo,p),join(d,p),{recursive:true});
// Row 333 R2 (#1831): what the business loader imports from outside its directory, model-quota.mjs included.
for(const p of businessImportedFiles(repo))if(!existsSync(join(d,p)))cpSync(join(repo,p),join(d,p));
if(id===C1)writeFileSync(join(d,'scripts','mosaic'),`#!/usr/bin/env bash\nexec node "${join(RCOPIES,B,'packages','cli','src','cli.mjs')}" "$@"\n`,{mode:0o755});
for(const [f,text] of [['packages/cli/src/cli.mjs',id===A?'// fixture host from before row 163\nprocess.exit(1);\n':script(rHost)],['packages/webui/src/cli.mjs',script(rConsole)],['packages/bus/src/store.mjs',rStore(v,froms)],['packages/bus/src/migrate-schema.mjs',script(rMigrate)],['packages/bus/src/broker.mjs','export class BusError extends Error { constructor(code) { super(code); this.code = code; } }\n']]){mkdirSync(dirname(join(d,f)),{recursive:true});writeFileSync(join(d,f),text)}
execFileSync('chmod',['-R','a-w',d]);