@truefoundry/tfy-infra-cli 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -6
- package/dist/index.js +18 -14
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -18,13 +18,10 @@ npx @truefoundry/tfy-infra-cli tpl render -c config.yaml
|
|
|
18
18
|
|
|
19
19
|
| Command | Description |
|
|
20
20
|
|---------|-------------|
|
|
21
|
-
| `tfy-init tpl render` | Render
|
|
22
|
-
| `tfy-init tpl schema` | Fetch and display a template's JSON Schema |
|
|
21
|
+
| `tfy-init tpl render` | Render, upgrade, or re-generate template files (auto-detects mode) |
|
|
23
22
|
| `tfy-init tpl validate` | Validate a local template (for authors) |
|
|
24
23
|
| `tfy-init tpl test` | Run test fixtures against a template |
|
|
25
24
|
| `tfy-init tpl verify` | Check integrity of files on disk against a manifest |
|
|
26
|
-
| `tfy-init tpl upgrade` | Upgrade platform files to a new template version |
|
|
27
|
-
| `tfy-init tpl hash` | Compute expected aggregate hash without generating files |
|
|
28
25
|
| `tfy-init tpl dev` | Watch mode for rapid template iteration |
|
|
29
26
|
|
|
30
27
|
For detailed usage, options, exit codes, and examples for each command, see the **[CLI Reference](../../docs/cli-reference.md)**.
|
|
@@ -32,13 +29,14 @@ For detailed usage, options, exit codes, and examples for each command, see the
|
|
|
32
29
|
## Supported Template Sources
|
|
33
30
|
|
|
34
31
|
- **Local files**: `file:///absolute/path` or `file://./relative/path`
|
|
35
|
-
- **
|
|
32
|
+
- **HTTPS bundles**: `https://registry.example.com/template/bundle.json`
|
|
36
33
|
|
|
37
34
|
## Global Options
|
|
38
35
|
|
|
39
36
|
```bash
|
|
40
37
|
--verbose Enable verbose output
|
|
41
38
|
-q, --quiet Suppress non-error output
|
|
39
|
+
--json Structured JSON to stdout (human logs to stderr)
|
|
42
40
|
-v, --version Show version
|
|
43
41
|
-h, --help Display help
|
|
44
42
|
```
|
|
@@ -73,8 +71,9 @@ All commands use a shared set of exit codes:
|
|
|
73
71
|
| 2 | FETCH_ERROR | Template fetch failed (not found, network) |
|
|
74
72
|
| 3 | RENDER_ERROR | Template rendering failed |
|
|
75
73
|
| 4 | FORMAT_ERROR | `tofu fmt` failed |
|
|
74
|
+
| 5 | DRIFT_BLOCKED | Source mismatch (`--allow-source-change`) or content drift (`--force`) blocked upgrade |
|
|
76
75
|
|
|
77
|
-
Some commands define additional codes for domain-specific outcomes (e.g., `DRIFT_DETECTED` for `verify`, `DRIFT_BLOCKED` for `upgrade
|
|
76
|
+
Some commands define additional codes for domain-specific outcomes (e.g., `DRIFT_DETECTED` for `verify`, `DRIFT_BLOCKED` for `render` in upgrade mode). Exit code 1 is also returned when overwrite confirmation is declined. See the [CLI Reference](../../docs/cli-reference.md) for details.
|
|
78
77
|
|
|
79
78
|
## License
|
|
80
79
|
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var Le=Object.create;var Ut=Object.defineProperty;var Je=Object.getOwnPropertyDescriptor;var ze=Object.getOwnPropertyNames;var We=Object.getPrototypeOf,Ge=Object.prototype.hasOwnProperty;var Be=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of ze(t))!Ge.call(e,o)&&o!==r&&Ut(e,o,{get:()=>t[o],enumerable:!(n=Je(t,o))||n.enumerable});return e};var V=(e,t,r)=>(r=e!=null?Le(We(e)):{},Be(t||!e||!e.__esModule?Ut(r,"default",{value:e,enumerable:!0}):r,e));var Ve=require("commander");var Me=require("commander");var ee=require("commander"),wt=require("fs/promises"),Z=require("path"),re=V(require("js-yaml")),vt=require("@truefoundry/tfy-infra-engine");var qe={timeout:3e4,retries:3,noCache:!1};function ut(e){return{...qe,...e}}async function pt(e,t,r=1e3){let n;for(let o=0;o<=t;o++)try{return await e()}catch(i){if(n=i,o<t){let u=r*Math.pow(2,o)*(.5+Math.random()*.5);await new Promise(s=>setTimeout(s,u))}}throw n instanceof Error?n:new Error(String(n))}function rt(e,t,r){return Promise.race([e,new Promise((n,o)=>setTimeout(()=>o(new Error(r??`Operation timed out after ${t}ms`)),t))])}function $t(e){let t=/\/(\d+(?:\.\d+){0,2})$/,r=e.match(t);return r?.[1]?{semver:r[1]}:null}var lt=require("@truefoundry/tfy-infra-engine");var jt=require("fs/promises"),Ct=require("path"),R=require("@truefoundry/tfy-infra-engine");var mt=require("fs/promises"),bt=require("path");async function G(e){let t=new Map,r=new Map;return await Mt(e,"",t,r),{files:t,staticFiles:r}}async function Mt(e,t,r,n){let o=t?(0,bt.join)(e,t):e,i=await(0,mt.readdir)(o,{withFileTypes:!0});for(let u of i){if(u.name.startsWith("_"))continue;let s=t?`${t}/${u.name}`:u.name;if(u.isDirectory())await Mt(e,s,r,n);else{let c=await(0,mt.readFile)((0,bt.join)(o,u.name),"utf-8");if(u.name.endsWith(".hbs")){let f=s.replace(/\.hbs$/,"");r.set(f,c)}else n.set(s,c)}}}var ot=class{canResolve(t){return t.startsWith("file://")}async resolve(t,r){let n=this.uriToPath(t);try{let o=(0,Ct.join)(n,"template.json"),i=await this.readFileWithError(o,"template.json"),u;try{u=JSON.parse(i)}catch(y){throw new R.EngineError(`Failed to parse template.json: ${y.message}`,R.EngineErrorCode.TEMPLATE_JSON_INVALID,y,{path:o})}let{metadata:s,jsonSchema:c}=(0,R.validateTemplateJson)(u),f=(0,Ct.join)(n,"src"),m,p;try{({files:m,staticFiles:p}=await G(f))}catch(y){throw y.code==="ENOENT"?new R.EngineError(`Template src/ directory not found in ${n}`,R.EngineErrorCode.TEMPLATE_NOT_FOUND,y,{path:f}):y}if(m.size===0&&p.size===0)throw new R.EngineError(`No template files found in ${f}`,R.EngineErrorCode.TEMPLATE_NOT_FOUND,void 0,{path:f});let l={semver:s.version,apiVersion:this.extractApiVersionFromUri(t)};return{metadata:s,jsonSchema:c,files:m,staticFiles:p,source:t,version:l}}catch(o){if(o instanceof R.EngineError)throw o;let i=o;throw i.code==="ENOENT"?new R.EngineError(`Template not found: ${n}`,R.EngineErrorCode.FILE_NOT_FOUND,o,{path:n,originalCode:i.code}):i.code==="EACCES"?new R.EngineError(`Permission denied: ${n}`,R.EngineErrorCode.FILE_NOT_FOUND,o,{path:n,originalCode:i.code}):new R.EngineError(`Failed to load template from ${n}: ${o.message}`,R.EngineErrorCode.FILE_NOT_FOUND,o,{path:n})}}uriToPath(t){return t.startsWith("file:///")||t.startsWith("file://")?t.substring(7):t}async readFileWithError(t,r){try{return await(0,jt.readFile)(t,"utf-8")}catch(n){throw n.code==="ENOENT"?new R.EngineError(`Required file not found: ${r}`,R.EngineErrorCode.TEMPLATE_JSON_NOT_FOUND,n,{path:t,filename:r}):n}}extractApiVersionFromUri(t){let n=this.uriToPath(t).split("/").filter(Boolean),o=n[n.length-1]??"";return/^v\d+$/.test(o)?o:void 0}};var U=require("fs/promises"),H=require("path"),Vt=require("stream/promises"),Ht=require("zlib"),Lt=require("tar"),h=require("@truefoundry/tfy-infra-engine");function It(e,t){if(!e)return`v${t}`;let r=e.split("/"),o=(r.at(-1)?.match(/^v\d+$/)?r.slice(0,-1):r).join("-");return o?`${o}-v${t}`:`v${t}`}function Xe(e){let t=e.split("/").at(-1);return t?.match(/^v\d+$/)?t:void 0}function Ke(e){let t=e.split(".").length;return t===3?"exact":t===2?"minor":"major"}function Jt(e){if(!e.startsWith("github://"))throw new h.EngineError(`Invalid GitHub URI: ${e}`,h.EngineErrorCode.GITHUB_NOT_FOUND);let r=e.substring(9).split("/");if(r.length<4)throw new h.EngineError(`Invalid GitHub URI - expected format github://org/repo/path/semver: ${e}`,h.EngineErrorCode.GITHUB_NOT_FOUND);let n=r[0]??"",o=r[1]??"",u=r.slice(2).join("/").match(/^(.*?)\/(\d+(?:\.\d+){0,2})$/);if(!u)throw new h.EngineError(`Invalid GitHub URI - could not extract version: ${e}`,h.EngineErrorCode.GITHUB_NOT_FOUND);let s=u[1]??"",c=u[2]??"0",f=Ke(c),m=It(s,c);return{owner:n,repo:o,path:s,version:{semver:c,tag:m,pinType:f}}}function zt(e){return`https://github.com/${e.owner}/${e.repo}/archive/refs/tags/${e.version.tag}.tar.gz`}async function Wt(e,t){if(e.version.pinType==="exact")return e.version.tag;let r=It(e.path,e.version.semver),n=`https://api.github.com/repos/${e.owner}/${e.repo}/git/matching-refs/tags/${r}`,o={"User-Agent":"tfy-infra-engine",Accept:"application/vnd.github.v3+json"},i=process.env.GITHUB_TOKEN||process.env.GH_TOKEN;i&&(o.Authorization=`token ${i}`);let u=await fetch(n,{headers:o});if(!u.ok)throw u.status===403?new h.EngineError("GitHub rate limit exceeded during version resolution. Try again later or set GITHUB_TOKEN.",h.EngineErrorCode.GITHUB_RATE_LIMITED):new h.EngineError(`GitHub API error during version resolution: HTTP ${u.status}`,h.EngineErrorCode.NETWORK_ERROR,void 0,{apiUrl:n,status:u.status});let s=await u.json(),c=/v(\d+\.\d+\.\d+)$/,f=[];for(let m of s){let p=m.ref.replace("refs/tags/",""),l=p.match(c);if(l?.[1]){let y=l[1].split(".").map(Number);y.length===3&&f.push({tag:p,parts:y})}}if(f.length===0)throw new h.EngineError(`No tags matching ${r}* found in ${e.owner}/${e.repo}`,h.EngineErrorCode.GITHUB_NOT_FOUND,void 0,{tagPrefix:r,apiUrl:n});return f.sort((m,p)=>{for(let l=0;l<3;l++){let y=(m.parts[l]??0)-(p.parts[l]??0);if(y!==0)return y}return 0}),f.at(-1).tag}var nt=class{tempDir;constructor(t){let r=process.env.HOME||process.env.USERPROFILE||"/tmp";this.tempDir=t??(0,H.join)(r,".cache","tfy-infra","github-temp")}canResolve(t){return t.startsWith("github://")}async resolve(t,r){let n=ut(r),o=Jt(t),i=o.version.tag;o.version.pinType!=="exact"&&(i=await rt(Wt(o,r),n.timeout,`Version resolution timed out after ${n.timeout}ms`));let u={...o,version:{...o.version,tag:i}},s=zt(u),c=(0,H.join)(this.tempDir,`${o.owner}-${o.repo}-${Date.now()}`);try{await(0,U.mkdir)(c,{recursive:!0}),await rt(pt(()=>this.downloadAndExtract(s,c),n.retries),n.timeout,`GitHub download timed out after ${n.timeout}ms`);let f=await(0,U.readdir)(c);if(f.length===0)throw new h.EngineError(`Empty archive from GitHub: ${t}`,h.EngineErrorCode.GITHUB_NOT_FOUND,void 0,{archiveUrl:s});let m=(0,H.join)(c,f[0]??""),p=o.path?(0,H.join)(m,o.path):m,l=(0,H.join)(p,"template.json"),y;try{y=await(0,U.readFile)(l,"utf-8")}catch{throw new h.EngineError(`template.json not found in GitHub template: ${t}`,h.EngineErrorCode.TEMPLATE_JSON_NOT_FOUND,void 0,{templateDir:p})}let $;try{$=JSON.parse(y)}catch(k){throw new h.EngineError(`Failed to parse template.json: ${k.message}`,h.EngineErrorCode.TEMPLATE_JSON_INVALID,k,{templateDir:p})}let{metadata:C,jsonSchema:M}=(0,h.validateTemplateJson)($),P=(0,H.join)(p,"src"),z,j;try{({files:z,staticFiles:j}=await G(P))}catch{throw new h.EngineError(`Template src/ directory not found in GitHub archive: ${t}`,h.EngineErrorCode.TEMPLATE_NOT_FOUND,void 0,{templateDir:p,srcDir:P})}if(z.size===0&&j.size===0)throw new h.EngineError(`No template files found in src/ for GitHub: ${t}`,h.EngineErrorCode.TEMPLATE_NOT_FOUND,void 0,{srcDir:P});let W={semver:C.version,apiVersion:Xe(o.path)};return{metadata:C,jsonSchema:M,files:z,staticFiles:j,source:t,version:W}}catch(f){if(f instanceof h.EngineError)throw f;let m=f;throw m.message?.includes("404")||m.status===404?new h.EngineError(`GitHub template not found: ${t}`,h.EngineErrorCode.GITHUB_NOT_FOUND,f,{archiveUrl:s}):m.message?.includes("rate limit")||m.status===403?new h.EngineError("GitHub rate limit exceeded. Try again later or use authentication.",h.EngineErrorCode.GITHUB_RATE_LIMITED,f):new h.EngineError(`GitHub error: ${f.message}`,h.EngineErrorCode.NETWORK_ERROR,f,{uri:t,archiveUrl:s})}finally{try{await(0,U.rm)(c,{recursive:!0,force:!0})}catch{}}}async downloadAndExtract(t,r){let n=await fetch(t,{headers:{"User-Agent":"tfy-infra-engine"}});if(!n.ok)throw new Error(`HTTP ${n.status}: ${n.statusText}`);if(!n.body)throw new Error("Empty response body");let o=n.body;await(0,Vt.pipeline)(o,(0,Ht.createGunzip)(),(0,Lt.extract)({cwd:r}))}};var D=require("@truefoundry/tfy-infra-engine");var Gt="JFROG_ACCESS_TOKEN";function Bt(e){try{let{hostname:t}=new URL(e);return t.includes("jfrog")}catch{return!1}}function Ye(e){let t={"User-Agent":"tfy-infra-cli",Accept:"application/json"};if(Bt(e)){let r=process.env[Gt];r&&(t.Authorization=`Bearer ${r}`)}return t}async function Ze(e,t){let r=await fetch(e,{headers:t});if(r.status===401||r.status===403){let n=Bt(e)?` Set ${Gt} in your environment to authenticate.`:"";throw new D.EngineError(`Authentication failed (HTTP ${r.status}) for ${e}.${n}`,D.EngineErrorCode.HTTPS_AUTH_FAILED,void 0,{uri:e,status:r.status})}if(!r.ok)throw new D.EngineError(`Bundle download failed: HTTP ${r.status} for ${e}`,D.EngineErrorCode.NETWORK_ERROR,void 0,{uri:e,status:r.status});return r}async function Qe(e,t){let n=await e.json(),{metadata:o,jsonSchema:i}=(0,D.validateTemplateJson)({template:n.metadata,schema:n.jsonSchema}),u=new Map(Object.entries(n.files??{})),s=new Map(Object.entries(n.staticFiles??{}));if(u.size===0&&s.size===0)throw new D.EngineError(`Bundle contains no template files: ${t}`,D.EngineErrorCode.TEMPLATE_NOT_FOUND,void 0,{uri:t});let c={semver:o.version};return{metadata:o,jsonSchema:i,files:u,staticFiles:s,source:t,version:c}}var it=class{canResolve(t){return t.startsWith("https://")&&t.toLowerCase().endsWith(".json")}async resolve(t,r){let n=ut(r),o=Ye(t),i=await rt(pt(()=>Ze(t,o),n.retries),n.timeout,`HTTPS bundle download timed out after ${n.timeout}ms`);return Qe(i,t)}};var E=require("fs/promises"),T=require("path"),qt=require("crypto"),B=require("@truefoundry/tfy-infra-engine");function Xt(){let e=process.env.HOME||process.env.USERPROFILE||"~";return(0,T.join)(e,".cache","tfy-infra","templates")}var st=class{cacheDir;constructor(t){this.cacheDir=t??Xt()}getCacheKey(t){return(0,qt.createHash)("sha256").update(t).digest("hex").substring(0,16)}getCachePath(t,r){let n=this.getCacheKey(t),o=r.replace(/\//g,"-");return(0,T.join)(this.cacheDir,n,o)}async has(t,r){let n=this.getCachePath(t,r);try{let o=(0,T.join)(n,"template.json");return await(0,E.stat)(o),!0}catch{return!1}}async get(t,r){let n=this.getCachePath(t,r);try{let o=(0,T.join)(n,"template.json"),i=await(0,E.readFile)(o,"utf-8"),u=JSON.parse(i),{metadata:s,jsonSchema:c}=(0,B.validateTemplateJson)(u),f=new Map,m=new Map;await this.readCacheDir(n,"",f,m);let p={semver:s.version};return{metadata:s,jsonSchema:c,files:f,staticFiles:m,source:t,version:p}}catch{return null}}async set(t){let r=this.getCachePath(t.source,t.version.semver);try{await(0,E.mkdir)(r,{recursive:!0});let n={template:{name:t.metadata.name,version:t.metadata.version,...t.metadata.description?{description:t.metadata.description}:{}},schema:t.jsonSchema};await(0,E.writeFile)((0,T.join)(r,"template.json"),JSON.stringify(n,null,2),"utf-8");for(let[o,i]of t.files){let u=(0,T.join)(r,`${o}.hbs`);await(0,E.mkdir)((0,T.dirname)(u),{recursive:!0}),await(0,E.writeFile)(u,i,"utf-8")}for(let[o,i]of t.staticFiles){let u=(0,T.join)(r,o);await(0,E.mkdir)((0,T.dirname)(u),{recursive:!0}),await(0,E.writeFile)(u,i,"utf-8")}}catch(n){throw new B.EngineError(`Failed to cache template: ${n.message}`,B.EngineErrorCode.CACHE_ERROR,n,{source:t.source,version:t.version.semver})}}async delete(t,r){if(r){let n=this.getCachePath(t,r);try{await(0,E.rm)(n,{recursive:!0,force:!0})}catch{}}else{let n=this.getCacheKey(t),o=(0,T.join)(this.cacheDir,n);try{await(0,E.rm)(o,{recursive:!0,force:!0})}catch{}}}async clear(){try{await(0,E.rm)(this.cacheDir,{recursive:!0,force:!0})}catch{}}async stats(){let t=0,r=0,n=0;try{let o=await(0,E.readdir)(this.cacheDir);t=o.length;for(let i of o){let u=(0,T.join)(this.cacheDir,i),s=await(0,E.readdir)(u);r+=s.length;for(let c of s){let f=(0,T.join)(u,c);n+=await this.dirSize(f)}}}catch{}return{sources:t,templates:r,size:n}}async readCacheDir(t,r,n,o){let i=r?(0,T.join)(t,r):t,u=await(0,E.readdir)(i,{withFileTypes:!0});for(let s of u){if(s.name==="template.json"||s.name.startsWith("_"))continue;let c=r?`${r}/${s.name}`:s.name;if(s.isDirectory())await this.readCacheDir(t,c,n,o);else{let f=await(0,E.readFile)((0,T.join)(i,s.name),"utf-8");s.name.endsWith(".hbs")?n.set(c.replace(/\.hbs$/,""),f):o.set(c,f)}}}async dirSize(t){let r=0,n=await(0,E.readdir)(t,{withFileTypes:!0});for(let o of n){let i=(0,T.join)(t,o.name);if(o.isDirectory())r+=await this.dirSize(i);else{let u=await(0,E.stat)(i);r+=u.size}}return r}};var Ft=class{resolvers=[];cache;constructor(t,r){this.cache=new st(r),t?this.resolvers=t:(this.register(new ot),this.register(new nt),this.register(new it))}register(t){this.resolvers.push(t)}getResolver(t){return this.resolvers.find(r=>r.canResolve(t))}async resolve(t,r){let n=this.getResolver(t);if(!n){let i=t.split("://")[0]||"unknown";throw new lt.EngineError(`No resolver found for URI scheme: ${i}://`,lt.EngineErrorCode.TEMPLATE_NOT_FOUND,void 0,{uri:t,scheme:i,supportedSchemes:["file://","github://","https://*.json"]})}if(!r?.noCache&&!t.startsWith("file://")){let i=$t(t);if(i){let u=await this.cache.get(t,i.semver);if(u)return u}}let o=await n.resolve(t,r);return t.startsWith("file://")||await this.cache.set(o),o}canResolve(t){return this.resolvers.some(r=>r.canResolve(t))}getSupportedSchemes(){return["file://","github://","https://*.json"]}async clearCache(t){t?await this.cache.delete(t):await this.cache.clear()}async getCacheStats(){return this.cache.stats()}};function I(e){return new Ft(void 0,e)}var Yt=require("fs/promises"),Zt=V(require("js-yaml"));var St=require("fs/promises"),Kt=require("path");async function Pt(e){try{return await(0,St.access)(e),!0}catch{return!1}}async function q(e){let t=(0,Kt.join)(e,"tests","default","inputs.yaml");try{return await(0,St.access)(t),t}catch{return null}}async function X(e){let t=await(0,Yt.readFile)(e,"utf-8"),r=e.endsWith(".json"),n;try{r?n=JSON.parse(t):n=Zt.load(t)}catch(o){let i=r?"JSON":"YAML";throw new Error(`Failed to parse ${i} config file: ${o.message}`,{cause:o})}if(!n.intentId||typeof n.intentId!="string"||n.intentId.trim()==="")throw new Error(`Configuration file is missing required field 'intentId'. Add 'intentId: "<your-cluster-identity>"' to ${e}. The intentId uniquely identifies the cluster for integrity tracking.`);return{template:n.template,inputs:n.inputs??{},intentId:n.intentId,platformPrefix:n.platformPrefix,options:n.options}}var Qt=require("fs/promises");async function _(e){let t;try{t=await(0,Qt.readFile)(e,"utf-8")}catch(o){throw o.code==="ENOENT"?new Error(`Manifest not found: ${e}`,{cause:o}):new Error(`Failed to read manifest: ${o.message}`,{cause:o})}let r;try{r=JSON.parse(t)}catch{throw new Error(`Invalid JSON in manifest: ${e}`)}let n=["manifestVersion","files","aggregateHash","inputs","platformPrefix","intentId","templateSource","templateVersion"];for(let o of n)if(r[o]===void 0)throw new Error(`Manifest is missing required field: ${o}`);return r}function K(e){if(!e.inputs)throw new Error("Cannot reconstruct envelope: manifest is missing inputs field");if(!e.platformPrefix)throw new Error("Cannot reconstruct envelope: manifest is missing platformPrefix field");return{template:e.templateSource,inputs:e.inputs,intentId:e.intentId,platformPrefix:e.platformPrefix}}var at=require("fs/promises"),dt=require("path");async function Y(e,t,r){await(0,at.mkdir)(e,{recursive:!0});for(let[n,o]of t.files){let i=(0,dt.join)(e,n);r==="upgrade"&&o.zone==="user"&&await Pt(i)||(await(0,at.mkdir)((0,dt.dirname)(i),{recursive:!0}),await(0,at.writeFile)(i,o.content,"utf-8"))}}var F=require("@truefoundry/tfy-infra-engine");var L="normal";function Dt(e){L=e}function Nt(){return L}function v(e){L!=="quiet"&&console.log(`\u2713 ${e}`)}function w(e){console.error(`\u2717 ${e}`)}function a(e){L!=="quiet"&&console.log(e)}function g(e){L==="verbose"&&console.log(` ${e}`)}function te(e){L!=="quiet"&&console.warn(`\u26A0 ${e}`)}function tr(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function gt(e){return e.map(t=>` \u2022 ${t.path}: ${t.message}`).join(`
|
|
3
|
-
`)}function
|
|
4
|
-
`),o=t.
|
|
5
|
-
`);return(0,me.createTwoFilesPatch)(`expected/${r}`,`actual/${r}`,n,o,"","",{context:3})}function he(){return new le.Command("test").description("Run test fixtures against a template").option("-d, --directory <dir>","Path to local template directory",".").option("--fixture <name>","Run specific fixture only").option("--update","Update expected outputs with actual results").option("--diff","Show unified diff on failures").option("--skip-format","Skip tofu fmt in tests").action(async t=>{try{let r=await ur(t);process.exit(r)}catch(r){b(r)}})}async function ur(e){let t=(0,N.resolve)(e.directory),r=(0,N.join)(t,"tests"),n=`file://${t}`;a(`Testing template at: ${t}`),a("");let o;try{let p=await(0,S.readdir)(r);o=[];for(let l of p)try{let y=(0,N.join)(r,l,"inputs.yaml");await(0,S.readFile)(y),o.push(l)}catch{}}catch{return w(`No tests directory found at ${r}`),d.VALIDATION_ERROR}if(o.length===0)return w("No test fixtures found"),a(""),a("Expected structure:"),a(" tests/"),a(" <fixture-name>/"),a(" inputs.yaml"),a(" expected/"),d.VALIDATION_ERROR;if(e.fixture){if(!o.includes(e.fixture))return w(`Fixture '${e.fixture}' not found`),a(`Available fixtures: ${o.join(", ")}`),d.VALIDATION_ERROR;o=[e.fixture]}a(`Running ${o.length} fixture(s)...`),a("");let u=await I().resolve(n,{noCache:!0}),s=(0,ge.createEngine)(),c=[];for(let p of o){let l=(0,N.join)(r,p),y=(0,N.join)(l,"inputs.yaml"),$=(0,N.join)(l,"expected");g(`Running fixture: ${p}`);try{let C=await(0,S.readFile)(y,"utf-8"),M=de.load(C),P={template:u,inputs:M,intentId:`test-${p}`,options:{skipFormat:e.skipFormat}},z=await s.install(P);if(e.update)await pr($,z),v(`${p}: updated`),c.push({name:p,passed:!0});else{let{errors:j,diffs:W}=await mr($,z,e.diff);if(j.length===0)v(`${p}: passed`),c.push({name:p,passed:!0});else{w(`${p}: failed`);for(let k of j)a(` ${k}`);if(e.diff&&W&&W.length>0)for(let k of W){a(` --- expected/${k.filename}`),a(` +++ actual/${k.filename}`);let He=k.diff.split(`
|
|
6
|
-
`)
|
|
7
|
-
`)),p=
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
`);if(
|
|
11
|
-
`).
|
|
12
|
-
`).
|
|
13
|
-
`)
|
|
14
|
-
`)}
|
|
15
|
-
`)
|
|
2
|
+
"use strict";var $t=Object.create;var ke=Object.defineProperty;var Pt=Object.getOwnPropertyDescriptor;var Dt=Object.getOwnPropertyNames;var jt=Object.getPrototypeOf,kt=Object.prototype.hasOwnProperty;var Nt=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of Dt(e))!kt.call(t,o)&&o!==r&&ke(t,o,{get:()=>e[o],enumerable:!(n=Pt(e,o))||n.enumerable});return t};var B=(t,e,r)=>(r=t!=null?$t(jt(t)):{},Nt(e||!t||!t.__esModule?ke(r,"default",{value:t,enumerable:!0}):r,t));var Ft=require("commander");var St=require("commander");var Ve=require("commander"),z=require("fs/promises"),J=require("path");var se=require("fs/promises"),ve=require("path");async function ae(t){let e=new Map,r=new Map;return await Ne(t,"",e,r),{files:e,staticFiles:r}}async function Ne(t,e,r,n){let o=e?(0,ve.join)(t,e):t,i=await(0,se.readdir)(o,{withFileTypes:!0});for(let a of i){if(a.name.startsWith("_"))continue;let c=e?`${e}/${a.name}`:a.name;if(a.isDirectory())await Ne(t,c,r,n);else{let u=await(0,se.readFile)((0,ve.join)(o,a.name),"utf-8");if(a.name.endsWith(".hbs")){let f=c.replace(/\.hbs$/,"");r.set(f,u)}else n.set(c,u)}}}var T=require("@truefoundry/tfy-infra-engine");var _="normal",X=!1;function Re(t){_=t}function Ee(){return _}function _e(t){X=t}function j(t){process.stdout.write(t+`
|
|
3
|
+
`)}function w(t){_!=="quiet"&&(X?console.error:console.log)(`\u2713 ${t}`)}function g(t){console.error(`\u2717 ${t}`)}function s(t){_!=="quiet"&&(X?console.error:console.log)(t)}function y(t){_==="verbose"&&(X?console.error:console.log)(` ${t}`)}function N(t){_!=="quiet"&&console.warn(`\u26A0 ${t}`)}function _t(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(1)} MB`}function ce(t){return t.map(e=>` \u2022 ${e.path}: ${e.message}`).join(`
|
|
4
|
+
`)}function ue(t){switch(t){case"content_drift":return"DRIFTED";case"missing_file":return"MISSING";case"unexpected_file":return"UNEXPECTED";case"metadata_inconsistency":return"INCONSISTENT";case"source_mismatch":return"SOURCE MISMATCH"}}function Ae(t){if(_==="quiet")return;let e=X?console.error:console.log;e("Files:");for(let r of t)e(` - ${r.path} (${_t(r.size)})`)}var d={SUCCESS:0,VALIDATION_ERROR:1,FETCH_ERROR:2,RENDER_ERROR:3,FORMAT_ERROR:4,DRIFT_BLOCKED:5};function S(t,e=d.RENDER_ERROR){if(t instanceof T.EngineError){if(g(t.message),Ee()==="verbose"&&t.details){s(""),s("Details:");for(let[r,n]of Object.entries(t.details))r!=="errors"&&s(` ${r}: ${JSON.stringify(n)}`)}switch(t.details?.errors&&(s(""),s("Errors:"),s(ce(t.details.errors))),t.code){case T.EngineErrorCode.INPUT_VALIDATION_FAILED:case T.EngineErrorCode.SCHEMA_INVALID:case T.EngineErrorCode.ENVELOPE_VALIDATION_FAILED:process.exit(d.VALIDATION_ERROR);break;case T.EngineErrorCode.TEMPLATE_NOT_FOUND:case T.EngineErrorCode.FILE_NOT_FOUND:case T.EngineErrorCode.NETWORK_ERROR:process.exit(d.FETCH_ERROR);break;case T.EngineErrorCode.TOFU_NOT_FOUND:case T.EngineErrorCode.TOFU_FMT_FAILED:s(""),s("Run with --skip-format to skip formatting."),process.exit(d.FORMAT_ERROR);break;case T.EngineErrorCode.MANIFEST_PARSE_ERROR:process.exit(d.VALIDATION_ERROR);break;default:process.exit(e)}}g(t.message),Ee()==="verbose"&&console.error(t.stack),process.exit(e)}function Me(){return new Ve.Command("bundle").description("Build a bundle.json from a schema package and src/ directory").requiredOption("--schema <path-or-dash>",'Schema package JSON file, or "-" for stdin').requiredOption("--src <dir>","Template source directory containing .hbs and static files").requiredOption("--name <string>","Template name").option("--description <string>","Template description").option("-o, --output <path>","Output file path (default: stdout)").action(async e=>{try{await Lt(e)}catch(r){S(r)}})}async function At(t){let e;t==="-"?e=await Vt():e=await(0,z.readFile)((0,J.resolve)(t),"utf-8");let r;try{r=JSON.parse(e)}catch(o){throw new Error(`Failed to parse schema package JSON: ${o.message}`,{cause:o})}let n=r;if(!n.jsonSchema||typeof n.jsonSchema!="object")throw new Error('Schema package must contain a "jsonSchema" object');if(!Array.isArray(n.uiSchema))throw new Error('Schema package must contain a "uiSchema" array');return{jsonSchema:n.jsonSchema,uiSchema:n.uiSchema}}function Vt(){return new Promise((t,e)=>{let r=[];process.stdin.on("data",n=>r.push(n)),process.stdin.on("end",()=>t(Buffer.concat(r).toString("utf-8"))),process.stdin.on("error",e)})}function Mt(t){let e=t.properties;if(!e)return;let r=e.version;if(!r)return;let n=r.default;return typeof n=="string"?n:void 0}async function Lt(t){let e=await At(t.schema),r=t.version??Mt(e.jsonSchema);if(!r)throw new Error('Could not determine template version. Provide --version or ensure the schema has a "version" property with a default value.');let n=(0,J.resolve)(t.src),{files:o,staticFiles:i}=await ae(n);if(o.size===0&&i.size===0)throw new Error(`No template files found in ${n}`);let a={name:t.name,version:r};t.description&&(a.description=t.description);let c={metadata:a,jsonSchema:e.jsonSchema,uiSchema:e.uiSchema,files:Object.fromEntries(o),staticFiles:Object.fromEntries(i),version:{semver:r}},u=JSON.stringify(c,null,2)+`
|
|
5
|
+
`;if(t.output){let f=(0,J.resolve)(t.output);await(0,z.mkdir)((0,J.dirname)(f),{recursive:!0}),await(0,z.writeFile)(f,u,"utf-8"),w(`Bundle written to ${f}`)}else process.stdout.write(u)}var rt=require("commander"),nt=require("readline"),M=require("fs/promises"),k=require("path"),ot=B(require("js-yaml")),ne=require("@truefoundry/tfy-infra-engine");var fe=require("@truefoundry/tfy-infra-engine");var G=require("fs/promises"),Le=require("path"),R=require("@truefoundry/tfy-infra-engine"),Y=class{canResolve(e){return e.startsWith("file://")}async resolve(e,r){let n=this.uriToPath(e);try{let o=await(0,G.stat)(n);if(o.isFile()&&n.endsWith(".json"))return this.resolveBundle(n,e);if(o.isDirectory()){let i=(0,Le.join)(n,"bundle.json");if(await this.fileExists(i))return this.resolveBundle(i,e);throw new R.EngineError(`bundle.json not found in ${n}`,R.EngineErrorCode.BUNDLE_NOT_FOUND,void 0,{path:n})}throw new R.EngineError(`Not a file or directory: ${n}`,R.EngineErrorCode.TEMPLATE_NOT_FOUND,void 0,{path:n})}catch(o){if(o instanceof R.EngineError)throw o;let i=o;throw i.code==="ENOENT"?new R.EngineError(`Template not found: ${n}`,R.EngineErrorCode.FILE_NOT_FOUND,o,{path:n,originalCode:i.code}):i.code==="EACCES"?new R.EngineError(`Permission denied: ${n}`,R.EngineErrorCode.FILE_NOT_FOUND,o,{path:n,originalCode:i.code}):new R.EngineError(`Failed to load template from ${n}: ${o.message}`,R.EngineErrorCode.FILE_NOT_FOUND,o,{path:n})}}async resolveBundle(e,r){let n=await(0,G.readFile)(e,"utf-8"),o;try{o=JSON.parse(n)}catch(l){throw new R.EngineError(`Failed to parse bundle.json: ${l.message}`,R.EngineErrorCode.BUNDLE_INVALID,l,{path:e})}let i=o.metadata;if(!i||typeof i!="object")throw new R.EngineError('bundle.json must contain a "metadata" object',R.EngineErrorCode.BUNDLE_INVALID,void 0,{path:e});if(!i.name||!i.version)throw new R.EngineError('bundle.json metadata must contain "name" and "version"',R.EngineErrorCode.BUNDLE_INVALID,void 0,{path:e});let a={name:i.name,version:i.version,...i.description?{description:i.description}:{}},c=o.jsonSchema;if(!c||typeof c!="object")throw new R.EngineError('bundle.json must contain a "jsonSchema" object',R.EngineErrorCode.BUNDLE_INVALID,void 0,{path:e});let u=new Map(Object.entries(o.files??{})),f=new Map(Object.entries(o.staticFiles??{}));if(u.size===0&&f.size===0)throw new R.EngineError(`Bundle contains no template files: ${e}`,R.EngineErrorCode.TEMPLATE_NOT_FOUND,void 0,{path:e});let m={semver:a.version};return{metadata:a,jsonSchema:c,files:u,staticFiles:f,source:r,version:m}}uriToPath(e){return e.startsWith("file:///")||e.startsWith("file://")?e.substring(7):e}async fileExists(e){try{return await(0,G.stat)(e),!0}catch{return!1}}};var F=require("@truefoundry/tfy-infra-engine");var Ut={timeout:3e4,retries:3,noCache:!1};function Ue(t){return{...Ut,...t}}async function Be(t,e,r=1e3){let n;for(let o=0;o<=e;o++)try{return await t()}catch(i){if(n=i,o<e){let a=r*Math.pow(2,o)*(.5+Math.random()*.5);await new Promise(c=>setTimeout(c,a))}}throw n instanceof Error?n:new Error(String(n))}function Je(t,e,r){let n,o=new Promise((i,a)=>{n=setTimeout(()=>a(new Error(r??`Operation timed out after ${e}ms`)),e)});return Promise.race([t,o]).finally(()=>clearTimeout(n))}var ze="JFROG_ACCESS_TOKEN";function We(t){try{let{hostname:e}=new URL(t);return e.includes("jfrog")}catch{return!1}}function Bt(t){let e={"User-Agent":"tfy-infra-cli",Accept:"application/json"};if(We(t)){let r=process.env[ze];r&&(e.Authorization=`Bearer ${r}`)}return e}async function Jt(t,e){let r=await fetch(t,{headers:e});if(r.status===401||r.status===403){let n=We(t)?` Set ${ze} in your environment to authenticate.`:"";throw new F.EngineError(`Authentication failed (HTTP ${r.status}) for ${t}.${n}`,F.EngineErrorCode.HTTPS_AUTH_FAILED,void 0,{uri:t,status:r.status})}if(!r.ok)throw new F.EngineError(`Bundle download failed: HTTP ${r.status} for ${t}`,F.EngineErrorCode.NETWORK_ERROR,void 0,{uri:t,status:r.status});return r}async function zt(t,e){let n=await t.json(),{metadata:o,jsonSchema:i}=(0,F.validateBundle)(n),a=new Map(Object.entries(n.files??{})),c=new Map(Object.entries(n.staticFiles??{}));if(a.size===0&&c.size===0)throw new F.EngineError(`Bundle contains no template files: ${e}`,F.EngineErrorCode.TEMPLATE_NOT_FOUND,void 0,{uri:e});let u={semver:o.version};return{metadata:o,jsonSchema:i,files:a,staticFiles:c,source:e,version:u}}var Z=class{canResolve(e){return e.startsWith("https://")&&e.toLowerCase().endsWith(".json")}async resolve(e,r){let n=Ue(r),o=Bt(e),i=await Je(Be(()=>Jt(e,o),n.retries),n.timeout,`HTTPS bundle download timed out after ${n.timeout}ms`);return zt(i,e)}};var x=require("fs/promises"),C=require("path"),He=require("crypto"),W=require("@truefoundry/tfy-infra-engine");function qe(){let t=process.env.HOME||process.env.USERPROFILE||"~";return(0,C.join)(t,".cache","tfy-infra","templates")}var Q=class{cacheDir;constructor(e){this.cacheDir=e??qe()}getCacheKey(e){return(0,He.createHash)("sha256").update(e).digest("hex").substring(0,16)}getCachePath(e,r){let n=this.getCacheKey(e),o=r.replace(/\//g,"-");return(0,C.join)(this.cacheDir,n,o)}async has(e,r){let n=this.getCachePath(e,r);try{let o=(0,C.join)(n,"bundle.json");return await(0,x.stat)(o),!0}catch{return!1}}async get(e,r){let n=this.getCachePath(e,r);try{let o=(0,C.join)(n,"bundle.json"),i=await(0,x.readFile)(o,"utf-8"),a=JSON.parse(i),{metadata:c,jsonSchema:u}=(0,W.validateBundle)(a),f=a,m=new Map(Object.entries(f.files??{})),l=new Map(Object.entries(f.staticFiles??{})),p={semver:c.version};return{metadata:c,jsonSchema:u,files:m,staticFiles:l,source:e,version:p}}catch{return null}}async set(e){let r=this.getCachePath(e.source,e.version.semver);try{await(0,x.mkdir)(r,{recursive:!0});let n={metadata:{name:e.metadata.name,version:e.metadata.version,...e.metadata.description?{description:e.metadata.description}:{}},jsonSchema:e.jsonSchema,files:Object.fromEntries(e.files),staticFiles:Object.fromEntries(e.staticFiles)};await(0,x.writeFile)((0,C.join)(r,"bundle.json"),JSON.stringify(n,null,2),"utf-8")}catch(n){throw new W.EngineError(`Failed to cache template: ${n.message}`,W.EngineErrorCode.CACHE_ERROR,n,{source:e.source,version:e.version.semver})}}async delete(e,r){if(r){let n=this.getCachePath(e,r);try{await(0,x.rm)(n,{recursive:!0,force:!0})}catch{}}else{let n=this.getCacheKey(e),o=(0,C.join)(this.cacheDir,n);try{await(0,x.rm)(o,{recursive:!0,force:!0})}catch{}}}async clear(){try{await(0,x.rm)(this.cacheDir,{recursive:!0,force:!0})}catch{}}async stats(){let e=0,r=0,n=0;try{let o=await(0,x.readdir)(this.cacheDir);e=o.length;for(let i of o){let a=(0,C.join)(this.cacheDir,i),c=await(0,x.readdir)(a);r+=c.length;for(let u of c){let f=(0,C.join)(a,u);n+=await this.dirSize(f)}}}catch{}return{sources:e,templates:r,size:n}}async dirSize(e){let r=0,n=await(0,x.readdir)(e,{withFileTypes:!0});for(let o of n){let i=(0,C.join)(e,o.name);if(o.isDirectory())r+=await this.dirSize(i);else{let a=await(0,x.stat)(i);r+=a.size}}return r}};var xe=class{resolvers=[];cache;constructor(e,r){this.cache=new Q(r),e?this.resolvers=e:(this.register(new Y),this.register(new Z))}register(e){this.resolvers.push(e)}getResolver(e){return this.resolvers.find(r=>r.canResolve(e))}async resolve(e,r){let n=this.getResolver(e);if(!n){let i=e.split("://")[0]||"unknown";throw new fe.EngineError(`No resolver found for URI scheme: ${i}://`,fe.EngineErrorCode.TEMPLATE_NOT_FOUND,void 0,{uri:e,scheme:i,supportedSchemes:["file://","https://*.json"]})}let o=await n.resolve(e,r);return e.startsWith("file://")||await this.cache.set(o),o}canResolve(e){return this.resolvers.some(r=>r.canResolve(e))}getSupportedSchemes(){return["file://","https://*.json"]}async clearCache(e){e?await this.cache.delete(e):await this.cache.clear()}async getCacheStats(){return this.cache.stats()}};function $(t){return new xe(void 0,t)}var Xe=require("fs/promises"),Ge=B(require("js-yaml"));var Oe=require("fs/promises"),Ke=require("path");async function be(t){try{return await(0,Oe.access)(t),!0}catch{return!1}}async function H(t){let e=(0,Ke.join)(t,"tests","default","inputs.yaml");try{return await(0,Oe.access)(e),e}catch{return null}}async function Te(t){let e=await(0,Xe.readFile)(t,"utf-8"),r=t.endsWith(".json"),n;try{r?n=JSON.parse(e):n=Ge.load(e)}catch(o){let i=r?"JSON":"YAML";throw new Error(`Failed to parse ${i} config file: ${o.message}`,{cause:o})}if(!n.intentId||typeof n.intentId!="string"||n.intentId.trim()==="")throw new Error(`Configuration file is missing required field 'intentId'. Add 'intentId: "<your-cluster-identity>"' to ${t}. The intentId uniquely identifies the cluster for integrity tracking.`);return{template:n.template,inputs:n.inputs??{},intentId:n.intentId,platformPrefix:n.platformPrefix,options:n.options}}var Ye=require("fs/promises");async function ee(t){let e;try{e=await(0,Ye.readFile)(t,"utf-8")}catch(o){throw o.code==="ENOENT"?new Error(`Manifest not found: ${t}`,{cause:o}):new Error(`Failed to read manifest: ${o.message}`,{cause:o})}let r;try{r=JSON.parse(e)}catch{throw new Error(`Invalid JSON in manifest: ${t}`)}let n=["manifestVersion","files","aggregateHash","inputs","platformPrefix","intentId","templateSource","templateVersion"];for(let o of n)if(r[o]===void 0)throw new Error(`Manifest is missing required field: ${o}`);return r}function Ze(t){if(!t.inputs)throw new Error("Cannot reconstruct envelope: manifest is missing inputs field");if(!t.platformPrefix)throw new Error("Cannot reconstruct envelope: manifest is missing platformPrefix field");return{template:t.templateSource,inputs:t.inputs,intentId:t.intentId,platformPrefix:t.platformPrefix}}var le=require("fs/promises"),Qe=require("path"),q=require("@truefoundry/tfy-infra-engine");async function pe(t,e=q.DEFAULT_PREFIX){let r=new Map,n;try{n=(await(0,le.readdir)(t,{withFileTypes:!0})).filter(i=>i.isFile()).map(i=>i.name)}catch(o){throw o.code==="ENOENT"?new Error(`Directory not found: ${t}`,{cause:o}):new Error(`Failed to read directory: ${o.message}`,{cause:o})}for(let o of n){if(o==="manifest.json")continue;let i=await(0,le.readFile)((0,Qe.join)(t,o),"utf-8"),a=(0,q.classifyFile)(o,e),c={content:i,zone:a};if(a==="platform"){let u=(0,q.parseHeader)(i);u&&(c.header=u)}r.set(o,c)}return r}var te=require("fs/promises"),me=require("path");async function re(t,e,r){await(0,te.mkdir)(t,{recursive:!0});for(let[n,o]of e.files){let i=(0,me.join)(t,n);r==="upgrade"&&o.zone==="user"&&await be(i)||(await(0,te.mkdir)((0,me.dirname)(i),{recursive:!0}),await(0,te.writeFile)(i,o.content,"utf-8"))}}var et=require("diff");function V(t,e,r){let n=t.replace(/\r\n/g,`
|
|
6
|
+
`),o=e.replace(/\r\n/g,`
|
|
7
|
+
`);return(0,et.createTwoFilesPatch)(`expected/${r}`,`actual/${r}`,n,o,"","",{context:3})}function Wt(t){if([t.config,t.fromManifest].filter(Boolean).length>1)throw new Error("--config and --from-manifest are mutually exclusive. Use one or the other.");if(t.config&&t.directory!==".")throw new Error("--config and --directory are mutually exclusive. Use one or the other.")}function it(){return new rt.Command("render").description("Render a template to HCL files").option("-c, --config <path>","Path to envelope config file (YAML or JSON)").option("-d, --directory <dir>","Path to template version directory",".").option("-f, --fixture <path>","Input file to use (when using --directory)").option("-i, --input <key=value>","Inline input override (can be repeated)",Ht,[]).requiredOption("-o, --output <dir>","Output directory").option("--from-manifest <path>","Path to manifest.json to use as input source").option("--force","Proceed even if managed files have drifted (upgrade mode)").option("--allow-source-change","Allow upgrade when template source has changed").option("--dry-run","Show what would change without writing files").option("--json","Output result as structured JSON").option("--skip-format","Skip tofu fmt formatting").option("--no-cache","Force re-fetch template").option("--timeout <ms>","Network timeout in milliseconds","30000").option("--intent-id <id>","Cluster identity for integrity tracking").action(async e=>{try{e.config&&(e.config=(0,k.resolve)(e.config)),e.fromManifest&&(e.fromManifest=(0,k.resolve)(e.fromManifest)),Wt({config:e.config,directory:e.directory,fromManifest:e.fromManifest});let r=await qt(e);process.exit(r)}catch(r){S(r)}})}function Ht(t,e){return[...e,t]}function Se(t){let e={};if(!t)return e;for(let r of t){let n=r.indexOf("=");if(n>0){let o=r.substring(0,n),i=r.substring(n+1);e[o]=i}}return e}async function qt(t){t.json&&_e(!0);let e=(0,k.resolve)(t.output),r=parseInt(t.timeout,10);if(t.fromManifest)return Gt(t.fromManifest,t,e,r);if(t.config){let o=(0,k.join)(e,"manifest.json");return await st(o)?Qt(t.config,t,e,r,o):Zt(t.config,t,e,r)}let n=(0,k.resolve)(t.directory);return Yt(n,t,e,r)}async function st(t){try{return await(0,M.access)(t),!0}catch{return!1}}async function Kt(t){if(!process.stdin.isTTY)return!1;let e=(0,nt.createInterface)({input:process.stdin,output:process.stderr});return new Promise(r=>{e.question(`${t} [y/N] `,n=>{e.close(),r(n.trim().toLowerCase()==="y")})})}async function Xt(t){try{return(await(0,M.readdir)(t,{withFileTypes:!0})).filter(r=>r.isFile()&&r.name!=="config.yaml"&&r.name!=="config.json").length}catch{return 0}}async function Ie(t,e){if(e.force)return null;let r=await Xt(t);return r===0||(N(`Output directory contains ${r} existing files that will be overwritten.`),await Kt("Proceed?"))?null:(e.json?j(JSON.stringify({aborted:!0,reason:"overwrite_confirmation",existingFiles:r},null,2)):s("Aborted. Use --force to skip confirmation."),d.VALIDATION_ERROR)}async function Gt(t,e,r,n){y(`Loading manifest from ${t}`);let o=await ee(t),i=Ze(o);e.intentId&&(i.intentId=e.intentId),w(`Template: ${i.template}`),y(`Intent ID: ${i.intentId}`),y("Resolving template...");let u={template:await $().resolve(i.template,{noCache:e.cache===!1,timeout:n}),inputs:i.inputs,intentId:i.intentId,platformPrefix:i.platformPrefix,options:{skipFormat:e.skipFormat}};y("Re-rendering from manifest...");let m=await(0,ne.createEngine)().install(u);if(e.dryRun)return await Fe(m,r,e.json);let l=await Ie(r,e);return l!==null?l:(w("Inputs validated"),w(`Generated ${m.files.size-1} files + manifest.json`),e.skipFormat||w("Formatted with tofu fmt"),await re(r,m,"install"),Ce(m,r,e.json),d.SUCCESS)}async function Yt(t,e,r,n){let o=`file://${t}`,i=e.fixture?(0,k.resolve)(t,e.fixture):null;if(i){if(!await st(i))return g(`Fixture not found: ${e.fixture}`),d.VALIDATION_ERROR}else if(i=await H(t),!i)return g("No default fixture found at tests/default/inputs.yaml"),s("Either create a default fixture or use --fixture to specify one."),d.VALIDATION_ERROR;let a=await(0,M.readFile)(i,"utf-8"),c=ot.load(a),u=Se(e.input);c={...c,...u};let f=e.intentId??c.intentId??`local-${Date.now()}`;w(`Template: ${o}`),y(`Fixture: ${i}`),y(`Intent ID: ${f}`),y("Resolving template...");let p={template:await $().resolve(o,{noCache:e.cache===!1,timeout:n}),inputs:c,intentId:f,options:{skipFormat:e.skipFormat}},E=(0,ne.createEngine)();y("Validating inputs...");let b=await E.install(p);if(e.dryRun)return await Fe(b,r,e.json);let O=await Ie(r,e);return O!==null?O:(w("Inputs validated"),w(`Generated ${b.files.size-1} files + manifest.json`),e.skipFormat||w("Formatted with tofu fmt"),await re(r,b,"install"),Ce(b,r,e.json),d.SUCCESS)}async function Zt(t,e,r,n){y(`Loading config from ${t}`);let o=await Te(t);if(e.intentId&&(o.intentId=e.intentId),e.input&&e.input.length>0){let l=Se(e.input);o.inputs={...o.inputs||{},...l}}w(`Template: ${o.template}`),y(`Intent ID: ${o.intentId}`),y("Resolving template...");let c={template:await $().resolve(o.template,{noCache:e.cache===!1,timeout:n}),inputs:o.inputs,intentId:o.intentId,platformPrefix:o.platformPrefix,options:{skipFormat:e.skipFormat??o.options?.skipFormat}},u=(0,ne.createEngine)();y("Validating inputs...");let f=await u.install(c);if(e.dryRun)return await Fe(f,r,e.json);let m=await Ie(r,e);return m!==null?m:(w("Inputs validated"),w(`Generated ${f.files.size-1} files + manifest.json`),c.options?.skipFormat||w("Formatted with tofu fmt"),await re(r,f,"install"),Ce(f,r,e.json),d.SUCCESS)}async function Qt(t,e,r,n,o){y(`Existing manifest detected at ${o} \u2014 running upgrade`);let i=await ee(o);y(`Reading files from ${r}`);let a=await pe(r,i.platformPrefix);y(`Loading config from ${t}`);let c=await Te(t);if(e.intentId&&(c.intentId=e.intentId),e.input&&e.input.length>0){let E=Se(e.input);c.inputs={...c.inputs||{},...E}}w(`Template: ${c.template}`),y(`Intent ID: ${c.intentId}`),y("Resolving template...");let m={template:await $().resolve(c.template,{noCache:e.cache===!1,timeout:n}),inputs:c.inputs,intentId:c.intentId,platformPrefix:c.platformPrefix,options:{skipFormat:e.skipFormat??c.options?.skipFormat}},p=await(0,ne.createEngine)().upgrade(m,a,i);return p.sourceBlocked&&!e.allowSourceChange?(N("Source mismatch: current files were generated from a different template source."),tt(p.driftReport,e.json),e.json?j(JSON.stringify({blocked:!0,reason:"source_mismatch",sourceBlocked:!0,driftReport:{drift:!p.driftReport.valid,summary:p.driftReport.summary}},null,2)):s("Use --allow-source-change to proceed with source migration."),d.DRIFT_BLOCKED):(p.sourceBlocked&&N("Proceeding with source migration (--allow-source-change)."),tt(p.driftReport,e.json),!p.driftReport.valid&&!e.force?(e.json?j(JSON.stringify({blocked:!0,reason:"content_drift",sourceBlocked:p.sourceBlocked,driftReport:{drift:!0,summary:p.driftReport.summary}},null,2)):s("Use --force to proceed despite drift."),d.DRIFT_BLOCKED):(p.driftReport.valid||N("Proceeding despite drift (--force)."),e.dryRun?rr(p,a,e.json):(await re(r,p,"upgrade"),e.json?j(nr(p)):s(ct(p)),d.SUCCESS)))}async function er(t,e){let r=[];for(let[n,o]of t){if(n==="manifest.json")continue;let i="";try{i=await(0,M.readFile)((0,k.join)(e,n),"utf-8")}catch{}i!==o.content&&r.push({filename:n,diff:V(i,o.content,n)})}return r}function tr(t,e){let r=[];for(let[n,o]of t){if(n==="manifest.json")continue;let a=e.get(n)?.content??"";a!==o.content&&r.push({filename:n,diff:V(a,o.content,n)})}return r}function at(t){if(t.length===0){s(`
|
|
8
|
+
No file changes detected.`);return}s(`
|
|
9
|
+
${t.length} file(s) changed:
|
|
10
|
+
`);for(let e of t)s(e.diff)}function tt(t,e){if(e){j(JSON.stringify({drift:!t.valid,summary:t.summary,entries:t.entries.map(n=>({path:n.path,type:n.type,details:n.details}))},null,2));return}if(t.valid){w("No drift detected in managed files.");return}let r=t.summary.driftedFiles+t.summary.missingFiles+t.summary.unexpectedFiles+t.summary.inconsistentFiles;N(`Drift detected in ${r} managed files:`);for(let n of t.entries)s(` ${ue(n.type)} ${n.path}`),s(` ${n.details}`);s(""),s("Use --force to override and proceed with upgrade.")}async function Fe(t,e,r){let n=await er(t.files,e);return r?j(JSON.stringify({dryRun:!0,aggregateHash:t.manifest.aggregateHash,templateSource:t.manifest.templateSource,templateVersion:t.manifest.templateVersion,fileCount:t.files.size,files:Array.from(t.files.entries()).map(([o,i])=>({path:o,size:Buffer.byteLength(i.content,"utf-8")})),changes:n.map(o=>({filename:o.filename,diff:o.diff}))},null,2)):(s("Dry run \u2014 no files written."),s(""),s(`Aggregate Hash: ${t.manifest.aggregateHash}`),s(`Template: ${t.manifest.templateSource}`),s(`Version: ${t.manifest.templateVersion}`),s(`Files: ${t.files.size}`),at(n)),d.SUCCESS}function Ce(t,e,r){if(r)j(JSON.stringify({success:!0,aggregateHash:t.manifest.aggregateHash,fileCount:t.files.size,outputDir:e,files:Array.from(t.files.entries()).map(([n,o])=>({path:n,size:Buffer.byteLength(o.content,"utf-8")}))},null,2));else{let n=[];for(let[o,i]of t.files)n.push({path:o,size:Buffer.byteLength(i.content,"utf-8")});s(""),s(`Output written to: ${e}`),s(`Aggregate Hash: ${t.manifest.aggregateHash}`),Ae(n)}}function rr(t,e,r){let n=tr(t.files,e);return r?j(JSON.stringify({dryRun:!0,templateSource:t.manifest.templateSource,templateVersion:t.manifest.templateVersion,aggregateHash:t.manifest.aggregateHash,driftReport:t.driftReport,files:Array.from(t.files.entries()).map(([o,i])=>({path:o,zone:i.zone})),changes:n.map(o=>({filename:o.filename,diff:o.diff}))},null,2)):(s("Dry run \u2014 no files written."),s(""),s(ct(t)),at(n)),d.SUCCESS}function ct(t){let e=[];e.push(`\u2713 Upgrade complete: ${t.manifest.templateSource} v${t.manifest.templateVersion}`),e.push("");let r=0,n=0;for(let[,o]of t.files)o.zone==="platform"?r++:n++;return e.push(` Updated: ${r} files (Platform Zone)`),e.push(` Unchanged: ${n} files (User Zone)`),e.push(""),e.push(`Aggregate Hash: ${t.manifest.aggregateHash}`),e.join(`
|
|
11
|
+
`)}function nr(t){return JSON.stringify({success:!0,templateSource:t.manifest.templateSource,templateVersion:t.manifest.templateVersion,aggregateHash:t.manifest.aggregateHash,driftReport:t.driftReport,files:Array.from(t.files.entries()).map(([e,r])=>({path:e,zone:r.zone}))},null,2)}var ut=require("commander"),de=require("fs/promises"),ge=require("path"),ft=B(require("handlebars")),he=require("@truefoundry/tfy-infra-engine");function lt(){return new ut.Command("validate").description("Validate a template (for template authors)").option("-d, --directory <dir>","Path to local template directory",".").option("--strict","Fail on warnings").option("--skip-format","Skip tofu fmt validation").action(async e=>{try{let r=await ir(e);process.exit(r)}catch(r){S(r)}})}async function or(t){try{return await(0,de.stat)(t),!0}catch{return!1}}async function ir(t){let e=(0,ge.resolve)(t.directory),r=[],n=!1;s(`Validating template at: ${e}`),s("");let o=(0,ge.join)(e,"bundle.json");if(!await or(o))return g("bundle.json not found in template directory"),d.VALIDATION_ERROR;y("Checking bundle.json...");try{let u=await(0,de.readFile)(o,"utf-8"),f;try{f=JSON.parse(u)}catch(D){throw new Error(`Invalid JSON: ${D.message}`,{cause:D})}let m=f.metadata;if(!m||typeof m!="object")throw new Error('bundle.json must contain a "metadata" object');if(!m.name||!m.version)throw new Error('bundle.json metadata must contain "name" and "version"');let l=f.jsonSchema;if(!l||typeof l!="object")throw new Error('bundle.json must contain a "jsonSchema" object');(0,he.validateJsonSchemaStructure)(l),w("bundle.json schema is valid");let p=f.files,E=f.staticFiles,b=Object.keys(p??{}).length,O=Object.keys(E??{}).length;if(b===0&&O===0)r.push({type:"error",message:"Bundle contains no template files"}),n=!0;else{for(let[D,U]of Object.entries(p??{}))try{ft.default.precompile(U),y(` \u2713 ${D}`)}catch(oe){r.push({type:"error",message:`Invalid Handlebars syntax: ${oe.message}`,file:D}),n=!0}w(`Found ${b} HBS template(s)`+(O>0?` and ${O} static file(s)`:"")+" with valid syntax")}}catch(u){r.push({type:"error",message:u.message,file:"bundle.json"}),n=!0}t.skipFormat||(y("Checking tofu fmt availability..."),await(0,he.isTofuAvailable)()?w("tofu fmt available"):r.push({type:"warning",message:"tofu not found - format validation skipped"})),s("");let a=r.filter(u=>u.type==="warning"),c=r.filter(u=>u.type==="error");if(a.length>0){N(`${a.length} warning(s):`);for(let u of a){let f=u.file?`${u.file}: `:"";s(` \u2022 ${f}${u.message}`)}s("")}if(c.length>0){g(`${c.length} error(s):`);for(let u of c){let f=u.file?`${u.file}: `:"";s(` \u2022 ${f}${u.message}`)}s("")}return n?(g("Template validation failed"),d.VALIDATION_ERROR):t.strict&&a.length>0?(g("Template validation failed (strict mode)"),d.VALIDATION_ERROR):(w("Template is valid"),d.SUCCESS)}var pt=require("commander"),I=require("fs/promises"),P=require("path"),mt=B(require("js-yaml")),dt=require("@truefoundry/tfy-infra-engine");function gt(){return new pt.Command("test").description("Run test fixtures against a template").option("-d, --directory <dir>","Path to local template directory",".").option("--fixture <name>","Run specific fixture only").option("--update","Update expected outputs with actual results").option("--diff","Show unified diff on failures").option("--skip-format","Skip tofu fmt in tests").action(async e=>{try{let r=await sr(e);process.exit(r)}catch(r){S(r)}})}async function sr(t){let e=(0,P.resolve)(t.directory),r=(0,P.join)(e,"tests"),n=`file://${e}`;s(`Testing template at: ${e}`),s("");let o;try{let l=await(0,I.readdir)(r);o=[];for(let p of l)try{let E=(0,P.join)(r,p,"inputs.yaml");await(0,I.readFile)(E),o.push(p)}catch{}}catch{return g(`No tests directory found at ${r}`),d.VALIDATION_ERROR}if(o.length===0)return g("No test fixtures found"),s(""),s("Expected structure:"),s(" tests/"),s(" <fixture-name>/"),s(" inputs.yaml"),s(" expected/"),d.VALIDATION_ERROR;if(t.fixture){if(!o.includes(t.fixture))return g(`Fixture '${t.fixture}' not found`),s(`Available fixtures: ${o.join(", ")}`),d.VALIDATION_ERROR;o=[t.fixture]}s(`Running ${o.length} fixture(s)...`),s("");let a=await $().resolve(n,{noCache:!0}),c=(0,dt.createEngine)(),u=[];for(let l of o){let p=(0,P.join)(r,l),E=(0,P.join)(p,"inputs.yaml"),b=(0,P.join)(p,"expected");y(`Running fixture: ${l}`);try{let O=await(0,I.readFile)(E,"utf-8"),D=mt.load(O),U={template:a,inputs:D,intentId:`test-${l}`,options:{skipFormat:t.skipFormat}},oe=await c.install(U);if(t.update)await ar(b,oe),w(`${l}: updated`),u.push({name:l,passed:!0});else{let{errors:we,diffs:ie}=await cr(b,oe,t.diff);if(we.length===0)w(`${l}: passed`),u.push({name:l,passed:!0});else{g(`${l}: failed`);for(let K of we)s(` ${K}`);if(t.diff&&ie&&ie.length>0)for(let K of ie){s(` --- expected/${K.filename}`),s(` +++ actual/${K.filename}`);let Ct=K.diff.split(`
|
|
12
|
+
`).slice(4);for(let je of Ct)je&&s(` ${je}`)}u.push({name:l,passed:!1,errors:we,diffs:ie})}}}catch(O){g(`${l}: error`),s(` ${O.message}`),u.push({name:l,passed:!1,errors:[O.message]})}}s("");let f=u.filter(l=>l.passed).length,m=u.filter(l=>!l.passed).length;return m===0?(w(`All ${f} fixture(s) passed`),d.SUCCESS):(g(`${m} fixture(s) failed, ${f} passed`),d.VALIDATION_ERROR)}function $e(t){return t.replace(/("source"\s*:\s*)"file:\/\/[^"]*"/g,'$1"<local>"')}async function ar(t,e){await(0,I.mkdir)(t,{recursive:!0});for(let[r,n]of e.files){if(r==="manifest.json")continue;let o=(0,P.join)(t,r);await(0,I.writeFile)(o,$e(n.content),"utf-8")}}async function cr(t,e,r){let n=[],o=[];for(let[i,a]of e.files){if(i==="manifest.json")continue;let c=a.content,u=(0,P.join)(t,i);try{let f=await(0,I.readFile)(u,"utf-8"),m=$e(c.trim().replace(/\r\n/g,`
|
|
13
|
+
`)),l=$e(f.trim().replace(/\r\n/g,`
|
|
14
|
+
`));if(m!==l&&(n.push(`${i}: content mismatch`),r)){let p=V(l,m,i);o.push({filename:i,diff:p})}}catch{n.push(`${i}: expected file not found`)}}try{let i=await(0,I.readdir)(t);for(let a of i)!e.files.has(a)&&a!=="manifest.json"&&n.push(`${a}: unexpected file in expected/`)}catch{}return{errors:n,diffs:r?o:void 0}}var vt=require("commander"),v=require("fs/promises"),h=require("path"),De=B(require("js-yaml")),L=require("@truefoundry/tfy-infra-engine");var ht=B(require("chokidar")),yt=require("path");function ur(t,e){let r=t.replace(/\\/g,"/"),n=(0,yt.basename)(r);return e&&r.endsWith(e.replace(/\\/g,"/"))||n==="bundle.json"||n==="schema-pkg.json"?"schema":/\/src\//.test(r)&&!n.startsWith("_")?"template":n==="inputs.yaml"?"inputs":"other"}function Pe(t,e={}){let{onChange:r,onReady:n,onError:o,ignored:i=/(^|[/\\])\..|(^|[/\\])node_modules($|[/\\])|(^|[/\\])dev($|[/\\])/,debounceMs:a=100,schemaFile:c}=e,u=ht.default.watch(t,{ignored:i,persistent:!0,ignoreInitial:!0,awaitWriteFinish:{stabilityThreshold:a,pollInterval:50}}),f=m=>l=>{let p=ur(l,c);r&&r({type:m,path:l,fileType:p})};return u.on("change",f("change")),u.on("add",f("add")),u.on("unlink",f("unlink")),n&&u.on("ready",n),o&&u.on("error",m=>o(m instanceof Error?m:new Error(String(m)))),u}function Rt(){return new vt.Command("dev").description("Watch mode for rapid template iteration").option("-d, --directory <dir>","Path to template version directory").option("--schema <path>","Path to schema package JSON (from tfy-schema-tool --schema-package)").option("--src <dir>","Path to template src/ directory").option("--name <string>","Template name (used with --schema mode)").option("--version <semver>","Template version (used with --schema mode)").option("-f, --fixture <path>","Input file or fixture to use for testing").requiredOption("-o, --output <dir>","Output directory for rendered files").option("--no-test","Only validate, do not run tests").action(async e=>{try{let r=await pr(e);process.exit(r)}catch(r){S(r)}})}async function fr(t){try{await(0,v.access)(t)}catch{return{valid:!1,error:`Directory not found: ${t}`}}let e=(0,h.join)(t,"bundle.json");try{return await(0,v.access)(e),{valid:!0}}catch{return{valid:!1,error:`bundle.json not found in ${t}`}}}async function Et(t,e){let r=(0,h.resolve)(e);try{await(0,v.rm)(r,{recursive:!0,force:!0})}catch{}return await(0,v.mkdir)(r,{recursive:!0}),r}function lr(t){return!!(t.schema&&t.src)}async function pr(t){return lr(t)?mr(t):yr(t)}async function mr(t){let e=(0,h.resolve)(t.schema),r=(0,h.resolve)(t.src),n=t.name??"dev-template",o=t.version,i;if(t.fixture){i=(0,h.resolve)(t.fixture);try{await(0,v.access)(i)}catch{return g(`Fixture not found: ${t.fixture}`),d.VALIDATION_ERROR}}else return g("--fixture is required in schema mode"),s("Use -f to specify an inputs.yaml file."),d.VALIDATION_ERROR;let a=await Et(".",t.output),c=(0,h.relative)(process.cwd(),a);s(`[schema] ${(0,h.relative)(process.cwd(),e)}`),s(`[src] ${(0,h.relative)(process.cwd(),r)}`),s(`[output] ${c}/`),s(`[fixture] ${(0,h.relative)(process.cwd(),i)}`),s("");let u=(0,L.createEngine)(),f=async()=>{try{let p=await gr(e,r,n,o);await hr(u,p,i,a,t.test)}catch(p){g("[render] \u2717 Render failed"),s(` ${p.message}`)}};await f();let l=Pe([e,r,i],{schemaFile:e,onChange:p=>{(async()=>(s(`[changed] ${(0,h.relative)(process.cwd(),p.path)}`),await f()))()},onReady:()=>{s("[ready] Watching for changes... (Ctrl+C to exit)"),s("")},onError:p=>{g(`Watch error: ${p.message}`)}});return new Promise(p=>{let E=()=>{s(""),s("[stopped] Watch mode ended"),l.close(),p(d.SUCCESS)};process.on("SIGINT",E),process.on("SIGTERM",E)})}function dr(t){let e=t.properties;if(!e)return;let r=e.version;if(!r||typeof r=="boolean")return;let n=r.default;return typeof n=="string"?n:void 0}async function gr(t,e,r,n){let o=await(0,v.readFile)(t,"utf-8"),a=JSON.parse(o).jsonSchema;if(!a||typeof a!="object")throw new Error('Schema package must contain a "jsonSchema" object');let c=n??dr(a)??"0.0.0-dev",{files:u,staticFiles:f}=await ae(e);return{metadata:{name:r,version:c},jsonSchema:a,files:u,staticFiles:f,source:`file://${e}`,version:{semver:c}}}async function hr(t,e,r,n,o){let i=await(0,v.readFile)(r,"utf-8"),a=De.load(i),c={template:e,inputs:a,intentId:`dev-${Date.now()}`,options:{skipFormat:!1}},u=await t.install(c),f=Array.from(u.files.keys()).filter(l=>l!=="manifest.json").length;s(`[validate] \u2713 ${f} template files valid`),await(0,v.rm)(n,{recursive:!0,force:!0}),await(0,v.mkdir)(n,{recursive:!0});let m=0;for(let[l,p]of u.files){if(l==="manifest.json")continue;let E=(0,h.join)(n,l);await(0,v.mkdir)((0,h.dirname)(E),{recursive:!0}),await(0,v.writeFile)(E,p.content,"utf-8"),m++}s(`[render] \u2713 ${m} files written to ${(0,h.relative)(process.cwd(),n)}/`),o!==!1&&await vr(u,n)}async function yr(t){let e=(0,h.resolve)(t.directory||"."),r=await fr(e);if(!r.valid)return g(r.error),d.VALIDATION_ERROR;let n;if(t.fixture){n=(0,h.resolve)(e,t.fixture);try{await(0,v.access)(n)}catch{return g(`Fixture not found: ${t.fixture}`),d.VALIDATION_ERROR}}else{let f=await H(e);if(!f)return g("No default fixture found at tests/default/inputs.yaml"),s("Either create a default fixture or use --fixture to specify one."),d.VALIDATION_ERROR;n=f}let o=await Et(e,t.output),i=(0,h.relative)(process.cwd(),o);s(`[watching] ${(0,h.relative)(process.cwd(),e)}`),s(`[output] ${i}/`),s(`[fixture] ${(0,h.relative)(process.cwd(),n)}`),s("");let a=(0,L.createEngine)(),c=`file://${e}`;await wt(a,c,e,n,o,t.test);let u=Pe(e,{onChange:f=>{(async()=>{let m=(0,h.relative)(e,f.path);s(`[changed] ${m}`),f.fileType==="schema"?await xt(c):(f.fileType==="template"||f.fileType==="inputs")&&await wt(a,c,e,n,o,t.test)})()},onReady:()=>{s("[ready] Watching for changes... (Ctrl+C to exit)"),s("")},onError:f=>{g(`Watch error: ${f.message}`)}});return new Promise(f=>{let m=()=>{s(""),s("[stopped] Watch mode ended"),u.close(),f(d.SUCCESS)};process.on("SIGINT",m),process.on("SIGTERM",m)})}async function xt(t){try{return(await $().resolve(t,{noCache:!0})).jsonSchema?(s("[validate] \u2713 schema valid"),!0):(g("[validate] \u2717 schema invalid"),!1)}catch(e){return g("[validate] \u2717 schema invalid"),s(` ${e.message}`),!1}}async function wt(t,e,r,n,o,i){if(await xt(e))try{let c=await(0,v.readFile)(n,"utf-8"),u=De.load(c),l={template:await $().resolve(e,{noCache:!0}),inputs:u,intentId:`dev-${Date.now()}`,options:{skipFormat:!1}},p=await t.install(l),E=Array.from(p.files.keys()).filter(O=>O!=="manifest.json").length;s(`[validate] \u2713 ${E} template files valid`),await(0,v.rm)(o,{recursive:!0,force:!0}),await(0,v.mkdir)(o,{recursive:!0});let b=0;for(let[O,D]of p.files){if(O==="manifest.json")continue;let U=(0,h.join)(o,O);await(0,v.mkdir)((0,h.dirname)(U),{recursive:!0}),await(0,v.writeFile)(U,D.content,"utf-8"),b++}s(`[render] \u2713 ${b} files written to ${(0,h.relative)(process.cwd(),o)}/`),i!==!1&&await wr(r,p,o)}catch(c){g("[render] \u2717 Render failed"),s(` ${c.message}`),c instanceof L.EngineError&&c.code===L.EngineErrorCode.INPUT_VALIDATION_FAILED&&c.details?.errors&&s(ce(c.details.errors))}}async function wr(t,e,r){let n=(0,h.join)(t,"tests","default","expected");await Rr(e,n)}async function vr(t,e){}async function Rr(t,e){let r=Date.now();try{await(0,v.access)(e)}catch{s("[test] \u26A0 No expected/ directory, skipping comparison");return}let n=[];for(let[i,a]of t.files){if(i==="manifest.json")continue;let c=a.content,u=(0,h.join)(e,i);try{let f=await(0,v.readFile)(u,"utf-8"),m=c.trim().replace(/\r\n/g,`
|
|
15
|
+
`),l=f.trim().replace(/\r\n/g,`
|
|
16
|
+
`);if(m!==l){let p=V(l,m,i);n.push({file:i,diff:p})}}catch{n.push({file:i})}}let o=Date.now()-r;if(n.length===0)s(`[test] \u2713 default: passed (${o}ms)`);else{g("[test] \u2717 default: failed");for(let i of n)if(s(` ${i.file}: content mismatch`),i.diff){let a=i.diff.split(`
|
|
17
|
+
`).slice(4,14);for(let c of a)c&&s(` ${c}`);i.diff.split(`
|
|
18
|
+
`).length>14&&s(" ... (diff truncated)")}}}var Ot=require("commander"),ye=require("path"),bt=require("@truefoundry/tfy-infra-engine");var Er=1;function Tt(){return new Ot.Command("verify").description("Check integrity of files on disk against a manifest").option("-d, --directory <dir>","Path to cluster directory containing manifest.json",".").option("--json","Output drift report as structured JSON").action(async e=>{try{let r=await xr(e);process.exit(r)}catch(r){S(r)}})}async function xr(t){let e=(0,ye.resolve)(t.directory),r=(0,ye.join)(e,"manifest.json");y(`Loading manifest from ${r}`);let n=await ee(r);y(`Reading files from ${e}`);let o=await pe(e,n.platformPrefix),a=await(0,bt.createEngine)().verify(o,n);return t.json?s(br(a.driftReport)):s(Or(a.driftReport)),a.driftReport.valid?d.SUCCESS:Er}function Or(t){let e=[],r=t.summary.driftedFiles+t.summary.missingFiles+t.summary.unexpectedFiles;if(t.valid)e.push(`\u2713 Verification passed: ${t.summary.totalFiles} files checked, ${r} issues`),e.push(""),e.push(`Aggregate Hash: ${t.aggregateHash.expected}`);else{e.push(`\u2717 Drift detected: ${r} issues found`),e.push("");for(let n of t.entries){let o=ue(n.type);e.push(` ${o} ${n.path}`),e.push(` ${n.details}`),e.push("")}e.push(`Summary: ${t.summary.totalFiles} files checked, ${t.summary.driftedFiles} drifted, ${t.summary.missingFiles} missing`+(t.summary.unexpectedFiles>0?`, ${t.summary.unexpectedFiles} unexpected`:""))}return e.join(`
|
|
19
|
+
`)}function br(t){return JSON.stringify(t,null,2)}function It(){let t=new St.Command("tpl").description("Template operations").enablePositionalOptions();return t.addCommand(Me()),t.addCommand(Rt()),t.addCommand(it()),t.addCommand(lt()),t.addCommand(gt()),t.addCommand(Tt()),t}process.env.INIT_CWD&&process.chdir(process.env.INIT_CWD);var Tr="0.1.4";async function Sr(){let t=new Ft.Command;t.name("tfy-init").description("TrueFoundry infrastructure templating CLI").enablePositionalOptions().version(Tr,"-v, --version","Show version").option("--verbose","Enable verbose output").option("-q, --quiet","Suppress non-error output").hook("preAction",e=>{let r=e.opts();r.quiet?Re("quiet"):r.verbose&&Re("verbose")}),t.addCommand(It()),await t.parseAsync(process.argv)}Sr().catch(t=>{console.error("Fatal error:",t),process.exit(1)});
|
|
16
20
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/commands/tpl/index.ts","../src/commands/tpl/render.ts","../src/resolvers/base.ts","../src/resolvers/index.ts","../src/resolvers/file.ts","../src/utils/walk-src.ts","../src/resolvers/github.ts","../src/resolvers/https.ts","../src/cache/template-cache.ts","../src/utils/config-loader.ts","../src/utils/fs-helpers.ts","../src/utils/manifest-loader.ts","../src/utils/file-writer.ts","../src/utils/error-handler.ts","../src/utils/output.ts","../src/commands/tpl/schema.ts","../src/commands/tpl/validate.ts","../src/commands/tpl/test.ts","../src/utils/diff.ts","../src/commands/tpl/dev.ts","../src/utils/file-watcher.ts","../src/commands/tpl/verify.ts","../src/utils/file-reader.ts","../src/commands/tpl/upgrade.ts","../src/commands/tpl/hash.ts"],"sourcesContent":["/**\n * @truefoundry/tfy-infra-cli\n *\n * CLI for TrueFoundry infrastructure templating engine.\n * Binary: tfy-init\n */\n\nimport { Command } from 'commander';\nimport { createTplCommand } from './commands/tpl/index.js';\nimport { setVerbosity } from './utils/output.js';\n\n// When invoked via `npm run`, npm sets CWD to the package root.\n// INIT_CWD preserves the directory the user actually ran the command from.\nif (process.env['INIT_CWD']) {\n process.chdir(process.env['INIT_CWD']);\n}\n\ndeclare const __CLI_VERSION__: string;\nconst VERSION = __CLI_VERSION__;\n\n/**\n * Main CLI entry point.\n */\nasync function main(): Promise<void> {\n const program = new Command();\n\n program\n .name('tfy-init')\n .description('TrueFoundry infrastructure templating CLI')\n .version(VERSION, '-v, --version', 'Show version')\n .option('--verbose', 'Enable verbose output')\n .option('-q, --quiet', 'Suppress non-error output')\n .hook('preAction', (thisCommand) => {\n const opts = thisCommand.opts();\n if (opts['quiet']) {\n setVerbosity('quiet');\n } else if (opts['verbose']) {\n setVerbosity('verbose');\n }\n });\n\n // Add command groups\n program.addCommand(createTplCommand());\n\n // Parse and execute\n await program.parseAsync(process.argv);\n}\n\n// Run CLI\nmain().catch((error) => {\n console.error('Fatal error:', error);\n process.exit(1);\n});\n","/**\n * tfy-init tpl command group\n *\n * CHANGE (006): Removed push command (S3 support removed).\n * CHANGE (008): Removed init command (templates scaffolded via CUE).\n */\n\nimport { Command } from 'commander';\nimport { createRenderCommand } from './render.js';\nimport { createSchemaCommand } from './schema.js';\nimport { createValidateCommand } from './validate.js';\nimport { createTestCommand } from './test.js';\n\nimport { createDevCommand } from './dev.js';\nimport { createVerifyCommand } from './verify.js';\nimport { createUpgradeCommand } from './upgrade.js';\nimport { createHashCommand } from './hash.js';\n\n/**\n * Create the tpl command group with all subcommands.\n */\nexport function createTplCommand(): Command {\n const tpl = new Command('tpl').description('Template operations');\n\n tpl.addCommand(createDevCommand());\n tpl.addCommand(createRenderCommand());\n tpl.addCommand(createSchemaCommand());\n tpl.addCommand(createValidateCommand());\n tpl.addCommand(createTestCommand());\n\n tpl.addCommand(createVerifyCommand());\n tpl.addCommand(createUpgradeCommand());\n tpl.addCommand(createHashCommand());\n\n return tpl;\n}\n","/**\n * tfy-init tpl render command\n *\n * CHANGE (006): Fetch-first pattern — resolves template via CLI's\n * ResolverRegistry before calling engine.install(). The engine receives\n * a pre-resolved Template object, not a URI string.\n */\n\nimport { Command } from 'commander';\nimport { readFile, access } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport * as yaml from 'js-yaml';\nimport { createEngine } from '@truefoundry/tfy-infra-engine';\nimport type { Envelope } from '@truefoundry/tfy-infra-engine';\nimport { createResolverRegistry } from '../../resolvers/index.js';\nimport { loadConfig } from '../../utils/config-loader.js';\nimport { loadManifest, envelopeFromManifest } from '../../utils/manifest-loader.js';\nimport { writeEngineOutput } from '../../utils/file-writer.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { findDefaultFixture } from '../../utils/fs-helpers.js';\n\nexport { EXIT_CODES };\n\nexport interface RenderArgs {\n config?: string;\n directory?: string;\n fromManifest?: string;\n}\n\nexport function validateRenderArgs(args: RenderArgs): void {\n const modes = [args.config, args.fromManifest].filter(Boolean);\n if (modes.length > 1) {\n throw new Error('--config and --from-manifest are mutually exclusive. Use one or the other.');\n }\n if (args.config && args.directory !== '.') {\n throw new Error('--config and --directory are mutually exclusive. Use one or the other.');\n }\n}\n\nexport function createRenderCommand(): Command {\n const cmd = new Command('render')\n .description('Render a template to HCL files')\n .option('-c, --config <path>', 'Path to envelope config file (YAML or JSON)')\n .option('-d, --directory <dir>', 'Path to template version directory', '.')\n .option('-f, --fixture <path>', 'Input file to use (when using --directory)')\n .option('-i, --input <key=value>', 'Inline input override (can be repeated)', collect, [])\n .requiredOption('-o, --output <dir>', 'Output directory')\n .option('--from-manifest <path>', 'Path to manifest.json to use as input source')\n .option('--skip-format', 'Skip tofu fmt formatting')\n .option('--no-cache', 'Force re-fetch template')\n .option('--timeout <ms>', 'Network timeout in milliseconds', '30000')\n .option('--intent-id <id>', 'Cluster identity for integrity tracking')\n .action(async (options: RenderOptions) => {\n try {\n if (options.config) options.config = resolve(options.config);\n if (options.fromManifest) options.fromManifest = resolve(options.fromManifest);\n\n validateRenderArgs({\n config: options.config,\n directory: options.directory,\n fromManifest: options.fromManifest,\n });\n const exitCode = await runRender(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\nfunction collect(value: string, previous: string[]): string[] {\n return [...previous, value];\n}\n\ninterface RenderOptions {\n config?: string;\n directory: string;\n fixture?: string;\n input?: string[];\n output: string;\n fromManifest?: string;\n skipFormat?: boolean;\n cache?: boolean;\n timeout: string;\n intentId?: string;\n}\n\nexport { findDefaultFixture } from '../../utils/fs-helpers.js';\n\nexport function parseInputOverrides(inputs?: string[]): Record<string, string> {\n const result: Record<string, string> = {};\n if (!inputs) return result;\n\n for (const input of inputs) {\n const eqIndex = input.indexOf('=');\n if (eqIndex > 0) {\n const key = input.substring(0, eqIndex);\n const value = input.substring(eqIndex + 1);\n result[key] = value;\n }\n }\n\n return result;\n}\n\nasync function runRender(options: RenderOptions): Promise<number> {\n const outputDir = resolve(options.output);\n const timeout = parseInt(options.timeout, 10);\n\n if (options.fromManifest) {\n return runRenderFromManifest(options.fromManifest, options, outputDir, timeout);\n }\n\n if (options.config) {\n return runRenderFromConfig(options.config, options, outputDir, timeout);\n }\n\n const templateDir = resolve(options.directory);\n return runRenderFromDirectory(templateDir, options, outputDir, timeout);\n}\n\n/**\n * Render from a manifest file (re-render / disaster recovery).\n */\nasync function runRenderFromManifest(\n manifestPath: string,\n options: RenderOptions,\n outputDir: string,\n timeout: number\n): Promise<number> {\n output.verbose(`Loading manifest from ${manifestPath}`);\n const manifest = await loadManifest(manifestPath);\n const rawConfig = envelopeFromManifest(manifest);\n\n if (options.intentId) {\n rawConfig.intentId = options.intentId;\n }\n\n output.success(`Template: ${rawConfig.template}`);\n output.verbose(`Intent ID: ${rawConfig.intentId}`);\n output.verbose('Resolving template...');\n\n // Fetch-first: resolve template URI before calling engine\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(rawConfig.template, {\n noCache: options.cache === false,\n timeout,\n });\n\n // Build proper Envelope with resolved Template object\n const envelope: Envelope = {\n template,\n inputs: rawConfig.inputs,\n intentId: rawConfig.intentId,\n platformPrefix: rawConfig.platformPrefix,\n options: {\n skipFormat: options.skipFormat,\n },\n };\n\n output.verbose('Re-rendering from manifest...');\n\n const engine = createEngine();\n const result = await engine.install(envelope);\n\n output.success('Inputs validated');\n output.success(`Generated ${result.files.size - 1} files + manifest.json`);\n\n if (!options.skipFormat) {\n output.success('Formatted with tofu fmt');\n }\n\n await writeEngineOutput(outputDir, result, 'install');\n\n const fileList: Array<{ path: string; size: number }> = [];\n for (const [filePath, entry] of result.files) {\n fileList.push({\n path: filePath,\n size: Buffer.byteLength(entry.content, 'utf-8'),\n });\n }\n\n output.info('');\n output.info(`Output written to: ${outputDir}`);\n output.info(`Aggregate Hash: ${result.manifest.aggregateHash}`);\n output.printFileList(fileList);\n\n return EXIT_CODES.SUCCESS;\n}\n\n/**\n * Render from a template directory with fixture inputs.\n */\nasync function runRenderFromDirectory(\n templateDir: string,\n options: RenderOptions,\n outputDir: string,\n timeout: number\n): Promise<number> {\n const templateUri = `file://${templateDir}`;\n\n let fixturePath: string | null = options.fixture ? resolve(templateDir, options.fixture) : null;\n\n if (fixturePath) {\n try {\n await access(fixturePath);\n } catch {\n output.error(`Fixture not found: ${options.fixture}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n } else {\n fixturePath = await findDefaultFixture(templateDir);\n if (!fixturePath) {\n output.error('No default fixture found at tests/default/inputs.yaml');\n output.info('Either create a default fixture or use --fixture to specify one.');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n }\n\n const fixtureContent = await readFile(fixturePath, 'utf-8');\n let inputs = yaml.load(fixtureContent) as Record<string, unknown>;\n\n const overrides = parseInputOverrides(options.input);\n inputs = { ...inputs, ...overrides };\n\n const intentId =\n options.intentId ?? (inputs['intentId'] as string | undefined) ?? `local-${Date.now()}`;\n\n output.success(`Template: ${templateUri}`);\n output.verbose(`Fixture: ${fixturePath}`);\n output.verbose(`Intent ID: ${intentId}`);\n\n // Fetch-first: resolve template URI before calling engine\n output.verbose('Resolving template...');\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(templateUri, {\n noCache: options.cache === false,\n timeout,\n });\n\n // Build proper Envelope with resolved Template object\n const envelope: Envelope = {\n template,\n inputs,\n intentId,\n options: {\n skipFormat: options.skipFormat,\n },\n };\n\n const engine = createEngine();\n\n output.verbose('Validating inputs...');\n const result = await engine.install(envelope);\n\n output.success('Inputs validated');\n output.success(`Generated ${result.files.size - 1} files + manifest.json`);\n\n if (!options.skipFormat) {\n output.success('Formatted with tofu fmt');\n }\n\n await writeEngineOutput(outputDir, result, 'install');\n\n const fileList: Array<{ path: string; size: number }> = [];\n for (const [filePath, entry] of result.files) {\n fileList.push({\n path: filePath,\n size: Buffer.byteLength(entry.content, 'utf-8'),\n });\n }\n\n output.info('');\n output.info(`Output written to: ${outputDir}`);\n output.info(`Aggregate Hash: ${result.manifest.aggregateHash}`);\n output.printFileList(fileList);\n\n return EXIT_CODES.SUCCESS;\n}\n\n/**\n * Render from a config file.\n */\nasync function runRenderFromConfig(\n configFile: string,\n options: RenderOptions,\n outputDir: string,\n timeout: number\n): Promise<number> {\n output.verbose(`Loading config from ${configFile}`);\n const rawConfig = await loadConfig(configFile);\n\n if (options.intentId) {\n rawConfig.intentId = options.intentId;\n }\n\n if (options.input && options.input.length > 0) {\n const overrides = parseInputOverrides(options.input);\n rawConfig.inputs = { ...(rawConfig.inputs || {}), ...overrides };\n }\n\n output.success(`Template: ${rawConfig.template}`);\n output.verbose(`Intent ID: ${rawConfig.intentId}`);\n\n // Fetch-first: resolve template URI before calling engine\n output.verbose('Resolving template...');\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(rawConfig.template, {\n noCache: options.cache === false,\n timeout,\n });\n\n // Build proper Envelope with resolved Template object\n const envelope: Envelope = {\n template,\n inputs: rawConfig.inputs,\n intentId: rawConfig.intentId,\n platformPrefix: rawConfig.platformPrefix,\n options: {\n skipFormat: options.skipFormat ?? rawConfig.options?.skipFormat,\n },\n };\n\n const engine = createEngine();\n\n output.verbose('Validating inputs...');\n const result = await engine.install(envelope);\n\n output.success('Inputs validated');\n output.success(`Generated ${result.files.size - 1} files + manifest.json`);\n\n if (!envelope.options?.skipFormat) {\n output.success('Formatted with tofu fmt');\n }\n\n await writeEngineOutput(outputDir, result, 'install');\n\n const fileList: Array<{ path: string; size: number }> = [];\n for (const [filePath, entry] of result.files) {\n fileList.push({\n path: filePath,\n size: Buffer.byteLength(entry.content, 'utf-8'),\n });\n }\n\n output.info('');\n output.info(`Output written to: ${outputDir}`);\n output.info(`Aggregate Hash: ${result.manifest.aggregateHash}`);\n output.printFileList(fileList);\n\n return EXIT_CODES.SUCCESS;\n}\n","/**\n * Base resolver interface and common utilities.\n *\n * Moved from @truefoundry/tfy-infra-engine (006).\n * Imports reference the engine package for shared types.\n */\n\nimport type { Template, ResolverOptions } from '@truefoundry/tfy-infra-engine';\n\n/**\n * Interface for template resolvers.\n * Each resolver handles a specific URI scheme (file://, github://).\n */\nexport interface Resolver {\n canResolve(uri: string): boolean;\n resolve(uri: string, options?: ResolverOptions): Promise<Template>;\n}\n\n/**\n * Default resolver options.\n */\nexport const DEFAULT_RESOLVER_OPTIONS: Required<ResolverOptions> = {\n timeout: 30000,\n retries: 3,\n noCache: false,\n};\n\n/**\n * Merge resolver options with defaults.\n */\nexport function mergeResolverOptions(options?: ResolverOptions): Required<ResolverOptions> {\n return {\n ...DEFAULT_RESOLVER_OPTIONS,\n ...options,\n };\n}\n\n/**\n * Retry an async operation with exponential backoff.\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n retries: number,\n baseDelay = 1000\n): Promise<T> {\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n\n if (attempt < retries) {\n const delay = baseDelay * Math.pow(2, attempt) * (0.5 + Math.random() * 0.5);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n\n throw lastError instanceof Error ? lastError : new Error(String(lastError));\n}\n\n/**\n * Create a timeout wrapper for promises.\n */\nexport function withTimeout<T>(promise: Promise<T>, ms: number, message?: string): Promise<T> {\n return Promise.race([\n promise,\n new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error(message ?? `Operation timed out after ${ms}ms`)), ms)\n ),\n ]);\n}\n\n/**\n * Extract version info from a template URI.\n *\n * Moved from engine's envelope.ts (006). Only used by resolvers.\n *\n * Supports formats like:\n * - github://org/repo/aws/platform/v1/0.1.0 (monorepo: semver at end)\n * - file:///path/to/template (version from template.json — returns null)\n *\n * @param uri - Template source URI\n * @returns Parsed semver string or null if not extractable from URI\n */\nexport function extractVersionFromUri(uri: string): { semver: string } | null {\n const versionPattern = /\\/(\\d+(?:\\.\\d+){0,2})$/;\n const match = uri.match(versionPattern);\n\n if (match?.[1]) {\n return { semver: match[1] };\n }\n\n return null;\n}\n","/**\n * Resolver registry for managing template source resolvers.\n *\n * Moved from @truefoundry/tfy-infra-engine (006).\n * Supports file://, github://, and https://*.json bundle URIs.\n */\n\nimport type { Resolver } from './base.js';\nimport type { Template, ResolverOptions } from '@truefoundry/tfy-infra-engine';\nimport { extractVersionFromUri } from './base.js';\nimport { EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\nimport { FileResolver } from './file.js';\nimport { GitHubResolver } from './github.js';\nimport { HttpsBundleResolver } from './https.js';\nimport { TemplateCache } from '../cache/template-cache.js';\n\nexport type { Resolver } from './base.js';\nexport { FileResolver } from './file.js';\nexport {\n GitHubResolver,\n parseGitHubUri,\n toArchiveUrl,\n buildTag,\n resolveVersion,\n type GitHubUri,\n type PinType,\n} from './github.js';\nexport { HttpsBundleResolver } from './https.js';\nexport { extractVersionFromUri } from './base.js';\nexport { TemplateCache, createTemplateCache, getDefaultCacheDir } from '../cache/template-cache.js';\n\n/**\n * Registry for template resolvers.\n * Routes template URIs to the appropriate resolver based on scheme.\n */\nexport class ResolverRegistry {\n private resolvers: Resolver[] = [];\n private cache: TemplateCache;\n\n constructor(resolvers?: Resolver[], cacheDir?: string) {\n this.cache = new TemplateCache(cacheDir);\n\n if (resolvers) {\n this.resolvers = resolvers;\n } else {\n this.register(new FileResolver());\n this.register(new GitHubResolver());\n this.register(new HttpsBundleResolver());\n }\n }\n\n register(resolver: Resolver): void {\n this.resolvers.push(resolver);\n }\n\n getResolver(uri: string): Resolver | undefined {\n return this.resolvers.find((r) => r.canResolve(uri));\n }\n\n async resolve(uri: string, options?: ResolverOptions): Promise<Template> {\n const resolver = this.getResolver(uri);\n\n if (!resolver) {\n const scheme = uri.split('://')[0] || 'unknown';\n throw new EngineError(\n `No resolver found for URI scheme: ${scheme}://`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n undefined,\n {\n uri,\n scheme,\n supportedSchemes: ['file://', 'github://', 'https://*.json'],\n }\n );\n }\n\n // Check cache first (unless noCache is set or it's a file:// URI)\n if (!options?.noCache && !uri.startsWith('file://')) {\n const versionInfo = extractVersionFromUri(uri);\n if (versionInfo) {\n const cached = await this.cache.get(uri, versionInfo.semver);\n if (cached) {\n return cached;\n }\n }\n }\n\n const template = await resolver.resolve(uri, options);\n\n // Cache the result (unless it's a file:// URI)\n if (!uri.startsWith('file://')) {\n await this.cache.set(template);\n }\n\n return template;\n }\n\n canResolve(uri: string): boolean {\n return this.resolvers.some((r) => r.canResolve(uri));\n }\n\n getSupportedSchemes(): string[] {\n return ['file://', 'github://', 'https://*.json'];\n }\n\n async clearCache(uri?: string): Promise<void> {\n if (uri) {\n await this.cache.delete(uri);\n } else {\n await this.cache.clear();\n }\n }\n\n async getCacheStats(): Promise<{ sources: number; templates: number; size: number }> {\n return this.cache.stats();\n }\n}\n\n/**\n * Create a default resolver registry with standard resolvers (file + github + https bundle).\n */\nexport function createResolverRegistry(cacheDir?: string): ResolverRegistry {\n return new ResolverRegistry(undefined, cacheDir);\n}\n","/**\n * File resolver for local template sources (file://).\n *\n * Moved from @truefoundry/tfy-infra-engine (006).\n * CHANGE (008): Reads template.json instead of schema.yaml.\n * Uses validateTemplateJson().\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Resolver } from './base.js';\nimport type { Template, ResolverOptions, VersionInfo } from '@truefoundry/tfy-infra-engine';\nimport { validateTemplateJson, EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\nimport { walkSrcDir } from '../utils/walk-src.js';\n\n/**\n * Resolver for file:// URIs.\n * Loads templates from the local filesystem.\n */\nexport class FileResolver implements Resolver {\n canResolve(uri: string): boolean {\n return uri.startsWith('file://');\n }\n\n async resolve(uri: string, _options?: ResolverOptions): Promise<Template> {\n const path = this.uriToPath(uri);\n\n try {\n const templateJsonPath = join(path, 'template.json');\n const templateJsonContent = await this.readFileWithError(templateJsonPath, 'template.json');\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(templateJsonContent);\n } catch (error) {\n throw new EngineError(\n `Failed to parse template.json: ${(error as Error).message}`,\n EngineErrorCode.TEMPLATE_JSON_INVALID,\n error as Error,\n { path: templateJsonPath }\n );\n }\n\n const { metadata, jsonSchema } = validateTemplateJson(parsed);\n\n const srcDir = join(path, 'src');\n\n let files: Map<string, string>;\n let staticFiles: Map<string, string>;\n try {\n ({ files, staticFiles } = await walkSrcDir(srcDir));\n } catch (error) {\n const nodeError = error as NodeJS.ErrnoException;\n if (nodeError.code === 'ENOENT') {\n throw new EngineError(\n `Template src/ directory not found in ${path}`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n error as Error,\n { path: srcDir }\n );\n }\n throw error;\n }\n\n if (files.size === 0 && staticFiles.size === 0) {\n throw new EngineError(\n `No template files found in ${srcDir}`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n undefined,\n { path: srcDir }\n );\n }\n\n const version: VersionInfo = {\n semver: metadata.version,\n apiVersion: this.extractApiVersionFromUri(uri),\n };\n\n return {\n metadata,\n jsonSchema,\n files,\n staticFiles,\n source: uri,\n version,\n };\n } catch (error) {\n if (error instanceof EngineError) {\n throw error;\n }\n\n const nodeError = error as NodeJS.ErrnoException;\n if (nodeError.code === 'ENOENT') {\n throw new EngineError(\n `Template not found: ${path}`,\n EngineErrorCode.FILE_NOT_FOUND,\n error as Error,\n { path, originalCode: nodeError.code }\n );\n }\n\n if (nodeError.code === 'EACCES') {\n throw new EngineError(\n `Permission denied: ${path}`,\n EngineErrorCode.FILE_NOT_FOUND,\n error as Error,\n { path, originalCode: nodeError.code }\n );\n }\n\n throw new EngineError(\n `Failed to load template from ${path}: ${(error as Error).message}`,\n EngineErrorCode.FILE_NOT_FOUND,\n error as Error,\n { path }\n );\n }\n }\n\n private uriToPath(uri: string): string {\n if (uri.startsWith('file:///')) {\n return uri.substring(7);\n }\n if (uri.startsWith('file://')) {\n return uri.substring(7);\n }\n return uri;\n }\n\n private async readFileWithError(path: string, filename: string): Promise<string> {\n try {\n return await readFile(path, 'utf-8');\n } catch (error) {\n const nodeError = error as NodeJS.ErrnoException;\n if (nodeError.code === 'ENOENT') {\n throw new EngineError(\n `Required file not found: ${filename}`,\n EngineErrorCode.TEMPLATE_JSON_NOT_FOUND,\n error as Error,\n { path, filename }\n );\n }\n throw error;\n }\n }\n\n private extractApiVersionFromUri(uri: string): string | undefined {\n const filePath = this.uriToPath(uri);\n const segments = filePath.split('/').filter(Boolean);\n const last = segments[segments.length - 1] ?? '';\n return /^v\\d+$/.test(last) ? last : undefined;\n }\n}\n","/**\n * Recursive directory walker for template src/ directories.\n *\n * Walks a directory tree and classifies files into Handlebars templates\n * (.hbs → rendered) and static files (copied verbatim). Skips entries\n * whose names start with '_'. Map keys are relative paths from the\n * root directory, preserving subdirectory structure.\n */\n\nimport { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nexport interface WalkResult {\n files: Map<string, string>;\n staticFiles: Map<string, string>;\n}\n\n/**\n * Recursively walk a directory, reading all files into maps.\n *\n * @param rootDir - The src/ directory to walk\n * @returns Maps of relative-path → content, split by .hbs (rendered) vs static\n */\nexport async function walkSrcDir(rootDir: string): Promise<WalkResult> {\n const files = new Map<string, string>();\n const staticFiles = new Map<string, string>();\n\n await walk(rootDir, '', files, staticFiles);\n\n return { files, staticFiles };\n}\n\nasync function walk(\n rootDir: string,\n relativePath: string,\n files: Map<string, string>,\n staticFiles: Map<string, string>\n): Promise<void> {\n const currentDir = relativePath ? join(rootDir, relativePath) : rootDir;\n const entries = await readdir(currentDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.name.startsWith('_')) continue;\n\n const entryRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;\n\n if (entry.isDirectory()) {\n await walk(rootDir, entryRelative, files, staticFiles);\n } else {\n const content = await readFile(join(currentDir, entry.name), 'utf-8');\n if (entry.name.endsWith('.hbs')) {\n const outputPath = entryRelative.replace(/\\.hbs$/, '');\n files.set(outputPath, content);\n } else {\n staticFiles.set(entryRelative, content);\n }\n }\n }\n}\n","/**\n * GitHub resolver for template sources (github://).\n *\n * Moved from @truefoundry/tfy-infra-engine (006).\n * CHANGE (008): Reads template.json instead of schema.yaml.\n * CHANGE (monorepo): New URI format with path-scoped tags and version pin support.\n *\n * URI format: github://org/repo/{cloud}/{sub-type}/{api-version}/{semver}\n * Example: github://truefoundry/tfy-infra-tf-templates/aws/platform/v1/0.1.0\n *\n * Tag derivation: path \"aws/platform/v1\" + semver \"0.1.0\" -> tag \"aws-platform-v0.1.0\"\n * (API version directory is stripped from the tag slug)\n */\n\nimport { mkdir, readdir, readFile, rm } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { pipeline } from 'node:stream/promises';\nimport { createGunzip } from 'zlib';\nimport { extract } from 'tar';\nimport type { Resolver } from './base.js';\nimport type { Template, ResolverOptions, VersionInfo } from '@truefoundry/tfy-infra-engine';\nimport { validateTemplateJson, EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\nimport { mergeResolverOptions, withRetry, withTimeout } from './base.js';\nimport { walkSrcDir } from '../utils/walk-src.js';\n\nexport type PinType = 'exact' | 'minor' | 'major';\n\nexport interface GitHubUri {\n owner: string;\n repo: string;\n /** Full path within the archive including API version dir (e.g., \"aws/platform/v1\") */\n path: string;\n version: {\n /** Version string as parsed from URI (e.g., \"0.1.0\", \"0.1\", or \"0\") */\n semver: string;\n /** Derived git tag (e.g., \"aws-platform-v0.1.0\") */\n tag: string;\n pinType: PinType;\n };\n}\n\n/**\n * Derive a git tag from the template path and semver.\n *\n * Strips the trailing API version directory (v\\d+) from the path,\n * slugifies the remaining segments, and prepends to the version.\n *\n * Examples:\n * (\"aws/platform/v1\", \"0.1.0\") -> \"aws-platform-v0.1.0\"\n * (\"azure/platform/v1\", \"0.2.0\") -> \"azure-platform-v0.2.0\"\n * (\"gcp/gateway/v1\", \"0.1.0\") -> \"gcp-gateway-v0.1.0\"\n * (\"\", \"1.0.0\") -> \"v1.0.0\"\n */\nexport function buildTag(path: string, semver: string): string {\n if (!path) return `v${semver}`;\n const parts = path.split('/');\n const tagParts = parts.at(-1)?.match(/^v\\d+$/) ? parts.slice(0, -1) : parts;\n const slug = tagParts.join('-');\n return slug ? `${slug}-v${semver}` : `v${semver}`;\n}\n\n/**\n * Extract the API version directory from a path (e.g., \"aws/platform/v1\" -> \"v1\").\n * Returns undefined if the path doesn't end with a v\\d+ directory.\n */\nfunction extractApiVersion(path: string): string | undefined {\n const lastSegment = path.split('/').at(-1);\n return lastSegment?.match(/^v\\d+$/) ? lastSegment : undefined;\n}\n\n/**\n * Determine pin type from the number of version segments.\n */\nfunction derivePinType(semver: string): PinType {\n const segments = semver.split('.').length;\n if (segments === 3) return 'exact';\n if (segments === 2) return 'minor';\n return 'major';\n}\n\n/**\n * Parse GitHub URI.\n *\n * Format: github://org/repo/{path}/{semver}\n * Where path includes the API version dir (e.g., aws/platform/v1)\n * and semver is 1-3 segments (e.g., 0.1.0, 0.1, or 0).\n */\nexport function parseGitHubUri(uri: string): GitHubUri {\n if (!uri.startsWith('github://')) {\n throw new EngineError(`Invalid GitHub URI: ${uri}`, EngineErrorCode.GITHUB_NOT_FOUND);\n }\n\n const uriPath = uri.substring(9);\n const parts = uriPath.split('/');\n\n if (parts.length < 4) {\n throw new EngineError(\n `Invalid GitHub URI - expected format github://org/repo/path/semver: ${uri}`,\n EngineErrorCode.GITHUB_NOT_FOUND\n );\n }\n\n const owner = parts[0] ?? '';\n const repo = parts[1] ?? '';\n\n const remainingPath = parts.slice(2).join('/');\n const versionMatch = remainingPath.match(/^(.*?)\\/(\\d+(?:\\.\\d+){0,2})$/);\n\n if (!versionMatch) {\n throw new EngineError(\n `Invalid GitHub URI - could not extract version: ${uri}`,\n EngineErrorCode.GITHUB_NOT_FOUND\n );\n }\n\n const templatePath = versionMatch[1] ?? '';\n const semver = versionMatch[2] ?? '0';\n const pinType = derivePinType(semver);\n const tag = buildTag(templatePath, semver);\n\n return {\n owner,\n repo,\n path: templatePath,\n version: { semver, tag, pinType },\n };\n}\n\n/**\n * Convert GitHub URI to archive download URL.\n */\nexport function toArchiveUrl(ghUri: GitHubUri): string {\n return `https://github.com/${ghUri.owner}/${ghUri.repo}/archive/refs/tags/${ghUri.version.tag}.tar.gz`;\n}\n\n/**\n * Resolve a range pin (minor or major) to an exact tag using the GitHub matching-refs API.\n *\n * For exact pins, returns the tag unchanged.\n * For minor/major pins, queries GitHub for matching tags and returns the highest semver match.\n */\nexport async function resolveVersion(\n ghUri: GitHubUri,\n _options?: ResolverOptions\n): Promise<string> {\n if (ghUri.version.pinType === 'exact') {\n return ghUri.version.tag;\n }\n\n const tagPrefix = buildTag(ghUri.path, ghUri.version.semver);\n\n const apiUrl = `https://api.github.com/repos/${ghUri.owner}/${ghUri.repo}/git/matching-refs/tags/${tagPrefix}`;\n const headers: Record<string, string> = {\n 'User-Agent': 'tfy-infra-engine',\n Accept: 'application/vnd.github.v3+json',\n };\n\n const ghToken = process.env['GITHUB_TOKEN'] || process.env['GH_TOKEN'];\n if (ghToken) {\n headers['Authorization'] = `token ${ghToken}`;\n }\n\n const response = await fetch(apiUrl, { headers });\n\n if (!response.ok) {\n if (response.status === 403) {\n throw new EngineError(\n 'GitHub rate limit exceeded during version resolution. Try again later or set GITHUB_TOKEN.',\n EngineErrorCode.GITHUB_RATE_LIMITED\n );\n }\n throw new EngineError(\n `GitHub API error during version resolution: HTTP ${response.status}`,\n EngineErrorCode.NETWORK_ERROR,\n undefined,\n { apiUrl, status: response.status }\n );\n }\n\n const refs = (await response.json()) as Array<{ ref: string }>;\n\n const semverPattern = /v(\\d+\\.\\d+\\.\\d+)$/;\n const versions: Array<{ tag: string; parts: number[] }> = [];\n\n for (const ref of refs) {\n const tagName = ref.ref.replace('refs/tags/', '');\n const match = tagName.match(semverPattern);\n if (match?.[1]) {\n const vParts = match[1].split('.').map(Number);\n if (vParts.length === 3) {\n versions.push({ tag: tagName, parts: vParts as [number, number, number] });\n }\n }\n }\n\n if (versions.length === 0) {\n throw new EngineError(\n `No tags matching ${tagPrefix}* found in ${ghUri.owner}/${ghUri.repo}`,\n EngineErrorCode.GITHUB_NOT_FOUND,\n undefined,\n { tagPrefix, apiUrl }\n );\n }\n\n versions.sort((a, b) => {\n for (let i = 0; i < 3; i++) {\n const diff = (a.parts[i] ?? 0) - (b.parts[i] ?? 0);\n if (diff !== 0) return diff;\n }\n return 0;\n });\n\n return versions.at(-1)!.tag;\n}\n\n/**\n * Resolver for github:// URIs.\n */\nexport class GitHubResolver implements Resolver {\n private readonly tempDir: string;\n\n constructor(tempDir?: string) {\n const home = process.env['HOME'] || process.env['USERPROFILE'] || '/tmp';\n this.tempDir = tempDir ?? join(home, '.cache', 'tfy-infra', 'github-temp');\n }\n\n canResolve(uri: string): boolean {\n return uri.startsWith('github://');\n }\n\n async resolve(uri: string, options?: ResolverOptions): Promise<Template> {\n const opts = mergeResolverOptions(options);\n const ghUri = parseGitHubUri(uri);\n\n let resolvedTag = ghUri.version.tag;\n if (ghUri.version.pinType !== 'exact') {\n resolvedTag = await withTimeout(\n resolveVersion(ghUri, options),\n opts.timeout,\n `Version resolution timed out after ${opts.timeout}ms`\n );\n }\n\n const archiveUri = { ...ghUri, version: { ...ghUri.version, tag: resolvedTag } };\n const archiveUrl = toArchiveUrl(archiveUri);\n\n const extractDir = join(this.tempDir, `${ghUri.owner}-${ghUri.repo}-${Date.now()}`);\n\n try {\n await mkdir(extractDir, { recursive: true });\n\n await withTimeout(\n withRetry(() => this.downloadAndExtract(archiveUrl, extractDir), opts.retries),\n opts.timeout,\n `GitHub download timed out after ${opts.timeout}ms`\n );\n\n const extractedDirs = await readdir(extractDir);\n if (extractedDirs.length === 0) {\n throw new EngineError(\n `Empty archive from GitHub: ${uri}`,\n EngineErrorCode.GITHUB_NOT_FOUND,\n undefined,\n { archiveUrl }\n );\n }\n\n const archiveRoot = join(extractDir, extractedDirs[0] ?? '');\n const templateDir = ghUri.path ? join(archiveRoot, ghUri.path) : archiveRoot;\n\n const templateJsonPath = join(templateDir, 'template.json');\n let templateJsonContent: string;\n try {\n templateJsonContent = await readFile(templateJsonPath, 'utf-8');\n } catch {\n throw new EngineError(\n `template.json not found in GitHub template: ${uri}`,\n EngineErrorCode.TEMPLATE_JSON_NOT_FOUND,\n undefined,\n { templateDir }\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(templateJsonContent);\n } catch (error) {\n throw new EngineError(\n `Failed to parse template.json: ${(error as Error).message}`,\n EngineErrorCode.TEMPLATE_JSON_INVALID,\n error as Error,\n { templateDir }\n );\n }\n\n const { metadata, jsonSchema } = validateTemplateJson(parsed);\n\n const srcDir = join(templateDir, 'src');\n\n let files: Map<string, string>;\n let staticFiles: Map<string, string>;\n try {\n ({ files, staticFiles } = await walkSrcDir(srcDir));\n } catch {\n throw new EngineError(\n `Template src/ directory not found in GitHub archive: ${uri}`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n undefined,\n { templateDir, srcDir }\n );\n }\n\n if (files.size === 0 && staticFiles.size === 0) {\n throw new EngineError(\n `No template files found in src/ for GitHub: ${uri}`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n undefined,\n { srcDir }\n );\n }\n\n const version: VersionInfo = {\n semver: metadata.version,\n apiVersion: extractApiVersion(ghUri.path),\n };\n\n return {\n metadata,\n jsonSchema,\n files,\n staticFiles,\n source: uri,\n version,\n };\n } catch (error) {\n if (error instanceof EngineError) {\n throw error;\n }\n\n const fetchError = error as Error & { status?: number; statusText?: string };\n\n if (fetchError.message?.includes('404') || fetchError.status === 404) {\n throw new EngineError(\n `GitHub template not found: ${uri}`,\n EngineErrorCode.GITHUB_NOT_FOUND,\n error as Error,\n { archiveUrl }\n );\n }\n\n if (fetchError.message?.includes('rate limit') || fetchError.status === 403) {\n throw new EngineError(\n `GitHub rate limit exceeded. Try again later or use authentication.`,\n EngineErrorCode.GITHUB_RATE_LIMITED,\n error as Error\n );\n }\n\n throw new EngineError(\n `GitHub error: ${(error as Error).message}`,\n EngineErrorCode.NETWORK_ERROR,\n error as Error,\n { uri, archiveUrl }\n );\n } finally {\n try {\n await rm(extractDir, { recursive: true, force: true });\n } catch {\n // Ignore cleanup errors\n }\n }\n }\n\n private async downloadAndExtract(url: string, extractDir: string): Promise<void> {\n const response = await fetch(url, {\n headers: {\n 'User-Agent': 'tfy-infra-engine',\n },\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n if (!response.body) {\n throw new Error('Empty response body');\n }\n\n const bodyStream = response.body as unknown as NodeJS.ReadableStream;\n await pipeline(bodyStream, createGunzip(), extract({ cwd: extractDir }));\n }\n}\n","/**\n * HTTPS bundle resolver for pre-built template bundles (https://*.json).\n *\n * Fetches a bundle JSON file over HTTPS and unpacks it into a Template.\n * Automatically attaches a Bearer token for Artifactory hosts when\n * JFROG_ACCESS_TOKEN is present in the environment.\n */\n\nimport type { Resolver } from './base.js';\nimport type { Template, ResolverOptions, VersionInfo } from '@truefoundry/tfy-infra-engine';\nimport { validateTemplateJson, EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\nimport { mergeResolverOptions, withRetry, withTimeout } from './base.js';\n\nconst JFROG_TOKEN_ENV = 'JFROG_ACCESS_TOKEN';\n\nfunction isArtifactoryHost(uri: string): boolean {\n try {\n const { hostname } = new URL(uri);\n return hostname.includes('jfrog');\n } catch {\n return false;\n }\n}\n\nfunction buildHeaders(uri: string): Record<string, string> {\n const headers: Record<string, string> = {\n 'User-Agent': 'tfy-infra-cli',\n Accept: 'application/json',\n };\n\n if (isArtifactoryHost(uri)) {\n const token = process.env[JFROG_TOKEN_ENV];\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n }\n\n return headers;\n}\n\nasync function fetchBundle(uri: string, headers: Record<string, string>): Promise<Response> {\n const response = await fetch(uri, { headers });\n\n if (response.status === 401 || response.status === 403) {\n const hint = isArtifactoryHost(uri)\n ? ` Set ${JFROG_TOKEN_ENV} in your environment to authenticate.`\n : '';\n throw new EngineError(\n `Authentication failed (HTTP ${response.status}) for ${uri}.${hint}`,\n EngineErrorCode.HTTPS_AUTH_FAILED,\n undefined,\n { uri, status: response.status }\n );\n }\n\n if (!response.ok) {\n throw new EngineError(\n `Bundle download failed: HTTP ${response.status} for ${uri}`,\n EngineErrorCode.NETWORK_ERROR,\n undefined,\n { uri, status: response.status }\n );\n }\n\n return response;\n}\n\nasync function unpackBundle(response: Response, uri: string): Promise<Template> {\n const body: unknown = await response.json();\n const raw = body as Record<string, unknown>;\n\n const { metadata, jsonSchema } = validateTemplateJson({\n template: raw['metadata'],\n schema: raw['jsonSchema'],\n });\n\n const files = new Map(Object.entries((raw['files'] as Record<string, string>) ?? {}));\n const staticFiles = new Map(Object.entries((raw['staticFiles'] as Record<string, string>) ?? {}));\n\n if (files.size === 0 && staticFiles.size === 0) {\n throw new EngineError(\n `Bundle contains no template files: ${uri}`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n undefined,\n { uri }\n );\n }\n\n const version: VersionInfo = { semver: metadata.version };\n\n return { metadata, jsonSchema, files, staticFiles, source: uri, version };\n}\n\n/**\n * Resolver for HTTPS bundle URLs (https://.../*.json).\n */\nexport class HttpsBundleResolver implements Resolver {\n canResolve(uri: string): boolean {\n return uri.startsWith('https://') && uri.toLowerCase().endsWith('.json');\n }\n\n async resolve(uri: string, options?: ResolverOptions): Promise<Template> {\n const opts = mergeResolverOptions(options);\n const headers = buildHeaders(uri);\n\n const response = await withTimeout(\n withRetry(() => fetchBundle(uri, headers), opts.retries),\n opts.timeout,\n `HTTPS bundle download timed out after ${opts.timeout}ms`\n );\n\n return unpackBundle(response, uri);\n }\n}\n","/**\n * Template cache for storing fetched templates locally.\n * Uses filesystem-based caching with version-keyed storage.\n *\n * Moved from @truefoundry/tfy-infra-engine (006).\n * CHANGE (008): Caches template.json instead of schema.yaml.\n */\n\nimport { mkdir, readFile, writeFile, rm, readdir, stat } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport type { Template, VersionInfo } from '@truefoundry/tfy-infra-engine';\nimport { validateTemplateJson, EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\n\n/**\n * Default cache directory.\n */\nexport function getDefaultCacheDir(): string {\n const home = process.env['HOME'] || process.env['USERPROFILE'] || '~';\n return join(home, '.cache', 'tfy-infra', 'templates');\n}\n\n/**\n * Template cache manager.\n */\nexport class TemplateCache {\n private readonly cacheDir: string;\n\n constructor(cacheDir?: string) {\n this.cacheDir = cacheDir ?? getDefaultCacheDir();\n }\n\n private getCacheKey(source: string): string {\n return createHash('sha256').update(source).digest('hex').substring(0, 16);\n }\n\n private getCachePath(source: string, version: string): string {\n const key = this.getCacheKey(source);\n const safeVersion = version.replace(/\\//g, '-');\n return join(this.cacheDir, key, safeVersion);\n }\n\n async has(source: string, version: string): Promise<boolean> {\n const cachePath = this.getCachePath(source, version);\n try {\n const templateJsonPath = join(cachePath, 'template.json');\n await stat(templateJsonPath);\n return true;\n } catch {\n return false;\n }\n }\n\n async get(source: string, version: string): Promise<Template | null> {\n const cachePath = this.getCachePath(source, version);\n\n try {\n const templateJsonPath = join(cachePath, 'template.json');\n const templateJsonContent = await readFile(templateJsonPath, 'utf-8');\n const parsed: unknown = JSON.parse(templateJsonContent);\n const { metadata, jsonSchema } = validateTemplateJson(parsed);\n\n const files = new Map<string, string>();\n const staticFiles = new Map<string, string>();\n await this.readCacheDir(cachePath, '', files, staticFiles);\n\n const versionInfo: VersionInfo = {\n semver: metadata.version,\n };\n\n return {\n metadata,\n jsonSchema,\n files,\n staticFiles,\n source,\n version: versionInfo,\n };\n } catch {\n return null;\n }\n }\n\n async set(template: Template): Promise<void> {\n const cachePath = this.getCachePath(template.source, template.version.semver);\n\n try {\n await mkdir(cachePath, { recursive: true });\n\n const templateJson = {\n template: {\n name: template.metadata.name,\n version: template.metadata.version,\n ...(template.metadata.description ? { description: template.metadata.description } : {}),\n },\n schema: template.jsonSchema,\n };\n await writeFile(\n join(cachePath, 'template.json'),\n JSON.stringify(templateJson, null, 2),\n 'utf-8'\n );\n\n for (const [name, content] of template.files) {\n const filePath = join(cachePath, `${name}.hbs`);\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, content, 'utf-8');\n }\n\n for (const [name, content] of template.staticFiles) {\n const filePath = join(cachePath, name);\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, content, 'utf-8');\n }\n } catch (error) {\n throw new EngineError(\n `Failed to cache template: ${(error as Error).message}`,\n EngineErrorCode.CACHE_ERROR,\n error as Error,\n { source: template.source, version: template.version.semver }\n );\n }\n }\n\n async delete(source: string, version?: string): Promise<void> {\n if (version) {\n const cachePath = this.getCachePath(source, version);\n try {\n await rm(cachePath, { recursive: true, force: true });\n } catch {\n // Ignore if doesn't exist\n }\n } else {\n const key = this.getCacheKey(source);\n const keyPath = join(this.cacheDir, key);\n try {\n await rm(keyPath, { recursive: true, force: true });\n } catch {\n // Ignore if doesn't exist\n }\n }\n }\n\n async clear(): Promise<void> {\n try {\n await rm(this.cacheDir, { recursive: true, force: true });\n } catch {\n // Ignore if doesn't exist\n }\n }\n\n async stats(): Promise<{ sources: number; templates: number; size: number }> {\n let sources = 0;\n let templates = 0;\n let size = 0;\n\n try {\n const sourceKeys = await readdir(this.cacheDir);\n sources = sourceKeys.length;\n\n for (const key of sourceKeys) {\n const keyPath = join(this.cacheDir, key);\n const versions = await readdir(keyPath);\n templates += versions.length;\n\n for (const version of versions) {\n const versionPath = join(keyPath, version);\n size += await this.dirSize(versionPath);\n }\n }\n } catch {\n // Cache doesn't exist or is empty\n }\n\n return { sources, templates, size };\n }\n\n private async readCacheDir(\n rootDir: string,\n relativePath: string,\n files: Map<string, string>,\n staticFiles: Map<string, string>\n ): Promise<void> {\n const currentDir = relativePath ? join(rootDir, relativePath) : rootDir;\n const entries = await readdir(currentDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.name === 'template.json' || entry.name.startsWith('_')) continue;\n const entryRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;\n\n if (entry.isDirectory()) {\n await this.readCacheDir(rootDir, entryRelative, files, staticFiles);\n } else {\n const content = await readFile(join(currentDir, entry.name), 'utf-8');\n if (entry.name.endsWith('.hbs')) {\n files.set(entryRelative.replace(/\\.hbs$/, ''), content);\n } else {\n staticFiles.set(entryRelative, content);\n }\n }\n }\n }\n\n private async dirSize(dir: string): Promise<number> {\n let total = 0;\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n total += await this.dirSize(fullPath);\n } else {\n const fileStat = await stat(fullPath);\n total += fileStat.size;\n }\n }\n return total;\n }\n}\n\n/**\n * Create a template cache instance.\n */\nexport function createTemplateCache(cacheDir?: string): TemplateCache {\n return new TemplateCache(cacheDir);\n}\n","/**\n * Configuration file loader for YAML and JSON formats.\n *\n * CHANGE (006): Returns RawConfig (with template as URI string) instead of\n * Envelope (which now requires a resolved Template object). The CLI command\n * is responsible for resolving the template URI before calling the engine.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport * as yaml from 'js-yaml';\n\n/**\n * Raw configuration from a config file.\n * `template` is a URI string that must be resolved before passing to the engine.\n */\nexport interface RawConfig {\n template: string;\n inputs: Record<string, unknown>;\n intentId: string;\n platformPrefix?: string;\n options?: {\n skipFormat?: boolean;\n };\n}\n\n/**\n * Load a configuration file (YAML or JSON).\n *\n * @param filePath - Path to the config file\n * @returns Raw configuration with template URI string\n */\nexport async function loadConfig(filePath: string): Promise<RawConfig> {\n const content = await readFile(filePath, 'utf-8');\n\n const isJson = filePath.endsWith('.json');\n\n let parsed: Record<string, unknown>;\n try {\n if (isJson) {\n parsed = JSON.parse(content) as Record<string, unknown>;\n } else {\n parsed = yaml.load(content) as Record<string, unknown>;\n }\n } catch (error) {\n const format = isJson ? 'JSON' : 'YAML';\n throw new Error(`Failed to parse ${format} config file: ${(error as Error).message}`, {\n cause: error,\n });\n }\n\n if (\n !parsed['intentId'] ||\n typeof parsed['intentId'] !== 'string' ||\n parsed['intentId'].trim() === ''\n ) {\n throw new Error(\n `Configuration file is missing required field 'intentId'. ` +\n `Add 'intentId: \"<your-cluster-identity>\"' to ${filePath}. ` +\n `The intentId uniquely identifies the cluster for integrity tracking.`\n );\n }\n\n return {\n template: parsed['template'] as string,\n inputs: (parsed['inputs'] as Record<string, unknown>) ?? {},\n intentId: parsed['intentId'],\n platformPrefix: parsed['platformPrefix'] as string | undefined,\n options: parsed['options'] as RawConfig['options'],\n };\n}\n\nexport { fileExists } from './fs-helpers.js';\n","/**\n * Shared filesystem helper utilities.\n */\n\nimport { access } from 'node:fs/promises';\nimport { join } from 'node:path';\n\n/**\n * Check if a file exists on disk.\n */\nexport async function fileExists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Find the default fixture path (tests/default/inputs.yaml) for a template version.\n * Returns null if the file does not exist.\n */\nexport async function findDefaultFixture(versionDir: string): Promise<string | null> {\n const defaultPath = join(versionDir, 'tests', 'default', 'inputs.yaml');\n try {\n await access(defaultPath);\n return defaultPath;\n } catch {\n return null;\n }\n}\n","/**\n * Manifest loading and raw config reconstruction utility.\n *\n * CHANGE (006): envelopeFromManifest now returns RawConfig (with template as\n * URI string) instead of Envelope. The CLI command resolves the URI.\n *\n * Reference: data-model.md §11, research.md R-008\n */\n\nimport { readFile } from 'node:fs/promises';\nimport type { Manifest } from '@truefoundry/tfy-infra-engine';\nimport type { RawConfig } from './config-loader.js';\n\n/**\n * Load and validate a manifest from disk.\n */\nexport async function loadManifest(manifestPath: string): Promise<Manifest> {\n let content: string;\n try {\n content = await readFile(manifestPath, 'utf-8');\n } catch (error) {\n const nodeError = error as NodeJS.ErrnoException;\n if (nodeError.code === 'ENOENT') {\n throw new Error(`Manifest not found: ${manifestPath}`, { cause: error });\n }\n throw new Error(`Failed to read manifest: ${(error as Error).message}`, { cause: error });\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(content) as Record<string, unknown>;\n } catch {\n throw new Error(`Invalid JSON in manifest: ${manifestPath}`);\n }\n\n const requiredFields = [\n 'manifestVersion',\n 'files',\n 'aggregateHash',\n 'inputs',\n 'platformPrefix',\n 'intentId',\n 'templateSource',\n 'templateVersion',\n ] as const;\n\n for (const field of requiredFields) {\n if (parsed[field] === undefined) {\n throw new Error(`Manifest is missing required field: ${field}`);\n }\n }\n\n return parsed as unknown as Manifest;\n}\n\n/**\n * Reconstruct a RawConfig from a manifest for re-render.\n *\n * CHANGE (006): Returns RawConfig with template URI string.\n */\nexport function envelopeFromManifest(manifest: Manifest): RawConfig {\n if (!manifest.inputs) {\n throw new Error('Cannot reconstruct envelope: manifest is missing inputs field');\n }\n if (!manifest.platformPrefix) {\n throw new Error('Cannot reconstruct envelope: manifest is missing platformPrefix field');\n }\n\n return {\n template: manifest.templateSource,\n inputs: manifest.inputs,\n intentId: manifest.intentId,\n platformPrefix: manifest.platformPrefix,\n };\n}\n","/**\n * Engine output-to-disk writer utility.\n *\n * Writes engine results to disk with zone-aware policies.\n *\n * CHANGE (008): Removed sideOutputs handling — no more expose_variable pipeline.\n *\n * Reference: data-model.md §10, spec.md FR-012a\n */\n\nimport { writeFile, mkdir } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport type { InstallResult, UpgradeResult } from '@truefoundry/tfy-infra-engine';\nimport { fileExists } from './fs-helpers.js';\n\n/**\n * Write engine output to disk with zone-aware policies.\n *\n * Modes:\n * - 'install': Overwrite all files\n * - 'upgrade': Overwrite platform zone files, skip existing user zone files,\n * create new user zone files\n *\n * @param dir - Target directory\n * @param result - InstallResult or UpgradeResult from engine\n * @param mode - 'install' (overwrite all) or 'upgrade' (zone-aware)\n */\nexport async function writeEngineOutput(\n dir: string,\n result: InstallResult | UpgradeResult,\n mode: 'install' | 'upgrade'\n): Promise<void> {\n await mkdir(dir, { recursive: true });\n\n for (const [filePath, entry] of result.files) {\n const fullPath = join(dir, filePath);\n\n if (mode === 'upgrade' && entry.zone === 'user') {\n if (await fileExists(fullPath)) {\n continue;\n }\n }\n\n await mkdir(dirname(fullPath), { recursive: true });\n await writeFile(fullPath, entry.content, 'utf-8');\n }\n}\n","/**\n * Shared error handling for CLI commands.\n *\n * Provides consistent error output and exit code mapping across all\n * tfy-init tpl commands.\n */\n\nimport { EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\nimport * as output from './output.js';\n\n/**\n * Standard exit codes shared across all commands.\n */\nexport const EXIT_CODES = {\n SUCCESS: 0,\n VALIDATION_ERROR: 1,\n FETCH_ERROR: 2,\n RENDER_ERROR: 3,\n FORMAT_ERROR: 4,\n} as const;\n\n/**\n * Handle an error from a CLI command and exit with the appropriate code.\n *\n * Provides consistent behavior:\n * - Displays the error message\n * - Shows validation error details when available\n * - Shows engine error details in verbose mode\n * - Maps EngineErrorCode to standardized exit codes\n * - Shows format hint for tofu-related errors\n *\n * @param error - The caught error\n * @param fallbackExitCode - Exit code for unrecognized errors (default: RENDER_ERROR)\n */\nexport function handleEngineError(\n error: unknown,\n fallbackExitCode: number = EXIT_CODES.RENDER_ERROR\n): never {\n if (error instanceof EngineError) {\n output.error(error.message);\n\n // Show engine error details in verbose mode\n if (output.getVerbosity() === 'verbose' && error.details) {\n output.info('');\n output.info('Details:');\n for (const [key, value] of Object.entries(error.details)) {\n if (key !== 'errors') {\n output.info(` ${key}: ${JSON.stringify(value)}`);\n }\n }\n }\n\n // Show validation error list when available\n if (error.details?.['errors']) {\n output.info('');\n output.info('Errors:');\n output.info(\n output.formatValidationErrors(\n error.details['errors'] as Array<{ path: string; message: string }>\n )\n );\n }\n\n // Map error codes to exit codes\n switch (error.code) {\n case EngineErrorCode.INPUT_VALIDATION_FAILED:\n case EngineErrorCode.SCHEMA_INVALID:\n case EngineErrorCode.ENVELOPE_VALIDATION_FAILED:\n process.exit(EXIT_CODES.VALIDATION_ERROR);\n break;\n\n case EngineErrorCode.TEMPLATE_NOT_FOUND:\n case EngineErrorCode.FILE_NOT_FOUND:\n case EngineErrorCode.NETWORK_ERROR:\n case EngineErrorCode.GITHUB_NOT_FOUND:\n process.exit(EXIT_CODES.FETCH_ERROR);\n break;\n\n case EngineErrorCode.TOFU_NOT_FOUND:\n case EngineErrorCode.TOFU_FMT_FAILED:\n output.info('');\n output.info('Run with --skip-format to skip formatting.');\n process.exit(EXIT_CODES.FORMAT_ERROR);\n break;\n\n case EngineErrorCode.MANIFEST_PARSE_ERROR:\n process.exit(EXIT_CODES.VALIDATION_ERROR);\n break;\n\n default:\n process.exit(fallbackExitCode);\n }\n }\n\n // Generic (non-engine) error\n output.error((error as Error).message);\n if (output.getVerbosity() === 'verbose') {\n console.error((error as Error).stack);\n }\n process.exit(fallbackExitCode);\n}\n","/**\n * Output formatting utilities for CLI.\n */\n\nimport type { DriftType } from '@truefoundry/tfy-infra-engine';\n\n/* eslint-disable no-console */\n\n/**\n * Output verbosity level.\n */\nexport type Verbosity = 'quiet' | 'normal' | 'verbose';\n\n/**\n * Global verbosity setting.\n */\nlet currentVerbosity: Verbosity = 'normal';\n\n/**\n * Set the output verbosity level.\n */\nexport function setVerbosity(level: Verbosity): void {\n currentVerbosity = level;\n}\n\n/**\n * Get the current verbosity level.\n */\nexport function getVerbosity(): Verbosity {\n return currentVerbosity;\n}\n\n/**\n * Print success message with checkmark.\n */\nexport function success(message: string): void {\n if (currentVerbosity !== 'quiet') {\n console.log(`✓ ${message}`);\n }\n}\n\n/**\n * Print error message with X.\n */\nexport function error(message: string): void {\n console.error(`✗ ${message}`);\n}\n\n/**\n * Print info message (only in normal/verbose mode).\n */\nexport function info(message: string): void {\n if (currentVerbosity !== 'quiet') {\n console.log(message);\n }\n}\n\n/**\n * Print verbose message (only in verbose mode).\n */\nexport function verbose(message: string): void {\n if (currentVerbosity === 'verbose') {\n console.log(` ${message}`);\n }\n}\n\n/**\n * Print warning message.\n */\nexport function warn(message: string): void {\n if (currentVerbosity !== 'quiet') {\n console.warn(`⚠ ${message}`);\n }\n}\n\n/**\n * Format file size in human readable format.\n */\nexport function formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n/**\n * Format validation errors for display.\n */\nexport function formatValidationErrors(errors: Array<{ path: string; message: string }>): string {\n return errors.map((e) => ` • ${e.path}: ${e.message}`).join('\\n');\n}\n\n/**\n * Map DriftType to a human-readable tag.\n */\nexport function driftTypeTag(type: DriftType): string {\n switch (type) {\n case 'content_drift':\n return 'DRIFTED';\n case 'missing_file':\n return 'MISSING';\n case 'unexpected_file':\n return 'UNEXPECTED';\n case 'metadata_inconsistency':\n return 'INCONSISTENT';\n case 'source_mismatch':\n return 'SOURCE MISMATCH';\n }\n}\n\n/**\n * Print a list of files with sizes.\n */\nexport function printFileList(files: Array<{ path: string; size: number }>): void {\n if (currentVerbosity === 'quiet') return;\n\n console.log('Files:');\n for (const file of files) {\n console.log(` - ${file.path} (${formatSize(file.size)})`);\n }\n}\n","/**\n * tfy-init tpl schema command\n *\n * CHANGE (006): Fetch-first — resolves template via ResolverRegistry.\n * CHANGE (008): Templates provide JSON Schema directly via template.json.\n * No more conversion from InputDefinition. The --json-schema flag is now\n * the default behavior. --full outputs the complete template.json.\n */\n\nimport { Command } from 'commander';\nimport { writeFile, mkdir } from 'node:fs/promises';\nimport { join, dirname, resolve } from 'node:path';\nimport * as yaml from 'js-yaml';\nimport { createResolverRegistry } from '../../resolvers/index.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\n\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\nexport function createSchemaCommand(): Command {\n const cmd = new Command('schema')\n .description('Fetch and display the schema for a template')\n .option('--template <uri>', 'Template source URI (github://, file://)')\n .option('--format <type>', 'Output format: json, yaml', 'json')\n .option('-o, --output <file>', 'Output file path (default: inputs.schema.json in template dir)')\n .option('--full', 'Include full template.json (metadata + schema)')\n .option('--no-cache', 'Force re-fetch template')\n .option('--timeout <ms>', 'Network timeout in milliseconds', '30000')\n .action(async (options: SchemaOptions) => {\n try {\n if (!options.template) {\n throw new Error('--template <uri> is required.');\n }\n\n if (options.output) options.output = resolve(options.output);\n\n const exitCode = await runSchema(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\ninterface SchemaOptions {\n template: string;\n format: 'json' | 'yaml';\n output?: string;\n full?: boolean;\n cache?: boolean;\n timeout: string;\n}\n\nexport function getDefaultOutputPath(templateUri: string): string {\n let basePath = templateUri;\n if (basePath.startsWith('file://')) {\n basePath = basePath.slice(7);\n }\n return join(basePath, 'inputs.schema.json');\n}\n\nexport async function writeJsonSchemaToFile(schema: unknown, outputPath: string): Promise<void> {\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, JSON.stringify(schema, null, 2), 'utf-8');\n}\n\nasync function runSchema(options: SchemaOptions): Promise<number> {\n const templateUri = options.template;\n const timeout = parseInt(options.timeout, 10);\n\n output.verbose(`Fetching schema from ${templateUri}`);\n\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(templateUri, {\n noCache: options.cache === false,\n timeout,\n });\n\n // Build output data\n let data: unknown;\n if (options.full) {\n data = {\n template: {\n name: template.metadata.name,\n version: template.metadata.version,\n ...(template.metadata.description ? { description: template.metadata.description } : {}),\n },\n schema: template.jsonSchema,\n };\n } else {\n data = template.jsonSchema;\n }\n\n // Write to file or stdout\n if (options.output) {\n await writeJsonSchemaToFile(data, options.output);\n output.success(`JSON Schema written to: ${options.output}`);\n return EXIT_CODES.SUCCESS;\n }\n\n if (options.format === 'yaml') {\n // eslint-disable-next-line no-console\n console.log(yaml.dump(data, { indent: 2, lineWidth: 100 }));\n } else {\n // eslint-disable-next-line no-console\n console.log(JSON.stringify(data, null, 2));\n }\n\n return EXIT_CODES.SUCCESS;\n}\n","/**\n * tfy-init tpl validate command\n *\n * CHANGE (008): Validates template.json instead of schema.yaml.\n * Uses validateTemplateJson(), validateJsonSchemaStructure().\n */\n\nimport { Command } from 'commander';\nimport { readFile } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport Handlebars from 'handlebars';\nimport {\n validateTemplateJson,\n validateJsonSchemaStructure,\n isTofuAvailable,\n} from '@truefoundry/tfy-infra-engine';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { walkSrcDir } from '../../utils/walk-src.js';\n\n// Re-export for backward compatibility\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\n/**\n * Create the validate command.\n */\nexport function createValidateCommand(): Command {\n const cmd = new Command('validate')\n .description('Validate a template (for template authors)')\n .option('-d, --directory <dir>', 'Path to local template directory', '.')\n .option('--strict', 'Fail on warnings')\n .option('--skip-format', 'Skip tofu fmt validation')\n .action(async (options: ValidateOptions) => {\n try {\n const exitCode = await runValidate(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\ninterface ValidateOptions {\n directory: string;\n strict?: boolean;\n skipFormat?: boolean;\n}\n\ninterface ValidationIssue {\n type: 'error' | 'warning';\n message: string;\n file?: string;\n}\n\n/**\n * Execute the validate command.\n */\nasync function runValidate(options: ValidateOptions): Promise<number> {\n const templateDir = resolve(options.directory);\n const issues: ValidationIssue[] = [];\n let hasErrors = false;\n\n output.info(`Validating template at: ${templateDir}`);\n output.info('');\n\n // 1. Check template.json exists, is valid JSON, and has correct structure\n output.verbose('Checking template.json...');\n const templateJsonPath = join(templateDir, 'template.json');\n try {\n const templateJsonContent = await readFile(templateJsonPath, 'utf-8');\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(templateJsonContent);\n } catch (error) {\n throw new Error(`Invalid JSON: ${(error as Error).message}`, { cause: error });\n }\n\n const { jsonSchema } = validateTemplateJson(parsed);\n\n validateJsonSchemaStructure(jsonSchema);\n\n output.success('template.json is valid');\n } catch (error) {\n issues.push({\n type: 'error',\n message: (error as Error).message,\n file: 'template.json',\n });\n hasErrors = true;\n }\n\n // 2. Check src/ directory for template files (recursive)\n output.verbose('Checking template files in src/...');\n const srcDir = join(templateDir, 'src');\n try {\n const { files: hbsMap, staticFiles: staticMap } = await walkSrcDir(srcDir);\n\n if (hbsMap.size === 0 && staticMap.size === 0) {\n issues.push({\n type: 'error',\n message: 'No template files found in src/',\n });\n hasErrors = true;\n } else {\n for (const [relPath, content] of hbsMap) {\n try {\n Handlebars.precompile(content);\n output.verbose(` ✓ ${relPath}.hbs`);\n } catch (error) {\n issues.push({\n type: 'error',\n message: `Invalid Handlebars syntax: ${(error as Error).message}`,\n file: `src/${relPath}.hbs`,\n });\n hasErrors = true;\n }\n }\n output.success(\n `Found ${hbsMap.size} HBS template(s)` +\n (staticMap.size > 0 ? ` and ${staticMap.size} static file(s)` : '') +\n ' with valid syntax'\n );\n }\n } catch (error) {\n issues.push({\n type: 'error',\n message: `Cannot read src/ directory: ${(error as Error).message}`,\n });\n hasErrors = true;\n }\n\n // 3. Check tofu fmt (if not skipped)\n if (!options.skipFormat) {\n output.verbose('Checking tofu fmt availability...');\n const tofuAvailable = await isTofuAvailable();\n if (tofuAvailable) {\n output.success('tofu fmt available');\n } else {\n issues.push({\n type: 'warning',\n message: 'tofu not found - format validation skipped',\n });\n }\n }\n\n // Print results\n output.info('');\n\n const warnings = issues.filter((i) => i.type === 'warning');\n const errors = issues.filter((i) => i.type === 'error');\n\n if (warnings.length > 0) {\n output.warn(`${warnings.length} warning(s):`);\n for (const issue of warnings) {\n const prefix = issue.file ? `${issue.file}: ` : '';\n output.info(` • ${prefix}${issue.message}`);\n }\n output.info('');\n }\n\n if (errors.length > 0) {\n output.error(`${errors.length} error(s):`);\n for (const issue of errors) {\n const prefix = issue.file ? `${issue.file}: ` : '';\n output.info(` • ${prefix}${issue.message}`);\n }\n output.info('');\n }\n\n // Determine exit code\n if (hasErrors) {\n output.error('Template validation failed');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n if (options.strict && warnings.length > 0) {\n output.error('Template validation failed (strict mode)');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n output.success('Template is valid');\n return EXIT_CODES.SUCCESS;\n}\n","/**\n * tfy-init tpl test command\n *\n * CHANGE (006): Fetch-first — resolves template via ResolverRegistry\n * before calling engine.install(). Envelope uses Template object.\n */\n\nimport { Command } from 'commander';\nimport { readFile, readdir, writeFile, mkdir } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport * as yaml from 'js-yaml';\nimport { createEngine, type Envelope, type InstallResult } from '@truefoundry/tfy-infra-engine';\nimport { createResolverRegistry } from '../../resolvers/index.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { generateUnifiedDiff } from '../../utils/diff.js';\n\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\nexport function createTestCommand(): Command {\n const cmd = new Command('test')\n .description('Run test fixtures against a template')\n .option('-d, --directory <dir>', 'Path to local template directory', '.')\n .option('--fixture <name>', 'Run specific fixture only')\n .option('--update', 'Update expected outputs with actual results')\n .option('--diff', 'Show unified diff on failures')\n .option('--skip-format', 'Skip tofu fmt in tests')\n .action(async (options: TestOptions) => {\n try {\n const exitCode = await runTest(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\ninterface TestOptions {\n directory: string;\n fixture?: string;\n update?: boolean;\n diff?: boolean;\n skipFormat?: boolean;\n}\n\ninterface TestResult {\n name: string;\n passed: boolean;\n errors?: string[];\n diffs?: Array<{ filename: string; diff: string }>;\n}\n\nasync function runTest(options: TestOptions): Promise<number> {\n const templateDir = resolve(options.directory);\n const testsDir = join(templateDir, 'tests');\n const templateUri = `file://${templateDir}`;\n\n output.info(`Testing template at: ${templateDir}`);\n output.info('');\n\n let fixtures: string[];\n try {\n const entries = await readdir(testsDir);\n fixtures = [];\n for (const entry of entries) {\n try {\n const inputsPath = join(testsDir, entry, 'inputs.yaml');\n await readFile(inputsPath);\n fixtures.push(entry);\n } catch {\n // Not a valid fixture directory\n }\n }\n } catch {\n output.error(`No tests directory found at ${testsDir}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n if (fixtures.length === 0) {\n output.error('No test fixtures found');\n output.info('');\n output.info('Expected structure:');\n output.info(' tests/');\n output.info(' <fixture-name>/');\n output.info(' inputs.yaml');\n output.info(' expected/');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n if (options.fixture) {\n if (!fixtures.includes(options.fixture)) {\n output.error(`Fixture '${options.fixture}' not found`);\n output.info(`Available fixtures: ${fixtures.join(', ')}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n fixtures = [options.fixture];\n }\n\n output.info(`Running ${fixtures.length} fixture(s)...`);\n output.info('');\n\n // Fetch-first: resolve template once for all fixtures\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(templateUri, { noCache: true });\n\n const engine = createEngine();\n const results: TestResult[] = [];\n\n for (const fixture of fixtures) {\n const fixtureDir = join(testsDir, fixture);\n const inputsPath = join(fixtureDir, 'inputs.yaml');\n const expectedDir = join(fixtureDir, 'expected');\n\n output.verbose(`Running fixture: ${fixture}`);\n\n try {\n const inputsContent = await readFile(inputsPath, 'utf-8');\n const inputs = yaml.load(inputsContent) as Record<string, unknown>;\n\n const envelope: Envelope = {\n template,\n inputs,\n intentId: `test-${fixture}`,\n options: {\n skipFormat: options.skipFormat,\n },\n };\n\n const result = await engine.install(envelope);\n\n if (options.update) {\n await updateExpected(expectedDir, result);\n output.success(`${fixture}: updated`);\n results.push({ name: fixture, passed: true });\n } else {\n const { errors, diffs } = await compareOutputs(expectedDir, result, options.diff);\n if (errors.length === 0) {\n output.success(`${fixture}: passed`);\n results.push({ name: fixture, passed: true });\n } else {\n output.error(`${fixture}: failed`);\n for (const err of errors) {\n output.info(` ${err}`);\n }\n if (options.diff && diffs && diffs.length > 0) {\n for (const d of diffs) {\n output.info(` --- expected/${d.filename}`);\n output.info(` +++ actual/${d.filename}`);\n const diffLines = d.diff.split('\\n').slice(4);\n for (const line of diffLines) {\n if (line) {\n output.info(` ${line}`);\n }\n }\n }\n }\n results.push({ name: fixture, passed: false, errors, diffs });\n }\n }\n } catch (error) {\n output.error(`${fixture}: error`);\n output.info(` ${(error as Error).message}`);\n results.push({ name: fixture, passed: false, errors: [(error as Error).message] });\n }\n }\n\n output.info('');\n const passed = results.filter((r) => r.passed).length;\n const failed = results.filter((r) => !r.passed).length;\n\n if (failed === 0) {\n output.success(`All ${passed} fixture(s) passed`);\n return EXIT_CODES.SUCCESS;\n } else {\n output.error(`${failed} fixture(s) failed, ${passed} passed`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n}\n\n/**\n * Replaces machine-specific `file://` paths in @tfy-status source fields\n * with a stable placeholder so snapshots are portable across environments.\n */\nfunction normalizeStatusSource(content: string): string {\n return content.replace(/(\"source\"\\s*:\\s*)\"file:\\/\\/[^\"]*\"/g, '$1\"<local>\"');\n}\n\nasync function updateExpected(expectedDir: string, result: InstallResult): Promise<void> {\n await mkdir(expectedDir, { recursive: true });\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const outputPath = join(expectedDir, filePath);\n await writeFile(outputPath, normalizeStatusSource(entry.content), 'utf-8');\n }\n}\n\ninterface CompareResult {\n errors: string[];\n diffs?: Array<{ filename: string; diff: string }>;\n}\n\nasync function compareOutputs(\n expectedDir: string,\n result: InstallResult,\n includeDiffs?: boolean\n): Promise<CompareResult> {\n const errors: string[] = [];\n const diffs: Array<{ filename: string; diff: string }> = [];\n\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const actual = entry.content;\n\n const expectedPath = join(expectedDir, filePath);\n try {\n const expected = await readFile(expectedPath, 'utf-8');\n const normalizedActual = normalizeStatusSource(actual.trim().replace(/\\r\\n/g, '\\n'));\n const normalizedExpected = normalizeStatusSource(expected.trim().replace(/\\r\\n/g, '\\n'));\n\n if (normalizedActual !== normalizedExpected) {\n errors.push(`${filePath}: content mismatch`);\n if (includeDiffs) {\n const diff = generateUnifiedDiff(normalizedExpected, normalizedActual, filePath);\n diffs.push({ filename: filePath, diff });\n }\n }\n } catch {\n errors.push(`${filePath}: expected file not found`);\n }\n }\n\n try {\n const expectedFiles = await readdir(expectedDir);\n for (const file of expectedFiles) {\n if (!result.files.has(file) && file !== 'manifest.json') {\n errors.push(`${file}: unexpected file in expected/`);\n }\n }\n } catch {\n // Expected directory might not exist\n }\n\n return { errors, diffs: includeDiffs ? diffs : undefined };\n}\n","/**\n * Unified diff generation utility\n * Generates unified diff output for test failures\n * @file packages/infra-cli/src/utils/diff.ts\n */\n\nimport { createTwoFilesPatch } from 'diff';\n\n/**\n * Generate a unified diff between expected and actual content.\n *\n * @param expected - Expected file content\n * @param actual - Actual file content\n * @param filename - Name of the file being compared\n * @returns Unified diff string, or empty string if contents match\n */\nexport function generateUnifiedDiff(expected: string, actual: string, filename: string): string {\n const normalizedExpected = expected.replace(/\\r\\n/g, '\\n');\n const normalizedActual = actual.replace(/\\r\\n/g, '\\n');\n\n const patch = createTwoFilesPatch(\n `expected/${filename}`,\n `actual/${filename}`,\n normalizedExpected,\n normalizedActual,\n '', // oldHeader\n '', // newHeader\n { context: 3 }\n );\n\n return patch;\n}\n\n/**\n * Diff entry for a single file\n */\nexport interface DiffEntry {\n filename: string;\n diff: string;\n}\n\n/**\n * Format multiple diffs for display output.\n *\n * @param diffs - Array of diff entries with filenames\n * @returns Formatted string with all diffs\n */\nexport function formatDiffOutput(diffs: DiffEntry[]): string {\n if (diffs.length === 0) {\n return '';\n }\n\n return diffs\n .map((entry) => {\n return ` ${entry.filename}: content mismatch\\n${entry.diff}`;\n })\n .join('\\n');\n}\n","/**\n * tfy-init tpl dev command\n * Watch mode for rapid template iteration\n *\n * CHANGE (006): Fetch-first — resolves template via ResolverRegistry\n * before calling engine.install().\n * CHANGE (008): Uses template.json instead of schema.yaml.\n */\n\nimport { Command } from 'commander';\nimport { readFile, mkdir, rm, writeFile, access } from 'node:fs/promises';\nimport { dirname, join, relative, resolve } from 'node:path';\nimport * as yaml from 'js-yaml';\nimport {\n createEngine,\n EngineError,\n EngineErrorCode,\n type Envelope,\n type InstallResult,\n} from '@truefoundry/tfy-infra-engine';\nimport { createResolverRegistry } from '../../resolvers/index.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { findDefaultFixture } from '../../utils/fs-helpers.js';\nimport { createWatcher, type WatchEvent } from '../../utils/file-watcher.js';\nimport { generateUnifiedDiff } from '../../utils/diff.js';\n\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\nexport interface DevOptions {\n directory: string;\n fixture?: string;\n output: string;\n test?: boolean;\n}\n\nexport function createDevCommand(): Command {\n const cmd = new Command('dev')\n .description('Watch mode for rapid template iteration')\n .option('-d, --directory <dir>', 'Path to template version directory', '.')\n .option('-f, --fixture <path>', 'Input file or fixture to use for testing')\n .requiredOption('-o, --output <dir>', 'Output directory for rendered files')\n .option('--no-test', 'Only validate, do not run tests')\n .action(async (options: DevOptions) => {\n try {\n const exitCode = await runDev(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\nexport { findDefaultFixture } from '../../utils/fs-helpers.js';\n\nexport async function validateTemplateDir(\n versionDir: string\n): Promise<{ valid: boolean; error?: string }> {\n try {\n await access(versionDir);\n } catch {\n return { valid: false, error: `Directory not found: ${versionDir}` };\n }\n\n const templateJsonPath = join(versionDir, 'template.json');\n try {\n await access(templateJsonPath);\n } catch {\n return { valid: false, error: `Missing template.json in ${versionDir}` };\n }\n\n return { valid: true };\n}\n\nexport async function ensureOutputDir(_versionDir: string, customOutput: string): Promise<string> {\n const outputDir = resolve(customOutput);\n\n try {\n await rm(outputDir, { recursive: true, force: true });\n } catch {\n // Directory might not exist\n }\n\n await mkdir(outputDir, { recursive: true });\n return outputDir;\n}\n\nasync function runDev(options: DevOptions): Promise<number> {\n const versionDir = resolve(options.directory);\n\n const validation = await validateTemplateDir(versionDir);\n if (!validation.valid) {\n output.error(validation.error!);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n let fixturePath: string;\n if (options.fixture) {\n fixturePath = resolve(versionDir, options.fixture);\n try {\n await access(fixturePath);\n } catch {\n output.error(`Fixture not found: ${options.fixture}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n } else {\n const defaultFixture = await findDefaultFixture(versionDir);\n if (!defaultFixture) {\n output.error('No default fixture found at tests/default/inputs.yaml');\n output.info('Either create a default fixture or use --fixture to specify one.');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n fixturePath = defaultFixture;\n }\n\n const outputDir = await ensureOutputDir(versionDir, options.output);\n const relativeOutputDir = relative(process.cwd(), outputDir);\n\n output.info(`[watching] ${relative(process.cwd(), versionDir)}`);\n output.info(`[output] ${relativeOutputDir}/`);\n output.info(`[fixture] ${relative(process.cwd(), fixturePath)}`);\n output.info('');\n\n const engine = createEngine();\n const templateUri = `file://${versionDir}`;\n\n await runRenderCycle(engine, templateUri, versionDir, fixturePath, outputDir, options.test);\n\n const watcher = createWatcher(versionDir, {\n onChange: (event: WatchEvent) => {\n void (async () => {\n const relPath = relative(versionDir, event.path);\n output.info(`[changed] ${relPath}`);\n\n if (event.fileType === 'schema') {\n await runValidation(templateUri);\n } else if (event.fileType === 'template' || event.fileType === 'inputs') {\n await runRenderCycle(\n engine,\n templateUri,\n versionDir,\n fixturePath,\n outputDir,\n options.test\n );\n }\n })();\n },\n onReady: () => {\n output.info('[ready] Watching for changes... (Ctrl+C to exit)');\n output.info('');\n },\n onError: (error) => {\n output.error(`Watch error: ${error.message}`);\n },\n });\n\n return new Promise<number>((resolvePromise) => {\n const cleanup = () => {\n output.info('');\n output.info('[stopped] Watch mode ended');\n void watcher.close();\n resolvePromise(EXIT_CODES.SUCCESS);\n };\n\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n });\n}\n\n/**\n * Run validation on schema.yaml by resolving the template.\n */\nasync function runValidation(templateUri: string): Promise<boolean> {\n try {\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(templateUri, { noCache: true });\n if (template.jsonSchema) {\n output.info('[validate] ✓ template.json valid');\n return true;\n }\n output.error('[validate] ✗ template.json invalid');\n return false;\n } catch (error) {\n output.error(`[validate] ✗ template.json invalid`);\n output.info(` ${(error as Error).message}`);\n return false;\n }\n}\n\n/**\n * Run a full render and test cycle.\n */\nasync function runRenderCycle(\n engine: ReturnType<typeof createEngine>,\n templateUri: string,\n versionDir: string,\n fixturePath: string,\n outputDir: string,\n runTests?: boolean\n): Promise<void> {\n const valid = await runValidation(templateUri);\n if (!valid) return;\n\n try {\n const inputsContent = await readFile(fixturePath, 'utf-8');\n const inputs = yaml.load(inputsContent) as Record<string, unknown>;\n\n // Fetch-first: resolve template, then call engine\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(templateUri, { noCache: true });\n\n const envelope: Envelope = {\n template,\n inputs,\n intentId: `dev-${Date.now()}`,\n options: { skipFormat: false },\n };\n\n const result = await engine.install(envelope);\n\n const templateFileCount = Array.from(result.files.keys()).filter(\n (f) => f !== 'manifest.json'\n ).length;\n output.info(`[validate] ✓ ${templateFileCount} template files valid`);\n\n await rm(outputDir, { recursive: true, force: true });\n await mkdir(outputDir, { recursive: true });\n\n let filesWritten = 0;\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const outputPath = join(outputDir, filePath);\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, entry.content, 'utf-8');\n filesWritten++;\n }\n output.info(\n `[render] ✓ ${filesWritten} files written to ${relative(process.cwd(), outputDir)}/`\n );\n\n if (runTests !== false) {\n await runTestComparison(versionDir, result, outputDir);\n }\n } catch (error) {\n output.error(`[render] ✗ Render failed`);\n output.info(` ${(error as Error).message}`);\n if (\n error instanceof EngineError &&\n error.code === EngineErrorCode.INPUT_VALIDATION_FAILED &&\n error.details?.['errors']\n ) {\n output.info(\n output.formatValidationErrors(\n error.details['errors'] as Array<{ path: string; message: string }>\n )\n );\n }\n }\n}\n\nasync function runTestComparison(\n versionDir: string,\n result: InstallResult,\n _outputDir: string\n): Promise<void> {\n const expectedDir = join(versionDir, 'tests', 'default', 'expected');\n const startTime = Date.now();\n\n try {\n await access(expectedDir);\n } catch {\n output.info('[test] ⚠ No expected/ directory, skipping comparison');\n return;\n }\n\n const errors: Array<{ file: string; diff?: string }> = [];\n\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const actual = entry.content;\n\n const expectedPath = join(expectedDir, filePath);\n try {\n const expected = await readFile(expectedPath, 'utf-8');\n const normalizedActual = actual.trim().replace(/\\r\\n/g, '\\n');\n const normalizedExpected = expected.trim().replace(/\\r\\n/g, '\\n');\n\n if (normalizedActual !== normalizedExpected) {\n const diff = generateUnifiedDiff(normalizedExpected, normalizedActual, filePath);\n errors.push({ file: filePath, diff });\n }\n } catch {\n errors.push({ file: filePath });\n }\n }\n\n const duration = Date.now() - startTime;\n\n if (errors.length === 0) {\n output.info(`[test] ✓ default: passed (${duration}ms)`);\n } else {\n output.error(`[test] ✗ default: failed`);\n for (const err of errors) {\n output.info(` ${err.file}: content mismatch`);\n if (err.diff) {\n const diffLines = err.diff.split('\\n').slice(4, 14);\n for (const line of diffLines) {\n if (line) {\n output.info(` ${line}`);\n }\n }\n if (err.diff.split('\\n').length > 14) {\n output.info(` ... (diff truncated)`);\n }\n }\n }\n }\n}\n","/**\n * File watcher utility\n * Watches template directories for changes and emits events\n * @file packages/infra-cli/src/utils/file-watcher.ts\n */\n\nimport chokidar, { type FSWatcher } from 'chokidar';\nimport { basename } from 'node:path';\n\n/**\n * Type of file change that occurred\n */\nexport type FileType = 'schema' | 'template' | 'inputs' | 'other';\n\n/**\n * Event emitted when a file changes\n */\nexport interface WatchEvent {\n type: 'change' | 'add' | 'unlink';\n path: string;\n fileType: FileType;\n}\n\n/**\n * Options for creating a file watcher\n */\nexport interface WatchOptions {\n /**\n * Callback when a file changes\n */\n onChange?: (event: WatchEvent) => void;\n\n /**\n * Callback when watcher is ready\n */\n onReady?: () => void;\n\n /**\n * Callback when an error occurs\n */\n onError?: (error: Error) => void;\n\n /**\n * Patterns to ignore\n */\n ignored?: string | RegExp | ((path: string) => boolean);\n\n /**\n * Debounce delay in milliseconds\n */\n debounceMs?: number;\n}\n\n/**\n * Classify a file path by its type.\n * Files inside the src/ subdirectory are classified as template-relevant.\n *\n * @param path - File path to classify\n * @returns File type classification\n */\nexport function classifyFileChange(path: string): FileType {\n const normalizedPath = path.replace(/\\\\/g, '/');\n const filename = basename(normalizedPath);\n\n if (filename === 'template.json') {\n return 'schema';\n }\n\n const inSrcDir = /\\/src\\//.test(normalizedPath);\n if (inSrcDir && !filename.startsWith('_')) {\n return 'template';\n }\n\n if (filename === 'inputs.yaml') {\n return 'inputs';\n }\n\n return 'other';\n}\n\n/**\n * Create a file watcher for a template directory.\n *\n * @param templateDir - Path to template version directory\n * @param options - Watcher options\n * @returns FSWatcher instance\n */\nexport function createWatcher(templateDir: string, options: WatchOptions = {}): FSWatcher {\n const {\n onChange,\n onReady,\n onError,\n ignored = /(^|[/\\\\])\\..|(^|[/\\\\])node_modules($|[/\\\\])|(^|[/\\\\])dev($|[/\\\\])/,\n debounceMs = 100,\n } = options;\n\n const watcher = chokidar.watch(templateDir, {\n ignored,\n persistent: true,\n ignoreInitial: true,\n awaitWriteFinish: {\n stabilityThreshold: debounceMs,\n pollInterval: 50,\n },\n });\n\n // Handle file events\n const handleEvent = (type: WatchEvent['type']) => (path: string) => {\n const fileType = classifyFileChange(path);\n const event: WatchEvent = { type, path, fileType };\n\n if (onChange) {\n onChange(event);\n }\n };\n\n watcher.on('change', handleEvent('change'));\n watcher.on('add', handleEvent('add'));\n watcher.on('unlink', handleEvent('unlink'));\n\n if (onReady) {\n watcher.on('ready', onReady);\n }\n\n if (onError) {\n watcher.on('error', (err: unknown) =>\n onError(err instanceof Error ? err : new Error(String(err)))\n );\n }\n\n return watcher;\n}\n","/**\n * tfy-init tpl verify command\n *\n * Checks integrity of files on disk against a manifest.json.\n * Wraps engine.verify() to produce human-readable or JSON drift reports.\n *\n * Reference: spec.md FR-016 through FR-020,\n * contracts/cli-commands.md \"tfy-init tpl verify\"\n */\n\nimport { Command } from 'commander';\nimport { join, resolve } from 'node:path';\nimport { createEngine } from '@truefoundry/tfy-infra-engine';\nimport type { DriftReport } from '@truefoundry/tfy-infra-engine';\nimport { loadManifest } from '../../utils/manifest-loader.js';\nimport { readDirectoryToFileMap } from '../../utils/file-reader.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { driftTypeTag } from '../../utils/output.js';\n\n// Re-export for backward compatibility\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\n/** Verify-specific exit code for drift detected (distinct from error). */\nconst DRIFT_DETECTED = 1;\n\ninterface VerifyOptions {\n directory: string;\n json?: boolean;\n}\n\n/**\n * Create the verify command.\n */\nexport function createVerifyCommand(): Command {\n const cmd = new Command('verify')\n .description('Check integrity of files on disk against a manifest')\n .option('-d, --directory <dir>', 'Path to cluster directory containing manifest.json', '.')\n .option('--json', 'Output drift report as structured JSON')\n .action(async (options: VerifyOptions) => {\n try {\n const exitCode = await runVerify(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\n/**\n * Execute the verify command.\n * Exported for testing.\n */\nexport async function runVerify(options: VerifyOptions): Promise<number> {\n const dir = resolve(options.directory);\n const manifestPath = join(dir, 'manifest.json');\n\n // Load manifest\n output.verbose(`Loading manifest from ${manifestPath}`);\n const manifest = await loadManifest(manifestPath);\n\n // Read current files on disk\n output.verbose(`Reading files from ${dir}`);\n const currentFiles = await readDirectoryToFileMap(dir, manifest.platformPrefix);\n\n // Create engine and verify\n const engine = createEngine();\n const result = await engine.verify(currentFiles, manifest);\n\n // Output the report\n if (options.json) {\n output.info(formatDriftReportJson(result.driftReport));\n } else {\n output.info(formatDriftReport(result.driftReport));\n }\n\n return result.driftReport.valid ? EXIT_CODES.SUCCESS : DRIFT_DETECTED;\n}\n\n/**\n * Format a drift report as human-readable text.\n * Exported for testing.\n */\nexport function formatDriftReport(report: DriftReport): string {\n const lines: string[] = [];\n const issueCount =\n report.summary.driftedFiles + report.summary.missingFiles + report.summary.unexpectedFiles;\n\n if (report.valid) {\n lines.push(\n `✓ Verification passed: ${report.summary.totalFiles} files checked, ${issueCount} issues`\n );\n lines.push('');\n lines.push(`Aggregate Hash: ${report.aggregateHash.expected}`);\n } else {\n lines.push(`✗ Drift detected: ${issueCount} issues found`);\n lines.push('');\n\n for (const entry of report.entries) {\n const tag = driftTypeTag(entry.type);\n lines.push(` ${tag} ${entry.path}`);\n lines.push(` ${entry.details}`);\n lines.push('');\n }\n\n lines.push(\n `Summary: ${report.summary.totalFiles} files checked, ` +\n `${report.summary.driftedFiles} drifted, ` +\n `${report.summary.missingFiles} missing` +\n (report.summary.unexpectedFiles > 0 ? `, ${report.summary.unexpectedFiles} unexpected` : '')\n );\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format a drift report as structured JSON.\n * Exported for testing.\n */\nexport function formatDriftReportJson(report: DriftReport): string {\n return JSON.stringify(report, null, 2);\n}\n","/**\n * Disk-to-FileMap reader utility.\n *\n * Reads a cluster directory into an in-memory FileMap for verify and\n * upgrade operations. Classifies files by zone and parses @tfy-status\n * headers for platform files.\n *\n * Reference: data-model.md §9, research.md R-012\n */\n\nimport { readFile, readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport {\n classifyFile,\n parseHeader,\n DEFAULT_PREFIX,\n type FileMap,\n type FileEntry,\n} from '@truefoundry/tfy-infra-engine';\n\n/**\n * Read all files from a cluster directory into a FileMap.\n *\n * Scans the directory, classifies each file by zone, parses @tfy-status\n * headers for platform files, and returns a typed FileMap.\n *\n * Does NOT include manifest.json in the output (callers load it separately).\n *\n * @param dir - Path to cluster directory\n * @param prefix - Platform Zone filename prefix (default: \"tfy_\")\n * @returns FileMap with zone-tagged entries and parsed headers\n */\nexport async function readDirectoryToFileMap(\n dir: string,\n prefix: string = DEFAULT_PREFIX\n): Promise<FileMap> {\n const fileMap: FileMap = new Map();\n\n let entries: string[];\n try {\n const dirEntries = await readdir(dir, { withFileTypes: true });\n entries = dirEntries.filter((e) => e.isFile()).map((e) => e.name);\n } catch (error) {\n const nodeError = error as NodeJS.ErrnoException;\n if (nodeError.code === 'ENOENT') {\n throw new Error(`Directory not found: ${dir}`, { cause: error });\n }\n throw new Error(`Failed to read directory: ${(error as Error).message}`, { cause: error });\n }\n\n for (const filename of entries) {\n // Skip manifest.json — callers load it separately\n if (filename === 'manifest.json') continue;\n\n const content = await readFile(join(dir, filename), 'utf-8');\n const zone = classifyFile(filename, prefix);\n\n const entry: FileEntry = { content, zone };\n\n if (zone === 'platform') {\n const header = parseHeader(content);\n if (header) {\n entry.header = header;\n }\n }\n\n fileMap.set(filename, entry);\n }\n\n return fileMap;\n}\n","/**\n * tfy-init tpl upgrade command\n *\n * CHANGE (006): Fetch-first — resolves template via ResolverRegistry\n * before calling engine.upgrade(). Envelope uses Template object.\n *\n * Reference: spec.md FR-021 through FR-028b\n */\n\nimport { Command } from 'commander';\nimport { join, resolve } from 'node:path';\nimport { createEngine } from '@truefoundry/tfy-infra-engine';\nimport type { Envelope, UpgradeResult } from '@truefoundry/tfy-infra-engine';\nimport { createResolverRegistry } from '../../resolvers/index.js';\nimport { loadConfig } from '../../utils/config-loader.js';\nimport { loadManifest, envelopeFromManifest } from '../../utils/manifest-loader.js';\nimport { readDirectoryToFileMap } from '../../utils/file-reader.js';\nimport { writeEngineOutput } from '../../utils/file-writer.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { driftTypeTag } from '../../utils/output.js';\n\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\nconst DRIFT_BLOCKED = 3;\n\nexport interface UpgradeArgs {\n config?: string;\n fromManifest?: string;\n template?: string;\n}\n\ninterface UpgradeOptions {\n config?: string;\n directory: string;\n output?: string;\n force?: boolean;\n dryRun?: boolean;\n json?: boolean;\n fromManifest?: string;\n template?: string;\n skipFormat?: boolean;\n cache?: boolean;\n timeout: string;\n}\n\nexport function validateUpgradeArgs(args: UpgradeArgs): void {\n if (args.config && args.fromManifest) {\n throw new Error('--config and --from-manifest are mutually exclusive. Use one or the other.');\n }\n if (!args.config && !args.fromManifest) {\n throw new Error('Either --config <path> or --from-manifest <manifest-path> is required.');\n }\n if (args.fromManifest && !args.template) {\n throw new Error(\n '--from-manifest requires --template <new-uri> to specify the new template version.'\n );\n }\n}\n\nexport function createUpgradeCommand(): Command {\n const cmd = new Command('upgrade')\n .description('Upgrade platform files to a new template version')\n .option('-c, --config <path>', 'Path to envelope config file (YAML or JSON)')\n .option('-d, --directory <dir>', 'Directory containing current cluster files', '.')\n .option('-o, --output <dir>', 'Output directory for upgraded files (defaults to --directory)')\n .option('--force', 'Proceed with upgrade even if drift is detected')\n .option('--dry-run', 'Show what would change without writing files')\n .option('--json', 'Output results as structured JSON')\n .option('--from-manifest <path>', 'Path to manifest.json as input source')\n .option('--template <uri>', 'New template URI (required with --from-manifest)')\n .option('--skip-format', 'Skip tofu fmt formatting')\n .option('--no-cache', 'Force re-fetch template')\n .option('--timeout <ms>', 'Network timeout in milliseconds', '30000')\n .action(async (options: UpgradeOptions) => {\n try {\n if (options.config) options.config = resolve(options.config);\n if (options.fromManifest) options.fromManifest = resolve(options.fromManifest);\n\n validateUpgradeArgs({\n config: options.config,\n fromManifest: options.fromManifest,\n template: options.template,\n });\n const exitCode = await runUpgrade(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\nasync function runUpgrade(options: UpgradeOptions): Promise<number> {\n const clusterDir = resolve(options.directory);\n const outputDir = options.output ? resolve(options.output) : clusterDir;\n const timeout = parseInt(options.timeout, 10);\n const manifestPath = join(clusterDir, 'manifest.json');\n\n output.verbose(`Loading manifest from ${manifestPath}`);\n const previousManifest = await loadManifest(manifestPath);\n\n output.verbose(`Reading files from ${clusterDir}`);\n const currentFiles = await readDirectoryToFileMap(clusterDir, previousManifest.platformPrefix);\n\n // Determine the template URI\n let rawConfig;\n if (options.fromManifest) {\n const fromManifest = await loadManifest(options.fromManifest);\n rawConfig = envelopeFromManifest(fromManifest);\n rawConfig.template = options.template!;\n } else {\n rawConfig = await loadConfig(options.config!);\n }\n\n output.success(`Template: ${rawConfig.template}`);\n output.verbose(`Intent ID: ${rawConfig.intentId}`);\n\n // Fetch-first: resolve template URI before calling engine\n output.verbose('Resolving template...');\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(rawConfig.template, {\n noCache: options.cache === false,\n timeout,\n });\n\n // Build proper Envelope with resolved Template object\n const envelope: Envelope = {\n template,\n inputs: rawConfig.inputs,\n intentId: rawConfig.intentId,\n platformPrefix: rawConfig.platformPrefix,\n options: {\n skipFormat: options.skipFormat ?? rawConfig.options?.skipFormat,\n },\n };\n\n const engine = createEngine();\n const result = await engine.upgrade(envelope, currentFiles, previousManifest);\n\n if (result.sourceBlocked) {\n output.error('Upgrade blocked: template source mismatch');\n output.info('The current files were generated from a different template source.');\n return DRIFT_BLOCKED;\n }\n\n if (!result.driftReport.valid && !options.force) {\n return handleDriftBlocked(result, options.json);\n }\n\n if (options.dryRun) {\n return handleDryRun(result, options.json);\n }\n\n await writeEngineOutput(outputDir, result, 'upgrade');\n\n if (options.json) {\n output.info(formatUpgradeResultJson(result));\n } else {\n output.info(formatUpgradeResult(result));\n }\n\n return EXIT_CODES.SUCCESS;\n}\n\nfunction handleDriftBlocked(result: UpgradeResult, json?: boolean): number {\n const issueCount =\n result.driftReport.summary.driftedFiles +\n result.driftReport.summary.missingFiles +\n result.driftReport.summary.unexpectedFiles;\n\n if (json) {\n output.info(\n JSON.stringify(\n { blocked: true, reason: 'drift_detected', driftReport: result.driftReport },\n null,\n 2\n )\n );\n } else {\n output.error(`Upgrade blocked: drift detected in ${issueCount} files`);\n output.info('');\n for (const entry of result.driftReport.entries) {\n const tag = driftTypeTag(entry.type);\n output.info(` ${tag} ${entry.path}`);\n output.info(` ${entry.details}`);\n }\n output.info('');\n output.info('Use --force to override and proceed with upgrade.');\n }\n\n return DRIFT_BLOCKED;\n}\n\nfunction handleDryRun(result: UpgradeResult, json?: boolean): number {\n if (json) {\n output.info(formatUpgradeResultJson(result));\n } else {\n output.info('Dry run — no files written.');\n output.info('');\n output.info(formatUpgradeResult(result));\n }\n return EXIT_CODES.SUCCESS;\n}\n\nfunction formatUpgradeResult(result: UpgradeResult): string {\n const lines: string[] = [];\n lines.push(\n `✓ Upgrade complete: ${result.manifest.templateSource} v${result.manifest.templateVersion}`\n );\n lines.push('');\n let platformCount = 0;\n let userCount = 0;\n for (const [, entry] of result.files) {\n if (entry.zone === 'platform') platformCount++;\n else userCount++;\n }\n lines.push(` Updated: ${platformCount} files (Platform Zone)`);\n lines.push(` Unchanged: ${userCount} files (User Zone)`);\n lines.push('');\n lines.push(`Aggregate Hash: ${result.manifest.aggregateHash}`);\n return lines.join('\\n');\n}\n\nfunction formatUpgradeResultJson(result: UpgradeResult): string {\n return JSON.stringify(\n {\n success: true,\n templateSource: result.manifest.templateSource,\n templateVersion: result.manifest.templateVersion,\n aggregateHash: result.manifest.aggregateHash,\n driftReport: result.driftReport,\n files: Array.from(result.files.entries()).map(([path, entry]) => ({\n path,\n zone: entry.zone,\n })),\n },\n null,\n 2\n );\n}\n","/**\n * tfy-init tpl hash command\n *\n * CHANGE (006): Fetch-first — resolves template via ResolverRegistry\n * before calling engine.hashOnly(). Envelope uses Template object.\n *\n * Reference: spec.md FR-029 through FR-032\n */\n\nimport { Command } from 'commander';\nimport { resolve } from 'node:path';\nimport { createEngine } from '@truefoundry/tfy-infra-engine';\nimport type { Envelope, HashOnlyResult } from '@truefoundry/tfy-infra-engine';\nimport { createResolverRegistry } from '../../resolvers/index.js';\nimport { loadConfig } from '../../utils/config-loader.js';\nimport { loadManifest, envelopeFromManifest } from '../../utils/manifest-loader.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\n\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\ninterface HashOptions {\n config?: string;\n json?: boolean;\n fromManifest?: string;\n skipFormat?: boolean;\n cache?: boolean;\n timeout: string;\n}\n\nexport function createHashCommand(): Command {\n const cmd = new Command('hash')\n .description('Compute the expected aggregate hash without generating files')\n .option('-c, --config <path>', 'Path to envelope config file (YAML or JSON)')\n .option('--from-manifest <path>', 'Path to manifest.json to use as input source')\n .option('--json', 'Output result as structured JSON')\n .option('--skip-format', 'Skip tofu fmt formatting')\n .option('--no-cache', 'Force re-fetch template')\n .option('--timeout <ms>', 'Network timeout in milliseconds', '30000')\n .action(async (options: HashOptions) => {\n try {\n if (options.config) options.config = resolve(options.config);\n if (options.fromManifest) options.fromManifest = resolve(options.fromManifest);\n\n if (options.config && options.fromManifest) {\n throw new Error(\n '--config and --from-manifest are mutually exclusive. Use one or the other.'\n );\n }\n if (!options.config && !options.fromManifest) {\n throw new Error('Either --config <path> or --from-manifest <manifest-path> is required.');\n }\n\n const exitCode = await runHash(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\nasync function runHash(options: HashOptions): Promise<number> {\n const timeout = parseInt(options.timeout, 10);\n\n let rawConfig;\n if (options.fromManifest) {\n output.verbose(`Loading manifest from ${options.fromManifest}`);\n const manifest = await loadManifest(options.fromManifest);\n rawConfig = envelopeFromManifest(manifest);\n } else {\n output.verbose(`Loading config from ${options.config}`);\n rawConfig = await loadConfig(options.config!);\n }\n\n output.verbose(`Template: ${rawConfig.template}`);\n\n // Fetch-first: resolve template URI before calling engine\n output.verbose('Resolving template...');\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(rawConfig.template, {\n noCache: options.cache === false,\n timeout,\n });\n\n // Build proper Envelope with resolved Template object\n const envelope: Envelope = {\n template,\n inputs: rawConfig.inputs,\n intentId: rawConfig.intentId,\n platformPrefix: rawConfig.platformPrefix,\n options: {\n skipFormat: options.skipFormat ?? rawConfig.options?.skipFormat,\n },\n };\n\n const engine = createEngine();\n const result = await engine.hashOnly(envelope);\n\n if (options.json) {\n output.info(formatHashResultJson(result));\n } else {\n output.info(formatHashResult(result));\n }\n\n return EXIT_CODES.SUCCESS;\n}\n\nexport function formatHashResult(result: HashOnlyResult): string {\n const lines: string[] = [];\n lines.push(`Aggregate Hash: ${result.aggregateHash}`);\n lines.push(`Template: ${result.templateSource}`);\n lines.push(`Version: ${result.templateVersion}`);\n lines.push(`Files: ${result.fileCount}`);\n return lines.join('\\n');\n}\n\nexport function formatHashResultJson(result: HashOnlyResult): string {\n return JSON.stringify(result, null, 2);\n}\n"],"mappings":"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","names":["import_commander","import_commander","import_commander","import_promises","import_node_path","yaml","import_tfy_infra_engine","DEFAULT_RESOLVER_OPTIONS","mergeResolverOptions","options","withRetry","fn","retries","baseDelay","lastError","attempt","error","delay","resolve","withTimeout","promise","ms","message","_","reject","extractVersionFromUri","uri","versionPattern","match","import_tfy_infra_engine","import_promises","import_node_path","import_tfy_infra_engine","import_promises","import_node_path","walkSrcDir","rootDir","files","staticFiles","walk","relativePath","currentDir","entries","entry","entryRelative","content","outputPath","FileResolver","uri","_options","path","templateJsonPath","templateJsonContent","parsed","error","metadata","jsonSchema","srcDir","files","staticFiles","walkSrcDir","version","nodeError","filename","segments","last","import_promises","import_node_path","import_zlib","import_tar","import_tfy_infra_engine","buildTag","path","semver","parts","slug","extractApiVersion","lastSegment","derivePinType","segments","parseGitHubUri","uri","owner","repo","versionMatch","templatePath","pinType","tag","toArchiveUrl","ghUri","resolveVersion","_options","tagPrefix","apiUrl","headers","ghToken","response","refs","semverPattern","versions","ref","tagName","match","vParts","a","b","i","diff","GitHubResolver","tempDir","home","options","opts","mergeResolverOptions","resolvedTag","withTimeout","archiveUri","archiveUrl","extractDir","withRetry","extractedDirs","archiveRoot","templateDir","templateJsonPath","templateJsonContent","parsed","error","metadata","jsonSchema","srcDir","files","staticFiles","walkSrcDir","version","fetchError","url","bodyStream","import_tfy_infra_engine","JFROG_TOKEN_ENV","isArtifactoryHost","uri","hostname","buildHeaders","headers","token","fetchBundle","response","hint","unpackBundle","raw","metadata","jsonSchema","files","staticFiles","version","HttpsBundleResolver","options","opts","mergeResolverOptions","withTimeout","withRetry","import_promises","import_node_path","import_node_crypto","import_tfy_infra_engine","getDefaultCacheDir","home","TemplateCache","cacheDir","source","version","key","safeVersion","cachePath","templateJsonPath","templateJsonContent","parsed","metadata","jsonSchema","files","staticFiles","versionInfo","template","templateJson","name","content","filePath","error","keyPath","sources","templates","size","sourceKeys","versions","versionPath","rootDir","relativePath","currentDir","entries","entry","entryRelative","dir","total","fullPath","fileStat","ResolverRegistry","resolvers","cacheDir","TemplateCache","FileResolver","GitHubResolver","HttpsBundleResolver","resolver","uri","options","scheme","versionInfo","extractVersionFromUri","cached","template","createResolverRegistry","import_promises","yaml","import_promises","import_node_path","fileExists","filePath","findDefaultFixture","versionDir","defaultPath","loadConfig","filePath","content","isJson","parsed","error","format","import_promises","loadManifest","manifestPath","content","error","parsed","requiredFields","field","envelopeFromManifest","manifest","import_promises","import_node_path","writeEngineOutput","dir","result","mode","filePath","entry","fullPath","fileExists","import_tfy_infra_engine","currentVerbosity","setVerbosity","level","getVerbosity","success","message","error","info","verbose","warn","formatSize","bytes","formatValidationErrors","errors","e","driftTypeTag","type","printFileList","files","file","EXIT_CODES","handleEngineError","error","fallbackExitCode","getVerbosity","info","key","value","formatValidationErrors","validateRenderArgs","args","createRenderCommand","collect","options","exitCode","runRender","error","handleEngineError","value","previous","parseInputOverrides","inputs","result","input","eqIndex","key","outputDir","timeout","runRenderFromManifest","runRenderFromConfig","templateDir","runRenderFromDirectory","manifestPath","verbose","manifest","loadManifest","rawConfig","envelopeFromManifest","success","envelope","createResolverRegistry","writeEngineOutput","fileList","filePath","entry","info","printFileList","EXIT_CODES","templateUri","fixturePath","findDefaultFixture","fixtureContent","overrides","intentId","engine","configFile","loadConfig","import_commander","import_promises","import_node_path","yaml","createSchemaCommand","options","exitCode","runSchema","error","handleEngineError","writeJsonSchemaToFile","schema","outputPath","runSchema","options","templateUri","timeout","verbose","template","createResolverRegistry","data","success","EXIT_CODES","import_commander","import_promises","import_node_path","import_handlebars","import_tfy_infra_engine","createValidateCommand","options","exitCode","runValidate","error","handleEngineError","templateDir","issues","hasErrors","info","verbose","templateJsonPath","templateJsonContent","parsed","jsonSchema","success","srcDir","hbsMap","staticMap","walkSrcDir","relPath","content","Handlebars","warnings","i","errors","warn","issue","prefix","EXIT_CODES","import_commander","import_promises","import_node_path","yaml","import_tfy_infra_engine","import_diff","generateUnifiedDiff","expected","actual","filename","normalizedExpected","normalizedActual","createTestCommand","options","exitCode","runTest","error","handleEngineError","templateDir","testsDir","templateUri","info","fixtures","entries","entry","inputsPath","EXIT_CODES","template","createResolverRegistry","engine","results","fixture","fixtureDir","expectedDir","verbose","inputsContent","inputs","envelope","result","updateExpected","success","errors","diffs","compareOutputs","err","d","diffLines","line","passed","r","failed","normalizeStatusSource","content","filePath","outputPath","includeDiffs","actual","expectedPath","expected","normalizedActual","normalizedExpected","diff","generateUnifiedDiff","expectedFiles","file","import_commander","import_promises","import_node_path","yaml","import_tfy_infra_engine","import_chokidar","import_node_path","classifyFileChange","path","normalizedPath","filename","createWatcher","templateDir","options","onChange","onReady","onError","ignored","debounceMs","watcher","chokidar","handleEvent","type","fileType","err","createDevCommand","options","exitCode","runDev","error","handleEngineError","validateTemplateDir","versionDir","templateJsonPath","ensureOutputDir","_versionDir","customOutput","outputDir","validation","EXIT_CODES","fixturePath","defaultFixture","findDefaultFixture","info","relativeOutputDir","engine","templateUri","runRenderCycle","watcher","createWatcher","event","relPath","runValidation","resolvePromise","cleanup","createResolverRegistry","runTests","inputsContent","inputs","envelope","result","templateFileCount","f","filesWritten","filePath","entry","outputPath","runTestComparison","formatValidationErrors","_outputDir","expectedDir","startTime","errors","actual","expectedPath","expected","normalizedActual","normalizedExpected","diff","generateUnifiedDiff","duration","err","diffLines","line","import_commander","import_node_path","import_tfy_infra_engine","import_promises","import_node_path","import_tfy_infra_engine","readDirectoryToFileMap","dir","prefix","fileMap","entries","e","error","filename","content","zone","entry","header","DRIFT_DETECTED","createVerifyCommand","options","exitCode","runVerify","error","handleEngineError","dir","manifestPath","verbose","manifest","loadManifest","currentFiles","readDirectoryToFileMap","result","info","formatDriftReportJson","formatDriftReport","EXIT_CODES","report","lines","issueCount","entry","tag","driftTypeTag","import_commander","import_node_path","import_tfy_infra_engine","DRIFT_BLOCKED","validateUpgradeArgs","args","createUpgradeCommand","options","exitCode","runUpgrade","error","handleEngineError","clusterDir","outputDir","timeout","manifestPath","verbose","previousManifest","loadManifest","currentFiles","readDirectoryToFileMap","rawConfig","fromManifest","envelopeFromManifest","loadConfig","success","envelope","createResolverRegistry","result","info","handleDriftBlocked","handleDryRun","writeEngineOutput","formatUpgradeResultJson","formatUpgradeResult","EXIT_CODES","json","issueCount","entry","tag","driftTypeTag","lines","platformCount","userCount","path","import_commander","import_node_path","import_tfy_infra_engine","createHashCommand","options","exitCode","runHash","error","handleEngineError","timeout","rawConfig","verbose","manifest","loadManifest","envelopeFromManifest","loadConfig","envelope","createResolverRegistry","result","info","formatHashResultJson","formatHashResult","EXIT_CODES","lines","createTplCommand","tpl","createDevCommand","createRenderCommand","createSchemaCommand","createValidateCommand","createTestCommand","createVerifyCommand","createUpgradeCommand","createHashCommand","VERSION","main","program","thisCommand","opts","setVerbosity","createTplCommand","error"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands/tpl/index.ts","../src/commands/tpl/bundle.ts","../src/utils/walk-src.ts","../src/utils/error-handler.ts","../src/utils/output.ts","../src/commands/tpl/render.ts","../src/resolvers/index.ts","../src/resolvers/file.ts","../src/resolvers/https.ts","../src/resolvers/base.ts","../src/cache/template-cache.ts","../src/utils/config-loader.ts","../src/utils/fs-helpers.ts","../src/utils/manifest-loader.ts","../src/utils/file-reader.ts","../src/utils/file-writer.ts","../src/utils/diff.ts","../src/commands/tpl/validate.ts","../src/commands/tpl/test.ts","../src/commands/tpl/dev.ts","../src/utils/file-watcher.ts","../src/commands/tpl/verify.ts"],"sourcesContent":["/**\n * @truefoundry/tfy-infra-cli\n *\n * CLI for TrueFoundry infrastructure templating engine.\n * Binary: tfy-init\n */\n\nimport { Command } from 'commander';\nimport { createTplCommand } from './commands/tpl/index.js';\nimport { setVerbosity } from './utils/output.js';\n\n// When invoked via `npm run`, npm sets CWD to the package root.\n// INIT_CWD preserves the directory the user actually ran the command from.\nif (process.env['INIT_CWD']) {\n process.chdir(process.env['INIT_CWD']);\n}\n\ndeclare const __CLI_VERSION__: string;\nconst VERSION = __CLI_VERSION__;\n\n/**\n * Main CLI entry point.\n */\nasync function main(): Promise<void> {\n const program = new Command();\n\n program\n .name('tfy-init')\n .description('TrueFoundry infrastructure templating CLI')\n .enablePositionalOptions()\n .version(VERSION, '-v, --version', 'Show version')\n .option('--verbose', 'Enable verbose output')\n .option('-q, --quiet', 'Suppress non-error output')\n .hook('preAction', (thisCommand) => {\n const opts = thisCommand.opts();\n if (opts['quiet']) {\n setVerbosity('quiet');\n } else if (opts['verbose']) {\n setVerbosity('verbose');\n }\n });\n\n // Add command groups\n program.addCommand(createTplCommand());\n\n // Parse and execute\n await program.parseAsync(process.argv);\n}\n\n// Run CLI\nmain().catch((error) => {\n console.error('Fatal error:', error);\n process.exit(1);\n});\n","/**\n * tfy-init tpl command group\n *\n * CHANGE (006): Removed push command (S3 support removed).\n * CHANGE (008): Removed init command (templates scaffolded via CUE).\n */\n\nimport { Command } from 'commander';\nimport { createBundleCommand } from './bundle.js';\nimport { createRenderCommand } from './render.js';\nimport { createValidateCommand } from './validate.js';\nimport { createTestCommand } from './test.js';\n\nimport { createDevCommand } from './dev.js';\nimport { createVerifyCommand } from './verify.js';\n/**\n * Create the tpl command group with all subcommands.\n */\nexport function createTplCommand(): Command {\n const tpl = new Command('tpl').description('Template operations').enablePositionalOptions();\n\n tpl.addCommand(createBundleCommand());\n tpl.addCommand(createDevCommand());\n tpl.addCommand(createRenderCommand());\n tpl.addCommand(createValidateCommand());\n tpl.addCommand(createTestCommand());\n\n tpl.addCommand(createVerifyCommand());\n\n return tpl;\n}\n","/**\n * tfy-init tpl bundle command\n *\n * Accepts a pre-computed schema package (from tfy-schema-tool --schema-package)\n * plus a src/ directory and metadata flags, and produces a bundle.json.\n * No CUE or Go dependencies — pure TypeScript.\n */\n\nimport { Command } from 'commander';\nimport { readFile, writeFile, mkdir } from 'node:fs/promises';\nimport { resolve, dirname } from 'node:path';\nimport { walkSrcDir } from '../../utils/walk-src.js';\nimport { handleEngineError } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\n\ninterface SchemaPackage {\n jsonSchema: Record<string, unknown>;\n uiSchema: unknown[];\n}\n\ninterface BundleJson {\n metadata: {\n name: string;\n version: string;\n description?: string;\n };\n jsonSchema: Record<string, unknown>;\n uiSchema: unknown[];\n files: Record<string, string>;\n staticFiles: Record<string, string>;\n version: {\n semver: string;\n };\n}\n\ninterface BundleOptions {\n schema: string;\n src: string;\n name: string;\n version?: string;\n description?: string;\n output?: string;\n}\n\nexport function createBundleCommand(): Command {\n const cmd = new Command('bundle')\n .description('Build a bundle.json from a schema package and src/ directory')\n .requiredOption('--schema <path-or-dash>', 'Schema package JSON file, or \"-\" for stdin')\n .requiredOption('--src <dir>', 'Template source directory containing .hbs and static files')\n .requiredOption('--name <string>', 'Template name')\n .option('--description <string>', 'Template description')\n .option('-o, --output <path>', 'Output file path (default: stdout)')\n .action(async (options: BundleOptions) => {\n try {\n await runBundle(options);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\nasync function readSchemaPackage(schemaPath: string): Promise<SchemaPackage> {\n let raw: string;\n\n if (schemaPath === '-') {\n raw = await readStdin();\n } else {\n raw = await readFile(resolve(schemaPath), 'utf-8');\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(`Failed to parse schema package JSON: ${(err as Error).message}`, {\n cause: err,\n });\n }\n\n const obj = parsed as Record<string, unknown>;\n if (!obj['jsonSchema'] || typeof obj['jsonSchema'] !== 'object') {\n throw new Error('Schema package must contain a \"jsonSchema\" object');\n }\n if (!Array.isArray(obj['uiSchema'])) {\n throw new Error('Schema package must contain a \"uiSchema\" array');\n }\n\n return {\n jsonSchema: obj['jsonSchema'] as Record<string, unknown>,\n uiSchema: obj['uiSchema'] as unknown[],\n };\n}\n\nfunction readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n process.stdin.on('data', (chunk: Buffer) => chunks.push(chunk));\n process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));\n process.stdin.on('error', reject);\n });\n}\n\nfunction extractVersionFromSchema(jsonSchema: Record<string, unknown>): string | undefined {\n const props = jsonSchema['properties'] as Record<string, unknown> | undefined;\n if (!props) return undefined;\n const versionProp = props['version'] as Record<string, unknown> | undefined;\n if (!versionProp) return undefined;\n const def = versionProp['default'];\n return typeof def === 'string' ? def : undefined;\n}\n\nasync function runBundle(options: BundleOptions): Promise<void> {\n const schemaPkg = await readSchemaPackage(options.schema);\n\n const version = options.version ?? extractVersionFromSchema(schemaPkg.jsonSchema);\n if (!version) {\n throw new Error(\n 'Could not determine template version. Provide --version or ensure the schema has a \"version\" property with a default value.'\n );\n }\n\n const srcDir = resolve(options.src);\n const { files, staticFiles } = await walkSrcDir(srcDir);\n\n if (files.size === 0 && staticFiles.size === 0) {\n throw new Error(`No template files found in ${srcDir}`);\n }\n\n const metadata: BundleJson['metadata'] = {\n name: options.name,\n version,\n };\n if (options.description) {\n metadata.description = options.description;\n }\n\n const bundle: BundleJson = {\n metadata,\n jsonSchema: schemaPkg.jsonSchema,\n uiSchema: schemaPkg.uiSchema,\n files: Object.fromEntries(files),\n staticFiles: Object.fromEntries(staticFiles),\n version: { semver: version },\n };\n\n const json = JSON.stringify(bundle, null, 2) + '\\n';\n\n if (options.output) {\n const outPath = resolve(options.output);\n await mkdir(dirname(outPath), { recursive: true });\n await writeFile(outPath, json, 'utf-8');\n output.success(`Bundle written to ${outPath}`);\n } else {\n process.stdout.write(json);\n }\n}\n","/**\n * Recursive directory walker for template src/ directories.\n *\n * Walks a directory tree and classifies files into Handlebars templates\n * (.hbs → rendered) and static files (copied verbatim). Skips entries\n * whose names start with '_'. Map keys are relative paths from the\n * root directory, preserving subdirectory structure.\n */\n\nimport { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nexport interface WalkResult {\n files: Map<string, string>;\n staticFiles: Map<string, string>;\n}\n\n/**\n * Recursively walk a directory, reading all files into maps.\n *\n * @param rootDir - The src/ directory to walk\n * @returns Maps of relative-path → content, split by .hbs (rendered) vs static\n */\nexport async function walkSrcDir(rootDir: string): Promise<WalkResult> {\n const files = new Map<string, string>();\n const staticFiles = new Map<string, string>();\n\n await walk(rootDir, '', files, staticFiles);\n\n return { files, staticFiles };\n}\n\nasync function walk(\n rootDir: string,\n relativePath: string,\n files: Map<string, string>,\n staticFiles: Map<string, string>\n): Promise<void> {\n const currentDir = relativePath ? join(rootDir, relativePath) : rootDir;\n const entries = await readdir(currentDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.name.startsWith('_')) continue;\n\n const entryRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;\n\n if (entry.isDirectory()) {\n await walk(rootDir, entryRelative, files, staticFiles);\n } else {\n const content = await readFile(join(currentDir, entry.name), 'utf-8');\n if (entry.name.endsWith('.hbs')) {\n const outputPath = entryRelative.replace(/\\.hbs$/, '');\n files.set(outputPath, content);\n } else {\n staticFiles.set(entryRelative, content);\n }\n }\n }\n}\n","/**\n * Shared error handling for CLI commands.\n *\n * Provides consistent error output and exit code mapping across all\n * tfy-init tpl commands.\n */\n\nimport { EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\nimport * as output from './output.js';\n\n/**\n * Standard exit codes shared across all commands.\n */\nexport const EXIT_CODES = {\n SUCCESS: 0,\n VALIDATION_ERROR: 1,\n FETCH_ERROR: 2,\n RENDER_ERROR: 3,\n FORMAT_ERROR: 4,\n DRIFT_BLOCKED: 5,\n} as const;\n\n/**\n * Handle an error from a CLI command and exit with the appropriate code.\n *\n * Provides consistent behavior:\n * - Displays the error message\n * - Shows validation error details when available\n * - Shows engine error details in verbose mode\n * - Maps EngineErrorCode to standardized exit codes\n * - Shows format hint for tofu-related errors\n *\n * @param error - The caught error\n * @param fallbackExitCode - Exit code for unrecognized errors (default: RENDER_ERROR)\n */\nexport function handleEngineError(\n error: unknown,\n fallbackExitCode: number = EXIT_CODES.RENDER_ERROR\n): never {\n if (error instanceof EngineError) {\n output.error(error.message);\n\n // Show engine error details in verbose mode\n if (output.getVerbosity() === 'verbose' && error.details) {\n output.info('');\n output.info('Details:');\n for (const [key, value] of Object.entries(error.details)) {\n if (key !== 'errors') {\n output.info(` ${key}: ${JSON.stringify(value)}`);\n }\n }\n }\n\n // Show validation error list when available\n if (error.details?.['errors']) {\n output.info('');\n output.info('Errors:');\n output.info(\n output.formatValidationErrors(\n error.details['errors'] as Array<{ path: string; message: string }>\n )\n );\n }\n\n // Map error codes to exit codes\n switch (error.code) {\n case EngineErrorCode.INPUT_VALIDATION_FAILED:\n case EngineErrorCode.SCHEMA_INVALID:\n case EngineErrorCode.ENVELOPE_VALIDATION_FAILED:\n process.exit(EXIT_CODES.VALIDATION_ERROR);\n break;\n\n case EngineErrorCode.TEMPLATE_NOT_FOUND:\n case EngineErrorCode.FILE_NOT_FOUND:\n case EngineErrorCode.NETWORK_ERROR:\n process.exit(EXIT_CODES.FETCH_ERROR);\n break;\n\n case EngineErrorCode.TOFU_NOT_FOUND:\n case EngineErrorCode.TOFU_FMT_FAILED:\n output.info('');\n output.info('Run with --skip-format to skip formatting.');\n process.exit(EXIT_CODES.FORMAT_ERROR);\n break;\n\n case EngineErrorCode.MANIFEST_PARSE_ERROR:\n process.exit(EXIT_CODES.VALIDATION_ERROR);\n break;\n\n default:\n process.exit(fallbackExitCode);\n }\n }\n\n // Generic (non-engine) error\n output.error((error as Error).message);\n if (output.getVerbosity() === 'verbose') {\n console.error((error as Error).stack);\n }\n process.exit(fallbackExitCode);\n}\n","/**\n * Output formatting utilities for CLI.\n *\n * Supports a JSON mode where human-readable output is redirected to stderr\n * so that stdout remains clean for structured machine output via `data()`.\n */\n\nimport type { DriftType } from '@truefoundry/tfy-infra-engine';\n\n/* eslint-disable no-console */\n\nexport type Verbosity = 'quiet' | 'normal' | 'verbose';\n\nlet currentVerbosity: Verbosity = 'normal';\nlet jsonMode = false;\n\nexport function setVerbosity(level: Verbosity): void {\n currentVerbosity = level;\n}\n\nexport function getVerbosity(): Verbosity {\n return currentVerbosity;\n}\n\n/**\n * Enable JSON mode: human-readable output routes to stderr,\n * structured output goes to stdout via `data()`.\n */\nexport function setJsonMode(enabled: boolean): void {\n jsonMode = enabled;\n}\n\nexport function getJsonMode(): boolean {\n return jsonMode;\n}\n\n/**\n * Write structured data to stdout. Use for JSON payloads\n * that machine consumers will parse.\n */\nexport function data(message: string): void {\n process.stdout.write(message + '\\n');\n}\n\nexport function success(message: string): void {\n if (currentVerbosity !== 'quiet') {\n const write = jsonMode ? console.error : console.log;\n write(`✓ ${message}`);\n }\n}\n\nexport function error(message: string): void {\n console.error(`✗ ${message}`);\n}\n\nexport function info(message: string): void {\n if (currentVerbosity !== 'quiet') {\n const write = jsonMode ? console.error : console.log;\n write(message);\n }\n}\n\nexport function verbose(message: string): void {\n if (currentVerbosity === 'verbose') {\n const write = jsonMode ? console.error : console.log;\n write(` ${message}`);\n }\n}\n\nexport function warn(message: string): void {\n if (currentVerbosity !== 'quiet') {\n console.warn(`⚠ ${message}`);\n }\n}\n\nexport function formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nexport function formatValidationErrors(errors: Array<{ path: string; message: string }>): string {\n return errors.map((e) => ` • ${e.path}: ${e.message}`).join('\\n');\n}\n\nexport function driftTypeTag(type: DriftType): string {\n switch (type) {\n case 'content_drift':\n return 'DRIFTED';\n case 'missing_file':\n return 'MISSING';\n case 'unexpected_file':\n return 'UNEXPECTED';\n case 'metadata_inconsistency':\n return 'INCONSISTENT';\n case 'source_mismatch':\n return 'SOURCE MISMATCH';\n }\n}\n\nexport function printFileList(files: Array<{ path: string; size: number }>): void {\n if (currentVerbosity === 'quiet') return;\n\n const write = jsonMode ? console.error : console.log;\n write('Files:');\n for (const file of files) {\n write(` - ${file.path} (${formatSize(file.size)})`);\n }\n}\n","/**\n * tfy-init tpl render command\n *\n * Unified render command that handles fresh install, upgrade, and\n * manifest re-generation via auto-detection.\n *\n * Config mode auto-detects upgrade when manifest.json exists in the\n * output directory. Manifest mode always performs a fresh install\n * (disaster recovery). Dev mode renders from a local template directory.\n */\n\nimport { Command } from 'commander';\nimport { createInterface } from 'node:readline';\nimport { readFile, readdir, access } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport * as yaml from 'js-yaml';\nimport { createEngine } from '@truefoundry/tfy-infra-engine';\nimport type {\n Envelope,\n InstallResult,\n UpgradeResult,\n DriftReport,\n FileMap,\n} from '@truefoundry/tfy-infra-engine';\nimport { createResolverRegistry } from '../../resolvers/index.js';\nimport { loadConfig } from '../../utils/config-loader.js';\nimport { loadManifest, envelopeFromManifest } from '../../utils/manifest-loader.js';\nimport { readDirectoryToFileMap } from '../../utils/file-reader.js';\nimport { writeEngineOutput } from '../../utils/file-writer.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { driftTypeTag } from '../../utils/output.js';\nimport { findDefaultFixture } from '../../utils/fs-helpers.js';\nimport { generateUnifiedDiff, type DiffEntry } from '../../utils/diff.js';\n\nexport { EXIT_CODES };\n\nexport interface RenderArgs {\n config?: string;\n directory?: string;\n fromManifest?: string;\n}\n\nexport function validateRenderArgs(args: RenderArgs): void {\n const modes = [args.config, args.fromManifest].filter(Boolean);\n if (modes.length > 1) {\n throw new Error('--config and --from-manifest are mutually exclusive. Use one or the other.');\n }\n if (args.config && args.directory !== '.') {\n throw new Error('--config and --directory are mutually exclusive. Use one or the other.');\n }\n}\n\nexport function createRenderCommand(): Command {\n const cmd = new Command('render')\n .description('Render a template to HCL files')\n .option('-c, --config <path>', 'Path to envelope config file (YAML or JSON)')\n .option('-d, --directory <dir>', 'Path to template version directory', '.')\n .option('-f, --fixture <path>', 'Input file to use (when using --directory)')\n .option('-i, --input <key=value>', 'Inline input override (can be repeated)', collect, [])\n .requiredOption('-o, --output <dir>', 'Output directory')\n .option('--from-manifest <path>', 'Path to manifest.json to use as input source')\n .option('--force', 'Proceed even if managed files have drifted (upgrade mode)')\n .option('--allow-source-change', 'Allow upgrade when template source has changed')\n .option('--dry-run', 'Show what would change without writing files')\n .option('--json', 'Output result as structured JSON')\n .option('--skip-format', 'Skip tofu fmt formatting')\n .option('--no-cache', 'Force re-fetch template')\n .option('--timeout <ms>', 'Network timeout in milliseconds', '30000')\n .option('--intent-id <id>', 'Cluster identity for integrity tracking')\n .action(async (options: RenderOptions) => {\n try {\n if (options.config) options.config = resolve(options.config);\n if (options.fromManifest) options.fromManifest = resolve(options.fromManifest);\n\n validateRenderArgs({\n config: options.config,\n directory: options.directory,\n fromManifest: options.fromManifest,\n });\n const exitCode = await runRender(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\nfunction collect(value: string, previous: string[]): string[] {\n return [...previous, value];\n}\n\ninterface RenderOptions {\n config?: string;\n directory: string;\n fixture?: string;\n input?: string[];\n output: string;\n fromManifest?: string;\n force?: boolean;\n allowSourceChange?: boolean;\n dryRun?: boolean;\n json?: boolean;\n skipFormat?: boolean;\n cache?: boolean;\n timeout: string;\n intentId?: string;\n}\n\nexport { findDefaultFixture } from '../../utils/fs-helpers.js';\n\nexport function parseInputOverrides(inputs?: string[]): Record<string, string> {\n const result: Record<string, string> = {};\n if (!inputs) return result;\n\n for (const input of inputs) {\n const eqIndex = input.indexOf('=');\n if (eqIndex > 0) {\n const key = input.substring(0, eqIndex);\n const value = input.substring(eqIndex + 1);\n result[key] = value;\n }\n }\n\n return result;\n}\n\nasync function runRender(options: RenderOptions): Promise<number> {\n if (options.json) {\n output.setJsonMode(true);\n }\n\n const outputDir = resolve(options.output);\n const timeout = parseInt(options.timeout, 10);\n\n if (options.fromManifest) {\n return runRenderFromManifest(options.fromManifest, options, outputDir, timeout);\n }\n\n if (options.config) {\n const manifestPath = join(outputDir, 'manifest.json');\n if (await fileExists(manifestPath)) {\n return runRenderFromConfigUpgrade(options.config, options, outputDir, timeout, manifestPath);\n }\n return runRenderFromConfig(options.config, options, outputDir, timeout);\n }\n\n const templateDir = resolve(options.directory);\n return runRenderFromDirectory(templateDir, options, outputDir, timeout);\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function confirmPrompt(message: string): Promise<boolean> {\n if (!process.stdin.isTTY) return false;\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n return new Promise((resolve) => {\n rl.question(`${message} [y/N] `, (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === 'y');\n });\n });\n}\n\nasync function countExistingFiles(dir: string): Promise<number> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isFile() && e.name !== 'config.yaml' && e.name !== 'config.json')\n .length;\n } catch {\n return 0;\n }\n}\n\nasync function guardOverwrite(outputDir: string, options: RenderOptions): Promise<number | null> {\n if (options.force) return null;\n const count = await countExistingFiles(outputDir);\n if (count === 0) return null;\n\n output.warn(`Output directory contains ${count} existing files that will be overwritten.`);\n const confirmed = await confirmPrompt('Proceed?');\n if (confirmed) return null;\n\n if (options.json) {\n output.data(\n JSON.stringify(\n { aborted: true, reason: 'overwrite_confirmation', existingFiles: count },\n null,\n 2\n )\n );\n } else {\n output.info('Aborted. Use --force to skip confirmation.');\n }\n return EXIT_CODES.VALIDATION_ERROR;\n}\n\n/**\n * Render from a manifest file (re-render / disaster recovery).\n */\nasync function runRenderFromManifest(\n manifestPath: string,\n options: RenderOptions,\n outputDir: string,\n timeout: number\n): Promise<number> {\n output.verbose(`Loading manifest from ${manifestPath}`);\n const manifest = await loadManifest(manifestPath);\n const rawConfig = envelopeFromManifest(manifest);\n\n if (options.intentId) {\n rawConfig.intentId = options.intentId;\n }\n\n output.success(`Template: ${rawConfig.template}`);\n output.verbose(`Intent ID: ${rawConfig.intentId}`);\n output.verbose('Resolving template...');\n\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(rawConfig.template, {\n noCache: options.cache === false,\n timeout,\n });\n\n const envelope: Envelope = {\n template,\n inputs: rawConfig.inputs,\n intentId: rawConfig.intentId,\n platformPrefix: rawConfig.platformPrefix,\n options: {\n skipFormat: options.skipFormat,\n },\n };\n\n output.verbose('Re-rendering from manifest...');\n\n const engine = createEngine();\n const result = await engine.install(envelope);\n\n if (options.dryRun) {\n return await handleInstallDryRun(result, outputDir, options.json);\n }\n\n const aborted = await guardOverwrite(outputDir, options);\n if (aborted !== null) return aborted;\n\n output.success('Inputs validated');\n output.success(`Generated ${result.files.size - 1} files + manifest.json`);\n\n if (!options.skipFormat) {\n output.success('Formatted with tofu fmt');\n }\n\n await writeEngineOutput(outputDir, result, 'install');\n\n printInstallResult(result, outputDir, options.json);\n\n return EXIT_CODES.SUCCESS;\n}\n\n/**\n * Render from a template directory with fixture inputs.\n */\nasync function runRenderFromDirectory(\n templateDir: string,\n options: RenderOptions,\n outputDir: string,\n timeout: number\n): Promise<number> {\n const templateUri = `file://${templateDir}`;\n\n let fixturePath: string | null = options.fixture ? resolve(templateDir, options.fixture) : null;\n\n if (fixturePath) {\n if (!(await fileExists(fixturePath))) {\n output.error(`Fixture not found: ${options.fixture}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n } else {\n fixturePath = await findDefaultFixture(templateDir);\n if (!fixturePath) {\n output.error('No default fixture found at tests/default/inputs.yaml');\n output.info('Either create a default fixture or use --fixture to specify one.');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n }\n\n const fixtureContent = await readFile(fixturePath, 'utf-8');\n let inputs = yaml.load(fixtureContent) as Record<string, unknown>;\n\n const overrides = parseInputOverrides(options.input);\n inputs = { ...inputs, ...overrides };\n\n const intentId =\n options.intentId ?? (inputs['intentId'] as string | undefined) ?? `local-${Date.now()}`;\n\n output.success(`Template: ${templateUri}`);\n output.verbose(`Fixture: ${fixturePath}`);\n output.verbose(`Intent ID: ${intentId}`);\n\n output.verbose('Resolving template...');\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(templateUri, {\n noCache: options.cache === false,\n timeout,\n });\n\n const envelope: Envelope = {\n template,\n inputs,\n intentId,\n options: {\n skipFormat: options.skipFormat,\n },\n };\n\n const engine = createEngine();\n\n output.verbose('Validating inputs...');\n const result = await engine.install(envelope);\n\n if (options.dryRun) {\n return await handleInstallDryRun(result, outputDir, options.json);\n }\n\n const aborted = await guardOverwrite(outputDir, options);\n if (aborted !== null) return aborted;\n\n output.success('Inputs validated');\n output.success(`Generated ${result.files.size - 1} files + manifest.json`);\n\n if (!options.skipFormat) {\n output.success('Formatted with tofu fmt');\n }\n\n await writeEngineOutput(outputDir, result, 'install');\n\n printInstallResult(result, outputDir, options.json);\n\n return EXIT_CODES.SUCCESS;\n}\n\n/**\n * Render from a config file (fresh install).\n */\nasync function runRenderFromConfig(\n configFile: string,\n options: RenderOptions,\n outputDir: string,\n timeout: number\n): Promise<number> {\n output.verbose(`Loading config from ${configFile}`);\n const rawConfig = await loadConfig(configFile);\n\n if (options.intentId) {\n rawConfig.intentId = options.intentId;\n }\n\n if (options.input && options.input.length > 0) {\n const overrides = parseInputOverrides(options.input);\n rawConfig.inputs = { ...(rawConfig.inputs || {}), ...overrides };\n }\n\n output.success(`Template: ${rawConfig.template}`);\n output.verbose(`Intent ID: ${rawConfig.intentId}`);\n\n output.verbose('Resolving template...');\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(rawConfig.template, {\n noCache: options.cache === false,\n timeout,\n });\n\n const envelope: Envelope = {\n template,\n inputs: rawConfig.inputs,\n intentId: rawConfig.intentId,\n platformPrefix: rawConfig.platformPrefix,\n options: {\n skipFormat: options.skipFormat ?? rawConfig.options?.skipFormat,\n },\n };\n\n const engine = createEngine();\n\n output.verbose('Validating inputs...');\n const result = await engine.install(envelope);\n\n if (options.dryRun) {\n return await handleInstallDryRun(result, outputDir, options.json);\n }\n\n const aborted = await guardOverwrite(outputDir, options);\n if (aborted !== null) return aborted;\n\n output.success('Inputs validated');\n output.success(`Generated ${result.files.size - 1} files + manifest.json`);\n\n if (!envelope.options?.skipFormat) {\n output.success('Formatted with tofu fmt');\n }\n\n await writeEngineOutput(outputDir, result, 'install');\n\n printInstallResult(result, outputDir, options.json);\n\n return EXIT_CODES.SUCCESS;\n}\n\n/**\n * Upgrade from a config file when manifest.json exists in the output directory.\n * Auto-detected when config mode finds an existing manifest.\n */\nasync function runRenderFromConfigUpgrade(\n configFile: string,\n options: RenderOptions,\n outputDir: string,\n timeout: number,\n manifestPath: string\n): Promise<number> {\n output.verbose(`Existing manifest detected at ${manifestPath} — running upgrade`);\n\n const previousManifest = await loadManifest(manifestPath);\n\n output.verbose(`Reading files from ${outputDir}`);\n const currentFiles = await readDirectoryToFileMap(outputDir, previousManifest.platformPrefix);\n\n output.verbose(`Loading config from ${configFile}`);\n const rawConfig = await loadConfig(configFile);\n\n if (options.intentId) {\n rawConfig.intentId = options.intentId;\n }\n\n if (options.input && options.input.length > 0) {\n const overrides = parseInputOverrides(options.input);\n rawConfig.inputs = { ...(rawConfig.inputs || {}), ...overrides };\n }\n\n output.success(`Template: ${rawConfig.template}`);\n output.verbose(`Intent ID: ${rawConfig.intentId}`);\n\n output.verbose('Resolving template...');\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(rawConfig.template, {\n noCache: options.cache === false,\n timeout,\n });\n\n const envelope: Envelope = {\n template,\n inputs: rawConfig.inputs,\n intentId: rawConfig.intentId,\n platformPrefix: rawConfig.platformPrefix,\n options: {\n skipFormat: options.skipFormat ?? rawConfig.options?.skipFormat,\n },\n };\n\n const engine = createEngine();\n const result = await engine.upgrade(envelope, currentFiles, previousManifest);\n\n if (result.sourceBlocked && !options.allowSourceChange) {\n output.warn('Source mismatch: current files were generated from a different template source.');\n printDriftReport(result.driftReport, options.json);\n if (options.json) {\n output.data(\n JSON.stringify(\n {\n blocked: true,\n reason: 'source_mismatch',\n sourceBlocked: true,\n driftReport: { drift: !result.driftReport.valid, summary: result.driftReport.summary },\n },\n null,\n 2\n )\n );\n } else {\n output.info('Use --allow-source-change to proceed with source migration.');\n }\n return EXIT_CODES.DRIFT_BLOCKED;\n }\n if (result.sourceBlocked) {\n output.warn('Proceeding with source migration (--allow-source-change).');\n }\n\n printDriftReport(result.driftReport, options.json);\n\n if (!result.driftReport.valid && !options.force) {\n if (options.json) {\n output.data(\n JSON.stringify(\n {\n blocked: true,\n reason: 'content_drift',\n sourceBlocked: result.sourceBlocked,\n driftReport: { drift: true, summary: result.driftReport.summary },\n },\n null,\n 2\n )\n );\n } else {\n output.info('Use --force to proceed despite drift.');\n }\n return EXIT_CODES.DRIFT_BLOCKED;\n }\n if (!result.driftReport.valid) {\n output.warn('Proceeding despite drift (--force).');\n }\n\n if (options.dryRun) {\n return handleUpgradeDryRun(result, currentFiles, options.json);\n }\n\n await writeEngineOutput(outputDir, result, 'upgrade');\n\n if (options.json) {\n output.data(formatUpgradeResultJson(result));\n } else {\n output.info(formatUpgradeResult(result));\n }\n\n return EXIT_CODES.SUCCESS;\n}\n\n// --- Diff computation helpers ---\n\nasync function computeInstallDiffs(newFiles: FileMap, outputDir: string): Promise<DiffEntry[]> {\n const diffs: DiffEntry[] = [];\n for (const [filename, entry] of newFiles) {\n if (filename === 'manifest.json') continue;\n let existing = '';\n try {\n existing = await readFile(join(outputDir, filename), 'utf-8');\n } catch {\n /* new file */\n }\n if (existing === entry.content) continue;\n diffs.push({ filename, diff: generateUnifiedDiff(existing, entry.content, filename) });\n }\n return diffs;\n}\n\nfunction computeUpgradeDiffs(newFiles: FileMap, currentFiles: FileMap): DiffEntry[] {\n const diffs: DiffEntry[] = [];\n for (const [filename, entry] of newFiles) {\n if (filename === 'manifest.json') continue;\n const current = currentFiles.get(filename);\n const currentContent = current?.content ?? '';\n if (currentContent === entry.content) continue;\n diffs.push({ filename, diff: generateUnifiedDiff(currentContent, entry.content, filename) });\n }\n return diffs;\n}\n\nfunction printDiffs(diffs: DiffEntry[]): void {\n if (diffs.length === 0) {\n output.info('\\nNo file changes detected.');\n return;\n }\n output.info(`\\n${diffs.length} file(s) changed:\\n`);\n for (const d of diffs) {\n output.info(d.diff);\n }\n}\n\n// --- Drift report helper ---\n\nfunction printDriftReport(report: DriftReport, json?: boolean): void {\n if (json) {\n output.data(\n JSON.stringify(\n {\n drift: !report.valid,\n summary: report.summary,\n entries: report.entries.map((e) => ({\n path: e.path,\n type: e.type,\n details: e.details,\n })),\n },\n null,\n 2\n )\n );\n return;\n }\n if (report.valid) {\n output.success('No drift detected in managed files.');\n return;\n }\n const total =\n report.summary.driftedFiles +\n report.summary.missingFiles +\n report.summary.unexpectedFiles +\n report.summary.inconsistentFiles;\n output.warn(`Drift detected in ${total} managed files:`);\n for (const entry of report.entries) {\n output.info(` ${driftTypeTag(entry.type)} ${entry.path}`);\n output.info(` ${entry.details}`);\n }\n output.info('');\n output.info('Use --force to override and proceed with upgrade.');\n}\n\n// --- Install mode output helpers ---\n\nasync function handleInstallDryRun(\n result: InstallResult,\n outputDir: string,\n json?: boolean\n): Promise<number> {\n const diffs = await computeInstallDiffs(result.files, outputDir);\n if (json) {\n output.data(\n JSON.stringify(\n {\n dryRun: true,\n aggregateHash: result.manifest.aggregateHash,\n templateSource: result.manifest.templateSource,\n templateVersion: result.manifest.templateVersion,\n fileCount: result.files.size,\n files: Array.from(result.files.entries()).map(([path, entry]) => ({\n path,\n size: Buffer.byteLength(entry.content, 'utf-8'),\n })),\n changes: diffs.map((d) => ({ filename: d.filename, diff: d.diff })),\n },\n null,\n 2\n )\n );\n } else {\n output.info('Dry run — no files written.');\n output.info('');\n output.info(`Aggregate Hash: ${result.manifest.aggregateHash}`);\n output.info(`Template: ${result.manifest.templateSource}`);\n output.info(`Version: ${result.manifest.templateVersion}`);\n output.info(`Files: ${result.files.size}`);\n printDiffs(diffs);\n }\n return EXIT_CODES.SUCCESS;\n}\n\nfunction printInstallResult(result: InstallResult, outputDir: string, json?: boolean): void {\n if (json) {\n output.data(\n JSON.stringify(\n {\n success: true,\n aggregateHash: result.manifest.aggregateHash,\n fileCount: result.files.size,\n outputDir,\n files: Array.from(result.files.entries()).map(([path, entry]) => ({\n path,\n size: Buffer.byteLength(entry.content, 'utf-8'),\n })),\n },\n null,\n 2\n )\n );\n } else {\n const fileList: Array<{ path: string; size: number }> = [];\n for (const [filePath, entry] of result.files) {\n fileList.push({\n path: filePath,\n size: Buffer.byteLength(entry.content, 'utf-8'),\n });\n }\n output.info('');\n output.info(`Output written to: ${outputDir}`);\n output.info(`Aggregate Hash: ${result.manifest.aggregateHash}`);\n output.printFileList(fileList);\n }\n}\n\n// --- Upgrade mode output helpers ---\n\nfunction handleUpgradeDryRun(result: UpgradeResult, currentFiles: FileMap, json?: boolean): number {\n const diffs = computeUpgradeDiffs(result.files, currentFiles);\n if (json) {\n output.data(\n JSON.stringify(\n {\n dryRun: true,\n templateSource: result.manifest.templateSource,\n templateVersion: result.manifest.templateVersion,\n aggregateHash: result.manifest.aggregateHash,\n driftReport: result.driftReport,\n files: Array.from(result.files.entries()).map(([path, entry]) => ({\n path,\n zone: entry.zone,\n })),\n changes: diffs.map((d) => ({ filename: d.filename, diff: d.diff })),\n },\n null,\n 2\n )\n );\n } else {\n output.info('Dry run — no files written.');\n output.info('');\n output.info(formatUpgradeResult(result));\n printDiffs(diffs);\n }\n return EXIT_CODES.SUCCESS;\n}\n\nfunction formatUpgradeResult(result: UpgradeResult): string {\n const lines: string[] = [];\n lines.push(\n `✓ Upgrade complete: ${result.manifest.templateSource} v${result.manifest.templateVersion}`\n );\n lines.push('');\n let platformCount = 0;\n let userCount = 0;\n for (const [, entry] of result.files) {\n if (entry.zone === 'platform') platformCount++;\n else userCount++;\n }\n lines.push(` Updated: ${platformCount} files (Platform Zone)`);\n lines.push(` Unchanged: ${userCount} files (User Zone)`);\n lines.push('');\n lines.push(`Aggregate Hash: ${result.manifest.aggregateHash}`);\n return lines.join('\\n');\n}\n\nfunction formatUpgradeResultJson(result: UpgradeResult): string {\n return JSON.stringify(\n {\n success: true,\n templateSource: result.manifest.templateSource,\n templateVersion: result.manifest.templateVersion,\n aggregateHash: result.manifest.aggregateHash,\n driftReport: result.driftReport,\n files: Array.from(result.files.entries()).map(([path, entry]) => ({\n path,\n zone: entry.zone,\n })),\n },\n null,\n 2\n );\n}\n","/**\n * Resolver registry for managing template source resolvers.\n *\n * Supports file:// and https://*.json bundle URIs.\n */\n\nimport type { Resolver } from './base.js';\nimport type { Template, ResolverOptions } from '@truefoundry/tfy-infra-engine';\nimport { EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\nimport { FileResolver } from './file.js';\nimport { HttpsBundleResolver } from './https.js';\nimport { TemplateCache } from '../cache/template-cache.js';\n\nexport type { Resolver } from './base.js';\nexport { FileResolver } from './file.js';\nexport { HttpsBundleResolver } from './https.js';\nexport { TemplateCache, createTemplateCache, getDefaultCacheDir } from '../cache/template-cache.js';\n\n/**\n * Registry for template resolvers.\n * Routes template URIs to the appropriate resolver based on scheme.\n */\nexport class ResolverRegistry {\n private resolvers: Resolver[] = [];\n private cache: TemplateCache;\n\n constructor(resolvers?: Resolver[], cacheDir?: string) {\n this.cache = new TemplateCache(cacheDir);\n\n if (resolvers) {\n this.resolvers = resolvers;\n } else {\n this.register(new FileResolver());\n this.register(new HttpsBundleResolver());\n }\n }\n\n register(resolver: Resolver): void {\n this.resolvers.push(resolver);\n }\n\n getResolver(uri: string): Resolver | undefined {\n return this.resolvers.find((r) => r.canResolve(uri));\n }\n\n async resolve(uri: string, options?: ResolverOptions): Promise<Template> {\n const resolver = this.getResolver(uri);\n\n if (!resolver) {\n const scheme = uri.split('://')[0] || 'unknown';\n throw new EngineError(\n `No resolver found for URI scheme: ${scheme}://`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n undefined,\n {\n uri,\n scheme,\n supportedSchemes: ['file://', 'https://*.json'],\n }\n );\n }\n\n const template = await resolver.resolve(uri, options);\n\n // Cache the result (unless it's a file:// URI)\n if (!uri.startsWith('file://')) {\n await this.cache.set(template);\n }\n\n return template;\n }\n\n canResolve(uri: string): boolean {\n return this.resolvers.some((r) => r.canResolve(uri));\n }\n\n getSupportedSchemes(): string[] {\n return ['file://', 'https://*.json'];\n }\n\n async clearCache(uri?: string): Promise<void> {\n if (uri) {\n await this.cache.delete(uri);\n } else {\n await this.cache.clear();\n }\n }\n\n async getCacheStats(): Promise<{ sources: number; templates: number; size: number }> {\n return this.cache.stats();\n }\n}\n\n/**\n * Create a default resolver registry with standard resolvers (file + https bundle).\n */\nexport function createResolverRegistry(cacheDir?: string): ResolverRegistry {\n return new ResolverRegistry(undefined, cacheDir);\n}\n","/**\n * File resolver for local template sources (file://).\n *\n * Supports bundle.json — single-file distribution format.\n *\n * Detection order: if the URI points directly at a .json file, treat it as a\n * bundle; if it's a directory containing bundle.json, load the bundle.\n */\n\nimport { readFile, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Resolver } from './base.js';\nimport type { Template, ResolverOptions, VersionInfo } from '@truefoundry/tfy-infra-engine';\nimport { EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\n\n/**\n * Resolver for file:// URIs.\n * Loads templates from the local filesystem.\n */\nexport class FileResolver implements Resolver {\n canResolve(uri: string): boolean {\n return uri.startsWith('file://');\n }\n\n async resolve(uri: string, _options?: ResolverOptions): Promise<Template> {\n const fsPath = this.uriToPath(uri);\n\n try {\n const info = await stat(fsPath);\n\n if (info.isFile() && fsPath.endsWith('.json')) {\n return this.resolveBundle(fsPath, uri);\n }\n\n if (info.isDirectory()) {\n const bundlePath = join(fsPath, 'bundle.json');\n if (await this.fileExists(bundlePath)) {\n return this.resolveBundle(bundlePath, uri);\n }\n throw new EngineError(\n `bundle.json not found in ${fsPath}`,\n EngineErrorCode.BUNDLE_NOT_FOUND,\n undefined,\n { path: fsPath }\n );\n }\n\n throw new EngineError(\n `Not a file or directory: ${fsPath}`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n undefined,\n { path: fsPath }\n );\n } catch (error) {\n if (error instanceof EngineError) {\n throw error;\n }\n\n const nodeError = error as NodeJS.ErrnoException;\n if (nodeError.code === 'ENOENT') {\n throw new EngineError(\n `Template not found: ${fsPath}`,\n EngineErrorCode.FILE_NOT_FOUND,\n error as Error,\n { path: fsPath, originalCode: nodeError.code }\n );\n }\n\n if (nodeError.code === 'EACCES') {\n throw new EngineError(\n `Permission denied: ${fsPath}`,\n EngineErrorCode.FILE_NOT_FOUND,\n error as Error,\n { path: fsPath, originalCode: nodeError.code }\n );\n }\n\n throw new EngineError(\n `Failed to load template from ${fsPath}: ${(error as Error).message}`,\n EngineErrorCode.FILE_NOT_FOUND,\n error as Error,\n { path: fsPath }\n );\n }\n }\n\n private async resolveBundle(bundlePath: string, uri: string): Promise<Template> {\n const content = await readFile(bundlePath, 'utf-8');\n\n let raw: Record<string, unknown>;\n try {\n raw = JSON.parse(content) as Record<string, unknown>;\n } catch (error) {\n throw new EngineError(\n `Failed to parse bundle.json: ${(error as Error).message}`,\n EngineErrorCode.BUNDLE_INVALID,\n error as Error,\n { path: bundlePath }\n );\n }\n\n const rawMetadata = raw['metadata'] as Record<string, unknown> | undefined;\n if (!rawMetadata || typeof rawMetadata !== 'object') {\n throw new EngineError(\n 'bundle.json must contain a \"metadata\" object',\n EngineErrorCode.BUNDLE_INVALID,\n undefined,\n { path: bundlePath }\n );\n }\n if (!rawMetadata['name'] || !rawMetadata['version']) {\n throw new EngineError(\n 'bundle.json metadata must contain \"name\" and \"version\"',\n EngineErrorCode.BUNDLE_INVALID,\n undefined,\n { path: bundlePath }\n );\n }\n\n const metadata = {\n name: rawMetadata['name'] as string,\n version: rawMetadata['version'] as string,\n ...(rawMetadata['description'] ? { description: rawMetadata['description'] as string } : {}),\n };\n\n const jsonSchema = raw['jsonSchema'] as Record<string, unknown>;\n if (!jsonSchema || typeof jsonSchema !== 'object') {\n throw new EngineError(\n 'bundle.json must contain a \"jsonSchema\" object',\n EngineErrorCode.BUNDLE_INVALID,\n undefined,\n { path: bundlePath }\n );\n }\n\n const files = new Map(Object.entries((raw['files'] as Record<string, string>) ?? {}));\n const staticFiles = new Map(\n Object.entries((raw['staticFiles'] as Record<string, string>) ?? {})\n );\n\n if (files.size === 0 && staticFiles.size === 0) {\n throw new EngineError(\n `Bundle contains no template files: ${bundlePath}`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n undefined,\n { path: bundlePath }\n );\n }\n\n const version: VersionInfo = { semver: metadata.version };\n\n return { metadata, jsonSchema, files, staticFiles, source: uri, version };\n }\n\n private uriToPath(uri: string): string {\n if (uri.startsWith('file:///')) {\n return uri.substring(7);\n }\n if (uri.startsWith('file://')) {\n return uri.substring(7);\n }\n return uri;\n }\n\n private async fileExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n }\n}\n","/**\n * HTTPS bundle resolver for pre-built template bundles (https://*.json).\n *\n * Fetches a bundle JSON file over HTTPS and unpacks it into a Template.\n * Automatically attaches a Bearer token for Artifactory hosts when\n * JFROG_ACCESS_TOKEN is present in the environment.\n */\n\nimport type { Resolver } from './base.js';\nimport type { Template, ResolverOptions, VersionInfo } from '@truefoundry/tfy-infra-engine';\nimport { validateBundle, EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\nimport { mergeResolverOptions, withRetry, withTimeout } from './base.js';\n\nconst JFROG_TOKEN_ENV = 'JFROG_ACCESS_TOKEN';\n\nfunction isArtifactoryHost(uri: string): boolean {\n try {\n const { hostname } = new URL(uri);\n return hostname.includes('jfrog');\n } catch {\n return false;\n }\n}\n\nfunction buildHeaders(uri: string): Record<string, string> {\n const headers: Record<string, string> = {\n 'User-Agent': 'tfy-infra-cli',\n Accept: 'application/json',\n };\n\n if (isArtifactoryHost(uri)) {\n const token = process.env[JFROG_TOKEN_ENV];\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n }\n\n return headers;\n}\n\nasync function fetchBundle(uri: string, headers: Record<string, string>): Promise<Response> {\n const response = await fetch(uri, { headers });\n\n if (response.status === 401 || response.status === 403) {\n const hint = isArtifactoryHost(uri)\n ? ` Set ${JFROG_TOKEN_ENV} in your environment to authenticate.`\n : '';\n throw new EngineError(\n `Authentication failed (HTTP ${response.status}) for ${uri}.${hint}`,\n EngineErrorCode.HTTPS_AUTH_FAILED,\n undefined,\n { uri, status: response.status }\n );\n }\n\n if (!response.ok) {\n throw new EngineError(\n `Bundle download failed: HTTP ${response.status} for ${uri}`,\n EngineErrorCode.NETWORK_ERROR,\n undefined,\n { uri, status: response.status }\n );\n }\n\n return response;\n}\n\nasync function unpackBundle(response: Response, uri: string): Promise<Template> {\n const body: unknown = await response.json();\n const raw = body as Record<string, unknown>;\n\n const { metadata, jsonSchema } = validateBundle(raw);\n\n const files = new Map(Object.entries((raw['files'] as Record<string, string>) ?? {}));\n const staticFiles = new Map(Object.entries((raw['staticFiles'] as Record<string, string>) ?? {}));\n\n if (files.size === 0 && staticFiles.size === 0) {\n throw new EngineError(\n `Bundle contains no template files: ${uri}`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n undefined,\n { uri }\n );\n }\n\n const version: VersionInfo = { semver: metadata.version };\n\n return { metadata, jsonSchema, files, staticFiles, source: uri, version };\n}\n\n/**\n * Resolver for HTTPS bundle URLs (https://.../*.json).\n */\nexport class HttpsBundleResolver implements Resolver {\n canResolve(uri: string): boolean {\n return uri.startsWith('https://') && uri.toLowerCase().endsWith('.json');\n }\n\n async resolve(uri: string, options?: ResolverOptions): Promise<Template> {\n const opts = mergeResolverOptions(options);\n const headers = buildHeaders(uri);\n\n const response = await withTimeout(\n withRetry(() => fetchBundle(uri, headers), opts.retries),\n opts.timeout,\n `HTTPS bundle download timed out after ${opts.timeout}ms`\n );\n\n return unpackBundle(response, uri);\n }\n}\n","/**\n * Base resolver interface and common utilities.\n *\n * Moved from @truefoundry/tfy-infra-engine (006).\n * Imports reference the engine package for shared types.\n */\n\nimport type { Template, ResolverOptions } from '@truefoundry/tfy-infra-engine';\n\n/**\n * Interface for template resolvers.\n * Each resolver handles a specific URI scheme (file://, https://).\n */\nexport interface Resolver {\n canResolve(uri: string): boolean;\n resolve(uri: string, options?: ResolverOptions): Promise<Template>;\n}\n\n/**\n * Default resolver options.\n */\nexport const DEFAULT_RESOLVER_OPTIONS: Required<ResolverOptions> = {\n timeout: 30000,\n retries: 3,\n noCache: false,\n};\n\n/**\n * Merge resolver options with defaults.\n */\nexport function mergeResolverOptions(options?: ResolverOptions): Required<ResolverOptions> {\n return {\n ...DEFAULT_RESOLVER_OPTIONS,\n ...options,\n };\n}\n\n/**\n * Retry an async operation with exponential backoff.\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n retries: number,\n baseDelay = 1000\n): Promise<T> {\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n\n if (attempt < retries) {\n const delay = baseDelay * Math.pow(2, attempt) * (0.5 + Math.random() * 0.5);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n\n throw lastError instanceof Error ? lastError : new Error(String(lastError));\n}\n\n/**\n * Create a timeout wrapper for promises.\n */\nexport function withTimeout<T>(promise: Promise<T>, ms: number, message?: string): Promise<T> {\n let timer: ReturnType<typeof setTimeout>;\n\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error(message ?? `Operation timed out after ${ms}ms`)), ms);\n });\n\n return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));\n}\n","/**\n * Template cache for storing fetched templates locally.\n * Uses filesystem-based caching with version-keyed storage.\n *\n * Stores templates as bundle.json files containing all template data\n * in a single self-contained JSON file.\n */\n\nimport { mkdir, readFile, writeFile, rm, readdir, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport type { Template, VersionInfo } from '@truefoundry/tfy-infra-engine';\nimport { validateBundle, EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\n\n/**\n * Default cache directory.\n */\nexport function getDefaultCacheDir(): string {\n const home = process.env['HOME'] || process.env['USERPROFILE'] || '~';\n return join(home, '.cache', 'tfy-infra', 'templates');\n}\n\n/**\n * Template cache manager.\n */\nexport class TemplateCache {\n private readonly cacheDir: string;\n\n constructor(cacheDir?: string) {\n this.cacheDir = cacheDir ?? getDefaultCacheDir();\n }\n\n private getCacheKey(source: string): string {\n return createHash('sha256').update(source).digest('hex').substring(0, 16);\n }\n\n private getCachePath(source: string, version: string): string {\n const key = this.getCacheKey(source);\n const safeVersion = version.replace(/\\//g, '-');\n return join(this.cacheDir, key, safeVersion);\n }\n\n async has(source: string, version: string): Promise<boolean> {\n const cachePath = this.getCachePath(source, version);\n try {\n const bundlePath = join(cachePath, 'bundle.json');\n await stat(bundlePath);\n return true;\n } catch {\n return false;\n }\n }\n\n async get(source: string, version: string): Promise<Template | null> {\n const cachePath = this.getCachePath(source, version);\n\n try {\n const bundlePath = join(cachePath, 'bundle.json');\n const bundleContent = await readFile(bundlePath, 'utf-8');\n const parsed: unknown = JSON.parse(bundleContent);\n const { metadata, jsonSchema } = validateBundle(parsed);\n\n const record = parsed as Record<string, unknown>;\n const files = new Map(Object.entries((record['files'] as Record<string, string>) ?? {}));\n const staticFiles = new Map(\n Object.entries((record['staticFiles'] as Record<string, string>) ?? {})\n );\n\n const versionInfo: VersionInfo = {\n semver: metadata.version,\n };\n\n return {\n metadata,\n jsonSchema,\n files,\n staticFiles,\n source,\n version: versionInfo,\n };\n } catch {\n return null;\n }\n }\n\n async set(template: Template): Promise<void> {\n const cachePath = this.getCachePath(template.source, template.version.semver);\n\n try {\n await mkdir(cachePath, { recursive: true });\n\n const bundle = {\n metadata: {\n name: template.metadata.name,\n version: template.metadata.version,\n ...(template.metadata.description ? { description: template.metadata.description } : {}),\n },\n jsonSchema: template.jsonSchema,\n files: Object.fromEntries(template.files),\n staticFiles: Object.fromEntries(template.staticFiles),\n };\n await writeFile(join(cachePath, 'bundle.json'), JSON.stringify(bundle, null, 2), 'utf-8');\n } catch (error) {\n throw new EngineError(\n `Failed to cache template: ${(error as Error).message}`,\n EngineErrorCode.CACHE_ERROR,\n error as Error,\n { source: template.source, version: template.version.semver }\n );\n }\n }\n\n async delete(source: string, version?: string): Promise<void> {\n if (version) {\n const cachePath = this.getCachePath(source, version);\n try {\n await rm(cachePath, { recursive: true, force: true });\n } catch {\n // Ignore if doesn't exist\n }\n } else {\n const key = this.getCacheKey(source);\n const keyPath = join(this.cacheDir, key);\n try {\n await rm(keyPath, { recursive: true, force: true });\n } catch {\n // Ignore if doesn't exist\n }\n }\n }\n\n async clear(): Promise<void> {\n try {\n await rm(this.cacheDir, { recursive: true, force: true });\n } catch {\n // Ignore if doesn't exist\n }\n }\n\n async stats(): Promise<{ sources: number; templates: number; size: number }> {\n let sources = 0;\n let templates = 0;\n let size = 0;\n\n try {\n const sourceKeys = await readdir(this.cacheDir);\n sources = sourceKeys.length;\n\n for (const key of sourceKeys) {\n const keyPath = join(this.cacheDir, key);\n const versions = await readdir(keyPath);\n templates += versions.length;\n\n for (const version of versions) {\n const versionPath = join(keyPath, version);\n size += await this.dirSize(versionPath);\n }\n }\n } catch {\n // Cache doesn't exist or is empty\n }\n\n return { sources, templates, size };\n }\n\n private async dirSize(dir: string): Promise<number> {\n let total = 0;\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n total += await this.dirSize(fullPath);\n } else {\n const fileStat = await stat(fullPath);\n total += fileStat.size;\n }\n }\n return total;\n }\n}\n\n/**\n * Create a template cache instance.\n */\nexport function createTemplateCache(cacheDir?: string): TemplateCache {\n return new TemplateCache(cacheDir);\n}\n","/**\n * Configuration file loader for YAML and JSON formats.\n *\n * The CLI command is responsible for resolving the template URI before calling the engine.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport * as yaml from 'js-yaml';\n\n/**\n * Raw configuration from a config file.\n * `template` is a URI string that must be resolved before passing to the engine.\n */\nexport interface RawConfig {\n template: string;\n inputs: Record<string, unknown>;\n intentId: string;\n platformPrefix?: string;\n options?: {\n skipFormat?: boolean;\n };\n}\n\n/**\n * Load a configuration file (YAML or JSON).\n *\n * @param filePath - Path to the config file\n * @returns Raw configuration with template URI string\n */\nexport async function loadConfig(filePath: string): Promise<RawConfig> {\n const content = await readFile(filePath, 'utf-8');\n\n const isJson = filePath.endsWith('.json');\n\n let parsed: Record<string, unknown>;\n try {\n if (isJson) {\n parsed = JSON.parse(content) as Record<string, unknown>;\n } else {\n parsed = yaml.load(content) as Record<string, unknown>;\n }\n } catch (error) {\n const format = isJson ? 'JSON' : 'YAML';\n throw new Error(`Failed to parse ${format} config file: ${(error as Error).message}`, {\n cause: error,\n });\n }\n\n if (\n !parsed['intentId'] ||\n typeof parsed['intentId'] !== 'string' ||\n parsed['intentId'].trim() === ''\n ) {\n throw new Error(\n `Configuration file is missing required field 'intentId'. ` +\n `Add 'intentId: \"<your-cluster-identity>\"' to ${filePath}. ` +\n `The intentId uniquely identifies the cluster for integrity tracking.`\n );\n }\n\n return {\n template: parsed['template'] as string,\n inputs: (parsed['inputs'] as Record<string, unknown>) ?? {},\n intentId: parsed['intentId'],\n platformPrefix: parsed['platformPrefix'] as string | undefined,\n options: parsed['options'] as RawConfig['options'],\n };\n}\n\nexport { fileExists } from './fs-helpers.js';\n","/**\n * Shared filesystem helper utilities.\n */\n\nimport { access } from 'node:fs/promises';\nimport { join } from 'node:path';\n\n/**\n * Check if a file exists on disk.\n */\nexport async function fileExists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Find the default fixture path (tests/default/inputs.yaml) for a template version.\n * Returns null if the file does not exist.\n */\nexport async function findDefaultFixture(versionDir: string): Promise<string | null> {\n const defaultPath = join(versionDir, 'tests', 'default', 'inputs.yaml');\n try {\n await access(defaultPath);\n return defaultPath;\n } catch {\n return null;\n }\n}\n","/**\n * Manifest loading and raw config reconstruction utility.\n *\n * CHANGE (006): envelopeFromManifest now returns RawConfig (with template as\n * URI string) instead of Envelope. The CLI command resolves the URI.\n *\n * Reference: data-model.md §11, research.md R-008\n */\n\nimport { readFile } from 'node:fs/promises';\nimport type { Manifest } from '@truefoundry/tfy-infra-engine';\nimport type { RawConfig } from './config-loader.js';\n\n/**\n * Load and validate a manifest from disk.\n */\nexport async function loadManifest(manifestPath: string): Promise<Manifest> {\n let content: string;\n try {\n content = await readFile(manifestPath, 'utf-8');\n } catch (error) {\n const nodeError = error as NodeJS.ErrnoException;\n if (nodeError.code === 'ENOENT') {\n throw new Error(`Manifest not found: ${manifestPath}`, { cause: error });\n }\n throw new Error(`Failed to read manifest: ${(error as Error).message}`, { cause: error });\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(content) as Record<string, unknown>;\n } catch {\n throw new Error(`Invalid JSON in manifest: ${manifestPath}`);\n }\n\n const requiredFields = [\n 'manifestVersion',\n 'files',\n 'aggregateHash',\n 'inputs',\n 'platformPrefix',\n 'intentId',\n 'templateSource',\n 'templateVersion',\n ] as const;\n\n for (const field of requiredFields) {\n if (parsed[field] === undefined) {\n throw new Error(`Manifest is missing required field: ${field}`);\n }\n }\n\n return parsed as unknown as Manifest;\n}\n\n/**\n * Reconstruct a RawConfig from a manifest for re-render.\n *\n * CHANGE (006): Returns RawConfig with template URI string.\n */\nexport function envelopeFromManifest(manifest: Manifest): RawConfig {\n if (!manifest.inputs) {\n throw new Error('Cannot reconstruct envelope: manifest is missing inputs field');\n }\n if (!manifest.platformPrefix) {\n throw new Error('Cannot reconstruct envelope: manifest is missing platformPrefix field');\n }\n\n return {\n template: manifest.templateSource,\n inputs: manifest.inputs,\n intentId: manifest.intentId,\n platformPrefix: manifest.platformPrefix,\n };\n}\n","/**\n * Disk-to-FileMap reader utility.\n *\n * Reads a cluster directory into an in-memory FileMap for verify and\n * upgrade operations. Classifies files by zone and parses @tfy-status\n * headers for platform files.\n *\n * Reference: data-model.md §9, research.md R-012\n */\n\nimport { readFile, readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport {\n classifyFile,\n parseHeader,\n DEFAULT_PREFIX,\n type FileMap,\n type FileEntry,\n} from '@truefoundry/tfy-infra-engine';\n\n/**\n * Read all files from a cluster directory into a FileMap.\n *\n * Scans the directory, classifies each file by zone, parses @tfy-status\n * headers for platform files, and returns a typed FileMap.\n *\n * Does NOT include manifest.json in the output (callers load it separately).\n *\n * @param dir - Path to cluster directory\n * @param prefix - Platform Zone filename prefix (default: \"tfy_\")\n * @returns FileMap with zone-tagged entries and parsed headers\n */\nexport async function readDirectoryToFileMap(\n dir: string,\n prefix: string = DEFAULT_PREFIX\n): Promise<FileMap> {\n const fileMap: FileMap = new Map();\n\n let entries: string[];\n try {\n const dirEntries = await readdir(dir, { withFileTypes: true });\n entries = dirEntries.filter((e) => e.isFile()).map((e) => e.name);\n } catch (error) {\n const nodeError = error as NodeJS.ErrnoException;\n if (nodeError.code === 'ENOENT') {\n throw new Error(`Directory not found: ${dir}`, { cause: error });\n }\n throw new Error(`Failed to read directory: ${(error as Error).message}`, { cause: error });\n }\n\n for (const filename of entries) {\n // Skip manifest.json — callers load it separately\n if (filename === 'manifest.json') continue;\n\n const content = await readFile(join(dir, filename), 'utf-8');\n const zone = classifyFile(filename, prefix);\n\n const entry: FileEntry = { content, zone };\n\n if (zone === 'platform') {\n const header = parseHeader(content);\n if (header) {\n entry.header = header;\n }\n }\n\n fileMap.set(filename, entry);\n }\n\n return fileMap;\n}\n","/**\n * Engine output-to-disk writer utility.\n *\n * Writes engine results to disk with zone-aware policies.\n *\n * CHANGE (008): Removed sideOutputs handling — no more expose_variable pipeline.\n *\n * Reference: data-model.md §10, spec.md FR-012a\n */\n\nimport { writeFile, mkdir } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport type { InstallResult, UpgradeResult } from '@truefoundry/tfy-infra-engine';\nimport { fileExists } from './fs-helpers.js';\n\n/**\n * Write engine output to disk with zone-aware policies.\n *\n * Modes:\n * - 'install': Overwrite all files\n * - 'upgrade': Overwrite platform zone files, skip existing user zone files,\n * create new user zone files\n *\n * @param dir - Target directory\n * @param result - InstallResult or UpgradeResult from engine\n * @param mode - 'install' (overwrite all) or 'upgrade' (zone-aware)\n */\nexport async function writeEngineOutput(\n dir: string,\n result: InstallResult | UpgradeResult,\n mode: 'install' | 'upgrade'\n): Promise<void> {\n await mkdir(dir, { recursive: true });\n\n for (const [filePath, entry] of result.files) {\n const fullPath = join(dir, filePath);\n\n if (mode === 'upgrade' && entry.zone === 'user') {\n if (await fileExists(fullPath)) {\n continue;\n }\n }\n\n await mkdir(dirname(fullPath), { recursive: true });\n await writeFile(fullPath, entry.content, 'utf-8');\n }\n}\n","/**\n * Unified diff generation utility\n * Generates unified diff output for test failures\n * @file packages/infra-cli/src/utils/diff.ts\n */\n\nimport { createTwoFilesPatch } from 'diff';\n\n/**\n * Generate a unified diff between expected and actual content.\n *\n * @param expected - Expected file content\n * @param actual - Actual file content\n * @param filename - Name of the file being compared\n * @returns Unified diff string, or empty string if contents match\n */\nexport function generateUnifiedDiff(expected: string, actual: string, filename: string): string {\n const normalizedExpected = expected.replace(/\\r\\n/g, '\\n');\n const normalizedActual = actual.replace(/\\r\\n/g, '\\n');\n\n const patch = createTwoFilesPatch(\n `expected/${filename}`,\n `actual/${filename}`,\n normalizedExpected,\n normalizedActual,\n '', // oldHeader\n '', // newHeader\n { context: 3 }\n );\n\n return patch;\n}\n\n/**\n * Diff entry for a single file\n */\nexport interface DiffEntry {\n filename: string;\n diff: string;\n}\n\n/**\n * Format multiple diffs for display output.\n *\n * @param diffs - Array of diff entries with filenames\n * @returns Formatted string with all diffs\n */\nexport function formatDiffOutput(diffs: DiffEntry[]): string {\n if (diffs.length === 0) {\n return '';\n }\n\n return diffs\n .map((entry) => {\n return ` ${entry.filename}: content mismatch\\n${entry.diff}`;\n })\n .join('\\n');\n}\n","/**\n * tfy-init tpl validate command\n *\n * Validates a bundle.json template package.\n * Detects bundle.json in the specified directory.\n */\n\nimport { Command } from 'commander';\nimport { readFile, stat } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport Handlebars from 'handlebars';\nimport { validateJsonSchemaStructure, isTofuAvailable } from '@truefoundry/tfy-infra-engine';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\n\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\nexport function createValidateCommand(): Command {\n const cmd = new Command('validate')\n .description('Validate a template (for template authors)')\n .option('-d, --directory <dir>', 'Path to local template directory', '.')\n .option('--strict', 'Fail on warnings')\n .option('--skip-format', 'Skip tofu fmt validation')\n .action(async (options: ValidateOptions) => {\n try {\n const exitCode = await runValidate(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\ninterface ValidateOptions {\n directory: string;\n strict?: boolean;\n skipFormat?: boolean;\n}\n\ninterface ValidationIssue {\n type: 'error' | 'warning';\n message: string;\n file?: string;\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function runValidate(options: ValidateOptions): Promise<number> {\n const templateDir = resolve(options.directory);\n const issues: ValidationIssue[] = [];\n let hasErrors = false;\n\n output.info(`Validating template at: ${templateDir}`);\n output.info('');\n\n const bundlePath = join(templateDir, 'bundle.json');\n const usesBundle = await fileExists(bundlePath);\n\n if (!usesBundle) {\n output.error('bundle.json not found in template directory');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n output.verbose('Checking bundle.json...');\n try {\n const content = await readFile(bundlePath, 'utf-8');\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(content) as Record<string, unknown>;\n } catch (error) {\n throw new Error(`Invalid JSON: ${(error as Error).message}`, { cause: error });\n }\n\n const rawMeta = parsed['metadata'] as Record<string, unknown> | undefined;\n if (!rawMeta || typeof rawMeta !== 'object') {\n throw new Error('bundle.json must contain a \"metadata\" object');\n }\n if (!rawMeta['name'] || !rawMeta['version']) {\n throw new Error('bundle.json metadata must contain \"name\" and \"version\"');\n }\n\n const jsonSchema = parsed['jsonSchema'] as Record<string, unknown> | undefined;\n if (!jsonSchema || typeof jsonSchema !== 'object') {\n throw new Error('bundle.json must contain a \"jsonSchema\" object');\n }\n\n validateJsonSchemaStructure(jsonSchema);\n output.success('bundle.json schema is valid');\n\n const files = parsed['files'] as Record<string, string> | undefined;\n const staticFiles = parsed['staticFiles'] as Record<string, string> | undefined;\n const fileCount = Object.keys(files ?? {}).length;\n const staticCount = Object.keys(staticFiles ?? {}).length;\n\n if (fileCount === 0 && staticCount === 0) {\n issues.push({ type: 'error', message: 'Bundle contains no template files' });\n hasErrors = true;\n } else {\n for (const [relPath, content] of Object.entries(files ?? {})) {\n try {\n Handlebars.precompile(content);\n output.verbose(` ✓ ${relPath}`);\n } catch (error) {\n issues.push({\n type: 'error',\n message: `Invalid Handlebars syntax: ${(error as Error).message}`,\n file: relPath,\n });\n hasErrors = true;\n }\n }\n output.success(\n `Found ${fileCount} HBS template(s)` +\n (staticCount > 0 ? ` and ${staticCount} static file(s)` : '') +\n ' with valid syntax'\n );\n }\n } catch (error) {\n issues.push({\n type: 'error',\n message: (error as Error).message,\n file: 'bundle.json',\n });\n hasErrors = true;\n }\n\n if (!options.skipFormat) {\n output.verbose('Checking tofu fmt availability...');\n const tofuAvailable = await isTofuAvailable();\n if (tofuAvailable) {\n output.success('tofu fmt available');\n } else {\n issues.push({ type: 'warning', message: 'tofu not found - format validation skipped' });\n }\n }\n\n output.info('');\n\n const warnings = issues.filter((i) => i.type === 'warning');\n const errors = issues.filter((i) => i.type === 'error');\n\n if (warnings.length > 0) {\n output.warn(`${warnings.length} warning(s):`);\n for (const issue of warnings) {\n const prefix = issue.file ? `${issue.file}: ` : '';\n output.info(` • ${prefix}${issue.message}`);\n }\n output.info('');\n }\n\n if (errors.length > 0) {\n output.error(`${errors.length} error(s):`);\n for (const issue of errors) {\n const prefix = issue.file ? `${issue.file}: ` : '';\n output.info(` • ${prefix}${issue.message}`);\n }\n output.info('');\n }\n\n if (hasErrors) {\n output.error('Template validation failed');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n if (options.strict && warnings.length > 0) {\n output.error('Template validation failed (strict mode)');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n output.success('Template is valid');\n return EXIT_CODES.SUCCESS;\n}\n","/**\n * tfy-init tpl test command\n *\n * CHANGE (006): Fetch-first — resolves template via ResolverRegistry\n * before calling engine.install(). Envelope uses Template object.\n */\n\nimport { Command } from 'commander';\nimport { readFile, readdir, writeFile, mkdir } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport * as yaml from 'js-yaml';\nimport { createEngine, type Envelope, type InstallResult } from '@truefoundry/tfy-infra-engine';\nimport { createResolverRegistry } from '../../resolvers/index.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { generateUnifiedDiff } from '../../utils/diff.js';\n\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\nexport function createTestCommand(): Command {\n const cmd = new Command('test')\n .description('Run test fixtures against a template')\n .option('-d, --directory <dir>', 'Path to local template directory', '.')\n .option('--fixture <name>', 'Run specific fixture only')\n .option('--update', 'Update expected outputs with actual results')\n .option('--diff', 'Show unified diff on failures')\n .option('--skip-format', 'Skip tofu fmt in tests')\n .action(async (options: TestOptions) => {\n try {\n const exitCode = await runTest(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\ninterface TestOptions {\n directory: string;\n fixture?: string;\n update?: boolean;\n diff?: boolean;\n skipFormat?: boolean;\n}\n\ninterface TestResult {\n name: string;\n passed: boolean;\n errors?: string[];\n diffs?: Array<{ filename: string; diff: string }>;\n}\n\nasync function runTest(options: TestOptions): Promise<number> {\n const templateDir = resolve(options.directory);\n const testsDir = join(templateDir, 'tests');\n const templateUri = `file://${templateDir}`;\n\n output.info(`Testing template at: ${templateDir}`);\n output.info('');\n\n let fixtures: string[];\n try {\n const entries = await readdir(testsDir);\n fixtures = [];\n for (const entry of entries) {\n try {\n const inputsPath = join(testsDir, entry, 'inputs.yaml');\n await readFile(inputsPath);\n fixtures.push(entry);\n } catch {\n // Not a valid fixture directory\n }\n }\n } catch {\n output.error(`No tests directory found at ${testsDir}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n if (fixtures.length === 0) {\n output.error('No test fixtures found');\n output.info('');\n output.info('Expected structure:');\n output.info(' tests/');\n output.info(' <fixture-name>/');\n output.info(' inputs.yaml');\n output.info(' expected/');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n if (options.fixture) {\n if (!fixtures.includes(options.fixture)) {\n output.error(`Fixture '${options.fixture}' not found`);\n output.info(`Available fixtures: ${fixtures.join(', ')}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n fixtures = [options.fixture];\n }\n\n output.info(`Running ${fixtures.length} fixture(s)...`);\n output.info('');\n\n // Fetch-first: resolve template once for all fixtures\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(templateUri, { noCache: true });\n\n const engine = createEngine();\n const results: TestResult[] = [];\n\n for (const fixture of fixtures) {\n const fixtureDir = join(testsDir, fixture);\n const inputsPath = join(fixtureDir, 'inputs.yaml');\n const expectedDir = join(fixtureDir, 'expected');\n\n output.verbose(`Running fixture: ${fixture}`);\n\n try {\n const inputsContent = await readFile(inputsPath, 'utf-8');\n const inputs = yaml.load(inputsContent) as Record<string, unknown>;\n\n const envelope: Envelope = {\n template,\n inputs,\n intentId: `test-${fixture}`,\n options: {\n skipFormat: options.skipFormat,\n },\n };\n\n const result = await engine.install(envelope);\n\n if (options.update) {\n await updateExpected(expectedDir, result);\n output.success(`${fixture}: updated`);\n results.push({ name: fixture, passed: true });\n } else {\n const { errors, diffs } = await compareOutputs(expectedDir, result, options.diff);\n if (errors.length === 0) {\n output.success(`${fixture}: passed`);\n results.push({ name: fixture, passed: true });\n } else {\n output.error(`${fixture}: failed`);\n for (const err of errors) {\n output.info(` ${err}`);\n }\n if (options.diff && diffs && diffs.length > 0) {\n for (const d of diffs) {\n output.info(` --- expected/${d.filename}`);\n output.info(` +++ actual/${d.filename}`);\n const diffLines = d.diff.split('\\n').slice(4);\n for (const line of diffLines) {\n if (line) {\n output.info(` ${line}`);\n }\n }\n }\n }\n results.push({ name: fixture, passed: false, errors, diffs });\n }\n }\n } catch (error) {\n output.error(`${fixture}: error`);\n output.info(` ${(error as Error).message}`);\n results.push({ name: fixture, passed: false, errors: [(error as Error).message] });\n }\n }\n\n output.info('');\n const passed = results.filter((r) => r.passed).length;\n const failed = results.filter((r) => !r.passed).length;\n\n if (failed === 0) {\n output.success(`All ${passed} fixture(s) passed`);\n return EXIT_CODES.SUCCESS;\n } else {\n output.error(`${failed} fixture(s) failed, ${passed} passed`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n}\n\n/**\n * Replaces machine-specific `file://` paths in @tfy-status source fields\n * with a stable placeholder so snapshots are portable across environments.\n */\nfunction normalizeStatusSource(content: string): string {\n return content.replace(/(\"source\"\\s*:\\s*)\"file:\\/\\/[^\"]*\"/g, '$1\"<local>\"');\n}\n\nasync function updateExpected(expectedDir: string, result: InstallResult): Promise<void> {\n await mkdir(expectedDir, { recursive: true });\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const outputPath = join(expectedDir, filePath);\n await writeFile(outputPath, normalizeStatusSource(entry.content), 'utf-8');\n }\n}\n\ninterface CompareResult {\n errors: string[];\n diffs?: Array<{ filename: string; diff: string }>;\n}\n\nasync function compareOutputs(\n expectedDir: string,\n result: InstallResult,\n includeDiffs?: boolean\n): Promise<CompareResult> {\n const errors: string[] = [];\n const diffs: Array<{ filename: string; diff: string }> = [];\n\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const actual = entry.content;\n\n const expectedPath = join(expectedDir, filePath);\n try {\n const expected = await readFile(expectedPath, 'utf-8');\n const normalizedActual = normalizeStatusSource(actual.trim().replace(/\\r\\n/g, '\\n'));\n const normalizedExpected = normalizeStatusSource(expected.trim().replace(/\\r\\n/g, '\\n'));\n\n if (normalizedActual !== normalizedExpected) {\n errors.push(`${filePath}: content mismatch`);\n if (includeDiffs) {\n const diff = generateUnifiedDiff(normalizedExpected, normalizedActual, filePath);\n diffs.push({ filename: filePath, diff });\n }\n }\n } catch {\n errors.push(`${filePath}: expected file not found`);\n }\n }\n\n try {\n const expectedFiles = await readdir(expectedDir);\n for (const file of expectedFiles) {\n if (!result.files.has(file) && file !== 'manifest.json') {\n errors.push(`${file}: unexpected file in expected/`);\n }\n }\n } catch {\n // Expected directory might not exist\n }\n\n return { errors, diffs: includeDiffs ? diffs : undefined };\n}\n","/**\n * tfy-init tpl dev command\n * Watch mode for rapid template iteration.\n *\n * Supports two modes:\n * 1. Schema mode (--schema + --src): reads a pre-computed schema package\n * and a src/ directory. Watches both for changes.\n * 2. Directory mode (--directory): reads bundle.json from a template directory.\n */\n\nimport { Command } from 'commander';\nimport { readFile, mkdir, rm, writeFile, access } from 'node:fs/promises';\nimport { dirname, join, relative, resolve } from 'node:path';\nimport * as yaml from 'js-yaml';\nimport {\n createEngine,\n EngineError,\n EngineErrorCode,\n type Envelope,\n type InstallResult,\n type JSONSchema7,\n type Template,\n type TemplateMetadata,\n} from '@truefoundry/tfy-infra-engine';\nimport { createResolverRegistry } from '../../resolvers/index.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { findDefaultFixture } from '../../utils/fs-helpers.js';\nimport { walkSrcDir } from '../../utils/walk-src.js';\nimport { createWatcher, type WatchEvent } from '../../utils/file-watcher.js';\nimport { generateUnifiedDiff } from '../../utils/diff.js';\n\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\nexport interface DevOptions {\n directory: string;\n schema?: string;\n src?: string;\n name?: string;\n version?: string;\n fixture?: string;\n output: string;\n test?: boolean;\n}\n\nexport function createDevCommand(): Command {\n const cmd = new Command('dev')\n .description('Watch mode for rapid template iteration')\n .option('-d, --directory <dir>', 'Path to template version directory')\n .option(\n '--schema <path>',\n 'Path to schema package JSON (from tfy-schema-tool --schema-package)'\n )\n .option('--src <dir>', 'Path to template src/ directory')\n .option('--name <string>', 'Template name (used with --schema mode)')\n .option('--version <semver>', 'Template version (used with --schema mode)')\n .option('-f, --fixture <path>', 'Input file or fixture to use for testing')\n .requiredOption('-o, --output <dir>', 'Output directory for rendered files')\n .option('--no-test', 'Only validate, do not run tests')\n .action(async (options: DevOptions) => {\n try {\n const exitCode = await runDev(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\nexport { findDefaultFixture } from '../../utils/fs-helpers.js';\n\nexport async function validateTemplateDir(\n versionDir: string\n): Promise<{ valid: boolean; error?: string }> {\n try {\n await access(versionDir);\n } catch {\n return { valid: false, error: `Directory not found: ${versionDir}` };\n }\n\n const bundleJsonPath = join(versionDir, 'bundle.json');\n try {\n await access(bundleJsonPath);\n return { valid: true };\n } catch {\n return { valid: false, error: `bundle.json not found in ${versionDir}` };\n }\n}\n\nexport async function ensureOutputDir(_versionDir: string, customOutput: string): Promise<string> {\n const outputDir = resolve(customOutput);\n\n try {\n await rm(outputDir, { recursive: true, force: true });\n } catch {\n // Directory might not exist\n }\n\n await mkdir(outputDir, { recursive: true });\n return outputDir;\n}\n\nfunction isSchemaMode(options: DevOptions): boolean {\n return !!(options.schema && options.src);\n}\n\nasync function runDev(options: DevOptions): Promise<number> {\n if (isSchemaMode(options)) {\n return runSchemaModeDev(options);\n }\n return runDirectoryModeDev(options);\n}\n\n/**\n * Schema mode: --schema + --src.\n * Reads schema package from file, walks src/, constructs Template in-memory.\n */\nasync function runSchemaModeDev(options: DevOptions): Promise<number> {\n const schemaPath = resolve(options.schema!);\n const srcDir = resolve(options.src!);\n const templateName = options.name ?? 'dev-template';\n const templateVersion = options.version;\n\n let fixturePath: string;\n if (options.fixture) {\n fixturePath = resolve(options.fixture);\n try {\n await access(fixturePath);\n } catch {\n output.error(`Fixture not found: ${options.fixture}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n } else {\n output.error('--fixture is required in schema mode');\n output.info('Use -f to specify an inputs.yaml file.');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n const outputDir = await ensureOutputDir('.', options.output);\n const relativeOutputDir = relative(process.cwd(), outputDir);\n\n output.info(`[schema] ${relative(process.cwd(), schemaPath)}`);\n output.info(`[src] ${relative(process.cwd(), srcDir)}`);\n output.info(`[output] ${relativeOutputDir}/`);\n output.info(`[fixture] ${relative(process.cwd(), fixturePath)}`);\n output.info('');\n\n const engine = createEngine();\n\n const renderFromSchema = async () => {\n try {\n const template = await buildTemplateFromSchema(\n schemaPath,\n srcDir,\n templateName,\n templateVersion\n );\n await renderWithTemplate(engine, template, fixturePath, outputDir, options.test);\n } catch (error) {\n output.error(`[render] ✗ Render failed`);\n output.info(` ${(error as Error).message}`);\n }\n };\n\n await renderFromSchema();\n\n const watchPaths = [schemaPath, srcDir, fixturePath];\n\n const watcher = createWatcher(watchPaths, {\n schemaFile: schemaPath,\n onChange: (event: WatchEvent) => {\n void (async () => {\n output.info(`[changed] ${relative(process.cwd(), event.path)}`);\n await renderFromSchema();\n })();\n },\n onReady: () => {\n output.info('[ready] Watching for changes... (Ctrl+C to exit)');\n output.info('');\n },\n onError: (error) => {\n output.error(`Watch error: ${error.message}`);\n },\n });\n\n return new Promise<number>((resolvePromise) => {\n const cleanup = () => {\n output.info('');\n output.info('[stopped] Watch mode ended');\n void watcher.close();\n resolvePromise(EXIT_CODES.SUCCESS);\n };\n\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n });\n}\n\nfunction extractVersionFromSchema(jsonSchema: JSONSchema7): string | undefined {\n const props = jsonSchema.properties;\n if (!props) return undefined;\n const versionProp = props['version'];\n if (!versionProp || typeof versionProp === 'boolean') return undefined;\n const def = versionProp.default;\n return typeof def === 'string' ? def : undefined;\n}\n\nasync function buildTemplateFromSchema(\n schemaPath: string,\n srcDir: string,\n name: string,\n version?: string\n): Promise<Template> {\n const schemaContent = await readFile(schemaPath, 'utf-8');\n const schemaPkg = JSON.parse(schemaContent) as Record<string, unknown>;\n\n const jsonSchema = schemaPkg['jsonSchema'] as JSONSchema7;\n if (!jsonSchema || typeof jsonSchema !== 'object') {\n throw new Error('Schema package must contain a \"jsonSchema\" object');\n }\n\n const resolvedVersion = version ?? extractVersionFromSchema(jsonSchema) ?? '0.0.0-dev';\n\n const { files, staticFiles } = await walkSrcDir(srcDir);\n\n const metadata: TemplateMetadata = { name, version: resolvedVersion };\n\n return {\n metadata,\n jsonSchema,\n files,\n staticFiles,\n source: `file://${srcDir}`,\n version: { semver: resolvedVersion },\n };\n}\n\nasync function renderWithTemplate(\n engine: ReturnType<typeof createEngine>,\n template: Template,\n fixturePath: string,\n outputDir: string,\n runTests?: boolean\n): Promise<void> {\n const inputsContent = await readFile(fixturePath, 'utf-8');\n const inputs = yaml.load(inputsContent) as Record<string, unknown>;\n\n const envelope: Envelope = {\n template,\n inputs,\n intentId: `dev-${Date.now()}`,\n options: { skipFormat: false },\n };\n\n const result = await engine.install(envelope);\n\n const templateFileCount = Array.from(result.files.keys()).filter(\n (f) => f !== 'manifest.json'\n ).length;\n output.info(`[validate] ✓ ${templateFileCount} template files valid`);\n\n await rm(outputDir, { recursive: true, force: true });\n await mkdir(outputDir, { recursive: true });\n\n let filesWritten = 0;\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const outputPath = join(outputDir, filePath);\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, entry.content, 'utf-8');\n filesWritten++;\n }\n output.info(\n `[render] ✓ ${filesWritten} files written to ${relative(process.cwd(), outputDir)}/`\n );\n\n if (runTests !== false) {\n await runTestComparisonFromResult(result, outputDir);\n }\n}\n\n/**\n * Directory mode: --directory (legacy, uses FileResolver).\n */\nasync function runDirectoryModeDev(options: DevOptions): Promise<number> {\n const versionDir = resolve(options.directory || '.');\n\n const validation = await validateTemplateDir(versionDir);\n if (!validation.valid) {\n output.error(validation.error!);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n let fixturePath: string;\n if (options.fixture) {\n fixturePath = resolve(versionDir, options.fixture);\n try {\n await access(fixturePath);\n } catch {\n output.error(`Fixture not found: ${options.fixture}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n } else {\n const defaultFixture = await findDefaultFixture(versionDir);\n if (!defaultFixture) {\n output.error('No default fixture found at tests/default/inputs.yaml');\n output.info('Either create a default fixture or use --fixture to specify one.');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n fixturePath = defaultFixture;\n }\n\n const outputDir = await ensureOutputDir(versionDir, options.output);\n const relativeOutputDir = relative(process.cwd(), outputDir);\n\n output.info(`[watching] ${relative(process.cwd(), versionDir)}`);\n output.info(`[output] ${relativeOutputDir}/`);\n output.info(`[fixture] ${relative(process.cwd(), fixturePath)}`);\n output.info('');\n\n const engine = createEngine();\n const templateUri = `file://${versionDir}`;\n\n await runRenderCycle(engine, templateUri, versionDir, fixturePath, outputDir, options.test);\n\n const watcher = createWatcher(versionDir, {\n onChange: (event: WatchEvent) => {\n void (async () => {\n const relPath = relative(versionDir, event.path);\n output.info(`[changed] ${relPath}`);\n\n if (event.fileType === 'schema') {\n await runValidation(templateUri);\n } else if (event.fileType === 'template' || event.fileType === 'inputs') {\n await runRenderCycle(\n engine,\n templateUri,\n versionDir,\n fixturePath,\n outputDir,\n options.test\n );\n }\n })();\n },\n onReady: () => {\n output.info('[ready] Watching for changes... (Ctrl+C to exit)');\n output.info('');\n },\n onError: (error) => {\n output.error(`Watch error: ${error.message}`);\n },\n });\n\n return new Promise<number>((resolvePromise) => {\n const cleanup = () => {\n output.info('');\n output.info('[stopped] Watch mode ended');\n void watcher.close();\n resolvePromise(EXIT_CODES.SUCCESS);\n };\n\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n });\n}\n\nasync function runValidation(templateUri: string): Promise<boolean> {\n try {\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(templateUri, { noCache: true });\n if (template.jsonSchema) {\n output.info('[validate] ✓ schema valid');\n return true;\n }\n output.error('[validate] ✗ schema invalid');\n return false;\n } catch (error) {\n output.error(`[validate] ✗ schema invalid`);\n output.info(` ${(error as Error).message}`);\n return false;\n }\n}\n\nasync function runRenderCycle(\n engine: ReturnType<typeof createEngine>,\n templateUri: string,\n versionDir: string,\n fixturePath: string,\n outputDir: string,\n runTests?: boolean\n): Promise<void> {\n const valid = await runValidation(templateUri);\n if (!valid) return;\n\n try {\n const inputsContent = await readFile(fixturePath, 'utf-8');\n const inputs = yaml.load(inputsContent) as Record<string, unknown>;\n\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(templateUri, { noCache: true });\n\n const envelope: Envelope = {\n template,\n inputs,\n intentId: `dev-${Date.now()}`,\n options: { skipFormat: false },\n };\n\n const result = await engine.install(envelope);\n\n const templateFileCount = Array.from(result.files.keys()).filter(\n (f) => f !== 'manifest.json'\n ).length;\n output.info(`[validate] ✓ ${templateFileCount} template files valid`);\n\n await rm(outputDir, { recursive: true, force: true });\n await mkdir(outputDir, { recursive: true });\n\n let filesWritten = 0;\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const outputPath = join(outputDir, filePath);\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, entry.content, 'utf-8');\n filesWritten++;\n }\n output.info(\n `[render] ✓ ${filesWritten} files written to ${relative(process.cwd(), outputDir)}/`\n );\n\n if (runTests !== false) {\n await runTestComparison(versionDir, result, outputDir);\n }\n } catch (error) {\n output.error(`[render] ✗ Render failed`);\n output.info(` ${(error as Error).message}`);\n if (\n error instanceof EngineError &&\n error.code === EngineErrorCode.INPUT_VALIDATION_FAILED &&\n error.details?.['errors']\n ) {\n output.info(\n output.formatValidationErrors(\n error.details['errors'] as Array<{ path: string; message: string }>\n )\n );\n }\n }\n}\n\nasync function runTestComparison(\n versionDir: string,\n result: InstallResult,\n _outputDir: string\n): Promise<void> {\n const expectedDir = join(versionDir, 'tests', 'default', 'expected');\n await runTestComparisonFromDir(result, expectedDir);\n}\n\nasync function runTestComparisonFromResult(\n _result: InstallResult,\n _outputDir: string\n): Promise<void> {\n // In schema mode, we don't have a versionDir to find expected/ in.\n // Test comparison is skipped unless running from directory mode.\n}\n\nasync function runTestComparisonFromDir(result: InstallResult, expectedDir: string): Promise<void> {\n const startTime = Date.now();\n\n try {\n await access(expectedDir);\n } catch {\n output.info('[test] ⚠ No expected/ directory, skipping comparison');\n return;\n }\n\n const errors: Array<{ file: string; diff?: string }> = [];\n\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const actual = entry.content;\n\n const expectedPath = join(expectedDir, filePath);\n try {\n const expected = await readFile(expectedPath, 'utf-8');\n const normalizedActual = actual.trim().replace(/\\r\\n/g, '\\n');\n const normalizedExpected = expected.trim().replace(/\\r\\n/g, '\\n');\n\n if (normalizedActual !== normalizedExpected) {\n const diff = generateUnifiedDiff(normalizedExpected, normalizedActual, filePath);\n errors.push({ file: filePath, diff });\n }\n } catch {\n errors.push({ file: filePath });\n }\n }\n\n const duration = Date.now() - startTime;\n\n if (errors.length === 0) {\n output.info(`[test] ✓ default: passed (${duration}ms)`);\n } else {\n output.error(`[test] ✗ default: failed`);\n for (const err of errors) {\n output.info(` ${err.file}: content mismatch`);\n if (err.diff) {\n const diffLines = err.diff.split('\\n').slice(4, 14);\n for (const line of diffLines) {\n if (line) {\n output.info(` ${line}`);\n }\n }\n if (err.diff.split('\\n').length > 14) {\n output.info(` ... (diff truncated)`);\n }\n }\n }\n }\n}\n","/**\n * File watcher utility\n * Watches template directories for changes and emits events\n * @file packages/infra-cli/src/utils/file-watcher.ts\n */\n\nimport chokidar, { type FSWatcher } from 'chokidar';\nimport { basename } from 'node:path';\n\n/**\n * Type of file change that occurred\n */\nexport type FileType = 'schema' | 'template' | 'inputs' | 'other';\n\n/**\n * Event emitted when a file changes\n */\nexport interface WatchEvent {\n type: 'change' | 'add' | 'unlink';\n path: string;\n fileType: FileType;\n}\n\n/**\n * Options for creating a file watcher\n */\nexport interface WatchOptions {\n /**\n * Callback when a file changes\n */\n onChange?: (event: WatchEvent) => void;\n\n /**\n * Callback when watcher is ready\n */\n onReady?: () => void;\n\n /**\n * Callback when an error occurs\n */\n onError?: (error: Error) => void;\n\n /**\n * Patterns to ignore\n */\n ignored?: string | RegExp | ((path: string) => boolean);\n\n /**\n * Debounce delay in milliseconds\n */\n debounceMs?: number;\n\n /**\n * Path to the schema file for classifying changes.\n */\n schemaFile?: string;\n}\n\n/**\n * Classify a file path by its type.\n *\n * @param path - File path to classify\n * @param schemaFile - Optional path to the schema file to match against\n * @returns File type classification\n */\nexport function classifyFileChange(path: string, schemaFile?: string): FileType {\n const normalizedPath = path.replace(/\\\\/g, '/');\n const filename = basename(normalizedPath);\n\n if (schemaFile && normalizedPath.endsWith(schemaFile.replace(/\\\\/g, '/'))) {\n return 'schema';\n }\n\n if (filename === 'bundle.json' || filename === 'schema-pkg.json') {\n return 'schema';\n }\n\n const inSrcDir = /\\/src\\//.test(normalizedPath);\n if (inSrcDir && !filename.startsWith('_')) {\n return 'template';\n }\n\n if (filename === 'inputs.yaml') {\n return 'inputs';\n }\n\n return 'other';\n}\n\n/**\n * Create a file watcher for one or more paths.\n *\n * @param paths - Path(s) to watch (directory or file)\n * @param options - Watcher options\n * @returns FSWatcher instance\n */\nexport function createWatcher(paths: string | string[], options: WatchOptions = {}): FSWatcher {\n const {\n onChange,\n onReady,\n onError,\n ignored = /(^|[/\\\\])\\..|(^|[/\\\\])node_modules($|[/\\\\])|(^|[/\\\\])dev($|[/\\\\])/,\n debounceMs = 100,\n schemaFile,\n } = options;\n\n const watcher = chokidar.watch(paths, {\n ignored,\n persistent: true,\n ignoreInitial: true,\n awaitWriteFinish: {\n stabilityThreshold: debounceMs,\n pollInterval: 50,\n },\n });\n\n const handleEvent = (type: WatchEvent['type']) => (path: string) => {\n const fileType = classifyFileChange(path, schemaFile);\n const event: WatchEvent = { type, path, fileType };\n\n if (onChange) {\n onChange(event);\n }\n };\n\n watcher.on('change', handleEvent('change'));\n watcher.on('add', handleEvent('add'));\n watcher.on('unlink', handleEvent('unlink'));\n\n if (onReady) {\n watcher.on('ready', onReady);\n }\n\n if (onError) {\n watcher.on('error', (err: unknown) =>\n onError(err instanceof Error ? err : new Error(String(err)))\n );\n }\n\n return watcher;\n}\n","/**\n * tfy-init tpl verify command\n *\n * Checks integrity of files on disk against a manifest.json.\n * Wraps engine.verify() to produce human-readable or JSON drift reports.\n *\n */\n\nimport { Command } from 'commander';\nimport { join, resolve } from 'node:path';\nimport { createEngine } from '@truefoundry/tfy-infra-engine';\nimport type { DriftReport } from '@truefoundry/tfy-infra-engine';\nimport { loadManifest } from '../../utils/manifest-loader.js';\nimport { readDirectoryToFileMap } from '../../utils/file-reader.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { driftTypeTag } from '../../utils/output.js';\n\n// Re-export for backward compatibility\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\n/** Verify-specific exit code for drift detected (distinct from error). */\nconst DRIFT_DETECTED = 1;\n\ninterface VerifyOptions {\n directory: string;\n json?: boolean;\n}\n\n/**\n * Create the verify command.\n */\nexport function createVerifyCommand(): Command {\n const cmd = new Command('verify')\n .description('Check integrity of files on disk against a manifest')\n .option('-d, --directory <dir>', 'Path to cluster directory containing manifest.json', '.')\n .option('--json', 'Output drift report as structured JSON')\n .action(async (options: VerifyOptions) => {\n try {\n const exitCode = await runVerify(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\n/**\n * Execute the verify command.\n * Exported for testing.\n */\nexport async function runVerify(options: VerifyOptions): Promise<number> {\n const dir = resolve(options.directory);\n const manifestPath = join(dir, 'manifest.json');\n\n // Load manifest\n output.verbose(`Loading manifest from ${manifestPath}`);\n const manifest = await loadManifest(manifestPath);\n\n // Read current files on disk\n output.verbose(`Reading files from ${dir}`);\n const currentFiles = await readDirectoryToFileMap(dir, manifest.platformPrefix);\n\n // Create engine and verify\n const engine = createEngine();\n const result = await engine.verify(currentFiles, manifest);\n\n // Output the report\n if (options.json) {\n output.info(formatDriftReportJson(result.driftReport));\n } else {\n output.info(formatDriftReport(result.driftReport));\n }\n\n return result.driftReport.valid ? EXIT_CODES.SUCCESS : DRIFT_DETECTED;\n}\n\n/**\n * Format a drift report as human-readable text.\n * Exported for testing.\n */\nexport function formatDriftReport(report: DriftReport): string {\n const lines: string[] = [];\n const issueCount =\n report.summary.driftedFiles + report.summary.missingFiles + report.summary.unexpectedFiles;\n\n if (report.valid) {\n lines.push(\n `✓ Verification passed: ${report.summary.totalFiles} files checked, ${issueCount} issues`\n );\n lines.push('');\n lines.push(`Aggregate Hash: ${report.aggregateHash.expected}`);\n } else {\n lines.push(`✗ Drift detected: ${issueCount} issues found`);\n lines.push('');\n\n for (const entry of report.entries) {\n const tag = driftTypeTag(entry.type);\n lines.push(` ${tag} ${entry.path}`);\n lines.push(` ${entry.details}`);\n lines.push('');\n }\n\n lines.push(\n `Summary: ${report.summary.totalFiles} files checked, ` +\n `${report.summary.driftedFiles} drifted, ` +\n `${report.summary.missingFiles} missing` +\n (report.summary.unexpectedFiles > 0 ? `, ${report.summary.unexpectedFiles} unexpected` : '')\n );\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format a drift report as structured JSON.\n * Exported for testing.\n */\nexport function formatDriftReportJson(report: DriftReport): string {\n return JSON.stringify(report, null, 2);\n}\n"],"mappings":"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","names":["import_commander","import_commander","import_commander","import_promises","import_node_path","import_promises","import_node_path","walkSrcDir","rootDir","files","staticFiles","walk","relativePath","currentDir","entries","entry","entryRelative","content","outputPath","import_tfy_infra_engine","currentVerbosity","jsonMode","setVerbosity","level","getVerbosity","setJsonMode","enabled","data","message","success","currentVerbosity","jsonMode","error","info","verbose","warn","formatSize","bytes","formatValidationErrors","errors","driftTypeTag","type","printFileList","files","write","file","EXIT_CODES","handleEngineError","error","fallbackExitCode","getVerbosity","info","key","value","formatValidationErrors","createBundleCommand","options","runBundle","error","handleEngineError","readSchemaPackage","schemaPath","raw","readStdin","parsed","err","obj","resolve","reject","chunks","chunk","extractVersionFromSchema","jsonSchema","props","versionProp","def","schemaPkg","version","srcDir","files","staticFiles","walkSrcDir","metadata","bundle","json","outPath","success","import_commander","import_node_readline","import_promises","import_node_path","yaml","import_tfy_infra_engine","import_tfy_infra_engine","import_promises","import_node_path","import_tfy_infra_engine","FileResolver","uri","_options","fsPath","info","bundlePath","error","nodeError","content","raw","rawMetadata","metadata","jsonSchema","files","staticFiles","version","path","import_tfy_infra_engine","DEFAULT_RESOLVER_OPTIONS","mergeResolverOptions","options","withRetry","fn","retries","baseDelay","lastError","attempt","error","delay","resolve","withTimeout","promise","ms","message","timer","timeout","_","reject","JFROG_TOKEN_ENV","isArtifactoryHost","uri","hostname","buildHeaders","headers","token","fetchBundle","response","hint","unpackBundle","raw","metadata","jsonSchema","files","staticFiles","version","HttpsBundleResolver","options","opts","mergeResolverOptions","withTimeout","withRetry","import_promises","import_node_path","import_node_crypto","import_tfy_infra_engine","getDefaultCacheDir","home","TemplateCache","cacheDir","source","version","key","safeVersion","cachePath","bundlePath","bundleContent","parsed","metadata","jsonSchema","record","files","staticFiles","versionInfo","template","bundle","error","keyPath","sources","templates","size","sourceKeys","versions","versionPath","dir","total","entries","entry","fullPath","fileStat","ResolverRegistry","resolvers","cacheDir","TemplateCache","FileResolver","HttpsBundleResolver","resolver","uri","options","scheme","template","createResolverRegistry","import_promises","yaml","import_promises","import_node_path","fileExists","filePath","findDefaultFixture","versionDir","defaultPath","loadConfig","filePath","content","isJson","parsed","error","format","import_promises","loadManifest","manifestPath","content","error","parsed","requiredFields","field","envelopeFromManifest","manifest","import_promises","import_node_path","import_tfy_infra_engine","readDirectoryToFileMap","dir","prefix","fileMap","entries","e","error","filename","content","zone","entry","header","import_promises","import_node_path","writeEngineOutput","dir","result","mode","filePath","entry","fullPath","fileExists","import_diff","generateUnifiedDiff","expected","actual","filename","normalizedExpected","normalizedActual","validateRenderArgs","args","createRenderCommand","collect","options","exitCode","runRender","error","handleEngineError","value","previous","parseInputOverrides","inputs","result","input","eqIndex","key","setJsonMode","outputDir","timeout","runRenderFromManifest","manifestPath","fileExists","runRenderFromConfigUpgrade","runRenderFromConfig","templateDir","runRenderFromDirectory","path","confirmPrompt","message","rl","resolve","answer","countExistingFiles","dir","e","guardOverwrite","count","warn","data","info","EXIT_CODES","verbose","manifest","loadManifest","rawConfig","envelopeFromManifest","success","envelope","createResolverRegistry","handleInstallDryRun","aborted","writeEngineOutput","printInstallResult","templateUri","fixturePath","findDefaultFixture","fixtureContent","overrides","intentId","engine","configFile","loadConfig","previousManifest","currentFiles","readDirectoryToFileMap","printDriftReport","handleUpgradeDryRun","formatUpgradeResultJson","formatUpgradeResult","computeInstallDiffs","newFiles","diffs","filename","entry","existing","generateUnifiedDiff","computeUpgradeDiffs","currentContent","printDiffs","d","report","json","total","driftTypeTag","fileList","filePath","printFileList","lines","platformCount","userCount","import_commander","import_promises","import_node_path","import_handlebars","import_tfy_infra_engine","createValidateCommand","options","exitCode","runValidate","error","handleEngineError","fileExists","path","templateDir","issues","hasErrors","info","bundlePath","EXIT_CODES","verbose","content","parsed","rawMeta","jsonSchema","success","files","staticFiles","fileCount","staticCount","relPath","Handlebars","warnings","i","errors","warn","issue","prefix","import_commander","import_promises","import_node_path","yaml","import_tfy_infra_engine","createTestCommand","options","exitCode","runTest","error","handleEngineError","templateDir","testsDir","templateUri","info","fixtures","entries","entry","inputsPath","EXIT_CODES","template","createResolverRegistry","engine","results","fixture","fixtureDir","expectedDir","verbose","inputsContent","inputs","envelope","result","updateExpected","success","errors","diffs","compareOutputs","err","d","diffLines","line","passed","r","failed","normalizeStatusSource","content","filePath","outputPath","includeDiffs","actual","expectedPath","expected","normalizedActual","normalizedExpected","diff","generateUnifiedDiff","expectedFiles","file","import_commander","import_promises","import_node_path","yaml","import_tfy_infra_engine","import_chokidar","import_node_path","classifyFileChange","path","schemaFile","normalizedPath","filename","createWatcher","paths","options","onChange","onReady","onError","ignored","debounceMs","watcher","chokidar","handleEvent","type","fileType","err","createDevCommand","options","exitCode","runDev","error","handleEngineError","validateTemplateDir","versionDir","bundleJsonPath","ensureOutputDir","_versionDir","customOutput","outputDir","isSchemaMode","runSchemaModeDev","runDirectoryModeDev","schemaPath","srcDir","templateName","templateVersion","fixturePath","EXIT_CODES","info","relativeOutputDir","engine","renderFromSchema","template","buildTemplateFromSchema","renderWithTemplate","watcher","createWatcher","event","resolvePromise","cleanup","extractVersionFromSchema","jsonSchema","props","versionProp","def","name","version","schemaContent","resolvedVersion","files","staticFiles","walkSrcDir","runTests","inputsContent","inputs","envelope","result","templateFileCount","f","filesWritten","filePath","entry","outputPath","runTestComparisonFromResult","validation","defaultFixture","findDefaultFixture","templateUri","runRenderCycle","relPath","runValidation","createResolverRegistry","runTestComparison","formatValidationErrors","_outputDir","expectedDir","runTestComparisonFromDir","_result","startTime","errors","actual","expectedPath","expected","normalizedActual","normalizedExpected","diff","generateUnifiedDiff","duration","err","diffLines","line","import_commander","import_node_path","import_tfy_infra_engine","DRIFT_DETECTED","createVerifyCommand","options","exitCode","runVerify","error","handleEngineError","dir","manifestPath","verbose","manifest","loadManifest","currentFiles","readDirectoryToFileMap","result","info","formatDriftReportJson","formatDriftReport","EXIT_CODES","report","lines","issueCount","entry","tag","driftTypeTag","createTplCommand","tpl","createBundleCommand","createDevCommand","createRenderCommand","createValidateCommand","createTestCommand","createVerifyCommand","VERSION","main","program","thisCommand","opts","setVerbosity","createTplCommand","error"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truefoundry/tfy-infra-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "CLI for TrueFoundry infrastructure templating engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -19,13 +19,12 @@
|
|
|
19
19
|
"prepublishOnly": "yarn build"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@truefoundry/tfy-infra-engine": "0.1.
|
|
22
|
+
"@truefoundry/tfy-infra-engine": "0.1.4",
|
|
23
23
|
"chokidar": "^5.0.0",
|
|
24
24
|
"commander": "^14.0.3",
|
|
25
25
|
"diff": "^8.0.3",
|
|
26
26
|
"handlebars": "^4.7.8",
|
|
27
|
-
"js-yaml": "^4.1.0"
|
|
28
|
-
"tar": "^7.5.9"
|
|
27
|
+
"js-yaml": "^4.1.0"
|
|
29
28
|
},
|
|
30
29
|
"devDependencies": {
|
|
31
30
|
"@types/diff": "^8.0.0",
|