@intentius/terragucci 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/terragucci.mjs +2 -2
- package/dist/terragucci.mjs.map +1 -1
- package/package.json +1 -1
package/dist/terragucci.mjs
CHANGED
|
@@ -82,13 +82,13 @@ ${f}:${u.get(f).sort().join(",")}`).join(""),p=[n,...a].join(`
|
|
|
82
82
|
`),p=mn(mn(mn(mn(`AWS4${e.secretAccessKey}`,s.slice(0,8)),e.region),"s3"),"aws4_request");return`${o.origin}${o.pathname}?${c}&X-Amz-Signature=${au("sha256",p).update(l).digest("hex")}`}var mi=class{constructor(t,n=fetch){this.target=t;this.fetchFn=n}target;fetchFn;creds;conditional=!0;get location(){return`s3://${this.target.bucket}`}async signer(){let t=this.target;if(!("webIdentity"in t))return t;let n=()=>this.creds=OR(t.webIdentity,this.fetchFn).catch(a=>{throw this.creds=void 0,a}),r=await(this.creds??n());r.expiration&&r.expiration.getTime()-Date.now()<6e4&&(r=await n());let{accessKeyId:o,secretAccessKey:s,sessionToken:i}=r;return{bucket:t.bucket,...t.endpoint?{endpoint:t.endpoint}:{},region:t.region,accessKeyId:o,secretAccessKey:s,...i?{sessionToken:i}:{}}}async put(t,n,r,o){let s=!o||!this.conditional?{}:"ifMatch"in o?{"if-match":o.ifMatch}:{"if-none-match":"*"},{url:i,headers:a}=ag(await this.signer(),"PUT",t,n,new Date,r,s),c=await this.fetchFn(i,{method:"PUT",headers:a,body:n});if(c.status===501&&Object.keys(s).length)return this.conditional=!1,this.put(t,n,r);if(c.status===412||c.status===409&&Object.keys(s).length)throw new bt(`PUT s3://${this.target.bucket}/${t}: ${c.status}, it changed since it was read`);if(!c.ok)throw new H(`PUT s3://${this.target.bucket}/${t}: ${c.status} ${(await c.text()).slice(0,300)}`);let u=c.headers?.get("etag")??void 0;return u?{etag:u}:{}}async read(t){let{url:n,headers:r}=ag(await this.signer(),"GET",t,""),o=await this.fetchFn(n,{method:"GET",headers:r});if(o.status===404)return{};if(!o.ok)throw new H(`GET s3://${this.target.bucket}/${t}: ${o.status} ${(await o.text()).slice(0,300)}`);let s=o.headers?.get("etag")??void 0;return{body:await o.text(),...s?{etag:s}:{}}}async presign(t,n,r=new Date){let o=await this.signer(),s=jR(o,ug(o,t),n,r),i=new Date(r.getTime()+n*1e3),a=this.creds?await this.creds:void 0;return a?.expiration&&a.expiration<i&&(i=a.expiration),{url:s,expires:i}}async get(t){return(await this.read(t)).body}};function Ut(e,t=process.env,n){let r=_r(e.bucket);if(r.kind!=="s3"&&e.role)throw new H(`reports.role is an AWS role, and ${e.bucket} is not an S3 bucket: the job writes it with its own oidc identity`);let o=e.endpoint?.replace(/\/+$/,"");return r.kind==="gcs"?new gi(ig({bucket:r.bucket,...o?{endpoint:o}:{}},t),n):r.kind==="azure"?new di(eg({account:r.account,container:r.container,...o?{endpoint:o}:{}},t),n):new mi(cg(e,t),n)}import{spawn as m0,spawnSync as iy}from"node:child_process";import{chmodSync as h0,existsSync as Qi,mkdirSync as ny,mkdtempSync as ay,readdirSync as y0,readFileSync as ns,realpathSync as b0,rmSync as $l,statSync as w0,writeFileSync as bl}from"node:fs";import{tmpdir as cy}from"node:os";import{basename as uy,join as ae,resolve as es}from"node:path";import{existsSync as WR,readdirSync as JR,readFileSync as KR}from"node:fs";import{join as pu,posix as Fo,relative as zR,resolve as VR,sep as YR}from"node:path";import{existsSync as IR,readdirSync as NR,readFileSync as DR,statSync as GR}from"node:fs";import{join as MR,posix as pg,relative as LR,resolve as uu,sep as FR}from"node:path";var UR=/\bsource\s*=\s*"(\.{1,2}\/[^"]*)"/g,BR=e=>e.split(FR).join("/");function lg(e,t){let n=BR(LR(e,t));return n===""?".":pg.normalize(n).replace(/\/+$/,"")}function qR(e){let t;try{t=NR(e)}catch{return[]}let n=[];for(let r of t){if(!r.endsWith(".tf")&&!r.endsWith(".tofu"))continue;let o;try{o=DR(MR(e,r),"utf8")}catch{continue}for(let s of o.matchAll(UR)){let i=uu(e,s[1].replace("//","/"));IR(i)&&GR(i).isDirectory()&&n.push(i)}}return n}function lu(e,t){let n=uu(e,t.dir),r=new Set,o=[n];for(;o.length>0;){let i=o.shift();r.has(i)||(r.add(i),o.push(...qR(i)))}let s=[...r].map(i=>lg(e,i));for(let i of t.varFiles??[])s.push(lg(e,uu(n,i)));return[...new Set(s)].sort()}function HR(e,t){return e==="."?!t.startsWith("../"):t===e||t.startsWith(`${e}/`)}function hi(e,t,n){let r=n.map(s=>pg.normalize(s)),o=[];for(let[s,i]of Object.entries(t)){let a=lu(e,i);r.some(c=>a.some(u=>HR(u,c)))&&o.push(s)}return o.sort()}var dg=["terragrunt","removed","module-file","module-call","stack-template"],XR=/\.(tf|tofu)(\.json)?$/,gg=e=>e.split(YR).join("/"),Uo=e=>Fo.normalize(gg(e)).replace(/^\.\//,"").replace(/\/+$/,""),Yn=(e,t)=>e==="."||t===e||t.startsWith(`${e}/`);function ZR(e){let t=[...Fn,...e.exclude??[]];return["find","--json","--no-color","--no-filters-file","--filter",`[${e.base}...${e.head??"HEAD"}]`,...t.flatMap(n=>["--filter",Oo(n)])]}function QR(e){return["diff","--name-only","--no-renames","--relative",`${e.base}...${e.head??"HEAD"}`]}function ex(e){let t=new Set;for(let n of e.reading??[])!XR.test(n)||Yn(e.path,n)||n.includes(".terragrunt-cache/")||t.add(Fo.dirname(n));return[...t].sort()}function tx(e,t,n){let r=Fo.dirname(Uo(t)),o=[],s=/\b(unit|stack)\s+"([^"]+)"\s*\{/g;for(let i=s.exec(n);i;i=s.exec(n)){let a=nx(n,s.lastIndex),c=fg(a,"source"),u=fg(a,"path");if(u===void 0)continue;let p=/^\s*no_dot_terragrunt_stack\s*=\s*true\b/m.test(a)?r:Fo.join(r,".terragrunt-stack"),f=Uo(Fo.join(p,u)),g;if(c!==void 0){let m=c.replace(/^\$\{get_terragrunt_dir\(\)\}\//,"./");/^\.{1,2}\//.test(m)&&(g=Uo(gg(zR(e,VR(e,r,m.replace("//","/"))))))}o.push({type:i[1],name:i[2],generated:f,...g!==void 0?{template:g}:{}})}return o}function nx(e,t){let n=1;for(let r=t;r<e.length;r++){let o=e[r];if(o==='"')for(r++;r<e.length&&e[r]!=='"';r++)e[r]==="\\"&&r++;else if(o==="#"||o==="/"&&e[r+1]==="/")for(;r<e.length&&e[r]!==`
|
|
83
83
|
`;)r++;else if(o==="{")n++;else if(o==="}"&&--n===0)return e.slice(t,r)}return e.slice(t)}function fg(e,t){let n=new RegExp(`^\\s*${t}\\s*=\\s*"([^"]*)"`),r=0;for(let o of e.split(`
|
|
84
84
|
`)){if(r===0){let i=n.exec(o);if(i)return i[1]}let s=o.replace(/"(?:[^"\\]|\\.)*"/g,'""');r+=(s.match(/[{[]/g)??[]).length-(s.match(/[}\]]/g)??[]).length}}var rx=new Set([".git",".terragrunt-cache","node_modules",".terraform"]);function ox(e){let t=[],n=r=>{let o;try{o=JR(pu(e,r),{withFileTypes:!0})}catch{return}for(let s of o){let i=r?`${r}/${s.name}`:s.name;s.isDirectory()&&!rx.has(s.name)?n(i):s.isFile()&&s.name==="terragrunt.stack.hcl"&&t.push(i)}};return n(""),t.sort()}function sx(e){let t=e.lastIndexOf("/.terragrunt-stack/");return t<0?void 0:`${e.slice(0,t)}/terragrunt.stack.hcl`}function ix(e){let t=[...new Set(e.changedFiles.map(Uo))].sort(),n=new Map(e.units.map(a=>[a.path,a])),r=new Map,o=[],s=(a,c)=>{let u=r.get(a)??[];u.push(c),r.set(a,u)};for(let a of new Set(e.terragruntSelected.map(Uo))){let c=n.get(a);if(!c){WR(pu(e.dir,a,"terragrunt.hcl"))?o.push(`${a}: Terragrunt's git range selected it, but discovery left it out, so it is not affected here`):s(a,{kind:"removed",files:t.filter(l=>Yn(a,l))});continue}let u=new Set([...c.reading??[],...Object.values(c.include??{}),sx(c.path)].filter(l=>!!l));s(a,{kind:"terragrunt",files:t.filter(l=>Yn(c.path,l)||u.has(l))})}for(let a of e.units){let c=new Set(a.reading??[]),u=ex(a),l=new Map;for(let g of u)for(let m of lu(e.dir,{dir:g}))!u.includes(m)&&!l.has(m)&&l.set(m,g);let p=new Map,f=new Map;for(let g of t){if(c.has(g))continue;let m=[...l.keys()].filter(h=>Yn(h,g)).sort((h,b)=>b.length-h.length)[0];if(m!==void 0){f.set(m,[...f.get(m)??[],g]);continue}let d=u.filter(h=>Yn(h,g)).sort((h,b)=>b.length-h.length)[0];d!==void 0&&p.set(d,[...p.get(d)??[],g])}for(let[g,m]of p)s(a.path,{kind:"module-file",files:m,via:g});for(let[g,m]of f)s(a.path,{kind:"module-call",files:m,via:g})}for(let a of ox(e.dir)){let c;try{c=KR(pu(e.dir,a),"utf8")}catch{continue}for(let u of tx(e.dir,a,c)){if(u.template===void 0)continue;let l=t.filter(f=>Yn(u.template,f));if(l.length===0)continue;let p=u.type==="unit"?n.has(u.generated)?[u.generated]:[]:e.units.filter(f=>Yn(u.generated,f.path)).map(f=>f.path);p.length===0&&o.push(`${u.generated}: generated by ${u.type} "${u.name}" in ${a} from the changed template ${u.template}, but discovery did not list it; run \`terragrunt stack generate\` before discovery`);for(let f of p)s(f,{kind:"stack-template",files:l,via:u.template})}}return{units:[...r.entries()].map(([a,c])=>({path:a,reasons:c.sort((u,l)=>dg.indexOf(u.kind)-dg.indexOf(l.kind)||(u.via??"").localeCompare(l.via??""))})).sort((a,c)=>a.path<c.path?-1:a.path>c.path?1:0),notes:[...new Set(o)].sort()}}function mg(e){let t=e.files.join(", ");switch(e.kind){case"terragrunt":return t?`Terragrunt's git range selected it (${t})`:"Terragrunt's git range selected it";case"removed":return"removed in the range; Terragrunt plans its destroy";case"module-file":return`${t} changed under its module ${e.via}, which Terragrunt does not track`;case"module-call":return`${t} changed under ${e.via}, a module its module calls`;case"stack-template":return`${t} changed under ${e.via}, the stack template it is generated from`}}async function hg(e){let t=e.exec??Hn,n=e.terragrunt??"terragrunt",r=e.git??"git",o=QR(e),s=await t(r,o,{cwd:e.dir,env:{}});if(s.code!==0)throw new ke(`\`${r} ${o.join(" ")}\` failed (exit ${s.code}): ${s.stderr.trim()}`);let i=ZR(e),a=await t(n,i,{cwd:e.dir,env:Bn(e.binary)});if(a.code!==0)throw new ke(`\`${n} ${i.join(" ")}\` failed (exit ${a.code}): ${a.stderr.trim()}`);let c;try{c=Us(a.stdout).map(l=>l.path)}catch(l){throw new ke(`could not read \`terragrunt find\` output: ${l instanceof Error?l.message:String(l)}`)}let u=e.units??(await Hs(e)).units;return ix({dir:e.dir,units:u,terragruntSelected:c,changedFiles:s.stdout.split(`
|
|
85
|
-
`).map(l=>l.trim()).filter(l=>l!=="")})}var Kr="0.4.
|
|
85
|
+
`).map(l=>l.trim()).filter(l=>l!=="")})}var Kr="0.4.1";import{createHash as cx}from"node:crypto";var Bt="policy-override",Xn=`_gates/${Bt}.jsonl`,zr=e=>[...new Set(e.map(t=>t.trim()).filter(Boolean))].sort();function du(e,t,n){let r=[Bt,e,t,...zr(n)].join(`
|
|
86
86
|
`);return`sha256:${cx("sha256").update(r).digest("hex")}`}function yi(e,t,n=!1){return`terragucci override ${e} ${zr(t).map(r=>`--rule ${r}`).join(" ")} --reason "<why>"${n?" --sign":""}`}var Ve=e=>new Date(e).getTime(),yg=e=>e.normalize("NFKC").trim().toLowerCase();function ux(e,t){return t.via!==void 0?`the override line was written by a job (via ${t.via}), and an override is a person's`:e.overriders.map(yg).includes(yg(t.resolvedBy))?t.note?.trim()?e.mode==="sealed"?Ds(e.signers,e.signersPath,t):null:`the override by ${t.resolvedBy} gives no reason`:`${t.resolvedBy} is not listed under policy.override at base${e.overriders.length?` (${e.overriders.join(", ")})`:""}`}function bi(e,t,n,r,o,s){let i=du(n,r,o),a;for(let g of e.pending)g.op===Bt&&g.gate===n&&(!a||Ve(g.timestamp)>=Ve(a.timestamp))&&(a=g);let c=a?Ve(a.timestamp):0,u,l,p=[];for(let g of e.resolutions){if(g.op!==Bt||g.gate!==n||Ve(g.timestamp)<c)continue;let m=ux(t,g);sn(g.planDigest,i)?m!==null?p.push(m):(!u||Ve(g.timestamp)>=Ve(u.timestamp))&&(u=g):m===null&&(!l||Ve(g.timestamp)>=Ve(l.timestamp))&&(l=g)}if(u)return{status:"overridden",override:{by:u.resolvedBy,at:u.timestamp,rules:zr(o),reason:u.note.trim(),plan_digest:r,digest:i,sealed:t.mode==="sealed"}};let f=a&&Ve(a.expiresAt)>Ve(s)&&sn(a.planDigest,i)?a:void 0;return l?{status:"moved",by:l.resolvedBy,was:l.planDigest,refusals:p,...f?{standing:f}:{}}:{status:"none",refusals:p,...f?{standing:f}:{}}}function bg(e){let t=new Map;for(let n of e.pending){let r=t.get(n.gate);n.op===Bt&&n.planDigest&&(!r||Ve(n.timestamp)>=Ve(r.timestamp))&&t.set(n.gate,n)}return[...t.values()].sort((n,r)=>n.gate<r.gate?-1:1)}import{existsSync as gu,readdirSync as vg,readFileSync as Bo,statSync as dx}from"node:fs";import{dirname as fx,join as qo,relative as wi,resolve as fu}from"node:path";import{execFile as lx}from"node:child_process";var px=["TG_TOKEN","TG_MERGE_TOKEN","GITHUB_TOKEN","GH_TOKEN","GITLAB_TOKEN","CI_JOB_TOKEN","FORGEJO_TOKEN","GITEA_TOKEN","ACTIONS_RUNTIME_TOKEN"];function Ge(e){let t=new Set([e.TG_TOKEN,e.TG_MERGE_TOKEN].filter(r=>typeof r=="string"&&r.length>0)),n={};for(let[r,o]of Object.entries(e))px.includes(r)||o!==void 0&&t.has(o)&&!r.startsWith("TF_")||(n[r]=o);return n}var hn=(e,t,n)=>new Promise(r=>{lx(e,[...t],{cwd:n.cwd,env:Ge({...process.env,...n.env}),maxBuffer:256*1024*1024},(o,s,i)=>{let a=o?typeof o.code=="number"?o.code:null:0;r({code:a,stdout:String(s),stderr:o&&a===null?`${String(i)}${o.message}`:String(i)})})});var wg=["root.hcl","terragrunt.hcl","terragrunt.stack.hcl"],gx=new Set([".git",".terraform",".terragrunt-cache","node_modules",".terragucci","terragucci-report"]),$i=e=>e.split("\\").join("/");function kg(e,t){let n=r=>{let o;try{o=vg(r)}catch{return}t(r,o);for(let s of o){if(gx.has(s)||s.startsWith("."))continue;let i=qo(r,s);dx(i).isDirectory()&&n(i)}};n(e)}function Vr(e){let t=new Map,n=[];kg(e,(o,s)=>{let i=$i(wi(e,o))||".";for(let a of wg)s.includes(a)&&!t.has(a)&&t.set(a,i==="."?a:`${i}/${a}`);s.includes("terragrunt.stack.hcl")&&n.push(i)});let r=wg.find(o=>t.has(o));return r?{reason:t.get(r),stacks:n.sort()}:void 0}function $g(e,t){let n=[],r=new RegExp(`^\\s*${t}\\b[^{\\n]*\\{`,"gm");for(let o=r.exec(e);o;o=r.exec(e)){let s=1,i=o.index+o[0].length,a=i;for(let c=!1;i<e.length&&s>0;i++){let u=e[i];c?u==="\\"?i++:u==='"'&&(c=!1):u==='"'?c=!0:u==="{"?s++:u==="}"&&s--}n.push(e.slice(a,i-1)),r.lastIndex=i}return n}function mu(e){let t=e.replace(/(^|[^:"$])(#|\/\/).*$/gm,"$1"),n=/^"([^"$]*)"$/,r=[];for(let o of $g(t,"dependency")){let s=/^\s*config_path\s*=\s*("[^"\n]*")\s*$/m.exec(o),i=s&&n.exec(s[1]);i&&r.push(i[1])}for(let o of $g(t,"dependencies")){let s=/\bpaths\s*=\s*\[([^\]]*)\]/.exec(o);for(let i of s?s[1].split(",").map(a=>a.trim()).filter(Boolean):[]){let a=n.exec(i);a&&r.push(a[1])}}return r}function mx(e,t=[]){let n=[...Fn,...t],r=[];kg(e,(s,i)=>{let a=$i(wi(e,s));if(!a||!i.includes("terragrunt.hcl")||n.some(l=>jr(a,l)))return;let c="";try{c=Bo(qo(s,"terragrunt.hcl"),"utf-8")}catch{}let u=mu(c).map(l=>$i(wi(e,fu(s,l))));r.push({path:a,dependencies:[...new Set(u)].sort()})});let o=new Set(r.map(s=>s.path));for(let s of r)s.dependencies=s.dependencies.filter(i=>o.has(i));return r.sort((s,i)=>s.path<i.path?-1:1)}async function yn(e,t={}){let n=t.terragrunt??process.env.TERRAGUCCI_TERRAGRUNT??"terragrunt",r={dir:e,terragrunt:n,...t.binary?{binary:t.binary}:{},exec:t.exec??hn},o;try{await Jd(r);let s=await Hs({...r,...t.exclude?{exclude:t.exclude}:{}});return{units:s.units,source:"terragrunt find",notes:s.warnings}}catch(s){o=s.message}return{units:mx(e,t.exclude),source:"terragrunt.hcl files",notes:[`units and their edges come from terragrunt.hcl files: Terragrunt discovery did not run (${o.split(`
|
|
87
87
|
`)[0]}), so a dependency path built with a function is not followed; the apply jobs take the waves from terragrunt find`]}}function ki(e,t=[]){return Ac(e,{canary:t}).filter(n=>n.length>0)}function _i(e,t){let n=new Map(t.map(s=>[s.path,s.dependencies])),r=new Map;e.forEach((s,i)=>s.forEach(a=>r.set(a,i)));let o=[];return e.forEach((s,i)=>{let a=new Set(s);for(let u of s){let l=(n.get(u)??[]).filter(p=>(r.get(p)??-1)>i);if(l.length>0)throw new _(`${u} reads ${l.join(", ")}, which the pipeline applies in a later wave; run terragucci init to cut the waves again`)}let c=s.map(u=>({path:u,dependencies:(n.get(u)??[]).filter(l=>a.has(l))}));o.push(...Ac(c).filter(u=>u.length>0))}),o}function hx(e){let t=qo(e,"root.hcl");if(!gu(t))return;let n=Bo(t,"utf-8").replace(/(^|[^:"])(#|\/\/).*$/gm,"$1"),r=/\bremote_state\s*\{[\s\S]*?\bbackend\s*=\s*"([^"]+)"/.exec(n);if(r)return{backend:r[1],gitlab:r[1]==="http"&&/\/api\/v4\/projects\/[^"]*\/terraform\/state\//.test(n)}}function _g(e,t={}){if(t.parallelism!==void 0)return{value:t.parallelism,reason:"terragucci.yml"};let n=hx(e);return n?.gitlab?{value:3,reason:"GitLab-managed state rate-limits concurrent inits"}:{value:16,reason:n?`the ${n.backend} backend`:"the default"}}function Rg(e){let t=qo(e,"root.hcl");if(gu(t))return/terragrunt_version_constraint\s*=\s*"\s*=?\s*(\d+\.\d+\.\d+)\s*"/.exec(Bo(t,"utf-8"))?.[1]}var vi="TERRAGUCCI_TG_ROLES";function xg(e,t){return Object.entries(e).map(([n,r])=>[n,r[t]])}function yx(e,t){let n=s=>{try{return/^\s*iam_role\s*=/m.test(Bo(s,"utf-8"))}catch{return!1}},r=fu(e),o=fu(t);for(let s=o;s.startsWith(r);s=fx(s)){if((s===o?["terragrunt.hcl"]:vg(s).filter(a=>/^[^.].*\.hcl$/.test(a)&&a!=="terragrunt.hcl")).some(a=>n(qo(s,a))))return!0;if(s===r)break}return!1}function Sg(e,t){let n=t.TERRAGUCCI_REPO??process.cwd(),r=$i(wi(n,e)),o;try{o=JSON.parse(t[vi]??"[]")}catch{throw new _(`${vi} is not JSON`)}if(yx(n,e))return{};let s=o.find(([a])=>jr(r,a));if(!s)return{};let i=t.AWS_WEB_IDENTITY_TOKEN_FILE;if(!i||!gu(i))throw new _(`${r} needs ${s[1]}, but the job has no OIDC token file`);return{awsRole:{roleARN:s[1],roleSessionName:`terragucci-${t.TERRAGUCCI_PHASE??"run"}`,webIdentityToken:Bo(i,"utf-8").trim()}}}var bx="npm i -D @cdktn/hcl2json";async function Zn(){try{return await import("@cdktn/hcl2json")}catch{throw new _(`terragucci rollout reads module pins with the HCL parser, which is not installed: ${bx}`)}}import{existsSync as tm,readdirSync as nm,readFileSync as rm}from"node:fs";import{join as wn,posix as Zg}from"node:path";import{existsSync as ku,readdirSync as Px,readFileSync as Ag}from"node:fs";import{join as Ri}from"node:path";import{appendFileSync as wx}from"fs";var hu=class extends Error{constructor(t){super(`Terraform carve-out needs the HCL parser, which is not installed.
|
|
88
88
|
Install it once: npm install -D @cdktn/hcl2json
|
|
89
89
|
(underlying error: ${t instanceof Error?t.message:String(t)})`),this.name="Hcl2JsonNotInstalled"}},$x="CHANT_HCL2JSON_RECORD";function vx(e,t){let n=r=>{wx(t,`${JSON.stringify(r)}
|
|
90
90
|
`)};return{parse:(r,o)=>(n({kind:"parse",filename:r,text:o}),e.parse(r,o)),getReferencesInExpression:(r,o)=>(n({kind:"refs",filename:r,text:o}),e.getReferencesInExpression(r,o))}}async function Eg(){let e;try{e=await import("@cdktn/hcl2json")}catch(n){throw new hu(n)}let t=process.env[$x];return t?vx(e,t):e}var kx=/^\s*(terraform|locals|remote_state)\s*\{/,_x=/^\s*(provider|module|variable|output|dependency|generate)\s+"([^"]*)"\s*\{/,Rx=/^\s*(resource|data)\s+"([^"]*)"\s+"([^"]*)"\s*\{/,xx=/^\s*(?:#|\/\/)\s*chant-ignore(-file|-block)?\s*:?\s*(.*)$/;function yu(e,t){switch(e){case"terraform":return"terraform";case"locals":return"locals";case"remote_state":return"remote_state";case"dependency":return`dependency.${t[0]}`;case"generate":return`generate.${t[0]}`;case"provider":return`provider.${t[0]}`;case"module":return`module.${t[0]}`;case"variable":return`var.${t[0]}`;case"output":return`output.${t[0]}`;case"resource":return`${t[0]}.${t[1]}`;case"data":return`data.${t[0]}.${t[1]}`;default:return""}}function Sx(e){return e.replace(/"(?:[^"\\]|\\.)*"/g,'""')}function Ex(e){let t=Sx(e),n=0;for(let r of t)r==="{"?n++:r==="}"&&n--;return n}function Tx(e){let t=e.split(/\s+/).filter(Boolean),n,r=[];for(let i of t){let a=/^exp:(\d{4}-\d{2}-\d{2})$/.exec(i);if(a){n=a[1];continue}r.push(i)}let o=r.join(" ").trim();return o===""||o.toLowerCase()==="all"?{ids:"all",expires:n}:{ids:new Set(o.split(/[,\s]+/).map(i=>i.trim().toUpperCase()).filter(Boolean)),expires:n}}function bu(e,t,n){let r=e.get(t)??[];r.push(n),e.set(t,r)}function wu(e,t){let n=t.split(`
|
|
91
|
-
`),r=new Map,o=new Map,s,i,a=0;for(let c=0;c<n.length;c++){let u=n[c],l=c+1,p=u.trim();i===void 0&&p!==""&&(i=l);let f=xx.exec(u);if(f){let g=f[1],{ids:m,expires:d}=Tx(f[2].trim());if(g==="-file")s={form:"chant-ignore-file",ids:m,expires:d,file:e,line:l,misplaced:l!==i,key:`${e}:${l}:chant-ignore-file`};else{let h=g==="-block"?"chant-ignore-block":"chant-ignore",b={form:h,ids:m,expires:d,file:e,line:l,key:`${e}:${l}:${h}`},$=l+1,w=o.get($)??[];w.push(b),o.set($,w)}}else if(a===0){let g=kx.exec(u);g?bu(r,yu(g[1],[]),l):(g=_x.exec(u))?bu(r,yu(g[1],[g[2]]),l):(g=Rx.exec(u))&&bu(r,yu(g[1],[g[2],g[3]]),l)}a+=Ex(u)}return{blockLineQueues:r,byAnchorLine:o,fileDirective:s}}function Ax(e,t){let n=e.blockLineQueues.get(t);if(!(!n||n.length===0))return n.shift()}function Tg(e,t){let n=Ax(e,t),r=n!==void 0?e.byAnchorLine.get(n)??[]:[],o=e.fileDirective?[...r,e.fileDirective]:r;return{line:n,suppressions:o.length>0?o:void 0}}var Ho="Terraform::Terraform",_u="Terraform::Provider",Ru="Terraform::Resource",xu="Terraform::Data",Ht="Terraform::Module",Ox="Terraform::Variable",Cx="Terraform::Output",jx="Terraform::Locals",$u="Terraform::Live",Ix="Terraform::TerragruntConfig",Su="Terraform::TerragruntSource",Wo="Terraform::TerragruntDependency",Eu="Terraform::TerragruntRemoteState",Tu="Terraform::TerragruntGenerate",Pg=["root.hcl","terragrunt.hcl"];function Nx(e){return Pg.includes(e)}var vu="estate.chdf.hcl";function Dx(e){if("count"in e)return"count";if("for_each"in e)return"for_each"}function Gx(e,t,n,r,o,s,i="",a,c){let u=Dx(n);return{[Mn]:!0,lexicon:"terraform",entityType:e,kind:"resource",stack:o,props:{address:t,body:n,file:r,root:o,source:i,line:a,...u?{expansion:u}:{},...s?.mode!==void 0?{mode:s.mode}:{},...s?.estate!==void 0?{estate:s.estate}:{},...s?.workspace!==void 0?{workspace:s.workspace}:{},...s?.delete!==void 0?{delete:s.delete}:{},...s?.configEstate!==void 0?{configEstate:s.configEstate}:{},...s?.callers!==void 0&&s.callers.length>0?{callers:s.callers}:{}},suppressions:c}}function Mx(e){return Px(e).filter(t=>t.endsWith(".tf")).sort()}function Lx(e){return Pg.filter(t=>ku(Ri(e,t)))}function Fx(e){let t=Ri(e,vu);if(ku(t))return{name:vu,source:Ag(t,"utf-8")}}function qt(e){return Array.isArray(e)?e:[]}function bn(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)?e:{}}function Ux(e){let t=[];for(let n of qt(e.terraform)){let r=bn(n);for(let o of qt(r.live))t.push(bn(o))}return t}var Au=".terraform.lock.hcl";async function Bx(e,t,n,r,o,s=[]){let i=n??await Eg(),a=new Map,c=s.length>0?`${t}/${s.join("/")}`:t,u=(d,h,b,$,w,R=h)=>{let{line:y,suppressions:k}=Tg(w,R),P=Gx(d,h,typeof b=="object"&&b!==null?b:{},$.name,t,{...r?.workspace!==void 0?{workspace:r.workspace}:{},...r?.delete!==void 0?{delete:r.delete}:{},...r?.configEstate!==void 0?{configEstate:r.configEstate}:{},...s.length>0?{callers:s}:{}},$.source,y,k),v=`${c}/${h}`;for(let S=2;a.has(v);S++)v=`${c}/${h}~${S}`;a.set(v,P)},l=(d,h,b,$,w)=>{for(let R of qt(d[h]))u(b,h,R,$,w)},p=(d,h,b,$,w,R)=>{for(let[y,k]of Object.entries(bn(d[h])))for(let P of qt(k))u(b,$(y),P,w,R)},f=(d,h,b,$,w,R)=>{for(let[y,k]of Object.entries(bn(d[h])))for(let[P,v]of Object.entries(bn(k)))for(let S of qt(v))u(b,$(y,P),S,w,R)};for(let d of e){let h=await i.parse(d.name,d.source),b=wu(d.name,d.source);if(Nx(d.name)){qx(h,d,b,u);continue}l(h,"terraform",Ho,d,b);for(let $ of Ux(h))u($u,"live",$,d,b);l(h,"locals",jx,d,b),p(h,"provider",_u,$=>`provider.${$}`,d,b),p(h,"module",Ht,$=>`module.${$}`,d,b),p(h,"variable",Ox,$=>`var.${$}`,d,b),p(h,"output",Cx,$=>`output.${$}`,d,b),f(h,"resource",Ru,($,w)=>`${$}.${w}`,d,b),f(h,"data",xu,($,w)=>`data.${$}.${w}`,d,b)}if(o){let d=await i.parse(o.name,o.source);typeof d.estate=="string"&&u($u,"live",d,o,wu(o.name,o.source))}let g;for(let d of a.values()){if(d.entityType!==$u)continue;let h=d.props,b=typeof h.body.estate=="string"?h.body.estate:void 0;b!==void 0&&(g===void 0||h.file===vu)&&(g=b)}let m=r?.binary==="choudoufu"&&g!==void 0?"live":"state";for(let[d,h]of a){let b=h,$={...b,props:{...b.props,mode:m,...m==="live"?{estate:g}:{},...r?.lockFile!==void 0?{lockFile:r.lockFile}:{}}};a.set(d,$)}return a}function qx(e,t,n,r){let o={includes:Object.keys(bn(e.include))};typeof e.terragrunt_version_constraint=="string"&&(o.terragrunt_version_constraint=e.terragrunt_version_constraint),r(Ix,"terragrunt",o,t,n,"");for(let s of qt(e.terraform))r(Su,"terragrunt.terraform",s,t,n,"terraform");for(let s of qt(e.remote_state))r(Eu,"remote_state",s,t,n);for(let[s,i]of Object.entries(bn(e.dependency)))for(let a of qt(i))r(Wo,`dependency.${s}`,a,t,n);for(let[s,i]of Object.entries(bn(e.generate)))for(let a of qt(i))r(Tu,`generate.${s}`,a,t,n)}async function Pu(e,t,n,r,o=[]){let i=[...Mx(e),...o.length===0?Lx(e):[]].map(u=>({name:u,source:Ag(Ri(e,u),"utf-8")})),a=o.length===0?Fx(e):void 0,c=o.length===0?{...r,lockFile:ku(Ri(e,Au))}:r;return Bx(i,t,n,c,a,o)}function Hx(e){if(!z(e))return[];let t=e.props;return Array.isArray(t.callers)?t.callers:[]}function Qn(e){return Hx(e).length===0}var Ou=new Set(["terraform"]);function Og(e){return e.split("_")[0]}function Cu(e,t){let n=new Map,r=new Set;for(let[o,s]of e){if(!z(s)||!Qn(s))continue;let i=s.props;if(i.root!==t)continue;let a=typeof i.address=="string"?i.address:"";if(s.entityType===_u){let c=a.slice(9);if(Ou.has(c))continue;n.set(c,o),r.add(c)}else if(s.entityType===Ru){let c=a.split(".")[0]??"",u=Og(c);if(Ou.has(u)||r.has(u))continue;n.has(u)||n.set(u,o)}else if(s.entityType===xu){let c=a.split(".")[1]??"",u=Og(c);if(Ou.has(u)||r.has(u))continue;n.has(u)||n.set(u,o)}}return n}function Wx(e){let t={},n=e.required_providers;if(!Array.isArray(n))return t;for(let r of n)typeof r=="object"&&r!==null&&Object.assign(t,r);return t}function Jx(e){if(e===void 0)return"no `required_providers` entry";if(typeof e=="string")return"the legacy shorthand form (a bare version string), which carries no `source`";if(typeof e!="object"||e===null)return"an entry that is not a valid object";let t=e,n=[];return(typeof t.source!="string"||t.source==="")&&n.push("`source`"),(typeof t.version!="string"||t.version==="")&&n.push("`version`"),n.length>0?`an entry missing ${n.join(" and ")}`:void 0}var Cg={id:"TF002",description:"A provider the root implies has no required_providers entry, or an incomplete one",check(e){let t=[],n=new Set,r=new Map;for(let o of e.entities.values()){if(o.entityType!==Ho||!z(o)||!Qn(o))continue;let s=o.props,i=typeof s.root=="string"?s.root:"";n.add(i);let a=typeof s.body=="object"&&s.body!==null?s.body:{},c=r.get(i)??{};Object.assign(c,Wx(a)),r.set(i,c)}for(let o of[...n].sort()){let s=r.get(o)??{},i=Cu(e.entities,o);for(let a of[...i.keys()].sort()){let c=Jx(s[a]);c&&t.push({checkId:"TF002",severity:"warning",message:`Provider "${a}" is used in root module "${o}" but has ${c} in \`required_providers\`. Add \`source\` and \`version\` for it in the terraform block's \`required_providers\`, so a future provider release can't silently change behavior.`,entity:i.get(a),lexicon:"terraform"})}}return t}};function Kx(e){return typeof e.required_version=="string"&&e.required_version.trim()!==""}var jg={id:"TF003",description:"Root module's terraform block has no required_version",check(e){let t=[],n=new Map;for(let[r,o]of e.entities){if(o.entityType!==Ho||!z(o)||!Qn(o))continue;let s=o.props,i=typeof s.root=="string"?s.root:"",a=typeof s.body=="object"&&s.body!==null?s.body:{},c=n.get(i),u=(c?.hasVersion??!1)||Kx(a);n.set(i,{anchor:c?.anchor??r,hasVersion:u})}for(let[r,{anchor:o,hasVersion:s}]of[...n].sort(([i],[a])=>i.localeCompare(a)))s||t.push({checkId:"TF003",severity:"warning",message:`Root module "${r}"'s terraform block has no \`required_version\`. Without it, an upgraded Terraform binary can change behavior with no version-check failure to flag it first. Add \`required_version = ">= <lowest supported version>"\`.`,entity:o,lexicon:"terraform"});return t}};var Ig=/^[0-9A-Za-z](?:[0-9A-Za-z_-]{0,62}[0-9A-Za-z])?$/,zx=/^[0-9a-z]{1,64}$/,Vx=new Set(["github.com","bitbucket.org"]);function Ng(e){let t=new Map;for(let n of e.split("&")){if(n==="")continue;let r=n.indexOf("="),o=r===-1?n:n.slice(0,r),s=r===-1?"":decodeURIComponent(n.slice(r+1));t.set(decodeURIComponent(o),s)}return t}function Dg(e){let t=e.indexOf("://"),n=t===-1?0:t+3,r=e.indexOf("//",n);return r===-1?e:e.slice(0,r)}function Yx(e){let t=Dg(e).split("/"),n,r,o;if(t.length===3)[n,r,o]=t;else if(t.length===4){let s=t[0];if(!s.includes(".")||Vx.has(s.toLowerCase()))return!1;[,n,r,o]=t}else return!1;return Ig.test(n)&&Ig.test(r)&&zx.test(o)}function Xx(e){if(/^[\w.-]+@[^:/]+:/.test(e))return!0;let t=e.replace(/^[a-z][a-z0-9+.-]*:\/\//i,"");return!!(/^(github\.com|bitbucket\.org)\//i.test(t)||/\.git($|[/?])/i.test(t))}function Zx(e,t){let n=Ng(t),r=Dg(e).replace(/^oci:\/\//i,""),o=n.get("tag")||void 0,s=n.get("digest")||void 0,i=r.indexOf("@");i!==-1&&(s=s??(r.slice(i+1)||void 0),r=r.slice(0,i));let a=r.lastIndexOf("/"),c=r.indexOf(":",a+1);return c!==-1&&(o=o??(r.slice(c+1)||void 0)),{kind:"oci",...o!==void 0?{tag:o}:{},...s!==void 0?{digest:s}:{}}}function _t(e){let t=e.trim();if(t.startsWith("./")||t.startsWith("../")||t.startsWith(".\\")||t.startsWith("..\\"))return{kind:"local"};let n=t.indexOf("?"),r=n===-1?t:t.slice(0,n),o=n===-1?"":t.slice(n+1);if(/^oci:\/\//i.test(r))return Zx(r,o);let s=/^(git|hg)::/i.exec(r),i=s?r.slice(s[0].length):r;if(s||Xx(i)){let a=Ng(o);return{kind:"git",ref:a.get("ref")||a.get("rev")||void 0}}return Yx(i)?{kind:"registry"}:{kind:"other"}}var Qx=/^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/,eS=/^[0-9a-f]{40}$/i,tS=new Set(["main","master","develop","trunk"]);function Gg(e){return Qx.test(e)||eS.test(e)}function Mg(e){return tS.has(e)}var nS=new Set(["latest","main","master","develop","dev","stable","edge","nightly"]);function Lg(e){return nS.has(e.toLowerCase())}function Fg(e){return/^(?:=\s*)?v?\d+(?:\.\d+){0,2}(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(e.trim())&&!e.includes(",")}var Ug={id:"TF004",description:"Registry-sourced module block has no version",check(e){let t=[];for(let[n,r]of e.entities){if(r.entityType!==Ht||!z(r))continue;let o=r.props,s=typeof o.body=="object"&&o.body!==null?o.body:{},i=typeof s.source=="string"?s.source:void 0;if(i===void 0||_t(i).kind!=="registry")continue;let a=s.version;if(typeof a=="string"&&a.trim()!=="")continue;let c=typeof o.address=="string"?o.address:n;t.push({checkId:"TF004",severity:"warning",message:`Module "${c}" sources "${i}" from a registry with no \`version\`. \`terraform init\` resolves to the newest release with no constraint to hold it back. Add a \`version\` constraint (e.g. \`~> 5.0\`).`,entity:n,lexicon:"terraform"})}return t}};var Bg={id:"TF005",description:"Git/hg module source is unpinned, or pinned to a mutable ref",check(e){let t=[];for(let[n,r]of e.entities){if(r.entityType!==Ht||!z(r))continue;let o=r.props,s=typeof o.body=="object"&&o.body!==null?o.body:{},i=typeof s.source=="string"?s.source:void 0;if(i===void 0)continue;let a=_t(i);if(a.kind!=="git")continue;let c=typeof o.address=="string"?o.address:n,u;a.ref?Mg(a.ref)?u=`Module "${c}" sources "${i}" pinned to "${a.ref}", a mutable branch. Pin to a tag or a full commit SHA instead.`:Gg(a.ref)||(u=`Module "${c}" sources "${i}" pinned to "${a.ref}", which is neither a tag nor a full commit SHA and can move. Pin to a tag or a full commit SHA instead.`):u=`Module "${c}" sources "${i}" from git/hg with no \`?ref=\`. Add one pinned to a tag or a full commit SHA.`,u&&t.push({checkId:"TF005",severity:"warning",message:u,entity:n,lexicon:"terraform"})}return t}};var qg={id:"TF038",description:"OCI module source has no tag or digest, or a mutable tag",check(e){let t=[];for(let[n,r]of e.entities){if(r.entityType!==Ht||!z(r))continue;let o=r.props,s=typeof o.body=="object"&&o.body!==null?o.body:{},i=typeof s.source=="string"?s.source:void 0;if(i===void 0)continue;let a=_t(i);if(a.kind!=="oci"||a.digest)continue;let c=typeof o.address=="string"?o.address:n,u;a.tag?Lg(a.tag)&&(u=`Module "${c}" sources "${i}" with the mutable tag "${a.tag}". Pin it to a digest (\`?digest=sha256:...\`) or an exact version tag (\`?tag=1.4.0\`).`):u=`Module "${c}" sources "${i}" with no tag or digest, so OpenTofu pulls the \`latest\` tag. Pin it to a digest (\`?digest=sha256:...\`) or an exact version tag (\`?tag=1.4.0\`).`,u&&t.push({checkId:"TF038",severity:"warning",message:u,entity:n,lexicon:"terraform"})}return t}};var Hg={id:"TF039",description:"Registry module version is a range, not an exact version",check(e){let t=[];for(let[n,r]of e.entities){if(r.entityType!==Ht||!z(r))continue;let o=r.props,s=typeof o.body=="object"&&o.body!==null?o.body:{},i=typeof s.source=="string"?s.source:void 0;if(i===void 0||_t(i).kind!=="registry")continue;let a=s.version;if(typeof a!="string"||a.trim()===""||Fg(a))continue;let c=typeof o.address=="string"?o.address:n;t.push({checkId:"TF039",severity:"warning",message:`Module "${c}" sources "${i}" with the version range "${a}". A release inside the range changes what the root provisions with no diff to review. Pin an exact version (\`version = "1.4.0"\`).`,entity:n,lexicon:"terraform"})}return t}};var Wg={id:"TF040",description:"Root has no committed .terraform.lock.hcl",auditOptIn:!0,check(e){let t=[],n=new Set;for(let r of e.entities.values()){if(!z(r)||!Qn(r))continue;let o=r.props;o.lockFile!==!1||o.mode==="live"||typeof o.root!="string"||n.add(o.root)}for(let r of[...n].sort()){let o=Cu(e.entities,r);if(o.size===0)continue;let s=[...o.keys()].sort();t.push({checkId:"TF040",severity:"warning",message:`Root module "${r}" uses ${s.length===1?"provider":"providers"} ${s.map(i=>`"${i}"`).join(", ")} but has no ${Au} beside its .tf files. Run \`terraform init\` and commit the file, so every init selects the same provider versions.`,entity:o.get(s[0]),lexicon:"terraform"})}return t}};function Wt(e,t){let n=[];for(let[r,o]of e){if(o.entityType!==t||!z(o))continue;let s=o.props;n.push({key:r,address:typeof s.address=="string"?s.address:r,file:typeof s.file=="string"?s.file:"terragrunt.hcl",body:typeof s.body=="object"&&s.body!==null?s.body:{}})}return n}function Jg(e){return typeof e=="string"&&e.includes("${")}function Kg(e){let t=e.trim();if(t.includes("${"))return{kind:"other"};if(/^tfr:\/\//i.test(t)){let r=t.includes("?")?t.slice(t.indexOf("?")+1):"",o=new URLSearchParams(r).get("version");return{kind:"tfr",pinned:o!==null&&o.trim()!==""}}if(t.startsWith("/"))return{kind:"local"};let n=_t(t);switch(n.kind){case"local":return{kind:"local"};case"git":return{kind:"git",pinned:n.ref!==void 0};case"oci":return{kind:"oci",pinned:n.tag!==void 0||n.digest!==void 0};default:return{kind:"other"}}}var zg={id:"TF041",description:"Terragrunt dependency lets mock_outputs stand in for apply",check(e){let t=[];for(let n of Wt(e.entities,Wo)){if(n.body.mock_outputs===void 0)continue;let r=n.body.mock_outputs_allowed_terraform_commands;if(Jg(r))continue;let o;r===void 0||Array.isArray(r)&&r.length===0?o="sets no `mock_outputs_allowed_terraform_commands`, and Terragrunt reads a missing or empty list as every command":Array.isArray(r)&&r.includes("apply")&&(o='lists "apply" in `mock_outputs_allowed_terraform_commands`'),o!==void 0&&t.push({checkId:"TF041",severity:"warning",message:`Dependency "${n.address.replace(/^dependency\./,"")}" in ${n.file} sets \`mock_outputs\` and ${o}. An apply that runs before the upstream unit is applied writes the mock values to real state. Set \`mock_outputs_allowed_terraform_commands = ["validate", "plan"]\`.`,entity:n.key,lexicon:"terraform"})}return t}};var Vg={id:"TF042",description:"Terragrunt dependency sets skip_outputs together with mock_outputs",check(e){let t=[];for(let n of Wt(e.entities,Wo))n.body.skip_outputs!==!0||n.body.mock_outputs===void 0||t.push({checkId:"TF042",severity:"warning",message:`Dependency "${n.address.replace(/^dependency\./,"")}" in ${n.file} sets \`skip_outputs = true\` with \`mock_outputs\`. Terragrunt then never reads the upstream unit's outputs, so every plan and apply runs on the mock values. Drop \`skip_outputs\`, or pass the values as \`inputs\` so they are visible as configuration.`,entity:n.key,lexicon:"terraform"});return t}};var rS={git:"Add `?ref=` with a tag or commit SHA.",tfr:"Add `?version=` with an exact version.",oci:"Add `?digest=sha256:...` or `?tag=` with an exact version tag."},Yg={id:"TF043",description:"Terragrunt terraform.source names no version (git ref, tfr version or oci tag)",check(e){let t=[];for(let n of Wt(e.entities,Su)){let r=n.body.source;if(typeof r!="string")continue;let o=Kg(r);o.pinned===!1&&t.push({checkId:"TF043",severity:"warning",message:`The \`terraform\` block in ${n.file} sources "${r}" with no version, so a push or a release changes the unit with no diff to review. `+rS[o.kind],entity:n.key,lexicon:"terraform"})}return t}};var oS=/\bbackend\s+"local"/,Xg={id:"TF044",description:"Terragrunt config keeps state in a local backend",check(e){let t=[],n=(r,o,s)=>{t.push({checkId:"TF044",severity:"warning",message:`${s} in ${o} keeps state in a local backend. The state lives on the runner's disk, so a later job cannot read it and a git-range run, which checks out each side in a temporary directory, never sees it. Use a shared backend such as S3 or GCS.`,entity:r,lexicon:"terraform"})};for(let r of Wt(e.entities,Eu))r.body.backend==="local"&&n(r.key,r.file,'`remote_state` sets `backend = "local"`');for(let r of Wt(e.entities,Tu)){let o=r.body.contents;typeof o=="string"&&oS.test(o)&&n(r.key,r.file,`\`generate "${r.address.replace(/^generate\./,"")}"\` writes a \`backend "local"\` block`)}return t}};var Qg={TF002:Cg,TF003:jg,TF004:Ug,TF005:Bg,TF038:qg,TF039:Hg,TF040:Wg,TF041:zg,TF042:Vg,TF043:Yg,TF044:Xg},sS="https://intentius.io/chant/lexicons/terraform/lint-rules/",iS="https://intentius.io/terragucci/reference/tips/",aS=10,cS=5,uS=100,em=(e,t,n)=>({rule:e,root:t,message:n,url:`${sS}#${e.toLowerCase()}`}),Yr=(e,t,n)=>({rule:e,...n!==void 0?{root:n}:{},message:t,url:`${iS}#${e}`});async function om(e,t,n){let r=[];if(!tm(wn(e,t)))return r;for(let o of nm(wn(e,t)).filter(s=>s.endsWith(".tf")).sort()){let s=await n.parse(o,rm(wn(e,t,o),"utf-8"));for(let i of Object.values(s.module??{}))for(let a of i)typeof a.source=="string"&&r.push({source:a.source,...typeof a.version=="string"?{version:a.version}:{}})}return r}async function lS(e,t,n){let r=[];if(!tm(wn(e,t)))return r;for(let o of nm(wn(e,t)).filter(s=>s.endsWith(".tf")).sort()){let s=await n.parse(o,rm(wn(e,t,o),"utf-8"));for(let i of s.terraform??[])for(let a of i.required_providers??[])for(let[c,u]of Object.entries(a)){let l=u?.version;typeof l=="string"&&Mo(l)===null&&r.push({name:c,version:l})}}return r}async function sm(e,t,n,r=new Set){let o=new Set;if(r.has(t))return o;r.add(t);for(let{source:s}of await om(e,t,n)){if(!/^\.\.?\//.test(s))continue;let i=Zg.normalize(Zg.join(t==="."?"":t,s))||".";o.add(i);for(let a of await sm(e,i,n,r))o.add(a)}return o}async function xi(e,t,n){if(n.settings.tips===!1)return[];let r=[],{parser:o}=n;if(o){let a=(n.rules??Object.keys(Qg)).map(l=>Qg[l]).filter(l=>l!==void 0),c=new Set,u=new Map;for(let l of n.configDirs??[]){let p=await Pu(wn(e,l),l,o),f={outputs:new Map,entities:p};for(let g of a)for(let m of await g.check(f))r.push(em(g.id,l,m.message))}for(let l of t){let p=await Pu(wn(e,l),l,o),f={outputs:new Map,entities:p};for(let g of a)for(let m of await g.check(f))r.push(em(g.id,l,m.message)),(g.id==="TF038"||g.id==="TF039")&&c.add(l);for(let g of await sm(e,l,o))u.set(g,(u.get(g)??new Set).add(l));for(let{name:g,version:m}of await lS(e,l,o))r.push(Yr("terragucci-floating-range",`Provider ${g} is constrained to the range "${m}". tf-rollout --provider moves the lock file, and a version outside the range fails init, so a rollout cannot cross it. Pin the exact version, or widen the range on purpose.`,l));if(!c.has(l))for(let{source:g,version:m}of await om(e,l,o))Jn(g,m).unpinned?.includes("is a constraint")&&r.push(Yr("terragucci-floating-range",`${g} is pinned to the range "${m}". A range cannot be bumped by a pin, so tf-rollout cannot move it in waves. Pin one version.`,l))}for(let[l,p]of[...u].sort(([f],[g])=>f<g?-1:1))p.size<aS||r.push(Yr("terragucci-shared-module",`${l} is included by ${p.size} roots, so every change under it plans every one of them. Publish it with tf-publish and pin it in each root, and only the roots whose pin moves are planned.`,l))}t.length>cS&&(n.settings.waves?.canary??[]).length===0&&r.push(Yr("terragucci-no-canary",`The project has ${t.length} roots and no waves.canary, so the first wave is chosen by apply order. Name one or two low-risk roots there, since the first wave is the riskiest.`));let s=n.destroying??[];if(n.settings.gate==="never"&&s.length>0)for(let a of[...s].sort())r.push(Yr("terragucci-ungated-destroy",`${a} plans a destroy or replacement and gate is never, so nothing stops it but the forge's own reviewers. Use gate: on-destroy.`,a));let i=n.planned??t.length;return i>=uS&&r.push(Yr("terragucci-many-roots",`${i} roots are planned on every change. Publishing shared modules and pinning them in each root shrinks the set a change reaches.`)),r}function Si(e){return e.map(t=>`tip (${t.rule}): ${t.root?`${t.root}: `:""}${t.message}`)}import{spawnSync as vS}from"node:child_process";import{createHash as dS}from"node:crypto";var ju={"ghcr.io/intentius/terragucci-tofu:0.4.0-tofu1.13.1":"sha256:1890cb7ce9691580c6f8b4d9cbd627ed64932ba48f8995bac455664fc9288e80","ghcr.io/intentius/terragucci-terraform:0.4.0-tf1.14.0":"sha256:85368b95ac852c8e47f5a1b1013124d37bf36191d02e96f44fef9da2096b92a1","ghcr.io/intentius/terragucci-terragrunt:0.4.0-tg1.1.6-tofu1.13.1":"sha256:5823a15adee65591618092363cdf6809a8a3945677da1c3d3446bf33c1f48468","ghcr.io/intentius/terragucci-choudoufu:0.4.0-choudoufu0.22.0":"sha256:99e7802b4c371f5aa1b63cc2531c43b2312d3d29de1ab354f2f2d0ffd3031b34"};var im="ghcr.io/intentius",$n={tofu:"1.13.1",terraform:"1.14.0",terragrunt:"1.1.6",choudoufu:"0.22.0"};function am(e,t=Kr){return e==="terragrunt"?`${t}-tg${$n.terragrunt}-tofu${$n.tofu}`:`${t}-${e==="terraform"?"tf":e}${$n[e]}`}function cm(e,t=ju){let n=`${im}/terragucci-${e}`,r=am(e);return{repository:n,tag:r,digest:t[`${n}:${r}`]}}function um(e=ju){let t=`${im}/terragucci-terragrunt`,n=am("terragrunt");return{repository:t,tag:n,digest:e[`${t}:${n}`]}}function Nu(e){return e.digest?`${e.repository}:${e.tag}@${e.digest}`:`${e.repository}:${e.tag}`}var Iu={laya:"0.3.28",torch:"2.14.0",pip:"26.2.1",checkpoint:{repo:"convaiinnovations/laya",name:"english",revision:"55cf4c4ebb4ebe31b2550e8bdf3bd21b99753851",weightsSha256:"891102d372688fc2a094dac56a384bc537b87c63f21f9f3dac0be2b7cbc8d86c"},port:8790},lm=`laya-${Iu.laya}-${Iu.checkpoint.name}@${Iu.checkpoint.revision.slice(0,12)}`;var fS="https://api.typesafe.ai",gS={noul:.8,choice:.7,score:.7},mS={laya:!0,jev:!0,von:!1,decider:!1};function hS(e,t){let n=r=>{throw new Error(`decide: question ${e} ${r}`)};if(/^[A-Za-z0-9_-]{1,64}$/.test(e)||n("needs a name of letters, digits, - and _"),t.instructions||n("has no instructions"),t.threshold!==void 0&&!(t.threshold>0&&t.threshold<=1)&&n("has a threshold outside (0, 1]"),t.type==="noul")(!t.criteria?.true||!t.criteria?.false)&&n("is a noul and needs criteria.true and criteria.false");else if(t.type==="choice"){let r=Object.keys(t.criteria??{}).length;(r<2||r>255)&&n(`is a choice with ${r} options; use 2 to 255`)}else if(t.type==="score"){let r=t.criteria?.length??0;(r<2||r>10)&&n(`is a score with ${r} levels; use 2 to 10`)}else n(`has type ${JSON.stringify(t.type)}; use noul, choice or score`)}function pm(e,t){return e.threshold??t?.thresholds?.[e.type]??gS[e.type]}var Ei=e=>typeof e=="number"&&Number.isFinite(e)&&e>=0&&e<=1;function yS(e){let t=e.url??(e.backend==="jev"?fS:void 0);if(!t)return;let n=t.replace(/\/+$/,"");return n.endsWith("/v1/systemone")?n:`${n}/v1/systemone`}function bS(e){return e.model??(e.backend==="laya"?lm:void 0)}function wS(e,t,n,r){let o={question:e,type:t.type,threshold:r},s=p=>({...o,status:"unavailable",reason:p});if(n===null||typeof n!="object")return s("the response has no answer for it");let i=n;if(i.type!==t.type)return s(`the answer's type is ${JSON.stringify(i.type)}, not ${t.type}`);let a,c,u,l;if(t.type==="noul"){if(!Ei(i.noul))return s("the noul answer is not a probability");a=i.noul>=.5?"true":"false",c=Math.max(i.noul,1-i.noul),u={true:i.noul,false:1-i.noul}}else{let p=i.probabilities;if(p===null||typeof p!="object"||!Object.values(p).every(Ei))return s("the answer has no probabilities");if(u=p,t.type==="choice"){if(typeof i.choice!="string"||!(i.choice in t.criteria))return s(`the choice ${JSON.stringify(i.choice)} is not one of the options`);if(!Ei(u[i.choice]))return s("the chosen option has no probability");a=i.choice,c=u[i.choice]}else{let f=t.criteria.map((m,d)=>String(d));if(!f.every(m=>Ei(u[m])))return s("the score's probabilities do not cover every level");let g=f.reduce((m,d)=>u[d]>u[m]?d:m,f[0]);a=t.criteria[Number(g)],c=u[g],typeof i.score=="number"&&Number.isFinite(i.score)&&(l=i.score)}}return{...o,status:c>=r?"confident":"not-confident",answer:a,probability:c,probabilities:u,...l===void 0?{}:{score:l}}}async function Xr(e,t,n,r={}){for(let[y,k]of Object.entries(n))hS(y,k);let o=`sha256:${dS("sha256").update(JSON.stringify(t)).digest("hex")}`,s=(y,k)=>Object.fromEntries(Object.entries(n).map(([P,v])=>[P,{question:P,type:v.type,status:y,threshold:pm(v,e),reason:k}]));if(!e)return{stateDigest:o,decisions:s("off","decide is not configured")};let i=yS(e),a=bS(e),c={backend:e.backend,url:i,model:a,stateDigest:o};if(!i)return{...c,decisions:s("unavailable","decide.url is not set")};let u={"content-type":"application/json",accept:"application/json"};if(e.token_env){let y=(r.env??process.env)[e.token_env];if(!y)return{...c,decisions:s("unavailable",`${e.token_env} is not set`)};u.authorization=`Bearer ${y}`}let l=Object.fromEntries(Object.entries(n).map(([y,k])=>[y,{type:k.type,instructions:k.instructions,criteria:k.criteria}])),p=JSON.stringify({...a?{model:a}:{},state:t,questions:l}),f=r.fetch??globalThis.fetch,g=Date.now(),m,d;try{let y=await f(i,{method:"POST",headers:u,body:p,signal:AbortSignal.timeout(r.timeoutMs??3e4)});if(m=y.status,d=await y.text(),!y.ok){let k=d.slice(0,300);try{let P=JSON.parse(d),v=P.detail??P.error;v!==void 0&&(k=typeof v=="string"?v:JSON.stringify(v))}catch{}return{...c,ms:Date.now()-g,decisions:s("unavailable",`the service answered HTTP ${m}: ${k}`)}}}catch(y){return{...c,ms:Date.now()-g,decisions:s("unavailable",`the service did not answer: ${y.message}`)}}let h=Date.now()-g,b;try{b=JSON.parse(d)}catch{return{...c,ms:h,decisions:s("unavailable","the response is not JSON")}}if(b===null||typeof b!="object"||b.answers===null||typeof b.answers!="object")return{...c,ms:h,decisions:s("unavailable","the response has no answers")};let $=typeof b.model=="string"?b.model:void 0;if(mS[e.backend]&&a&&$!==a)return{...c,model:$??a,ms:h,decisions:s("unavailable",`the service answered as ${$??"no model"}, and decide.model pins ${a}`)};let w=b.answers,R=Object.fromEntries(Object.entries(n).map(([y,k])=>[y,wS(y,k,w[y],pm(k,e))]));return{...c,model:$??a,ms:h,decisions:R}}function Zr(e){return e?.status==="confident"}function vn(e,t){let n=t.model?`, ${t.model}`:"";if(e.status==="off")return`${e.question}: not asked; decide is not configured`;if(e.status==="unavailable")return`${e.question}: no decision; ${e.reason}`;let r=(e.probability??0).toFixed(2),o=e.threshold.toFixed(2);return e.status==="confident"?`${e.question}: ${e.answer} (${r}, threshold ${o}${n})`:`${e.question}: ${e.answer} (${r}, below the ${o} threshold, so not used${n})`}var Du={type:"noul",instructions:"Read the pull request's title and description, then the summary of what its plan does. Does the description leave out or misstate a destroy or a replacement the plan makes?",criteria:{true:"the description does not describe the plan: a destroy or replacement in the plan is missing from it or contradicted by it",false:"the description describes what the plan does, including every destroy and replacement"}},Gu={type:"choice",instructions:"An attribute of a resource managed by Terraform changed outside Terraform. From the resource, the attribute and the two values, which kind of change is it most likely?",criteria:{controller:"a controller or the cloud service wrote it, as autoscaling, a scheduler or a service that adds tags does",human:"a person edited it by hand in a console or with a CLI","provider-default":"the provider or the cloud API changed a default value it reports"}},$S={type:"choice",instructions:"These are the commits to a Terraform module since its last release, and a summary of its diff. Which semantic version bump does the change call for?",criteria:{major:"a breaking change: a variable or output removed or renamed, a required variable added, or a resource replaced for every caller",minor:"a new feature that keeps every existing caller working, such as a new optional variable, output or resource",patch:"a fix or an internal change that callers do not see"}},dm={prIntent:Du,driftActor:Gu,versionBump:$S};var Jo=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),er=e=>typeof e=="number",kS=/^(aws:|eks:|elasticbeanstalk:|kubernetes\.io\/|k8s\.io\/|karpenter\.sh\/)/;function _S(e){let t=Jo(e.before)?e.before:{},n=Jo(e.live)?e.live:{},r=[...new Set([...Object.keys(t),...Object.keys(n)])].filter(o=>JSON.stringify(t[o]??null)!==JSON.stringify(n[o]??null));return r.length>0&&r.every(o=>kS.test(o))}var RS=e=>t=>{let n=Array.isArray(t.before)?t.before[0]:t.before,r=Array.isArray(t.live)?t.live[0]:t.live;if(!Jo(n)||!Jo(r))return!1;let o=[...new Set([...Object.keys(n),...Object.keys(r)])].filter(s=>JSON.stringify(n[s]??null)!==JSON.stringify(r[s]??null));return o.length>0&&o.every(s=>s===e)},xS=[{type:"aws_ecs_service",path:"desired_count",actor:"controller",why:"Application Auto Scaling sets an ECS service's task count",when:e=>er(e.live)},{type:"aws_autoscaling_group",path:"desired_capacity",actor:"controller",why:"the group's scaling policies and scheduled actions set its desired capacity",when:e=>er(e.live)},{type:"aws_eks_node_group",path:"scaling_config",actor:"controller",why:"Cluster Autoscaler or Karpenter sets a node group's desired size",when:RS("desired_size")},{type:"aws_dynamodb_table",path:"read_capacity",actor:"controller",why:"Application Auto Scaling adjusts a provisioned table's read capacity",when:e=>er(e.live)},{type:"aws_dynamodb_table",path:"write_capacity",actor:"controller",why:"Application Auto Scaling adjusts a provisioned table's write capacity",when:e=>er(e.live)},{type:"aws_appautoscaling_target",path:"min_capacity",actor:"controller",why:"a scheduled action on the scalable target moves its minimum",when:e=>er(e.live)},{type:"aws_appautoscaling_target",path:"max_capacity",actor:"controller",why:"a scheduled action on the scalable target moves its maximum",when:e=>er(e.live)},{type:"aws_db_instance",path:"engine_version",actor:"controller",why:"RDS applies minor engine upgrades itself when auto_minor_version_upgrade is on",when:e=>typeof e.live=="string"&&typeof e.before=="string"},{type:"aws_launch_template",path:"latest_version",actor:"controller",why:"a service that publishes a launch template version (EC2 Image Builder, Karpenter) moves latest_version",when:e=>er(e.live)},{type:"aws_elasticache_replication_group",path:"engine_version_actual",actor:"controller",why:"ElastiCache applies minor engine upgrades itself"},...["tags","tags_all"].map(e=>({type:"*",path:e,actor:"controller",why:"a service adds tags in its own namespace (aws:, eks:, karpenter.sh/ and the like)",when:_S}))];function SS(e,t,n){return xS.find(r=>(r.type===e||r.type==="*")&&r.path===t&&(r.when?.(n)??!0))}var ES=/terraform|opentofu|tofu/i;function TS(e){let t=e.userIdentity??{},n=t.invokedBy??e.invokedBy;if(t.type==="AWSService"||n&&/\.amazonaws\.com$/.test(n))return{actor:"controller",who:n??"an AWS service"};let o=(t.arn??"").split("/").at(-1)??"";if(t.type==="IAMUser")return{actor:"human",who:t.userName??o};if(t.type==="Root")return{actor:"human",who:"the root user"};if(t.type==="IdentityCenterUser"||t.type==="FederatedUser")return{actor:"human",who:t.userName??o};if(t.type==="AssumedRole"){let s=t.sessionContext?.sessionIssuer?.userName??"";if(/^AWSReservedSSO_/.test(s)||o.includes("@"))return{actor:"human",who:o||s}}}function AS(e){let n=e.filter(o=>o.readOnly!==!0&&o.readOnly!=="true"&&!ES.test(o.userAgent??"")).sort((o,s)=>String(s.eventTime??"").localeCompare(String(o.eventTime??"")))[0],r=n&&TS(n);return n&&r?{status:"found",...r,event:n.eventName??"an event",at:n.eventTime??""}:{status:"silent"}}function Ti(e={}){let t=e.run??(n=>{let r=vS("aws",n,{encoding:"utf-8",maxBuffer:67108864});return{status:r.status,stdout:r.stdout??"",stderr:r.stderr??"",error:r.error}});return{async lookup(n){if(!n.type.startsWith("aws_"))return{status:"unavailable",reason:`no audit log reader for ${n.type}`};let r=new Date((e.now?.()??new Date).getTime()-(e.days??14)*864e5).toISOString(),o=t(["cloudtrail","lookup-events","--output","json","--max-results","50","--start-time",r,"--lookup-attributes",`AttributeKey=ResourceName,AttributeValue=${n.ref}`,...e.region?["--region",e.region]:[]]);if(o.error?.code==="ENOENT")return{status:"unavailable",reason:"the aws CLI is not installed"};if(o.error||o.status!==0)return{status:"unavailable",reason:(o.error?.message??(o.stderr.trim()||`aws exited ${o.status}`)).split(`
|
|
91
|
+
`),r=new Map,o=new Map,s,i,a=0;for(let c=0;c<n.length;c++){let u=n[c],l=c+1,p=u.trim();i===void 0&&p!==""&&(i=l);let f=xx.exec(u);if(f){let g=f[1],{ids:m,expires:d}=Tx(f[2].trim());if(g==="-file")s={form:"chant-ignore-file",ids:m,expires:d,file:e,line:l,misplaced:l!==i,key:`${e}:${l}:chant-ignore-file`};else{let h=g==="-block"?"chant-ignore-block":"chant-ignore",b={form:h,ids:m,expires:d,file:e,line:l,key:`${e}:${l}:${h}`},$=l+1,w=o.get($)??[];w.push(b),o.set($,w)}}else if(a===0){let g=kx.exec(u);g?bu(r,yu(g[1],[]),l):(g=_x.exec(u))?bu(r,yu(g[1],[g[2]]),l):(g=Rx.exec(u))&&bu(r,yu(g[1],[g[2],g[3]]),l)}a+=Ex(u)}return{blockLineQueues:r,byAnchorLine:o,fileDirective:s}}function Ax(e,t){let n=e.blockLineQueues.get(t);if(!(!n||n.length===0))return n.shift()}function Tg(e,t){let n=Ax(e,t),r=n!==void 0?e.byAnchorLine.get(n)??[]:[],o=e.fileDirective?[...r,e.fileDirective]:r;return{line:n,suppressions:o.length>0?o:void 0}}var Ho="Terraform::Terraform",_u="Terraform::Provider",Ru="Terraform::Resource",xu="Terraform::Data",Ht="Terraform::Module",Ox="Terraform::Variable",Cx="Terraform::Output",jx="Terraform::Locals",$u="Terraform::Live",Ix="Terraform::TerragruntConfig",Su="Terraform::TerragruntSource",Wo="Terraform::TerragruntDependency",Eu="Terraform::TerragruntRemoteState",Tu="Terraform::TerragruntGenerate",Pg=["root.hcl","terragrunt.hcl"];function Nx(e){return Pg.includes(e)}var vu="estate.chdf.hcl";function Dx(e){if("count"in e)return"count";if("for_each"in e)return"for_each"}function Gx(e,t,n,r,o,s,i="",a,c){let u=Dx(n);return{[Mn]:!0,lexicon:"terraform",entityType:e,kind:"resource",stack:o,props:{address:t,body:n,file:r,root:o,source:i,line:a,...u?{expansion:u}:{},...s?.mode!==void 0?{mode:s.mode}:{},...s?.estate!==void 0?{estate:s.estate}:{},...s?.workspace!==void 0?{workspace:s.workspace}:{},...s?.delete!==void 0?{delete:s.delete}:{},...s?.configEstate!==void 0?{configEstate:s.configEstate}:{},...s?.callers!==void 0&&s.callers.length>0?{callers:s.callers}:{}},suppressions:c}}function Mx(e){return Px(e).filter(t=>t.endsWith(".tf")).sort()}function Lx(e){return Pg.filter(t=>ku(Ri(e,t)))}function Fx(e){let t=Ri(e,vu);if(ku(t))return{name:vu,source:Ag(t,"utf-8")}}function qt(e){return Array.isArray(e)?e:[]}function bn(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)?e:{}}function Ux(e){let t=[];for(let n of qt(e.terraform)){let r=bn(n);for(let o of qt(r.live))t.push(bn(o))}return t}var Au=".terraform.lock.hcl";async function Bx(e,t,n,r,o,s=[]){let i=n??await Eg(),a=new Map,c=s.length>0?`${t}/${s.join("/")}`:t,u=(d,h,b,$,w,R=h)=>{let{line:y,suppressions:k}=Tg(w,R),P=Gx(d,h,typeof b=="object"&&b!==null?b:{},$.name,t,{...r?.workspace!==void 0?{workspace:r.workspace}:{},...r?.delete!==void 0?{delete:r.delete}:{},...r?.configEstate!==void 0?{configEstate:r.configEstate}:{},...s.length>0?{callers:s}:{}},$.source,y,k),v=`${c}/${h}`;for(let S=2;a.has(v);S++)v=`${c}/${h}~${S}`;a.set(v,P)},l=(d,h,b,$,w)=>{for(let R of qt(d[h]))u(b,h,R,$,w)},p=(d,h,b,$,w,R)=>{for(let[y,k]of Object.entries(bn(d[h])))for(let P of qt(k))u(b,$(y),P,w,R)},f=(d,h,b,$,w,R)=>{for(let[y,k]of Object.entries(bn(d[h])))for(let[P,v]of Object.entries(bn(k)))for(let S of qt(v))u(b,$(y,P),S,w,R)};for(let d of e){let h=await i.parse(d.name,d.source),b=wu(d.name,d.source);if(Nx(d.name)){qx(h,d,b,u);continue}l(h,"terraform",Ho,d,b);for(let $ of Ux(h))u($u,"live",$,d,b);l(h,"locals",jx,d,b),p(h,"provider",_u,$=>`provider.${$}`,d,b),p(h,"module",Ht,$=>`module.${$}`,d,b),p(h,"variable",Ox,$=>`var.${$}`,d,b),p(h,"output",Cx,$=>`output.${$}`,d,b),f(h,"resource",Ru,($,w)=>`${$}.${w}`,d,b),f(h,"data",xu,($,w)=>`data.${$}.${w}`,d,b)}if(o){let d=await i.parse(o.name,o.source);typeof d.estate=="string"&&u($u,"live",d,o,wu(o.name,o.source))}let g;for(let d of a.values()){if(d.entityType!==$u)continue;let h=d.props,b=typeof h.body.estate=="string"?h.body.estate:void 0;b!==void 0&&(g===void 0||h.file===vu)&&(g=b)}let m=r?.binary==="choudoufu"&&g!==void 0?"live":"state";for(let[d,h]of a){let b=h,$={...b,props:{...b.props,mode:m,...m==="live"?{estate:g}:{},...r?.lockFile!==void 0?{lockFile:r.lockFile}:{}}};a.set(d,$)}return a}function qx(e,t,n,r){let o={includes:Object.keys(bn(e.include))};typeof e.terragrunt_version_constraint=="string"&&(o.terragrunt_version_constraint=e.terragrunt_version_constraint),r(Ix,"terragrunt",o,t,n,"");for(let s of qt(e.terraform))r(Su,"terragrunt.terraform",s,t,n,"terraform");for(let s of qt(e.remote_state))r(Eu,"remote_state",s,t,n);for(let[s,i]of Object.entries(bn(e.dependency)))for(let a of qt(i))r(Wo,`dependency.${s}`,a,t,n);for(let[s,i]of Object.entries(bn(e.generate)))for(let a of qt(i))r(Tu,`generate.${s}`,a,t,n)}async function Pu(e,t,n,r,o=[]){let i=[...Mx(e),...o.length===0?Lx(e):[]].map(u=>({name:u,source:Ag(Ri(e,u),"utf-8")})),a=o.length===0?Fx(e):void 0,c=o.length===0?{...r,lockFile:ku(Ri(e,Au))}:r;return Bx(i,t,n,c,a,o)}function Hx(e){if(!z(e))return[];let t=e.props;return Array.isArray(t.callers)?t.callers:[]}function Qn(e){return Hx(e).length===0}var Ou=new Set(["terraform"]);function Og(e){return e.split("_")[0]}function Cu(e,t){let n=new Map,r=new Set;for(let[o,s]of e){if(!z(s)||!Qn(s))continue;let i=s.props;if(i.root!==t)continue;let a=typeof i.address=="string"?i.address:"";if(s.entityType===_u){let c=a.slice(9);if(Ou.has(c))continue;n.set(c,o),r.add(c)}else if(s.entityType===Ru){let c=a.split(".")[0]??"",u=Og(c);if(Ou.has(u)||r.has(u))continue;n.has(u)||n.set(u,o)}else if(s.entityType===xu){let c=a.split(".")[1]??"",u=Og(c);if(Ou.has(u)||r.has(u))continue;n.has(u)||n.set(u,o)}}return n}function Wx(e){let t={},n=e.required_providers;if(!Array.isArray(n))return t;for(let r of n)typeof r=="object"&&r!==null&&Object.assign(t,r);return t}function Jx(e){if(e===void 0)return"no `required_providers` entry";if(typeof e=="string")return"the legacy shorthand form (a bare version string), which carries no `source`";if(typeof e!="object"||e===null)return"an entry that is not a valid object";let t=e,n=[];return(typeof t.source!="string"||t.source==="")&&n.push("`source`"),(typeof t.version!="string"||t.version==="")&&n.push("`version`"),n.length>0?`an entry missing ${n.join(" and ")}`:void 0}var Cg={id:"TF002",description:"A provider the root implies has no required_providers entry, or an incomplete one",check(e){let t=[],n=new Set,r=new Map;for(let o of e.entities.values()){if(o.entityType!==Ho||!z(o)||!Qn(o))continue;let s=o.props,i=typeof s.root=="string"?s.root:"";n.add(i);let a=typeof s.body=="object"&&s.body!==null?s.body:{},c=r.get(i)??{};Object.assign(c,Wx(a)),r.set(i,c)}for(let o of[...n].sort()){let s=r.get(o)??{},i=Cu(e.entities,o);for(let a of[...i.keys()].sort()){let c=Jx(s[a]);c&&t.push({checkId:"TF002",severity:"warning",message:`Provider "${a}" is used in root module "${o}" but has ${c} in \`required_providers\`. Add \`source\` and \`version\` for it in the terraform block's \`required_providers\`, so a future provider release can't silently change behavior.`,entity:i.get(a),lexicon:"terraform"})}}return t}};function Kx(e){return typeof e.required_version=="string"&&e.required_version.trim()!==""}var jg={id:"TF003",description:"Root module's terraform block has no required_version",check(e){let t=[],n=new Map;for(let[r,o]of e.entities){if(o.entityType!==Ho||!z(o)||!Qn(o))continue;let s=o.props,i=typeof s.root=="string"?s.root:"",a=typeof s.body=="object"&&s.body!==null?s.body:{},c=n.get(i),u=(c?.hasVersion??!1)||Kx(a);n.set(i,{anchor:c?.anchor??r,hasVersion:u})}for(let[r,{anchor:o,hasVersion:s}]of[...n].sort(([i],[a])=>i.localeCompare(a)))s||t.push({checkId:"TF003",severity:"warning",message:`Root module "${r}"'s terraform block has no \`required_version\`. Without it, an upgraded Terraform binary can change behavior with no version-check failure to flag it first. Add \`required_version = ">= <lowest supported version>"\`.`,entity:o,lexicon:"terraform"});return t}};var Ig=/^[0-9A-Za-z](?:[0-9A-Za-z_-]{0,62}[0-9A-Za-z])?$/,zx=/^[0-9a-z]{1,64}$/,Vx=new Set(["github.com","bitbucket.org"]);function Ng(e){let t=new Map;for(let n of e.split("&")){if(n==="")continue;let r=n.indexOf("="),o=r===-1?n:n.slice(0,r),s=r===-1?"":decodeURIComponent(n.slice(r+1));t.set(decodeURIComponent(o),s)}return t}function Dg(e){let t=e.indexOf("://"),n=t===-1?0:t+3,r=e.indexOf("//",n);return r===-1?e:e.slice(0,r)}function Yx(e){let t=Dg(e).split("/"),n,r,o;if(t.length===3)[n,r,o]=t;else if(t.length===4){let s=t[0];if(!s.includes(".")||Vx.has(s.toLowerCase()))return!1;[,n,r,o]=t}else return!1;return Ig.test(n)&&Ig.test(r)&&zx.test(o)}function Xx(e){if(/^[\w.-]+@[^:/]+:/.test(e))return!0;let t=e.replace(/^[a-z][a-z0-9+.-]*:\/\//i,"");return!!(/^(github\.com|bitbucket\.org)\//i.test(t)||/\.git($|[/?])/i.test(t))}function Zx(e,t){let n=Ng(t),r=Dg(e).replace(/^oci:\/\//i,""),o=n.get("tag")||void 0,s=n.get("digest")||void 0,i=r.indexOf("@");i!==-1&&(s=s??(r.slice(i+1)||void 0),r=r.slice(0,i));let a=r.lastIndexOf("/"),c=r.indexOf(":",a+1);return c!==-1&&(o=o??(r.slice(c+1)||void 0)),{kind:"oci",...o!==void 0?{tag:o}:{},...s!==void 0?{digest:s}:{}}}function _t(e){let t=e.trim();if(t.startsWith("./")||t.startsWith("../")||t.startsWith(".\\")||t.startsWith("..\\"))return{kind:"local"};let n=t.indexOf("?"),r=n===-1?t:t.slice(0,n),o=n===-1?"":t.slice(n+1);if(/^oci:\/\//i.test(r))return Zx(r,o);let s=/^(git|hg)::/i.exec(r),i=s?r.slice(s[0].length):r;if(s||Xx(i)){let a=Ng(o);return{kind:"git",ref:a.get("ref")||a.get("rev")||void 0}}return Yx(i)?{kind:"registry"}:{kind:"other"}}var Qx=/^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/,eS=/^[0-9a-f]{40}$/i,tS=new Set(["main","master","develop","trunk"]);function Gg(e){return Qx.test(e)||eS.test(e)}function Mg(e){return tS.has(e)}var nS=new Set(["latest","main","master","develop","dev","stable","edge","nightly"]);function Lg(e){return nS.has(e.toLowerCase())}function Fg(e){return/^(?:=\s*)?v?\d+(?:\.\d+){0,2}(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(e.trim())&&!e.includes(",")}var Ug={id:"TF004",description:"Registry-sourced module block has no version",check(e){let t=[];for(let[n,r]of e.entities){if(r.entityType!==Ht||!z(r))continue;let o=r.props,s=typeof o.body=="object"&&o.body!==null?o.body:{},i=typeof s.source=="string"?s.source:void 0;if(i===void 0||_t(i).kind!=="registry")continue;let a=s.version;if(typeof a=="string"&&a.trim()!=="")continue;let c=typeof o.address=="string"?o.address:n;t.push({checkId:"TF004",severity:"warning",message:`Module "${c}" sources "${i}" from a registry with no \`version\`. \`terraform init\` resolves to the newest release with no constraint to hold it back. Add a \`version\` constraint (e.g. \`~> 5.0\`).`,entity:n,lexicon:"terraform"})}return t}};var Bg={id:"TF005",description:"Git/hg module source is unpinned, or pinned to a mutable ref",check(e){let t=[];for(let[n,r]of e.entities){if(r.entityType!==Ht||!z(r))continue;let o=r.props,s=typeof o.body=="object"&&o.body!==null?o.body:{},i=typeof s.source=="string"?s.source:void 0;if(i===void 0)continue;let a=_t(i);if(a.kind!=="git")continue;let c=typeof o.address=="string"?o.address:n,u;a.ref?Mg(a.ref)?u=`Module "${c}" sources "${i}" pinned to "${a.ref}", a mutable branch. Pin to a tag or a full commit SHA instead.`:Gg(a.ref)||(u=`Module "${c}" sources "${i}" pinned to "${a.ref}", which is neither a tag nor a full commit SHA and can move. Pin to a tag or a full commit SHA instead.`):u=`Module "${c}" sources "${i}" from git/hg with no \`?ref=\`. Add one pinned to a tag or a full commit SHA.`,u&&t.push({checkId:"TF005",severity:"warning",message:u,entity:n,lexicon:"terraform"})}return t}};var qg={id:"TF038",description:"OCI module source has no tag or digest, or a mutable tag",check(e){let t=[];for(let[n,r]of e.entities){if(r.entityType!==Ht||!z(r))continue;let o=r.props,s=typeof o.body=="object"&&o.body!==null?o.body:{},i=typeof s.source=="string"?s.source:void 0;if(i===void 0)continue;let a=_t(i);if(a.kind!=="oci"||a.digest)continue;let c=typeof o.address=="string"?o.address:n,u;a.tag?Lg(a.tag)&&(u=`Module "${c}" sources "${i}" with the mutable tag "${a.tag}". Pin it to a digest (\`?digest=sha256:...\`) or an exact version tag (\`?tag=1.4.0\`).`):u=`Module "${c}" sources "${i}" with no tag or digest, so OpenTofu pulls the \`latest\` tag. Pin it to a digest (\`?digest=sha256:...\`) or an exact version tag (\`?tag=1.4.0\`).`,u&&t.push({checkId:"TF038",severity:"warning",message:u,entity:n,lexicon:"terraform"})}return t}};var Hg={id:"TF039",description:"Registry module version is a range, not an exact version",check(e){let t=[];for(let[n,r]of e.entities){if(r.entityType!==Ht||!z(r))continue;let o=r.props,s=typeof o.body=="object"&&o.body!==null?o.body:{},i=typeof s.source=="string"?s.source:void 0;if(i===void 0||_t(i).kind!=="registry")continue;let a=s.version;if(typeof a!="string"||a.trim()===""||Fg(a))continue;let c=typeof o.address=="string"?o.address:n;t.push({checkId:"TF039",severity:"warning",message:`Module "${c}" sources "${i}" with the version range "${a}". A release inside the range changes what the root provisions with no diff to review. Pin an exact version (\`version = "1.4.0"\`).`,entity:n,lexicon:"terraform"})}return t}};var Wg={id:"TF040",description:"Root has no committed .terraform.lock.hcl",auditOptIn:!0,check(e){let t=[],n=new Set;for(let r of e.entities.values()){if(!z(r)||!Qn(r))continue;let o=r.props;o.lockFile!==!1||o.mode==="live"||typeof o.root!="string"||n.add(o.root)}for(let r of[...n].sort()){let o=Cu(e.entities,r);if(o.size===0)continue;let s=[...o.keys()].sort();t.push({checkId:"TF040",severity:"warning",message:`Root module "${r}" uses ${s.length===1?"provider":"providers"} ${s.map(i=>`"${i}"`).join(", ")} but has no ${Au} beside its .tf files. Run \`terraform init\` and commit the file, so every init selects the same provider versions.`,entity:o.get(s[0]),lexicon:"terraform"})}return t}};function Wt(e,t){let n=[];for(let[r,o]of e){if(o.entityType!==t||!z(o))continue;let s=o.props;n.push({key:r,address:typeof s.address=="string"?s.address:r,file:typeof s.file=="string"?s.file:"terragrunt.hcl",body:typeof s.body=="object"&&s.body!==null?s.body:{}})}return n}function Jg(e){return typeof e=="string"&&e.includes("${")}function Kg(e){let t=e.trim();if(t.includes("${"))return{kind:"other"};if(/^tfr:\/\//i.test(t)){let r=t.includes("?")?t.slice(t.indexOf("?")+1):"",o=new URLSearchParams(r).get("version");return{kind:"tfr",pinned:o!==null&&o.trim()!==""}}if(t.startsWith("/"))return{kind:"local"};let n=_t(t);switch(n.kind){case"local":return{kind:"local"};case"git":return{kind:"git",pinned:n.ref!==void 0};case"oci":return{kind:"oci",pinned:n.tag!==void 0||n.digest!==void 0};default:return{kind:"other"}}}var zg={id:"TF041",description:"Terragrunt dependency lets mock_outputs stand in for apply",check(e){let t=[];for(let n of Wt(e.entities,Wo)){if(n.body.mock_outputs===void 0)continue;let r=n.body.mock_outputs_allowed_terraform_commands;if(Jg(r))continue;let o;r===void 0||Array.isArray(r)&&r.length===0?o="sets no `mock_outputs_allowed_terraform_commands`, and Terragrunt reads a missing or empty list as every command":Array.isArray(r)&&r.includes("apply")&&(o='lists "apply" in `mock_outputs_allowed_terraform_commands`'),o!==void 0&&t.push({checkId:"TF041",severity:"warning",message:`Dependency "${n.address.replace(/^dependency\./,"")}" in ${n.file} sets \`mock_outputs\` and ${o}. An apply that runs before the upstream unit is applied writes the mock values to real state. Set \`mock_outputs_allowed_terraform_commands = ["validate", "plan"]\`.`,entity:n.key,lexicon:"terraform"})}return t}};var Vg={id:"TF042",description:"Terragrunt dependency sets skip_outputs together with mock_outputs",check(e){let t=[];for(let n of Wt(e.entities,Wo))n.body.skip_outputs!==!0||n.body.mock_outputs===void 0||t.push({checkId:"TF042",severity:"warning",message:`Dependency "${n.address.replace(/^dependency\./,"")}" in ${n.file} sets \`skip_outputs = true\` with \`mock_outputs\`. Terragrunt then never reads the upstream unit's outputs, so every plan and apply runs on the mock values. Drop \`skip_outputs\`, or pass the values as \`inputs\` so they are visible as configuration.`,entity:n.key,lexicon:"terraform"});return t}};var rS={git:"Add `?ref=` with a tag or commit SHA.",tfr:"Add `?version=` with an exact version.",oci:"Add `?digest=sha256:...` or `?tag=` with an exact version tag."},Yg={id:"TF043",description:"Terragrunt terraform.source names no version (git ref, tfr version or oci tag)",check(e){let t=[];for(let n of Wt(e.entities,Su)){let r=n.body.source;if(typeof r!="string")continue;let o=Kg(r);o.pinned===!1&&t.push({checkId:"TF043",severity:"warning",message:`The \`terraform\` block in ${n.file} sources "${r}" with no version, so a push or a release changes the unit with no diff to review. `+rS[o.kind],entity:n.key,lexicon:"terraform"})}return t}};var oS=/\bbackend\s+"local"/,Xg={id:"TF044",description:"Terragrunt config keeps state in a local backend",check(e){let t=[],n=(r,o,s)=>{t.push({checkId:"TF044",severity:"warning",message:`${s} in ${o} keeps state in a local backend. The state lives on the runner's disk, so a later job cannot read it and a git-range run, which checks out each side in a temporary directory, never sees it. Use a shared backend such as S3 or GCS.`,entity:r,lexicon:"terraform"})};for(let r of Wt(e.entities,Eu))r.body.backend==="local"&&n(r.key,r.file,'`remote_state` sets `backend = "local"`');for(let r of Wt(e.entities,Tu)){let o=r.body.contents;typeof o=="string"&&oS.test(o)&&n(r.key,r.file,`\`generate "${r.address.replace(/^generate\./,"")}"\` writes a \`backend "local"\` block`)}return t}};var Qg={TF002:Cg,TF003:jg,TF004:Ug,TF005:Bg,TF038:qg,TF039:Hg,TF040:Wg,TF041:zg,TF042:Vg,TF043:Yg,TF044:Xg},sS="https://intentius.io/chant/lexicons/terraform/lint-rules/",iS="https://intentius.io/terragucci/reference/tips/",aS=10,cS=5,uS=100,em=(e,t,n)=>({rule:e,root:t,message:n,url:`${sS}#${e.toLowerCase()}`}),Yr=(e,t,n)=>({rule:e,...n!==void 0?{root:n}:{},message:t,url:`${iS}#${e}`});async function om(e,t,n){let r=[];if(!tm(wn(e,t)))return r;for(let o of nm(wn(e,t)).filter(s=>s.endsWith(".tf")).sort()){let s=await n.parse(o,rm(wn(e,t,o),"utf-8"));for(let i of Object.values(s.module??{}))for(let a of i)typeof a.source=="string"&&r.push({source:a.source,...typeof a.version=="string"?{version:a.version}:{}})}return r}async function lS(e,t,n){let r=[];if(!tm(wn(e,t)))return r;for(let o of nm(wn(e,t)).filter(s=>s.endsWith(".tf")).sort()){let s=await n.parse(o,rm(wn(e,t,o),"utf-8"));for(let i of s.terraform??[])for(let a of i.required_providers??[])for(let[c,u]of Object.entries(a)){let l=u?.version;typeof l=="string"&&Mo(l)===null&&r.push({name:c,version:l})}}return r}async function sm(e,t,n,r=new Set){let o=new Set;if(r.has(t))return o;r.add(t);for(let{source:s}of await om(e,t,n)){if(!/^\.\.?\//.test(s))continue;let i=Zg.normalize(Zg.join(t==="."?"":t,s))||".";o.add(i);for(let a of await sm(e,i,n,r))o.add(a)}return o}async function xi(e,t,n){if(n.settings.tips===!1)return[];let r=[],{parser:o}=n;if(o){let a=(n.rules??Object.keys(Qg)).map(l=>Qg[l]).filter(l=>l!==void 0),c=new Set,u=new Map;for(let l of n.configDirs??[]){let p=await Pu(wn(e,l),l,o),f={outputs:new Map,entities:p};for(let g of a)for(let m of await g.check(f))r.push(em(g.id,l,m.message))}for(let l of t){let p=await Pu(wn(e,l),l,o),f={outputs:new Map,entities:p};for(let g of a)for(let m of await g.check(f))r.push(em(g.id,l,m.message)),(g.id==="TF038"||g.id==="TF039")&&c.add(l);for(let g of await sm(e,l,o))u.set(g,(u.get(g)??new Set).add(l));for(let{name:g,version:m}of await lS(e,l,o))r.push(Yr("terragucci-floating-range",`Provider ${g} is constrained to the range "${m}". tf-rollout --provider moves the lock file, and a version outside the range fails init, so a rollout cannot cross it. Pin the exact version, or widen the range on purpose.`,l));if(!c.has(l))for(let{source:g,version:m}of await om(e,l,o))Jn(g,m).unpinned?.includes("is a constraint")&&r.push(Yr("terragucci-floating-range",`${g} is pinned to the range "${m}". A range cannot be bumped by a pin, so tf-rollout cannot move it in waves. Pin one version.`,l))}for(let[l,p]of[...u].sort(([f],[g])=>f<g?-1:1))p.size<aS||r.push(Yr("terragucci-shared-module",`${l} is included by ${p.size} roots, so every change under it plans every one of them. Publish it with tf-publish and pin it in each root, and only the roots whose pin moves are planned.`,l))}t.length>cS&&(n.settings.waves?.canary??[]).length===0&&r.push(Yr("terragucci-no-canary",`The project has ${t.length} roots and no waves.canary, so the first wave is chosen by apply order. Name one or two low-risk roots there, since the first wave is the riskiest.`));let s=n.destroying??[];if(n.settings.gate==="never"&&s.length>0)for(let a of[...s].sort())r.push(Yr("terragucci-ungated-destroy",`${a} plans a destroy or replacement and gate is never, so nothing stops it but the forge's own reviewers. Use gate: on-destroy.`,a));let i=n.planned??t.length;return i>=uS&&r.push(Yr("terragucci-many-roots",`${i} roots are planned on every change. Publishing shared modules and pinning them in each root shrinks the set a change reaches.`)),r}function Si(e){return e.map(t=>`tip (${t.rule}): ${t.root?`${t.root}: `:""}${t.message}`)}import{spawnSync as vS}from"node:child_process";import{createHash as dS}from"node:crypto";var ju={"ghcr.io/intentius/terragucci-tofu:0.4.1-tofu1.13.1":"sha256:f7b2c7b8148bbfa6e1410a8926bb1fdeeaca16ed4f3e5959a9884cb823d4a704","ghcr.io/intentius/terragucci-terraform:0.4.1-tf1.14.0":"sha256:84e0d24d711ba56e4c0428276f090c6ccb1bb1dd50d52d7bb604bd6b5d770817","ghcr.io/intentius/terragucci-terragrunt:0.4.1-tg1.1.6-tofu1.13.1":"sha256:94b537a69cb45f080ac0cd2ecb64fc23bb6da3011c58bdc1ae256c54fd64b0e3","ghcr.io/intentius/terragucci-choudoufu:0.4.1-choudoufu0.23.0":"sha256:fede26da45a6c59405fdb0e25c22d46e99ba0ea76ca04afee8e09c3bea057fb8"};var im="ghcr.io/intentius",$n={tofu:"1.13.1",terraform:"1.14.0",terragrunt:"1.1.6",choudoufu:"0.23.0"};function am(e,t=Kr){return e==="terragrunt"?`${t}-tg${$n.terragrunt}-tofu${$n.tofu}`:`${t}-${e==="terraform"?"tf":e}${$n[e]}`}function cm(e,t=ju){let n=`${im}/terragucci-${e}`,r=am(e);return{repository:n,tag:r,digest:t[`${n}:${r}`]}}function um(e=ju){let t=`${im}/terragucci-terragrunt`,n=am("terragrunt");return{repository:t,tag:n,digest:e[`${t}:${n}`]}}function Nu(e){return e.digest?`${e.repository}:${e.tag}@${e.digest}`:`${e.repository}:${e.tag}`}var Iu={laya:"0.3.28",torch:"2.14.0",pip:"26.2.1",checkpoint:{repo:"convaiinnovations/laya",name:"english",revision:"55cf4c4ebb4ebe31b2550e8bdf3bd21b99753851",weightsSha256:"891102d372688fc2a094dac56a384bc537b87c63f21f9f3dac0be2b7cbc8d86c"},port:8790},lm=`laya-${Iu.laya}-${Iu.checkpoint.name}@${Iu.checkpoint.revision.slice(0,12)}`;var fS="https://api.typesafe.ai",gS={noul:.8,choice:.7,score:.7},mS={laya:!0,jev:!0,von:!1,decider:!1};function hS(e,t){let n=r=>{throw new Error(`decide: question ${e} ${r}`)};if(/^[A-Za-z0-9_-]{1,64}$/.test(e)||n("needs a name of letters, digits, - and _"),t.instructions||n("has no instructions"),t.threshold!==void 0&&!(t.threshold>0&&t.threshold<=1)&&n("has a threshold outside (0, 1]"),t.type==="noul")(!t.criteria?.true||!t.criteria?.false)&&n("is a noul and needs criteria.true and criteria.false");else if(t.type==="choice"){let r=Object.keys(t.criteria??{}).length;(r<2||r>255)&&n(`is a choice with ${r} options; use 2 to 255`)}else if(t.type==="score"){let r=t.criteria?.length??0;(r<2||r>10)&&n(`is a score with ${r} levels; use 2 to 10`)}else n(`has type ${JSON.stringify(t.type)}; use noul, choice or score`)}function pm(e,t){return e.threshold??t?.thresholds?.[e.type]??gS[e.type]}var Ei=e=>typeof e=="number"&&Number.isFinite(e)&&e>=0&&e<=1;function yS(e){let t=e.url??(e.backend==="jev"?fS:void 0);if(!t)return;let n=t.replace(/\/+$/,"");return n.endsWith("/v1/systemone")?n:`${n}/v1/systemone`}function bS(e){return e.model??(e.backend==="laya"?lm:void 0)}function wS(e,t,n,r){let o={question:e,type:t.type,threshold:r},s=p=>({...o,status:"unavailable",reason:p});if(n===null||typeof n!="object")return s("the response has no answer for it");let i=n;if(i.type!==t.type)return s(`the answer's type is ${JSON.stringify(i.type)}, not ${t.type}`);let a,c,u,l;if(t.type==="noul"){if(!Ei(i.noul))return s("the noul answer is not a probability");a=i.noul>=.5?"true":"false",c=Math.max(i.noul,1-i.noul),u={true:i.noul,false:1-i.noul}}else{let p=i.probabilities;if(p===null||typeof p!="object"||!Object.values(p).every(Ei))return s("the answer has no probabilities");if(u=p,t.type==="choice"){if(typeof i.choice!="string"||!(i.choice in t.criteria))return s(`the choice ${JSON.stringify(i.choice)} is not one of the options`);if(!Ei(u[i.choice]))return s("the chosen option has no probability");a=i.choice,c=u[i.choice]}else{let f=t.criteria.map((m,d)=>String(d));if(!f.every(m=>Ei(u[m])))return s("the score's probabilities do not cover every level");let g=f.reduce((m,d)=>u[d]>u[m]?d:m,f[0]);a=t.criteria[Number(g)],c=u[g],typeof i.score=="number"&&Number.isFinite(i.score)&&(l=i.score)}}return{...o,status:c>=r?"confident":"not-confident",answer:a,probability:c,probabilities:u,...l===void 0?{}:{score:l}}}async function Xr(e,t,n,r={}){for(let[y,k]of Object.entries(n))hS(y,k);let o=`sha256:${dS("sha256").update(JSON.stringify(t)).digest("hex")}`,s=(y,k)=>Object.fromEntries(Object.entries(n).map(([P,v])=>[P,{question:P,type:v.type,status:y,threshold:pm(v,e),reason:k}]));if(!e)return{stateDigest:o,decisions:s("off","decide is not configured")};let i=yS(e),a=bS(e),c={backend:e.backend,url:i,model:a,stateDigest:o};if(!i)return{...c,decisions:s("unavailable","decide.url is not set")};let u={"content-type":"application/json",accept:"application/json"};if(e.token_env){let y=(r.env??process.env)[e.token_env];if(!y)return{...c,decisions:s("unavailable",`${e.token_env} is not set`)};u.authorization=`Bearer ${y}`}let l=Object.fromEntries(Object.entries(n).map(([y,k])=>[y,{type:k.type,instructions:k.instructions,criteria:k.criteria}])),p=JSON.stringify({...a?{model:a}:{},state:t,questions:l}),f=r.fetch??globalThis.fetch,g=Date.now(),m,d;try{let y=await f(i,{method:"POST",headers:u,body:p,signal:AbortSignal.timeout(r.timeoutMs??3e4)});if(m=y.status,d=await y.text(),!y.ok){let k=d.slice(0,300);try{let P=JSON.parse(d),v=P.detail??P.error;v!==void 0&&(k=typeof v=="string"?v:JSON.stringify(v))}catch{}return{...c,ms:Date.now()-g,decisions:s("unavailable",`the service answered HTTP ${m}: ${k}`)}}}catch(y){return{...c,ms:Date.now()-g,decisions:s("unavailable",`the service did not answer: ${y.message}`)}}let h=Date.now()-g,b;try{b=JSON.parse(d)}catch{return{...c,ms:h,decisions:s("unavailable","the response is not JSON")}}if(b===null||typeof b!="object"||b.answers===null||typeof b.answers!="object")return{...c,ms:h,decisions:s("unavailable","the response has no answers")};let $=typeof b.model=="string"?b.model:void 0;if(mS[e.backend]&&a&&$!==a)return{...c,model:$??a,ms:h,decisions:s("unavailable",`the service answered as ${$??"no model"}, and decide.model pins ${a}`)};let w=b.answers,R=Object.fromEntries(Object.entries(n).map(([y,k])=>[y,wS(y,k,w[y],pm(k,e))]));return{...c,model:$??a,ms:h,decisions:R}}function Zr(e){return e?.status==="confident"}function vn(e,t){let n=t.model?`, ${t.model}`:"";if(e.status==="off")return`${e.question}: not asked; decide is not configured`;if(e.status==="unavailable")return`${e.question}: no decision; ${e.reason}`;let r=(e.probability??0).toFixed(2),o=e.threshold.toFixed(2);return e.status==="confident"?`${e.question}: ${e.answer} (${r}, threshold ${o}${n})`:`${e.question}: ${e.answer} (${r}, below the ${o} threshold, so not used${n})`}var Du={type:"noul",instructions:"Read the pull request's title and description, then the summary of what its plan does. Does the description leave out or misstate a destroy or a replacement the plan makes?",criteria:{true:"the description does not describe the plan: a destroy or replacement in the plan is missing from it or contradicted by it",false:"the description describes what the plan does, including every destroy and replacement"}},Gu={type:"choice",instructions:"An attribute of a resource managed by Terraform changed outside Terraform. From the resource, the attribute and the two values, which kind of change is it most likely?",criteria:{controller:"a controller or the cloud service wrote it, as autoscaling, a scheduler or a service that adds tags does",human:"a person edited it by hand in a console or with a CLI","provider-default":"the provider or the cloud API changed a default value it reports"}},$S={type:"choice",instructions:"These are the commits to a Terraform module since its last release, and a summary of its diff. Which semantic version bump does the change call for?",criteria:{major:"a breaking change: a variable or output removed or renamed, a required variable added, or a resource replaced for every caller",minor:"a new feature that keeps every existing caller working, such as a new optional variable, output or resource",patch:"a fix or an internal change that callers do not see"}},dm={prIntent:Du,driftActor:Gu,versionBump:$S};var Jo=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),er=e=>typeof e=="number",kS=/^(aws:|eks:|elasticbeanstalk:|kubernetes\.io\/|k8s\.io\/|karpenter\.sh\/)/;function _S(e){let t=Jo(e.before)?e.before:{},n=Jo(e.live)?e.live:{},r=[...new Set([...Object.keys(t),...Object.keys(n)])].filter(o=>JSON.stringify(t[o]??null)!==JSON.stringify(n[o]??null));return r.length>0&&r.every(o=>kS.test(o))}var RS=e=>t=>{let n=Array.isArray(t.before)?t.before[0]:t.before,r=Array.isArray(t.live)?t.live[0]:t.live;if(!Jo(n)||!Jo(r))return!1;let o=[...new Set([...Object.keys(n),...Object.keys(r)])].filter(s=>JSON.stringify(n[s]??null)!==JSON.stringify(r[s]??null));return o.length>0&&o.every(s=>s===e)},xS=[{type:"aws_ecs_service",path:"desired_count",actor:"controller",why:"Application Auto Scaling sets an ECS service's task count",when:e=>er(e.live)},{type:"aws_autoscaling_group",path:"desired_capacity",actor:"controller",why:"the group's scaling policies and scheduled actions set its desired capacity",when:e=>er(e.live)},{type:"aws_eks_node_group",path:"scaling_config",actor:"controller",why:"Cluster Autoscaler or Karpenter sets a node group's desired size",when:RS("desired_size")},{type:"aws_dynamodb_table",path:"read_capacity",actor:"controller",why:"Application Auto Scaling adjusts a provisioned table's read capacity",when:e=>er(e.live)},{type:"aws_dynamodb_table",path:"write_capacity",actor:"controller",why:"Application Auto Scaling adjusts a provisioned table's write capacity",when:e=>er(e.live)},{type:"aws_appautoscaling_target",path:"min_capacity",actor:"controller",why:"a scheduled action on the scalable target moves its minimum",when:e=>er(e.live)},{type:"aws_appautoscaling_target",path:"max_capacity",actor:"controller",why:"a scheduled action on the scalable target moves its maximum",when:e=>er(e.live)},{type:"aws_db_instance",path:"engine_version",actor:"controller",why:"RDS applies minor engine upgrades itself when auto_minor_version_upgrade is on",when:e=>typeof e.live=="string"&&typeof e.before=="string"},{type:"aws_launch_template",path:"latest_version",actor:"controller",why:"a service that publishes a launch template version (EC2 Image Builder, Karpenter) moves latest_version",when:e=>er(e.live)},{type:"aws_elasticache_replication_group",path:"engine_version_actual",actor:"controller",why:"ElastiCache applies minor engine upgrades itself"},...["tags","tags_all"].map(e=>({type:"*",path:e,actor:"controller",why:"a service adds tags in its own namespace (aws:, eks:, karpenter.sh/ and the like)",when:_S}))];function SS(e,t,n){return xS.find(r=>(r.type===e||r.type==="*")&&r.path===t&&(r.when?.(n)??!0))}var ES=/terraform|opentofu|tofu/i;function TS(e){let t=e.userIdentity??{},n=t.invokedBy??e.invokedBy;if(t.type==="AWSService"||n&&/\.amazonaws\.com$/.test(n))return{actor:"controller",who:n??"an AWS service"};let o=(t.arn??"").split("/").at(-1)??"";if(t.type==="IAMUser")return{actor:"human",who:t.userName??o};if(t.type==="Root")return{actor:"human",who:"the root user"};if(t.type==="IdentityCenterUser"||t.type==="FederatedUser")return{actor:"human",who:t.userName??o};if(t.type==="AssumedRole"){let s=t.sessionContext?.sessionIssuer?.userName??"";if(/^AWSReservedSSO_/.test(s)||o.includes("@"))return{actor:"human",who:o||s}}}function AS(e){let n=e.filter(o=>o.readOnly!==!0&&o.readOnly!=="true"&&!ES.test(o.userAgent??"")).sort((o,s)=>String(s.eventTime??"").localeCompare(String(o.eventTime??"")))[0],r=n&&TS(n);return n&&r?{status:"found",...r,event:n.eventName??"an event",at:n.eventTime??""}:{status:"silent"}}function Ti(e={}){let t=e.run??(n=>{let r=vS("aws",n,{encoding:"utf-8",maxBuffer:67108864});return{status:r.status,stdout:r.stdout??"",stderr:r.stderr??"",error:r.error}});return{async lookup(n){if(!n.type.startsWith("aws_"))return{status:"unavailable",reason:`no audit log reader for ${n.type}`};let r=new Date((e.now?.()??new Date).getTime()-(e.days??14)*864e5).toISOString(),o=t(["cloudtrail","lookup-events","--output","json","--max-results","50","--start-time",r,"--lookup-attributes",`AttributeKey=ResourceName,AttributeValue=${n.ref}`,...e.region?["--region",e.region]:[]]);if(o.error?.code==="ENOENT")return{status:"unavailable",reason:"the aws CLI is not installed"};if(o.error||o.status!==0)return{status:"unavailable",reason:(o.error?.message??(o.stderr.trim()||`aws exited ${o.status}`)).split(`
|
|
92
92
|
`)[0]};let s;try{s=JSON.parse(o.stdout)}catch{return{status:"unavailable",reason:"aws printed no JSON"}}let i=[];for(let a of s.Events??[])try{i.push({eventName:a.EventName,eventTime:a.EventTime,...JSON.parse(a.CloudTrailEvent??"{}")})}catch{}return AS(i)}}}function fm(e,t){return t?"(sensitive)":e==null?null:typeof e=="number"||typeof e=="boolean"?e:typeof e=="string"?`(a string of ${e.length} characters)`:Array.isArray(e)?`(a list of ${e.length})`:Jo(e)?`(a map with keys ${Object.keys(e).slice(0,12).join(", ")})`:"(a value)"}var Ko="attributions.json";async function Ai(e,t,n={}){let r=[],o=[],s=new Map,i;for(let a of t)for(let c of a.attributes){let u={root:e,address:a.address,path:c.path},l=c.sensitive?void 0:SS(a.type,c.path,c);if(l){r.push({...u,actor:l.actor,source:"table",detail:l.why});continue}if(n.audit&&a.ref&&i===void 0){let p=`${a.type}\0${a.ref}`,f=s.get(p);if(f||(f=await n.audit.lookup({type:a.type,address:a.address,ref:a.ref}),s.set(p,f)),f.status==="found"){r.push({...u,actor:f.actor,source:"audit",detail:`${f.event} by ${f.who}${f.at?` at ${f.at}`:""}`});continue}f.status==="unavailable"&&(i=f.reason,o.push(`audit log unavailable: ${f.reason}`))}if(n.decide){let p={resource_type:a.type,attribute:c.path,value_in_code:fm(c.before,c.sensitive),live_value:fm(c.live,c.sensitive)},f=await Xr(n.decide,p,{actor:Gu},n.options),g=f.decisions.actor;if(Zr(g)){r.push({...u,actor:g.answer,source:"model",detail:vn(g,f),probability:g.probability});continue}o.push(`${a.address} ${c.path}: ${vn(g,f)}`)}r.push({...u,source:"unattributed",detail:"neither the known-writes table nor the audit log answered"+(n.decide?", and the decision was not used":"")})}return{attributions:r,notes:o}}var PS={table:"known write",audit:"audit log",model:"model",unattributed:"unattributed"};function gm(e){let t=new Map,n=[];for(let r of e){let o=`\`${r.address}\` \`${r.path}\``;if(r.source==="unattributed"){n.push(`- unattributed: ${o}: ${r.detail}; the default response runs`);continue}let s=`${PS[r.source]}: ${r.detail}`;if(r.actor==="human"){n.push(`- human edit: ${o} (${s}); the pull request codifies it for the owner to accept or revert`);continue}let i=t.get(r.address)??new Set;i.add(r.path),t.set(r.address,i),r.actor==="controller"?n.push(`- controller write: ${o} (${s}); add \`lifecycle { ignore_changes = [${r.path}] }\` to the resource, and leave it out of the code`):n.push(`- provider default change: ${o} (${s}); pin the version that kept the old value, or set \`${r.path}\` to the value you want`)}return{leave:t,lines:n}}function mm(e,t){return e.map(n=>({...n,attributes:n.attributes.filter(r=>!t.get(n.address)?.has(r.path))})).filter((n,r)=>n.attributes.length>0||e[r].attributes.length===0)}import{existsSync as ym,readdirSync as bm,readFileSync as OS}from"node:fs";import{join as CS,posix as jS}from"node:path";var wm=(e,t)=>JSON.stringify(e??null)===JSON.stringify(t??null);function Pi(e){return(e?.resource_drift??[]).filter(n=>n.mode!=="data").map(n=>{let r=n.change.before??{},o=n.change.after??{},s=n.change.actions.includes("delete")?"delete":"update",i=u=>{let l=n.change.before_sensitive?.[u]??n.change.after_sensitive?.[u];return l!==void 0&&l!==!1&&!(typeof l=="object"&&l!==null&&Object.keys(l).length===0)},a=s==="delete"?[]:[...new Set([...Object.keys(r),...Object.keys(o)])].sort().filter(u=>!wm(r[u],o[u])).map(u=>({path:u,before:r[u],live:o[u],...i(u)?{sensitive:!0}:{}})),c=["name","id","function_name","bucket"].map(u=>o[u]??r[u]).find(u=>typeof u=="string"&&u!=="");return{address:n.address,action:s,attributes:a,type:n.type,name:n.name,...c?{ref:c}:{},...n.module_address?{module:n.module_address}:{},...n.index!==void 0?{index:n.index}:{}}})}function IS(e){let t=e.trim().replace(/\s*(#|\/\/).*$/,"");if(/^-?\d+(\.\d+)?$/.test(t))return{value:Number(t)};if(t==="true"||t==="false")return{value:t==="true"};if(/^"(?:[^"\\$%]|\\.|\$(?!\{)|%(?!\{))*"$/.test(t))try{return{value:JSON.parse(t)}}catch{return}}function NS(e){if(typeof e=="number"||typeof e=="boolean")return String(e);if(typeof e=="string")return JSON.stringify(e).replace(/\$\{/g,()=>"$${").replace(/%\{/g,()=>"%%{")}function DS(e){return ym(e)?bm(e).filter(t=>t.endsWith(".tf")||t.endsWith(".tofu")).sort():[]}function hm(e,t,n){let r=new RegExp(`^\\s*resource\\s+"${t}"\\s+"${n}"\\s*\\{`),o=e.findIndex(i=>r.test(i));if(o<0)return;let s=0;for(let i=o;i<e.length;i++){for(let a of e[i].replace(/"(?:[^"\\]|\\.)*"/g,'""').replace(/#.*$/,""))(a==="{"||a==="["||a==="(")&&s++,(a==="}"||a==="]"||a===")")&&s--;if(s===0)return[o,i]}}function $m(e,t,n,r=new Map){let o=[],s=[],i=a=>r.get(a)??OS(CS(t,a),"utf-8");for(let a of n){let c=(m,d)=>s.push({root:e,address:a.address,...d?{path:d}:{},reason:m});if(a.action==="delete"){c("deleted outside Terraform; the next apply makes it again, or remove it from the code if that was the intent");continue}if(a.module){c(`reached through ${a.module}; the value is set in a module`);continue}if(a.index!==void 0){c("an instance of count or for_each; one literal sets every instance");continue}let u=DS(t).find(m=>hm(i(m).split(`
|
|
93
93
|
`),a.type,a.name));if(!u){c("its resource block is not in the root's files");continue}let l=i(u).split(`
|
|
94
94
|
`),[p,f]=hm(l,a.type,a.name);for(let m of a.attributes){let d=0,h=-1;for(let R=p+1;R<f&&h<0;R++){d===0&&new RegExp(`^\\s*${m.path}\\s*=`).test(l[R])&&(h=R);for(let y of l[R].replace(/"(?:[^"\\]|\\.)*"/g,'""').replace(/#.*$/,""))(y==="{"||y==="["||y==="(")&&d++,(y==="}"||y==="]"||y===")")&&d--}if(h<0){c("not set in the root's block; it comes from a default or the provider",m.path);continue}let b=/^(\s*\S+\s*=\s*)(.*?)(\s*(?:#.*|\/\/.*)?)$/.exec(l[h]),$=IS(b[2]);if(!$){c(`set from an expression (${b[2].trim()})`,m.path);continue}if(!wm($.value,m.before)){c("the root's value is not the one last applied, so the code moved too",m.path);continue}let w=NS(m.live);if(w===void 0){c("its live value is a list, a map or null",m.path);continue}l[h]=`${b[1]}${w}${b[3]}`,o.push({root:e,address:a.address,path:m.path,file:jS.join(e,u),from:b[2].trim(),to:w})}let g=l.join(`
|