@truefoundry/tfy-infra-cli 0.0.0-canary.edab06d
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 +81 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# @truefoundry/tfy-infra-cli
|
|
2
|
+
|
|
3
|
+
CLI for TrueFoundry infrastructure templating engine.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
yarn global add @truefoundry/tfy-infra-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run directly with npx:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @truefoundry/tfy-infra-cli tpl render -c config.yaml
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Commands
|
|
18
|
+
|
|
19
|
+
| Command | Description |
|
|
20
|
+
|---------|-------------|
|
|
21
|
+
| `tfy-init tpl render` | Render a template to HCL files |
|
|
22
|
+
| `tfy-init tpl schema` | Fetch and display a template's JSON Schema |
|
|
23
|
+
| `tfy-init tpl validate` | Validate a local template (for authors) |
|
|
24
|
+
| `tfy-init tpl test` | Run test fixtures against a template |
|
|
25
|
+
| `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
|
+
| `tfy-init tpl dev` | Watch mode for rapid template iteration |
|
|
29
|
+
|
|
30
|
+
For detailed usage, options, exit codes, and examples for each command, see the **[CLI Reference](../../docs/cli-reference.md)**.
|
|
31
|
+
|
|
32
|
+
## Supported Template Sources
|
|
33
|
+
|
|
34
|
+
- **Local files**: `file:///absolute/path` or `file://./relative/path`
|
|
35
|
+
- **GitHub**: `github://owner/repo/path/v1/1.0.0`
|
|
36
|
+
|
|
37
|
+
## Global Options
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
--verbose Enable verbose output
|
|
41
|
+
-q, --quiet Suppress non-error output
|
|
42
|
+
-v, --version Show version
|
|
43
|
+
-h, --help Display help
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Start watch mode for rapid iteration
|
|
50
|
+
tfy-init tpl dev -d ./templates/my-service/v1
|
|
51
|
+
|
|
52
|
+
# Validate your template
|
|
53
|
+
tfy-init tpl validate -d ./templates/my-service/v1
|
|
54
|
+
|
|
55
|
+
# Run tests
|
|
56
|
+
tfy-init tpl test -d ./templates/my-service/v1
|
|
57
|
+
|
|
58
|
+
# Render from a config file
|
|
59
|
+
tfy-init tpl render -c config.yaml -o ./terraform
|
|
60
|
+
|
|
61
|
+
# Verify rendered files against their manifest
|
|
62
|
+
tfy-init tpl verify -d ./terraform
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Exit Codes
|
|
66
|
+
|
|
67
|
+
All commands use a shared set of exit codes:
|
|
68
|
+
|
|
69
|
+
| Code | Name | Description |
|
|
70
|
+
|------|------|-------------|
|
|
71
|
+
| 0 | SUCCESS | Operation completed successfully |
|
|
72
|
+
| 1 | VALIDATION_ERROR | Input validation or schema error |
|
|
73
|
+
| 2 | FETCH_ERROR | Template fetch failed (not found, network) |
|
|
74
|
+
| 3 | RENDER_ERROR | Template rendering failed |
|
|
75
|
+
| 4 | FORMAT_ERROR | `tofu fmt` failed |
|
|
76
|
+
|
|
77
|
+
Some commands define additional codes for domain-specific outcomes (e.g., `DRIFT_DETECTED` for `verify`, `DRIFT_BLOCKED` for `upgrade`). See the [CLI Reference](../../docs/cli-reference.md) for details.
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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 ht(e){switch(e){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 yt(e){if(L!=="quiet"){console.log("Files:");for(let t of e)console.log(` - ${t.path} (${tr(t.size)})`)}}var d={SUCCESS:0,VALIDATION_ERROR:1,FETCH_ERROR:2,RENDER_ERROR:3,FORMAT_ERROR:4};function b(e,t=d.RENDER_ERROR){if(e instanceof F.EngineError){if(w(e.message),Nt()==="verbose"&&e.details){a(""),a("Details:");for(let[r,n]of Object.entries(e.details))r!=="errors"&&a(` ${r}: ${JSON.stringify(n)}`)}switch(e.details?.errors&&(a(""),a("Errors:"),a(gt(e.details.errors))),e.code){case F.EngineErrorCode.INPUT_VALIDATION_FAILED:case F.EngineErrorCode.SCHEMA_INVALID:case F.EngineErrorCode.ENVELOPE_VALIDATION_FAILED:process.exit(d.VALIDATION_ERROR);break;case F.EngineErrorCode.TEMPLATE_NOT_FOUND:case F.EngineErrorCode.FILE_NOT_FOUND:case F.EngineErrorCode.NETWORK_ERROR:case F.EngineErrorCode.GITHUB_NOT_FOUND:process.exit(d.FETCH_ERROR);break;case F.EngineErrorCode.TOFU_NOT_FOUND:case F.EngineErrorCode.TOFU_FMT_FAILED:a(""),a("Run with --skip-format to skip formatting."),process.exit(d.FORMAT_ERROR);break;case F.EngineErrorCode.MANIFEST_PARSE_ERROR:process.exit(d.VALIDATION_ERROR);break;default:process.exit(t)}}w(e.message),Nt()==="verbose"&&console.error(e.stack),process.exit(t)}function er(e){if([e.config,e.fromManifest].filter(Boolean).length>1)throw new Error("--config and --from-manifest are mutually exclusive. Use one or the other.");if(e.config&&e.directory!==".")throw new Error("--config and --directory are mutually exclusive. Use one or the other.")}function oe(){return new ee.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)",rr,[]).requiredOption("-o, --output <dir>","Output directory").option("--from-manifest <path>","Path to manifest.json to use as input source").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 t=>{try{t.config&&(t.config=(0,Z.resolve)(t.config)),t.fromManifest&&(t.fromManifest=(0,Z.resolve)(t.fromManifest)),er({config:t.config,directory:t.directory,fromManifest:t.fromManifest});let r=await or(t);process.exit(r)}catch(r){b(r)}})}function rr(e,t){return[...t,e]}function ne(e){let t={};if(!e)return t;for(let r of e){let n=r.indexOf("=");if(n>0){let o=r.substring(0,n),i=r.substring(n+1);t[o]=i}}return t}async function or(e){let t=(0,Z.resolve)(e.output),r=parseInt(e.timeout,10);if(e.fromManifest)return nr(e.fromManifest,e,t,r);if(e.config)return sr(e.config,e,t,r);let n=(0,Z.resolve)(e.directory);return ir(n,e,t,r)}async function nr(e,t,r,n){g(`Loading manifest from ${e}`);let o=await _(e),i=K(o);t.intentId&&(i.intentId=t.intentId),v(`Template: ${i.template}`),g(`Intent ID: ${i.intentId}`),g("Resolving template...");let c={template:await I().resolve(i.template,{noCache:t.cache===!1,timeout:n}),inputs:i.inputs,intentId:i.intentId,platformPrefix:i.platformPrefix,options:{skipFormat:t.skipFormat}};g("Re-rendering from manifest...");let m=await(0,vt.createEngine)().install(c);v("Inputs validated"),v(`Generated ${m.files.size-1} files + manifest.json`),t.skipFormat||v("Formatted with tofu fmt"),await Y(r,m,"install");let p=[];for(let[l,y]of m.files)p.push({path:l,size:Buffer.byteLength(y.content,"utf-8")});return a(""),a(`Output written to: ${r}`),a(`Aggregate Hash: ${m.manifest.aggregateHash}`),yt(p),d.SUCCESS}async function ir(e,t,r,n){let o=`file://${e}`,i=t.fixture?(0,Z.resolve)(e,t.fixture):null;if(i)try{await(0,wt.access)(i)}catch{return w(`Fixture not found: ${t.fixture}`),d.VALIDATION_ERROR}else if(i=await q(e),!i)return w("No default fixture found at tests/default/inputs.yaml"),a("Either create a default fixture or use --fixture to specify one."),d.VALIDATION_ERROR;let u=await(0,wt.readFile)(i,"utf-8"),s=re.load(u),c=ne(t.input);s={...s,...c};let f=t.intentId??s.intentId??`local-${Date.now()}`;v(`Template: ${o}`),g(`Fixture: ${i}`),g(`Intent ID: ${f}`),g("Resolving template...");let l={template:await I().resolve(o,{noCache:t.cache===!1,timeout:n}),inputs:s,intentId:f,options:{skipFormat:t.skipFormat}},y=(0,vt.createEngine)();g("Validating inputs...");let $=await y.install(l);v("Inputs validated"),v(`Generated ${$.files.size-1} files + manifest.json`),t.skipFormat||v("Formatted with tofu fmt"),await Y(r,$,"install");let C=[];for(let[M,P]of $.files)C.push({path:M,size:Buffer.byteLength(P.content,"utf-8")});return a(""),a(`Output written to: ${r}`),a(`Aggregate Hash: ${$.manifest.aggregateHash}`),yt(C),d.SUCCESS}async function sr(e,t,r,n){g(`Loading config from ${e}`);let o=await X(e);if(t.intentId&&(o.intentId=t.intentId),t.input&&t.input.length>0){let p=ne(t.input);o.inputs={...o.inputs||{},...p}}v(`Template: ${o.template}`),g(`Intent ID: ${o.intentId}`),g("Resolving template...");let s={template:await I().resolve(o.template,{noCache:t.cache===!1,timeout:n}),inputs:o.inputs,intentId:o.intentId,platformPrefix:o.platformPrefix,options:{skipFormat:t.skipFormat??o.options?.skipFormat}},c=(0,vt.createEngine)();g("Validating inputs...");let f=await c.install(s);v("Inputs validated"),v(`Generated ${f.files.size-1} files + manifest.json`),s.options?.skipFormat||v("Formatted with tofu fmt"),await Y(r,f,"install");let m=[];for(let[p,l]of f.files)m.push({path:p,size:Buffer.byteLength(l.content,"utf-8")});return a(""),a(`Output written to: ${r}`),a(`Aggregate Hash: ${f.manifest.aggregateHash}`),yt(m),d.SUCCESS}var ie=require("commander"),Et=require("fs/promises"),ct=require("path"),se=V(require("js-yaml"));function ae(){return new ie.Command("schema").description("Fetch and display the schema for a template").option("--template <uri>","Template source URI (github://, file://)").option("--format <type>","Output format: json, yaml","json").option("-o, --output <file>","Output file path (default: inputs.schema.json in template dir)").option("--full","Include full template.json (metadata + schema)").option("--no-cache","Force re-fetch template").option("--timeout <ms>","Network timeout in milliseconds","30000").action(async t=>{try{if(!t.template)throw new Error("--template <uri> is required.");t.output&&(t.output=(0,ct.resolve)(t.output));let r=await cr(t);process.exit(r)}catch(r){b(r)}})}async function ar(e,t){await(0,Et.mkdir)((0,ct.dirname)(t),{recursive:!0}),await(0,Et.writeFile)(t,JSON.stringify(e,null,2),"utf-8")}async function cr(e){let t=e.template,r=parseInt(e.timeout,10);g(`Fetching schema from ${t}`);let o=await I().resolve(t,{noCache:e.cache===!1,timeout:r}),i;return e.full?i={template:{name:o.metadata.name,version:o.metadata.version,...o.metadata.description?{description:o.metadata.description}:{}},schema:o.jsonSchema}:i=o.jsonSchema,e.output?(await ar(i,e.output),v(`JSON Schema written to: ${e.output}`),d.SUCCESS):(e.format==="yaml"?console.log(se.dump(i,{indent:2,lineWidth:100})):console.log(JSON.stringify(i,null,2)),d.SUCCESS)}var ce=require("commander"),fe=require("fs/promises"),ft=require("path"),ue=V(require("handlebars")),Q=require("@truefoundry/tfy-infra-engine");function pe(){return new ce.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 t=>{try{let r=await fr(t);process.exit(r)}catch(r){b(r)}})}async function fr(e){let t=(0,ft.resolve)(e.directory),r=[],n=!1;a(`Validating template at: ${t}`),a(""),g("Checking template.json...");let o=(0,ft.join)(t,"template.json");try{let c=await(0,fe.readFile)(o,"utf-8"),f;try{f=JSON.parse(c)}catch(p){throw new Error(`Invalid JSON: ${p.message}`,{cause:p})}let{jsonSchema:m}=(0,Q.validateTemplateJson)(f);(0,Q.validateJsonSchemaStructure)(m),v("template.json is valid")}catch(c){r.push({type:"error",message:c.message,file:"template.json"}),n=!0}g("Checking template files in src/...");let i=(0,ft.join)(t,"src");try{let{files:c,staticFiles:f}=await G(i);if(c.size===0&&f.size===0)r.push({type:"error",message:"No template files found in src/"}),n=!0;else{for(let[m,p]of c)try{ue.default.precompile(p),g(` \u2713 ${m}.hbs`)}catch(l){r.push({type:"error",message:`Invalid Handlebars syntax: ${l.message}`,file:`src/${m}.hbs`}),n=!0}v(`Found ${c.size} HBS template(s)`+(f.size>0?` and ${f.size} static file(s)`:"")+" with valid syntax")}}catch(c){r.push({type:"error",message:`Cannot read src/ directory: ${c.message}`}),n=!0}e.skipFormat||(g("Checking tofu fmt availability..."),await(0,Q.isTofuAvailable)()?v("tofu fmt available"):r.push({type:"warning",message:"tofu not found - format validation skipped"})),a("");let u=r.filter(c=>c.type==="warning"),s=r.filter(c=>c.type==="error");if(u.length>0){te(`${u.length} warning(s):`);for(let c of u){let f=c.file?`${c.file}: `:"";a(` \u2022 ${f}${c.message}`)}a("")}if(s.length>0){w(`${s.length} error(s):`);for(let c of s){let f=c.file?`${c.file}: `:"";a(` \u2022 ${f}${c.message}`)}a("")}return n?(w("Template validation failed"),d.VALIDATION_ERROR):e.strict&&u.length>0?(w("Template validation failed (strict mode)"),d.VALIDATION_ERROR):(v("Template is valid"),d.SUCCESS)}var le=require("commander"),S=require("fs/promises"),N=require("path"),de=V(require("js-yaml")),ge=require("@truefoundry/tfy-infra-engine");var me=require("diff");function Rt(e,t,r){let n=e.replace(/\r\n/g,`
|
|
4
|
+
`),o=t.replace(/\r\n/g,`
|
|
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
|
+
`).slice(4);for(let kt of He)kt&&a(` ${kt}`)}c.push({name:p,passed:!1,errors:j,diffs:W})}}}catch(C){w(`${p}: error`),a(` ${C.message}`),c.push({name:p,passed:!1,errors:[C.message]})}}a("");let f=c.filter(p=>p.passed).length,m=c.filter(p=>!p.passed).length;return m===0?(v(`All ${f} fixture(s) passed`),d.SUCCESS):(w(`${m} fixture(s) failed, ${f} passed`),d.VALIDATION_ERROR)}function _t(e){return e.replace(/("source"\s*:\s*)"file:\/\/[^"]*"/g,'$1"<local>"')}async function pr(e,t){await(0,S.mkdir)(e,{recursive:!0});for(let[r,n]of t.files){if(r==="manifest.json")continue;let o=(0,N.join)(e,r);await(0,S.writeFile)(o,_t(n.content),"utf-8")}}async function mr(e,t,r){let n=[],o=[];for(let[i,u]of t.files){if(i==="manifest.json")continue;let s=u.content,c=(0,N.join)(e,i);try{let f=await(0,S.readFile)(c,"utf-8"),m=_t(s.trim().replace(/\r\n/g,`
|
|
7
|
+
`)),p=_t(f.trim().replace(/\r\n/g,`
|
|
8
|
+
`));if(m!==p&&(n.push(`${i}: content mismatch`),r)){let l=Rt(p,m,i);o.push({filename:i,diff:l})}}catch{n.push(`${i}: expected file not found`)}}try{let i=await(0,S.readdir)(e);for(let u of i)!t.files.has(u)&&u!=="manifest.json"&&n.push(`${u}: unexpected file in expected/`)}catch{}return{errors:n,diffs:r?o:void 0}}var Re=require("commander"),O=require("fs/promises"),x=require("path"),Te=V(require("js-yaml")),tt=require("@truefoundry/tfy-infra-engine");var ye=V(require("chokidar")),we=require("path");function lr(e){let t=e.replace(/\\/g,"/"),r=(0,we.basename)(t);return r==="template.json"?"schema":/\/src\//.test(t)&&!r.startsWith("_")?"template":r==="inputs.yaml"?"inputs":"other"}function ve(e,t={}){let{onChange:r,onReady:n,onError:o,ignored:i=/(^|[/\\])\..|(^|[/\\])node_modules($|[/\\])|(^|[/\\])dev($|[/\\])/,debounceMs:u=100}=t,s=ye.default.watch(e,{ignored:i,persistent:!0,ignoreInitial:!0,awaitWriteFinish:{stabilityThreshold:u,pollInterval:50}}),c=f=>m=>{let p=lr(m);r&&r({type:f,path:m,fileType:p})};return s.on("change",c("change")),s.on("add",c("add")),s.on("unlink",c("unlink")),n&&s.on("ready",n),o&&s.on("error",f=>o(f instanceof Error?f:new Error(String(f)))),s}function xe(){return new Re.Command("dev").description("Watch mode for rapid template iteration").option("-d, --directory <dir>","Path to template version directory",".").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 t=>{try{let r=await hr(t);process.exit(r)}catch(r){b(r)}})}async function dr(e){try{await(0,O.access)(e)}catch{return{valid:!1,error:`Directory not found: ${e}`}}let t=(0,x.join)(e,"template.json");try{await(0,O.access)(t)}catch{return{valid:!1,error:`Missing template.json in ${e}`}}return{valid:!0}}async function gr(e,t){let r=(0,x.resolve)(t);try{await(0,O.rm)(r,{recursive:!0,force:!0})}catch{}return await(0,O.mkdir)(r,{recursive:!0}),r}async function hr(e){let t=(0,x.resolve)(e.directory),r=await dr(t);if(!r.valid)return w(r.error),d.VALIDATION_ERROR;let n;if(e.fixture){n=(0,x.resolve)(t,e.fixture);try{await(0,O.access)(n)}catch{return w(`Fixture not found: ${e.fixture}`),d.VALIDATION_ERROR}}else{let f=await q(t);if(!f)return w("No default fixture found at tests/default/inputs.yaml"),a("Either create a default fixture or use --fixture to specify one."),d.VALIDATION_ERROR;n=f}let o=await gr(t,e.output),i=(0,x.relative)(process.cwd(),o);a(`[watching] ${(0,x.relative)(process.cwd(),t)}`),a(`[output] ${i}/`),a(`[fixture] ${(0,x.relative)(process.cwd(),n)}`),a("");let u=(0,tt.createEngine)(),s=`file://${t}`;await Ee(u,s,t,n,o,e.test);let c=ve(t,{onChange:f=>{(async()=>{let m=(0,x.relative)(t,f.path);a(`[changed] ${m}`),f.fileType==="schema"?await Oe(s):(f.fileType==="template"||f.fileType==="inputs")&&await Ee(u,s,t,n,o,e.test)})()},onReady:()=>{a("[ready] Watching for changes... (Ctrl+C to exit)"),a("")},onError:f=>{w(`Watch error: ${f.message}`)}});return new Promise(f=>{let m=()=>{a(""),a("[stopped] Watch mode ended"),c.close(),f(d.SUCCESS)};process.on("SIGINT",m),process.on("SIGTERM",m)})}async function Oe(e){try{return(await I().resolve(e,{noCache:!0})).jsonSchema?(a("[validate] \u2713 template.json valid"),!0):(w("[validate] \u2717 template.json invalid"),!1)}catch(t){return w("[validate] \u2717 template.json invalid"),a(` ${t.message}`),!1}}async function Ee(e,t,r,n,o,i){if(await Oe(t))try{let s=await(0,O.readFile)(n,"utf-8"),c=Te.load(s),p={template:await I().resolve(t,{noCache:!0}),inputs:c,intentId:`dev-${Date.now()}`,options:{skipFormat:!1}},l=await e.install(p),y=Array.from(l.files.keys()).filter(C=>C!=="manifest.json").length;a(`[validate] \u2713 ${y} template files valid`),await(0,O.rm)(o,{recursive:!0,force:!0}),await(0,O.mkdir)(o,{recursive:!0});let $=0;for(let[C,M]of l.files){if(C==="manifest.json")continue;let P=(0,x.join)(o,C);await(0,O.mkdir)((0,x.dirname)(P),{recursive:!0}),await(0,O.writeFile)(P,M.content,"utf-8"),$++}a(`[render] \u2713 ${$} files written to ${(0,x.relative)(process.cwd(),o)}/`),i!==!1&&await yr(r,l,o)}catch(s){w("[render] \u2717 Render failed"),a(` ${s.message}`),s instanceof tt.EngineError&&s.code===tt.EngineErrorCode.INPUT_VALIDATION_FAILED&&s.details?.errors&&a(gt(s.details.errors))}}async function yr(e,t,r){let n=(0,x.join)(e,"tests","default","expected"),o=Date.now();try{await(0,O.access)(n)}catch{a("[test] \u26A0 No expected/ directory, skipping comparison");return}let i=[];for(let[s,c]of t.files){if(s==="manifest.json")continue;let f=c.content,m=(0,x.join)(n,s);try{let p=await(0,O.readFile)(m,"utf-8"),l=f.trim().replace(/\r\n/g,`
|
|
9
|
+
`),y=p.trim().replace(/\r\n/g,`
|
|
10
|
+
`);if(l!==y){let $=Rt(y,l,s);i.push({file:s,diff:$})}}catch{i.push({file:s})}}let u=Date.now()-o;if(i.length===0)a(`[test] \u2713 default: passed (${u}ms)`);else{w("[test] \u2717 default: failed");for(let s of i)if(a(` ${s.file}: content mismatch`),s.diff){let c=s.diff.split(`
|
|
11
|
+
`).slice(4,14);for(let f of c)f&&a(` ${f}`);s.diff.split(`
|
|
12
|
+
`).length>14&&a(" ... (diff truncated)")}}}var be=require("commander"),Ot=require("path"),Ce=require("@truefoundry/tfy-infra-engine");var Tt=require("fs/promises"),$e=require("path"),et=require("@truefoundry/tfy-infra-engine");async function xt(e,t=et.DEFAULT_PREFIX){let r=new Map,n;try{n=(await(0,Tt.readdir)(e,{withFileTypes:!0})).filter(i=>i.isFile()).map(i=>i.name)}catch(o){throw o.code==="ENOENT"?new Error(`Directory not found: ${e}`,{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,Tt.readFile)((0,$e.join)(e,o),"utf-8"),u=(0,et.classifyFile)(o,t),s={content:i,zone:u};if(u==="platform"){let c=(0,et.parseHeader)(i);c&&(s.header=c)}r.set(o,s)}return r}var wr=1;function Ie(){return new be.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 t=>{try{let r=await vr(t);process.exit(r)}catch(r){b(r)}})}async function vr(e){let t=(0,Ot.resolve)(e.directory),r=(0,Ot.join)(t,"manifest.json");g(`Loading manifest from ${r}`);let n=await _(r);g(`Reading files from ${t}`);let o=await xt(t,n.platformPrefix),u=await(0,Ce.createEngine)().verify(o,n);return e.json?a(Rr(u.driftReport)):a(Er(u.driftReport)),u.driftReport.valid?d.SUCCESS:wr}function Er(e){let t=[],r=e.summary.driftedFiles+e.summary.missingFiles+e.summary.unexpectedFiles;if(e.valid)t.push(`\u2713 Verification passed: ${e.summary.totalFiles} files checked, ${r} issues`),t.push(""),t.push(`Aggregate Hash: ${e.aggregateHash.expected}`);else{t.push(`\u2717 Drift detected: ${r} issues found`),t.push("");for(let n of e.entries){let o=ht(n.type);t.push(` ${o} ${n.path}`),t.push(` ${n.details}`),t.push("")}t.push(`Summary: ${e.summary.totalFiles} files checked, ${e.summary.driftedFiles} drifted, ${e.summary.missingFiles} missing`+(e.summary.unexpectedFiles>0?`, ${e.summary.unexpectedFiles} unexpected`:""))}return t.join(`
|
|
13
|
+
`)}function Rr(e){return JSON.stringify(e,null,2)}var Fe=require("commander"),J=require("path"),Se=require("@truefoundry/tfy-infra-engine");var Pe=3;function Tr(e){if(e.config&&e.fromManifest)throw new Error("--config and --from-manifest are mutually exclusive. Use one or the other.");if(!e.config&&!e.fromManifest)throw new Error("Either --config <path> or --from-manifest <manifest-path> is required.");if(e.fromManifest&&!e.template)throw new Error("--from-manifest requires --template <new-uri> to specify the new template version.")}function De(){return new Fe.Command("upgrade").description("Upgrade platform files to a new template version").option("-c, --config <path>","Path to envelope config file (YAML or JSON)").option("-d, --directory <dir>","Directory containing current cluster files",".").option("-o, --output <dir>","Output directory for upgraded files (defaults to --directory)").option("--force","Proceed with upgrade even if drift is detected").option("--dry-run","Show what would change without writing files").option("--json","Output results as structured JSON").option("--from-manifest <path>","Path to manifest.json as input source").option("--template <uri>","New template URI (required with --from-manifest)").option("--skip-format","Skip tofu fmt formatting").option("--no-cache","Force re-fetch template").option("--timeout <ms>","Network timeout in milliseconds","30000").action(async t=>{try{t.config&&(t.config=(0,J.resolve)(t.config)),t.fromManifest&&(t.fromManifest=(0,J.resolve)(t.fromManifest)),Tr({config:t.config,fromManifest:t.fromManifest,template:t.template});let r=await xr(t);process.exit(r)}catch(r){b(r)}})}async function xr(e){let t=(0,J.resolve)(e.directory),r=e.output?(0,J.resolve)(e.output):t,n=parseInt(e.timeout,10),o=(0,J.join)(t,"manifest.json");g(`Loading manifest from ${o}`);let i=await _(o);g(`Reading files from ${t}`);let u=await xt(t,i.platformPrefix),s;if(e.fromManifest){let y=await _(e.fromManifest);s=K(y),s.template=e.template}else s=await X(e.config);v(`Template: ${s.template}`),g(`Intent ID: ${s.intentId}`),g("Resolving template...");let m={template:await I().resolve(s.template,{noCache:e.cache===!1,timeout:n}),inputs:s.inputs,intentId:s.intentId,platformPrefix:s.platformPrefix,options:{skipFormat:e.skipFormat??s.options?.skipFormat}},l=await(0,Se.createEngine)().upgrade(m,u,i);return l.sourceBlocked?(w("Upgrade blocked: template source mismatch"),a("The current files were generated from a different template source."),Pe):!l.driftReport.valid&&!e.force?Or(l,e.json):e.dryRun?$r(l,e.json):(await Y(r,l,"upgrade"),e.json?a(_e(l)):a(Ne(l)),d.SUCCESS)}function Or(e,t){let r=e.driftReport.summary.driftedFiles+e.driftReport.summary.missingFiles+e.driftReport.summary.unexpectedFiles;if(t)a(JSON.stringify({blocked:!0,reason:"drift_detected",driftReport:e.driftReport},null,2));else{w(`Upgrade blocked: drift detected in ${r} files`),a("");for(let n of e.driftReport.entries){let o=ht(n.type);a(` ${o} ${n.path}`),a(` ${n.details}`)}a(""),a("Use --force to override and proceed with upgrade.")}return Pe}function $r(e,t){return t?a(_e(e)):(a("Dry run \u2014 no files written."),a(""),a(Ne(e))),d.SUCCESS}function Ne(e){let t=[];t.push(`\u2713 Upgrade complete: ${e.manifest.templateSource} v${e.manifest.templateVersion}`),t.push("");let r=0,n=0;for(let[,o]of e.files)o.zone==="platform"?r++:n++;return t.push(` Updated: ${r} files (Platform Zone)`),t.push(` Unchanged: ${n} files (User Zone)`),t.push(""),t.push(`Aggregate Hash: ${e.manifest.aggregateHash}`),t.join(`
|
|
14
|
+
`)}function _e(e){return JSON.stringify({success:!0,templateSource:e.manifest.templateSource,templateVersion:e.manifest.templateVersion,aggregateHash:e.manifest.aggregateHash,driftReport:e.driftReport,files:Array.from(e.files.entries()).map(([t,r])=>({path:t,zone:r.zone}))},null,2)}var Ae=require("commander"),At=require("path"),ke=require("@truefoundry/tfy-infra-engine");function Ue(){return new Ae.Command("hash").description("Compute the expected aggregate hash without generating files").option("-c, --config <path>","Path to envelope config file (YAML or JSON)").option("--from-manifest <path>","Path to manifest.json to use as input source").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").action(async t=>{try{if(t.config&&(t.config=(0,At.resolve)(t.config)),t.fromManifest&&(t.fromManifest=(0,At.resolve)(t.fromManifest)),t.config&&t.fromManifest)throw new Error("--config and --from-manifest are mutually exclusive. Use one or the other.");if(!t.config&&!t.fromManifest)throw new Error("Either --config <path> or --from-manifest <manifest-path> is required.");let r=await br(t);process.exit(r)}catch(r){b(r)}})}async function br(e){let t=parseInt(e.timeout,10),r;if(e.fromManifest){g(`Loading manifest from ${e.fromManifest}`);let c=await _(e.fromManifest);r=K(c)}else g(`Loading config from ${e.config}`),r=await X(e.config);g(`Template: ${r.template}`),g("Resolving template...");let i={template:await I().resolve(r.template,{noCache:e.cache===!1,timeout:t}),inputs:r.inputs,intentId:r.intentId,platformPrefix:r.platformPrefix,options:{skipFormat:e.skipFormat??r.options?.skipFormat}},s=await(0,ke.createEngine)().hashOnly(i);return e.json?a(Ir(s)):a(Cr(s)),d.SUCCESS}function Cr(e){let t=[];return t.push(`Aggregate Hash: ${e.aggregateHash}`),t.push(`Template: ${e.templateSource}`),t.push(`Version: ${e.templateVersion}`),t.push(`Files: ${e.fileCount}`),t.join(`
|
|
15
|
+
`)}function Ir(e){return JSON.stringify(e,null,2)}function je(){let e=new Me.Command("tpl").description("Template operations");return e.addCommand(xe()),e.addCommand(oe()),e.addCommand(ae()),e.addCommand(pe()),e.addCommand(he()),e.addCommand(Ie()),e.addCommand(De()),e.addCommand(Ue()),e}process.env.INIT_CWD&&process.chdir(process.env.INIT_CWD);var Fr="0.0.0-canary.edab06d";async function Sr(){let e=new Ve.Command;e.name("tfy-init").description("TrueFoundry infrastructure templating CLI").version(Fr,"-v, --version","Show version").option("--verbose","Enable verbose output").option("-q, --quiet","Suppress non-error output").hook("preAction",t=>{let r=t.opts();r.quiet?Dt("quiet"):r.verbose&&Dt("verbose")}),e.addCommand(je()),await e.parseAsync(process.argv)}Sr().catch(e=>{console.error("Fatal error:",e),process.exit(1)});
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@truefoundry/tfy-infra-cli",
|
|
3
|
+
"version": "0.0.0-canary.edab06d",
|
|
4
|
+
"description": "CLI for TrueFoundry infrastructure templating engine",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"tfy-init": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"test": "jest --config jest.config.cjs",
|
|
16
|
+
"test:watch": "jest --watch",
|
|
17
|
+
"test:coverage": "jest --coverage",
|
|
18
|
+
"clean": "rm -rf dist",
|
|
19
|
+
"prepublishOnly": "yarn build"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@truefoundry/tfy-infra-engine": "0.0.0-canary.edab06d",
|
|
23
|
+
"chokidar": "^5.0.0",
|
|
24
|
+
"commander": "^14.0.3",
|
|
25
|
+
"diff": "^8.0.3",
|
|
26
|
+
"handlebars": "^4.7.8",
|
|
27
|
+
"js-yaml": "^4.1.0",
|
|
28
|
+
"tar": "^7.5.9"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/diff": "^8.0.0",
|
|
32
|
+
"@types/js-yaml": "^4.0.0",
|
|
33
|
+
"@types/tar": "^6.1.0",
|
|
34
|
+
"tsup": "^8.5.1"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"cli",
|
|
38
|
+
"terraform",
|
|
39
|
+
"opentofu",
|
|
40
|
+
"hcl",
|
|
41
|
+
"templating",
|
|
42
|
+
"infrastructure"
|
|
43
|
+
],
|
|
44
|
+
"author": "TrueFoundry",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/truefoundry/tfy-infra-tools.git",
|
|
49
|
+
"directory": "packages/infra-cli"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=20.12.2"
|
|
53
|
+
}
|
|
54
|
+
}
|