@truefoundry/tfy-infra-cli 0.1.4 → 0.1.5

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/dist/index.js CHANGED
@@ -1,20 +1,20 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var $t=Object.create;var ke=Object.defineProperty;var Pt=Object.getOwnPropertyDescriptor;var Dt=Object.getOwnPropertyNames;var jt=Object.getPrototypeOf,kt=Object.prototype.hasOwnProperty;var Nt=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of Dt(e))!kt.call(t,o)&&o!==r&&ke(t,o,{get:()=>e[o],enumerable:!(n=Pt(e,o))||n.enumerable});return t};var B=(t,e,r)=>(r=t!=null?$t(jt(t)):{},Nt(e||!t||!t.__esModule?ke(r,"default",{value:t,enumerable:!0}):r,t));var Ft=require("commander");var St=require("commander");var Ve=require("commander"),z=require("fs/promises"),J=require("path");var se=require("fs/promises"),ve=require("path");async function ae(t){let e=new Map,r=new Map;return await Ne(t,"",e,r),{files:e,staticFiles:r}}async function Ne(t,e,r,n){let o=e?(0,ve.join)(t,e):t,i=await(0,se.readdir)(o,{withFileTypes:!0});for(let a of i){if(a.name.startsWith("_"))continue;let c=e?`${e}/${a.name}`:a.name;if(a.isDirectory())await Ne(t,c,r,n);else{let u=await(0,se.readFile)((0,ve.join)(o,a.name),"utf-8");if(a.name.endsWith(".hbs")){let f=c.replace(/\.hbs$/,"");r.set(f,u)}else n.set(c,u)}}}var T=require("@truefoundry/tfy-infra-engine");var _="normal",X=!1;function Re(t){_=t}function Ee(){return _}function _e(t){X=t}function j(t){process.stdout.write(t+`
3
- `)}function w(t){_!=="quiet"&&(X?console.error:console.log)(`\u2713 ${t}`)}function g(t){console.error(`\u2717 ${t}`)}function s(t){_!=="quiet"&&(X?console.error:console.log)(t)}function y(t){_==="verbose"&&(X?console.error:console.log)(` ${t}`)}function N(t){_!=="quiet"&&console.warn(`\u26A0 ${t}`)}function _t(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(1)} MB`}function ce(t){return t.map(e=>` \u2022 ${e.path}: ${e.message}`).join(`
4
- `)}function ue(t){switch(t){case"content_drift":return"DRIFTED";case"missing_file":return"MISSING";case"unexpected_file":return"UNEXPECTED";case"metadata_inconsistency":return"INCONSISTENT";case"source_mismatch":return"SOURCE MISMATCH"}}function Ae(t){if(_==="quiet")return;let e=X?console.error:console.log;e("Files:");for(let r of t)e(` - ${r.path} (${_t(r.size)})`)}var d={SUCCESS:0,VALIDATION_ERROR:1,FETCH_ERROR:2,RENDER_ERROR:3,FORMAT_ERROR:4,DRIFT_BLOCKED:5};function S(t,e=d.RENDER_ERROR){if(t instanceof T.EngineError){if(g(t.message),Ee()==="verbose"&&t.details){s(""),s("Details:");for(let[r,n]of Object.entries(t.details))r!=="errors"&&s(` ${r}: ${JSON.stringify(n)}`)}switch(t.details?.errors&&(s(""),s("Errors:"),s(ce(t.details.errors))),t.code){case T.EngineErrorCode.INPUT_VALIDATION_FAILED:case T.EngineErrorCode.SCHEMA_INVALID:case T.EngineErrorCode.ENVELOPE_VALIDATION_FAILED:process.exit(d.VALIDATION_ERROR);break;case T.EngineErrorCode.TEMPLATE_NOT_FOUND:case T.EngineErrorCode.FILE_NOT_FOUND:case T.EngineErrorCode.NETWORK_ERROR:process.exit(d.FETCH_ERROR);break;case T.EngineErrorCode.TOFU_NOT_FOUND:case T.EngineErrorCode.TOFU_FMT_FAILED:s(""),s("Run with --skip-format to skip formatting."),process.exit(d.FORMAT_ERROR);break;case T.EngineErrorCode.MANIFEST_PARSE_ERROR:process.exit(d.VALIDATION_ERROR);break;default:process.exit(e)}}g(t.message),Ee()==="verbose"&&console.error(t.stack),process.exit(e)}function Me(){return new Ve.Command("bundle").description("Build a bundle.json from a schema package and src/ directory").requiredOption("--schema <path-or-dash>",'Schema package JSON file, or "-" for stdin').requiredOption("--src <dir>","Template source directory containing .hbs and static files").requiredOption("--name <string>","Template name").option("--description <string>","Template description").option("-o, --output <path>","Output file path (default: stdout)").action(async e=>{try{await Lt(e)}catch(r){S(r)}})}async function At(t){let e;t==="-"?e=await Vt():e=await(0,z.readFile)((0,J.resolve)(t),"utf-8");let r;try{r=JSON.parse(e)}catch(o){throw new Error(`Failed to parse schema package JSON: ${o.message}`,{cause:o})}let n=r;if(!n.jsonSchema||typeof n.jsonSchema!="object")throw new Error('Schema package must contain a "jsonSchema" object');if(!Array.isArray(n.uiSchema))throw new Error('Schema package must contain a "uiSchema" array');return{jsonSchema:n.jsonSchema,uiSchema:n.uiSchema}}function Vt(){return new Promise((t,e)=>{let r=[];process.stdin.on("data",n=>r.push(n)),process.stdin.on("end",()=>t(Buffer.concat(r).toString("utf-8"))),process.stdin.on("error",e)})}function Mt(t){let e=t.properties;if(!e)return;let r=e.version;if(!r)return;let n=r.default;return typeof n=="string"?n:void 0}async function Lt(t){let e=await At(t.schema),r=t.version??Mt(e.jsonSchema);if(!r)throw new Error('Could not determine template version. Provide --version or ensure the schema has a "version" property with a default value.');let n=(0,J.resolve)(t.src),{files:o,staticFiles:i}=await ae(n);if(o.size===0&&i.size===0)throw new Error(`No template files found in ${n}`);let a={name:t.name,version:r};t.description&&(a.description=t.description);let c={metadata:a,jsonSchema:e.jsonSchema,uiSchema:e.uiSchema,files:Object.fromEntries(o),staticFiles:Object.fromEntries(i),version:{semver:r}},u=JSON.stringify(c,null,2)+`
5
- `;if(t.output){let f=(0,J.resolve)(t.output);await(0,z.mkdir)((0,J.dirname)(f),{recursive:!0}),await(0,z.writeFile)(f,u,"utf-8"),w(`Bundle written to ${f}`)}else process.stdout.write(u)}var rt=require("commander"),nt=require("readline"),M=require("fs/promises"),k=require("path"),ot=B(require("js-yaml")),ne=require("@truefoundry/tfy-infra-engine");var fe=require("@truefoundry/tfy-infra-engine");var G=require("fs/promises"),Le=require("path"),R=require("@truefoundry/tfy-infra-engine"),Y=class{canResolve(e){return e.startsWith("file://")}async resolve(e,r){let n=this.uriToPath(e);try{let o=await(0,G.stat)(n);if(o.isFile()&&n.endsWith(".json"))return this.resolveBundle(n,e);if(o.isDirectory()){let i=(0,Le.join)(n,"bundle.json");if(await this.fileExists(i))return this.resolveBundle(i,e);throw new R.EngineError(`bundle.json not found in ${n}`,R.EngineErrorCode.BUNDLE_NOT_FOUND,void 0,{path:n})}throw new R.EngineError(`Not a file or directory: ${n}`,R.EngineErrorCode.TEMPLATE_NOT_FOUND,void 0,{path:n})}catch(o){if(o instanceof R.EngineError)throw o;let i=o;throw i.code==="ENOENT"?new R.EngineError(`Template not found: ${n}`,R.EngineErrorCode.FILE_NOT_FOUND,o,{path:n,originalCode:i.code}):i.code==="EACCES"?new R.EngineError(`Permission denied: ${n}`,R.EngineErrorCode.FILE_NOT_FOUND,o,{path:n,originalCode:i.code}):new R.EngineError(`Failed to load template from ${n}: ${o.message}`,R.EngineErrorCode.FILE_NOT_FOUND,o,{path:n})}}async resolveBundle(e,r){let n=await(0,G.readFile)(e,"utf-8"),o;try{o=JSON.parse(n)}catch(l){throw new R.EngineError(`Failed to parse bundle.json: ${l.message}`,R.EngineErrorCode.BUNDLE_INVALID,l,{path:e})}let i=o.metadata;if(!i||typeof i!="object")throw new R.EngineError('bundle.json must contain a "metadata" object',R.EngineErrorCode.BUNDLE_INVALID,void 0,{path:e});if(!i.name||!i.version)throw new R.EngineError('bundle.json metadata must contain "name" and "version"',R.EngineErrorCode.BUNDLE_INVALID,void 0,{path:e});let a={name:i.name,version:i.version,...i.description?{description:i.description}:{}},c=o.jsonSchema;if(!c||typeof c!="object")throw new R.EngineError('bundle.json must contain a "jsonSchema" object',R.EngineErrorCode.BUNDLE_INVALID,void 0,{path:e});let u=new Map(Object.entries(o.files??{})),f=new Map(Object.entries(o.staticFiles??{}));if(u.size===0&&f.size===0)throw new R.EngineError(`Bundle contains no template files: ${e}`,R.EngineErrorCode.TEMPLATE_NOT_FOUND,void 0,{path:e});let m={semver:a.version};return{metadata:a,jsonSchema:c,files:u,staticFiles:f,source:r,version:m}}uriToPath(e){return e.startsWith("file:///")||e.startsWith("file://")?e.substring(7):e}async fileExists(e){try{return await(0,G.stat)(e),!0}catch{return!1}}};var F=require("@truefoundry/tfy-infra-engine");var Ut={timeout:3e4,retries:3,noCache:!1};function Ue(t){return{...Ut,...t}}async function Be(t,e,r=1e3){let n;for(let o=0;o<=e;o++)try{return await t()}catch(i){if(n=i,o<e){let a=r*Math.pow(2,o)*(.5+Math.random()*.5);await new Promise(c=>setTimeout(c,a))}}throw n instanceof Error?n:new Error(String(n))}function Je(t,e,r){let n,o=new Promise((i,a)=>{n=setTimeout(()=>a(new Error(r??`Operation timed out after ${e}ms`)),e)});return Promise.race([t,o]).finally(()=>clearTimeout(n))}var ze="JFROG_ACCESS_TOKEN";function We(t){try{let{hostname:e}=new URL(t);return e.includes("jfrog")}catch{return!1}}function Bt(t){let e={"User-Agent":"tfy-infra-cli",Accept:"application/json"};if(We(t)){let r=process.env[ze];r&&(e.Authorization=`Bearer ${r}`)}return e}async function Jt(t,e){let r=await fetch(t,{headers:e});if(r.status===401||r.status===403){let n=We(t)?` Set ${ze} in your environment to authenticate.`:"";throw new F.EngineError(`Authentication failed (HTTP ${r.status}) for ${t}.${n}`,F.EngineErrorCode.HTTPS_AUTH_FAILED,void 0,{uri:t,status:r.status})}if(!r.ok)throw new F.EngineError(`Bundle download failed: HTTP ${r.status} for ${t}`,F.EngineErrorCode.NETWORK_ERROR,void 0,{uri:t,status:r.status});return r}async function zt(t,e){let n=await t.json(),{metadata:o,jsonSchema:i}=(0,F.validateBundle)(n),a=new Map(Object.entries(n.files??{})),c=new Map(Object.entries(n.staticFiles??{}));if(a.size===0&&c.size===0)throw new F.EngineError(`Bundle contains no template files: ${e}`,F.EngineErrorCode.TEMPLATE_NOT_FOUND,void 0,{uri:e});let u={semver:o.version};return{metadata:o,jsonSchema:i,files:a,staticFiles:c,source:e,version:u}}var Z=class{canResolve(e){return e.startsWith("https://")&&e.toLowerCase().endsWith(".json")}async resolve(e,r){let n=Ue(r),o=Bt(e),i=await Je(Be(()=>Jt(e,o),n.retries),n.timeout,`HTTPS bundle download timed out after ${n.timeout}ms`);return zt(i,e)}};var x=require("fs/promises"),C=require("path"),He=require("crypto"),W=require("@truefoundry/tfy-infra-engine");function qe(){let t=process.env.HOME||process.env.USERPROFILE||"~";return(0,C.join)(t,".cache","tfy-infra","templates")}var Q=class{cacheDir;constructor(e){this.cacheDir=e??qe()}getCacheKey(e){return(0,He.createHash)("sha256").update(e).digest("hex").substring(0,16)}getCachePath(e,r){let n=this.getCacheKey(e),o=r.replace(/\//g,"-");return(0,C.join)(this.cacheDir,n,o)}async has(e,r){let n=this.getCachePath(e,r);try{let o=(0,C.join)(n,"bundle.json");return await(0,x.stat)(o),!0}catch{return!1}}async get(e,r){let n=this.getCachePath(e,r);try{let o=(0,C.join)(n,"bundle.json"),i=await(0,x.readFile)(o,"utf-8"),a=JSON.parse(i),{metadata:c,jsonSchema:u}=(0,W.validateBundle)(a),f=a,m=new Map(Object.entries(f.files??{})),l=new Map(Object.entries(f.staticFiles??{})),p={semver:c.version};return{metadata:c,jsonSchema:u,files:m,staticFiles:l,source:e,version:p}}catch{return null}}async set(e){let r=this.getCachePath(e.source,e.version.semver);try{await(0,x.mkdir)(r,{recursive:!0});let n={metadata:{name:e.metadata.name,version:e.metadata.version,...e.metadata.description?{description:e.metadata.description}:{}},jsonSchema:e.jsonSchema,files:Object.fromEntries(e.files),staticFiles:Object.fromEntries(e.staticFiles)};await(0,x.writeFile)((0,C.join)(r,"bundle.json"),JSON.stringify(n,null,2),"utf-8")}catch(n){throw new W.EngineError(`Failed to cache template: ${n.message}`,W.EngineErrorCode.CACHE_ERROR,n,{source:e.source,version:e.version.semver})}}async delete(e,r){if(r){let n=this.getCachePath(e,r);try{await(0,x.rm)(n,{recursive:!0,force:!0})}catch{}}else{let n=this.getCacheKey(e),o=(0,C.join)(this.cacheDir,n);try{await(0,x.rm)(o,{recursive:!0,force:!0})}catch{}}}async clear(){try{await(0,x.rm)(this.cacheDir,{recursive:!0,force:!0})}catch{}}async stats(){let e=0,r=0,n=0;try{let o=await(0,x.readdir)(this.cacheDir);e=o.length;for(let i of o){let a=(0,C.join)(this.cacheDir,i),c=await(0,x.readdir)(a);r+=c.length;for(let u of c){let f=(0,C.join)(a,u);n+=await this.dirSize(f)}}}catch{}return{sources:e,templates:r,size:n}}async dirSize(e){let r=0,n=await(0,x.readdir)(e,{withFileTypes:!0});for(let o of n){let i=(0,C.join)(e,o.name);if(o.isDirectory())r+=await this.dirSize(i);else{let a=await(0,x.stat)(i);r+=a.size}}return r}};var xe=class{resolvers=[];cache;constructor(e,r){this.cache=new Q(r),e?this.resolvers=e:(this.register(new Y),this.register(new Z))}register(e){this.resolvers.push(e)}getResolver(e){return this.resolvers.find(r=>r.canResolve(e))}async resolve(e,r){let n=this.getResolver(e);if(!n){let i=e.split("://")[0]||"unknown";throw new fe.EngineError(`No resolver found for URI scheme: ${i}://`,fe.EngineErrorCode.TEMPLATE_NOT_FOUND,void 0,{uri:e,scheme:i,supportedSchemes:["file://","https://*.json"]})}let o=await n.resolve(e,r);return e.startsWith("file://")||await this.cache.set(o),o}canResolve(e){return this.resolvers.some(r=>r.canResolve(e))}getSupportedSchemes(){return["file://","https://*.json"]}async clearCache(e){e?await this.cache.delete(e):await this.cache.clear()}async getCacheStats(){return this.cache.stats()}};function $(t){return new xe(void 0,t)}var Xe=require("fs/promises"),Ge=B(require("js-yaml"));var Oe=require("fs/promises"),Ke=require("path");async function be(t){try{return await(0,Oe.access)(t),!0}catch{return!1}}async function H(t){let e=(0,Ke.join)(t,"tests","default","inputs.yaml");try{return await(0,Oe.access)(e),e}catch{return null}}async function Te(t){let e=await(0,Xe.readFile)(t,"utf-8"),r=t.endsWith(".json"),n;try{r?n=JSON.parse(e):n=Ge.load(e)}catch(o){let i=r?"JSON":"YAML";throw new Error(`Failed to parse ${i} config file: ${o.message}`,{cause:o})}if(!n.intentId||typeof n.intentId!="string"||n.intentId.trim()==="")throw new Error(`Configuration file is missing required field 'intentId'. Add 'intentId: "<your-cluster-identity>"' to ${t}. The intentId uniquely identifies the cluster for integrity tracking.`);return{template:n.template,inputs:n.inputs??{},intentId:n.intentId,platformPrefix:n.platformPrefix,options:n.options}}var Ye=require("fs/promises");async function ee(t){let e;try{e=await(0,Ye.readFile)(t,"utf-8")}catch(o){throw o.code==="ENOENT"?new Error(`Manifest not found: ${t}`,{cause:o}):new Error(`Failed to read manifest: ${o.message}`,{cause:o})}let r;try{r=JSON.parse(e)}catch{throw new Error(`Invalid JSON in manifest: ${t}`)}let n=["manifestVersion","files","aggregateHash","inputs","platformPrefix","intentId","templateSource","templateVersion"];for(let o of n)if(r[o]===void 0)throw new Error(`Manifest is missing required field: ${o}`);return r}function Ze(t){if(!t.inputs)throw new Error("Cannot reconstruct envelope: manifest is missing inputs field");if(!t.platformPrefix)throw new Error("Cannot reconstruct envelope: manifest is missing platformPrefix field");return{template:t.templateSource,inputs:t.inputs,intentId:t.intentId,platformPrefix:t.platformPrefix}}var le=require("fs/promises"),Qe=require("path"),q=require("@truefoundry/tfy-infra-engine");async function pe(t,e=q.DEFAULT_PREFIX){let r=new Map,n;try{n=(await(0,le.readdir)(t,{withFileTypes:!0})).filter(i=>i.isFile()).map(i=>i.name)}catch(o){throw o.code==="ENOENT"?new Error(`Directory not found: ${t}`,{cause:o}):new Error(`Failed to read directory: ${o.message}`,{cause:o})}for(let o of n){if(o==="manifest.json")continue;let i=await(0,le.readFile)((0,Qe.join)(t,o),"utf-8"),a=(0,q.classifyFile)(o,e),c={content:i,zone:a};if(a==="platform"){let u=(0,q.parseHeader)(i);u&&(c.header=u)}r.set(o,c)}return r}var te=require("fs/promises"),me=require("path");async function re(t,e,r){await(0,te.mkdir)(t,{recursive:!0});for(let[n,o]of e.files){let i=(0,me.join)(t,n);r==="upgrade"&&o.zone==="user"&&await be(i)||(await(0,te.mkdir)((0,me.dirname)(i),{recursive:!0}),await(0,te.writeFile)(i,o.content,"utf-8"))}}var et=require("diff");function V(t,e,r){let n=t.replace(/\r\n/g,`
2
+ "use strict";var Pt=Object.create;var Ne=Object.defineProperty;var Dt=Object.getOwnPropertyDescriptor;var jt=Object.getOwnPropertyNames;var kt=Object.getPrototypeOf,Nt=Object.prototype.hasOwnProperty;var _t=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of jt(e))!Nt.call(t,o)&&o!==r&&Ne(t,o,{get:()=>e[o],enumerable:!(n=Dt(e,o))||n.enumerable});return t};var B=(t,e,r)=>(r=t!=null?Pt(kt(t)):{},_t(e||!t||!t.__esModule?Ne(r,"default",{value:t,enumerable:!0}):r,t));var Ct=require("commander");var It=require("commander");var Me=require("commander"),z=require("fs/promises"),J=require("path");var ce=require("fs/promises"),Re=require("path");async function ue(t){let e=new Map,r=new Map;return await _e(t,"",e,r),{files:e,staticFiles:r}}async function _e(t,e,r,n){let o=e?(0,Re.join)(t,e):t,i=await(0,ce.readdir)(o,{withFileTypes:!0});for(let a of i){if(a.name.startsWith("_"))continue;let c=e?`${e}/${a.name}`:a.name;if(a.isDirectory())await _e(t,c,r,n);else{let f=await(0,ce.readFile)((0,Re.join)(o,a.name),"utf-8");if(a.name.endsWith(".hbs")){let u=c.replace(/\.hbs$/,"");r.set(u,f)}else n.set(c,f)}}}var T=require("@truefoundry/tfy-infra-engine");var A="normal",G=!1;function Ee(t){A=t}function be(){return A}function Ae(t){G=t}function j(t){process.stdout.write(t+`
3
+ `)}function w(t){A!=="quiet"&&(G?console.error:console.log)(`\u2713 ${t}`)}function g(t){console.error(`\u2717 ${t}`)}function s(t){A!=="quiet"&&(G?console.error:console.log)(t)}function y(t){A==="verbose"&&(G?console.error:console.log)(` ${t}`)}function N(t){A!=="quiet"&&console.warn(`\u26A0 ${t}`)}function At(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(1)} MB`}function fe(t){return t.map(e=>` \u2022 ${e.path}: ${e.message}`).join(`
4
+ `)}function le(t){switch(t){case"content_drift":return"DRIFTED";case"missing_file":return"MISSING";case"unexpected_file":return"UNEXPECTED";case"metadata_inconsistency":return"INCONSISTENT";case"source_mismatch":return"SOURCE MISMATCH"}}function Ve(t){if(A==="quiet")return;let e=G?console.error:console.log;e("Files:");for(let r of t)e(` - ${r.path} (${At(r.size)})`)}var d={SUCCESS:0,VALIDATION_ERROR:1,FETCH_ERROR:2,RENDER_ERROR:3,FORMAT_ERROR:4,DRIFT_BLOCKED:5};function S(t,e=d.RENDER_ERROR){if(t instanceof T.EngineError){if(g(t.message),be()==="verbose"&&t.details){s(""),s("Details:");for(let[r,n]of Object.entries(t.details))r!=="errors"&&s(` ${r}: ${JSON.stringify(n)}`)}switch(t.details?.errors&&(s(""),s("Errors:"),s(fe(t.details.errors))),t.code){case T.EngineErrorCode.INPUT_VALIDATION_FAILED:case T.EngineErrorCode.SCHEMA_INVALID:case T.EngineErrorCode.ENVELOPE_VALIDATION_FAILED:process.exit(d.VALIDATION_ERROR);break;case T.EngineErrorCode.TEMPLATE_NOT_FOUND:case T.EngineErrorCode.FILE_NOT_FOUND:case T.EngineErrorCode.NETWORK_ERROR:process.exit(d.FETCH_ERROR);break;case T.EngineErrorCode.TOFU_NOT_FOUND:case T.EngineErrorCode.TOFU_FMT_FAILED:s(""),s("Run with --skip-format to skip formatting."),process.exit(d.FORMAT_ERROR);break;case T.EngineErrorCode.MANIFEST_PARSE_ERROR:process.exit(d.VALIDATION_ERROR);break;default:process.exit(e)}}g(t.message),be()==="verbose"&&console.error(t.stack),process.exit(e)}function Le(){return new Me.Command("bundle").description("Build a bundle.json from a schema package and src/ directory").requiredOption("--schema <path-or-dash>",'Schema package JSON file, or "-" for stdin').requiredOption("--src <dir>","Template source directory containing .hbs and static files").requiredOption("--name <string>","Template name").option("--description <string>","Template description").option("-o, --output <path>","Output file path (default: stdout)").action(async e=>{try{await Ut(e)}catch(r){S(r)}})}async function Vt(t){let e;t==="-"?e=await Mt():e=await(0,z.readFile)((0,J.resolve)(t),"utf-8");let r;try{r=JSON.parse(e)}catch(o){throw new Error(`Failed to parse schema package JSON: ${o.message}`,{cause:o})}let n=r;if(!n.jsonSchema||typeof n.jsonSchema!="object")throw new Error('Schema package must contain a "jsonSchema" object');if(!Array.isArray(n.uiSchema))throw new Error('Schema package must contain a "uiSchema" array');return{jsonSchema:n.jsonSchema,uiSchema:n.uiSchema}}function Mt(){return new Promise((t,e)=>{let r=[];process.stdin.on("data",n=>r.push(n)),process.stdin.on("end",()=>t(Buffer.concat(r).toString("utf-8"))),process.stdin.on("error",e)})}function Lt(t){let e=t.properties;if(!e)return;let r=e.version;if(!r)return;let n=r.default;return typeof n=="string"?n:void 0}async function Ut(t){let e=await Vt(t.schema),r=t.version??Lt(e.jsonSchema);if(!r)throw new Error('Could not determine template version. Provide --version or ensure the schema has a "version" property with a default value.');let n=(0,J.resolve)(t.src),{files:o,staticFiles:i}=await ue(n);if(o.size===0&&i.size===0)throw new Error(`No template files found in ${n}`);let a={name:t.name,version:r};t.description&&(a.description=t.description);let c={metadata:a,jsonSchema:e.jsonSchema,uiSchema:e.uiSchema,files:Object.fromEntries(o),staticFiles:Object.fromEntries(i),version:{semver:r}},f=JSON.stringify(c,null,2)+`
5
+ `;if(t.output){let u=(0,J.resolve)(t.output);await(0,z.mkdir)((0,J.dirname)(u),{recursive:!0}),await(0,z.writeFile)(u,f,"utf-8"),w(`Bundle written to ${u}`)}else process.stdout.write(f)}var rt=require("commander"),nt=require("readline"),L=require("fs/promises"),k=require("path"),ot=B(require("js-yaml")),oe=require("@truefoundry/tfy-infra-engine");var pe=require("@truefoundry/tfy-infra-engine");var Y=require("fs/promises"),xe=require("path"),R=require("@truefoundry/tfy-infra-engine"),Z=class{canResolve(e){return e.startsWith("file://")}async resolve(e,r){let n=this.uriToPath(e);try{let o=await(0,Y.stat)(n);if(o.isFile()&&n.endsWith(".json"))return this.resolveBundle(n,e);if(o.isDirectory()){let i=(0,xe.join)(n,"build","bundle.json");if(await this.fileExists(i))return this.resolveBundle(i,e);let a=(0,xe.join)(n,"bundle.json");if(await this.fileExists(a))return this.resolveBundle(a,e);throw new R.EngineError(`bundle.json not found in ${n} or ${n}/build`,R.EngineErrorCode.BUNDLE_NOT_FOUND,void 0,{path:n})}throw new R.EngineError(`Not a file or directory: ${n}`,R.EngineErrorCode.TEMPLATE_NOT_FOUND,void 0,{path:n})}catch(o){if(o instanceof R.EngineError)throw o;let i=o;throw i.code==="ENOENT"?new R.EngineError(`Template not found: ${n}`,R.EngineErrorCode.FILE_NOT_FOUND,o,{path:n,originalCode:i.code}):i.code==="EACCES"?new R.EngineError(`Permission denied: ${n}`,R.EngineErrorCode.FILE_NOT_FOUND,o,{path:n,originalCode:i.code}):new R.EngineError(`Failed to load template from ${n}: ${o.message}`,R.EngineErrorCode.FILE_NOT_FOUND,o,{path:n})}}async resolveBundle(e,r){let n=await(0,Y.readFile)(e,"utf-8"),o;try{o=JSON.parse(n)}catch(l){throw new R.EngineError(`Failed to parse bundle.json: ${l.message}`,R.EngineErrorCode.BUNDLE_INVALID,l,{path:e})}let i=o.metadata;if(!i||typeof i!="object")throw new R.EngineError('bundle.json must contain a "metadata" object',R.EngineErrorCode.BUNDLE_INVALID,void 0,{path:e});if(!i.name||!i.version)throw new R.EngineError('bundle.json metadata must contain "name" and "version"',R.EngineErrorCode.BUNDLE_INVALID,void 0,{path:e});let a={name:i.name,version:i.version,...i.description?{description:i.description}:{}},c=o.jsonSchema;if(!c||typeof c!="object")throw new R.EngineError('bundle.json must contain a "jsonSchema" object',R.EngineErrorCode.BUNDLE_INVALID,void 0,{path:e});let f=new Map(Object.entries(o.files??{})),u=new Map(Object.entries(o.staticFiles??{}));if(f.size===0&&u.size===0)throw new R.EngineError(`Bundle contains no template files: ${e}`,R.EngineErrorCode.TEMPLATE_NOT_FOUND,void 0,{path:e});let p={semver:a.version};return{metadata:a,jsonSchema:c,files:f,staticFiles:u,source:r,version:p}}uriToPath(e){return e.startsWith("file:///")||e.startsWith("file://")?e.substring(7):e}async fileExists(e){try{return await(0,Y.stat)(e),!0}catch{return!1}}};var C=require("@truefoundry/tfy-infra-engine");var Bt={timeout:3e4,retries:3,noCache:!1};function Ue(t){return{...Bt,...t}}async function Be(t,e,r=1e3){let n;for(let o=0;o<=e;o++)try{return await t()}catch(i){if(n=i,o<e){let a=r*Math.pow(2,o)*(.5+Math.random()*.5);await new Promise(c=>setTimeout(c,a))}}throw n instanceof Error?n:new Error(String(n))}function Je(t,e,r){let n,o=new Promise((i,a)=>{n=setTimeout(()=>a(new Error(r??`Operation timed out after ${e}ms`)),e)});return Promise.race([t,o]).finally(()=>clearTimeout(n))}var ze="JFROG_ACCESS_TOKEN";function We(t){try{let{hostname:e}=new URL(t);return e.includes("jfrog")}catch{return!1}}function Jt(t){let e={"User-Agent":"tfy-infra-cli",Accept:"application/json"};if(We(t)){let r=process.env[ze];r&&(e.Authorization=`Bearer ${r}`)}return e}async function zt(t,e){let r=await fetch(t,{headers:e});if(r.status===401||r.status===403){let n=We(t)?` Set ${ze} in your environment to authenticate.`:"";throw new C.EngineError(`Authentication failed (HTTP ${r.status}) for ${t}.${n}`,C.EngineErrorCode.HTTPS_AUTH_FAILED,void 0,{uri:t,status:r.status})}if(!r.ok)throw new C.EngineError(`Bundle download failed: HTTP ${r.status} for ${t}`,C.EngineErrorCode.NETWORK_ERROR,void 0,{uri:t,status:r.status});return r}async function Wt(t,e){let n=await t.json(),{metadata:o,jsonSchema:i}=(0,C.validateBundle)(n),a=new Map(Object.entries(n.files??{})),c=new Map(Object.entries(n.staticFiles??{}));if(a.size===0&&c.size===0)throw new C.EngineError(`Bundle contains no template files: ${e}`,C.EngineErrorCode.TEMPLATE_NOT_FOUND,void 0,{uri:e});let f={semver:o.version};return{metadata:o,jsonSchema:i,files:a,staticFiles:c,source:e,version:f}}var Q=class{canResolve(e){return e.startsWith("https://")&&e.toLowerCase().endsWith(".json")}async resolve(e,r){let n=Ue(r),o=Jt(e),i=await Je(Be(()=>zt(e,o),n.retries),n.timeout,`HTTPS bundle download timed out after ${n.timeout}ms`);return Wt(i,e)}};var b=require("fs/promises"),$=require("path"),He=require("crypto"),W=require("@truefoundry/tfy-infra-engine");function qe(){let t=process.env.HOME||process.env.USERPROFILE||"~";return(0,$.join)(t,".cache","tfy-infra","templates")}var ee=class{cacheDir;constructor(e){this.cacheDir=e??qe()}getCacheKey(e){return(0,He.createHash)("sha256").update(e).digest("hex").substring(0,16)}getCachePath(e,r){let n=this.getCacheKey(e),o=r.replace(/\//g,"-");return(0,$.join)(this.cacheDir,n,o)}async has(e,r){let n=this.getCachePath(e,r);try{let o=(0,$.join)(n,"bundle.json");return await(0,b.stat)(o),!0}catch{return!1}}async get(e,r){let n=this.getCachePath(e,r);try{let o=(0,$.join)(n,"bundle.json"),i=await(0,b.readFile)(o,"utf-8"),a=JSON.parse(i),{metadata:c,jsonSchema:f}=(0,W.validateBundle)(a),u=a,p=new Map(Object.entries(u.files??{})),l=new Map(Object.entries(u.staticFiles??{})),m={semver:c.version};return{metadata:c,jsonSchema:f,files:p,staticFiles:l,source:e,version:m}}catch{return null}}async set(e){let r=this.getCachePath(e.source,e.version.semver);try{await(0,b.mkdir)(r,{recursive:!0});let n={metadata:{name:e.metadata.name,version:e.metadata.version,...e.metadata.description?{description:e.metadata.description}:{}},jsonSchema:e.jsonSchema,files:Object.fromEntries(e.files),staticFiles:Object.fromEntries(e.staticFiles)};await(0,b.writeFile)((0,$.join)(r,"bundle.json"),JSON.stringify(n,null,2),"utf-8")}catch(n){throw new W.EngineError(`Failed to cache template: ${n.message}`,W.EngineErrorCode.CACHE_ERROR,n,{source:e.source,version:e.version.semver})}}async delete(e,r){if(r){let n=this.getCachePath(e,r);try{await(0,b.rm)(n,{recursive:!0,force:!0})}catch{}}else{let n=this.getCacheKey(e),o=(0,$.join)(this.cacheDir,n);try{await(0,b.rm)(o,{recursive:!0,force:!0})}catch{}}}async clear(){try{await(0,b.rm)(this.cacheDir,{recursive:!0,force:!0})}catch{}}async stats(){let e=0,r=0,n=0;try{let o=await(0,b.readdir)(this.cacheDir);e=o.length;for(let i of o){let a=(0,$.join)(this.cacheDir,i),c=await(0,b.readdir)(a);r+=c.length;for(let f of c){let u=(0,$.join)(a,f);n+=await this.dirSize(u)}}}catch{}return{sources:e,templates:r,size:n}}async dirSize(e){let r=0,n=await(0,b.readdir)(e,{withFileTypes:!0});for(let o of n){let i=(0,$.join)(e,o.name);if(o.isDirectory())r+=await this.dirSize(i);else{let a=await(0,b.stat)(i);r+=a.size}}return r}};var Oe=class{resolvers=[];cache;constructor(e,r){this.cache=new ee(r),e?this.resolvers=e:(this.register(new Z),this.register(new Q))}register(e){this.resolvers.push(e)}getResolver(e){return this.resolvers.find(r=>r.canResolve(e))}async resolve(e,r){let n=this.getResolver(e);if(!n){let i=e.split("://")[0]||"unknown";throw new pe.EngineError(`No resolver found for URI scheme: ${i}://`,pe.EngineErrorCode.TEMPLATE_NOT_FOUND,void 0,{uri:e,scheme:i,supportedSchemes:["file://","https://*.json"]})}let o=await n.resolve(e,r);return e.startsWith("file://")||await this.cache.set(o),o}canResolve(e){return this.resolvers.some(r=>r.canResolve(e))}getSupportedSchemes(){return["file://","https://*.json"]}async clearCache(e){e?await this.cache.delete(e):await this.cache.clear()}async getCacheStats(){return this.cache.stats()}};function P(t){return new Oe(void 0,t)}var Xe=require("fs/promises"),Ge=B(require("js-yaml"));var Ke=require("fs/promises"),Te=require("path");async function me(t){try{return await(0,Ke.access)(t),!0}catch{return!1}}async function H(t){let e=[(0,Te.join)(t,"tests","default","inputs.yaml"),(0,Te.join)(t,"tests","minimal","inputs.yaml")];for(let r of e)if(await me(r))return r;return null}async function Se(t){let e=await(0,Xe.readFile)(t,"utf-8"),r=t.endsWith(".json"),n;try{r?n=JSON.parse(e):n=Ge.load(e)}catch(o){let i=r?"JSON":"YAML";throw new Error(`Failed to parse ${i} config file: ${o.message}`,{cause:o})}if(!n.intentId||typeof n.intentId!="string"||n.intentId.trim()==="")throw new Error(`Configuration file is missing required field 'intentId'. Add 'intentId: "<your-cluster-identity>"' to ${t}. The intentId uniquely identifies the cluster for integrity tracking.`);return{template:n.template,inputs:n.inputs??{},intentId:n.intentId,platformPrefix:n.platformPrefix,options:n.options}}var Ye=require("fs/promises");async function te(t){let e;try{e=await(0,Ye.readFile)(t,"utf-8")}catch(o){throw o.code==="ENOENT"?new Error(`Manifest not found: ${t}`,{cause:o}):new Error(`Failed to read manifest: ${o.message}`,{cause:o})}let r;try{r=JSON.parse(e)}catch{throw new Error(`Invalid JSON in manifest: ${t}`)}let n=["manifestVersion","files","aggregateHash","inputs","platformPrefix","intentId","templateSource","templateVersion"];for(let o of n)if(r[o]===void 0)throw new Error(`Manifest is missing required field: ${o}`);return r}function Ze(t){if(!t.inputs)throw new Error("Cannot reconstruct envelope: manifest is missing inputs field");if(!t.platformPrefix)throw new Error("Cannot reconstruct envelope: manifest is missing platformPrefix field");return{template:t.templateSource,inputs:t.inputs,intentId:t.intentId,platformPrefix:t.platformPrefix}}var de=require("fs/promises"),Qe=require("path"),q=require("@truefoundry/tfy-infra-engine");async function ge(t,e=q.DEFAULT_PREFIX){let r=new Map,n;try{n=(await(0,de.readdir)(t,{withFileTypes:!0})).filter(i=>i.isFile()).map(i=>i.name)}catch(o){throw o.code==="ENOENT"?new Error(`Directory not found: ${t}`,{cause:o}):new Error(`Failed to read directory: ${o.message}`,{cause:o})}for(let o of n){if(o==="manifest.json")continue;let i=await(0,de.readFile)((0,Qe.join)(t,o),"utf-8"),a=(0,q.classifyFile)(o,e),c={content:i,zone:a};if(a==="platform"){let f=(0,q.parseHeader)(i);f&&(c.header=f)}r.set(o,c)}return r}var re=require("fs/promises"),he=require("path");async function ne(t,e,r){await(0,re.mkdir)(t,{recursive:!0});for(let[n,o]of e.files){let i=(0,he.join)(t,n);r==="upgrade"&&o.zone==="user"&&await me(i)||(await(0,re.mkdir)((0,he.dirname)(i),{recursive:!0}),await(0,re.writeFile)(i,o.content,"utf-8"))}}var et=require("diff");function M(t,e,r){let n=t.replace(/\r\n/g,`
6
6
  `),o=e.replace(/\r\n/g,`
