@peripheries/engine 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peripheries/engine",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Build, check, knowledge and deploy for peripheries component repos",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{existsSync,readdirSync,readFileSync,statSync}from"node:fs";import{join,relative,resolve}from"node:path";import{fileURLToPath}from"node:url";import{budgetVerdict,loadConfig}from"../lib/config.mjs";import{REPO_ROOT}from"../lib/root.mjs";const SKIP=/(^|\/)(node_modules|__output__|dist[^/]*)(\/|$)|\.(fixtures|test|stories)\.[jt]sx?$|\.d\.ts$/;function onlyCustomProperties(ts,expr){while(ts.isParenthesizedExpression(expr)||ts.isAsExpression(expr)||ts.isSatisfiesExpression?.(expr))expr=expr.expression;if(ts.isCallExpression(expr)&&ts.isIdentifier(expr.expression)&&expr.expression.text==="vars")return true;if(ts.isIdentifier(expr)&&expr.text==="style"||ts.isPropertyAccessExpression(expr)&&expr.name.text==="style")return true;if(!ts.isObjectLiteralExpression(expr)||!expr.properties.length)return false;return expr.properties.every(p=>{if(ts.isSpreadAssignment(p))return onlyCustomProperties(ts,p.expression);const name=p.name&&(ts.isStringLiteral(p.name)||ts.isNoSubstitutionTemplateLiteral(p.name)?p.name.text:null);return typeof name==="string"&&name.startsWith("--")})}function inlineStyles(ts,code,fileName="x.tsx"){const sf=ts.createSourceFile(fileName,code,ts.ScriptTarget.Latest,true,ts.ScriptKind.TSX);const found=[];const visit=node=>{if(ts.isJsxAttribute(node)&&node.name.getText(sf)==="style"&&node.initializer&&ts.isJsxExpression(node.initializer)&&node.initializer.expression){if(!onlyCustomProperties(ts,node.initializer.expression)){const{line}=sf.getLineAndCharacterOfPosition(node.getStart(sf));found.push(`${line+1}: ${node.getText(sf).replace(/\s+/g," ").slice(0,80)}`)}}ts.forEachChild(node,visit)};visit(sf);return found}async function main(){const strict=process.argv.includes("--strict");const ts=(await import("typescript")).default;const walk=p=>{if(!existsSync(p)||SKIP.test(p))return[];return statSync(p).isDirectory()?readdirSync(p).flatMap(n=>walk(join(p,n))):[p]};const files=walk(resolve(REPO_ROOT,"src")).filter(f=>f.endsWith(".tsx"));const findings=files.flatMap(f=>inlineStyles(ts,readFileSync(f,"utf8"),f).map(x=>`${relative(REPO_ROOT,f)}:${x}`));const shown=strict||process.argv.includes("--all")?findings:findings.slice(0,20);for(const f of shown)console[strict?"error":"warn"](`[inline-styles] ${f}`);if(shown.length<findings.length)console.warn(`[inline-styles] \u2026 ${findings.length-shown.length} more (--all lists them)`);console.log(`[inline-styles] ${files.length} file(s), ${findings.length} inline style(s) beyond custom properties.`);const{checks}=await loadConfig();const budget=budgetVerdict("check-inline-styles",findings.length,checks.budgets["check-inline-styles"]);if(budget.message)console[budget.ok?"log":"error"](`[inline-styles] ${budget.message}`);return strict&&findings.length||!budget.ok?1:0}if(process.argv[1]&&resolve(process.argv[1])===fileURLToPath(import.meta.url)){process.exit(await main())}export{inlineStyles};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{existsSync}from"node:fs";import{resolve}from"node:path";import{fileURLToPath}from"node:url";import{loadConfig}from"../lib/config.mjs";import{REPO_ROOT}from"../lib/root.mjs";function shadowFindings(lineageExports,sharedExports,shadows={}){const findings=[];const colliding=new Set;for(const[lineage,names]of lineageExports){for(const name of names){const owner=[...sharedExports].find(([,shared])=>shared.has(name))?.[0];if(!owner)continue;colliding.add(name);if(!(name in shadows))findings.push(`lineage "${lineage}" exports ${name}, which ${owner} also exports: declare it in shadows with a reason, or rename it`);else if(typeof shadows[name]!=="string"||!shadows[name].trim())findings.push(`shadows.${name} needs a reason`)}}for(const name of Object.keys(shadows)){if(!colliding.has(name))findings.push(`shadows.${name} is declared, but no lineage exports ${name} over a shared lineage: remove it`)}return findings}async function main(){const{lineages,shared,shadows}=await loadConfig();const own=lineages.filter(l=>l.kind!=="shared");const specifiers=[...new Set([...shared,...own.flatMap(l=>l.shared??[])])];if(!own.length||!specifiers.length){console.log("[shadowing] no org lineage imports a shared lineage; nothing to check.");return 0}const ts=(await import("typescript")).default;const configPath=resolve(REPO_ROOT,"tsconfig.json");const options=existsSync(configPath)?ts.parseJsonConfigFileContent(ts.readConfigFile(configPath,ts.sys.readFile).config,ts.sys,REPO_ROOT).options:{module:ts.ModuleKind.ESNext,moduleResolution:ts.ModuleResolutionKind.Bundler,jsx:ts.JsxEmit.ReactJSX};const from=resolve(REPO_ROOT,own[0].entry);const sharedFiles=new Map;for(const spec of specifiers){const resolved=ts.resolveModuleName(spec,from,options,ts.sys).resolvedModule?.resolvedFileName;if(!resolved){console.error(`[shadowing] can't resolve ${spec}: install it (npm:@peripheries/\u2026) or map it in tsconfig paths`);return 1}sharedFiles.set(spec,resolved)}const entries=own.map(l=>resolve(REPO_ROOT,l.entry)).filter(f=>existsSync(f));const program=ts.createProgram({rootNames:[...entries,...sharedFiles.values()],options:{...options,noEmit:true}});const checker=program.getTypeChecker();const valuesOf=file=>{const sf=program.getSourceFile(file);const symbol=sf&&checker.getSymbolAtLocation(sf);if(!symbol)return new Set;return new Set(checker.getExportsOfModule(symbol).filter(s=>{const target=s.flags&ts.SymbolFlags.Alias?checker.getAliasedSymbol(s):s;return(target.flags&ts.SymbolFlags.Value)!==0}).map(s=>s.name).filter(n=>/^[A-Z]/.test(n)))};const sharedExports=new Map([...sharedFiles].map(([spec,file])=>[spec,valuesOf(file)]));const lineageExports=new Map(own.map(l=>[l.name,valuesOf(resolve(REPO_ROOT,l.entry))]));const findings=shadowFindings(lineageExports,sharedExports,shadows);for(const f of findings)console.error(`[shadowing] ${f}`);const declared=Object.keys(shadows).length;console.log(`[shadowing] ${own.length} lineage(s) against ${specifiers.join(", ")} (${[...sharedExports.values()].reduce((n,s)=>n+s.size,0)} shared names); ${declared} declared shadow(s), ${findings.length} finding(s).`);return findings.length?1:0}if(process.argv[1]&&resolve(process.argv[1])===fileURLToPath(import.meta.url)){process.exit(await main())}export{shadowFindings};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{existsSync,readdirSync,readFileSync,statSync}from"node:fs";import{createRequire}from"node:module";import{join,relative,resolve}from"node:path";import{fileURLToPath,pathToFileURL}from"node:url";import{REPO_ROOT}from"../lib/root.mjs";const ROOTS=["src/org_templates","src/project_templates"];function themesIn(value,path=""){if(!value||typeof value!=="object")return[];if(Array.isArray(value))return value.flatMap((v,i)=>themesIn(v,`${path}[${i}]`));return Object.entries(value).flatMap(([k,v])=>{const here=path?`${path}.${k}`:k;if(k==="theme"&&v&&typeof v==="object"&&!Array.isArray(v))return[{path:here,theme:v}];return themesIn(v,here)})}function failing(raw,diagnostics,strict){if(strict)return diagnostics;if(raw.version!==2)return[];return diagnostics.filter(d=>d.severity==="error"||d.code==="THEME_CONTRAST")}async function main(){const strict=process.argv.includes("--strict");let api;try{const entry=createRequire(resolve(REPO_ROOT,"package.json")).resolve("@peripheries/renderer/theme");api=await import(pathToFileURL(entry).href)}catch{console.log("[theme] @peripheries/renderer/theme not found (renderer 0.7 or later has it); nothing checked.");return 0}const walk=p=>statSync(p).isDirectory()?readdirSync(p).flatMap(n=>walk(join(p,n))):[p];const files=ROOTS.map(r=>resolve(REPO_ROOT,r)).filter(existsSync).flatMap(walk).filter(f=>f.endsWith("template.json"));let themes=0;let failed=0;for(const file of files){for(const{path,theme:raw}of themesIn(JSON.parse(readFileSync(file,"utf8")))){themes++;const where=`${relative(REPO_ROOT,file)} ${path}`;const{theme,diagnostics}=api.parseTheme(raw);const all=[...diagnostics,...api.checkTheme(theme)];const fails=new Set(failing(raw,all,strict));if(raw.version!==2)console.warn(`[theme] ${where}: version 1; move it to version 2 (design \xA76.1)`);for(const d of all)console[fails.has(d)?"error":"warn"](`[theme] ${where}: ${d.code} ${d.path}: ${d.message}`);failed+=fails.size}}console.log(`[theme] ${files.length} template(s), ${themes} theme(s), ${failed} failure(s).`);return failed?1:0}if(process.argv[1]&&resolve(process.argv[1])===fileURLToPath(import.meta.url)){process.exit(await main())}export{failing,themesIn};
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{existsSync,readdirSync,readFileSync,statSync}from"node:fs";import{join,relative,resolve}from"node:path";import{REPO_ROOT}from"../lib/root.mjs";const strict=process.argv.includes("--strict");const SKIP=/(^|\/)(node_modules|__output__|dist[^/]*)(\/|$)|\.(fixtures|test|stories)\.[jt]sx?$|\.d\.ts$/;const RULES=[[/var\(\s*--palette-[\w-]+/,"a primitive token (read a semantic one)"],[/var\(\s*--oa-[\w-]+/,"a legacy --oa-* name"],[/(?<![\w-])#[0-9a-fA-F]{3,8}\b(?![\w-])/,"a hex colour (use a token)"],[/\b(rgba?|hsla?|oklch|oklab)\(\s*\d/,"a literal colour (use a token)"]];const walk=p=>{if(!existsSync(p)||SKIP.test(p))return[];return statSync(p).isDirectory()?readdirSync(p).flatMap(n=>walk(join(p,n))):[p]};const files=walk(resolve(REPO_ROOT,"src")).filter(f=>/\.(css|tsx?|mts)$/.test(f));const findings=[];for(const file of files){const lines=readFileSync(file,"utf8").split("\n");if(lines.slice(0,15).some(l=>l.includes("check-tokens: defines tokens")))continue;const css=file.endsWith(".css");lines.forEach((line,i)=>{const trimmed=line.trim();if(trimmed.startsWith("//")||trimmed.startsWith("*")||trimmed.startsWith("/*"))return;for(const[re,what]of RULES){if(!css&&re.source.startsWith("(?<!")&&!/['"`][^'"`]*#[0-9a-fA-F]{3,8}\b/.test(line))continue;const m=line.match(re);if(m)findings.push(`${relative(REPO_ROOT,file)}:${i+1}: ${what}: ${m[0]}`)}})}const shown=strict||process.argv.includes("--all")?findings:findings.slice(0,20);for(const f of shown)console[strict?"error":"warn"](`[tokens] ${f}`);if(shown.length<findings.length)console.warn(`[tokens] \u2026 ${findings.length-shown.length} more (--all lists them)`);console.log(`[tokens] ${files.length} file(s), ${findings.length} finding(s)${findings.length&&!strict?" (warnings: --strict fails on them)":""}.`);process.exit(strict&&findings.length?1:0);
2
+ import{existsSync,readdirSync,readFileSync,statSync}from"node:fs";import{join,relative,resolve}from"node:path";import{budgetVerdict,loadConfig}from"../lib/config.mjs";import{REPO_ROOT}from"../lib/root.mjs";const strict=process.argv.includes("--strict");const SKIP=/(^|\/)(node_modules|__output__|dist[^/]*)(\/|$)|\.(fixtures|test|stories)\.[jt]sx?$|\.d\.ts$/;const RULES=[[/var\(\s*--palette-[\w-]+/,"a primitive token (read a semantic one)"],[/var\(\s*--oa-[\w-]+/,"a legacy --oa-* name"],[/(?<![\w-])#[0-9a-fA-F]{3,8}\b(?![\w-])/,"a hex colour (use a token)"],[/\b(rgba?|hsla?|oklch|oklab)\(\s*\d/,"a literal colour (use a token)"]];const walk=p=>{if(!existsSync(p)||SKIP.test(p))return[];return statSync(p).isDirectory()?readdirSync(p).flatMap(n=>walk(join(p,n))):[p]};const files=walk(resolve(REPO_ROOT,"src")).filter(f=>/\.(css|tsx?|mts)$/.test(f));const findings=[];for(const file of files){const lines=readFileSync(file,"utf8").split("\n");if(lines.slice(0,15).some(l=>l.includes("check-tokens: defines tokens")))continue;const css=file.endsWith(".css");lines.forEach((line,i)=>{const trimmed=line.trim();if(trimmed.startsWith("//")||trimmed.startsWith("*")||trimmed.startsWith("/*"))return;for(const[re,what]of RULES){if(!css&&re.source.startsWith("(?<!")&&!/['"`][^'"`]*#[0-9a-fA-F]{3,8}\b/.test(line))continue;const m=line.match(re);if(m)findings.push(`${relative(REPO_ROOT,file)}:${i+1}: ${what}: ${m[0]}`)}})}const shown=strict||process.argv.includes("--all")?findings:findings.slice(0,20);for(const f of shown)console[strict?"error":"warn"](`[tokens] ${f}`);if(shown.length<findings.length)console.warn(`[tokens] \u2026 ${findings.length-shown.length} more (--all lists them)`);console.log(`[tokens] ${files.length} file(s), ${findings.length} finding(s)${findings.length&&!strict?" (warnings: --strict fails on them)":""}.`);const{checks}=await loadConfig();const budget=budgetVerdict("check-tokens",findings.length,checks.budgets["check-tokens"]);if(budget.message)console[budget.ok?"log":"error"](`[tokens] ${budget.message}`);process.exit(strict&&findings.length||!budget.ok?1:0);
package/src/index.d.mts CHANGED
@@ -19,6 +19,7 @@ export type EngineConfig = {
19
19
  org: string;
20
20
  lineages?: LineageSpec[];
21
21
  shared?: string[];
22
+ shadows?: Record<string, string>;
22
23
  harness?: {
23
24
  wrapper?: string;
24
25
  widths?: number[];
@@ -31,6 +32,7 @@ export type EngineConfig = {
31
32
  run?: string[];
32
33
  peerRepos?: string[];
33
34
  gated?: { projectTemplates?: string[]; orgTemplates?: string[] };
35
+ budgets?: Partial<Record<'check-tokens' | 'check-inline-styles', number>>;
34
36
  };
35
37
  };
36
38
 
@@ -56,7 +58,8 @@ export type LoadedConfig = {
56
58
  org: string;
57
59
  lineages: Lineage[];
58
60
  shared: string[];
59
- checks: { run: string[]; peerRepos: string[]; gated: { projectTemplates: string[]; orgTemplates: string[] } };
61
+ checks: { run: string[]; peerRepos: string[]; gated: { projectTemplates: string[]; orgTemplates: string[] }; budgets: Record<string, number> };
62
+ shadows: Record<string, string>;
60
63
  harness: { wrapper: string | null; widths: number[]; container: number; overflow: 'warn' | 'error'; output: string; alias: Record<string, string> };
61
64
  };
62
65
  export function loadConfig(): Promise<LoadedConfig>;
@@ -1 +1 @@
1
- import{existsSync,readFileSync}from"node:fs";import{resolve}from"node:path";import{pathToFileURL}from"node:url";import{REPO_ROOT}from"./root.mjs";const MAIN_LINEAGE="main";const DEFAULT_CHECKS=["validate-externals","check-wired","check-routes","check-design-refs","check-skills","check-work-orders","check-cutover-ownership","check-fixtures","check-tokens","check-css-scope","knowledge"];const PUBLIC_LINEAGE="public";const CONFIG_FILES=["engine.config.ts","engine.config.mts","engine.config.mjs","engine.config.js"];function defineConfig(config){return config}function lineageDefaults(name,kind){if(name===MAIN_LINEAGE){return{name,kind:"main",entry:"src/index.ts",sourceRoot:null,dist:"dist"}}if(name===PUBLIC_LINEAGE){return{name,kind:"public",entry:"src/index.public.ts",sourceRoot:"components/public/",dist:"dist-public"}}const k=kind??"page";return{name,kind:k,entry:`src/index.${name}.ts`,sourceRoot:k==="form"?`components/form/${name}/`:k==="shared"?`${name}/`:`components/project_page/${name}/`,dist:`dist-bundles/${name}`}}function normalize(raw,origin){if(!raw||typeof raw!=="object")throw new Error(`[engine] ${origin} must export a config object`);if(typeof raw.org!=="string"||!raw.org)throw new Error(`[engine] ${origin}: \`org\` (the org slug) is required`);const list=raw.lineages??[MAIN_LINEAGE];const lineages=list.map(l=>{const spec=typeof l==="string"?{name:l}:l;if(!spec?.name)throw new Error(`[engine] ${origin}: every lineage needs a name`);return{...lineageDefaults(spec.name,spec.kind),...spec}});const mainAt=lineages.findIndex(l=>l.name===MAIN_LINEAGE);if(mainAt>0)throw new Error(`[engine] ${origin}: main must be the first lineage`);if(mainAt===-1&&!lineages.every(l=>l.kind==="shared")){throw new Error(`[engine] ${origin}: lineages must start with "main", unless every lineage is shared`)}for(const l of lineages){if(l.kind==="shared"&&!/^@[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/.test(l.specifier??"")){throw new Error(`[engine] ${origin}: shared lineage "${l.name}" needs a specifier like "@stack/${l.name}"`)}}const gated=raw.checks?.gated??{};const checks={run:raw.checks?.run??DEFAULT_CHECKS,peerRepos:raw.checks?.peerRepos??[],gated:{projectTemplates:gated.projectTemplates??[],orgTemplates:gated.orgTemplates??[]}};const shared=raw.shared??[];const harness={wrapper:raw.harness?.wrapper??null,widths:raw.harness?.widths??[390,768,1280],container:raw.harness?.container??320,overflow:raw.harness?.overflow??"warn",output:raw.harness?.output??"test/visual/__output__",alias:raw.harness?.alias??{}};return{org:raw.org,lineages,checks,shared,harness}}const normalizeForTest=raw=>normalize(raw,"test");let cached;async function loadConfig(){if(cached)return cached;const file=CONFIG_FILES.map(f=>resolve(REPO_ROOT,f)).find(f=>existsSync(f));if(file){const mod=await import(pathToFileURL(file).href);cached=normalize(mod.default??mod,file);return cached}const pkg=JSON.parse(readFileSync(resolve(REPO_ROOT,"package.json"),"utf8"));let org=pkg.oaBundle?.name;const orgJson=resolve(REPO_ROOT,"template/config/org.json");if(existsSync(orgJson))org=JSON.parse(readFileSync(orgJson,"utf8")).slug??org;const lineages=(pkg.oaBundle?.lineages??[MAIN_LINEAGE]).map(n=>n==="workbooks"?{name:n,kind:"form"}:n);cached=normalize({org,lineages},"package.json oaBundle");return cached}async function lineageConfig(name){const{lineages}=await loadConfig();return lineages.find(l=>l.name===name)??lineageDefaults(name)}export{DEFAULT_CHECKS,MAIN_LINEAGE,PUBLIC_LINEAGE,defineConfig,lineageConfig,lineageDefaults,loadConfig,normalizeForTest};
1
+ import{existsSync,readFileSync}from"node:fs";import{resolve}from"node:path";import{pathToFileURL}from"node:url";import{REPO_ROOT}from"./root.mjs";const MAIN_LINEAGE="main";const DEFAULT_CHECKS=["validate-externals","check-wired","check-routes","check-design-refs","check-skills","check-work-orders","check-cutover-ownership","check-fixtures","check-tokens","check-css-scope","check-inline-styles","check-shadowing","check-theme","knowledge"];function budgetVerdict(name,count,budget){if(budget===void 0||budget===null)return{ok:true,message:null};if(count>budget)return{ok:false,message:`${count} finding(s), over the budget of ${budget}: the count may only fall (checks.budgets['${name}'])`};if(count<budget)return{ok:true,message:`${count} finding(s), under the budget of ${budget}: lower checks.budgets['${name}'] to ${count}`};return{ok:true,message:`${count} finding(s), at the budget`}}const PUBLIC_LINEAGE="public";const CONFIG_FILES=["engine.config.ts","engine.config.mts","engine.config.mjs","engine.config.js"];function defineConfig(config){return config}function lineageDefaults(name,kind){if(name===MAIN_LINEAGE){return{name,kind:"main",entry:"src/index.ts",sourceRoot:null,dist:"dist"}}if(name===PUBLIC_LINEAGE){return{name,kind:"public",entry:"src/index.public.ts",sourceRoot:"components/public/",dist:"dist-public"}}const k=kind??"page";return{name,kind:k,entry:`src/index.${name}.ts`,sourceRoot:k==="form"?`components/form/${name}/`:k==="shared"?`${name}/`:`components/project_page/${name}/`,dist:`dist-bundles/${name}`}}function normalize(raw,origin){if(!raw||typeof raw!=="object")throw new Error(`[engine] ${origin} must export a config object`);if(typeof raw.org!=="string"||!raw.org)throw new Error(`[engine] ${origin}: \`org\` (the org slug) is required`);const list=raw.lineages??[MAIN_LINEAGE];const lineages=list.map(l=>{const spec=typeof l==="string"?{name:l}:l;if(!spec?.name)throw new Error(`[engine] ${origin}: every lineage needs a name`);return{...lineageDefaults(spec.name,spec.kind),...spec}});const mainAt=lineages.findIndex(l=>l.name===MAIN_LINEAGE);if(mainAt>0)throw new Error(`[engine] ${origin}: main must be the first lineage`);if(mainAt===-1&&!lineages.every(l=>l.kind==="shared")){throw new Error(`[engine] ${origin}: lineages must start with "main", unless every lineage is shared`)}for(const l of lineages){if(l.kind==="shared"&&!/^@[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/.test(l.specifier??"")){throw new Error(`[engine] ${origin}: shared lineage "${l.name}" needs a specifier like "@stack/${l.name}"`)}}const gated=raw.checks?.gated??{};const checks={run:raw.checks?.run??DEFAULT_CHECKS,peerRepos:raw.checks?.peerRepos??[],gated:{projectTemplates:gated.projectTemplates??[],orgTemplates:gated.orgTemplates??[]},budgets:raw.checks?.budgets??{}};const shadows=raw.shadows??{};const shared=raw.shared??[];const harness={wrapper:raw.harness?.wrapper??null,widths:raw.harness?.widths??[390,768,1280],container:raw.harness?.container??320,overflow:raw.harness?.overflow??"warn",output:raw.harness?.output??"test/visual/__output__",alias:raw.harness?.alias??{}};return{org:raw.org,lineages,checks,shared,shadows,harness}}const normalizeForTest=raw=>normalize(raw,"test");let cached;async function loadConfig(){if(cached)return cached;const file=CONFIG_FILES.map(f=>resolve(REPO_ROOT,f)).find(f=>existsSync(f));if(file){const mod=await import(pathToFileURL(file).href);cached=normalize(mod.default??mod,file);return cached}const pkg=JSON.parse(readFileSync(resolve(REPO_ROOT,"package.json"),"utf8"));let org=pkg.oaBundle?.name;const orgJson=resolve(REPO_ROOT,"template/config/org.json");if(existsSync(orgJson))org=JSON.parse(readFileSync(orgJson,"utf8")).slug??org;const lineages=(pkg.oaBundle?.lineages??[MAIN_LINEAGE]).map(n=>n==="workbooks"?{name:n,kind:"form"}:n);cached=normalize({org,lineages},"package.json oaBundle");return cached}async function lineageConfig(name){const{lineages}=await loadConfig();return lineages.find(l=>l.name===name)??lineageDefaults(name)}export{DEFAULT_CHECKS,MAIN_LINEAGE,PUBLIC_LINEAGE,budgetVerdict,defineConfig,lineageConfig,lineageDefaults,loadConfig,normalizeForTest};
package/src/lib/run.mjs CHANGED
@@ -1 +1 @@
1
- import{spawnSync}from"node:child_process";import{existsSync}from"node:fs";import{dirname,resolve}from"node:path";import{fileURLToPath}from"node:url";import{REPO_ROOT}from"./root.mjs";const COMMANDS_DIR=resolve(dirname(fileURLToPath(import.meta.url)),"..","commands");const TSX=import.meta.resolve("tsx/esm");const COMMANDS={build:"build.mjs",deploy:"deploy.mjs",check:"check.mjs",harness:"harness.mjs","test-visual":"test-visual.mjs",template:"template.mjs",upload:"upload-bundle.mjs",register:"register-components.mjs",cutover:"cutover.mjs",knowledge:"emit-knowledge.mjs",contracts:"emit-contracts.mjs","bundle-size":"bundle-size.mjs","check-bundle-leak":"check-bundle-leak.mjs","validate-externals":"validate-externals.mjs","check-wired":"check-wired-bindings.mjs","check-routes":"check-template-routes.mjs","check-design-refs":"check-design-refs.mjs","check-skills":"check-skills.mjs","check-work-orders":"check-work-orders.mjs","check-cutover-ownership":"check-cutover-ownership.mjs","check-fixtures":"check-fixtures.mjs","check-props-schema":"check-props-schema.mjs","check-tokens":"check-tokens.mjs","check-css-scope":"check-css-scope.mjs"};function commandFile(name){if(!COMMANDS[name])return null;const file=resolve(COMMANDS_DIR,COMMANDS[name]);const ts=file.replace(/\.mjs$/,".mts");return existsSync(file)?file:existsSync(ts)?ts:null}function runCommand(name,args=[],env={}){const file=commandFile(name);if(!file)throw new Error(`[engine] unknown command "${name}"`);const r=spawnSync(process.execPath,["--import",TSX,file,...args],{cwd:REPO_ROOT,stdio:"inherit",env:{...process.env,ENGINE_ROOT:REPO_ROOT,...env}});return r.status??1}export{COMMANDS,commandFile,runCommand};
1
+ import{spawnSync}from"node:child_process";import{existsSync}from"node:fs";import{dirname,resolve}from"node:path";import{fileURLToPath}from"node:url";import{REPO_ROOT}from"./root.mjs";const COMMANDS_DIR=resolve(dirname(fileURLToPath(import.meta.url)),"..","commands");const TSX=import.meta.resolve("tsx/esm");const COMMANDS={build:"build.mjs",deploy:"deploy.mjs",check:"check.mjs",harness:"harness.mjs","test-visual":"test-visual.mjs",template:"template.mjs",upload:"upload-bundle.mjs",register:"register-components.mjs",cutover:"cutover.mjs",knowledge:"emit-knowledge.mjs",contracts:"emit-contracts.mjs","bundle-size":"bundle-size.mjs","check-bundle-leak":"check-bundle-leak.mjs","validate-externals":"validate-externals.mjs","check-wired":"check-wired-bindings.mjs","check-routes":"check-template-routes.mjs","check-design-refs":"check-design-refs.mjs","check-skills":"check-skills.mjs","check-work-orders":"check-work-orders.mjs","check-cutover-ownership":"check-cutover-ownership.mjs","check-fixtures":"check-fixtures.mjs","check-props-schema":"check-props-schema.mjs","check-tokens":"check-tokens.mjs","check-css-scope":"check-css-scope.mjs","check-inline-styles":"check-inline-styles.mjs","check-shadowing":"check-shadowing.mjs","check-theme":"check-theme.mjs"};function commandFile(name){if(!COMMANDS[name])return null;const file=resolve(COMMANDS_DIR,COMMANDS[name]);const ts=file.replace(/\.mjs$/,".mts");return existsSync(file)?file:existsSync(ts)?ts:null}function runCommand(name,args=[],env={}){const file=commandFile(name);if(!file)throw new Error(`[engine] unknown command "${name}"`);const r=spawnSync(process.execPath,["--import",TSX,file,...args],{cwd:REPO_ROOT,stdio:"inherit",env:{...process.env,ENGINE_ROOT:REPO_ROOT,...env}});return r.status??1}export{COMMANDS,commandFile,runCommand};