archicat 0.1.0 → 0.1.2

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.
@@ -1,28 +1,1512 @@
1
- import{createRequire as e}from"node:module";import{Console as t}from"@buildplease/core/node";import n from"node:fs";import r from"node:path";import i from"typescript";import{createJiti as a}from"jiti";const o={packageName:`archicat`,configFileName:`archicat.config.ts`,root:`.`,outDir:`.archicat`,alias:{},definitions:{moduleFileName:`archicat.module.ts`,libraryFileName:`archicat.library.ts`,appFileName:`archicat.app.ts`},generated:{modulesDirName:`modules`,librariesDirName:`libraries`,typesDirName:`types`,reportsDirName:`reports`,buildReportFileName:`build.report.json`,graphReportFileName:`graph.report.json`,tsconfigFileName:`tsconfig.json`,typesInclude:`./types/**/*.d.ts`,ignoredDirectoryNames:[`node_modules`,`.git`,`.archicat`,`dist`,`build`,`coverage`]},typescript:{consumerTsconfigFileName:`tsconfig.json`,tsConfig:{include:[],exclude:[],files:[]}},modules:{include:[`./src/modules`],alias:`#modules`},libraries:{include:[],alias:`#library`},apps:{include:[]}};function s(e){let t=n.readFileSync(e,`utf8`),r=i.parseConfigFileTextToJson(e,t);if(r.error)throw Error(te(r.error));if(r.config==null||typeof r.config!=`object`||Array.isArray(r.config))throw Error(`Invalid tsconfig object: ${e}`);return r.config}function c(e,t=`tsconfig`){let n=e.compilerOptions;if(n==null)return{};if(typeof n!=`object`||Array.isArray(n))throw Error(`Tsconfig compilerOptions must be an object: ${t}`);return n}function l(e,t){let n=e.extends;if(n===void 0)return[];if(typeof n==`string`)return[d(t,n)];if(Array.isArray(n)&&n.every(e=>typeof e==`string`))return n.map(e=>d(t,e));throw Error(`Tsconfig extends must be a string or string array: ${t}`)}function u(t,n){return r.isAbsolute(n)||n.startsWith(`.`)?ee(r.resolve(t,n),n):f(e(r.join(t,o.configFileName)),n,n)}function d(t,n){return r.isAbsolute(n)||n.startsWith(`.`)?ee(r.resolve(r.dirname(t),n),t):f(e(t),n,t)}function f(e,t,n){for(let n of p(t))try{return e.resolve(n)}catch{continue}throw Error(`Unable to resolve tsconfig extends "${t}": ${n}`)}function ee(e,t){for(let t of p(e))if(n.existsSync(t)&&n.statSync(t).isFile())return t;throw Error(`Unable to resolve tsconfig extends "${e}": ${t}`)}function p(e){return[e,`${e}.json`,r.join(e,o.typescript.consumerTsconfigFileName)]}function te(e){let t=i.flattenDiagnosticMessageText(e.messageText,`
2
- `);if(e.file&&e.start!==void 0){let n=e.file.getLineAndCharacterOfPosition(e.start);return`${e.file.fileName}:${n.line+1}:${n.character+1} - ${t}`}return t}async function ne(e,t=o.configFileName){let i=r.resolve(e,t);if(!n.existsSync(i))throw Error(`Archicat config was not found: ${i}`);let a=await re(i,e);ue(a,i);let s=ie(a);me(s.modules.alias,s.libraries.alias,i);let c=r.resolve(e,s.root),l=r.resolve(c,s.outDir),u=r.resolve(l,o.generated.reportsDirName),d=le(c,s.typescript.tsConfig.extends);return{configFilePath:i,rootDir:c,outDir:l,reportsDir:u,...d?{tsconfigPath:d}:{},resolvedConfig:s}}async function re(e,t){return await a(t,{interopDefault:!0,extensions:[`.js`,`.cjs`,`.mjs`,`.ts`,`.cts`,`.mts`,`.json`]}).import(e,{default:!0})}function ie(e){return{root:e.root??o.root,outDir:e.outDir??o.outDir,typescript:ae(e),alias:{...o.alias,...e.alias??{}},modules:oe(e.modules),libraries:se(e.libraries),apps:ce(e.apps)}}function ae(e){let t=e.typescript?.tsConfig,n=o.typescript.tsConfig,r={include:[...t?.include??n.include],exclude:[...t?.exclude??n.exclude],files:[...t?.files??n.files]};return t?.extends&&(r.extends=t.extends),{tsConfig:r}}function oe(e){return{include:[...e?.include??o.modules.include],alias:e?.alias??o.modules.alias}}function se(e){return{include:[...e?.include??o.libraries.include],alias:e?.alias??o.libraries.alias}}function ce(e){return{include:[...e?.include??o.apps.include]}}function le(e,t){if(t)return u(e,t)}function ue(e,t){if(typeof e!=`object`||!e)throw Error(`Invalid Archicat config: ${t}`);let n=e;m(n.root,`root`,t),m(n.outDir,`outDir`,t),_(n.typescript,`typescript`,t),_(n.modules,`modules`,t),_(n.libraries,`libraries`,t),_(n.apps,`apps`,t),de(n.typescript?.tsConfig,t),h(n.modules?.include,`modules.include`,t),h(n.libraries?.include,`libraries.include`,t),h(n.apps?.include,`apps.include`,t),g(n.modules?.alias,`modules.alias`,t),g(n.libraries?.alias,`libraries.alias`,t),pe(n.alias,`alias`,t)}function de(e,t){if(e===void 0)return;if(typeof e!=`object`||!e||Array.isArray(e))throw Error(`Archicat config typescript.tsConfig must be an object: ${t}`);let n=e;m(n.extends,`typescript.tsConfig.extends`,t),h(n.include,`typescript.tsConfig.include`,t),h(n.exclude,`typescript.tsConfig.exclude`,t),h(n.files,`typescript.tsConfig.files`,t),fe(n.compilerOptions,t)}function fe(e,t){if(e===void 0)return;if(typeof e!=`object`||!e||Array.isArray(e))throw Error(`Archicat config typescript.tsConfig.compilerOptions must be an object: ${t}`);let n=e;if(Object.hasOwn(n,`paths`))throw Error(`Archicat config typescript.tsConfig.compilerOptions.paths is not supported. Move aliases into archicat.config.ts alias.`);if(Object.hasOwn(n,`baseUrl`))throw Error(`Archicat config typescript.tsConfig.compilerOptions.baseUrl is not supported. Move aliases into archicat.config.ts alias.`);if(Object.keys(n).length>0)throw Error(`Archicat config typescript.tsConfig.compilerOptions is not supported. Put compiler options in the base or app tsconfig.`)}function m(e,t,n){if(e!==void 0&&(typeof e!=`string`||e.trim()===``))throw Error(`Archicat ${t} must be a non-empty string when defined: ${n}`)}function h(e,t,n){if(e!==void 0&&(!Array.isArray(e)||e.some(e=>typeof e!=`string`||e.trim()===``)))throw Error(`Archicat config ${t} must be an array of non-empty strings: ${n}`)}function g(e,t,n){if(e!==void 0&&(typeof e!=`string`||e.trim()===``||e.includes(`*`)||e.endsWith(`/`)))throw Error(`Archicat config ${t} must be a non-empty alias without wildcard or trailing slash: ${n}`)}function pe(e,t,n){if(e!==void 0){if(typeof e!=`object`||!e||Array.isArray(e))throw Error(`Archicat config ${t} must be an object of non-empty string aliases: ${n}`);for(let[r,i]of Object.entries(e))if(r.trim()===``||typeof i!=`string`||i.trim()===``)throw Error(`Archicat config ${t} must contain non-empty string aliases: ${n}`)}}function _(e,t,n){if(e!==void 0&&(typeof e!=`object`||!e||Array.isArray(e)))throw Error(`Archicat config ${t} must be an object: ${n}`)}function me(e,t,n){if(e===t||e.startsWith(`${t}/`)||t.startsWith(`${e}/`))throw Error(`Archicat module and library aliases must use separate roots: "${e}" and "${t}" (${n})`)}function v(e){return e.split(r.sep).join(`/`)}function he(e,t){let n=r.dirname(e),i=r.parse(t),a=r.join(i.dir,i.name),o=v(r.relative(n,a));return o.startsWith(`.`)||(o=`./${o}`),`${o}.js`}function y(e,t){let n=r.relative(t,e);return n===``||!!n&&!n.startsWith(`..`)&&!r.isAbsolute(n)}function ge(e,t){let n=b(e),r=b(t);if(r===n||!y(r,n))throw Error(`Archicat outDir must be a directory inside the project root: ${t}`)}function b(e){let t=r.resolve(e),i=[],a=t;for(;!n.existsSync(a);){let e=r.dirname(a);if(e===a)return t;i.unshift(r.basename(a)),a=e}return r.join(n.realpathSync(a),...i)}function x(e){return e.replace(/\.(?:js|mjs|cjs|ts|mts|cts|tsx)$/u,``)}function S(e,t){return v(r.relative(e,t))}function C(e,t,n,r=[]){let i=t.flatMap(t=>_e(e,t,n,r));return Array.from(new Set(i)).sort((e,t)=>e.localeCompare(t))}function _e(e,t,i,a){let o=r.resolve(e,t);if(t.includes(`*`))return ye(e,t).filter(e=>!a.some(t=>y(e,t))).filter(e=>r.basename(e)===i);if(!n.existsSync(o))return[];let s=n.statSync(o);return s.isFile()?r.basename(o)===i?[o]:[]:s.isDirectory()?ve(o,i,a):[]}function ve(e,t,i){let a=[],o=n.readdirSync(e,{withFileTypes:!0});for(let n of o){if(xe(n.name))continue;let o=r.join(e,n.name);if(!i.some(e=>y(o,e))){if(n.isDirectory()){a.push(...ve(o,t,i));continue}n.isFile()&&n.name===t&&a.push(o)}}return a}function ye(e,t){let i=r.resolve(e,t).split(r.sep);if(i.filter(e=>e.includes(`*`)).length!==1)throw Error(`Archicat supports exactly one wildcard segment per include pattern: ${t}`);let a=i.findIndex(e=>e.includes(`*`)),o=i.slice(0,a).join(r.sep)||r.sep,s=i[a]??`*`,c=i.slice(a+1),l=be(s);return n.existsSync(o)?n.readdirSync(o,{withFileTypes:!0}).filter(e=>e.isDirectory()).filter(e=>l.test(e.name)).map(e=>r.join(o,e.name,...c)).filter(e=>n.existsSync(e)&&n.statSync(e).isFile()):[]}function be(e){let t=e.replace(/[.+?^${}()|[\]\\]/gu,`\\$&`).replace(/\*/gu,`.*`);return RegExp(`^${t}$`,`u`)}function xe(e){return o.generated.ignoredDirectoryNames.includes(e)}async function w(e,t){switch(t){case`module`:return Se(e);case`library`:return Ce(e);case`app`:return we(e)}}async function Se(e){let t=await T(e,r.dirname(e));return E(t,e,`module`),{kind:`module`,contractFilePath:e,definitionDir:r.dirname(e),contract:t}}async function Ce(e){let t=await T(e,r.dirname(e));return E(t,e,`library`),{kind:`library`,contractFilePath:e,definitionDir:r.dirname(e),contract:t}}async function we(e){let t=await T(e,r.dirname(e));return E(t,e,`app`),{kind:`app`,contractFilePath:e,definitionDir:r.dirname(e),contract:t}}async function T(e,t){return await a(t,{interopDefault:!0,extensions:[`.js`,`.cjs`,`.mjs`,`.ts`,`.cts`,`.mts`,`.json`]}).import(e,{default:!0})}function E(e,t,n){if(typeof e!=`object`||!e)throw Error(`Invalid Archicat ${n} definition: ${t}`);let r=e;if(r.kind!==n)throw Error(`Archicat ${n} file must export a ${n} definition: ${t}`);if(typeof r.name!=`string`||r.name.trim()===``)throw Error(`Archicat ${n} must define a non-empty name: ${t}`);if(n===`app`){k(r.root,`root`,t),O(r.dependencies,t,n);return}let i=r;D(i.api,`api`,t,n),D(i.impl,`impl`,t,n)}function D(e,t,n,r){if(typeof e!=`object`||!e)throw Error(`Archicat ${r}.${t} must be a surface object: ${n}`);k(e.root,`${t}.root`,n),O(e.dependencies,n,`${r}.${t}`)}function O(e,t,n){if(!Array.isArray(e)||e.some(e=>typeof e!=`string`||e.trim()===``))throw Error(`Archicat ${n} dependencies must be an array of non-empty strings: ${t}`)}function k(e,t,n){if(e!==void 0&&(typeof e!=`string`||e.trim()===``))throw Error(`Archicat ${t} must be a non-empty string when defined: ${n}`)}function Te(e){let t=new Map;for(let n of e){let e=t.get(n.from)??[];e.push(n.to),t.set(n.from,e)}let n=new Set,r=new Set;for(let e of t.keys())A(e,t,n,r,[])}function A(e,t,n,r,i){if(!r.has(e)){if(n.has(e)){let t=i.indexOf(e),n=[...i.slice(t<0?0:t),e];throw Error(`Cyclic Archicat dependency detected: ${n.join(` -> `)}`)}n.add(e);for(let a of t.get(e)??[])A(a,t,n,r,[...i,e]);n.delete(e),r.add(e)}}function Ee(e){let t=/^(module|library)\.([a-z][a-z0-9-]*)\.(api|impl)$/u.exec(e);if(t)return{kind:t[1],name:t[2],surface:t[3]}}function j(e,t,n){let r=Ee(t);if(!r)throw Error(`${M(e)} declares invalid dependency target "${t}".`);if(!n.has(t))throw Error(`${M(e)} declares unknown dependency "${t}".`);if(e.target===t)throw Error(`${M(e)} cannot depend on itself: ${t}`);if(e.surface===`api`&&r.surface===`impl`)throw Error(`${M(e)} cannot depend on implementation target "${t}" from an API surface.`)}function M(e){return e.kind===`app`?`App "${e.name}"`:`${De(e.kind)} "${e.name}" ${e.surface}`}function De(e){return`${e.charAt(0).toUpperCase()}${e.slice(1)}`}function Oe(e,t){let n=t.filter(e=>e.kind===`module`).map(t=>ke(e,t)),r=t.filter(e=>e.kind===`library`).map(t=>Ae(e,t)),i=t.filter(e=>e.kind===`app`).map(e=>je(e)),a=[...n,...r];Ne([...a,...i]);let o=Me(a,i);return Pe(a,i,o.targets),Te(o.dependencies),{rootDir:e.rootDir,outDir:e.outDir,reportsDir:e.reportsDir,...e.tsconfigPath?{tsconfigPath:e.tsconfigPath}:{},configFilePath:e.configFilePath,config:e.resolvedConfig,modules:n,libraries:r,apps:i,definitions:a,graph:o}}function ke(e,t){let{contract:n,contractFilePath:i,definitionDir:a}=t;F(n.name,i,`module`);let s=n.api.root?P(a,n.api.root,`api`,n.name):void 0,c=n.impl.root?P(a,n.impl.root,`impl`,n.name):void 0,l=`${e.resolvedConfig.modules.alias}/${n.name}`,u=`${l}/api`,d=`${l}/impl`;return{kind:`module`,name:n.name,apiTarget:`module.${n.name}.api`,implTarget:`module.${n.name}.impl`,alias:u,aliasGlob:`${u}/*`,implAlias:d,implAliasGlob:`${d}/*`,contractFilePath:i,definitionDir:a,api:N(s,n.api.dependencies,r.join(e.outDir,o.generated.modulesDirName,n.name,`api`)),impl:N(c,n.impl.dependencies,r.join(e.outDir,o.generated.modulesDirName,n.name,`impl`))}}function Ae(e,t){let{contract:n,contractFilePath:i,definitionDir:a}=t;F(n.name,i,`library`);let s=n.api.root?P(a,n.api.root,`api`,n.name):void 0,c=n.impl.root?P(a,n.impl.root,`impl`,n.name):void 0,l=`${e.resolvedConfig.libraries.alias}/${n.name}`,u=`${l}/api`,d=`${l}/impl`;return{kind:`library`,name:n.name,apiTarget:`library.${n.name}.api`,implTarget:`library.${n.name}.impl`,alias:u,aliasGlob:`${u}/*`,implAlias:d,implAliasGlob:`${d}/*`,contractFilePath:i,definitionDir:a,api:N(s,n.api.dependencies,r.join(e.outDir,o.generated.librariesDirName,n.name,`api`)),impl:N(c,n.impl.dependencies,r.join(e.outDir,o.generated.librariesDirName,n.name,`impl`))}}function je(e){let{contract:t,contractFilePath:n,definitionDir:r}=e;return F(t.name,n,`app`),{kind:`app`,name:t.name,target:`app.${t.name}`,contractFilePath:n,rootPath:t.root?P(r,t.root,`app`,t.name):r,dependencies:[...t.dependencies]}}function N(e,t,n){return{...e?{rootPath:e}:{},mirrorRootPath:n,dependencies:[...t]}}function P(e,t,i,a){let o=r.resolve(e,t);if(!n.existsSync(o))throw Error(`Definition "${a}" declares ${i} root that does not exist: ${o}`);if(!n.statSync(o).isDirectory())throw Error(`Definition "${a}" declares ${i} root that is not a directory: ${o}`);return o}function Me(e,t){let n=e.flatMap(e=>[{key:e.apiTarget,surface:`api`},{key:e.implTarget,surface:`impl`}]),r=[...e.flatMap(e=>[...e.api.dependencies.map(t=>({from:e.apiTarget,to:t,origin:`declared`})),...e.impl.dependencies.map(t=>({from:e.implTarget,to:t,origin:`declared`}))]),...t.flatMap(e=>e.dependencies.map(t=>({from:e.target,to:t,origin:`declared`})))];return{targets:n,dependencies:[...e.map(e=>({from:e.implTarget,to:e.apiTarget,origin:`derived`})),...r]}}function F(e,t,n){if(!/^[a-z][a-z0-9-]*$/u.test(e))throw Error(`Invalid Archicat ${n} name "${e}" in ${t}. Use ^[a-z][a-z0-9-]*$`)}function Ne(e){let t=new Map;for(let n of e){let e=`${n.kind}.${n.name}`,r=t.get(e);if(r)throw Error(`Duplicate Archicat ${n.kind} name "${n.name}" in ${r} and ${n.contractFilePath}`);t.set(e,n.contractFilePath)}}function Pe(e,t,n){let r=new Set(n.map(e=>e.key));for(let t of e){for(let e of t.api.dependencies)j(Fe(t,`api`),e,r);for(let e of t.impl.dependencies)j(Fe(t,`impl`),e,r)}for(let e of t)for(let t of e.dependencies)j({kind:`app`,name:e.name,surface:`app`,target:e.target},t,r)}function Fe(e,t){return{kind:e.kind,name:e.name,surface:t,target:t===`api`?e.apiTarget:e.implTarget}}async function Ie(e,t){let n=await ne(e,t),r=C(n.rootDir,n.resolvedConfig.modules.include,o.definitions.moduleFileName,[n.outDir]),i=C(n.rootDir,n.resolvedConfig.libraries.include,o.definitions.libraryFileName,[n.outDir]),a=C(n.rootDir,n.resolvedConfig.apps.include,o.definitions.appFileName,[n.outDir]);if(r.length===0&&i.length===0&&a.length===0)throw Error(`No Archicat definition files matched configured include roots.`);return Oe(n,[...await Promise.all(r.map(e=>w(e,`module`))),...await Promise.all(i.map(e=>w(e,`library`))),...await Promise.all(a.map(e=>w(e,`app`)))])}function Le(e){let t=[];Re(t,`Modules`,e.modules.length,e.modules,e.graph.dependencies),Re(t,`Libraries`,e.libraries.length,e.libraries,e.graph.dependencies),t.push(`Apps: ${e.apps.length}`),e.apps.length>0&&t.push(``);for(let n of e.apps)t.push(n.name),t.push(` app: ${n.target}`),I(t,e.graph.dependencies.filter(e=>e.from===n.target),` dependsOn`),t.push(``);return ze(t)}function Re(e,t,n,r,i){e.push(`${t}: ${n}`),r.length>0&&e.push(``);for(let t of r)e.push(t.name),e.push(` api: ${t.apiTarget}`),I(e,i.filter(e=>e.from===t.apiTarget),` api dependsOn`),e.push(` impl: ${t.implTarget}`),I(e,i.filter(e=>e.from===t.implTarget),` impl dependsOn`),e.push(``)}function I(e,t,n){if(t.length===0){e.push(`${n}: none`);return}e.push(`${n}:`);for(let n of t){let t=n.origin===`derived`?` (derived)`:``;e.push(` ${n.to}${t}`)}}function ze(e){for(;e.at(-1)===``;)e.pop();return e}async function Be(e,t){return{exitCode:0,lines:Le(await Ie(t,e.config)).map(e=>({kind:`info`,message:e}))}}function Ve(e){return[...He(e),...Xe(e)]}function He(e){let t=Ze(e);if(!n.existsSync(t))return[`Consumer tsconfig was not found: ${t}`];let r=[],i=Ue(t,r);if(!i)return r;let a=We(i,t,r);return a?(Ge(r,e,i),Ke(r,a),qe(r,a),Je(r,a),Ye(r,i),r):r}function Ue(e,t){try{return s(e)}catch(e){t.push(`Failed to parse consumer tsconfig: ${tt(e)}`);return}}function We(e,t,n){try{return c(e,t)}catch(e){n.push(tt(e));return}}function Ge(e,t,n){let r=$e(t);n.extends!==r&&e.push(`Consumer tsconfig should extend ${r} for generated Archicat aliases to work.`)}function Ke(e,t){let n=t.rootDir;(n===`src`||n===`./src`)&&e.push(`compilerOptions.rootDir is set to src. Generated Archicat files live outside src and may break tsc.`)}function qe(e,t){Object.hasOwn(t,`paths`)&&e.push(`compilerOptions.paths should move to archicat.config.ts alias. Consumer tsconfig paths can override generated Archicat aliases.`)}function Je(e,t){Object.hasOwn(t,`baseUrl`)&&e.push(`compilerOptions.baseUrl is not supported. Move aliases into archicat.config.ts alias.`)}function Ye(e,t){(t.include!==void 0||t.exclude!==void 0||t.files!==void 0)&&e.push(`Consumer tsconfig include/exclude/files should move to archicat.config.ts typescript.tsConfig so generated Archicat types stay included.`)}function Xe(e){let t=[];for(let i of e.definitions){let e=r.join(i.definitionDir,`api`),a=r.join(i.definitionDir,`impl`);!i.api.rootPath&&n.existsSync(e)&&t.push(`${L(i.kind)} "${i.name}" has a physical api directory but its contract omits api.root. Archicat treats the public API as empty.`),!i.impl.rootPath&&n.existsSync(a)&&t.push(`${L(i.kind)} "${i.name}" has a physical impl directory but its contract omits impl.root. Archicat treats the implementation as no-op.`)}return t}function Ze(e){return r.join(e.rootDir,o.typescript.consumerTsconfigFileName)}function Qe(e){return r.join(e.outDir,o.generated.tsconfigFileName)}function $e(e){let t=v(r.relative(e.rootDir,Qe(e)));return et(t)||(t=`./${t}`),t}function et(e){return e.startsWith(`./`)||e.startsWith(`../`)}function tt(e){return e instanceof Error?e.message:String(e)}function L(e){return`${e.charAt(0).toUpperCase()}${e.slice(1)}`}function nt(e,t){ge(e,t),rt(e,t),n.rmSync(t,{recursive:!0,force:!0}),n.mkdirSync(t,{recursive:!0})}function R(e,t){n.mkdirSync(r.dirname(e),{recursive:!0}),n.writeFileSync(e,t,`utf8`)}function z(e,t){R(e,`${JSON.stringify(t,null,2)}\n`)}function rt(e,t){if(!(!n.existsSync(t)||n.readdirSync(t).length===0)&&!it(e,t))throw Error(`Archicat refuses to replace a non-owned outDir: ${t}`)}function it(e,t){let i=r.join(t,o.generated.reportsDirName,o.generated.buildReportFileName);if(!n.existsSync(i))return!1;try{let a=JSON.parse(n.readFileSync(i,`utf8`));return a.generatedBy===`archicat`&&a.schemaVersion===2&&typeof a.outputs?.outDir==`string`&&r.resolve(e,a.outputs.outDir)===r.resolve(t)}catch{return!1}}function at(e){let t=e.graph.targets.map(e=>e.key),n=e.graph.targets.filter(e=>e.surface===`api`).map(e=>e.key),i=o.packageName,a=`import '${i}';
1
+ import { createRequire } from "node:module";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import ts from "typescript";
5
+ import { createJiti } from "jiti";
6
+ import consola from "consola";
3
7
 
4
- declare module '${i}' {
5
- ${B(`ArchicatModuleApiDependencies`,n)}
8
+ //#region src-internal/configuration/archicat-defaults.ts
9
+ const ArchicatDefaults = {
10
+ packageName: "archicat",
11
+ configFileName: "archicat.config.ts",
12
+ root: ".",
13
+ outDir: ".archicat",
14
+ alias: {},
15
+ definitions: {
16
+ moduleFileName: "archicat.module.ts",
17
+ libraryFileName: "archicat.library.ts",
18
+ appFileName: "archicat.app.ts"
19
+ },
20
+ generated: {
21
+ modulesDirName: "modules",
22
+ librariesDirName: "libraries",
23
+ typesDirName: "types",
24
+ reportsDirName: "reports",
25
+ buildReportFileName: "build.report.json",
26
+ graphReportFileName: "graph.report.json",
27
+ tsconfigFileName: "tsconfig.json",
28
+ typesInclude: "./types/**/*.d.ts",
29
+ ignoredDirectoryNames: [
30
+ "node_modules",
31
+ ".git",
32
+ ".archicat",
33
+ "dist",
34
+ "build",
35
+ "coverage"
36
+ ]
37
+ },
38
+ typescript: {
39
+ consumerTsconfigFileName: "tsconfig.json",
40
+ tsConfig: {
41
+ include: [],
42
+ exclude: [],
43
+ files: []
44
+ }
45
+ },
46
+ modules: {
47
+ include: ["./src/modules"],
48
+ alias: "#modules"
49
+ },
50
+ libraries: {
51
+ include: [],
52
+ alias: "#library"
53
+ },
54
+ apps: { include: [] }
55
+ };
6
56
 
7
- ${B(`ArchicatModuleImplDependencies`,t)}
57
+ //#endregion
58
+ //#region src-internal/tsconfig/tsconfig-utils.ts
59
+ function readTsconfigFile(tsconfigPath) {
60
+ const raw = fs.readFileSync(tsconfigPath, "utf8");
61
+ const parsed = ts.parseConfigFileTextToJson(tsconfigPath, raw);
62
+ if (parsed.error) throw new Error(formatTsDiagnostic(parsed.error));
63
+ if (parsed.config == null || typeof parsed.config !== "object" || Array.isArray(parsed.config)) throw new Error(`Invalid tsconfig object: ${tsconfigPath}`);
64
+ return parsed.config;
65
+ }
66
+ function readTsconfigCompilerOptions(tsconfig, source = "tsconfig") {
67
+ const compilerOptions = tsconfig.compilerOptions;
68
+ if (compilerOptions == null) return {};
69
+ if (typeof compilerOptions !== "object" || Array.isArray(compilerOptions)) throw new Error(`Tsconfig compilerOptions must be an object: ${source}`);
70
+ return compilerOptions;
71
+ }
72
+ function readTsconfigExtendsPaths(tsconfig, tsconfigPath) {
73
+ const extended = tsconfig.extends;
74
+ if (extended === void 0) return [];
75
+ if (typeof extended === "string") return [resolveTsconfigExtends(tsconfigPath, extended)];
76
+ if (Array.isArray(extended) && extended.every((entry) => typeof entry === "string")) return extended.map((entry) => resolveTsconfigExtends(tsconfigPath, entry));
77
+ throw new Error(`Tsconfig extends must be a string or string array: ${tsconfigPath}`);
78
+ }
79
+ function resolveProjectTsconfig(rootDir, configuredExtends) {
80
+ if (path.isAbsolute(configuredExtends) || configuredExtends.startsWith(".")) return resolveTsconfigFile(path.resolve(rootDir, configuredExtends), configuredExtends);
81
+ return resolvePackageTsconfig(createRequire(path.join(rootDir, ArchicatDefaults.configFileName)), configuredExtends, configuredExtends);
82
+ }
83
+ function resolveTsconfigExtends(tsconfigPath, extended) {
84
+ if (path.isAbsolute(extended) || extended.startsWith(".")) return resolveTsconfigFile(path.resolve(path.dirname(tsconfigPath), extended), tsconfigPath);
85
+ return resolvePackageTsconfig(createRequire(tsconfigPath), extended, tsconfigPath);
86
+ }
87
+ function resolvePackageTsconfig(resolver, value, source) {
88
+ for (const candidate of makeTsconfigCandidates(value)) try {
89
+ return resolver.resolve(candidate);
90
+ } catch {
91
+ continue;
92
+ }
93
+ throw new Error(`Unable to resolve tsconfig extends "${value}": ${source}`);
94
+ }
95
+ function resolveTsconfigFile(candidate, source) {
96
+ for (const entry of makeTsconfigCandidates(candidate)) if (fs.existsSync(entry) && fs.statSync(entry).isFile()) return entry;
97
+ throw new Error(`Unable to resolve tsconfig extends "${candidate}": ${source}`);
98
+ }
99
+ function makeTsconfigCandidates(value) {
100
+ return [
101
+ value,
102
+ `${value}.json`,
103
+ path.join(value, ArchicatDefaults.typescript.consumerTsconfigFileName)
104
+ ];
105
+ }
106
+ function formatTsDiagnostic(diagnostic) {
107
+ const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
108
+ if (diagnostic.file && diagnostic.start !== void 0) {
109
+ const position = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
110
+ return `${diagnostic.file.fileName}:${position.line + 1}:${position.character + 1} - ${message}`;
111
+ }
112
+ return message;
113
+ }
114
+
115
+ //#endregion
116
+ //#region src-internal/configuration/load-config.ts
117
+ async function loadArchicatConfig(cwd, configFileName = ArchicatDefaults.configFileName) {
118
+ const configFilePath = path.resolve(cwd, configFileName);
119
+ if (!fs.existsSync(configFilePath)) throw new Error(`Archicat config was not found: ${configFilePath}`);
120
+ const config = await importDefault$1(configFilePath, cwd);
121
+ assertArchicatConfig(config, configFilePath);
122
+ const resolvedConfig = resolveConfig(config);
123
+ assertDistinctReservedAliases(resolvedConfig.modules.alias, resolvedConfig.libraries.alias, configFilePath);
124
+ const rootDir = path.resolve(cwd, resolvedConfig.root);
125
+ const outDir = path.resolve(rootDir, resolvedConfig.outDir);
126
+ const reportsDir = path.resolve(outDir, ArchicatDefaults.generated.reportsDirName);
127
+ const tsconfigPath = resolveTsconfigPath(rootDir, resolvedConfig.typescript.tsConfig.extends);
128
+ return {
129
+ configFilePath,
130
+ rootDir,
131
+ outDir,
132
+ reportsDir,
133
+ ...tsconfigPath ? { tsconfigPath } : {},
134
+ resolvedConfig
135
+ };
136
+ }
137
+ async function importDefault$1(filePath, rootDir) {
138
+ return await createJiti(rootDir, {
139
+ interopDefault: true,
140
+ extensions: [
141
+ ".js",
142
+ ".cjs",
143
+ ".mjs",
144
+ ".ts",
145
+ ".cts",
146
+ ".mts",
147
+ ".json"
148
+ ]
149
+ }).import(filePath, { default: true });
150
+ }
151
+ function resolveConfig(config) {
152
+ return {
153
+ root: config.root ?? ArchicatDefaults.root,
154
+ outDir: config.outDir ?? ArchicatDefaults.outDir,
155
+ typescript: resolveTypeScriptConfig(config),
156
+ alias: {
157
+ ...ArchicatDefaults.alias,
158
+ ...config.alias ?? {}
159
+ },
160
+ modules: resolveModulesConfig(config.modules),
161
+ libraries: resolveLibrariesConfig(config.libraries),
162
+ apps: resolveAppsConfig(config.apps)
163
+ };
164
+ }
165
+ function resolveTypeScriptConfig(config) {
166
+ const tsConfig = config.typescript?.tsConfig;
167
+ const defaults = ArchicatDefaults.typescript.tsConfig;
168
+ const resolvedTsConfig = {
169
+ include: [...tsConfig?.include ?? defaults.include],
170
+ exclude: [...tsConfig?.exclude ?? defaults.exclude],
171
+ files: [...tsConfig?.files ?? defaults.files]
172
+ };
173
+ if (tsConfig?.extends) resolvedTsConfig.extends = tsConfig.extends;
174
+ return { tsConfig: resolvedTsConfig };
175
+ }
176
+ function resolveModulesConfig(config) {
177
+ return {
178
+ include: [...config?.include ?? ArchicatDefaults.modules.include],
179
+ alias: config?.alias ?? ArchicatDefaults.modules.alias
180
+ };
181
+ }
182
+ function resolveLibrariesConfig(config) {
183
+ return {
184
+ include: [...config?.include ?? ArchicatDefaults.libraries.include],
185
+ alias: config?.alias ?? ArchicatDefaults.libraries.alias
186
+ };
187
+ }
188
+ function resolveAppsConfig(config) {
189
+ return { include: [...config?.include ?? ArchicatDefaults.apps.include] };
190
+ }
191
+ function resolveTsconfigPath(rootDir, configuredExtends) {
192
+ if (!configuredExtends) return;
193
+ return resolveProjectTsconfig(rootDir, configuredExtends);
194
+ }
195
+ function assertArchicatConfig(input, filePath) {
196
+ if (input == null || typeof input !== "object") throw new Error(`Invalid Archicat config: ${filePath}`);
197
+ const config = input;
198
+ assertOptionalNonEmptyString$1(config.root, "root", filePath);
199
+ assertOptionalNonEmptyString$1(config.outDir, "outDir", filePath);
200
+ assertOptionalObject(config.typescript, "typescript", filePath);
201
+ assertOptionalObject(config.modules, "modules", filePath);
202
+ assertOptionalObject(config.libraries, "libraries", filePath);
203
+ assertOptionalObject(config.apps, "apps", filePath);
204
+ assertOptionalTsConfig(config.typescript?.tsConfig, filePath);
205
+ assertOptionalInclude(config.modules?.include, "modules.include", filePath);
206
+ assertOptionalInclude(config.libraries?.include, "libraries.include", filePath);
207
+ assertOptionalInclude(config.apps?.include, "apps.include", filePath);
208
+ assertOptionalImportAlias(config.modules?.alias, "modules.alias", filePath);
209
+ assertOptionalImportAlias(config.libraries?.alias, "libraries.alias", filePath);
210
+ assertOptionalAlias(config.alias, "alias", filePath);
211
+ }
212
+ function assertOptionalTsConfig(value, filePath) {
213
+ if (value === void 0) return;
214
+ if (value == null || typeof value !== "object" || Array.isArray(value)) throw new Error(`Archicat config typescript.tsConfig must be an object: ${filePath}`);
215
+ const config = value;
216
+ assertOptionalNonEmptyString$1(config.extends, "typescript.tsConfig.extends", filePath);
217
+ assertOptionalInclude(config.include, "typescript.tsConfig.include", filePath);
218
+ assertOptionalInclude(config.exclude, "typescript.tsConfig.exclude", filePath);
219
+ assertOptionalInclude(config.files, "typescript.tsConfig.files", filePath);
220
+ assertNoUnsupportedCompilerOptions(config.compilerOptions, filePath);
221
+ }
222
+ function assertNoUnsupportedCompilerOptions(value, filePath) {
223
+ if (value === void 0) return;
224
+ if (value == null || typeof value !== "object" || Array.isArray(value)) throw new Error(`Archicat config typescript.tsConfig.compilerOptions must be an object: ${filePath}`);
225
+ const compilerOptions = value;
226
+ if (Object.hasOwn(compilerOptions, "paths")) throw new Error("Archicat config typescript.tsConfig.compilerOptions.paths is not supported. Move aliases into archicat.config.ts alias.");
227
+ if (Object.hasOwn(compilerOptions, "baseUrl")) throw new Error("Archicat config typescript.tsConfig.compilerOptions.baseUrl is not supported. Move aliases into archicat.config.ts alias.");
228
+ if (Object.keys(compilerOptions).length > 0) throw new Error("Archicat config typescript.tsConfig.compilerOptions is not supported. Put compiler options in the base or app tsconfig.");
229
+ }
230
+ function assertOptionalNonEmptyString$1(value, key, filePath) {
231
+ if (value !== void 0 && (typeof value !== "string" || value.trim() === "")) throw new Error(`Archicat ${key} must be a non-empty string when defined: ${filePath}`);
232
+ }
233
+ function assertOptionalInclude(value, key, filePath) {
234
+ if (value !== void 0 && (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.trim() === ""))) throw new Error(`Archicat config ${key} must be an array of non-empty strings: ${filePath}`);
235
+ }
236
+ function assertOptionalImportAlias(value, key, filePath) {
237
+ if (value === void 0) return;
238
+ if (typeof value !== "string" || value.trim() === "" || value.includes("*") || value.endsWith("/")) throw new Error(`Archicat config ${key} must be a non-empty alias without wildcard or trailing slash: ${filePath}`);
239
+ }
240
+ function assertOptionalAlias(value, key, filePath) {
241
+ if (value === void 0) return;
242
+ if (value == null || typeof value !== "object" || Array.isArray(value)) throw new Error(`Archicat config ${key} must be an object of non-empty string aliases: ${filePath}`);
243
+ for (const [alias, target] of Object.entries(value)) if (alias.trim() === "" || typeof target !== "string" || target.trim() === "") throw new Error(`Archicat config ${key} must contain non-empty string aliases: ${filePath}`);
244
+ }
245
+ function assertOptionalObject(value, key, filePath) {
246
+ if (value !== void 0 && (value == null || typeof value !== "object" || Array.isArray(value))) throw new Error(`Archicat config ${key} must be an object: ${filePath}`);
247
+ }
248
+ function assertDistinctReservedAliases(moduleAlias, libraryAlias, filePath) {
249
+ if (moduleAlias === libraryAlias || moduleAlias.startsWith(`${libraryAlias}/`) || libraryAlias.startsWith(`${moduleAlias}/`)) throw new Error(`Archicat module and library aliases must use separate roots: "${moduleAlias}" and "${libraryAlias}" (${filePath})`);
250
+ }
251
+
252
+ //#endregion
253
+ //#region src-internal/path/path-utils.ts
254
+ function normalizePath(value) {
255
+ return value.split(path.sep).join("/");
256
+ }
257
+ function toPosixRelativeImport(fromFilePath, targetFilePath) {
258
+ const fromDir = path.dirname(fromFilePath);
259
+ const parsedTarget = path.parse(targetFilePath);
260
+ const targetWithoutExtension = path.join(parsedTarget.dir, parsedTarget.name);
261
+ let relative = normalizePath(path.relative(fromDir, targetWithoutExtension));
262
+ if (!relative.startsWith(".")) relative = `./${relative}`;
263
+ return `${relative}.js`;
264
+ }
265
+ function isPathInside(childPath, parentPath) {
266
+ const relative = path.relative(parentPath, childPath);
267
+ return relative === "" || !!relative && !relative.startsWith("..") && !path.isAbsolute(relative);
268
+ }
269
+ function assertSafeGeneratedDirectory(rootDir, directoryPath) {
270
+ const root = resolvePhysicalPath(rootDir);
271
+ const directory = resolvePhysicalPath(directoryPath);
272
+ if (directory === root || !isPathInside(directory, root)) throw new Error(`Archicat outDir must be a directory inside the project root: ${directoryPath}`);
273
+ }
274
+ function resolvePhysicalPath(filePath) {
275
+ const absolutePath = path.resolve(filePath);
276
+ const missing = [];
277
+ let currentPath = absolutePath;
278
+ while (!fs.existsSync(currentPath)) {
279
+ const parentPath = path.dirname(currentPath);
280
+ if (parentPath === currentPath) return absolutePath;
281
+ missing.unshift(path.basename(currentPath));
282
+ currentPath = parentPath;
283
+ }
284
+ return path.join(fs.realpathSync(currentPath), ...missing);
285
+ }
286
+ function stripKnownExtension(filePath) {
287
+ return filePath.replace(/\.(?:js|mjs|cjs|ts|mts|cts|tsx)$/u, "");
288
+ }
289
+ function makeRelativeDisplayPath(rootDir, filePath) {
290
+ return normalizePath(path.relative(rootDir, filePath));
291
+ }
292
+
293
+ //#endregion
294
+ //#region src-internal/definitions/discover-definition-files.ts
295
+ function discoverDefinitionFiles(rootDir, includes, markerFileName, excludedRootPaths = []) {
296
+ const files = includes.flatMap((include) => expandInclude(rootDir, include, markerFileName, excludedRootPaths));
297
+ return Array.from(new Set(files)).sort((a, b) => a.localeCompare(b));
298
+ }
299
+ function expandInclude(rootDir, include, markerFileName, excludedRootPaths) {
300
+ const resolved = path.resolve(rootDir, include);
301
+ if (include.includes("*")) return expandSingleStarPattern(rootDir, include).filter((filePath) => !excludedRootPaths.some((excludedRootPath) => isPathInside(filePath, excludedRootPath))).filter((filePath) => path.basename(filePath) === markerFileName);
302
+ if (!fs.existsSync(resolved)) return [];
303
+ const stat = fs.statSync(resolved);
304
+ if (stat.isFile()) return path.basename(resolved) === markerFileName ? [resolved] : [];
305
+ if (!stat.isDirectory()) return [];
306
+ return findMarkerFiles(resolved, markerFileName, excludedRootPaths);
307
+ }
308
+ function findMarkerFiles(rootPath, markerFileName, excludedRootPaths) {
309
+ const result = [];
310
+ const entries = fs.readdirSync(rootPath, { withFileTypes: true });
311
+ for (const entry of entries) {
312
+ if (shouldSkip(entry.name)) continue;
313
+ const entryPath = path.join(rootPath, entry.name);
314
+ if (excludedRootPaths.some((excludedRootPath) => isPathInside(entryPath, excludedRootPath))) continue;
315
+ if (entry.isDirectory()) {
316
+ result.push(...findMarkerFiles(entryPath, markerFileName, excludedRootPaths));
317
+ continue;
318
+ }
319
+ if (entry.isFile() && entry.name === markerFileName) result.push(entryPath);
320
+ }
321
+ return result;
322
+ }
323
+ function expandSingleStarPattern(rootDir, pattern) {
324
+ const parts = path.resolve(rootDir, pattern).split(path.sep);
325
+ if (parts.filter((part) => part.includes("*")).length !== 1) throw new Error(`Archicat supports exactly one wildcard segment per include pattern: ${pattern}`);
326
+ const starIndex = parts.findIndex((part) => part.includes("*"));
327
+ const beforeStar = parts.slice(0, starIndex).join(path.sep) || path.sep;
328
+ const wildcard = parts[starIndex] ?? "*";
329
+ const afterStar = parts.slice(starIndex + 1);
330
+ const regexp = makeWildcardRegExp(wildcard);
331
+ if (!fs.existsSync(beforeStar)) return [];
332
+ return fs.readdirSync(beforeStar, { withFileTypes: true }).filter((entry) => entry.isDirectory()).filter((entry) => regexp.test(entry.name)).map((entry) => path.join(beforeStar, entry.name, ...afterStar)).filter((candidate) => fs.existsSync(candidate) && fs.statSync(candidate).isFile());
333
+ }
334
+ function makeWildcardRegExp(pattern) {
335
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/gu, "\\$&").replace(/\*/gu, ".*");
336
+ return new RegExp(`^${escaped}$`, "u");
337
+ }
338
+ function shouldSkip(name) {
339
+ return ArchicatDefaults.generated.ignoredDirectoryNames.includes(name);
340
+ }
341
+
342
+ //#endregion
343
+ //#region src-internal/definitions/load-definition.ts
344
+ async function loadArchicatDefinition(filePath, kind) {
345
+ switch (kind) {
346
+ case "module": return loadArchicatModule(filePath);
347
+ case "library": return loadArchicatLibrary(filePath);
348
+ case "app": return loadArchicatApp(filePath);
349
+ }
350
+ }
351
+ async function loadArchicatModule(filePath) {
352
+ const contract = await importDefault(filePath, path.dirname(filePath));
353
+ assertArchicatDefinition(contract, filePath, "module");
354
+ return {
355
+ kind: "module",
356
+ contractFilePath: filePath,
357
+ definitionDir: path.dirname(filePath),
358
+ contract
359
+ };
360
+ }
361
+ async function loadArchicatLibrary(filePath) {
362
+ const contract = await importDefault(filePath, path.dirname(filePath));
363
+ assertArchicatDefinition(contract, filePath, "library");
364
+ return {
365
+ kind: "library",
366
+ contractFilePath: filePath,
367
+ definitionDir: path.dirname(filePath),
368
+ contract
369
+ };
370
+ }
371
+ async function loadArchicatApp(filePath) {
372
+ const contract = await importDefault(filePath, path.dirname(filePath));
373
+ assertArchicatDefinition(contract, filePath, "app");
374
+ return {
375
+ kind: "app",
376
+ contractFilePath: filePath,
377
+ definitionDir: path.dirname(filePath),
378
+ contract
379
+ };
380
+ }
381
+ async function importDefault(filePath, rootDir) {
382
+ return await createJiti(rootDir, {
383
+ interopDefault: true,
384
+ extensions: [
385
+ ".js",
386
+ ".cjs",
387
+ ".mjs",
388
+ ".ts",
389
+ ".cts",
390
+ ".mts",
391
+ ".json"
392
+ ]
393
+ }).import(filePath, { default: true });
394
+ }
395
+ function assertArchicatDefinition(input, filePath, expectedKind) {
396
+ if (input == null || typeof input !== "object") throw new Error(`Invalid Archicat ${expectedKind} definition: ${filePath}`);
397
+ const definition = input;
398
+ if (definition.kind !== expectedKind) throw new Error(`Archicat ${expectedKind} file must export a ${expectedKind} definition: ${filePath}`);
399
+ if (typeof definition.name !== "string" || definition.name.trim() === "") throw new Error(`Archicat ${expectedKind} must define a non-empty name: ${filePath}`);
400
+ if (expectedKind === "app") {
401
+ assertOptionalNonEmptyString(definition.root, "root", filePath);
402
+ assertDependencyArray(definition.dependencies, filePath, expectedKind);
403
+ return;
404
+ }
405
+ const surfaced = definition;
406
+ assertSurface(surfaced.api, "api", filePath, expectedKind);
407
+ assertSurface(surfaced.impl, "impl", filePath, expectedKind);
408
+ }
409
+ function assertSurface(surface, surfaceName, filePath, definitionKind) {
410
+ if (surface == null || typeof surface !== "object") throw new Error(`Archicat ${definitionKind}.${surfaceName} must be a surface object: ${filePath}`);
411
+ assertOptionalNonEmptyString(surface.root, `${surfaceName}.root`, filePath);
412
+ assertDependencyArray(surface.dependencies, filePath, `${definitionKind}.${surfaceName}`);
413
+ }
414
+ function assertDependencyArray(value, filePath, owner) {
415
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.trim() === "")) throw new Error(`Archicat ${owner} dependencies must be an array of non-empty strings: ${filePath}`);
416
+ }
417
+ function assertOptionalNonEmptyString(value, key, filePath) {
418
+ if (value !== void 0 && (typeof value !== "string" || value.trim() === "")) throw new Error(`Archicat ${key} must be a non-empty string when defined: ${filePath}`);
419
+ }
420
+
421
+ //#endregion
422
+ //#region src-internal/dependency-graph/detect-dependency-cycles.ts
423
+ function assertNoDependencyCycles(dependencies) {
424
+ const graph = /* @__PURE__ */ new Map();
425
+ for (const dependency of dependencies) {
426
+ const targets = graph.get(dependency.from) ?? [];
427
+ targets.push(dependency.to);
428
+ graph.set(dependency.from, targets);
429
+ }
430
+ const visiting = /* @__PURE__ */ new Set();
431
+ const visited = /* @__PURE__ */ new Set();
432
+ for (const node of graph.keys()) visit(node, graph, visiting, visited, []);
433
+ }
434
+ function visit(node, graph, visiting, visited, stack) {
435
+ if (visited.has(node)) return;
436
+ if (visiting.has(node)) {
437
+ const cycleStart = stack.indexOf(node);
438
+ const cycle = [...stack.slice(cycleStart < 0 ? 0 : cycleStart), node];
439
+ throw new Error(`Cyclic Archicat dependency detected: ${cycle.join(" -> ")}`);
440
+ }
441
+ visiting.add(node);
442
+ for (const target of graph.get(node) ?? []) visit(target, graph, visiting, visited, [...stack, node]);
443
+ visiting.delete(node);
444
+ visited.add(node);
445
+ }
446
+
447
+ //#endregion
448
+ //#region src-internal/dependency-graph/parse-dependency-target.ts
449
+ function parseDependencyTarget(target) {
450
+ const match = /^(module|library)\.([a-z][a-z0-9-]*)\.(api|impl)$/u.exec(target);
451
+ if (!match) return;
452
+ return {
453
+ kind: match[1],
454
+ name: match[2],
455
+ surface: match[3]
456
+ };
457
+ }
458
+
459
+ //#endregion
460
+ //#region src-internal/dependency-graph/validate-dependency-rules.ts
461
+ function validateDeclaredDependency(owner, dependency, knownTargets) {
462
+ const target = parseDependencyTarget(dependency);
463
+ if (!target) throw new Error(`${formatOwner$1(owner)} declares invalid dependency target "${dependency}".`);
464
+ if (!knownTargets.has(dependency)) throw new Error(`${formatOwner$1(owner)} declares unknown dependency "${dependency}".`);
465
+ if (owner.target === dependency) throw new Error(`${formatOwner$1(owner)} cannot depend on itself: ${dependency}`);
466
+ if (owner.surface === "api" && target.surface === "impl") throw new Error(`${formatOwner$1(owner)} cannot depend on implementation target "${dependency}" from an API surface.`);
467
+ }
468
+ function formatOwner$1(owner) {
469
+ if (owner.kind === "app") return `App "${owner.name}"`;
470
+ return `${capitalize$2(owner.kind)} "${owner.name}" ${owner.surface}`;
471
+ }
472
+ function capitalize$2(value) {
473
+ return `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
474
+ }
475
+
476
+ //#endregion
477
+ //#region src-internal/resolver/resolve-project.ts
478
+ function resolveArchicatProject(loadedConfig, loadedDefinitions) {
479
+ const modules = loadedDefinitions.filter((definition) => definition.kind === "module").map((definition) => resolveModule(loadedConfig, definition));
480
+ const libraries = loadedDefinitions.filter((definition) => definition.kind === "library").map((definition) => resolveLibrary(loadedConfig, definition));
481
+ const apps = loadedDefinitions.filter((definition) => definition.kind === "app").map((definition) => resolveApp(definition));
482
+ const definitions = [...modules, ...libraries];
483
+ assertUniqueDefinitionNames([...definitions, ...apps]);
484
+ const graph = buildGraph(definitions, apps);
485
+ assertDependencyReferences(definitions, apps, graph.targets);
486
+ assertNoDependencyCycles(graph.dependencies);
487
+ return {
488
+ rootDir: loadedConfig.rootDir,
489
+ outDir: loadedConfig.outDir,
490
+ reportsDir: loadedConfig.reportsDir,
491
+ ...loadedConfig.tsconfigPath ? { tsconfigPath: loadedConfig.tsconfigPath } : {},
492
+ configFilePath: loadedConfig.configFilePath,
493
+ config: loadedConfig.resolvedConfig,
494
+ modules,
495
+ libraries,
496
+ apps,
497
+ definitions,
498
+ graph
499
+ };
500
+ }
501
+ function resolveModule(loadedConfig, loadedModule) {
502
+ const { contract, contractFilePath, definitionDir } = loadedModule;
503
+ assertValidName(contract.name, contractFilePath, "module");
504
+ const apiRootPath = contract.api.root ? resolveDeclaredRoot(definitionDir, contract.api.root, "api", contract.name) : void 0;
505
+ const implRootPath = contract.impl.root ? resolveDeclaredRoot(definitionDir, contract.impl.root, "impl", contract.name) : void 0;
506
+ const aliasBase = `${loadedConfig.resolvedConfig.modules.alias}/${contract.name}`;
507
+ const apiAlias = `${aliasBase}/api`;
508
+ const implAlias = `${aliasBase}/impl`;
509
+ return {
510
+ kind: "module",
511
+ name: contract.name,
512
+ apiTarget: `module.${contract.name}.api`,
513
+ implTarget: `module.${contract.name}.impl`,
514
+ alias: apiAlias,
515
+ aliasGlob: `${apiAlias}/*`,
516
+ implAlias,
517
+ implAliasGlob: `${implAlias}/*`,
518
+ contractFilePath,
519
+ definitionDir,
520
+ api: resolveSurface(apiRootPath, contract.api.dependencies, path.join(loadedConfig.outDir, ArchicatDefaults.generated.modulesDirName, contract.name, "api")),
521
+ impl: resolveSurface(implRootPath, contract.impl.dependencies, path.join(loadedConfig.outDir, ArchicatDefaults.generated.modulesDirName, contract.name, "impl"))
522
+ };
523
+ }
524
+ function resolveLibrary(loadedConfig, loadedLibrary) {
525
+ const { contract, contractFilePath, definitionDir } = loadedLibrary;
526
+ assertValidName(contract.name, contractFilePath, "library");
527
+ const apiRootPath = contract.api.root ? resolveDeclaredRoot(definitionDir, contract.api.root, "api", contract.name) : void 0;
528
+ const implRootPath = contract.impl.root ? resolveDeclaredRoot(definitionDir, contract.impl.root, "impl", contract.name) : void 0;
529
+ const aliasBase = `${loadedConfig.resolvedConfig.libraries.alias}/${contract.name}`;
530
+ const apiAlias = `${aliasBase}/api`;
531
+ const implAlias = `${aliasBase}/impl`;
532
+ return {
533
+ kind: "library",
534
+ name: contract.name,
535
+ apiTarget: `library.${contract.name}.api`,
536
+ implTarget: `library.${contract.name}.impl`,
537
+ alias: apiAlias,
538
+ aliasGlob: `${apiAlias}/*`,
539
+ implAlias,
540
+ implAliasGlob: `${implAlias}/*`,
541
+ contractFilePath,
542
+ definitionDir,
543
+ api: resolveSurface(apiRootPath, contract.api.dependencies, path.join(loadedConfig.outDir, ArchicatDefaults.generated.librariesDirName, contract.name, "api")),
544
+ impl: resolveSurface(implRootPath, contract.impl.dependencies, path.join(loadedConfig.outDir, ArchicatDefaults.generated.librariesDirName, contract.name, "impl"))
545
+ };
546
+ }
547
+ function resolveApp(loadedApp) {
548
+ const { contract, contractFilePath, definitionDir } = loadedApp;
549
+ assertValidName(contract.name, contractFilePath, "app");
550
+ return {
551
+ kind: "app",
552
+ name: contract.name,
553
+ target: `app.${contract.name}`,
554
+ contractFilePath,
555
+ rootPath: contract.root ? resolveDeclaredRoot(definitionDir, contract.root, "app", contract.name) : definitionDir,
556
+ dependencies: [...contract.dependencies]
557
+ };
558
+ }
559
+ function resolveSurface(rootPath, dependencies, mirrorRootPath) {
560
+ return {
561
+ ...rootPath ? { rootPath } : {},
562
+ mirrorRootPath,
563
+ dependencies: [...dependencies]
564
+ };
565
+ }
566
+ function resolveDeclaredRoot(definitionDir, relativeRoot, kind, name) {
567
+ const resolved = path.resolve(definitionDir, relativeRoot);
568
+ if (!fs.existsSync(resolved)) throw new Error(`Definition "${name}" declares ${kind} root that does not exist: ${resolved}`);
569
+ if (!fs.statSync(resolved).isDirectory()) throw new Error(`Definition "${name}" declares ${kind} root that is not a directory: ${resolved}`);
570
+ return resolved;
571
+ }
572
+ function buildGraph(definitions, apps) {
573
+ const targets = definitions.flatMap((definition) => [{
574
+ key: definition.apiTarget,
575
+ surface: "api"
576
+ }, {
577
+ key: definition.implTarget,
578
+ surface: "impl"
579
+ }]);
580
+ const declaredDependencies = [...definitions.flatMap((definition) => [...definition.api.dependencies.map((dependency) => ({
581
+ from: definition.apiTarget,
582
+ to: dependency,
583
+ origin: "declared"
584
+ })), ...definition.impl.dependencies.map((dependency) => ({
585
+ from: definition.implTarget,
586
+ to: dependency,
587
+ origin: "declared"
588
+ }))]), ...apps.flatMap((app) => app.dependencies.map((dependency) => ({
589
+ from: app.target,
590
+ to: dependency,
591
+ origin: "declared"
592
+ })))];
593
+ return {
594
+ targets,
595
+ dependencies: [...definitions.map((definition) => ({
596
+ from: definition.implTarget,
597
+ to: definition.apiTarget,
598
+ origin: "derived"
599
+ })), ...declaredDependencies]
600
+ };
601
+ }
602
+ function assertValidName(name, filePath, kind) {
603
+ if (!/^[a-z][a-z0-9-]*$/u.test(name)) throw new Error(`Invalid Archicat ${kind} name "${name}" in ${filePath}. Use ^[a-z][a-z0-9-]*$`);
604
+ }
605
+ function assertUniqueDefinitionNames(definitions) {
606
+ const seen = /* @__PURE__ */ new Map();
607
+ for (const definition of definitions) {
608
+ const key = `${definition.kind}.${definition.name}`;
609
+ const current = seen.get(key);
610
+ if (current) throw new Error(`Duplicate Archicat ${definition.kind} name "${definition.name}" in ${current} and ${definition.contractFilePath}`);
611
+ seen.set(key, definition.contractFilePath);
612
+ }
613
+ }
614
+ function assertDependencyReferences(definitions, apps, targets) {
615
+ const targetKeys = new Set(targets.map((target) => target.key));
616
+ for (const definition of definitions) {
617
+ for (const dependency of definition.api.dependencies) validateDeclaredDependency(makeOwner(definition, "api"), dependency, targetKeys);
618
+ for (const dependency of definition.impl.dependencies) validateDeclaredDependency(makeOwner(definition, "impl"), dependency, targetKeys);
619
+ }
620
+ for (const app of apps) for (const dependency of app.dependencies) validateDeclaredDependency({
621
+ kind: "app",
622
+ name: app.name,
623
+ surface: "app",
624
+ target: app.target
625
+ }, dependency, targetKeys);
626
+ }
627
+ function makeOwner(definition, surface) {
628
+ return {
629
+ kind: definition.kind,
630
+ name: definition.name,
631
+ surface,
632
+ target: surface === "api" ? definition.apiTarget : definition.implTarget
633
+ };
634
+ }
635
+
636
+ //#endregion
637
+ //#region src-internal/context/load-project.ts
638
+ async function loadArchicatProject(cwd, configFileName) {
639
+ const loadedConfig = await loadArchicatConfig(cwd, configFileName);
640
+ const moduleFiles = discoverDefinitionFiles(loadedConfig.rootDir, loadedConfig.resolvedConfig.modules.include, ArchicatDefaults.definitions.moduleFileName, [loadedConfig.outDir]);
641
+ const libraryFiles = discoverDefinitionFiles(loadedConfig.rootDir, loadedConfig.resolvedConfig.libraries.include, ArchicatDefaults.definitions.libraryFileName, [loadedConfig.outDir]);
642
+ const appFiles = discoverDefinitionFiles(loadedConfig.rootDir, loadedConfig.resolvedConfig.apps.include, ArchicatDefaults.definitions.appFileName, [loadedConfig.outDir]);
643
+ if (moduleFiles.length === 0 && libraryFiles.length === 0 && appFiles.length === 0) throw new Error("No Archicat definition files matched configured include roots.");
644
+ const loadedDefinitions = [
645
+ ...await Promise.all(moduleFiles.map((file) => loadArchicatDefinition(file, "module"))),
646
+ ...await Promise.all(libraryFiles.map((file) => loadArchicatDefinition(file, "library"))),
647
+ ...await Promise.all(appFiles.map((file) => loadArchicatDefinition(file, "app")))
648
+ ];
649
+ return resolveArchicatProject(loadedConfig, loadedDefinitions);
650
+ }
651
+
652
+ //#endregion
653
+ //#region src-internal/graph/format-project-graph.ts
654
+ function formatProjectGraph(project) {
655
+ const lines = [];
656
+ pushSection(lines, "Modules", project.modules.length, project.modules, project.graph.dependencies);
657
+ pushSection(lines, "Libraries", project.libraries.length, project.libraries, project.graph.dependencies);
658
+ lines.push(`Apps: ${project.apps.length}`);
659
+ if (project.apps.length > 0) lines.push("");
660
+ for (const app of project.apps) {
661
+ lines.push(app.name);
662
+ lines.push(` app: ${app.target}`);
663
+ pushDependencies(lines, project.graph.dependencies.filter((dependency) => dependency.from === app.target), " dependsOn");
664
+ lines.push("");
665
+ }
666
+ return trimTrailingEmptyLines(lines);
667
+ }
668
+ function pushSection(lines, title, count, definitions, dependencies) {
669
+ lines.push(`${title}: ${count}`);
670
+ if (definitions.length > 0) lines.push("");
671
+ for (const definition of definitions) {
672
+ lines.push(definition.name);
673
+ lines.push(` api: ${definition.apiTarget}`);
674
+ pushDependencies(lines, dependencies.filter((dependency) => dependency.from === definition.apiTarget), " api dependsOn");
675
+ lines.push(` impl: ${definition.implTarget}`);
676
+ pushDependencies(lines, dependencies.filter((dependency) => dependency.from === definition.implTarget), " impl dependsOn");
677
+ lines.push("");
678
+ }
679
+ }
680
+ function pushDependencies(lines, dependencies, label) {
681
+ if (dependencies.length === 0) {
682
+ lines.push(`${label}: none`);
683
+ return;
684
+ }
685
+ lines.push(`${label}:`);
686
+ for (const dependency of dependencies) {
687
+ const suffix = dependency.origin === "derived" ? " (derived)" : "";
688
+ lines.push(` ${dependency.to}${suffix}`);
689
+ }
690
+ }
691
+ function trimTrailingEmptyLines(lines) {
692
+ while (lines.at(-1) === "") lines.pop();
693
+ return lines;
694
+ }
695
+
696
+ //#endregion
697
+ //#region src-cli/commands/run-graph-command.ts
698
+ async function runGraphCommand(options, cwd) {
699
+ const project = await loadArchicatProject(cwd, options.config);
700
+ return {
701
+ exitCode: 0,
702
+ lines: formatProjectGraph(project).map((message) => ({
703
+ kind: "info",
704
+ message
705
+ }))
706
+ };
707
+ }
708
+
709
+ //#endregion
710
+ //#region src-cli/output/cli-output.ts
711
+ var CliOutput = class {
712
+ log(message) {
713
+ consola.log(message);
714
+ }
715
+ info(message) {
716
+ consola.info(message);
717
+ }
718
+ success(message) {
719
+ consola.success(message);
720
+ }
721
+ warning(message) {
722
+ consola.warn(message);
723
+ }
724
+ error(message) {
725
+ consola.error(message);
726
+ }
727
+ emptyLine() {
728
+ consola.log("");
729
+ }
730
+ title(product, command, rows = []) {
731
+ consola.log([`${product} ${command}`, ...formatRows(rows)].join("\n"));
732
+ }
733
+ panel(title, rows, badge) {
734
+ const heading = badge === void 0 ? title : `${title} (${badge})`;
735
+ consola.log([heading, ...formatRows(rows)].join("\n"));
736
+ }
737
+ step(label, message) {
738
+ return `${label}: ${message}`;
739
+ }
740
+ duration(milliseconds) {
741
+ if (milliseconds < 1e3) return `${milliseconds}ms`;
742
+ const seconds = milliseconds / 1e3;
743
+ return `${seconds.toFixed(seconds < 10 ? 2 : 1)}s`;
744
+ }
745
+ };
746
+ function formatRows(rows) {
747
+ if (rows.length === 0) return [];
748
+ const width = Math.max(...rows.map((row) => row.label.length));
749
+ return rows.map((row) => ` ${row.label.padEnd(width)} ${String(row.value)}`);
750
+ }
751
+
752
+ //#endregion
753
+ //#region src-internal/doctor/doctor.ts
754
+ function doctorProject(project) {
755
+ return checkPhysicalOmissions(project);
756
+ }
757
+ function checkPhysicalOmissions(project) {
758
+ const issues = [];
759
+ for (const definition of project.definitions) {
760
+ const apiPath = path.join(definition.definitionDir, "api");
761
+ const implPath = path.join(definition.definitionDir, "impl");
762
+ if (!definition.api.rootPath && fs.existsSync(apiPath)) issues.push(`${capitalize$1(definition.kind)} "${definition.name}" has a physical api directory but its contract omits api.root. Archicat treats the public API as empty.`);
763
+ if (!definition.impl.rootPath && fs.existsSync(implPath)) issues.push(`${capitalize$1(definition.kind)} "${definition.name}" has a physical impl directory but its contract omits impl.root. Archicat treats the implementation as no-op.`);
764
+ }
765
+ return issues;
766
+ }
767
+ function capitalize$1(value) {
768
+ return `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
769
+ }
770
+
771
+ //#endregion
772
+ //#region src-internal/generator/file-writer.ts
773
+ function resetDirectory(rootDir, directoryPath) {
774
+ assertSafeGeneratedDirectory(rootDir, directoryPath);
775
+ assertReplaceableDirectory(rootDir, directoryPath);
776
+ fs.rmSync(directoryPath, {
777
+ recursive: true,
778
+ force: true
779
+ });
780
+ fs.mkdirSync(directoryPath, { recursive: true });
781
+ }
782
+ function writeTextFile(filePath, content) {
783
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
784
+ fs.writeFileSync(filePath, content, "utf8");
785
+ }
786
+ function writeJsonFile(filePath, value) {
787
+ writeTextFile(filePath, `${JSON.stringify(value, null, 2)}\n`);
788
+ }
789
+ function assertReplaceableDirectory(rootDir, directoryPath) {
790
+ if (!fs.existsSync(directoryPath) || fs.readdirSync(directoryPath).length === 0) return;
791
+ if (isOwnedGeneratedDirectory(rootDir, directoryPath)) return;
792
+ throw new Error(`Archicat refuses to replace a non-owned outDir: ${directoryPath}`);
793
+ }
794
+ function isOwnedGeneratedDirectory(rootDir, directoryPath) {
795
+ const reportPath = path.join(directoryPath, ArchicatDefaults.generated.reportsDirName, ArchicatDefaults.generated.buildReportFileName);
796
+ if (!fs.existsSync(reportPath)) return false;
797
+ try {
798
+ const report = JSON.parse(fs.readFileSync(reportPath, "utf8"));
799
+ return report.generatedBy === "archicat" && report.schemaVersion === 2 && typeof report.outputs?.outDir === "string" && path.resolve(rootDir, report.outputs.outDir) === path.resolve(directoryPath);
800
+ } catch {
801
+ return false;
802
+ }
803
+ }
804
+
805
+ //#endregion
806
+ //#region src-internal/generator/generate-graph-types.ts
807
+ function generateGraphTypes(project) {
808
+ const allTargets = project.graph.targets.map((target) => target.key);
809
+ const apiTargets = project.graph.targets.filter((target) => target.surface === "api").map((target) => target.key);
810
+ const packageName = ArchicatDefaults.packageName;
811
+ const content = `import '${packageName}';
812
+
813
+ declare module '${packageName}' {
814
+ ${renderInterface("ArchicatModuleApiDependencies", apiTargets)}
8
815
 
9
- ${B(`ArchicatLibraryApiDependencies`,n)}
816
+ ${renderInterface("ArchicatModuleImplDependencies", allTargets)}
10
817
 
11
- ${B(`ArchicatLibraryImplDependencies`,t)}
818
+ ${renderInterface("ArchicatLibraryApiDependencies", apiTargets)}
12
819
 
13
- ${B(`ArchicatAppDependencies`,t)}
820
+ ${renderInterface("ArchicatLibraryImplDependencies", allTargets)}
821
+
822
+ ${renderInterface("ArchicatAppDependencies", allTargets)}
14
823
  }
15
824
 
16
825
  export {};
17
- `;R(r.join(e.outDir,o.generated.typesDirName,`graph.d.ts`),a)}function B(e,t){return` interface ${e} {\n${Array.from(new Set(t)).sort((e,t)=>e.localeCompare(t)).map(e=>` '${e}': true;`).join(`
18
- `)}\n }`}function V(e){return n.existsSync(e)?n.statSync(e).isFile()?H(e)?[e]:[]:U(e).filter(H).sort((e,t)=>e.localeCompare(t)):[]}function H(e){return/\.(?:ts|mts|cts|tsx)$/u.test(e)&&!/\.d\.(?:ts|mts|cts)$/u.test(e)}function U(e){let t=[],i=n.readdirSync(e,{withFileTypes:!0});for(let n of i){let i=r.join(e,n.name);if(n.isDirectory()){t.push(...U(i));continue}n.isFile()&&t.push(i)}return t}function ot(e){let t=i.createSourceFile(e,n.readFileSync(e,`utf8`),i.ScriptTarget.Latest,!0),r=[],a=e=>{if(i.isImportDeclaration(e)&&i.isStringLiteral(e.moduleSpecifier)&&r.push({moduleSpecifier:e.moduleSpecifier.text,kind:`import`}),i.isExportDeclaration(e)&&e.moduleSpecifier&&i.isStringLiteral(e.moduleSpecifier)&&r.push({moduleSpecifier:e.moduleSpecifier.text,kind:`export`}),i.isCallExpression(e)&&e.expression.kind===i.SyntaxKind.ImportKeyword){let[t]=e.arguments;t&&i.isStringLiteral(t)&&r.push({moduleSpecifier:t.text,kind:`dynamic-import`})}i.forEachChild(e,a)};return a(t),r}function st(e){let t=i.createSourceFile(e,n.readFileSync(e,`utf8`),i.ScriptTarget.Latest,!0),r=!1,a=e=>{if(!r){if(i.isExportAssignment(e)&&!e.isExportEquals){r=!0;return}if(ct(e)){r=!0;return}if(i.isExportDeclaration(e)&&e.exportClause&&i.isNamedExports(e.exportClause))for(let t of e.exportClause.elements){let e=t.name.text,n=t.propertyName?.text;if(e==="default"||n==="default"){r=!0;return}}i.forEachChild(e,a)}};return a(t),r}function ct(e){let t=i.canHaveModifiers(e)?i.getModifiers(e):void 0;if(!t)return!1;let n=t.some(e=>e.kind===i.SyntaxKind.ExportKeyword),r=t.some(e=>e.kind===i.SyntaxKind.DefaultKeyword);return n&&r}function lt(e){for(let t of e)ut(t.api),dt(t)}function ut(e){if(!e.rootPath){W(r.join(e.mirrorRootPath,`index.ts`));return}let t=new Set;for(let n of V(e.rootPath)){let i=v(r.relative(e.rootPath,n));t.add(i),G(r.join(e.mirrorRootPath,i),n)}t.has(`index.ts`)||W(r.join(e.mirrorRootPath,`index.ts`))}function dt(e){let t=e.impl.rootPath?ft(e.impl.rootPath):void 0;if(t){G(r.join(e.impl.mirrorRootPath,`index.ts`),t);return}let n=e.kind===`module`?`ArchicatModuleImplementation`:`ArchicatLibraryImplementation`,i=`${K()}
19
- export const ${n} = {
20
- name: '${e.name}',
826
+ `;
827
+ writeTextFile(path.join(project.outDir, ArchicatDefaults.generated.typesDirName, "graph.d.ts"), content);
828
+ }
829
+ function renderInterface(name, entries) {
830
+ return ` interface ${name} {\n${Array.from(new Set(entries)).sort((a, b) => a.localeCompare(b)).map((entry) => ` '${entry}': true;`).join("\n")}\n }`;
831
+ }
832
+
833
+ //#endregion
834
+ //#region src-internal/scanner/file-scanner.ts
835
+ function listTypeScriptFiles(rootPath) {
836
+ if (!fs.existsSync(rootPath)) return [];
837
+ if (fs.statSync(rootPath).isFile()) return isTypeScriptSourceFile(rootPath) ? [rootPath] : [];
838
+ return walk(rootPath).filter(isTypeScriptSourceFile).sort((a, b) => a.localeCompare(b));
839
+ }
840
+ function isTypeScriptSourceFile(filePath) {
841
+ return /\.(?:ts|mts|cts|tsx)$/u.test(filePath) && !/\.d\.(?:ts|mts|cts)$/u.test(filePath);
842
+ }
843
+ function walk(rootPath) {
844
+ const result = [];
845
+ const entries = fs.readdirSync(rootPath, { withFileTypes: true });
846
+ for (const entry of entries) {
847
+ const entryPath = path.join(rootPath, entry.name);
848
+ if (entry.isDirectory()) {
849
+ result.push(...walk(entryPath));
850
+ continue;
851
+ }
852
+ if (entry.isFile()) result.push(entryPath);
853
+ }
854
+ return result;
855
+ }
856
+
857
+ //#endregion
858
+ //#region src-internal/scanner/import-scanner.ts
859
+ function scanImports(filePath) {
860
+ const source = ts.createSourceFile(filePath, fs.readFileSync(filePath, "utf8"), ts.ScriptTarget.Latest, true);
861
+ const imports = [];
862
+ const visit = (node) => {
863
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) imports.push({
864
+ moduleSpecifier: node.moduleSpecifier.text,
865
+ kind: "import"
866
+ });
867
+ if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) imports.push({
868
+ moduleSpecifier: node.moduleSpecifier.text,
869
+ kind: "export"
870
+ });
871
+ if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
872
+ const [argument] = node.arguments;
873
+ if (argument && ts.isStringLiteral(argument)) imports.push({
874
+ moduleSpecifier: argument.text,
875
+ kind: "dynamic-import"
876
+ });
877
+ }
878
+ ts.forEachChild(node, visit);
879
+ };
880
+ visit(source);
881
+ return imports;
882
+ }
883
+ function hasDefaultExport(filePath) {
884
+ const source = ts.createSourceFile(filePath, fs.readFileSync(filePath, "utf8"), ts.ScriptTarget.Latest, true);
885
+ let found = false;
886
+ const visit = (node) => {
887
+ if (found) return;
888
+ if (ts.isExportAssignment(node) && !node.isExportEquals) {
889
+ found = true;
890
+ return;
891
+ }
892
+ if (hasExportDefaultModifiers(node)) {
893
+ found = true;
894
+ return;
895
+ }
896
+ if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamedExports(node.exportClause)) for (const element of node.exportClause.elements) {
897
+ const exportedName = element.name.text;
898
+ const sourceName = element.propertyName?.text;
899
+ if (exportedName === "default" || sourceName === "default") {
900
+ found = true;
901
+ return;
902
+ }
903
+ }
904
+ ts.forEachChild(node, visit);
905
+ };
906
+ visit(source);
907
+ return found;
908
+ }
909
+ function hasExportDefaultModifiers(node) {
910
+ const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : void 0;
911
+ if (!modifiers) return false;
912
+ const hasExport = modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
913
+ const hasDefault = modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword);
914
+ return hasExport && hasDefault;
915
+ }
916
+
917
+ //#endregion
918
+ //#region src-internal/generator/generate-mirrors.ts
919
+ function generateMirrors(definitions) {
920
+ for (const definition of definitions) {
921
+ generateApiMirror(definition.api);
922
+ generateImplMirror(definition);
923
+ }
924
+ }
925
+ function generateApiMirror(surface) {
926
+ if (!surface.rootPath) {
927
+ writeEmptyMirror(path.join(surface.mirrorRootPath, "index.ts"));
928
+ return;
929
+ }
930
+ const mirroredPaths = /* @__PURE__ */ new Set();
931
+ for (const sourceFilePath of listTypeScriptFiles(surface.rootPath)) {
932
+ const relativePath = normalizePath(path.relative(surface.rootPath, sourceFilePath));
933
+ mirroredPaths.add(relativePath);
934
+ writeMirrorFile(path.join(surface.mirrorRootPath, relativePath), sourceFilePath);
935
+ }
936
+ if (!mirroredPaths.has("index.ts")) writeEmptyMirror(path.join(surface.mirrorRootPath, "index.ts"));
937
+ }
938
+ function generateImplMirror(definition) {
939
+ const indexPath = definition.impl.rootPath ? findIndexFile(definition.impl.rootPath) : void 0;
940
+ if (indexPath) {
941
+ writeMirrorFile(path.join(definition.impl.mirrorRootPath, "index.ts"), indexPath);
942
+ return;
943
+ }
944
+ const constName = definition.kind === "module" ? "ArchicatModuleImplementation" : "ArchicatLibraryImplementation";
945
+ const content = `${makeMirrorHeader()}
946
+ export const ${constName} = {
947
+ name: '${definition.name}',
21
948
  } as const;
22
949
 
23
- export default ${n};
24
- `;R(r.join(e.impl.mirrorRootPath,`index.ts`),i)}function W(e){R(e,`${K()}export {};\n`)}function G(e,t){let n=he(e,t),r=st(t)?`export { default } from '${n}';\n`:``;R(e,`${K()}
25
- export * from '${n}';
26
- ${r}`)}function ft(e){for(let t of[`index.ts`,`index.mts`,`index.cts`,`index.tsx`]){let i=r.join(e,t);if(n.existsSync(i)&&n.statSync(i).isFile())return i}}function K(){return`// Mirrored by Archicat.
27
- `}function pt(e){z(r.join(e.reportsDir,o.generated.buildReportFileName),mt(e)),z(r.join(e.reportsDir,o.generated.graphReportFileName),ht(e))}function mt(e){return{generatedBy:`archicat`,schemaVersion:2,aliases:{module:e.config.modules.alias,library:e.config.libraries.alias},outputs:{outDir:S(e.rootDir,e.outDir),reportsDir:S(e.rootDir,e.reportsDir)},targets:e.graph.targets.map(e=>e.key),definitions:[...e.definitions.map(t=>gt(e,t)),...e.apps.map(t=>({kind:t.kind,name:t.name,targets:{app:t.target},aliases:{},dependencies:t.dependencies,contractFilePath:S(e.rootDir,t.contractFilePath),source:{root:S(e.rootDir,t.rootPath)},mirror:{}}))],dependencies:e.graph.dependencies}}function ht(e){return{generatedBy:`archicat`,schemaVersion:1,targets:e.graph.targets.map(e=>e.key),dependencies:e.graph.dependencies}}function gt(e,t){return{kind:t.kind,name:t.name,targets:{api:t.apiTarget,impl:t.implTarget},aliases:{api:t.alias,impl:t.implAlias},dependencies:{api:t.api.dependencies,impl:t.impl.dependencies},contractFilePath:S(e.rootDir,t.contractFilePath),source:{root:S(e.rootDir,t.definitionDir),api:t.api.rootPath?S(e.rootDir,t.api.rootPath):void 0,impl:t.impl.rootPath?S(e.rootDir,t.impl.rootPath):void 0},mirror:{api:S(e.rootDir,t.api.mirrorRootPath),impl:S(e.rootDir,t.impl.mirrorRootPath)}}}function _t(e){Dt(e.tsconfigPath),z(r.join(e.outDir,o.generated.tsconfigFileName),vt(e))}function vt(e){let t={compilerOptions:yt(e),include:xt(e)},n=bt(e),r=St(e),i=Ct(e);return n&&(t.extends=n),r.length>0&&(t.exclude=r),i.length>0&&(t.files=i),t}function yt(e){let t=Tt(e),n=wt(e);return jt(e,t,n),{paths:{...t,...n}}}function bt(e){return e.tsconfigPath?J(e.outDir,e.tsconfigPath):void 0}function xt(e){return Et([...q(e,e.config.typescript.tsConfig.include),o.generated.typesInclude])}function St(e){return q(e,e.config.typescript.tsConfig.exclude)}function Ct(e){return q(e,e.config.typescript.tsConfig.files)}function wt(e){let t={};for(let n of e.definitions)n.api.rootPath&&(t[n.aliasGlob]=[J(e.outDir,r.join(n.api.rootPath,`*`))]),n.impl.rootPath&&(t[n.implAliasGlob]=[J(e.outDir,r.join(n.impl.rootPath,`*`))]);return t}function Tt(e){let t={};for(let[n,i]of Object.entries(e.config.alias)){let a=r.isAbsolute(i)?i:r.resolve(e.rootDir,i);t[n]=[J(e.outDir,a)]}return t}function q(e,t){return t.map(t=>{let n=r.isAbsolute(t)?t:r.resolve(e.rootDir,t);return J(e.outDir,n)})}function J(e,t){let n=v(r.relative(e,t));return n.startsWith(`.`)||(n=`./${n}`),n}function Et(e){return[...new Set(e)]}function Dt(e){e&&Ot(e,new Set,new Set)}function Ot(e,t,n){let i=r.resolve(e);if(n.has(i))return;if(t.has(i))throw Error(`Circular tsconfig extends chain detected: ${i}`);t.add(i);let a=s(i),o=c(a,i);kt(o,i),At(o,i);for(let e of l(a,i))Ot(e,t,n);t.delete(i),n.add(i)}function kt(e,t){if(Object.hasOwn(e,`paths`))throw Error(`Base tsconfig compilerOptions.paths is not supported by Archicat. Move aliases into archicat.config.ts alias: ${t}`)}function At(e,t){if(Object.hasOwn(e,`baseUrl`))throw Error(`Base tsconfig compilerOptions.baseUrl is not supported by Archicat. Move aliases into archicat.config.ts alias: ${t}`)}function jt(e,t,n){let r=Object.keys(t),i=Object.keys(n),a=[e.config.modules.alias,e.config.libraries.alias];for(let e of r){if(i.includes(e))throw Error(`Alias conflict: archicat.config.ts alias already defines "${e}", but Archicat needs it.`);for(let t of a)if(e===t||e===`${t}/*`||e.startsWith(`${t}/`))throw Error(`Alias conflict: archicat.config.ts alias "${e}" is inside Archicat reserved alias "${t}". Remove the alias or configure another module/library alias.`)}}function Mt(e){nt(e.rootDir,e.outDir),n.mkdirSync(r.join(e.outDir,o.generated.modulesDirName),{recursive:!0}),n.mkdirSync(r.join(e.outDir,o.generated.librariesDirName),{recursive:!0}),n.mkdirSync(r.join(e.outDir,o.generated.typesDirName),{recursive:!0}),n.mkdirSync(e.reportsDir,{recursive:!0}),lt(e.definitions),at(e),_t(e),pt(e)}function Nt(e){let t=Bt(e.graph.dependencies),n=[],r=[...e.definitions.flatMap(e=>[...e.api.rootPath?V(e.api.rootPath):[],...e.impl.rootPath?V(e.impl.rootPath):[]]),...e.apps.flatMap(e=>V(e.rootPath))];for(let i of r){let r=It(e,i);if(r)for(let a of ot(i)){let o=Ft(e,t,r,i,a.moduleSpecifier);o&&n.push(o)}}return n}function Pt(e){return`${e.filePath}\n import: ${e.importPath}\n ${e.message}`}function Ft(e,t,n,r,i){let a=Rt(e,i);if(a)return Y(e,r,i,`Unsupported Archicat alias "${i}". Use an explicit file import under "${a.apiAlias}/*" or "${a.implAlias}/*".`);let o=Lt(e,i);if(o)return zt(n,o)||Vt(t,n.target,o.target)?void 0:Y(e,r,i,`${X(n)} imports "${o.target}" but does not declare a dependency that allows it.`);if(!i.startsWith(`.`)&&!i.startsWith(`/`))return;let s=It(e,Ht(r,i));if(!(!s||s.target===n.target))return Y(e,r,i,`${X(n)} imports ${X(s)} through a source path. Use an Archicat alias instead.`)}function It(e,t){let n=x(t);for(let t of e.definitions){if(t.api.rootPath&&y(n,x(t.api.rootPath)))return{kind:t.kind,name:t.name,surface:`api`,target:t.apiTarget};if(t.impl.rootPath&&y(n,x(t.impl.rootPath)))return{kind:t.kind,name:t.name,surface:`impl`,target:t.implTarget}}for(let t of e.apps)if(y(n,x(t.rootPath)))return{kind:`app`,name:t.name,surface:`app`,target:t.target}}function Lt(e,t){for(let n of e.definitions){if(t.startsWith(`${n.implAlias}/`))return{kind:n.kind,name:n.name,surface:`impl`,target:n.implTarget};if(t.startsWith(`${n.alias}/`))return{kind:n.kind,name:n.name,surface:`api`,target:n.apiTarget}}}function Rt(e,t){for(let n of e.definitions){let r=`${n.kind===`module`?e.config.modules.alias:e.config.libraries.alias}/${n.name}`,i=n.implAlias;if(t===r||t===n.alias||t===i||t.startsWith(`${r}/`)&&!t.startsWith(`${n.alias}/`)&&!t.startsWith(`${i}/`))return{apiAlias:n.alias,implAlias:i}}}function zt(e,t){return e.kind===t.kind&&e.name===t.name&&t.surface===`api`}function Bt(e){let t=new Map;for(let n of e){let e=t.get(n.from)??[];e.push(n.to),t.set(n.from,e)}return t}function Vt(e,t,n){let r=new Set,i=[t];for(;i.length>0;){let t=i.shift();if(t){if(t===n)return!0;r.has(t)||(r.add(t),i.push(...e.get(t)??[]))}}return!1}function Ht(e,t){return x(t.startsWith(`/`)?t:r.resolve(r.dirname(e),t))}function Y(e,t,n,r){return{filePath:S(e.rootDir,t),importPath:n,message:r}}function X(e){return e.kind===`app`?`App "${e.name}"`:`${Ut(e.kind)} "${e.name}" ${e.surface}`}function Ut(e){return`${e.charAt(0).toUpperCase()}${e.slice(1)}`}const Wt=new t;async function Z(e,t,n){let r=Date.now(),i=await Ie(n,t.config),a=Jt(e,n,i),o=[],s=[];if((e===`doctor`||e===`generate`)&&s.push(...Gt(i)),e===`validate`||e===`generate`){let e=Kt(i);if(s.push(...e.lines),e.exitCode!==0)return qt(a,o,s,e.exitCode,r)}return e===`generate`&&(Mt(i),o.push({kind:`panel`,title:`mirrored`,rows:[{label:`modules`,value:i.modules.length},{label:`libraries`,value:i.libraries.length},{label:`apps`,value:i.apps.length}]})),qt(a,o,s,0,r)}function Gt(e){let t=Ve(e);return t.length===0?[{kind:`success`,label:`doctor`,message:`Project diagnostics passed`}]:[{kind:`warning`,label:`doctor`,message:`Project diagnostics completed with warnings`},...t.map(e=>({kind:`warning`,message:` ${e}`}))]}function Kt(e){let t=Nt(e);return t.length===0?{exitCode:0,lines:[{kind:`success`,label:`validate`,message:`Architecture boundaries passed`}]}:{exitCode:1,lines:[{kind:`error`,label:`validate`,message:`Architecture validation failed`},...t.map(e=>({kind:`error`,message:Pt(e)}))]}}function qt(e,t,n,r,i){let a=r===0?{kind:`success`,label:`done`,message:`Completed in ${Wt.duration(Date.now()-i)}`}:{kind:`error`,label:`failed`,message:`Failed in ${Wt.duration(Date.now()-i)}`};return{exitCode:r,lines:[e,...t,...n,a,{kind:`info`,message:``}]}}function Jt(e,t,n){return{kind:`title`,product:`ArchiCat`,command:e,rows:[{label:`config`,value:Yt(t,n.configFilePath)},{label:`output`,value:Yt(t,n.outDir)}]}}function Yt(e,t){let n=r.relative(e,t);return!n||n.startsWith(`..`)?t:n}const Q=new t;async function Xt(e=process.argv.slice(2),t=process.cwd()){let[n,...r]=e;try{let e=await Zt(n,tn(r),t);if(!e)return;Qt(e),process.exitCode=e.exitCode}catch(e){Q.error(e instanceof Error?e.message:String(e)),process.exitCode=1}}async function Zt(e,t,n){switch(e){case`generate`:return await Z(`generate`,t,n);case`validate`:case`check`:return await Z(`validate`,t,n);case`graph`:return await Be(t,n);case`doctor`:return await Z(`doctor`,t,n);case`help`:case`--help`:case`-h`:case void 0:en();return;default:return Q.error(`Unknown command: ${e}`),en(),{exitCode:1,lines:[]}}}function Qt(e){for(let t of e.lines)$t(t)}function $t(e){switch(e.kind){case`title`:Q.title(e.product,e.command,e.rows??[]);return;case`panel`:Q.panel(e.title,e.rows,e.badge);return;case`success`:Q.success($(e));return;case`info`:if(e.message.length===0){Q.emptyLine();return}Q.info($(e));return;case`warning`:Q.warn($(e));return;case`error`:Q.error($(e));return}}function $(e){return e.label?Q.step(e.label,e.message):e.message}function en(){Q.log([`ArchiCat`,``,`Usage:`,` archicat generate [--config archicat.config.ts]`,` archicat validate [--config archicat.config.ts]`,` archicat graph [--config archicat.config.ts]`,` archicat doctor [--config archicat.config.ts]`,``,`Aliases:`,` archicat check -> archicat validate`,``].join(`
28
- `))}function tn(e){let t={};for(let n=0;n<e.length;n+=1){let r=e[n];if(r===`--config`||r===`-c`){let i=e[n+1];if(!i)throw Error(`${r} requires a value.`);t.config=i,n+=1}}return t}export{Xt as runMain};
950
+ export default ${constName};
951
+ `;
952
+ writeTextFile(path.join(definition.impl.mirrorRootPath, "index.ts"), content);
953
+ }
954
+ function writeEmptyMirror(filePath) {
955
+ writeTextFile(filePath, `${makeMirrorHeader()}export {};\n`);
956
+ }
957
+ function writeMirrorFile(targetFilePath, sourceFilePath) {
958
+ const sourceImport = toPosixRelativeImport(targetFilePath, sourceFilePath);
959
+ const defaultExport = hasDefaultExport(sourceFilePath) ? `export { default } from '${sourceImport}';\n` : "";
960
+ writeTextFile(targetFilePath, `${makeMirrorHeader()}
961
+ export * from '${sourceImport}';
962
+ ${defaultExport}`);
963
+ }
964
+ function findIndexFile(rootPath) {
965
+ for (const fileName of [
966
+ "index.ts",
967
+ "index.mts",
968
+ "index.cts",
969
+ "index.tsx"
970
+ ]) {
971
+ const filePath = path.join(rootPath, fileName);
972
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) return filePath;
973
+ }
974
+ }
975
+ function makeMirrorHeader() {
976
+ return "// Mirrored by Archicat.\n";
977
+ }
978
+
979
+ //#endregion
980
+ //#region src-internal/generator/generate-report.ts
981
+ function generateReport(project) {
982
+ writeJsonFile(path.join(project.reportsDir, ArchicatDefaults.generated.buildReportFileName), makeBuildReport(project));
983
+ writeJsonFile(path.join(project.reportsDir, ArchicatDefaults.generated.graphReportFileName), makeGraphReport(project));
984
+ }
985
+ function makeBuildReport(project) {
986
+ return {
987
+ generatedBy: "archicat",
988
+ schemaVersion: 2,
989
+ aliases: {
990
+ module: project.config.modules.alias,
991
+ library: project.config.libraries.alias
992
+ },
993
+ outputs: {
994
+ outDir: makeRelativeDisplayPath(project.rootDir, project.outDir),
995
+ reportsDir: makeRelativeDisplayPath(project.rootDir, project.reportsDir)
996
+ },
997
+ targets: project.graph.targets.map((target) => target.key),
998
+ definitions: [...project.definitions.map((definition) => makeDefinitionReport(project, definition)), ...project.apps.map((app) => ({
999
+ kind: app.kind,
1000
+ name: app.name,
1001
+ targets: { app: app.target },
1002
+ aliases: {},
1003
+ dependencies: app.dependencies,
1004
+ contractFilePath: makeRelativeDisplayPath(project.rootDir, app.contractFilePath),
1005
+ source: { root: makeRelativeDisplayPath(project.rootDir, app.rootPath) },
1006
+ mirror: {}
1007
+ }))],
1008
+ dependencies: project.graph.dependencies
1009
+ };
1010
+ }
1011
+ function makeGraphReport(project) {
1012
+ return {
1013
+ generatedBy: "archicat",
1014
+ schemaVersion: 1,
1015
+ targets: project.graph.targets.map((target) => target.key),
1016
+ dependencies: project.graph.dependencies
1017
+ };
1018
+ }
1019
+ function makeDefinitionReport(project, definition) {
1020
+ return {
1021
+ kind: definition.kind,
1022
+ name: definition.name,
1023
+ targets: {
1024
+ api: definition.apiTarget,
1025
+ impl: definition.implTarget
1026
+ },
1027
+ aliases: {
1028
+ api: definition.alias,
1029
+ impl: definition.implAlias
1030
+ },
1031
+ dependencies: {
1032
+ api: definition.api.dependencies,
1033
+ impl: definition.impl.dependencies
1034
+ },
1035
+ contractFilePath: makeRelativeDisplayPath(project.rootDir, definition.contractFilePath),
1036
+ source: {
1037
+ root: makeRelativeDisplayPath(project.rootDir, definition.definitionDir),
1038
+ api: definition.api.rootPath ? makeRelativeDisplayPath(project.rootDir, definition.api.rootPath) : void 0,
1039
+ impl: definition.impl.rootPath ? makeRelativeDisplayPath(project.rootDir, definition.impl.rootPath) : void 0
1040
+ },
1041
+ mirror: {
1042
+ api: makeRelativeDisplayPath(project.rootDir, definition.api.mirrorRootPath),
1043
+ impl: makeRelativeDisplayPath(project.rootDir, definition.impl.mirrorRootPath)
1044
+ }
1045
+ };
1046
+ }
1047
+
1048
+ //#endregion
1049
+ //#region src-internal/generator/generate-tsconfig.ts
1050
+ function generateTsconfig(project) {
1051
+ assertSupportedBaseTsconfigChain(project.tsconfigPath);
1052
+ writeJsonFile(path.join(project.outDir, ArchicatDefaults.generated.tsconfigFileName), makeGeneratedTsconfig(project));
1053
+ }
1054
+ function makeGeneratedTsconfig(project) {
1055
+ const tsconfig = {
1056
+ compilerOptions: makeCompilerOptions(project),
1057
+ include: makeInclude(project)
1058
+ };
1059
+ const extendedTsconfig = makeExtends(project);
1060
+ const exclude = makeExclude(project);
1061
+ const files = makeFiles(project);
1062
+ if (extendedTsconfig) tsconfig.extends = extendedTsconfig;
1063
+ if (exclude.length > 0) tsconfig.exclude = exclude;
1064
+ if (files.length > 0) tsconfig.files = files;
1065
+ return tsconfig;
1066
+ }
1067
+ function makeCompilerOptions(project) {
1068
+ const userAliasPaths = makeConfiguredAliasPaths(project);
1069
+ const archicatPaths = makeArchicatAliasPaths(project);
1070
+ assertNoAliasConflicts(project, userAliasPaths, archicatPaths);
1071
+ return { paths: {
1072
+ ...userAliasPaths,
1073
+ ...archicatPaths
1074
+ } };
1075
+ }
1076
+ function makeExtends(project) {
1077
+ return project.tsconfigPath ? makeRelativeTsconfigPath(project.outDir, project.tsconfigPath) : void 0;
1078
+ }
1079
+ function makeInclude(project) {
1080
+ return unique([...rewriteProjectPaths(project, project.config.typescript.tsConfig.include), ArchicatDefaults.generated.typesInclude]);
1081
+ }
1082
+ function makeExclude(project) {
1083
+ return rewriteProjectPaths(project, project.config.typescript.tsConfig.exclude);
1084
+ }
1085
+ function makeFiles(project) {
1086
+ return rewriteProjectPaths(project, project.config.typescript.tsConfig.files);
1087
+ }
1088
+ function makeArchicatAliasPaths(project) {
1089
+ const paths = {};
1090
+ for (const definition of project.definitions) {
1091
+ if (definition.api.rootPath) paths[definition.aliasGlob] = [makeRelativeTsconfigPath(project.outDir, path.join(definition.api.rootPath, "*"))];
1092
+ if (definition.impl.rootPath) paths[definition.implAliasGlob] = [makeRelativeTsconfigPath(project.outDir, path.join(definition.impl.rootPath, "*"))];
1093
+ }
1094
+ return paths;
1095
+ }
1096
+ function makeConfiguredAliasPaths(project) {
1097
+ const result = {};
1098
+ for (const [alias, target] of Object.entries(project.config.alias)) {
1099
+ const absolute = path.isAbsolute(target) ? target : path.resolve(project.rootDir, target);
1100
+ result[alias] = [makeRelativeTsconfigPath(project.outDir, absolute)];
1101
+ }
1102
+ return result;
1103
+ }
1104
+ function rewriteProjectPaths(project, values) {
1105
+ return values.map((value) => {
1106
+ const absolute = path.isAbsolute(value) ? value : path.resolve(project.rootDir, value);
1107
+ return makeRelativeTsconfigPath(project.outDir, absolute);
1108
+ });
1109
+ }
1110
+ function makeRelativeTsconfigPath(fromDir, targetPath) {
1111
+ let relative = normalizePath(path.relative(fromDir, targetPath));
1112
+ if (!relative.startsWith(".")) relative = `./${relative}`;
1113
+ return relative;
1114
+ }
1115
+ function unique(values) {
1116
+ return [...new Set(values)];
1117
+ }
1118
+ function assertSupportedBaseTsconfigChain(tsconfigPath) {
1119
+ if (!tsconfigPath) return;
1120
+ validateBaseTsconfig(tsconfigPath, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set());
1121
+ }
1122
+ function validateBaseTsconfig(tsconfigPath, visiting, visited) {
1123
+ const resolvedPath = path.resolve(tsconfigPath);
1124
+ if (visited.has(resolvedPath)) return;
1125
+ if (visiting.has(resolvedPath)) throw new Error(`Circular tsconfig extends chain detected: ${resolvedPath}`);
1126
+ visiting.add(resolvedPath);
1127
+ const config = readTsconfigFile(resolvedPath);
1128
+ const compilerOptions = readTsconfigCompilerOptions(config, resolvedPath);
1129
+ assertNoBaseTsconfigPaths(compilerOptions, resolvedPath);
1130
+ assertNoBaseTsconfigBaseUrl(compilerOptions, resolvedPath);
1131
+ for (const extendedPath of readTsconfigExtendsPaths(config, resolvedPath)) validateBaseTsconfig(extendedPath, visiting, visited);
1132
+ visiting.delete(resolvedPath);
1133
+ visited.add(resolvedPath);
1134
+ }
1135
+ function assertNoBaseTsconfigPaths(compilerOptions, tsconfigPath) {
1136
+ if (Object.hasOwn(compilerOptions, "paths")) throw new Error(`Base tsconfig compilerOptions.paths is not supported by Archicat. Move aliases into archicat.config.ts alias: ${tsconfigPath}`);
1137
+ }
1138
+ function assertNoBaseTsconfigBaseUrl(compilerOptions, tsconfigPath) {
1139
+ if (Object.hasOwn(compilerOptions, "baseUrl")) throw new Error(`Base tsconfig compilerOptions.baseUrl is not supported by Archicat. Move aliases into archicat.config.ts alias: ${tsconfigPath}`);
1140
+ }
1141
+ function assertNoAliasConflicts(project, userAliases, archicatPaths) {
1142
+ const configuredAliases = Object.keys(userAliases);
1143
+ const archicatAliases = Object.keys(archicatPaths);
1144
+ const reservedAliases = [project.config.modules.alias, project.config.libraries.alias];
1145
+ for (const alias of configuredAliases) {
1146
+ if (archicatAliases.includes(alias)) throw new Error(`Alias conflict: archicat.config.ts alias already defines "${alias}", but Archicat needs it.`);
1147
+ for (const reservedAlias of reservedAliases) if (alias === reservedAlias || alias === `${reservedAlias}/*` || alias.startsWith(`${reservedAlias}/`)) throw new Error(`Alias conflict: archicat.config.ts alias "${alias}" is inside Archicat reserved alias "${reservedAlias}". Remove the alias or configure another module/library alias.`);
1148
+ }
1149
+ }
1150
+
1151
+ //#endregion
1152
+ //#region src-internal/generator/generator.ts
1153
+ function generateArtifacts(project) {
1154
+ resetDirectory(project.rootDir, project.outDir);
1155
+ fs.mkdirSync(path.join(project.outDir, ArchicatDefaults.generated.modulesDirName), { recursive: true });
1156
+ fs.mkdirSync(path.join(project.outDir, ArchicatDefaults.generated.librariesDirName), { recursive: true });
1157
+ fs.mkdirSync(path.join(project.outDir, ArchicatDefaults.generated.typesDirName), { recursive: true });
1158
+ fs.mkdirSync(project.reportsDir, { recursive: true });
1159
+ generateMirrors(project.definitions);
1160
+ generateGraphTypes(project);
1161
+ generateTsconfig(project);
1162
+ generateReport(project);
1163
+ }
1164
+
1165
+ //#endregion
1166
+ //#region src-internal/validator/validator.ts
1167
+ function validateProject(project) {
1168
+ const graph = makeDependencyGraph(project.graph.dependencies);
1169
+ const violations = [];
1170
+ const sourceFiles = [...project.definitions.flatMap((definition) => [...definition.api.rootPath ? listTypeScriptFiles(definition.api.rootPath) : [], ...definition.impl.rootPath ? listTypeScriptFiles(definition.impl.rootPath) : []]), ...project.apps.flatMap((app) => listTypeScriptFiles(app.rootPath))];
1171
+ for (const filePath of sourceFiles) {
1172
+ const owner = findOwner(project, filePath);
1173
+ if (!owner) continue;
1174
+ for (const scannedImport of scanImports(filePath)) {
1175
+ const violation = validateImport(project, graph, owner, filePath, scannedImport.moduleSpecifier);
1176
+ if (violation) violations.push(violation);
1177
+ }
1178
+ }
1179
+ return violations;
1180
+ }
1181
+ function formatViolation(violation) {
1182
+ return `${violation.filePath}\n import: ${violation.importPath}\n ${violation.message}`;
1183
+ }
1184
+ function validateImport(project, graph, owner, filePath, importPath) {
1185
+ const unsupportedAlias = resolveUnsupportedArchicatAlias(project, importPath);
1186
+ if (unsupportedAlias) return makeViolation(project, filePath, importPath, `Unsupported Archicat alias "${importPath}". Use an explicit file import under "${unsupportedAlias.apiAlias}/*" or "${unsupportedAlias.implAlias}/*".`);
1187
+ const target = resolveAlias(project, importPath);
1188
+ if (target) {
1189
+ if (isOwnApiImport(owner, target) || canReach(graph, owner.target, target.target)) return;
1190
+ return makeViolation(project, filePath, importPath, `${formatOwner(owner)} imports "${target.target}" but does not declare a dependency that allows it.`);
1191
+ }
1192
+ if (!importPath.startsWith(".") && !importPath.startsWith("/")) return;
1193
+ const targetOwner = findOwner(project, resolveImportPath(filePath, importPath));
1194
+ if (!targetOwner || targetOwner.target === owner.target) return;
1195
+ return makeViolation(project, filePath, importPath, `${formatOwner(owner)} imports ${formatOwner(targetOwner)} through a source path. Use an Archicat alias instead.`);
1196
+ }
1197
+ function findOwner(project, filePath) {
1198
+ const extensionless = stripKnownExtension(filePath);
1199
+ for (const definition of project.definitions) {
1200
+ if (definition.api.rootPath && isPathInside(extensionless, stripKnownExtension(definition.api.rootPath))) return {
1201
+ kind: definition.kind,
1202
+ name: definition.name,
1203
+ surface: "api",
1204
+ target: definition.apiTarget
1205
+ };
1206
+ if (definition.impl.rootPath && isPathInside(extensionless, stripKnownExtension(definition.impl.rootPath))) return {
1207
+ kind: definition.kind,
1208
+ name: definition.name,
1209
+ surface: "impl",
1210
+ target: definition.implTarget
1211
+ };
1212
+ }
1213
+ for (const app of project.apps) if (isPathInside(extensionless, stripKnownExtension(app.rootPath))) return {
1214
+ kind: "app",
1215
+ name: app.name,
1216
+ surface: "app",
1217
+ target: app.target
1218
+ };
1219
+ }
1220
+ function resolveAlias(project, importPath) {
1221
+ for (const definition of project.definitions) {
1222
+ if (importPath.startsWith(`${definition.implAlias}/`)) return {
1223
+ kind: definition.kind,
1224
+ name: definition.name,
1225
+ surface: "impl",
1226
+ target: definition.implTarget
1227
+ };
1228
+ if (importPath.startsWith(`${definition.alias}/`)) return {
1229
+ kind: definition.kind,
1230
+ name: definition.name,
1231
+ surface: "api",
1232
+ target: definition.apiTarget
1233
+ };
1234
+ }
1235
+ }
1236
+ function resolveUnsupportedArchicatAlias(project, importPath) {
1237
+ for (const definition of project.definitions) {
1238
+ const prefix = `${definition.kind === "module" ? project.config.modules.alias : project.config.libraries.alias}/${definition.name}`;
1239
+ const implAlias = definition.implAlias;
1240
+ if (importPath === prefix || importPath === definition.alias || importPath === implAlias) return {
1241
+ apiAlias: definition.alias,
1242
+ implAlias
1243
+ };
1244
+ if (importPath.startsWith(`${prefix}/`) && !importPath.startsWith(`${definition.alias}/`) && !importPath.startsWith(`${implAlias}/`)) return {
1245
+ apiAlias: definition.alias,
1246
+ implAlias
1247
+ };
1248
+ }
1249
+ }
1250
+ function isOwnApiImport(owner, target) {
1251
+ return owner.kind === target.kind && owner.name === target.name && target.surface === "api";
1252
+ }
1253
+ function makeDependencyGraph(dependencies) {
1254
+ const graph = /* @__PURE__ */ new Map();
1255
+ for (const dependency of dependencies) {
1256
+ const targets = graph.get(dependency.from) ?? [];
1257
+ targets.push(dependency.to);
1258
+ graph.set(dependency.from, targets);
1259
+ }
1260
+ return graph;
1261
+ }
1262
+ function canReach(graph, from, to) {
1263
+ const visited = /* @__PURE__ */ new Set();
1264
+ const queue = [from];
1265
+ while (queue.length > 0) {
1266
+ const current = queue.shift();
1267
+ if (!current) continue;
1268
+ if (current === to) return true;
1269
+ if (visited.has(current)) continue;
1270
+ visited.add(current);
1271
+ queue.push(...graph.get(current) ?? []);
1272
+ }
1273
+ return false;
1274
+ }
1275
+ function resolveImportPath(filePath, importPath) {
1276
+ return stripKnownExtension(importPath.startsWith("/") ? importPath : path.resolve(path.dirname(filePath), importPath));
1277
+ }
1278
+ function makeViolation(project, filePath, importPath, message) {
1279
+ return {
1280
+ filePath: makeRelativeDisplayPath(project.rootDir, filePath),
1281
+ importPath,
1282
+ message
1283
+ };
1284
+ }
1285
+ function formatOwner(owner) {
1286
+ if (owner.kind === "app") return `App "${owner.name}"`;
1287
+ return `${capitalize(owner.kind)} "${owner.name}" ${owner.surface}`;
1288
+ }
1289
+ function capitalize(value) {
1290
+ return `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
1291
+ }
1292
+
1293
+ //#endregion
1294
+ //#region src-cli/commands/run-project-command.ts
1295
+ const output$1 = new CliOutput();
1296
+ async function runProjectCommand(command, options, cwd) {
1297
+ const startedAt = Date.now();
1298
+ const project = await loadArchicatProject(cwd, options.config);
1299
+ const title = makeTitle(command, cwd, project);
1300
+ const panels = [];
1301
+ const lines = [];
1302
+ if (command === "doctor" || command === "generate") lines.push(...makeDoctorLines(project));
1303
+ if (command === "validate" || command === "generate") {
1304
+ const validation = makeValidationResult(project);
1305
+ lines.push(...validation.lines);
1306
+ if (validation.exitCode !== 0) return finishResult(title, panels, lines, validation.exitCode, startedAt);
1307
+ }
1308
+ if (command === "generate") {
1309
+ generateArtifacts(project);
1310
+ panels.push({
1311
+ kind: "panel",
1312
+ title: "mirrored",
1313
+ rows: [
1314
+ {
1315
+ label: "modules",
1316
+ value: project.modules.length
1317
+ },
1318
+ {
1319
+ label: "libraries",
1320
+ value: project.libraries.length
1321
+ },
1322
+ {
1323
+ label: "apps",
1324
+ value: project.apps.length
1325
+ }
1326
+ ]
1327
+ });
1328
+ }
1329
+ return finishResult(title, panels, lines, 0, startedAt);
1330
+ }
1331
+ function makeDoctorLines(project) {
1332
+ const issues = doctorProject(project);
1333
+ if (issues.length === 0) return [{
1334
+ kind: "success",
1335
+ label: "doctor",
1336
+ message: "Project diagnostics passed"
1337
+ }];
1338
+ return [{
1339
+ kind: "warning",
1340
+ label: "doctor",
1341
+ message: "Project diagnostics completed with warnings"
1342
+ }, ...issues.map((message) => ({
1343
+ kind: "warning",
1344
+ message: ` ${message}`
1345
+ }))];
1346
+ }
1347
+ function makeValidationResult(project) {
1348
+ const violations = validateProject(project);
1349
+ if (violations.length === 0) return {
1350
+ exitCode: 0,
1351
+ lines: [{
1352
+ kind: "success",
1353
+ label: "validate",
1354
+ message: "Architecture boundaries passed"
1355
+ }]
1356
+ };
1357
+ return {
1358
+ exitCode: 1,
1359
+ lines: [{
1360
+ kind: "error",
1361
+ label: "validate",
1362
+ message: "Architecture validation failed"
1363
+ }, ...violations.map((violation) => ({
1364
+ kind: "error",
1365
+ message: formatViolation(violation)
1366
+ }))]
1367
+ };
1368
+ }
1369
+ function finishResult(title, panels, lines, exitCode, startedAt) {
1370
+ const status = exitCode === 0 ? {
1371
+ kind: "success",
1372
+ label: "done",
1373
+ message: `Completed in ${output$1.duration(Date.now() - startedAt)}`
1374
+ } : {
1375
+ kind: "error",
1376
+ label: "failed",
1377
+ message: `Failed in ${output$1.duration(Date.now() - startedAt)}`
1378
+ };
1379
+ return {
1380
+ exitCode,
1381
+ lines: [
1382
+ title,
1383
+ ...panels,
1384
+ ...lines,
1385
+ status,
1386
+ {
1387
+ kind: "info",
1388
+ message: ""
1389
+ }
1390
+ ]
1391
+ };
1392
+ }
1393
+ function makeTitle(command, cwd, project) {
1394
+ return {
1395
+ kind: "title",
1396
+ product: "ArchiCat",
1397
+ command,
1398
+ rows: [{
1399
+ label: "config",
1400
+ value: formatPath(cwd, project.configFilePath)
1401
+ }, {
1402
+ label: "output",
1403
+ value: formatPath(cwd, project.outDir)
1404
+ }]
1405
+ };
1406
+ }
1407
+ function formatPath(cwd, filePath) {
1408
+ const relativePath = path.relative(cwd, filePath);
1409
+ if (!relativePath || relativePath.startsWith("..")) return filePath;
1410
+ return relativePath;
1411
+ }
1412
+
1413
+ //#endregion
1414
+ //#region src-cli/run-main.ts
1415
+ const output = new CliOutput();
1416
+ async function runMain(argv = process.argv.slice(2), cwd = process.cwd()) {
1417
+ const [command, ...rest] = argv;
1418
+ try {
1419
+ const result = await runCommand(command, parseOptions(rest), cwd);
1420
+ if (!result) return;
1421
+ printResult(result);
1422
+ process.exitCode = result.exitCode;
1423
+ } catch (error) {
1424
+ output.error(error instanceof Error ? error.message : String(error));
1425
+ process.exitCode = 1;
1426
+ }
1427
+ }
1428
+ async function runCommand(command, options, cwd) {
1429
+ switch (command) {
1430
+ case "generate": return await runProjectCommand("generate", options, cwd);
1431
+ case "validate":
1432
+ case "check": return await runProjectCommand("validate", options, cwd);
1433
+ case "graph": return await runGraphCommand(options, cwd);
1434
+ case "doctor": return await runProjectCommand("doctor", options, cwd);
1435
+ case "help":
1436
+ case "--help":
1437
+ case "-h":
1438
+ case void 0:
1439
+ printHelp();
1440
+ return;
1441
+ default:
1442
+ output.error(`Unknown command: ${command}`);
1443
+ printHelp();
1444
+ return {
1445
+ exitCode: 1,
1446
+ lines: []
1447
+ };
1448
+ }
1449
+ }
1450
+ function printResult(result) {
1451
+ for (const line of result.lines) printLine(line);
1452
+ }
1453
+ function printLine(line) {
1454
+ switch (line.kind) {
1455
+ case "title":
1456
+ output.title(line.product, line.command, line.rows ?? []);
1457
+ return;
1458
+ case "panel":
1459
+ output.panel(line.title, line.rows, line.badge);
1460
+ return;
1461
+ case "success":
1462
+ output.success(formatStatusLine(line));
1463
+ return;
1464
+ case "info":
1465
+ if (line.message.length === 0) {
1466
+ output.emptyLine();
1467
+ return;
1468
+ }
1469
+ output.info(formatStatusLine(line));
1470
+ return;
1471
+ case "warning":
1472
+ output.warning(formatStatusLine(line));
1473
+ return;
1474
+ case "error":
1475
+ output.error(formatStatusLine(line));
1476
+ return;
1477
+ }
1478
+ }
1479
+ function formatStatusLine(line) {
1480
+ return line.label ? output.step(line.label, line.message) : line.message;
1481
+ }
1482
+ function printHelp() {
1483
+ output.log([
1484
+ "ArchiCat",
1485
+ "",
1486
+ "Usage:",
1487
+ " archicat generate [--config archicat.config.ts]",
1488
+ " archicat validate [--config archicat.config.ts]",
1489
+ " archicat graph [--config archicat.config.ts]",
1490
+ " archicat doctor [--config archicat.config.ts]",
1491
+ "",
1492
+ "Aliases:",
1493
+ " archicat check -> archicat validate",
1494
+ ""
1495
+ ].join("\n"));
1496
+ }
1497
+ function parseOptions(args) {
1498
+ const options = {};
1499
+ for (let index = 0; index < args.length; index += 1) {
1500
+ const arg = args[index];
1501
+ if (arg === "--config" || arg === "-c") {
1502
+ const value = args[index + 1];
1503
+ if (!value) throw new Error(`${arg} requires a value.`);
1504
+ options.config = value;
1505
+ index += 1;
1506
+ }
1507
+ }
1508
+ return options;
1509
+ }
1510
+
1511
+ //#endregion
1512
+ export { runMain };