7
- `);return(0,et.createTwoFilesPatch)(`expected/${r}`,`actual/${r}`,n,o,"","",{context:3})}function Wt(t){if([t.config,t.fromManifest].filter(Boolean).length>1)throw new Error("--config and --from-manifest are mutually exclusive. Use one or the other.");if(t.config&&t.directory!==".")throw new Error("--config and --directory are mutually exclusive. Use one or the other.")}function it(){return new rt.Command("render").description("Render a template to HCL files").option("-c, --config <path>","Path to envelope config file (YAML or JSON)").option("-d, --directory <dir>","Path to template version directory",".").option("-f, --fixture <path>","Input file to use (when using --directory)").option("-i, --input <key=value>","Inline input override (can be repeated)",Ht,[]).requiredOption("-o, --output <dir>","Output directory").option("--from-manifest <path>","Path to manifest.json to use as input source").option("--force","Proceed even if managed files have drifted (upgrade mode)").option("--allow-source-change","Allow upgrade when template source has changed").option("--dry-run","Show what would change without writing files").option("--json","Output result as structured JSON").option("--skip-format","Skip tofu fmt formatting").option("--no-cache","Force re-fetch template").option("--timeout <ms>","Network timeout in milliseconds","30000").option("--intent-id <id>","Cluster identity for integrity tracking").action(async e=>{try{e.config&&(e.config=(0,k.resolve)(e.config)),e.fromManifest&&(e.fromManifest=(0,k.resolve)(e.fromManifest)),Wt({config:e.config,directory:e.directory,fromManifest:e.fromManifest});let r=await qt(e);process.exit(r)}catch(r){S(r)}})}function Ht(t,e){return[...e,t]}function Se(t){let e={};if(!t)return e;for(let r of t){let n=r.indexOf("=");if(n>0){let o=r.substring(0,n),i=r.substring(n+1);e[o]=i}}return e}async function qt(t){t.json&&_e(!0);let e=(0,k.resolve)(t.output),r=parseInt(t.timeout,10);if(t.fromManifest)return Gt(t.fromManifest,t,e,r);if(t.config){let o=(0,k.join)(e,"manifest.json");return await st(o)?Qt(t.config,t,e,r,o):Zt(t.config,t,e,r)}let n=(0,k.resolve)(t.directory);return Yt(n,t,e,r)}async function st(t){try{return await(0,M.access)(t),!0}catch{return!1}}async function Kt(t){if(!process.stdin.isTTY)return!1;let e=(0,nt.createInterface)({input:process.stdin,output:process.stderr});return new Promise(r=>{e.question(`${t} [y/N] `,n=>{e.close(),r(n.trim().toLowerCase()==="y")})})}async function Xt(t){try{return(await(0,M.readdir)(t,{withFileTypes:!0})).filter(r=>r.isFile()&&r.name!=="config.yaml"&&r.name!=="config.json").length}catch{return 0}}async function Ie(t,e){if(e.force)return null;let r=await Xt(t);return r===0||(N(`Output directory contains ${r} existing files that will be overwritten.`),await Kt("Proceed?"))?null:(e.json?j(JSON.stringify({aborted:!0,reason:"overwrite_confirmation",existingFiles:r},null,2)):s("Aborted. Use --force to skip confirmation."),d.VALIDATION_ERROR)}async function Gt(t,e,r,n){y(`Loading manifest from ${t}`);let o=await ee(t),i=Ze(o);e.intentId&&(i.intentId=e.intentId),w(`Template: ${i.template}`),y(`Intent ID: ${i.intentId}`),y("Resolving template...");let u={template:await $().resolve(i.template,{noCache:e.cache===!1,timeout:n}),inputs:i.inputs,intentId:i.intentId,platformPrefix:i.platformPrefix,options:{skipFormat:e.skipFormat}};y("Re-rendering from manifest...");let m=await(0,ne.createEngine)().install(u);if(e.dryRun)return await Fe(m,r,e.json);let l=await Ie(r,e);return l!==null?l:(w("Inputs validated"),w(`Generated ${m.files.size-1} files + manifest.json`),e.skipFormat||w("Formatted with tofu fmt"),await re(r,m,"install"),Ce(m,r,e.json),d.SUCCESS)}async function Yt(t,e,r,n){let o=`file://${t}`,i=e.fixture?(0,k.resolve)(t,e.fixture):null;if(i){if(!await st(i))return g(`Fixture not found: ${e.fixture}`),d.VALIDATION_ERROR}else if(i=await H(t),!i)return g("No default fixture found at tests/default/inputs.yaml"),s("Either create a default fixture or use --fixture to specify one."),d.VALIDATION_ERROR;let a=await(0,M.readFile)(i,"utf-8"),c=ot.load(a),u=Se(e.input);c={...c,...u};let f=e.intentId??c.intentId??`local-${Date.now()}`;w(`Template: ${o}`),y(`Fixture: ${i}`),y(`Intent ID: ${f}`),y("Resolving template...");let p={template:await $().resolve(o,{noCache:e.cache===!1,timeout:n}),inputs:c,intentId:f,options:{skipFormat:e.skipFormat}},E=(0,ne.createEngine)();y("Validating inputs...");let b=await E.install(p);if(e.dryRun)return await Fe(b,r,e.json);let O=await Ie(r,e);return O!==null?O:(w("Inputs validated"),w(`Generated ${b.files.size-1} files + manifest.json`),e.skipFormat||w("Formatted with tofu fmt"),await re(r,b,"install"),Ce(b,r,e.json),d.SUCCESS)}async function Zt(t,e,r,n){y(`Loading config from ${t}`);let o=await Te(t);if(e.intentId&&(o.intentId=e.intentId),e.input&&e.input.length>0){let l=Se(e.input);o.inputs={...o.inputs||{},...l}}w(`Template: ${o.template}`),y(`Intent ID: ${o.intentId}`),y("Resolving template...");let c={template:await $().resolve(o.template,{noCache:e.cache===!1,timeout:n}),inputs:o.inputs,intentId:o.intentId,platformPrefix:o.platformPrefix,options:{skipFormat:e.skipFormat??o.options?.skipFormat}},u=(0,ne.createEngine)();y("Validating inputs...");let f=await u.install(c);if(e.dryRun)return await Fe(f,r,e.json);let m=await Ie(r,e);return m!==null?m:(w("Inputs validated"),w(`Generated ${f.files.size-1} files + manifest.json`),c.options?.skipFormat||w("Formatted with tofu fmt"),await re(r,f,"install"),Ce(f,r,e.json),d.SUCCESS)}async function Qt(t,e,r,n,o){y(`Existing manifest detected at ${o} \u2014 running upgrade`);let i=await ee(o);y(`Reading files from ${r}`);let a=await pe(r,i.platformPrefix);y(`Loading config from ${t}`);let c=await Te(t);if(e.intentId&&(c.intentId=e.intentId),e.input&&e.input.length>0){let E=Se(e.input);c.inputs={...c.inputs||{},...E}}w(`Template: ${c.template}`),y(`Intent ID: ${c.intentId}`),y("Resolving template...");let m={template:await $().resolve(c.template,{noCache:e.cache===!1,timeout:n}),inputs:c.inputs,intentId:c.intentId,platformPrefix:c.platformPrefix,options:{skipFormat:e.skipFormat??c.options?.skipFormat}},p=await(0,ne.createEngine)().upgrade(m,a,i);return p.sourceBlocked&&!e.allowSourceChange?(N("Source mismatch: current files were generated from a different template source."),tt(p.driftReport,e.json),e.json?j(JSON.stringify({blocked:!0,reason:"source_mismatch",sourceBlocked:!0,driftReport:{drift:!p.driftReport.valid,summary:p.driftReport.summary}},null,2)):s("Use --allow-source-change to proceed with source migration."),d.DRIFT_BLOCKED):(p.sourceBlocked&&N("Proceeding with source migration (--allow-source-change)."),tt(p.driftReport,e.json),!p.driftReport.valid&&!e.force?(e.json?j(JSON.stringify({blocked:!0,reason:"content_drift",sourceBlocked:p.sourceBlocked,driftReport:{drift:!0,summary:p.driftReport.summary}},null,2)):s("Use --force to proceed despite drift."),d.DRIFT_BLOCKED):(p.driftReport.valid||N("Proceeding despite drift (--force)."),e.dryRun?rr(p,a,e.json):(await re(r,p,"upgrade"),e.json?j(nr(p)):s(ct(p)),d.SUCCESS)))}async function er(t,e){let r=[];for(let[n,o]of t){if(n==="manifest.json")continue;let i="";try{i=await(0,M.readFile)((0,k.join)(e,n),"utf-8")}catch{}i!==o.content&&r.push({filename:n,diff:V(i,o.content,n)})}return r}function tr(t,e){let r=[];for(let[n,o]of t){if(n==="manifest.json")continue;let a=e.get(n)?.content??"";a!==o.content&&r.push({filename:n,diff:V(a,o.content,n)})}return r}function at(t){if(t.length===0){s(`
7
+ `);return(0,et.createTwoFilesPatch)(`expected/${r}`,`actual/${r}`,n,o,"","",{context:3})}function Ht(t){if([t.config,t.fromManifest].filter(Boolean).length>1)throw new Error("--config and --from-manifest are mutually exclusive. Use one or the other.");if(t.config&&t.directory!==".")throw new Error("--config and --directory are mutually exclusive. Use one or the other.")}function it(){return new rt.Command("render").description("Render a template to HCL files").option("-c, --config <path>","Path to envelope config file (YAML or JSON)").option("-d, --directory <dir>","Path to template version directory",".").option("-f, --fixture <path>","Input file to use (when using --directory)").option("-i, --input <key=value>","Inline input override (can be repeated)",qt,[]).requiredOption("-o, --output <dir>","Output directory").option("--from-manifest <path>","Path to manifest.json to use as input source").option("--force","Proceed even if managed files have drifted (upgrade mode)").option("--allow-source-change","Allow upgrade when template source has changed").option("--dry-run","Show what would change without writing files").option("--json","Output result as structured JSON").option("--skip-format","Skip tofu fmt formatting").option("--no-cache","Force re-fetch template").option("--timeout <ms>","Network timeout in milliseconds","30000").option("--intent-id <id>","Cluster identity for integrity tracking").action(async e=>{try{e.config&&(e.config=(0,k.resolve)(e.config)),e.fromManifest&&(e.fromManifest=(0,k.resolve)(e.fromManifest)),Ht({config:e.config,directory:e.directory,fromManifest:e.fromManifest});let r=await Kt(e);process.exit(r)}catch(r){S(r)}})}function qt(t,e){return[...e,t]}function Ie(t){let e={};if(!t)return e;for(let r of t){let n=r.indexOf("=");if(n>0){let o=r.substring(0,n),i=r.substring(n+1);e[o]=i}}return e}async function Kt(t){t.json&&Ae(!0);let e=(0,k.resolve)(t.output),r=parseInt(t.timeout,10);if(t.fromManifest)return Yt(t.fromManifest,t,e,r);if(t.config){let o=(0,k.join)(e,"manifest.json");return await st(o)?er(t.config,t,e,r,o):Qt(t.config,t,e,r)}let n=(0,k.resolve)(t.directory);return Zt(n,t,e,r)}async function st(t){try{return await(0,L.access)(t),!0}catch{return!1}}async function Xt(t){if(!process.stdin.isTTY)return!1;let e=(0,nt.createInterface)({input:process.stdin,output:process.stderr});return new Promise(r=>{e.question(`${t} [y/N] `,n=>{e.close(),r(n.trim().toLowerCase()==="y")})})}async function Gt(t){try{return(await(0,L.readdir)(t,{withFileTypes:!0})).filter(r=>r.isFile()&&r.name!=="config.yaml"&&r.name!=="config.json").length}catch{return 0}}async function Fe(t,e){if(e.force)return null;let r=await Gt(t);return r===0||(N(`Output directory contains ${r} existing files that will be overwritten.`),await Xt("Proceed?"))?null:(e.json?j(JSON.stringify({aborted:!0,reason:"overwrite_confirmation",existingFiles:r},null,2)):s("Aborted. Use --force to skip confirmation."),d.VALIDATION_ERROR)}async function Yt(t,e,r,n){y(`Loading manifest from ${t}`);let o=await te(t),i=Ze(o);e.intentId&&(i.intentId=e.intentId),w(`Template: ${i.template}`),y(`Intent ID: ${i.intentId}`),y("Resolving template...");let f={template:await P().resolve(i.template,{noCache:e.cache===!1,timeout:n}),inputs:i.inputs,intentId:i.intentId,platformPrefix:i.platformPrefix,options:{skipFormat:e.skipFormat}};y("Re-rendering from manifest...");let p=await(0,oe.createEngine)().install(f);if(e.dryRun)return await Ce(p,r,e.json);let l=await Fe(r,e);return l!==null?l:(w("Inputs validated"),w(`Generated ${p.files.size-1} files + manifest.json`),e.skipFormat||w("Formatted with tofu fmt"),await ne(r,p,"install"),$e(p,r,e.json),d.SUCCESS)}async function Zt(t,e,r,n){let o=`file://${t}`,i=e.fixture?(0,k.resolve)(t,e.fixture):null;if(i){if(!await st(i))return g(`Fixture not found: ${e.fixture}`),d.VALIDATION_ERROR}else if(i=await H(t),!i)return g("No default fixture found at tests/default/inputs.yaml"),s("Either create a default fixture or use --fixture to specify one."),d.VALIDATION_ERROR;let a=await(0,L.readFile)(i,"utf-8"),c=ot.load(a),f=Ie(e.input);c={...c,...f};let u=e.intentId??c.intentId??`local-${Date.now()}`;w(`Template: ${o}`),y(`Fixture: ${i}`),y(`Intent ID: ${u}`),y("Resolving template...");let m={template:await P().resolve(o,{noCache:e.cache===!1,timeout:n}),inputs:c,intentId:u,options:{skipFormat:e.skipFormat}},E=(0,oe.createEngine)();y("Validating inputs...");let O=await E.install(m);if(e.dryRun)return await Ce(O,r,e.json);let x=await Fe(r,e);return x!==null?x:(w("Inputs validated"),w(`Generated ${O.files.size-1} files + manifest.json`),e.skipFormat||w("Formatted with tofu fmt"),await ne(r,O,"install"),$e(O,r,e.json),d.SUCCESS)}async function Qt(t,e,r,n){y(`Loading config from ${t}`);let o=await Se(t);if(e.intentId&&(o.intentId=e.intentId),e.input&&e.input.length>0){let l=Ie(e.input);o.inputs={...o.inputs||{},...l}}w(`Template: ${o.template}`),y(`Intent ID: ${o.intentId}`),y("Resolving template...");let c={template:await P().resolve(o.template,{noCache:e.cache===!1,timeout:n}),inputs:o.inputs,intentId:o.intentId,platformPrefix:o.platformPrefix,options:{skipFormat:e.skipFormat??o.options?.skipFormat}},f=(0,oe.createEngine)();y("Validating inputs...");let u=await f.install(c);if(e.dryRun)return await Ce(u,r,e.json);let p=await Fe(r,e);return p!==null?p:(w("Inputs validated"),w(`Generated ${u.files.size-1} files + manifest.json`),c.options?.skipFormat||w("Formatted with tofu fmt"),await ne(r,u,"install"),$e(u,r,e.json),d.SUCCESS)}async function er(t,e,r,n,o){y(`Existing manifest detected at ${o} \u2014 running upgrade`);let i=await te(o);y(`Reading files from ${r}`);let a=await ge(r,i.platformPrefix);y(`Loading config from ${t}`);let c=await Se(t);if(e.intentId&&(c.intentId=e.intentId),e.input&&e.input.length>0){let E=Ie(e.input);c.inputs={...c.inputs||{},...E}}w(`Template: ${c.template}`),y(`Intent ID: ${c.intentId}`),y("Resolving template...");let p={template:await P().resolve(c.template,{noCache:e.cache===!1,timeout:n}),inputs:c.inputs,intentId:c.intentId,platformPrefix:c.platformPrefix,options:{skipFormat:e.skipFormat??c.options?.skipFormat}},m=await(0,oe.createEngine)().upgrade(p,a,i);return m.sourceBlocked&&!e.allowSourceChange?(N("Source mismatch: current files were generated from a different template source."),tt(m.driftReport,e.json),e.json?j(JSON.stringify({blocked:!0,reason:"source_mismatch",sourceBlocked:!0,driftReport:{drift:!m.driftReport.valid,summary:m.driftReport.summary}},null,2)):s("Use --allow-source-change to proceed with source migration."),d.DRIFT_BLOCKED):(m.sourceBlocked&&N("Proceeding with source migration (--allow-source-change)."),tt(m.driftReport,e.json),!m.driftReport.valid&&!e.force?(e.json?j(JSON.stringify({blocked:!0,reason:"content_drift",sourceBlocked:m.sourceBlocked,driftReport:{drift:!0,summary:m.driftReport.summary}},null,2)):s("Use --force to proceed despite drift."),d.DRIFT_BLOCKED):(m.driftReport.valid||N("Proceeding despite drift (--force)."),e.dryRun?nr(m,a,e.json):(await ne(r,m,"upgrade"),e.json?j(or(m)):s(ct(m)),d.SUCCESS)))}async function tr(t,e){let r=[];for(let[n,o]of t){if(n==="manifest.json")continue;let i="";try{i=await(0,L.readFile)((0,k.join)(e,n),"utf-8")}catch{}i!==o.content&&r.push({filename:n,diff:M(i,o.content,n)})}return r}function rr(t,e){let r=[];for(let[n,o]of t){if(n==="manifest.json")continue;let a=e.get(n)?.content??"";a!==o.content&&r.push({filename:n,diff:M(a,o.content,n)})}return r}function at(t){if(t.length===0){s(`
8
8
  No file changes detected.`);return}s(`
9
9
  ${t.length} file(s) changed:
10
- `);for(let e of t)s(e.diff)}function tt(t,e){if(e){j(JSON.stringify({drift:!t.valid,summary:t.summary,entries:t.entries.map(n=>({path:n.path,type:n.type,details:n.details}))},null,2));return}if(t.valid){w("No drift detected in managed files.");return}let r=t.summary.driftedFiles+t.summary.missingFiles+t.summary.unexpectedFiles+t.summary.inconsistentFiles;N(`Drift detected in ${r} managed files:`);for(let n of t.entries)s(` ${ue(n.type)} ${n.path}`),s(` ${n.details}`);s(""),s("Use --force to override and proceed with upgrade.")}async function Fe(t,e,r){let n=await er(t.files,e);return r?j(JSON.stringify({dryRun:!0,aggregateHash:t.manifest.aggregateHash,templateSource:t.manifest.templateSource,templateVersion:t.manifest.templateVersion,fileCount:t.files.size,files:Array.from(t.files.entries()).map(([o,i])=>({path:o,size:Buffer.byteLength(i.content,"utf-8")})),changes:n.map(o=>({filename:o.filename,diff:o.diff}))},null,2)):(s("Dry run \u2014 no files written."),s(""),s(`Aggregate Hash: ${t.manifest.aggregateHash}`),s(`Template: ${t.manifest.templateSource}`),s(`Version: ${t.manifest.templateVersion}`),s(`Files: ${t.files.size}`),at(n)),d.SUCCESS}function Ce(t,e,r){if(r)j(JSON.stringify({success:!0,aggregateHash:t.manifest.aggregateHash,fileCount:t.files.size,outputDir:e,files:Array.from(t.files.entries()).map(([n,o])=>({path:n,size:Buffer.byteLength(o.content,"utf-8")}))},null,2));else{let n=[];for(let[o,i]of t.files)n.push({path:o,size:Buffer.byteLength(i.content,"utf-8")});s(""),s(`Output written to: ${e}`),s(`Aggregate Hash: ${t.manifest.aggregateHash}`),Ae(n)}}function rr(t,e,r){let n=tr(t.files,e);return r?j(JSON.stringify({dryRun:!0,templateSource:t.manifest.templateSource,templateVersion:t.manifest.templateVersion,aggregateHash:t.manifest.aggregateHash,driftReport:t.driftReport,files:Array.from(t.files.entries()).map(([o,i])=>({path:o,zone:i.zone})),changes:n.map(o=>({filename:o.filename,diff:o.diff}))},null,2)):(s("Dry run \u2014 no files written."),s(""),s(ct(t)),at(n)),d.SUCCESS}function ct(t){let e=[];e.push(`\u2713 Upgrade complete: ${t.manifest.templateSource} v${t.manifest.templateVersion}`),e.push("");let r=0,n=0;for(let[,o]of t.files)o.zone==="platform"?r++:n++;return e.push(` Updated: ${r} files (Platform Zone)`),e.push(` Unchanged: ${n} files (User Zone)`),e.push(""),e.push(`Aggregate Hash: ${t.manifest.aggregateHash}`),e.join(`
11
- `)}function nr(t){return JSON.stringify({success:!0,templateSource:t.manifest.templateSource,templateVersion:t.manifest.templateVersion,aggregateHash:t.manifest.aggregateHash,driftReport:t.driftReport,files:Array.from(t.files.entries()).map(([e,r])=>({path:e,zone:r.zone}))},null,2)}var ut=require("commander"),de=require("fs/promises"),ge=require("path"),ft=B(require("handlebars")),he=require("@truefoundry/tfy-infra-engine");function lt(){return new ut.Command("validate").description("Validate a template (for template authors)").option("-d, --directory <dir>","Path to local template directory",".").option("--strict","Fail on warnings").option("--skip-format","Skip tofu fmt validation").action(async e=>{try{let r=await ir(e);process.exit(r)}catch(r){S(r)}})}async function or(t){try{return await(0,de.stat)(t),!0}catch{return!1}}async function ir(t){let e=(0,ge.resolve)(t.directory),r=[],n=!1;s(`Validating template at: ${e}`),s("");let o=(0,ge.join)(e,"bundle.json");if(!await or(o))return g("bundle.json not found in template directory"),d.VALIDATION_ERROR;y("Checking bundle.json...");try{let u=await(0,de.readFile)(o,"utf-8"),f;try{f=JSON.parse(u)}catch(D){throw new Error(`Invalid JSON: ${D.message}`,{cause:D})}let m=f.metadata;if(!m||typeof m!="object")throw new Error('bundle.json must contain a "metadata" object');if(!m.name||!m.version)throw new Error('bundle.json metadata must contain "name" and "version"');let l=f.jsonSchema;if(!l||typeof l!="object")throw new Error('bundle.json must contain a "jsonSchema" object');(0,he.validateJsonSchemaStructure)(l),w("bundle.json schema is valid");let p=f.files,E=f.staticFiles,b=Object.keys(p??{}).length,O=Object.keys(E??{}).length;if(b===0&&O===0)r.push({type:"error",message:"Bundle contains no template files"}),n=!0;else{for(let[D,U]of Object.entries(p??{}))try{ft.default.precompile(U),y(` \u2713 ${D}`)}catch(oe){r.push({type:"error",message:`Invalid Handlebars syntax: ${oe.message}`,file:D}),n=!0}w(`Found ${b} HBS template(s)`+(O>0?` and ${O} static file(s)`:"")+" with valid syntax")}}catch(u){r.push({type:"error",message:u.message,file:"bundle.json"}),n=!0}t.skipFormat||(y("Checking tofu fmt availability..."),await(0,he.isTofuAvailable)()?w("tofu fmt available"):r.push({type:"warning",message:"tofu not found - format validation skipped"})),s("");let a=r.filter(u=>u.type==="warning"),c=r.filter(u=>u.type==="error");if(a.length>0){N(`${a.length} warning(s):`);for(let u of a){let f=u.file?`${u.file}: `:"";s(` \u2022 ${f}${u.message}`)}s("")}if(c.length>0){g(`${c.length} error(s):`);for(let u of c){let f=u.file?`${u.file}: `:"";s(` \u2022 ${f}${u.message}`)}s("")}return n?(g("Template validation failed"),d.VALIDATION_ERROR):t.strict&&a.length>0?(g("Template validation failed (strict mode)"),d.VALIDATION_ERROR):(w("Template is valid"),d.SUCCESS)}var pt=require("commander"),I=require("fs/promises"),P=require("path"),mt=B(require("js-yaml")),dt=require("@truefoundry/tfy-infra-engine");function gt(){return new pt.Command("test").description("Run test fixtures against a template").option("-d, --directory <dir>","Path to local template directory",".").option("--fixture <name>","Run specific fixture only").option("--update","Update expected outputs with actual results").option("--diff","Show unified diff on failures").option("--skip-format","Skip tofu fmt in tests").action(async e=>{try{let r=await sr(e);process.exit(r)}catch(r){S(r)}})}async function sr(t){let e=(0,P.resolve)(t.directory),r=(0,P.join)(e,"tests"),n=`file://${e}`;s(`Testing template at: ${e}`),s("");let o;try{let l=await(0,I.readdir)(r);o=[];for(let p of l)try{let E=(0,P.join)(r,p,"inputs.yaml");await(0,I.readFile)(E),o.push(p)}catch{}}catch{return g(`No tests directory found at ${r}`),d.VALIDATION_ERROR}if(o.length===0)return g("No test fixtures found"),s(""),s("Expected structure:"),s(" tests/"),s(" <fixture-name>/"),s(" inputs.yaml"),s(" expected/"),d.VALIDATION_ERROR;if(t.fixture){if(!o.includes(t.fixture))return g(`Fixture '${t.fixture}' not found`),s(`Available fixtures: ${o.join(", ")}`),d.VALIDATION_ERROR;o=[t.fixture]}s(`Running ${o.length} fixture(s)...`),s("");let a=await $().resolve(n,{noCache:!0}),c=(0,dt.createEngine)(),u=[];for(let l of o){let p=(0,P.join)(r,l),E=(0,P.join)(p,"inputs.yaml"),b=(0,P.join)(p,"expected");y(`Running fixture: ${l}`);try{let O=await(0,I.readFile)(E,"utf-8"),D=mt.load(O),U={template:a,inputs:D,intentId:`test-${l}`,options:{skipFormat:t.skipFormat}},oe=await c.install(U);if(t.update)await ar(b,oe),w(`${l}: updated`),u.push({name:l,passed:!0});else{let{errors:we,diffs:ie}=await cr(b,oe,t.diff);if(we.length===0)w(`${l}: passed`),u.push({name:l,passed:!0});else{g(`${l}: failed`);for(let K of we)s(` ${K}`);if(t.diff&&ie&&ie.length>0)for(let K of ie){s(` --- expected/${K.filename}`),s(` +++ actual/${K.filename}`);let Ct=K.diff.split(`
12
- `).slice(4);for(let je of Ct)je&&s(` ${je}`)}u.push({name:l,passed:!1,errors:we,diffs:ie})}}}catch(O){g(`${l}: error`),s(` ${O.message}`),u.push({name:l,passed:!1,errors:[O.message]})}}s("");let f=u.filter(l=>l.passed).length,m=u.filter(l=>!l.passed).length;return m===0?(w(`All ${f} fixture(s) passed`),d.SUCCESS):(g(`${m} fixture(s) failed, ${f} passed`),d.VALIDATION_ERROR)}function $e(t){return t.replace(/("source"\s*:\s*)"file:\/\/[^"]*"/g,'$1"<local>"')}async function ar(t,e){await(0,I.mkdir)(t,{recursive:!0});for(let[r,n]of e.files){if(r==="manifest.json")continue;let o=(0,P.join)(t,r);await(0,I.writeFile)(o,$e(n.content),"utf-8")}}async function cr(t,e,r){let n=[],o=[];for(let[i,a]of e.files){if(i==="manifest.json")continue;let c=a.content,u=(0,P.join)(t,i);try{let f=await(0,I.readFile)(u,"utf-8"),m=$e(c.trim().replace(/\r\n/g,`
13
- `)),l=$e(f.trim().replace(/\r\n/g,`
14
- `));if(m!==l&&(n.push(`${i}: content mismatch`),r)){let p=V(l,m,i);o.push({filename:i,diff:p})}}catch{n.push(`${i}: expected file not found`)}}try{let i=await(0,I.readdir)(t);for(let a of i)!e.files.has(a)&&a!=="manifest.json"&&n.push(`${a}: unexpected file in expected/`)}catch{}return{errors:n,diffs:r?o:void 0}}var vt=require("commander"),v=require("fs/promises"),h=require("path"),De=B(require("js-yaml")),L=require("@truefoundry/tfy-infra-engine");var ht=B(require("chokidar")),yt=require("path");function ur(t,e){let r=t.replace(/\\/g,"/"),n=(0,yt.basename)(r);return e&&r.endsWith(e.replace(/\\/g,"/"))||n==="bundle.json"||n==="schema-pkg.json"?"schema":/\/src\//.test(r)&&!n.startsWith("_")?"template":n==="inputs.yaml"?"inputs":"other"}function Pe(t,e={}){let{onChange:r,onReady:n,onError:o,ignored:i=/(^|[/\\])\..|(^|[/\\])node_modules($|[/\\])|(^|[/\\])dev($|[/\\])/,debounceMs:a=100,schemaFile:c}=e,u=ht.default.watch(t,{ignored:i,persistent:!0,ignoreInitial:!0,awaitWriteFinish:{stabilityThreshold:a,pollInterval:50}}),f=m=>l=>{let p=ur(l,c);r&&r({type:m,path:l,fileType:p})};return u.on("change",f("change")),u.on("add",f("add")),u.on("unlink",f("unlink")),n&&u.on("ready",n),o&&u.on("error",m=>o(m instanceof Error?m:new Error(String(m)))),u}function Rt(){return new vt.Command("dev").description("Watch mode for rapid template iteration").option("-d, --directory <dir>","Path to template version directory").option("--schema <path>","Path to schema package JSON (from tfy-schema-tool --schema-package)").option("--src <dir>","Path to template src/ directory").option("--name <string>","Template name (used with --schema mode)").option("--version <semver>","Template version (used with --schema mode)").option("-f, --fixture <path>","Input file or fixture to use for testing").requiredOption("-o, --output <dir>","Output directory for rendered files").option("--no-test","Only validate, do not run tests").action(async e=>{try{let r=await pr(e);process.exit(r)}catch(r){S(r)}})}async function fr(t){try{await(0,v.access)(t)}catch{return{valid:!1,error:`Directory not found: ${t}`}}let e=(0,h.join)(t,"bundle.json");try{return await(0,v.access)(e),{valid:!0}}catch{return{valid:!1,error:`bundle.json not found in ${t}`}}}async function Et(t,e){let r=(0,h.resolve)(e);try{await(0,v.rm)(r,{recursive:!0,force:!0})}catch{}return await(0,v.mkdir)(r,{recursive:!0}),r}function lr(t){return!!(t.schema&&t.src)}async function pr(t){return lr(t)?mr(t):yr(t)}async function mr(t){let e=(0,h.resolve)(t.schema),r=(0,h.resolve)(t.src),n=t.name??"dev-template",o=t.version,i;if(t.fixture){i=(0,h.resolve)(t.fixture);try{await(0,v.access)(i)}catch{return g(`Fixture not found: ${t.fixture}`),d.VALIDATION_ERROR}}else return g("--fixture is required in schema mode"),s("Use -f to specify an inputs.yaml file."),d.VALIDATION_ERROR;let a=await Et(".",t.output),c=(0,h.relative)(process.cwd(),a);s(`[schema] ${(0,h.relative)(process.cwd(),e)}`),s(`[src] ${(0,h.relative)(process.cwd(),r)}`),s(`[output] ${c}/`),s(`[fixture] ${(0,h.relative)(process.cwd(),i)}`),s("");let u=(0,L.createEngine)(),f=async()=>{try{let p=await gr(e,r,n,o);await hr(u,p,i,a,t.test)}catch(p){g("[render] \u2717 Render failed"),s(` ${p.message}`)}};await f();let l=Pe([e,r,i],{schemaFile:e,onChange:p=>{(async()=>(s(`[changed] ${(0,h.relative)(process.cwd(),p.path)}`),await f()))()},onReady:()=>{s("[ready] Watching for changes... (Ctrl+C to exit)"),s("")},onError:p=>{g(`Watch error: ${p.message}`)}});return new Promise(p=>{let E=()=>{s(""),s("[stopped] Watch mode ended"),l.close(),p(d.SUCCESS)};process.on("SIGINT",E),process.on("SIGTERM",E)})}function dr(t){let e=t.properties;if(!e)return;let r=e.version;if(!r||typeof r=="boolean")return;let n=r.default;return typeof n=="string"?n:void 0}async function gr(t,e,r,n){let o=await(0,v.readFile)(t,"utf-8"),a=JSON.parse(o).jsonSchema;if(!a||typeof a!="object")throw new Error('Schema package must contain a "jsonSchema" object');let c=n??dr(a)??"0.0.0-dev",{files:u,staticFiles:f}=await ae(e);return{metadata:{name:r,version:c},jsonSchema:a,files:u,staticFiles:f,source:`file://${e}`,version:{semver:c}}}async function hr(t,e,r,n,o){let i=await(0,v.readFile)(r,"utf-8"),a=De.load(i),c={template:e,inputs:a,intentId:`dev-${Date.now()}`,options:{skipFormat:!1}},u=await t.install(c),f=Array.from(u.files.keys()).filter(l=>l!=="manifest.json").length;s(`[validate] \u2713 ${f} template files valid`),await(0,v.rm)(n,{recursive:!0,force:!0}),await(0,v.mkdir)(n,{recursive:!0});let m=0;for(let[l,p]of u.files){if(l==="manifest.json")continue;let E=(0,h.join)(n,l);await(0,v.mkdir)((0,h.dirname)(E),{recursive:!0}),await(0,v.writeFile)(E,p.content,"utf-8"),m++}s(`[render] \u2713 ${m} files written to ${(0,h.relative)(process.cwd(),n)}/`),o!==!1&&await vr(u,n)}async function yr(t){let e=(0,h.resolve)(t.directory||"."),r=await fr(e);if(!r.valid)return g(r.error),d.VALIDATION_ERROR;let n;if(t.fixture){n=(0,h.resolve)(e,t.fixture);try{await(0,v.access)(n)}catch{return g(`Fixture not found: ${t.fixture}`),d.VALIDATION_ERROR}}else{let f=await H(e);if(!f)return g("No default fixture found at tests/default/inputs.yaml"),s("Either create a default fixture or use --fixture to specify one."),d.VALIDATION_ERROR;n=f}let o=await Et(e,t.output),i=(0,h.relative)(process.cwd(),o);s(`[watching] ${(0,h.relative)(process.cwd(),e)}`),s(`[output] ${i}/`),s(`[fixture] ${(0,h.relative)(process.cwd(),n)}`),s("");let a=(0,L.createEngine)(),c=`file://${e}`;await wt(a,c,e,n,o,t.test);let u=Pe(e,{onChange:f=>{(async()=>{let m=(0,h.relative)(e,f.path);s(`[changed] ${m}`),f.fileType==="schema"?await xt(c):(f.fileType==="template"||f.fileType==="inputs")&&await wt(a,c,e,n,o,t.test)})()},onReady:()=>{s("[ready] Watching for changes... (Ctrl+C to exit)"),s("")},onError:f=>{g(`Watch error: ${f.message}`)}});return new Promise(f=>{let m=()=>{s(""),s("[stopped] Watch mode ended"),u.close(),f(d.SUCCESS)};process.on("SIGINT",m),process.on("SIGTERM",m)})}async function xt(t){try{return(await $().resolve(t,{noCache:!0})).jsonSchema?(s("[validate] \u2713 schema valid"),!0):(g("[validate] \u2717 schema invalid"),!1)}catch(e){return g("[validate] \u2717 schema invalid"),s(` ${e.message}`),!1}}async function wt(t,e,r,n,o,i){if(await xt(e))try{let c=await(0,v.readFile)(n,"utf-8"),u=De.load(c),l={template:await $().resolve(e,{noCache:!0}),inputs:u,intentId:`dev-${Date.now()}`,options:{skipFormat:!1}},p=await t.install(l),E=Array.from(p.files.keys()).filter(O=>O!=="manifest.json").length;s(`[validate] \u2713 ${E} template files valid`),await(0,v.rm)(o,{recursive:!0,force:!0}),await(0,v.mkdir)(o,{recursive:!0});let b=0;for(let[O,D]of p.files){if(O==="manifest.json")continue;let U=(0,h.join)(o,O);await(0,v.mkdir)((0,h.dirname)(U),{recursive:!0}),await(0,v.writeFile)(U,D.content,"utf-8"),b++}s(`[render] \u2713 ${b} files written to ${(0,h.relative)(process.cwd(),o)}/`),i!==!1&&await wr(r,p,o)}catch(c){g("[render] \u2717 Render failed"),s(` ${c.message}`),c instanceof L.EngineError&&c.code===L.EngineErrorCode.INPUT_VALIDATION_FAILED&&c.details?.errors&&s(ce(c.details.errors))}}async function wr(t,e,r){let n=(0,h.join)(t,"tests","default","expected");await Rr(e,n)}async function vr(t,e){}async function Rr(t,e){let r=Date.now();try{await(0,v.access)(e)}catch{s("[test] \u26A0 No expected/ directory, skipping comparison");return}let n=[];for(let[i,a]of t.files){if(i==="manifest.json")continue;let c=a.content,u=(0,h.join)(e,i);try{let f=await(0,v.readFile)(u,"utf-8"),m=c.trim().replace(/\r\n/g,`
15
- `),l=f.trim().replace(/\r\n/g,`
16
- `);if(m!==l){let p=V(l,m,i);n.push({file:i,diff:p})}}catch{n.push({file:i})}}let o=Date.now()-r;if(n.length===0)s(`[test] \u2713 default: passed (${o}ms)`);else{g("[test] \u2717 default: failed");for(let i of n)if(s(` ${i.file}: content mismatch`),i.diff){let a=i.diff.split(`
10
+ `);for(let e of t)s(e.diff)}function tt(t,e){if(e){j(JSON.stringify({drift:!t.valid,summary:t.summary,entries:t.entries.map(n=>({path:n.path,type:n.type,details:n.details}))},null,2));return}if(t.valid){w("No drift detected in managed files.");return}let r=t.summary.driftedFiles+t.summary.missingFiles+t.summary.unexpectedFiles+t.summary.inconsistentFiles;N(`Drift detected in ${r} managed files:`);for(let n of t.entries)s(` ${le(n.type)} ${n.path}`),s(` ${n.details}`);s(""),s("Use --force to override and proceed with upgrade.")}async function Ce(t,e,r){let n=await tr(t.files,e);return r?j(JSON.stringify({dryRun:!0,aggregateHash:t.manifest.aggregateHash,templateSource:t.manifest.templateSource,templateVersion:t.manifest.templateVersion,fileCount:t.files.size,files:Array.from(t.files.entries()).map(([o,i])=>({path:o,size:Buffer.byteLength(i.content,"utf-8")})),changes:n.map(o=>({filename:o.filename,diff:o.diff}))},null,2)):(s("Dry run \u2014 no files written."),s(""),s(`Aggregate Hash: ${t.manifest.aggregateHash}`),s(`Template: ${t.manifest.templateSource}`),s(`Version: ${t.manifest.templateVersion}`),s(`Files: ${t.files.size}`),at(n)),d.SUCCESS}function $e(t,e,r){if(r)j(JSON.stringify({success:!0,aggregateHash:t.manifest.aggregateHash,fileCount:t.files.size,outputDir:e,files:Array.from(t.files.entries()).map(([n,o])=>({path:n,size:Buffer.byteLength(o.content,"utf-8")}))},null,2));else{let n=[];for(let[o,i]of t.files)n.push({path:o,size:Buffer.byteLength(i.content,"utf-8")});s(""),s(`Output written to: ${e}`),s(`Aggregate Hash: ${t.manifest.aggregateHash}`),Ve(n)}}function nr(t,e,r){let n=rr(t.files,e);return r?j(JSON.stringify({dryRun:!0,templateSource:t.manifest.templateSource,templateVersion:t.manifest.templateVersion,aggregateHash:t.manifest.aggregateHash,driftReport:t.driftReport,files:Array.from(t.files.entries()).map(([o,i])=>({path:o,zone:i.zone})),changes:n.map(o=>({filename:o.filename,diff:o.diff}))},null,2)):(s("Dry run \u2014 no files written."),s(""),s(ct(t)),at(n)),d.SUCCESS}function ct(t){let e=[];e.push(`\u2713 Upgrade complete: ${t.manifest.templateSource} v${t.manifest.templateVersion}`),e.push("");let r=0,n=0;for(let[,o]of t.files)o.zone==="platform"?r++:n++;return e.push(` Updated: ${r} files (Platform Zone)`),e.push(` Unchanged: ${n} files (User Zone)`),e.push(""),e.push(`Aggregate Hash: ${t.manifest.aggregateHash}`),e.join(`
11
+ `)}function or(t){return JSON.stringify({success:!0,templateSource:t.manifest.templateSource,templateVersion:t.manifest.templateVersion,aggregateHash:t.manifest.aggregateHash,driftReport:t.driftReport,files:Array.from(t.files.entries()).map(([e,r])=>({path:e,zone:r.zone}))},null,2)}var ft=require("commander"),ye=require("fs/promises"),ie=require("path"),lt=B(require("handlebars")),we=require("@truefoundry/tfy-infra-engine");function pt(){return new ft.Command("validate").description("Validate a template (for template authors)").option("-d, --directory <dir>","Path to local template directory",".").option("--strict","Fail on warnings").option("--skip-format","Skip tofu fmt validation").action(async e=>{try{let r=await ir(e);process.exit(r)}catch(r){S(r)}})}async function ut(t){try{return await(0,ye.stat)(t),!0}catch{return!1}}async function ir(t){let e=(0,ie.resolve)(t.directory),r=[],n=!1;s(`Validating template at: ${e}`),s("");let o=(0,ie.join)(e,"build","bundle.json"),i=(0,ie.join)(e,"bundle.json"),a=await ut(o)?o:i;if(!await ut(a))return g("bundle.json not found in template directory or build/ subdirectory"),d.VALIDATION_ERROR;y("Checking bundle.json...");try{let u=await(0,ye.readFile)(a,"utf-8"),p;try{p=JSON.parse(u)}catch(F){throw new Error(`Invalid JSON: ${F.message}`,{cause:F})}let l=p.metadata;if(!l||typeof l!="object")throw new Error('bundle.json must contain a "metadata" object');if(!l.name||!l.version)throw new Error('bundle.json metadata must contain "name" and "version"');let m=p.jsonSchema;if(!m||typeof m!="object")throw new Error('bundle.json must contain a "jsonSchema" object');(0,we.validateJsonSchemaStructure)(m),w("bundle.json schema is valid");let E=p.files,O=p.staticFiles,x=Object.keys(E??{}).length,_=Object.keys(O??{}).length;if(x===0&&_===0)r.push({type:"error",message:"Bundle contains no template files"}),n=!0;else{for(let[F,se]of Object.entries(E??{}))try{lt.default.precompile(se),y(` \u2713 ${F}`)}catch(K){r.push({type:"error",message:`Invalid Handlebars syntax: ${K.message}`,file:F}),n=!0}w(`Found ${x} HBS template(s)`+(_>0?` and ${_} static file(s)`:"")+" with valid syntax")}}catch(u){r.push({type:"error",message:u.message,file:"bundle.json"}),n=!0}t.skipFormat||(y("Checking tofu fmt availability..."),await(0,we.isTofuAvailable)()?w("tofu fmt available"):r.push({type:"warning",message:"tofu not found - format validation skipped"})),s("");let c=r.filter(u=>u.type==="warning"),f=r.filter(u=>u.type==="error");if(c.length>0){N(`${c.length} warning(s):`);for(let u of c){let p=u.file?`${u.file}: `:"";s(` \u2022 ${p}${u.message}`)}s("")}if(f.length>0){g(`${f.length} error(s):`);for(let u of f){let p=u.file?`${u.file}: `:"";s(` \u2022 ${p}${u.message}`)}s("")}return n?(g("Template validation failed"),d.VALIDATION_ERROR):t.strict&&c.length>0?(g("Template validation failed (strict mode)"),d.VALIDATION_ERROR):(w("Template is valid"),d.SUCCESS)}var mt=require("commander"),I=require("fs/promises"),D=require("path"),dt=B(require("js-yaml")),gt=require("@truefoundry/tfy-infra-engine");function ht(){return new mt.Command("test").description("Run test fixtures against a template").option("-d, --directory <dir>","Path to local template directory",".").option("--fixture <name>","Run specific fixture only").option("--update","Update expected outputs with actual results").option("--diff","Show unified diff on failures").option("--skip-format","Skip tofu fmt in tests").action(async e=>{try{let r=await sr(e);process.exit(r)}catch(r){S(r)}})}async function sr(t){let e=(0,D.resolve)(t.directory),r=(0,D.join)(e,"tests"),n=`file://${e}`;s(`Testing template at: ${e}`),s("");let o;try{let l=await(0,I.readdir)(r);o=[];for(let m of l)try{let E=(0,D.join)(r,m,"inputs.yaml");await(0,I.readFile)(E),o.push(m)}catch{}}catch{return g(`No tests directory found at ${r}`),d.VALIDATION_ERROR}if(o.length===0)return g("No test fixtures found"),s(""),s("Expected structure:"),s(" tests/"),s(" <fixture-name>/"),s(" inputs.yaml"),s(" expected/"),d.VALIDATION_ERROR;if(t.fixture){if(!o.includes(t.fixture))return g(`Fixture '${t.fixture}' not found`),s(`Available fixtures: ${o.join(", ")}`),d.VALIDATION_ERROR;o=[t.fixture]}s(`Running ${o.length} fixture(s)...`),s("");let a=await P().resolve(n,{noCache:!0}),c=(0,gt.createEngine)(),f=[];for(let l of o){let m=(0,D.join)(r,l),E=(0,D.join)(m,"inputs.yaml"),O=(0,D.join)(m,"expected");y(`Running fixture: ${l}`);try{let x=await(0,I.readFile)(E,"utf-8"),_=dt.load(x),F={template:a,inputs:_,intentId:`test-${l}`,options:{skipFormat:t.skipFormat}},se=await c.install(F);if(t.update)await ar(O,se),w(`${l}: updated`),f.push({name:l,passed:!0});else{let{errors:K,diffs:ae}=await cr(O,se,t.diff);if(K.length===0)w(`${l}: passed`),f.push({name:l,passed:!0});else{g(`${l}: failed`);for(let X of K)s(` ${X}`);if(t.diff&&ae&&ae.length>0)for(let X of ae){s(` --- expected/${X.filename}`),s(` +++ actual/${X.filename}`);let $t=X.diff.split(`
12
+ `).slice(4);for(let ke of $t)ke&&s(` ${ke}`)}f.push({name:l,passed:!1,errors:K,diffs:ae})}}}catch(x){g(`${l}: error`),s(` ${x.message}`),f.push({name:l,passed:!1,errors:[x.message]})}}s("");let u=f.filter(l=>l.passed).length,p=f.filter(l=>!l.passed).length;return p===0?(w(`All ${u} fixture(s) passed`),d.SUCCESS):(g(`${p} fixture(s) failed, ${u} passed`),d.VALIDATION_ERROR)}function Pe(t){return t.replace(/("source"\s*:\s*)"file:\/\/[^"]*"/g,'$1"<local>"')}async function ar(t,e){await(0,I.mkdir)(t,{recursive:!0});for(let[r,n]of e.files){if(r==="manifest.json")continue;let o=(0,D.join)(t,r);await(0,I.writeFile)(o,Pe(n.content),"utf-8")}}async function cr(t,e,r){let n=[],o=[];for(let[i,a]of e.files){if(i==="manifest.json")continue;let c=a.content,f=(0,D.join)(t,i);try{let u=await(0,I.readFile)(f,"utf-8"),p=Pe(c.trim().replace(/\r\n/g,`
13
+ `)),l=Pe(u.trim().replace(/\r\n/g,`
14
+ `));if(p!==l&&(n.push(`${i}: content mismatch`),r)){let m=M(l,p,i);o.push({filename:i,diff:m})}}catch{n.push(`${i}: expected file not found`)}}try{let i=await(0,I.readdir)(t);for(let a of i)!e.files.has(a)&&a!=="manifest.json"&&n.push(`${a}: unexpected file in expected/`)}catch{}return{errors:n,diffs:r?o:void 0}}var Rt=require("commander"),v=require("fs/promises"),h=require("path"),je=B(require("js-yaml")),U=require("@truefoundry/tfy-infra-engine");var yt=B(require("chokidar")),wt=require("path");function ur(t,e){let r=t.replace(/\\/g,"/"),n=(0,wt.basename)(r);return e&&r.endsWith(e.replace(/\\/g,"/"))||n==="bundle.json"||n==="schema-pkg.json"?"schema":/\/src\//.test(r)&&!n.startsWith("_")?"template":n==="inputs.yaml"?"inputs":"other"}function De(t,e={}){let{onChange:r,onReady:n,onError:o,ignored:i=/(^|[/\\])\..|(^|[/\\])node_modules($|[/\\])|(^|[/\\])dev($|[/\\])/,debounceMs:a=100,schemaFile:c}=e,f=yt.default.watch(t,{ignored:i,persistent:!0,ignoreInitial:!0,awaitWriteFinish:{stabilityThreshold:a,pollInterval:50}}),u=p=>l=>{let m=ur(l,c);r&&r({type:p,path:l,fileType:m})};return f.on("change",u("change")),f.on("add",u("add")),f.on("unlink",u("unlink")),n&&f.on("ready",n),o&&f.on("error",p=>o(p instanceof Error?p:new Error(String(p)))),f}function Et(){return new Rt.Command("dev").description("Watch mode for rapid template iteration").option("-d, --directory <dir>","Path to template version directory").option("--schema <path>","Path to schema package JSON (from tfy-schema-tool --schema-package)").option("--src <dir>","Path to template src/ directory").option("--name <string>","Template name (used with --schema mode)").option("--version <semver>","Template version (used with --schema mode)").option("-f, --fixture <path>","Input file or fixture to use for testing").requiredOption("-o, --output <dir>","Output directory for rendered files").option("--no-test","Only validate, do not run tests").action(async e=>{try{let r=await pr(e);process.exit(r)}catch(r){S(r)}})}async function fr(t){try{await(0,v.access)(t)}catch{return{valid:!1,error:`Directory not found: ${t}`}}let e=(0,h.join)(t,"build","bundle.json");try{return await(0,v.access)(e),{valid:!0}}catch{}let r=(0,h.join)(t,"bundle.json");try{return await(0,v.access)(r),{valid:!0}}catch{return{valid:!1,error:`bundle.json not found in ${t} or ${t}/build`}}}async function bt(t,e){let r=(0,h.resolve)(e);try{await(0,v.rm)(r,{recursive:!0,force:!0})}catch{}return await(0,v.mkdir)(r,{recursive:!0}),r}function lr(t){return!!(t.schema&&t.src)}async function pr(t){return lr(t)?mr(t):yr(t)}async function mr(t){let e=(0,h.resolve)(t.schema),r=(0,h.resolve)(t.src),n=t.name??"dev-template",o=t.version,i;if(t.fixture){i=(0,h.resolve)(t.fixture);try{await(0,v.access)(i)}catch{return g(`Fixture not found: ${t.fixture}`),d.VALIDATION_ERROR}}else return g("--fixture is required in schema mode"),s("Use -f to specify an inputs.yaml file."),d.VALIDATION_ERROR;let a=await bt(".",t.output),c=(0,h.relative)(process.cwd(),a);s(`[schema] ${(0,h.relative)(process.cwd(),e)}`),s(`[src] ${(0,h.relative)(process.cwd(),r)}`),s(`[output] ${c}/`),s(`[fixture] ${(0,h.relative)(process.cwd(),i)}`),s("");let f=(0,U.createEngine)(),u=async()=>{try{let m=await gr(e,r,n,o);await hr(f,m,i,a,t.test)}catch(m){g("[render] \u2717 Render failed"),s(` ${m.message}`)}};await u();let l=De([e,r,i],{schemaFile:e,onChange:m=>{(async()=>(s(`[changed] ${(0,h.relative)(process.cwd(),m.path)}`),await u()))()},onReady:()=>{s("[ready] Watching for changes... (Ctrl+C to exit)"),s("")},onError:m=>{g(`Watch error: ${m.message}`)}});return new Promise(m=>{let E=()=>{s(""),s("[stopped] Watch mode ended"),l.close(),m(d.SUCCESS)};process.on("SIGINT",E),process.on("SIGTERM",E)})}function dr(t){let e=t.properties;if(!e)return;let r=e.version;if(!r||typeof r=="boolean")return;let n=r.default;return typeof n=="string"?n:void 0}async function gr(t,e,r,n){let o=await(0,v.readFile)(t,"utf-8"),a=JSON.parse(o).jsonSchema;if(!a||typeof a!="object")throw new Error('Schema package must contain a "jsonSchema" object');let c=n??dr(a)??"0.0.0-dev",{files:f,staticFiles:u}=await ue(e);return{metadata:{name:r,version:c},jsonSchema:a,files:f,staticFiles:u,source:`file://${e}`,version:{semver:c}}}async function hr(t,e,r,n,o){let i=await(0,v.readFile)(r,"utf-8"),a=je.load(i),c={template:e,inputs:a,intentId:`dev-${Date.now()}`,options:{skipFormat:!1}},f=await t.install(c),u=Array.from(f.files.keys()).filter(l=>l!=="manifest.json").length;s(`[validate] \u2713 ${u} template files valid`),await(0,v.rm)(n,{recursive:!0,force:!0}),await(0,v.mkdir)(n,{recursive:!0});let p=0;for(let[l,m]of f.files){if(l==="manifest.json")continue;let E=(0,h.join)(n,l);await(0,v.mkdir)((0,h.dirname)(E),{recursive:!0}),await(0,v.writeFile)(E,m.content,"utf-8"),p++}s(`[render] \u2713 ${p} files written to ${(0,h.relative)(process.cwd(),n)}/`),o!==!1&&await vr(f,n)}async function yr(t){let e=(0,h.resolve)(t.directory||"."),r=await fr(e);if(!r.valid)return g(r.error),d.VALIDATION_ERROR;let n;if(t.fixture){n=(0,h.resolve)(e,t.fixture);try{await(0,v.access)(n)}catch{return g(`Fixture not found: ${t.fixture}`),d.VALIDATION_ERROR}}else{let u=await H(e);if(!u)return g("No fixture found under tests/ (checked tests/default/ and tests/minimal/)"),s("Either create a fixture or use --fixture to specify one."),d.VALIDATION_ERROR;n=u}let o=await bt(e,t.output),i=(0,h.relative)(process.cwd(),o);s(`[watching] ${(0,h.relative)(process.cwd(),e)}`),s(`[output] ${i}/`),s(`[fixture] ${(0,h.relative)(process.cwd(),n)}`),s("");let a=(0,U.createEngine)(),c=`file://${e}`;await vt(a,c,e,n,o,t.test);let f=De(e,{onChange:u=>{(async()=>{let p=(0,h.relative)(e,u.path);s(`[changed] ${p}`),u.fileType==="schema"?await xt(c):(u.fileType==="template"||u.fileType==="inputs")&&await vt(a,c,e,n,o,t.test)})()},onReady:()=>{s("[ready] Watching for changes... (Ctrl+C to exit)"),s("")},onError:u=>{g(`Watch error: ${u.message}`)}});return new Promise(u=>{let p=()=>{s(""),s("[stopped] Watch mode ended"),f.close(),u(d.SUCCESS)};process.on("SIGINT",p),process.on("SIGTERM",p)})}async function xt(t){try{return(await P().resolve(t,{noCache:!0})).jsonSchema?(s("[validate] \u2713 schema valid"),!0):(g("[validate] \u2717 schema invalid"),!1)}catch(e){return g("[validate] \u2717 schema invalid"),s(` ${e.message}`),!1}}async function vt(t,e,r,n,o,i){if(await xt(e))try{let c=await(0,v.readFile)(n,"utf-8"),f=je.load(c),l={template:await P().resolve(e,{noCache:!0}),inputs:f,intentId:`dev-${Date.now()}`,options:{skipFormat:!1}},m=await t.install(l),E=Array.from(m.files.keys()).filter(x=>x!=="manifest.json").length;s(`[validate] \u2713 ${E} template files valid`),await(0,v.rm)(o,{recursive:!0,force:!0}),await(0,v.mkdir)(o,{recursive:!0});let O=0;for(let[x,_]of m.files){if(x==="manifest.json")continue;let F=(0,h.join)(o,x);await(0,v.mkdir)((0,h.dirname)(F),{recursive:!0}),await(0,v.writeFile)(F,_.content,"utf-8"),O++}s(`[render] \u2713 ${O} files written to ${(0,h.relative)(process.cwd(),o)}/`),i!==!1&&await wr(r,m,o)}catch(c){g("[render] \u2717 Render failed"),s(` ${c.message}`),c instanceof U.EngineError&&c.code===U.EngineErrorCode.INPUT_VALIDATION_FAILED&&c.details?.errors&&s(fe(c.details.errors))}}async function wr(t,e,r){let n=(0,h.join)(t,"tests","default","expected");await Rr(e,n)}async function vr(t,e){}async function Rr(t,e){let r=Date.now();try{await(0,v.access)(e)}catch{s("[test] \u26A0 No expected/ directory, skipping comparison");return}let n=[];for(let[i,a]of t.files){if(i==="manifest.json")continue;let c=a.content,f=(0,h.join)(e,i);try{let u=await(0,v.readFile)(f,"utf-8"),p=c.trim().replace(/\r\n/g,`
15
+ `),l=u.trim().replace(/\r\n/g,`
16
+ `);if(p!==l){let m=M(l,p,i);n.push({file:i,diff:m})}}catch{n.push({file:i})}}let o=Date.now()-r;if(n.length===0)s(`[test] \u2713 default: passed (${o}ms)`);else{g("[test] \u2717 default: failed");for(let i of n)if(s(` ${i.file}: content mismatch`),i.diff){let a=i.diff.split(`
17
17
  `).slice(4,14);for(let c of a)c&&s(` ${c}`);i.diff.split(`
