@peripheries/engine 0.17.0 → 0.17.1
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
package/src/commands/build.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import{spawnSync}from"node:child_process";import{existsSync,readdirSync,readFileSync,rmSync,writeFileSync}from"node:fs";import{createRequire}from"node:module";import{resolve}from"node:path";import{pathToFileURL}from"node:url";import{assetAliases,assetFileNames,assetsPlugin}from"../lib/assets.mjs";import{loadConfig}from"../lib/config.mjs";import{bucketForLineage,parseFlagValue}from"../lib/env.mjs";import{layerCss,layerFor}from"../lib/layers.mjs";import{REPO_ROOT}from"../lib/root.mjs";import{runCommand}from"../lib/run.mjs";import{EXTERNALS}from"./externals.mjs";const argv=process.argv.slice(2);const only=parseFlagValue(argv,"lineage");const config=await loadConfig();const lineages=only?config.lineages.filter(l=>l.name===only):config.lineages;if(only&&lineages.length===0)throw new Error(`[build] --lineage ${only} is not in the config's lineages`);const fromRepo=createRequire(resolve(REPO_ROOT,"package.json"));const importFromRepo=name=>import(pathToFileURL(fromRepo.resolve(name)).href);function check(label,status){if(status!==0){console.error(`[build] ${label} failed (exit ${status}).`);process.exit(status??1)}}if(!argv.includes("--no-typecheck")&&existsSync(resolve(REPO_ROOT,"tsconfig.build.json"))){console.log("[build] typecheck: tsc -p tsconfig.build.json --noEmit");const tsc=fromRepo.resolve("typescript/bin/tsc");check("typecheck",spawnSync(process.execPath,[tsc,"-p","tsconfig.build.json","--noEmit"],{cwd:REPO_ROOT,stdio:"inherit"}).status)}process.env.NODE_ENV="production";const{build}=await importFromRepo("vite");const react=(await importFromRepo("@vitejs/plugin-react")).default;for(const lineage of lineages){const entry=resolve(REPO_ROOT,lineage.entry);if(!existsSync(entry))throw new Error(`[build] no barrel at ${lineage.entry} for lineage "${lineage.name}"`);const external=[...new Set([...EXTERNALS,...config.shared,...lineage.shared??[]])].filter(s=>s!==lineage.specifier);console.log(`
|
|
3
|
-
[build] ${lineage.name}: ${lineage.entry} \u2192 ${lineage.dist}/index.js`);await build({configFile:false,mode:"production",root:REPO_ROOT,base:"./",logLevel:"warn",plugins:[assetsPlugin({lineage:lineage.name,bucket:bucketForLineage(lineage),limits:config.assets}),react()],define:{"process.env.NODE_ENV":JSON.stringify("production"),__OA_BUNDLE_NAME__:JSON.stringify(config.org)},resolve:{alias:assetAliases(resolve(REPO_ROOT,"src"))},optimizeDeps:{exclude:external},build:{lib:{entry,formats:["es"],fileName:()=>"index.js"},rollupOptions:{external,output:{assetFileNames}},outDir:resolve(REPO_ROOT,lineage.dist),emptyOutDir:true,sourcemap:
|
|
4
|
-
[build] \u2705 ${lineages.map(l=>l.name).join(", ")}`);async function writeLayeredCss(lineage){const dist=resolve(REPO_ROOT,lineage.dist);const files=readdirSync(dist).filter(f=>f.endsWith(".css"));if(files.length===0)return;const css=files.map(f=>readFileSync(resolve(dist,f),"utf8")).join("\n");for(const f of files)rmSync(resolve(dist,f));const postcss=(await importFromRepo("postcss")).default;const layer=layerFor(lineage);writeFileSync(resolve(dist,"index.css"),layerCss(postcss,css,layer));console.log(`[build] ${lineage.name}: index.css in @layer ${layer}`)}
|
|
3
|
+
[build] ${lineage.name}: ${lineage.entry} \u2192 ${lineage.dist}/index.js`);await build({configFile:false,mode:"production",root:REPO_ROOT,base:"./",logLevel:"warn",plugins:[assetsPlugin({lineage:lineage.name,bucket:bucketForLineage(lineage),limits:config.assets}),react()],define:{"process.env.NODE_ENV":JSON.stringify("production"),__OA_BUNDLE_NAME__:JSON.stringify(config.org)},resolve:{alias:assetAliases(resolve(REPO_ROOT,"src"))},optimizeDeps:{exclude:external},build:{lib:{entry,formats:["es"],fileName:()=>"index.js"},rollupOptions:{external,output:{assetFileNames}},outDir:resolve(REPO_ROOT,lineage.dist),emptyOutDir:true,sourcemap:false}});await writeLayeredCss(lineage);await stripComments(lineage);check(`${lineage.name} contracts`,runCommand("contracts",["--entry",lineage.entry,"--out",`${lineage.dist}/contracts.json`]));check(`${lineage.name} knowledge`,runCommand("knowledge",["--bundle",lineage.name]))}console.log(`
|
|
4
|
+
[build] \u2705 ${lineages.map(l=>l.name).join(", ")}`);async function writeLayeredCss(lineage){const dist=resolve(REPO_ROOT,lineage.dist);const files=readdirSync(dist).filter(f=>f.endsWith(".css"));if(files.length===0)return;const css=files.map(f=>readFileSync(resolve(dist,f),"utf8")).join("\n");for(const f of files)rmSync(resolve(dist,f));const postcss=(await importFromRepo("postcss")).default;const layer=layerFor(lineage);writeFileSync(resolve(dist,"index.css"),layerCss(postcss,css,layer));console.log(`[build] ${lineage.name}: index.css in @layer ${layer}`)}async function stripComments(lineage){const dist=resolve(REPO_ROOT,lineage.dist);const{transform}=await importFromRepo("esbuild");for(const[file,loader]of[["index.js","js"],["index.css","css"]]){const path=resolve(dist,file);if(!existsSync(path))continue;const{code}=await transform(readFileSync(path,"utf8"),{loader,minifyWhitespace:true,legalComments:"none",...loader==="js"?{format:"esm"}:{}});writeFileSync(path,code)}for(const f of readdirSync(dist))if(f.endsWith(".map"))rmSync(resolve(dist,f));console.log(`[build] ${lineage.name}: comments and source maps removed`)}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{existsSync,statSync}from"node:fs";import{resolve}from"node:path";import{bundleFiles,metadataFiles,uploadBundleFiles}from"../lib/assets.mjs";import{bucketForLineage,initFromArgv,parseFlagValue}from"../lib/env.mjs";import{REPO_ROOT as REPO_ROOT_}from"../lib/root.mjs";import{lineageConfig}from"../lib/config.mjs";const argv=process.argv.slice(2);const{target,orgSlug:ORG_SLUG,supabase}=initFromArgv(argv);const BUNDLE_NAME=parseFlagValue(argv,"bundle")??"main";const dryRun=argv.includes("--dry-run");const IS_MAIN=BUNDLE_NAME==="main";const IS_PUBLIC=BUNDLE_NAME==="public";const IS_ORG_WIDE=IS_MAIN||IS_PUBLIC;const LINEAGE=await lineageConfig(BUNDLE_NAME);const IS_SHARED=LINEAGE.kind==="shared";const DEFAULT_BUCKET=bucketForLineage(LINEAGE);const STORAGE_BUCKET=process.env.STORAGE_BUCKET||DEFAULT_BUCKET;if(STORAGE_BUCKET!==DEFAULT_BUCKET){console.error(`[upload-bundle] STORAGE_BUCKET must be "${DEFAULT_BUCKET}" for lineage "${BUNDLE_NAME}", got "${STORAGE_BUCKET}".`);process.exit(2)}const VERSION=process.env.VERSION||`v_${target}_${new Date().toISOString().replace(/[-:T]/g,"").replace(/\..+$/,"").slice(0,14)}`;if(!process.env.VERSION){console.log(`[upload-bundle] VERSION auto-generated: ${VERSION}`);process.env.VERSION=VERSION}const distDir=resolve(REPO_ROOT_,LINEAGE.dist);const bundlePath=resolve(distDir,"index.js");if(!existsSync(bundlePath)){console.error(`[upload-bundle] ${bundlePath} does not exist. `+(IS_MAIN?"Did `npm run build` run?":`Did \`OA_PROJECT_BUNDLE=${BUNDLE_NAME} npm run build:bundle\` run?`));process.exit(1)}console.log(`[upload-bundle] lineage=${BUNDLE_NAME} dist=${distDir.split("/").slice(-2).join("/")}`);const{data:orgRow,error:orgError}=await supabase.from("organization").select("id").eq("metadata->>org_slug",ORG_SLUG).maybeSingle();if(orgError||!orgRow?.id){console.error(`[upload-bundle] Could not resolve org_id for org_slug="${ORG_SLUG}":`,orgError??"no matching organization row");process.exit(1)}const ORG_ID=orgRow.id;const remoteBase=IS_ORG_WIDE?`${ORG_SLUG}/${VERSION}`:`${ORG_SLUG}/${BUNDLE_NAME}/${VERSION}`;let files;try{files=bundleFiles(distDir,{bucket:STORAGE_BUCKET,lineage:BUNDLE_NAME});await uploadBundleFiles({bucket:supabase.storage.from(STORAGE_BUCKET),remoteBase,files,dryRun})}catch(e){console.error(e.message);process.exit(1)}const mapPath=resolve(distDir,"index.js.map");const{data:existingBundle,error:existingBundleError}=await supabase.from("component_bundle").select("id, status").eq("org_id",ORG_ID).eq("bundle_name",BUNDLE_NAME).eq("version",VERSION).maybeSingle();if(existingBundleError){console.error(`[upload-bundle] Failed to look up existing component_bundle for (org=${ORG_SLUG}, bundle=${BUNDLE_NAME}, version=${VERSION}):`,existingBundleError);process.exit(1)}const bundleBytes=statSync(bundlePath).size;const hasSourcemap=existsSync(mapPath);const bundlePayload={org_id:ORG_ID,org_slug:ORG_SLUG,bundle_name:BUNDLE_NAME,version:VERSION,status:"uploaded",storage_bucket:STORAGE_BUCKET,storage_path:`${remoteBase}/index.js`,...IS_SHARED?{is_public:true}:{},uploaded_at:new Date().toISOString(),registered_at:null,cutover_at:null,git_sha:process.env.DEPLOY_SHA??process.env.GITHUB_SHA??null,git_ref:process.env.DEPLOY_REF??process.env.GITHUB_REF??null,git_repo:process.env.GITHUB_REPOSITORY??null,process_id:process.env.PROCESS_ID||null,metadata:{bundle_bytes:bundleBytes,has_sourcemap:hasSourcemap,bundle_scope:BUNDLE_NAME,files:metadataFiles(files),...process.env.GITHUB_RUN_ID?{github_run_id:process.env.GITHUB_RUN_ID}:{},...process.env.GITHUB_RUN_ATTEMPT?{github_run_attempt:process.env.GITHUB_RUN_ATTEMPT}:{}}};if(dryRun){console.log(`[upload-bundle] dry-run \u2014 would ${existingBundle?.id?`UPDATE ${existingBundle.id}`:"INSERT"} component_bundle:`);console.log(JSON.stringify(bundlePayload,null,2));console.log("[upload-bundle] dry-run \u2014 nothing written.")}else if(existingBundle?.id){const{error:updateError}=await supabase.from("component_bundle").update(bundlePayload).eq("id",existingBundle.id);if(updateError){console.error(`[upload-bundle] Failed to update component_bundle row:`,updateError);process.exit(1)}console.log(`[upload-bundle] ~ component_bundle ${existingBundle.id} reset to status='uploaded' (was '${existingBundle.status}').`)}else{const{data:inserted,error:insertError}=await supabase.from("component_bundle").insert(bundlePayload).select("id").single();if(insertError){console.error(`[upload-bundle] Failed to insert component_bundle row:`,insertError);process.exit(1)}console.log(`[upload-bundle] + component_bundle ${inserted.id} created (status='uploaded').`)}console.log(`[upload-bundle] Done \u2014 target=${target} org=${ORG_SLUG} bundle=${BUNDLE_NAME} version=${VERSION}. Run register-components.mjs next.`);
|
|
2
|
+
import{existsSync,readFileSync,statSync}from"node:fs";import{createRequire}from"node:module";import{pathToFileURL}from"node:url";import{resolve}from"node:path";import{bundleFiles,metadataFiles,uploadBundleFiles}from"../lib/assets.mjs";import{auditBundle,otherOrgNames}from"../lib/audit-bundle.mjs";import{bucketForLineage,initFromArgv,parseFlagValue}from"../lib/env.mjs";import{REPO_ROOT as REPO_ROOT_}from"../lib/root.mjs";import{lineageConfig,loadConfig}from"../lib/config.mjs";const argv=process.argv.slice(2);const{target,orgSlug:ORG_SLUG,supabase}=initFromArgv(argv);const BUNDLE_NAME=parseFlagValue(argv,"bundle")??"main";const dryRun=argv.includes("--dry-run");const IS_MAIN=BUNDLE_NAME==="main";const IS_PUBLIC=BUNDLE_NAME==="public";const IS_ORG_WIDE=IS_MAIN||IS_PUBLIC;const LINEAGE=await lineageConfig(BUNDLE_NAME);const IS_SHARED=LINEAGE.kind==="shared";const DEFAULT_BUCKET=bucketForLineage(LINEAGE);const STORAGE_BUCKET=process.env.STORAGE_BUCKET||DEFAULT_BUCKET;if(STORAGE_BUCKET!==DEFAULT_BUCKET){console.error(`[upload-bundle] STORAGE_BUCKET must be "${DEFAULT_BUCKET}" for lineage "${BUNDLE_NAME}", got "${STORAGE_BUCKET}".`);process.exit(2)}const VERSION=process.env.VERSION||`v_${target}_${new Date().toISOString().replace(/[-:T]/g,"").replace(/\..+$/,"").slice(0,14)}`;if(!process.env.VERSION){console.log(`[upload-bundle] VERSION auto-generated: ${VERSION}`);process.env.VERSION=VERSION}const distDir=resolve(REPO_ROOT_,LINEAGE.dist);const bundlePath=resolve(distDir,"index.js");if(!existsSync(bundlePath)){console.error(`[upload-bundle] ${bundlePath} does not exist. `+(IS_MAIN?"Did `npm run build` run?":`Did \`OA_PROJECT_BUNDLE=${BUNDLE_NAME} npm run build:bundle\` run?`));process.exit(1)}console.log(`[upload-bundle] lineage=${BUNDLE_NAME} dist=${distDir.split("/").slice(-2).join("/")}`);const{data:orgRow,error:orgError}=await supabase.from("organization").select("id").eq("metadata->>org_slug",ORG_SLUG).maybeSingle();if(orgError||!orgRow?.id){console.error(`[upload-bundle] Could not resolve org_id for org_slug="${ORG_SLUG}":`,orgError??"no matching organization row");process.exit(1)}const ORG_ID=orgRow.id;const remoteBase=IS_ORG_WIDE?`${ORG_SLUG}/${VERSION}`:`${ORG_SLUG}/${BUNDLE_NAME}/${VERSION}`;let files;try{files=bundleFiles(distDir,{bucket:STORAGE_BUCKET,lineage:BUNDLE_NAME});const fromRepo=createRequire(resolve(REPO_ROOT_,"package.json"));const ts=(await import(pathToFileURL(fromRepo.resolve("typescript")).href)).default;const allowNames=Object.keys((await loadConfig()).audit.allowNames);const{data:orgs,error:orgsError}=await supabase.from("organization").select("id, name, slug:metadata->>org_slug").not("metadata->>org_slug","is",null);if(orgsError)throw new Error(`[upload-bundle] couldn't read the orgs to audit against: ${orgsError.message}`);const findings=auditBundle(files,{ts,read:p=>readFileSync(p,"utf8"),otherNames:otherOrgNames(orgs??[],ORG_ID),allowNames});if(findings.length){for(const f of findings)console.error(`[upload-bundle] audit: ${f}`);throw new Error(`[upload-bundle] ${findings.length} problem(s) in lineage "${BUNDLE_NAME}"; nothing was uploaded. Rebuild with \`engine build\`.`)}await uploadBundleFiles({bucket:supabase.storage.from(STORAGE_BUCKET),remoteBase,files,dryRun})}catch(e){console.error(e.message);process.exit(1)}const mapPath=resolve(distDir,"index.js.map");const{data:existingBundle,error:existingBundleError}=await supabase.from("component_bundle").select("id, status").eq("org_id",ORG_ID).eq("bundle_name",BUNDLE_NAME).eq("version",VERSION).maybeSingle();if(existingBundleError){console.error(`[upload-bundle] Failed to look up existing component_bundle for (org=${ORG_SLUG}, bundle=${BUNDLE_NAME}, version=${VERSION}):`,existingBundleError);process.exit(1)}const bundleBytes=statSync(bundlePath).size;const hasSourcemap=existsSync(mapPath);const bundlePayload={org_id:ORG_ID,org_slug:ORG_SLUG,bundle_name:BUNDLE_NAME,version:VERSION,status:"uploaded",storage_bucket:STORAGE_BUCKET,storage_path:`${remoteBase}/index.js`,...IS_SHARED?{is_public:true}:{},uploaded_at:new Date().toISOString(),registered_at:null,cutover_at:null,git_sha:process.env.DEPLOY_SHA??process.env.GITHUB_SHA??null,git_ref:process.env.DEPLOY_REF??process.env.GITHUB_REF??null,git_repo:process.env.GITHUB_REPOSITORY??null,process_id:process.env.PROCESS_ID||null,metadata:{bundle_bytes:bundleBytes,has_sourcemap:hasSourcemap,bundle_scope:BUNDLE_NAME,files:metadataFiles(files),...process.env.GITHUB_RUN_ID?{github_run_id:process.env.GITHUB_RUN_ID}:{},...process.env.GITHUB_RUN_ATTEMPT?{github_run_attempt:process.env.GITHUB_RUN_ATTEMPT}:{}}};if(dryRun){console.log(`[upload-bundle] dry-run \u2014 would ${existingBundle?.id?`UPDATE ${existingBundle.id}`:"INSERT"} component_bundle:`);console.log(JSON.stringify(bundlePayload,null,2));console.log("[upload-bundle] dry-run \u2014 nothing written.")}else if(existingBundle?.id){const{error:updateError}=await supabase.from("component_bundle").update(bundlePayload).eq("id",existingBundle.id);if(updateError){console.error(`[upload-bundle] Failed to update component_bundle row:`,updateError);process.exit(1)}console.log(`[upload-bundle] ~ component_bundle ${existingBundle.id} reset to status='uploaded' (was '${existingBundle.status}').`)}else{const{data:inserted,error:insertError}=await supabase.from("component_bundle").insert(bundlePayload).select("id").single();if(insertError){console.error(`[upload-bundle] Failed to insert component_bundle row:`,insertError);process.exit(1)}console.log(`[upload-bundle] + component_bundle ${inserted.id} created (status='uploaded').`)}console.log(`[upload-bundle] Done \u2014 target=${target} org=${ORG_SLUG} bundle=${BUNDLE_NAME} version=${VERSION}. Run register-components.mjs next.`);
|
package/src/index.d.mts
CHANGED
|
@@ -20,6 +20,7 @@ export type EngineConfig = {
|
|
|
20
20
|
lineages?: LineageSpec[];
|
|
21
21
|
shared?: string[];
|
|
22
22
|
shadows?: Record<string, string>;
|
|
23
|
+
audit?: { allowNames?: Record<string, string> };
|
|
23
24
|
harness?: {
|
|
24
25
|
wrapper?: string;
|
|
25
26
|
widths?: number[];
|
|
@@ -78,6 +79,7 @@ export type LoadedConfig = {
|
|
|
78
79
|
shadows: Record<string, string>;
|
|
79
80
|
harness: { wrapper: string | null; widths: number[]; container: number; overflow: 'warn' | 'error'; output: string; alias: Record<string, string> };
|
|
80
81
|
assets: { maxAssetBytes: number; maxTotalBytes: number };
|
|
82
|
+
audit: { allowNames: Record<string, string> };
|
|
81
83
|
};
|
|
82
84
|
export function loadConfig(): Promise<LoadedConfig>;
|
|
83
85
|
export function lineageConfig(name: string): Promise<Lineage>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const FORBIDDEN=[[/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[a-z]{2,}\b/,"an email address"],[/\bsb_(secret|publishable)_[A-Za-z0-9_-]+/,"a Supabase key"],[/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/,"a JWT"],[/\b[a-z]{20}\.supabase\.(co|in)\b/,"a Supabase project URL"],[/https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?/,"a localhost URL"],[/\/Users\/[A-Za-z0-9._-]+/,"a local path"],[/sourceMappingURL=/,"a source map reference"]];const TEXT=/\.(m?js|css|json|svg|txt|html)$/i;const normalize=s=>s.toLowerCase().replace(/[\s_-]+/g," ").trim();const escape=s=>s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");const namePattern=name=>new RegExp(`\\b${normalize(name).split(" ").map(escape).join("[\\s_-]+")}\\b`,"gi");function jsComments(code,ts){const sf=ts.createSourceFile("bundle.js",code,ts.ScriptTarget.Latest,true,ts.ScriptKind.JS);const found=new Set;const visit=node=>{for(const r of[...ts.getLeadingCommentRanges(code,node.pos)??[],...ts.getTrailingCommentRanges(code,node.end)??[]])found.add(code.slice(r.pos,r.end));ts.forEachChild(node,visit)};visit(sf);for(const r of ts.getLeadingCommentRanges(code,sf.endOfFileToken.pos)??[])found.add(code.slice(r.pos,r.end));return[...found]}function cssComments(css){const bare=css.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g,'""');return bare.match(/\/\*[\s\S]*?\*\//g)??[]}function fileProblems(name,text,{ts,otherNames=[],allowNames=[]}){const out=[];if(/\.map$/i.test(name))return[`a source map (${name}); build with \`engine build\`, which writes none`];if(/\.m?js$/i.test(name))for(const c of jsComments(text,ts))out.push(`a comment: ${c.slice(0,60)}`);if(/\.css$/i.test(name))for(const c of cssComments(text))out.push(`a comment: ${c.slice(0,60)}`);if(/\.svg$/i.test(name)&&/<!--/.test(text))out.push("a comment: <!-- \u2026 -->");for(const[re,what]of FORBIDDEN){const m=text.match(re);if(m)out.push(`${what}: ${m[0].slice(0,80)}`)}const allowed=new Set(allowNames.map(normalize));for(const other of otherNames){if(allowed.has(normalize(other)))continue;const m=text.match(namePattern(other));if(m&&!out.includes(`an org name: ${m[0]}`))out.push(`an org name: ${m[0]}`)}return out}function auditBundle(files,{ts,read,otherNames=[],allowNames=[]}){const out=[];for(const f of files){if(!TEXT.test(f.remote)&&!/\.map$/i.test(f.remote))continue;for(const p of fileProblems(f.remote,read(f.local),{ts,otherNames,allowNames}))out.push(`${f.remote}: ${p}`)}return out}function otherOrgNames(rows,ownOrgId){const out=new Set;for(const r of rows){if(r.id===ownOrgId||!r.slug||r.slug==="stack")continue;for(const n of[r.name,r.slug])if(typeof n==="string"&&normalize(n).replace(/ /g,"").length>=4)out.add(n)}return[...out]}export{auditBundle,cssComments,fileProblems,jsComments,otherOrgNames};
|
package/src/lib/config.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync,readFileSync}from"node:fs";import{resolve}from"node:path";import{pathToFileURL}from"node:url";import{DEFAULT_ASSET_LIMITS}from"./assets.mjs";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","check-inline-assets","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??{},propsSchema:{families:raw.checks?.propsSchema?.families??null,names:raw.checks?.propsSchema?.names??[],floor:raw.checks?.propsSchema?.floor??1,propless:raw.checks?.propsSchema?.propless??[]}};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??{}};const assets={maxAssetBytes:raw.assets?.maxAssetBytes??DEFAULT_ASSET_LIMITS.maxAssetBytes,maxTotalBytes:raw.assets?.maxTotalBytes??DEFAULT_ASSET_LIMITS.maxTotalBytes};for(const[k,v]of Object.entries(assets)){if(!Number.isInteger(v)||v<=0)throw new Error(`[engine] ${origin}: assets.${k} must be a positive number of bytes`)}return{org:raw.org,lineages,checks,shared,shadows,harness,assets}}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};
|
|
1
|
+
import{existsSync,readFileSync}from"node:fs";import{resolve}from"node:path";import{pathToFileURL}from"node:url";import{DEFAULT_ASSET_LIMITS}from"./assets.mjs";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","check-inline-assets","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??{},propsSchema:{families:raw.checks?.propsSchema?.families??null,names:raw.checks?.propsSchema?.names??[],floor:raw.checks?.propsSchema?.floor??1,propless:raw.checks?.propsSchema?.propless??[]}};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??{}};const assets={maxAssetBytes:raw.assets?.maxAssetBytes??DEFAULT_ASSET_LIMITS.maxAssetBytes,maxTotalBytes:raw.assets?.maxTotalBytes??DEFAULT_ASSET_LIMITS.maxTotalBytes};for(const[k,v]of Object.entries(assets)){if(!Number.isInteger(v)||v<=0)throw new Error(`[engine] ${origin}: assets.${k} must be a positive number of bytes`)}const audit={allowNames:raw.audit?.allowNames??{}};for(const[name,reason]of Object.entries(audit.allowNames)){if(typeof reason!=="string"||!reason.trim())throw new Error(`[engine] ${origin}: audit.allowNames["${name}"] needs a reason`)}return{org:raw.org,lineages,checks,shared,shadows,harness,assets,audit}}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};
|