18
- `).length>14&&s(" ... (diff truncated)")}}}var Ot=require("commander"),ye=require("path"),bt=require("@truefoundry/tfy-infra-engine");var Er=1;function Tt(){return new Ot.Command("verify").description("Check integrity of files on disk against a manifest").option("-d, --directory <dir>","Path to cluster directory containing manifest.json",".").option("--json","Output drift report as structured JSON").action(async e=>{try{let r=await xr(e);process.exit(r)}catch(r){S(r)}})}async function xr(t){let e=(0,ye.resolve)(t.directory),r=(0,ye.join)(e,"manifest.json");y(`Loading manifest from ${r}`);let n=await ee(r);y(`Reading files from ${e}`);let o=await pe(e,n.platformPrefix),a=await(0,bt.createEngine)().verify(o,n);return t.json?s(br(a.driftReport)):s(Or(a.driftReport)),a.driftReport.valid?d.SUCCESS:Er}function Or(t){let e=[],r=t.summary.driftedFiles+t.summary.missingFiles+t.summary.unexpectedFiles;if(t.valid)e.push(`\u2713 Verification passed: ${t.summary.totalFiles} files checked, ${r} issues`),e.push(""),e.push(`Aggregate Hash: ${t.aggregateHash.expected}`);else{e.push(`\u2717 Drift detected: ${r} issues found`),e.push("");for(let n of t.entries){let o=ue(n.type);e.push(` ${o} ${n.path}`),e.push(` ${n.details}`),e.push("")}e.push(`Summary: ${t.summary.totalFiles} files checked, ${t.summary.driftedFiles} drifted, ${t.summary.missingFiles} missing`+(t.summary.unexpectedFiles>0?`, ${t.summary.unexpectedFiles} unexpected`:""))}return e.join(`
19
- `)}function br(t){return JSON.stringify(t,null,2)}function It(){let t=new St.Command("tpl").description("Template operations").enablePositionalOptions();return t.addCommand(Me()),t.addCommand(Rt()),t.addCommand(it()),t.addCommand(lt()),t.addCommand(gt()),t.addCommand(Tt()),t}process.env.INIT_CWD&&process.chdir(process.env.INIT_CWD);var Tr="0.1.4";async function Sr(){let t=new Ft.Command;t.name("tfy-init").description("TrueFoundry infrastructure templating CLI").enablePositionalOptions().version(Tr,"-v, --version","Show version").option("--verbose","Enable verbose output").option("-q, --quiet","Suppress non-error output").hook("preAction",e=>{let r=e.opts();r.quiet?Re("quiet"):r.verbose&&Re("verbose")}),t.addCommand(It()),await t.parseAsync(process.argv)}Sr().catch(t=>{console.error("Fatal error:",t),process.exit(1)});
18
+ `).length>14&&s(" ... (diff truncated)")}}}var Ot=require("commander"),ve=require("path"),Tt=require("@truefoundry/tfy-infra-engine");var Er=1;function St(){return new Ot.Command("verify").description("Check integrity of files on disk against a manifest").option("-d, --directory <dir>","Path to cluster directory containing manifest.json",".").option("--json","Output drift report as structured JSON").action(async e=>{try{let r=await br(e);process.exit(r)}catch(r){S(r)}})}async function br(t){let e=(0,ve.resolve)(t.directory),r=(0,ve.join)(e,"manifest.json");y(`Loading manifest from ${r}`);let n=await te(r);y(`Reading files from ${e}`);let o=await ge(e,n.platformPrefix),a=await(0,Tt.createEngine)().verify(o,n);return t.json?s(Or(a.driftReport)):s(xr(a.driftReport)),a.driftReport.valid?d.SUCCESS:Er}function xr(t){let e=[],r=t.summary.driftedFiles+t.summary.missingFiles+t.summary.unexpectedFiles;if(t.valid)e.push(`\u2713 Verification passed: ${t.summary.totalFiles} files checked, ${r} issues`),e.push(""),e.push(`Aggregate Hash: ${t.aggregateHash.expected}`);else{e.push(`\u2717 Drift detected: ${r} issues found`),e.push("");for(let n of t.entries){let o=le(n.type);e.push(` ${o} ${n.path}`),e.push(` ${n.details}`),e.push("")}e.push(`Summary: ${t.summary.totalFiles} files checked, ${t.summary.driftedFiles} drifted, ${t.summary.missingFiles} missing`+(t.summary.unexpectedFiles>0?`, ${t.summary.unexpectedFiles} unexpected`:""))}return e.join(`
19
+ `)}function Or(t){return JSON.stringify(t,null,2)}function Ft(){let t=new It.Command("tpl").description("Template operations").enablePositionalOptions();return t.addCommand(Le()),t.addCommand(Et()),t.addCommand(it()),t.addCommand(pt()),t.addCommand(ht()),t.addCommand(St()),t}process.env.INIT_CWD&&process.chdir(process.env.INIT_CWD);var Tr="0.1.5";async function Sr(){let t=new Ct.Command;t.name("tfy-init").description("TrueFoundry infrastructure templating CLI").enablePositionalOptions().version(Tr,"-v, --version","Show version").option("--verbose","Enable verbose output").option("-q, --quiet","Suppress non-error output").hook("preAction",e=>{let r=e.opts();r.quiet?Ee("quiet"):r.verbose&&Ee("verbose")}),t.addCommand(Ft()),await t.parseAsync(process.argv)}Sr().catch(t=>{console.error("Fatal error:",t),process.exit(1)});
20
20
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/commands/tpl/index.ts","../src/commands/tpl/bundle.ts","../src/utils/walk-src.ts","../src/utils/error-handler.ts","../src/utils/output.ts","../src/commands/tpl/render.ts","../src/resolvers/index.ts","../src/resolvers/file.ts","../src/resolvers/https.ts","../src/resolvers/base.ts","../src/cache/template-cache.ts","../src/utils/config-loader.ts","../src/utils/fs-helpers.ts","../src/utils/manifest-loader.ts","../src/utils/file-reader.ts","../src/utils/file-writer.ts","../src/utils/diff.ts","../src/commands/tpl/validate.ts","../src/commands/tpl/test.ts","../src/commands/tpl/dev.ts","../src/utils/file-watcher.ts","../src/commands/tpl/verify.ts"],"sourcesContent":["/**\n * @truefoundry/tfy-infra-cli\n *\n * CLI for TrueFoundry infrastructure templating engine.\n * Binary: tfy-init\n */\n\nimport { Command } from 'commander';\nimport { createTplCommand } from './commands/tpl/index.js';\nimport { setVerbosity } from './utils/output.js';\n\n// When invoked via `npm run`, npm sets CWD to the package root.\n// INIT_CWD preserves the directory the user actually ran the command from.\nif (process.env['INIT_CWD']) {\n process.chdir(process.env['INIT_CWD']);\n}\n\ndeclare const __CLI_VERSION__: string;\nconst VERSION = __CLI_VERSION__;\n\n/**\n * Main CLI entry point.\n */\nasync function main(): Promise<void> {\n const program = new Command();\n\n program\n .name('tfy-init')\n .description('TrueFoundry infrastructure templating CLI')\n .enablePositionalOptions()\n .version(VERSION, '-v, --version', 'Show version')\n .option('--verbose', 'Enable verbose output')\n .option('-q, --quiet', 'Suppress non-error output')\n .hook('preAction', (thisCommand) => {\n const opts = thisCommand.opts();\n if (opts['quiet']) {\n setVerbosity('quiet');\n } else if (opts['verbose']) {\n setVerbosity('verbose');\n }\n });\n\n // Add command groups\n program.addCommand(createTplCommand());\n\n // Parse and execute\n await program.parseAsync(process.argv);\n}\n\n// Run CLI\nmain().catch((error) => {\n console.error('Fatal error:', error);\n process.exit(1);\n});\n","/**\n * tfy-init tpl command group\n *\n * CHANGE (006): Removed push command (S3 support removed).\n * CHANGE (008): Removed init command (templates scaffolded via CUE).\n */\n\nimport { Command } from 'commander';\nimport { createBundleCommand } from './bundle.js';\nimport { createRenderCommand } from './render.js';\nimport { createValidateCommand } from './validate.js';\nimport { createTestCommand } from './test.js';\n\nimport { createDevCommand } from './dev.js';\nimport { createVerifyCommand } from './verify.js';\n/**\n * Create the tpl command group with all subcommands.\n */\nexport function createTplCommand(): Command {\n const tpl = new Command('tpl').description('Template operations').enablePositionalOptions();\n\n tpl.addCommand(createBundleCommand());\n tpl.addCommand(createDevCommand());\n tpl.addCommand(createRenderCommand());\n tpl.addCommand(createValidateCommand());\n tpl.addCommand(createTestCommand());\n\n tpl.addCommand(createVerifyCommand());\n\n return tpl;\n}\n","/**\n * tfy-init tpl bundle command\n *\n * Accepts a pre-computed schema package (from tfy-schema-tool --schema-package)\n * plus a src/ directory and metadata flags, and produces a bundle.json.\n * No CUE or Go dependencies — pure TypeScript.\n */\n\nimport { Command } from 'commander';\nimport { readFile, writeFile, mkdir } from 'node:fs/promises';\nimport { resolve, dirname } from 'node:path';\nimport { walkSrcDir } from '../../utils/walk-src.js';\nimport { handleEngineError } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\n\ninterface SchemaPackage {\n jsonSchema: Record<string, unknown>;\n uiSchema: unknown[];\n}\n\ninterface BundleJson {\n metadata: {\n name: string;\n version: string;\n description?: string;\n };\n jsonSchema: Record<string, unknown>;\n uiSchema: unknown[];\n files: Record<string, string>;\n staticFiles: Record<string, string>;\n version: {\n semver: string;\n };\n}\n\ninterface BundleOptions {\n schema: string;\n src: string;\n name: string;\n version?: string;\n description?: string;\n output?: string;\n}\n\nexport function createBundleCommand(): Command {\n const cmd = new Command('bundle')\n .description('Build a bundle.json from a schema package and src/ directory')\n .requiredOption('--schema <path-or-dash>', 'Schema package JSON file, or \"-\" for stdin')\n .requiredOption('--src <dir>', 'Template source directory containing .hbs and static files')\n .requiredOption('--name <string>', 'Template name')\n .option('--description <string>', 'Template description')\n .option('-o, --output <path>', 'Output file path (default: stdout)')\n .action(async (options: BundleOptions) => {\n try {\n await runBundle(options);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\nasync function readSchemaPackage(schemaPath: string): Promise<SchemaPackage> {\n let raw: string;\n\n if (schemaPath === '-') {\n raw = await readStdin();\n } else {\n raw = await readFile(resolve(schemaPath), 'utf-8');\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(`Failed to parse schema package JSON: ${(err as Error).message}`, {\n cause: err,\n });\n }\n\n const obj = parsed as Record<string, unknown>;\n if (!obj['jsonSchema'] || typeof obj['jsonSchema'] !== 'object') {\n throw new Error('Schema package must contain a \"jsonSchema\" object');\n }\n if (!Array.isArray(obj['uiSchema'])) {\n throw new Error('Schema package must contain a \"uiSchema\" array');\n }\n\n return {\n jsonSchema: obj['jsonSchema'] as Record<string, unknown>,\n uiSchema: obj['uiSchema'] as unknown[],\n };\n}\n\nfunction readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n process.stdin.on('data', (chunk: Buffer) => chunks.push(chunk));\n process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));\n process.stdin.on('error', reject);\n });\n}\n\nfunction extractVersionFromSchema(jsonSchema: Record<string, unknown>): string | undefined {\n const props = jsonSchema['properties'] as Record<string, unknown> | undefined;\n if (!props) return undefined;\n const versionProp = props['version'] as Record<string, unknown> | undefined;\n if (!versionProp) return undefined;\n const def = versionProp['default'];\n return typeof def === 'string' ? def : undefined;\n}\n\nasync function runBundle(options: BundleOptions): Promise<void> {\n const schemaPkg = await readSchemaPackage(options.schema);\n\n const version = options.version ?? extractVersionFromSchema(schemaPkg.jsonSchema);\n if (!version) {\n throw new Error(\n 'Could not determine template version. Provide --version or ensure the schema has a \"version\" property with a default value.'\n );\n }\n\n const srcDir = resolve(options.src);\n const { files, staticFiles } = await walkSrcDir(srcDir);\n\n if (files.size === 0 && staticFiles.size === 0) {\n throw new Error(`No template files found in ${srcDir}`);\n }\n\n const metadata: BundleJson['metadata'] = {\n name: options.name,\n version,\n };\n if (options.description) {\n metadata.description = options.description;\n }\n\n const bundle: BundleJson = {\n metadata,\n jsonSchema: schemaPkg.jsonSchema,\n uiSchema: schemaPkg.uiSchema,\n files: Object.fromEntries(files),\n staticFiles: Object.fromEntries(staticFiles),\n version: { semver: version },\n };\n\n const json = JSON.stringify(bundle, null, 2) + '\\n';\n\n if (options.output) {\n const outPath = resolve(options.output);\n await mkdir(dirname(outPath), { recursive: true });\n await writeFile(outPath, json, 'utf-8');\n output.success(`Bundle written to ${outPath}`);\n } else {\n process.stdout.write(json);\n }\n}\n","/**\n * Recursive directory walker for template src/ directories.\n *\n * Walks a directory tree and classifies files into Handlebars templates\n * (.hbs → rendered) and static files (copied verbatim). Skips entries\n * whose names start with '_'. Map keys are relative paths from the\n * root directory, preserving subdirectory structure.\n */\n\nimport { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nexport interface WalkResult {\n files: Map<string, string>;\n staticFiles: Map<string, string>;\n}\n\n/**\n * Recursively walk a directory, reading all files into maps.\n *\n * @param rootDir - The src/ directory to walk\n * @returns Maps of relative-path → content, split by .hbs (rendered) vs static\n */\nexport async function walkSrcDir(rootDir: string): Promise<WalkResult> {\n const files = new Map<string, string>();\n const staticFiles = new Map<string, string>();\n\n await walk(rootDir, '', files, staticFiles);\n\n return { files, staticFiles };\n}\n\nasync function walk(\n rootDir: string,\n relativePath: string,\n files: Map<string, string>,\n staticFiles: Map<string, string>\n): Promise<void> {\n const currentDir = relativePath ? join(rootDir, relativePath) : rootDir;\n const entries = await readdir(currentDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.name.startsWith('_')) continue;\n\n const entryRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;\n\n if (entry.isDirectory()) {\n await walk(rootDir, entryRelative, files, staticFiles);\n } else {\n const content = await readFile(join(currentDir, entry.name), 'utf-8');\n if (entry.name.endsWith('.hbs')) {\n const outputPath = entryRelative.replace(/\\.hbs$/, '');\n files.set(outputPath, content);\n } else {\n staticFiles.set(entryRelative, content);\n }\n }\n }\n}\n","/**\n * Shared error handling for CLI commands.\n *\n * Provides consistent error output and exit code mapping across all\n * tfy-init tpl commands.\n */\n\nimport { EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\nimport * as output from './output.js';\n\n/**\n * Standard exit codes shared across all commands.\n */\nexport const EXIT_CODES = {\n SUCCESS: 0,\n VALIDATION_ERROR: 1,\n FETCH_ERROR: 2,\n RENDER_ERROR: 3,\n FORMAT_ERROR: 4,\n DRIFT_BLOCKED: 5,\n} as const;\n\n/**\n * Handle an error from a CLI command and exit with the appropriate code.\n *\n * Provides consistent behavior:\n * - Displays the error message\n * - Shows validation error details when available\n * - Shows engine error details in verbose mode\n * - Maps EngineErrorCode to standardized exit codes\n * - Shows format hint for tofu-related errors\n *\n * @param error - The caught error\n * @param fallbackExitCode - Exit code for unrecognized errors (default: RENDER_ERROR)\n */\nexport function handleEngineError(\n error: unknown,\n fallbackExitCode: number = EXIT_CODES.RENDER_ERROR\n): never {\n if (error instanceof EngineError) {\n output.error(error.message);\n\n // Show engine error details in verbose mode\n if (output.getVerbosity() === 'verbose' && error.details) {\n output.info('');\n output.info('Details:');\n for (const [key, value] of Object.entries(error.details)) {\n if (key !== 'errors') {\n output.info(` ${key}: ${JSON.stringify(value)}`);\n }\n }\n }\n\n // Show validation error list when available\n if (error.details?.['errors']) {\n output.info('');\n output.info('Errors:');\n output.info(\n output.formatValidationErrors(\n error.details['errors'] as Array<{ path: string; message: string }>\n )\n );\n }\n\n // Map error codes to exit codes\n switch (error.code) {\n case EngineErrorCode.INPUT_VALIDATION_FAILED:\n case EngineErrorCode.SCHEMA_INVALID:\n case EngineErrorCode.ENVELOPE_VALIDATION_FAILED:\n process.exit(EXIT_CODES.VALIDATION_ERROR);\n break;\n\n case EngineErrorCode.TEMPLATE_NOT_FOUND:\n case EngineErrorCode.FILE_NOT_FOUND:\n case EngineErrorCode.NETWORK_ERROR:\n process.exit(EXIT_CODES.FETCH_ERROR);\n break;\n\n case EngineErrorCode.TOFU_NOT_FOUND:\n case EngineErrorCode.TOFU_FMT_FAILED:\n output.info('');\n output.info('Run with --skip-format to skip formatting.');\n process.exit(EXIT_CODES.FORMAT_ERROR);\n break;\n\n case EngineErrorCode.MANIFEST_PARSE_ERROR:\n process.exit(EXIT_CODES.VALIDATION_ERROR);\n break;\n\n default:\n process.exit(fallbackExitCode);\n }\n }\n\n // Generic (non-engine) error\n output.error((error as Error).message);\n if (output.getVerbosity() === 'verbose') {\n console.error((error as Error).stack);\n }\n process.exit(fallbackExitCode);\n}\n","/**\n * Output formatting utilities for CLI.\n *\n * Supports a JSON mode where human-readable output is redirected to stderr\n * so that stdout remains clean for structured machine output via `data()`.\n */\n\nimport type { DriftType } from '@truefoundry/tfy-infra-engine';\n\n/* eslint-disable no-console */\n\nexport type Verbosity = 'quiet' | 'normal' | 'verbose';\n\nlet currentVerbosity: Verbosity = 'normal';\nlet jsonMode = false;\n\nexport function setVerbosity(level: Verbosity): void {\n currentVerbosity = level;\n}\n\nexport function getVerbosity(): Verbosity {\n return currentVerbosity;\n}\n\n/**\n * Enable JSON mode: human-readable output routes to stderr,\n * structured output goes to stdout via `data()`.\n */\nexport function setJsonMode(enabled: boolean): void {\n jsonMode = enabled;\n}\n\nexport function getJsonMode(): boolean {\n return jsonMode;\n}\n\n/**\n * Write structured data to stdout. Use for JSON payloads\n * that machine consumers will parse.\n */\nexport function data(message: string): void {\n process.stdout.write(message + '\\n');\n}\n\nexport function success(message: string): void {\n if (currentVerbosity !== 'quiet') {\n const write = jsonMode ? console.error : console.log;\n write(`✓ ${message}`);\n }\n}\n\nexport function error(message: string): void {\n console.error(`✗ ${message}`);\n}\n\nexport function info(message: string): void {\n if (currentVerbosity !== 'quiet') {\n const write = jsonMode ? console.error : console.log;\n write(message);\n }\n}\n\nexport function verbose(message: string): void {\n if (currentVerbosity === 'verbose') {\n const write = jsonMode ? console.error : console.log;\n write(` ${message}`);\n }\n}\n\nexport function warn(message: string): void {\n if (currentVerbosity !== 'quiet') {\n console.warn(`⚠ ${message}`);\n }\n}\n\nexport function formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nexport function formatValidationErrors(errors: Array<{ path: string; message: string }>): string {\n return errors.map((e) => ` • ${e.path}: ${e.message}`).join('\\n');\n}\n\nexport function driftTypeTag(type: DriftType): string {\n switch (type) {\n case 'content_drift':\n return 'DRIFTED';\n case 'missing_file':\n return 'MISSING';\n case 'unexpected_file':\n return 'UNEXPECTED';\n case 'metadata_inconsistency':\n return 'INCONSISTENT';\n case 'source_mismatch':\n return 'SOURCE MISMATCH';\n }\n}\n\nexport function printFileList(files: Array<{ path: string; size: number }>): void {\n if (currentVerbosity === 'quiet') return;\n\n const write = jsonMode ? console.error : console.log;\n write('Files:');\n for (const file of files) {\n write(` - ${file.path} (${formatSize(file.size)})`);\n }\n}\n","/**\n * tfy-init tpl render command\n *\n * Unified render command that handles fresh install, upgrade, and\n * manifest re-generation via auto-detection.\n *\n * Config mode auto-detects upgrade when manifest.json exists in the\n * output directory. Manifest mode always performs a fresh install\n * (disaster recovery). Dev mode renders from a local template directory.\n */\n\nimport { Command } from 'commander';\nimport { createInterface } from 'node:readline';\nimport { readFile, readdir, access } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport * as yaml from 'js-yaml';\nimport { createEngine } from '@truefoundry/tfy-infra-engine';\nimport type {\n Envelope,\n InstallResult,\n UpgradeResult,\n DriftReport,\n FileMap,\n} from '@truefoundry/tfy-infra-engine';\nimport { createResolverRegistry } from '../../resolvers/index.js';\nimport { loadConfig } from '../../utils/config-loader.js';\nimport { loadManifest, envelopeFromManifest } from '../../utils/manifest-loader.js';\nimport { readDirectoryToFileMap } from '../../utils/file-reader.js';\nimport { writeEngineOutput } from '../../utils/file-writer.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { driftTypeTag } from '../../utils/output.js';\nimport { findDefaultFixture } from '../../utils/fs-helpers.js';\nimport { generateUnifiedDiff, type DiffEntry } from '../../utils/diff.js';\n\nexport { EXIT_CODES };\n\nexport interface RenderArgs {\n config?: string;\n directory?: string;\n fromManifest?: string;\n}\n\nexport function validateRenderArgs(args: RenderArgs): void {\n const modes = [args.config, args.fromManifest].filter(Boolean);\n if (modes.length > 1) {\n throw new Error('--config and --from-manifest are mutually exclusive. Use one or the other.');\n }\n if (args.config && args.directory !== '.') {\n throw new Error('--config and --directory are mutually exclusive. Use one or the other.');\n }\n}\n\nexport function createRenderCommand(): Command {\n const cmd = new Command('render')\n .description('Render a template to HCL files')\n .option('-c, --config <path>', 'Path to envelope config file (YAML or JSON)')\n .option('-d, --directory <dir>', 'Path to template version directory', '.')\n .option('-f, --fixture <path>', 'Input file to use (when using --directory)')\n .option('-i, --input <key=value>', 'Inline input override (can be repeated)', collect, [])\n .requiredOption('-o, --output <dir>', 'Output directory')\n .option('--from-manifest <path>', 'Path to manifest.json to use as input source')\n .option('--force', 'Proceed even if managed files have drifted (upgrade mode)')\n .option('--allow-source-change', 'Allow upgrade when template source has changed')\n .option('--dry-run', 'Show what would change without writing files')\n .option('--json', 'Output result as structured JSON')\n .option('--skip-format', 'Skip tofu fmt formatting')\n .option('--no-cache', 'Force re-fetch template')\n .option('--timeout <ms>', 'Network timeout in milliseconds', '30000')\n .option('--intent-id <id>', 'Cluster identity for integrity tracking')\n .action(async (options: RenderOptions) => {\n try {\n if (options.config) options.config = resolve(options.config);\n if (options.fromManifest) options.fromManifest = resolve(options.fromManifest);\n\n validateRenderArgs({\n config: options.config,\n directory: options.directory,\n fromManifest: options.fromManifest,\n });\n const exitCode = await runRender(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\nfunction collect(value: string, previous: string[]): string[] {\n return [...previous, value];\n}\n\ninterface RenderOptions {\n config?: string;\n directory: string;\n fixture?: string;\n input?: string[];\n output: string;\n fromManifest?: string;\n force?: boolean;\n allowSourceChange?: boolean;\n dryRun?: boolean;\n json?: boolean;\n skipFormat?: boolean;\n cache?: boolean;\n timeout: string;\n intentId?: string;\n}\n\nexport { findDefaultFixture } from '../../utils/fs-helpers.js';\n\nexport function parseInputOverrides(inputs?: string[]): Record<string, string> {\n const result: Record<string, string> = {};\n if (!inputs) return result;\n\n for (const input of inputs) {\n const eqIndex = input.indexOf('=');\n if (eqIndex > 0) {\n const key = input.substring(0, eqIndex);\n const value = input.substring(eqIndex + 1);\n result[key] = value;\n }\n }\n\n return result;\n}\n\nasync function runRender(options: RenderOptions): Promise<number> {\n if (options.json) {\n output.setJsonMode(true);\n }\n\n const outputDir = resolve(options.output);\n const timeout = parseInt(options.timeout, 10);\n\n if (options.fromManifest) {\n return runRenderFromManifest(options.fromManifest, options, outputDir, timeout);\n }\n\n if (options.config) {\n const manifestPath = join(outputDir, 'manifest.json');\n if (await fileExists(manifestPath)) {\n return runRenderFromConfigUpgrade(options.config, options, outputDir, timeout, manifestPath);\n }\n return runRenderFromConfig(options.config, options, outputDir, timeout);\n }\n\n const templateDir = resolve(options.directory);\n return runRenderFromDirectory(templateDir, options, outputDir, timeout);\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function confirmPrompt(message: string): Promise<boolean> {\n if (!process.stdin.isTTY) return false;\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n return new Promise((resolve) => {\n rl.question(`${message} [y/N] `, (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === 'y');\n });\n });\n}\n\nasync function countExistingFiles(dir: string): Promise<number> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isFile() && e.name !== 'config.yaml' && e.name !== 'config.json')\n .length;\n } catch {\n return 0;\n }\n}\n\nasync function guardOverwrite(outputDir: string, options: RenderOptions): Promise<number | null> {\n if (options.force) return null;\n const count = await countExistingFiles(outputDir);\n if (count === 0) return null;\n\n output.warn(`Output directory contains ${count} existing files that will be overwritten.`);\n const confirmed = await confirmPrompt('Proceed?');\n if (confirmed) return null;\n\n if (options.json) {\n output.data(\n JSON.stringify(\n { aborted: true, reason: 'overwrite_confirmation', existingFiles: count },\n null,\n 2\n )\n );\n } else {\n output.info('Aborted. Use --force to skip confirmation.');\n }\n return EXIT_CODES.VALIDATION_ERROR;\n}\n\n/**\n * Render from a manifest file (re-render / disaster recovery).\n */\nasync function runRenderFromManifest(\n manifestPath: string,\n options: RenderOptions,\n outputDir: string,\n timeout: number\n): Promise<number> {\n output.verbose(`Loading manifest from ${manifestPath}`);\n const manifest = await loadManifest(manifestPath);\n const rawConfig = envelopeFromManifest(manifest);\n\n if (options.intentId) {\n rawConfig.intentId = options.intentId;\n }\n\n output.success(`Template: ${rawConfig.template}`);\n output.verbose(`Intent ID: ${rawConfig.intentId}`);\n output.verbose('Resolving template...');\n\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(rawConfig.template, {\n noCache: options.cache === false,\n timeout,\n });\n\n const envelope: Envelope = {\n template,\n inputs: rawConfig.inputs,\n intentId: rawConfig.intentId,\n platformPrefix: rawConfig.platformPrefix,\n options: {\n skipFormat: options.skipFormat,\n },\n };\n\n output.verbose('Re-rendering from manifest...');\n\n const engine = createEngine();\n const result = await engine.install(envelope);\n\n if (options.dryRun) {\n return await handleInstallDryRun(result, outputDir, options.json);\n }\n\n const aborted = await guardOverwrite(outputDir, options);\n if (aborted !== null) return aborted;\n\n output.success('Inputs validated');\n output.success(`Generated ${result.files.size - 1} files + manifest.json`);\n\n if (!options.skipFormat) {\n output.success('Formatted with tofu fmt');\n }\n\n await writeEngineOutput(outputDir, result, 'install');\n\n printInstallResult(result, outputDir, options.json);\n\n return EXIT_CODES.SUCCESS;\n}\n\n/**\n * Render from a template directory with fixture inputs.\n */\nasync function runRenderFromDirectory(\n templateDir: string,\n options: RenderOptions,\n outputDir: string,\n timeout: number\n): Promise<number> {\n const templateUri = `file://${templateDir}`;\n\n let fixturePath: string | null = options.fixture ? resolve(templateDir, options.fixture) : null;\n\n if (fixturePath) {\n if (!(await fileExists(fixturePath))) {\n output.error(`Fixture not found: ${options.fixture}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n } else {\n fixturePath = await findDefaultFixture(templateDir);\n if (!fixturePath) {\n output.error('No default fixture found at tests/default/inputs.yaml');\n output.info('Either create a default fixture or use --fixture to specify one.');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n }\n\n const fixtureContent = await readFile(fixturePath, 'utf-8');\n let inputs = yaml.load(fixtureContent) as Record<string, unknown>;\n\n const overrides = parseInputOverrides(options.input);\n inputs = { ...inputs, ...overrides };\n\n const intentId =\n options.intentId ?? (inputs['intentId'] as string | undefined) ?? `local-${Date.now()}`;\n\n output.success(`Template: ${templateUri}`);\n output.verbose(`Fixture: ${fixturePath}`);\n output.verbose(`Intent ID: ${intentId}`);\n\n output.verbose('Resolving template...');\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(templateUri, {\n noCache: options.cache === false,\n timeout,\n });\n\n const envelope: Envelope = {\n template,\n inputs,\n intentId,\n options: {\n skipFormat: options.skipFormat,\n },\n };\n\n const engine = createEngine();\n\n output.verbose('Validating inputs...');\n const result = await engine.install(envelope);\n\n if (options.dryRun) {\n return await handleInstallDryRun(result, outputDir, options.json);\n }\n\n const aborted = await guardOverwrite(outputDir, options);\n if (aborted !== null) return aborted;\n\n output.success('Inputs validated');\n output.success(`Generated ${result.files.size - 1} files + manifest.json`);\n\n if (!options.skipFormat) {\n output.success('Formatted with tofu fmt');\n }\n\n await writeEngineOutput(outputDir, result, 'install');\n\n printInstallResult(result, outputDir, options.json);\n\n return EXIT_CODES.SUCCESS;\n}\n\n/**\n * Render from a config file (fresh install).\n */\nasync function runRenderFromConfig(\n configFile: string,\n options: RenderOptions,\n outputDir: string,\n timeout: number\n): Promise<number> {\n output.verbose(`Loading config from ${configFile}`);\n const rawConfig = await loadConfig(configFile);\n\n if (options.intentId) {\n rawConfig.intentId = options.intentId;\n }\n\n if (options.input && options.input.length > 0) {\n const overrides = parseInputOverrides(options.input);\n rawConfig.inputs = { ...(rawConfig.inputs || {}), ...overrides };\n }\n\n output.success(`Template: ${rawConfig.template}`);\n output.verbose(`Intent ID: ${rawConfig.intentId}`);\n\n output.verbose('Resolving template...');\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(rawConfig.template, {\n noCache: options.cache === false,\n timeout,\n });\n\n const envelope: Envelope = {\n template,\n inputs: rawConfig.inputs,\n intentId: rawConfig.intentId,\n platformPrefix: rawConfig.platformPrefix,\n options: {\n skipFormat: options.skipFormat ?? rawConfig.options?.skipFormat,\n },\n };\n\n const engine = createEngine();\n\n output.verbose('Validating inputs...');\n const result = await engine.install(envelope);\n\n if (options.dryRun) {\n return await handleInstallDryRun(result, outputDir, options.json);\n }\n\n const aborted = await guardOverwrite(outputDir, options);\n if (aborted !== null) return aborted;\n\n output.success('Inputs validated');\n output.success(`Generated ${result.files.size - 1} files + manifest.json`);\n\n if (!envelope.options?.skipFormat) {\n output.success('Formatted with tofu fmt');\n }\n\n await writeEngineOutput(outputDir, result, 'install');\n\n printInstallResult(result, outputDir, options.json);\n\n return EXIT_CODES.SUCCESS;\n}\n\n/**\n * Upgrade from a config file when manifest.json exists in the output directory.\n * Auto-detected when config mode finds an existing manifest.\n */\nasync function runRenderFromConfigUpgrade(\n configFile: string,\n options: RenderOptions,\n outputDir: string,\n timeout: number,\n manifestPath: string\n): Promise<number> {\n output.verbose(`Existing manifest detected at ${manifestPath} — running upgrade`);\n\n const previousManifest = await loadManifest(manifestPath);\n\n output.verbose(`Reading files from ${outputDir}`);\n const currentFiles = await readDirectoryToFileMap(outputDir, previousManifest.platformPrefix);\n\n output.verbose(`Loading config from ${configFile}`);\n const rawConfig = await loadConfig(configFile);\n\n if (options.intentId) {\n rawConfig.intentId = options.intentId;\n }\n\n if (options.input && options.input.length > 0) {\n const overrides = parseInputOverrides(options.input);\n rawConfig.inputs = { ...(rawConfig.inputs || {}), ...overrides };\n }\n\n output.success(`Template: ${rawConfig.template}`);\n output.verbose(`Intent ID: ${rawConfig.intentId}`);\n\n output.verbose('Resolving template...');\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(rawConfig.template, {\n noCache: options.cache === false,\n timeout,\n });\n\n const envelope: Envelope = {\n template,\n inputs: rawConfig.inputs,\n intentId: rawConfig.intentId,\n platformPrefix: rawConfig.platformPrefix,\n options: {\n skipFormat: options.skipFormat ?? rawConfig.options?.skipFormat,\n },\n };\n\n const engine = createEngine();\n const result = await engine.upgrade(envelope, currentFiles, previousManifest);\n\n if (result.sourceBlocked && !options.allowSourceChange) {\n output.warn('Source mismatch: current files were generated from a different template source.');\n printDriftReport(result.driftReport, options.json);\n if (options.json) {\n output.data(\n JSON.stringify(\n {\n blocked: true,\n reason: 'source_mismatch',\n sourceBlocked: true,\n driftReport: { drift: !result.driftReport.valid, summary: result.driftReport.summary },\n },\n null,\n 2\n )\n );\n } else {\n output.info('Use --allow-source-change to proceed with source migration.');\n }\n return EXIT_CODES.DRIFT_BLOCKED;\n }\n if (result.sourceBlocked) {\n output.warn('Proceeding with source migration (--allow-source-change).');\n }\n\n printDriftReport(result.driftReport, options.json);\n\n if (!result.driftReport.valid && !options.force) {\n if (options.json) {\n output.data(\n JSON.stringify(\n {\n blocked: true,\n reason: 'content_drift',\n sourceBlocked: result.sourceBlocked,\n driftReport: { drift: true, summary: result.driftReport.summary },\n },\n null,\n 2\n )\n );\n } else {\n output.info('Use --force to proceed despite drift.');\n }\n return EXIT_CODES.DRIFT_BLOCKED;\n }\n if (!result.driftReport.valid) {\n output.warn('Proceeding despite drift (--force).');\n }\n\n if (options.dryRun) {\n return handleUpgradeDryRun(result, currentFiles, options.json);\n }\n\n await writeEngineOutput(outputDir, result, 'upgrade');\n\n if (options.json) {\n output.data(formatUpgradeResultJson(result));\n } else {\n output.info(formatUpgradeResult(result));\n }\n\n return EXIT_CODES.SUCCESS;\n}\n\n// --- Diff computation helpers ---\n\nasync function computeInstallDiffs(newFiles: FileMap, outputDir: string): Promise<DiffEntry[]> {\n const diffs: DiffEntry[] = [];\n for (const [filename, entry] of newFiles) {\n if (filename === 'manifest.json') continue;\n let existing = '';\n try {\n existing = await readFile(join(outputDir, filename), 'utf-8');\n } catch {\n /* new file */\n }\n if (existing === entry.content) continue;\n diffs.push({ filename, diff: generateUnifiedDiff(existing, entry.content, filename) });\n }\n return diffs;\n}\n\nfunction computeUpgradeDiffs(newFiles: FileMap, currentFiles: FileMap): DiffEntry[] {\n const diffs: DiffEntry[] = [];\n for (const [filename, entry] of newFiles) {\n if (filename === 'manifest.json') continue;\n const current = currentFiles.get(filename);\n const currentContent = current?.content ?? '';\n if (currentContent === entry.content) continue;\n diffs.push({ filename, diff: generateUnifiedDiff(currentContent, entry.content, filename) });\n }\n return diffs;\n}\n\nfunction printDiffs(diffs: DiffEntry[]): void {\n if (diffs.length === 0) {\n output.info('\\nNo file changes detected.');\n return;\n }\n output.info(`\\n${diffs.length} file(s) changed:\\n`);\n for (const d of diffs) {\n output.info(d.diff);\n }\n}\n\n// --- Drift report helper ---\n\nfunction printDriftReport(report: DriftReport, json?: boolean): void {\n if (json) {\n output.data(\n JSON.stringify(\n {\n drift: !report.valid,\n summary: report.summary,\n entries: report.entries.map((e) => ({\n path: e.path,\n type: e.type,\n details: e.details,\n })),\n },\n null,\n 2\n )\n );\n return;\n }\n if (report.valid) {\n output.success('No drift detected in managed files.');\n return;\n }\n const total =\n report.summary.driftedFiles +\n report.summary.missingFiles +\n report.summary.unexpectedFiles +\n report.summary.inconsistentFiles;\n output.warn(`Drift detected in ${total} managed files:`);\n for (const entry of report.entries) {\n output.info(` ${driftTypeTag(entry.type)} ${entry.path}`);\n output.info(` ${entry.details}`);\n }\n output.info('');\n output.info('Use --force to override and proceed with upgrade.');\n}\n\n// --- Install mode output helpers ---\n\nasync function handleInstallDryRun(\n result: InstallResult,\n outputDir: string,\n json?: boolean\n): Promise<number> {\n const diffs = await computeInstallDiffs(result.files, outputDir);\n if (json) {\n output.data(\n JSON.stringify(\n {\n dryRun: true,\n aggregateHash: result.manifest.aggregateHash,\n templateSource: result.manifest.templateSource,\n templateVersion: result.manifest.templateVersion,\n fileCount: result.files.size,\n files: Array.from(result.files.entries()).map(([path, entry]) => ({\n path,\n size: Buffer.byteLength(entry.content, 'utf-8'),\n })),\n changes: diffs.map((d) => ({ filename: d.filename, diff: d.diff })),\n },\n null,\n 2\n )\n );\n } else {\n output.info('Dry run — no files written.');\n output.info('');\n output.info(`Aggregate Hash: ${result.manifest.aggregateHash}`);\n output.info(`Template: ${result.manifest.templateSource}`);\n output.info(`Version: ${result.manifest.templateVersion}`);\n output.info(`Files: ${result.files.size}`);\n printDiffs(diffs);\n }\n return EXIT_CODES.SUCCESS;\n}\n\nfunction printInstallResult(result: InstallResult, outputDir: string, json?: boolean): void {\n if (json) {\n output.data(\n JSON.stringify(\n {\n success: true,\n aggregateHash: result.manifest.aggregateHash,\n fileCount: result.files.size,\n outputDir,\n files: Array.from(result.files.entries()).map(([path, entry]) => ({\n path,\n size: Buffer.byteLength(entry.content, 'utf-8'),\n })),\n },\n null,\n 2\n )\n );\n } else {\n const fileList: Array<{ path: string; size: number }> = [];\n for (const [filePath, entry] of result.files) {\n fileList.push({\n path: filePath,\n size: Buffer.byteLength(entry.content, 'utf-8'),\n });\n }\n output.info('');\n output.info(`Output written to: ${outputDir}`);\n output.info(`Aggregate Hash: ${result.manifest.aggregateHash}`);\n output.printFileList(fileList);\n }\n}\n\n// --- Upgrade mode output helpers ---\n\nfunction handleUpgradeDryRun(result: UpgradeResult, currentFiles: FileMap, json?: boolean): number {\n const diffs = computeUpgradeDiffs(result.files, currentFiles);\n if (json) {\n output.data(\n JSON.stringify(\n {\n dryRun: true,\n templateSource: result.manifest.templateSource,\n templateVersion: result.manifest.templateVersion,\n aggregateHash: result.manifest.aggregateHash,\n driftReport: result.driftReport,\n files: Array.from(result.files.entries()).map(([path, entry]) => ({\n path,\n zone: entry.zone,\n })),\n changes: diffs.map((d) => ({ filename: d.filename, diff: d.diff })),\n },\n null,\n 2\n )\n );\n } else {\n output.info('Dry run — no files written.');\n output.info('');\n output.info(formatUpgradeResult(result));\n printDiffs(diffs);\n }\n return EXIT_CODES.SUCCESS;\n}\n\nfunction formatUpgradeResult(result: UpgradeResult): string {\n const lines: string[] = [];\n lines.push(\n `✓ Upgrade complete: ${result.manifest.templateSource} v${result.manifest.templateVersion}`\n );\n lines.push('');\n let platformCount = 0;\n let userCount = 0;\n for (const [, entry] of result.files) {\n if (entry.zone === 'platform') platformCount++;\n else userCount++;\n }\n lines.push(` Updated: ${platformCount} files (Platform Zone)`);\n lines.push(` Unchanged: ${userCount} files (User Zone)`);\n lines.push('');\n lines.push(`Aggregate Hash: ${result.manifest.aggregateHash}`);\n return lines.join('\\n');\n}\n\nfunction formatUpgradeResultJson(result: UpgradeResult): string {\n return JSON.stringify(\n {\n success: true,\n templateSource: result.manifest.templateSource,\n templateVersion: result.manifest.templateVersion,\n aggregateHash: result.manifest.aggregateHash,\n driftReport: result.driftReport,\n files: Array.from(result.files.entries()).map(([path, entry]) => ({\n path,\n zone: entry.zone,\n })),\n },\n null,\n 2\n );\n}\n","/**\n * Resolver registry for managing template source resolvers.\n *\n * Supports file:// and https://*.json bundle URIs.\n */\n\nimport type { Resolver } from './base.js';\nimport type { Template, ResolverOptions } from '@truefoundry/tfy-infra-engine';\nimport { EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\nimport { FileResolver } from './file.js';\nimport { HttpsBundleResolver } from './https.js';\nimport { TemplateCache } from '../cache/template-cache.js';\n\nexport type { Resolver } from './base.js';\nexport { FileResolver } from './file.js';\nexport { HttpsBundleResolver } from './https.js';\nexport { TemplateCache, createTemplateCache, getDefaultCacheDir } from '../cache/template-cache.js';\n\n/**\n * Registry for template resolvers.\n * Routes template URIs to the appropriate resolver based on scheme.\n */\nexport class ResolverRegistry {\n private resolvers: Resolver[] = [];\n private cache: TemplateCache;\n\n constructor(resolvers?: Resolver[], cacheDir?: string) {\n this.cache = new TemplateCache(cacheDir);\n\n if (resolvers) {\n this.resolvers = resolvers;\n } else {\n this.register(new FileResolver());\n this.register(new HttpsBundleResolver());\n }\n }\n\n register(resolver: Resolver): void {\n this.resolvers.push(resolver);\n }\n\n getResolver(uri: string): Resolver | undefined {\n return this.resolvers.find((r) => r.canResolve(uri));\n }\n\n async resolve(uri: string, options?: ResolverOptions): Promise<Template> {\n const resolver = this.getResolver(uri);\n\n if (!resolver) {\n const scheme = uri.split('://')[0] || 'unknown';\n throw new EngineError(\n `No resolver found for URI scheme: ${scheme}://`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n undefined,\n {\n uri,\n scheme,\n supportedSchemes: ['file://', 'https://*.json'],\n }\n );\n }\n\n const template = await resolver.resolve(uri, options);\n\n // Cache the result (unless it's a file:// URI)\n if (!uri.startsWith('file://')) {\n await this.cache.set(template);\n }\n\n return template;\n }\n\n canResolve(uri: string): boolean {\n return this.resolvers.some((r) => r.canResolve(uri));\n }\n\n getSupportedSchemes(): string[] {\n return ['file://', 'https://*.json'];\n }\n\n async clearCache(uri?: string): Promise<void> {\n if (uri) {\n await this.cache.delete(uri);\n } else {\n await this.cache.clear();\n }\n }\n\n async getCacheStats(): Promise<{ sources: number; templates: number; size: number }> {\n return this.cache.stats();\n }\n}\n\n/**\n * Create a default resolver registry with standard resolvers (file + https bundle).\n */\nexport function createResolverRegistry(cacheDir?: string): ResolverRegistry {\n return new ResolverRegistry(undefined, cacheDir);\n}\n","/**\n * File resolver for local template sources (file://).\n *\n * Supports bundle.json — single-file distribution format.\n *\n * Detection order: if the URI points directly at a .json file, treat it as a\n * bundle; if it's a directory containing bundle.json, load the bundle.\n */\n\nimport { readFile, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Resolver } from './base.js';\nimport type { Template, ResolverOptions, VersionInfo } from '@truefoundry/tfy-infra-engine';\nimport { EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\n\n/**\n * Resolver for file:// URIs.\n * Loads templates from the local filesystem.\n */\nexport class FileResolver implements Resolver {\n canResolve(uri: string): boolean {\n return uri.startsWith('file://');\n }\n\n async resolve(uri: string, _options?: ResolverOptions): Promise<Template> {\n const fsPath = this.uriToPath(uri);\n\n try {\n const info = await stat(fsPath);\n\n if (info.isFile() && fsPath.endsWith('.json')) {\n return this.resolveBundle(fsPath, uri);\n }\n\n if (info.isDirectory()) {\n const bundlePath = join(fsPath, 'bundle.json');\n if (await this.fileExists(bundlePath)) {\n return this.resolveBundle(bundlePath, uri);\n }\n throw new EngineError(\n `bundle.json not found in ${fsPath}`,\n EngineErrorCode.BUNDLE_NOT_FOUND,\n undefined,\n { path: fsPath }\n );\n }\n\n throw new EngineError(\n `Not a file or directory: ${fsPath}`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n undefined,\n { path: fsPath }\n );\n } catch (error) {\n if (error instanceof EngineError) {\n throw error;\n }\n\n const nodeError = error as NodeJS.ErrnoException;\n if (nodeError.code === 'ENOENT') {\n throw new EngineError(\n `Template not found: ${fsPath}`,\n EngineErrorCode.FILE_NOT_FOUND,\n error as Error,\n { path: fsPath, originalCode: nodeError.code }\n );\n }\n\n if (nodeError.code === 'EACCES') {\n throw new EngineError(\n `Permission denied: ${fsPath}`,\n EngineErrorCode.FILE_NOT_FOUND,\n error as Error,\n { path: fsPath, originalCode: nodeError.code }\n );\n }\n\n throw new EngineError(\n `Failed to load template from ${fsPath}: ${(error as Error).message}`,\n EngineErrorCode.FILE_NOT_FOUND,\n error as Error,\n { path: fsPath }\n );\n }\n }\n\n private async resolveBundle(bundlePath: string, uri: string): Promise<Template> {\n const content = await readFile(bundlePath, 'utf-8');\n\n let raw: Record<string, unknown>;\n try {\n raw = JSON.parse(content) as Record<string, unknown>;\n } catch (error) {\n throw new EngineError(\n `Failed to parse bundle.json: ${(error as Error).message}`,\n EngineErrorCode.BUNDLE_INVALID,\n error as Error,\n { path: bundlePath }\n );\n }\n\n const rawMetadata = raw['metadata'] as Record<string, unknown> | undefined;\n if (!rawMetadata || typeof rawMetadata !== 'object') {\n throw new EngineError(\n 'bundle.json must contain a \"metadata\" object',\n EngineErrorCode.BUNDLE_INVALID,\n undefined,\n { path: bundlePath }\n );\n }\n if (!rawMetadata['name'] || !rawMetadata['version']) {\n throw new EngineError(\n 'bundle.json metadata must contain \"name\" and \"version\"',\n EngineErrorCode.BUNDLE_INVALID,\n undefined,\n { path: bundlePath }\n );\n }\n\n const metadata = {\n name: rawMetadata['name'] as string,\n version: rawMetadata['version'] as string,\n ...(rawMetadata['description'] ? { description: rawMetadata['description'] as string } : {}),\n };\n\n const jsonSchema = raw['jsonSchema'] as Record<string, unknown>;\n if (!jsonSchema || typeof jsonSchema !== 'object') {\n throw new EngineError(\n 'bundle.json must contain a \"jsonSchema\" object',\n EngineErrorCode.BUNDLE_INVALID,\n undefined,\n { path: bundlePath }\n );\n }\n\n const files = new Map(Object.entries((raw['files'] as Record<string, string>) ?? {}));\n const staticFiles = new Map(\n Object.entries((raw['staticFiles'] as Record<string, string>) ?? {})\n );\n\n if (files.size === 0 && staticFiles.size === 0) {\n throw new EngineError(\n `Bundle contains no template files: ${bundlePath}`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n undefined,\n { path: bundlePath }\n );\n }\n\n const version: VersionInfo = { semver: metadata.version };\n\n return { metadata, jsonSchema, files, staticFiles, source: uri, version };\n }\n\n private uriToPath(uri: string): string {\n if (uri.startsWith('file:///')) {\n return uri.substring(7);\n }\n if (uri.startsWith('file://')) {\n return uri.substring(7);\n }\n return uri;\n }\n\n private async fileExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n }\n}\n","/**\n * HTTPS bundle resolver for pre-built template bundles (https://*.json).\n *\n * Fetches a bundle JSON file over HTTPS and unpacks it into a Template.\n * Automatically attaches a Bearer token for Artifactory hosts when\n * JFROG_ACCESS_TOKEN is present in the environment.\n */\n\nimport type { Resolver } from './base.js';\nimport type { Template, ResolverOptions, VersionInfo } from '@truefoundry/tfy-infra-engine';\nimport { validateBundle, EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\nimport { mergeResolverOptions, withRetry, withTimeout } from './base.js';\n\nconst JFROG_TOKEN_ENV = 'JFROG_ACCESS_TOKEN';\n\nfunction isArtifactoryHost(uri: string): boolean {\n try {\n const { hostname } = new URL(uri);\n return hostname.includes('jfrog');\n } catch {\n return false;\n }\n}\n\nfunction buildHeaders(uri: string): Record<string, string> {\n const headers: Record<string, string> = {\n 'User-Agent': 'tfy-infra-cli',\n Accept: 'application/json',\n };\n\n if (isArtifactoryHost(uri)) {\n const token = process.env[JFROG_TOKEN_ENV];\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n }\n\n return headers;\n}\n\nasync function fetchBundle(uri: string, headers: Record<string, string>): Promise<Response> {\n const response = await fetch(uri, { headers });\n\n if (response.status === 401 || response.status === 403) {\n const hint = isArtifactoryHost(uri)\n ? ` Set ${JFROG_TOKEN_ENV} in your environment to authenticate.`\n : '';\n throw new EngineError(\n `Authentication failed (HTTP ${response.status}) for ${uri}.${hint}`,\n EngineErrorCode.HTTPS_AUTH_FAILED,\n undefined,\n { uri, status: response.status }\n );\n }\n\n if (!response.ok) {\n throw new EngineError(\n `Bundle download failed: HTTP ${response.status} for ${uri}`,\n EngineErrorCode.NETWORK_ERROR,\n undefined,\n { uri, status: response.status }\n );\n }\n\n return response;\n}\n\nasync function unpackBundle(response: Response, uri: string): Promise<Template> {\n const body: unknown = await response.json();\n const raw = body as Record<string, unknown>;\n\n const { metadata, jsonSchema } = validateBundle(raw);\n\n const files = new Map(Object.entries((raw['files'] as Record<string, string>) ?? {}));\n const staticFiles = new Map(Object.entries((raw['staticFiles'] as Record<string, string>) ?? {}));\n\n if (files.size === 0 && staticFiles.size === 0) {\n throw new EngineError(\n `Bundle contains no template files: ${uri}`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n undefined,\n { uri }\n );\n }\n\n const version: VersionInfo = { semver: metadata.version };\n\n return { metadata, jsonSchema, files, staticFiles, source: uri, version };\n}\n\n/**\n * Resolver for HTTPS bundle URLs (https://.../*.json).\n */\nexport class HttpsBundleResolver implements Resolver {\n canResolve(uri: string): boolean {\n return uri.startsWith('https://') && uri.toLowerCase().endsWith('.json');\n }\n\n async resolve(uri: string, options?: ResolverOptions): Promise<Template> {\n const opts = mergeResolverOptions(options);\n const headers = buildHeaders(uri);\n\n const response = await withTimeout(\n withRetry(() => fetchBundle(uri, headers), opts.retries),\n opts.timeout,\n `HTTPS bundle download timed out after ${opts.timeout}ms`\n );\n\n return unpackBundle(response, uri);\n }\n}\n","/**\n * Base resolver interface and common utilities.\n *\n * Moved from @truefoundry/tfy-infra-engine (006).\n * Imports reference the engine package for shared types.\n */\n\nimport type { Template, ResolverOptions } from '@truefoundry/tfy-infra-engine';\n\n/**\n * Interface for template resolvers.\n * Each resolver handles a specific URI scheme (file://, https://).\n */\nexport interface Resolver {\n canResolve(uri: string): boolean;\n resolve(uri: string, options?: ResolverOptions): Promise<Template>;\n}\n\n/**\n * Default resolver options.\n */\nexport const DEFAULT_RESOLVER_OPTIONS: Required<ResolverOptions> = {\n timeout: 30000,\n retries: 3,\n noCache: false,\n};\n\n/**\n * Merge resolver options with defaults.\n */\nexport function mergeResolverOptions(options?: ResolverOptions): Required<ResolverOptions> {\n return {\n ...DEFAULT_RESOLVER_OPTIONS,\n ...options,\n };\n}\n\n/**\n * Retry an async operation with exponential backoff.\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n retries: number,\n baseDelay = 1000\n): Promise<T> {\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n\n if (attempt < retries) {\n const delay = baseDelay * Math.pow(2, attempt) * (0.5 + Math.random() * 0.5);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n\n throw lastError instanceof Error ? lastError : new Error(String(lastError));\n}\n\n/**\n * Create a timeout wrapper for promises.\n */\nexport function withTimeout<T>(promise: Promise<T>, ms: number, message?: string): Promise<T> {\n let timer: ReturnType<typeof setTimeout>;\n\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error(message ?? `Operation timed out after ${ms}ms`)), ms);\n });\n\n return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));\n}\n","/**\n * Template cache for storing fetched templates locally.\n * Uses filesystem-based caching with version-keyed storage.\n *\n * Stores templates as bundle.json files containing all template data\n * in a single self-contained JSON file.\n */\n\nimport { mkdir, readFile, writeFile, rm, readdir, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport type { Template, VersionInfo } from '@truefoundry/tfy-infra-engine';\nimport { validateBundle, EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\n\n/**\n * Default cache directory.\n */\nexport function getDefaultCacheDir(): string {\n const home = process.env['HOME'] || process.env['USERPROFILE'] || '~';\n return join(home, '.cache', 'tfy-infra', 'templates');\n}\n\n/**\n * Template cache manager.\n */\nexport class TemplateCache {\n private readonly cacheDir: string;\n\n constructor(cacheDir?: string) {\n this.cacheDir = cacheDir ?? getDefaultCacheDir();\n }\n\n private getCacheKey(source: string): string {\n return createHash('sha256').update(source).digest('hex').substring(0, 16);\n }\n\n private getCachePath(source: string, version: string): string {\n const key = this.getCacheKey(source);\n const safeVersion = version.replace(/\\//g, '-');\n return join(this.cacheDir, key, safeVersion);\n }\n\n async has(source: string, version: string): Promise<boolean> {\n const cachePath = this.getCachePath(source, version);\n try {\n const bundlePath = join(cachePath, 'bundle.json');\n await stat(bundlePath);\n return true;\n } catch {\n return false;\n }\n }\n\n async get(source: string, version: string): Promise<Template | null> {\n const cachePath = this.getCachePath(source, version);\n\n try {\n const bundlePath = join(cachePath, 'bundle.json');\n const bundleContent = await readFile(bundlePath, 'utf-8');\n const parsed: unknown = JSON.parse(bundleContent);\n const { metadata, jsonSchema } = validateBundle(parsed);\n\n const record = parsed as Record<string, unknown>;\n const files = new Map(Object.entries((record['files'] as Record<string, string>) ?? {}));\n const staticFiles = new Map(\n Object.entries((record['staticFiles'] as Record<string, string>) ?? {})\n );\n\n const versionInfo: VersionInfo = {\n semver: metadata.version,\n };\n\n return {\n metadata,\n jsonSchema,\n files,\n staticFiles,\n source,\n version: versionInfo,\n };\n } catch {\n return null;\n }\n }\n\n async set(template: Template): Promise<void> {\n const cachePath = this.getCachePath(template.source, template.version.semver);\n\n try {\n await mkdir(cachePath, { recursive: true });\n\n const bundle = {\n metadata: {\n name: template.metadata.name,\n version: template.metadata.version,\n ...(template.metadata.description ? { description: template.metadata.description } : {}),\n },\n jsonSchema: template.jsonSchema,\n files: Object.fromEntries(template.files),\n staticFiles: Object.fromEntries(template.staticFiles),\n };\n await writeFile(join(cachePath, 'bundle.json'), JSON.stringify(bundle, null, 2), 'utf-8');\n } catch (error) {\n throw new EngineError(\n `Failed to cache template: ${(error as Error).message}`,\n EngineErrorCode.CACHE_ERROR,\n error as Error,\n { source: template.source, version: template.version.semver }\n );\n }\n }\n\n async delete(source: string, version?: string): Promise<void> {\n if (version) {\n const cachePath = this.getCachePath(source, version);\n try {\n await rm(cachePath, { recursive: true, force: true });\n } catch {\n // Ignore if doesn't exist\n }\n } else {\n const key = this.getCacheKey(source);\n const keyPath = join(this.cacheDir, key);\n try {\n await rm(keyPath, { recursive: true, force: true });\n } catch {\n // Ignore if doesn't exist\n }\n }\n }\n\n async clear(): Promise<void> {\n try {\n await rm(this.cacheDir, { recursive: true, force: true });\n } catch {\n // Ignore if doesn't exist\n }\n }\n\n async stats(): Promise<{ sources: number; templates: number; size: number }> {\n let sources = 0;\n let templates = 0;\n let size = 0;\n\n try {\n const sourceKeys = await readdir(this.cacheDir);\n sources = sourceKeys.length;\n\n for (const key of sourceKeys) {\n const keyPath = join(this.cacheDir, key);\n const versions = await readdir(keyPath);\n templates += versions.length;\n\n for (const version of versions) {\n const versionPath = join(keyPath, version);\n size += await this.dirSize(versionPath);\n }\n }\n } catch {\n // Cache doesn't exist or is empty\n }\n\n return { sources, templates, size };\n }\n\n private async dirSize(dir: string): Promise<number> {\n let total = 0;\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n total += await this.dirSize(fullPath);\n } else {\n const fileStat = await stat(fullPath);\n total += fileStat.size;\n }\n }\n return total;\n }\n}\n\n/**\n * Create a template cache instance.\n */\nexport function createTemplateCache(cacheDir?: string): TemplateCache {\n return new TemplateCache(cacheDir);\n}\n","/**\n * Configuration file loader for YAML and JSON formats.\n *\n * The CLI command is responsible for resolving the template URI before calling the engine.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport * as yaml from 'js-yaml';\n\n/**\n * Raw configuration from a config file.\n * `template` is a URI string that must be resolved before passing to the engine.\n */\nexport interface RawConfig {\n template: string;\n inputs: Record<string, unknown>;\n intentId: string;\n platformPrefix?: string;\n options?: {\n skipFormat?: boolean;\n };\n}\n\n/**\n * Load a configuration file (YAML or JSON).\n *\n * @param filePath - Path to the config file\n * @returns Raw configuration with template URI string\n */\nexport async function loadConfig(filePath: string): Promise<RawConfig> {\n const content = await readFile(filePath, 'utf-8');\n\n const isJson = filePath.endsWith('.json');\n\n let parsed: Record<string, unknown>;\n try {\n if (isJson) {\n parsed = JSON.parse(content) as Record<string, unknown>;\n } else {\n parsed = yaml.load(content) as Record<string, unknown>;\n }\n } catch (error) {\n const format = isJson ? 'JSON' : 'YAML';\n throw new Error(`Failed to parse ${format} config file: ${(error as Error).message}`, {\n cause: error,\n });\n }\n\n if (\n !parsed['intentId'] ||\n typeof parsed['intentId'] !== 'string' ||\n parsed['intentId'].trim() === ''\n ) {\n throw new Error(\n `Configuration file is missing required field 'intentId'. ` +\n `Add 'intentId: \"<your-cluster-identity>\"' to ${filePath}. ` +\n `The intentId uniquely identifies the cluster for integrity tracking.`\n );\n }\n\n return {\n template: parsed['template'] as string,\n inputs: (parsed['inputs'] as Record<string, unknown>) ?? {},\n intentId: parsed['intentId'],\n platformPrefix: parsed['platformPrefix'] as string | undefined,\n options: parsed['options'] as RawConfig['options'],\n };\n}\n\nexport { fileExists } from './fs-helpers.js';\n","/**\n * Shared filesystem helper utilities.\n */\n\nimport { access } from 'node:fs/promises';\nimport { join } from 'node:path';\n\n/**\n * Check if a file exists on disk.\n */\nexport async function fileExists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Find the default fixture path (tests/default/inputs.yaml) for a template version.\n * Returns null if the file does not exist.\n */\nexport async function findDefaultFixture(versionDir: string): Promise<string | null> {\n const defaultPath = join(versionDir, 'tests', 'default', 'inputs.yaml');\n try {\n await access(defaultPath);\n return defaultPath;\n } catch {\n return null;\n }\n}\n","/**\n * Manifest loading and raw config reconstruction utility.\n *\n * CHANGE (006): envelopeFromManifest now returns RawConfig (with template as\n * URI string) instead of Envelope. The CLI command resolves the URI.\n *\n * Reference: data-model.md §11, research.md R-008\n */\n\nimport { readFile } from 'node:fs/promises';\nimport type { Manifest } from '@truefoundry/tfy-infra-engine';\nimport type { RawConfig } from './config-loader.js';\n\n/**\n * Load and validate a manifest from disk.\n */\nexport async function loadManifest(manifestPath: string): Promise<Manifest> {\n let content: string;\n try {\n content = await readFile(manifestPath, 'utf-8');\n } catch (error) {\n const nodeError = error as NodeJS.ErrnoException;\n if (nodeError.code === 'ENOENT') {\n throw new Error(`Manifest not found: ${manifestPath}`, { cause: error });\n }\n throw new Error(`Failed to read manifest: ${(error as Error).message}`, { cause: error });\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(content) as Record<string, unknown>;\n } catch {\n throw new Error(`Invalid JSON in manifest: ${manifestPath}`);\n }\n\n const requiredFields = [\n 'manifestVersion',\n 'files',\n 'aggregateHash',\n 'inputs',\n 'platformPrefix',\n 'intentId',\n 'templateSource',\n 'templateVersion',\n ] as const;\n\n for (const field of requiredFields) {\n if (parsed[field] === undefined) {\n throw new Error(`Manifest is missing required field: ${field}`);\n }\n }\n\n return parsed as unknown as Manifest;\n}\n\n/**\n * Reconstruct a RawConfig from a manifest for re-render.\n *\n * CHANGE (006): Returns RawConfig with template URI string.\n */\nexport function envelopeFromManifest(manifest: Manifest): RawConfig {\n if (!manifest.inputs) {\n throw new Error('Cannot reconstruct envelope: manifest is missing inputs field');\n }\n if (!manifest.platformPrefix) {\n throw new Error('Cannot reconstruct envelope: manifest is missing platformPrefix field');\n }\n\n return {\n template: manifest.templateSource,\n inputs: manifest.inputs,\n intentId: manifest.intentId,\n platformPrefix: manifest.platformPrefix,\n };\n}\n","/**\n * Disk-to-FileMap reader utility.\n *\n * Reads a cluster directory into an in-memory FileMap for verify and\n * upgrade operations. Classifies files by zone and parses @tfy-status\n * headers for platform files.\n *\n * Reference: data-model.md §9, research.md R-012\n */\n\nimport { readFile, readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport {\n classifyFile,\n parseHeader,\n DEFAULT_PREFIX,\n type FileMap,\n type FileEntry,\n} from '@truefoundry/tfy-infra-engine';\n\n/**\n * Read all files from a cluster directory into a FileMap.\n *\n * Scans the directory, classifies each file by zone, parses @tfy-status\n * headers for platform files, and returns a typed FileMap.\n *\n * Does NOT include manifest.json in the output (callers load it separately).\n *\n * @param dir - Path to cluster directory\n * @param prefix - Platform Zone filename prefix (default: \"tfy_\")\n * @returns FileMap with zone-tagged entries and parsed headers\n */\nexport async function readDirectoryToFileMap(\n dir: string,\n prefix: string = DEFAULT_PREFIX\n): Promise<FileMap> {\n const fileMap: FileMap = new Map();\n\n let entries: string[];\n try {\n const dirEntries = await readdir(dir, { withFileTypes: true });\n entries = dirEntries.filter((e) => e.isFile()).map((e) => e.name);\n } catch (error) {\n const nodeError = error as NodeJS.ErrnoException;\n if (nodeError.code === 'ENOENT') {\n throw new Error(`Directory not found: ${dir}`, { cause: error });\n }\n throw new Error(`Failed to read directory: ${(error as Error).message}`, { cause: error });\n }\n\n for (const filename of entries) {\n // Skip manifest.json — callers load it separately\n if (filename === 'manifest.json') continue;\n\n const content = await readFile(join(dir, filename), 'utf-8');\n const zone = classifyFile(filename, prefix);\n\n const entry: FileEntry = { content, zone };\n\n if (zone === 'platform') {\n const header = parseHeader(content);\n if (header) {\n entry.header = header;\n }\n }\n\n fileMap.set(filename, entry);\n }\n\n return fileMap;\n}\n","/**\n * Engine output-to-disk writer utility.\n *\n * Writes engine results to disk with zone-aware policies.\n *\n * CHANGE (008): Removed sideOutputs handling — no more expose_variable pipeline.\n *\n * Reference: data-model.md §10, spec.md FR-012a\n */\n\nimport { writeFile, mkdir } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport type { InstallResult, UpgradeResult } from '@truefoundry/tfy-infra-engine';\nimport { fileExists } from './fs-helpers.js';\n\n/**\n * Write engine output to disk with zone-aware policies.\n *\n * Modes:\n * - 'install': Overwrite all files\n * - 'upgrade': Overwrite platform zone files, skip existing user zone files,\n * create new user zone files\n *\n * @param dir - Target directory\n * @param result - InstallResult or UpgradeResult from engine\n * @param mode - 'install' (overwrite all) or 'upgrade' (zone-aware)\n */\nexport async function writeEngineOutput(\n dir: string,\n result: InstallResult | UpgradeResult,\n mode: 'install' | 'upgrade'\n): Promise<void> {\n await mkdir(dir, { recursive: true });\n\n for (const [filePath, entry] of result.files) {\n const fullPath = join(dir, filePath);\n\n if (mode === 'upgrade' && entry.zone === 'user') {\n if (await fileExists(fullPath)) {\n continue;\n }\n }\n\n await mkdir(dirname(fullPath), { recursive: true });\n await writeFile(fullPath, entry.content, 'utf-8');\n }\n}\n","/**\n * Unified diff generation utility\n * Generates unified diff output for test failures\n * @file packages/infra-cli/src/utils/diff.ts\n */\n\nimport { createTwoFilesPatch } from 'diff';\n\n/**\n * Generate a unified diff between expected and actual content.\n *\n * @param expected - Expected file content\n * @param actual - Actual file content\n * @param filename - Name of the file being compared\n * @returns Unified diff string, or empty string if contents match\n */\nexport function generateUnifiedDiff(expected: string, actual: string, filename: string): string {\n const normalizedExpected = expected.replace(/\\r\\n/g, '\\n');\n const normalizedActual = actual.replace(/\\r\\n/g, '\\n');\n\n const patch = createTwoFilesPatch(\n `expected/${filename}`,\n `actual/${filename}`,\n normalizedExpected,\n normalizedActual,\n '', // oldHeader\n '', // newHeader\n { context: 3 }\n );\n\n return patch;\n}\n\n/**\n * Diff entry for a single file\n */\nexport interface DiffEntry {\n filename: string;\n diff: string;\n}\n\n/**\n * Format multiple diffs for display output.\n *\n * @param diffs - Array of diff entries with filenames\n * @returns Formatted string with all diffs\n */\nexport function formatDiffOutput(diffs: DiffEntry[]): string {\n if (diffs.length === 0) {\n return '';\n }\n\n return diffs\n .map((entry) => {\n return ` ${entry.filename}: content mismatch\\n${entry.diff}`;\n })\n .join('\\n');\n}\n","/**\n * tfy-init tpl validate command\n *\n * Validates a bundle.json template package.\n * Detects bundle.json in the specified directory.\n */\n\nimport { Command } from 'commander';\nimport { readFile, stat } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport Handlebars from 'handlebars';\nimport { validateJsonSchemaStructure, isTofuAvailable } from '@truefoundry/tfy-infra-engine';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\n\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\nexport function createValidateCommand(): Command {\n const cmd = new Command('validate')\n .description('Validate a template (for template authors)')\n .option('-d, --directory <dir>', 'Path to local template directory', '.')\n .option('--strict', 'Fail on warnings')\n .option('--skip-format', 'Skip tofu fmt validation')\n .action(async (options: ValidateOptions) => {\n try {\n const exitCode = await runValidate(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\ninterface ValidateOptions {\n directory: string;\n strict?: boolean;\n skipFormat?: boolean;\n}\n\ninterface ValidationIssue {\n type: 'error' | 'warning';\n message: string;\n file?: string;\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function runValidate(options: ValidateOptions): Promise<number> {\n const templateDir = resolve(options.directory);\n const issues: ValidationIssue[] = [];\n let hasErrors = false;\n\n output.info(`Validating template at: ${templateDir}`);\n output.info('');\n\n const bundlePath = join(templateDir, 'bundle.json');\n const usesBundle = await fileExists(bundlePath);\n\n if (!usesBundle) {\n output.error('bundle.json not found in template directory');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n output.verbose('Checking bundle.json...');\n try {\n const content = await readFile(bundlePath, 'utf-8');\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(content) as Record<string, unknown>;\n } catch (error) {\n throw new Error(`Invalid JSON: ${(error as Error).message}`, { cause: error });\n }\n\n const rawMeta = parsed['metadata'] as Record<string, unknown> | undefined;\n if (!rawMeta || typeof rawMeta !== 'object') {\n throw new Error('bundle.json must contain a \"metadata\" object');\n }\n if (!rawMeta['name'] || !rawMeta['version']) {\n throw new Error('bundle.json metadata must contain \"name\" and \"version\"');\n }\n\n const jsonSchema = parsed['jsonSchema'] as Record<string, unknown> | undefined;\n if (!jsonSchema || typeof jsonSchema !== 'object') {\n throw new Error('bundle.json must contain a \"jsonSchema\" object');\n }\n\n validateJsonSchemaStructure(jsonSchema);\n output.success('bundle.json schema is valid');\n\n const files = parsed['files'] as Record<string, string> | undefined;\n const staticFiles = parsed['staticFiles'] as Record<string, string> | undefined;\n const fileCount = Object.keys(files ?? {}).length;\n const staticCount = Object.keys(staticFiles ?? {}).length;\n\n if (fileCount === 0 && staticCount === 0) {\n issues.push({ type: 'error', message: 'Bundle contains no template files' });\n hasErrors = true;\n } else {\n for (const [relPath, content] of Object.entries(files ?? {})) {\n try {\n Handlebars.precompile(content);\n output.verbose(` ✓ ${relPath}`);\n } catch (error) {\n issues.push({\n type: 'error',\n message: `Invalid Handlebars syntax: ${(error as Error).message}`,\n file: relPath,\n });\n hasErrors = true;\n }\n }\n output.success(\n `Found ${fileCount} HBS template(s)` +\n (staticCount > 0 ? ` and ${staticCount} static file(s)` : '') +\n ' with valid syntax'\n );\n }\n } catch (error) {\n issues.push({\n type: 'error',\n message: (error as Error).message,\n file: 'bundle.json',\n });\n hasErrors = true;\n }\n\n if (!options.skipFormat) {\n output.verbose('Checking tofu fmt availability...');\n const tofuAvailable = await isTofuAvailable();\n if (tofuAvailable) {\n output.success('tofu fmt available');\n } else {\n issues.push({ type: 'warning', message: 'tofu not found - format validation skipped' });\n }\n }\n\n output.info('');\n\n const warnings = issues.filter((i) => i.type === 'warning');\n const errors = issues.filter((i) => i.type === 'error');\n\n if (warnings.length > 0) {\n output.warn(`${warnings.length} warning(s):`);\n for (const issue of warnings) {\n const prefix = issue.file ? `${issue.file}: ` : '';\n output.info(` • ${prefix}${issue.message}`);\n }\n output.info('');\n }\n\n if (errors.length > 0) {\n output.error(`${errors.length} error(s):`);\n for (const issue of errors) {\n const prefix = issue.file ? `${issue.file}: ` : '';\n output.info(` • ${prefix}${issue.message}`);\n }\n output.info('');\n }\n\n if (hasErrors) {\n output.error('Template validation failed');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n if (options.strict && warnings.length > 0) {\n output.error('Template validation failed (strict mode)');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n output.success('Template is valid');\n return EXIT_CODES.SUCCESS;\n}\n","/**\n * tfy-init tpl test command\n *\n * CHANGE (006): Fetch-first — resolves template via ResolverRegistry\n * before calling engine.install(). Envelope uses Template object.\n */\n\nimport { Command } from 'commander';\nimport { readFile, readdir, writeFile, mkdir } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport * as yaml from 'js-yaml';\nimport { createEngine, type Envelope, type InstallResult } from '@truefoundry/tfy-infra-engine';\nimport { createResolverRegistry } from '../../resolvers/index.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { generateUnifiedDiff } from '../../utils/diff.js';\n\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\nexport function createTestCommand(): Command {\n const cmd = new Command('test')\n .description('Run test fixtures against a template')\n .option('-d, --directory <dir>', 'Path to local template directory', '.')\n .option('--fixture <name>', 'Run specific fixture only')\n .option('--update', 'Update expected outputs with actual results')\n .option('--diff', 'Show unified diff on failures')\n .option('--skip-format', 'Skip tofu fmt in tests')\n .action(async (options: TestOptions) => {\n try {\n const exitCode = await runTest(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\ninterface TestOptions {\n directory: string;\n fixture?: string;\n update?: boolean;\n diff?: boolean;\n skipFormat?: boolean;\n}\n\ninterface TestResult {\n name: string;\n passed: boolean;\n errors?: string[];\n diffs?: Array<{ filename: string; diff: string }>;\n}\n\nasync function runTest(options: TestOptions): Promise<number> {\n const templateDir = resolve(options.directory);\n const testsDir = join(templateDir, 'tests');\n const templateUri = `file://${templateDir}`;\n\n output.info(`Testing template at: ${templateDir}`);\n output.info('');\n\n let fixtures: string[];\n try {\n const entries = await readdir(testsDir);\n fixtures = [];\n for (const entry of entries) {\n try {\n const inputsPath = join(testsDir, entry, 'inputs.yaml');\n await readFile(inputsPath);\n fixtures.push(entry);\n } catch {\n // Not a valid fixture directory\n }\n }\n } catch {\n output.error(`No tests directory found at ${testsDir}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n if (fixtures.length === 0) {\n output.error('No test fixtures found');\n output.info('');\n output.info('Expected structure:');\n output.info(' tests/');\n output.info(' <fixture-name>/');\n output.info(' inputs.yaml');\n output.info(' expected/');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n if (options.fixture) {\n if (!fixtures.includes(options.fixture)) {\n output.error(`Fixture '${options.fixture}' not found`);\n output.info(`Available fixtures: ${fixtures.join(', ')}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n fixtures = [options.fixture];\n }\n\n output.info(`Running ${fixtures.length} fixture(s)...`);\n output.info('');\n\n // Fetch-first: resolve template once for all fixtures\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(templateUri, { noCache: true });\n\n const engine = createEngine();\n const results: TestResult[] = [];\n\n for (const fixture of fixtures) {\n const fixtureDir = join(testsDir, fixture);\n const inputsPath = join(fixtureDir, 'inputs.yaml');\n const expectedDir = join(fixtureDir, 'expected');\n\n output.verbose(`Running fixture: ${fixture}`);\n\n try {\n const inputsContent = await readFile(inputsPath, 'utf-8');\n const inputs = yaml.load(inputsContent) as Record<string, unknown>;\n\n const envelope: Envelope = {\n template,\n inputs,\n intentId: `test-${fixture}`,\n options: {\n skipFormat: options.skipFormat,\n },\n };\n\n const result = await engine.install(envelope);\n\n if (options.update) {\n await updateExpected(expectedDir, result);\n output.success(`${fixture}: updated`);\n results.push({ name: fixture, passed: true });\n } else {\n const { errors, diffs } = await compareOutputs(expectedDir, result, options.diff);\n if (errors.length === 0) {\n output.success(`${fixture}: passed`);\n results.push({ name: fixture, passed: true });\n } else {\n output.error(`${fixture}: failed`);\n for (const err of errors) {\n output.info(` ${err}`);\n }\n if (options.diff && diffs && diffs.length > 0) {\n for (const d of diffs) {\n output.info(` --- expected/${d.filename}`);\n output.info(` +++ actual/${d.filename}`);\n const diffLines = d.diff.split('\\n').slice(4);\n for (const line of diffLines) {\n if (line) {\n output.info(` ${line}`);\n }\n }\n }\n }\n results.push({ name: fixture, passed: false, errors, diffs });\n }\n }\n } catch (error) {\n output.error(`${fixture}: error`);\n output.info(` ${(error as Error).message}`);\n results.push({ name: fixture, passed: false, errors: [(error as Error).message] });\n }\n }\n\n output.info('');\n const passed = results.filter((r) => r.passed).length;\n const failed = results.filter((r) => !r.passed).length;\n\n if (failed === 0) {\n output.success(`All ${passed} fixture(s) passed`);\n return EXIT_CODES.SUCCESS;\n } else {\n output.error(`${failed} fixture(s) failed, ${passed} passed`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n}\n\n/**\n * Replaces machine-specific `file://` paths in @tfy-status source fields\n * with a stable placeholder so snapshots are portable across environments.\n */\nfunction normalizeStatusSource(content: string): string {\n return content.replace(/(\"source\"\\s*:\\s*)\"file:\\/\\/[^\"]*\"/g, '$1\"<local>\"');\n}\n\nasync function updateExpected(expectedDir: string, result: InstallResult): Promise<void> {\n await mkdir(expectedDir, { recursive: true });\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const outputPath = join(expectedDir, filePath);\n await writeFile(outputPath, normalizeStatusSource(entry.content), 'utf-8');\n }\n}\n\ninterface CompareResult {\n errors: string[];\n diffs?: Array<{ filename: string; diff: string }>;\n}\n\nasync function compareOutputs(\n expectedDir: string,\n result: InstallResult,\n includeDiffs?: boolean\n): Promise<CompareResult> {\n const errors: string[] = [];\n const diffs: Array<{ filename: string; diff: string }> = [];\n\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const actual = entry.content;\n\n const expectedPath = join(expectedDir, filePath);\n try {\n const expected = await readFile(expectedPath, 'utf-8');\n const normalizedActual = normalizeStatusSource(actual.trim().replace(/\\r\\n/g, '\\n'));\n const normalizedExpected = normalizeStatusSource(expected.trim().replace(/\\r\\n/g, '\\n'));\n\n if (normalizedActual !== normalizedExpected) {\n errors.push(`${filePath}: content mismatch`);\n if (includeDiffs) {\n const diff = generateUnifiedDiff(normalizedExpected, normalizedActual, filePath);\n diffs.push({ filename: filePath, diff });\n }\n }\n } catch {\n errors.push(`${filePath}: expected file not found`);\n }\n }\n\n try {\n const expectedFiles = await readdir(expectedDir);\n for (const file of expectedFiles) {\n if (!result.files.has(file) && file !== 'manifest.json') {\n errors.push(`${file}: unexpected file in expected/`);\n }\n }\n } catch {\n // Expected directory might not exist\n }\n\n return { errors, diffs: includeDiffs ? diffs : undefined };\n}\n","/**\n * tfy-init tpl dev command\n * Watch mode for rapid template iteration.\n *\n * Supports two modes:\n * 1. Schema mode (--schema + --src): reads a pre-computed schema package\n * and a src/ directory. Watches both for changes.\n * 2. Directory mode (--directory): reads bundle.json from a template directory.\n */\n\nimport { Command } from 'commander';\nimport { readFile, mkdir, rm, writeFile, access } from 'node:fs/promises';\nimport { dirname, join, relative, resolve } from 'node:path';\nimport * as yaml from 'js-yaml';\nimport {\n createEngine,\n EngineError,\n EngineErrorCode,\n type Envelope,\n type InstallResult,\n type JSONSchema7,\n type Template,\n type TemplateMetadata,\n} from '@truefoundry/tfy-infra-engine';\nimport { createResolverRegistry } from '../../resolvers/index.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { findDefaultFixture } from '../../utils/fs-helpers.js';\nimport { walkSrcDir } from '../../utils/walk-src.js';\nimport { createWatcher, type WatchEvent } from '../../utils/file-watcher.js';\nimport { generateUnifiedDiff } from '../../utils/diff.js';\n\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\nexport interface DevOptions {\n directory: string;\n schema?: string;\n src?: string;\n name?: string;\n version?: string;\n fixture?: string;\n output: string;\n test?: boolean;\n}\n\nexport function createDevCommand(): Command {\n const cmd = new Command('dev')\n .description('Watch mode for rapid template iteration')\n .option('-d, --directory <dir>', 'Path to template version directory')\n .option(\n '--schema <path>',\n 'Path to schema package JSON (from tfy-schema-tool --schema-package)'\n )\n .option('--src <dir>', 'Path to template src/ directory')\n .option('--name <string>', 'Template name (used with --schema mode)')\n .option('--version <semver>', 'Template version (used with --schema mode)')\n .option('-f, --fixture <path>', 'Input file or fixture to use for testing')\n .requiredOption('-o, --output <dir>', 'Output directory for rendered files')\n .option('--no-test', 'Only validate, do not run tests')\n .action(async (options: DevOptions) => {\n try {\n const exitCode = await runDev(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\nexport { findDefaultFixture } from '../../utils/fs-helpers.js';\n\nexport async function validateTemplateDir(\n versionDir: string\n): Promise<{ valid: boolean; error?: string }> {\n try {\n await access(versionDir);\n } catch {\n return { valid: false, error: `Directory not found: ${versionDir}` };\n }\n\n const bundleJsonPath = join(versionDir, 'bundle.json');\n try {\n await access(bundleJsonPath);\n return { valid: true };\n } catch {\n return { valid: false, error: `bundle.json not found in ${versionDir}` };\n }\n}\n\nexport async function ensureOutputDir(_versionDir: string, customOutput: string): Promise<string> {\n const outputDir = resolve(customOutput);\n\n try {\n await rm(outputDir, { recursive: true, force: true });\n } catch {\n // Directory might not exist\n }\n\n await mkdir(outputDir, { recursive: true });\n return outputDir;\n}\n\nfunction isSchemaMode(options: DevOptions): boolean {\n return !!(options.schema && options.src);\n}\n\nasync function runDev(options: DevOptions): Promise<number> {\n if (isSchemaMode(options)) {\n return runSchemaModeDev(options);\n }\n return runDirectoryModeDev(options);\n}\n\n/**\n * Schema mode: --schema + --src.\n * Reads schema package from file, walks src/, constructs Template in-memory.\n */\nasync function runSchemaModeDev(options: DevOptions): Promise<number> {\n const schemaPath = resolve(options.schema!);\n const srcDir = resolve(options.src!);\n const templateName = options.name ?? 'dev-template';\n const templateVersion = options.version;\n\n let fixturePath: string;\n if (options.fixture) {\n fixturePath = resolve(options.fixture);\n try {\n await access(fixturePath);\n } catch {\n output.error(`Fixture not found: ${options.fixture}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n } else {\n output.error('--fixture is required in schema mode');\n output.info('Use -f to specify an inputs.yaml file.');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n const outputDir = await ensureOutputDir('.', options.output);\n const relativeOutputDir = relative(process.cwd(), outputDir);\n\n output.info(`[schema] ${relative(process.cwd(), schemaPath)}`);\n output.info(`[src] ${relative(process.cwd(), srcDir)}`);\n output.info(`[output] ${relativeOutputDir}/`);\n output.info(`[fixture] ${relative(process.cwd(), fixturePath)}`);\n output.info('');\n\n const engine = createEngine();\n\n const renderFromSchema = async () => {\n try {\n const template = await buildTemplateFromSchema(\n schemaPath,\n srcDir,\n templateName,\n templateVersion\n );\n await renderWithTemplate(engine, template, fixturePath, outputDir, options.test);\n } catch (error) {\n output.error(`[render] ✗ Render failed`);\n output.info(` ${(error as Error).message}`);\n }\n };\n\n await renderFromSchema();\n\n const watchPaths = [schemaPath, srcDir, fixturePath];\n\n const watcher = createWatcher(watchPaths, {\n schemaFile: schemaPath,\n onChange: (event: WatchEvent) => {\n void (async () => {\n output.info(`[changed] ${relative(process.cwd(), event.path)}`);\n await renderFromSchema();\n })();\n },\n onReady: () => {\n output.info('[ready] Watching for changes... (Ctrl+C to exit)');\n output.info('');\n },\n onError: (error) => {\n output.error(`Watch error: ${error.message}`);\n },\n });\n\n return new Promise<number>((resolvePromise) => {\n const cleanup = () => {\n output.info('');\n output.info('[stopped] Watch mode ended');\n void watcher.close();\n resolvePromise(EXIT_CODES.SUCCESS);\n };\n\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n });\n}\n\nfunction extractVersionFromSchema(jsonSchema: JSONSchema7): string | undefined {\n const props = jsonSchema.properties;\n if (!props) return undefined;\n const versionProp = props['version'];\n if (!versionProp || typeof versionProp === 'boolean') return undefined;\n const def = versionProp.default;\n return typeof def === 'string' ? def : undefined;\n}\n\nasync function buildTemplateFromSchema(\n schemaPath: string,\n srcDir: string,\n name: string,\n version?: string\n): Promise<Template> {\n const schemaContent = await readFile(schemaPath, 'utf-8');\n const schemaPkg = JSON.parse(schemaContent) as Record<string, unknown>;\n\n const jsonSchema = schemaPkg['jsonSchema'] as JSONSchema7;\n if (!jsonSchema || typeof jsonSchema !== 'object') {\n throw new Error('Schema package must contain a \"jsonSchema\" object');\n }\n\n const resolvedVersion = version ?? extractVersionFromSchema(jsonSchema) ?? '0.0.0-dev';\n\n const { files, staticFiles } = await walkSrcDir(srcDir);\n\n const metadata: TemplateMetadata = { name, version: resolvedVersion };\n\n return {\n metadata,\n jsonSchema,\n files,\n staticFiles,\n source: `file://${srcDir}`,\n version: { semver: resolvedVersion },\n };\n}\n\nasync function renderWithTemplate(\n engine: ReturnType<typeof createEngine>,\n template: Template,\n fixturePath: string,\n outputDir: string,\n runTests?: boolean\n): Promise<void> {\n const inputsContent = await readFile(fixturePath, 'utf-8');\n const inputs = yaml.load(inputsContent) as Record<string, unknown>;\n\n const envelope: Envelope = {\n template,\n inputs,\n intentId: `dev-${Date.now()}`,\n options: { skipFormat: false },\n };\n\n const result = await engine.install(envelope);\n\n const templateFileCount = Array.from(result.files.keys()).filter(\n (f) => f !== 'manifest.json'\n ).length;\n output.info(`[validate] ✓ ${templateFileCount} template files valid`);\n\n await rm(outputDir, { recursive: true, force: true });\n await mkdir(outputDir, { recursive: true });\n\n let filesWritten = 0;\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const outputPath = join(outputDir, filePath);\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, entry.content, 'utf-8');\n filesWritten++;\n }\n output.info(\n `[render] ✓ ${filesWritten} files written to ${relative(process.cwd(), outputDir)}/`\n );\n\n if (runTests !== false) {\n await runTestComparisonFromResult(result, outputDir);\n }\n}\n\n/**\n * Directory mode: --directory (legacy, uses FileResolver).\n */\nasync function runDirectoryModeDev(options: DevOptions): Promise<number> {\n const versionDir = resolve(options.directory || '.');\n\n const validation = await validateTemplateDir(versionDir);\n if (!validation.valid) {\n output.error(validation.error!);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n let fixturePath: string;\n if (options.fixture) {\n fixturePath = resolve(versionDir, options.fixture);\n try {\n await access(fixturePath);\n } catch {\n output.error(`Fixture not found: ${options.fixture}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n } else {\n const defaultFixture = await findDefaultFixture(versionDir);\n if (!defaultFixture) {\n output.error('No default fixture found at tests/default/inputs.yaml');\n output.info('Either create a default fixture or use --fixture to specify one.');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n fixturePath = defaultFixture;\n }\n\n const outputDir = await ensureOutputDir(versionDir, options.output);\n const relativeOutputDir = relative(process.cwd(), outputDir);\n\n output.info(`[watching] ${relative(process.cwd(), versionDir)}`);\n output.info(`[output] ${relativeOutputDir}/`);\n output.info(`[fixture] ${relative(process.cwd(), fixturePath)}`);\n output.info('');\n\n const engine = createEngine();\n const templateUri = `file://${versionDir}`;\n\n await runRenderCycle(engine, templateUri, versionDir, fixturePath, outputDir, options.test);\n\n const watcher = createWatcher(versionDir, {\n onChange: (event: WatchEvent) => {\n void (async () => {\n const relPath = relative(versionDir, event.path);\n output.info(`[changed] ${relPath}`);\n\n if (event.fileType === 'schema') {\n await runValidation(templateUri);\n } else if (event.fileType === 'template' || event.fileType === 'inputs') {\n await runRenderCycle(\n engine,\n templateUri,\n versionDir,\n fixturePath,\n outputDir,\n options.test\n );\n }\n })();\n },\n onReady: () => {\n output.info('[ready] Watching for changes... (Ctrl+C to exit)');\n output.info('');\n },\n onError: (error) => {\n output.error(`Watch error: ${error.message}`);\n },\n });\n\n return new Promise<number>((resolvePromise) => {\n const cleanup = () => {\n output.info('');\n output.info('[stopped] Watch mode ended');\n void watcher.close();\n resolvePromise(EXIT_CODES.SUCCESS);\n };\n\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n });\n}\n\nasync function runValidation(templateUri: string): Promise<boolean> {\n try {\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(templateUri, { noCache: true });\n if (template.jsonSchema) {\n output.info('[validate] ✓ schema valid');\n return true;\n }\n output.error('[validate] ✗ schema invalid');\n return false;\n } catch (error) {\n output.error(`[validate] ✗ schema invalid`);\n output.info(` ${(error as Error).message}`);\n return false;\n }\n}\n\nasync function runRenderCycle(\n engine: ReturnType<typeof createEngine>,\n templateUri: string,\n versionDir: string,\n fixturePath: string,\n outputDir: string,\n runTests?: boolean\n): Promise<void> {\n const valid = await runValidation(templateUri);\n if (!valid) return;\n\n try {\n const inputsContent = await readFile(fixturePath, 'utf-8');\n const inputs = yaml.load(inputsContent) as Record<string, unknown>;\n\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(templateUri, { noCache: true });\n\n const envelope: Envelope = {\n template,\n inputs,\n intentId: `dev-${Date.now()}`,\n options: { skipFormat: false },\n };\n\n const result = await engine.install(envelope);\n\n const templateFileCount = Array.from(result.files.keys()).filter(\n (f) => f !== 'manifest.json'\n ).length;\n output.info(`[validate] ✓ ${templateFileCount} template files valid`);\n\n await rm(outputDir, { recursive: true, force: true });\n await mkdir(outputDir, { recursive: true });\n\n let filesWritten = 0;\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const outputPath = join(outputDir, filePath);\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, entry.content, 'utf-8');\n filesWritten++;\n }\n output.info(\n `[render] ✓ ${filesWritten} files written to ${relative(process.cwd(), outputDir)}/`\n );\n\n if (runTests !== false) {\n await runTestComparison(versionDir, result, outputDir);\n }\n } catch (error) {\n output.error(`[render] ✗ Render failed`);\n output.info(` ${(error as Error).message}`);\n if (\n error instanceof EngineError &&\n error.code === EngineErrorCode.INPUT_VALIDATION_FAILED &&\n error.details?.['errors']\n ) {\n output.info(\n output.formatValidationErrors(\n error.details['errors'] as Array<{ path: string; message: string }>\n )\n );\n }\n }\n}\n\nasync function runTestComparison(\n versionDir: string,\n result: InstallResult,\n _outputDir: string\n): Promise<void> {\n const expectedDir = join(versionDir, 'tests', 'default', 'expected');\n await runTestComparisonFromDir(result, expectedDir);\n}\n\nasync function runTestComparisonFromResult(\n _result: InstallResult,\n _outputDir: string\n): Promise<void> {\n // In schema mode, we don't have a versionDir to find expected/ in.\n // Test comparison is skipped unless running from directory mode.\n}\n\nasync function runTestComparisonFromDir(result: InstallResult, expectedDir: string): Promise<void> {\n const startTime = Date.now();\n\n try {\n await access(expectedDir);\n } catch {\n output.info('[test] ⚠ No expected/ directory, skipping comparison');\n return;\n }\n\n const errors: Array<{ file: string; diff?: string }> = [];\n\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const actual = entry.content;\n\n const expectedPath = join(expectedDir, filePath);\n try {\n const expected = await readFile(expectedPath, 'utf-8');\n const normalizedActual = actual.trim().replace(/\\r\\n/g, '\\n');\n const normalizedExpected = expected.trim().replace(/\\r\\n/g, '\\n');\n\n if (normalizedActual !== normalizedExpected) {\n const diff = generateUnifiedDiff(normalizedExpected, normalizedActual, filePath);\n errors.push({ file: filePath, diff });\n }\n } catch {\n errors.push({ file: filePath });\n }\n }\n\n const duration = Date.now() - startTime;\n\n if (errors.length === 0) {\n output.info(`[test] ✓ default: passed (${duration}ms)`);\n } else {\n output.error(`[test] ✗ default: failed`);\n for (const err of errors) {\n output.info(` ${err.file}: content mismatch`);\n if (err.diff) {\n const diffLines = err.diff.split('\\n').slice(4, 14);\n for (const line of diffLines) {\n if (line) {\n output.info(` ${line}`);\n }\n }\n if (err.diff.split('\\n').length > 14) {\n output.info(` ... (diff truncated)`);\n }\n }\n }\n }\n}\n","/**\n * File watcher utility\n * Watches template directories for changes and emits events\n * @file packages/infra-cli/src/utils/file-watcher.ts\n */\n\nimport chokidar, { type FSWatcher } from 'chokidar';\nimport { basename } from 'node:path';\n\n/**\n * Type of file change that occurred\n */\nexport type FileType = 'schema' | 'template' | 'inputs' | 'other';\n\n/**\n * Event emitted when a file changes\n */\nexport interface WatchEvent {\n type: 'change' | 'add' | 'unlink';\n path: string;\n fileType: FileType;\n}\n\n/**\n * Options for creating a file watcher\n */\nexport interface WatchOptions {\n /**\n * Callback when a file changes\n */\n onChange?: (event: WatchEvent) => void;\n\n /**\n * Callback when watcher is ready\n */\n onReady?: () => void;\n\n /**\n * Callback when an error occurs\n */\n onError?: (error: Error) => void;\n\n /**\n * Patterns to ignore\n */\n ignored?: string | RegExp | ((path: string) => boolean);\n\n /**\n * Debounce delay in milliseconds\n */\n debounceMs?: number;\n\n /**\n * Path to the schema file for classifying changes.\n */\n schemaFile?: string;\n}\n\n/**\n * Classify a file path by its type.\n *\n * @param path - File path to classify\n * @param schemaFile - Optional path to the schema file to match against\n * @returns File type classification\n */\nexport function classifyFileChange(path: string, schemaFile?: string): FileType {\n const normalizedPath = path.replace(/\\\\/g, '/');\n const filename = basename(normalizedPath);\n\n if (schemaFile && normalizedPath.endsWith(schemaFile.replace(/\\\\/g, '/'))) {\n return 'schema';\n }\n\n if (filename === 'bundle.json' || filename === 'schema-pkg.json') {\n return 'schema';\n }\n\n const inSrcDir = /\\/src\\//.test(normalizedPath);\n if (inSrcDir && !filename.startsWith('_')) {\n return 'template';\n }\n\n if (filename === 'inputs.yaml') {\n return 'inputs';\n }\n\n return 'other';\n}\n\n/**\n * Create a file watcher for one or more paths.\n *\n * @param paths - Path(s) to watch (directory or file)\n * @param options - Watcher options\n * @returns FSWatcher instance\n */\nexport function createWatcher(paths: string | string[], options: WatchOptions = {}): FSWatcher {\n const {\n onChange,\n onReady,\n onError,\n ignored = /(^|[/\\\\])\\..|(^|[/\\\\])node_modules($|[/\\\\])|(^|[/\\\\])dev($|[/\\\\])/,\n debounceMs = 100,\n schemaFile,\n } = options;\n\n const watcher = chokidar.watch(paths, {\n ignored,\n persistent: true,\n ignoreInitial: true,\n awaitWriteFinish: {\n stabilityThreshold: debounceMs,\n pollInterval: 50,\n },\n });\n\n const handleEvent = (type: WatchEvent['type']) => (path: string) => {\n const fileType = classifyFileChange(path, schemaFile);\n const event: WatchEvent = { type, path, fileType };\n\n if (onChange) {\n onChange(event);\n }\n };\n\n watcher.on('change', handleEvent('change'));\n watcher.on('add', handleEvent('add'));\n watcher.on('unlink', handleEvent('unlink'));\n\n if (onReady) {\n watcher.on('ready', onReady);\n }\n\n if (onError) {\n watcher.on('error', (err: unknown) =>\n onError(err instanceof Error ? err : new Error(String(err)))\n );\n }\n\n return watcher;\n}\n","/**\n * tfy-init tpl verify command\n *\n * Checks integrity of files on disk against a manifest.json.\n * Wraps engine.verify() to produce human-readable or JSON drift reports.\n *\n */\n\nimport { Command } from 'commander';\nimport { join, resolve } from 'node:path';\nimport { createEngine } from '@truefoundry/tfy-infra-engine';\nimport type { DriftReport } from '@truefoundry/tfy-infra-engine';\nimport { loadManifest } from '../../utils/manifest-loader.js';\nimport { readDirectoryToFileMap } from '../../utils/file-reader.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { driftTypeTag } from '../../utils/output.js';\n\n// Re-export for backward compatibility\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\n/** Verify-specific exit code for drift detected (distinct from error). */\nconst DRIFT_DETECTED = 1;\n\ninterface VerifyOptions {\n directory: string;\n json?: boolean;\n}\n\n/**\n * Create the verify command.\n */\nexport function createVerifyCommand(): Command {\n const cmd = new Command('verify')\n .description('Check integrity of files on disk against a manifest')\n .option('-d, --directory <dir>', 'Path to cluster directory containing manifest.json', '.')\n .option('--json', 'Output drift report as structured JSON')\n .action(async (options: VerifyOptions) => {\n try {\n const exitCode = await runVerify(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\n/**\n * Execute the verify command.\n * Exported for testing.\n */\nexport async function runVerify(options: VerifyOptions): Promise<number> {\n const dir = resolve(options.directory);\n const manifestPath = join(dir, 'manifest.json');\n\n // Load manifest\n output.verbose(`Loading manifest from ${manifestPath}`);\n const manifest = await loadManifest(manifestPath);\n\n // Read current files on disk\n output.verbose(`Reading files from ${dir}`);\n const currentFiles = await readDirectoryToFileMap(dir, manifest.platformPrefix);\n\n // Create engine and verify\n const engine = createEngine();\n const result = await engine.verify(currentFiles, manifest);\n\n // Output the report\n if (options.json) {\n output.info(formatDriftReportJson(result.driftReport));\n } else {\n output.info(formatDriftReport(result.driftReport));\n }\n\n return result.driftReport.valid ? EXIT_CODES.SUCCESS : DRIFT_DETECTED;\n}\n\n/**\n * Format a drift report as human-readable text.\n * Exported for testing.\n */\nexport function formatDriftReport(report: DriftReport): string {\n const lines: string[] = [];\n const issueCount =\n report.summary.driftedFiles + report.summary.missingFiles + report.summary.unexpectedFiles;\n\n if (report.valid) {\n lines.push(\n `✓ Verification passed: ${report.summary.totalFiles} files checked, ${issueCount} issues`\n );\n lines.push('');\n lines.push(`Aggregate Hash: ${report.aggregateHash.expected}`);\n } else {\n lines.push(`✗ Drift detected: ${issueCount} issues found`);\n lines.push('');\n\n for (const entry of report.entries) {\n const tag = driftTypeTag(entry.type);\n lines.push(` ${tag} ${entry.path}`);\n lines.push(` ${entry.details}`);\n lines.push('');\n }\n\n lines.push(\n `Summary: ${report.summary.totalFiles} files checked, ` +\n `${report.summary.driftedFiles} drifted, ` +\n `${report.summary.missingFiles} missing` +\n (report.summary.unexpectedFiles > 0 ? `, ${report.summary.unexpectedFiles} unexpected` : '')\n );\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format a drift report as structured JSON.\n * Exported for testing.\n */\nexport function formatDriftReportJson(report: DriftReport): string {\n return JSON.stringify(report, null, 2);\n}\n"],"mappings":"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","names":["import_commander","import_commander","import_commander","import_promises","import_node_path","import_promises","import_node_path","walkSrcDir","rootDir","files","staticFiles","walk","relativePath","currentDir","entries","entry","entryRelative","content","outputPath","import_tfy_infra_engine","currentVerbosity","jsonMode","setVerbosity","level","getVerbosity","setJsonMode","enabled","data","message","success","currentVerbosity","jsonMode","error","info","verbose","warn","formatSize","bytes","formatValidationErrors","errors","driftTypeTag","type","printFileList","files","write","file","EXIT_CODES","handleEngineError","error","fallbackExitCode","getVerbosity","info","key","value","formatValidationErrors","createBundleCommand","options","runBundle","error","handleEngineError","readSchemaPackage","schemaPath","raw","readStdin","parsed","err","obj","resolve","reject","chunks","chunk","extractVersionFromSchema","jsonSchema","props","versionProp","def","schemaPkg","version","srcDir","files","staticFiles","walkSrcDir","metadata","bundle","json","outPath","success","import_commander","import_node_readline","import_promises","import_node_path","yaml","import_tfy_infra_engine","import_tfy_infra_engine","import_promises","import_node_path","import_tfy_infra_engine","FileResolver","uri","_options","fsPath","info","bundlePath","error","nodeError","content","raw","rawMetadata","metadata","jsonSchema","files","staticFiles","version","path","import_tfy_infra_engine","DEFAULT_RESOLVER_OPTIONS","mergeResolverOptions","options","withRetry","fn","retries","baseDelay","lastError","attempt","error","delay","resolve","withTimeout","promise","ms","message","timer","timeout","_","reject","JFROG_TOKEN_ENV","isArtifactoryHost","uri","hostname","buildHeaders","headers","token","fetchBundle","response","hint","unpackBundle","raw","metadata","jsonSchema","files","staticFiles","version","HttpsBundleResolver","options","opts","mergeResolverOptions","withTimeout","withRetry","import_promises","import_node_path","import_node_crypto","import_tfy_infra_engine","getDefaultCacheDir","home","TemplateCache","cacheDir","source","version","key","safeVersion","cachePath","bundlePath","bundleContent","parsed","metadata","jsonSchema","record","files","staticFiles","versionInfo","template","bundle","error","keyPath","sources","templates","size","sourceKeys","versions","versionPath","dir","total","entries","entry","fullPath","fileStat","ResolverRegistry","resolvers","cacheDir","TemplateCache","FileResolver","HttpsBundleResolver","resolver","uri","options","scheme","template","createResolverRegistry","import_promises","yaml","import_promises","import_node_path","fileExists","filePath","findDefaultFixture","versionDir","defaultPath","loadConfig","filePath","content","isJson","parsed","error","format","import_promises","loadManifest","manifestPath","content","error","parsed","requiredFields","field","envelopeFromManifest","manifest","import_promises","import_node_path","import_tfy_infra_engine","readDirectoryToFileMap","dir","prefix","fileMap","entries","e","error","filename","content","zone","entry","header","import_promises","import_node_path","writeEngineOutput","dir","result","mode","filePath","entry","fullPath","fileExists","import_diff","generateUnifiedDiff","expected","actual","filename","normalizedExpected","normalizedActual","validateRenderArgs","args","createRenderCommand","collect","options","exitCode","runRender","error","handleEngineError","value","previous","parseInputOverrides","inputs","result","input","eqIndex","key","setJsonMode","outputDir","timeout","runRenderFromManifest","manifestPath","fileExists","runRenderFromConfigUpgrade","runRenderFromConfig","templateDir","runRenderFromDirectory","path","confirmPrompt","message","rl","resolve","answer","countExistingFiles","dir","e","guardOverwrite","count","warn","data","info","EXIT_CODES","verbose","manifest","loadManifest","rawConfig","envelopeFromManifest","success","envelope","createResolverRegistry","handleInstallDryRun","aborted","writeEngineOutput","printInstallResult","templateUri","fixturePath","findDefaultFixture","fixtureContent","overrides","intentId","engine","configFile","loadConfig","previousManifest","currentFiles","readDirectoryToFileMap","printDriftReport","handleUpgradeDryRun","formatUpgradeResultJson","formatUpgradeResult","computeInstallDiffs","newFiles","diffs","filename","entry","existing","generateUnifiedDiff","computeUpgradeDiffs","currentContent","printDiffs","d","report","json","total","driftTypeTag","fileList","filePath","printFileList","lines","platformCount","userCount","import_commander","import_promises","import_node_path","import_handlebars","import_tfy_infra_engine","createValidateCommand","options","exitCode","runValidate","error","handleEngineError","fileExists","path","templateDir","issues","hasErrors","info","bundlePath","EXIT_CODES","verbose","content","parsed","rawMeta","jsonSchema","success","files","staticFiles","fileCount","staticCount","relPath","Handlebars","warnings","i","errors","warn","issue","prefix","import_commander","import_promises","import_node_path","yaml","import_tfy_infra_engine","createTestCommand","options","exitCode","runTest","error","handleEngineError","templateDir","testsDir","templateUri","info","fixtures","entries","entry","inputsPath","EXIT_CODES","template","createResolverRegistry","engine","results","fixture","fixtureDir","expectedDir","verbose","inputsContent","inputs","envelope","result","updateExpected","success","errors","diffs","compareOutputs","err","d","diffLines","line","passed","r","failed","normalizeStatusSource","content","filePath","outputPath","includeDiffs","actual","expectedPath","expected","normalizedActual","normalizedExpected","diff","generateUnifiedDiff","expectedFiles","file","import_commander","import_promises","import_node_path","yaml","import_tfy_infra_engine","import_chokidar","import_node_path","classifyFileChange","path","schemaFile","normalizedPath","filename","createWatcher","paths","options","onChange","onReady","onError","ignored","debounceMs","watcher","chokidar","handleEvent","type","fileType","err","createDevCommand","options","exitCode","runDev","error","handleEngineError","validateTemplateDir","versionDir","bundleJsonPath","ensureOutputDir","_versionDir","customOutput","outputDir","isSchemaMode","runSchemaModeDev","runDirectoryModeDev","schemaPath","srcDir","templateName","templateVersion","fixturePath","EXIT_CODES","info","relativeOutputDir","engine","renderFromSchema","template","buildTemplateFromSchema","renderWithTemplate","watcher","createWatcher","event","resolvePromise","cleanup","extractVersionFromSchema","jsonSchema","props","versionProp","def","name","version","schemaContent","resolvedVersion","files","staticFiles","walkSrcDir","runTests","inputsContent","inputs","envelope","result","templateFileCount","f","filesWritten","filePath","entry","outputPath","runTestComparisonFromResult","validation","defaultFixture","findDefaultFixture","templateUri","runRenderCycle","relPath","runValidation","createResolverRegistry","runTestComparison","formatValidationErrors","_outputDir","expectedDir","runTestComparisonFromDir","_result","startTime","errors","actual","expectedPath","expected","normalizedActual","normalizedExpected","diff","generateUnifiedDiff","duration","err","diffLines","line","import_commander","import_node_path","import_tfy_infra_engine","DRIFT_DETECTED","createVerifyCommand","options","exitCode","runVerify","error","handleEngineError","dir","manifestPath","verbose","manifest","loadManifest","currentFiles","readDirectoryToFileMap","result","info","formatDriftReportJson","formatDriftReport","EXIT_CODES","report","lines","issueCount","entry","tag","driftTypeTag","createTplCommand","tpl","createBundleCommand","createDevCommand","createRenderCommand","createValidateCommand","createTestCommand","createVerifyCommand","VERSION","main","program","thisCommand","opts","setVerbosity","createTplCommand","error"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/tpl/index.ts","../src/commands/tpl/bundle.ts","../src/utils/walk-src.ts","../src/utils/error-handler.ts","../src/utils/output.ts","../src/commands/tpl/render.ts","../src/resolvers/index.ts","../src/resolvers/file.ts","../src/resolvers/https.ts","../src/resolvers/base.ts","../src/cache/template-cache.ts","../src/utils/config-loader.ts","../src/utils/fs-helpers.ts","../src/utils/manifest-loader.ts","../src/utils/file-reader.ts","../src/utils/file-writer.ts","../src/utils/diff.ts","../src/commands/tpl/validate.ts","../src/commands/tpl/test.ts","../src/commands/tpl/dev.ts","../src/utils/file-watcher.ts","../src/commands/tpl/verify.ts"],"sourcesContent":["/**\n * @truefoundry/tfy-infra-cli\n *\n * CLI for TrueFoundry infrastructure templating engine.\n * Binary: tfy-init\n */\n\nimport { Command } from 'commander';\nimport { createTplCommand } from './commands/tpl/index.js';\nimport { setVerbosity } from './utils/output.js';\n\n// When invoked via `npm run`, npm sets CWD to the package root.\n// INIT_CWD preserves the directory the user actually ran the command from.\nif (process.env['INIT_CWD']) {\n process.chdir(process.env['INIT_CWD']);\n}\n\ndeclare const __CLI_VERSION__: string;\nconst VERSION = __CLI_VERSION__;\n\n/**\n * Main CLI entry point.\n */\nasync function main(): Promise<void> {\n const program = new Command();\n\n program\n .name('tfy-init')\n .description('TrueFoundry infrastructure templating CLI')\n .enablePositionalOptions()\n .version(VERSION, '-v, --version', 'Show version')\n .option('--verbose', 'Enable verbose output')\n .option('-q, --quiet', 'Suppress non-error output')\n .hook('preAction', (thisCommand) => {\n const opts = thisCommand.opts();\n if (opts['quiet']) {\n setVerbosity('quiet');\n } else if (opts['verbose']) {\n setVerbosity('verbose');\n }\n });\n\n // Add command groups\n program.addCommand(createTplCommand());\n\n // Parse and execute\n await program.parseAsync(process.argv);\n}\n\n// Run CLI\nmain().catch((error) => {\n console.error('Fatal error:', error);\n process.exit(1);\n});\n","/**\n * tfy-init tpl command group\n *\n * CHANGE (006): Removed push command (S3 support removed).\n * CHANGE (008): Removed init command (templates scaffolded via CUE).\n */\n\nimport { Command } from 'commander';\nimport { createBundleCommand } from './bundle.js';\nimport { createRenderCommand } from './render.js';\nimport { createValidateCommand } from './validate.js';\nimport { createTestCommand } from './test.js';\n\nimport { createDevCommand } from './dev.js';\nimport { createVerifyCommand } from './verify.js';\n/**\n * Create the tpl command group with all subcommands.\n */\nexport function createTplCommand(): Command {\n const tpl = new Command('tpl').description('Template operations').enablePositionalOptions();\n\n tpl.addCommand(createBundleCommand());\n tpl.addCommand(createDevCommand());\n tpl.addCommand(createRenderCommand());\n tpl.addCommand(createValidateCommand());\n tpl.addCommand(createTestCommand());\n\n tpl.addCommand(createVerifyCommand());\n\n return tpl;\n}\n","/**\n * tfy-init tpl bundle command\n *\n * Accepts a pre-computed schema package (from tfy-schema-tool --schema-package)\n * plus a src/ directory and metadata flags, and produces a bundle.json.\n * No CUE or Go dependencies — pure TypeScript.\n */\n\nimport { Command } from 'commander';\nimport { readFile, writeFile, mkdir } from 'node:fs/promises';\nimport { resolve, dirname } from 'node:path';\nimport { walkSrcDir } from '../../utils/walk-src.js';\nimport { handleEngineError } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\n\ninterface SchemaPackage {\n jsonSchema: Record<string, unknown>;\n uiSchema: unknown[];\n}\n\ninterface BundleJson {\n metadata: {\n name: string;\n version: string;\n description?: string;\n };\n jsonSchema: Record<string, unknown>;\n uiSchema: unknown[];\n files: Record<string, string>;\n staticFiles: Record<string, string>;\n version: {\n semver: string;\n };\n}\n\ninterface BundleOptions {\n schema: string;\n src: string;\n name: string;\n version?: string;\n description?: string;\n output?: string;\n}\n\nexport function createBundleCommand(): Command {\n const cmd = new Command('bundle')\n .description('Build a bundle.json from a schema package and src/ directory')\n .requiredOption('--schema <path-or-dash>', 'Schema package JSON file, or \"-\" for stdin')\n .requiredOption('--src <dir>', 'Template source directory containing .hbs and static files')\n .requiredOption('--name <string>', 'Template name')\n .option('--description <string>', 'Template description')\n .option('-o, --output <path>', 'Output file path (default: stdout)')\n .action(async (options: BundleOptions) => {\n try {\n await runBundle(options);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\nasync function readSchemaPackage(schemaPath: string): Promise<SchemaPackage> {\n let raw: string;\n\n if (schemaPath === '-') {\n raw = await readStdin();\n } else {\n raw = await readFile(resolve(schemaPath), 'utf-8');\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(`Failed to parse schema package JSON: ${(err as Error).message}`, {\n cause: err,\n });\n }\n\n const obj = parsed as Record<string, unknown>;\n if (!obj['jsonSchema'] || typeof obj['jsonSchema'] !== 'object') {\n throw new Error('Schema package must contain a \"jsonSchema\" object');\n }\n if (!Array.isArray(obj['uiSchema'])) {\n throw new Error('Schema package must contain a \"uiSchema\" array');\n }\n\n return {\n jsonSchema: obj['jsonSchema'] as Record<string, unknown>,\n uiSchema: obj['uiSchema'] as unknown[],\n };\n}\n\nfunction readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n process.stdin.on('data', (chunk: Buffer) => chunks.push(chunk));\n process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));\n process.stdin.on('error', reject);\n });\n}\n\nfunction extractVersionFromSchema(jsonSchema: Record<string, unknown>): string | undefined {\n const props = jsonSchema['properties'] as Record<string, unknown> | undefined;\n if (!props) return undefined;\n const versionProp = props['version'] as Record<string, unknown> | undefined;\n if (!versionProp) return undefined;\n const def = versionProp['default'];\n return typeof def === 'string' ? def : undefined;\n}\n\nasync function runBundle(options: BundleOptions): Promise<void> {\n const schemaPkg = await readSchemaPackage(options.schema);\n\n const version = options.version ?? extractVersionFromSchema(schemaPkg.jsonSchema);\n if (!version) {\n throw new Error(\n 'Could not determine template version. Provide --version or ensure the schema has a \"version\" property with a default value.'\n );\n }\n\n const srcDir = resolve(options.src);\n const { files, staticFiles } = await walkSrcDir(srcDir);\n\n if (files.size === 0 && staticFiles.size === 0) {\n throw new Error(`No template files found in ${srcDir}`);\n }\n\n const metadata: BundleJson['metadata'] = {\n name: options.name,\n version,\n };\n if (options.description) {\n metadata.description = options.description;\n }\n\n const bundle: BundleJson = {\n metadata,\n jsonSchema: schemaPkg.jsonSchema,\n uiSchema: schemaPkg.uiSchema,\n files: Object.fromEntries(files),\n staticFiles: Object.fromEntries(staticFiles),\n version: { semver: version },\n };\n\n const json = JSON.stringify(bundle, null, 2) + '\\n';\n\n if (options.output) {\n const outPath = resolve(options.output);\n await mkdir(dirname(outPath), { recursive: true });\n await writeFile(outPath, json, 'utf-8');\n output.success(`Bundle written to ${outPath}`);\n } else {\n process.stdout.write(json);\n }\n}\n","/**\n * Recursive directory walker for template src/ directories.\n *\n * Walks a directory tree and classifies files into Handlebars templates\n * (.hbs → rendered) and static files (copied verbatim). Skips entries\n * whose names start with '_'. Map keys are relative paths from the\n * root directory, preserving subdirectory structure.\n */\n\nimport { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nexport interface WalkResult {\n files: Map<string, string>;\n staticFiles: Map<string, string>;\n}\n\n/**\n * Recursively walk a directory, reading all files into maps.\n *\n * @param rootDir - The src/ directory to walk\n * @returns Maps of relative-path → content, split by .hbs (rendered) vs static\n */\nexport async function walkSrcDir(rootDir: string): Promise<WalkResult> {\n const files = new Map<string, string>();\n const staticFiles = new Map<string, string>();\n\n await walk(rootDir, '', files, staticFiles);\n\n return { files, staticFiles };\n}\n\nasync function walk(\n rootDir: string,\n relativePath: string,\n files: Map<string, string>,\n staticFiles: Map<string, string>\n): Promise<void> {\n const currentDir = relativePath ? join(rootDir, relativePath) : rootDir;\n const entries = await readdir(currentDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.name.startsWith('_')) continue;\n\n const entryRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;\n\n if (entry.isDirectory()) {\n await walk(rootDir, entryRelative, files, staticFiles);\n } else {\n const content = await readFile(join(currentDir, entry.name), 'utf-8');\n if (entry.name.endsWith('.hbs')) {\n const outputPath = entryRelative.replace(/\\.hbs$/, '');\n files.set(outputPath, content);\n } else {\n staticFiles.set(entryRelative, content);\n }\n }\n }\n}\n","/**\n * Shared error handling for CLI commands.\n *\n * Provides consistent error output and exit code mapping across all\n * tfy-init tpl commands.\n */\n\nimport { EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\nimport * as output from './output.js';\n\n/**\n * Standard exit codes shared across all commands.\n */\nexport const EXIT_CODES = {\n SUCCESS: 0,\n VALIDATION_ERROR: 1,\n FETCH_ERROR: 2,\n RENDER_ERROR: 3,\n FORMAT_ERROR: 4,\n DRIFT_BLOCKED: 5,\n} as const;\n\n/**\n * Handle an error from a CLI command and exit with the appropriate code.\n *\n * Provides consistent behavior:\n * - Displays the error message\n * - Shows validation error details when available\n * - Shows engine error details in verbose mode\n * - Maps EngineErrorCode to standardized exit codes\n * - Shows format hint for tofu-related errors\n *\n * @param error - The caught error\n * @param fallbackExitCode - Exit code for unrecognized errors (default: RENDER_ERROR)\n */\nexport function handleEngineError(\n error: unknown,\n fallbackExitCode: number = EXIT_CODES.RENDER_ERROR\n): never {\n if (error instanceof EngineError) {\n output.error(error.message);\n\n // Show engine error details in verbose mode\n if (output.getVerbosity() === 'verbose' && error.details) {\n output.info('');\n output.info('Details:');\n for (const [key, value] of Object.entries(error.details)) {\n if (key !== 'errors') {\n output.info(` ${key}: ${JSON.stringify(value)}`);\n }\n }\n }\n\n // Show validation error list when available\n if (error.details?.['errors']) {\n output.info('');\n output.info('Errors:');\n output.info(\n output.formatValidationErrors(\n error.details['errors'] as Array<{ path: string; message: string }>\n )\n );\n }\n\n // Map error codes to exit codes\n switch (error.code) {\n case EngineErrorCode.INPUT_VALIDATION_FAILED:\n case EngineErrorCode.SCHEMA_INVALID:\n case EngineErrorCode.ENVELOPE_VALIDATION_FAILED:\n process.exit(EXIT_CODES.VALIDATION_ERROR);\n break;\n\n case EngineErrorCode.TEMPLATE_NOT_FOUND:\n case EngineErrorCode.FILE_NOT_FOUND:\n case EngineErrorCode.NETWORK_ERROR:\n process.exit(EXIT_CODES.FETCH_ERROR);\n break;\n\n case EngineErrorCode.TOFU_NOT_FOUND:\n case EngineErrorCode.TOFU_FMT_FAILED:\n output.info('');\n output.info('Run with --skip-format to skip formatting.');\n process.exit(EXIT_CODES.FORMAT_ERROR);\n break;\n\n case EngineErrorCode.MANIFEST_PARSE_ERROR:\n process.exit(EXIT_CODES.VALIDATION_ERROR);\n break;\n\n default:\n process.exit(fallbackExitCode);\n }\n }\n\n // Generic (non-engine) error\n output.error((error as Error).message);\n if (output.getVerbosity() === 'verbose') {\n console.error((error as Error).stack);\n }\n process.exit(fallbackExitCode);\n}\n","/**\n * Output formatting utilities for CLI.\n *\n * Supports a JSON mode where human-readable output is redirected to stderr\n * so that stdout remains clean for structured machine output via `data()`.\n */\n\nimport type { DriftType } from '@truefoundry/tfy-infra-engine';\n\n/* eslint-disable no-console */\n\nexport type Verbosity = 'quiet' | 'normal' | 'verbose';\n\nlet currentVerbosity: Verbosity = 'normal';\nlet jsonMode = false;\n\nexport function setVerbosity(level: Verbosity): void {\n currentVerbosity = level;\n}\n\nexport function getVerbosity(): Verbosity {\n return currentVerbosity;\n}\n\n/**\n * Enable JSON mode: human-readable output routes to stderr,\n * structured output goes to stdout via `data()`.\n */\nexport function setJsonMode(enabled: boolean): void {\n jsonMode = enabled;\n}\n\nexport function getJsonMode(): boolean {\n return jsonMode;\n}\n\n/**\n * Write structured data to stdout. Use for JSON payloads\n * that machine consumers will parse.\n */\nexport function data(message: string): void {\n process.stdout.write(message + '\\n');\n}\n\nexport function success(message: string): void {\n if (currentVerbosity !== 'quiet') {\n const write = jsonMode ? console.error : console.log;\n write(`✓ ${message}`);\n }\n}\n\nexport function error(message: string): void {\n console.error(`✗ ${message}`);\n}\n\nexport function info(message: string): void {\n if (currentVerbosity !== 'quiet') {\n const write = jsonMode ? console.error : console.log;\n write(message);\n }\n}\n\nexport function verbose(message: string): void {\n if (currentVerbosity === 'verbose') {\n const write = jsonMode ? console.error : console.log;\n write(` ${message}`);\n }\n}\n\nexport function warn(message: string): void {\n if (currentVerbosity !== 'quiet') {\n console.warn(`⚠ ${message}`);\n }\n}\n\nexport function formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nexport function formatValidationErrors(errors: Array<{ path: string; message: string }>): string {\n return errors.map((e) => ` • ${e.path}: ${e.message}`).join('\\n');\n}\n\nexport function driftTypeTag(type: DriftType): string {\n switch (type) {\n case 'content_drift':\n return 'DRIFTED';\n case 'missing_file':\n return 'MISSING';\n case 'unexpected_file':\n return 'UNEXPECTED';\n case 'metadata_inconsistency':\n return 'INCONSISTENT';\n case 'source_mismatch':\n return 'SOURCE MISMATCH';\n }\n}\n\nexport function printFileList(files: Array<{ path: string; size: number }>): void {\n if (currentVerbosity === 'quiet') return;\n\n const write = jsonMode ? console.error : console.log;\n write('Files:');\n for (const file of files) {\n write(` - ${file.path} (${formatSize(file.size)})`);\n }\n}\n","/**\n * tfy-init tpl render command\n *\n * Unified render command that handles fresh install, upgrade, and\n * manifest re-generation via auto-detection.\n *\n * Config mode auto-detects upgrade when manifest.json exists in the\n * output directory. Manifest mode always performs a fresh install\n * (disaster recovery). Dev mode renders from a local template directory.\n */\n\nimport { Command } from 'commander';\nimport { createInterface } from 'node:readline';\nimport { readFile, readdir, access } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport * as yaml from 'js-yaml';\nimport { createEngine } from '@truefoundry/tfy-infra-engine';\nimport type {\n Envelope,\n InstallResult,\n UpgradeResult,\n DriftReport,\n FileMap,\n} from '@truefoundry/tfy-infra-engine';\nimport { createResolverRegistry } from '../../resolvers/index.js';\nimport { loadConfig } from '../../utils/config-loader.js';\nimport { loadManifest, envelopeFromManifest } from '../../utils/manifest-loader.js';\nimport { readDirectoryToFileMap } from '../../utils/file-reader.js';\nimport { writeEngineOutput } from '../../utils/file-writer.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { driftTypeTag } from '../../utils/output.js';\nimport { findDefaultFixture } from '../../utils/fs-helpers.js';\nimport { generateUnifiedDiff, type DiffEntry } from '../../utils/diff.js';\n\nexport { EXIT_CODES };\n\nexport interface RenderArgs {\n config?: string;\n directory?: string;\n fromManifest?: string;\n}\n\nexport function validateRenderArgs(args: RenderArgs): void {\n const modes = [args.config, args.fromManifest].filter(Boolean);\n if (modes.length > 1) {\n throw new Error('--config and --from-manifest are mutually exclusive. Use one or the other.');\n }\n if (args.config && args.directory !== '.') {\n throw new Error('--config and --directory are mutually exclusive. Use one or the other.');\n }\n}\n\nexport function createRenderCommand(): Command {\n const cmd = new Command('render')\n .description('Render a template to HCL files')\n .option('-c, --config <path>', 'Path to envelope config file (YAML or JSON)')\n .option('-d, --directory <dir>', 'Path to template version directory', '.')\n .option('-f, --fixture <path>', 'Input file to use (when using --directory)')\n .option('-i, --input <key=value>', 'Inline input override (can be repeated)', collect, [])\n .requiredOption('-o, --output <dir>', 'Output directory')\n .option('--from-manifest <path>', 'Path to manifest.json to use as input source')\n .option('--force', 'Proceed even if managed files have drifted (upgrade mode)')\n .option('--allow-source-change', 'Allow upgrade when template source has changed')\n .option('--dry-run', 'Show what would change without writing files')\n .option('--json', 'Output result as structured JSON')\n .option('--skip-format', 'Skip tofu fmt formatting')\n .option('--no-cache', 'Force re-fetch template')\n .option('--timeout <ms>', 'Network timeout in milliseconds', '30000')\n .option('--intent-id <id>', 'Cluster identity for integrity tracking')\n .action(async (options: RenderOptions) => {\n try {\n if (options.config) options.config = resolve(options.config);\n if (options.fromManifest) options.fromManifest = resolve(options.fromManifest);\n\n validateRenderArgs({\n config: options.config,\n directory: options.directory,\n fromManifest: options.fromManifest,\n });\n const exitCode = await runRender(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\nfunction collect(value: string, previous: string[]): string[] {\n return [...previous, value];\n}\n\ninterface RenderOptions {\n config?: string;\n directory: string;\n fixture?: string;\n input?: string[];\n output: string;\n fromManifest?: string;\n force?: boolean;\n allowSourceChange?: boolean;\n dryRun?: boolean;\n json?: boolean;\n skipFormat?: boolean;\n cache?: boolean;\n timeout: string;\n intentId?: string;\n}\n\nexport { findDefaultFixture } from '../../utils/fs-helpers.js';\n\nexport function parseInputOverrides(inputs?: string[]): Record<string, string> {\n const result: Record<string, string> = {};\n if (!inputs) return result;\n\n for (const input of inputs) {\n const eqIndex = input.indexOf('=');\n if (eqIndex > 0) {\n const key = input.substring(0, eqIndex);\n const value = input.substring(eqIndex + 1);\n result[key] = value;\n }\n }\n\n return result;\n}\n\nasync function runRender(options: RenderOptions): Promise<number> {\n if (options.json) {\n output.setJsonMode(true);\n }\n\n const outputDir = resolve(options.output);\n const timeout = parseInt(options.timeout, 10);\n\n if (options.fromManifest) {\n return runRenderFromManifest(options.fromManifest, options, outputDir, timeout);\n }\n\n if (options.config) {\n const manifestPath = join(outputDir, 'manifest.json');\n if (await fileExists(manifestPath)) {\n return runRenderFromConfigUpgrade(options.config, options, outputDir, timeout, manifestPath);\n }\n return runRenderFromConfig(options.config, options, outputDir, timeout);\n }\n\n const templateDir = resolve(options.directory);\n return runRenderFromDirectory(templateDir, options, outputDir, timeout);\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function confirmPrompt(message: string): Promise<boolean> {\n if (!process.stdin.isTTY) return false;\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n return new Promise((resolve) => {\n rl.question(`${message} [y/N] `, (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === 'y');\n });\n });\n}\n\nasync function countExistingFiles(dir: string): Promise<number> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isFile() && e.name !== 'config.yaml' && e.name !== 'config.json')\n .length;\n } catch {\n return 0;\n }\n}\n\nasync function guardOverwrite(outputDir: string, options: RenderOptions): Promise<number | null> {\n if (options.force) return null;\n const count = await countExistingFiles(outputDir);\n if (count === 0) return null;\n\n output.warn(`Output directory contains ${count} existing files that will be overwritten.`);\n const confirmed = await confirmPrompt('Proceed?');\n if (confirmed) return null;\n\n if (options.json) {\n output.data(\n JSON.stringify(\n { aborted: true, reason: 'overwrite_confirmation', existingFiles: count },\n null,\n 2\n )\n );\n } else {\n output.info('Aborted. Use --force to skip confirmation.');\n }\n return EXIT_CODES.VALIDATION_ERROR;\n}\n\n/**\n * Render from a manifest file (re-render / disaster recovery).\n */\nasync function runRenderFromManifest(\n manifestPath: string,\n options: RenderOptions,\n outputDir: string,\n timeout: number\n): Promise<number> {\n output.verbose(`Loading manifest from ${manifestPath}`);\n const manifest = await loadManifest(manifestPath);\n const rawConfig = envelopeFromManifest(manifest);\n\n if (options.intentId) {\n rawConfig.intentId = options.intentId;\n }\n\n output.success(`Template: ${rawConfig.template}`);\n output.verbose(`Intent ID: ${rawConfig.intentId}`);\n output.verbose('Resolving template...');\n\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(rawConfig.template, {\n noCache: options.cache === false,\n timeout,\n });\n\n const envelope: Envelope = {\n template,\n inputs: rawConfig.inputs,\n intentId: rawConfig.intentId,\n platformPrefix: rawConfig.platformPrefix,\n options: {\n skipFormat: options.skipFormat,\n },\n };\n\n output.verbose('Re-rendering from manifest...');\n\n const engine = createEngine();\n const result = await engine.install(envelope);\n\n if (options.dryRun) {\n return await handleInstallDryRun(result, outputDir, options.json);\n }\n\n const aborted = await guardOverwrite(outputDir, options);\n if (aborted !== null) return aborted;\n\n output.success('Inputs validated');\n output.success(`Generated ${result.files.size - 1} files + manifest.json`);\n\n if (!options.skipFormat) {\n output.success('Formatted with tofu fmt');\n }\n\n await writeEngineOutput(outputDir, result, 'install');\n\n printInstallResult(result, outputDir, options.json);\n\n return EXIT_CODES.SUCCESS;\n}\n\n/**\n * Render from a template directory with fixture inputs.\n */\nasync function runRenderFromDirectory(\n templateDir: string,\n options: RenderOptions,\n outputDir: string,\n timeout: number\n): Promise<number> {\n const templateUri = `file://${templateDir}`;\n\n let fixturePath: string | null = options.fixture ? resolve(templateDir, options.fixture) : null;\n\n if (fixturePath) {\n if (!(await fileExists(fixturePath))) {\n output.error(`Fixture not found: ${options.fixture}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n } else {\n fixturePath = await findDefaultFixture(templateDir);\n if (!fixturePath) {\n output.error('No default fixture found at tests/default/inputs.yaml');\n output.info('Either create a default fixture or use --fixture to specify one.');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n }\n\n const fixtureContent = await readFile(fixturePath, 'utf-8');\n let inputs = yaml.load(fixtureContent) as Record<string, unknown>;\n\n const overrides = parseInputOverrides(options.input);\n inputs = { ...inputs, ...overrides };\n\n const intentId =\n options.intentId ?? (inputs['intentId'] as string | undefined) ?? `local-${Date.now()}`;\n\n output.success(`Template: ${templateUri}`);\n output.verbose(`Fixture: ${fixturePath}`);\n output.verbose(`Intent ID: ${intentId}`);\n\n output.verbose('Resolving template...');\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(templateUri, {\n noCache: options.cache === false,\n timeout,\n });\n\n const envelope: Envelope = {\n template,\n inputs,\n intentId,\n options: {\n skipFormat: options.skipFormat,\n },\n };\n\n const engine = createEngine();\n\n output.verbose('Validating inputs...');\n const result = await engine.install(envelope);\n\n if (options.dryRun) {\n return await handleInstallDryRun(result, outputDir, options.json);\n }\n\n const aborted = await guardOverwrite(outputDir, options);\n if (aborted !== null) return aborted;\n\n output.success('Inputs validated');\n output.success(`Generated ${result.files.size - 1} files + manifest.json`);\n\n if (!options.skipFormat) {\n output.success('Formatted with tofu fmt');\n }\n\n await writeEngineOutput(outputDir, result, 'install');\n\n printInstallResult(result, outputDir, options.json);\n\n return EXIT_CODES.SUCCESS;\n}\n\n/**\n * Render from a config file (fresh install).\n */\nasync function runRenderFromConfig(\n configFile: string,\n options: RenderOptions,\n outputDir: string,\n timeout: number\n): Promise<number> {\n output.verbose(`Loading config from ${configFile}`);\n const rawConfig = await loadConfig(configFile);\n\n if (options.intentId) {\n rawConfig.intentId = options.intentId;\n }\n\n if (options.input && options.input.length > 0) {\n const overrides = parseInputOverrides(options.input);\n rawConfig.inputs = { ...(rawConfig.inputs || {}), ...overrides };\n }\n\n output.success(`Template: ${rawConfig.template}`);\n output.verbose(`Intent ID: ${rawConfig.intentId}`);\n\n output.verbose('Resolving template...');\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(rawConfig.template, {\n noCache: options.cache === false,\n timeout,\n });\n\n const envelope: Envelope = {\n template,\n inputs: rawConfig.inputs,\n intentId: rawConfig.intentId,\n platformPrefix: rawConfig.platformPrefix,\n options: {\n skipFormat: options.skipFormat ?? rawConfig.options?.skipFormat,\n },\n };\n\n const engine = createEngine();\n\n output.verbose('Validating inputs...');\n const result = await engine.install(envelope);\n\n if (options.dryRun) {\n return await handleInstallDryRun(result, outputDir, options.json);\n }\n\n const aborted = await guardOverwrite(outputDir, options);\n if (aborted !== null) return aborted;\n\n output.success('Inputs validated');\n output.success(`Generated ${result.files.size - 1} files + manifest.json`);\n\n if (!envelope.options?.skipFormat) {\n output.success('Formatted with tofu fmt');\n }\n\n await writeEngineOutput(outputDir, result, 'install');\n\n printInstallResult(result, outputDir, options.json);\n\n return EXIT_CODES.SUCCESS;\n}\n\n/**\n * Upgrade from a config file when manifest.json exists in the output directory.\n * Auto-detected when config mode finds an existing manifest.\n */\nasync function runRenderFromConfigUpgrade(\n configFile: string,\n options: RenderOptions,\n outputDir: string,\n timeout: number,\n manifestPath: string\n): Promise<number> {\n output.verbose(`Existing manifest detected at ${manifestPath} — running upgrade`);\n\n const previousManifest = await loadManifest(manifestPath);\n\n output.verbose(`Reading files from ${outputDir}`);\n const currentFiles = await readDirectoryToFileMap(outputDir, previousManifest.platformPrefix);\n\n output.verbose(`Loading config from ${configFile}`);\n const rawConfig = await loadConfig(configFile);\n\n if (options.intentId) {\n rawConfig.intentId = options.intentId;\n }\n\n if (options.input && options.input.length > 0) {\n const overrides = parseInputOverrides(options.input);\n rawConfig.inputs = { ...(rawConfig.inputs || {}), ...overrides };\n }\n\n output.success(`Template: ${rawConfig.template}`);\n output.verbose(`Intent ID: ${rawConfig.intentId}`);\n\n output.verbose('Resolving template...');\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(rawConfig.template, {\n noCache: options.cache === false,\n timeout,\n });\n\n const envelope: Envelope = {\n template,\n inputs: rawConfig.inputs,\n intentId: rawConfig.intentId,\n platformPrefix: rawConfig.platformPrefix,\n options: {\n skipFormat: options.skipFormat ?? rawConfig.options?.skipFormat,\n },\n };\n\n const engine = createEngine();\n const result = await engine.upgrade(envelope, currentFiles, previousManifest);\n\n if (result.sourceBlocked && !options.allowSourceChange) {\n output.warn('Source mismatch: current files were generated from a different template source.');\n printDriftReport(result.driftReport, options.json);\n if (options.json) {\n output.data(\n JSON.stringify(\n {\n blocked: true,\n reason: 'source_mismatch',\n sourceBlocked: true,\n driftReport: { drift: !result.driftReport.valid, summary: result.driftReport.summary },\n },\n null,\n 2\n )\n );\n } else {\n output.info('Use --allow-source-change to proceed with source migration.');\n }\n return EXIT_CODES.DRIFT_BLOCKED;\n }\n if (result.sourceBlocked) {\n output.warn('Proceeding with source migration (--allow-source-change).');\n }\n\n printDriftReport(result.driftReport, options.json);\n\n if (!result.driftReport.valid && !options.force) {\n if (options.json) {\n output.data(\n JSON.stringify(\n {\n blocked: true,\n reason: 'content_drift',\n sourceBlocked: result.sourceBlocked,\n driftReport: { drift: true, summary: result.driftReport.summary },\n },\n null,\n 2\n )\n );\n } else {\n output.info('Use --force to proceed despite drift.');\n }\n return EXIT_CODES.DRIFT_BLOCKED;\n }\n if (!result.driftReport.valid) {\n output.warn('Proceeding despite drift (--force).');\n }\n\n if (options.dryRun) {\n return handleUpgradeDryRun(result, currentFiles, options.json);\n }\n\n await writeEngineOutput(outputDir, result, 'upgrade');\n\n if (options.json) {\n output.data(formatUpgradeResultJson(result));\n } else {\n output.info(formatUpgradeResult(result));\n }\n\n return EXIT_CODES.SUCCESS;\n}\n\n// --- Diff computation helpers ---\n\nasync function computeInstallDiffs(newFiles: FileMap, outputDir: string): Promise<DiffEntry[]> {\n const diffs: DiffEntry[] = [];\n for (const [filename, entry] of newFiles) {\n if (filename === 'manifest.json') continue;\n let existing = '';\n try {\n existing = await readFile(join(outputDir, filename), 'utf-8');\n } catch {\n /* new file */\n }\n if (existing === entry.content) continue;\n diffs.push({ filename, diff: generateUnifiedDiff(existing, entry.content, filename) });\n }\n return diffs;\n}\n\nfunction computeUpgradeDiffs(newFiles: FileMap, currentFiles: FileMap): DiffEntry[] {\n const diffs: DiffEntry[] = [];\n for (const [filename, entry] of newFiles) {\n if (filename === 'manifest.json') continue;\n const current = currentFiles.get(filename);\n const currentContent = current?.content ?? '';\n if (currentContent === entry.content) continue;\n diffs.push({ filename, diff: generateUnifiedDiff(currentContent, entry.content, filename) });\n }\n return diffs;\n}\n\nfunction printDiffs(diffs: DiffEntry[]): void {\n if (diffs.length === 0) {\n output.info('\\nNo file changes detected.');\n return;\n }\n output.info(`\\n${diffs.length} file(s) changed:\\n`);\n for (const d of diffs) {\n output.info(d.diff);\n }\n}\n\n// --- Drift report helper ---\n\nfunction printDriftReport(report: DriftReport, json?: boolean): void {\n if (json) {\n output.data(\n JSON.stringify(\n {\n drift: !report.valid,\n summary: report.summary,\n entries: report.entries.map((e) => ({\n path: e.path,\n type: e.type,\n details: e.details,\n })),\n },\n null,\n 2\n )\n );\n return;\n }\n if (report.valid) {\n output.success('No drift detected in managed files.');\n return;\n }\n const total =\n report.summary.driftedFiles +\n report.summary.missingFiles +\n report.summary.unexpectedFiles +\n report.summary.inconsistentFiles;\n output.warn(`Drift detected in ${total} managed files:`);\n for (const entry of report.entries) {\n output.info(` ${driftTypeTag(entry.type)} ${entry.path}`);\n output.info(` ${entry.details}`);\n }\n output.info('');\n output.info('Use --force to override and proceed with upgrade.');\n}\n\n// --- Install mode output helpers ---\n\nasync function handleInstallDryRun(\n result: InstallResult,\n outputDir: string,\n json?: boolean\n): Promise<number> {\n const diffs = await computeInstallDiffs(result.files, outputDir);\n if (json) {\n output.data(\n JSON.stringify(\n {\n dryRun: true,\n aggregateHash: result.manifest.aggregateHash,\n templateSource: result.manifest.templateSource,\n templateVersion: result.manifest.templateVersion,\n fileCount: result.files.size,\n files: Array.from(result.files.entries()).map(([path, entry]) => ({\n path,\n size: Buffer.byteLength(entry.content, 'utf-8'),\n })),\n changes: diffs.map((d) => ({ filename: d.filename, diff: d.diff })),\n },\n null,\n 2\n )\n );\n } else {\n output.info('Dry run — no files written.');\n output.info('');\n output.info(`Aggregate Hash: ${result.manifest.aggregateHash}`);\n output.info(`Template: ${result.manifest.templateSource}`);\n output.info(`Version: ${result.manifest.templateVersion}`);\n output.info(`Files: ${result.files.size}`);\n printDiffs(diffs);\n }\n return EXIT_CODES.SUCCESS;\n}\n\nfunction printInstallResult(result: InstallResult, outputDir: string, json?: boolean): void {\n if (json) {\n output.data(\n JSON.stringify(\n {\n success: true,\n aggregateHash: result.manifest.aggregateHash,\n fileCount: result.files.size,\n outputDir,\n files: Array.from(result.files.entries()).map(([path, entry]) => ({\n path,\n size: Buffer.byteLength(entry.content, 'utf-8'),\n })),\n },\n null,\n 2\n )\n );\n } else {\n const fileList: Array<{ path: string; size: number }> = [];\n for (const [filePath, entry] of result.files) {\n fileList.push({\n path: filePath,\n size: Buffer.byteLength(entry.content, 'utf-8'),\n });\n }\n output.info('');\n output.info(`Output written to: ${outputDir}`);\n output.info(`Aggregate Hash: ${result.manifest.aggregateHash}`);\n output.printFileList(fileList);\n }\n}\n\n// --- Upgrade mode output helpers ---\n\nfunction handleUpgradeDryRun(result: UpgradeResult, currentFiles: FileMap, json?: boolean): number {\n const diffs = computeUpgradeDiffs(result.files, currentFiles);\n if (json) {\n output.data(\n JSON.stringify(\n {\n dryRun: true,\n templateSource: result.manifest.templateSource,\n templateVersion: result.manifest.templateVersion,\n aggregateHash: result.manifest.aggregateHash,\n driftReport: result.driftReport,\n files: Array.from(result.files.entries()).map(([path, entry]) => ({\n path,\n zone: entry.zone,\n })),\n changes: diffs.map((d) => ({ filename: d.filename, diff: d.diff })),\n },\n null,\n 2\n )\n );\n } else {\n output.info('Dry run — no files written.');\n output.info('');\n output.info(formatUpgradeResult(result));\n printDiffs(diffs);\n }\n return EXIT_CODES.SUCCESS;\n}\n\nfunction formatUpgradeResult(result: UpgradeResult): string {\n const lines: string[] = [];\n lines.push(\n `✓ Upgrade complete: ${result.manifest.templateSource} v${result.manifest.templateVersion}`\n );\n lines.push('');\n let platformCount = 0;\n let userCount = 0;\n for (const [, entry] of result.files) {\n if (entry.zone === 'platform') platformCount++;\n else userCount++;\n }\n lines.push(` Updated: ${platformCount} files (Platform Zone)`);\n lines.push(` Unchanged: ${userCount} files (User Zone)`);\n lines.push('');\n lines.push(`Aggregate Hash: ${result.manifest.aggregateHash}`);\n return lines.join('\\n');\n}\n\nfunction formatUpgradeResultJson(result: UpgradeResult): string {\n return JSON.stringify(\n {\n success: true,\n templateSource: result.manifest.templateSource,\n templateVersion: result.manifest.templateVersion,\n aggregateHash: result.manifest.aggregateHash,\n driftReport: result.driftReport,\n files: Array.from(result.files.entries()).map(([path, entry]) => ({\n path,\n zone: entry.zone,\n })),\n },\n null,\n 2\n );\n}\n","/**\n * Resolver registry for managing template source resolvers.\n *\n * Supports file:// and https://*.json bundle URIs.\n */\n\nimport type { Resolver } from './base.js';\nimport type { Template, ResolverOptions } from '@truefoundry/tfy-infra-engine';\nimport { EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\nimport { FileResolver } from './file.js';\nimport { HttpsBundleResolver } from './https.js';\nimport { TemplateCache } from '../cache/template-cache.js';\n\nexport type { Resolver } from './base.js';\nexport { FileResolver } from './file.js';\nexport { HttpsBundleResolver } from './https.js';\nexport { TemplateCache, createTemplateCache, getDefaultCacheDir } from '../cache/template-cache.js';\n\n/**\n * Registry for template resolvers.\n * Routes template URIs to the appropriate resolver based on scheme.\n */\nexport class ResolverRegistry {\n private resolvers: Resolver[] = [];\n private cache: TemplateCache;\n\n constructor(resolvers?: Resolver[], cacheDir?: string) {\n this.cache = new TemplateCache(cacheDir);\n\n if (resolvers) {\n this.resolvers = resolvers;\n } else {\n this.register(new FileResolver());\n this.register(new HttpsBundleResolver());\n }\n }\n\n register(resolver: Resolver): void {\n this.resolvers.push(resolver);\n }\n\n getResolver(uri: string): Resolver | undefined {\n return this.resolvers.find((r) => r.canResolve(uri));\n }\n\n async resolve(uri: string, options?: ResolverOptions): Promise<Template> {\n const resolver = this.getResolver(uri);\n\n if (!resolver) {\n const scheme = uri.split('://')[0] || 'unknown';\n throw new EngineError(\n `No resolver found for URI scheme: ${scheme}://`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n undefined,\n {\n uri,\n scheme,\n supportedSchemes: ['file://', 'https://*.json'],\n }\n );\n }\n\n const template = await resolver.resolve(uri, options);\n\n // Cache the result (unless it's a file:// URI)\n if (!uri.startsWith('file://')) {\n await this.cache.set(template);\n }\n\n return template;\n }\n\n canResolve(uri: string): boolean {\n return this.resolvers.some((r) => r.canResolve(uri));\n }\n\n getSupportedSchemes(): string[] {\n return ['file://', 'https://*.json'];\n }\n\n async clearCache(uri?: string): Promise<void> {\n if (uri) {\n await this.cache.delete(uri);\n } else {\n await this.cache.clear();\n }\n }\n\n async getCacheStats(): Promise<{ sources: number; templates: number; size: number }> {\n return this.cache.stats();\n }\n}\n\n/**\n * Create a default resolver registry with standard resolvers (file + https bundle).\n */\nexport function createResolverRegistry(cacheDir?: string): ResolverRegistry {\n return new ResolverRegistry(undefined, cacheDir);\n}\n","/**\n * File resolver for local template sources (file://).\n *\n * Supports bundle.json — single-file distribution format.\n *\n * Detection order: if the URI points directly at a .json file, treat it as a\n * bundle; if it's a directory, check build/bundle.json first (new convention),\n * then bundle.json at the directory root (legacy fallback).\n */\n\nimport { readFile, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Resolver } from './base.js';\nimport type { Template, ResolverOptions, VersionInfo } from '@truefoundry/tfy-infra-engine';\nimport { EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\n\n/**\n * Resolver for file:// URIs.\n * Loads templates from the local filesystem.\n */\nexport class FileResolver implements Resolver {\n canResolve(uri: string): boolean {\n return uri.startsWith('file://');\n }\n\n async resolve(uri: string, _options?: ResolverOptions): Promise<Template> {\n const fsPath = this.uriToPath(uri);\n\n try {\n const info = await stat(fsPath);\n\n if (info.isFile() && fsPath.endsWith('.json')) {\n return this.resolveBundle(fsPath, uri);\n }\n\n if (info.isDirectory()) {\n const buildBundlePath = join(fsPath, 'build', 'bundle.json');\n if (await this.fileExists(buildBundlePath)) {\n return this.resolveBundle(buildBundlePath, uri);\n }\n const bundlePath = join(fsPath, 'bundle.json');\n if (await this.fileExists(bundlePath)) {\n return this.resolveBundle(bundlePath, uri);\n }\n throw new EngineError(\n `bundle.json not found in ${fsPath} or ${fsPath}/build`,\n EngineErrorCode.BUNDLE_NOT_FOUND,\n undefined,\n { path: fsPath }\n );\n }\n\n throw new EngineError(\n `Not a file or directory: ${fsPath}`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n undefined,\n { path: fsPath }\n );\n } catch (error) {\n if (error instanceof EngineError) {\n throw error;\n }\n\n const nodeError = error as NodeJS.ErrnoException;\n if (nodeError.code === 'ENOENT') {\n throw new EngineError(\n `Template not found: ${fsPath}`,\n EngineErrorCode.FILE_NOT_FOUND,\n error as Error,\n { path: fsPath, originalCode: nodeError.code }\n );\n }\n\n if (nodeError.code === 'EACCES') {\n throw new EngineError(\n `Permission denied: ${fsPath}`,\n EngineErrorCode.FILE_NOT_FOUND,\n error as Error,\n { path: fsPath, originalCode: nodeError.code }\n );\n }\n\n throw new EngineError(\n `Failed to load template from ${fsPath}: ${(error as Error).message}`,\n EngineErrorCode.FILE_NOT_FOUND,\n error as Error,\n { path: fsPath }\n );\n }\n }\n\n private async resolveBundle(bundlePath: string, uri: string): Promise<Template> {\n const content = await readFile(bundlePath, 'utf-8');\n\n let raw: Record<string, unknown>;\n try {\n raw = JSON.parse(content) as Record<string, unknown>;\n } catch (error) {\n throw new EngineError(\n `Failed to parse bundle.json: ${(error as Error).message}`,\n EngineErrorCode.BUNDLE_INVALID,\n error as Error,\n { path: bundlePath }\n );\n }\n\n const rawMetadata = raw['metadata'] as Record<string, unknown> | undefined;\n if (!rawMetadata || typeof rawMetadata !== 'object') {\n throw new EngineError(\n 'bundle.json must contain a \"metadata\" object',\n EngineErrorCode.BUNDLE_INVALID,\n undefined,\n { path: bundlePath }\n );\n }\n if (!rawMetadata['name'] || !rawMetadata['version']) {\n throw new EngineError(\n 'bundle.json metadata must contain \"name\" and \"version\"',\n EngineErrorCode.BUNDLE_INVALID,\n undefined,\n { path: bundlePath }\n );\n }\n\n const metadata = {\n name: rawMetadata['name'] as string,\n version: rawMetadata['version'] as string,\n ...(rawMetadata['description'] ? { description: rawMetadata['description'] as string } : {}),\n };\n\n const jsonSchema = raw['jsonSchema'] as Record<string, unknown>;\n if (!jsonSchema || typeof jsonSchema !== 'object') {\n throw new EngineError(\n 'bundle.json must contain a \"jsonSchema\" object',\n EngineErrorCode.BUNDLE_INVALID,\n undefined,\n { path: bundlePath }\n );\n }\n\n const files = new Map(Object.entries((raw['files'] as Record<string, string>) ?? {}));\n const staticFiles = new Map(\n Object.entries((raw['staticFiles'] as Record<string, string>) ?? {})\n );\n\n if (files.size === 0 && staticFiles.size === 0) {\n throw new EngineError(\n `Bundle contains no template files: ${bundlePath}`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n undefined,\n { path: bundlePath }\n );\n }\n\n const version: VersionInfo = { semver: metadata.version };\n\n return { metadata, jsonSchema, files, staticFiles, source: uri, version };\n }\n\n private uriToPath(uri: string): string {\n if (uri.startsWith('file:///')) {\n return uri.substring(7);\n }\n if (uri.startsWith('file://')) {\n return uri.substring(7);\n }\n return uri;\n }\n\n private async fileExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n }\n}\n","/**\n * HTTPS bundle resolver for pre-built template bundles (https://*.json).\n *\n * Fetches a bundle JSON file over HTTPS and unpacks it into a Template.\n * Automatically attaches a Bearer token for Artifactory hosts when\n * JFROG_ACCESS_TOKEN is present in the environment.\n */\n\nimport type { Resolver } from './base.js';\nimport type { Template, ResolverOptions, VersionInfo } from '@truefoundry/tfy-infra-engine';\nimport { validateBundle, EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\nimport { mergeResolverOptions, withRetry, withTimeout } from './base.js';\n\nconst JFROG_TOKEN_ENV = 'JFROG_ACCESS_TOKEN';\n\nfunction isArtifactoryHost(uri: string): boolean {\n try {\n const { hostname } = new URL(uri);\n return hostname.includes('jfrog');\n } catch {\n return false;\n }\n}\n\nfunction buildHeaders(uri: string): Record<string, string> {\n const headers: Record<string, string> = {\n 'User-Agent': 'tfy-infra-cli',\n Accept: 'application/json',\n };\n\n if (isArtifactoryHost(uri)) {\n const token = process.env[JFROG_TOKEN_ENV];\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n }\n\n return headers;\n}\n\nasync function fetchBundle(uri: string, headers: Record<string, string>): Promise<Response> {\n const response = await fetch(uri, { headers });\n\n if (response.status === 401 || response.status === 403) {\n const hint = isArtifactoryHost(uri)\n ? ` Set ${JFROG_TOKEN_ENV} in your environment to authenticate.`\n : '';\n throw new EngineError(\n `Authentication failed (HTTP ${response.status}) for ${uri}.${hint}`,\n EngineErrorCode.HTTPS_AUTH_FAILED,\n undefined,\n { uri, status: response.status }\n );\n }\n\n if (!response.ok) {\n throw new EngineError(\n `Bundle download failed: HTTP ${response.status} for ${uri}`,\n EngineErrorCode.NETWORK_ERROR,\n undefined,\n { uri, status: response.status }\n );\n }\n\n return response;\n}\n\nasync function unpackBundle(response: Response, uri: string): Promise<Template> {\n const body: unknown = await response.json();\n const raw = body as Record<string, unknown>;\n\n const { metadata, jsonSchema } = validateBundle(raw);\n\n const files = new Map(Object.entries((raw['files'] as Record<string, string>) ?? {}));\n const staticFiles = new Map(Object.entries((raw['staticFiles'] as Record<string, string>) ?? {}));\n\n if (files.size === 0 && staticFiles.size === 0) {\n throw new EngineError(\n `Bundle contains no template files: ${uri}`,\n EngineErrorCode.TEMPLATE_NOT_FOUND,\n undefined,\n { uri }\n );\n }\n\n const version: VersionInfo = { semver: metadata.version };\n\n return { metadata, jsonSchema, files, staticFiles, source: uri, version };\n}\n\n/**\n * Resolver for HTTPS bundle URLs (https://.../*.json).\n */\nexport class HttpsBundleResolver implements Resolver {\n canResolve(uri: string): boolean {\n return uri.startsWith('https://') && uri.toLowerCase().endsWith('.json');\n }\n\n async resolve(uri: string, options?: ResolverOptions): Promise<Template> {\n const opts = mergeResolverOptions(options);\n const headers = buildHeaders(uri);\n\n const response = await withTimeout(\n withRetry(() => fetchBundle(uri, headers), opts.retries),\n opts.timeout,\n `HTTPS bundle download timed out after ${opts.timeout}ms`\n );\n\n return unpackBundle(response, uri);\n }\n}\n","/**\n * Base resolver interface and common utilities.\n *\n * Moved from @truefoundry/tfy-infra-engine (006).\n * Imports reference the engine package for shared types.\n */\n\nimport type { Template, ResolverOptions } from '@truefoundry/tfy-infra-engine';\n\n/**\n * Interface for template resolvers.\n * Each resolver handles a specific URI scheme (file://, https://).\n */\nexport interface Resolver {\n canResolve(uri: string): boolean;\n resolve(uri: string, options?: ResolverOptions): Promise<Template>;\n}\n\n/**\n * Default resolver options.\n */\nexport const DEFAULT_RESOLVER_OPTIONS: Required<ResolverOptions> = {\n timeout: 30000,\n retries: 3,\n noCache: false,\n};\n\n/**\n * Merge resolver options with defaults.\n */\nexport function mergeResolverOptions(options?: ResolverOptions): Required<ResolverOptions> {\n return {\n ...DEFAULT_RESOLVER_OPTIONS,\n ...options,\n };\n}\n\n/**\n * Retry an async operation with exponential backoff.\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n retries: number,\n baseDelay = 1000\n): Promise<T> {\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n\n if (attempt < retries) {\n const delay = baseDelay * Math.pow(2, attempt) * (0.5 + Math.random() * 0.5);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n\n throw lastError instanceof Error ? lastError : new Error(String(lastError));\n}\n\n/**\n * Create a timeout wrapper for promises.\n */\nexport function withTimeout<T>(promise: Promise<T>, ms: number, message?: string): Promise<T> {\n let timer: ReturnType<typeof setTimeout>;\n\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error(message ?? `Operation timed out after ${ms}ms`)), ms);\n });\n\n return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));\n}\n","/**\n * Template cache for storing fetched templates locally.\n * Uses filesystem-based caching with version-keyed storage.\n *\n * Stores templates as bundle.json files containing all template data\n * in a single self-contained JSON file.\n */\n\nimport { mkdir, readFile, writeFile, rm, readdir, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport type { Template, VersionInfo } from '@truefoundry/tfy-infra-engine';\nimport { validateBundle, EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';\n\n/**\n * Default cache directory.\n */\nexport function getDefaultCacheDir(): string {\n const home = process.env['HOME'] || process.env['USERPROFILE'] || '~';\n return join(home, '.cache', 'tfy-infra', 'templates');\n}\n\n/**\n * Template cache manager.\n */\nexport class TemplateCache {\n private readonly cacheDir: string;\n\n constructor(cacheDir?: string) {\n this.cacheDir = cacheDir ?? getDefaultCacheDir();\n }\n\n private getCacheKey(source: string): string {\n return createHash('sha256').update(source).digest('hex').substring(0, 16);\n }\n\n private getCachePath(source: string, version: string): string {\n const key = this.getCacheKey(source);\n const safeVersion = version.replace(/\\//g, '-');\n return join(this.cacheDir, key, safeVersion);\n }\n\n async has(source: string, version: string): Promise<boolean> {\n const cachePath = this.getCachePath(source, version);\n try {\n const bundlePath = join(cachePath, 'bundle.json');\n await stat(bundlePath);\n return true;\n } catch {\n return false;\n }\n }\n\n async get(source: string, version: string): Promise<Template | null> {\n const cachePath = this.getCachePath(source, version);\n\n try {\n const bundlePath = join(cachePath, 'bundle.json');\n const bundleContent = await readFile(bundlePath, 'utf-8');\n const parsed: unknown = JSON.parse(bundleContent);\n const { metadata, jsonSchema } = validateBundle(parsed);\n\n const record = parsed as Record<string, unknown>;\n const files = new Map(Object.entries((record['files'] as Record<string, string>) ?? {}));\n const staticFiles = new Map(\n Object.entries((record['staticFiles'] as Record<string, string>) ?? {})\n );\n\n const versionInfo: VersionInfo = {\n semver: metadata.version,\n };\n\n return {\n metadata,\n jsonSchema,\n files,\n staticFiles,\n source,\n version: versionInfo,\n };\n } catch {\n return null;\n }\n }\n\n async set(template: Template): Promise<void> {\n const cachePath = this.getCachePath(template.source, template.version.semver);\n\n try {\n await mkdir(cachePath, { recursive: true });\n\n const bundle = {\n metadata: {\n name: template.metadata.name,\n version: template.metadata.version,\n ...(template.metadata.description ? { description: template.metadata.description } : {}),\n },\n jsonSchema: template.jsonSchema,\n files: Object.fromEntries(template.files),\n staticFiles: Object.fromEntries(template.staticFiles),\n };\n await writeFile(join(cachePath, 'bundle.json'), JSON.stringify(bundle, null, 2), 'utf-8');\n } catch (error) {\n throw new EngineError(\n `Failed to cache template: ${(error as Error).message}`,\n EngineErrorCode.CACHE_ERROR,\n error as Error,\n { source: template.source, version: template.version.semver }\n );\n }\n }\n\n async delete(source: string, version?: string): Promise<void> {\n if (version) {\n const cachePath = this.getCachePath(source, version);\n try {\n await rm(cachePath, { recursive: true, force: true });\n } catch {\n // Ignore if doesn't exist\n }\n } else {\n const key = this.getCacheKey(source);\n const keyPath = join(this.cacheDir, key);\n try {\n await rm(keyPath, { recursive: true, force: true });\n } catch {\n // Ignore if doesn't exist\n }\n }\n }\n\n async clear(): Promise<void> {\n try {\n await rm(this.cacheDir, { recursive: true, force: true });\n } catch {\n // Ignore if doesn't exist\n }\n }\n\n async stats(): Promise<{ sources: number; templates: number; size: number }> {\n let sources = 0;\n let templates = 0;\n let size = 0;\n\n try {\n const sourceKeys = await readdir(this.cacheDir);\n sources = sourceKeys.length;\n\n for (const key of sourceKeys) {\n const keyPath = join(this.cacheDir, key);\n const versions = await readdir(keyPath);\n templates += versions.length;\n\n for (const version of versions) {\n const versionPath = join(keyPath, version);\n size += await this.dirSize(versionPath);\n }\n }\n } catch {\n // Cache doesn't exist or is empty\n }\n\n return { sources, templates, size };\n }\n\n private async dirSize(dir: string): Promise<number> {\n let total = 0;\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n total += await this.dirSize(fullPath);\n } else {\n const fileStat = await stat(fullPath);\n total += fileStat.size;\n }\n }\n return total;\n }\n}\n\n/**\n * Create a template cache instance.\n */\nexport function createTemplateCache(cacheDir?: string): TemplateCache {\n return new TemplateCache(cacheDir);\n}\n","/**\n * Configuration file loader for YAML and JSON formats.\n *\n * The CLI command is responsible for resolving the template URI before calling the engine.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport * as yaml from 'js-yaml';\n\n/**\n * Raw configuration from a config file.\n * `template` is a URI string that must be resolved before passing to the engine.\n */\nexport interface RawConfig {\n template: string;\n inputs: Record<string, unknown>;\n intentId: string;\n platformPrefix?: string;\n options?: {\n skipFormat?: boolean;\n };\n}\n\n/**\n * Load a configuration file (YAML or JSON).\n *\n * @param filePath - Path to the config file\n * @returns Raw configuration with template URI string\n */\nexport async function loadConfig(filePath: string): Promise<RawConfig> {\n const content = await readFile(filePath, 'utf-8');\n\n const isJson = filePath.endsWith('.json');\n\n let parsed: Record<string, unknown>;\n try {\n if (isJson) {\n parsed = JSON.parse(content) as Record<string, unknown>;\n } else {\n parsed = yaml.load(content) as Record<string, unknown>;\n }\n } catch (error) {\n const format = isJson ? 'JSON' : 'YAML';\n throw new Error(`Failed to parse ${format} config file: ${(error as Error).message}`, {\n cause: error,\n });\n }\n\n if (\n !parsed['intentId'] ||\n typeof parsed['intentId'] !== 'string' ||\n parsed['intentId'].trim() === ''\n ) {\n throw new Error(\n `Configuration file is missing required field 'intentId'. ` +\n `Add 'intentId: \"<your-cluster-identity>\"' to ${filePath}. ` +\n `The intentId uniquely identifies the cluster for integrity tracking.`\n );\n }\n\n return {\n template: parsed['template'] as string,\n inputs: (parsed['inputs'] as Record<string, unknown>) ?? {},\n intentId: parsed['intentId'],\n platformPrefix: parsed['platformPrefix'] as string | undefined,\n options: parsed['options'] as RawConfig['options'],\n };\n}\n\nexport { fileExists } from './fs-helpers.js';\n","/**\n * Shared filesystem helper utilities.\n */\n\nimport { access } from 'node:fs/promises';\nimport { join } from 'node:path';\n\n/**\n * Check if a file exists on disk.\n */\nexport async function fileExists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Find the default fixture path for a template version.\n * Checks tests/default/inputs.yaml first, then tests/minimal/inputs.yaml.\n * Returns null if neither exists.\n */\nexport async function findDefaultFixture(versionDir: string): Promise<string | null> {\n const candidates = [\n join(versionDir, 'tests', 'default', 'inputs.yaml'),\n join(versionDir, 'tests', 'minimal', 'inputs.yaml'),\n ];\n for (const candidate of candidates) {\n if (await fileExists(candidate)) {\n return candidate;\n }\n }\n return null;\n}\n","/**\n * Manifest loading and raw config reconstruction utility.\n *\n * CHANGE (006): envelopeFromManifest now returns RawConfig (with template as\n * URI string) instead of Envelope. The CLI command resolves the URI.\n *\n * Reference: data-model.md §11, research.md R-008\n */\n\nimport { readFile } from 'node:fs/promises';\nimport type { Manifest } from '@truefoundry/tfy-infra-engine';\nimport type { RawConfig } from './config-loader.js';\n\n/**\n * Load and validate a manifest from disk.\n */\nexport async function loadManifest(manifestPath: string): Promise<Manifest> {\n let content: string;\n try {\n content = await readFile(manifestPath, 'utf-8');\n } catch (error) {\n const nodeError = error as NodeJS.ErrnoException;\n if (nodeError.code === 'ENOENT') {\n throw new Error(`Manifest not found: ${manifestPath}`, { cause: error });\n }\n throw new Error(`Failed to read manifest: ${(error as Error).message}`, { cause: error });\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(content) as Record<string, unknown>;\n } catch {\n throw new Error(`Invalid JSON in manifest: ${manifestPath}`);\n }\n\n const requiredFields = [\n 'manifestVersion',\n 'files',\n 'aggregateHash',\n 'inputs',\n 'platformPrefix',\n 'intentId',\n 'templateSource',\n 'templateVersion',\n ] as const;\n\n for (const field of requiredFields) {\n if (parsed[field] === undefined) {\n throw new Error(`Manifest is missing required field: ${field}`);\n }\n }\n\n return parsed as unknown as Manifest;\n}\n\n/**\n * Reconstruct a RawConfig from a manifest for re-render.\n *\n * CHANGE (006): Returns RawConfig with template URI string.\n */\nexport function envelopeFromManifest(manifest: Manifest): RawConfig {\n if (!manifest.inputs) {\n throw new Error('Cannot reconstruct envelope: manifest is missing inputs field');\n }\n if (!manifest.platformPrefix) {\n throw new Error('Cannot reconstruct envelope: manifest is missing platformPrefix field');\n }\n\n return {\n template: manifest.templateSource,\n inputs: manifest.inputs,\n intentId: manifest.intentId,\n platformPrefix: manifest.platformPrefix,\n };\n}\n","/**\n * Disk-to-FileMap reader utility.\n *\n * Reads a cluster directory into an in-memory FileMap for verify and\n * upgrade operations. Classifies files by zone and parses @tfy-status\n * headers for platform files.\n *\n * Reference: data-model.md §9, research.md R-012\n */\n\nimport { readFile, readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport {\n classifyFile,\n parseHeader,\n DEFAULT_PREFIX,\n type FileMap,\n type FileEntry,\n} from '@truefoundry/tfy-infra-engine';\n\n/**\n * Read all files from a cluster directory into a FileMap.\n *\n * Scans the directory, classifies each file by zone, parses @tfy-status\n * headers for platform files, and returns a typed FileMap.\n *\n * Does NOT include manifest.json in the output (callers load it separately).\n *\n * @param dir - Path to cluster directory\n * @param prefix - Platform Zone filename prefix (default: \"tfy_\")\n * @returns FileMap with zone-tagged entries and parsed headers\n */\nexport async function readDirectoryToFileMap(\n dir: string,\n prefix: string = DEFAULT_PREFIX\n): Promise<FileMap> {\n const fileMap: FileMap = new Map();\n\n let entries: string[];\n try {\n const dirEntries = await readdir(dir, { withFileTypes: true });\n entries = dirEntries.filter((e) => e.isFile()).map((e) => e.name);\n } catch (error) {\n const nodeError = error as NodeJS.ErrnoException;\n if (nodeError.code === 'ENOENT') {\n throw new Error(`Directory not found: ${dir}`, { cause: error });\n }\n throw new Error(`Failed to read directory: ${(error as Error).message}`, { cause: error });\n }\n\n for (const filename of entries) {\n // Skip manifest.json — callers load it separately\n if (filename === 'manifest.json') continue;\n\n const content = await readFile(join(dir, filename), 'utf-8');\n const zone = classifyFile(filename, prefix);\n\n const entry: FileEntry = { content, zone };\n\n if (zone === 'platform') {\n const header = parseHeader(content);\n if (header) {\n entry.header = header;\n }\n }\n\n fileMap.set(filename, entry);\n }\n\n return fileMap;\n}\n","/**\n * Engine output-to-disk writer utility.\n *\n * Writes engine results to disk with zone-aware policies.\n *\n * CHANGE (008): Removed sideOutputs handling — no more expose_variable pipeline.\n *\n * Reference: data-model.md §10, spec.md FR-012a\n */\n\nimport { writeFile, mkdir } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport type { InstallResult, UpgradeResult } from '@truefoundry/tfy-infra-engine';\nimport { fileExists } from './fs-helpers.js';\n\n/**\n * Write engine output to disk with zone-aware policies.\n *\n * Modes:\n * - 'install': Overwrite all files\n * - 'upgrade': Overwrite platform zone files, skip existing user zone files,\n * create new user zone files\n *\n * @param dir - Target directory\n * @param result - InstallResult or UpgradeResult from engine\n * @param mode - 'install' (overwrite all) or 'upgrade' (zone-aware)\n */\nexport async function writeEngineOutput(\n dir: string,\n result: InstallResult | UpgradeResult,\n mode: 'install' | 'upgrade'\n): Promise<void> {\n await mkdir(dir, { recursive: true });\n\n for (const [filePath, entry] of result.files) {\n const fullPath = join(dir, filePath);\n\n if (mode === 'upgrade' && entry.zone === 'user') {\n if (await fileExists(fullPath)) {\n continue;\n }\n }\n\n await mkdir(dirname(fullPath), { recursive: true });\n await writeFile(fullPath, entry.content, 'utf-8');\n }\n}\n","/**\n * Unified diff generation utility\n * Generates unified diff output for test failures\n * @file packages/infra-cli/src/utils/diff.ts\n */\n\nimport { createTwoFilesPatch } from 'diff';\n\n/**\n * Generate a unified diff between expected and actual content.\n *\n * @param expected - Expected file content\n * @param actual - Actual file content\n * @param filename - Name of the file being compared\n * @returns Unified diff string, or empty string if contents match\n */\nexport function generateUnifiedDiff(expected: string, actual: string, filename: string): string {\n const normalizedExpected = expected.replace(/\\r\\n/g, '\\n');\n const normalizedActual = actual.replace(/\\r\\n/g, '\\n');\n\n const patch = createTwoFilesPatch(\n `expected/${filename}`,\n `actual/${filename}`,\n normalizedExpected,\n normalizedActual,\n '', // oldHeader\n '', // newHeader\n { context: 3 }\n );\n\n return patch;\n}\n\n/**\n * Diff entry for a single file\n */\nexport interface DiffEntry {\n filename: string;\n diff: string;\n}\n\n/**\n * Format multiple diffs for display output.\n *\n * @param diffs - Array of diff entries with filenames\n * @returns Formatted string with all diffs\n */\nexport function formatDiffOutput(diffs: DiffEntry[]): string {\n if (diffs.length === 0) {\n return '';\n }\n\n return diffs\n .map((entry) => {\n return ` ${entry.filename}: content mismatch\\n${entry.diff}`;\n })\n .join('\\n');\n}\n","/**\n * tfy-init tpl validate command\n *\n * Validates a bundle.json template package.\n * Checks build/bundle.json first (new convention), then bundle.json at root (legacy).\n */\n\nimport { Command } from 'commander';\nimport { readFile, stat } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport Handlebars from 'handlebars';\nimport { validateJsonSchemaStructure, isTofuAvailable } from '@truefoundry/tfy-infra-engine';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\n\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\nexport function createValidateCommand(): Command {\n const cmd = new Command('validate')\n .description('Validate a template (for template authors)')\n .option('-d, --directory <dir>', 'Path to local template directory', '.')\n .option('--strict', 'Fail on warnings')\n .option('--skip-format', 'Skip tofu fmt validation')\n .action(async (options: ValidateOptions) => {\n try {\n const exitCode = await runValidate(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\ninterface ValidateOptions {\n directory: string;\n strict?: boolean;\n skipFormat?: boolean;\n}\n\ninterface ValidationIssue {\n type: 'error' | 'warning';\n message: string;\n file?: string;\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function runValidate(options: ValidateOptions): Promise<number> {\n const templateDir = resolve(options.directory);\n const issues: ValidationIssue[] = [];\n let hasErrors = false;\n\n output.info(`Validating template at: ${templateDir}`);\n output.info('');\n\n const buildBundlePath = join(templateDir, 'build', 'bundle.json');\n const legacyBundlePath = join(templateDir, 'bundle.json');\n const bundlePath = (await fileExists(buildBundlePath)) ? buildBundlePath : legacyBundlePath;\n\n if (!(await fileExists(bundlePath))) {\n output.error('bundle.json not found in template directory or build/ subdirectory');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n output.verbose('Checking bundle.json...');\n try {\n const content = await readFile(bundlePath, 'utf-8');\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(content) as Record<string, unknown>;\n } catch (error) {\n throw new Error(`Invalid JSON: ${(error as Error).message}`, { cause: error });\n }\n\n const rawMeta = parsed['metadata'] as Record<string, unknown> | undefined;\n if (!rawMeta || typeof rawMeta !== 'object') {\n throw new Error('bundle.json must contain a \"metadata\" object');\n }\n if (!rawMeta['name'] || !rawMeta['version']) {\n throw new Error('bundle.json metadata must contain \"name\" and \"version\"');\n }\n\n const jsonSchema = parsed['jsonSchema'] as Record<string, unknown> | undefined;\n if (!jsonSchema || typeof jsonSchema !== 'object') {\n throw new Error('bundle.json must contain a \"jsonSchema\" object');\n }\n\n validateJsonSchemaStructure(jsonSchema);\n output.success('bundle.json schema is valid');\n\n const files = parsed['files'] as Record<string, string> | undefined;\n const staticFiles = parsed['staticFiles'] as Record<string, string> | undefined;\n const fileCount = Object.keys(files ?? {}).length;\n const staticCount = Object.keys(staticFiles ?? {}).length;\n\n if (fileCount === 0 && staticCount === 0) {\n issues.push({ type: 'error', message: 'Bundle contains no template files' });\n hasErrors = true;\n } else {\n for (const [relPath, content] of Object.entries(files ?? {})) {\n try {\n Handlebars.precompile(content);\n output.verbose(` ✓ ${relPath}`);\n } catch (error) {\n issues.push({\n type: 'error',\n message: `Invalid Handlebars syntax: ${(error as Error).message}`,\n file: relPath,\n });\n hasErrors = true;\n }\n }\n output.success(\n `Found ${fileCount} HBS template(s)` +\n (staticCount > 0 ? ` and ${staticCount} static file(s)` : '') +\n ' with valid syntax'\n );\n }\n } catch (error) {\n issues.push({\n type: 'error',\n message: (error as Error).message,\n file: 'bundle.json',\n });\n hasErrors = true;\n }\n\n if (!options.skipFormat) {\n output.verbose('Checking tofu fmt availability...');\n const tofuAvailable = await isTofuAvailable();\n if (tofuAvailable) {\n output.success('tofu fmt available');\n } else {\n issues.push({ type: 'warning', message: 'tofu not found - format validation skipped' });\n }\n }\n\n output.info('');\n\n const warnings = issues.filter((i) => i.type === 'warning');\n const errors = issues.filter((i) => i.type === 'error');\n\n if (warnings.length > 0) {\n output.warn(`${warnings.length} warning(s):`);\n for (const issue of warnings) {\n const prefix = issue.file ? `${issue.file}: ` : '';\n output.info(` • ${prefix}${issue.message}`);\n }\n output.info('');\n }\n\n if (errors.length > 0) {\n output.error(`${errors.length} error(s):`);\n for (const issue of errors) {\n const prefix = issue.file ? `${issue.file}: ` : '';\n output.info(` • ${prefix}${issue.message}`);\n }\n output.info('');\n }\n\n if (hasErrors) {\n output.error('Template validation failed');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n if (options.strict && warnings.length > 0) {\n output.error('Template validation failed (strict mode)');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n output.success('Template is valid');\n return EXIT_CODES.SUCCESS;\n}\n","/**\n * tfy-init tpl test command\n *\n * CHANGE (006): Fetch-first — resolves template via ResolverRegistry\n * before calling engine.install(). Envelope uses Template object.\n */\n\nimport { Command } from 'commander';\nimport { readFile, readdir, writeFile, mkdir } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport * as yaml from 'js-yaml';\nimport { createEngine, type Envelope, type InstallResult } from '@truefoundry/tfy-infra-engine';\nimport { createResolverRegistry } from '../../resolvers/index.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { generateUnifiedDiff } from '../../utils/diff.js';\n\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\nexport function createTestCommand(): Command {\n const cmd = new Command('test')\n .description('Run test fixtures against a template')\n .option('-d, --directory <dir>', 'Path to local template directory', '.')\n .option('--fixture <name>', 'Run specific fixture only')\n .option('--update', 'Update expected outputs with actual results')\n .option('--diff', 'Show unified diff on failures')\n .option('--skip-format', 'Skip tofu fmt in tests')\n .action(async (options: TestOptions) => {\n try {\n const exitCode = await runTest(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\ninterface TestOptions {\n directory: string;\n fixture?: string;\n update?: boolean;\n diff?: boolean;\n skipFormat?: boolean;\n}\n\ninterface TestResult {\n name: string;\n passed: boolean;\n errors?: string[];\n diffs?: Array<{ filename: string; diff: string }>;\n}\n\nasync function runTest(options: TestOptions): Promise<number> {\n const templateDir = resolve(options.directory);\n const testsDir = join(templateDir, 'tests');\n const templateUri = `file://${templateDir}`;\n\n output.info(`Testing template at: ${templateDir}`);\n output.info('');\n\n let fixtures: string[];\n try {\n const entries = await readdir(testsDir);\n fixtures = [];\n for (const entry of entries) {\n try {\n const inputsPath = join(testsDir, entry, 'inputs.yaml');\n await readFile(inputsPath);\n fixtures.push(entry);\n } catch {\n // Not a valid fixture directory\n }\n }\n } catch {\n output.error(`No tests directory found at ${testsDir}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n if (fixtures.length === 0) {\n output.error('No test fixtures found');\n output.info('');\n output.info('Expected structure:');\n output.info(' tests/');\n output.info(' <fixture-name>/');\n output.info(' inputs.yaml');\n output.info(' expected/');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n if (options.fixture) {\n if (!fixtures.includes(options.fixture)) {\n output.error(`Fixture '${options.fixture}' not found`);\n output.info(`Available fixtures: ${fixtures.join(', ')}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n fixtures = [options.fixture];\n }\n\n output.info(`Running ${fixtures.length} fixture(s)...`);\n output.info('');\n\n // Fetch-first: resolve template once for all fixtures\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(templateUri, { noCache: true });\n\n const engine = createEngine();\n const results: TestResult[] = [];\n\n for (const fixture of fixtures) {\n const fixtureDir = join(testsDir, fixture);\n const inputsPath = join(fixtureDir, 'inputs.yaml');\n const expectedDir = join(fixtureDir, 'expected');\n\n output.verbose(`Running fixture: ${fixture}`);\n\n try {\n const inputsContent = await readFile(inputsPath, 'utf-8');\n const inputs = yaml.load(inputsContent) as Record<string, unknown>;\n\n const envelope: Envelope = {\n template,\n inputs,\n intentId: `test-${fixture}`,\n options: {\n skipFormat: options.skipFormat,\n },\n };\n\n const result = await engine.install(envelope);\n\n if (options.update) {\n await updateExpected(expectedDir, result);\n output.success(`${fixture}: updated`);\n results.push({ name: fixture, passed: true });\n } else {\n const { errors, diffs } = await compareOutputs(expectedDir, result, options.diff);\n if (errors.length === 0) {\n output.success(`${fixture}: passed`);\n results.push({ name: fixture, passed: true });\n } else {\n output.error(`${fixture}: failed`);\n for (const err of errors) {\n output.info(` ${err}`);\n }\n if (options.diff && diffs && diffs.length > 0) {\n for (const d of diffs) {\n output.info(` --- expected/${d.filename}`);\n output.info(` +++ actual/${d.filename}`);\n const diffLines = d.diff.split('\\n').slice(4);\n for (const line of diffLines) {\n if (line) {\n output.info(` ${line}`);\n }\n }\n }\n }\n results.push({ name: fixture, passed: false, errors, diffs });\n }\n }\n } catch (error) {\n output.error(`${fixture}: error`);\n output.info(` ${(error as Error).message}`);\n results.push({ name: fixture, passed: false, errors: [(error as Error).message] });\n }\n }\n\n output.info('');\n const passed = results.filter((r) => r.passed).length;\n const failed = results.filter((r) => !r.passed).length;\n\n if (failed === 0) {\n output.success(`All ${passed} fixture(s) passed`);\n return EXIT_CODES.SUCCESS;\n } else {\n output.error(`${failed} fixture(s) failed, ${passed} passed`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n}\n\n/**\n * Replaces machine-specific `file://` paths in @tfy-status source fields\n * with a stable placeholder so snapshots are portable across environments.\n */\nfunction normalizeStatusSource(content: string): string {\n return content.replace(/(\"source\"\\s*:\\s*)\"file:\\/\\/[^\"]*\"/g, '$1\"<local>\"');\n}\n\nasync function updateExpected(expectedDir: string, result: InstallResult): Promise<void> {\n await mkdir(expectedDir, { recursive: true });\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const outputPath = join(expectedDir, filePath);\n await writeFile(outputPath, normalizeStatusSource(entry.content), 'utf-8');\n }\n}\n\ninterface CompareResult {\n errors: string[];\n diffs?: Array<{ filename: string; diff: string }>;\n}\n\nasync function compareOutputs(\n expectedDir: string,\n result: InstallResult,\n includeDiffs?: boolean\n): Promise<CompareResult> {\n const errors: string[] = [];\n const diffs: Array<{ filename: string; diff: string }> = [];\n\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const actual = entry.content;\n\n const expectedPath = join(expectedDir, filePath);\n try {\n const expected = await readFile(expectedPath, 'utf-8');\n const normalizedActual = normalizeStatusSource(actual.trim().replace(/\\r\\n/g, '\\n'));\n const normalizedExpected = normalizeStatusSource(expected.trim().replace(/\\r\\n/g, '\\n'));\n\n if (normalizedActual !== normalizedExpected) {\n errors.push(`${filePath}: content mismatch`);\n if (includeDiffs) {\n const diff = generateUnifiedDiff(normalizedExpected, normalizedActual, filePath);\n diffs.push({ filename: filePath, diff });\n }\n }\n } catch {\n errors.push(`${filePath}: expected file not found`);\n }\n }\n\n try {\n const expectedFiles = await readdir(expectedDir);\n for (const file of expectedFiles) {\n if (!result.files.has(file) && file !== 'manifest.json') {\n errors.push(`${file}: unexpected file in expected/`);\n }\n }\n } catch {\n // Expected directory might not exist\n }\n\n return { errors, diffs: includeDiffs ? diffs : undefined };\n}\n","/**\n * tfy-init tpl dev command\n * Watch mode for rapid template iteration.\n *\n * Supports two modes:\n * 1. Schema mode (--schema + --src): reads a pre-computed schema package\n * and a src/ directory. Watches both for changes.\n * 2. Directory mode (--directory): reads bundle.json from a template directory.\n */\n\nimport { Command } from 'commander';\nimport { readFile, mkdir, rm, writeFile, access } from 'node:fs/promises';\nimport { dirname, join, relative, resolve } from 'node:path';\nimport * as yaml from 'js-yaml';\nimport {\n createEngine,\n EngineError,\n EngineErrorCode,\n type Envelope,\n type InstallResult,\n type JSONSchema7,\n type Template,\n type TemplateMetadata,\n} from '@truefoundry/tfy-infra-engine';\nimport { createResolverRegistry } from '../../resolvers/index.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { findDefaultFixture } from '../../utils/fs-helpers.js';\nimport { walkSrcDir } from '../../utils/walk-src.js';\nimport { createWatcher, type WatchEvent } from '../../utils/file-watcher.js';\nimport { generateUnifiedDiff } from '../../utils/diff.js';\n\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\nexport interface DevOptions {\n directory: string;\n schema?: string;\n src?: string;\n name?: string;\n version?: string;\n fixture?: string;\n output: string;\n test?: boolean;\n}\n\nexport function createDevCommand(): Command {\n const cmd = new Command('dev')\n .description('Watch mode for rapid template iteration')\n .option('-d, --directory <dir>', 'Path to template version directory')\n .option(\n '--schema <path>',\n 'Path to schema package JSON (from tfy-schema-tool --schema-package)'\n )\n .option('--src <dir>', 'Path to template src/ directory')\n .option('--name <string>', 'Template name (used with --schema mode)')\n .option('--version <semver>', 'Template version (used with --schema mode)')\n .option('-f, --fixture <path>', 'Input file or fixture to use for testing')\n .requiredOption('-o, --output <dir>', 'Output directory for rendered files')\n .option('--no-test', 'Only validate, do not run tests')\n .action(async (options: DevOptions) => {\n try {\n const exitCode = await runDev(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\nexport { findDefaultFixture } from '../../utils/fs-helpers.js';\n\nexport async function validateTemplateDir(\n versionDir: string\n): Promise<{ valid: boolean; error?: string }> {\n try {\n await access(versionDir);\n } catch {\n return { valid: false, error: `Directory not found: ${versionDir}` };\n }\n\n const buildBundlePath = join(versionDir, 'build', 'bundle.json');\n try {\n await access(buildBundlePath);\n return { valid: true };\n } catch {\n // fall through to legacy path\n }\n\n const bundleJsonPath = join(versionDir, 'bundle.json');\n try {\n await access(bundleJsonPath);\n return { valid: true };\n } catch {\n return { valid: false, error: `bundle.json not found in ${versionDir} or ${versionDir}/build` };\n }\n}\n\nexport async function ensureOutputDir(_versionDir: string, customOutput: string): Promise<string> {\n const outputDir = resolve(customOutput);\n\n try {\n await rm(outputDir, { recursive: true, force: true });\n } catch {\n // Directory might not exist\n }\n\n await mkdir(outputDir, { recursive: true });\n return outputDir;\n}\n\nfunction isSchemaMode(options: DevOptions): boolean {\n return !!(options.schema && options.src);\n}\n\nasync function runDev(options: DevOptions): Promise<number> {\n if (isSchemaMode(options)) {\n return runSchemaModeDev(options);\n }\n return runDirectoryModeDev(options);\n}\n\n/**\n * Schema mode: --schema + --src.\n * Reads schema package from file, walks src/, constructs Template in-memory.\n */\nasync function runSchemaModeDev(options: DevOptions): Promise<number> {\n const schemaPath = resolve(options.schema!);\n const srcDir = resolve(options.src!);\n const templateName = options.name ?? 'dev-template';\n const templateVersion = options.version;\n\n let fixturePath: string;\n if (options.fixture) {\n fixturePath = resolve(options.fixture);\n try {\n await access(fixturePath);\n } catch {\n output.error(`Fixture not found: ${options.fixture}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n } else {\n output.error('--fixture is required in schema mode');\n output.info('Use -f to specify an inputs.yaml file.');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n const outputDir = await ensureOutputDir('.', options.output);\n const relativeOutputDir = relative(process.cwd(), outputDir);\n\n output.info(`[schema] ${relative(process.cwd(), schemaPath)}`);\n output.info(`[src] ${relative(process.cwd(), srcDir)}`);\n output.info(`[output] ${relativeOutputDir}/`);\n output.info(`[fixture] ${relative(process.cwd(), fixturePath)}`);\n output.info('');\n\n const engine = createEngine();\n\n const renderFromSchema = async () => {\n try {\n const template = await buildTemplateFromSchema(\n schemaPath,\n srcDir,\n templateName,\n templateVersion\n );\n await renderWithTemplate(engine, template, fixturePath, outputDir, options.test);\n } catch (error) {\n output.error(`[render] ✗ Render failed`);\n output.info(` ${(error as Error).message}`);\n }\n };\n\n await renderFromSchema();\n\n const watchPaths = [schemaPath, srcDir, fixturePath];\n\n const watcher = createWatcher(watchPaths, {\n schemaFile: schemaPath,\n onChange: (event: WatchEvent) => {\n void (async () => {\n output.info(`[changed] ${relative(process.cwd(), event.path)}`);\n await renderFromSchema();\n })();\n },\n onReady: () => {\n output.info('[ready] Watching for changes... (Ctrl+C to exit)');\n output.info('');\n },\n onError: (error) => {\n output.error(`Watch error: ${error.message}`);\n },\n });\n\n return new Promise<number>((resolvePromise) => {\n const cleanup = () => {\n output.info('');\n output.info('[stopped] Watch mode ended');\n void watcher.close();\n resolvePromise(EXIT_CODES.SUCCESS);\n };\n\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n });\n}\n\nfunction extractVersionFromSchema(jsonSchema: JSONSchema7): string | undefined {\n const props = jsonSchema.properties;\n if (!props) return undefined;\n const versionProp = props['version'];\n if (!versionProp || typeof versionProp === 'boolean') return undefined;\n const def = versionProp.default;\n return typeof def === 'string' ? def : undefined;\n}\n\nasync function buildTemplateFromSchema(\n schemaPath: string,\n srcDir: string,\n name: string,\n version?: string\n): Promise<Template> {\n const schemaContent = await readFile(schemaPath, 'utf-8');\n const schemaPkg = JSON.parse(schemaContent) as Record<string, unknown>;\n\n const jsonSchema = schemaPkg['jsonSchema'] as JSONSchema7;\n if (!jsonSchema || typeof jsonSchema !== 'object') {\n throw new Error('Schema package must contain a \"jsonSchema\" object');\n }\n\n const resolvedVersion = version ?? extractVersionFromSchema(jsonSchema) ?? '0.0.0-dev';\n\n const { files, staticFiles } = await walkSrcDir(srcDir);\n\n const metadata: TemplateMetadata = { name, version: resolvedVersion };\n\n return {\n metadata,\n jsonSchema,\n files,\n staticFiles,\n source: `file://${srcDir}`,\n version: { semver: resolvedVersion },\n };\n}\n\nasync function renderWithTemplate(\n engine: ReturnType<typeof createEngine>,\n template: Template,\n fixturePath: string,\n outputDir: string,\n runTests?: boolean\n): Promise<void> {\n const inputsContent = await readFile(fixturePath, 'utf-8');\n const inputs = yaml.load(inputsContent) as Record<string, unknown>;\n\n const envelope: Envelope = {\n template,\n inputs,\n intentId: `dev-${Date.now()}`,\n options: { skipFormat: false },\n };\n\n const result = await engine.install(envelope);\n\n const templateFileCount = Array.from(result.files.keys()).filter(\n (f) => f !== 'manifest.json'\n ).length;\n output.info(`[validate] ✓ ${templateFileCount} template files valid`);\n\n await rm(outputDir, { recursive: true, force: true });\n await mkdir(outputDir, { recursive: true });\n\n let filesWritten = 0;\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const outputPath = join(outputDir, filePath);\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, entry.content, 'utf-8');\n filesWritten++;\n }\n output.info(\n `[render] ✓ ${filesWritten} files written to ${relative(process.cwd(), outputDir)}/`\n );\n\n if (runTests !== false) {\n await runTestComparisonFromResult(result, outputDir);\n }\n}\n\n/**\n * Directory mode: --directory (legacy, uses FileResolver).\n */\nasync function runDirectoryModeDev(options: DevOptions): Promise<number> {\n const versionDir = resolve(options.directory || '.');\n\n const validation = await validateTemplateDir(versionDir);\n if (!validation.valid) {\n output.error(validation.error!);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n\n let fixturePath: string;\n if (options.fixture) {\n fixturePath = resolve(versionDir, options.fixture);\n try {\n await access(fixturePath);\n } catch {\n output.error(`Fixture not found: ${options.fixture}`);\n return EXIT_CODES.VALIDATION_ERROR;\n }\n } else {\n const defaultFixture = await findDefaultFixture(versionDir);\n if (!defaultFixture) {\n output.error('No fixture found under tests/ (checked tests/default/ and tests/minimal/)');\n output.info('Either create a fixture or use --fixture to specify one.');\n return EXIT_CODES.VALIDATION_ERROR;\n }\n fixturePath = defaultFixture;\n }\n\n const outputDir = await ensureOutputDir(versionDir, options.output);\n const relativeOutputDir = relative(process.cwd(), outputDir);\n\n output.info(`[watching] ${relative(process.cwd(), versionDir)}`);\n output.info(`[output] ${relativeOutputDir}/`);\n output.info(`[fixture] ${relative(process.cwd(), fixturePath)}`);\n output.info('');\n\n const engine = createEngine();\n const templateUri = `file://${versionDir}`;\n\n await runRenderCycle(engine, templateUri, versionDir, fixturePath, outputDir, options.test);\n\n const watcher = createWatcher(versionDir, {\n onChange: (event: WatchEvent) => {\n void (async () => {\n const relPath = relative(versionDir, event.path);\n output.info(`[changed] ${relPath}`);\n\n if (event.fileType === 'schema') {\n await runValidation(templateUri);\n } else if (event.fileType === 'template' || event.fileType === 'inputs') {\n await runRenderCycle(\n engine,\n templateUri,\n versionDir,\n fixturePath,\n outputDir,\n options.test\n );\n }\n })();\n },\n onReady: () => {\n output.info('[ready] Watching for changes... (Ctrl+C to exit)');\n output.info('');\n },\n onError: (error) => {\n output.error(`Watch error: ${error.message}`);\n },\n });\n\n return new Promise<number>((resolvePromise) => {\n const cleanup = () => {\n output.info('');\n output.info('[stopped] Watch mode ended');\n void watcher.close();\n resolvePromise(EXIT_CODES.SUCCESS);\n };\n\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n });\n}\n\nasync function runValidation(templateUri: string): Promise<boolean> {\n try {\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(templateUri, { noCache: true });\n if (template.jsonSchema) {\n output.info('[validate] ✓ schema valid');\n return true;\n }\n output.error('[validate] ✗ schema invalid');\n return false;\n } catch (error) {\n output.error(`[validate] ✗ schema invalid`);\n output.info(` ${(error as Error).message}`);\n return false;\n }\n}\n\nasync function runRenderCycle(\n engine: ReturnType<typeof createEngine>,\n templateUri: string,\n versionDir: string,\n fixturePath: string,\n outputDir: string,\n runTests?: boolean\n): Promise<void> {\n const valid = await runValidation(templateUri);\n if (!valid) return;\n\n try {\n const inputsContent = await readFile(fixturePath, 'utf-8');\n const inputs = yaml.load(inputsContent) as Record<string, unknown>;\n\n const resolverRegistry = createResolverRegistry();\n const template = await resolverRegistry.resolve(templateUri, { noCache: true });\n\n const envelope: Envelope = {\n template,\n inputs,\n intentId: `dev-${Date.now()}`,\n options: { skipFormat: false },\n };\n\n const result = await engine.install(envelope);\n\n const templateFileCount = Array.from(result.files.keys()).filter(\n (f) => f !== 'manifest.json'\n ).length;\n output.info(`[validate] ✓ ${templateFileCount} template files valid`);\n\n await rm(outputDir, { recursive: true, force: true });\n await mkdir(outputDir, { recursive: true });\n\n let filesWritten = 0;\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const outputPath = join(outputDir, filePath);\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, entry.content, 'utf-8');\n filesWritten++;\n }\n output.info(\n `[render] ✓ ${filesWritten} files written to ${relative(process.cwd(), outputDir)}/`\n );\n\n if (runTests !== false) {\n await runTestComparison(versionDir, result, outputDir);\n }\n } catch (error) {\n output.error(`[render] ✗ Render failed`);\n output.info(` ${(error as Error).message}`);\n if (\n error instanceof EngineError &&\n error.code === EngineErrorCode.INPUT_VALIDATION_FAILED &&\n error.details?.['errors']\n ) {\n output.info(\n output.formatValidationErrors(\n error.details['errors'] as Array<{ path: string; message: string }>\n )\n );\n }\n }\n}\n\nasync function runTestComparison(\n versionDir: string,\n result: InstallResult,\n _outputDir: string\n): Promise<void> {\n const expectedDir = join(versionDir, 'tests', 'default', 'expected');\n await runTestComparisonFromDir(result, expectedDir);\n}\n\nasync function runTestComparisonFromResult(\n _result: InstallResult,\n _outputDir: string\n): Promise<void> {\n // In schema mode, we don't have a versionDir to find expected/ in.\n // Test comparison is skipped unless running from directory mode.\n}\n\nasync function runTestComparisonFromDir(result: InstallResult, expectedDir: string): Promise<void> {\n const startTime = Date.now();\n\n try {\n await access(expectedDir);\n } catch {\n output.info('[test] ⚠ No expected/ directory, skipping comparison');\n return;\n }\n\n const errors: Array<{ file: string; diff?: string }> = [];\n\n for (const [filePath, entry] of result.files) {\n if (filePath === 'manifest.json') continue;\n const actual = entry.content;\n\n const expectedPath = join(expectedDir, filePath);\n try {\n const expected = await readFile(expectedPath, 'utf-8');\n const normalizedActual = actual.trim().replace(/\\r\\n/g, '\\n');\n const normalizedExpected = expected.trim().replace(/\\r\\n/g, '\\n');\n\n if (normalizedActual !== normalizedExpected) {\n const diff = generateUnifiedDiff(normalizedExpected, normalizedActual, filePath);\n errors.push({ file: filePath, diff });\n }\n } catch {\n errors.push({ file: filePath });\n }\n }\n\n const duration = Date.now() - startTime;\n\n if (errors.length === 0) {\n output.info(`[test] ✓ default: passed (${duration}ms)`);\n } else {\n output.error(`[test] ✗ default: failed`);\n for (const err of errors) {\n output.info(` ${err.file}: content mismatch`);\n if (err.diff) {\n const diffLines = err.diff.split('\\n').slice(4, 14);\n for (const line of diffLines) {\n if (line) {\n output.info(` ${line}`);\n }\n }\n if (err.diff.split('\\n').length > 14) {\n output.info(` ... (diff truncated)`);\n }\n }\n }\n }\n}\n","/**\n * File watcher utility\n * Watches template directories for changes and emits events\n * @file packages/infra-cli/src/utils/file-watcher.ts\n */\n\nimport chokidar, { type FSWatcher } from 'chokidar';\nimport { basename } from 'node:path';\n\n/**\n * Type of file change that occurred\n */\nexport type FileType = 'schema' | 'template' | 'inputs' | 'other';\n\n/**\n * Event emitted when a file changes\n */\nexport interface WatchEvent {\n type: 'change' | 'add' | 'unlink';\n path: string;\n fileType: FileType;\n}\n\n/**\n * Options for creating a file watcher\n */\nexport interface WatchOptions {\n /**\n * Callback when a file changes\n */\n onChange?: (event: WatchEvent) => void;\n\n /**\n * Callback when watcher is ready\n */\n onReady?: () => void;\n\n /**\n * Callback when an error occurs\n */\n onError?: (error: Error) => void;\n\n /**\n * Patterns to ignore\n */\n ignored?: string | RegExp | ((path: string) => boolean);\n\n /**\n * Debounce delay in milliseconds\n */\n debounceMs?: number;\n\n /**\n * Path to the schema file for classifying changes.\n */\n schemaFile?: string;\n}\n\n/**\n * Classify a file path by its type.\n *\n * @param path - File path to classify\n * @param schemaFile - Optional path to the schema file to match against\n * @returns File type classification\n */\nexport function classifyFileChange(path: string, schemaFile?: string): FileType {\n const normalizedPath = path.replace(/\\\\/g, '/');\n const filename = basename(normalizedPath);\n\n if (schemaFile && normalizedPath.endsWith(schemaFile.replace(/\\\\/g, '/'))) {\n return 'schema';\n }\n\n if (filename === 'bundle.json' || filename === 'schema-pkg.json') {\n return 'schema';\n }\n\n const inSrcDir = /\\/src\\//.test(normalizedPath);\n if (inSrcDir && !filename.startsWith('_')) {\n return 'template';\n }\n\n if (filename === 'inputs.yaml') {\n return 'inputs';\n }\n\n return 'other';\n}\n\n/**\n * Create a file watcher for one or more paths.\n *\n * @param paths - Path(s) to watch (directory or file)\n * @param options - Watcher options\n * @returns FSWatcher instance\n */\nexport function createWatcher(paths: string | string[], options: WatchOptions = {}): FSWatcher {\n const {\n onChange,\n onReady,\n onError,\n ignored = /(^|[/\\\\])\\..|(^|[/\\\\])node_modules($|[/\\\\])|(^|[/\\\\])dev($|[/\\\\])/,\n debounceMs = 100,\n schemaFile,\n } = options;\n\n const watcher = chokidar.watch(paths, {\n ignored,\n persistent: true,\n ignoreInitial: true,\n awaitWriteFinish: {\n stabilityThreshold: debounceMs,\n pollInterval: 50,\n },\n });\n\n const handleEvent = (type: WatchEvent['type']) => (path: string) => {\n const fileType = classifyFileChange(path, schemaFile);\n const event: WatchEvent = { type, path, fileType };\n\n if (onChange) {\n onChange(event);\n }\n };\n\n watcher.on('change', handleEvent('change'));\n watcher.on('add', handleEvent('add'));\n watcher.on('unlink', handleEvent('unlink'));\n\n if (onReady) {\n watcher.on('ready', onReady);\n }\n\n if (onError) {\n watcher.on('error', (err: unknown) =>\n onError(err instanceof Error ? err : new Error(String(err)))\n );\n }\n\n return watcher;\n}\n","/**\n * tfy-init tpl verify command\n *\n * Checks integrity of files on disk against a manifest.json.\n * Wraps engine.verify() to produce human-readable or JSON drift reports.\n *\n */\n\nimport { Command } from 'commander';\nimport { join, resolve } from 'node:path';\nimport { createEngine } from '@truefoundry/tfy-infra-engine';\nimport type { DriftReport } from '@truefoundry/tfy-infra-engine';\nimport { loadManifest } from '../../utils/manifest-loader.js';\nimport { readDirectoryToFileMap } from '../../utils/file-reader.js';\nimport { handleEngineError, EXIT_CODES } from '../../utils/error-handler.js';\nimport * as output from '../../utils/output.js';\nimport { driftTypeTag } from '../../utils/output.js';\n\n// Re-export for backward compatibility\nexport { EXIT_CODES } from '../../utils/error-handler.js';\n\n/** Verify-specific exit code for drift detected (distinct from error). */\nconst DRIFT_DETECTED = 1;\n\ninterface VerifyOptions {\n directory: string;\n json?: boolean;\n}\n\n/**\n * Create the verify command.\n */\nexport function createVerifyCommand(): Command {\n const cmd = new Command('verify')\n .description('Check integrity of files on disk against a manifest')\n .option('-d, --directory <dir>', 'Path to cluster directory containing manifest.json', '.')\n .option('--json', 'Output drift report as structured JSON')\n .action(async (options: VerifyOptions) => {\n try {\n const exitCode = await runVerify(options);\n process.exit(exitCode);\n } catch (error) {\n handleEngineError(error);\n }\n });\n\n return cmd;\n}\n\n/**\n * Execute the verify command.\n * Exported for testing.\n */\nexport async function runVerify(options: VerifyOptions): Promise<number> {\n const dir = resolve(options.directory);\n const manifestPath = join(dir, 'manifest.json');\n\n // Load manifest\n output.verbose(`Loading manifest from ${manifestPath}`);\n const manifest = await loadManifest(manifestPath);\n\n // Read current files on disk\n output.verbose(`Reading files from ${dir}`);\n const currentFiles = await readDirectoryToFileMap(dir, manifest.platformPrefix);\n\n // Create engine and verify\n const engine = createEngine();\n const result = await engine.verify(currentFiles, manifest);\n\n // Output the report\n if (options.json) {\n output.info(formatDriftReportJson(result.driftReport));\n } else {\n output.info(formatDriftReport(result.driftReport));\n }\n\n return result.driftReport.valid ? EXIT_CODES.SUCCESS : DRIFT_DETECTED;\n}\n\n/**\n * Format a drift report as human-readable text.\n * Exported for testing.\n */\nexport function formatDriftReport(report: DriftReport): string {\n const lines: string[] = [];\n const issueCount =\n report.summary.driftedFiles + report.summary.missingFiles + report.summary.unexpectedFiles;\n\n if (report.valid) {\n lines.push(\n `✓ Verification passed: ${report.summary.totalFiles} files checked, ${issueCount} issues`\n );\n lines.push('');\n lines.push(`Aggregate Hash: ${report.aggregateHash.expected}`);\n } else {\n lines.push(`✗ Drift detected: ${issueCount} issues found`);\n lines.push('');\n\n for (const entry of report.entries) {\n const tag = driftTypeTag(entry.type);\n lines.push(` ${tag} ${entry.path}`);\n lines.push(` ${entry.details}`);\n lines.push('');\n }\n\n lines.push(\n `Summary: ${report.summary.totalFiles} files checked, ` +\n `${report.summary.driftedFiles} drifted, ` +\n `${report.summary.missingFiles} missing` +\n (report.summary.unexpectedFiles > 0 ? `, ${report.summary.unexpectedFiles} unexpected` : '')\n );\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format a drift report as structured JSON.\n * Exported for testing.\n */\nexport function formatDriftReportJson(report: DriftReport): string {\n return JSON.stringify(report, null, 2);\n}\n"],"mappings":"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","names":["import_commander","import_commander","import_commander","import_promises","import_node_path","import_promises","import_node_path","walkSrcDir","rootDir","files","staticFiles","walk","relativePath","currentDir","entries","entry","entryRelative","content","outputPath","import_tfy_infra_engine","currentVerbosity","jsonMode","setVerbosity","level","getVerbosity","setJsonMode","enabled","data","message","success","currentVerbosity","jsonMode","error","info","verbose","warn","formatSize","bytes","formatValidationErrors","errors","driftTypeTag","type","printFileList","files","write","file","EXIT_CODES","handleEngineError","error","fallbackExitCode","getVerbosity","info","key","value","formatValidationErrors","createBundleCommand","options","runBundle","error","handleEngineError","readSchemaPackage","schemaPath","raw","readStdin","parsed","err","obj","resolve","reject","chunks","chunk","extractVersionFromSchema","jsonSchema","props","versionProp","def","schemaPkg","version","srcDir","files","staticFiles","walkSrcDir","metadata","bundle","json","outPath","success","import_commander","import_node_readline","import_promises","import_node_path","yaml","import_tfy_infra_engine","import_tfy_infra_engine","import_promises","import_node_path","import_tfy_infra_engine","FileResolver","uri","_options","fsPath","info","buildBundlePath","bundlePath","error","nodeError","content","raw","rawMetadata","metadata","jsonSchema","files","staticFiles","version","path","import_tfy_infra_engine","DEFAULT_RESOLVER_OPTIONS","mergeResolverOptions","options","withRetry","fn","retries","baseDelay","lastError","attempt","error","delay","resolve","withTimeout","promise","ms","message","timer","timeout","_","reject","JFROG_TOKEN_ENV","isArtifactoryHost","uri","hostname","buildHeaders","headers","token","fetchBundle","response","hint","unpackBundle","raw","metadata","jsonSchema","files","staticFiles","version","HttpsBundleResolver","options","opts","mergeResolverOptions","withTimeout","withRetry","import_promises","import_node_path","import_node_crypto","import_tfy_infra_engine","getDefaultCacheDir","home","TemplateCache","cacheDir","source","version","key","safeVersion","cachePath","bundlePath","bundleContent","parsed","metadata","jsonSchema","record","files","staticFiles","versionInfo","template","bundle","error","keyPath","sources","templates","size","sourceKeys","versions","versionPath","dir","total","entries","entry","fullPath","fileStat","ResolverRegistry","resolvers","cacheDir","TemplateCache","FileResolver","HttpsBundleResolver","resolver","uri","options","scheme","template","createResolverRegistry","import_promises","yaml","import_promises","import_node_path","fileExists","filePath","findDefaultFixture","versionDir","candidates","candidate","loadConfig","filePath","content","isJson","parsed","error","format","import_promises","loadManifest","manifestPath","content","error","parsed","requiredFields","field","envelopeFromManifest","manifest","import_promises","import_node_path","import_tfy_infra_engine","readDirectoryToFileMap","dir","prefix","fileMap","entries","e","error","filename","content","zone","entry","header","import_promises","import_node_path","writeEngineOutput","dir","result","mode","filePath","entry","fullPath","fileExists","import_diff","generateUnifiedDiff","expected","actual","filename","normalizedExpected","normalizedActual","validateRenderArgs","args","createRenderCommand","collect","options","exitCode","runRender","error","handleEngineError","value","previous","parseInputOverrides","inputs","result","input","eqIndex","key","setJsonMode","outputDir","timeout","runRenderFromManifest","manifestPath","fileExists","runRenderFromConfigUpgrade","runRenderFromConfig","templateDir","runRenderFromDirectory","path","confirmPrompt","message","rl","resolve","answer","countExistingFiles","dir","e","guardOverwrite","count","warn","data","info","EXIT_CODES","verbose","manifest","loadManifest","rawConfig","envelopeFromManifest","success","envelope","createResolverRegistry","handleInstallDryRun","aborted","writeEngineOutput","printInstallResult","templateUri","fixturePath","findDefaultFixture","fixtureContent","overrides","intentId","engine","configFile","loadConfig","previousManifest","currentFiles","readDirectoryToFileMap","printDriftReport","handleUpgradeDryRun","formatUpgradeResultJson","formatUpgradeResult","computeInstallDiffs","newFiles","diffs","filename","entry","existing","generateUnifiedDiff","computeUpgradeDiffs","currentContent","printDiffs","d","report","json","total","driftTypeTag","fileList","filePath","printFileList","lines","platformCount","userCount","import_commander","import_promises","import_node_path","import_handlebars","import_tfy_infra_engine","createValidateCommand","options","exitCode","runValidate","error","handleEngineError","fileExists","path","templateDir","issues","hasErrors","info","buildBundlePath","legacyBundlePath","bundlePath","EXIT_CODES","verbose","content","parsed","rawMeta","jsonSchema","success","files","staticFiles","fileCount","staticCount","relPath","Handlebars","warnings","i","errors","warn","issue","prefix","import_commander","import_promises","import_node_path","yaml","import_tfy_infra_engine","createTestCommand","options","exitCode","runTest","error","handleEngineError","templateDir","testsDir","templateUri","info","fixtures","entries","entry","inputsPath","EXIT_CODES","template","createResolverRegistry","engine","results","fixture","fixtureDir","expectedDir","verbose","inputsContent","inputs","envelope","result","updateExpected","success","errors","diffs","compareOutputs","err","d","diffLines","line","passed","r","failed","normalizeStatusSource","content","filePath","outputPath","includeDiffs","actual","expectedPath","expected","normalizedActual","normalizedExpected","diff","generateUnifiedDiff","expectedFiles","file","import_commander","import_promises","import_node_path","yaml","import_tfy_infra_engine","import_chokidar","import_node_path","classifyFileChange","path","schemaFile","normalizedPath","filename","createWatcher","paths","options","onChange","onReady","onError","ignored","debounceMs","watcher","chokidar","handleEvent","type","fileType","err","createDevCommand","options","exitCode","runDev","error","handleEngineError","validateTemplateDir","versionDir","buildBundlePath","bundleJsonPath","ensureOutputDir","_versionDir","customOutput","outputDir","isSchemaMode","runSchemaModeDev","runDirectoryModeDev","schemaPath","srcDir","templateName","templateVersion","fixturePath","EXIT_CODES","info","relativeOutputDir","engine","renderFromSchema","template","buildTemplateFromSchema","renderWithTemplate","watcher","createWatcher","event","resolvePromise","cleanup","extractVersionFromSchema","jsonSchema","props","versionProp","def","name","version","schemaContent","resolvedVersion","files","staticFiles","walkSrcDir","runTests","inputsContent","inputs","envelope","result","templateFileCount","f","filesWritten","filePath","entry","outputPath","runTestComparisonFromResult","validation","defaultFixture","findDefaultFixture","templateUri","runRenderCycle","relPath","runValidation","createResolverRegistry","runTestComparison","formatValidationErrors","_outputDir","expectedDir","runTestComparisonFromDir","_result","startTime","errors","actual","expectedPath","expected","normalizedActual","normalizedExpected","diff","generateUnifiedDiff","duration","err","diffLines","line","import_commander","import_node_path","import_tfy_infra_engine","DRIFT_DETECTED","createVerifyCommand","options","exitCode","runVerify","error","handleEngineError","dir","manifestPath","verbose","manifest","loadManifest","currentFiles","readDirectoryToFileMap","result","info","formatDriftReportJson","formatDriftReport","EXIT_CODES","report","lines","issueCount","entry","tag","driftTypeTag","createTplCommand","tpl","createBundleCommand","createDevCommand","createRenderCommand","createValidateCommand","createTestCommand","createVerifyCommand","VERSION","main","program","thisCommand","opts","setVerbosity","createTplCommand","error"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truefoundry/tfy-infra-cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "CLI for TrueFoundry infrastructure templating engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",