@intentius/terragucci 0.4.22 → 0.4.23

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.
@@ -438,7 +438,7 @@ ${Kse(e)}
438
438
  ${JSON.stringify(e).replace(/<\//g,"<\\/").replace(/<!--/g,"<\\u0021--")}
439
439
  </script>
440
440
  </main></body></html>
441
- `}async function ZG(e,t,n,r,o=new Date().toISOString(),s){let i=Yg(n.project,n.commit,t),{value:a}=await aa(e,`${i}/run.json`,c=>Wse(c,n,r,o),`wave ${r.number}'s row`,s);return await e.put(`${i}/run.html`,Zse(a),"text/html; charset=utf-8"),`${i}/run.html`}import{spawnSync as Bfe}from"node:child_process";import{spawn as Vpe,spawnSync as pO}from"node:child_process";import{chmodSync as FB,existsSync as dp,mkdirSync as pp,mkdtempSync as Km,readdirSync as zB,readFileSync as jc,realpathSync as UB,rmSync as Jm,statSync as Zpe,writeFileSync as Ra}from"node:fs";import{tmpdir as fO}from"node:os";import{basename as gO,join as Ne,relative as BB,resolve as Rs,sep as WB}from"node:path";var Pr="0.4.22";import{existsSync as Xse,readFileSync as Qse}from"node:fs";import{join as eie}from"node:path";function YG(e,t,n,r){return e?{mode:e,source:`approval: ${e} in the config ${r}`,...e!=="sealed"&&t>0?{note:`chant.workspace.json ${r} still lists gates under identity.gates, and terragucci approve refuses an unsigned approval of a gate it lists; run terragucci init to drop them`}:{}}:t>0?{mode:"sealed",source:`identity.gates in chant.workspace.json ${r}, with no approval key`,note:"set approval: sealed in terragucci.yml to keep sealed approvals, or approval: ledger and run terragucci init to drop the gates"}:n?{mode:n,source:"the pipeline's --approval"}:{mode:"ledger",source:"the default"}}function Xg(e){if(e===void 0)return 0;try{return Object.keys(JSON.parse(e).identity?.gates??{}).length}catch{return 0}}async function Yr(e,t={}){let n=t.at??bs(e),r=WM(e,n),o=await ln(e,n,t.config?{config:t.config}:{});if("error"in o)throw new S(`approval is read from the config at base, and it could not be read (${o.error}), so the gate cannot be decided`);let s=!o.config.projects,i=s?Bs(o.config.approval):void 0,a=s&&qC(o.config.approval),c=s&&typeof o.config.agent?.token_env=="string"?o.config.agent.token_env:void 0,u=s&&typeof o.config.agent?.login=="string"&&o.config.agent.login?o.config.agent.login:void 0,l=s&&Array.isArray(o.config.policy?.override)?o.config.policy.override.filter(p=>typeof p=="string"):[],d=s&&typeof o.config.apply?.point=="string"?o.config.apply.point:void 0;return{...YG(i,r.gates.size,t.flag,"at base"),...r,base:n,overriders:l,...d?{point:d}:{},...a?{requireSigned:a}:{},...c?{agentTokenEnv:c}:{},...u?{agentLogin:u}:{}}}function Yu(e,t){if(t.projects)return;let n=eie(e,"chant.workspace.json");return YG(Bs(t.approval),Xg(Xse(n)?Qse(n,"utf-8"):void 0),void 0,"here")}var tie=(e,t)=>`-estate-outputs-from=${e}=${t}`;function XG(e,t,n,r,o){let s=[],i=[],a=new Map;for(let c of t){let u=n.get(c.estate);if(!u||u===e)continue;let l=f=>{i.push({upstream:u,data:c.name,outputs:"applied",why:f})};if(!r(u)){l("this change does not reach it, so its records stand");continue}let d=o(u);if(!d){l("it did not plan in this run");continue}let p=Wo(d.plan);p?p.changed?(i.push({upstream:u,data:c.name,outputs:"planned"}),a.has(c.estate)||(a.set(c.estate,u),s.push(tie(c.estate,d.file)))):l("its plan changes no output, so its records stand"):l("its plan names no output changes")}return{args:s,reads:i,upstreamOf:a}}function QG(e){let t=e.replace(/^[ \t]*[│╷╵]/gm," ").replace(/\s+/g," "),n=[],r=/Estate "([^"]+)"'s planned output "([^"]+)" \(in .*?\) is (known only after|sensitive)/g;for(let o of t.matchAll(r)){let s={kind:o[3]==="sensitive"?"sensitive":"unknown",estate:o[1],output:o[2]};n.some(i=>i.kind===s.kind&&i.estate===s.estate&&i.output===s.output)||n.push(s)}return n}var eF=e=>"`"+e+"`";function tF(e,t,n){let r=[...new Set(e.map(s=>s.output))].sort(),o=[...new Set(e.map(s=>t.get(s.estate)??s.estate))].sort();return`reads ${r.map(eF).join(", ")} of ${o.join(", ")}, unknown until ${n.length?`wave ${n.join(", ")}`:"it"} applies`}function nF(e,t){let n=[...new Set(e.map(o=>o.output))].sort(),r=[...new Set(e.map(o=>t.get(o.estate)??o.estate))].sort();return`reads sensitive ${n.length===1?"output":"outputs"} ${n.map(eF).join(", ")} of ${r.join(", ")}, which choudoufu never hands to another estate`}import{existsSync as nie,readdirSync as rie,readFileSync as iF,rmSync as oie,statSync as sie,writeFileSync as tT}from"node:fs";import{join as aF}from"node:path";function nT(e){return JSON.stringify(e).replace(/\$\{/g,()=>"$${").replace(/%\{/g,()=>"%%{")}function Qg(e,t,n){if(t===!0)return n;if(Array.isArray(e)||Array.isArray(t)){let o=Array.isArray(e)?e:[],s=Array.isArray(t)?t:[],i=Math.max(o.length,s.length);return`[${Array.from({length:i},(a,c)=>Qg(o[c],s[c],n)).join(", ")}]`}let r=o=>!!o&&typeof o=="object";if(r(e)||r(t)){let o=r(e)?e:{},s=r(t)?t:{};return`{ ${[...new Set([...Object.keys(o),...Object.keys(s).filter(a=>mc(s[a]))])].sort().map(a=>`${nT(a)} = ${Qg(o[a],s[a],n)}`).join(", ")} }`}return e==null?"null":typeof e=="string"?nT(e):JSON.stringify(e)}var iie=e=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),em=(e,t="g")=>new RegExp(`\\bdata\\.terraform_remote_state\\.${iie(e)}(?![\\w-])`,t);function ov(e,t){let n=new RegExp(`${em(t,"").source}(\\.outputs(?:\\.([A-Za-z_][\\w-]*)|\\[\\s*"([^"]+)"\\s*\\])?)?`,"g"),r=new Set;for(let o of e)for(let s of o.matchAll(n)){let i=s[2]??s[3];if(!i)return;r.add(i)}return r}function tm(e){try{return rie(e).filter(t=>t.endsWith(".tf")||t.endsWith(".tofu")||t.endsWith(".tf.json")||t.endsWith(".tofu.json")).map(t=>aF(e,t)).filter(t=>sie(t).isFile())}catch{return[]}}function rT(e,t){return ov(tm(e).map(n=>iF(n,"utf-8")),t)}var rF="terragucci_linked.tf",oF="terragucci_unknown",sF=e=>`terragucci_linked_${e}`;function cF(e,t){let n=aF(e,rF);if(nie(n))throw new Error(`${rF} is already in the root`);let r=tm(e),o=new Map(r.map(l=>[l,iF(l,"utf-8")])),s=[...o.values()],i=[],a=!1,c=[` ${oF} = jsondecode(timestamp())`];for(let l of t){let d=Gb(l.upstream,l.name,l.outputs,ov(s,l.name));d.unknown&&(a=!0),i.push(d);let p=[...l.outputs].sort(([f],[g])=>f<g?-1:1).map(([f,g])=>{let m=Qg(g.value,g.unknown,`local.${oF}`);return`${nT(f)} = ${g.sensitive?`sensitive(${m})`:m}`});c.push(` ${sF(l.name)} = { outputs = { ${p.join(", ")} } }`)}let u=()=>{for(let[l,d]of o)tT(l,d);oie(n,{force:!0})};try{for(let[l,d]of o){let p=d;for(let f of t)p=p.replace(em(f.name),`local.${sF(f.name)}`);p!==d&&tT(l,p)}tT(n,`# Written by terragucci for a linked plan, and removed once it is made.
441
+ `}async function ZG(e,t,n,r,o=new Date().toISOString(),s){let i=Yg(n.project,n.commit,t),{value:a}=await aa(e,`${i}/run.json`,c=>Wse(c,n,r,o),`wave ${r.number}'s row`,s);return await e.put(`${i}/run.html`,Zse(a),"text/html; charset=utf-8"),`${i}/run.html`}import{spawnSync as Bfe}from"node:child_process";import{spawn as Vpe,spawnSync as pO}from"node:child_process";import{chmodSync as FB,existsSync as dp,mkdirSync as pp,mkdtempSync as Km,readdirSync as zB,readFileSync as jc,realpathSync as UB,rmSync as Jm,statSync as Zpe,writeFileSync as Ra}from"node:fs";import{tmpdir as fO}from"node:os";import{basename as gO,join as Ne,relative as BB,resolve as Rs,sep as WB}from"node:path";var Pr="0.4.23";import{existsSync as Xse,readFileSync as Qse}from"node:fs";import{join as eie}from"node:path";function YG(e,t,n,r){return e?{mode:e,source:`approval: ${e} in the config ${r}`,...e!=="sealed"&&t>0?{note:`chant.workspace.json ${r} still lists gates under identity.gates, and terragucci approve refuses an unsigned approval of a gate it lists; run terragucci init to drop them`}:{}}:t>0?{mode:"sealed",source:`identity.gates in chant.workspace.json ${r}, with no approval key`,note:"set approval: sealed in terragucci.yml to keep sealed approvals, or approval: ledger and run terragucci init to drop the gates"}:n?{mode:n,source:"the pipeline's --approval"}:{mode:"ledger",source:"the default"}}function Xg(e){if(e===void 0)return 0;try{return Object.keys(JSON.parse(e).identity?.gates??{}).length}catch{return 0}}async function Yr(e,t={}){let n=t.at??bs(e),r=WM(e,n),o=await ln(e,n,t.config?{config:t.config}:{});if("error"in o)throw new S(`approval is read from the config at base, and it could not be read (${o.error}), so the gate cannot be decided`);let s=!o.config.projects,i=s?Bs(o.config.approval):void 0,a=s&&qC(o.config.approval),c=s&&typeof o.config.agent?.token_env=="string"?o.config.agent.token_env:void 0,u=s&&typeof o.config.agent?.login=="string"&&o.config.agent.login?o.config.agent.login:void 0,l=s&&Array.isArray(o.config.policy?.override)?o.config.policy.override.filter(p=>typeof p=="string"):[],d=s&&typeof o.config.apply?.point=="string"?o.config.apply.point:void 0;return{...YG(i,r.gates.size,t.flag,"at base"),...r,base:n,overriders:l,...d?{point:d}:{},...a?{requireSigned:a}:{},...c?{agentTokenEnv:c}:{},...u?{agentLogin:u}:{}}}function Yu(e,t){if(t.projects)return;let n=eie(e,"chant.workspace.json");return YG(Bs(t.approval),Xg(Xse(n)?Qse(n,"utf-8"):void 0),void 0,"here")}var tie=(e,t)=>`-estate-outputs-from=${e}=${t}`;function XG(e,t,n,r,o){let s=[],i=[],a=new Map;for(let c of t){let u=n.get(c.estate);if(!u||u===e)continue;let l=f=>{i.push({upstream:u,data:c.name,outputs:"applied",why:f})};if(!r(u)){l("this change does not reach it, so its records stand");continue}let d=o(u);if(!d){l("it did not plan in this run");continue}let p=Wo(d.plan);p?p.changed?(i.push({upstream:u,data:c.name,outputs:"planned"}),a.has(c.estate)||(a.set(c.estate,u),s.push(tie(c.estate,d.file)))):l("its plan changes no output, so its records stand"):l("its plan names no output changes")}return{args:s,reads:i,upstreamOf:a}}function QG(e){let t=e.replace(/^[ \t]*[│╷╵]/gm," ").replace(/\s+/g," "),n=[],r=/Estate "([^"]+)"'s planned output "([^"]+)" \(in .*?\) is (known only after|sensitive)/g;for(let o of t.matchAll(r)){let s={kind:o[3]==="sensitive"?"sensitive":"unknown",estate:o[1],output:o[2]};n.some(i=>i.kind===s.kind&&i.estate===s.estate&&i.output===s.output)||n.push(s)}return n}var eF=e=>"`"+e+"`";function tF(e,t,n){let r=[...new Set(e.map(s=>s.output))].sort(),o=[...new Set(e.map(s=>t.get(s.estate)??s.estate))].sort();return`reads ${r.map(eF).join(", ")} of ${o.join(", ")}, unknown until ${n.length?`wave ${n.join(", ")}`:"it"} applies`}function nF(e,t){let n=[...new Set(e.map(o=>o.output))].sort(),r=[...new Set(e.map(o=>t.get(o.estate)??o.estate))].sort();return`reads sensitive ${n.length===1?"output":"outputs"} ${n.map(eF).join(", ")} of ${r.join(", ")}, which choudoufu never hands to another estate`}import{existsSync as nie,readdirSync as rie,readFileSync as iF,rmSync as oie,statSync as sie,writeFileSync as tT}from"node:fs";import{join as aF}from"node:path";function nT(e){return JSON.stringify(e).replace(/\$\{/g,()=>"$${").replace(/%\{/g,()=>"%%{")}function Qg(e,t,n){if(t===!0)return n;if(Array.isArray(e)||Array.isArray(t)){let o=Array.isArray(e)?e:[],s=Array.isArray(t)?t:[],i=Math.max(o.length,s.length);return`[${Array.from({length:i},(a,c)=>Qg(o[c],s[c],n)).join(", ")}]`}let r=o=>!!o&&typeof o=="object";if(r(e)||r(t)){let o=r(e)?e:{},s=r(t)?t:{};return`{ ${[...new Set([...Object.keys(o),...Object.keys(s).filter(a=>mc(s[a]))])].sort().map(a=>`${nT(a)} = ${Qg(o[a],s[a],n)}`).join(", ")} }`}return e==null?"null":typeof e=="string"?nT(e):JSON.stringify(e)}var iie=e=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),em=(e,t="g")=>new RegExp(`\\bdata\\.terraform_remote_state\\.${iie(e)}(?![\\w-])`,t);function ov(e,t){let n=new RegExp(`${em(t,"").source}(\\.outputs(?:\\.([A-Za-z_][\\w-]*)|\\[\\s*"([^"]+)"\\s*\\])?)?`,"g"),r=new Set;for(let o of e)for(let s of o.matchAll(n)){let i=s[2]??s[3];if(!i)return;r.add(i)}return r}function tm(e){try{return rie(e).filter(t=>t.endsWith(".tf")||t.endsWith(".tofu")||t.endsWith(".tf.json")||t.endsWith(".tofu.json")).map(t=>aF(e,t)).filter(t=>sie(t).isFile())}catch{return[]}}function rT(e,t){return ov(tm(e).map(n=>iF(n,"utf-8")),t)}var rF="terragucci_linked.tf",oF="terragucci_unknown",sF=e=>`terragucci_linked_${e}`;function cF(e,t){let n=aF(e,rF);if(nie(n))throw new Error(`${rF} is already in the root`);let r=tm(e),o=new Map(r.map(l=>[l,iF(l,"utf-8")])),s=[...o.values()],i=[],a=!1,c=[` ${oF} = jsondecode(timestamp())`];for(let l of t){let d=Gb(l.upstream,l.name,l.outputs,ov(s,l.name));d.unknown&&(a=!0),i.push(d);let p=[...l.outputs].sort(([f],[g])=>f<g?-1:1).map(([f,g])=>{let m=Qg(g.value,g.unknown,`local.${oF}`);return`${nT(f)} = ${g.sensitive?`sensitive(${m})`:m}`});c.push(` ${sF(l.name)} = { outputs = { ${p.join(", ")} } }`)}let u=()=>{for(let[l,d]of o)tT(l,d);oie(n,{force:!0})};try{for(let[l,d]of o){let p=d;for(let f of t)p=p.replace(em(f.name),`local.${sF(f.name)}`);p!==d&&tT(l,p)}tT(n,`# Written by terragucci for a linked plan, and removed once it is made.
442
442
  locals {
443
443
  ${c.join(`
444
444
  `)}
@@ -468,7 +468,7 @@ ${n}`).digest("hex").slice(0,16)}`;if(kv("git",["-C",e,"fetch","-q","--no-tags",
468
468
  `).join(`
469
469
  `)}function UT(e,t){let[n,r]=t.split(".");if(!n||!r||!fce(e))return;let o=new RegExp(`^\\s*resource\\s+"${n}"\\s+"${r}"\\s*\\{`,"m");return gce(e).filter(s=>s.endsWith(".tf")).sort().find(s=>o.test(mce(hce(e,s),"utf-8")))}function fz(e,t){return`${e.replace(/\n*$/,`
470
470
  `)}
471
- ${t}`}import{spawnSync as Ece}from"node:child_process";import{createHash as wce}from"node:crypto";var BT={"ghcr.io/intentius/terragucci-tofu:0.4.22-tofu1.13.1":"sha256:24c43f1c6ef58813bb8a6cfd3417be64beac6c200803828d8290b54b11a67d17","ghcr.io/intentius/terragucci-terraform:0.4.22-tf1.14.9":"sha256:50ade495669d859eb193fc5eea0ba93af721494ecaee62bea407b01c90bb2fcb","ghcr.io/intentius/terragucci-terragrunt:0.4.22-tg1.1.6-tofu1.13.1":"sha256:33340a3b96ab10af6cd37e42a86f4c827842c2fc9ce096e59c41ebd0cf273b10","ghcr.io/intentius/terragucci-choudoufu:0.4.22-choudoufu0.28.0":"sha256:969acd0016430d803e90c4d71392e08bacd80f4be0c6e28fddea0a53ca4c7e1c"};var gz="ghcr.io/intentius",ai={tofu:"1.13.1",terraform:"1.14.9",terragrunt:"1.1.6",choudoufu:"0.28.0"};function mz(e,t=Pr){return e==="terragrunt"?`${t}-tg${ai.terragrunt}-tofu${ai.tofu}`:`${t}-${e==="terraform"?"tf":e}${ai[e]}`}function HT(e,t=BT){let n=`${gz}/terragucci-${e}`,r=mz(e);return{repository:n,tag:r,digest:t[`${n}:${r}`]}}function hz(e=BT){let t=`${gz}/terragucci-terragrunt`,n=mz("terragrunt");return{repository:t,tag:n,digest:e[`${t}:${n}`]}}function qT(e){return e.digest?`${e.repository}:${e.tag}@${e.digest}`:`${e.repository}:${e.tag}`}var WT={laya:"0.3.28",torch:"2.14.0",pip:"26.2.1",checkpoint:{repo:"convaiinnovations/laya",name:"english",revision:"55cf4c4ebb4ebe31b2550e8bdf3bd21b99753851",weightsSha256:"891102d372688fc2a094dac56a384bc537b87c63f21f9f3dac0be2b7cbc8d86c"},port:8790},yz=`laya-${WT.laya}-${WT.checkpoint.name}@${WT.checkpoint.revision.slice(0,12)}`;var bce="https://api.typesafe.ai",vce={noul:.8,choice:.7,score:.7},$ce={laya:!0,jev:!0,von:!1,decider:!1};function kce(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 wz(e,t){return e.threshold??t?.thresholds?.[e.type]??vce[e.type]}var Ev=e=>typeof e=="number"&&Number.isFinite(e)&&e>=0&&e<=1;function _ce(e){let t=e.url??(e.backend==="jev"?bce:void 0);if(!t)return;let n=t.replace(/\/+$/,"");return n.endsWith("/v1/systemone")?n:`${n}/v1/systemone`}function Sce(e){return e.model??(e.backend==="laya"?yz:void 0)}function Rce(e,t,n,r){let o={question:e,type:t.type,threshold:r},s=d=>({...o,status:"unavailable",reason:d});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(!Ev(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 d=i.probabilities;if(d===null||typeof d!="object"||!Object.values(d).every(Ev))return s("the answer has no probabilities");if(u=d,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(!Ev(u[i.choice]))return s("the chosen option has no probability");a=i.choice,c=u[i.choice]}else{let p=t.criteria.map((g,m)=>String(m));if(!p.every(g=>Ev(u[g])))return s("the score's probabilities do not cover every level");let f=p.reduce((g,m)=>u[m]>u[g]?m:g,p[0]);a=t.criteria[Number(f)],c=u[f],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 Xd(e,t,n,r={}){for(let[v,$]of Object.entries(n))kce(v,$);let o=`sha256:${wce("sha256").update(JSON.stringify(t)).digest("hex")}`,s=(v,$)=>Object.fromEntries(Object.entries(n).map(([_,E])=>[_,{question:_,type:E.type,status:v,threshold:wz(E,e),reason:$}]));if(!e)return{stateDigest:o,decisions:s("off","decide is not configured")};let i=_ce(e),a=Sce(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 v=(r.env??process.env)[e.token_env];if(!v)return{...c,decisions:s("unavailable",`${e.token_env} is not set`)};u.authorization=`Bearer ${v}`}let l=Object.fromEntries(Object.entries(n).map(([v,$])=>[v,{type:$.type,instructions:$.instructions,criteria:$.criteria}])),d=JSON.stringify({...a?{model:a}:{},state:t,questions:l}),p=r.fetch??globalThis.fetch,f=Date.now(),g,m;try{let v=await p(i,{method:"POST",headers:u,body:d,signal:AbortSignal.timeout(r.timeoutMs??3e4)});if(g=v.status,m=await v.text(),!v.ok){let $=m.slice(0,300);try{let _=JSON.parse(m),E=_.detail??_.error;E!==void 0&&($=typeof E=="string"?E:JSON.stringify(E))}catch{}return{...c,ms:Date.now()-f,decisions:s("unavailable",`the service answered HTTP ${g}: ${$}`)}}}catch(v){return{...c,ms:Date.now()-f,decisions:s("unavailable",`the service did not answer: ${v.message}`)}}let h=Date.now()-f,y;try{y=JSON.parse(m)}catch{return{...c,ms:h,decisions:s("unavailable","the response is not JSON")}}if(y===null||typeof y!="object"||y.answers===null||typeof y.answers!="object")return{...c,ms:h,decisions:s("unavailable","the response has no answers")};let w=typeof y.model=="string"?y.model:void 0;if($ce[e.backend]&&a&&w!==a)return{...c,model:w??a,ms:h,decisions:s("unavailable",`the service answered as ${w??"no model"}, and decide.model pins ${a}`)};let b=y.answers,k=Object.fromEntries(Object.entries(n).map(([v,$])=>[v,Rce(v,$,b[v],wz($,e))]));return{...c,model:w??a,ms:h,decisions:k}}function Qd(e){return e?.status==="confident"}function _c(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 JT={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"}},KT={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"}},xce={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"}},bz={prIntent:JT,driftActor:KT,versionBump:xce};var wm=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),tl=e=>typeof e=="number",Tce=/^(aws:|eks:|elasticbeanstalk:|kubernetes\.io\/|k8s\.io\/|karpenter\.sh\/)/;function Ace(e){let t=wm(e.before)?e.before:{},n=wm(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=>Tce.test(o))}var Oce=e=>t=>{let n=Array.isArray(t.before)?t.before[0]:t.before,r=Array.isArray(t.live)?t.live[0]:t.live;if(!wm(n)||!wm(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)},Pce=[{type:"aws_ecs_service",path:"desired_count",actor:"controller",why:"Application Auto Scaling sets an ECS service's task count",when:e=>tl(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=>tl(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:Oce("desired_size")},{type:"aws_dynamodb_table",path:"read_capacity",actor:"controller",why:"Application Auto Scaling adjusts a provisioned table's read capacity",when:e=>tl(e.live)},{type:"aws_dynamodb_table",path:"write_capacity",actor:"controller",why:"Application Auto Scaling adjusts a provisioned table's write capacity",when:e=>tl(e.live)},{type:"aws_appautoscaling_target",path:"min_capacity",actor:"controller",why:"a scheduled action on the scalable target moves its minimum",when:e=>tl(e.live)},{type:"aws_appautoscaling_target",path:"max_capacity",actor:"controller",why:"a scheduled action on the scalable target moves its maximum",when:e=>tl(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=>tl(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:Ace}))];function jce(e,t,n){return Pce.find(r=>(r.type===e||r.type==="*")&&r.path===t&&(r.when?.(n)??!0))}var Ice=/terraform|opentofu|tofu/i;function Cce(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 Nce(e){let n=e.filter(o=>o.readOnly!==!0&&o.readOnly!=="true"&&!Ice.test(o.userAgent??"")).sort((o,s)=>String(s.eventTime??"").localeCompare(String(o.eventTime??"")))[0],r=n&&Cce(n);return n&&r?{status:"found",...r,event:n.eventName??"an event",at:n.eventTime??""}:{status:"silent"}}function bm(e={}){let t=e.run??(n=>{let r=Ece("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(`
471
+ ${t}`}import{spawnSync as Ece}from"node:child_process";import{createHash as wce}from"node:crypto";var BT={"ghcr.io/intentius/terragucci-tofu:0.4.23-tofu1.13.1":"sha256:830319f3ca4d9d141bf145646d2a7db826235ea8036d9ac9e3f3b85723405b3c","ghcr.io/intentius/terragucci-terraform:0.4.23-tf1.14.9":"sha256:8d605c34fec65c3905f0a087b3433729a3ade9e49f4ccd5a133c3e73a4bb2da9","ghcr.io/intentius/terragucci-terragrunt:0.4.23-tg1.1.6-tofu1.13.1":"sha256:9b44b3e22bda52e0c1fa33d29747313da35ba380001d82ae4ab60cb2409f9147","ghcr.io/intentius/terragucci-choudoufu:0.4.23-choudoufu0.28.0":"sha256:21bbddd722bef0b8a5649c3fa13a9de98ce3c485cf7c14e4f16d2933edd21818"};var gz="ghcr.io/intentius",ai={tofu:"1.13.1",terraform:"1.14.9",terragrunt:"1.1.6",choudoufu:"0.28.0"};function mz(e,t=Pr){return e==="terragrunt"?`${t}-tg${ai.terragrunt}-tofu${ai.tofu}`:`${t}-${e==="terraform"?"tf":e}${ai[e]}`}function HT(e,t=BT){let n=`${gz}/terragucci-${e}`,r=mz(e);return{repository:n,tag:r,digest:t[`${n}:${r}`]}}function hz(e=BT){let t=`${gz}/terragucci-terragrunt`,n=mz("terragrunt");return{repository:t,tag:n,digest:e[`${t}:${n}`]}}function qT(e){return e.digest?`${e.repository}:${e.tag}@${e.digest}`:`${e.repository}:${e.tag}`}var WT={laya:"0.3.28",torch:"2.14.0",pip:"26.2.1",checkpoint:{repo:"convaiinnovations/laya",name:"english",revision:"55cf4c4ebb4ebe31b2550e8bdf3bd21b99753851",weightsSha256:"891102d372688fc2a094dac56a384bc537b87c63f21f9f3dac0be2b7cbc8d86c"},port:8790},yz=`laya-${WT.laya}-${WT.checkpoint.name}@${WT.checkpoint.revision.slice(0,12)}`;var bce="https://api.typesafe.ai",vce={noul:.8,choice:.7,score:.7},$ce={laya:!0,jev:!0,von:!1,decider:!1};function kce(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 wz(e,t){return e.threshold??t?.thresholds?.[e.type]??vce[e.type]}var Ev=e=>typeof e=="number"&&Number.isFinite(e)&&e>=0&&e<=1;function _ce(e){let t=e.url??(e.backend==="jev"?bce:void 0);if(!t)return;let n=t.replace(/\/+$/,"");return n.endsWith("/v1/systemone")?n:`${n}/v1/systemone`}function Sce(e){return e.model??(e.backend==="laya"?yz:void 0)}function Rce(e,t,n,r){let o={question:e,type:t.type,threshold:r},s=d=>({...o,status:"unavailable",reason:d});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(!Ev(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 d=i.probabilities;if(d===null||typeof d!="object"||!Object.values(d).every(Ev))return s("the answer has no probabilities");if(u=d,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(!Ev(u[i.choice]))return s("the chosen option has no probability");a=i.choice,c=u[i.choice]}else{let p=t.criteria.map((g,m)=>String(m));if(!p.every(g=>Ev(u[g])))return s("the score's probabilities do not cover every level");let f=p.reduce((g,m)=>u[m]>u[g]?m:g,p[0]);a=t.criteria[Number(f)],c=u[f],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 Xd(e,t,n,r={}){for(let[v,$]of Object.entries(n))kce(v,$);let o=`sha256:${wce("sha256").update(JSON.stringify(t)).digest("hex")}`,s=(v,$)=>Object.fromEntries(Object.entries(n).map(([_,E])=>[_,{question:_,type:E.type,status:v,threshold:wz(E,e),reason:$}]));if(!e)return{stateDigest:o,decisions:s("off","decide is not configured")};let i=_ce(e),a=Sce(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 v=(r.env??process.env)[e.token_env];if(!v)return{...c,decisions:s("unavailable",`${e.token_env} is not set`)};u.authorization=`Bearer ${v}`}let l=Object.fromEntries(Object.entries(n).map(([v,$])=>[v,{type:$.type,instructions:$.instructions,criteria:$.criteria}])),d=JSON.stringify({...a?{model:a}:{},state:t,questions:l}),p=r.fetch??globalThis.fetch,f=Date.now(),g,m;try{let v=await p(i,{method:"POST",headers:u,body:d,signal:AbortSignal.timeout(r.timeoutMs??3e4)});if(g=v.status,m=await v.text(),!v.ok){let $=m.slice(0,300);try{let _=JSON.parse(m),E=_.detail??_.error;E!==void 0&&($=typeof E=="string"?E:JSON.stringify(E))}catch{}return{...c,ms:Date.now()-f,decisions:s("unavailable",`the service answered HTTP ${g}: ${$}`)}}}catch(v){return{...c,ms:Date.now()-f,decisions:s("unavailable",`the service did not answer: ${v.message}`)}}let h=Date.now()-f,y;try{y=JSON.parse(m)}catch{return{...c,ms:h,decisions:s("unavailable","the response is not JSON")}}if(y===null||typeof y!="object"||y.answers===null||typeof y.answers!="object")return{...c,ms:h,decisions:s("unavailable","the response has no answers")};let w=typeof y.model=="string"?y.model:void 0;if($ce[e.backend]&&a&&w!==a)return{...c,model:w??a,ms:h,decisions:s("unavailable",`the service answered as ${w??"no model"}, and decide.model pins ${a}`)};let b=y.answers,k=Object.fromEntries(Object.entries(n).map(([v,$])=>[v,Rce(v,$,b[v],wz($,e))]));return{...c,model:w??a,ms:h,decisions:k}}function Qd(e){return e?.status==="confident"}function _c(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 JT={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"}},KT={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"}},xce={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"}},bz={prIntent:JT,driftActor:KT,versionBump:xce};var wm=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),tl=e=>typeof e=="number",Tce=/^(aws:|eks:|elasticbeanstalk:|kubernetes\.io\/|k8s\.io\/|karpenter\.sh\/)/;function Ace(e){let t=wm(e.before)?e.before:{},n=wm(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=>Tce.test(o))}var Oce=e=>t=>{let n=Array.isArray(t.before)?t.before[0]:t.before,r=Array.isArray(t.live)?t.live[0]:t.live;if(!wm(n)||!wm(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)},Pce=[{type:"aws_ecs_service",path:"desired_count",actor:"controller",why:"Application Auto Scaling sets an ECS service's task count",when:e=>tl(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=>tl(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:Oce("desired_size")},{type:"aws_dynamodb_table",path:"read_capacity",actor:"controller",why:"Application Auto Scaling adjusts a provisioned table's read capacity",when:e=>tl(e.live)},{type:"aws_dynamodb_table",path:"write_capacity",actor:"controller",why:"Application Auto Scaling adjusts a provisioned table's write capacity",when:e=>tl(e.live)},{type:"aws_appautoscaling_target",path:"min_capacity",actor:"controller",why:"a scheduled action on the scalable target moves its minimum",when:e=>tl(e.live)},{type:"aws_appautoscaling_target",path:"max_capacity",actor:"controller",why:"a scheduled action on the scalable target moves its maximum",when:e=>tl(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=>tl(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:Ace}))];function jce(e,t,n){return Pce.find(r=>(r.type===e||r.type==="*")&&r.path===t&&(r.when?.(n)??!0))}var Ice=/terraform|opentofu|tofu/i;function Cce(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 Nce(e){let n=e.filter(o=>o.readOnly!==!0&&o.readOnly!=="true"&&!Ice.test(o.userAgent??"")).sort((o,s)=>String(s.eventTime??"").localeCompare(String(o.eventTime??"")))[0],r=n&&Cce(n);return n&&r?{status:"found",...r,event:n.eventName??"an event",at:n.eventTime??""}:{status:"silent"}}function bm(e={}){let t=e.run??(n=>{let r=Ece("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(`
472
472
  `)[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 Nce(i)}}}function vz(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})`:wm(e)?`(a map with keys ${Object.keys(e).slice(0,12).join(", ")})`:"(a value)"}var vm="attributions.json";async function ep(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:jce(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 d=`${a.type}\0${a.ref}`,p=s.get(d);if(p||(p=await n.audit.lookup({type:a.type,address:a.address,ref:a.ref}),s.set(d,p)),p.status==="found"){r.push({...u,actor:p.actor,source:"audit",detail:`${p.event} by ${p.who}${p.at?` at ${p.at}`:""}`});continue}p.status==="unavailable"&&(i=p.reason,o.push(`audit log unavailable: ${p.reason}`))}if(n.decide){let d={resource_type:a.type,attribute:c.path,value_in_code:vz(c.before,c.sensitive),live_value:vz(c.live,c.sensitive)},p=await Xd(n.decide,d,{actor:KT},n.options),f=p.decisions.actor;if(Qd(f)){r.push({...u,actor:f.answer,source:"model",detail:_c(f,p),probability:f.probability});continue}o.push(`${a.address} ${c.path}: ${_c(f,p)}`)}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 Dce={table:"known write",audit:"audit log",model:"model",unattributed:"unattributed"};function VT(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=`${Dce[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 ZT(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 kz,readdirSync as _z,readFileSync as Mce}from"node:fs";import{join as Lce,posix as Gce}from"node:path";var Sz=(e,t)=>JSON.stringify(e??null)===JSON.stringify(t??null);function nl(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=>!Sz(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 Tv(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 YT(e){if(typeof e=="number"||typeof e=="boolean")return String(e);if(typeof e=="string")return JSON.stringify(e).replace(/\$\{/g,()=>"$${").replace(/%\{/g,()=>"%%{")}function Fce(e){return kz(e)?_z(e).filter(t=>t.endsWith(".tf")||t.endsWith(".tofu")).sort():[]}function $z(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 Av(e,t,n,r=new Map){let o=[],s=[],i=a=>r.get(a)??Mce(Lce(t,a),"utf-8");for(let a of n){let c=(g,m)=>s.push({root:e,address:a.address,...m?{path:m}:{},reason:g});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=Fce(t).find(g=>$z(i(g).split(`
473
473
  `),a.type,a.name));if(!u){c("its resource block is not in the root's files");continue}let l=i(u).split(`
474
474
  `),[d,p]=$z(l,a.type,a.name);for(let g of a.attributes){let m=0,h=-1;for(let k=d+1;k<p&&h<0;k++){m===0&&new RegExp(`^\\s*${g.path}\\s*=`).test(l[k])&&(h=k);for(let v of l[k].replace(/"(?:[^"\\]|\\.)*"/g,'""').replace(/#.*$/,""))(v==="{"||v==="["||v==="(")&&m++,(v==="}"||v==="]"||v===")")&&m--}if(h<0){c("not set in the root's block; it comes from a default or the provider",g.path);continue}let y=/^(\s*\S+\s*=\s*)(.*?)(\s*(?:#.*|\/\/.*)?)$/.exec(l[h]),w=Tv(y[2]);if(!w){c(`set from an expression (${y[2].trim()})`,g.path);continue}if(!Sz(w.value,g.before)){c("the root's value is not the one last applied, so the code moved too",g.path);continue}let b=YT(g.live);if(b===void 0){c("its live value is a list, a map or null",g.path);continue}l[h]=`${y[1]}${b}${y[3]}`,o.push({root:e,address:a.address,path:g.path,file:Gce.join(e,u),from:y[2].trim(),to:b})}let f=l.join(`
@@ -881,29 +881,29 @@ ${ie}`}),new se({name:"Keep the check report",if:"always()",uses:D,with:{name:Rp
881
881
  ${H$(n,s,t,a,_)}`}),new se({name:"Keep the plan report",if:"always()",uses:D,with:{name:be,path:`${be}/`,"if-no-files-found":"ignore"}})]}),Kt=(q,Se,ze,He,qe)=>new pt({"runs-on":"ubuntu-latest",container:{image:r},needs:q,if:`always() && (needs.${q}.result == 'success' || needs.${q}.result == 'failure') && ${Se}`,permissions:{statuses:"write","pull-requests":"write",...ht},env:{TG_TOKEN:"${{ github.token }}",TG_PLAN_RESULT:`\${{ needs.${q}.result }}`,...He},steps:[new se({name:"Fetch the plan report",uses:pe,"continue-on-error":!0,with:{name:ze,path:be}}),new se({name:"Post the plan note and terragucci/plan",if:"always()",shell:"bash",run:yye(t,{replan:qe,prReview:b,...k?{agentLogin:k}:{}})})]}),zr=Kt("plan",`github.event_name == 'pull_request' && ${Gs}`,be,{TG_SHA:"${{ github.event.pull_request.head.sha }}",TG_PR:"${{ github.event.pull_request.number }}"},!1),Sn=q=>`startsWith(github.event.comment.body, '${q}')`,jo=e.atlantisComments===!0,au=jo?`${Sn("/terragucci apply")} || ${Sn("atlantis apply")}`:Sn("/terragucci apply"),cu=jo?`(${Sn("/terragucci plan")} || ${Sn("atlantis plan")})`:Sn("/terragucci plan"),uu=V?`(${au} || ${Sn("/terragucci lock")} || ${Sn("/terragucci unlock")})`:jo?`(${au})`:au,lu="(startsWith(github.event.comment.body, '/terragucci lock') || startsWith(github.event.comment.body, '/terragucci unlock'))",du=qn&&!V?` && !${lu}`:"",cs="steps.decide.outputs.go == '1'",pu=!!a?.azure,jn=$&&!pu,ff={...p,...O,...N,...ne,...z$(t,e.reports)},_y=new pt({"runs-on":"ubuntu-latest",container:{image:r},if:`(github.event_name == 'issue_comment' && ${jo?`(${Sn("/terragucci")} || ${Sn("atlantis plan")})`:Sn("/terragucci")} && !${uu}${du}${e.agentComment?` && !${oP}`:""}) || ${Hq}`,permissions:{contents:"read",statuses:"write","pull-requests":"write",...$?{"id-token":"write"}:{}},...Po($),concurrency:{group:"terragucci-replan-${{ github.repository }}-${{ github.event.issue.number || github.event.inputs.pr }}","cancel-in-progress":!1},...Object.keys(ff).length?{env:ff}:{},outputs:{go:"${{ steps.decide.outputs.go }}",pr:"${{ steps.decide.outputs.pr }}",sha:"${{ steps.decide.outputs.sha }}",root:"${{ steps.decide.outputs.root }}"},steps:[ge(!1),new se({id:"decide",name:"Read the comment and decide whether it re-plans",shell:"bash",env:{TG_TOKEN:"${{ github.token }}"},run:hye(s,t,e.agentComment!==void 0)}),ge(!0,"refs/pull/${{ steps.decide.outputs.pr }}/head",cs),...Ye(!0,!0,cs,!0),...jn?[new se({if:cs,name:"Trade the job's OIDC token for the plan identities",shell:"bash",run:pye(t,a,g,e.reports?.role)})]:[],new se({if:cs,name:"Re-plan the pull request on request and write the plan report",shell:"bash",env:{TG_PR:"${{ steps.decide.outputs.pr }}",TG_SHA:"${{ steps.decide.outputs.sha }}",TG_ROOT:"${{ steps.decide.outputs.root }}",TG_BASE:"origin/${{ steps.decide.outputs.base }}"},run:pu?`${U$()}
882
882
  ${fye}
883
883
  ${H$(n,s,t,a,_,{replan:!0,sweep:!1})}`:`${U$("bash",!0)}
884
- ${H$(n,s,t,a,_,{replan:!0,traded:jn})}`}),new se({name:"Keep the plan report",if:`always() && ${cs}`,uses:D,with:{name:`${be}-replan`,path:`${be}/`,"if-no-files-found":"ignore"}})]}),Sy=Kt("replan","needs.replan.outputs.go == '1'",`${be}-replan`,{TG_PR:"${{ needs.replan.outputs.pr }}",TG_SHA:"${{ needs.replan.outputs.sha }}",TG_ROOT:"${{ needs.replan.outputs.root }}"},!0),Ft=new Map([["workflow",ky],["check",Gt],["plan",_n],["plan-note",zr],["replan",_y],["replan-note",Sy]]);if(vt&&Ft.set("fmt",new pt({"runs-on":"ubuntu-latest",container:{image:r},needs:"check",if:`always() && needs.check.result == 'failure' && github.event_name == 'push' && github.ref != format('refs/heads/{0}', github.event.repository.default_branch)${h.map(q=>` && github.ref != 'refs/heads/${q}'`).join("")}`,permissions:{contents:"write",...qn?{statuses:"write","pull-requests":"write"}:{}},env:{TG_TOKEN:"${{ github.token }}"},steps:[new se({uses:"actions/checkout@v4"}),...M?[new se({name:te,run:M})]:[],new se({name:"Commit the formatting",shell:"bash",run:_q(n,t,c)}),...qn?[new se({name:"Lock the roots the pull request's new head reaches",shell:"bash",run:Eq(s,t,V,!!l)})]:[]]})),ee?Ft.set("apply-comment",new pt({"runs-on":"ubuntu-latest",container:{image:r},if:`github.event_name == 'issue_comment' && ${uu}`,permissions:{contents:"read","pull-requests":"write"},env:{TG_TOKEN:"${{ github.token }}"},steps:as(new se({name:"Answer that a comment applies nothing here",shell:"bash",run:oye(s,t,e.canary)}),!0,!0,void 0,!0)})):Ft.set("apply-comment",new pt({"runs-on":"ubuntu-latest",container:{image:r},if:`github.event_name == 'issue_comment' && ${uu}`,permissions:{contents:kr||V||st||Ot==="resource"?"write":"read",statuses:"write","pull-requests":"write",...V&&t==="github"?{checks:"read"}:{},...v,...$?{"id-token":"write"}:{}},...Po($),...rt?{concurrency:dP(t)}:{},env:{...p,TG_TOKEN:"${{ github.token }}",...O,...ye,...ne},...K?{outputs:{merge:"${{ steps.apply.outputs.merge }}",sha:"${{ steps.apply.outputs.sha }}",waves:"${{ steps.apply.outputs.waves }}"}}:{},steps:[...as(new se({...K?{id:"apply"}:{},name:V?"Apply a pull request on request, from its head before merge or its merge commit after":"Apply a merged pull request on request, from its merge commit",shell:"bash",run:rye(n,s,t,a,mt)}),!0,!0,void 0,!0),new se({name:"Keep the apply report",if:"always()",uses:t==="forgejo"?"actions/upload-artifact@v3":"actions/upload-artifact@v4",with:{name:`${be}-apply-comment`,path:`${be}/`,"if-no-files-found":"ignore"}})]})),K&&Ft.set("pr-merge",new pt({"runs-on":"ubuntu-latest",container:{image:r},needs:"apply-comment",if:"needs.apply-comment.outputs.merge == '1'",permissions:{contents:"write","pull-requests":"write"},env:{TG_TOKEN:"${{ github.token }}",...e.applyMergeTokenEnv?{TG_MERGE_TOKEN:`\${{ secrets.${e.applyMergeTokenEnv} }}`}:{},TG_PR:"${{ github.event.issue.number }}",TG_SHA:"${{ needs.apply-comment.outputs.sha }}",TG_WAVES:"${{ needs.apply-comment.outputs.waves }}"},steps:[new se({uses:"actions/checkout@v4"}),new se({name:"Merge the pull request whose every wave applied",shell:"bash",run:Qhe(t)})]})),b&&Ft.set("approval",new pt({"runs-on":"ubuntu-latest",container:{image:r},if:`github.event_name == 'pull_request_review' && ${Gs}`,permissions:{contents:"read",statuses:"write","pull-requests":"read"},env:{TG_TOKEN:"${{ github.token }}",TG_SHA:"${{ github.event.pull_request.head.sha }}",TG_PR:"${{ github.event.pull_request.number }}"},steps:[new se({name:"Say on the head whether its waiting waves are approved",shell:"bash",run:`terragucci approval-status --forge ${t}${k?` --agent-login ${k}`:""}`})]})),e.agentComment)for(let[q,Se]of tq(t,r,e.agentComment))Ft.set(q,Se);if(qn){let q=V?cu:`(${cu} || ${lu})`;Ft.set("pr-lock",new pt({"runs-on":"ubuntu-latest",container:{image:r},if:`github.event_name == 'pull_request_target' || (github.event_name == 'issue_comment' && ${q})`,permissions:{contents:"write",statuses:"write","pull-requests":"write"},concurrency:{group:"terragucci-lock-${{ github.repository }}-${{ github.event.pull_request.number || github.event.issue.number }}","cancel-in-progress":!1},env:{TG_TOKEN:"${{ github.token }}"},steps:[new se({uses:"actions/checkout@v4",with:{"fetch-depth":0}}),new se({name:"Lock the roots the pull request reaches, or release them",shell:"bash",run:Eq(s,t,V,!!l)})]}))}y&&Ft.set("ephemeral",new pt({"runs-on":"ubuntu-latest",container:{image:r},if:`github.event_name == 'pull_request_target' && ${Gs}`,permissions:{contents:"write","pull-requests":"read",...$?{"id-token":"write"}:{}},...Po($),concurrency:{group:"terragucci-ephemeral-${{ github.repository }}-${{ github.event.pull_request.number }}","cancel-in-progress":!1},env:{...p,TG_TOKEN:"${{ github.token }}",TG_PR:"${{ github.event.pull_request.number }}",TG_SHA:"${{ github.event.pull_request.head.sha }}",TG_ACTION:"${{ github.event.action }}",...O},steps:as(new se({name:"Apply the pull request's copy of the ephemeral roots, or destroy it once it closes",shell:"bash",run:Lhe(t,a,n,w)}),!1,!0)}));let ct=["github.ref == format('refs/heads/{0}', github.event.repository.default_branch)",...h.map(q=>`github.ref == 'refs/heads/${q}'`)],nr=`${E?"github.event_name == 'push' && ":""}${ct.length>1?`(${ct.join(" || ")})`:ct[0]}`;for(let q of $r){if(q.done){Ft.set(q.name,new pt({"runs-on":"ubuntu-latest",container:{image:r},needs:q.needs,if:nr,permissions:{contents:"read",statuses:"write"},env:{TG_TOKEN:"${{ github.token }}",TG_SHA:"${{ github.sha }}"},steps:[new se({name:q.step,shell:"bash",run:q.body})]}));continue}Ft.set(q.name,new pt({"runs-on":"ubuntu-latest",container:{image:r},needs:q.needs.length===0?"check":q.needs.length===1?q.needs[0]:q.needs,if:nr,permissions:{contents:kr||st||Ot==="resource"?"write":"read",statuses:"write","pull-requests":"write",...v,...$?{"id-token":"write"}:{}},...Po($),...q.environment!==void 0?{environment:q.environment}:{},...q.share===void 0&&q.group===void 0&&rt?{concurrency:dP(t)}:{},env:{...p,TG_TOKEN:"${{ github.token }}",TG_SHA:"${{ github.sha }}",TG_BEFORE:"${{ github.event.before }}",TG_BRANCH:"${{ github.event.repository.default_branch }}",...O,...ye,...ne},steps:[...as(new se({name:q.step,shell:"bash",run:q.body}),!0,!1,void 0,!0,q.share!==void 0||q.group!==void 0?new se({name:"Fetch the wave's decision",uses:pe,with:{name:`${hl}-${q.wave}`,path:hl}}):void 0),...q.decides?[new se({name:C?"Hand the decision to the wave's groups":"Hand the decision to the wave's shares",uses:D,with:{name:`${hl}-${q.wave}`,path:`${hl}/`,"if-no-files-found":"error"}})]:[],new se({name:"Keep the apply report",if:"always()",uses:t==="forgejo"?"actions/upload-artifact@v3":"actions/upload-artifact@v4",with:{name:`${be}-${q.name}`,path:`${be}/`,"if-no-files-found":"ignore"}}),...Ce?[new se({name:"Keep the agent input",if:"failure()",uses:t==="forgejo"?"actions/upload-artifact@v3":"actions/upload-artifact@v4",with:{name:`${vl}-${q.name}`,path:`${vl}/`,"if-no-files-found":"ignore"}})]:[]]}))}if(V&&Ft.set("confirm",new pt({"runs-on":"ubuntu-latest",container:{image:r},needs:"check",if:`${E?"github.event_name == 'push' && ":""}github.ref == format('refs/heads/{0}', github.event.repository.default_branch)`,permissions:{contents:"read",statuses:"write",...$?{"id-token":"write"}:{}},...Po($),env:{...p,TG_TOKEN:"${{ github.token }}",TG_SHA:"${{ github.sha }}",...O},steps:[...as(new se({name:`Plan every ${Ze} to confirm the merge applied`,shell:"bash",run:Pq(n,s,t,a,_)}),!0),new se({name:"Keep the plan report",if:"always()",uses:t==="forgejo"?"actions/upload-artifact@v3":"actions/upload-artifact@v4",with:{name:`${be}-confirm`,path:`${be}/`,"if-no-files-found":"ignore"}})]})),is&&Ft.set("tips",new pt({"runs-on":"ubuntu-latest",container:{image:r},needs:de,if:`${E?"github.event_name == 'push' && ":""}github.ref == format('refs/heads/{0}', github.event.repository.default_branch)`,permissions:{contents:"write","pull-requests":"write"},concurrency:{group:"terragucci-tips-${{ github.repository }}","cancel-in-progress":!1},env:{TG_TOKEN:"${{ github.token }}"},steps:[new se({uses:"actions/checkout@v4",with:{"fetch-depth":0}}),...M?[new se({name:te,run:M})]:[],new se({name:"Open a pull request for each tip",shell:"bash",run:Sq(n,t,c,e.synth)})]})),U&&Ft.set("version-bump",new pt({"runs-on":"ubuntu-latest",container:{image:r},needs:de,if:`${E?"github.event_name == 'push' && ":""}github.ref == format('refs/heads/{0}', github.event.repository.default_branch)`,permissions:{contents:"write","pull-requests":"write"},concurrency:{group:"terragucci-version-bump-${{ github.repository }}","cancel-in-progress":!1},env:{TG_TOKEN:"${{ github.token }}",...I},steps:[new se({uses:"actions/checkout@v4",with:{"fetch-depth":0}}),new se({name:"Suggest the next version of each module that changed",shell:"bash",run:Rq(t,c)})]})),e.publish&&Ft.set("publish",new pt({"runs-on":"ubuntu-latest",container:{image:r},needs:de,if:"github.ref == format('refs/heads/{0}', github.event.repository.default_branch)",permissions:{contents:"write"},concurrency:{group:"terragucci-publish-${{ github.repository }}","cancel-in-progress":!1},env:{TERRAGUCCI_REGISTRY_USER:"${{ secrets.TERRAGUCCI_REGISTRY_USER }}",TERRAGUCCI_REGISTRY_PASSWORD:"${{ secrets.TERRAGUCCI_REGISTRY_PASSWORD }}",TERRAGUCCI_REGISTRY_INSECURE:"${{ secrets.TERRAGUCCI_REGISTRY_INSECURE }}",...e.attest?{COSIGN_PRIVATE_KEY:"${{ secrets.COSIGN_PRIVATE_KEY }}",COSIGN_PASSWORD:"${{ secrets.COSIGN_PASSWORD }}"}:{},...e.publishBucket?z$(t,{bucket:e.publishBucket}):{}},steps:[new se({uses:"actions/checkout@v4",with:{"fetch-depth":0}}),...M&&o?[new se({name:`Install ${o.binary} ${o.version}`,run:M})]:[],...e.attest?[new se({name:`Install cosign ${Ud}`,run:F$("cosign",Ud,t)})]:[],new se({name:"Publish the modules that changed",shell:"bash",run:Aq(t)})]})),E&&Ft.set("drift",new pt({"runs-on":"ubuntu-latest",container:{image:r},if:"github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.pr == '')",permissions:{contents:_r?"write":"read",issues:"write",..._r?{"pull-requests":"write"}:{},...a?{"id-token":"write"}:{}},...Po(!!a),...e.agentDrift?{outputs:{agent:"${{ steps.drift.outputs.agent }}",issue:"${{ steps.drift.outputs.issue }}"}}:{},env:{...p,TG_TOKEN:"${{ github.token }}",TG_SHA:"${{ github.sha }}",...O,...L,...z$(t,e.reports),...Z},steps:[...as(new se({...e.agentDrift?{id:"drift"}:{},name:`Plan every ${Ze} against what exists, and keep the drift issue`,shell:"bash",run:Oq(n,s,t,a,_,_r,me,e.agentDrift!==void 0)}),!0,!1,Bl),new se({name:"Keep the drift report",if:"always()",uses:t==="forgejo"?"actions/upload-artifact@v3":"actions/upload-artifact@v4",with:{name:`${be}-drift`,path:`${be}/`,"if-no-files-found":"ignore"}}),...Ul?[new se({name:"Keep the agent input",if:"always()",uses:t==="forgejo"?"actions/upload-artifact@v3":"actions/upload-artifact@v4",with:{name:`${vl}-drift`,path:`${vl}/`,"if-no-files-found":"ignore"}})]:[]]})),E&&e.agentDrift)for(let[q,Se]of nq(t,r,e.agentDrift,`${be}-drift`))Ft.set(q,Se);t==="forgejo"&&Dhe(Ft);let rr=q=>(vq(q,e.runner,t,Oi),Iq((t==="forgejo"?Ohe:iE).serialize(q))),Io=[];if(e.resume){let q=new Map([["workflow",new Gu({name:"terragucci resume",on:{schedule:[{cron:kq(e.resume)}],workflow_dispatch:{}},env:A,permissions:{contents:"read"}})],["resume",new pt({"runs-on":"ubuntu-latest",container:{image:r},permissions:{contents:"write",statuses:"write","pull-requests":"write",...v,...$?{"id-token":"write"}:{}},...Po($),...rt?{concurrency:dP(t)}:{},env:{...p,TG_TOKEN:"${{ github.token }}",...O,...ye,...ne},steps:[...as(new se({name:"Apply a waiting wave once its approval stands",shell:"bash",run:aye(n,s,t,a,mt)}),!0,!0,void 0,!0),new se({name:"Keep the apply report",if:"always()",uses:t==="forgejo"?"actions/upload-artifact@v3":"actions/upload-artifact@v4",with:{name:`${be}-resume`,path:`${be}/`,"if-no-files-found":"ignore"}})]})]]);Io.push({path:Mhe[t],content:Tp(r,e.imageFromConfig)+rr(q)})}if(y){let q=new Map([["workflow",new Gu({name:"terragucci ephemeral",on:{schedule:[{cron:kq(y.sweep)}],workflow_dispatch:{}},env:A,permissions:{contents:"read"}})],["sweep",new pt({"runs-on":"ubuntu-latest",container:{image:r},permissions:{contents:"write","pull-requests":"read",...$?{"id-token":"write"}:{}},...Po($),concurrency:{group:"terragucci-ephemeral-sweep-${{ github.repository }}","cancel-in-progress":!1},env:{...p,TG_TOKEN:"${{ github.token }}",...O},steps:as(new se({name:"Destroy the ephemeral copies whose TTL passed or whose pull request closed",shell:"bash",run:$q(t,a,n)}),!1,!0)})]]);Io.push({path:mP[t],content:Tp(r,e.imageFromConfig)+rr(q)})}return e.review&&Io.push({path:Dc[t],content:Tp(r,e.imageFromConfig)+rr(sq(t,r,e.review,{pipelineName:"terragucci",env:A}))}),R&&Io.push({path:gP[t],content:Tp(r,e.imageFromConfig)+rr(Eye(t,r,R,A,c,M?{name:te,run:M}:void 0))}),{path:Xr[t],content:Tp(r,e.imageFromConfig)+rr(Ft)+jq(e.ownJobs,[...Ft.keys()].filter(q=>q!=="workflow"),t),applyJobs:Ge,...Io.length?{extra:Io}:{}}}function Eye(e,t,n,r,o,s){let i=o?`\${{ secrets.${o} }}`:"${{ github.token }}",a=new Gu({name:"terragucci-rollout",on:{schedule:[{cron:n}],workflow_dispatch:{}},env:r,permissions:{contents:"read"},concurrency:{group:"terragucci-rollout-${{ github.repository }}","cancel-in-progress":!1}}),c=new pt({"runs-on":"ubuntu-latest",container:{image:t},permissions:{contents:"write","pull-requests":"write",statuses:"read",...e==="github"?{checks:"read"}:{}},env:{TG_TOKEN:i},steps:[new se({uses:"actions/checkout@v4",with:{"fetch-depth":0,...o?{token:i}:{}}}),...s?[new se(s)]:[],new se({name:"Open the next wave of each rollout whose last wave merged and applied",shell:"bash",run:Cq(e,o)})]});return new Map([["workflow",a],["rollout",c]])}var Z$=e=>e.length<2?e.join(""):`${e.slice(0,-1).join(", ")} and ${e[e.length-1]}`;function Jq(e,t){if(e===void 0)return;let n=typeof e=="string"?{labels:[e]}:Array.isArray(e)?{labels:[...e]}:{group:e.group,labels:[...e.labels??[]]};if(!((t==="github"||t===void 0)&&n.group===void 0&&!n.labels.includes("self-hosted")))return n}function Kq(e,t){if(e.group!==void 0&&t.group!==void 0&&e.group!==t.group)return!1;let n=(r,o)=>r.every(s=>o.includes(s));return n(e.labels,t.labels)||n(t.labels,e.labels)}var V$=e=>[...e.group!==void 0?[`group ${e.group}`]:[],...e.labels.length?[`labels ${e.labels.join(", ")}`]:[]].join(" and ");function Tye(e,t,n){let r=(e??[]).filter(a=>a.environment!==void 0);if(r.length===0)return[];let o=Jq(kl(t,"plan"),n),s=r.map(a=>({name:a.name,at:Jq(a.runner??kl(t,"apply"),n)})),i=[];for(let[a,c]of s.entries())if(c.at){o&&Kq(c.at,o)&&i.push({rule:kx,message:`the apply jobs of environment ${c.name} (${V$(c.at)}) and the plan jobs (${V$(o)}) can run on the same runner, so a pull request's code planned there can leave files or processes for ${c.name}'s apply; give ${c.name}'s apply runners a label no plan job uses, and use runners that start clean for each job`});for(let u of s.slice(a+1))!u.at||!Kq(c.at,u.at)||i.push({rule:kx,message:`the apply jobs of environments ${c.name} (${V$(c.at)}) and ${u.name} (${V$(u.at)}) can run on the same runner, so one environment's apply can leave files or processes for the other's; give each environment's apply runners a label of its own`})}return i}function Aye(e,t){let n=e.oidc;return!n||!t?[]:[...n.apply_role!==void 0&&n.apply_role===t.apply_role?[{what:"oidc.apply_role",id:n.apply_role}]:[],...n.gcp?.apply_service_account!==void 0&&n.gcp.apply_service_account===t.gcp?.apply_service_account?[{what:"oidc.gcp.apply_service_account",id:n.gcp.apply_service_account}]:[],...n.azure?.apply_client_id!==void 0&&n.azure.apply_client_id===t.azure?.apply_client_id?[{what:"oidc.azure.apply_client_id",id:n.azure.apply_client_id}]:[]]}function Oye(e,t){if(!e||e.length<2)return[];let n=new Map;for(let o of e)for(let{what:s,id:i}of Aye(o,t)){let a=n.get(i)??{what:s,names:[],envs:[]};a.names.push(o.name),o.environment!==void 0&&a.envs.push(o.name),n.set(i,a)}let r=[];for(let[o,{what:s,names:i,envs:a}]of n){if(i.length<2||a.length===0)continue;let c=s==="oidc.apply_role"?"apply_role":s==="oidc.gcp.apply_service_account"?"gcp.apply_service_account":"azure.apply_client_id";r.push({rule:iM,message:`the apply jobs of ${i.length===a.length?"environments":"the groups"} ${Z$(i)} all apply as ${o} (${s}), so its trust must accept each of their jobs and no condition keeps one environment's job from another's role; set environments.<name>.oidc.${c} for ${Z$(a)}`})}return r}var Pye=e=>!!(e&&(e.apply_role||e.roles&&Object.keys(e.roles).length>0));function jye(e,t,n){if(n!=="gitlab"||!e||!e.some(s=>s.environment!==void 0))return[];let r=new Map;for(let s of e){if(!Pye(s.oidc))continue;let i=s.audience??Es(t,"apply");r.set(i,[...r.get(i)??[],s])}let o=[];for(let[s,i]of r){let a=i.filter(u=>u.environment!==void 0&&u.audience===void 0).map(u=>u.name);if(i.length<2||a.length===0)continue;let c=i.map(u=>u.name);o.push({rule:aM,message:`on GitLab the apply jobs of ${c.every(u=>u!==Ka)&&i.every(u=>u.environment!==void 0)?"environments":"the groups"} ${Z$(c)} carry the same AWS token audience ${s}, and AWS cannot read GitLab's environment claims, so an apply role's trust cannot tell their jobs apart; set environments.<name>.oidc.audience for ${Z$(a)} and have each apply role trust its environment's audience alone`})}return o}function Vq(e,t,n){return[...Tye(e,t.runner,n),...Oye(e,t.oidc),...jye(e,t.oidc,n)]}var Iye="https://intentius.io/terragucci/terragucci.schema.json",ko={type:"string"},Rl={type:"boolean"},xl={type:"string",pattern:"\\S"},EP={type:"string",pattern:"^[^\\r\\n]*\\S[^\\r\\n]*$"},$n=e=>({type:"string",pattern:e.source}),_i=$n(rd),Y$={type:"array",items:ko},Zq={anyOf:[ko,{const:!1}]},Et=e=>({enum:[...e]}),Si=(e,t)=>({type:"integer",minimum:e,...t===void 0?{}:{maximum:t}}),Wc=e=>({$ref:`#/$defs/${e}`}),Sl=e=>({anyOf:[{type:"null"},e]}),TP={type:"string",pattern:"^[^\\s,;=']+$"},n2={type:"string",minLength:1,pattern:"^(?!/)(?!(?:.*/)?\\.\\.(?:/|$))"};function Qe(e,t,n={}){let r=e.filter(s=>!(s in t)),o=Object.keys(t).filter(s=>!e.includes(s));if(r.length||o.length)throw new Error(`config schema: keys ${[...r,...o].join(", ")} disagree with config.ts`);return{type:"object",properties:t,additionalProperties:!1,...n}}var AP=(e,t)=>Object.fromEntries(e.map(n=>[n,t(n)])),Cye=e=>[...e].map(t=>/[a-z]/i.test(t)?`[${t.toUpperCase()}${t.toLowerCase()}]`:t).join(""),Yq={type:"string",pattern:`^(?!(?:${ku.map(Cye).join("|")}))(?!(?:${nd.join("|")})$)[A-Za-z_][A-Za-z0-9_]*$`},xP=Qe(["plan","apply"],{plan:{type:"string",minLength:1},apply:{type:"string",minLength:1}},{required:["plan","apply"]}),X$={type:"string",pattern:"^[^@\\s]+@[^@\\s]+$"},Nye=Qe(["plan","apply"],{plan:X$,apply:X$},{required:["plan","apply"]}),Q$={anyOf:[$n(jf),{type:"array",minItems:1,uniqueItems:!0,items:$n(jf)},Qe(["group","labels"],{group:xl,labels:{type:"array",minItems:1,uniqueItems:!0,items:$n(jf)}},{required:["group"]})]},Xq={anyOf:[Rl,Qe(bf,{command:EP,key_secret:_i,max_turns:Si(1),timeout:Si(1)})]},Dye={type:"object",propertyNames:$n(qa),additionalProperties:Sl(Wc("hcl_value"))};function Qq(e){let t={backend:Sl({type:"object",minProperties:1,maxProperties:1,propertyNames:$n(qa),additionalProperties:Dye}),providers:{type:"object",propertyNames:$n($S),additionalProperties:Sl({type:"object",properties:{source:{type:["string","null"]},version:{type:["string","null"]}},propertyNames:$n(qa),additionalProperties:Sl(Wc("hcl_value")),not:{required:["alias"]}})},required_version:Sl(xl),disable_init:{type:["boolean","null"]}};if(!e)return Qe(bu,t);let n={type:"object",additionalProperties:Sl(Wc("generate_level"))};return Qe(yf,{...t,dirs:n,roots:n})}var Mye=Qe(xf,{when:Et(id),merge:Et(LS),merge_token_env:_i,requires:{type:"array",uniqueItems:!0,items:Et(Cn)},resume:Si(5,60),branches:{type:"object",minProperties:1,propertyNames:$n(lR),additionalProperties:{type:"array",minItems:1,items:TP}},point:$n(uR)}),Lye=Qe(Uy,{name:xl,run:xl,before:Et(td),after:Et(td),roots:Y$,on_failure:Et(FS)},{required:["run"],oneOf:[{required:["before"]},{required:["after"]}],if:{properties:{on_failure:{const:"approve"}},required:["on_failure"]},then:{properties:{before:Et(["init","plan"]),after:Et(["init","plan"])}}}),Gye=Qe(Jy,{bucket:ko,dir:n2,endpoint:ko,prefix:ko,url:{type:"string",pattern:"^https://[^/\\s?#]+/?$"},namespace:$n(_u),namespaces:{type:"object",propertyNames:{pattern:"^[^/]"},additionalProperties:$n(_u)},system:$n(rR),download:Et(["tarball","git-tags","oci"])},{required:["url","namespace"],oneOf:[{required:["bucket"]},{required:["dir"]}],dependentRequired:{endpoint:["bucket"]}}),e2={type:"string",pattern:"^(?:git-tags$|oci://[^/]+/.)"},Fye=Qe([...qy],{path:ko,publish:{anyOf:[e2,{type:"array",items:e2}]},attest:{anyOf:[Rl,Qe(["key"],{key:{type:"string",minLength:1}})]},require:{const:"attested"},trusted:{type:"array",items:Qe(["source","key","ledger"],{source:{type:"string",pattern:"^(oci://[^/]+/.+|(git::)?(https?|ssh)://.+)$"},key:{type:"string",minLength:1},ledger:{type:"string",pattern:"^(https?|ssh|file)://."}},{required:["source","key","ledger"]})},test:Rl,registry:Gye}),zye=Qe(kf,{version:$n(Ja),exclude:Y$,parallelism:Si(1),dependents:Et(CS),credentials:{type:"object",additionalProperties:xP}}),Uye=Qe(Wy,{engine:Et(NS),path:n2,source:{type:"string",pattern:"^git\\+(https?|file)://.*@"},namespace:{type:"string",pattern:"^[A-Za-z_][A-Za-z0-9_.]*$"},input:Et(DS),override:{type:"array",minItems:1,items:EP}}),t2={type:"array",minItems:1,items:{type:"string",pattern:YS}},Bye=Qe(_f,{modules:t2,providers:t2},{minProperties:1}),Wye=Qe(Hy,{plan_role:{type:"string",minLength:1},apply_role:{type:"string",minLength:1},audience:{anyOf:[ko,Qe(Sf,{plan:{type:"string",minLength:1},apply:{type:"string",minLength:1},drift:{type:"string",minLength:1}})]},roles:{type:"object",minProperties:1,additionalProperties:xP},gcp:Qe(["workload_identity_provider","plan_service_account","apply_service_account","token_url","roles"],{workload_identity_provider:$n(gR),plan_service_account:{type:"string",pattern:"^[^@\\s]+@[^@\\s]+$"},apply_service_account:{type:"string",pattern:"^[^@\\s]+@[^@\\s]+$"},token_url:{type:"string",pattern:"^https://[^\\s/]+/\\S*$"},roles:{type:"object",minProperties:1,additionalProperties:Nye}},{required:["workload_identity_provider","plan_service_account","apply_service_account"]}),azure:Qe(["tenant_id","subscription_id","plan_client_id","apply_client_id","audience","roles"],{tenant_id:{type:"string",minLength:1},subscription_id:{type:"string",minLength:1},plan_client_id:{type:"string",minLength:1},apply_client_id:{type:"string",minLength:1},audience:{type:"string",minLength:1},roles:{type:"object",minProperties:1,additionalProperties:xP}},{required:["tenant_id","subscription_id","plan_client_id","apply_client_id"]})},{minProperties:1,dependentRequired:{plan_role:["apply_role"],apply_role:["plan_role"]},if:{required:["audience"],not:{required:["roles"]}},then:{required:["plan_role","apply_role"]}}),Hye=Qe(Ef,{backend:Et(Af),url:{type:"string",pattern:"^https?://[^/\\s]+"},model:{type:"string",minLength:1,pattern:"^(?![\\s\\S]*(?:^|[-_.])(?:latest|preview)$)"},token_env:_i,thresholds:Qe($u,AP($u,()=>({type:"number",exclusiveMinimum:0,maximum:1})))},{required:["backend"],allOf:[{if:{properties:{backend:{const:"jev"}}},then:{required:["token_env"]},else:{required:["url"]}},{if:{properties:{backend:{const:"laya"}}},else:{required:["model"]}}]}),qye={anyOf:[Rl,Qe(vu,AP(vu,e=>US.includes(e)?$n(fR):e==="dir"?{type:"string",pattern:"^(?!/)(?!(?:.*/)?\\.\\.(?:/|$))[^\\r\\n]+$"}:{type:"string",pattern:"^[^\\r\\n]+$"}))]},Jye=Qe(Rf,{roots:{type:"array",minItems:1,items:TP},ttl:{type:"string",pattern:"^[1-9]\\d*[mhd]$"},sweep:Si(5,60)},{required:["roots"]}),Kye=Qe(["plan_role","apply_role","audience","gcp","azure"],{plan_role:{type:"string",minLength:1},apply_role:{type:"string",minLength:1},audience:{type:"string",minLength:1,pattern:"\\S"},gcp:Qe(["plan_service_account","apply_service_account"],{plan_service_account:X$,apply_service_account:X$},{required:["plan_service_account","apply_service_account"]}),azure:Qe(["plan_client_id","apply_client_id"],{plan_client_id:{type:"string",minLength:1},apply_client_id:{type:"string",minLength:1}},{required:["plan_client_id","apply_client_id"]})},{minProperties:1,dependentRequired:{plan_role:["apply_role"],apply_role:["plan_role"]}});function Vye(e){return Qe(["roots","gate","oidc","runner","parallelism","forge_environment"],{roots:{type:"array",minItems:1,items:TP},gate:Et(jS),oidc:Kye,runner:{anyOf:[Q$,Qe(["apply"],{apply:Q$},{required:["apply"]})]},parallelism:Si(1),forge_environment:$n(hR)},e?{required:["roots"]}:{})}function r2(e){return{roots:{...Y$,description:"Globs of root directories. Detected when absent."},binary:{...Et(od),description:"The binary the pipeline runs. Detected when absent."},version:e?{anyOf:[ko,{type:"object",additionalProperties:$n(Ja)}],description:"The binary's release, or a map of root glob to release."}:{...ko,description:"The binary's release."},generate:{...Wc("generate"),description:"Backend, provider and version files terragucci generate writes for each plain root."},forge:{...Et(Wr),description:"The forge, for a host terragucci cannot name."},url:{...ko,description:"Where the project lives, for a forge not on https or the default port."},gate:{...Et(jS),description:"When a wave waits for an approval. on-destroy is an older name for on-destructive."},approval:{anyOf:[Et(Do),{type:"object",additionalProperties:!1,properties:{mode:{...Et(Do),description:"What counts as a waiting wave's approval."},require_signed:{type:"boolean",description:"Refuse an unsigned approval on the ledger, so every ledger approval is tied to the principal who signed it."}}}],description:"What counts as a waiting wave's approval: the mode, or a map with the mode and require_signed."},apply:{...Mye,description:"When a change applies."},locks:{...Et(MS),description:"When a pull request takes its root locks."},waves:{type:"object",properties:{canary:Y$,jobs:Si(1),after:{type:"object",additionalProperties:{type:"array",minItems:1,items:{type:"string",minLength:1}}},compose:{type:"boolean"},compose_max:Si(2)},description:"The canary wave, the jobs one wave spreads across, extra apply order, and whether a change's roots apply as one composed run."},drift:{...Zq,description:"A cron schedule for tf-drift, or false."},synth:{...xl,description:"The command that writes the roots before any job reads them."},steps:{type:"array",items:Lye,description:"Commands run before or after a root's init, plan, apply and drift."},image:{type:"string",pattern:"^[^\\s]+$",description:"The image every job runs in, built FROM terragucci's."},notify:{...Qe(ZS,{slack:_i,teams:_i,webhook:_i,webhook_key:_i,relay:{type:"string",pattern:"^[A-Za-z0-9][A-Za-z0-9 ._-]{0,63}$"}},{minProperties:1,dependentRequired:{webhook:["webhook_key"],webhook_key:["webhook"]},if:{required:["relay"]},then:{anyOf:[{required:["slack"]},{required:["teams"]}]}}),description:"The secrets holding Slack, Teams or webhook addresses, and the relay's name."},cost:{anyOf:[{const:!0},Qe(eR,{key_secret:_i,command:xl,approve_above:{type:"number",minimum:0}})],description:"Cost estimates per root in the plan note."},comments:{...Zq,description:"GitLab only: the cron of the comments schedule, or false."},rollouts:e?{anyOf:[xl,{const:!1}],description:"A cron schedule for the job that opens each rollout's next wave, or false."}:{const:!1,description:"A single repo's key; a control repo runs terragucci respond rollout itself."},gitlab:{...Qe(QS,{token:Et(GS)}),description:"GitLab only: how the project keeps its forge token."},runtime:{...Et(IS),description:"Where the stages run."},reports:{type:"object",properties:{bucket:ko,endpoint:{},prefix:{},url:{type:"string",pattern:"^https?://[^\\s?#]+$"},role:{type:"string",pattern:"^arn:aws[\\w-]*:iam::\\d{12}:role/\\S+$"}},required:["bucket"],description:"A bucket for plan reports: s3://, gs:// or az://."},token_env:{...ko,description:"The environment variable holding the forge token."},env:{type:"object",additionalProperties:ko,description:"Environment variables every job gets. Values only, never secrets."},runner:{anyOf:[Q$,{type:"object",minProperties:1,not:{required:["group"]},propertyNames:Et($f),additionalProperties:Q$}],description:"The runner each job runs on: a label, a list of labels, a GitHub runner group, or one per stage."},pass:{...Qe(tR,{secrets:{type:"array",minItems:1,uniqueItems:!0,items:Yq},vars:{type:"array",minItems:1,uniqueItems:!0,items:Yq}},{minProperties:1}),description:"Names of CI secrets and variables the plan, apply and drift jobs get."},telemetry:{...Qe(XS,{headers_secret:_i,trace_url:{type:"string",pattern:"^https?://\\S*\\{trace_id\\}\\S*$"}},{minProperties:1}),description:"The secret holding OTLP headers, and a trace link with {trace_id}."},tips:{...Rl,description:"Tips in the plan note."},modules:{...Fye,description:"Module publishing, attestation and the module registry."},oidc:{...Wye,description:"Cloud identities the jobs take over OIDC: a read-only one for plan, a write one for apply."},parallelism:{...Si(1),description:"How many roots of one dependency layer plan at once."},terragrunt:{...zye,description:"Terragrunt settings."},atmos:{...Qe(Tf,{version:{type:"string",pattern:"^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.]+)?$"}}),description:"Atmos settings."},policy:{...Uye,description:"Policy checks over each plan."},sources:{...Bye,description:"Globs of the module sources and provider addresses a root may use, checked before init."},atlantis_comments:{...Rl,description:"Read atlantis plan and atlantis apply comments as terragucci's."},respond:{...Qe(Object.keys(Us),AP(Object.keys(Us),t=>Et(Us[t]))),description:"The response to each pipeline event."},agent:{...Qe(nR,{via:Et(zS),token_env:{type:"string",minLength:1},login:{type:"string",pattern:Cf.source,description:"The forge login agent.token_env's token acts as. No approval by it counts."},comment:Xq,drift:Xq},{required:["via","token_env"]}),description:"The coding agent behind /terragucci agent and the drift agent."},review:{...Qe(vf,{agent:Rl,command:EP,key_secret:_i,instructions:{type:"string",pattern:"^(?:\\./)?(?!/)[A-Za-z0-9_./-]+$"},timeout:Si(1)},{anyOf:[{maxProperties:0},{required:["agent"]}]}),description:"A model's review of each pull request's intent against its plan."},decide:{...Hye,description:"The typed-decision service."},audit_region:{type:"string",pattern:"^[a-z]{2}(-[a-z]+)+-\\d+$",description:"The AWS region whose CloudTrail drift attribution reads."},dashboards:{...qye,description:"Dashboards and alert rules written next to the pipeline."},own_jobs:{anyOf:[{type:"string",pattern:"^(?!/)(?!(?:.*/)?\\.\\.(?:/|$)).*\\.ya?ml$"},{type:"object",minProperties:1,propertyNames:$n(aR),additionalProperties:{type:"object",minProperties:1}}],description:"Jobs of your own, in the forge's syntax, or the path of a YAML file holding them."},ephemeral:{...Jye,description:"Roots each pull request gets a copy of."},environments:{type:"object",minProperties:1,propertyNames:{...$n(WS),not:{anyOf:[{enum:["default","decide"]},{pattern:"^share-[0-9]+$"}]}},additionalProperties:Vye(e),description:"Named environments: each one's root globs, and its gate, identities, apply runner, parallelism and forge environment."}}}function Zye(){return{type:"object",properties:r2(!1),additionalProperties:!1}}function Yye(){let e=r2(!0);return{$schema:"https://json-schema.org/draft/2020-12/schema",$id:Iye,title:"terragucci.yml",description:"One repo's settings, or a control repo's defaults and projects. https://intentius.io/terragucci/reference/config/",type:["object","null"],properties:{...e,defaults:{...Wc("project"),not:{required:["url"]},description:"Settings every project takes, under its own."},projects:{type:"object",propertyNames:{pattern:"^(?:https?://)?[^/]+(?:/[^/]+){2,}/*$"},additionalProperties:Sl(Wc("project")),description:"<host>/<owner>/<name> to that project's settings."}},additionalProperties:!1,dependentRequired:{defaults:["projects"]},if:{required:["projects"]},then:{propertyNames:Et(["defaults","projects"])},$defs:{project:Zye(),generate:Qq(!0),generate_level:Qq(!1),hcl_value:{anyOf:[{type:["string","number","boolean"]},{type:"array",items:Wc("hcl_value")},{type:"object",additionalProperties:Wc("hcl_value")}]}}}}var Bc=e=>e!==null&&typeof e=="object"&&!Array.isArray(e);function Xye(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function Qye(e,t){return t==="integer"?Number.isInteger(e):Xye(e)===t}var gh=e=>JSON.stringify(e,(t,n)=>Bc(n)?Object.fromEntries(Object.keys(n).sort().map(r=>[r,n[r]])):n);function _l(e,t,n=e,r="config"){let o=[],s=e;if(typeof s.$ref=="string"){let a=n.$defs[s.$ref.replace("#/$defs/","")];if(!a)throw new Error(`config schema: no ${s.$ref}`);o.push(..._l(a,t,n,r))}if(s.type!==void 0){let a=Array.isArray(s.type)?s.type:[s.type];if(!a.some(c=>Qye(t,c)))return[...o,`${r}: must be ${a.join(" or ")}`]}if("const"in s&&gh(t)!==gh(s.const)&&o.push(`${r}: must be ${JSON.stringify(s.const)}`),Array.isArray(s.enum)&&!s.enum.some(a=>gh(a)===gh(t))&&o.push(`${r}: must be one of ${s.enum.join(", ")}`),typeof t=="string"&&(typeof s.minLength=="number"&&[...t].length<s.minLength&&o.push(`${r}: must not be empty`),typeof s.pattern=="string"&&!new RegExp(s.pattern,"u").test(t)&&o.push(`${r}: does not match ${s.pattern}`)),typeof t=="number"&&(typeof s.minimum=="number"&&t<s.minimum&&o.push(`${r}: must be ${s.minimum} or more`),typeof s.maximum=="number"&&t>s.maximum&&o.push(`${r}: must be ${s.maximum} or less`),typeof s.exclusiveMinimum=="number"&&t<=s.exclusiveMinimum&&o.push(`${r}: must be more than ${s.exclusiveMinimum}`)),Array.isArray(t)&&(typeof s.minItems=="number"&&t.length<s.minItems&&o.push(`${r}: must list at least ${s.minItems}`),s.uniqueItems===!0&&new Set(t.map(gh)).size!==t.length&&o.push(`${r}: lists an item twice`),Bc(s.items)&&t.forEach((a,c)=>o.push(..._l(s.items,a,n,`${r}[${c}]`)))),Bc(t)){let a=Object.keys(t),c=s.properties??{};typeof s.minProperties=="number"&&a.length<s.minProperties&&o.push(`${r}: must set at least ${s.minProperties} key(s)`),typeof s.maxProperties=="number"&&a.length>s.maxProperties&&o.push(`${r}: must set at most ${s.maxProperties} key(s)`);for(let u of s.required??[])u in t||o.push(`${r}.${u}: is required`);for(let[u,l]of Object.entries(s.dependentRequired??{}))if(u in t)for(let d of l)d in t||o.push(`${r}.${d}: is required with ${u}`);for(let u of a){let l=`${r}.${u}`;if(Bc(s.propertyNames))for(let d of _l(s.propertyNames,u,n,l))o.push(`${l}: is not a key here (${d.slice(l.length+2)})`);u in c?o.push(..._l(c[u],t[u],n,l)):s.additionalProperties===!1?o.push(`${l}: is not a setting`):Bc(s.additionalProperties)&&o.push(..._l(s.additionalProperties,t[u],n,l))}}let i=a=>_l(a,t,n,r);for(let a of s.allOf??[])o.push(...i(a));if(Array.isArray(s.anyOf)){let a=s.anyOf.map(i);a.some(c=>c.length===0)||o.push(...a.reduce((c,u)=>u.length<c.length?u:c))}if(Array.isArray(s.oneOf)&&s.oneOf.filter(c=>i(c).length===0).length!==1&&o.push(`${r}: must match exactly one of ${s.oneOf.map(c=>JSON.stringify(c)).join(", ")}`),Bc(s.not)&&i(s.not).length===0&&o.push(`${r}: must not match ${JSON.stringify(s.not)}`),Bc(s.if)){let a=i(s.if).length===0?s.then:s.else;Bc(a)&&o.push(...i(a))}return o}function o2(e){return _l(Yye(),e)}var ewe=1440,twe=30,nwe=50,s2="terragucci/apply",i2=e=>`<!-- terragucci:note=${e} -->`,rwe=/<!-- terragucci:note=(\d+) -->/g,Ts=e=>e.slice(0,8);function owe(e){let t=e.CI_API_V4_URL,n=e.CI_PROJECT_ID,r=e.TG_TOKEN,o=e.CI_DEFAULT_BRANCH;if(!t||!n||!r||!o)throw new S("comment --forge gitlab --poll needs CI_API_V4_URL, CI_PROJECT_ID, CI_DEFAULT_BRANCH and TG_TOKEN in the environment");return{t:{forge:"gitlab",origin:e.CI_SERVER_URL??t,path:n,token:r,api:t},base:o}}async function a2(e){let t=e.env??process.env,n=e.fetch??fetch,r=e.now??new Date,{t:o,base:s}=owe(t),i=encodeURIComponent(o.path),a=(y,w,b)=>kt(n,o,y,w,b),c=new Date(r.getTime()-ewe*6e4).toISOString(),u,l,d;try{let y=await a("GET","/user");u=Number(y?.id),l=String(y?.username??""),d=await a("GET",`/projects/${i}/merge_requests?state=all&updated_after=${encodeURIComponent(c)}&order_by=updated_at&sort=desc&per_page=100`)}catch(y){return{outcomes:[],fail:`could not list the merge requests (${y.message})`}}if(!Array.isArray(d))return{outcomes:[],fail:"GitLab answered the merge request list with something other than a list"};let p=[],f=[],g=[],m=jt("gitlab",t,n),h={api:a,id:i,me:u,meName:l,forge:m,apiUrl:o.api,token:o.token,fetch:n,reportDir:e.reportDir??"terragucci-report"};for(let y of d){let w=y?.iid;if(!Number.isInteger(w)||w<1)continue;let b;try{b=await a("GET",`/projects/${i}/merge_requests/${w}/notes?order_by=created_at&sort=desc&per_page=100`)}catch(_){return{outcomes:p,plans:f,reviews:g,fail:`could not read the notes of !${w} (${_.message})`}}if(!Array.isArray(b))continue;let k=e.planNotes?await _W(h,y,b):void 0;k&&f.push(k);let v=new Set;for(let _ of b)if(!(_?.author?.id!==u||typeof _?.body!="string"))for(let E of _.body.matchAll(rwe))v.add(Number(E[1]));let $=b.filter(_=>Number.isInteger(_?.id)&&_.system!==!0&&!v.has(_.id)).filter(_=>typeof _.created_at=="string"&&_.created_at>=c).filter(_=>Cr(_.body,$s(t))!==void 0).sort((_,E)=>_.created_at<E.created_at?-1:_.created_at>E.created_at?1:_.id-E.id);for(let _ of $)p.push(await swe({api:a,forge:m,meName:l,id:i,base:s,layers:e.layers,mr:y,note:_,env:t,fetch:n,...e.when?{when:e.when}:{},...e.requires?{requires:e.requires}:{},...e.wait?{wait:e.wait}:{},...e.agent?{agent:!0}:{}}));if(e.review){let _=await qW({api:a,id:i,me:u,meName:l,base:s,env:t,fetch:n},y,b);_&&g.push(_)}}return{outcomes:p,plans:f,reviews:g}}async function swe(e){let{api:t,id:n,base:r,layers:o,mr:s,note:i}=e,a=e.when==="pull-request",c=s.iid,u={mr:c,note:i.id},l=async(O,I=!1)=>{try{await e.forge.upsertComment(c,i2(i.id),`terragucci: ${O}
884
+ ${H$(n,s,t,a,_,{replan:!0,traded:jn})}`}),new se({name:"Keep the plan report",if:`always() && ${cs}`,uses:D,with:{name:`${be}-replan`,path:`${be}/`,"if-no-files-found":"ignore"}})]}),Sy=Kt("replan","needs.replan.outputs.go == '1'",`${be}-replan`,{TG_PR:"${{ needs.replan.outputs.pr }}",TG_SHA:"${{ needs.replan.outputs.sha }}",TG_ROOT:"${{ needs.replan.outputs.root }}"},!0),Ft=new Map([["workflow",ky],["check",Gt],["plan",_n],["plan-note",zr],["replan",_y],["replan-note",Sy]]);if(vt&&Ft.set("fmt",new pt({"runs-on":"ubuntu-latest",container:{image:r},needs:"check",if:`always() && needs.check.result == 'failure' && github.event_name == 'push' && github.ref != format('refs/heads/{0}', github.event.repository.default_branch)${h.map(q=>` && github.ref != 'refs/heads/${q}'`).join("")}`,permissions:{contents:"write",...qn?{statuses:"write","pull-requests":"write"}:{}},env:{TG_TOKEN:"${{ github.token }}"},steps:[new se({uses:"actions/checkout@v4"}),...M?[new se({name:te,run:M})]:[],new se({name:"Commit the formatting",shell:"bash",run:_q(n,t,c)}),...qn?[new se({name:"Lock the roots the pull request's new head reaches",shell:"bash",run:Eq(s,t,V,!!l)})]:[]]})),ee?Ft.set("apply-comment",new pt({"runs-on":"ubuntu-latest",container:{image:r},if:`github.event_name == 'issue_comment' && ${uu}`,permissions:{contents:"read","pull-requests":"write"},env:{TG_TOKEN:"${{ github.token }}"},steps:as(new se({name:"Answer that a comment applies nothing here",shell:"bash",run:oye(s,t,e.canary)}),!0,!0,void 0,!0)})):Ft.set("apply-comment",new pt({"runs-on":"ubuntu-latest",container:{image:r},if:`github.event_name == 'issue_comment' && ${uu}`,permissions:{contents:kr||V||st||Ot==="resource"?"write":"read",statuses:"write","pull-requests":"write",...V&&t==="github"?{checks:"read"}:{},...v,...$?{"id-token":"write"}:{}},...Po($),...rt?{concurrency:dP(t)}:{},env:{...p,TG_TOKEN:"${{ github.token }}",...O,...ye,...ne},...K?{outputs:{merge:"${{ steps.apply.outputs.merge }}",sha:"${{ steps.apply.outputs.sha }}",waves:"${{ steps.apply.outputs.waves }}"}}:{},steps:[...as(new se({...K?{id:"apply"}:{},name:V?"Apply a pull request on request, from its head before merge or its merge commit after":"Apply a merged pull request on request, from its merge commit",shell:"bash",run:rye(n,s,t,a,mt)}),!0,!0,void 0,!0),new se({name:"Keep the apply report",if:"always()",uses:t==="forgejo"?"actions/upload-artifact@v3":"actions/upload-artifact@v4",with:{name:`${be}-apply-comment`,path:`${be}/`,"if-no-files-found":"ignore"}})]})),K&&Ft.set("pr-merge",new pt({"runs-on":"ubuntu-latest",container:{image:r},needs:"apply-comment",if:"needs.apply-comment.outputs.merge == '1'",permissions:{contents:"write","pull-requests":"write"},env:{TG_TOKEN:"${{ github.token }}",...e.applyMergeTokenEnv?{TG_MERGE_TOKEN:`\${{ secrets.${e.applyMergeTokenEnv} }}`}:{},TG_PR:"${{ github.event.issue.number }}",TG_SHA:"${{ needs.apply-comment.outputs.sha }}",TG_WAVES:"${{ needs.apply-comment.outputs.waves }}"},steps:[new se({uses:"actions/checkout@v4"}),new se({name:"Merge the pull request whose every wave applied",shell:"bash",run:Qhe(t)})]})),b&&Ft.set("approval",new pt({"runs-on":"ubuntu-latest",container:{image:r},if:`github.event_name == 'pull_request_review' && ${Gs}`,permissions:{contents:"read",statuses:"write","pull-requests":"read"},env:{TG_TOKEN:"${{ github.token }}",TG_SHA:"${{ github.event.pull_request.head.sha }}",TG_PR:"${{ github.event.pull_request.number }}"},steps:[new se({name:"Say on the head whether its waiting waves are approved",shell:"bash",run:`terragucci approval-status --forge ${t}${k?` --agent-login ${k}`:""}`})]})),e.agentComment)for(let[q,Se]of tq(t,r,e.agentComment))Ft.set(q,Se);if(qn){let q=V?cu:`(${cu} || ${lu})`;Ft.set("pr-lock",new pt({"runs-on":"ubuntu-latest",container:{image:r},if:`github.event_name == 'pull_request_target' || (github.event_name == 'issue_comment' && ${q})`,permissions:{contents:"write",statuses:"write","pull-requests":"write"},concurrency:{group:"terragucci-lock-${{ github.repository }}-${{ github.event.pull_request.number || github.event.issue.number }}","cancel-in-progress":!1},env:{TG_TOKEN:"${{ github.token }}"},steps:[new se({uses:"actions/checkout@v4",with:{"fetch-depth":0}}),new se({name:"Lock the roots the pull request reaches, or release them",shell:"bash",run:Eq(s,t,V,!!l)})]}))}y&&Ft.set("ephemeral",new pt({"runs-on":"ubuntu-latest",container:{image:r},if:`github.event_name == 'pull_request_target' && ${Gs}`,permissions:{contents:"write","pull-requests":"read",...$?{"id-token":"write"}:{}},...Po($),concurrency:{group:"terragucci-ephemeral-${{ github.repository }}-${{ github.event.pull_request.number }}","cancel-in-progress":!1},env:{...p,TG_TOKEN:"${{ github.token }}",TG_PR:"${{ github.event.pull_request.number }}",TG_SHA:"${{ github.event.pull_request.head.sha }}",TG_ACTION:"${{ github.event.action }}",...O},steps:as(new se({name:"Apply the pull request's copy of the ephemeral roots, or destroy it once it closes",shell:"bash",run:Lhe(t,a,n,w)}),!1,!0)}));let ct=["github.ref == format('refs/heads/{0}', github.event.repository.default_branch)",...h.map(q=>`github.ref == 'refs/heads/${q}'`)],nr=`${E?"github.event_name == 'push' && ":""}${ct.length>1?`(${ct.join(" || ")})`:ct[0]}`;for(let q of $r){if(q.done){Ft.set(q.name,new pt({"runs-on":"ubuntu-latest",container:{image:r},needs:q.needs,if:nr,permissions:{contents:"read",statuses:"write"},env:{TG_TOKEN:"${{ github.token }}",TG_SHA:"${{ github.sha }}"},steps:[new se({name:q.step,shell:"bash",run:q.body})]}));continue}Ft.set(q.name,new pt({"runs-on":"ubuntu-latest",container:{image:r},needs:q.needs.length===0?"check":q.needs.length===1?q.needs[0]:q.needs,if:nr,permissions:{contents:kr||st||Ot==="resource"?"write":"read",statuses:"write","pull-requests":"write",...v,...$?{"id-token":"write"}:{}},...Po($),...q.environment!==void 0?{environment:q.environment}:{},...q.share===void 0&&q.group===void 0&&rt?{concurrency:dP(t)}:{},env:{...p,TG_TOKEN:"${{ github.token }}",TG_SHA:"${{ github.sha }}",TG_BEFORE:"${{ github.event.before }}",TG_BRANCH:"${{ github.event.repository.default_branch }}",...O,...ye,...ne},steps:[...as(new se({name:q.step,shell:"bash",run:q.body}),!0,!1,void 0,!0,q.share!==void 0||q.group!==void 0?new se({name:"Fetch the wave's decision",uses:pe,with:{name:`${hl}-${q.wave}`,path:hl}}):void 0),...q.decides?[new se({name:C?"Hand the decision to the wave's groups":"Hand the decision to the wave's shares",uses:D,with:{name:`${hl}-${q.wave}`,path:`${hl}/`,"if-no-files-found":"error"}})]:[],new se({name:"Keep the apply report",if:"always()",uses:t==="forgejo"?"actions/upload-artifact@v3":"actions/upload-artifact@v4",with:{name:`${be}-${q.name}`,path:`${be}/`,"if-no-files-found":"ignore"}}),...Ce?[new se({name:"Keep the agent input",if:"failure()",uses:t==="forgejo"?"actions/upload-artifact@v3":"actions/upload-artifact@v4",with:{name:`${vl}-${q.name}`,path:`${vl}/`,"if-no-files-found":"ignore"}})]:[]]}))}if(V&&Ft.set("confirm",new pt({"runs-on":"ubuntu-latest",container:{image:r},needs:"check",if:`${E?"github.event_name == 'push' && ":""}github.ref == format('refs/heads/{0}', github.event.repository.default_branch)`,permissions:{contents:"read",statuses:"write",...$?{"id-token":"write"}:{}},...Po($),env:{...p,TG_TOKEN:"${{ github.token }}",TG_SHA:"${{ github.sha }}",...O},steps:[...as(new se({name:`Plan every ${Ze} to confirm the merge applied`,shell:"bash",run:Pq(n,s,t,a,_)}),!0),new se({name:"Keep the plan report",if:"always()",uses:t==="forgejo"?"actions/upload-artifact@v3":"actions/upload-artifact@v4",with:{name:`${be}-confirm`,path:`${be}/`,"if-no-files-found":"ignore"}})]})),is&&Ft.set("tips",new pt({"runs-on":"ubuntu-latest",container:{image:r},needs:de,if:`${E?"github.event_name == 'push' && ":""}github.ref == format('refs/heads/{0}', github.event.repository.default_branch)`,permissions:{contents:"write","pull-requests":"write"},concurrency:{group:"terragucci-tips-${{ github.repository }}","cancel-in-progress":!1},env:{TG_TOKEN:"${{ github.token }}"},steps:[new se({uses:"actions/checkout@v4",with:{"fetch-depth":0}}),...M?[new se({name:te,run:M})]:[],new se({name:"Open a pull request for each tip",shell:"bash",run:Sq(n,t,c,e.synth)})]})),U&&Ft.set("version-bump",new pt({"runs-on":"ubuntu-latest",container:{image:r},needs:de,if:`${E?"github.event_name == 'push' && ":""}github.ref == format('refs/heads/{0}', github.event.repository.default_branch)`,permissions:{contents:"write","pull-requests":"write"},concurrency:{group:"terragucci-version-bump-${{ github.repository }}","cancel-in-progress":!1},env:{TG_TOKEN:"${{ github.token }}",...I},steps:[new se({uses:"actions/checkout@v4",with:{"fetch-depth":0}}),new se({name:"Suggest the next version of each module that changed",shell:"bash",run:Rq(t,c)})]})),e.publish&&Ft.set("publish",new pt({"runs-on":"ubuntu-latest",container:{image:r},needs:de,if:"github.ref == format('refs/heads/{0}', github.event.repository.default_branch)",permissions:{contents:"write"},concurrency:{group:"terragucci-publish-${{ github.repository }}","cancel-in-progress":!1},env:{TERRAGUCCI_REGISTRY_USER:"${{ secrets.TERRAGUCCI_REGISTRY_USER }}",TERRAGUCCI_REGISTRY_PASSWORD:"${{ secrets.TERRAGUCCI_REGISTRY_PASSWORD }}",TERRAGUCCI_REGISTRY_INSECURE:"${{ secrets.TERRAGUCCI_REGISTRY_INSECURE }}",...e.attest?{COSIGN_PRIVATE_KEY:"${{ secrets.COSIGN_PRIVATE_KEY }}",COSIGN_PASSWORD:"${{ secrets.COSIGN_PASSWORD }}"}:{},...e.publishBucket?z$(t,{bucket:e.publishBucket}):{}},steps:[new se({uses:"actions/checkout@v4",with:{"fetch-depth":0}}),...M&&o?[new se({name:`Install ${o.binary} ${o.version}`,run:M})]:[],...e.attest?[new se({name:`Install cosign ${Ud}`,run:F$("cosign",Ud,t)})]:[],new se({name:"Publish the modules that changed",shell:"bash",run:Aq(t)})]})),E&&Ft.set("drift",new pt({"runs-on":"ubuntu-latest",container:{image:r},if:"github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.pr == '')",permissions:{contents:_r?"write":"read",issues:"write",..._r?{"pull-requests":"write"}:{},...a?{"id-token":"write"}:{}},...Po(!!a),...e.agentDrift?{outputs:{agent:"${{ steps.drift.outputs.agent }}",issue:"${{ steps.drift.outputs.issue }}"}}:{},env:{...p,TG_TOKEN:"${{ github.token }}",TG_SHA:"${{ github.sha }}",...O,...L,...z$(t,e.reports),...Z},steps:[...as(new se({...e.agentDrift?{id:"drift"}:{},name:`Plan every ${Ze} against what exists, and keep the drift issue`,shell:"bash",run:Oq(n,s,t,a,_,_r,me,e.agentDrift!==void 0)}),!0,!1,Bl),new se({name:"Keep the drift report",if:"always()",uses:t==="forgejo"?"actions/upload-artifact@v3":"actions/upload-artifact@v4",with:{name:`${be}-drift`,path:`${be}/`,"if-no-files-found":"ignore"}}),...Ul?[new se({name:"Keep the agent input",if:"always()",uses:t==="forgejo"?"actions/upload-artifact@v3":"actions/upload-artifact@v4",with:{name:`${vl}-drift`,path:`${vl}/`,"if-no-files-found":"ignore"}})]:[]]})),E&&e.agentDrift)for(let[q,Se]of nq(t,r,e.agentDrift,`${be}-drift`))Ft.set(q,Se);t==="forgejo"&&Dhe(Ft);let rr=q=>(vq(q,e.runner,t,Oi),Iq((t==="forgejo"?Ohe:iE).serialize(q))),Io=[];if(e.resume){let q=new Map([["workflow",new Gu({name:"terragucci resume",on:{schedule:[{cron:kq(e.resume)}],workflow_dispatch:{}},env:A,permissions:{contents:"read"}})],["resume",new pt({"runs-on":"ubuntu-latest",container:{image:r},permissions:{contents:"write",statuses:"write","pull-requests":"write",...v,...$?{"id-token":"write"}:{}},...Po($),...rt?{concurrency:dP(t)}:{},env:{...p,TG_TOKEN:"${{ github.token }}",...O,...ye,...ne},steps:[...as(new se({name:"Apply a waiting wave once its approval stands",shell:"bash",run:aye(n,s,t,a,mt)}),!0,!0,void 0,!0),new se({name:"Keep the apply report",if:"always()",uses:t==="forgejo"?"actions/upload-artifact@v3":"actions/upload-artifact@v4",with:{name:`${be}-resume`,path:`${be}/`,"if-no-files-found":"ignore"}})]})]]);Io.push({path:Mhe[t],content:Tp(r,e.imageFromConfig)+rr(q)})}if(y){let q=new Map([["workflow",new Gu({name:"terragucci ephemeral",on:{schedule:[{cron:kq(y.sweep)}],workflow_dispatch:{}},env:A,permissions:{contents:"read"}})],["sweep",new pt({"runs-on":"ubuntu-latest",container:{image:r},permissions:{contents:"write","pull-requests":"read",...$?{"id-token":"write"}:{}},...Po($),concurrency:{group:"terragucci-ephemeral-sweep-${{ github.repository }}","cancel-in-progress":!1},env:{...p,TG_TOKEN:"${{ github.token }}",...O},steps:as(new se({name:"Destroy the ephemeral copies whose TTL passed or whose pull request closed",shell:"bash",run:$q(t,a,n)}),!1,!0)})]]);Io.push({path:mP[t],content:Tp(r,e.imageFromConfig)+rr(q)})}return e.review&&Io.push({path:Dc[t],content:Tp(r,e.imageFromConfig)+rr(sq(t,r,e.review,{pipelineName:"terragucci",env:A}))}),R&&Io.push({path:gP[t],content:Tp(r,e.imageFromConfig)+rr(Eye(t,r,R,A,c,M?{name:te,run:M}:void 0))}),{path:Xr[t],content:Tp(r,e.imageFromConfig)+rr(Ft)+jq(e.ownJobs,[...Ft.keys()].filter(q=>q!=="workflow"),t),applyJobs:Ge,...Io.length?{extra:Io}:{}}}function Eye(e,t,n,r,o,s){let i=o?`\${{ secrets.${o} }}`:"${{ github.token }}",a=new Gu({name:"terragucci-rollout",on:{schedule:[{cron:n}],workflow_dispatch:{}},env:r,permissions:{contents:"read"},concurrency:{group:"terragucci-rollout-${{ github.repository }}","cancel-in-progress":!1}}),c=new pt({"runs-on":"ubuntu-latest",container:{image:t},permissions:{contents:"write","pull-requests":"write",statuses:"read",...e==="github"?{checks:"read"}:{}},env:{TG_TOKEN:i},steps:[new se({uses:"actions/checkout@v4",with:{"fetch-depth":0,...o?{token:i}:{}}}),...s?[new se(s)]:[],new se({name:"Open the next wave of each rollout whose last wave merged and applied",shell:"bash",run:Cq(e,o)})]});return new Map([["workflow",a],["rollout",c]])}var Z$=e=>e.length<2?e.join(""):`${e.slice(0,-1).join(", ")} and ${e[e.length-1]}`;function Jq(e,t){if(e===void 0)return;let n=typeof e=="string"?{labels:[e]}:Array.isArray(e)?{labels:[...e]}:{group:e.group,labels:[...e.labels??[]]};if(!((t==="github"||t===void 0)&&n.group===void 0&&!n.labels.includes("self-hosted")))return n}function Kq(e,t){if(e.group!==void 0&&t.group!==void 0&&e.group!==t.group)return!1;let n=(r,o)=>r.every(s=>o.includes(s));return n(e.labels,t.labels)||n(t.labels,e.labels)}var V$=e=>[...e.group!==void 0?[`group ${e.group}`]:[],...e.labels.length?[`labels ${e.labels.join(", ")}`]:[]].join(" and ");function Tye(e,t,n){let r=(e??[]).filter(a=>a.environment!==void 0);if(r.length===0)return[];let o=Jq(kl(t,"plan"),n),s=r.map(a=>({name:a.name,at:Jq(a.runner??kl(t,"apply"),n)})),i=[];for(let[a,c]of s.entries())if(c.at){o&&Kq(c.at,o)&&i.push({rule:kx,message:`the apply jobs of environment ${c.name} (${V$(c.at)}) and the plan jobs (${V$(o)}) can run on the same runner, so a pull request's code planned there can leave files or processes for ${c.name}'s apply; give ${c.name}'s apply runners a label no plan job uses, and use runners that start clean for each job`});for(let u of s.slice(a+1))!u.at||!Kq(c.at,u.at)||i.push({rule:kx,message:`the apply jobs of environments ${c.name} (${V$(c.at)}) and ${u.name} (${V$(u.at)}) can run on the same runner, so one environment's apply can leave files or processes for the other's; give each environment's apply runners a label of its own`})}return i}function Aye(e,t){let n=e.oidc;return!n||!t?[]:[...n.apply_role!==void 0&&n.apply_role===t.apply_role?[{what:"oidc.apply_role",id:n.apply_role}]:[],...n.gcp?.apply_service_account!==void 0&&n.gcp.apply_service_account===t.gcp?.apply_service_account?[{what:"oidc.gcp.apply_service_account",id:n.gcp.apply_service_account}]:[],...n.azure?.apply_client_id!==void 0&&n.azure.apply_client_id===t.azure?.apply_client_id?[{what:"oidc.azure.apply_client_id",id:n.azure.apply_client_id}]:[]]}function Oye(e,t){if(!e||e.length<2)return[];let n=new Map;for(let o of e)for(let{what:s,id:i}of Aye(o,t)){let a=n.get(i)??{what:s,names:[],envs:[]};a.names.push(o.name),o.environment!==void 0&&a.envs.push(o.name),n.set(i,a)}let r=[];for(let[o,{what:s,names:i,envs:a}]of n){if(i.length<2||a.length===0)continue;let c=s==="oidc.apply_role"?"apply_role":s==="oidc.gcp.apply_service_account"?"gcp.apply_service_account":"azure.apply_client_id";r.push({rule:iM,message:`the apply jobs of ${i.length===a.length?"environments":"the groups"} ${Z$(i)} all apply as ${o} (${s}), so its trust must accept each of their jobs and no condition keeps one environment's job from another's role; set environments.<name>.oidc.${c} for ${Z$(a)}`})}return r}var Pye=e=>!!(e&&(e.apply_role||e.roles&&Object.keys(e.roles).length>0));function jye(e,t,n){if(n!=="gitlab"||!e||!e.some(s=>s.environment!==void 0))return[];let r=new Map;for(let s of e){if(!Pye(s.oidc))continue;let i=s.audience??Es(t,"apply");r.set(i,[...r.get(i)??[],s])}let o=[];for(let[s,i]of r){let a=i.filter(u=>u.environment!==void 0&&u.audience===void 0).map(u=>u.name);if(i.length<2||a.length===0)continue;let c=i.map(u=>u.name);o.push({rule:aM,message:`on GitLab the apply jobs of ${c.every(u=>u!==Ka)&&i.every(u=>u.environment!==void 0)?"environments":"the groups"} ${Z$(c)} carry the same AWS token audience ${s}, and AWS cannot read GitLab's environment claims, so an apply role's trust cannot tell their jobs apart; set environments.<name>.oidc.audience for ${Z$(a)} and have each apply role trust its environment's audience alone`})}return o}function Vq(e,t,n){return[...Tye(e,t.runner,n),...Oye(e,t.oidc),...jye(e,t.oidc,n)]}var Iye="https://intentius.io/terragucci/terragucci.schema.json",ko={type:"string"},Rl={type:"boolean"},xl={type:"string",pattern:"\\S"},EP={type:"string",pattern:"^[^\\r\\n]*\\S[^\\r\\n]*$"},$n=e=>({type:"string",pattern:e.source}),_i=$n(rd),Y$={type:"array",items:ko},Zq={anyOf:[ko,{const:!1}]},Et=e=>({enum:[...e]}),Si=(e,t)=>({type:"integer",minimum:e,...t===void 0?{}:{maximum:t}}),Wc=e=>({$ref:`#/$defs/${e}`}),Sl=e=>({anyOf:[{type:"null"},e]}),TP={type:"string",pattern:"^[^\\s,;=']+$"},nJ={type:"string",minLength:1,pattern:"^(?!/)(?!(?:.*/)?\\.\\.(?:/|$))"};function Qe(e,t,n={}){let r=e.filter(s=>!(s in t)),o=Object.keys(t).filter(s=>!e.includes(s));if(r.length||o.length)throw new Error(`config schema: keys ${[...r,...o].join(", ")} disagree with config.ts`);return{type:"object",properties:t,additionalProperties:!1,...n}}var AP=(e,t)=>Object.fromEntries(e.map(n=>[n,t(n)])),Cye=e=>[...e].map(t=>/[a-z]/i.test(t)?`[${t.toUpperCase()}${t.toLowerCase()}]`:t).join(""),Yq={type:"string",pattern:`^(?!(?:${ku.map(Cye).join("|")}))(?!(?:${nd.join("|")})$)[A-Za-z_][A-Za-z0-9_]*$`},xP=Qe(["plan","apply"],{plan:{type:"string",minLength:1},apply:{type:"string",minLength:1}},{required:["plan","apply"]}),X$={type:"string",pattern:"^[^@\\s]+@[^@\\s]+$"},Nye=Qe(["plan","apply"],{plan:X$,apply:X$},{required:["plan","apply"]}),Q$={anyOf:[$n(jf),{type:"array",minItems:1,uniqueItems:!0,items:$n(jf)},Qe(["group","labels"],{group:xl,labels:{type:"array",minItems:1,uniqueItems:!0,items:$n(jf)}},{required:["group"]})]},Xq={anyOf:[Rl,Qe(bf,{command:EP,key_secret:_i,max_turns:Si(1),timeout:Si(1)})]},Dye={type:"object",propertyNames:$n(qa),additionalProperties:Sl(Wc("hcl_value"))};function Qq(e){let t={backend:Sl({type:"object",minProperties:1,maxProperties:1,propertyNames:$n(qa),additionalProperties:Dye}),providers:{type:"object",propertyNames:$n($S),additionalProperties:Sl({type:"object",properties:{source:{type:["string","null"]},version:{type:["string","null"]}},propertyNames:$n(qa),additionalProperties:Sl(Wc("hcl_value")),not:{required:["alias"]}})},required_version:Sl(xl),disable_init:{type:["boolean","null"]}};if(!e)return Qe(bu,t);let n={type:"object",additionalProperties:Sl(Wc("generate_level"))};return Qe(yf,{...t,dirs:n,roots:n})}var Mye=Qe(xf,{when:Et(id),merge:Et(LS),merge_token_env:_i,requires:{type:"array",uniqueItems:!0,items:Et(Cn)},resume:Si(5,60),branches:{type:"object",minProperties:1,propertyNames:$n(lR),additionalProperties:{type:"array",minItems:1,items:TP}},point:$n(uR)}),Lye=Qe(Uy,{name:xl,run:xl,before:Et(td),after:Et(td),roots:Y$,on_failure:Et(FS)},{required:["run"],oneOf:[{required:["before"]},{required:["after"]}],if:{properties:{on_failure:{const:"approve"}},required:["on_failure"]},then:{properties:{before:Et(["init","plan"]),after:Et(["init","plan"])}}}),Gye=Qe(Jy,{bucket:ko,dir:nJ,endpoint:ko,prefix:ko,url:{type:"string",pattern:"^https://[^/\\s?#]+/?$"},namespace:$n(_u),namespaces:{type:"object",propertyNames:{pattern:"^[^/]"},additionalProperties:$n(_u)},system:$n(rR),download:Et(["tarball","git-tags","oci"])},{required:["url","namespace"],oneOf:[{required:["bucket"]},{required:["dir"]}],dependentRequired:{endpoint:["bucket"]}}),eJ={type:"string",pattern:"^(?:git-tags$|oci://[^/]+/.)"},Fye=Qe([...qy],{path:ko,publish:{anyOf:[eJ,{type:"array",items:eJ}]},attest:{anyOf:[Rl,Qe(["key"],{key:{type:"string",minLength:1}})]},require:{const:"attested"},trusted:{type:"array",items:Qe(["source","key","ledger"],{source:{type:"string",pattern:"^(oci://[^/]+/.+|(git::)?(https?|ssh)://.+)$"},key:{type:"string",minLength:1},ledger:{type:"string",pattern:"^(https?|ssh|file)://."}},{required:["source","key","ledger"]})},test:Rl,registry:Gye}),zye=Qe(kf,{version:$n(Ja),exclude:Y$,parallelism:Si(1),dependents:Et(CS),credentials:{type:"object",additionalProperties:xP}}),Uye=Qe(Wy,{engine:Et(NS),path:nJ,source:{type:"string",pattern:"^git\\+(https?|file)://.*@"},namespace:{type:"string",pattern:"^[A-Za-z_][A-Za-z0-9_.]*$"},input:Et(DS),override:{type:"array",minItems:1,items:EP}}),tJ={type:"array",minItems:1,items:{type:"string",pattern:YS}},Bye=Qe(_f,{modules:tJ,providers:tJ},{minProperties:1}),Wye=Qe(Hy,{plan_role:{type:"string",minLength:1},apply_role:{type:"string",minLength:1},audience:{anyOf:[ko,Qe(Sf,{plan:{type:"string",minLength:1},apply:{type:"string",minLength:1},drift:{type:"string",minLength:1}})]},roles:{type:"object",minProperties:1,additionalProperties:xP},gcp:Qe(["workload_identity_provider","plan_service_account","apply_service_account","token_url","roles"],{workload_identity_provider:$n(gR),plan_service_account:{type:"string",pattern:"^[^@\\s]+@[^@\\s]+$"},apply_service_account:{type:"string",pattern:"^[^@\\s]+@[^@\\s]+$"},token_url:{type:"string",pattern:"^https://[^\\s/]+/\\S*$"},roles:{type:"object",minProperties:1,additionalProperties:Nye}},{required:["workload_identity_provider","plan_service_account","apply_service_account"]}),azure:Qe(["tenant_id","subscription_id","plan_client_id","apply_client_id","audience","roles"],{tenant_id:{type:"string",minLength:1},subscription_id:{type:"string",minLength:1},plan_client_id:{type:"string",minLength:1},apply_client_id:{type:"string",minLength:1},audience:{type:"string",minLength:1},roles:{type:"object",minProperties:1,additionalProperties:xP}},{required:["tenant_id","subscription_id","plan_client_id","apply_client_id"]})},{minProperties:1,dependentRequired:{plan_role:["apply_role"],apply_role:["plan_role"]},if:{required:["audience"],not:{required:["roles"]}},then:{required:["plan_role","apply_role"]}}),Hye=Qe(Ef,{backend:Et(Af),url:{type:"string",pattern:"^https?://[^/\\s]+"},model:{type:"string",minLength:1,pattern:"^(?![\\s\\S]*(?:^|[-_.])(?:latest|preview)$)"},token_env:_i,thresholds:Qe($u,AP($u,()=>({type:"number",exclusiveMinimum:0,maximum:1})))},{required:["backend"],allOf:[{if:{properties:{backend:{const:"jev"}}},then:{required:["token_env"]},else:{required:["url"]}},{if:{properties:{backend:{const:"laya"}}},else:{required:["model"]}}]}),qye={anyOf:[Rl,Qe(vu,AP(vu,e=>US.includes(e)?$n(fR):e==="dir"?{type:"string",pattern:"^(?!/)(?!(?:.*/)?\\.\\.(?:/|$))[^\\r\\n]+$"}:{type:"string",pattern:"^[^\\r\\n]+$"}))]},Jye=Qe(Rf,{roots:{type:"array",minItems:1,items:TP},ttl:{type:"string",pattern:"^[1-9]\\d*[mhd]$"},sweep:Si(5,60)},{required:["roots"]}),Kye=Qe(["plan_role","apply_role","audience","gcp","azure"],{plan_role:{type:"string",minLength:1},apply_role:{type:"string",minLength:1},audience:{type:"string",minLength:1,pattern:"\\S"},gcp:Qe(["plan_service_account","apply_service_account"],{plan_service_account:X$,apply_service_account:X$},{required:["plan_service_account","apply_service_account"]}),azure:Qe(["plan_client_id","apply_client_id"],{plan_client_id:{type:"string",minLength:1},apply_client_id:{type:"string",minLength:1}},{required:["plan_client_id","apply_client_id"]})},{minProperties:1,dependentRequired:{plan_role:["apply_role"],apply_role:["plan_role"]}});function Vye(e){return Qe(["roots","gate","oidc","runner","parallelism","forge_environment"],{roots:{type:"array",minItems:1,items:TP},gate:Et(jS),oidc:Kye,runner:{anyOf:[Q$,Qe(["apply"],{apply:Q$},{required:["apply"]})]},parallelism:Si(1),forge_environment:$n(hR)},e?{required:["roots"]}:{})}function rJ(e){return{roots:{...Y$,description:"Globs of root directories. Detected when absent."},binary:{...Et(od),description:"The binary the pipeline runs. Detected when absent."},version:e?{anyOf:[ko,{type:"object",additionalProperties:$n(Ja)}],description:"The binary's release, or a map of root glob to release."}:{...ko,description:"The binary's release."},generate:{...Wc("generate"),description:"Backend, provider and version files terragucci generate writes for each plain root."},forge:{...Et(Wr),description:"The forge, for a host terragucci cannot name."},url:{...ko,description:"Where the project lives, for a forge not on https or the default port."},gate:{...Et(jS),description:"When a wave waits for an approval. on-destroy is an older name for on-destructive."},approval:{anyOf:[Et(Do),{type:"object",additionalProperties:!1,properties:{mode:{...Et(Do),description:"What counts as a waiting wave's approval."},require_signed:{type:"boolean",description:"Refuse an unsigned approval on the ledger, so every ledger approval is tied to the principal who signed it."}}}],description:"What counts as a waiting wave's approval: the mode, or a map with the mode and require_signed."},apply:{...Mye,description:"When a change applies."},locks:{...Et(MS),description:"When a pull request takes its root locks."},waves:{type:"object",properties:{canary:Y$,jobs:Si(1),after:{type:"object",additionalProperties:{type:"array",minItems:1,items:{type:"string",minLength:1}}},compose:{type:"boolean"},compose_max:Si(2)},description:"The canary wave, the jobs one wave spreads across, extra apply order, and whether a change's roots apply as one composed run."},drift:{...Zq,description:"A cron schedule for tf-drift, or false."},synth:{...xl,description:"The command that writes the roots before any job reads them."},steps:{type:"array",items:Lye,description:"Commands run before or after a root's init, plan, apply and drift."},image:{type:"string",pattern:"^[^\\s]+$",description:"The image every job runs in, built FROM terragucci's."},notify:{...Qe(ZS,{slack:_i,teams:_i,webhook:_i,webhook_key:_i,relay:{type:"string",pattern:"^[A-Za-z0-9][A-Za-z0-9 ._-]{0,63}$"}},{minProperties:1,dependentRequired:{webhook:["webhook_key"],webhook_key:["webhook"]},if:{required:["relay"]},then:{anyOf:[{required:["slack"]},{required:["teams"]}]}}),description:"The secrets holding Slack, Teams or webhook addresses, and the relay's name."},cost:{anyOf:[{const:!0},Qe(eR,{key_secret:_i,command:xl,approve_above:{type:"number",minimum:0}})],description:"Cost estimates per root in the plan note."},comments:{...Zq,description:"GitLab only: the cron of the comments schedule, or false."},rollouts:e?{anyOf:[xl,{const:!1}],description:"A cron schedule for the job that opens each rollout's next wave, or false."}:{const:!1,description:"A single repo's key; a control repo runs terragucci respond rollout itself."},gitlab:{...Qe(QS,{token:Et(GS)}),description:"GitLab only: how the project keeps its forge token."},runtime:{...Et(IS),description:"Where the stages run."},reports:{type:"object",properties:{bucket:ko,endpoint:{},prefix:{},url:{type:"string",pattern:"^https?://[^\\s?#]+$"},role:{type:"string",pattern:"^arn:aws[\\w-]*:iam::\\d{12}:role/\\S+$"}},required:["bucket"],description:"A bucket for plan reports: s3://, gs:// or az://."},token_env:{...ko,description:"The environment variable holding the forge token."},env:{type:"object",additionalProperties:ko,description:"Environment variables every job gets. Values only, never secrets."},runner:{anyOf:[Q$,{type:"object",minProperties:1,not:{required:["group"]},propertyNames:Et($f),additionalProperties:Q$}],description:"The runner each job runs on: a label, a list of labels, a GitHub runner group, or one per stage."},pass:{...Qe(tR,{secrets:{type:"array",minItems:1,uniqueItems:!0,items:Yq},vars:{type:"array",minItems:1,uniqueItems:!0,items:Yq}},{minProperties:1}),description:"Names of CI secrets and variables the plan, apply and drift jobs get."},telemetry:{...Qe(XS,{headers_secret:_i,trace_url:{type:"string",pattern:"^https?://\\S*\\{trace_id\\}\\S*$"}},{minProperties:1}),description:"The secret holding OTLP headers, and a trace link with {trace_id}."},tips:{...Rl,description:"Tips in the plan note."},modules:{...Fye,description:"Module publishing, attestation and the module registry."},oidc:{...Wye,description:"Cloud identities the jobs take over OIDC: a read-only one for plan, a write one for apply."},parallelism:{...Si(1),description:"How many roots of one dependency layer plan at once."},terragrunt:{...zye,description:"Terragrunt settings."},atmos:{...Qe(Tf,{version:{type:"string",pattern:"^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.]+)?$"}}),description:"Atmos settings."},policy:{...Uye,description:"Policy checks over each plan."},sources:{...Bye,description:"Globs of the module sources and provider addresses a root may use, checked before init."},atlantis_comments:{...Rl,description:"Read atlantis plan and atlantis apply comments as terragucci's."},respond:{...Qe(Object.keys(Us),AP(Object.keys(Us),t=>Et(Us[t]))),description:"The response to each pipeline event."},agent:{...Qe(nR,{via:Et(zS),token_env:{type:"string",minLength:1},login:{type:"string",pattern:Cf.source,description:"The forge login agent.token_env's token acts as. No approval by it counts."},comment:Xq,drift:Xq},{required:["via","token_env"]}),description:"The coding agent behind /terragucci agent and the drift agent."},review:{...Qe(vf,{agent:Rl,command:EP,key_secret:_i,instructions:{type:"string",pattern:"^(?:\\./)?(?!/)[A-Za-z0-9_./-]+$"},timeout:Si(1)},{anyOf:[{maxProperties:0},{required:["agent"]}]}),description:"A model's review of each pull request's intent against its plan."},decide:{...Hye,description:"The typed-decision service."},audit_region:{type:"string",pattern:"^[a-z]{2}(-[a-z]+)+-\\d+$",description:"The AWS region whose CloudTrail drift attribution reads."},dashboards:{...qye,description:"Dashboards and alert rules written next to the pipeline."},own_jobs:{anyOf:[{type:"string",pattern:"^(?!/)(?!(?:.*/)?\\.\\.(?:/|$)).*\\.ya?ml$"},{type:"object",minProperties:1,propertyNames:$n(aR),additionalProperties:{type:"object",minProperties:1}}],description:"Jobs of your own, in the forge's syntax, or the path of a YAML file holding them."},ephemeral:{...Jye,description:"Roots each pull request gets a copy of."},environments:{type:"object",minProperties:1,propertyNames:{...$n(WS),not:{anyOf:[{enum:["default","decide"]},{pattern:"^share-[0-9]+$"}]}},additionalProperties:Vye(e),description:"Named environments: each one's root globs, and its gate, identities, apply runner, parallelism and forge environment."}}}function Zye(){return{type:"object",properties:rJ(!1),additionalProperties:!1}}function Yye(){let e=rJ(!0);return{$schema:"https://json-schema.org/draft/2020-12/schema",$id:Iye,title:"terragucci.yml",description:"One repo's settings, or a control repo's defaults and projects. https://intentius.io/terragucci/reference/config/",type:["object","null"],properties:{...e,defaults:{...Wc("project"),not:{required:["url"]},description:"Settings every project takes, under its own."},projects:{type:"object",propertyNames:{pattern:"^(?:https?://)?[^/]+(?:/[^/]+){2,}/*$"},additionalProperties:Sl(Wc("project")),description:"<host>/<owner>/<name> to that project's settings."}},additionalProperties:!1,dependentRequired:{defaults:["projects"]},if:{required:["projects"]},then:{propertyNames:Et(["defaults","projects"])},$defs:{project:Zye(),generate:Qq(!0),generate_level:Qq(!1),hcl_value:{anyOf:[{type:["string","number","boolean"]},{type:"array",items:Wc("hcl_value")},{type:"object",additionalProperties:Wc("hcl_value")}]}}}}var Bc=e=>e!==null&&typeof e=="object"&&!Array.isArray(e);function Xye(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function Qye(e,t){return t==="integer"?Number.isInteger(e):Xye(e)===t}var gh=e=>JSON.stringify(e,(t,n)=>Bc(n)?Object.fromEntries(Object.keys(n).sort().map(r=>[r,n[r]])):n);function _l(e,t,n=e,r="config"){let o=[],s=e;if(typeof s.$ref=="string"){let a=n.$defs[s.$ref.replace("#/$defs/","")];if(!a)throw new Error(`config schema: no ${s.$ref}`);o.push(..._l(a,t,n,r))}if(s.type!==void 0){let a=Array.isArray(s.type)?s.type:[s.type];if(!a.some(c=>Qye(t,c)))return[...o,`${r}: must be ${a.join(" or ")}`]}if("const"in s&&gh(t)!==gh(s.const)&&o.push(`${r}: must be ${JSON.stringify(s.const)}`),Array.isArray(s.enum)&&!s.enum.some(a=>gh(a)===gh(t))&&o.push(`${r}: must be one of ${s.enum.join(", ")}`),typeof t=="string"&&(typeof s.minLength=="number"&&[...t].length<s.minLength&&o.push(`${r}: must not be empty`),typeof s.pattern=="string"&&!new RegExp(s.pattern,"u").test(t)&&o.push(`${r}: does not match ${s.pattern}`)),typeof t=="number"&&(typeof s.minimum=="number"&&t<s.minimum&&o.push(`${r}: must be ${s.minimum} or more`),typeof s.maximum=="number"&&t>s.maximum&&o.push(`${r}: must be ${s.maximum} or less`),typeof s.exclusiveMinimum=="number"&&t<=s.exclusiveMinimum&&o.push(`${r}: must be more than ${s.exclusiveMinimum}`)),Array.isArray(t)&&(typeof s.minItems=="number"&&t.length<s.minItems&&o.push(`${r}: must list at least ${s.minItems}`),s.uniqueItems===!0&&new Set(t.map(gh)).size!==t.length&&o.push(`${r}: lists an item twice`),Bc(s.items)&&t.forEach((a,c)=>o.push(..._l(s.items,a,n,`${r}[${c}]`)))),Bc(t)){let a=Object.keys(t),c=s.properties??{};typeof s.minProperties=="number"&&a.length<s.minProperties&&o.push(`${r}: must set at least ${s.minProperties} key(s)`),typeof s.maxProperties=="number"&&a.length>s.maxProperties&&o.push(`${r}: must set at most ${s.maxProperties} key(s)`);for(let u of s.required??[])u in t||o.push(`${r}.${u}: is required`);for(let[u,l]of Object.entries(s.dependentRequired??{}))if(u in t)for(let d of l)d in t||o.push(`${r}.${d}: is required with ${u}`);for(let u of a){let l=`${r}.${u}`;if(Bc(s.propertyNames))for(let d of _l(s.propertyNames,u,n,l))o.push(`${l}: is not a key here (${d.slice(l.length+2)})`);u in c?o.push(..._l(c[u],t[u],n,l)):s.additionalProperties===!1?o.push(`${l}: is not a setting`):Bc(s.additionalProperties)&&o.push(..._l(s.additionalProperties,t[u],n,l))}}let i=a=>_l(a,t,n,r);for(let a of s.allOf??[])o.push(...i(a));if(Array.isArray(s.anyOf)){let a=s.anyOf.map(i);a.some(c=>c.length===0)||o.push(...a.reduce((c,u)=>u.length<c.length?u:c))}if(Array.isArray(s.oneOf)&&s.oneOf.filter(c=>i(c).length===0).length!==1&&o.push(`${r}: must match exactly one of ${s.oneOf.map(c=>JSON.stringify(c)).join(", ")}`),Bc(s.not)&&i(s.not).length===0&&o.push(`${r}: must not match ${JSON.stringify(s.not)}`),Bc(s.if)){let a=i(s.if).length===0?s.then:s.else;Bc(a)&&o.push(...i(a))}return o}function oJ(e){return _l(Yye(),e)}var ewe=1440,twe=30,nwe=50,sJ="terragucci/apply",iJ=e=>`<!-- terragucci:note=${e} -->`,rwe=/<!-- terragucci:note=(\d+) -->/g,Ts=e=>e.slice(0,8);function owe(e){let t=e.CI_API_V4_URL,n=e.CI_PROJECT_ID,r=e.TG_TOKEN,o=e.CI_DEFAULT_BRANCH;if(!t||!n||!r||!o)throw new S("comment --forge gitlab --poll needs CI_API_V4_URL, CI_PROJECT_ID, CI_DEFAULT_BRANCH and TG_TOKEN in the environment");return{t:{forge:"gitlab",origin:e.CI_SERVER_URL??t,path:n,token:r,api:t},base:o}}async function aJ(e){let t=e.env??process.env,n=e.fetch??fetch,r=e.now??new Date,{t:o,base:s}=owe(t),i=encodeURIComponent(o.path),a=(y,w,b)=>kt(n,o,y,w,b),c=new Date(r.getTime()-ewe*6e4).toISOString(),u,l,d;try{let y=await a("GET","/user");u=Number(y?.id),l=String(y?.username??""),d=await a("GET",`/projects/${i}/merge_requests?state=all&updated_after=${encodeURIComponent(c)}&order_by=updated_at&sort=desc&per_page=100`)}catch(y){return{outcomes:[],fail:`could not list the merge requests (${y.message})`}}if(!Array.isArray(d))return{outcomes:[],fail:"GitLab answered the merge request list with something other than a list"};let p=[],f=[],g=[],m=jt("gitlab",t,n),h={api:a,id:i,me:u,meName:l,forge:m,apiUrl:o.api,token:o.token,fetch:n,reportDir:e.reportDir??"terragucci-report"};for(let y of d){let w=y?.iid;if(!Number.isInteger(w)||w<1)continue;let b;try{b=await a("GET",`/projects/${i}/merge_requests/${w}/notes?order_by=created_at&sort=desc&per_page=100`)}catch(_){return{outcomes:p,plans:f,reviews:g,fail:`could not read the notes of !${w} (${_.message})`}}if(!Array.isArray(b))continue;let k=e.planNotes?await _W(h,y,b):void 0;k&&f.push(k);let v=new Set;for(let _ of b)if(!(_?.author?.id!==u||typeof _?.body!="string"))for(let E of _.body.matchAll(rwe))v.add(Number(E[1]));let $=b.filter(_=>Number.isInteger(_?.id)&&_.system!==!0&&!v.has(_.id)).filter(_=>typeof _.created_at=="string"&&_.created_at>=c).filter(_=>Cr(_.body,$s(t))!==void 0).sort((_,E)=>_.created_at<E.created_at?-1:_.created_at>E.created_at?1:_.id-E.id);for(let _ of $)p.push(await swe({api:a,forge:m,meName:l,id:i,base:s,layers:e.layers,mr:y,note:_,env:t,fetch:n,...e.when?{when:e.when}:{},...e.requires?{requires:e.requires}:{},...e.wait?{wait:e.wait}:{},...e.agent?{agent:!0}:{}}));if(e.review){let _=await qW({api:a,id:i,me:u,meName:l,base:s,env:t,fetch:n},y,b);_&&g.push(_)}}return{outcomes:p,plans:f,reviews:g}}async function swe(e){let{api:t,id:n,base:r,layers:o,mr:s,note:i}=e,a=e.when==="pull-request",c=s.iid,u={mr:c,note:i.id},l=async(O,I=!1)=>{try{await e.forge.upsertComment(c,iJ(i.id),`terragucci: ${O}
885
885
 
886
- ${i2(i.id)}`,{match:"anywhere",author:e.meName})}catch(N){return{...u,ran:I,replied:!1,fail:!0,reason:`${O}; and the reply failed (${N.message})`}}return{...u,ran:I,replied:!0,reason:O}},d=O=>({...u,ran:!1,replied:!1,fail:!0,reason:O}),p=O=>({...u,ran:!1,replied:!1,reason:O}),f=Cr(i.body,$s(e.env)),g=i.author?.username,m=i.author?.id;if(typeof g!="string"||!qo.test(g)||!Number.isInteger(m))return p("the note has no usable author");let h;try{h=(await t("GET",`/projects/${n}/members/all/${m}`))?.access_level}catch(O){return/ answered 404/.test(O.message)?p(`${g} is not a member of the project, so the note is ignored`):d(`could not read ${g}'s access, so nothing runs (${O.message})`)}if(typeof h!="number"||h<twe)return p(`${g} is below Developer on the project, so the note is ignored`);if(f.kind==="refused")return l(f.reason);if(f.kind==="agent"&&!e.agent)return l("the agent command is off in this project; `agent.comment` in terragucci.yml turns it on");if((f.kind==="lock"||f.kind==="unlock")&&!a)return l(`\`/terragucci ${f.kind}\` does not run here: this project applies after merge, so merge requests take no locks`);let y=s.source_project_id!==void 0&&s.target_project_id!==void 0&&s.source_project_id!==s.target_project_id;if(f.kind==="agent"){if(s.state!=="opened")return l(`!${c} is not open, so the agent does not run`);if(y)return l("a merge request from a fork gets no agent: its branch is not this project's to push to");if(s.source_branch===r)return l(`!${c}'s source branch is the default branch, and an agent never pushes there`);let O=s.sha;if(typeof O!="string"||!St.test(O))return d(`!${c}'s head is not a commit`);let I;try{I=await GO(e.env,e.fetch,{[gl]:String(c),[E$]:String(i.id),[T$]:O})}catch(L){return d(`could not start the agent's pipeline on ${r} for !${c} (${L.message})`)}let N=typeof I?.web_url=="string"&&/^https?:\/\//.test(I.web_url)?` ${I.web_url}`:"";return l(`started pipeline${N} on ${r} to run the agent on !${c}'s head ${Ts(O)} for ${g}; it reads !${c} and this note again before the agent runs, and pushes what the agent changes to \`${String(s.source_branch)}\``,!0)}if(a&&f.kind!=="plan"){if(s.state!=="opened")return f.kind==="apply"?l(s.state==="merged"?`!${c} is merged, and this project applies a merge request from its head before it merges, so there is nothing of it left to apply; the push after the merge ran \`confirm\``:`!${c} was closed without merging, so there is nothing of it to apply`):l(`!${c} is not open, so it holds no lock: the next merge request that reaches its roots takes them over`);if(y)return l("a merge request from a fork is never applied: its code would run with this project's apply credentials");if(s.target_branch!==r)return l(`!${c} targets ${String(s.target_branch)}, not the default branch ${r}, so it is not applied`);let O=s.sha;if(typeof O!="string"||!St.test(O))return d(`!${c}'s head is not a commit`);if(f.kind==="apply"){let M=await rP({api:t,id:n,mr:s,base:r,...await rl(process.cwd()),...e.env?{env:e.env}:{},...e.fetch?{fetch:e.fetch}:{},...e.requires?{requires:e.requires}:{},...e.wait?{wait:e.wait}:{}});if(M&&"fail"in M)return d(M.fail);if(M)return l(M.refuse)}if(!e.env.TG_MERGE_TOKEN)return d(`the comments job has no TG_MERGE_TOKEN, the token apply.merge_token_env names, so it cannot start a pipeline on ${r} for !${c}`);let I;try{I=await $o(e.env,e.env.TG_MERGE_TOKEN,e.fetch)("POST",`/projects/${n}/pipeline`,{ref:r,variables:[{key:wl,value:String(c),variable_type:"env_var"},{key:M$,value:String(i.id),variable_type:"env_var"},{key:L$,value:O,variable_type:"env_var"}]})}catch(M){return d(`could not start a pipeline on ${r} for !${c} (${M.message})`)}let N=typeof I?.web_url=="string"&&/^https?:\/\//.test(I.web_url)?` ${I.web_url}`:"",L=f.kind==="apply"?f.wave:void 0,U=L!==void 0?` through wave ${L}`:"",F=f.kind==="apply"?`apply !${c}'s head ${Ts(O)}${U}`:`${f.kind} the roots of !${c}`;return l(`started pipeline${N} on ${r} to ${F} for ${g}; its job reads !${c} again before it ${f.kind==="apply"?"applies":f.kind+"s"}`,!0)}if(f.kind==="plan"){if(f.root!==void 0&&!tA(f.root,o))return l(`${f.root} is not a root of this repository. The roots are ${o.flat().sort().map(L=>`\`${L}\``).join(", ")}.`);if(s.state!=="opened")return l(`!${c} is not open, so it is not planned`);if(y)return l("a merge request from a fork is not re-planned: its code never runs with this project's credentials");let O;try{O=await t("POST",`/projects/${n}/merge_requests/${c}/pipelines`)}catch(L){return d(`could not start a pipeline for !${c} (${L.message})`)}let I=typeof O?.web_url=="string"&&/^https?:\/\//.test(O.web_url)?` ${O.web_url}`:"",N=f.root!==void 0?`; GitLab re-plans every root the merge request reaches, \`${f.root}\` among them`:"";return l(`started pipeline${I} to re-plan !${c} for ${g}${N}`,!0)}if(f.kind!=="apply")return l(`\`/terragucci ${f.kind}\` does not run here`);if(f.wave!==void 0)return l(`GitLab runs the waves after a retried apply job by itself, so \`wave-${f.wave}\` cannot stop them; write \`/terragucci apply\`, and each wave still waits behind its own gate`);if(s.state==="opened")return l(`!${c} is not merged: \`/terragucci apply\` retries the apply of a merged merge request, from the default branch, and never applies a merge request's branch`);if(s.state!=="merged")return l(`!${c} was closed without merging, so there is nothing of it to apply`);if(y)return l("a merge request from a fork is not applied on a note: its apply runs when it reaches the default branch");if(s.target_branch!==r)return l(`!${c} was merged into ${String(s.target_branch)}, not the default branch ${r}, so nothing applies from it`);let w=[s.merge_commit_sha,s.squash_commit_sha,s.sha].find(O=>typeof O=="string"&&St.test(O));if(!w)return d(`!${c} has no merge commit`);let b,k;try{let O=await t("GET",`/projects/${n}/pipelines?sha=${w}&ref=${encodeURIComponent(r)}&source=push&order_by=id&sort=desc&per_page=1`);if(b=Array.isArray(O)?O[0]:void 0,!b)return l(`no pipeline ran on ${r} at the merge commit ${Ts(w)} of !${c}, so there is no apply job to retry; push to ${r} again`);k=await t("GET",`/projects/${n}/pipelines?ref=${encodeURIComponent(r)}&source=push&order_by=id&sort=desc&per_page=${nwe}`)}catch(O){return d(`could not read the pipelines of ${r} (${O.message})`)}let v=[...new Set((Array.isArray(k)?k:[]).filter(O=>Number(O?.id)>Number(b.id)&&O?.sha!==w).map(O=>O.sha))];for(let O of v){if(typeof O!="string"||!St.test(O))continue;let I;try{I=await t("GET",`/projects/${n}/repository/commits/${O}/statuses?name=${encodeURIComponent(s2)}&ref=${encodeURIComponent(r)}`)}catch(L){return d(`could not read the statuses of ${Ts(O)} (${L.message})`)}let N=Array.isArray(I)?I.find(L=>L?.name===s2):void 0;if(N){let L=typeof N.target_url=="string"&&/^https?:\/\//.test(N.target_url)?` (${N.target_url})`:"";return l(`a later apply already ran on ${r} at ${Ts(O)}${L}, so the merge commit ${Ts(w)} of !${c} is not applied: comment on the merge request that commit came from, or push again`)}}let $;try{$=await t("GET",`/projects/${n}/pipelines/${b.id}/jobs?per_page=100`)}catch(O){return d(`could not read the jobs of pipeline ${b.id} (${O.message})`)}let _=(Array.isArray($)?$:[]).map(O=>{let I=/^apply-wave-([1-9][0-9]*)(?:-share-([1-9][0-9]*))?$/.exec(String(O?.name));return{j:O,n:Number(I?.[1]),s:I?.[2]?Number(I[2]):0}}).filter(O=>Number.isInteger(O.n)).sort((O,I)=>O.n-I.n||O.s-I.s);if(_.length===0)return l(`pipeline ${b.id} at ${Ts(w)} has no apply jobs, so there is nothing to retry`);let E=_.find(O=>O.j.status!=="success");if(!E)return l(`every wave of !${c}'s merge commit ${Ts(w)} already applied`);let R=String(E.j.name);if(["created","pending","running","waiting_for_resource","preparing","scheduled"].includes(E.j.status))return l(`${R} at ${Ts(w)} is ${E.j.status} already, so it is not retried`);if(E.j.status!=="failed"&&E.j.status!=="canceled")return l(`${R} at ${Ts(w)} was ${String(E.j.status)}, and GitLab retries only a failed or canceled job; push to ${r} again`);let x;try{x=await t("POST",`/projects/${n}/jobs/${E.j.id}/retry`)}catch(O){return d(`could not retry ${R} of pipeline ${b.id} (${O.message})`)}let A=typeof x?.web_url=="string"&&/^https?:\/\//.test(x.web_url)?` (${x.web_url})`:"";return l(`retried ${R} of !${c}'s merge commit ${Ts(w)}${A} for ${g}; its gate decides again, and the waves after it follow, each behind its own gate`,!0)}import{readFileSync as iwe}from"node:fs";var c2=["status","note","reply","stale"],u2="<!-- terragucci:stale -->",awe=new Set(["pending","success","failure"]);async function d2(e,t,n){let r=n.env??process.env;if(!c2.includes(e))throw new S(`post is ${c2.join(", ")}`);if(!r.TG_TOKEN)return`terragucci: no forge token, so no ${e}`;try{let o=jt(n.forge,r,n.fetch);e==="status"?await cwe(o,t,n,r):e==="note"?await o.upsertComment(l2(r),yi,iwe(OP(t[0],"note <file>"),"utf-8")):e==="reply"?await o.addComment(l2(r),`terragucci: ${OP(t[0],"reply <text>")}`):await dwe(o,OP(t[0],"stale <roots> <branch>").split(","),t[1]??"",n,r);return}catch(o){return`terragucci: ${e} failed: ${o.message}`}}function OP(e,t){if(e===void 0)throw new S(`post ${t}`);return e}function l2(e){let t=Number((e.TG_PR??"").trim());if(!Number.isInteger(t)||t<1)throw new S("TG_PR names no pull request");return t}async function cwe(e,t,n,r){let[o,s,i=""]=t;if(!o||!s||!awe.has(s))throw new S("post status <context> pending|success|failure <description>");let a=(r.TG_SHA??"").trim();if(!a)throw new S("TG_SHA names no commit");let c=n.forge==="gitlab"&&a===r.CI_COMMIT_SHA&&/^[0-9]+$/.test(r.CI_PIPELINE_ID??"")?Number(r.CI_PIPELINE_ID):void 0;await e.setStatus(a,{context:o,state:s==="pending"?"running":s,description:i,...e.runUrl?{url:e.runUrl}:{},...c!==void 0?{pipelineId:c}:{}})}async function uwe(e,t,n){let r=t.fetch??globalThis.fetch,o=encodeURIComponent(e);if(t.forge==="gitlab"){let a=await $o(n,n.TG_TOKEN,r)("GET",`/projects/${encodeURIComponent(n.CI_PROJECT_ID??"")}/merge_requests?state=opened&target_branch=${o}&per_page=100`);return(Array.isArray(a)?a:[]).map(c=>c?.iid).filter(c=>Number.isInteger(c))}let s=pb(n,{kind:t.forge,tokenEnv:"TG_TOKEN"}),i=await kt(r,{forge:t.forge,origin:`https://${s.host}`,path:s.repo,token:s.token,api:s.apiBase},"GET",`/repos/${s.repo}/pulls?state=open&base=${o}&per_page=100`);return(Array.isArray(i)?i:[]).map(a=>a?.number).filter(a=>Number.isInteger(a))}function lwe(e,t,n){if(e.includes(u2))return;let r=(/roots=(\S*) -->/.exec(e)?.[1]??"").split(",").filter(s=>t.includes(s));if(!r.length)return;let o=e.split(`
887
- `);return o.splice(1,0,`> This plan is stale: ${n} moved under ${r.join(", ")}. Push to this pull request to plan again. ${u2}`),o.join(`
888
- `)}async function dwe(e,t,n,r,o){for(let s of await uwe(n,r,o)){let i=await e.findComment(s,yi),a=i?lwe(i.body,t,n):void 0;a&&await e.upsertComment(s,yi,a)}}import{spawnSync as fk}from"node:child_process";import{delimiter as gve,join as mve}from"node:path";import{spawn as mwe,spawnSync as hwe}from"node:child_process";import{delimiter as ywe,join as wwe}from"node:path";var Ri=e=>new Date(e).getTime();function pwe(e,t){let n=new Map;for(let r of e){if(t!==void 0&&r.gate!==t)continue;let o=`${r.gate}\0${r.environment??""}`,s=n.get(o);(!s||Ri(r.timestamp)>=Ri(s.timestamp))&&n.set(o,r)}return[...n.values()]}var fwe=(e,t)=>{if(t.approval)return;let n=e.filter(r=>t.environment===void 0||r.environment===t.environment);return qw(n,t.gate,t.timestamp,t.planDigest).resolution};function p2(e,t,n,r){if(n===void 0)return;let o={gate:t,planDigest:n,timestamp:r.timestamp,...r.review?{review:{pr:r.review.pr,head:r.review.head}}:{}};if(wg(e,o))return e.find(s=>wg([s],o))}function m2(e){let t=e.applied??[],n=e.finished??[],r=e.answering??fwe,o=[],s=pwe(e.pending,e.gate).filter(i=>!hg(i,e.now));for(let i of s){let a={source:"pending",op:i.op||e.op,gate:i.gate,...i.environment!==void 0?{environment:i.environment}:{},...i.planDigest!==void 0?{planDigest:i.planDigest}:{},...Pg(i.resume)?{run:i.resume}:{},pending:i};if(i.answer){let l=iH(i,e.questions?.get(i.answer.id));if(!l.resumable){o.push({...a,resumable:!1,reason:"not-approved",message:l.message});continue}let p=e.resolutions.filter(f=>f.gate===i.gate&&f.answer?.id===i.answer.id&&Ie(f.planDigest,i.planDigest)).map(f=>({r:f,by:p2(t,i.gate,i.planDigest,f)})).find(f=>f.by);if(p?.by){o.push({...a,resumable:!1,reason:"spent",approvedBy:l.answeredBy,approvedAt:p.r.timestamp,message:g2(p.by),spent:f2(p.by)});continue}o.push({...a,resumable:!0,reason:"answered",approvedBy:l.answeredBy,...l.answeredOn?{approvedAt:l.answeredOn}:{},message:`question ${i.answer.id} has an answer the gate passes on`});continue}let c=r(e.resolutions,i,e.pending);if(!c){o.push({...a,resumable:!1,reason:"not-approved",message:i.approval&&!e.answering?"its gate has an approval block, which needs chant's tally (pass answering)":"no approval answers it yet"});continue}let u=p2(t,i.gate,i.planDigest,c);if(u){o.push({...a,resumable:!1,reason:"spent",approvedBy:c.resolvedBy,approvedAt:c.timestamp,message:g2(u),spent:f2(u)});continue}o.push({...a,resumable:!0,reason:"approved",approvedBy:c.resolvedBy,approvedAt:c.timestamp,message:`approved by ${c.resolvedBy} at ${c.timestamp}`})}for(let i of gwe(t,n,e.pending,e.gate))o.push({source:"stopped",op:i.op||e.op,gate:i.gate,planDigest:i.planDigest,resumable:!0,reason:"stopped",approvedBy:i.approvedBy,approvedAt:i.approvedAt,run:i.resume,stopped:{appliedAt:i.timestamp,approvedAt:i.approvedAt,kind:i.kind,...i.wave?{wave:i.wave}:{}},message:`the apply of ${i.planDigest} under the approval by ${i.approvedBy||"?"} began at ${i.timestamp} and never recorded how it ended; it resumes once run ${i.resume.run} is gone`});return o}var f2=e=>({appliedAt:e.timestamp,approvedAt:e.approvedAt,...e.wave?{wave:e.wave}:{}}),g2=e=>`the approval that answers it is spent: ${e.wave?`wave ${e.wave.number} (${e.wave.name})`:"a wave"} applied under it at ${e.timestamp}`;function gwe(e,t,n,r){let o=new Map;for(let i of e){if(r!==void 0&&i.gate!==r)continue;let a=o.get(i.gate);(!a||Ri(i.timestamp)>=Ri(a.timestamp))&&o.set(i.gate,i)}let s=[];for(let[i,a]of o){let c=e.filter(l=>l.gate===i&&Ie(l.planDigest,a.planDigest)&&Ri(l.approvedAt)===Ri(a.approvedAt)&&Pg(l.resume)),u=c.reduce((l,d)=>!l||Ri(d.timestamp)>=Ri(l.timestamp)?d:l,void 0);u&&(c.some(l=>DM(t,l))||n.some(l=>l.gate===i&&Ri(l.timestamp)>Ri(u.timestamp))||s.push(u))}return s}function bwe(e,t,n,r){let o=(i,a)=>m2({op:i,pending:a.pending,resolutions:a.resolutions,applied:a.applied??[],finished:a.finished??[],now:t,...r?{questions:r}:{}}).filter(c=>c.resumable),s=o(Yt,e).filter(i=>/^wave-\d+$/.test(i.gate)).map(i=>({wave:Number(i.gate.slice(5)),...h2(i)}));return n&&s.push(...o(mr,n).filter(i=>i.source==="pending").map(i=>({wave:1,migration:i.gate,...h2(i)}))),s.sort((i,a)=>i.wave-a.wave||+!!a.migration-+!!i.migration)}function h2(e){let t=e.pending,n=t?yl(t):e.run?.run;return{digest:e.planDigest??"",by:e.approvedBy??"",...n?{runId:n}:{},...t?.commit?{commit:t.commit}:{},...e.source==="stopped"&&e.stopped?{stopped:{holder:e.run?.run??"",at:e.stopped.appliedAt}}:{}}}async function y2(e){if(e.forge==="gitlab")return{kind:"none",why:"on GitLab chant run resume starts each job that waits, or whose apply stopped"};let t=e.liveness??ib(e.env),n=[];for(let o of bwe(e.ledger,e.now??new Date().toISOString(),e.migrations,e.questions))(!o.stopped||o.stopped.holder&&await t({run:o.stopped.holder,at:o.stopped.at})==="dead")&&n.push(o);if(n.length===0)return{kind:"none",why:"no wave waits with an approval that stands, and no approved apply stopped before it finished"};let r;try{r=(await xa(to(e.env,e.fetch),{...e.env,TG_PR:""},e.sha))?.number}catch{}return{kind:"apply",sha:e.sha,...r!==void 0?{pr:r}:{},waves:n}}function mh(e,t){let n=/^(?:([a-z+]+):\/\/)?(?:[^@/]+@)?([^/:]+)(:\d+)?[/:](.+?)(?:\.git)?\/?$/.exec(e.trim());if(!n)return;let r=n[1],o=n[2],s=r==="http"||r==="https"?`${r}://${o}${n[3]??""}`:`https://${o}`,i=o==="github.com"?"github":o==="gitlab.com"?"gitlab":t==="github"||t==="gitlab"||t==="forgejo"?t:void 0;return i?{forge:i,host:o,web:s,path:n[4]}:void 0}function vwe(e,t){if(e==="gitlab")return t.GITLAB_TOKEN||void 0;if(e==="forgejo")return t.FORGEJO_TOKEN||void 0;if(t.GH_TOKEN||t.GITHUB_TOKEN)return t.GH_TOKEN||t.GITHUB_TOKEN;let n=hwe("gh",["auth","token"],{encoding:"utf-8"});return n.status===0&&n.stdout.trim()?n.stdout.trim():void 0}var $we={github:"GH_TOKEN (or gh auth login)",forgejo:"FORGEJO_TOKEN",gitlab:"GITLAB_TOKEN"};async function PP(e){let t=e.env??process.env,n=e.fetch??fetch,{forge:r,host:o,web:s,path:i}=e.origin,a="the pipeline's resume job applies it on its next run when apply.resume is set; otherwise run its job again, or comment /terragucci apply",c=vwe(r,t);if(!c)return`not resumed from here: no token in ${$we[r]}; ${a}`;let u=async(l,d,p,f)=>{let g=await n(d,{method:l,headers:{"content-type":"application/json",...p},...f===void 0?{}:{body:JSON.stringify(f)}});if(!g.ok)throw new Error(`${l} ${d.replace(/^https?:\/\/[^/]+/,"")} answered ${g.status}`);return g.status===204?null:g.json().catch(()=>null)};try{if(r==="github"||r==="gitlab"){if(!e.wave.resume)return`not resumed from here: the pending fact names no CI job; ${a}`;let f=await e1(e.wave.resume,{token:c,...e.fetch?{fetch:e.fetch}:{}});return f.status!=="resumed"?`not resumed from here: ${"reason"in f?f.reason:f.how}; ${a}`:`resumed: ${f.how}, so wave ${e.wave.wave} runs again and its gate decides`}if(!e.wave.commit)return`not resumed from here: the pending fact names no commit; ${a}`;let l=`${s}/api/v1/repos/${i}`,d={authorization:`token ${c}`},p=await u("GET",`${l}/commits/${e.wave.commit}/pull`,d).catch(()=>null);return Number.isInteger(p?.number)?(await Ag({apiBase:`${s}/api/v1`,repo:i,token:c,host:o,fetch:n}).addComment(p.number,"/terragucci apply"),`resumed: commented /terragucci apply on pull request ${p.number}, so its waves run again and each gate decides`):`not resumed from here: no merged pull request made ${e.wave.commit.slice(0,8)}; ${a}`}catch(l){return`not resumed from here (${l.message}); ${a}`}}var kwe=["tf-apply",mr];async function w2(e,t,n={}){let r=n.log??(i=>console.log(i)),o=t.CHANT_FORGE_TOKEN||t.TG_TOKEN||t.GITLAB_TOKEN,s=0;for(let i of kwe){r(`terragucci resume: chant run resume --op ${i}`);let a=await _we(e,["run","resume","--op",i],{...t,...o?{CHANT_FORGE_TOKEN:o}:{}},n.chant,"inherit");a.error?(r(`terragucci resume: chant run resume did not start (${a.error.message}); install terragucci again, which installs chant with it`),s=1):a.status!==0&&(s=a.status??1)}return s}function _we(e,t,n,r,o){return new Promise(s=>{let i=mwe(vi(e,r?{chant:r}:{}),t,{cwd:e,stdio:["ignore",o,o],env:{...n,PATH:[wwe(e,"node_modules",".bin"),n.PATH??""].join(ywe)}}),a="",c="";i.stdout?.on("data",u=>a+=u),i.stderr?.on("data",u=>c+=u),i.on("error",u=>s({status:null,stdout:a,stderr:c,error:u})),i.on("close",u=>s({status:u,stdout:a,stderr:c}))})}import{spawnSync as k2}from"node:child_process";import{createHash as Swe}from"node:crypto";import{existsSync as CP,mkdtempSync as _2,realpathSync as b2,rmSync as Rwe,writeFileSync as xwe}from"node:fs";import{tmpdir as S2}from"node:os";import{basename as Ewe,dirname as jP,isAbsolute as Twe,join as El,relative as Awe,resolve as Owe}from"node:path";var Hc="tf-state-export",ek=`_gates/${Hc}.jsonl`,IP=`_gates/${Hc}/done.jsonl`,Pwe=48,v2=(e,t,n=!1)=>`terragucci approve export ${e} --plan ${t}${n?" --sign":""}`,R2=e=>`sha256:${Swe("sha256").update(e).digest("hex")}`,Pa=e=>Date.parse(e)||0;function jwe(e){return R2(JSON.stringify({op:Hc,root:e.root,location:e.location,version_id:e.version_id,by:e.by,at:e.at}))}function $2(e,t,n){let r=k2("git",t,{cwd:e,encoding:"utf-8",env:n});return r.status===0?r.stdout.trim():""}function Iwe(e){let t=new Map;for(let n of e.split(`
889
- `).map(r=>r.trim()).filter(Boolean))try{let r=JSON.parse(n);r.version===1&&r.kind==="state-export"&&typeof r.planDigest=="string"&&t.set(r.planDigest,r)}catch{}return t}function Cwe(e,t,n,r){let o=`${n.replace(/[^\w.-]+/g,"_")}.${r.replace(/[^\w.-]+/g,"_")}.tfstate`;if(!t)return El(_2(El(S2(),"terragucci-export-")),o);let s=Owe(t);if(!CP(jP(s)))throw new S(`--out ${t}: ${jP(s)} is not a directory`);if(CP(s))throw new S(`--out ${t} is there already; name a new file`);let i=Awe(b2(e),El(b2(jP(s)),Ewe(s)));if(!i.startsWith("..")&&!Twe(i))throw new S(`--out ${t} is inside the repo, where a state could be committed; write it outside the repo`);return s}async function x2(e,t){let n=t.log??(F=>console.log(F)),r=t.env??process.env,o=t.exec??li,s=t.now??new Date().toISOString(),i=t.root.replace(/\/+$/,"");if(!i)throw new S("state export takes the root whose state to export: terragucci state export <root> [--version <id>]");let a=t.config??fe(e),c=a?ue(await Re(a)):void 0,u=et(e,c??ue({}));await u.prepareRoots({roots:[i],...t.terragrunt?.path?{terragrunt:t.terragrunt.path}:{},...t.terragrunt?.exec?{exec:t.terragrunt.exec}:{}});let l=OA(e,i);if(l)throw new S(`state export: ${l}`);let d=CP(El(e,i,"terragrunt.hcl")),p=t.actor||$2(e,["config","user.name"],r)||$2(e,["config","user.email"],r);if(!p)throw new S("state export names who asks: set git's user.name, or pass --actor <name>");let f=t.binary??c?.binary??u.binary([i]).value,g=_2(El(S2(),"terragucci-export-data-")),m;try{if(d){let F=await Dm(e,i,f,r,g,t.terragrunt);m=Hr(F.dir,F.env)}else{let F={...r,TF_DATA_DIR:g},M=await o(f,["init","-input=false","-no-color"],El(e,i),u.rootInit(i,F)?.init??F);if(M.code!==0)throw new S(`init in ${i} failed: ${M.out.trim().split(`
890
- `).slice(-4).join(" ").slice(0,400)}`);m=Hr(El(e,i),u.rootEnv(i,F))}}finally{Rwe(g,{recursive:!0,force:!0})}if("unsupported"in m)throw new S(`state export: ${i}: ${m.unsupported.replace(Wf,"exports state from s3, gcs and azurerm backends and GitLab-managed state")}`);let h,y=t.version,w;if("gitlab"in m){let F=m.gitlab,M=t.fetch??globalThis.fetch;h=m.location;let te=async ie=>{try{return await ie()}catch(Q){throw new S(`state export: ${Q.message}`)}};if(y)if(/^\d+$/.test(y)){if(!await te(()=>eN(F,y,M)))throw new S(`state export: ${h} has no version ${y}; GitLab keeps no version with that serial`)}else throw new S(`state export: ${i}'s state is GitLab-managed, whose versions are serials; --version ${y} is not one`);else{let ie=await te(()=>Yy(F,M));if(!ie)throw new S(`state export: ${h} holds no state`);y=String(ie.serial)}w=ie=>te(()=>tN(F,ie,M))}else{if(!ar(m))throw new S(`state export: ${i}'s state is a local file, ${m.path}, which keeps no versions; export reads a version of an s3, gcs or azurerm state by its id`);let F=await Ws(m,t.fetch);if(h=F.location,y){if(!await F.hasVersion(y))throw new S(`state export: ${h} has no version ${y}; a lifecycle rule may have expired it`)}else{let M=await F.version(!0);if(!M.exists)throw new S(`state export: ${h} holds no state`);if(!M.versionId)throw new S(`state export: ${h} keeps no versions, so there is no version id to export: ${M.off??"the store gave none"}`);y=M.versionId}w=M=>F.readVersion(M)}let b=ft(e,ek),k=k2("git",["show",`refs/remotes/origin/chant/lifecycle:${IP}`],{cwd:e,encoding:"utf-8"}),v=Iwe(k.status===0?k.stdout:""),$=b.pending.filter(F=>F.op===Hc&&F.gate===i&&F.request&&F.request.by===p&&F.request.version_id===y&&F.request.location===h).filter(F=>Pa(F.expiresAt)>Pa(s)&&F.planDigest!==void 0&&!v.has(F.planDigest)).sort((F,M)=>Pa(M.timestamp)-Pa(F.timestamp))[0],_=await Yr(e,{at:"HEAD",...a?{config:a}:{}}),E=_.mode==="sealed";if(!$){let F={root:i,location:h,version_id:y,by:p,at:s},M=jwe(F),te={version:1,kind:"pending",op:Hc,gate:i,timestamp:s,expiresAt:new Date(Pa(s)+Pwe*3600*1e3).toISOString(),planDigest:M,description:`state export: ${i} version ${y}, for ${p}`,request:F,neverOverMcp:!0};ha(e,te,{},ek);let ie=v2(i,M,E);return n(`state export: ${p} asks for ${i}'s state, ${h} version ${y}; request ${M}`),n("someone other than you approves it with:"),n(` ${ie}`),n("then run this command again to download it."),{code:3,digest:M,command:ie}}let R=$.planDigest,x=v2(i,R,E),I=b.resolutions.filter(F=>F.gate===i&&Ie(F.planDigest,R)&&Pa(F.timestamp)>=Pa($.timestamp)).filter(F=>{if(F.resolvedBy===p)return n(`state export: an approval by ${p} does not count: the person who asks for an export does not approve it`),!1;let M=E?ws(_.signers,F):null;return M&&n(`state export: an approval does not count: ${M}`),M===null}).sort((F,M)=>Pa(M.timestamp)-Pa(F.timestamp))[0];if(!I)return n(`state export: request ${R} for ${i} version ${y} waits for an approval by someone other than ${p}:`),n(` ${x}`),{code:3,digest:R,command:x};let N=Cwe(e,t.out,i,y),L=await w(y);if(L===void 0)throw new S(`state export: ${h} has no version ${y}; it was deleted since the request`);let U={version:1,kind:"state-export",op:Hc,gate:i,planDigest:R,root:i,location:h,version_id:y,exportedBy:p,approvedBy:I.resolvedBy,approvedAt:I.timestamp,timestamp:new Date().toISOString(),content_digest:R2(L)};return hr(e,IP,[JSON.stringify(U)],{},`State exported: ${i} version ${y} for ${p}`),xwe(N,L,{mode:384,flag:"wx"}),n(`state export: wrote ${N}: ${i}'s state, ${h} version ${y}, approved by ${I.resolvedBy}`),n(`state export: recorded on chant/lifecycle in ${IP}; delete the file when you are done with it`),{code:0,digest:R,file:N}}import{spawnSync as RJ}from"node:child_process";import{existsSync as ove,statSync as sve}from"node:fs";import{resolve as vJ}from"node:path";import{execFileSync as Ybe}from"node:child_process";import{mkdirSync as Xbe,mkdtempSync as Qbe,rmSync as eve,writeFileSync as tve}from"node:fs";import{tmpdir as nve}from"node:os";import{dirname as rve,join as lk}from"node:path";import{execFileSync as mbe}from"node:child_process";import{mkdtempSync as hbe,rmSync as ybe,unlinkSync as wbe,writeFileSync as bbe}from"node:fs";import{tmpdir as vbe}from"node:os";import{join as W2,relative as $be}from"node:path";import{execFileSync as F2}from"node:child_process";import{existsSync as So,mkdirSync as sbe,readFileSync as Ro,unlinkSync as ibe,writeFileSync as abe}from"node:fs";import{basename as cbe,dirname as ube,join as _o,relative as qc}from"node:path";import{gunzipSync as Nwe}from"node:zlib";var E2=JSON.parse(Nwe(Buffer.from("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","base64")).toString("utf8"));var tk={dir:"TGPH0DIR",prometheus:"TGPH0PROMETHEUS",tempo:"TGPH0TEMPO",folder:"TGPH0FOLDER",path:"TGPH0PATH",drift_age:"7919001s",wave_wait:"7919002s",schedule:"7919003s"},T2="https://tgph0.invalid/TGPH0REPORTS",NP=["drift_age","wave_wait","schedule"],DP=["prometheus","tempo","folder","path"];function A2(e){if(e==="0")return 0;let t=/^(?:(\d+)y)?(?:(\d+)w)?(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?(?:(\d+)ms)?$/.exec(e);if(!e||!t)return 0;let n=[365*864e5,7*864e5,864e5,36e5,6e4,1e3,1],r=0;for(let o=0;o<n.length;o++)t[o+1]!==void 0&&(r+=Number(t[o+1])*n[o]);return Math.round(r/1e3)}var Dwe=["y","Y","yes","Yes","YES","on","On","ON","n","N","no","No","NO","off","Off","OFF"],Mwe=/^[-+]?[0-9_]+(?::[0-9_]+)+(?:\.[0-9_]*)?$/,Lwe=/^(?:[-+]?(?:[0-9]+)(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?|\.[0-9]+(?:[eE][-+]?[0-9]+)?|[-+]?\.(?:inf|Inf|INF)|\.(?:nan|NaN|NAN))$/,Gwe=/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/,Fwe=/^[0-9]{4}-[0-9][0-9]?-[0-9][0-9]?(?:[Tt]|[ \t]+)[0-9][0-9]?:[0-9]{2}:[0-9]{2}(?:\.[0-9]*)?(?:[ \t]*(Z|[-+][0-9][0-9]?(?::[0-9]{2})?))?$/,zwe=/^[-+]?(?:0b[01]+|0x[0-9a-fA-F]+|0o[0-7]+|[0-9]+)$/,Uwe={0:"\\0",7:"\\a",8:"\\b",9:"\\t",10:"\\n",11:"\\v",12:"\\f",13:"\\r",27:"\\e",34:'\\"',92:"\\\\",133:"\\N",160:"\\_",8232:"\\L",8233:"\\P"},nk=e=>e>=32&&e<=126||e>=161&&e<=55295&&e!==8232&&e!==8233||e>=57344&&e<=65533&&e!==65279||e>=65536&&e<=1114111,rk=e=>e===32||e===9,MP=e=>nk(e)&&e!==65279&&e!==13&&e!==10,Bwe=(e,t)=>{let n=MP(e)&&!rk(e);return MP(e)&&e!==35&&!(t===58&&!n)||t!==void 0&&MP(t)&&!rk(t)&&e===35||t===58&&n},Wwe=e=>nk(e)&&e!==65279&&!rk(e)&&!"-?:,[]{}#&*!|=>'\"%@`".includes(String.fromCodePoint(e)),Hwe=e=>{if(!zwe.test(e))return!1;let t=e.replace(/^[-+]/,""),n={b:2,o:8,x:16}[t[1]]??10;return Number.isFinite(Number.parseInt(n===10?t:t.slice(2),n))},qwe=e=>["~","null","Null","NULL","true","True","TRUE","false","False","FALSE","<<"].includes(e)||Hwe(e)||Lwe.test(e)&&(Number.isFinite(Number.parseFloat(e))||/\.(inf|Inf|INF|nan|NaN|NAN)$/.test(e))||Gwe.test(e)||Fwe.test(e);function Jwe(e){if(e==="")return'""';if(Dwe.includes(e)||Mwe.test(e))return`"${e}"`;let t=[...e].map(o=>o.codePointAt(0)),n=Wwe(t[0])&&!rk(t[t.length-1])&&t[t.length-1]!==58,r=!1;return t.forEach((o,s)=>{(o===10||!nk(o))&&(r=!0),n=n&&Bwe(o,s?t[s-1]:void 0)}),!r&&n&&!qwe(e)?e:`"${t.map(o=>Uwe[o]??(nk(o)?String.fromCodePoint(o):`\\${o<=255?"x":o<=65535?"u":"U"}${o.toString(16).toUpperCase().padStart(o<=255?2:o<=65535?4:8,"0")}`)).join("")}"`}var Kwe=e=>e.replace(/[.*+?^${}()|[\]\\/]/g,"\\$&");function O2(e,t,n={}){for(let m of NP)if(!/^[0-9a-z]+$/.test(t[m]))throw new Error(`dashboards.${m} is ${JSON.stringify(t[m])}; use a duration such as 4h or 1d`);for(let m of DP)if(/[\r\n]/.test(t[m]))throw new Error(`dashboards.${m} must be one line`);let r=new Map;for(let m of NP)r.set(tk[m],t[m]);for(let m of NP)r.set(String(A2(tk[m])),String(A2(t[m])));for(let m of DP)r.set(tk[m],t[m]);r.set(T2,n.reports);let o=new Set([...DP.map(m=>tk[m]),T2]),s=[...r.keys()].map(Kwe).join("|"),i=new RegExp(s,"g"),a=new RegExp(s),c=m=>{let h=r.get(m);if(h===void 0)throw new Error(`the dashboards template has ${m} and nothing to fill it with`);return h},u=m=>m.replace(i,c),l=m=>typeof m=="string"?u(m):Array.isArray(m)?m.map(l):m&&typeof m=="object"?Object.fromEntries(Object.entries(m).map(([h,y])=>[u(h),l(y)])):m,d=m=>{let h=/^(.*(?:: |- ))(\S+)$/.exec(m);return h&&o.has(h[2])&&!a.test(h[1])?`${h[1]}${Jwe(c(h[2]))}`:m.replace(i,y=>{if(o.has(y))throw new Error(`the dashboards template has ${y} inside a YAML scalar: ${m}`);return c(y)})},p=t.dir.replace(/\/+$/,""),f=n.reports!==void 0,g=[];for(let m of e.files){if(m.links!==void 0&&m.links!==f)continue;let h="json"in m?JSON.stringify(l(m.json),null,2)+m.suffix:m.yaml.map(d).join(`
891
- `);g.push({path:`${p}/${m.path}`,content:h})}return g.sort((m,h)=>m.path<h.path?-1:1)}function P2(e,t={}){return O2(E2,e,t)}var Vwe={dir:"observability/terragucci",prometheus:"prometheus",tempo:"tempo",folder:"terragucci",path:"/var/lib/grafana/dashboards/terragucci",drift_age:"1d",wave_wait:"4h",schedule:"2d"};function j2(e){if(e)return{...Vwe,...e===!0?{}:e}}var Zwe="# Generated by terragucci from terragucci.yml (dashboards). Edit terragucci.yml, then run terragucci init.",Ywe="terragucci";function I2(e,t){if(!e.endsWith(".json"))return t.startsWith(Zwe);try{let n=JSON.parse(t).tags;return Array.isArray(n)&&n.includes(Ywe)}catch{return!1}}var Op=".gitlab-ci.yml",ok=`- local: ${Xr.gitlab}`,Xwe=["# terragucci's jobs are in .gitlab/terragucci.yml, which terragucci init writes","# and this file includes. Add your own jobs here; init keeps them.","include:",` ${ok}`,""].join(`
886
+ ${iJ(i.id)}`,{match:"anywhere",author:e.meName})}catch(N){return{...u,ran:I,replied:!1,fail:!0,reason:`${O}; and the reply failed (${N.message})`}}return{...u,ran:I,replied:!0,reason:O}},d=O=>({...u,ran:!1,replied:!1,fail:!0,reason:O}),p=O=>({...u,ran:!1,replied:!1,reason:O}),f=Cr(i.body,$s(e.env)),g=i.author?.username,m=i.author?.id;if(typeof g!="string"||!qo.test(g)||!Number.isInteger(m))return p("the note has no usable author");let h;try{h=(await t("GET",`/projects/${n}/members/all/${m}`))?.access_level}catch(O){return/ answered 404/.test(O.message)?p(`${g} is not a member of the project, so the note is ignored`):d(`could not read ${g}'s access, so nothing runs (${O.message})`)}if(typeof h!="number"||h<twe)return p(`${g} is below Developer on the project, so the note is ignored`);if(f.kind==="refused")return l(f.reason);if(f.kind==="agent"&&!e.agent)return l("the agent command is off in this project; `agent.comment` in terragucci.yml turns it on");if((f.kind==="lock"||f.kind==="unlock")&&!a)return l(`\`/terragucci ${f.kind}\` does not run here: this project applies after merge, so merge requests take no locks`);let y=s.source_project_id!==void 0&&s.target_project_id!==void 0&&s.source_project_id!==s.target_project_id;if(f.kind==="agent"){if(s.state!=="opened")return l(`!${c} is not open, so the agent does not run`);if(y)return l("a merge request from a fork gets no agent: its branch is not this project's to push to");if(s.source_branch===r)return l(`!${c}'s source branch is the default branch, and an agent never pushes there`);let O=s.sha;if(typeof O!="string"||!St.test(O))return d(`!${c}'s head is not a commit`);let I;try{I=await GO(e.env,e.fetch,{[gl]:String(c),[E$]:String(i.id),[T$]:O})}catch(L){return d(`could not start the agent's pipeline on ${r} for !${c} (${L.message})`)}let N=typeof I?.web_url=="string"&&/^https?:\/\//.test(I.web_url)?` ${I.web_url}`:"";return l(`started pipeline${N} on ${r} to run the agent on !${c}'s head ${Ts(O)} for ${g}; it reads !${c} and this note again before the agent runs, and pushes what the agent changes to \`${String(s.source_branch)}\``,!0)}if(a&&f.kind!=="plan"){if(s.state!=="opened")return f.kind==="apply"?l(s.state==="merged"?`!${c} is merged, and this project applies a merge request from its head before it merges, so there is nothing of it left to apply; the push after the merge ran \`confirm\``:`!${c} was closed without merging, so there is nothing of it to apply`):l(`!${c} is not open, so it holds no lock: the next merge request that reaches its roots takes them over`);if(y)return l("a merge request from a fork is never applied: its code would run with this project's apply credentials");if(s.target_branch!==r)return l(`!${c} targets ${String(s.target_branch)}, not the default branch ${r}, so it is not applied`);let O=s.sha;if(typeof O!="string"||!St.test(O))return d(`!${c}'s head is not a commit`);if(f.kind==="apply"){let M=await rP({api:t,id:n,mr:s,base:r,...await rl(process.cwd()),...e.env?{env:e.env}:{},...e.fetch?{fetch:e.fetch}:{},...e.requires?{requires:e.requires}:{},...e.wait?{wait:e.wait}:{}});if(M&&"fail"in M)return d(M.fail);if(M)return l(M.refuse)}if(!e.env.TG_MERGE_TOKEN)return d(`the comments job has no TG_MERGE_TOKEN, the token apply.merge_token_env names, so it cannot start a pipeline on ${r} for !${c}`);let I;try{I=await $o(e.env,e.env.TG_MERGE_TOKEN,e.fetch)("POST",`/projects/${n}/pipeline`,{ref:r,variables:[{key:wl,value:String(c),variable_type:"env_var"},{key:M$,value:String(i.id),variable_type:"env_var"},{key:L$,value:O,variable_type:"env_var"}]})}catch(M){return d(`could not start a pipeline on ${r} for !${c} (${M.message})`)}let N=typeof I?.web_url=="string"&&/^https?:\/\//.test(I.web_url)?` ${I.web_url}`:"",L=f.kind==="apply"?f.wave:void 0,U=L!==void 0?` through wave ${L}`:"",F=f.kind==="apply"?`apply !${c}'s head ${Ts(O)}${U}`:`${f.kind} the roots of !${c}`;return l(`started pipeline${N} on ${r} to ${F} for ${g}; its job reads !${c} again before it ${f.kind==="apply"?"applies":f.kind+"s"}`,!0)}if(f.kind==="plan"){if(f.root!==void 0&&!tA(f.root,o))return l(`${f.root} is not a root of this repository. The roots are ${o.flat().sort().map(L=>`\`${L}\``).join(", ")}.`);if(s.state!=="opened")return l(`!${c} is not open, so it is not planned`);if(y)return l("a merge request from a fork is not re-planned: its code never runs with this project's credentials");let O;try{O=await t("POST",`/projects/${n}/merge_requests/${c}/pipelines`)}catch(L){return d(`could not start a pipeline for !${c} (${L.message})`)}let I=typeof O?.web_url=="string"&&/^https?:\/\//.test(O.web_url)?` ${O.web_url}`:"",N=f.root!==void 0?`; GitLab re-plans every root the merge request reaches, \`${f.root}\` among them`:"";return l(`started pipeline${I} to re-plan !${c} for ${g}${N}`,!0)}if(f.kind!=="apply")return l(`\`/terragucci ${f.kind}\` does not run here`);if(f.wave!==void 0)return l(`GitLab runs the waves after a retried apply job by itself, so \`wave-${f.wave}\` cannot stop them; write \`/terragucci apply\`, and each wave still waits behind its own gate`);if(s.state==="opened")return l(`!${c} is not merged: \`/terragucci apply\` retries the apply of a merged merge request, from the default branch, and never applies a merge request's branch`);if(s.state!=="merged")return l(`!${c} was closed without merging, so there is nothing of it to apply`);if(y)return l("a merge request from a fork is not applied on a note: its apply runs when it reaches the default branch");if(s.target_branch!==r)return l(`!${c} was merged into ${String(s.target_branch)}, not the default branch ${r}, so nothing applies from it`);let w=[s.merge_commit_sha,s.squash_commit_sha,s.sha].find(O=>typeof O=="string"&&St.test(O));if(!w)return d(`!${c} has no merge commit`);let b,k;try{let O=await t("GET",`/projects/${n}/pipelines?sha=${w}&ref=${encodeURIComponent(r)}&source=push&order_by=id&sort=desc&per_page=1`);if(b=Array.isArray(O)?O[0]:void 0,!b)return l(`no pipeline ran on ${r} at the merge commit ${Ts(w)} of !${c}, so there is no apply job to retry; push to ${r} again`);k=await t("GET",`/projects/${n}/pipelines?ref=${encodeURIComponent(r)}&source=push&order_by=id&sort=desc&per_page=${nwe}`)}catch(O){return d(`could not read the pipelines of ${r} (${O.message})`)}let v=[...new Set((Array.isArray(k)?k:[]).filter(O=>Number(O?.id)>Number(b.id)&&O?.sha!==w).map(O=>O.sha))];for(let O of v){if(typeof O!="string"||!St.test(O))continue;let I;try{I=await t("GET",`/projects/${n}/repository/commits/${O}/statuses?name=${encodeURIComponent(sJ)}&ref=${encodeURIComponent(r)}`)}catch(L){return d(`could not read the statuses of ${Ts(O)} (${L.message})`)}let N=Array.isArray(I)?I.find(L=>L?.name===sJ):void 0;if(N){let L=typeof N.target_url=="string"&&/^https?:\/\//.test(N.target_url)?` (${N.target_url})`:"";return l(`a later apply already ran on ${r} at ${Ts(O)}${L}, so the merge commit ${Ts(w)} of !${c} is not applied: comment on the merge request that commit came from, or push again`)}}let $;try{$=await t("GET",`/projects/${n}/pipelines/${b.id}/jobs?per_page=100`)}catch(O){return d(`could not read the jobs of pipeline ${b.id} (${O.message})`)}let _=(Array.isArray($)?$:[]).map(O=>{let I=/^apply-wave-([1-9][0-9]*)(?:-share-([1-9][0-9]*))?$/.exec(String(O?.name));return{j:O,n:Number(I?.[1]),s:I?.[2]?Number(I[2]):0}}).filter(O=>Number.isInteger(O.n)).sort((O,I)=>O.n-I.n||O.s-I.s);if(_.length===0)return l(`pipeline ${b.id} at ${Ts(w)} has no apply jobs, so there is nothing to retry`);let E=_.find(O=>O.j.status!=="success");if(!E)return l(`every wave of !${c}'s merge commit ${Ts(w)} already applied`);let R=String(E.j.name);if(["created","pending","running","waiting_for_resource","preparing","scheduled"].includes(E.j.status))return l(`${R} at ${Ts(w)} is ${E.j.status} already, so it is not retried`);if(E.j.status!=="failed"&&E.j.status!=="canceled")return l(`${R} at ${Ts(w)} was ${String(E.j.status)}, and GitLab retries only a failed or canceled job; push to ${r} again`);let x;try{x=await t("POST",`/projects/${n}/jobs/${E.j.id}/retry`)}catch(O){return d(`could not retry ${R} of pipeline ${b.id} (${O.message})`)}let A=typeof x?.web_url=="string"&&/^https?:\/\//.test(x.web_url)?` (${x.web_url})`:"";return l(`retried ${R} of !${c}'s merge commit ${Ts(w)}${A} for ${g}; its gate decides again, and the waves after it follow, each behind its own gate`,!0)}import{readFileSync as iwe}from"node:fs";var cJ=["status","note","reply","stale"],uJ="<!-- terragucci:stale -->",awe=new Set(["pending","success","failure"]);async function dJ(e,t,n){let r=n.env??process.env;if(!cJ.includes(e))throw new S(`post is ${cJ.join(", ")}`);if(!r.TG_TOKEN)return`terragucci: no forge token, so no ${e}`;try{let o=jt(n.forge,r,n.fetch);e==="status"?await cwe(o,t,n,r):e==="note"?await o.upsertComment(lJ(r),yi,iwe(OP(t[0],"note <file>"),"utf-8")):e==="reply"?await o.addComment(lJ(r),`terragucci: ${OP(t[0],"reply <text>")}`):await dwe(o,OP(t[0],"stale <roots> <branch>").split(","),t[1]??"",n,r);return}catch(o){return`terragucci: ${e} failed: ${o.message}`}}function OP(e,t){if(e===void 0)throw new S(`post ${t}`);return e}function lJ(e){let t=Number((e.TG_PR??"").trim());if(!Number.isInteger(t)||t<1)throw new S("TG_PR names no pull request");return t}async function cwe(e,t,n,r){let[o,s,i=""]=t;if(!o||!s||!awe.has(s))throw new S("post status <context> pending|success|failure <description>");let a=(r.TG_SHA??"").trim();if(!a)throw new S("TG_SHA names no commit");let c=n.forge==="gitlab"&&a===r.CI_COMMIT_SHA&&/^[0-9]+$/.test(r.CI_PIPELINE_ID??"")?Number(r.CI_PIPELINE_ID):void 0;await e.setStatus(a,{context:o,state:s==="pending"?"running":s,description:i,...e.runUrl?{url:e.runUrl}:{},...c!==void 0?{pipelineId:c}:{}})}async function uwe(e,t,n){let r=t.fetch??globalThis.fetch,o=encodeURIComponent(e);if(t.forge==="gitlab"){let a=await $o(n,n.TG_TOKEN,r)("GET",`/projects/${encodeURIComponent(n.CI_PROJECT_ID??"")}/merge_requests?state=opened&target_branch=${o}&per_page=100`);return(Array.isArray(a)?a:[]).map(c=>c?.iid).filter(c=>Number.isInteger(c))}let s=pb(n,{kind:t.forge,tokenEnv:"TG_TOKEN"}),i=await kt(r,{forge:t.forge,origin:`https://${s.host}`,path:s.repo,token:s.token,api:s.apiBase},"GET",`/repos/${s.repo}/pulls?state=open&base=${o}&per_page=100`);return(Array.isArray(i)?i:[]).map(a=>a?.number).filter(a=>Number.isInteger(a))}function lwe(e,t,n){if(e.includes(uJ))return;let r=(/roots=(\S*) -->/.exec(e)?.[1]??"").split(",").filter(s=>t.includes(s));if(!r.length)return;let o=e.split(`
887
+ `);return o.splice(1,0,`> This plan is stale: ${n} moved under ${r.join(", ")}. Push to this pull request to plan again. ${uJ}`),o.join(`
888
+ `)}async function dwe(e,t,n,r,o){for(let s of await uwe(n,r,o)){let i=await e.findComment(s,yi),a=i?lwe(i.body,t,n):void 0;a&&await e.upsertComment(s,yi,a)}}import{spawnSync as fk}from"node:child_process";import{delimiter as gve,join as mve}from"node:path";import{spawn as mwe,spawnSync as hwe}from"node:child_process";import{delimiter as ywe,join as wwe}from"node:path";var Ri=e=>new Date(e).getTime();function pwe(e,t){let n=new Map;for(let r of e){if(t!==void 0&&r.gate!==t)continue;let o=`${r.gate}\0${r.environment??""}`,s=n.get(o);(!s||Ri(r.timestamp)>=Ri(s.timestamp))&&n.set(o,r)}return[...n.values()]}var fwe=(e,t)=>{if(t.approval)return;let n=e.filter(r=>t.environment===void 0||r.environment===t.environment);return qw(n,t.gate,t.timestamp,t.planDigest).resolution};function pJ(e,t,n,r){if(n===void 0)return;let o={gate:t,planDigest:n,timestamp:r.timestamp,...r.review?{review:{pr:r.review.pr,head:r.review.head}}:{}};if(wg(e,o))return e.find(s=>wg([s],o))}function mJ(e){let t=e.applied??[],n=e.finished??[],r=e.answering??fwe,o=[],s=pwe(e.pending,e.gate).filter(i=>!hg(i,e.now));for(let i of s){let a={source:"pending",op:i.op||e.op,gate:i.gate,...i.environment!==void 0?{environment:i.environment}:{},...i.planDigest!==void 0?{planDigest:i.planDigest}:{},...Pg(i.resume)?{run:i.resume}:{},pending:i};if(i.answer){let l=iH(i,e.questions?.get(i.answer.id));if(!l.resumable){o.push({...a,resumable:!1,reason:"not-approved",message:l.message});continue}let p=e.resolutions.filter(f=>f.gate===i.gate&&f.answer?.id===i.answer.id&&Ie(f.planDigest,i.planDigest)).map(f=>({r:f,by:pJ(t,i.gate,i.planDigest,f)})).find(f=>f.by);if(p?.by){o.push({...a,resumable:!1,reason:"spent",approvedBy:l.answeredBy,approvedAt:p.r.timestamp,message:gJ(p.by),spent:fJ(p.by)});continue}o.push({...a,resumable:!0,reason:"answered",approvedBy:l.answeredBy,...l.answeredOn?{approvedAt:l.answeredOn}:{},message:`question ${i.answer.id} has an answer the gate passes on`});continue}let c=r(e.resolutions,i,e.pending);if(!c){o.push({...a,resumable:!1,reason:"not-approved",message:i.approval&&!e.answering?"its gate has an approval block, which needs chant's tally (pass answering)":"no approval answers it yet"});continue}let u=pJ(t,i.gate,i.planDigest,c);if(u){o.push({...a,resumable:!1,reason:"spent",approvedBy:c.resolvedBy,approvedAt:c.timestamp,message:gJ(u),spent:fJ(u)});continue}o.push({...a,resumable:!0,reason:"approved",approvedBy:c.resolvedBy,approvedAt:c.timestamp,message:`approved by ${c.resolvedBy} at ${c.timestamp}`})}for(let i of gwe(t,n,e.pending,e.gate))o.push({source:"stopped",op:i.op||e.op,gate:i.gate,planDigest:i.planDigest,resumable:!0,reason:"stopped",approvedBy:i.approvedBy,approvedAt:i.approvedAt,run:i.resume,stopped:{appliedAt:i.timestamp,approvedAt:i.approvedAt,kind:i.kind,...i.wave?{wave:i.wave}:{}},message:`the apply of ${i.planDigest} under the approval by ${i.approvedBy||"?"} began at ${i.timestamp} and never recorded how it ended; it resumes once run ${i.resume.run} is gone`});return o}var fJ=e=>({appliedAt:e.timestamp,approvedAt:e.approvedAt,...e.wave?{wave:e.wave}:{}}),gJ=e=>`the approval that answers it is spent: ${e.wave?`wave ${e.wave.number} (${e.wave.name})`:"a wave"} applied under it at ${e.timestamp}`;function gwe(e,t,n,r){let o=new Map;for(let i of e){if(r!==void 0&&i.gate!==r)continue;let a=o.get(i.gate);(!a||Ri(i.timestamp)>=Ri(a.timestamp))&&o.set(i.gate,i)}let s=[];for(let[i,a]of o){let c=e.filter(l=>l.gate===i&&Ie(l.planDigest,a.planDigest)&&Ri(l.approvedAt)===Ri(a.approvedAt)&&Pg(l.resume)),u=c.reduce((l,d)=>!l||Ri(d.timestamp)>=Ri(l.timestamp)?d:l,void 0);u&&(c.some(l=>DM(t,l))||n.some(l=>l.gate===i&&Ri(l.timestamp)>Ri(u.timestamp))||s.push(u))}return s}function bwe(e,t,n,r){let o=(i,a)=>mJ({op:i,pending:a.pending,resolutions:a.resolutions,applied:a.applied??[],finished:a.finished??[],now:t,...r?{questions:r}:{}}).filter(c=>c.resumable),s=o(Yt,e).filter(i=>/^wave-\d+$/.test(i.gate)).map(i=>({wave:Number(i.gate.slice(5)),...hJ(i)}));return n&&s.push(...o(mr,n).filter(i=>i.source==="pending").map(i=>({wave:1,migration:i.gate,...hJ(i)}))),s.sort((i,a)=>i.wave-a.wave||+!!a.migration-+!!i.migration)}function hJ(e){let t=e.pending,n=t?yl(t):e.run?.run;return{digest:e.planDigest??"",by:e.approvedBy??"",...n?{runId:n}:{},...t?.commit?{commit:t.commit}:{},...e.source==="stopped"&&e.stopped?{stopped:{holder:e.run?.run??"",at:e.stopped.appliedAt}}:{}}}async function yJ(e){if(e.forge==="gitlab")return{kind:"none",why:"on GitLab chant run resume starts each job that waits, or whose apply stopped"};let t=e.liveness??ib(e.env),n=[];for(let o of bwe(e.ledger,e.now??new Date().toISOString(),e.migrations,e.questions))(!o.stopped||o.stopped.holder&&await t({run:o.stopped.holder,at:o.stopped.at})==="dead")&&n.push(o);if(n.length===0)return{kind:"none",why:"no wave waits with an approval that stands, and no approved apply stopped before it finished"};let r;try{r=(await xa(to(e.env,e.fetch),{...e.env,TG_PR:""},e.sha))?.number}catch{}return{kind:"apply",sha:e.sha,...r!==void 0?{pr:r}:{},waves:n}}function mh(e,t){let n=/^(?:([a-z+]+):\/\/)?(?:[^@/]+@)?([^/:]+)(:\d+)?[/:](.+?)(?:\.git)?\/?$/.exec(e.trim());if(!n)return;let r=n[1],o=n[2],s=r==="http"||r==="https"?`${r}://${o}${n[3]??""}`:`https://${o}`,i=o==="github.com"?"github":o==="gitlab.com"?"gitlab":t==="github"||t==="gitlab"||t==="forgejo"?t:void 0;return i?{forge:i,host:o,web:s,path:n[4]}:void 0}function vwe(e,t){if(e==="gitlab")return t.GITLAB_TOKEN||void 0;if(e==="forgejo")return t.FORGEJO_TOKEN||void 0;if(t.GH_TOKEN||t.GITHUB_TOKEN)return t.GH_TOKEN||t.GITHUB_TOKEN;let n=hwe("gh",["auth","token"],{encoding:"utf-8"});return n.status===0&&n.stdout.trim()?n.stdout.trim():void 0}var $we={github:"GH_TOKEN (or gh auth login)",forgejo:"FORGEJO_TOKEN",gitlab:"GITLAB_TOKEN"};async function PP(e){let t=e.env??process.env,n=e.fetch??fetch,{forge:r,host:o,web:s,path:i}=e.origin,a="the pipeline's resume job applies it on its next run when apply.resume is set; otherwise run its job again, or comment /terragucci apply",c=vwe(r,t);if(!c)return`not resumed from here: no token in ${$we[r]}; ${a}`;let u=async(l,d,p,f)=>{let g=await n(d,{method:l,headers:{"content-type":"application/json",...p},...f===void 0?{}:{body:JSON.stringify(f)}});if(!g.ok)throw new Error(`${l} ${d.replace(/^https?:\/\/[^/]+/,"")} answered ${g.status}`);return g.status===204?null:g.json().catch(()=>null)};try{if(r==="github"||r==="gitlab"){if(!e.wave.resume)return`not resumed from here: the pending fact names no CI job; ${a}`;let f=await e1(e.wave.resume,{token:c,...e.fetch?{fetch:e.fetch}:{}});return f.status!=="resumed"?`not resumed from here: ${"reason"in f?f.reason:f.how}; ${a}`:`resumed: ${f.how}, so wave ${e.wave.wave} runs again and its gate decides`}if(!e.wave.commit)return`not resumed from here: the pending fact names no commit; ${a}`;let l=`${s}/api/v1/repos/${i}`,d={authorization:`token ${c}`},p=await u("GET",`${l}/commits/${e.wave.commit}/pull`,d).catch(()=>null);return Number.isInteger(p?.number)?(await Ag({apiBase:`${s}/api/v1`,repo:i,token:c,host:o,fetch:n}).addComment(p.number,"/terragucci apply"),`resumed: commented /terragucci apply on pull request ${p.number}, so its waves run again and each gate decides`):`not resumed from here: no merged pull request made ${e.wave.commit.slice(0,8)}; ${a}`}catch(l){return`not resumed from here (${l.message}); ${a}`}}var kwe=["tf-apply",mr];async function wJ(e,t,n={}){let r=n.log??(i=>console.log(i)),o=t.CHANT_FORGE_TOKEN||t.TG_TOKEN||t.GITLAB_TOKEN,s=0;for(let i of kwe){r(`terragucci resume: chant run resume --op ${i}`);let a=await _we(e,["run","resume","--op",i],{...t,...o?{CHANT_FORGE_TOKEN:o}:{}},n.chant,"inherit");a.error?(r(`terragucci resume: chant run resume did not start (${a.error.message}); install terragucci again, which installs chant with it`),s=1):a.status!==0&&(s=a.status??1)}return s}function _we(e,t,n,r,o){return new Promise(s=>{let i=mwe(vi(e,r?{chant:r}:{}),t,{cwd:e,stdio:["ignore",o,o],env:{...n,PATH:[wwe(e,"node_modules",".bin"),n.PATH??""].join(ywe)}}),a="",c="";i.stdout?.on("data",u=>a+=u),i.stderr?.on("data",u=>c+=u),i.on("error",u=>s({status:null,stdout:a,stderr:c,error:u})),i.on("close",u=>s({status:u,stdout:a,stderr:c}))})}import{spawnSync as kJ}from"node:child_process";import{createHash as Swe}from"node:crypto";import{existsSync as CP,mkdtempSync as _J,realpathSync as bJ,rmSync as Rwe,writeFileSync as xwe}from"node:fs";import{tmpdir as SJ}from"node:os";import{basename as Ewe,dirname as jP,isAbsolute as Twe,join as El,relative as Awe,resolve as Owe}from"node:path";var Hc="tf-state-export",ek=`_gates/${Hc}.jsonl`,IP=`_gates/${Hc}/done.jsonl`,Pwe=48,vJ=(e,t,n=!1)=>`terragucci approve export ${e} --plan ${t}${n?" --sign":""}`,RJ=e=>`sha256:${Swe("sha256").update(e).digest("hex")}`,Pa=e=>Date.parse(e)||0;function jwe(e){return RJ(JSON.stringify({op:Hc,root:e.root,location:e.location,version_id:e.version_id,by:e.by,at:e.at}))}function $J(e,t,n){let r=kJ("git",t,{cwd:e,encoding:"utf-8",env:n});return r.status===0?r.stdout.trim():""}function Iwe(e){let t=new Map;for(let n of e.split(`
889
+ `).map(r=>r.trim()).filter(Boolean))try{let r=JSON.parse(n);r.version===1&&r.kind==="state-export"&&typeof r.planDigest=="string"&&t.set(r.planDigest,r)}catch{}return t}function Cwe(e,t,n,r){let o=`${n.replace(/[^\w.-]+/g,"_")}.${r.replace(/[^\w.-]+/g,"_")}.tfstate`;if(!t)return El(_J(El(SJ(),"terragucci-export-")),o);let s=Owe(t);if(!CP(jP(s)))throw new S(`--out ${t}: ${jP(s)} is not a directory`);if(CP(s))throw new S(`--out ${t} is there already; name a new file`);let i=Awe(bJ(e),El(bJ(jP(s)),Ewe(s)));if(!i.startsWith("..")&&!Twe(i))throw new S(`--out ${t} is inside the repo, where a state could be committed; write it outside the repo`);return s}async function xJ(e,t){let n=t.log??(F=>console.log(F)),r=t.env??process.env,o=t.exec??li,s=t.now??new Date().toISOString(),i=t.root.replace(/\/+$/,"");if(!i)throw new S("state export takes the root whose state to export: terragucci state export <root> [--version <id>]");let a=t.config??fe(e),c=a?ue(await Re(a)):void 0,u=et(e,c??ue({}));await u.prepareRoots({roots:[i],...t.terragrunt?.path?{terragrunt:t.terragrunt.path}:{},...t.terragrunt?.exec?{exec:t.terragrunt.exec}:{}});let l=OA(e,i);if(l)throw new S(`state export: ${l}`);let d=CP(El(e,i,"terragrunt.hcl")),p=t.actor||$J(e,["config","user.name"],r)||$J(e,["config","user.email"],r);if(!p)throw new S("state export names who asks: set git's user.name, or pass --actor <name>");let f=t.binary??c?.binary??u.binary([i]).value,g=_J(El(SJ(),"terragucci-export-data-")),m;try{if(d){let F=await Dm(e,i,f,r,g,t.terragrunt);m=Hr(F.dir,F.env)}else{let F={...r,TF_DATA_DIR:g},M=await o(f,["init","-input=false","-no-color"],El(e,i),u.rootInit(i,F)?.init??F);if(M.code!==0)throw new S(`init in ${i} failed: ${M.out.trim().split(`
890
+ `).slice(-4).join(" ").slice(0,400)}`);m=Hr(El(e,i),u.rootEnv(i,F))}}finally{Rwe(g,{recursive:!0,force:!0})}if("unsupported"in m)throw new S(`state export: ${i}: ${m.unsupported.replace(Wf,"exports state from s3, gcs and azurerm backends and GitLab-managed state")}`);let h,y=t.version,w;if("gitlab"in m){let F=m.gitlab,M=t.fetch??globalThis.fetch;h=m.location;let te=async ie=>{try{return await ie()}catch(Q){throw new S(`state export: ${Q.message}`)}};if(y)if(/^\d+$/.test(y)){if(!await te(()=>eN(F,y,M)))throw new S(`state export: ${h} has no version ${y}; GitLab keeps no version with that serial`)}else throw new S(`state export: ${i}'s state is GitLab-managed, whose versions are serials; --version ${y} is not one`);else{let ie=await te(()=>Yy(F,M));if(!ie)throw new S(`state export: ${h} holds no state`);y=String(ie.serial)}w=ie=>te(()=>tN(F,ie,M))}else{if(!ar(m))throw new S(`state export: ${i}'s state is a local file, ${m.path}, which keeps no versions; export reads a version of an s3, gcs or azurerm state by its id`);let F=await Ws(m,t.fetch);if(h=F.location,y){if(!await F.hasVersion(y))throw new S(`state export: ${h} has no version ${y}; a lifecycle rule may have expired it`)}else{let M=await F.version(!0);if(!M.exists)throw new S(`state export: ${h} holds no state`);if(!M.versionId)throw new S(`state export: ${h} keeps no versions, so there is no version id to export: ${M.off??"the store gave none"}`);y=M.versionId}w=M=>F.readVersion(M)}let b=ft(e,ek),k=kJ("git",["show",`refs/remotes/origin/chant/lifecycle:${IP}`],{cwd:e,encoding:"utf-8"}),v=Iwe(k.status===0?k.stdout:""),$=b.pending.filter(F=>F.op===Hc&&F.gate===i&&F.request&&F.request.by===p&&F.request.version_id===y&&F.request.location===h).filter(F=>Pa(F.expiresAt)>Pa(s)&&F.planDigest!==void 0&&!v.has(F.planDigest)).sort((F,M)=>Pa(M.timestamp)-Pa(F.timestamp))[0],_=await Yr(e,{at:"HEAD",...a?{config:a}:{}}),E=_.mode==="sealed";if(!$){let F={root:i,location:h,version_id:y,by:p,at:s},M=jwe(F),te={version:1,kind:"pending",op:Hc,gate:i,timestamp:s,expiresAt:new Date(Pa(s)+Pwe*3600*1e3).toISOString(),planDigest:M,description:`state export: ${i} version ${y}, for ${p}`,request:F,neverOverMcp:!0};ha(e,te,{},ek);let ie=vJ(i,M,E);return n(`state export: ${p} asks for ${i}'s state, ${h} version ${y}; request ${M}`),n("someone other than you approves it with:"),n(` ${ie}`),n("then run this command again to download it."),{code:3,digest:M,command:ie}}let R=$.planDigest,x=vJ(i,R,E),I=b.resolutions.filter(F=>F.gate===i&&Ie(F.planDigest,R)&&Pa(F.timestamp)>=Pa($.timestamp)).filter(F=>{if(F.resolvedBy===p)return n(`state export: an approval by ${p} does not count: the person who asks for an export does not approve it`),!1;let M=E?ws(_.signers,F):null;return M&&n(`state export: an approval does not count: ${M}`),M===null}).sort((F,M)=>Pa(M.timestamp)-Pa(F.timestamp))[0];if(!I)return n(`state export: request ${R} for ${i} version ${y} waits for an approval by someone other than ${p}:`),n(` ${x}`),{code:3,digest:R,command:x};let N=Cwe(e,t.out,i,y),L=await w(y);if(L===void 0)throw new S(`state export: ${h} has no version ${y}; it was deleted since the request`);let U={version:1,kind:"state-export",op:Hc,gate:i,planDigest:R,root:i,location:h,version_id:y,exportedBy:p,approvedBy:I.resolvedBy,approvedAt:I.timestamp,timestamp:new Date().toISOString(),content_digest:RJ(L)};return hr(e,IP,[JSON.stringify(U)],{},`State exported: ${i} version ${y} for ${p}`),xwe(N,L,{mode:384,flag:"wx"}),n(`state export: wrote ${N}: ${i}'s state, ${h} version ${y}, approved by ${I.resolvedBy}`),n(`state export: recorded on chant/lifecycle in ${IP}; delete the file when you are done with it`),{code:0,digest:R,file:N}}import{spawnSync as R2}from"node:child_process";import{existsSync as ove,statSync as sve}from"node:fs";import{resolve as v2}from"node:path";import{execFileSync as Ybe}from"node:child_process";import{mkdirSync as Xbe,mkdtempSync as Qbe,rmSync as eve,writeFileSync as tve}from"node:fs";import{tmpdir as nve}from"node:os";import{dirname as rve,join as lk}from"node:path";import{execFileSync as mbe}from"node:child_process";import{mkdtempSync as hbe,rmSync as ybe,unlinkSync as wbe,writeFileSync as bbe}from"node:fs";import{tmpdir as vbe}from"node:os";import{join as WJ,relative as $be}from"node:path";import{execFileSync as FJ}from"node:child_process";import{existsSync as So,mkdirSync as sbe,readFileSync as Ro,unlinkSync as ibe,writeFileSync as abe}from"node:fs";import{basename as cbe,dirname as ube,join as _o,relative as qc}from"node:path";import{gunzipSync as Nwe}from"node:zlib";var EJ=JSON.parse(Nwe(Buffer.from("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","base64")).toString("utf8"));var tk={dir:"TGPH0DIR",prometheus:"TGPH0PROMETHEUS",tempo:"TGPH0TEMPO",folder:"TGPH0FOLDER",path:"TGPH0PATH",drift_age:"7919001s",wave_wait:"7919002s",schedule:"7919003s"},TJ="https://tgph0.invalid/TGPH0REPORTS",NP=["drift_age","wave_wait","schedule"],DP=["prometheus","tempo","folder","path"];function AJ(e){if(e==="0")return 0;let t=/^(?:(\d+)y)?(?:(\d+)w)?(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?(?:(\d+)ms)?$/.exec(e);if(!e||!t)return 0;let n=[365*864e5,7*864e5,864e5,36e5,6e4,1e3,1],r=0;for(let o=0;o<n.length;o++)t[o+1]!==void 0&&(r+=Number(t[o+1])*n[o]);return Math.round(r/1e3)}var Dwe=["y","Y","yes","Yes","YES","on","On","ON","n","N","no","No","NO","off","Off","OFF"],Mwe=/^[-+]?[0-9_]+(?::[0-9_]+)+(?:\.[0-9_]*)?$/,Lwe=/^(?:[-+]?(?:[0-9]+)(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?|\.[0-9]+(?:[eE][-+]?[0-9]+)?|[-+]?\.(?:inf|Inf|INF)|\.(?:nan|NaN|NAN))$/,Gwe=/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/,Fwe=/^[0-9]{4}-[0-9][0-9]?-[0-9][0-9]?(?:[Tt]|[ \t]+)[0-9][0-9]?:[0-9]{2}:[0-9]{2}(?:\.[0-9]*)?(?:[ \t]*(Z|[-+][0-9][0-9]?(?::[0-9]{2})?))?$/,zwe=/^[-+]?(?:0b[01]+|0x[0-9a-fA-F]+|0o[0-7]+|[0-9]+)$/,Uwe={0:"\\0",7:"\\a",8:"\\b",9:"\\t",10:"\\n",11:"\\v",12:"\\f",13:"\\r",27:"\\e",34:'\\"',92:"\\\\",133:"\\N",160:"\\_",8232:"\\L",8233:"\\P"},nk=e=>e>=32&&e<=126||e>=161&&e<=55295&&e!==8232&&e!==8233||e>=57344&&e<=65533&&e!==65279||e>=65536&&e<=1114111,rk=e=>e===32||e===9,MP=e=>nk(e)&&e!==65279&&e!==13&&e!==10,Bwe=(e,t)=>{let n=MP(e)&&!rk(e);return MP(e)&&e!==35&&!(t===58&&!n)||t!==void 0&&MP(t)&&!rk(t)&&e===35||t===58&&n},Wwe=e=>nk(e)&&e!==65279&&!rk(e)&&!"-?:,[]{}#&*!|=>'\"%@`".includes(String.fromCodePoint(e)),Hwe=e=>{if(!zwe.test(e))return!1;let t=e.replace(/^[-+]/,""),n={b:2,o:8,x:16}[t[1]]??10;return Number.isFinite(Number.parseInt(n===10?t:t.slice(2),n))},qwe=e=>["~","null","Null","NULL","true","True","TRUE","false","False","FALSE","<<"].includes(e)||Hwe(e)||Lwe.test(e)&&(Number.isFinite(Number.parseFloat(e))||/\.(inf|Inf|INF|nan|NaN|NAN)$/.test(e))||Gwe.test(e)||Fwe.test(e);function Jwe(e){if(e==="")return'""';if(Dwe.includes(e)||Mwe.test(e))return`"${e}"`;let t=[...e].map(o=>o.codePointAt(0)),n=Wwe(t[0])&&!rk(t[t.length-1])&&t[t.length-1]!==58,r=!1;return t.forEach((o,s)=>{(o===10||!nk(o))&&(r=!0),n=n&&Bwe(o,s?t[s-1]:void 0)}),!r&&n&&!qwe(e)?e:`"${t.map(o=>Uwe[o]??(nk(o)?String.fromCodePoint(o):`\\${o<=255?"x":o<=65535?"u":"U"}${o.toString(16).toUpperCase().padStart(o<=255?2:o<=65535?4:8,"0")}`)).join("")}"`}var Kwe=e=>e.replace(/[.*+?^${}()|[\]\\/]/g,"\\$&");function OJ(e,t,n={}){for(let m of NP)if(!/^[0-9a-z]+$/.test(t[m]))throw new Error(`dashboards.${m} is ${JSON.stringify(t[m])}; use a duration such as 4h or 1d`);for(let m of DP)if(/[\r\n]/.test(t[m]))throw new Error(`dashboards.${m} must be one line`);let r=new Map;for(let m of NP)r.set(tk[m],t[m]);for(let m of NP)r.set(String(AJ(tk[m])),String(AJ(t[m])));for(let m of DP)r.set(tk[m],t[m]);r.set(TJ,n.reports);let o=new Set([...DP.map(m=>tk[m]),TJ]),s=[...r.keys()].map(Kwe).join("|"),i=new RegExp(s,"g"),a=new RegExp(s),c=m=>{let h=r.get(m);if(h===void 0)throw new Error(`the dashboards template has ${m} and nothing to fill it with`);return h},u=m=>m.replace(i,c),l=m=>typeof m=="string"?u(m):Array.isArray(m)?m.map(l):m&&typeof m=="object"?Object.fromEntries(Object.entries(m).map(([h,y])=>[u(h),l(y)])):m,d=m=>{let h=/^(.*(?:: |- ))(\S+)$/.exec(m);return h&&o.has(h[2])&&!a.test(h[1])?`${h[1]}${Jwe(c(h[2]))}`:m.replace(i,y=>{if(o.has(y))throw new Error(`the dashboards template has ${y} inside a YAML scalar: ${m}`);return c(y)})},p=t.dir.replace(/\/+$/,""),f=n.reports!==void 0,g=[];for(let m of e.files){if(m.links!==void 0&&m.links!==f)continue;let h="json"in m?JSON.stringify(l(m.json),null,2)+m.suffix:m.yaml.map(d).join(`
891
+ `);g.push({path:`${p}/${m.path}`,content:h})}return g.sort((m,h)=>m.path<h.path?-1:1)}function PJ(e,t={}){return OJ(EJ,e,t)}var Vwe={dir:"observability/terragucci",prometheus:"prometheus",tempo:"tempo",folder:"terragucci",path:"/var/lib/grafana/dashboards/terragucci",drift_age:"1d",wave_wait:"4h",schedule:"2d"};function jJ(e){if(e)return{...Vwe,...e===!0?{}:e}}var Zwe="# Generated by terragucci from terragucci.yml (dashboards). Edit terragucci.yml, then run terragucci init.",Ywe="terragucci";function IJ(e,t){if(!e.endsWith(".json"))return t.startsWith(Zwe);try{let n=JSON.parse(t).tags;return Array.isArray(n)&&n.includes(Ywe)}catch{return!1}}var Op=".gitlab-ci.yml",ok=`- local: ${Xr.gitlab}`,Xwe=["# terragucci's jobs are in .gitlab/terragucci.yml, which terragucci init writes","# and this file includes. Add your own jobs here; init keeps them.","include:",` ${ok}`,""].join(`
892
892
  `);function LP(e){let t=n=>{let r=typeof n=="string"?n:n&&typeof n=="object"?n.local:void 0;return typeof r=="string"&&r.replace(/^\.?\//,"")===Xr.gitlab};return Array.isArray(e)?e.some(t):t(e)}function GP(e){try{return uo(e)}catch(t){throw new S(`${Op} is not YAML init can read, so it cannot add the include of ${Xr.gitlab} (${t.message})`)}}function Qwe(e,t){let n=e.split(`
893
893
  `);if(!("include"in t)){let i=0;for(;i<n.length&&(n[i].trim()===""||n[i].trim().startsWith("#")||n[i].trim()==="---");)i++;return[...n.slice(0,i),"include:",` ${ok}`,"",...n.slice(i)].join(`
894
894
  `)}let r=n.findIndex(i=>/^include:\s*(#.*)?$/.test(i));if(r<0||!Array.isArray(t.include))return;let o=n.slice(r+1).find(i=>i.trim()!==""&&!i.trim().startsWith("#")),s=o&&/^(\s*)- /.exec(o)?.[1];if(s!==void 0)return[...n.slice(0,r+1),`${s}${ok}`,...n.slice(r+1)].join(`
895
- `)}function C2(e,t){if(e===void 0||e.startsWith(pi)||e.trim()==="")return Xwe;let n=GP(e),r=GP(t),s=Object.keys(r).filter(u=>u!=="stages").filter(u=>u in n);if(s.length)throw new S(`${Op} has jobs named ${s.join(", ")}, as ${Xr.gitlab} does, and GitLab would merge the two; rename yours`);if(Array.isArray(n.stages)){let u=[...lh.before,...lh.after],l=r.stages.filter(p=>!u.includes(p)),d=l.map(p=>n.stages.indexOf(p));if(d.some((p,f)=>p<0||f>0&&p<d[f-1]))throw new S(`${Op} lists its own stages, which GitLab reads in place of the included file's; add ${l.join(", ")} to them, in that order`)}if(LP(n.include))return e;let i=Qwe(e,n),a=i===void 0?void 0:GP(i),c=(u,l)=>{let{include:d,...p}=u,{include:f,...g}=l,m=Array.isArray(f)?f.filter(h=>!LP(h)):[];return JSON.stringify(p)===JSON.stringify(g)&&JSON.stringify(d??[])===JSON.stringify(m)};if(i===void 0||!a||!LP(a.include)||!c(n,a))throw new S(`${Op} has an include init cannot add a line to; write its include as a list and add \`${ok}\` to it`);return i}var zP="terragucci.components.json",ebe="terragucci.components/v1",FP="terragucci",tbe="applied by terragucci's pipeline; a failed wave leaves what applied, and a re-run finishes it";function nbe(e,t){let n=new Set,r=[];for(;n.size<e.length;){let o=e.filter(s=>!n.has(s)&&[...t.get(s)??[]].every(i=>n.has(i))).sort();if(o.length===0)throw new Error(`these roots depend on each other in a cycle: ${e.filter(s=>!n.has(s)).join(", ")}`);o.forEach(s=>n.add(s)),r.push(o)}return r}var rbe=(e,t)=>JSON.stringify(e.map(n=>[...n].sort()))===JSON.stringify(t.map(n=>[...n].sort()));function obe(e,t){let n=e.flat(),r=new Set(n),o=new Map(n.map(s=>[s,new Set([...t.get(s)??[]].filter(i=>r.has(i)&&i!==s))]));return rbe(nbe(n,o),e)||e.forEach((s,i)=>{if(i>0)for(let a of s)for(let c of e[i-1])o.get(a).add(c)}),o}function N2(e){let t=e.waves.map(s=>[...s].sort()),n=obe(t,e.order),r=e.dirOf??(s=>s),o=t.flat();return{schema:ebe,generated_by:`${FP} ${Pr}`,roots:Object.fromEntries(o.map(s=>[s,{dir:r(s)}])),components:o.map(s=>({name:s,dependsOn:[...n.get(s)].sort(),deploy:[{phase:"Apply",steps:[{kind:"terraform-apply",root:s,noRollback:tbe}]}]})),pipeline:{path:e.pipeline.path,forge:e.pipeline.forge,jobs:e.pipeline.applyJobs}}}var D2=e=>`${JSON.stringify(e,null,2)}
896
- `;function M2(e){try{let t=JSON.parse(e);return typeof t.generated_by=="string"&&(t.generated_by===FP||t.generated_by.startsWith(`${FP} `))}catch{return!1}}function As(e,t){return So(e)?{path:e,status:Ro(e,"utf-8")===t?"unchanged":"updated",content:t}:{path:e,status:"created",content:t}}function lbe(e,t){if(typeof t!="string")return t;let n=_o(e,t);if(!So(n))throw new S(`own_jobs names ${t}, which the repo does not have`);let r;try{r=Yl(Ro(n,"utf-8"))}catch(s){throw new S(`own_jobs: ${t} is not YAML (${s.message})`)}let o=typeof r=="string"?[`own_jobs (${t}) must be a map of job name to job`]:cR(r,`own_jobs (${t})`);if(o.length>0)throw new S(o.join("; "));return r}async function ik(e,t={}){let n=t.settings?void 0:fe(e),r=t.settings??ue(n?await Re(n):{}),o=[],s=et(e,r);oL(s);let i=s.kind==="terragrunt",a=Ks(e,[]),c=r.binary?{value:r.binary,reason:"terragucci.yml"}:t.binary?{value:t.binary,reason:"--binary"}:a,u=await s.discover({binary:c.value,...t.terragrunt?{terragrunt:t.terragrunt}:{},...t.atmos?{atmos:t.atmos}:{},...t.terramate?{terramate:t.terramate}:{}});o.push(...u.notes);let l=u.roots,d=u.layers,p,f,g;if(s.kind==="atmos")f={reason:s.reason,version:r.atmos?.version??yw};else if(s.kind==="terramate")g={reason:s.reason,version:rb,skipped:u.skipped??[]};else if(s.terragrunt){let J=r.terragrunt??{};s.terragrunt.stacks.length>0&&o.push(`explicit stacks: ${s.terragrunt.stacks.join(", ")}; terragrunt stack generate wrote their units, and every job generates them again before discovery`);let j=BN(e);p={reason:s.reason,version:J.version?{value:J.version,reason:"terragucci.yml"}:j?{value:j,reason:"terragrunt_version_constraint"}:{value:ai.terragrunt,reason:"the image"},parallelism:UN(e,J),source:u.source??"terragrunt.hcl files",stacks:s.terragrunt.stacks}}let m=l.map(J=>J.root);!i&&!r.binary&&!t.binary&&Object.assign(c,s.binary(m));let h=i||!ea[c.value].releaseInRequiredVersion?void 0:iD(e,m),y=i?void 0:om(c.value),w=y?_o(e,rm[y]):void 0,b=w&&So(w)?iT(Ro(w,"utf-8")):void 0,k=typeof r.version=="string"?{value:r.version,reason:"terragucci.yml"}:b&&y?{value:b,reason:rm[y]}:h?{value:h,reason:"required_version"}:{value:ai[c.value]??"",reason:"the image"},v=[];for(let J of m){let j=cT(e,J,c.value,r.version);j&&j.version!==k.value&&v.push({root:J,...j});let z=i?iv(e,J):void 0;z&&z!==p?.version.value&&v.push({root:J,tool:"terragrunt",version:z,source:"terragrunt_version_constraint"})}let $=Kr(e),_=r.forge?{value:r.forge,reason:t.settings?"the project":"terragucci.yml"}:t.forge?{value:t.forge,reason:"--forge"}:$;if(!_)throw new S("cannot tell which forge this repo is on; pass --forge github, gitlab or forgejo");if(_.value==="gitlab"){let J=Nf(r,"config");if(J.length)throw new S(J.join("; "))}let E=_o(e,"chant.workspace.json"),x=Xg(So(E)?Ro(E,"utf-8"):void 0)>0?"sealed":"ledger",A=Bs(r.approval),O=A?{value:A,explicit:!0}:t.approval?{value:t.approval,explicit:!0}:{value:x,explicit:!1},I,N;p?(I=c.value==="choudoufu"?HT("choudoufu"):hz(),N={version:p.version.value,parallelism:p.parallelism.value,exclude:r.terragrunt?.exclude??[],...s.terragrunt&&s.terragrunt.stacks.length>0?{stacks:!0}:{},...r.generate?{generate:!0}:{},...r.sources?{sources:!0}:{},...r.terragrunt?.credentials?{credentials:r.terragrunt.credentials}:{},installs:hq(c.value,k.value,p.version.value,c.value==="choudoufu"?{choudoufu:ai.choudoufu}:{tofu:ai.tofu,terragrunt:ai.terragrunt})}):I=HT(c.value);let L=N?void 0:sc(r,d.flat());if(L&&_.value==="forgejo"){let J=Object.entries(r.environments??{}).filter(([,j])=>j.forge_environment!==void 0).map(([j])=>j);if(J.length)throw new S(J.map(j=>`environments.${j}.forge_environment: ${vR}`).join("; "))}let U=ai[c.value],F=r.rollouts&&Kn(r,"rollout")!=="off"?r.rollouts:void 0,M=qq({forge:_.value,binary:c.value,version:k.value,image:r.image??qT(I),...r.image?{imageFromConfig:!0}:{},install:!N&&k.value!==U?{binary:c.value,version:k.value}:void 0,...v.length>0?{rootPins:!0}:{},...r.generate&&!N?{generate:!0}:{},...r.sources&&!N?{sources:!0}:{},...N?{terragrunt:N}:{},layers:d,env:r.env,oidc:Zs(r.oidc,r.environments),...L?{applyGroups:L}:{},tokenEnv:r.token_env,...r.decide?.token_env?{decideTokenEnv:r.decide.token_env}:{},...r.telemetry?.headers_secret?{headersSecret:r.telemetry.headers_secret}:{},...r.modules?.publish||r.modules?.registry?{publish:!0,...r.modules.attest?{attest:!0}:{},...r.modules.registry?.bucket?{publishBucket:r.modules.registry.bucket}:{}}:{},...r.reports?{reports:r.reports}:{},...r.drift?{drift:r.drift}:{},...F?{rollouts:F}:{},...s.prepare?{synth:s.prepare}:{},...f?{atmos:{version:f.version}}:{},...g?{terramate:{version:g.version}}:{},...r.notify?{notify:r.notify}:{},...r.cost?{cost:{keySecret:r.cost!==!0&&r.cost.key_secret||If,install:r.cost===!0||!r.cost.command,...r.cost!==!0&&r.cost.approve_above!==void 0?{approveAbove:!0}:{}}}:{},...r.comments?{comments:r.comments}:{},...r.gitlab?.token?{gitlabToken:r.gitlab.token}:{},...!N&&!f&&!g&&r.waves?.canary?.length?{canary:r.waves.canary}:{},...r.waves?.jobs&&r.waves.jobs>1?{waveJobs:r.waves.jobs}:{},gate:r.gate,...QR(r).length?{environmentGates:QR(r)}:{},...Bs(t.settings?.approval)?{approval:Bs(t.settings?.approval)}:{},...O.value==="pr-review"?{prReview:!0}:{},...r.agent?.login?{agentLogin:r.agent.login}:{},...r.respond?{respond:r.respond}:{},...r.policy?{policy:!0}:{},...Nv(r)?{agentComment:Nv(r)}:{},...aA(r)?{agentDrift:aA(r)}:{},...r.atlantis_comments?{atlantisComments:!0}:{},...NO(r)?{review:NO(r)}:{},...r.apply?.resume?{resume:r.apply.resume}:{},...Vv(e).length>0?{migrations:!0}:{},...r.apply?.when==="pull-request"?{applyWhen:"pull-request",...r.apply.merge?{applyMerge:r.apply.merge}:{},...r.apply.merge_token_env?{applyMergeTokenEnv:r.apply.merge_token_env}:{},...r.apply.requires?{applyRequires:r.apply.requires}:{}}:{},...r.locks==="plan"?{locksPlan:!0}:{},...r.apply?.branches&&Object.keys(r.apply.branches).length>0?{applyBranches:r.apply.branches}:{},...r.own_jobs!==void 0?{ownJobs:lbe(e,r.own_jobs)}:{},...r.ephemeral?{ephemeral:{sweep:r.ephemeral.sweep??KC}}:{},...r.runner!==void 0?{runner:r.runner}:{},...r.pass!==void 0?{pass:r.pass}:{}}),te=_o(e,M.path);if(So(te)&&!t.force&&!Ro(te,"utf-8").startsWith(pi))throw new S(`${M.path} exists and terragucci did not write it; move it aside or pass --force`);let ie=_.value==="gitlab"?void 0:gP[_.value],Q=_.value==="gitlab"?void 0:Dc[_.value],ne=_.value==="gitlab"?void 0:mP[_.value],ae=[As(te,M.content)];for(let J of M.extra??[]){let j=_o(e,J.path);if((J.path===ie||J.path===Q||J.path===ne)&&So(j)&&!t.force&&!Ro(j,"utf-8").startsWith(pi))throw new S(`${J.path} exists and terragucci did not write it; move it aside or pass --force`);ae.push(As(j,J.content))}let he=_o(e,zP);if(So(he)&&!t.force&&!M2(Ro(he,"utf-8")))throw new S(`${zP} exists and terragucci did not write it; move it aside or pass --force`);for(let J of[ie,Q,ne]){if(!J||(M.extra??[]).some(z=>z.path===J)||t.settings)continue;let j=_o(e,J);So(j)&&Ro(j,"utf-8").startsWith(pi)&&ae.push({path:j,status:"removed",content:""})}if(_.value==="gitlab"){let J=_o(e,Op);ae.push(As(J,C2(So(J)?Ro(J,"utf-8"):void 0,M.content)))}let $e=j2(r.dashboards);if($e)for(let J of P2($e,{...Zu(r.reports)?{reports:Zu(r.reports)}:{}})){let j=_o(e,J.path);if(So(j)&&!t.force&&!I2(J.path,Ro(j,"utf-8")))throw new S(`${J.path} exists and terragucci did not write it; move it aside, set dashboards.dir, or pass --force`);ae.push(As(j,J.content))}let ye=pbe(e,i||f||g?d.length:an(d,r.waves?.canary).length,O.value==="sealed"?"seal":O.explicit?"unseal":"leave",t.name);if(ye&&ae.push(ye),O.value==="sealed"){let J=_o(e,Rg);So(J)?t.signer&&o.push(`${Rg} exists and init does not edit it; add ${t.signer} to it by hand`):t.signer?(ae.push(As(J,fbe(e,t.signer))),o.push(`${Rg} lists ${t.signer}; merge it in a reviewed pull request before the first wave waits`)):o.push(`approval: sealed counts only approvals sealed by a key ${Rg} lists, and there is none yet; terragucci init --signer <your principal> writes it from git config user.signingkey`)}if(t.settings){let J=await dbe(e,t.settings);J&&ae.push(J)}t.settings||(t.forge&&r.forge&&t.forge!==r.forge&&o.push(`--forge ${t.forge} is ignored: ${qc(e,n??"terragucci.yml")} sets forge: ${r.forge}`),t.binary&&r.binary&&t.binary!==r.binary&&o.push(`--binary ${t.binary} is ignored: ${qc(e,n??"terragucci.yml")} sets binary: ${r.binary}`),t.approval&&A&&t.approval!==A&&o.push(`--approval ${t.approval} is ignored: ${qc(e,n??"terragucci.yml")} sets approval: ${A}`));let Z=n?`using ${qc(e,n)}`:"no terragucci.yml needed (defaults fit)";if(!t.settings){let J={};if(t.forge&&!r.forge&&t.forge!==$?.value&&(J.forge=t.forge),t.binary&&!r.binary&&t.binary!==a.value&&(J.binary=t.binary),t.approval&&!A&&t.approval!==x&&(J.approval=t.approval),Object.keys(J).length){if(n)throw new S(`${qc(e,n)} exists and init does not edit it; add ${Object.entries(J).map(([z,X])=>`${z}: ${X}`).join(", ")} to it`);let j=_o(e,"terragucci.yml");ae.push(As(j,`${xr(J,0).trim()}
897
- `)),Z=`terragucci.yml records ${Object.keys(J).join(" and ")}, which detection cannot find`}}if(r.drift&&_.value==="gitlab"&&o.push(`drift is set: add a pipeline schedule with the cron ${r.drift} under CI/CD > Schedules, and give ${r.token_env??"GITLAB_TOKEN"} the api scope so the drift issue can be kept`),F&&_.value==="gitlab"&&o.push(`rollouts is set: add a pipeline schedule with the cron ${F} and the variable TERRAGUCCI_SCHEDULE set to rollouts under CI/CD > Schedules, and give ${r.token_env??"GITLAB_TOKEN"} the api and write_repository scopes so the rollout job can push a wave's branch and open its merge request`),r.rollouts&&!F&&o.push("rollouts is set and respond.rollout is off, so no rollout job is written"),r.comments&&_.value==="gitlab"&&o.push(`comments is set: add a pipeline schedule with the cron ${r.comments} and the variable TERRAGUCCI_SCHEDULE set to comments under CI/CD > Schedules, and give ${r.token_env??"GITLAB_TOKEN"} the api scope and the Developer role so the comments job can answer notes and start pipelines`),r.gitlab?.token==="protected"&&_.value==="gitlab"){let J=r.token_env??"GITLAB_TOKEN";o.push(`gitlab.token is protected: under Settings > CI/CD > Variables, edit ${J} and tick Protect variable and Mask variable, and keep the default branch protected; merge request and branch pipelines then never see the token, the plan job stops if it does, the comments job posts the plan notes, and no fmt job commits formatting`)}r.waves?.canary?.length&&g&&o.push("waves.canary is set; the canary stacks' layers apply first, then the layers of the rest, each wave behind its gate"),r.waves?.canary?.length&&f&&o.push("waves.canary is set; the canary instances' layers apply first, then the layers of the rest, each wave behind its gate"),r.waves?.canary?.length&&p&&o.push("waves.canary is set; the canary units' layers apply first, then the layers of the rest, each wave behind its gate");let me=N2({waves:i||f||g?d:an(d,r.waves?.canary),order:u.order,...f?{dirOf:J=>s.sourceOf(J)}:{},pipeline:{path:M.path,forge:_.value,applyJobs:M.applyJobs}});if(ae.push(As(he,D2(me))),!t.dryRun){for(let J of ae)if(J.status!=="unchanged"){if(J.status==="removed"){ibe(J.path);continue}sbe(ube(J.path),{recursive:!0}),abe(J.path,J.content)}}return{roots:m,rootReasons:l,layers:d,...p?{terragrunt:p}:{},...f?{atmos:f}:{},...g?{terramate:g}:{},binary:c,image:r.image??qT(I),version:k,pins:v,forge:_,files:ae,notes:o,configNote:Z}}var L2="# terragucci reconcile writes this file from the control repo's terragucci.yml; edit that file, not this one.";function z2(e,t){let n=r=>r!==null&&typeof r=="object"&&!Array.isArray(r)?Object.fromEntries(Object.keys(r).sort().map(o=>[o,n(r[o])])):r;return JSON.stringify(n(e??null))===JSON.stringify(n(t??null))}function sk(e,t){let n=e[t];return n===void 0||z2(n,Ky[t])?void 0:n}async function dbe(e,t){let n=fe(e),r=_o(e,"terragucci.yml"),o=n===r&&Ro(r,"utf-8").startsWith(L2),s=VS.filter(a=>sk(t,a)!==void 0);if(n&&!o){let a=ue(await Re(n)),c=VS.filter(l=>!z2(sk(a,l),sk(t,l)));if(c.length===0)return;let u=c.map(l=>sk(t,l)!==void 0?`set its ${l} key to the control repo's (${JSON.stringify(t[l])})`:`remove its ${l} key`).join(" and ");throw new S(`${qc(e,n)} exists and terragucci did not write it, and the jobs read ${c.join(" and ")} from it; ${u}, or remove the file so the control repo writes it`)}if(s.length===0&&!o)return;let i=s.length?xr(Object.fromEntries(s.map(a=>[a,t[a]])),0).trim():"{}";return As(r,`${L2}
895
+ `)}function CJ(e,t){if(e===void 0||e.startsWith(pi)||e.trim()==="")return Xwe;let n=GP(e),r=GP(t),s=Object.keys(r).filter(u=>u!=="stages").filter(u=>u in n);if(s.length)throw new S(`${Op} has jobs named ${s.join(", ")}, as ${Xr.gitlab} does, and GitLab would merge the two; rename yours`);if(Array.isArray(n.stages)){let u=[...lh.before,...lh.after],l=r.stages.filter(p=>!u.includes(p)),d=l.map(p=>n.stages.indexOf(p));if(d.some((p,f)=>p<0||f>0&&p<d[f-1]))throw new S(`${Op} lists its own stages, which GitLab reads in place of the included file's; add ${l.join(", ")} to them, in that order`)}if(LP(n.include))return e;let i=Qwe(e,n),a=i===void 0?void 0:GP(i),c=(u,l)=>{let{include:d,...p}=u,{include:f,...g}=l,m=Array.isArray(f)?f.filter(h=>!LP(h)):[];return JSON.stringify(p)===JSON.stringify(g)&&JSON.stringify(d??[])===JSON.stringify(m)};if(i===void 0||!a||!LP(a.include)||!c(n,a))throw new S(`${Op} has an include init cannot add a line to; write its include as a list and add \`${ok}\` to it`);return i}var zP="terragucci.components.json",ebe="terragucci.components/v1",FP="terragucci",tbe="applied by terragucci's pipeline; a failed wave leaves what applied, and a re-run finishes it";function nbe(e,t){let n=new Set,r=[];for(;n.size<e.length;){let o=e.filter(s=>!n.has(s)&&[...t.get(s)??[]].every(i=>n.has(i))).sort();if(o.length===0)throw new Error(`these roots depend on each other in a cycle: ${e.filter(s=>!n.has(s)).join(", ")}`);o.forEach(s=>n.add(s)),r.push(o)}return r}var rbe=(e,t)=>JSON.stringify(e.map(n=>[...n].sort()))===JSON.stringify(t.map(n=>[...n].sort()));function obe(e,t){let n=e.flat(),r=new Set(n),o=new Map(n.map(s=>[s,new Set([...t.get(s)??[]].filter(i=>r.has(i)&&i!==s))]));return rbe(nbe(n,o),e)||e.forEach((s,i)=>{if(i>0)for(let a of s)for(let c of e[i-1])o.get(a).add(c)}),o}function NJ(e){let t=e.waves.map(s=>[...s].sort()),n=obe(t,e.order),r=e.dirOf??(s=>s),o=t.flat();return{schema:ebe,generated_by:`${FP} ${Pr}`,roots:Object.fromEntries(o.map(s=>[s,{dir:r(s)}])),components:o.map(s=>({name:s,dependsOn:[...n.get(s)].sort(),deploy:[{phase:"Apply",steps:[{kind:"terraform-apply",root:s,noRollback:tbe}]}]})),pipeline:{path:e.pipeline.path,forge:e.pipeline.forge,jobs:e.pipeline.applyJobs}}}var DJ=e=>`${JSON.stringify(e,null,2)}
896
+ `;function MJ(e){try{let t=JSON.parse(e);return typeof t.generated_by=="string"&&(t.generated_by===FP||t.generated_by.startsWith(`${FP} `))}catch{return!1}}function As(e,t){return So(e)?{path:e,status:Ro(e,"utf-8")===t?"unchanged":"updated",content:t}:{path:e,status:"created",content:t}}function lbe(e,t){if(typeof t!="string")return t;let n=_o(e,t);if(!So(n))throw new S(`own_jobs names ${t}, which the repo does not have`);let r;try{r=Yl(Ro(n,"utf-8"))}catch(s){throw new S(`own_jobs: ${t} is not YAML (${s.message})`)}let o=typeof r=="string"?[`own_jobs (${t}) must be a map of job name to job`]:cR(r,`own_jobs (${t})`);if(o.length>0)throw new S(o.join("; "));return r}async function ik(e,t={}){let n=t.settings?void 0:fe(e),r=t.settings??ue(n?await Re(n):{}),o=[],s=et(e,r);oL(s);let i=s.kind==="terragrunt",a=Ks(e,[]),c=r.binary?{value:r.binary,reason:"terragucci.yml"}:t.binary?{value:t.binary,reason:"--binary"}:a,u=await s.discover({binary:c.value,...t.terragrunt?{terragrunt:t.terragrunt}:{},...t.atmos?{atmos:t.atmos}:{},...t.terramate?{terramate:t.terramate}:{}});o.push(...u.notes);let l=u.roots,d=u.layers,p,f,g;if(s.kind==="atmos")f={reason:s.reason,version:r.atmos?.version??yw};else if(s.kind==="terramate")g={reason:s.reason,version:rb,skipped:u.skipped??[]};else if(s.terragrunt){let J=r.terragrunt??{};s.terragrunt.stacks.length>0&&o.push(`explicit stacks: ${s.terragrunt.stacks.join(", ")}; terragrunt stack generate wrote their units, and every job generates them again before discovery`);let j=BN(e);p={reason:s.reason,version:J.version?{value:J.version,reason:"terragucci.yml"}:j?{value:j,reason:"terragrunt_version_constraint"}:{value:ai.terragrunt,reason:"the image"},parallelism:UN(e,J),source:u.source??"terragrunt.hcl files",stacks:s.terragrunt.stacks}}let m=l.map(J=>J.root);!i&&!r.binary&&!t.binary&&Object.assign(c,s.binary(m));let h=i||!ea[c.value].releaseInRequiredVersion?void 0:iD(e,m),y=i?void 0:om(c.value),w=y?_o(e,rm[y]):void 0,b=w&&So(w)?iT(Ro(w,"utf-8")):void 0,k=typeof r.version=="string"?{value:r.version,reason:"terragucci.yml"}:b&&y?{value:b,reason:rm[y]}:h?{value:h,reason:"required_version"}:{value:ai[c.value]??"",reason:"the image"},v=[];for(let J of m){let j=cT(e,J,c.value,r.version);j&&j.version!==k.value&&v.push({root:J,...j});let z=i?iv(e,J):void 0;z&&z!==p?.version.value&&v.push({root:J,tool:"terragrunt",version:z,source:"terragrunt_version_constraint"})}let $=Kr(e),_=r.forge?{value:r.forge,reason:t.settings?"the project":"terragucci.yml"}:t.forge?{value:t.forge,reason:"--forge"}:$;if(!_)throw new S("cannot tell which forge this repo is on; pass --forge github, gitlab or forgejo");if(_.value==="gitlab"){let J=Nf(r,"config");if(J.length)throw new S(J.join("; "))}let E=_o(e,"chant.workspace.json"),x=Xg(So(E)?Ro(E,"utf-8"):void 0)>0?"sealed":"ledger",A=Bs(r.approval),O=A?{value:A,explicit:!0}:t.approval?{value:t.approval,explicit:!0}:{value:x,explicit:!1},I,N;p?(I=c.value==="choudoufu"?HT("choudoufu"):hz(),N={version:p.version.value,parallelism:p.parallelism.value,exclude:r.terragrunt?.exclude??[],...s.terragrunt&&s.terragrunt.stacks.length>0?{stacks:!0}:{},...r.generate?{generate:!0}:{},...r.sources?{sources:!0}:{},...r.terragrunt?.credentials?{credentials:r.terragrunt.credentials}:{},installs:hq(c.value,k.value,p.version.value,c.value==="choudoufu"?{choudoufu:ai.choudoufu}:{tofu:ai.tofu,terragrunt:ai.terragrunt})}):I=HT(c.value);let L=N?void 0:sc(r,d.flat());if(L&&_.value==="forgejo"){let J=Object.entries(r.environments??{}).filter(([,j])=>j.forge_environment!==void 0).map(([j])=>j);if(J.length)throw new S(J.map(j=>`environments.${j}.forge_environment: ${vR}`).join("; "))}let U=ai[c.value],F=r.rollouts&&Kn(r,"rollout")!=="off"?r.rollouts:void 0,M=qq({forge:_.value,binary:c.value,version:k.value,image:r.image??qT(I),...r.image?{imageFromConfig:!0}:{},install:!N&&k.value!==U?{binary:c.value,version:k.value}:void 0,...v.length>0?{rootPins:!0}:{},...r.generate&&!N?{generate:!0}:{},...r.sources&&!N?{sources:!0}:{},...N?{terragrunt:N}:{},layers:d,env:r.env,oidc:Zs(r.oidc,r.environments),...L?{applyGroups:L}:{},tokenEnv:r.token_env,...r.decide?.token_env?{decideTokenEnv:r.decide.token_env}:{},...r.telemetry?.headers_secret?{headersSecret:r.telemetry.headers_secret}:{},...r.modules?.publish||r.modules?.registry?{publish:!0,...r.modules.attest?{attest:!0}:{},...r.modules.registry?.bucket?{publishBucket:r.modules.registry.bucket}:{}}:{},...r.reports?{reports:r.reports}:{},...r.drift?{drift:r.drift}:{},...F?{rollouts:F}:{},...s.prepare?{synth:s.prepare}:{},...f?{atmos:{version:f.version}}:{},...g?{terramate:{version:g.version}}:{},...r.notify?{notify:r.notify}:{},...r.cost?{cost:{keySecret:r.cost!==!0&&r.cost.key_secret||If,install:r.cost===!0||!r.cost.command,...r.cost!==!0&&r.cost.approve_above!==void 0?{approveAbove:!0}:{}}}:{},...r.comments?{comments:r.comments}:{},...r.gitlab?.token?{gitlabToken:r.gitlab.token}:{},...!N&&!f&&!g&&r.waves?.canary?.length?{canary:r.waves.canary}:{},...r.waves?.jobs&&r.waves.jobs>1?{waveJobs:r.waves.jobs}:{},gate:r.gate,...QR(r).length?{environmentGates:QR(r)}:{},...Bs(t.settings?.approval)?{approval:Bs(t.settings?.approval)}:{},...O.value==="pr-review"?{prReview:!0}:{},...r.agent?.login?{agentLogin:r.agent.login}:{},...r.respond?{respond:r.respond}:{},...r.policy?{policy:!0}:{},...Nv(r)?{agentComment:Nv(r)}:{},...aA(r)?{agentDrift:aA(r)}:{},...r.atlantis_comments?{atlantisComments:!0}:{},...NO(r)?{review:NO(r)}:{},...r.apply?.resume?{resume:r.apply.resume}:{},...Vv(e).length>0?{migrations:!0}:{},...r.apply?.when==="pull-request"?{applyWhen:"pull-request",...r.apply.merge?{applyMerge:r.apply.merge}:{},...r.apply.merge_token_env?{applyMergeTokenEnv:r.apply.merge_token_env}:{},...r.apply.requires?{applyRequires:r.apply.requires}:{}}:{},...r.locks==="plan"?{locksPlan:!0}:{},...r.apply?.branches&&Object.keys(r.apply.branches).length>0?{applyBranches:r.apply.branches}:{},...r.own_jobs!==void 0?{ownJobs:lbe(e,r.own_jobs)}:{},...r.ephemeral?{ephemeral:{sweep:r.ephemeral.sweep??KC}}:{},...r.runner!==void 0?{runner:r.runner}:{},...r.pass!==void 0?{pass:r.pass}:{}}),te=_o(e,M.path);if(So(te)&&!t.force&&!Ro(te,"utf-8").startsWith(pi))throw new S(`${M.path} exists and terragucci did not write it; move it aside or pass --force`);let ie=_.value==="gitlab"?void 0:gP[_.value],Q=_.value==="gitlab"?void 0:Dc[_.value],ne=_.value==="gitlab"?void 0:mP[_.value],ae=[As(te,M.content)];for(let J of M.extra??[]){let j=_o(e,J.path);if((J.path===ie||J.path===Q||J.path===ne)&&So(j)&&!t.force&&!Ro(j,"utf-8").startsWith(pi))throw new S(`${J.path} exists and terragucci did not write it; move it aside or pass --force`);ae.push(As(j,J.content))}let he=_o(e,zP);if(So(he)&&!t.force&&!MJ(Ro(he,"utf-8")))throw new S(`${zP} exists and terragucci did not write it; move it aside or pass --force`);for(let J of[ie,Q,ne]){if(!J||(M.extra??[]).some(z=>z.path===J)||t.settings)continue;let j=_o(e,J);So(j)&&Ro(j,"utf-8").startsWith(pi)&&ae.push({path:j,status:"removed",content:""})}if(_.value==="gitlab"){let J=_o(e,Op);ae.push(As(J,CJ(So(J)?Ro(J,"utf-8"):void 0,M.content)))}let $e=jJ(r.dashboards);if($e)for(let J of PJ($e,{...Zu(r.reports)?{reports:Zu(r.reports)}:{}})){let j=_o(e,J.path);if(So(j)&&!t.force&&!IJ(J.path,Ro(j,"utf-8")))throw new S(`${J.path} exists and terragucci did not write it; move it aside, set dashboards.dir, or pass --force`);ae.push(As(j,J.content))}let ye=pbe(e,i||f||g?d.length:an(d,r.waves?.canary).length,O.value==="sealed"?"seal":O.explicit?"unseal":"leave",t.name);if(ye&&ae.push(ye),O.value==="sealed"){let J=_o(e,Rg);So(J)?t.signer&&o.push(`${Rg} exists and init does not edit it; add ${t.signer} to it by hand`):t.signer?(ae.push(As(J,fbe(e,t.signer))),o.push(`${Rg} lists ${t.signer}; merge it in a reviewed pull request before the first wave waits`)):o.push(`approval: sealed counts only approvals sealed by a key ${Rg} lists, and there is none yet; terragucci init --signer <your principal> writes it from git config user.signingkey`)}if(t.settings){let J=await dbe(e,t.settings);J&&ae.push(J)}t.settings||(t.forge&&r.forge&&t.forge!==r.forge&&o.push(`--forge ${t.forge} is ignored: ${qc(e,n??"terragucci.yml")} sets forge: ${r.forge}`),t.binary&&r.binary&&t.binary!==r.binary&&o.push(`--binary ${t.binary} is ignored: ${qc(e,n??"terragucci.yml")} sets binary: ${r.binary}`),t.approval&&A&&t.approval!==A&&o.push(`--approval ${t.approval} is ignored: ${qc(e,n??"terragucci.yml")} sets approval: ${A}`));let Z=n?`using ${qc(e,n)}`:"no terragucci.yml needed (defaults fit)";if(!t.settings){let J={};if(t.forge&&!r.forge&&t.forge!==$?.value&&(J.forge=t.forge),t.binary&&!r.binary&&t.binary!==a.value&&(J.binary=t.binary),t.approval&&!A&&t.approval!==x&&(J.approval=t.approval),Object.keys(J).length){if(n)throw new S(`${qc(e,n)} exists and init does not edit it; add ${Object.entries(J).map(([z,X])=>`${z}: ${X}`).join(", ")} to it`);let j=_o(e,"terragucci.yml");ae.push(As(j,`${xr(J,0).trim()}
897
+ `)),Z=`terragucci.yml records ${Object.keys(J).join(" and ")}, which detection cannot find`}}if(r.drift&&_.value==="gitlab"&&o.push(`drift is set: add a pipeline schedule with the cron ${r.drift} under CI/CD > Schedules, and give ${r.token_env??"GITLAB_TOKEN"} the api scope so the drift issue can be kept`),F&&_.value==="gitlab"&&o.push(`rollouts is set: add a pipeline schedule with the cron ${F} and the variable TERRAGUCCI_SCHEDULE set to rollouts under CI/CD > Schedules, and give ${r.token_env??"GITLAB_TOKEN"} the api and write_repository scopes so the rollout job can push a wave's branch and open its merge request`),r.rollouts&&!F&&o.push("rollouts is set and respond.rollout is off, so no rollout job is written"),r.comments&&_.value==="gitlab"&&o.push(`comments is set: add a pipeline schedule with the cron ${r.comments} and the variable TERRAGUCCI_SCHEDULE set to comments under CI/CD > Schedules, and give ${r.token_env??"GITLAB_TOKEN"} the api scope and the Developer role so the comments job can answer notes and start pipelines`),r.gitlab?.token==="protected"&&_.value==="gitlab"){let J=r.token_env??"GITLAB_TOKEN";o.push(`gitlab.token is protected: under Settings > CI/CD > Variables, edit ${J} and tick Protect variable and Mask variable, and keep the default branch protected; merge request and branch pipelines then never see the token, the plan job stops if it does, the comments job posts the plan notes, and no fmt job commits formatting`)}r.waves?.canary?.length&&g&&o.push("waves.canary is set; the canary stacks' layers apply first, then the layers of the rest, each wave behind its gate"),r.waves?.canary?.length&&f&&o.push("waves.canary is set; the canary instances' layers apply first, then the layers of the rest, each wave behind its gate"),r.waves?.canary?.length&&p&&o.push("waves.canary is set; the canary units' layers apply first, then the layers of the rest, each wave behind its gate");let me=NJ({waves:i||f||g?d:an(d,r.waves?.canary),order:u.order,...f?{dirOf:J=>s.sourceOf(J)}:{},pipeline:{path:M.path,forge:_.value,applyJobs:M.applyJobs}});if(ae.push(As(he,DJ(me))),!t.dryRun){for(let J of ae)if(J.status!=="unchanged"){if(J.status==="removed"){ibe(J.path);continue}sbe(ube(J.path),{recursive:!0}),abe(J.path,J.content)}}return{roots:m,rootReasons:l,layers:d,...p?{terragrunt:p}:{},...f?{atmos:f}:{},...g?{terramate:g}:{},binary:c,image:r.image??qT(I),version:k,pins:v,forge:_,files:ae,notes:o,configNote:Z}}var LJ="# terragucci reconcile writes this file from the control repo's terragucci.yml; edit that file, not this one.";function zJ(e,t){let n=r=>r!==null&&typeof r=="object"&&!Array.isArray(r)?Object.fromEntries(Object.keys(r).sort().map(o=>[o,n(r[o])])):r;return JSON.stringify(n(e??null))===JSON.stringify(n(t??null))}function sk(e,t){let n=e[t];return n===void 0||zJ(n,Ky[t])?void 0:n}async function dbe(e,t){let n=fe(e),r=_o(e,"terragucci.yml"),o=n===r&&Ro(r,"utf-8").startsWith(LJ),s=VS.filter(a=>sk(t,a)!==void 0);if(n&&!o){let a=ue(await Re(n)),c=VS.filter(l=>!zJ(sk(a,l),sk(t,l)));if(c.length===0)return;let u=c.map(l=>sk(t,l)!==void 0?`set its ${l} key to the control repo's (${JSON.stringify(t[l])})`:`remove its ${l} key`).join(" and ");throw new S(`${qc(e,n)} exists and terragucci did not write it, and the jobs read ${c.join(" and ")} from it; ${u}, or remove the file so the control repo writes it`)}if(s.length===0&&!o)return;let i=s.length?xr(Object.fromEntries(s.map(a=>[a,t[a]])),0).trim():"{}";return As(r,`${LJ}
898
898
  ${i}
899
- `)}var G2="0.102.0";function pbe(e,t,n,r){let o=_o(e,"chant.workspace.json"),s,i=So(o)?Ro(o,"utf-8"):void 0;if(n!=="seal"){if(n==="leave"||i===void 0)return;try{s=JSON.parse(i)}catch{throw new S("chant.workspace.json is not valid JSON, so init cannot drop the tf-apply gates from it")}let p=s.identity,f=Object.keys(p?.gates??{}).filter(g=>/^wave-\d+$/.test(g));if(f.length===0)return As(o,i);for(let g of f)delete p.gates[g];return Object.keys(p.gates).length===0&&delete p.gates,Object.keys(p).length===0&&delete s.identity,As(o,`${JSON.stringify(s,null,2)}
900
- `)}if(i!==void 0)try{s=JSON.parse(i)}catch{throw new S("chant.workspace.json is not valid JSON, so init cannot add the tf-apply gates to it")}else s={name:gbe(e,r),schema:1,minReader:G2,members:[]};let a=i===void 0,[c=0,u=0]=String(s.minReader).split(".").map(Number);(typeof s.minReader!="string"||c===0&&u<102)&&(s.minReader=G2,a=!0);let l=s.identity??={},d=l.gates??={};for(let p=1;p<=t;p++)d[Ct(p)]===void 0&&(d[Ct(p)]={},a=!0);return As(o,a?`${JSON.stringify(s,null,2)}
901
- `:i)}function fbe(e,t){if(!/^[^\s,"*?!]+$/.test(t))throw new S(`--signer ${JSON.stringify(t)} is not a principal; use a name with no spaces, commas, quotes or patterns, such as github:alice`);let n="";try{n=F2("git",["-C",e,"config","user.signingkey"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}).trim()}catch{}if(!n)throw new S("--signer reads git config user.signingkey, which is not set; set it to your ssh key (git config --global user.signingkey ~/.ssh/id_ed25519.pub)");let r=n.replace(/^key::/,"");if(!/^(ssh-|sk-|ecdsa-)/.test(r)){let i=r.replace(/^~(?=\/)/,process.env.HOME??"~"),a=i.endsWith(".pub")?i:`${i}.pub`;if(!So(a))throw new S(`--signer found user.signingkey ${n}, and no public key at ${a}`);r=Ro(a,"utf-8").trim()}let[o,s]=r.split(/\s+/);if(!o||!s||!/^(ssh-(ed25519|rsa)|sk-ssh-ed25519@openssh\.com)$/.test(o))throw new S(`--signer needs an ed25519 or RSA ssh key; user.signingkey gives ${o??"nothing"}`);return`${t} ${o} ${s}
902
- `}function gbe(e,t){let n=t;if(!n)try{n=F2("git",["-C",e,"remote","get-url","origin"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}).trim().replace(/\.git$/,"").split(/[/:]/).pop()}catch{}return(n||cbe(e)).toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+/,"").slice(0,40).replace(/-+$/,"")||"infra"}function U2(e,t,n=!1){let r=a=>a==="unchanged"?"unchanged":n?a==="created"?"would write":a==="removed"?"would remove":"would update":a==="created"?"wrote":a==="removed"?"removed":"updated",o=(a,c)=>`${a} ${c}${a===1?"":"s"}`,s=t.terragrunt;return[t.terramate?`found Terramate (${t.terramate.reason}): ${o(t.roots.length,"stack")} in ${o(t.layers.length,"wave")} from terramate list and the stacks' order, terramate ${t.terramate.version}, ${t.binary.value} ${t.version.value} (${t.binary.reason}), forge ${t.forge.value} (${t.forge.reason})`:t.atmos?`found Atmos (${t.atmos.reason}): ${o(t.roots.length,"instance")} in ${o(t.layers.length,"wave")} from atmos describe stacks, atmos ${t.atmos.version} calling ${t.binary.value} ${t.version.value} (${t.binary.reason}), forge ${t.forge.value} (${t.forge.reason})`:s?`found Terragrunt (${s.reason}): ${o(t.roots.length,"unit")} in ${o(t.layers.length,"wave")} from ${s.source}, terragrunt ${s.version.value} (${s.version.reason}) calling ${t.binary.value} ${t.version.value} (${t.binary.reason}), parallelism ${s.parallelism.value} (${s.parallelism.reason}), forge ${t.forge.value} (${t.forge.reason})`:`found ${o(t.roots.length,"root")} in ${o(t.layers.length,"layer")}, ${t.binary.value} ${t.version.value} (${t.binary.reason}), forge ${t.forge.value} (${t.forge.reason})`,...t.pins.length>0?[`${o(t.pins.length,"root")} ${t.pins.length===1?"pins its":"pin their"} own version: ${t.pins.map(a=>`${a.root} ${a.tool??t.binary.value} ${a.version} (${a.source})`).join(", ")}`]:[],...t.files.map(a=>`${r(a.status)} ${qc(e,a.path)}`),t.configNote,...t.notes.map(a=>`note: ${a}`)].join(`
903
- `)}function B2(e,t,n){return{dryRun:n,roots:t.rootReasons.map(({root:r,reason:o})=>({path:r,reason:o})),layers:t.layers,...t.terragrunt?{terragrunt:t.terragrunt}:{},...t.atmos?{atmos:t.atmos}:{},...t.terramate?{terramate:t.terramate}:{},binary:t.binary,version:t.version,...t.pins.length>0?{pins:t.pins}:{},forge:t.forge,image:t.image,files:t.files.map(r=>({path:qc(e,r.path),status:r.status,content:r.content})),notes:t.notes,configNote:t.configNote}}var UP="terragucci/pipeline",Tl=["-c","user.name=terragucci","-c","user.email=terragucci@users.noreply.intentius.io","-c","commit.gpgsign=false"];function yh(e,t){let n=po(e);if(!t)return{cloneUrl:`https://${n.host}/${n.path}.git`,origin:`https://${n.host}`};if(t.startsWith("/")||t.startsWith("file://"))return{cloneUrl:t,origin:`https://${n.host}`};let r=new URL(t);return{cloneUrl:t.endsWith(".git")?t:`${t}.git`,origin:`${r.protocol}//${r.host}`}}function wh(e,t){return!t||!/^https?:\/\//.test(e)?e:e.replace(/^(https?:\/\/)/,`$1oauth2:${encodeURIComponent(t)}@`)}function hh(e,t,n){try{return mbe("git",["-C",e,...t],{encoding:"utf-8",stdio:["ignore","pipe","pipe"]})}catch(r){let o=String(r.stderr??r.message);throw new Error(n?o.split(n).join("***"):o)}}async function kbe(e,t){try{let n=await yo().catch(()=>{});return await Lw(e,wt(e,t.roots),{settings:t,...n?{parser:n}:{}})}catch{return[]}}function _be(e,t){let n=qm(e,t);if(n.foreign.length)throw new S(n.foreign.join("; "));for(let r of n.files)r.status==="removed"?wbe(r.path):r.status!=="unchanged"&&bbe(r.path,r.content);return n.files}async function H2(e,t){if(!e.projects||Object.keys(e.projects).length===0)throw new S("reconcile needs a control repo config with projects; in a single repo, run terragucci init");let n=t.project?[t.project]:Object.keys(e.projects),r=t.env??process.env,o=t.fetch??globalThis.fetch,s=[];for(let i of n){let a=hbe(W2(vbe(),"terragucci-reconcile-"));try{let c=ot(e,i),u=po(i),l=c.forge??ir(u.host);if(!l)throw new S(`cannot tell which forge ${u.host} is; set forge for this project`);let d=c.token_env??Gn[l],p=r[d];if(t.mode==="apply"&&!p)throw new S(`${d} is not set; it holds the token for ${u.host}`);let{cloneUrl:f,origin:g}=yh(i,c.url),m=W2(a,"repo");hh(a,["clone","-q","--depth","1",wh(f,p),m],p);let h={...c,forge:l,...c.generate?{generate:AB(c)}:{}},y=c.generate?_be(m,h):[],w=await ik(m,{settings:h,name:u.name,dryRun:t.mode==="dry-run"}),b=[...y,...w.files].map(x=>({...x,path:$be(m,x.path)})),k=b.filter(x=>x.status!=="unchanged"),v=t.mode==="dry-run"&&c.tips?await kbe(m,c):void 0,$=v?{tips:v}:{};if(k.length===0){s.push({key:i,status:"unchanged",changes:b,...$});continue}if(t.mode==="dry-run"){s.push({key:i,status:"would-change",changes:b,...$});continue}let _={forge:l,origin:g,path:u.path,token:p},E=await lc(o,_);hh(m,["checkout","-q","-B",UP]),hh(m,["add","-A"]),hh(m,[...Tl,"commit","-q","-m","terragucci: update the pipeline from the control repo"]),hh(m,["push","-q","--force","origin",`HEAD:refs/heads/${UP}`],p);let R=await dc(o,_,{head:UP,base:E,title:"terragucci: update the pipeline",body:["The control repo's terragucci config changed what this repo's pipeline should be.","",...k.map(x=>`- ${x.status==="created"?"adds":"updates"} \`${x.path}\``),"","Review and merge it here; terragucci never writes this repo's default branch."].join(`
904
- `)});s.push({key:i,status:"pull-request",changes:b,pullRequest:R.url})}catch(c){s.push({key:i,status:"failed",changes:[],error:c.message})}finally{ybe(a,{recursive:!0,force:!0})}}return s}function q2(e,t){let n=[];for(let o of e){let s=o.changes.filter(i=>i.status!=="unchanged").map(i=>`${i.status==="created"?"new":"changed"} ${i.path}`);o.status==="unchanged"?n.push(`${o.key}: unchanged`):o.status==="would-change"?n.push(`${o.key}: would write ${s.join(", ")}`):o.status==="pull-request"?n.push(`${o.key}: ${s.join(", ")} -> ${o.pullRequest}`):n.push(`${o.key}: FAILED ${o.error}`);for(let i of Gw(o.tips??[]))n.push(` ${i}`)}let r=e.filter(o=>o.status==="failed").length;return n.push(t==="dry-run"?`dry run: ${e.filter(o=>o.status==="would-change").length} of ${e.length} would change; nothing was written`:`${e.filter(o=>o.status==="pull-request").length} pull request(s) opened or updated`),r&&n.push(`${r} project(s) failed`),n.join(`
905
- `)}import{execFileSync as Cbe}from"node:child_process";import{existsSync as VP,mkdtempSync as sJ,readdirSync as iJ,readFileSync as ck,rmSync as aJ,statSync as Nbe,writeFileSync as Dbe}from"node:fs";import{tmpdir as cJ}from"node:os";import{join as Ps,posix as uJ}from"node:path";import{execFileSync as Sbe}from"node:child_process";import{posix as Rbe}from"node:path";function xbe(e,t){for(let n of[["ls-remote","--tags","--refs","origin",`refs/tags/${t}/v*`],["tag","--list",`${t}/v*`]])try{let o=Sbe("git",["-C",e,...n],{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}).split(`
906
- `).filter(Boolean).map(s=>s.split(" ").pop().replace(/^refs\/tags\//,""));if(n[0]==="ls-remote"||o.length>0)return o}catch{}return[]}async function J2(e){let t,n=(i,a)=>{let c=pr(i);c&&(!t||wo(c,t.v)>0)&&(t={v:c,from:a})},r=e.module.replace(/^\.\//,"").replace(/\/+$/,"");if(!/^[a-z]+:\/\/|::/.test(r))for(let i of e.repos)for(let a of xbe(i,r))n(a.slice(r.length+1),`tag ${a}`);let o=e.env??process.env,s=/^(1|true|yes)$/i.test(o.TERRAGUCCI_REGISTRY_INSECURE??"");for(let i of new Set(e.oci)){let{host:a,repo:c}=wc(i),u=new ii(a,{fetch:e.fetch,scheme:s?"http":"https",user:o.TERRAGUCCI_REGISTRY_USER,password:o.TERRAGUCCI_REGISTRY_PASSWORD});for(let l of await u.tags(c))n(l,`${i}:${l}`)}for(let i of new Set(e.registries??[]))for(let a of await JF(i,e.fetch))n(a,`${i} ${a}`);return t?{version:Je(t.v),from:t.from}:void 0}function K2(e,t){return`${e.replace(/\/+$/,"")}/${Rbe.basename(t.replace(/\/+$/,""))}`}function V2(e,t){let n=e.replace(/^\.\//,"").replace(/\/+$/,"");if(/^[a-z]+:\/\/|::/.test(n))return[];if(vc(n))return[n.split("?")[0].toLowerCase()];let r=new Set;for(let o of t){let s=o.modules?.registry;if(s)try{r.add(bv(s,n).source.toLowerCase())}catch{}}return[...r]}var Z2=500,X2={success:"success",neutral:"success",skipped:"success",failure:"failure",failed:"failure",error:"failure",cancelled:"failure",canceled:"failure",timed_out:"failure",action_required:"failure",startup_failure:"failure",stale:"failure",warning:"failure"},Y2=e=>X2[e??""]??"pending";function BP(e,t){let n=new Map;for(let r of e){let o=t(r),s=n.get(o);(!s||(r.id??0)>(s.id??0))&&n.set(o,r)}return[...n.values()]}function WP(e,t){let n=o=>kt(e,t,"GET",o),r=encodeURIComponent(t.path);return{async defaultBranch(){return t.forge==="gitlab"?(await n(`/projects/${r}`)).default_branch??"main":(await n(`/repos/${t.path}`)).default_branch??"main"},async findPullRequest(o){if(t.forge==="gitlab"){let l=(await n(`/projects/${r}/merge_requests?state=all&source_branch=${encodeURIComponent(o)}&order_by=created_at&sort=desc`))[0];if(!l)return null;let d=l.state==="merged"?"merged":l.state==="opened"?"open":"closed",p=l.merge_commit_sha??l.squash_commit_sha??l.sha;return{url:l.web_url,state:d,body:l.description??"",...d==="merged"&&p?{mergeCommit:p}:{}}}let s=[];if(t.forge==="github"){let u=t.path.split("/")[0];s=await n(`/repos/${t.path}/pulls?state=all&head=${encodeURIComponent(`${u}:${o}`)}&per_page=10`)}else for(let u=1;u<=20;u++){let l;try{l=await n(`/repos/${t.path}/pulls?state=all&limit=50&page=${u}`)}catch(d){if(!(d instanceof ei&&d.status===404&&(await n(`/repos/${t.path}`)).empty===!0))throw d;l=[]}if(s.push(...l.filter(d=>d.head?.ref===o)),l.length<50)break}let i=s.sort((u,l)=>l.number-u.number)[0];if(!i)return null;let a=i.merged===!0||!!i.merged_at,c=a?"merged":i.state==="open"?"open":"closed";return{url:i.html_url,state:c,body:i.body??"",...a&&i.merge_commit_sha?{mergeCommit:i.merge_commit_sha}:{}}},async listPullRequests(o){let s=[];if(t.forge==="gitlab"){for(let a=1;a<=Z2/100;a++){let c=await n(`/projects/${r}/merge_requests?state=all&order_by=updated_at&sort=desc&per_page=100&page=${a}`);for(let u of c)u.source_branch?.startsWith(o)&&s.push({branch:u.source_branch,url:u.web_url,title:u.title??"",state:u.state==="merged"?"merged":u.state==="opened"?"open":"closed",body:u.description??""});if(c.length<100)break}return s}let i=t.forge==="github"?100:50;for(let a=1;a<=Z2/i;a++){let c;try{c=await n(t.forge==="github"?`/repos/${t.path}/pulls?state=all&sort=updated&direction=desc&per_page=100&page=${a}`:`/repos/${t.path}/pulls?state=all&sort=recentupdate&limit=50&page=${a}`)}catch(u){if(!(t.forge==="forgejo"&&u instanceof ei&&u.status===404&&(await n(`/repos/${t.path}`)).empty===!0))throw u;c=[]}for(let u of c){let l=u.head?.ref;if(!l?.startsWith(o))continue;let d=u.merged===!0||!!u.merged_at;s.push({branch:l,url:u.html_url,title:u.title??"",state:d?"merged":u.state==="open"?"open":"closed",body:u.body??""})}if(c.length<i)break}return s},async createPullRequest(o){return(await dc(e,t,o)).url},async commitChecks(o){if(t.forge==="gitlab"){let a=await n(`/projects/${r}/repository/commits/${o}/statuses?per_page=100`);return BP(a,c=>c.name).map(c=>({name:c.name,state:Y2(c.status)}))}let s=[];if(t.forge==="github"){let a=await n(`/repos/${t.path}/commits/${o}/check-runs?per_page=100`);for(let c of BP(a.check_runs??[],u=>u.name))s.push({name:c.name,state:c.status==="completed"?X2[c.conclusion??""]??"failure":"pending"})}let i=await n(`/repos/${t.path}/commits/${o}/statuses?${t.forge==="github"?"per_page":"limit"}=100`);for(let a of BP(i,c=>c.context))s.push({name:a.context,state:Y2(a.status??a.state)});return s}}}function HP(e,t){let n=e.find(s=>s.name===`apply/${t}`);if(n)return n.state;let r=e.find(s=>s.name==="terragucci/apply");if(r)return r.state;let o=e.filter(s=>/(^|\/ )apply(-wave-\d+)?( \(|$)/.test(s.name));return o.some(s=>s.state==="failure")?"failure":o.length===0||o.some(s=>s.state==="pending")?"pending":"success"}import{spawnSync as Ebe}from"node:child_process";import{existsSync as Tbe,readFileSync as bh,readdirSync as Abe,rmSync as Q2,writeFileSync as qP}from"node:fs";import{join as Os,posix as Obe}from"node:path";var pn=".terraform.lock.hcl",JP="terragucci_rollout_override.tf";function Al(e){let t=new Map;for(let n of e.matchAll(/provider\s+"([^"]+)"\s*\{([^}]*)\}/g)){let r=/\bversion\s*=\s*"([^"]+)"/.exec(n[2]);r&&t.set(n[1],r[1])}return t}function KP(e,t){let n=t.toLowerCase(),r=e.toLowerCase();return r===n||r.endsWith(`/${n}`)}function ak(e,t){for(let[n,r]of e)if(KP(n,t))return{address:n,version:r}}function eJ(e){return Tbe(e)?Abe(e).filter(t=>t.endsWith(".tf")&&t!==JP).sort():[]}function Pbe(e,t){let n=t.split("/").slice(-2).join("/").toLowerCase();for(let r of eJ(e))for(let o of bh(Os(e,r),"utf-8").matchAll(/([A-Za-z_][\w-]*)\s*=\s*\{([^{}]*)\}/g)){let s=/\bsource\s*=\s*"([^"]+)"/.exec(o[2])?.[1]?.toLowerCase();if(s&&(s===n||s.endsWith(`/${n}`)))return o[1]}return t.split("/").pop()}function jbe(e,t,n,r){let o=t.split("/").slice(-2).join("/").toLowerCase();return e.replace(/(\{[^{}]*\})/g,s=>{let i=/\bsource\s*=\s*"([^"]+)"/.exec(s)?.[1]?.toLowerCase();return!i||!(i===o||i.endsWith(`/${o}`))?s:s.replace(/(\bversion\s*=\s*")(=?\s*)([^"]+)(")/,(a,c,u,l,d)=>l.trim()===n?`${c}${u}${r}${d}`:a)})}var Ibe=(e,t,n)=>{let r=Ebe(e,t,{cwd:n,encoding:"utf-8",env:Ve({...process.env,TF_IN_AUTOMATION:"1",TF_INPUT:"0"})});return{status:r.status,output:`${r.stdout??""}${r.stderr??""}${r.error?r.error.message:""}`}};function tJ(e,t=Ibe){return(n,r,o)=>{let s=Pbe(n,r);qP(Os(n,JP),`terraform {
899
+ `)}var GJ="0.102.0";function pbe(e,t,n,r){let o=_o(e,"chant.workspace.json"),s,i=So(o)?Ro(o,"utf-8"):void 0;if(n!=="seal"){if(n==="leave"||i===void 0)return;try{s=JSON.parse(i)}catch{throw new S("chant.workspace.json is not valid JSON, so init cannot drop the tf-apply gates from it")}let p=s.identity,f=Object.keys(p?.gates??{}).filter(g=>/^wave-\d+$/.test(g));if(f.length===0)return As(o,i);for(let g of f)delete p.gates[g];return Object.keys(p.gates).length===0&&delete p.gates,Object.keys(p).length===0&&delete s.identity,As(o,`${JSON.stringify(s,null,2)}
900
+ `)}if(i!==void 0)try{s=JSON.parse(i)}catch{throw new S("chant.workspace.json is not valid JSON, so init cannot add the tf-apply gates to it")}else s={name:gbe(e,r),schema:1,minReader:GJ,members:[]};let a=i===void 0,[c=0,u=0]=String(s.minReader).split(".").map(Number);(typeof s.minReader!="string"||c===0&&u<102)&&(s.minReader=GJ,a=!0);let l=s.identity??={},d=l.gates??={};for(let p=1;p<=t;p++)d[Ct(p)]===void 0&&(d[Ct(p)]={},a=!0);return As(o,a?`${JSON.stringify(s,null,2)}
901
+ `:i)}function fbe(e,t){if(!/^[^\s,"*?!]+$/.test(t))throw new S(`--signer ${JSON.stringify(t)} is not a principal; use a name with no spaces, commas, quotes or patterns, such as github:alice`);let n="";try{n=FJ("git",["-C",e,"config","user.signingkey"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}).trim()}catch{}if(!n)throw new S("--signer reads git config user.signingkey, which is not set; set it to your ssh key (git config --global user.signingkey ~/.ssh/id_ed25519.pub)");let r=n.replace(/^key::/,"");if(!/^(ssh-|sk-|ecdsa-)/.test(r)){let i=r.replace(/^~(?=\/)/,process.env.HOME??"~"),a=i.endsWith(".pub")?i:`${i}.pub`;if(!So(a))throw new S(`--signer found user.signingkey ${n}, and no public key at ${a}`);r=Ro(a,"utf-8").trim()}let[o,s]=r.split(/\s+/);if(!o||!s||!/^(ssh-(ed25519|rsa)|sk-ssh-ed25519@openssh\.com)$/.test(o))throw new S(`--signer needs an ed25519 or RSA ssh key; user.signingkey gives ${o??"nothing"}`);return`${t} ${o} ${s}
902
+ `}function gbe(e,t){let n=t;if(!n)try{n=FJ("git",["-C",e,"remote","get-url","origin"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}).trim().replace(/\.git$/,"").split(/[/:]/).pop()}catch{}return(n||cbe(e)).toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+/,"").slice(0,40).replace(/-+$/,"")||"infra"}function UJ(e,t,n=!1){let r=a=>a==="unchanged"?"unchanged":n?a==="created"?"would write":a==="removed"?"would remove":"would update":a==="created"?"wrote":a==="removed"?"removed":"updated",o=(a,c)=>`${a} ${c}${a===1?"":"s"}`,s=t.terragrunt;return[t.terramate?`found Terramate (${t.terramate.reason}): ${o(t.roots.length,"stack")} in ${o(t.layers.length,"wave")} from terramate list and the stacks' order, terramate ${t.terramate.version}, ${t.binary.value} ${t.version.value} (${t.binary.reason}), forge ${t.forge.value} (${t.forge.reason})`:t.atmos?`found Atmos (${t.atmos.reason}): ${o(t.roots.length,"instance")} in ${o(t.layers.length,"wave")} from atmos describe stacks, atmos ${t.atmos.version} calling ${t.binary.value} ${t.version.value} (${t.binary.reason}), forge ${t.forge.value} (${t.forge.reason})`:s?`found Terragrunt (${s.reason}): ${o(t.roots.length,"unit")} in ${o(t.layers.length,"wave")} from ${s.source}, terragrunt ${s.version.value} (${s.version.reason}) calling ${t.binary.value} ${t.version.value} (${t.binary.reason}), parallelism ${s.parallelism.value} (${s.parallelism.reason}), forge ${t.forge.value} (${t.forge.reason})`:`found ${o(t.roots.length,"root")} in ${o(t.layers.length,"layer")}, ${t.binary.value} ${t.version.value} (${t.binary.reason}), forge ${t.forge.value} (${t.forge.reason})`,...t.pins.length>0?[`${o(t.pins.length,"root")} ${t.pins.length===1?"pins its":"pin their"} own version: ${t.pins.map(a=>`${a.root} ${a.tool??t.binary.value} ${a.version} (${a.source})`).join(", ")}`]:[],...t.files.map(a=>`${r(a.status)} ${qc(e,a.path)}`),t.configNote,...t.notes.map(a=>`note: ${a}`)].join(`
903
+ `)}function BJ(e,t,n){return{dryRun:n,roots:t.rootReasons.map(({root:r,reason:o})=>({path:r,reason:o})),layers:t.layers,...t.terragrunt?{terragrunt:t.terragrunt}:{},...t.atmos?{atmos:t.atmos}:{},...t.terramate?{terramate:t.terramate}:{},binary:t.binary,version:t.version,...t.pins.length>0?{pins:t.pins}:{},forge:t.forge,image:t.image,files:t.files.map(r=>({path:qc(e,r.path),status:r.status,content:r.content})),notes:t.notes,configNote:t.configNote}}var UP="terragucci/pipeline",Tl=["-c","user.name=terragucci","-c","user.email=terragucci@users.noreply.intentius.io","-c","commit.gpgsign=false"];function yh(e,t){let n=po(e);if(!t)return{cloneUrl:`https://${n.host}/${n.path}.git`,origin:`https://${n.host}`};if(t.startsWith("/")||t.startsWith("file://"))return{cloneUrl:t,origin:`https://${n.host}`};let r=new URL(t);return{cloneUrl:t.endsWith(".git")?t:`${t}.git`,origin:`${r.protocol}//${r.host}`}}function wh(e,t){return!t||!/^https?:\/\//.test(e)?e:e.replace(/^(https?:\/\/)/,`$1oauth2:${encodeURIComponent(t)}@`)}function hh(e,t,n){try{return mbe("git",["-C",e,...t],{encoding:"utf-8",stdio:["ignore","pipe","pipe"]})}catch(r){let o=String(r.stderr??r.message);throw new Error(n?o.split(n).join("***"):o)}}async function kbe(e,t){try{let n=await yo().catch(()=>{});return await Lw(e,wt(e,t.roots),{settings:t,...n?{parser:n}:{}})}catch{return[]}}function _be(e,t){let n=qm(e,t);if(n.foreign.length)throw new S(n.foreign.join("; "));for(let r of n.files)r.status==="removed"?wbe(r.path):r.status!=="unchanged"&&bbe(r.path,r.content);return n.files}async function HJ(e,t){if(!e.projects||Object.keys(e.projects).length===0)throw new S("reconcile needs a control repo config with projects; in a single repo, run terragucci init");let n=t.project?[t.project]:Object.keys(e.projects),r=t.env??process.env,o=t.fetch??globalThis.fetch,s=[];for(let i of n){let a=hbe(WJ(vbe(),"terragucci-reconcile-"));try{let c=ot(e,i),u=po(i),l=c.forge??ir(u.host);if(!l)throw new S(`cannot tell which forge ${u.host} is; set forge for this project`);let d=c.token_env??Gn[l],p=r[d];if(t.mode==="apply"&&!p)throw new S(`${d} is not set; it holds the token for ${u.host}`);let{cloneUrl:f,origin:g}=yh(i,c.url),m=WJ(a,"repo");hh(a,["clone","-q","--depth","1",wh(f,p),m],p);let h={...c,forge:l,...c.generate?{generate:AB(c)}:{}},y=c.generate?_be(m,h):[],w=await ik(m,{settings:h,name:u.name,dryRun:t.mode==="dry-run"}),b=[...y,...w.files].map(x=>({...x,path:$be(m,x.path)})),k=b.filter(x=>x.status!=="unchanged"),v=t.mode==="dry-run"&&c.tips?await kbe(m,c):void 0,$=v?{tips:v}:{};if(k.length===0){s.push({key:i,status:"unchanged",changes:b,...$});continue}if(t.mode==="dry-run"){s.push({key:i,status:"would-change",changes:b,...$});continue}let _={forge:l,origin:g,path:u.path,token:p},E=await lc(o,_);hh(m,["checkout","-q","-B",UP]),hh(m,["add","-A"]),hh(m,[...Tl,"commit","-q","-m","terragucci: update the pipeline from the control repo"]),hh(m,["push","-q","--force","origin",`HEAD:refs/heads/${UP}`],p);let R=await dc(o,_,{head:UP,base:E,title:"terragucci: update the pipeline",body:["The control repo's terragucci config changed what this repo's pipeline should be.","",...k.map(x=>`- ${x.status==="created"?"adds":"updates"} \`${x.path}\``),"","Review and merge it here; terragucci never writes this repo's default branch."].join(`
904
+ `)});s.push({key:i,status:"pull-request",changes:b,pullRequest:R.url})}catch(c){s.push({key:i,status:"failed",changes:[],error:c.message})}finally{ybe(a,{recursive:!0,force:!0})}}return s}function qJ(e,t){let n=[];for(let o of e){let s=o.changes.filter(i=>i.status!=="unchanged").map(i=>`${i.status==="created"?"new":"changed"} ${i.path}`);o.status==="unchanged"?n.push(`${o.key}: unchanged`):o.status==="would-change"?n.push(`${o.key}: would write ${s.join(", ")}`):o.status==="pull-request"?n.push(`${o.key}: ${s.join(", ")} -> ${o.pullRequest}`):n.push(`${o.key}: FAILED ${o.error}`);for(let i of Gw(o.tips??[]))n.push(` ${i}`)}let r=e.filter(o=>o.status==="failed").length;return n.push(t==="dry-run"?`dry run: ${e.filter(o=>o.status==="would-change").length} of ${e.length} would change; nothing was written`:`${e.filter(o=>o.status==="pull-request").length} pull request(s) opened or updated`),r&&n.push(`${r} project(s) failed`),n.join(`
905
+ `)}import{execFileSync as Cbe}from"node:child_process";import{existsSync as VP,mkdtempSync as s2,readdirSync as i2,readFileSync as ck,rmSync as a2,statSync as Nbe,writeFileSync as Dbe}from"node:fs";import{tmpdir as c2}from"node:os";import{join as Ps,posix as u2}from"node:path";import{execFileSync as Sbe}from"node:child_process";import{posix as Rbe}from"node:path";function xbe(e,t){for(let n of[["ls-remote","--tags","--refs","origin",`refs/tags/${t}/v*`],["tag","--list",`${t}/v*`]])try{let o=Sbe("git",["-C",e,...n],{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}).split(`
906
+ `).filter(Boolean).map(s=>s.split(" ").pop().replace(/^refs\/tags\//,""));if(n[0]==="ls-remote"||o.length>0)return o}catch{}return[]}async function JJ(e){let t,n=(i,a)=>{let c=pr(i);c&&(!t||wo(c,t.v)>0)&&(t={v:c,from:a})},r=e.module.replace(/^\.\//,"").replace(/\/+$/,"");if(!/^[a-z]+:\/\/|::/.test(r))for(let i of e.repos)for(let a of xbe(i,r))n(a.slice(r.length+1),`tag ${a}`);let o=e.env??process.env,s=/^(1|true|yes)$/i.test(o.TERRAGUCCI_REGISTRY_INSECURE??"");for(let i of new Set(e.oci)){let{host:a,repo:c}=wc(i),u=new ii(a,{fetch:e.fetch,scheme:s?"http":"https",user:o.TERRAGUCCI_REGISTRY_USER,password:o.TERRAGUCCI_REGISTRY_PASSWORD});for(let l of await u.tags(c))n(l,`${i}:${l}`)}for(let i of new Set(e.registries??[]))for(let a of await JF(i,e.fetch))n(a,`${i} ${a}`);return t?{version:Je(t.v),from:t.from}:void 0}function KJ(e,t){return`${e.replace(/\/+$/,"")}/${Rbe.basename(t.replace(/\/+$/,""))}`}function VJ(e,t){let n=e.replace(/^\.\//,"").replace(/\/+$/,"");if(/^[a-z]+:\/\/|::/.test(n))return[];if(vc(n))return[n.split("?")[0].toLowerCase()];let r=new Set;for(let o of t){let s=o.modules?.registry;if(s)try{r.add(bv(s,n).source.toLowerCase())}catch{}}return[...r]}var ZJ=500,XJ={success:"success",neutral:"success",skipped:"success",failure:"failure",failed:"failure",error:"failure",cancelled:"failure",canceled:"failure",timed_out:"failure",action_required:"failure",startup_failure:"failure",stale:"failure",warning:"failure"},YJ=e=>XJ[e??""]??"pending";function BP(e,t){let n=new Map;for(let r of e){let o=t(r),s=n.get(o);(!s||(r.id??0)>(s.id??0))&&n.set(o,r)}return[...n.values()]}function WP(e,t){let n=o=>kt(e,t,"GET",o),r=encodeURIComponent(t.path);return{async defaultBranch(){return t.forge==="gitlab"?(await n(`/projects/${r}`)).default_branch??"main":(await n(`/repos/${t.path}`)).default_branch??"main"},async findPullRequest(o){if(t.forge==="gitlab"){let l=(await n(`/projects/${r}/merge_requests?state=all&source_branch=${encodeURIComponent(o)}&order_by=created_at&sort=desc`))[0];if(!l)return null;let d=l.state==="merged"?"merged":l.state==="opened"?"open":"closed",p=l.merge_commit_sha??l.squash_commit_sha??l.sha;return{url:l.web_url,state:d,body:l.description??"",...d==="merged"&&p?{mergeCommit:p}:{}}}let s=[];if(t.forge==="github"){let u=t.path.split("/")[0];s=await n(`/repos/${t.path}/pulls?state=all&head=${encodeURIComponent(`${u}:${o}`)}&per_page=10`)}else for(let u=1;u<=20;u++){let l;try{l=await n(`/repos/${t.path}/pulls?state=all&limit=50&page=${u}`)}catch(d){if(!(d instanceof ei&&d.status===404&&(await n(`/repos/${t.path}`)).empty===!0))throw d;l=[]}if(s.push(...l.filter(d=>d.head?.ref===o)),l.length<50)break}let i=s.sort((u,l)=>l.number-u.number)[0];if(!i)return null;let a=i.merged===!0||!!i.merged_at,c=a?"merged":i.state==="open"?"open":"closed";return{url:i.html_url,state:c,body:i.body??"",...a&&i.merge_commit_sha?{mergeCommit:i.merge_commit_sha}:{}}},async listPullRequests(o){let s=[];if(t.forge==="gitlab"){for(let a=1;a<=ZJ/100;a++){let c=await n(`/projects/${r}/merge_requests?state=all&order_by=updated_at&sort=desc&per_page=100&page=${a}`);for(let u of c)u.source_branch?.startsWith(o)&&s.push({branch:u.source_branch,url:u.web_url,title:u.title??"",state:u.state==="merged"?"merged":u.state==="opened"?"open":"closed",body:u.description??""});if(c.length<100)break}return s}let i=t.forge==="github"?100:50;for(let a=1;a<=ZJ/i;a++){let c;try{c=await n(t.forge==="github"?`/repos/${t.path}/pulls?state=all&sort=updated&direction=desc&per_page=100&page=${a}`:`/repos/${t.path}/pulls?state=all&sort=recentupdate&limit=50&page=${a}`)}catch(u){if(!(t.forge==="forgejo"&&u instanceof ei&&u.status===404&&(await n(`/repos/${t.path}`)).empty===!0))throw u;c=[]}for(let u of c){let l=u.head?.ref;if(!l?.startsWith(o))continue;let d=u.merged===!0||!!u.merged_at;s.push({branch:l,url:u.html_url,title:u.title??"",state:d?"merged":u.state==="open"?"open":"closed",body:u.body??""})}if(c.length<i)break}return s},async createPullRequest(o){return(await dc(e,t,o)).url},async commitChecks(o){if(t.forge==="gitlab"){let a=await n(`/projects/${r}/repository/commits/${o}/statuses?per_page=100`);return BP(a,c=>c.name).map(c=>({name:c.name,state:YJ(c.status)}))}let s=[];if(t.forge==="github"){let a=await n(`/repos/${t.path}/commits/${o}/check-runs?per_page=100`);for(let c of BP(a.check_runs??[],u=>u.name))s.push({name:c.name,state:c.status==="completed"?XJ[c.conclusion??""]??"failure":"pending"})}let i=await n(`/repos/${t.path}/commits/${o}/statuses?${t.forge==="github"?"per_page":"limit"}=100`);for(let a of BP(i,c=>c.context))s.push({name:a.context,state:YJ(a.status??a.state)});return s}}}function HP(e,t){let n=e.find(s=>s.name===`apply/${t}`);if(n)return n.state;let r=e.find(s=>s.name==="terragucci/apply");if(r)return r.state;let o=e.filter(s=>/(^|\/ )apply(-wave-\d+)?( \(|$)/.test(s.name));return o.some(s=>s.state==="failure")?"failure":o.length===0||o.some(s=>s.state==="pending")?"pending":"success"}import{spawnSync as Ebe}from"node:child_process";import{existsSync as Tbe,readFileSync as bh,readdirSync as Abe,rmSync as QJ,writeFileSync as qP}from"node:fs";import{join as Os,posix as Obe}from"node:path";var pn=".terraform.lock.hcl",JP="terragucci_rollout_override.tf";function Al(e){let t=new Map;for(let n of e.matchAll(/provider\s+"([^"]+)"\s*\{([^}]*)\}/g)){let r=/\bversion\s*=\s*"([^"]+)"/.exec(n[2]);r&&t.set(n[1],r[1])}return t}function KP(e,t){let n=t.toLowerCase(),r=e.toLowerCase();return r===n||r.endsWith(`/${n}`)}function ak(e,t){for(let[n,r]of e)if(KP(n,t))return{address:n,version:r}}function e2(e){return Tbe(e)?Abe(e).filter(t=>t.endsWith(".tf")&&t!==JP).sort():[]}function Pbe(e,t){let n=t.split("/").slice(-2).join("/").toLowerCase();for(let r of e2(e))for(let o of bh(Os(e,r),"utf-8").matchAll(/([A-Za-z_][\w-]*)\s*=\s*\{([^{}]*)\}/g)){let s=/\bsource\s*=\s*"([^"]+)"/.exec(o[2])?.[1]?.toLowerCase();if(s&&(s===n||s.endsWith(`/${n}`)))return o[1]}return t.split("/").pop()}function jbe(e,t,n,r){let o=t.split("/").slice(-2).join("/").toLowerCase();return e.replace(/(\{[^{}]*\})/g,s=>{let i=/\bsource\s*=\s*"([^"]+)"/.exec(s)?.[1]?.toLowerCase();return!i||!(i===o||i.endsWith(`/${o}`))?s:s.replace(/(\bversion\s*=\s*")(=?\s*)([^"]+)(")/,(a,c,u,l,d)=>l.trim()===n?`${c}${u}${r}${d}`:a)})}var Ibe=(e,t,n)=>{let r=Ebe(e,t,{cwd:n,encoding:"utf-8",env:Ve({...process.env,TF_IN_AUTOMATION:"1",TF_INPUT:"0"})});return{status:r.status,output:`${r.stdout??""}${r.stderr??""}${r.error?r.error.message:""}`}};function t2(e,t=Ibe){return(n,r,o)=>{let s=Pbe(n,r);qP(Os(n,JP),`terraform {
907
907
  required_providers {
908
908
  ${s} = {
909
909
  source = "${r}"
@@ -912,21 +912,21 @@ ${i}
912
912
  }
913
913
  }
914
914
  `);try{for(let i of[["get","-no-color"],["providers","lock","-no-color",r]]){let a=t(e,i,n);if(a.status!==0)throw new Error(`${e} ${i.join(" ")} failed: ${a.output.trim().split(`
915
- `).slice(-3).join(" ")}`)}}finally{Q2(Os(n,JP),{force:!0}),Q2(Os(n,".terraform"),{recursive:!0,force:!0})}}}function nJ(e,t,n,r,o,s){let i=Os(e,t),a=m=>t==="."?m:Obe.join(t,m),c=Al(bh(Os(i,pn),"utf-8")),u=ak(c,n);if(!u)return{refused:`${a(pn)} has no ${n}`};let l=new Map;for(let m of eJ(i)){let h=bh(Os(i,m),"utf-8"),y=jbe(h,u.address,r,o);y!==h&&(qP(Os(i,m),y),l.set(a(m),y))}let d=bh(Os(i,pn),"utf-8"),p=u.address.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");qP(Os(i,pn),d.replace(new RegExp(`\\n?provider\\s+"${p}"\\s*\\{[^}]*\\}\\n?`),`
916
- `));try{s(i,u.address,o)}catch(m){return{refused:m.message}}let f=bh(Os(i,pn),"utf-8"),g=Al(f);if(g.get(u.address)!==o)return{refused:`${a(pn)} has ${u.address} at ${g.get(u.address)??"nothing"} after locking, not ${o}`};for(let[m,h]of c)if(m!==u.address&&g.get(m)!==h)return{refused:`locking ${u.address} also moved ${m} from ${h} to ${g.get(m)??"nothing"}, so nothing was written`};return l.set(a(pn),f),{edits:l}}function rJ(e){let t=[],n=[];for(let o of e){let s=o.roots.filter(a=>o.canary.some(c=>Pe(c,a))),i=Nw(o.roots.map(a=>({root:a,dependsOn:[...o.dependsOn.get(a)??[]]})),s);s.length>0&&t.push({project:o.key,roots:i[0].roots}),n.push({project:o.key,layers:i.filter(a=>!a.canary).map(a=>a.roots)})}let r=[];t.length>0&&r.push({wave:1,canary:!0,parts:t});for(let{project:o,layers:s}of n)for(let i of s)r.push({wave:r.length+1,canary:!1,parts:[{project:o,roots:i}]});return r}function lJ(e){return e.status==="stopped"?1:e.status==="waiting"?3:0}function xi(e,t,n){try{return Cbe("git",["-C",e,...t],{encoding:"utf-8",stdio:["ignore","pipe","pipe"]})}catch(r){let o=String(r.stderr||r.message);throw new Error(n?o.split(n).join("***"):o)}}function dJ(e,t){return(n,r,o)=>{xi(e,["add","--",...r]),xi(e,[...Tl,"commit","-q","--no-verify","-m",o]),xi(e,["push","-q","--force","origin",`HEAD:refs/heads/${n}`],t)}}function uk(e){let t=sg(e);if(!t)return;let n=/^(https?):\/\/(?:[^@/]+@)?[^/]+\/(.+?)(?:\.git)?\/?$/.exec(e);if(n)return{origin:`${n[1]}://${t}`,path:n[2],host:t};let r=/^(?:[a-z+]+:\/\/)?(?:[^@]+@)?[^:/]+[:/](.+?)(?:\.git)?\/?$/.exec(e);return r?{origin:`https://${t.replace(/:\d+$/,"")}`,path:r[1].replace(/^\d+\//,""),host:t}:void 0}function pJ(e,t,n){let r=n.env??process.env,o;try{o=xi(e,["remote","get-url","origin"]).trim()}catch{throw new S("this repository has no origin remote, so there is nowhere to open a pull request")}let s=t.forge??Kr(e)?.value,i=t.url?uk(t.url):uk(o),a=s?r[t.token_env??Gn[s]]??"":"",c=s&&i?{forge:s,origin:i.origin,path:i.path,token:a}:void 0,u=i?`${i.host}/${i.path}`:".",l;if(n.forge)l=n.forge({key:u,target:c});else if(c)l=WP(n.fetch??globalThis.fetch,c);else throw new S("cannot tell which forge this repository's origin is on; set forge (and url) in terragucci.yml");if(n.mode==="apply"&&!n.forge&&!a)throw new S(`${t.token_env??Gn[s]} is not set; it holds the forge token`);return{key:u,forge:l,forgeName:s,token:a}}function fJ(e,t,n){let r=n.env??process.env,o=ot(e,t),s=po(t),i=o.forge??ir(s.host);if(!i)throw new S(`cannot tell which forge ${s.host} is; set forge for this project`);let a=o.token_env??Gn[i],c=r[a]??"";if(n.mode==="apply"&&!n.forge&&!c)throw new S(`${a} is not set; it holds the token for ${s.host}`);let{cloneUrl:u,origin:l}=yh(t,o.url),d={forge:i,origin:l,path:s.path,token:c},p=n.forge?n.forge({key:t,target:d}):WP(n.fetch??globalThis.fetch,d);return{key:t,forge:p,forgeName:i,token:c,settings:o,cloneUrl:u}}async function Mbe(e,t,n){let{key:r,forge:o}=pJ(e,t,n);xi(e,["fetch","-q","origin"]);let s=await o.defaultBranch(),i=sJ(Ps(cJ(),"terragucci-rollout-"));return xi(e,["worktree","add","-q","--detach",i,`refs/remotes/origin/${s}`]),{key:r,settings:t,dir:i,base:s,forge:o,push:dJ(i),cleanup:()=>{try{xi(e,["worktree","remove","--force",i])}catch{aJ(i,{recursive:!0,force:!0})}}}}async function Lbe(e,t,n){let{settings:r,forge:o,token:s,cloneUrl:i}=fJ(e,t,n),a=await o.defaultBranch(),c=sJ(Ps(cJ(),"terragucci-rollout-")),u=Ps(c,"repo");return xi(c,["clone","-q","--depth","1","--branch",a,wh(i,s||void 0),u],s||void 0),{key:t,settings:r,dir:u,base:a,forge:o,push:dJ(u,s||void 0),cleanup:()=>aJ(c,{recursive:!0,force:!0})}}var Gbe=new Set(["node_modules",".terraform",".terragrunt-cache"]);function gJ(e){let t=[],n=r=>{let o=Ps(e,r);for(let s of iJ(o)){if(s.startsWith(".")||Gbe.has(s))continue;let i=r?uJ.join(r,s):s;Nbe(Ps(o,s)).isDirectory()?n(i):s==="terragrunt.hcl"&&r&&t.push(r)}};return n(""),t}function Fbe(e){return[...new Set([...wt(e.dir,e.settings.roots),...gJ(e.dir)])].sort()}function zbe(e,t,n,r,o){let s={project:e,root:t};if(n.length===0)return{status:{...s,state:"absent"}};let i=n.filter(u=>u.pin.pin===null);if(i.length>0)return{status:{...s,state:"refused",reason:i.map(u=>`${u.file} ${u.call}: ${u.pin.unpinned}`).join("; ")},calls:n};let a=(u,l)=>u.version===l||u.pin.pin===l;if(n.every(u=>a(u,o)))return{status:{...s,state:"to",version:o},calls:n};if(r!==void 0&&n.every(u=>a(u,r)||a(u,o)))return{status:{...s,state:"from",version:r},calls:n};let c=[...new Set(n.filter(u=>!(r!==void 0&&a(u,r))&&!a(u,o)).map(u=>u.version))];return r!==void 0&&n.some(u=>a(u,r))?{status:{...s,state:"refused",reason:`its calls are pinned at more than one version (${[r,...c].join(", ")})`},calls:n}:{status:{...s,state:"elsewhere",version:c.join(", "),reason:`pinned at ${c.join(", ")}`},calls:n}}function Ube(e){return e.split("/").slice(-2).join("/").toLowerCase()}function Bbe(e,t,n,r,o,s){let i={project:t,root:n},a=Ps(e,n,pn);if(!VP(a)){let u=Ube(r);return iJ(Ps(e,n)).filter(d=>d.endsWith(".tf")).some(d=>ck(Ps(e,n,d),"utf-8").toLowerCase().includes(`"${u}"`))?{...i,state:"refused",reason:`it has no ${pn}, so init picks the ${u} version and the diff cannot show it`}:{...i,state:"absent"}}let c=ak(Al(ck(a,"utf-8")),r);return c?c.version===s?{...i,state:"to",version:s}:c.version===o?{...i,state:"from",version:o}:{...i,state:"elsewhere",version:c.version,reason:`locked at ${c.version}`}:{...i,state:"absent"}}function Wbe(e,t){if(e.state!=="refused"||!e.reason||e.reason===ed||e.reason===wf)return;let n={project:e.project,root:e.root};return/is a constraint/.test(e.reason)?{...n,rule:"rollout-floating-pin",message:`${e.root}: ${e.reason}. Pin one version, such as version = "${t}", so a rollout can move it and the diff shows which roots it moves.`}:/is an expression/.test(e.reason)?{...n,rule:"rollout-literal-pin",message:`${e.root}: ${e.reason}. Write the pin as a literal: a pin set from a variable or local cannot be read from the diff, so a rollout cannot tell which roots it moves.`}:/no \.terraform\.lock\.hcl/.test(e.reason)?{...n,rule:"rollout-lock-file",message:`${e.root}: ${e.reason}. Commit ${pn} so a provider bump is a reviewed change.`}:/more than one version/.test(e.reason)?{...n,rule:"rollout-one-pin",message:`${e.root}: ${e.reason}. Pin every call of the module at one version.`}:{...n,rule:"rollout-pin",message:`${e.root}: ${e.reason}. Pin it to one version (a ?ref=, ?tag= or version) so a rollout can move it.`}}var mJ="terragucci-rollout";function oJ(e){return e.replace(/^[a-z]+:\/\//,"").replace(/[^A-Za-z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,60)}function Hbe(e,t,n){return`terragucci/rollout/${oJ(e)}-${oJ(t)}/wave-${n}`}function hJ(e){let t=new RegExp(`<!-- ${mJ} (\\{.*?\\}) -->`).exec(e);try{return t?JSON.parse(t[1]):void 0}catch{return}}function qbe(e){return hJ(e)?.roots}async function ZP(e,t){let n=t.mode??"dry-run",r;try{r=xi(e,["rev-parse","--show-toplevel"]).trim()}catch{throw new S("terragucci rollout runs in a git repository")}let o=t.config??fe(r),s=o?await Re(o):{};if(t.kind==="provider"&&!t.to)throw new S("a provider rollout needs the version to move to");let i=t.parser??(t.kind==="module"?await yo():void 0),a=[];try{if(s.projects)for(let c of Object.keys(s.projects))a.push(await Lbe(s,c,{...t,mode:n}));else{let c=ue(s),u=et(r,c).refuses("rollouts");if(u)throw new S(`terragucci rollout: ${u}`);a.push(await Mbe(r,c,{...t,mode:n}))}return await Kbe(r,s,a,{...t,mode:n,parser:i})}finally{for(let c of a)c.cleanup()}}var Jbe="terragucci/rollout/";function YP(e){return e.rollouts.some(t=>t.action==="failed")?1:0}async function yJ(e,t={}){let n=t.mode??"dry-run",r;try{r=xi(e,["rev-parse","--show-toplevel"]).trim()}catch{throw new S("terragucci respond rollout runs in a git repository")}let o=t.config??fe(r),s=o?await Re(o):{},i=s.projects?void 0:et(r,ue(s)).refuses("rollouts");if(i)throw new S(`terragucci respond rollout: ${i}`);let a=s.projects?Object.keys(s.projects).map(l=>fJ(s,l,{...t,mode:n})):[pJ(r,ue(s),{...t,mode:n})],c=new Map;for(let l of a){let d=new Set;for(let p of await l.forge.listPullRequests(Jbe)){if(d.has(p.branch))continue;d.add(p.branch);let f=hJ(p.body);if(!f||f.kind!=="module"&&f.kind!=="provider"||!f.name||!f.to||!Number.isInteger(f.wave))continue;let g=/, wave \d+ of (\d+)$/.exec(p.title),m={...f,...f.waves===void 0&&g?{waves:Number(g[1])}:{}},h=JSON.stringify([f.kind,f.name,f.to]);c.set(h,[...c.get(h)??[],{marker:m,state:p.state,url:p.url}])}}let u=[];for(let l of c.values()){let d=Math.max(...l.map(w=>w.marker.wave)),p=l.filter(w=>w.marker.wave===d),f=p[0].marker,g=p.map(w=>w.marker.waves).find(w=>w!==void 0),m={kind:f.kind,name:f.name,...f.from!==void 0?{from:f.from}:{},to:f.to,wave:d,...g!==void 0?{waves:g}:{},action:"ran",pullRequests:p.map(w=>w.url)},h=p.filter(w=>w.state==="open"),y=p.filter(w=>w.state==="closed");if(h.length>0)u.push({...m,action:"waiting",reason:`wave ${d} is open: ${h.map(w=>w.url).join(", ")}`});else if(y.length>0)u.push({...m,action:"stopped",reason:`wave ${d} was closed without merging: ${y.map(w=>w.url).join(", ")}`});else if(g!==void 0&&d>=g)u.push({...m,action:"done",reason:`wave ${d} of ${g}, the last, merged`});else try{let w=await ZP(r,{...t,mode:n,kind:f.kind,name:f.name,to:f.to,...f.from!==void 0?{from:f.from}:{}});u.push({...m,result:w})}catch(w){u.push({...m,action:"failed",reason:w.message})}}return{mode:n,rollouts:u}}function wJ(e){return e.rollouts.length===0?"no rollout in flight":e.rollouts.map(t=>t.result?XP(t.result):`${t.kind==="module"?t.name:`provider ${t.name}`} ${t.from??"?"} -> ${t.to}: ${t.action} (${t.reason})`).join(`
917
- `)}async function Kbe(e,t,n,r){let o=r.kind==="module"||n.some($=>gJ($.dir).length>0),s=r.parser??(o?await yo():void 0),i=new Map,a=new Map;r.kind==="module"&&(r={...r,aliases:V2(r.name,[...n.map($=>$.settings),t.defaults??{}])});let c=new Map(n.map($=>[$.key,et($.dir,$.settings).refuses("rollouts")]));for(let $ of n)if(i.set($.key,c.get($.key)?[]:Fbe($)),r.kind==="module")for(let _ of i.get($.key))a.set(`${$.key}\0${_}`,await pT($.dir,_,r.name,s,r.aliases));let u=r.to,l;if(!u){let $=new Set;for(let R of[...n.map(x=>x.settings),t.defaults??{}]){let x=R.modules?.publish;for(let A of Array.isArray(x)?x:x?[x]:[])A.startsWith("oci://")&&$.add(K2(A,r.name))}for(let R of a.values())for(let x of R)x.pin.module.startsWith("oci://")&&$.add(x.pin.module.split("?")[0]);let _=new Set(r.aliases??[]);for(let R of a.values())for(let x of R)x.pin.at==="version"&&vc(x.pin.module)&&_.add(x.pin.module);let E=await J2({module:r.name,repos:[e,...n.map(R=>R.dir)],oci:[...$],registries:[..._],env:r.env,fetch:r.registryFetch});if(!E)throw new S(`found no published version of ${r.name}; name the version to roll out`);u=E.version,l=E.from}let d=r.kind==="module"?hc(u):u,p=r.from!==void 0?hc(r.from):void 0;if(p===void 0){let $=new Set;if(r.kind==="module")for(let _ of a.values())for(let E of _)E.version!==null&&E.version!==d&&$.add(E.version);else for(let _ of n)for(let E of i.get(_.key)){let R=Ps(_.dir,E,pn),x=VP(R)?ak(Al(ck(R,"utf-8")),r.name):void 0;x&&x.version!==d&&$.add(x.version)}if($.size>1)throw new S(`${r.name} is at ${[...$].sort().join(", ")} across the roots; pass --from to say which version moves`);p=[...$][0]}let f=[],g=new Map(n.map($=>[$.key,$])),m=new Map;for(let $ of n){let _=[],E=c.get($.key);E&&f.push({project:$.key,root:".",state:"refused",reason:E});for(let R of i.get($.key)){let x=r.kind==="module"?zbe($.key,R,a.get(`${$.key}\0${R}`),p,d).status:Bbe($.dir,$.key,R,r.name,p,d);x.state!=="absent"&&f.push(x),(x.state==="from"||x.state==="to")&&_.push(R)}m.set($.key,_)}let h=($,_)=>f.find(E=>E.project===$&&E.root===_)?.state,y=[],w=[];for(let $ of n){let _=m.get($.key);if(_.length===0)continue;let E=wn($.dir,i.get($.key));for(let R of _){let x=Ps($.dir,R,"terragrunt.hcl");if(VP(x))for(let A of await Dw(R,ck(x,"utf-8"),s))E.get(R)?.add(A)??E.set(R,new Set([A]))}w.push({key:$.key,roots:_,dependsOn:E,canary:$.settings.waves?.canary??[]})}try{y.push(...rJ(w))}catch($){throw new S($.message)}let b={kind:r.kind,name:r.name,...p!==void 0?{from:p}:{},to:d,...l?{discovered:l}:{},mode:r.mode,status:"complete",waves:y.map($=>({wave:$.wave,canary:$.canary,parts:$.parts.map(_=>({..._,branch:Hbe(r.name,d,$.wave),state:"not-reached"}))})),roots:f,tips:[]},k=$=>(r.locker??tJ)($.settings.binary??Ks($.dir,i.get($.key)).value);for(let $ of b.waves){let _=[],E=!1;for(let R of $.parts){let x=g.get(R.project),A=await x.forge.findPullRequest(R.branch);if(A&&(R.pullRequest=A.url),A?.state==="open")R.state="open",E=!0;else if(A?.state==="closed")R.state="closed",b.stop??=`wave ${$.wave}: ${A.url} was closed without merging`;else if(A?.state==="merged"){let O=qbe(A.body)??R.roots,I=A.mergeCommit?await x.forge.commitChecks(A.mergeCommit):[];R.failed=O.filter(N=>HP(I,N)==="failure"),R.pending=O.filter(N=>HP(I,N)==="pending"),R.failed.length>0?(R.state="failed",b.stop??=`wave ${$.wave}: ${R.failed.join(", ")} in ${R.project} failed to apply after ${A.url} merged`):R.pending.length>0?(R.state="waiting-apply",E=!0):R.state="applied"}else R.roots.some(O=>h(R.project,O)==="from")?_.push(R):R.state="nothing-to-move"}if(b.stop){b.status="stopped";break}if(_.length>0){for(let R of _)await Vbe(g.get(R.project),R,$,b,r,k,s);b.stop?b.status="stopped":b.status=r.mode==="apply"?"opened":"would-open";break}if(E){b.status="waiting";break}}let v=$=>g.get($)?.settings.tips!==!1;return b.tips=f.filter($=>v($.project)).map($=>Wbe($,d)).filter($=>$!==void 0),b}async function Vbe(e,t,n,r,o,s,i){let a=t.roots.filter(g=>r.roots.find(m=>m.project===t.project&&m.root===g)?.state==="from");if(o.mode==="dry-run"&&o.kind==="provider"){t.files=a.map(g=>g==="."?pn:uJ.join(g,pn)).sort(),t.state="would-open";return}let c=new Map;for(let g of a){let m=o.kind==="module"?await wF(e.dir,await pT(e.dir,g,o.name,i,o.aliases),r.from,r.to,i):nJ(e.dir,g,o.name,r.from,r.to,s(e));if("refused"in m){t.state="failed",t.reason=m.refused,r.stop??=`wave ${n.wave}: ${g} in ${t.project} cannot move: ${m.refused}`;return}for(let[h,y]of m.edits)c.set(h,y)}if(t.files=[...c.keys()].sort(),o.mode==="dry-run"){t.state="would-open";return}for(let[g,m]of c)Dbe(Ps(e.dir,g),m);let u=r.waves.length,l=o.kind==="module"?`${o.name} ${r.from} -> ${r.to}`:`provider ${o.name} ${r.from} -> ${r.to}`,d=`terragucci rollout: ${l}, wave ${n.wave} of ${u}`,p=r.roots.filter(g=>g.project===t.project&&(g.state==="refused"||g.state==="elsewhere")),f=[`<!-- ${mJ} ${JSON.stringify({kind:o.kind,name:o.name,from:r.from,to:r.to,wave:n.wave,waves:u,roots:a})} -->`,`Wave ${n.wave} of ${u}${n.canary?" (canaries)":""}: ${l}.`,"",`This pull request moves ${o.kind==="module"?"the pin":`\`${pn}\``} for these roots and changes no other file:`,"",...a.map(g=>`- \`${g}\``),...p.length>0?["","Not moved by this rollout:","",...p.map(g=>`- \`${g.root}\`: ${g.reason}`)]:[],"",n.wave<u?`Wave ${n.wave+1} opens on a later run, once every pull request of this wave has merged and its roots have applied.`:"This is the last wave."].join(`
915
+ `).slice(-3).join(" ")}`)}}finally{QJ(Os(n,JP),{force:!0}),QJ(Os(n,".terraform"),{recursive:!0,force:!0})}}}function n2(e,t,n,r,o,s){let i=Os(e,t),a=m=>t==="."?m:Obe.join(t,m),c=Al(bh(Os(i,pn),"utf-8")),u=ak(c,n);if(!u)return{refused:`${a(pn)} has no ${n}`};let l=new Map;for(let m of e2(i)){let h=bh(Os(i,m),"utf-8"),y=jbe(h,u.address,r,o);y!==h&&(qP(Os(i,m),y),l.set(a(m),y))}let d=bh(Os(i,pn),"utf-8"),p=u.address.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");qP(Os(i,pn),d.replace(new RegExp(`\\n?provider\\s+"${p}"\\s*\\{[^}]*\\}\\n?`),`
916
+ `));try{s(i,u.address,o)}catch(m){return{refused:m.message}}let f=bh(Os(i,pn),"utf-8"),g=Al(f);if(g.get(u.address)!==o)return{refused:`${a(pn)} has ${u.address} at ${g.get(u.address)??"nothing"} after locking, not ${o}`};for(let[m,h]of c)if(m!==u.address&&g.get(m)!==h)return{refused:`locking ${u.address} also moved ${m} from ${h} to ${g.get(m)??"nothing"}, so nothing was written`};return l.set(a(pn),f),{edits:l}}function r2(e){let t=[],n=[];for(let o of e){let s=o.roots.filter(a=>o.canary.some(c=>Pe(c,a))),i=Nw(o.roots.map(a=>({root:a,dependsOn:[...o.dependsOn.get(a)??[]]})),s);s.length>0&&t.push({project:o.key,roots:i[0].roots}),n.push({project:o.key,layers:i.filter(a=>!a.canary).map(a=>a.roots)})}let r=[];t.length>0&&r.push({wave:1,canary:!0,parts:t});for(let{project:o,layers:s}of n)for(let i of s)r.push({wave:r.length+1,canary:!1,parts:[{project:o,roots:i}]});return r}function l2(e){return e.status==="stopped"?1:e.status==="waiting"?3:0}function xi(e,t,n){try{return Cbe("git",["-C",e,...t],{encoding:"utf-8",stdio:["ignore","pipe","pipe"]})}catch(r){let o=String(r.stderr||r.message);throw new Error(n?o.split(n).join("***"):o)}}function d2(e,t){return(n,r,o)=>{xi(e,["add","--",...r]),xi(e,[...Tl,"commit","-q","--no-verify","-m",o]),xi(e,["push","-q","--force","origin",`HEAD:refs/heads/${n}`],t)}}function uk(e){let t=sg(e);if(!t)return;let n=/^(https?):\/\/(?:[^@/]+@)?[^/]+\/(.+?)(?:\.git)?\/?$/.exec(e);if(n)return{origin:`${n[1]}://${t}`,path:n[2],host:t};let r=/^(?:[a-z+]+:\/\/)?(?:[^@]+@)?[^:/]+[:/](.+?)(?:\.git)?\/?$/.exec(e);return r?{origin:`https://${t.replace(/:\d+$/,"")}`,path:r[1].replace(/^\d+\//,""),host:t}:void 0}function p2(e,t,n){let r=n.env??process.env,o;try{o=xi(e,["remote","get-url","origin"]).trim()}catch{throw new S("this repository has no origin remote, so there is nowhere to open a pull request")}let s=t.forge??Kr(e)?.value,i=t.url?uk(t.url):uk(o),a=s?r[t.token_env??Gn[s]]??"":"",c=s&&i?{forge:s,origin:i.origin,path:i.path,token:a}:void 0,u=i?`${i.host}/${i.path}`:".",l;if(n.forge)l=n.forge({key:u,target:c});else if(c)l=WP(n.fetch??globalThis.fetch,c);else throw new S("cannot tell which forge this repository's origin is on; set forge (and url) in terragucci.yml");if(n.mode==="apply"&&!n.forge&&!a)throw new S(`${t.token_env??Gn[s]} is not set; it holds the forge token`);return{key:u,forge:l,forgeName:s,token:a}}function f2(e,t,n){let r=n.env??process.env,o=ot(e,t),s=po(t),i=o.forge??ir(s.host);if(!i)throw new S(`cannot tell which forge ${s.host} is; set forge for this project`);let a=o.token_env??Gn[i],c=r[a]??"";if(n.mode==="apply"&&!n.forge&&!c)throw new S(`${a} is not set; it holds the token for ${s.host}`);let{cloneUrl:u,origin:l}=yh(t,o.url),d={forge:i,origin:l,path:s.path,token:c},p=n.forge?n.forge({key:t,target:d}):WP(n.fetch??globalThis.fetch,d);return{key:t,forge:p,forgeName:i,token:c,settings:o,cloneUrl:u}}async function Mbe(e,t,n){let{key:r,forge:o}=p2(e,t,n);xi(e,["fetch","-q","origin"]);let s=await o.defaultBranch(),i=s2(Ps(c2(),"terragucci-rollout-"));return xi(e,["worktree","add","-q","--detach",i,`refs/remotes/origin/${s}`]),{key:r,settings:t,dir:i,base:s,forge:o,push:d2(i),cleanup:()=>{try{xi(e,["worktree","remove","--force",i])}catch{a2(i,{recursive:!0,force:!0})}}}}async function Lbe(e,t,n){let{settings:r,forge:o,token:s,cloneUrl:i}=f2(e,t,n),a=await o.defaultBranch(),c=s2(Ps(c2(),"terragucci-rollout-")),u=Ps(c,"repo");return xi(c,["clone","-q","--depth","1","--branch",a,wh(i,s||void 0),u],s||void 0),{key:t,settings:r,dir:u,base:a,forge:o,push:d2(u,s||void 0),cleanup:()=>a2(c,{recursive:!0,force:!0})}}var Gbe=new Set(["node_modules",".terraform",".terragrunt-cache"]);function g2(e){let t=[],n=r=>{let o=Ps(e,r);for(let s of i2(o)){if(s.startsWith(".")||Gbe.has(s))continue;let i=r?u2.join(r,s):s;Nbe(Ps(o,s)).isDirectory()?n(i):s==="terragrunt.hcl"&&r&&t.push(r)}};return n(""),t}function Fbe(e){return[...new Set([...wt(e.dir,e.settings.roots),...g2(e.dir)])].sort()}function zbe(e,t,n,r,o){let s={project:e,root:t};if(n.length===0)return{status:{...s,state:"absent"}};let i=n.filter(u=>u.pin.pin===null);if(i.length>0)return{status:{...s,state:"refused",reason:i.map(u=>`${u.file} ${u.call}: ${u.pin.unpinned}`).join("; ")},calls:n};let a=(u,l)=>u.version===l||u.pin.pin===l;if(n.every(u=>a(u,o)))return{status:{...s,state:"to",version:o},calls:n};if(r!==void 0&&n.every(u=>a(u,r)||a(u,o)))return{status:{...s,state:"from",version:r},calls:n};let c=[...new Set(n.filter(u=>!(r!==void 0&&a(u,r))&&!a(u,o)).map(u=>u.version))];return r!==void 0&&n.some(u=>a(u,r))?{status:{...s,state:"refused",reason:`its calls are pinned at more than one version (${[r,...c].join(", ")})`},calls:n}:{status:{...s,state:"elsewhere",version:c.join(", "),reason:`pinned at ${c.join(", ")}`},calls:n}}function Ube(e){return e.split("/").slice(-2).join("/").toLowerCase()}function Bbe(e,t,n,r,o,s){let i={project:t,root:n},a=Ps(e,n,pn);if(!VP(a)){let u=Ube(r);return i2(Ps(e,n)).filter(d=>d.endsWith(".tf")).some(d=>ck(Ps(e,n,d),"utf-8").toLowerCase().includes(`"${u}"`))?{...i,state:"refused",reason:`it has no ${pn}, so init picks the ${u} version and the diff cannot show it`}:{...i,state:"absent"}}let c=ak(Al(ck(a,"utf-8")),r);return c?c.version===s?{...i,state:"to",version:s}:c.version===o?{...i,state:"from",version:o}:{...i,state:"elsewhere",version:c.version,reason:`locked at ${c.version}`}:{...i,state:"absent"}}function Wbe(e,t){if(e.state!=="refused"||!e.reason||e.reason===ed||e.reason===wf)return;let n={project:e.project,root:e.root};return/is a constraint/.test(e.reason)?{...n,rule:"rollout-floating-pin",message:`${e.root}: ${e.reason}. Pin one version, such as version = "${t}", so a rollout can move it and the diff shows which roots it moves.`}:/is an expression/.test(e.reason)?{...n,rule:"rollout-literal-pin",message:`${e.root}: ${e.reason}. Write the pin as a literal: a pin set from a variable or local cannot be read from the diff, so a rollout cannot tell which roots it moves.`}:/no \.terraform\.lock\.hcl/.test(e.reason)?{...n,rule:"rollout-lock-file",message:`${e.root}: ${e.reason}. Commit ${pn} so a provider bump is a reviewed change.`}:/more than one version/.test(e.reason)?{...n,rule:"rollout-one-pin",message:`${e.root}: ${e.reason}. Pin every call of the module at one version.`}:{...n,rule:"rollout-pin",message:`${e.root}: ${e.reason}. Pin it to one version (a ?ref=, ?tag= or version) so a rollout can move it.`}}var m2="terragucci-rollout";function o2(e){return e.replace(/^[a-z]+:\/\//,"").replace(/[^A-Za-z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,60)}function Hbe(e,t,n){return`terragucci/rollout/${o2(e)}-${o2(t)}/wave-${n}`}function h2(e){let t=new RegExp(`<!-- ${m2} (\\{.*?\\}) -->`).exec(e);try{return t?JSON.parse(t[1]):void 0}catch{return}}function qbe(e){return h2(e)?.roots}async function ZP(e,t){let n=t.mode??"dry-run",r;try{r=xi(e,["rev-parse","--show-toplevel"]).trim()}catch{throw new S("terragucci rollout runs in a git repository")}let o=t.config??fe(r),s=o?await Re(o):{};if(t.kind==="provider"&&!t.to)throw new S("a provider rollout needs the version to move to");let i=t.parser??(t.kind==="module"?await yo():void 0),a=[];try{if(s.projects)for(let c of Object.keys(s.projects))a.push(await Lbe(s,c,{...t,mode:n}));else{let c=ue(s),u=et(r,c).refuses("rollouts");if(u)throw new S(`terragucci rollout: ${u}`);a.push(await Mbe(r,c,{...t,mode:n}))}return await Kbe(r,s,a,{...t,mode:n,parser:i})}finally{for(let c of a)c.cleanup()}}var Jbe="terragucci/rollout/";function YP(e){return e.rollouts.some(t=>t.action==="failed")?1:0}async function y2(e,t={}){let n=t.mode??"dry-run",r;try{r=xi(e,["rev-parse","--show-toplevel"]).trim()}catch{throw new S("terragucci respond rollout runs in a git repository")}let o=t.config??fe(r),s=o?await Re(o):{},i=s.projects?void 0:et(r,ue(s)).refuses("rollouts");if(i)throw new S(`terragucci respond rollout: ${i}`);let a=s.projects?Object.keys(s.projects).map(l=>f2(s,l,{...t,mode:n})):[p2(r,ue(s),{...t,mode:n})],c=new Map;for(let l of a){let d=new Set;for(let p of await l.forge.listPullRequests(Jbe)){if(d.has(p.branch))continue;d.add(p.branch);let f=h2(p.body);if(!f||f.kind!=="module"&&f.kind!=="provider"||!f.name||!f.to||!Number.isInteger(f.wave))continue;let g=/, wave \d+ of (\d+)$/.exec(p.title),m={...f,...f.waves===void 0&&g?{waves:Number(g[1])}:{}},h=JSON.stringify([f.kind,f.name,f.to]);c.set(h,[...c.get(h)??[],{marker:m,state:p.state,url:p.url}])}}let u=[];for(let l of c.values()){let d=Math.max(...l.map(w=>w.marker.wave)),p=l.filter(w=>w.marker.wave===d),f=p[0].marker,g=p.map(w=>w.marker.waves).find(w=>w!==void 0),m={kind:f.kind,name:f.name,...f.from!==void 0?{from:f.from}:{},to:f.to,wave:d,...g!==void 0?{waves:g}:{},action:"ran",pullRequests:p.map(w=>w.url)},h=p.filter(w=>w.state==="open"),y=p.filter(w=>w.state==="closed");if(h.length>0)u.push({...m,action:"waiting",reason:`wave ${d} is open: ${h.map(w=>w.url).join(", ")}`});else if(y.length>0)u.push({...m,action:"stopped",reason:`wave ${d} was closed without merging: ${y.map(w=>w.url).join(", ")}`});else if(g!==void 0&&d>=g)u.push({...m,action:"done",reason:`wave ${d} of ${g}, the last, merged`});else try{let w=await ZP(r,{...t,mode:n,kind:f.kind,name:f.name,to:f.to,...f.from!==void 0?{from:f.from}:{}});u.push({...m,result:w})}catch(w){u.push({...m,action:"failed",reason:w.message})}}return{mode:n,rollouts:u}}function w2(e){return e.rollouts.length===0?"no rollout in flight":e.rollouts.map(t=>t.result?XP(t.result):`${t.kind==="module"?t.name:`provider ${t.name}`} ${t.from??"?"} -> ${t.to}: ${t.action} (${t.reason})`).join(`
917
+ `)}async function Kbe(e,t,n,r){let o=r.kind==="module"||n.some($=>g2($.dir).length>0),s=r.parser??(o?await yo():void 0),i=new Map,a=new Map;r.kind==="module"&&(r={...r,aliases:VJ(r.name,[...n.map($=>$.settings),t.defaults??{}])});let c=new Map(n.map($=>[$.key,et($.dir,$.settings).refuses("rollouts")]));for(let $ of n)if(i.set($.key,c.get($.key)?[]:Fbe($)),r.kind==="module")for(let _ of i.get($.key))a.set(`${$.key}\0${_}`,await pT($.dir,_,r.name,s,r.aliases));let u=r.to,l;if(!u){let $=new Set;for(let R of[...n.map(x=>x.settings),t.defaults??{}]){let x=R.modules?.publish;for(let A of Array.isArray(x)?x:x?[x]:[])A.startsWith("oci://")&&$.add(KJ(A,r.name))}for(let R of a.values())for(let x of R)x.pin.module.startsWith("oci://")&&$.add(x.pin.module.split("?")[0]);let _=new Set(r.aliases??[]);for(let R of a.values())for(let x of R)x.pin.at==="version"&&vc(x.pin.module)&&_.add(x.pin.module);let E=await JJ({module:r.name,repos:[e,...n.map(R=>R.dir)],oci:[...$],registries:[..._],env:r.env,fetch:r.registryFetch});if(!E)throw new S(`found no published version of ${r.name}; name the version to roll out`);u=E.version,l=E.from}let d=r.kind==="module"?hc(u):u,p=r.from!==void 0?hc(r.from):void 0;if(p===void 0){let $=new Set;if(r.kind==="module")for(let _ of a.values())for(let E of _)E.version!==null&&E.version!==d&&$.add(E.version);else for(let _ of n)for(let E of i.get(_.key)){let R=Ps(_.dir,E,pn),x=VP(R)?ak(Al(ck(R,"utf-8")),r.name):void 0;x&&x.version!==d&&$.add(x.version)}if($.size>1)throw new S(`${r.name} is at ${[...$].sort().join(", ")} across the roots; pass --from to say which version moves`);p=[...$][0]}let f=[],g=new Map(n.map($=>[$.key,$])),m=new Map;for(let $ of n){let _=[],E=c.get($.key);E&&f.push({project:$.key,root:".",state:"refused",reason:E});for(let R of i.get($.key)){let x=r.kind==="module"?zbe($.key,R,a.get(`${$.key}\0${R}`),p,d).status:Bbe($.dir,$.key,R,r.name,p,d);x.state!=="absent"&&f.push(x),(x.state==="from"||x.state==="to")&&_.push(R)}m.set($.key,_)}let h=($,_)=>f.find(E=>E.project===$&&E.root===_)?.state,y=[],w=[];for(let $ of n){let _=m.get($.key);if(_.length===0)continue;let E=wn($.dir,i.get($.key));for(let R of _){let x=Ps($.dir,R,"terragrunt.hcl");if(VP(x))for(let A of await Dw(R,ck(x,"utf-8"),s))E.get(R)?.add(A)??E.set(R,new Set([A]))}w.push({key:$.key,roots:_,dependsOn:E,canary:$.settings.waves?.canary??[]})}try{y.push(...r2(w))}catch($){throw new S($.message)}let b={kind:r.kind,name:r.name,...p!==void 0?{from:p}:{},to:d,...l?{discovered:l}:{},mode:r.mode,status:"complete",waves:y.map($=>({wave:$.wave,canary:$.canary,parts:$.parts.map(_=>({..._,branch:Hbe(r.name,d,$.wave),state:"not-reached"}))})),roots:f,tips:[]},k=$=>(r.locker??t2)($.settings.binary??Ks($.dir,i.get($.key)).value);for(let $ of b.waves){let _=[],E=!1;for(let R of $.parts){let x=g.get(R.project),A=await x.forge.findPullRequest(R.branch);if(A&&(R.pullRequest=A.url),A?.state==="open")R.state="open",E=!0;else if(A?.state==="closed")R.state="closed",b.stop??=`wave ${$.wave}: ${A.url} was closed without merging`;else if(A?.state==="merged"){let O=qbe(A.body)??R.roots,I=A.mergeCommit?await x.forge.commitChecks(A.mergeCommit):[];R.failed=O.filter(N=>HP(I,N)==="failure"),R.pending=O.filter(N=>HP(I,N)==="pending"),R.failed.length>0?(R.state="failed",b.stop??=`wave ${$.wave}: ${R.failed.join(", ")} in ${R.project} failed to apply after ${A.url} merged`):R.pending.length>0?(R.state="waiting-apply",E=!0):R.state="applied"}else R.roots.some(O=>h(R.project,O)==="from")?_.push(R):R.state="nothing-to-move"}if(b.stop){b.status="stopped";break}if(_.length>0){for(let R of _)await Vbe(g.get(R.project),R,$,b,r,k,s);b.stop?b.status="stopped":b.status=r.mode==="apply"?"opened":"would-open";break}if(E){b.status="waiting";break}}let v=$=>g.get($)?.settings.tips!==!1;return b.tips=f.filter($=>v($.project)).map($=>Wbe($,d)).filter($=>$!==void 0),b}async function Vbe(e,t,n,r,o,s,i){let a=t.roots.filter(g=>r.roots.find(m=>m.project===t.project&&m.root===g)?.state==="from");if(o.mode==="dry-run"&&o.kind==="provider"){t.files=a.map(g=>g==="."?pn:u2.join(g,pn)).sort(),t.state="would-open";return}let c=new Map;for(let g of a){let m=o.kind==="module"?await wF(e.dir,await pT(e.dir,g,o.name,i,o.aliases),r.from,r.to,i):n2(e.dir,g,o.name,r.from,r.to,s(e));if("refused"in m){t.state="failed",t.reason=m.refused,r.stop??=`wave ${n.wave}: ${g} in ${t.project} cannot move: ${m.refused}`;return}for(let[h,y]of m.edits)c.set(h,y)}if(t.files=[...c.keys()].sort(),o.mode==="dry-run"){t.state="would-open";return}for(let[g,m]of c)Dbe(Ps(e.dir,g),m);let u=r.waves.length,l=o.kind==="module"?`${o.name} ${r.from} -> ${r.to}`:`provider ${o.name} ${r.from} -> ${r.to}`,d=`terragucci rollout: ${l}, wave ${n.wave} of ${u}`,p=r.roots.filter(g=>g.project===t.project&&(g.state==="refused"||g.state==="elsewhere")),f=[`<!-- ${m2} ${JSON.stringify({kind:o.kind,name:o.name,from:r.from,to:r.to,wave:n.wave,waves:u,roots:a})} -->`,`Wave ${n.wave} of ${u}${n.canary?" (canaries)":""}: ${l}.`,"",`This pull request moves ${o.kind==="module"?"the pin":`\`${pn}\``} for these roots and changes no other file:`,"",...a.map(g=>`- \`${g}\``),...p.length>0?["","Not moved by this rollout:","",...p.map(g=>`- \`${g.root}\`: ${g.reason}`)]:[],"",n.wave<u?`Wave ${n.wave+1} opens on a later run, once every pull request of this wave has merged and its roots have applied.`:"This is the last wave."].join(`
918
918
  `);e.push(t.branch,t.files,`${d}
919
919
 
920
920
  Roots: ${a.join(", ")}
921
921
  `),t.pullRequest=await e.forge.createPullRequest({base:e.base,head:t.branch,title:d,body:f}),t.state="opened"}var Zbe={applied:"merged and applied","nothing-to-move":"nothing to move",opened:"pull request opened","would-open":"next; would open its pull request",open:"pull request open, waiting for merge","waiting-apply":"merged, waiting for apply",failed:"failed",closed:"pull request closed without merging","not-reached":"not opened"};function XP(e){let n=[`${e.kind==="module"?e.name:`provider ${e.name}`} ${e.from??"?"} -> ${e.to}${e.discovered?` (newest published: ${e.discovered})`:""}: ${e.status}${e.stop?` (${e.stop})`:""}`],r=new Set(e.waves.flatMap(o=>o.parts.map(s=>s.project))).size>1;for(let o of e.waves){n.push(` wave ${o.wave}${o.canary?" (canaries)":""}`);for(let s of o.parts)n.push(` ${r?`${s.project}: `:""}${Zbe[s.state]}${s.pullRequest?` ${s.pullRequest}`:""}${s.reason?` (${s.reason})`:""}`),n.push(` roots: ${s.roots.join(", ")}`),s.pending?.length&&n.push(` apply pending: ${s.pending.join(", ")}`),s.failed?.length&&n.push(` apply failed: ${s.failed.join(", ")}`),s.files?.length&&n.push(` files: ${s.files.join(", ")}`)}for(let o of e.roots.filter(s=>s.state==="refused"||s.state==="elsewhere"))n.push(` not in the rollout: ${r?`${o.project} `:""}${o.root}: ${o.reason}`);for(let o of e.tips)n.push(` tip (${o.rule}): ${o.message}`);return e.mode==="dry-run"&&(e.status==="would-open"||e.status==="complete")&&n.push("dry run: nothing was opened"),n.join(`
922
- `)}function bJ(e,t){let n=i=>{let a=t[i];if(a===!0)throw new S(`--${i} needs a value`);return a},r=cd(n("mode")??"dry-run"),o=n("provider"),s="usage: terragucci rollout <module> [<version>] | terragucci rollout --provider <address> <version>";if(o){if(e.length!==1)throw new S(s);return{kind:"provider",name:o,to:e[0],from:n("from"),mode:r,config:n("config")}}if(e.length<1||e.length>2)throw new S(s);return{kind:"module",name:e[0],to:e[1],from:n("from"),mode:r,config:n("config")}}function no(e,t){try{return Ybe("git",["-C",e,...t],{encoding:"utf-8",stdio:["ignore","pipe","pipe"]}).trim()}catch(n){throw new S(`git ${t[0]}: ${String(n.stderr||n.message).trim()}`)}}function vh(e,t,n){let r="";try{r=no(e,["remote","get-url","origin"])}catch{throw new S("this repository has no origin remote, so there is nowhere to open a pull request")}let o=t.forge??Kr(e)?.value,s=uk(t.url??r);if(!o||!s)throw new S("cannot tell which forge this repository's origin is on; set forge (and url) in terragucci.yml");return{forge:o,origin:s.origin,path:s.path,token:n[t.token_env??Gn[o]]??""}}function QP(e,t){let n=lk(Qbe(lk(nve(),"terragucci-respond-")),"tree");return no(e,["worktree","add","-q","--detach",n,t]),{dir:n,done:()=>{try{no(e,["worktree","remove","--force",n])}catch{eve(n,{recursive:!0,force:!0})}}}}async function dk(e,t,n,r){if(r.mode==="dry-run")return n.map(u=>({branch:u.branch,title:u.title,files:[...u.files.keys(),...u.expect??[]].sort(),state:"would-open"}));let o=r.env??process.env,s=vh(e,t,o);if(!s.token)throw new S(`${t.token_env??Gn[s.forge]} is not set; it holds the forge token`);let i=r.fetch??globalThis.fetch,a,c=[];for(let u of n){let l=u.base??(a??=await lc(i,s));no(e,["fetch","-q","origin",l]);let d=QP(e,"FETCH_HEAD");try{for(let[g,m]of u.files)Xbe(rve(lk(d.dir,g)),{recursive:!0}),tve(lk(d.dir,g),m);u.run?.(d.dir),no(d.dir,["add","-A"]);let p=no(d.dir,["diff","--cached","--name-only"]).split(`
923
- `).filter(Boolean);if(p.length===0){c.push({branch:u.branch,title:u.title,files:p,state:"nothing-to-change"});continue}no(d.dir,[...Tl,"commit","-q","--no-verify","-m",u.title]),no(d.dir,["push","-q","--force","origin",`HEAD:refs/heads/${u.branch}`]);let f=await dc(i,s,{head:u.branch,base:l,title:u.title,body:u.body});c.push({branch:u.branch,title:u.title,files:p,state:f.existing?"open":"opened",pullRequest:f.url})}finally{d.done()}}return c}var Pp="tf-unlock",pk=`_gates/${Pp}.jsonl`,$J=`_gates/${Pp}/done.jsonl`,Ol={released:0,refused:1,waiting:3,other:4},ive=120*1e3;function xJ(e){try{let t=JSON.parse(e);if(!t||typeof t!="object"||typeof t.ID!="string"||t.ID==="")return;let n=r=>typeof t[r]=="string"?t[r]:void 0;return{ID:t.ID,Operation:n("Operation"),Info:n("Info"),Who:n("Who"),Version:n("Version"),Created:n("Created"),Path:n("Path")}}catch{return}}var ave=(e,t,n)=>Rn("terragucci-state-lock",{root:e,location:t,id:n}),cve=(e,t,n=!1)=>`terragucci approve unlock ${e} --plan ${t}${n?" --sign":""}`,kh=e=>e?Date.parse(e):Number.NaN;async function uve(e,t){let n=new Map;if(t.forge==="gitlab"){for(let s of["running","pending","preparing","waiting_for_resource"]){let i=await kt(e,t,"GET",`/projects/${encodeURIComponent(t.path)}/pipelines?status=${s}&per_page=100`);for(let a of Array.isArray(i)?i:[])n.set(String(a.id),{id:String(a.id),status:String(a.status??s),started:a.started_at??a.created_at,url:a.web_url})}return[...n.values()]}let r=t.forge==="github"?["in_progress","queued","waiting","pending","requested"]:["running","waiting","blocked"],o=t.forge==="github"?"per_page=100":"limit=50";for(let s of r){let i=await kt(e,t,"GET",`/repos/${t.path}/actions/runs?status=${s}&${o}`);for(let a of i.workflow_runs??[]){let c=t.forge==="github"?a.run_started_at??a.created_at:a.started??a.created,u=c&&kh(c)>0?c:a.created??a.created_at;n.set(String(a.id),{id:String(a.id),status:String(a.status??s),started:u,url:a.html_url})}}return[...n.values()]}function lve(e,t,n=ive){let r=kh(t);return e.filter(o=>Number.isNaN(r)||Number.isNaN(kh(o.started))||kh(o.started)<=r+n)}var dve=(e,t,n,r)=>{let o=RJ(e,[`-chdir=${n}`,...t],{encoding:"utf-8",env:Ve(r),maxBuffer:67108864});return{status:o.status??1,out:`${o.stdout??""}${o.stderr??""}${o.error?o.error.message:""}`}},EJ=e=>e.trim().split(`
922
+ `)}function b2(e,t){let n=i=>{let a=t[i];if(a===!0)throw new S(`--${i} needs a value`);return a},r=cd(n("mode")??"dry-run"),o=n("provider"),s="usage: terragucci rollout <module> [<version>] | terragucci rollout --provider <address> <version>";if(o){if(e.length!==1)throw new S(s);return{kind:"provider",name:o,to:e[0],from:n("from"),mode:r,config:n("config")}}if(e.length<1||e.length>2)throw new S(s);return{kind:"module",name:e[0],to:e[1],from:n("from"),mode:r,config:n("config")}}function no(e,t){try{return Ybe("git",["-C",e,...t],{encoding:"utf-8",stdio:["ignore","pipe","pipe"]}).trim()}catch(n){throw new S(`git ${t[0]}: ${String(n.stderr||n.message).trim()}`)}}function vh(e,t,n){let r="";try{r=no(e,["remote","get-url","origin"])}catch{throw new S("this repository has no origin remote, so there is nowhere to open a pull request")}let o=t.forge??Kr(e)?.value,s=uk(t.url??r);if(!o||!s)throw new S("cannot tell which forge this repository's origin is on; set forge (and url) in terragucci.yml");return{forge:o,origin:s.origin,path:s.path,token:n[t.token_env??Gn[o]]??""}}function QP(e,t){let n=lk(Qbe(lk(nve(),"terragucci-respond-")),"tree");return no(e,["worktree","add","-q","--detach",n,t]),{dir:n,done:()=>{try{no(e,["worktree","remove","--force",n])}catch{eve(n,{recursive:!0,force:!0})}}}}async function dk(e,t,n,r){if(r.mode==="dry-run")return n.map(u=>({branch:u.branch,title:u.title,files:[...u.files.keys(),...u.expect??[]].sort(),state:"would-open"}));let o=r.env??process.env,s=vh(e,t,o);if(!s.token)throw new S(`${t.token_env??Gn[s.forge]} is not set; it holds the forge token`);let i=r.fetch??globalThis.fetch,a,c=[];for(let u of n){let l=u.base??(a??=await lc(i,s));no(e,["fetch","-q","origin",l]);let d=QP(e,"FETCH_HEAD");try{for(let[g,m]of u.files)Xbe(rve(lk(d.dir,g)),{recursive:!0}),tve(lk(d.dir,g),m);u.run?.(d.dir),no(d.dir,["add","-A"]);let p=no(d.dir,["diff","--cached","--name-only"]).split(`
923
+ `).filter(Boolean);if(p.length===0){c.push({branch:u.branch,title:u.title,files:p,state:"nothing-to-change"});continue}no(d.dir,[...Tl,"commit","-q","--no-verify","-m",u.title]),no(d.dir,["push","-q","--force","origin",`HEAD:refs/heads/${u.branch}`]);let f=await dc(i,s,{head:u.branch,base:l,title:u.title,body:u.body});c.push({branch:u.branch,title:u.title,files:p,state:f.existing?"open":"opened",pullRequest:f.url})}finally{d.done()}}return c}var Pp="tf-unlock",pk=`_gates/${Pp}.jsonl`,$2=`_gates/${Pp}/done.jsonl`,Ol={released:0,refused:1,waiting:3,other:4},ive=120*1e3;function x2(e){try{let t=JSON.parse(e);if(!t||typeof t!="object"||typeof t.ID!="string"||t.ID==="")return;let n=r=>typeof t[r]=="string"?t[r]:void 0;return{ID:t.ID,Operation:n("Operation"),Info:n("Info"),Who:n("Who"),Version:n("Version"),Created:n("Created"),Path:n("Path")}}catch{return}}var ave=(e,t,n)=>Rn("terragucci-state-lock",{root:e,location:t,id:n}),cve=(e,t,n=!1)=>`terragucci approve unlock ${e} --plan ${t}${n?" --sign":""}`,kh=e=>e?Date.parse(e):Number.NaN;async function uve(e,t){let n=new Map;if(t.forge==="gitlab"){for(let s of["running","pending","preparing","waiting_for_resource"]){let i=await kt(e,t,"GET",`/projects/${encodeURIComponent(t.path)}/pipelines?status=${s}&per_page=100`);for(let a of Array.isArray(i)?i:[])n.set(String(a.id),{id:String(a.id),status:String(a.status??s),started:a.started_at??a.created_at,url:a.web_url})}return[...n.values()]}let r=t.forge==="github"?["in_progress","queued","waiting","pending","requested"]:["running","waiting","blocked"],o=t.forge==="github"?"per_page=100":"limit=50";for(let s of r){let i=await kt(e,t,"GET",`/repos/${t.path}/actions/runs?status=${s}&${o}`);for(let a of i.workflow_runs??[]){let c=t.forge==="github"?a.run_started_at??a.created_at:a.started??a.created,u=c&&kh(c)>0?c:a.created??a.created_at;n.set(String(a.id),{id:String(a.id),status:String(a.status??s),started:u,url:a.html_url})}}return[...n.values()]}function lve(e,t,n=ive){let r=kh(t);return e.filter(o=>Number.isNaN(r)||Number.isNaN(kh(o.started))||kh(o.started)<=r+n)}var dve=(e,t,n,r)=>{let o=R2(e,[`-chdir=${n}`,...t],{encoding:"utf-8",env:Ve(r),maxBuffer:67108864});return{status:o.status??1,out:`${o.stdout??""}${o.stderr??""}${o.error?o.error.message:""}`}},E2=e=>e.trim().split(`
924
924
  `).slice(-10).join(`
925
- `);function kJ(e,t){let n=RJ("git",t,{cwd:e,encoding:"utf-8"});return n.status===0?n.stdout.trim():""}var $h=e=>`lock ${e.ID}${e.Operation?`, ${e.Operation}`:""}${e.Who?` by ${e.Who}`:""}${e.Created?` at ${e.Created}`:""}`,_J=e=>`run ${e.id} (${e.status}${e.started?`, began ${e.started}`:""})${e.url?` ${e.url}`:""}`,SJ="unlock-state releases the lock of an s3 backend's lock file, a gcs or azurerm backend, and a GitLab-managed state";function pve(e,t){if("unsupported"in t)throw new S(`${e}: ${t.unsupported}; ${SJ}`);if("gitlab"in t){if(!t.gitlab.lockAddress)throw new S(`${e}: its http backend names no lock_address (TF_HTTP_LOCK_ADDRESS), so it takes no lock`);if(!t.gitlab.unlockAddress)throw new S(`${e}: its http backend names no unlock_address (TF_HTTP_UNLOCK_ADDRESS), so no lock it takes can be released`);return t}if(!ar(t))throw new S(`${e} keeps its state in a local file, whose lock goes with the process that took it; ${SJ}`);if(t.backend==="s3"&&!t.lockfile)throw new S(`${e}: its s3 backend takes no lock file (use_lockfile = true)${t.dynamodb?", only a DynamoDB lock,":""} and unlock-state releases the lock file`);return t}async function fve(e,t,n,r,o,s,i){if("gitlab"in t){let u=t.gitlab,l=i.s3Fetch??globalThis.fetch,d=xn(u.lockAddress),p=async f=>{try{return await f()}catch(g){throw new S(`${e}: ${g.message}`)}};return{location:d,read:async()=>{let f=await p(()=>$R(u,l));return f===void 0?void 0:{text:f}},release:async f=>{await p(()=>kR(u,f,l));let g=await p(()=>$R(u,l));if(g&&xJ(g)?.ID===f)throw new S(`${e}: GitLab released lock ${f}, and ${d} still holds it`)}}}let a=await Ws(t,i.s3Fetch),c=a.lockLocation;return{location:c,read:async()=>{let u=await a.heldLock();return u===void 0?void 0:{text:u.info,...u.id?{id:u.id}:{}}},release:async u=>{let l=s(n,["force-unlock","-force","-no-color",u],r,o);if(l.status!==0)throw new S(`${e}: ${n} force-unlock ${u} failed:
926
- ${EJ(l.out)}`);if(await a.heldLock())throw new S(`${e}: ${n} force-unlock ran, and ${c} is still locked`)}}}async function TJ(e,t,n={}){let r=n.log??(Q=>console.log(Q)),o=n.env??process.env,s=n.exec??dve,i=t.replace(/\/+$/,""),a=vJ(e,i);if(!i||i.split("/").includes("..")||!ove(a)||!sve(a).isDirectory())throw new S(`${t} is not a root of this repository`);let c=n.config??fe(e),u=c?await Re(vJ(c)):{};if(u.projects)throw new S("unlock-state runs in a project's own checkout, not a control repo");let l=ue(u),d=et(e,l),p=n.binary??l.binary??d.binary([i]).value;if(p==="choudoufu"&&ms(e,[i]).length>0)return r(`${i}: choudoufu keeps its resources under live resource markers, with no state file and no state lock; nothing to release`),{code:Ol.released,live:!0};let f=s(p,["init","-input=false","-no-color"],a,d.rootInit(i,o)?.init??o);if(f.status!==0)throw new S(`${i}: ${p} init failed:
927
- ${EJ(f.out)}`);let g=pve(i,Hr(a,d.rootEnv(i,o))),m=await fve(i,g,p,a,o,s,n),h=m.location,y=await m.read();if(y===void 0)return r(`${i}: no lock is held on ${h}; nothing to release`),{code:Ol.released,location:h};let w=xJ(y.text);if(!w)throw new S(`${i}: ${h} holds no lock ID the binary wrote; read it, and remove it by hand if it is not a lock`);let b={...w,...y.id?{ID:y.id}:{}},k=ave(i,h,b.ID);r(`${i}: ${h} holds ${$h(b)}`);let v=vh(e,l,o),$=l.token_env??Gn[v.forge];if(!v.token)throw new S(`${$} is not set; unlock-state reads the forge's runs with it, to know no run holding the lock is alive`);let _=n.fetch??globalThis.fetch,E=0,R=async()=>{let Q;try{Q=await uve(_,v)}catch(ne){throw new S(`${i}: the forge's runs could not be read (${ne.message}), so nothing says the run holding the lock is gone`)}return E=Q.length,lve(Q,b.Created)},x=await R();if(x.length>0){r(`${i}: ${x.length===1?"a run that began before the lock is":`${x.length} runs that began before the lock are`} still alive, so the lock may be theirs and is not released:`);for(let Q of x)r(` ${_J(Q)}`);return r("Run this again once they have finished, or stop them on the forge first."),{code:Ol.refused,lock:b,location:h,digest:k,alive:x}}r(`${i}: no run that began before the lock is alive`);let A=n.now??new Date().toISOString(),O=await Yr(e,{at:"HEAD",...c?{config:c}:{}}),I=O.mode==="sealed",N=ft(e,pk);I&&(N={...N,resolutions:N.resolutions.filter(Q=>{if(Q.gate!==i)return!0;let ne=ws(O.signers,Q);return ne!==null&&Ie(Q.planDigest,k)&&r(`${i}: an approval does not count: ${ne}`),ne===null})});let L=ma(N,i,k,A),U=cve(i,k,I),F=kJ(e,["rev-parse","HEAD"]);if(L.status!=="approved"){if(!L.standing){let Q={version:1,kind:"pending",op:Pp,gate:i,timestamp:A,expiresAt:new Date(kh(A)+1728e5).toISOString(),planDigest:k,description:`release ${$h(b)} on ${h}`,...F?{commit:F}:{},neverOverMcp:!0};ha(e,Q,{},pk)}return L.status==="refused"?(r(`${i}: ${L.by} approved ${L.approved??"another digest"}, the release of another lock; this one is not released`),r(`${i}: to release ${$h(b)}, approve it with:`),r(` ${U}`),{code:Ol.other,lock:b,location:h,digest:k,command:U}):(r(`${i}: releasing ${$h(b)} waits for an approval of digest ${k}. Approve it with:`),r(` ${U}`),r("Then run this again."),{code:Ol.waiting,lock:b,location:h,digest:k,command:U})}r(`${i}: approved by ${L.by} for this lock`);let M=await R();if(M.length>0){r(`${i}: a run that began before the lock is alive again, so the lock is not released:`);for(let Q of M)r(` ${_J(Q)}`);return{code:Ol.refused,lock:b,location:h,digest:k,alive:M}}await m.release(b.ID);let te=n.actor||kJ(e,["config","user.name"])||o.USER||o.USERNAME||"unknown",ie={version:1,kind:"unlock",op:Pp,gate:i,root:i,location:h,lock:b,planDigest:k,approvedBy:L.by,approvedAt:L.at,releasedBy:te,timestamp:new Date().toISOString(),liveRuns:E,...F?{commit:F}:{}};return hr(e,$J,[JSON.stringify(ie)],{},`Released the state lock of ${i}: ${b.ID}`),r(`${i}: released ${$h(b)}, approved by ${L.by}; recorded in ${$J} on chant/lifecycle`),{code:Ol.released,lock:b,location:h,digest:k}}var ro=e=>new Date(e).getTime();function _h(e){let t=new Map;for(let r of e.pending){let o=t.get(r.gate);/^wave-\d+$/.test(r.gate)&&r.planDigest&&(!o||ro(r.timestamp)>=ro(o.timestamp))&&t.set(r.gate,r)}let n=[];for(let[r,o]of t)e.resolutions.some(i=>i.gate===r&&i.planDigest===o.planDigest&&ro(i.timestamp)>=ro(o.timestamp))||n.push({wave:Number(r.slice(5)),digest:o.planDigest,since:o.timestamp,expiresAt:o.expiresAt,...o.description?{description:o.description}:{},...yl(o)?{runId:yl(o)}:{},...o.commit?{commit:o.commit}:{},...o.resume?{resume:o.resume}:{},...o.answer?.id?{question:o.answer.id}:{}});return n.sort((r,o)=>r.wave-o.wave)}function hve(e){if(!e)return[" (the wave kept no report of its plans; read them in its job's log or report)"];try{let t=JSON.parse(e),n=[` roots: ${(t.roots??[]).map(o=>o.path).join(", ")||"none"}`],r=(t.named??[]).filter(o=>o.action==="delete"||o.action==="replace");for(let o of r)n.push(` ${o.action==="delete"?"destroys":"replaces"} ${o.root}${o.address?`: ${o.address}`:""}`);return r.length===0&&n.push(" destroys and replaces nothing"),n}catch{return[" (the wave's kept report could not be read)"]}}async function AJ(e,t={}){let n=t.log??(g=>console.log(g)),r=_h(ft(e)),o=yve(ft(e,ga)),s=t.wave!==void 0&&!/^(?:wave-)?\d+$/.test(t.wave)?t.wave:void 0;if(s!==void 0){let g=o.find(m=>m.migration===s);if(!g)throw new S(`no migration ${s} waits for an approval${o.length?`; waiting: ${o.map(m=>m.migration).join(", ")}`:""}`);return t.plan!==void 0&&!Ie(g.digest,t.plan.trim())?(n(`not approved: migration ${s} waits for ${g.digest}, not ${t.plan.trim()}. The states moved since that digest; read the new proof, then approve its digest`),{code:1,command:""}):e0(e,g,t,n)}if(t.plan!==void 0&&t.wave===void 0){let g=o.find(m=>Ie(m.digest,t.plan.trim()));if(g)return e0(e,g,t,n)}if(t.plan===void 0&&t.wave===void 0&&r.length+o.length===1&&o.length===1)return e0(e,o[0],t,n);if(t.plan===void 0&&t.wave===void 0&&r.length+o.length>1&&o.length>0)throw new S(`${r.length+o.length} gates wait (${[...r.map(g=>Ct(g.wave)),...o.map(g=>`migration ${g.migration}`)].join(", ")}); name one: terragucci approve wave-<k> or terragucci approve <migration>`);let i;if(t.plan!==void 0){let g=t.plan.trim();if(!/^\S+$/.test(g))throw new S("--plan takes the digest to approve, such as jcs1-sha256:...");let m=t.wave===void 0?void 0:Number(/^(?:wave-)?(\d+)$/.exec(t.wave)?.[1]);if(m!==void 0&&(!Number.isInteger(m)||m<1))throw new S(`approve takes a wave as wave-<k> or <k>, not ${JSON.stringify(t.wave)}`);let h=m===void 0?r:r.filter(y=>y.wave===m);if(i=h.find(y=>Ie(y.digest,g)),!i){let y=m===void 0?"no wave":Ct(m),w=h.length>0?`; waiting: ${h.map(b=>`${Ct(b.wave)} for ${b.digest}`).join(", ")}`:m===void 0?": no wave waits for an approval":" is not waiting";return n(`not approved: ${y} waits for ${g}${w}. The plans moved since that digest, or were approved and applied; read the waiting plans, then approve their digest`),{code:1,command:""}}}else if(t.wave!==void 0){let g=Number(/^(?:wave-)?(\d+)$/.exec(t.wave)?.[1]);if(!Number.isInteger(g)||g<1)throw new S(`approve takes a wave as wave-<k> or <k>, not ${JSON.stringify(t.wave)}`);if(i=r.find(m=>m.wave===g),!i)throw new S(`${Ct(g)} is not waiting${r.length?`; waiting: ${r.map(m=>Ct(m.wave)).join(", ")}`:": no wave waits for an approval"}`)}else if(r.length===1)i=r[0];else throw r.length===0?new S("no wave waits for an approval on chant/lifecycle"):new S(`${r.length} waves wait (${r.map(g=>Ct(g.wave)).join(", ")}); name one: terragucci approve wave-<k>`);let a=i,c=fe(e),u=Yu(e,c?await Re(c):{})?.mode??"ledger";n(`${Ct(a.wave)} waits for an approval of ${a.digest}${a.description?` (${a.description})`:""}, since ${a.since}`);for(let g of hve(QO(e,a.wave,a.digest)))n(g);ro(a.expiresAt)<Date.now()&&n(` its pending fact expired at ${a.expiresAt}; the approval still counts if the next run plans the same digest`);let l=t.sign??(u==="sealed"?!0:void 0),d=["approve","tf-apply",Ct(a.wave),"--plan",a.digest,...t.actor?["--actor",t.actor]:[],...l===void 0?[]:l===!0?["--sign"]:["--sign",l]],p=`chant ${d.join(" ")}`;if(t.dryRun)return n(`would run: ${p}`),{code:0,command:p,wave:a};let f=gk(e,d,p,t,n);if(f===0){n(`approved ${Ct(a.wave)}`);let g=c?await Re(c):{},m=fk("git",["remote","get-url","origin"],{cwd:e,encoding:"utf-8"}).stdout?.trim()??"",h=mh(m,typeof g.forge=="string"?g.forge:void 0);t.resume===!1?n("not resumed (--no-resume): run its job again, comment /terragucci apply, or let the resume job apply it"):n(h?await PP({origin:h,wave:a,...t.env?{env:t.env}:{},...t.fetch?{fetch:t.fetch}:{}}):"not resumed from here: the origin is not on github.com or gitlab.com and terragucci.yml names no forge; run its job again, or comment /terragucci apply")}return{code:f,command:p,wave:a}}function yve(e){let t=new Map;for(let r of e.pending){let o=t.get(r.gate);r.planDigest&&(!o||ro(r.timestamp)>=ro(o.timestamp))&&t.set(r.gate,r)}let n=[];for(let[r,o]of t)e.resolutions.some(s=>s.gate===r&&Ie(s.planDigest,o.planDigest)&&ro(s.timestamp)>=ro(o.timestamp))||n.push({migration:r,digest:o.planDigest,since:o.timestamp,expiresAt:o.expiresAt,...o.description?{description:o.description}:{},...yl(o)?{runId:yl(o)}:{},...o.commit?{commit:o.commit}:{},...o.resume?{resume:o.resume}:{}});return n.sort((r,o)=>r.migration<o.migration?-1:1)}function wve(e){if(!e)return[" (the migration kept no plan record; read its proof in wave 1's log)"];try{let t=JSON.parse(e);if(t.change==="retag")return[...(t.retags??[]).map(r=>` retags ${r.address} (${r.live_id}) from estate ${r.from_estate} to estate ${r.to_estate}`)," every change each root planned is one the retag removes"];if(t.change==="adopt")return[...(t.roots??[]).map(r=>` adopts ${r.source?.location??"the state"}${r.source?.version_id?` at version ${r.source.version_id}`:""} into ${r.location??r.root}, ${(t.stamps??[]).filter(o=>o.root===r.root).length} resource instances`)," every resource verified against the live system"];let n=(t.moves??[]).map(r=>` moves ${r.addresses.join(", ")} from ${r.from} to ${r.to}`);for(let r of t.roots??[])n.push(` ${r.root}: ${r.location??"state"}${r.before?.version_id?` at version ${r.before.version_id}`:""}${r.before?.digest?"":", no state yet"}`);return n.push(" every root planned with no change against its new state"),n}catch{return[" (the migration's plan record could not be read)"]}}async function e0(e,t,n,r){let o=fe(e),s=o?await Re(o):{},i=Yu(e,s)?.mode??"ledger";r(`migration ${t.migration} waits for an approval of ${t.digest}${t.description?` (${t.description})`:""}, since ${t.since}`);let a=fk("git",["show",`refs/remotes/origin/chant/lifecycle:${Cm(t.migration,t.digest)}`],{cwd:e,encoding:"utf-8"});for(let p of wve(a.status===0?a.stdout:void 0))r(p);ro(t.expiresAt)<Date.now()&&r(` its pending fact expired at ${t.expiresAt}; the approval still counts if wave 1 proves the same digest`);let c=n.sign??(i==="sealed"?!0:void 0),u=["approve",mr,t.migration,"--plan",t.digest,...n.actor?["--actor",n.actor]:[],...c===void 0?[]:c===!0?["--sign"]:["--sign",c]],l=`chant ${u.join(" ")}`;if(n.dryRun)return r(`would run: ${l}`),{code:0,command:l,migration:t};let d=gk(e,u,l,n,r);if(d===0){r(`approved migration ${t.migration}`);let p=fk("git",["remote","get-url","origin"],{cwd:e,encoding:"utf-8"}).stdout?.trim()??"",f=mh(p,typeof s.forge=="string"?s.forge:void 0);n.resume===!1?r("not resumed (--no-resume): run wave 1 again, comment /terragucci apply, or let the resume job run it"):r(f?await PP({origin:f,wave:{wave:1,...t.runId?{runId:t.runId}:{},...t.commit?{commit:t.commit}:{},...t.resume?{resume:t.resume}:{}},...n.env?{env:n.env}:{},...n.fetch?{fetch:n.fetch}:{}}):"not resumed from here: the origin is not on github.com or gitlab.com and terragucci.yml names no forge; run wave 1 again, or comment /terragucci apply")}return{code:d,command:l,migration:t}}function gk(e,t,n,r,o){o(`running: ${n}`);let s=r.env??process.env,i=t[1]===vs?{CHANT_OVERRIDERS_FROM:"policy.override in terragucci.yml"}:{},a=fk(vi(e,r),t,{cwd:e,stdio:"inherit",env:{...s,...i,PATH:[mve(e,"node_modules",".bin"),s.PATH??""].join(gve)}});if(a.error)throw new S(`could not record the approval (${a.error.message}): the @intentius/chant package that @intentius/terragucci installs with itself is missing; install terragucci again with npm i -D @intentius/terragucci`);return a.status??1}var OJ=e=>/^[\w@%+=:,./-]+$/.test(e)?e:`'${e.replace(/'/g,"'\\''")}'`;async function PJ(e,t){let n=t.log??(g=>console.log(g));if(!t.root)throw new S("override takes the root the policy denied: terragucci override <root> --rule <id> --reason <text>");let r=sa(t.rules);if(r.length===0)throw new S("override names each rule it overrides with --rule <id>");if(!t.reason.trim())throw new S("override needs --reason <text>: why this plan goes out although the policy denies it");let o=eG(ft(e,Dg)),s=o.find(g=>g.gate===t.root);if(!s)throw new S(`no denial of ${t.root} is recorded on chant/lifecycle${o.length?`; denied: ${o.map(g=>g.gate).join(", ")}`:""}. A tf-apply wave records one when the policy denies the root and policy.override in the config at base names who may override it`);let i=sa(s.rules??[]);if(i.join(`
925
+ `);function k2(e,t){let n=R2("git",t,{cwd:e,encoding:"utf-8"});return n.status===0?n.stdout.trim():""}var $h=e=>`lock ${e.ID}${e.Operation?`, ${e.Operation}`:""}${e.Who?` by ${e.Who}`:""}${e.Created?` at ${e.Created}`:""}`,_2=e=>`run ${e.id} (${e.status}${e.started?`, began ${e.started}`:""})${e.url?` ${e.url}`:""}`,S2="unlock-state releases the lock of an s3 backend's lock file, a gcs or azurerm backend, and a GitLab-managed state";function pve(e,t){if("unsupported"in t)throw new S(`${e}: ${t.unsupported}; ${S2}`);if("gitlab"in t){if(!t.gitlab.lockAddress)throw new S(`${e}: its http backend names no lock_address (TF_HTTP_LOCK_ADDRESS), so it takes no lock`);if(!t.gitlab.unlockAddress)throw new S(`${e}: its http backend names no unlock_address (TF_HTTP_UNLOCK_ADDRESS), so no lock it takes can be released`);return t}if(!ar(t))throw new S(`${e} keeps its state in a local file, whose lock goes with the process that took it; ${S2}`);if(t.backend==="s3"&&!t.lockfile)throw new S(`${e}: its s3 backend takes no lock file (use_lockfile = true)${t.dynamodb?", only a DynamoDB lock,":""} and unlock-state releases the lock file`);return t}async function fve(e,t,n,r,o,s,i){if("gitlab"in t){let u=t.gitlab,l=i.s3Fetch??globalThis.fetch,d=xn(u.lockAddress),p=async f=>{try{return await f()}catch(g){throw new S(`${e}: ${g.message}`)}};return{location:d,read:async()=>{let f=await p(()=>$R(u,l));return f===void 0?void 0:{text:f}},release:async f=>{await p(()=>kR(u,f,l));let g=await p(()=>$R(u,l));if(g&&x2(g)?.ID===f)throw new S(`${e}: GitLab released lock ${f}, and ${d} still holds it`)}}}let a=await Ws(t,i.s3Fetch),c=a.lockLocation;return{location:c,read:async()=>{let u=await a.heldLock();return u===void 0?void 0:{text:u.info,...u.id?{id:u.id}:{}}},release:async u=>{let l=s(n,["force-unlock","-force","-no-color",u],r,o);if(l.status!==0)throw new S(`${e}: ${n} force-unlock ${u} failed:
926
+ ${E2(l.out)}`);if(await a.heldLock())throw new S(`${e}: ${n} force-unlock ran, and ${c} is still locked`)}}}async function T2(e,t,n={}){let r=n.log??(Q=>console.log(Q)),o=n.env??process.env,s=n.exec??dve,i=t.replace(/\/+$/,""),a=v2(e,i);if(!i||i.split("/").includes("..")||!ove(a)||!sve(a).isDirectory())throw new S(`${t} is not a root of this repository`);let c=n.config??fe(e),u=c?await Re(v2(c)):{};if(u.projects)throw new S("unlock-state runs in a project's own checkout, not a control repo");let l=ue(u),d=et(e,l),p=n.binary??l.binary??d.binary([i]).value;if(p==="choudoufu"&&ms(e,[i]).length>0)return r(`${i}: choudoufu keeps its resources under live resource markers, with no state file and no state lock; nothing to release`),{code:Ol.released,live:!0};let f=s(p,["init","-input=false","-no-color"],a,d.rootInit(i,o)?.init??o);if(f.status!==0)throw new S(`${i}: ${p} init failed:
927
+ ${E2(f.out)}`);let g=pve(i,Hr(a,d.rootEnv(i,o))),m=await fve(i,g,p,a,o,s,n),h=m.location,y=await m.read();if(y===void 0)return r(`${i}: no lock is held on ${h}; nothing to release`),{code:Ol.released,location:h};let w=x2(y.text);if(!w)throw new S(`${i}: ${h} holds no lock ID the binary wrote; read it, and remove it by hand if it is not a lock`);let b={...w,...y.id?{ID:y.id}:{}},k=ave(i,h,b.ID);r(`${i}: ${h} holds ${$h(b)}`);let v=vh(e,l,o),$=l.token_env??Gn[v.forge];if(!v.token)throw new S(`${$} is not set; unlock-state reads the forge's runs with it, to know no run holding the lock is alive`);let _=n.fetch??globalThis.fetch,E=0,R=async()=>{let Q;try{Q=await uve(_,v)}catch(ne){throw new S(`${i}: the forge's runs could not be read (${ne.message}), so nothing says the run holding the lock is gone`)}return E=Q.length,lve(Q,b.Created)},x=await R();if(x.length>0){r(`${i}: ${x.length===1?"a run that began before the lock is":`${x.length} runs that began before the lock are`} still alive, so the lock may be theirs and is not released:`);for(let Q of x)r(` ${_2(Q)}`);return r("Run this again once they have finished, or stop them on the forge first."),{code:Ol.refused,lock:b,location:h,digest:k,alive:x}}r(`${i}: no run that began before the lock is alive`);let A=n.now??new Date().toISOString(),O=await Yr(e,{at:"HEAD",...c?{config:c}:{}}),I=O.mode==="sealed",N=ft(e,pk);I&&(N={...N,resolutions:N.resolutions.filter(Q=>{if(Q.gate!==i)return!0;let ne=ws(O.signers,Q);return ne!==null&&Ie(Q.planDigest,k)&&r(`${i}: an approval does not count: ${ne}`),ne===null})});let L=ma(N,i,k,A),U=cve(i,k,I),F=k2(e,["rev-parse","HEAD"]);if(L.status!=="approved"){if(!L.standing){let Q={version:1,kind:"pending",op:Pp,gate:i,timestamp:A,expiresAt:new Date(kh(A)+1728e5).toISOString(),planDigest:k,description:`release ${$h(b)} on ${h}`,...F?{commit:F}:{},neverOverMcp:!0};ha(e,Q,{},pk)}return L.status==="refused"?(r(`${i}: ${L.by} approved ${L.approved??"another digest"}, the release of another lock; this one is not released`),r(`${i}: to release ${$h(b)}, approve it with:`),r(` ${U}`),{code:Ol.other,lock:b,location:h,digest:k,command:U}):(r(`${i}: releasing ${$h(b)} waits for an approval of digest ${k}. Approve it with:`),r(` ${U}`),r("Then run this again."),{code:Ol.waiting,lock:b,location:h,digest:k,command:U})}r(`${i}: approved by ${L.by} for this lock`);let M=await R();if(M.length>0){r(`${i}: a run that began before the lock is alive again, so the lock is not released:`);for(let Q of M)r(` ${_2(Q)}`);return{code:Ol.refused,lock:b,location:h,digest:k,alive:M}}await m.release(b.ID);let te=n.actor||k2(e,["config","user.name"])||o.USER||o.USERNAME||"unknown",ie={version:1,kind:"unlock",op:Pp,gate:i,root:i,location:h,lock:b,planDigest:k,approvedBy:L.by,approvedAt:L.at,releasedBy:te,timestamp:new Date().toISOString(),liveRuns:E,...F?{commit:F}:{}};return hr(e,$2,[JSON.stringify(ie)],{},`Released the state lock of ${i}: ${b.ID}`),r(`${i}: released ${$h(b)}, approved by ${L.by}; recorded in ${$2} on chant/lifecycle`),{code:Ol.released,lock:b,location:h,digest:k}}var ro=e=>new Date(e).getTime();function _h(e){let t=new Map;for(let r of e.pending){let o=t.get(r.gate);/^wave-\d+$/.test(r.gate)&&r.planDigest&&(!o||ro(r.timestamp)>=ro(o.timestamp))&&t.set(r.gate,r)}let n=[];for(let[r,o]of t)e.resolutions.some(i=>i.gate===r&&i.planDigest===o.planDigest&&ro(i.timestamp)>=ro(o.timestamp))||n.push({wave:Number(r.slice(5)),digest:o.planDigest,since:o.timestamp,expiresAt:o.expiresAt,...o.description?{description:o.description}:{},...yl(o)?{runId:yl(o)}:{},...o.commit?{commit:o.commit}:{},...o.resume?{resume:o.resume}:{},...o.answer?.id?{question:o.answer.id}:{}});return n.sort((r,o)=>r.wave-o.wave)}function hve(e){if(!e)return[" (the wave kept no report of its plans; read them in its job's log or report)"];try{let t=JSON.parse(e),n=[` roots: ${(t.roots??[]).map(o=>o.path).join(", ")||"none"}`],r=(t.named??[]).filter(o=>o.action==="delete"||o.action==="replace");for(let o of r)n.push(` ${o.action==="delete"?"destroys":"replaces"} ${o.root}${o.address?`: ${o.address}`:""}`);return r.length===0&&n.push(" destroys and replaces nothing"),n}catch{return[" (the wave's kept report could not be read)"]}}async function A2(e,t={}){let n=t.log??(g=>console.log(g)),r=_h(ft(e)),o=yve(ft(e,ga)),s=t.wave!==void 0&&!/^(?:wave-)?\d+$/.test(t.wave)?t.wave:void 0;if(s!==void 0){let g=o.find(m=>m.migration===s);if(!g)throw new S(`no migration ${s} waits for an approval${o.length?`; waiting: ${o.map(m=>m.migration).join(", ")}`:""}`);return t.plan!==void 0&&!Ie(g.digest,t.plan.trim())?(n(`not approved: migration ${s} waits for ${g.digest}, not ${t.plan.trim()}. The states moved since that digest; read the new proof, then approve its digest`),{code:1,command:""}):e0(e,g,t,n)}if(t.plan!==void 0&&t.wave===void 0){let g=o.find(m=>Ie(m.digest,t.plan.trim()));if(g)return e0(e,g,t,n)}if(t.plan===void 0&&t.wave===void 0&&r.length+o.length===1&&o.length===1)return e0(e,o[0],t,n);if(t.plan===void 0&&t.wave===void 0&&r.length+o.length>1&&o.length>0)throw new S(`${r.length+o.length} gates wait (${[...r.map(g=>Ct(g.wave)),...o.map(g=>`migration ${g.migration}`)].join(", ")}); name one: terragucci approve wave-<k> or terragucci approve <migration>`);let i;if(t.plan!==void 0){let g=t.plan.trim();if(!/^\S+$/.test(g))throw new S("--plan takes the digest to approve, such as jcs1-sha256:...");let m=t.wave===void 0?void 0:Number(/^(?:wave-)?(\d+)$/.exec(t.wave)?.[1]);if(m!==void 0&&(!Number.isInteger(m)||m<1))throw new S(`approve takes a wave as wave-<k> or <k>, not ${JSON.stringify(t.wave)}`);let h=m===void 0?r:r.filter(y=>y.wave===m);if(i=h.find(y=>Ie(y.digest,g)),!i){let y=m===void 0?"no wave":Ct(m),w=h.length>0?`; waiting: ${h.map(b=>`${Ct(b.wave)} for ${b.digest}`).join(", ")}`:m===void 0?": no wave waits for an approval":" is not waiting";return n(`not approved: ${y} waits for ${g}${w}. The plans moved since that digest, or were approved and applied; read the waiting plans, then approve their digest`),{code:1,command:""}}}else if(t.wave!==void 0){let g=Number(/^(?:wave-)?(\d+)$/.exec(t.wave)?.[1]);if(!Number.isInteger(g)||g<1)throw new S(`approve takes a wave as wave-<k> or <k>, not ${JSON.stringify(t.wave)}`);if(i=r.find(m=>m.wave===g),!i)throw new S(`${Ct(g)} is not waiting${r.length?`; waiting: ${r.map(m=>Ct(m.wave)).join(", ")}`:": no wave waits for an approval"}`)}else if(r.length===1)i=r[0];else throw r.length===0?new S("no wave waits for an approval on chant/lifecycle"):new S(`${r.length} waves wait (${r.map(g=>Ct(g.wave)).join(", ")}); name one: terragucci approve wave-<k>`);let a=i,c=fe(e),u=Yu(e,c?await Re(c):{})?.mode??"ledger";n(`${Ct(a.wave)} waits for an approval of ${a.digest}${a.description?` (${a.description})`:""}, since ${a.since}`);for(let g of hve(QO(e,a.wave,a.digest)))n(g);ro(a.expiresAt)<Date.now()&&n(` its pending fact expired at ${a.expiresAt}; the approval still counts if the next run plans the same digest`);let l=t.sign??(u==="sealed"?!0:void 0),d=["approve","tf-apply",Ct(a.wave),"--plan",a.digest,...t.actor?["--actor",t.actor]:[],...l===void 0?[]:l===!0?["--sign"]:["--sign",l]],p=`chant ${d.join(" ")}`;if(t.dryRun)return n(`would run: ${p}`),{code:0,command:p,wave:a};let f=gk(e,d,p,t,n);if(f===0){n(`approved ${Ct(a.wave)}`);let g=c?await Re(c):{},m=fk("git",["remote","get-url","origin"],{cwd:e,encoding:"utf-8"}).stdout?.trim()??"",h=mh(m,typeof g.forge=="string"?g.forge:void 0);t.resume===!1?n("not resumed (--no-resume): run its job again, comment /terragucci apply, or let the resume job apply it"):n(h?await PP({origin:h,wave:a,...t.env?{env:t.env}:{},...t.fetch?{fetch:t.fetch}:{}}):"not resumed from here: the origin is not on github.com or gitlab.com and terragucci.yml names no forge; run its job again, or comment /terragucci apply")}return{code:f,command:p,wave:a}}function yve(e){let t=new Map;for(let r of e.pending){let o=t.get(r.gate);r.planDigest&&(!o||ro(r.timestamp)>=ro(o.timestamp))&&t.set(r.gate,r)}let n=[];for(let[r,o]of t)e.resolutions.some(s=>s.gate===r&&Ie(s.planDigest,o.planDigest)&&ro(s.timestamp)>=ro(o.timestamp))||n.push({migration:r,digest:o.planDigest,since:o.timestamp,expiresAt:o.expiresAt,...o.description?{description:o.description}:{},...yl(o)?{runId:yl(o)}:{},...o.commit?{commit:o.commit}:{},...o.resume?{resume:o.resume}:{}});return n.sort((r,o)=>r.migration<o.migration?-1:1)}function wve(e){if(!e)return[" (the migration kept no plan record; read its proof in wave 1's log)"];try{let t=JSON.parse(e);if(t.change==="retag")return[...(t.retags??[]).map(r=>` retags ${r.address} (${r.live_id}) from estate ${r.from_estate} to estate ${r.to_estate}`)," every change each root planned is one the retag removes"];if(t.change==="adopt")return[...(t.roots??[]).map(r=>` adopts ${r.source?.location??"the state"}${r.source?.version_id?` at version ${r.source.version_id}`:""} into ${r.location??r.root}, ${(t.stamps??[]).filter(o=>o.root===r.root).length} resource instances`)," every resource verified against the live system"];let n=(t.moves??[]).map(r=>` moves ${r.addresses.join(", ")} from ${r.from} to ${r.to}`);for(let r of t.roots??[])n.push(` ${r.root}: ${r.location??"state"}${r.before?.version_id?` at version ${r.before.version_id}`:""}${r.before?.digest?"":", no state yet"}`);return n.push(" every root planned with no change against its new state"),n}catch{return[" (the migration's plan record could not be read)"]}}async function e0(e,t,n,r){let o=fe(e),s=o?await Re(o):{},i=Yu(e,s)?.mode??"ledger";r(`migration ${t.migration} waits for an approval of ${t.digest}${t.description?` (${t.description})`:""}, since ${t.since}`);let a=fk("git",["show",`refs/remotes/origin/chant/lifecycle:${Cm(t.migration,t.digest)}`],{cwd:e,encoding:"utf-8"});for(let p of wve(a.status===0?a.stdout:void 0))r(p);ro(t.expiresAt)<Date.now()&&r(` its pending fact expired at ${t.expiresAt}; the approval still counts if wave 1 proves the same digest`);let c=n.sign??(i==="sealed"?!0:void 0),u=["approve",mr,t.migration,"--plan",t.digest,...n.actor?["--actor",n.actor]:[],...c===void 0?[]:c===!0?["--sign"]:["--sign",c]],l=`chant ${u.join(" ")}`;if(n.dryRun)return r(`would run: ${l}`),{code:0,command:l,migration:t};let d=gk(e,u,l,n,r);if(d===0){r(`approved migration ${t.migration}`);let p=fk("git",["remote","get-url","origin"],{cwd:e,encoding:"utf-8"}).stdout?.trim()??"",f=mh(p,typeof s.forge=="string"?s.forge:void 0);n.resume===!1?r("not resumed (--no-resume): run wave 1 again, comment /terragucci apply, or let the resume job run it"):r(f?await PP({origin:f,wave:{wave:1,...t.runId?{runId:t.runId}:{},...t.commit?{commit:t.commit}:{},...t.resume?{resume:t.resume}:{}},...n.env?{env:n.env}:{},...n.fetch?{fetch:n.fetch}:{}}):"not resumed from here: the origin is not on github.com or gitlab.com and terragucci.yml names no forge; run wave 1 again, or comment /terragucci apply")}return{code:d,command:l,migration:t}}function gk(e,t,n,r,o){o(`running: ${n}`);let s=r.env??process.env,i=t[1]===vs?{CHANT_OVERRIDERS_FROM:"policy.override in terragucci.yml"}:{},a=fk(vi(e,r),t,{cwd:e,stdio:"inherit",env:{...s,...i,PATH:[mve(e,"node_modules",".bin"),s.PATH??""].join(gve)}});if(a.error)throw new S(`could not record the approval (${a.error.message}): the @intentius/chant package that @intentius/terragucci installs with itself is missing; install terragucci again with npm i -D @intentius/terragucci`);return a.status??1}var O2=e=>/^[\w@%+=:,./-]+$/.test(e)?e:`'${e.replace(/'/g,"'\\''")}'`;async function P2(e,t){let n=t.log??(g=>console.log(g));if(!t.root)throw new S("override takes the root the policy denied: terragucci override <root> --rule <id> --reason <text>");let r=sa(t.rules);if(r.length===0)throw new S("override names each rule it overrides with --rule <id>");if(!t.reason.trim())throw new S("override needs --reason <text>: why this plan goes out although the policy denies it");let o=eG(ft(e,Dg)),s=o.find(g=>g.gate===t.root);if(!s)throw new S(`no denial of ${t.root} is recorded on chant/lifecycle${o.length?`; denied: ${o.map(g=>g.gate).join(", ")}`:""}. A tf-apply wave records one when the policy denies the root and policy.override in the config at base names who may override it`);let i=sa(s.rules??[]);if(i.join(`
928
928
  `)!==r.join(`
929
- `))throw new S(`${t.root} was denied by ${i.join(", ")||"no rule it named"}; an override names exactly those rules, and --rule gave ${r.join(", ")}`);let a=fe(e),c=Yu(e,a?await Re(a):{})?.mode??"ledger",u=s.plan||s.members?.[0]?.planDigest;n(`${t.root}: its plan${u?` ${u}`:""} was denied by ${i.join(", ")}, since ${s.timestamp}`),n(` the override binds the root, that plan and those rules: ${s.planDigest}`),ro(s.expiresAt)<Date.now()&&n(` the denial's pending fact expired at ${s.expiresAt}; the override still counts if the next run plans the same digest`);let l=t.sign??(c==="sealed"?!0:void 0),d=["approve",vs,t.root,...r.flatMap(g=>["--rule",g]),"--reason",t.reason.trim(),...t.actor?["--actor",t.actor]:[],...l===void 0?[]:l===!0?["--sign"]:["--sign",l]],p=`chant ${d.map(OJ).join(" ")}`;if(t.dryRun)return n(`would run: ${p}`),{code:0,command:p};let f=gk(e,d,p,t,n);return f===0&&n(`overrode the denial of ${t.root}; run its wave again and it applies this plan, if policy.override at base lists you`),{code:f,command:p}}var jJ={export:{op:Hc,ledger:ek,what:"the state export of",next:e=>`the person who asked runs terragucci state export ${e} again to download it`},unlock:{op:Pp,ledger:pk,what:"releasing the state lock of",next:e=>`run terragucci unlock-state ${e} again to release it`},ephemeral:{op:ka,ledger:ap,what:"the ephemeral copy of",next:()=>"run the pull request's ephemeral job again, or push to it"}},IJ=e=>e.length>=2&&Object.hasOwn(jJ,e[0])&&e[1]!=="";async function CJ(e,t){let n=t.log??(h=>console.log(h)),r=jJ[t.kind],o=t.kind==="ephemeral"?"terragucci approve ephemeral <pr> --plan <digest>":`terragucci approve ${t.kind} <root> --plan <digest>`,s=t.target;if(t.kind==="ephemeral"){let h=Number(/^(?:pr-)?(\d+)$/.exec(t.target)?.[1]);if(!Number.isInteger(h)||h<1)throw new S(`approve ephemeral takes the pull request's number, not ${JSON.stringify(t.target)}: ${o}`);s=t$(h)}let i=t.plan?.trim();if(!i||!/^\S+$/.test(i))throw new S(`approve ${t.kind} needs --plan <digest>, the digest its request printed: ${o}`);let c=ft(e,r.ledger).pending.filter(h=>h.gate===s&&h.planDigest).sort((h,y)=>ro(y.timestamp)-ro(h.timestamp)),u=(t.kind==="export"?c.find(h=>Ie(h.planDigest,i)):void 0)??c[0];if(!u)return n(`not approved: nothing waits for an approval of ${r.what} ${s} on chant/lifecycle`),{code:1,command:""};if(!Ie(u.planDigest,i))return n(`not approved: ${r.what} ${s} waits for ${u.planDigest}, not ${i}. What it asks moved since that digest; read the new request, then approve its digest`),{code:1,command:""};let l=fe(e),d=Yu(e,l?await Re(l):{})?.mode??"ledger";n(`${r.what} ${s} waits for an approval of ${u.planDigest}${u.description?` (${u.description})`:""}, since ${u.timestamp}`),ro(u.expiresAt)<Date.now()&&n(` its pending fact expired at ${u.expiresAt}`);let p=t.sign??(d==="sealed"?!0:void 0),f=["approve",r.op,s,"--plan",u.planDigest,...t.actor?["--actor",t.actor]:[],...p===void 0?[]:p===!0?["--sign"]:["--sign",p]],g=`chant ${f.map(OJ).join(" ")}`;if(t.dryRun)return n(`would run: ${g}`),{code:0,command:g};let m=gk(e,f,g,t,n);return m===0&&n(`approved ${r.what} ${s}; ${r.next(t.kind==="ephemeral"?s:t.target)}`),{code:m,command:g}}var NJ=["init","reconcile","plan","stage","config","forge","rollout","respond","query"],bve={0:"ok",1:"failed",2:"usage",3:"waiting"};function Xo(e,t,n,r){let o={schema:1,command:e,exit:t,status:bve[t],results:n};return r!==void 0&&(o.error=r),o}import{existsSync as XJ,readFileSync as Xve,writeFileSync as Qve}from"node:fs";import{join as eK,relative as Dp,resolve as QJ}from"node:path";var Ei=class{constructor(t=_m){this.url=t}url;list=[];add(t,n,r,o,s){this.list.some(i=>i.key===n&&i.row===r)||this.list.push({key:n,kind:t,row:r,text:o,...s?{detail:s}:{}})}mapped(t,n,r){this.add("mapped",t,n,Pv(n),r)}covered(t,n,r){this.add("default",t,n,Pv(n),r)}unmapped(t,n,r){this.add("unmapped",t,n,Pv(n),r)}leftOut(t,n,r){let o=Sm(n);this.add("left-out",t,n,`${o.rule}. Instead: ${o.instead}`,r)}unknown(t,n){this.add("unmapped",t,"",`the guide has no row for it, so nothing was written; see ${this.url}`,n)}cell(t,n,r,o,s){this.add(t,n,r,o,s)}scale(t,n,r,o){this.add(t,n,r,Oz(r),o)}own(t,n,r,o){this.add(n,t,"",r,o)}};function Me(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function oo(e){return Array.isArray(e)?e:e==null?[]:[e]}function xo(e){if(typeof e!="string")return;let t=e.trim().split("/").filter(n=>n!==""&&n!==".");if(!(e.trim().startsWith("/")||t.includes("..")))return t.length?t.join("/"):"."}function jp(e,t=new Map){let n=(u,l)=>{let d=new Set,p=f=>[...t.get(f)??[]].some(g=>g===l||!d.has(g)&&(d.add(g),p(g)));return p(u)},r=new Set,o={};for(let[u,l]of e)if(u!==l){if(n(u,l)){r.add(`${u}\0${l}`);continue}(o[u]??[]).includes(l)||(o[u]=[...o[u]??[],l].sort())}let s=u=>[...new Set([...o[u]??[],...t.get(u)??[]])],i=new Map,a=[],c=u=>{if(i.get(u)!==2){if(i.get(u)===1)return[...a.slice(a.indexOf(u)),u];i.set(u,1),a.push(u);for(let l of s(u)){let d=c(l);if(d)return d}a.pop(),i.set(u,2)}};for(let u of Object.keys(o).sort()){let l=c(u);if(l)return{after:{},fromReads:r,cycle:l}}return{after:Object.fromEntries(Object.entries(o).sort(([u],[l])=>u.localeCompare(l))),fromReads:r}}import{existsSync as LJ,readdirSync as Ove,readFileSync as Pve,statSync as jve}from"node:fs";import{dirname as n0,join as wk,relative as Ive}from"node:path";import{readdirSync as vve,readFileSync as $ve,statSync as kve}from"node:fs";import{join as _ve,relative as Sve}from"node:path";var Rve=new Set(["node_modules",".git",".terraform",".terragrunt-cache",".terragucci"]),t0=class{constructor(t){this.s=t}s;i=0;ws(t=!1){let n=this.s;for(;this.i<n.length;){let r=n[this.i];if(r===" "||r===" "||r==="\r"||!t&&r===`
929
+ `))throw new S(`${t.root} was denied by ${i.join(", ")||"no rule it named"}; an override names exactly those rules, and --rule gave ${r.join(", ")}`);let a=fe(e),c=Yu(e,a?await Re(a):{})?.mode??"ledger",u=s.plan||s.members?.[0]?.planDigest;n(`${t.root}: its plan${u?` ${u}`:""} was denied by ${i.join(", ")}, since ${s.timestamp}`),n(` the override binds the root, that plan and those rules: ${s.planDigest}`),ro(s.expiresAt)<Date.now()&&n(` the denial's pending fact expired at ${s.expiresAt}; the override still counts if the next run plans the same digest`);let l=t.sign??(c==="sealed"?!0:void 0),d=["approve",vs,t.root,...r.flatMap(g=>["--rule",g]),"--reason",t.reason.trim(),...t.actor?["--actor",t.actor]:[],...l===void 0?[]:l===!0?["--sign"]:["--sign",l]],p=`chant ${d.map(O2).join(" ")}`;if(t.dryRun)return n(`would run: ${p}`),{code:0,command:p};let f=gk(e,d,p,t,n);return f===0&&n(`overrode the denial of ${t.root}; run its wave again and it applies this plan, if policy.override at base lists you`),{code:f,command:p}}var j2={export:{op:Hc,ledger:ek,what:"the state export of",next:e=>`the person who asked runs terragucci state export ${e} again to download it`},unlock:{op:Pp,ledger:pk,what:"releasing the state lock of",next:e=>`run terragucci unlock-state ${e} again to release it`},ephemeral:{op:ka,ledger:ap,what:"the ephemeral copy of",next:()=>"run the pull request's ephemeral job again, or push to it"}},I2=e=>e.length>=2&&Object.hasOwn(j2,e[0])&&e[1]!=="";async function C2(e,t){let n=t.log??(h=>console.log(h)),r=j2[t.kind],o=t.kind==="ephemeral"?"terragucci approve ephemeral <pr> --plan <digest>":`terragucci approve ${t.kind} <root> --plan <digest>`,s=t.target;if(t.kind==="ephemeral"){let h=Number(/^(?:pr-)?(\d+)$/.exec(t.target)?.[1]);if(!Number.isInteger(h)||h<1)throw new S(`approve ephemeral takes the pull request's number, not ${JSON.stringify(t.target)}: ${o}`);s=t$(h)}let i=t.plan?.trim();if(!i||!/^\S+$/.test(i))throw new S(`approve ${t.kind} needs --plan <digest>, the digest its request printed: ${o}`);let c=ft(e,r.ledger).pending.filter(h=>h.gate===s&&h.planDigest).sort((h,y)=>ro(y.timestamp)-ro(h.timestamp)),u=(t.kind==="export"?c.find(h=>Ie(h.planDigest,i)):void 0)??c[0];if(!u)return n(`not approved: nothing waits for an approval of ${r.what} ${s} on chant/lifecycle`),{code:1,command:""};if(!Ie(u.planDigest,i))return n(`not approved: ${r.what} ${s} waits for ${u.planDigest}, not ${i}. What it asks moved since that digest; read the new request, then approve its digest`),{code:1,command:""};let l=fe(e),d=Yu(e,l?await Re(l):{})?.mode??"ledger";n(`${r.what} ${s} waits for an approval of ${u.planDigest}${u.description?` (${u.description})`:""}, since ${u.timestamp}`),ro(u.expiresAt)<Date.now()&&n(` its pending fact expired at ${u.expiresAt}`);let p=t.sign??(d==="sealed"?!0:void 0),f=["approve",r.op,s,"--plan",u.planDigest,...t.actor?["--actor",t.actor]:[],...p===void 0?[]:p===!0?["--sign"]:["--sign",p]],g=`chant ${f.map(O2).join(" ")}`;if(t.dryRun)return n(`would run: ${g}`),{code:0,command:g};let m=gk(e,f,g,t,n);return m===0&&n(`approved ${r.what} ${s}; ${r.next(t.kind==="ephemeral"?s:t.target)}`),{code:m,command:g}}var N2=["init","reconcile","plan","stage","config","forge","rollout","respond","query"],bve={0:"ok",1:"failed",2:"usage",3:"waiting"};function Xo(e,t,n,r){let o={schema:1,command:e,exit:t,status:bve[t],results:n};return r!==void 0&&(o.error=r),o}import{existsSync as X2,readFileSync as Xve,writeFileSync as Qve}from"node:fs";import{join as eK,relative as Dp,resolve as Q2}from"node:path";var Ei=class{constructor(t=_m){this.url=t}url;list=[];add(t,n,r,o,s){this.list.some(i=>i.key===n&&i.row===r)||this.list.push({key:n,kind:t,row:r,text:o,...s?{detail:s}:{}})}mapped(t,n,r){this.add("mapped",t,n,Pv(n),r)}covered(t,n,r){this.add("default",t,n,Pv(n),r)}unmapped(t,n,r){this.add("unmapped",t,n,Pv(n),r)}leftOut(t,n,r){let o=Sm(n);this.add("left-out",t,n,`${o.rule}. Instead: ${o.instead}`,r)}unknown(t,n){this.add("unmapped",t,"",`the guide has no row for it, so nothing was written; see ${this.url}`,n)}cell(t,n,r,o,s){this.add(t,n,r,o,s)}scale(t,n,r,o){this.add(t,n,r,Oz(r),o)}own(t,n,r,o){this.add(n,t,"",r,o)}};function Me(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function oo(e){return Array.isArray(e)?e:e==null?[]:[e]}function xo(e){if(typeof e!="string")return;let t=e.trim().split("/").filter(n=>n!==""&&n!==".");if(!(e.trim().startsWith("/")||t.includes("..")))return t.length?t.join("/"):"."}function jp(e,t=new Map){let n=(u,l)=>{let d=new Set,p=f=>[...t.get(f)??[]].some(g=>g===l||!d.has(g)&&(d.add(g),p(g)));return p(u)},r=new Set,o={};for(let[u,l]of e)if(u!==l){if(n(u,l)){r.add(`${u}\0${l}`);continue}(o[u]??[]).includes(l)||(o[u]=[...o[u]??[],l].sort())}let s=u=>[...new Set([...o[u]??[],...t.get(u)??[]])],i=new Map,a=[],c=u=>{if(i.get(u)!==2){if(i.get(u)===1)return[...a.slice(a.indexOf(u)),u];i.set(u,1),a.push(u);for(let l of s(u)){let d=c(l);if(d)return d}a.pop(),i.set(u,2)}};for(let u of Object.keys(o).sort()){let l=c(u);if(l)return{after:{},fromReads:r,cycle:l}}return{after:Object.fromEntries(Object.entries(o).sort(([u],[l])=>u.localeCompare(l))),fromReads:r}}import{existsSync as L2,readdirSync as Ove,readFileSync as Pve,statSync as jve}from"node:fs";import{dirname as n0,join as wk,relative as Ive}from"node:path";import{readdirSync as vve,readFileSync as $ve,statSync as kve}from"node:fs";import{join as _ve,relative as Sve}from"node:path";var Rve=new Set(["node_modules",".git",".terraform",".terragrunt-cache",".terragucci"]),t0=class{constructor(t){this.s=t}s;i=0;ws(t=!1){let n=this.s;for(;this.i<n.length;){let r=n[this.i];if(r===" "||r===" "||r==="\r"||!t&&r===`
930
930
  `)this.i++;else if(r==="#"||r==="/"&&n[this.i+1]==="/")for(;this.i<n.length&&n[this.i]!==`
931
931
  `;)this.i++;else if(r==="/"&&n[this.i+1]==="*"){let o=n.indexOf("*/",this.i+2);this.i=o<0?n.length:o+2}else break}}ident(){let t=/^[A-Za-z_][\w-]*/.exec(this.s.slice(this.i,this.i+200));return t?(this.i+=t[0].length,t[0]):""}str(){let t=this.s,n="";for(this.i++;this.i<t.length&&t[this.i]!=='"';)if(t[this.i]==="\\"){let r=t[this.i+1];n+=r==="n"?`
932
932
  `:r==="t"?" ":r??"",this.i+=2}else if(t[this.i]==="$"&&t[this.i+1]==="{"){let r=this.i;this.skipBalanced("{","}",this.i+1),n+=t.slice(r,this.i)}else n+=t[this.i++];return this.i++,n}skipBalanced(t,n,r){let o=0;for(this.i=r;this.i<this.s.length;){let s=this.s[this.i];if(s==='"'){this.str();continue}if(s===t)o++;else if(s===n&&--o===0){this.i++;return}this.i++}}heredoc(){let t=/^<<(-?)([A-Za-z_]\w*)[^\n]*\n/.exec(this.s.slice(this.i));if(!t)return this.raw();this.i+=t[0].length;let n=[];for(;this.i<this.s.length;){let r=this.s.indexOf(`
@@ -937,15 +937,15 @@ ${EJ(f.out)}`);let g=pve(i,Hr(a,d.rootEnv(i,o))),m=await fve(i,g,p,a,o,s,n),h=m.
937
937
  `}raw(){let t=this.s,n=this.i,r=0;for(;this.i<t.length;){let o=t[this.i];if(o==='"'){this.str();continue}if(o==="("||o==="["||o==="{")r++;else if(o===")"||o==="]"||o==="}"){if(r===0)break;r--}else if(r===0&&(o===`
938
938
  `||o===","))break;this.i++}return t.slice(n,this.i).trim()}value(){this.ws(!0);let t=this.s[this.i];if(t==='"'){let o=this.i,s=this.str();this.ws(!0);let i=this.s[this.i];return i!==void 0&&!`
939
939
  ,]})#/`.includes(i)?(this.i=o,{expr:this.raw()}):s}if(t==="["){this.i++;let o=[];for(;;){if(this.ws(),this.s[this.i]==="]"||this.i>=this.s.length)return this.i++,o;o.push(this.value()),this.ws(),this.s[this.i]===","&&this.i++}}if(t==="{"){let o=this.i;return this.skipBalanced("{","}",this.i),{expr:this.s.slice(o,this.i)}}if(t==="<"&&this.s[this.i+1]==="<")return this.heredoc();let n=this.raw();if(n==="true"||n==="false")return n==="true";if(/^-?\d+(\.\d+)?$/.test(n))return Number(n);if(/^[A-Za-z_][\w-]*(\.[\w-]+)+$/.test(n))return{ref:n};let r=/^file\(\s*"([^"]*)"\s*\)$/.exec(n);return r?{file:r[1]}:{expr:n}}body(){let t={attrs:{},blocks:[]};for(;;){if(this.ws(),this.i>=this.s.length)return t;if(this.s[this.i]==="}")return this.i++,t;let n=this.ident();if(!n){for(;this.i<this.s.length&&this.s[this.i]!==`
940
- `;)this.i++;continue}if(this.ws(!0),this.s[this.i]==="="&&this.s[this.i+1]!=="="){this.i++,t.attrs[n]=this.value();continue}let r=[];for(;this.s[this.i]==='"'||/[A-Za-z_]/.test(this.s[this.i]??"");)r.push(this.s[this.i]==='"'?this.str():this.ident()),this.ws(!0);this.s[this.i]==="{"&&(this.i++,t.blocks.push({type:n,labels:r,body:this.body()}))}}};function xve(e){return new t0(e).body()}function Eve(e){let t=[],n=r=>{let o;try{o=vve(r).sort()}catch{return}for(let s of o){if(Rve.has(s)||s.startsWith("."))continue;let i=_ve(r,s),a;try{a=kve(i)}catch{continue}a.isDirectory()?n(i):(s.endsWith(".tf")||s.endsWith(".tofu"))&&t.push(i)}};return n(e),t}function mk(e,t){let n=[];for(let r of Eve(e)){let o=$ve(r,"utf-8");if(o.includes(`"${t}`))for(let s of xve(o).blocks)s.type==="resource"&&s.labels.length===2&&s.labels[0].startsWith(t)&&n.push({type:s.labels[0],name:s.labels[1],file:Sve(e,r),body:s.body})}return n}function Tt(e){return typeof e=="string"&&!e.includes("${")?e:void 0}function Ip(e){if(!Array.isArray(e))return;let t=e.map(Tt);return t.every(n=>n!==void 0)?t:void 0}function js(e){if(!e||typeof e!="object"||!("ref"in e))return;let t=e.ref.split(".");return t.length>=2?`${t[0]}.${t[1]}`:void 0}function so(e){return e===void 0?"":typeof e=="string"?JSON.stringify(e):typeof e!="object"?String(e):Array.isArray(e)?`[${e.map(so).join(", ")}]`:"ref"in e?e.ref:"file"in e?`file("${e.file}")`:e.expr}var hk="https://intentius.io/terragucci/guides/coming-from-spacelift-or-env-zero/",Tve=[["Root","stack","environment","a [root](/terragucci/concepts/glossary/#root): one directory, one state; an import writes `roots` from the stacks' project roots or the environments' template paths"],["Shared code","several stacks on one project root","several environments from one template","a directory per stack or environment, each with its backend key and `terraform.tfvars`, calling a shared module"],["Workspace","`terraform_workspace`","`workspaceName`, `workspace`","none: each root is a directory with one state"],["Managed state","managed state, `manage_state`","env zero's remote backend, `isRemoteBackend`","a key in your S3 bucket, moved by a [backend move](#state-move); an import names each stack or environment whose state moves"],["Own backend","your own backend","your own S3 backend","the same backend; nothing moves"],["Variables","context and stack environment variables","variables","[`env`](/terragucci/reference/config/#keys) for values every root gets; a secret's name under [`pass`](#variables), whose value you create as a CI secret; a root's own values in its `terraform.tfvars`"],["Files","context and stack mounted files","","files committed in the root, such as `terraform.tfvars`"],["Cloud credentials","cloud integration","credentials","[`oidc`](/terragucci/reference/environment/#cloud-roles-over-oidc): a plan role and an apply role per cloud"],["Version","`terraform_version`, `opentofu_version`, `terraform_workflow_tool`","a template's `type`, `terraform_version`, `opentofu_version`","`binary`, and [`version`](/terragucci/reference/config/#a-version-per-root) for every root or per root glob, or the root's `.terraform-version`"],["Hooks","hooks: `before_init`, `after_plan`, `before_apply` and the rest","custom flows (`env0.yml`)","[`steps`](/terragucci/guides/run-steps/) before or after `init`, `plan`, `apply` and `drift`, for the roots the hook ran in; nothing runs at a destroy or a task"],["Runner image","`runner_image`","","`image`: one image for every job, built FROM the terragucci image for the binary"],["Plan policy","plan policy","approval policy (OPA)","[`policy`](/terragucci/reference/policy/) over each plan, with [cost](/terragucci/guides/estimate-cost/#cost-in-the-policy) in `input.cost`"],["Approval","approval policy, `autodeploy` off","approval policy, `requiresApproval`","[`gate`](/terragucci/reference/stages/#gate-policy) and [approval modes](/terragucci/guides/approve-a-wave/#approval-modes); [`cost.approve_above`](/terragucci/guides/estimate-cost/#hold-a-wave-over-an-amount); an import writes `gate: always` when every stack or environment waits for an approval"],["Triggers","push and trigger policies, project globs","triggers, `continuousDeployment`, `pullRequestPlanDeployments`","the [generated pipeline](/terragucci/getting-started/): a pull request plans the roots its files reach; merge applies them"],["Order","stack dependencies, output references","workflows","[waves](/terragucci/concepts/waves-and-approvals/#linked-roots): a root that reads another through `terraform_remote_state` applies in a later wave; an import writes `waves.after` for a stack dependency the reads do not give"],["Access","login policy, spaces","RBAC","your forge's permissions and cloud IAM ([access and identity](/terragucci/standards/access-and-identity/))"],["Notifications","notification policy","notifications","[`notify`](/terragucci/guides/notify-a-chat-channel/): Slack, Teams or a signed webhook"],["Drift","drift detection","drift detection, `driftDetectionCron`","[`drift`](/terragucci/guides/turn-on-drift-checks/), one cron schedule over every root, and a pull request for drift on a literal; it never reconciles"],["TTL","","environment TTL","[ephemeral environments](/terragucci/guides/ephemeral-environments/) per pull request, with a TTL; an import writes `ephemeral` for the environments that expire"],["Cost","","cost monitoring","[`cost`](/terragucci/guides/estimate-cost/): an estimate per plan, not billed spend"],["Workers","private workers, `worker_pool_id`","self-hosted agents","your CI's runners, your own ones by label with [`runner`](/terragucci/guides/add-to-a-repo/#self-hosted-runners)"],["Module registry","module registry","module registry","[a module registry](/terragucci/guides/publish-modules/#serve-a-module-registry) in your bucket"],["Run history","run history","deployment log","the [report](/terragucci/guides/keep-reports-in-a-bucket/) per run and [`chant/lifecycle`](/terragucci/concepts/glossary/#chantlifecycle)"]];function Ave(e){return bo(Tve.find(t=>t[0]===e)[3])}function DJ(e,t,n,r,o){e.cell(t,n,r,Ave(r),o)}function Xt(e,t,n){e.own(t,"unmapped",`the guide has no row for it, so nothing was written; see ${hk}`,n)}function Qo(e,t,n,r){e.own(t,"default",`${n}'s own setting, with nothing to carry over`,r)}var MJ=/^[A-Za-z_][A-Za-z0-9_]*$/,Jc=class{constructor(t){this.platform=t}platform;notes=new Ei;s={};units=[];steps=[];versions=new Map;binaries=new Map;env=new Map;envConflicts=new Set;secrets=new Map;approvals=[];drift=[];edges=[];ephemeral=[];ttl;note(t,n,r,o){DJ(this.notes,t,n,r,o)}version(t,n,r){if(n===void 0)return;let o=String(n).trim().replace(/^v/,"");if(!Ja.test(o))return this.note("default",r,"Version",`${JSON.stringify(n)} is not one release, so the root's required_version pin decides`);if(t===void 0)return this.note("unmapped",r,"Version","the import cannot tell which root it is for");let s=this.versions.get(t);if(s&&s.version!==o)return this.note("unmapped",r,"Version",`${t} runs ${s.version} by ${s.key}`);s||this.versions.set(t,{version:o,key:r})}binary(t,n){this.binaries.set(t,[...this.binaries.get(t)??[],n])}takeEnv(t,n,r){if(!MJ.test(n))return this.note("unmapped",t,"Variables",`${JSON.stringify(n)} is not a variable name`);let o=this.env.get(n);if(o&&o.value!==r)return this.envConflicts.add(n),this.note("unmapped",t,"Variables",`${n} is set to two values (in ${o.key} too), and \`env\` holds one`);o||this.env.set(n,{value:r,key:t})}takeSecret(t,n){if(!MJ.test(n))return this.note("unmapped",t,"Variables",`${JSON.stringify(n)} is not a variable name`);if(ku.some(r=>n.toUpperCase().startsWith(r))||nd.includes(n))return this.note("unmapped",t,"Variables",`${n} is a name the forge refuses for a secret or terragucci sets itself; pass it under another name`);this.secrets.set(n,[...this.secrets.get(n)??[],t])}static script(t){return t.length===1?t[0]:["set -e",...t].join(`
941
- `)}};function yk(e,t,n){let{notes:r,s:o}=e,s=new Map;for(let p of e.units)p.root!==void 0&&s.set(p.root,[...s.get(p.root)??[],p]);let i=e.units.filter(p=>p.root===void 0);s.size&&!i.length?(o.roots=[...s.keys()].sort(),e.note("mapped",n,"Root",`roots: ${o.roots.join(", ")}`)):s.size&&e.note("unmapped",n,"Root",`no roots written, so terragucci detects them: ${i.map(p=>p.id).join(", ")} ${i.length===1?"has":"have"} no directory the files name`);for(let[p,f]of s)f.length>1&&e.note("unmapped",f.map(g=>g.key).join(", "),"Shared code",`${f.map(g=>g.id).join(", ")} all run ${p}, which is one root with one state; give each a directory`);let a=[...e.binaries.keys()];a.length===1?(o.binary=a[0],e.note("mapped",e.binaries.get(a[0])[0],"Version",`binary: ${a[0]}`)):a.length>1&&e.note("unmapped",[...e.binaries.values()].flat().join(", "),"Version",`the ${e.platform} config runs ${a.join(" and ")}, and the pipeline runs one binary`);let c=new Set([...e.versions.values()].map(p=>p.version));if(c.size===1){let[p]=c;o.version=p;for(let f of e.versions.values())e.note("mapped",f.key,"Version",`version: ${p}`)}else if(c.size>1){o.version=Object.fromEntries([...e.versions].sort(([p],[f])=>p.localeCompare(f)).map(([p,f])=>[p,f.version]));for(let[p,f]of e.versions)e.note("mapped",f.key,"Version",`version: ${p}: ${f.version}`)}if(e.edges.length){let{after:p,fromReads:f,cycle:g}=jp(e.edges.map(m=>[m.d,m.u]),t.repo?.reads);Object.keys(p).length&&!g&&(o.waves={...o.waves,after:p});for(let m of e.edges){let h=`${m.d} after ${m.u}`;m.d===m.u?e.note("unmapped",m.key,"Order",`both stacks run ${m.d}`):f.has(`${m.d}\0${m.u}`)?e.note("default",m.key,"Order",`the terraform_remote_state reads already order ${h}`):g?e.note("unmapped",m.key,"Order",`${h} is not kept: with the terraform_remote_state reads it makes a cycle, ${g.join(" after ")}`):e.note("mapped",m.key,"Order",`waves.after: ${h}`)}}let u=`${e.platform} applies the branch a ${n==="stacks"?"stack":"environment"} tracks after a push, terragucci the default branch after a merge`;if(t.applyWhen==="pull-request"&&t.forge==="gitlab"?r.own("when it applies","unmapped",u,`left at apply.when: merge: ${us.comments}; and ${us.token}`):t.applyWhen==="pull-request"?(o.apply={when:"pull-request"},r.own("when it applies","mapped",u,"apply.when: pull-request, from --apply-when")):r.own("when it applies","default",u,"apply.when: merge, terragucci's default"),e.approvals.length){let p=e.approvals.filter(f=>f.waits);if(p.length===e.approvals.length){o.gate="always";for(let f of p)e.note("mapped",f.key,"Approval","gate: always")}else if(p.length)for(let f of p)e.note("unmapped",f.key,"Approval",`gate is one setting for every wave, and ${e.approvals.length-p.length} of ${e.approvals.length} ${n} apply without an approval; gate: always holds them all`);else for(let f of e.approvals)e.note("default",f.key,"Approval","applies without an approval, as gate's default does for a change that destroys nothing")}let l=[...new Set(e.drift.map(p=>p.cron))];if(l.length===1){o.drift=l[0];let p=new Set(e.drift.map(f=>f.unit)).size>=e.units.length;for(let f of e.drift)e.note("mapped",f.key,"Drift",`drift: "${l[0]}"${p?"":`, over every root, not only ${[...new Set(e.drift.map(g=>g.unit))].join(", ")}`}`)}else if(l.length>1)for(let p of e.drift)e.note("unmapped",p.key,"Drift",`drift is one schedule, and the ${n} give ${l.join(", ")}`);if(e.ephemeral.length){let p=[...new Set(e.ephemeral.map(f=>f.root))].sort();o.ephemeral={roots:p,...e.ttl?{ttl:e.ttl.value}:{}};for(let f of e.ephemeral)e.note("mapped",f.key,"TTL",`ephemeral.roots: ${f.root}${e.ttl?`, ttl: ${e.ttl.value}`:", ttl: 24h, terragucci's default"}`);e.ttl&&e.note("mapped",e.ttl.key,"TTL",`ephemeral.ttl: ${e.ttl.value}`)}let d=[...e.env].filter(([p])=>!e.envConflicts.has(p)&&!e.secrets.has(p));for(let[p,f]of e.env)e.secrets.has(p)&&e.note("unmapped",f.key,"Variables",`${p} is a secret elsewhere, so it goes under pass`);if(d.length){o.env=Object.fromEntries(d.map(([p,f])=>[p,f.value]));for(let[p,f]of d)e.note("mapped",f.key,"Variables",`env: ${p}`)}if(e.secrets.size){o.pass={secrets:[...e.secrets.keys()].sort()};for(let[p,f]of e.secrets){let g=f.length>1?`; set in ${f.length} places, and a CI secret holds one value, so give a root's own value a name of its own`:"";e.note("mapped",f.join(", "),"Variables",`pass.secrets: ${p}; create the CI secret ${p}, whose value ${e.platform} does not give back${g}`)}}e.steps.length&&(o.steps=e.steps)}var bk=["env0-discovery.yml","env0-discovery.yaml"],Cve=new Set(["env0.yml","env0.yaml"]),Nve=new Set(["name","templateId","templateName","projectId","projectName","workspaceName","revision","variableFiles","requiresApproval","isRemoteBackend","continuousDeployment","autoDeployOnPathChangesOnly","autoDeployByCustomGlob","pullRequestPlanDeployments","vcsPrCommentsEnabled","vcsCommandsAlias","driftDetectionCron"]),Dve={setupVariables:{after:{before:"init"}},terraformInit:{before:{before:"init"},after:{after:"init"}},opentofuInit:{before:{before:"init"},after:{after:"init"}},terraformPlan:{before:{before:"plan"},after:{after:"plan"}},opentofuPlan:{before:{before:"plan"},after:{after:"plan"}},terraformApply:{before:{before:"apply"},after:{after:"apply"}},opentofuApply:{before:{before:"apply"},after:{after:"apply"}},storeState:{before:{after:"apply"},after:{after:"apply"}},terraformOutput:{before:{after:"apply"},after:{after:"apply"}},opentofuOutput:{before:{after:"apply"},after:{after:"apply"}}},Mve=/^(pulumi|cf|k8s|helm|ansible)/;function GJ(e){let t=/^([1-9]\d*)-([hdwM])$/.exec(e.trim());if(!t)return;let n=Number(t[1]),r=t[2]==="h"?`${n}h`:t[2]==="d"?`${n}d`:t[2]==="w"?`${n*7}d`:`${n*30}d`;return ad(r)?r:void 0}var wr=e=>`${e.type}.${e.name}`;function Lve(e){let t=[],n=r=>{let o;try{o=Ove(r).sort()}catch{return}for(let s of o){if(s.startsWith(".")||s==="node_modules")continue;let i=wk(r,s);try{jve(i).isDirectory()?n(i):Cve.has(s)&&t.push(Ive(e,i))}catch{continue}}};return n(e),t}function FJ(e,t){let n;try{n=uo(Pve(wk(e,t),"utf-8"))}catch(r){throw new S(`${t} is not YAML: ${r.message}`)}if(n==null)return{};if(!Me(n))throw new S(`${t} is not a map of settings`);return n}function BJ(e,t={}){let n=t.file??bk.find(a=>LJ(wk(e,a)));if(t.file!==void 0&&!LJ(wk(e,t.file)))throw new S(`import env0 reads ${t.file}, and there is none in ${e}`);let r=n!==void 0?FJ(e,n):void 0,o=Lve(e).map(a=>({file:a,doc:FJ(e,a)})),s=mk(e,"env0_");if(r===void 0&&!o.length&&!s.length)throw new S(`import env0 reads ${bk[0]}, env0.yml custom flows or env0_* resources, and there are none in ${e}`);let i=[...n?[n]:[],...o.map(a=>a.file)];for(let a of s)i.includes(a.file)||i.push(a.file);return{...r0({discovery:r,flows:o,resources:s},t),read:i}}function Gve(e,t){return e.filter(n=>n==="."||t===n||t.startsWith(`${n}/`)).sort((n,r)=>r.length-n.length)[0]}function r0(e,t={}){let n=new Jc("env zero"),{notes:r}=n,{resources:o}=e,s=g=>o.filter(m=>m.type===g),i=t.repo?.roots,a=s("env0_template"),c=new Map;for(let g of a){let m=wr(g),h=g.body.attrs.path,y=h===void 0?".":Tt(h),w=y===void 0?void 0:xo(y.replace(/^\/+/,"")||".");c.set(m,w),w===void 0&&n.note("unmapped",`${m}.path`,"Root",`${so(h)} is not a directory inside the repo`)}let u=[];for(let g of s("env0_environment")){let h={id:Tt(g.body.attrs.name)??g.name,key:wr(g),res:g,template:js(g.body.attrs.template_id),project:js(g.body.attrs.project_id)},y=g.body.blocks.find(w=>w.type==="without_template_settings");if(y){let w=y.body.attrs.path===void 0?".":Tt(y.body.attrs.path);h.root=w===void 0?void 0:xo(w.replace(/^\/+/,"")||"."),zJ(n,`${h.key}.without_template_settings`,y.body,[h])}else h.template&&c.has(h.template)&&(h.root=c.get(h.template));u.push(h)}let l=e.discovery??{};for(let g of Object.keys(l))g!=="environments"&&Xt(r,g);let d=Me(l.environments)?l.environments:{};l.environments!==void 0&&!Me(l.environments)&&Xt(r,"environments");for(let[g,m]of Object.entries(d)){let h=Me(m)?m:{},y=`environments.${g}`,w=typeof h.name=="string"?h.name:g,b=u.find(v=>v.id===w),k=b??{id:w,key:y};k.conf=h,k.confKey=y,b||u.push(k);for(let v of Object.keys(h))Nve.has(v)||Xt(r,`${y}.${v}`);if(k.root===void 0&&typeof h.templateName=="string"){let v=a.find($=>Tt($.body.attrs.name)===h.templateName);v&&(k.template=wr(v),k.root=c.get(wr(v)))}if(k.root===void 0&&i){let v=oo(h.variableFiles).find(_=>Me(_)&&typeof _.path=="string"),$=v?Gve(i,n0(v.path.replace(/^\.?\//,""))):void 0;$&&(k.root=$,n.note("mapped",`${y}.variableFiles`,"Root",`${$}, the root ${v.path} is in`))}if(k.root===void 0){let v=h.templateName??h.templateId;n.note("unmapped",y,"Root",`its template ${v===void 0?"":`${JSON.stringify(v)} `}is not an env0_template in the repo and its variable files sit in no root, so the import cannot tell its directory`)}}n.units.push(...u);for(let g of a){let m=u.filter(h=>h.template===wr(g));zJ(n,wr(g),g.body,m),m.length||r.own(wr(g),"default","no environment the import read deploys this template, so it is not a root of its own")}let p=new Map;for(let g of s("env0_project_policy")){let m=js(g.body.attrs.project_id);m&&p.set(m,g);let h=wr(g);for(let[y,w]of Object.entries(g.body.attrs)){let b=`${h}.${y}`;if(y!=="project_id")if(y==="default_ttl"){let k=typeof w=="string"?GJ(w):void 0;k&&(!n.ttl||n.ttl.value===k)?n.ttl={value:k,key:b}:k?n.note("unmapped",b,"TTL",`ephemeral.ttl is one setting, and ${n.ttl.key} gives ${n.ttl.value}`):n.note("default",b,"TTL",`${so(w)} is no fixed TTL, so nothing expires`)}else if(y==="max_ttl")n.note("default",b,"TTL","a copy lives until its pull request closes or its TTL passes");else if(y==="drift_detection_cron"){let k=Tt(w);k&&n.drift.push({cron:k,key:b,unit:m??h})}else y==="include_cost_estimation"?w===!0?(n.s.cost=!0,n.note("mapped",b,"Cost","cost: true")):n.note("default",b,"Cost","off, as terragucci's default"):y==="requires_approval_default"||y==="continuous_deployment_default"||y==="run_pull_request_plan_default"||y==="vcs_pr_comments_enabled_default"?Qo(r,b,"env zero","a default for new environments; each environment's own setting is read"):y==="auto_drift_remediation"?w!=="DISABLED"&&n.note("unmapped",b,"Drift","a drift run never applies; respond.drift opens a pull request for it"):y==="force_remote_backend"?w===!0&&n.note("unmapped",b,"Managed state","the project's environments keep their state in env zero"):Qo(r,b,"env zero")}}for(let g of u){let m=g.conf,h=g.res,y=(N,L)=>{if(m&&m[N]!==void 0)return{v:m[N],key:`${g.confKey}.${N}`};if(h&&h.body.attrs[L]!==void 0)return{v:h.body.attrs[L],key:`${g.key}.${L}`}},w=y("workspaceName","workspace");w&&n.note("unmapped",w.key,"Workspace",`workspace ${so(w.v)} is not planned`);let b=y("revision","revision");b&&Qo(r,b.key,"env zero","terragucci applies the default branch, and apply.branches applies roots from another");let k=y("requiresApproval","approve_plan_automatically"),v=k?k.key.endsWith("requiresApproval")?k.v===!0:k.v===!1:void 0;k&&v!==void 0&&n.approvals.push({key:k.key,waits:v});let $=y("isRemoteBackend","is_remote_backend"),_=g.project?p.get(g.project):void 0;$?.v===!0||!$&&_?.body.attrs.force_remote_backend===!0?n.note("unmapped",$?.key??`${g.key}: ${g.id}`,"Managed state",`${g.id}'s state is in env zero's remote backend: move it to ${g.root??"its root"}'s backend before the root's backend block merges`):$&&n.note("default",$.key,"Own backend");let E=y("continuousDeployment","deploy_on_push");E?.v===!1?n.note("unmapped",E.key,"Triggers","merge applies what changed; gate: always holds each wave for an approval first"):E&&n.note("default",E.key,"Triggers");for(let[N,L]of[["autoDeployOnPathChangesOnly","auto_deploy_on_path_changes_only"],["autoDeployByCustomGlob","auto_deploy_by_custom_glob"],["pullRequestPlanDeployments","run_plan_on_pull_requests"],["vcsPrCommentsEnabled","vcs_pr_comments_enabled"]]){let U=y(N,L);U&&n.note("default",U.key,"Triggers",N==="vcsPrCommentsEnabled"?"a comment of /terragucci plan plans again":void 0)}let R=y("vcsCommandsAlias","vcs_commands_alias");R&&n.note("unmapped",R.key,"Triggers","comments name a root by its path: /terragucci plan <root>");let x=y("driftDetectionCron","drift_detection_cron");x&&typeof x.v=="string"&&n.drift.push({cron:x.v,key:x.key,unit:g.id});let A=y("projectName","project_id")??y("projectId","project_id");if(A&&Qo(r,A.key.replace(/\.[^.]+$/,".project"),"env zero","a project's environments are this repo's roots"),m){for(let N of["name","templateName","templateId"])m[N]!==void 0&&n.note("default",`environments.*.${N}`,"Root","a root goes by its path from the repo root");oo(m.variableFiles).forEach((N,L)=>{let U=`${g.confKey}.variableFiles[${L}]`,F=Me(N)&&typeof N.path=="string"?N.path.replace(/^\.?\//,""):void 0;if(F===void 0)return Xt(r,U);let M=F.split("/").pop(),te=M==="terraform.tfvars"||M.endsWith(".auto.tfvars");g.root!==void 0&&n0(F)===g.root&&te?n.note("default",U,"Variables",`the binary reads ${F} in ${g.root} itself`):n.note("unmapped",U,"Variables",`copy ${F} to ${g.root??"the root"}/terraform.tfvars (or a *.auto.tfvars there), which the binary reads`)})}let O=h?.body.attrs.ttl,I=_&&typeof _.body.attrs.default_ttl=="string"?GJ(_.body.attrs.default_ttl):void 0;if(O!==void 0||I){let N=O!==void 0?`${g.key}.ttl`:`${g.key}: ${g.id}`;g.root===void 0?n.note("unmapped",N,"TTL",`${g.id} has no root`):n.ephemeral.push({root:g.root,key:N})}if(h){let N=new Set(["name","template_id","project_id","workspace","revision","approve_plan_automatically","is_remote_backend","deploy_on_push","auto_deploy_on_path_changes_only","auto_deploy_by_custom_glob","run_plan_on_pull_requests","vcs_pr_comments_enabled","vcs_commands_alias","drift_detection_cron","ttl"]);for(let L of Object.keys(h.body.attrs))N.has(L)||(L==="variable_sets"?n.note("unmapped",`${g.key}.variable_sets`,"Variables","set the variable sets' values by hand: env for values, pass for secrets"):L==="terragrunt_working_directory"?n.note("default",`${g.key}.${L}`,"Root","Terragrunt is detected, and each unit is a root"):L==="k8s_namespace"||L==="sub_environment_configuration"?n.note("unmapped",`${g.key}.${L}`,"Root","terragucci runs Terraform, OpenTofu and Terragrunt"):Qo(r,`env0_environment.*.${L}`,"env zero"));for(let L of h.body.blocks)if(L.type==="configuration")UJ(n,`${g.key}.configuration: ${Tt(L.body.attrs.name)??"?"}`,L.body,!1,g);else{if(L.type==="without_template_settings")continue;L.type==="sub_environment_configuration"?n.note("unmapped",`${g.key}.sub_environment_configuration`,"Order","a workflow's sub environments are roots, ordered by their terraform_remote_state reads"):Qo(r,`env0_environment.*.${L.type}`,"env zero")}}}for(let g of s("env0_configuration_variable")){let m=wr(g),h=js(g.body.attrs.environment_id),y=js(g.body.attrs.template_id),w=h?u.find(v=>v.res&&wr(v.res)===h):void 0,b=y?u.filter(v=>v.template===y):void 0,k=!h&&(!y||b!==void 0&&b.length===u.length);UJ(n,m,g.body,k,w??(b?.length===1?b[0]:void 0))}for(let g of s("env0_environment_drift_detection")){let m=js(g.body.attrs.environment_id),h=Tt(g.body.attrs.cron);h?n.drift.push({cron:h,key:`${wr(g)}.cron`,unit:u.find(w=>w.res&&wr(w.res)===m)?.id??wr(g)}):n.note("unmapped",`${wr(g)}.cron`,"Drift","it is not a plain cron schedule");let y=g.body.attrs.auto_drift_remediation;y!==void 0&&y!=="DISABLED"&&n.note("unmapped",`${wr(g)}.auto_drift_remediation`,"Drift","a drift run never applies; respond.drift opens a pull request for it")}for(let g of e.flows)Fve(n,g.file,g.doc,u);let f=new Set(["env0_template","env0_environment","env0_project_policy","env0_configuration_variable","env0_environment_drift_detection"]);for(let g of o){if(f.has(g.type))continue;let m=wr(g);g.type==="env0_project"||g.type==="env0_template_project_assignment"?Qo(r,m,"env zero","a project's environments are this repo's roots"):g.type.includes("credentials")?n.note("unmapped",m,g.type.startsWith("env0_cost_")?"Cost":"Cloud credentials",g.type.startsWith("env0_cost_")?"Infracost prices on your INFRACOST_API_KEY":"name a read-only plan role and an apply role under oidc"):g.type.startsWith("env0_variable_set")?n.note("unmapped",m,"Variables","set its values by hand: env for values, pass for secrets"):g.type.startsWith("env0_agent")?n.note("unmapped",m,"Workers"):g.type.startsWith("env0_notification")?n.note("unmapped",m,"Notifications"):g.type.startsWith("env0_approval_policy")?n.note("unmapped",m,"Plan policy","copy its Rego into policy; a rule that asked for an approval becomes cost.approve_above or gate: always"):/^env0_(user|team|custom_role|.*role_assignment|organization)/.test(g.type)?n.note("unmapped",m,"Access"):g.type==="env0_module"||g.type.startsWith("env0_module_")?n.note("unmapped",m,"Module registry"):g.type.includes("trigger")?n.note("unmapped",m,"Order","a root that reads another's state through terraform_remote_state applies after it"):Xt(r,m)}return yk(n,t,"environments"),{settings:n.s,notes:r.list}}function zJ(e,t,n,r){let o=Tt(n.attrs.type)??"terraform",s=!1;if(o==="opentofu")s=!0,e.binary("tofu",`${t}.type`);else if(o==="terraform")n.attrs.type!==void 0&&e.binary("terraform",`${t}.type`);else if(o==="terragrunt")e.note("default",`${t}.type`,"Root","Terragrunt is detected from the repo's root.hcl or terragrunt.hcl, with terragrunt.version for its release"),s=(Tt(n.attrs.terragrunt_tf_binary)??"opentofu")==="opentofu",e.binary(s?"tofu":"terraform",`${t}.terragrunt_tf_binary`);else{e.note("unmapped",`${t}.type`,"Root",`terragucci runs Terraform, OpenTofu and Terragrunt, not ${o}`);return}let i=s?n.attrs.opentofu_version:n.attrs.terraform_version,a=`${t}.${s?"opentofu_version":"terraform_version"}`;if(i!==void 0){let u=[...new Set(r.map(l=>l.root).filter(l=>l!==void 0))];u.length||e.version(void 0,i,a);for(let l of u)e.version(l,i,a)}(s?n.attrs.terraform_version:n.attrs.opentofu_version)!==void 0&&e.note("default",`${t}.${s?"terraform_version":"opentofu_version"}`,"Version",`the template runs ${s?"OpenTofu":"Terraform"}`)}function UJ(e,t,n,r,o){let s=Tt(n.attrs.name);if(!s)return e.note("unmapped",t,"Variables","its name is not a plain string");let i=Tt(n.attrs.type)==="terraform"?`TF_VAR_${s}`:s;if(n.attrs.is_sensitive===!0)return e.takeSecret(t,i);let a=Tt(n.attrs.value);if(a===void 0)return e.note("unmapped",t,"Variables","its value is not a plain string in the code; set it by hand");if(r)return e.takeEnv(t,i,a);let c=o?.root;e.note("unmapped",t,"Variables",i.startsWith("TF_VAR_")?`it is ${o?.id??"one environment"}'s alone: set ${s} in ${c??"its root"}/terraform.tfvars`:`it is ${o?.id??"one environment"}'s alone, and env is every root's`)}function Fve(e,t,n,r){let{notes:o}=e,s=xo(n0(t))??".",i=[...new Set(r.map(p=>p.root).filter(p=>p!==void 0))],a=r.length>0&&r.every(p=>p.root===s),c=s==="."&&!i.includes(".");if(!c&&!i.includes(s)&&r.length){o.own(t,"unmapped",`no environment the import read runs ${s}`,"a custom flow runs in its template's directory");return}let u=n.shell==="bash";for(let p of Object.keys(n))p==="version"?o.own(`${t}: version`,"default","the file's format version; terragucci.yml has none"):p==="shell"?o.own(`${t}: shell`,u?"mapped":"default",u?"each step runs its commands under bash":"a step runs under sh"):p==="destroy"?e.note("unmapped",`${t}: destroy`,"Hooks","a destroy is a reviewed change that removes the code, or an ephemeral copy's sweep"):p==="task"?e.note("unmapped",`${t}: task`,"Hooks","a task has no counterpart: state changes come from a reviewed commit"):p!=="deploy"&&Xt(o,`${t}: ${p}`);let l=Me(n.deploy)?n.deploy:{},d=(p,f,g)=>{let m=[];if(oo(g).forEach((y,w)=>{let b=typeof y=="string"?y:Me(y)&&typeof y.run=="string"?y.run.trimEnd():void 0;if(b===void 0)return Xt(o,`${p}[${w}]`);let k=/^echo\s+["']?([A-Za-z_][A-Za-z0-9_]*)=([^"'$`]*)["']?\s*>>\s*"?\$\{?ENV0_ENV\}?"?\s*$/.exec(b.trim());if(k){a||c?e.takeEnv(`${p}[${w}]`,k[1],k[2]):e.note("unmapped",`${p}[${w}]`,"Variables",`it is ${s}'s alone, and env is every root's`);return}b.includes("ENV0_ENV")&&e.note("unmapped",`${p}[${w}]`,"Hooks","it sets a variable for later steps, and a step cannot set the binary's environment"),m.push(b)}),!m.length)return;let h=Jc.script(m);u&&(h=`bash -c '${h.replace(/'/g,"'\\''")}'`),c&&(h=`cd "$TG_REPO" && ${h}`),e.steps.push({name:p,run:h,...f,...c||a?{}:{roots:[s]}}),e.note("mapped",p,"Hooks",`steps: ${f.before?"before":"after"}: ${f.before??f.after}${c||a?" for every root":` for ${s}`}${c?", from the repo's root":""}`)};for(let[p,f]of Object.entries(l)){let g=`${t}: deploy.${p}`;if(p==="onSuccess")d(g,{after:"apply"},f);else if(p==="onFailure"||p==="onCompletion")e.note("unmapped",g,"Hooks","a step after a stage runs when the stage succeeded");else if(p==="steps"&&Me(f))for(let[m,h]of Object.entries(f)){let y=`${t}: deploy.steps.${m}`,w=Dve[m];if(!w){Mve.test(m)?e.note("unmapped",y,"Root","terragucci runs Terraform, OpenTofu and Terragrunt"):Xt(o,y);continue}if(!Me(h)){Xt(o,y);continue}for(let[b,k]of Object.entries(h)){let v=b==="before"?w.before:b==="after"?w.after:void 0;v?d(`${y}.${b}`,v,k):Xt(o,`${y}.${b}`)}}else Xt(o,g)}}import{existsSync as WJ,readFileSync as zve}from"node:fs";import{join as o0}from"node:path";var $k=[".spacelift/config.yml",".spacelift/config.yaml"],vk={before_init:{before:"init"},after_init:{after:"init"},before_plan:{before:"plan"},after_plan:{after:"plan"},before_apply:{before:"apply"},after_apply:{after:"apply"},before_perform:void 0,after_perform:void 0,before_destroy:void 0,after_destroy:void 0,after_run:void 0},HJ=new Set([...Object.keys(vk),"environment","project_root","runner_image","terraform_version","opentofu_version","terraform_workflow_tool","git_sparse_checkout_paths","terragrunt"]),qJ=new Set(["name","slug","repository","branch","description","space_id","protect_from_deletion","enable_local_preview","autoretry","allow_run_promotion","github_action_deploy","enable_well_known_secret_masking","terraform_smart_sanitization","terraform_external_state_access","prevent_changes_when_locked","git_sparse_checkout_paths","enable_sensitive_outputs_upload","import_state","import_state_file","labels","github_enterprise","gitlab","azure_devops","bitbucket_cloud","bitbucket_datacenter","raw_git","origin","spacelift_repo","showcase"]);function Uve(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}function JJ(e,t){return t in e.body.attrs||e.body.blocks.some(n=>n.type===t)}var Zn=e=>`${e.type}.${e.name}`;function KJ(e,t={}){let n=t.file??$k.find(i=>WJ(o0(e,i))),r={},o=[];if(n!==void 0){if(!WJ(o0(e,n)))throw new S(`import spacelift reads ${n}, and there is none in ${e}`);let i;try{i=uo(zve(o0(e,n),"utf-8"))}catch(a){throw new S(`${n} is not YAML: ${a.message}`)}if(i!=null&&!Me(i))throw new S(`${n} is not a map of settings`);r=i??{},o.push(n)}let s=mk(e,"spacelift_");for(let i of s)o.includes(i.file)||o.push(i.file);if(n===void 0&&!s.length)throw new S(`import spacelift reads ${$k[0]} or spacelift_stack resources, and there are neither in ${e}`);return{...s0(r,s,t),read:o}}function s0(e,t,n={}){let r=new Jc("Spacelift"),{notes:o}=r,s=new Map(t.map(w=>[Zn(w),w])),i=w=>t.filter(b=>b.type===w);for(let w of Object.keys(e))w==="version"?o.own("version","default","the file's format version; terragucci.yml has none"):w==="module_version"||w==="tests"?r.note("unmapped",w,"Module registry","a module's version and tests are the module's own release"):w!=="stack_defaults"&&w!=="stacks"&&Xt(o,w);let a=Me(e.stack_defaults)?e.stack_defaults:{};e.stack_defaults!==void 0&&!Me(e.stack_defaults)&&Xt(o,"stack_defaults");let c=Me(e.stacks)?e.stacks:{};e.stacks!==void 0&&!Me(e.stacks)&&Xt(o,"stacks");let u=[],l=new Map;for(let w of i("spacelift_stack")){let b=Tt(w.body.attrs.name),k=Tt(w.body.attrs.slug)??(b?Uve(b):w.name),v={id:k,key:Zn(w),res:w,labels:Ip(w.body.attrs.labels)??[]};u.push(v),l.set(k,v)}for(let[w,b]of Object.entries(c)){let k=l.get(w)??{id:w,key:`stacks.${w}`,labels:[]};l.has(w)||(u.push(k),l.set(w,k)),k.conf=Me(b)?b:{},k.confKey=`stacks.${w}`,!Me(b)&&b!==null&&Xt(o,`stacks.${w}`);for(let v of Object.keys(k.conf))HJ.has(v)||Xt(o,`stacks.${w}.${v}`)}for(let w of Object.keys(a))HJ.has(w)||Xt(o,`stack_defaults.${w}`);!u.length&&Me(e.stack_defaults)&&u.push({id:"the repo's stack",key:"stack_defaults",labels:[]});let d=(w,b)=>{if(w.conf&&w.conf[b]!==void 0)return{v:w.conf[b],key:`${w.confKey}.${b}`};if(a[b]!==void 0)return{v:a[b],key:`stack_defaults.${b}`};let k=w.res?.body.attrs[b];if(k!==void 0)return{v:k,key:`${w.key}.${b}`}};for(let w of u){let b=d(w,"project_root"),k=b===void 0?".":typeof b.v=="string"?Tt(b.v):void 0;w.root=k===void 0?void 0:xo(k===""?".":k),b&&w.root===void 0&&r.note("unmapped",b.key,"Root",`${so(b.v)} is not a directory inside the repo`),r.units.push(w)}for(let w of u){let b=d(w,"terraform_workflow_tool"),k=w.res&&w.res.body.blocks.some(E=>E.type==="opentofu"),v=!1;b?b.v==="OPEN_TOFU"?(v=!0,r.binary("tofu",b.key)):b.v==="TERRAFORM_FOSS"?r.binary("terraform",b.key):r.note("unmapped",b.key,"Version",`${so(b.v)} is not Terraform or OpenTofu; set binary to the one the stack runs`):k&&(v=!0,r.binary("tofu",`${w.key}.opentofu`));let $=v?d(w,"opentofu_version")??d(w,"terraform_version"):d(w,"terraform_version");$&&r.version(w.root,$.v,$.key);let _=v?d(w,"terraform_version"):d(w,"opentofu_version");_&&_!==$&&r.note("default",_.key,"Version",`the stack runs ${v?"OpenTofu":"Terraform"}`)}let p=i("spacelift_context"),f=new Map(p.map(w=>[Zn(w),new Set])),g=w=>{let b=js(w);if(b)return u.find(v=>v.res&&Zn(v.res)===b);let k=Tt(w);return k?l.get(k):void 0};for(let w of i("spacelift_context_attachment")){let b=js(w.body.attrs.context_id),k=g(w.body.attrs.stack_id);b&&f.has(b)&&k?(f.get(b).add(k),Qo(o,Zn(w),"Spacelift",`attaches ${b} to ${k.id}`)):r.note("unmapped",Zn(w),"Variables","the import cannot tell which context and stack it joins")}for(let w of p){for(let b of Ip(w.body.attrs.labels)??[]){let k=/^autoattach:(.+)$/.exec(b);if(k)for(let v of u)(k[1]==="*"||v.labels.includes(k[1]))&&f.get(Zn(w)).add(v)}for(let b of Object.keys(w.body.attrs))!(b in vk)&&b!=="name"&&b!=="labels"&&b!=="description"&&b!=="space_id"&&Xt(o,`${Zn(w)}.${b}`)}let m=w=>u.every(b=>w.has(b)),h=new Map;for(let[w,b]of Object.entries(vk))for(let k of u){let v=d(k,w),$=v?Array.isArray(v.v)?Ip(v.v):void 0:[];if(v&&$===void 0){r.note("unmapped",v.key,"Hooks","it is not a list of commands");continue}let _=p.filter(I=>f.get(Zn(I)).has(k)&&I.body.attrs[w]!==void 0),E=_.flatMap(I=>Ip(I.body.attrs[w])??[]),R=w.startsWith("before")?[...E,...$]:[...$,...E],x=[...v?[v.key]:[],..._.map(I=>`${Zn(I)}.${w}`)];if(!R.length)continue;if(!b){for(let I of x)r.note("unmapped",I,"Hooks",w.includes("destroy")?"a destroy is a reviewed change that removes the code":w.includes("perform")?"a task has no counterpart: state changes come from a reviewed commit":"after_run runs whatever the run's outcome; a step after a stage runs when the stage succeeded");continue}if(k.root===void 0){for(let I of x)r.note("unmapped",I,"Hooks",`${k.id} has no root`);continue}let A=Jc.script(R),O=h.get(`${w}\0${A}`)??{stage:b,hook:w,script:A,roots:new Set,keys:new Set,stacks:new Set};O.roots.add(k.root),O.stacks.add(k),x.forEach(I=>O.keys.add(I)),h.set(`${w}\0${A}`,O)}for(let w of h.values()){let b=m(w.stacks),k=[...w.roots].sort();r.steps.push({name:w.hook,run:w.script,...w.stage,...b?{}:{roots:k}});let v=`${w.stage.before?"before":"after"}: ${w.stage.before??w.stage.after}`;for(let $ of w.keys)r.note("mapped",$,"Hooks",`steps: ${v}${b?" for every root":` for ${k.join(", ")}`}`)}if(Me(a.environment))for(let[w,b]of Object.entries(a.environment))typeof b=="string"||typeof b=="number"||typeof b=="boolean"?r.takeEnv(`stack_defaults.environment.${w}`,w,String(b)):r.note("unmapped",`stack_defaults.environment.${w}`,"Variables","its value is not a string");for(let w of u)if(!(!w.conf||!Me(w.conf.environment)))for(let b of Object.keys(w.conf.environment))r.note("unmapped",`${w.confKey}.environment.${b}`,"Variables",`it is ${w.id}'s alone, and env is every root's; a TF_VAR_ value goes in ${w.root??"its root"}'s terraform.tfvars`);for(let w of i("spacelift_environment_variable")){let b=Zn(w),k=Tt(w.body.attrs.name);if(!k){r.note("unmapped",b,"Variables","its name is not a plain string");continue}let v=js(w.body.attrs.context_id),$=w.body.attrs.stack_id!==void 0?g(w.body.attrs.stack_id):void 0;if(w.body.attrs.module_id!==void 0){r.note("unmapped",b,"Module registry","a module's test variable");continue}if(w.body.attrs.write_only!==!1){r.takeSecret(b,k);continue}let _=Tt(w.body.attrs.value),E=v&&f.has(v)?f.get(v):$?new Set([$]):void 0;_===void 0?r.note("unmapped",b,"Variables","its value is not a plain string in the code; set it by hand"):E&&m(E)?r.takeEnv(b,k,_):r.note("unmapped",b,"Variables",`it is for ${E?[...E].map(R=>R.id).join(", ")||"no stack":"a stack the import cannot tell"}, and env is every root's; a TF_VAR_ value goes in the root's terraform.tfvars`)}for(let w of i("spacelift_mounted_file")){let b=Tt(w.body.attrs.relative_path)??so(w.body.attrs.relative_path);r.note("unmapped",Zn(w),"Files",w.body.attrs.write_only===!1?`commit ${b} in the root`:`${b} is secret: keep it in a CI secret and write it in a step before init`)}for(let w of i("spacelift_stack_dependency")){let b=Zn(w),k=g(w.body.attrs.stack_id),v=g(w.body.attrs.depends_on_stack_id);!k||!v?r.note("unmapped",b,"Order",`${k?so(w.body.attrs.depends_on_stack_id):so(w.body.attrs.stack_id)} names no stack the import read`):k.root===void 0||v.root===void 0?r.note("unmapped",b,"Order",`${k.root===void 0?k.id:v.id} has no root`):r.edges.push({d:k.root,u:v.root,key:b}),w.body.attrs.trigger_always!==void 0&&r.note("default",`${b}.trigger_always`,"Triggers","a root that reads a changed root's state plans with it")}for(let w of i("spacelift_stack_dependency_reference")){let b=Tt(w.body.attrs.input_name)??so(w.body.attrs.input_name),k=Tt(w.body.attrs.output_name)??so(w.body.attrs.output_name);r.note("unmapped",Zn(w),"Order",`read ${k} through a terraform_remote_state block of the upstream root in place of the input ${b}`)}for(let w of i("spacelift_drift_detection")){let b=Zn(w),k=g(w.body.attrs.stack_id),v=Ip(w.body.attrs.schedule);if(!v?.length)r.note("unmapped",`${b}.schedule`,"Drift","it is not a list of cron schedules");else for(let _ of v)r.drift.push({cron:_,key:v.length>1?`${b}.schedule: ${_}`:`${b}.schedule`,unit:k?.id??b});w.body.attrs.reconcile===!0&&r.note("unmapped",`${b}.reconcile`,"Drift","a drift run never applies; respond.drift opens a pull request for it");let $=Tt(w.body.attrs.timezone);$&&$!=="UTC"&&r.note("unmapped",`${b}.timezone`,"Drift",`the cron runs in UTC, not ${$}`)}for(let w of u){let b=w.res;if(!b)continue;let k=w.key;r.approvals.push({key:`${k}.autodeploy`,waits:b.body.attrs.autodeploy!==!0}),b.body.attrs.manage_state!==!1?r.note("unmapped",`${k}.manage_state`,"Managed state",`${w.id}'s state is in Spacelift: pull it and move it to ${w.root??"its root"}'s backend before the root's backend block merges`):r.note("default",`${k}.manage_state`,"Own backend"),b.body.attrs.terraform_workspace!==void 0&&r.note("unmapped",`${k}.terraform_workspace`,"Workspace",`workspace ${so(b.body.attrs.terraform_workspace)} is not planned`),b.body.attrs.worker_pool_id!==void 0&&r.note("unmapped",`${k}.worker_pool_id`,"Workers","name the runner label of your own runners under runner"),b.body.attrs.additional_project_globs!==void 0&&r.note("default",`${k}.additional_project_globs`,"Triggers","a root plans when a file in it, a local module it calls or its var files changed"),b.body.attrs.administrative!==void 0&&o.own(`${k}.administrative`,"unmapped","an administrative stack manages Spacelift itself","once every stack it declares has moved, stop applying it");for(let v of["pulumi","kubernetes","cloudformation","ansible"])JJ(b,v)&&r.note("unmapped",`${k}.${v}`,"Root",`terragucci runs Terraform, OpenTofu and Terragrunt, not ${v}`);JJ(b,"terragrunt")&&r.note("default",`${k}.terragrunt`,"Root","Terragrunt is detected from the repo's root.hcl or terragrunt.hcl");for(let v of Object.keys(b.body.attrs))qJ.has(v)?Qo(o,`spacelift_stack.*.${v}`,"Spacelift"):!["project_root","terraform_version","terraform_workflow_tool","autodeploy","manage_state","terraform_workspace","worker_pool_id","additional_project_globs","administrative","runner_image"].includes(v)&&!(v in vk)&&Xt(o,`${k}.${v}`);for(let v of b.body.blocks)!qJ.has(v.type)&&!["opentofu","terragrunt","pulumi","kubernetes","cloudformation","ansible"].includes(v.type)&&Xt(o,`${k}.${v.type}`)}for(let w of u){let b=d(w,"runner_image");b&&r.note("unmapped",b.key,"Runner image","build an image FROM terragucci's with what the hooks need, and set image")}(Me(a.terragrunt)||u.some(w=>w.conf&&w.conf.terragrunt!==void 0))&&r.note("default","terragrunt","Root","Terragrunt is detected from the repo's root.hcl or terragrunt.hcl, with terragrunt.version for its release");for(let w of i("spacelift_policy")){let b=Zn(w),k=Tt(w.body.attrs.type)??"",v=w.body.attrs.body,$=v&&typeof v=="object"&&"file"in v?` in ${v.file.replace(/^\$\{path\.module\}\//,"")}`:"";k==="PLAN"?r.note("unmapped",b,"Plan policy",`copy the Rego${$} into policy with input: plan, and change input.terraform to input; input.spacelift has no counterpart`):k==="APPROVAL"?r.note("unmapped",b,"Approval","gate decides which waves wait, and how many reviewers a change needs is the forge's branch protection"):k==="GIT_PUSH"||k==="TRIGGER"?r.note("default",b,"Triggers"):k==="LOGIN"||k==="ACCESS"||k==="STACK_ACCESS"?r.note("unmapped",b,"Access"):k==="NOTIFICATION"?r.note("unmapped",b,"Notifications","set notify for the channels it routed to"):o.own(b,"unmapped",`a ${k||"policy"} policy has no counterpart`,"a state change comes from an import, removed or moved block, or a migration, in a reviewed commit")}let y=new Set(["spacelift_stack","spacelift_context","spacelift_context_attachment","spacelift_environment_variable","spacelift_mounted_file","spacelift_stack_dependency","spacelift_stack_dependency_reference","spacelift_drift_detection","spacelift_policy"]);for(let w of t){if(y.has(w.type))continue;let b=Zn(w);w.type==="spacelift_policy_attachment"?Qo(o,b,"Spacelift","the policy's own note says where it goes"):/_(integration|integration_attachment|aws_role)$/.test(w.type)||w.type.startsWith("spacelift_aws_")||w.type.startsWith("spacelift_gcp_")||w.type.startsWith("spacelift_azure_")?r.note("unmapped",b,"Cloud credentials","name a read-only plan role and an apply role under oidc"):w.type==="spacelift_worker_pool"?r.note("unmapped",b,"Workers"):w.type==="spacelift_module"||w.type.startsWith("spacelift_module_")?r.note("unmapped",b,"Module registry"):/^spacelift_(space|role|role_attachment|user|idp_group_mapping|api_key|team|saml_saml2|audit_trail_webhook)/.test(w.type)?r.note("unmapped",b,"Access"):w.type==="spacelift_webhook"||w.type==="spacelift_named_webhook"?r.note("unmapped",b,"Notifications","notify.webhook posts a signed body to an address"):Xt(o,b)}return yk(r,n,"stacks"),{settings:r.s,notes:o.list}}import{existsSync as Sh,readdirSync as Bve,readFileSync as i0,statSync as VJ}from"node:fs";import{join as Kc,relative as Wve,resolve as Hve}from"node:path";var qve=/[A-Za-z0-9_-]/;function a0(e){let t=0,n=p=>{let f=e.slice(0,t).split(`
940
+ `;)this.i++;continue}if(this.ws(!0),this.s[this.i]==="="&&this.s[this.i+1]!=="="){this.i++,t.attrs[n]=this.value();continue}let r=[];for(;this.s[this.i]==='"'||/[A-Za-z_]/.test(this.s[this.i]??"");)r.push(this.s[this.i]==='"'?this.str():this.ident()),this.ws(!0);this.s[this.i]==="{"&&(this.i++,t.blocks.push({type:n,labels:r,body:this.body()}))}}};function xve(e){return new t0(e).body()}function Eve(e){let t=[],n=r=>{let o;try{o=vve(r).sort()}catch{return}for(let s of o){if(Rve.has(s)||s.startsWith("."))continue;let i=_ve(r,s),a;try{a=kve(i)}catch{continue}a.isDirectory()?n(i):(s.endsWith(".tf")||s.endsWith(".tofu"))&&t.push(i)}};return n(e),t}function mk(e,t){let n=[];for(let r of Eve(e)){let o=$ve(r,"utf-8");if(o.includes(`"${t}`))for(let s of xve(o).blocks)s.type==="resource"&&s.labels.length===2&&s.labels[0].startsWith(t)&&n.push({type:s.labels[0],name:s.labels[1],file:Sve(e,r),body:s.body})}return n}function Tt(e){return typeof e=="string"&&!e.includes("${")?e:void 0}function Ip(e){if(!Array.isArray(e))return;let t=e.map(Tt);return t.every(n=>n!==void 0)?t:void 0}function js(e){if(!e||typeof e!="object"||!("ref"in e))return;let t=e.ref.split(".");return t.length>=2?`${t[0]}.${t[1]}`:void 0}function so(e){return e===void 0?"":typeof e=="string"?JSON.stringify(e):typeof e!="object"?String(e):Array.isArray(e)?`[${e.map(so).join(", ")}]`:"ref"in e?e.ref:"file"in e?`file("${e.file}")`:e.expr}var hk="https://intentius.io/terragucci/guides/coming-from-spacelift-or-env-zero/",Tve=[["Root","stack","environment","a [root](/terragucci/concepts/glossary/#root): one directory, one state; an import writes `roots` from the stacks' project roots or the environments' template paths"],["Shared code","several stacks on one project root","several environments from one template","a directory per stack or environment, each with its backend key and `terraform.tfvars`, calling a shared module"],["Workspace","`terraform_workspace`","`workspaceName`, `workspace`","none: each root is a directory with one state"],["Managed state","managed state, `manage_state`","env zero's remote backend, `isRemoteBackend`","a key in your S3 bucket, moved by a [backend move](#state-move); an import names each stack or environment whose state moves"],["Own backend","your own backend","your own S3 backend","the same backend; nothing moves"],["Variables","context and stack environment variables","variables","[`env`](/terragucci/reference/config/#keys) for values every root gets; a secret's name under [`pass`](#variables), whose value you create as a CI secret; a root's own values in its `terraform.tfvars`"],["Files","context and stack mounted files","","files committed in the root, such as `terraform.tfvars`"],["Cloud credentials","cloud integration","credentials","[`oidc`](/terragucci/reference/environment/#cloud-roles-over-oidc): a plan role and an apply role per cloud"],["Version","`terraform_version`, `opentofu_version`, `terraform_workflow_tool`","a template's `type`, `terraform_version`, `opentofu_version`","`binary`, and [`version`](/terragucci/reference/config/#a-version-per-root) for every root or per root glob, or the root's `.terraform-version`"],["Hooks","hooks: `before_init`, `after_plan`, `before_apply` and the rest","custom flows (`env0.yml`)","[`steps`](/terragucci/guides/run-steps/) before or after `init`, `plan`, `apply` and `drift`, for the roots the hook ran in; nothing runs at a destroy or a task"],["Runner image","`runner_image`","","`image`: one image for every job, built FROM the terragucci image for the binary"],["Plan policy","plan policy","approval policy (OPA)","[`policy`](/terragucci/reference/policy/) over each plan, with [cost](/terragucci/guides/estimate-cost/#cost-in-the-policy) in `input.cost`"],["Approval","approval policy, `autodeploy` off","approval policy, `requiresApproval`","[`gate`](/terragucci/reference/stages/#gate-policy) and [approval modes](/terragucci/guides/approve-a-wave/#approval-modes); [`cost.approve_above`](/terragucci/guides/estimate-cost/#hold-a-wave-over-an-amount); an import writes `gate: always` when every stack or environment waits for an approval"],["Triggers","push and trigger policies, project globs","triggers, `continuousDeployment`, `pullRequestPlanDeployments`","the [generated pipeline](/terragucci/getting-started/): a pull request plans the roots its files reach; merge applies them"],["Order","stack dependencies, output references","workflows","[waves](/terragucci/concepts/waves-and-approvals/#linked-roots): a root that reads another through `terraform_remote_state` applies in a later wave; an import writes `waves.after` for a stack dependency the reads do not give"],["Access","login policy, spaces","RBAC","your forge's permissions and cloud IAM ([access and identity](/terragucci/standards/access-and-identity/))"],["Notifications","notification policy","notifications","[`notify`](/terragucci/guides/notify-a-chat-channel/): Slack, Teams or a signed webhook"],["Drift","drift detection","drift detection, `driftDetectionCron`","[`drift`](/terragucci/guides/turn-on-drift-checks/), one cron schedule over every root, and a pull request for drift on a literal; it never reconciles"],["TTL","","environment TTL","[ephemeral environments](/terragucci/guides/ephemeral-environments/) per pull request, with a TTL; an import writes `ephemeral` for the environments that expire"],["Cost","","cost monitoring","[`cost`](/terragucci/guides/estimate-cost/): an estimate per plan, not billed spend"],["Workers","private workers, `worker_pool_id`","self-hosted agents","your CI's runners, your own ones by label with [`runner`](/terragucci/guides/add-to-a-repo/#self-hosted-runners)"],["Module registry","module registry","module registry","[a module registry](/terragucci/guides/publish-modules/#serve-a-module-registry) in your bucket"],["Run history","run history","deployment log","the [report](/terragucci/guides/keep-reports-in-a-bucket/) per run and [`chant/lifecycle`](/terragucci/concepts/glossary/#chantlifecycle)"]];function Ave(e){return bo(Tve.find(t=>t[0]===e)[3])}function D2(e,t,n,r,o){e.cell(t,n,r,Ave(r),o)}function Xt(e,t,n){e.own(t,"unmapped",`the guide has no row for it, so nothing was written; see ${hk}`,n)}function Qo(e,t,n,r){e.own(t,"default",`${n}'s own setting, with nothing to carry over`,r)}var M2=/^[A-Za-z_][A-Za-z0-9_]*$/,Jc=class{constructor(t){this.platform=t}platform;notes=new Ei;s={};units=[];steps=[];versions=new Map;binaries=new Map;env=new Map;envConflicts=new Set;secrets=new Map;approvals=[];drift=[];edges=[];ephemeral=[];ttl;note(t,n,r,o){D2(this.notes,t,n,r,o)}version(t,n,r){if(n===void 0)return;let o=String(n).trim().replace(/^v/,"");if(!Ja.test(o))return this.note("default",r,"Version",`${JSON.stringify(n)} is not one release, so the root's required_version pin decides`);if(t===void 0)return this.note("unmapped",r,"Version","the import cannot tell which root it is for");let s=this.versions.get(t);if(s&&s.version!==o)return this.note("unmapped",r,"Version",`${t} runs ${s.version} by ${s.key}`);s||this.versions.set(t,{version:o,key:r})}binary(t,n){this.binaries.set(t,[...this.binaries.get(t)??[],n])}takeEnv(t,n,r){if(!M2.test(n))return this.note("unmapped",t,"Variables",`${JSON.stringify(n)} is not a variable name`);let o=this.env.get(n);if(o&&o.value!==r)return this.envConflicts.add(n),this.note("unmapped",t,"Variables",`${n} is set to two values (in ${o.key} too), and \`env\` holds one`);o||this.env.set(n,{value:r,key:t})}takeSecret(t,n){if(!M2.test(n))return this.note("unmapped",t,"Variables",`${JSON.stringify(n)} is not a variable name`);if(ku.some(r=>n.toUpperCase().startsWith(r))||nd.includes(n))return this.note("unmapped",t,"Variables",`${n} is a name the forge refuses for a secret or terragucci sets itself; pass it under another name`);this.secrets.set(n,[...this.secrets.get(n)??[],t])}static script(t){return t.length===1?t[0]:["set -e",...t].join(`
941
+ `)}};function yk(e,t,n){let{notes:r,s:o}=e,s=new Map;for(let p of e.units)p.root!==void 0&&s.set(p.root,[...s.get(p.root)??[],p]);let i=e.units.filter(p=>p.root===void 0);s.size&&!i.length?(o.roots=[...s.keys()].sort(),e.note("mapped",n,"Root",`roots: ${o.roots.join(", ")}`)):s.size&&e.note("unmapped",n,"Root",`no roots written, so terragucci detects them: ${i.map(p=>p.id).join(", ")} ${i.length===1?"has":"have"} no directory the files name`);for(let[p,f]of s)f.length>1&&e.note("unmapped",f.map(g=>g.key).join(", "),"Shared code",`${f.map(g=>g.id).join(", ")} all run ${p}, which is one root with one state; give each a directory`);let a=[...e.binaries.keys()];a.length===1?(o.binary=a[0],e.note("mapped",e.binaries.get(a[0])[0],"Version",`binary: ${a[0]}`)):a.length>1&&e.note("unmapped",[...e.binaries.values()].flat().join(", "),"Version",`the ${e.platform} config runs ${a.join(" and ")}, and the pipeline runs one binary`);let c=new Set([...e.versions.values()].map(p=>p.version));if(c.size===1){let[p]=c;o.version=p;for(let f of e.versions.values())e.note("mapped",f.key,"Version",`version: ${p}`)}else if(c.size>1){o.version=Object.fromEntries([...e.versions].sort(([p],[f])=>p.localeCompare(f)).map(([p,f])=>[p,f.version]));for(let[p,f]of e.versions)e.note("mapped",f.key,"Version",`version: ${p}: ${f.version}`)}if(e.edges.length){let{after:p,fromReads:f,cycle:g}=jp(e.edges.map(m=>[m.d,m.u]),t.repo?.reads);Object.keys(p).length&&!g&&(o.waves={...o.waves,after:p});for(let m of e.edges){let h=`${m.d} after ${m.u}`;m.d===m.u?e.note("unmapped",m.key,"Order",`both stacks run ${m.d}`):f.has(`${m.d}\0${m.u}`)?e.note("default",m.key,"Order",`the terraform_remote_state reads already order ${h}`):g?e.note("unmapped",m.key,"Order",`${h} is not kept: with the terraform_remote_state reads it makes a cycle, ${g.join(" after ")}`):e.note("mapped",m.key,"Order",`waves.after: ${h}`)}}let u=`${e.platform} applies the branch a ${n==="stacks"?"stack":"environment"} tracks after a push, terragucci the default branch after a merge`;if(t.applyWhen==="pull-request"&&t.forge==="gitlab"?r.own("when it applies","unmapped",u,`left at apply.when: merge: ${us.comments}; and ${us.token}`):t.applyWhen==="pull-request"?(o.apply={when:"pull-request"},r.own("when it applies","mapped",u,"apply.when: pull-request, from --apply-when")):r.own("when it applies","default",u,"apply.when: merge, terragucci's default"),e.approvals.length){let p=e.approvals.filter(f=>f.waits);if(p.length===e.approvals.length){o.gate="always";for(let f of p)e.note("mapped",f.key,"Approval","gate: always")}else if(p.length)for(let f of p)e.note("unmapped",f.key,"Approval",`gate is one setting for every wave, and ${e.approvals.length-p.length} of ${e.approvals.length} ${n} apply without an approval; gate: always holds them all`);else for(let f of e.approvals)e.note("default",f.key,"Approval","applies without an approval, as gate's default does for a change that destroys nothing")}let l=[...new Set(e.drift.map(p=>p.cron))];if(l.length===1){o.drift=l[0];let p=new Set(e.drift.map(f=>f.unit)).size>=e.units.length;for(let f of e.drift)e.note("mapped",f.key,"Drift",`drift: "${l[0]}"${p?"":`, over every root, not only ${[...new Set(e.drift.map(g=>g.unit))].join(", ")}`}`)}else if(l.length>1)for(let p of e.drift)e.note("unmapped",p.key,"Drift",`drift is one schedule, and the ${n} give ${l.join(", ")}`);if(e.ephemeral.length){let p=[...new Set(e.ephemeral.map(f=>f.root))].sort();o.ephemeral={roots:p,...e.ttl?{ttl:e.ttl.value}:{}};for(let f of e.ephemeral)e.note("mapped",f.key,"TTL",`ephemeral.roots: ${f.root}${e.ttl?`, ttl: ${e.ttl.value}`:", ttl: 24h, terragucci's default"}`);e.ttl&&e.note("mapped",e.ttl.key,"TTL",`ephemeral.ttl: ${e.ttl.value}`)}let d=[...e.env].filter(([p])=>!e.envConflicts.has(p)&&!e.secrets.has(p));for(let[p,f]of e.env)e.secrets.has(p)&&e.note("unmapped",f.key,"Variables",`${p} is a secret elsewhere, so it goes under pass`);if(d.length){o.env=Object.fromEntries(d.map(([p,f])=>[p,f.value]));for(let[p,f]of d)e.note("mapped",f.key,"Variables",`env: ${p}`)}if(e.secrets.size){o.pass={secrets:[...e.secrets.keys()].sort()};for(let[p,f]of e.secrets){let g=f.length>1?`; set in ${f.length} places, and a CI secret holds one value, so give a root's own value a name of its own`:"";e.note("mapped",f.join(", "),"Variables",`pass.secrets: ${p}; create the CI secret ${p}, whose value ${e.platform} does not give back${g}`)}}e.steps.length&&(o.steps=e.steps)}var bk=["env0-discovery.yml","env0-discovery.yaml"],Cve=new Set(["env0.yml","env0.yaml"]),Nve=new Set(["name","templateId","templateName","projectId","projectName","workspaceName","revision","variableFiles","requiresApproval","isRemoteBackend","continuousDeployment","autoDeployOnPathChangesOnly","autoDeployByCustomGlob","pullRequestPlanDeployments","vcsPrCommentsEnabled","vcsCommandsAlias","driftDetectionCron"]),Dve={setupVariables:{after:{before:"init"}},terraformInit:{before:{before:"init"},after:{after:"init"}},opentofuInit:{before:{before:"init"},after:{after:"init"}},terraformPlan:{before:{before:"plan"},after:{after:"plan"}},opentofuPlan:{before:{before:"plan"},after:{after:"plan"}},terraformApply:{before:{before:"apply"},after:{after:"apply"}},opentofuApply:{before:{before:"apply"},after:{after:"apply"}},storeState:{before:{after:"apply"},after:{after:"apply"}},terraformOutput:{before:{after:"apply"},after:{after:"apply"}},opentofuOutput:{before:{after:"apply"},after:{after:"apply"}}},Mve=/^(pulumi|cf|k8s|helm|ansible)/;function G2(e){let t=/^([1-9]\d*)-([hdwM])$/.exec(e.trim());if(!t)return;let n=Number(t[1]),r=t[2]==="h"?`${n}h`:t[2]==="d"?`${n}d`:t[2]==="w"?`${n*7}d`:`${n*30}d`;return ad(r)?r:void 0}var wr=e=>`${e.type}.${e.name}`;function Lve(e){let t=[],n=r=>{let o;try{o=Ove(r).sort()}catch{return}for(let s of o){if(s.startsWith(".")||s==="node_modules")continue;let i=wk(r,s);try{jve(i).isDirectory()?n(i):Cve.has(s)&&t.push(Ive(e,i))}catch{continue}}};return n(e),t}function F2(e,t){let n;try{n=uo(Pve(wk(e,t),"utf-8"))}catch(r){throw new S(`${t} is not YAML: ${r.message}`)}if(n==null)return{};if(!Me(n))throw new S(`${t} is not a map of settings`);return n}function B2(e,t={}){let n=t.file??bk.find(a=>L2(wk(e,a)));if(t.file!==void 0&&!L2(wk(e,t.file)))throw new S(`import env0 reads ${t.file}, and there is none in ${e}`);let r=n!==void 0?F2(e,n):void 0,o=Lve(e).map(a=>({file:a,doc:F2(e,a)})),s=mk(e,"env0_");if(r===void 0&&!o.length&&!s.length)throw new S(`import env0 reads ${bk[0]}, env0.yml custom flows or env0_* resources, and there are none in ${e}`);let i=[...n?[n]:[],...o.map(a=>a.file)];for(let a of s)i.includes(a.file)||i.push(a.file);return{...r0({discovery:r,flows:o,resources:s},t),read:i}}function Gve(e,t){return e.filter(n=>n==="."||t===n||t.startsWith(`${n}/`)).sort((n,r)=>r.length-n.length)[0]}function r0(e,t={}){let n=new Jc("env zero"),{notes:r}=n,{resources:o}=e,s=g=>o.filter(m=>m.type===g),i=t.repo?.roots,a=s("env0_template"),c=new Map;for(let g of a){let m=wr(g),h=g.body.attrs.path,y=h===void 0?".":Tt(h),w=y===void 0?void 0:xo(y.replace(/^\/+/,"")||".");c.set(m,w),w===void 0&&n.note("unmapped",`${m}.path`,"Root",`${so(h)} is not a directory inside the repo`)}let u=[];for(let g of s("env0_environment")){let h={id:Tt(g.body.attrs.name)??g.name,key:wr(g),res:g,template:js(g.body.attrs.template_id),project:js(g.body.attrs.project_id)},y=g.body.blocks.find(w=>w.type==="without_template_settings");if(y){let w=y.body.attrs.path===void 0?".":Tt(y.body.attrs.path);h.root=w===void 0?void 0:xo(w.replace(/^\/+/,"")||"."),z2(n,`${h.key}.without_template_settings`,y.body,[h])}else h.template&&c.has(h.template)&&(h.root=c.get(h.template));u.push(h)}let l=e.discovery??{};for(let g of Object.keys(l))g!=="environments"&&Xt(r,g);let d=Me(l.environments)?l.environments:{};l.environments!==void 0&&!Me(l.environments)&&Xt(r,"environments");for(let[g,m]of Object.entries(d)){let h=Me(m)?m:{},y=`environments.${g}`,w=typeof h.name=="string"?h.name:g,b=u.find(v=>v.id===w),k=b??{id:w,key:y};k.conf=h,k.confKey=y,b||u.push(k);for(let v of Object.keys(h))Nve.has(v)||Xt(r,`${y}.${v}`);if(k.root===void 0&&typeof h.templateName=="string"){let v=a.find($=>Tt($.body.attrs.name)===h.templateName);v&&(k.template=wr(v),k.root=c.get(wr(v)))}if(k.root===void 0&&i){let v=oo(h.variableFiles).find(_=>Me(_)&&typeof _.path=="string"),$=v?Gve(i,n0(v.path.replace(/^\.?\//,""))):void 0;$&&(k.root=$,n.note("mapped",`${y}.variableFiles`,"Root",`${$}, the root ${v.path} is in`))}if(k.root===void 0){let v=h.templateName??h.templateId;n.note("unmapped",y,"Root",`its template ${v===void 0?"":`${JSON.stringify(v)} `}is not an env0_template in the repo and its variable files sit in no root, so the import cannot tell its directory`)}}n.units.push(...u);for(let g of a){let m=u.filter(h=>h.template===wr(g));z2(n,wr(g),g.body,m),m.length||r.own(wr(g),"default","no environment the import read deploys this template, so it is not a root of its own")}let p=new Map;for(let g of s("env0_project_policy")){let m=js(g.body.attrs.project_id);m&&p.set(m,g);let h=wr(g);for(let[y,w]of Object.entries(g.body.attrs)){let b=`${h}.${y}`;if(y!=="project_id")if(y==="default_ttl"){let k=typeof w=="string"?G2(w):void 0;k&&(!n.ttl||n.ttl.value===k)?n.ttl={value:k,key:b}:k?n.note("unmapped",b,"TTL",`ephemeral.ttl is one setting, and ${n.ttl.key} gives ${n.ttl.value}`):n.note("default",b,"TTL",`${so(w)} is no fixed TTL, so nothing expires`)}else if(y==="max_ttl")n.note("default",b,"TTL","a copy lives until its pull request closes or its TTL passes");else if(y==="drift_detection_cron"){let k=Tt(w);k&&n.drift.push({cron:k,key:b,unit:m??h})}else y==="include_cost_estimation"?w===!0?(n.s.cost=!0,n.note("mapped",b,"Cost","cost: true")):n.note("default",b,"Cost","off, as terragucci's default"):y==="requires_approval_default"||y==="continuous_deployment_default"||y==="run_pull_request_plan_default"||y==="vcs_pr_comments_enabled_default"?Qo(r,b,"env zero","a default for new environments; each environment's own setting is read"):y==="auto_drift_remediation"?w!=="DISABLED"&&n.note("unmapped",b,"Drift","a drift run never applies; respond.drift opens a pull request for it"):y==="force_remote_backend"?w===!0&&n.note("unmapped",b,"Managed state","the project's environments keep their state in env zero"):Qo(r,b,"env zero")}}for(let g of u){let m=g.conf,h=g.res,y=(N,L)=>{if(m&&m[N]!==void 0)return{v:m[N],key:`${g.confKey}.${N}`};if(h&&h.body.attrs[L]!==void 0)return{v:h.body.attrs[L],key:`${g.key}.${L}`}},w=y("workspaceName","workspace");w&&n.note("unmapped",w.key,"Workspace",`workspace ${so(w.v)} is not planned`);let b=y("revision","revision");b&&Qo(r,b.key,"env zero","terragucci applies the default branch, and apply.branches applies roots from another");let k=y("requiresApproval","approve_plan_automatically"),v=k?k.key.endsWith("requiresApproval")?k.v===!0:k.v===!1:void 0;k&&v!==void 0&&n.approvals.push({key:k.key,waits:v});let $=y("isRemoteBackend","is_remote_backend"),_=g.project?p.get(g.project):void 0;$?.v===!0||!$&&_?.body.attrs.force_remote_backend===!0?n.note("unmapped",$?.key??`${g.key}: ${g.id}`,"Managed state",`${g.id}'s state is in env zero's remote backend: move it to ${g.root??"its root"}'s backend before the root's backend block merges`):$&&n.note("default",$.key,"Own backend");let E=y("continuousDeployment","deploy_on_push");E?.v===!1?n.note("unmapped",E.key,"Triggers","merge applies what changed; gate: always holds each wave for an approval first"):E&&n.note("default",E.key,"Triggers");for(let[N,L]of[["autoDeployOnPathChangesOnly","auto_deploy_on_path_changes_only"],["autoDeployByCustomGlob","auto_deploy_by_custom_glob"],["pullRequestPlanDeployments","run_plan_on_pull_requests"],["vcsPrCommentsEnabled","vcs_pr_comments_enabled"]]){let U=y(N,L);U&&n.note("default",U.key,"Triggers",N==="vcsPrCommentsEnabled"?"a comment of /terragucci plan plans again":void 0)}let R=y("vcsCommandsAlias","vcs_commands_alias");R&&n.note("unmapped",R.key,"Triggers","comments name a root by its path: /terragucci plan <root>");let x=y("driftDetectionCron","drift_detection_cron");x&&typeof x.v=="string"&&n.drift.push({cron:x.v,key:x.key,unit:g.id});let A=y("projectName","project_id")??y("projectId","project_id");if(A&&Qo(r,A.key.replace(/\.[^.]+$/,".project"),"env zero","a project's environments are this repo's roots"),m){for(let N of["name","templateName","templateId"])m[N]!==void 0&&n.note("default",`environments.*.${N}`,"Root","a root goes by its path from the repo root");oo(m.variableFiles).forEach((N,L)=>{let U=`${g.confKey}.variableFiles[${L}]`,F=Me(N)&&typeof N.path=="string"?N.path.replace(/^\.?\//,""):void 0;if(F===void 0)return Xt(r,U);let M=F.split("/").pop(),te=M==="terraform.tfvars"||M.endsWith(".auto.tfvars");g.root!==void 0&&n0(F)===g.root&&te?n.note("default",U,"Variables",`the binary reads ${F} in ${g.root} itself`):n.note("unmapped",U,"Variables",`copy ${F} to ${g.root??"the root"}/terraform.tfvars (or a *.auto.tfvars there), which the binary reads`)})}let O=h?.body.attrs.ttl,I=_&&typeof _.body.attrs.default_ttl=="string"?G2(_.body.attrs.default_ttl):void 0;if(O!==void 0||I){let N=O!==void 0?`${g.key}.ttl`:`${g.key}: ${g.id}`;g.root===void 0?n.note("unmapped",N,"TTL",`${g.id} has no root`):n.ephemeral.push({root:g.root,key:N})}if(h){let N=new Set(["name","template_id","project_id","workspace","revision","approve_plan_automatically","is_remote_backend","deploy_on_push","auto_deploy_on_path_changes_only","auto_deploy_by_custom_glob","run_plan_on_pull_requests","vcs_pr_comments_enabled","vcs_commands_alias","drift_detection_cron","ttl"]);for(let L of Object.keys(h.body.attrs))N.has(L)||(L==="variable_sets"?n.note("unmapped",`${g.key}.variable_sets`,"Variables","set the variable sets' values by hand: env for values, pass for secrets"):L==="terragrunt_working_directory"?n.note("default",`${g.key}.${L}`,"Root","Terragrunt is detected, and each unit is a root"):L==="k8s_namespace"||L==="sub_environment_configuration"?n.note("unmapped",`${g.key}.${L}`,"Root","terragucci runs Terraform, OpenTofu and Terragrunt"):Qo(r,`env0_environment.*.${L}`,"env zero"));for(let L of h.body.blocks)if(L.type==="configuration")U2(n,`${g.key}.configuration: ${Tt(L.body.attrs.name)??"?"}`,L.body,!1,g);else{if(L.type==="without_template_settings")continue;L.type==="sub_environment_configuration"?n.note("unmapped",`${g.key}.sub_environment_configuration`,"Order","a workflow's sub environments are roots, ordered by their terraform_remote_state reads"):Qo(r,`env0_environment.*.${L.type}`,"env zero")}}}for(let g of s("env0_configuration_variable")){let m=wr(g),h=js(g.body.attrs.environment_id),y=js(g.body.attrs.template_id),w=h?u.find(v=>v.res&&wr(v.res)===h):void 0,b=y?u.filter(v=>v.template===y):void 0,k=!h&&(!y||b!==void 0&&b.length===u.length);U2(n,m,g.body,k,w??(b?.length===1?b[0]:void 0))}for(let g of s("env0_environment_drift_detection")){let m=js(g.body.attrs.environment_id),h=Tt(g.body.attrs.cron);h?n.drift.push({cron:h,key:`${wr(g)}.cron`,unit:u.find(w=>w.res&&wr(w.res)===m)?.id??wr(g)}):n.note("unmapped",`${wr(g)}.cron`,"Drift","it is not a plain cron schedule");let y=g.body.attrs.auto_drift_remediation;y!==void 0&&y!=="DISABLED"&&n.note("unmapped",`${wr(g)}.auto_drift_remediation`,"Drift","a drift run never applies; respond.drift opens a pull request for it")}for(let g of e.flows)Fve(n,g.file,g.doc,u);let f=new Set(["env0_template","env0_environment","env0_project_policy","env0_configuration_variable","env0_environment_drift_detection"]);for(let g of o){if(f.has(g.type))continue;let m=wr(g);g.type==="env0_project"||g.type==="env0_template_project_assignment"?Qo(r,m,"env zero","a project's environments are this repo's roots"):g.type.includes("credentials")?n.note("unmapped",m,g.type.startsWith("env0_cost_")?"Cost":"Cloud credentials",g.type.startsWith("env0_cost_")?"Infracost prices on your INFRACOST_API_KEY":"name a read-only plan role and an apply role under oidc"):g.type.startsWith("env0_variable_set")?n.note("unmapped",m,"Variables","set its values by hand: env for values, pass for secrets"):g.type.startsWith("env0_agent")?n.note("unmapped",m,"Workers"):g.type.startsWith("env0_notification")?n.note("unmapped",m,"Notifications"):g.type.startsWith("env0_approval_policy")?n.note("unmapped",m,"Plan policy","copy its Rego into policy; a rule that asked for an approval becomes cost.approve_above or gate: always"):/^env0_(user|team|custom_role|.*role_assignment|organization)/.test(g.type)?n.note("unmapped",m,"Access"):g.type==="env0_module"||g.type.startsWith("env0_module_")?n.note("unmapped",m,"Module registry"):g.type.includes("trigger")?n.note("unmapped",m,"Order","a root that reads another's state through terraform_remote_state applies after it"):Xt(r,m)}return yk(n,t,"environments"),{settings:n.s,notes:r.list}}function z2(e,t,n,r){let o=Tt(n.attrs.type)??"terraform",s=!1;if(o==="opentofu")s=!0,e.binary("tofu",`${t}.type`);else if(o==="terraform")n.attrs.type!==void 0&&e.binary("terraform",`${t}.type`);else if(o==="terragrunt")e.note("default",`${t}.type`,"Root","Terragrunt is detected from the repo's root.hcl or terragrunt.hcl, with terragrunt.version for its release"),s=(Tt(n.attrs.terragrunt_tf_binary)??"opentofu")==="opentofu",e.binary(s?"tofu":"terraform",`${t}.terragrunt_tf_binary`);else{e.note("unmapped",`${t}.type`,"Root",`terragucci runs Terraform, OpenTofu and Terragrunt, not ${o}`);return}let i=s?n.attrs.opentofu_version:n.attrs.terraform_version,a=`${t}.${s?"opentofu_version":"terraform_version"}`;if(i!==void 0){let u=[...new Set(r.map(l=>l.root).filter(l=>l!==void 0))];u.length||e.version(void 0,i,a);for(let l of u)e.version(l,i,a)}(s?n.attrs.terraform_version:n.attrs.opentofu_version)!==void 0&&e.note("default",`${t}.${s?"terraform_version":"opentofu_version"}`,"Version",`the template runs ${s?"OpenTofu":"Terraform"}`)}function U2(e,t,n,r,o){let s=Tt(n.attrs.name);if(!s)return e.note("unmapped",t,"Variables","its name is not a plain string");let i=Tt(n.attrs.type)==="terraform"?`TF_VAR_${s}`:s;if(n.attrs.is_sensitive===!0)return e.takeSecret(t,i);let a=Tt(n.attrs.value);if(a===void 0)return e.note("unmapped",t,"Variables","its value is not a plain string in the code; set it by hand");if(r)return e.takeEnv(t,i,a);let c=o?.root;e.note("unmapped",t,"Variables",i.startsWith("TF_VAR_")?`it is ${o?.id??"one environment"}'s alone: set ${s} in ${c??"its root"}/terraform.tfvars`:`it is ${o?.id??"one environment"}'s alone, and env is every root's`)}function Fve(e,t,n,r){let{notes:o}=e,s=xo(n0(t))??".",i=[...new Set(r.map(p=>p.root).filter(p=>p!==void 0))],a=r.length>0&&r.every(p=>p.root===s),c=s==="."&&!i.includes(".");if(!c&&!i.includes(s)&&r.length){o.own(t,"unmapped",`no environment the import read runs ${s}`,"a custom flow runs in its template's directory");return}let u=n.shell==="bash";for(let p of Object.keys(n))p==="version"?o.own(`${t}: version`,"default","the file's format version; terragucci.yml has none"):p==="shell"?o.own(`${t}: shell`,u?"mapped":"default",u?"each step runs its commands under bash":"a step runs under sh"):p==="destroy"?e.note("unmapped",`${t}: destroy`,"Hooks","a destroy is a reviewed change that removes the code, or an ephemeral copy's sweep"):p==="task"?e.note("unmapped",`${t}: task`,"Hooks","a task has no counterpart: state changes come from a reviewed commit"):p!=="deploy"&&Xt(o,`${t}: ${p}`);let l=Me(n.deploy)?n.deploy:{},d=(p,f,g)=>{let m=[];if(oo(g).forEach((y,w)=>{let b=typeof y=="string"?y:Me(y)&&typeof y.run=="string"?y.run.trimEnd():void 0;if(b===void 0)return Xt(o,`${p}[${w}]`);let k=/^echo\s+["']?([A-Za-z_][A-Za-z0-9_]*)=([^"'$`]*)["']?\s*>>\s*"?\$\{?ENV0_ENV\}?"?\s*$/.exec(b.trim());if(k){a||c?e.takeEnv(`${p}[${w}]`,k[1],k[2]):e.note("unmapped",`${p}[${w}]`,"Variables",`it is ${s}'s alone, and env is every root's`);return}b.includes("ENV0_ENV")&&e.note("unmapped",`${p}[${w}]`,"Hooks","it sets a variable for later steps, and a step cannot set the binary's environment"),m.push(b)}),!m.length)return;let h=Jc.script(m);u&&(h=`bash -c '${h.replace(/'/g,"'\\''")}'`),c&&(h=`cd "$TG_REPO" && ${h}`),e.steps.push({name:p,run:h,...f,...c||a?{}:{roots:[s]}}),e.note("mapped",p,"Hooks",`steps: ${f.before?"before":"after"}: ${f.before??f.after}${c||a?" for every root":` for ${s}`}${c?", from the repo's root":""}`)};for(let[p,f]of Object.entries(l)){let g=`${t}: deploy.${p}`;if(p==="onSuccess")d(g,{after:"apply"},f);else if(p==="onFailure"||p==="onCompletion")e.note("unmapped",g,"Hooks","a step after a stage runs when the stage succeeded");else if(p==="steps"&&Me(f))for(let[m,h]of Object.entries(f)){let y=`${t}: deploy.steps.${m}`,w=Dve[m];if(!w){Mve.test(m)?e.note("unmapped",y,"Root","terragucci runs Terraform, OpenTofu and Terragrunt"):Xt(o,y);continue}if(!Me(h)){Xt(o,y);continue}for(let[b,k]of Object.entries(h)){let v=b==="before"?w.before:b==="after"?w.after:void 0;v?d(`${y}.${b}`,v,k):Xt(o,`${y}.${b}`)}}else Xt(o,g)}}import{existsSync as W2,readFileSync as zve}from"node:fs";import{join as o0}from"node:path";var $k=[".spacelift/config.yml",".spacelift/config.yaml"],vk={before_init:{before:"init"},after_init:{after:"init"},before_plan:{before:"plan"},after_plan:{after:"plan"},before_apply:{before:"apply"},after_apply:{after:"apply"},before_perform:void 0,after_perform:void 0,before_destroy:void 0,after_destroy:void 0,after_run:void 0},H2=new Set([...Object.keys(vk),"environment","project_root","runner_image","terraform_version","opentofu_version","terraform_workflow_tool","git_sparse_checkout_paths","terragrunt"]),q2=new Set(["name","slug","repository","branch","description","space_id","protect_from_deletion","enable_local_preview","autoretry","allow_run_promotion","github_action_deploy","enable_well_known_secret_masking","terraform_smart_sanitization","terraform_external_state_access","prevent_changes_when_locked","git_sparse_checkout_paths","enable_sensitive_outputs_upload","import_state","import_state_file","labels","github_enterprise","gitlab","azure_devops","bitbucket_cloud","bitbucket_datacenter","raw_git","origin","spacelift_repo","showcase"]);function Uve(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}function J2(e,t){return t in e.body.attrs||e.body.blocks.some(n=>n.type===t)}var Zn=e=>`${e.type}.${e.name}`;function K2(e,t={}){let n=t.file??$k.find(i=>W2(o0(e,i))),r={},o=[];if(n!==void 0){if(!W2(o0(e,n)))throw new S(`import spacelift reads ${n}, and there is none in ${e}`);let i;try{i=uo(zve(o0(e,n),"utf-8"))}catch(a){throw new S(`${n} is not YAML: ${a.message}`)}if(i!=null&&!Me(i))throw new S(`${n} is not a map of settings`);r=i??{},o.push(n)}let s=mk(e,"spacelift_");for(let i of s)o.includes(i.file)||o.push(i.file);if(n===void 0&&!s.length)throw new S(`import spacelift reads ${$k[0]} or spacelift_stack resources, and there are neither in ${e}`);return{...s0(r,s,t),read:o}}function s0(e,t,n={}){let r=new Jc("Spacelift"),{notes:o}=r,s=new Map(t.map(w=>[Zn(w),w])),i=w=>t.filter(b=>b.type===w);for(let w of Object.keys(e))w==="version"?o.own("version","default","the file's format version; terragucci.yml has none"):w==="module_version"||w==="tests"?r.note("unmapped",w,"Module registry","a module's version and tests are the module's own release"):w!=="stack_defaults"&&w!=="stacks"&&Xt(o,w);let a=Me(e.stack_defaults)?e.stack_defaults:{};e.stack_defaults!==void 0&&!Me(e.stack_defaults)&&Xt(o,"stack_defaults");let c=Me(e.stacks)?e.stacks:{};e.stacks!==void 0&&!Me(e.stacks)&&Xt(o,"stacks");let u=[],l=new Map;for(let w of i("spacelift_stack")){let b=Tt(w.body.attrs.name),k=Tt(w.body.attrs.slug)??(b?Uve(b):w.name),v={id:k,key:Zn(w),res:w,labels:Ip(w.body.attrs.labels)??[]};u.push(v),l.set(k,v)}for(let[w,b]of Object.entries(c)){let k=l.get(w)??{id:w,key:`stacks.${w}`,labels:[]};l.has(w)||(u.push(k),l.set(w,k)),k.conf=Me(b)?b:{},k.confKey=`stacks.${w}`,!Me(b)&&b!==null&&Xt(o,`stacks.${w}`);for(let v of Object.keys(k.conf))H2.has(v)||Xt(o,`stacks.${w}.${v}`)}for(let w of Object.keys(a))H2.has(w)||Xt(o,`stack_defaults.${w}`);!u.length&&Me(e.stack_defaults)&&u.push({id:"the repo's stack",key:"stack_defaults",labels:[]});let d=(w,b)=>{if(w.conf&&w.conf[b]!==void 0)return{v:w.conf[b],key:`${w.confKey}.${b}`};if(a[b]!==void 0)return{v:a[b],key:`stack_defaults.${b}`};let k=w.res?.body.attrs[b];if(k!==void 0)return{v:k,key:`${w.key}.${b}`}};for(let w of u){let b=d(w,"project_root"),k=b===void 0?".":typeof b.v=="string"?Tt(b.v):void 0;w.root=k===void 0?void 0:xo(k===""?".":k),b&&w.root===void 0&&r.note("unmapped",b.key,"Root",`${so(b.v)} is not a directory inside the repo`),r.units.push(w)}for(let w of u){let b=d(w,"terraform_workflow_tool"),k=w.res&&w.res.body.blocks.some(E=>E.type==="opentofu"),v=!1;b?b.v==="OPEN_TOFU"?(v=!0,r.binary("tofu",b.key)):b.v==="TERRAFORM_FOSS"?r.binary("terraform",b.key):r.note("unmapped",b.key,"Version",`${so(b.v)} is not Terraform or OpenTofu; set binary to the one the stack runs`):k&&(v=!0,r.binary("tofu",`${w.key}.opentofu`));let $=v?d(w,"opentofu_version")??d(w,"terraform_version"):d(w,"terraform_version");$&&r.version(w.root,$.v,$.key);let _=v?d(w,"terraform_version"):d(w,"opentofu_version");_&&_!==$&&r.note("default",_.key,"Version",`the stack runs ${v?"OpenTofu":"Terraform"}`)}let p=i("spacelift_context"),f=new Map(p.map(w=>[Zn(w),new Set])),g=w=>{let b=js(w);if(b)return u.find(v=>v.res&&Zn(v.res)===b);let k=Tt(w);return k?l.get(k):void 0};for(let w of i("spacelift_context_attachment")){let b=js(w.body.attrs.context_id),k=g(w.body.attrs.stack_id);b&&f.has(b)&&k?(f.get(b).add(k),Qo(o,Zn(w),"Spacelift",`attaches ${b} to ${k.id}`)):r.note("unmapped",Zn(w),"Variables","the import cannot tell which context and stack it joins")}for(let w of p){for(let b of Ip(w.body.attrs.labels)??[]){let k=/^autoattach:(.+)$/.exec(b);if(k)for(let v of u)(k[1]==="*"||v.labels.includes(k[1]))&&f.get(Zn(w)).add(v)}for(let b of Object.keys(w.body.attrs))!(b in vk)&&b!=="name"&&b!=="labels"&&b!=="description"&&b!=="space_id"&&Xt(o,`${Zn(w)}.${b}`)}let m=w=>u.every(b=>w.has(b)),h=new Map;for(let[w,b]of Object.entries(vk))for(let k of u){let v=d(k,w),$=v?Array.isArray(v.v)?Ip(v.v):void 0:[];if(v&&$===void 0){r.note("unmapped",v.key,"Hooks","it is not a list of commands");continue}let _=p.filter(I=>f.get(Zn(I)).has(k)&&I.body.attrs[w]!==void 0),E=_.flatMap(I=>Ip(I.body.attrs[w])??[]),R=w.startsWith("before")?[...E,...$]:[...$,...E],x=[...v?[v.key]:[],..._.map(I=>`${Zn(I)}.${w}`)];if(!R.length)continue;if(!b){for(let I of x)r.note("unmapped",I,"Hooks",w.includes("destroy")?"a destroy is a reviewed change that removes the code":w.includes("perform")?"a task has no counterpart: state changes come from a reviewed commit":"after_run runs whatever the run's outcome; a step after a stage runs when the stage succeeded");continue}if(k.root===void 0){for(let I of x)r.note("unmapped",I,"Hooks",`${k.id} has no root`);continue}let A=Jc.script(R),O=h.get(`${w}\0${A}`)??{stage:b,hook:w,script:A,roots:new Set,keys:new Set,stacks:new Set};O.roots.add(k.root),O.stacks.add(k),x.forEach(I=>O.keys.add(I)),h.set(`${w}\0${A}`,O)}for(let w of h.values()){let b=m(w.stacks),k=[...w.roots].sort();r.steps.push({name:w.hook,run:w.script,...w.stage,...b?{}:{roots:k}});let v=`${w.stage.before?"before":"after"}: ${w.stage.before??w.stage.after}`;for(let $ of w.keys)r.note("mapped",$,"Hooks",`steps: ${v}${b?" for every root":` for ${k.join(", ")}`}`)}if(Me(a.environment))for(let[w,b]of Object.entries(a.environment))typeof b=="string"||typeof b=="number"||typeof b=="boolean"?r.takeEnv(`stack_defaults.environment.${w}`,w,String(b)):r.note("unmapped",`stack_defaults.environment.${w}`,"Variables","its value is not a string");for(let w of u)if(!(!w.conf||!Me(w.conf.environment)))for(let b of Object.keys(w.conf.environment))r.note("unmapped",`${w.confKey}.environment.${b}`,"Variables",`it is ${w.id}'s alone, and env is every root's; a TF_VAR_ value goes in ${w.root??"its root"}'s terraform.tfvars`);for(let w of i("spacelift_environment_variable")){let b=Zn(w),k=Tt(w.body.attrs.name);if(!k){r.note("unmapped",b,"Variables","its name is not a plain string");continue}let v=js(w.body.attrs.context_id),$=w.body.attrs.stack_id!==void 0?g(w.body.attrs.stack_id):void 0;if(w.body.attrs.module_id!==void 0){r.note("unmapped",b,"Module registry","a module's test variable");continue}if(w.body.attrs.write_only!==!1){r.takeSecret(b,k);continue}let _=Tt(w.body.attrs.value),E=v&&f.has(v)?f.get(v):$?new Set([$]):void 0;_===void 0?r.note("unmapped",b,"Variables","its value is not a plain string in the code; set it by hand"):E&&m(E)?r.takeEnv(b,k,_):r.note("unmapped",b,"Variables",`it is for ${E?[...E].map(R=>R.id).join(", ")||"no stack":"a stack the import cannot tell"}, and env is every root's; a TF_VAR_ value goes in the root's terraform.tfvars`)}for(let w of i("spacelift_mounted_file")){let b=Tt(w.body.attrs.relative_path)??so(w.body.attrs.relative_path);r.note("unmapped",Zn(w),"Files",w.body.attrs.write_only===!1?`commit ${b} in the root`:`${b} is secret: keep it in a CI secret and write it in a step before init`)}for(let w of i("spacelift_stack_dependency")){let b=Zn(w),k=g(w.body.attrs.stack_id),v=g(w.body.attrs.depends_on_stack_id);!k||!v?r.note("unmapped",b,"Order",`${k?so(w.body.attrs.depends_on_stack_id):so(w.body.attrs.stack_id)} names no stack the import read`):k.root===void 0||v.root===void 0?r.note("unmapped",b,"Order",`${k.root===void 0?k.id:v.id} has no root`):r.edges.push({d:k.root,u:v.root,key:b}),w.body.attrs.trigger_always!==void 0&&r.note("default",`${b}.trigger_always`,"Triggers","a root that reads a changed root's state plans with it")}for(let w of i("spacelift_stack_dependency_reference")){let b=Tt(w.body.attrs.input_name)??so(w.body.attrs.input_name),k=Tt(w.body.attrs.output_name)??so(w.body.attrs.output_name);r.note("unmapped",Zn(w),"Order",`read ${k} through a terraform_remote_state block of the upstream root in place of the input ${b}`)}for(let w of i("spacelift_drift_detection")){let b=Zn(w),k=g(w.body.attrs.stack_id),v=Ip(w.body.attrs.schedule);if(!v?.length)r.note("unmapped",`${b}.schedule`,"Drift","it is not a list of cron schedules");else for(let _ of v)r.drift.push({cron:_,key:v.length>1?`${b}.schedule: ${_}`:`${b}.schedule`,unit:k?.id??b});w.body.attrs.reconcile===!0&&r.note("unmapped",`${b}.reconcile`,"Drift","a drift run never applies; respond.drift opens a pull request for it");let $=Tt(w.body.attrs.timezone);$&&$!=="UTC"&&r.note("unmapped",`${b}.timezone`,"Drift",`the cron runs in UTC, not ${$}`)}for(let w of u){let b=w.res;if(!b)continue;let k=w.key;r.approvals.push({key:`${k}.autodeploy`,waits:b.body.attrs.autodeploy!==!0}),b.body.attrs.manage_state!==!1?r.note("unmapped",`${k}.manage_state`,"Managed state",`${w.id}'s state is in Spacelift: pull it and move it to ${w.root??"its root"}'s backend before the root's backend block merges`):r.note("default",`${k}.manage_state`,"Own backend"),b.body.attrs.terraform_workspace!==void 0&&r.note("unmapped",`${k}.terraform_workspace`,"Workspace",`workspace ${so(b.body.attrs.terraform_workspace)} is not planned`),b.body.attrs.worker_pool_id!==void 0&&r.note("unmapped",`${k}.worker_pool_id`,"Workers","name the runner label of your own runners under runner"),b.body.attrs.additional_project_globs!==void 0&&r.note("default",`${k}.additional_project_globs`,"Triggers","a root plans when a file in it, a local module it calls or its var files changed"),b.body.attrs.administrative!==void 0&&o.own(`${k}.administrative`,"unmapped","an administrative stack manages Spacelift itself","once every stack it declares has moved, stop applying it");for(let v of["pulumi","kubernetes","cloudformation","ansible"])J2(b,v)&&r.note("unmapped",`${k}.${v}`,"Root",`terragucci runs Terraform, OpenTofu and Terragrunt, not ${v}`);J2(b,"terragrunt")&&r.note("default",`${k}.terragrunt`,"Root","Terragrunt is detected from the repo's root.hcl or terragrunt.hcl");for(let v of Object.keys(b.body.attrs))q2.has(v)?Qo(o,`spacelift_stack.*.${v}`,"Spacelift"):!["project_root","terraform_version","terraform_workflow_tool","autodeploy","manage_state","terraform_workspace","worker_pool_id","additional_project_globs","administrative","runner_image"].includes(v)&&!(v in vk)&&Xt(o,`${k}.${v}`);for(let v of b.body.blocks)!q2.has(v.type)&&!["opentofu","terragrunt","pulumi","kubernetes","cloudformation","ansible"].includes(v.type)&&Xt(o,`${k}.${v.type}`)}for(let w of u){let b=d(w,"runner_image");b&&r.note("unmapped",b.key,"Runner image","build an image FROM terragucci's with what the hooks need, and set image")}(Me(a.terragrunt)||u.some(w=>w.conf&&w.conf.terragrunt!==void 0))&&r.note("default","terragrunt","Root","Terragrunt is detected from the repo's root.hcl or terragrunt.hcl, with terragrunt.version for its release");for(let w of i("spacelift_policy")){let b=Zn(w),k=Tt(w.body.attrs.type)??"",v=w.body.attrs.body,$=v&&typeof v=="object"&&"file"in v?` in ${v.file.replace(/^\$\{path\.module\}\//,"")}`:"";k==="PLAN"?r.note("unmapped",b,"Plan policy",`copy the Rego${$} into policy with input: plan, and change input.terraform to input; input.spacelift has no counterpart`):k==="APPROVAL"?r.note("unmapped",b,"Approval","gate decides which waves wait, and how many reviewers a change needs is the forge's branch protection"):k==="GIT_PUSH"||k==="TRIGGER"?r.note("default",b,"Triggers"):k==="LOGIN"||k==="ACCESS"||k==="STACK_ACCESS"?r.note("unmapped",b,"Access"):k==="NOTIFICATION"?r.note("unmapped",b,"Notifications","set notify for the channels it routed to"):o.own(b,"unmapped",`a ${k||"policy"} policy has no counterpart`,"a state change comes from an import, removed or moved block, or a migration, in a reviewed commit")}let y=new Set(["spacelift_stack","spacelift_context","spacelift_context_attachment","spacelift_environment_variable","spacelift_mounted_file","spacelift_stack_dependency","spacelift_stack_dependency_reference","spacelift_drift_detection","spacelift_policy"]);for(let w of t){if(y.has(w.type))continue;let b=Zn(w);w.type==="spacelift_policy_attachment"?Qo(o,b,"Spacelift","the policy's own note says where it goes"):/_(integration|integration_attachment|aws_role)$/.test(w.type)||w.type.startsWith("spacelift_aws_")||w.type.startsWith("spacelift_gcp_")||w.type.startsWith("spacelift_azure_")?r.note("unmapped",b,"Cloud credentials","name a read-only plan role and an apply role under oidc"):w.type==="spacelift_worker_pool"?r.note("unmapped",b,"Workers"):w.type==="spacelift_module"||w.type.startsWith("spacelift_module_")?r.note("unmapped",b,"Module registry"):/^spacelift_(space|role|role_attachment|user|idp_group_mapping|api_key|team|saml_saml2|audit_trail_webhook)/.test(w.type)?r.note("unmapped",b,"Access"):w.type==="spacelift_webhook"||w.type==="spacelift_named_webhook"?r.note("unmapped",b,"Notifications","notify.webhook posts a signed body to an address"):Xt(o,b)}return yk(r,n,"stacks"),{settings:r.s,notes:o.list}}import{existsSync as Sh,readdirSync as Bve,readFileSync as i0,statSync as V2}from"node:fs";import{join as Kc,relative as Wve,resolve as Hve}from"node:path";var qve=/[A-Za-z0-9_-]/;function a0(e){let t=0,n=p=>{let f=e.slice(0,t).split(`
942
942
  `).length;throw new Error(`line ${f}: ${p}`)},r=p=>{for(;t<e.length;){let f=e[t];if(f===" "||f===" "||f==="\r"||p&&f===`
943
943
  `)t++;else if(f==="#"||f==="/"&&e[t+1]==="/")for(;t<e.length&&e[t]!==`
944
944
  `;)t++;else if(f==="/"&&e[t+1]==="*"){let g=e.indexOf("*/",t+2);t=g<0?e.length:g+2}else break}},o=()=>{let p=t;for(;t<e.length&&qve.test(e[t]);)t++;return t===p&&n(`expected a name, found ${JSON.stringify(e[t]??"the end")}`),e.slice(p,t)},s=()=>{let p=t,f=0;for(let g=!1;t<e.length;t++){let m=e[t];if(g)m==="\\"?t++:m==='"'&&(g=!1);else if(m==='"')g=!0;else if("([{".includes(m))f++;else if(")]}".includes(m)&&--f===0){t++;break}}return e.slice(p,t)},i=()=>{t++;let p=[],f="";for(;t<e.length&&e[t]!=='"';)if(e[t]==="\\"){let g=e[t+1];f+=g==="n"?`
945
945
  `:g==="t"?" ":g,t+=2}else if(e[t]==="$"&&e[t+1]==="{"){f&&p.push(f),f="",t++;let g=s();p.push(l(g.slice(1,-1)))}else f+=e[t++];return e[t]!=='"'&&n("a string has no closing quote"),t++,(f||!p.length)&&p.push(f),{t:"str",parts:p}},a=()=>{r(!1);let p=e[t];if(p==='"')return i();if(p==="["){t++;let f=[];for(;;){if(r(!0),e[t]==="]")return t++,{t:"list",items:f};f.push(u()),r(!0),e[t]===","?t++:e[t]!=="]"&&n("expected , or ] in a list")}}if(p==="{"){t++;let f=[];for(;;){if(r(!0),e[t]==="}")return t++,{t:"obj",entries:f};let g=e[t]==='"'?Cp(i())??n("a map key is not a plain string"):o();r(!1),e[t]!=="="&&e[t]!==":"&&n("expected = in a map"),t++,f.push([g,u()]),r(!1),e[t]===","&&t++}}if(p!==void 0&&/[0-9]/.test(p)){let f=/^[0-9]+(\.[0-9]+)?/.exec(e.slice(t));return t+=f[0].length,{t:"lit",v:Number(f[0])}}if(p!==void 0&&/[A-Za-z_]/.test(p)){let f=t,g=o();if(g==="true"||g==="false")return{t:"lit",v:g==="true"};if(g==="null")return{t:"lit",v:null};if(e[t]==="(")return s(),{t:"raw",text:e.slice(f,t)};let m=[g];for(;;)if(e[t]==="."&&/[A-Za-z_]/.test(e[t+1]??""))t++,m.push(o());else if(e[t]==="["&&e[t+1]==='"'){t++;let h=Cp(i());if(h===void 0||e[t]!=="]")return{t:"raw",text:e.slice(f,t=c(f))};t++,m.push(h)}else break;return{t:"ref",path:m}}return n(`expected a value, found ${JSON.stringify(p??"the end")}`)},c=p=>{let f=p,g=0;for(let m=!1;f<e.length;f++){let h=e[f];if(m)h==="\\"?f++:h==='"'&&(m=!1);else if(h==='"')m=!0;else if("([{".includes(h))g++;else if(")]}".includes(h)){if(g===0)break;g--}else if((h===`
946
946
  `||h===",")&&g===0)break}return f},u=()=>{r(!1);let p=t,f=a();r(!1);let g=e[t];return g===void 0||g===`
947
947
  `||g===","||g==="]"||g==="}"||g===")"?f:(t=c(p),{t:"raw",text:e.slice(p,t).trim()})};function l(p){try{return a0(`x = ${p}
948
- `).attrs.get("x")??{t:"raw",text:p}}catch{return{t:"raw",text:p}}}let d=p=>{let f={type:"",labels:[],attrs:new Map,blocks:[]};for(;;){if(r(!0),t>=e.length)return p||n("a block has no closing }"),f;if(e[t]==="}")return p&&n("a } closes no block"),t++,f;let g=o();if(r(!1),e[t]==="="){t++,f.attrs.set(g,u());continue}let m=[];for(;e[t]!=="{";)e[t]==='"'?m.push(Cp(i())??n("a block label is not a plain string")):m.push(o()),r(!1);t++;let h=d(!1);f.blocks.push({...h,type:g,labels:m})}};return d(!0)}function Cp(e){return e.t==="str"&&e.parts.every(t=>typeof t=="string")?e.parts.join(""):void 0}function Np(e){switch(e.t){case"str":return`"${e.parts.map(t=>typeof t=="string"?t:`\${${Np(t)}}`).join("")}"`;case"lit":return String(e.v);case"list":return`[${e.items.map(Np).join(", ")}]`;case"obj":return`{ ${e.entries.map(([t,n])=>`${t} = ${Np(n)}`).join(", ")} }`;case"ref":return e.path.join(".");case"raw":return e.text}}var ZJ=".gruntwork";function Jve(e){let t=e.trim().split("/").filter(o=>o!==""&&o!==".");if(e.trim().startsWith("/")||t.includes(".."))return;if(!t.length)return["**"];let n=t[t.length-1],r=t.join("/");return n==="**"?[r]:n==="*"?[`${r}*`]:[r,`${r}/**`]}var Kve=new Set(["environment","aws","repository","annotation"]),Vve=new Set(["account_id","plan_iam_role_arn","apply_iam_role_arn","region","session_duration"]),Zve={opentofu:"tofu",terraform:"terraform"};function Yve(e){let t=new Ei(Iv),n=e.dir??ZJ,r=[];for(let p of e.global)try{r.push({file:p.path,body:a0(p.text)})}catch(f){throw new S(`${p.path} is not HCL this import reads: ${f.message}`)}let o=new Map;for(let{file:p,body:f}of r)for(let g of f.blocks.filter(m=>m.type==="aws"))for(let m of g.blocks){let h=`${p}: aws.accounts.${m.labels[0]??""}`;if(m.type!=="accounts"||m.labels.length!==1){t.unknown(`${p}: aws.${m.type}`);continue}let y=m.attrs.get("path"),w=y&&Cp(y);if(w===void 0){t.scale("unmapped",`${h}.path`,"Accounts","its path is not a plain string, so the accounts it names are not read");continue}let k=[Kc(".",w),Kc(n,w)].map($=>$.split("\\").join("/")).map($=>({x:$,text:$.startsWith("..")?void 0:e.read($)})).find($=>$.text!==void 0);if(!k){t.scale("unmapped",`${h}.path`,"Accounts",`${w} is not in the repo, so the roles that name its accounts are not read`);continue}let v;try{v=uo(k.text)}catch($){throw new S(`${k.x} is not YAML: ${$.message}`)}if(!Me(v))throw new S(`${k.x} is not a map of accounts`);o.set(m.labels[0],v),t.scale("default",h,"Accounts",`read ${k.x}`)}let s=p=>{if(p){if(p.t==="str"){let f=p.parts.map(g=>typeof g=="string"?g:s(g));return f.every(g=>g!==void 0)?f.join(""):void 0}if(p.t==="ref"&&p.path.length===5&&p.path[0]==="aws"&&p.path[1]==="accounts"){let f=o.get(p.path[2])?.[p.path[3]],g=Me(f)?f[p.path[4]]:void 0;return typeof g=="string"||typeof g=="number"?String(g):void 0}}},i=(p,f)=>{if(!p){t.scale("unmapped",f,"Roles","it has no authentication block");return}for(let m of p.attrs.keys())t.unknown(`${f}.authentication.${m}`);if(!p.blocks.length)return t.scale("default",`${f}.authentication`,"No authentication"),"none";let g;for(let m of p.blocks){let h=`${f}.authentication.${m.type}`;if(m.type==="azure_oidc"||m.type==="gcp_oidc"||m.type==="custom"){t.scale("unmapped",h,"Other clouds");continue}if(m.type!=="aws_oidc"){t.unknown(h);continue}for(let k of m.attrs.keys())Vve.has(k)||t.unknown(`${h}.${k}`);let y=s(m.attrs.get("plan_iam_role_arn")),w=s(m.attrs.get("apply_iam_role_arn")),b=["plan_iam_role_arn","apply_iam_role_arn"].filter(k=>s(m.attrs.get(k))===void 0);if(b.length){let k=b.map(v=>`${v} = ${m.attrs.has(v)?Np(m.attrs.get(v)):"(unset)"}`).join("; ");t.scale("unmapped",h,"Roles",`${k}: only Pipelines can evaluate it, so set this pair in terragrunt.credentials by hand`);continue}if(y===w){t.scale("unmapped",h,"Roles",`plan and apply are both ${y}; plan runs pull-request code, so give it a read-only role of its own and add the pair by hand`);continue}g={plan:y,apply:w}}return g},a=[],c=(p,f,g)=>{let m=[];for(let h of p){let y=a.find(([w])=>w===h);if(y&&(y[1].plan!==f.plan||y[1].apply!==f.apply)){t.scale("unmapped",`${g}: ${h}`,"Roles",`${h} already takes ${y[1].plan} and ${y[1].apply}`);continue}y||a.push([h,f]),m.push(h)}m.length&&t.scale("mapped",g,"Roles",`terragrunt.credentials ${m.map(h=>`"${h}"`).join(", ")}: plan ${f.plan}, apply ${f.apply}`)};for(let{unit:p,file:f}of[...e.units].sort((g,m)=>g.unit.localeCompare(m.unit))){let g;try{g=a0(f.text)}catch(m){throw new S(`${f.path} is not HCL this import reads: ${m.message}`)}for(let m of g.attrs.keys())t.unknown(`${f.path}: ${m}`);for(let m of g.blocks){if(m.type!=="unit"){t.unknown(`${f.path}: ${m.type}`);continue}let h=`${f.path}: unit`,y=m.blocks.find(b=>b.type==="authentication");for(let b of m.blocks)b.type!=="authentication"&&t.unknown(`${h}.${b.type}`);if(y&&!y.blocks.length&&!y.attrs.size){t.scale("unmapped",`${h}.authentication`,"No authentication",`${p} still takes the roles of the environment it is in, if any`);continue}let w=i(y,h);w&&w!=="none"&&c([p],w,`${h}.authentication.aws_oidc`)}}let u;for(let{file:p,body:f}of r){for(let g of f.attrs.keys())t.unknown(`${p}: ${g}`);for(let g of f.blocks){if(!Kve.has(g.type)){t.unknown(`${p}: ${g.type}`);continue}if(g.type==="environment"){let m=`${p}: environment.${g.labels[0]??""}`,y=g.blocks.find(k=>k.type==="filter")?.attrs.get("paths"),w=[];if(y?.t==="list")for(let k of y.items){let v=Cp(k),$=v===void 0?void 0:Jve(v);$===void 0?t.scale("unmapped",`${m}.filter.paths`,"Roles",`${Np(k)} is not a path inside the repo`):w.push(...$)}else t.scale("unmapped",`${m}.filter`,"Roles","it has no filter.paths list");let b=i(g.blocks.find(k=>k.type==="authentication"),m);b&&b!=="none"&&w.length&&c(w,b,`${m}.authentication.aws_oidc`)}else if(g.type==="repository"){for(let[m,h]of g.attrs){let y=`${p}: repository.${m}`,w=m==="tf_binary"?Zve[Cp(h)??""]:void 0;w?(u=w,t.scale("mapped",y,"Binary",`binary: ${w}`)):m==="tf_binary"?t.scale("unmapped",y,"Binary",`${Np(h)} is not opentofu or terraform`):t.scale("unmapped",y,"Other settings")}for(let m of g.blocks)t.scale("unmapped",`${p}: repository.${m.type}${m.labels.length?`.${m.labels[0]}`:""}`,"Other settings")}else g.type==="annotation"&&t.scale("unmapped",`${p}: annotation.${g.labels[0]??""}`,"Other settings")}}t.own("when it applies","default","Pipelines plans on the pull request and applies after merge","apply.when: merge, terragucci's default");let l={};u&&(l.binary=u),a.length&&(l.terragrunt={credentials:Object.fromEntries(a)}),Ni(l,"terragucci.yml");let d=e.unitPaths?a.map(([p])=>p).filter(p=>!e.unitPaths.some(f=>Pe(p,f))):[];return{settings:l,notes:t.list,missing:d}}function YJ(e,t=ZJ){let n=Hve(e,t);if(!Sh(n)||!VJ(n).isDirectory())throw new S(`import terragrunt-scale reads the ${t} directory, and there is none in ${e}`);let r=Wve(e,n).split("\\").join("/")||".",o=Bve(n).filter(c=>c.endsWith(".hcl")).sort();if(!o.length){let c=Sh(Kc(n,"config.yml"))||Sh(Kc(n,"config.yaml"));throw new S(c?`${r} holds the legacy config.yml, which names no roles; Pipelines' HCL configuration does (environment blocks), or set terragrunt.credentials by hand`:`${r} holds no .hcl files`)}if(!vd(e))throw new S(`${e} has no root.hcl, terragrunt.hcl or terragrunt.stack.hcl; import terragrunt-scale writes roles by unit, for a Terragrunt repo`);let s=Dn(e).map(c=>c.path),i=c=>{let u=Kc(e,c);return Sh(u)&&VJ(u).isFile()?i0(u,"utf-8"):void 0};return{...Yve({dir:r,global:o.map(c=>({path:`${r}/${c}`,text:i0(Kc(n,c),"utf-8")})),units:s.filter(c=>Sh(Kc(e,c,"gruntwork.hcl"))).map(c=>({unit:c,file:{path:`${c}/gruntwork.hcl`,text:i0(Kc(e,c,"gruntwork.hcl"),"utf-8")}})),read:i,unitPaths:s}),from:r}}var p0=["atlantis","digger","terragrunt-scale","spacelift","env0"],c0={atlantis:["atlantis.yaml","atlantis.yml"],digger:["digger.yml","digger.yaml"],"terragrunt-scale":[".gruntwork"],spacelift:$k,env0:bk},e$e={atlantis:"Atlantis",digger:"OpenTaco","terragrunt-scale":"Terragrunt Scale",spacelift:"Spacelift",env0:"env zero"},tK={atlantis:_m,digger:_m,"terragrunt-scale":Iv,spacelift:hk,env0:hk},l0={approved:{row:"Required approval",to:["approved"]},mergeable:{row:"Checks green",to:["mergeable","checks"]},undiverged:{row:"Up to date",to:["undiverged"]}};function t$e(e){let[t,n]=Az;return`${e==="atlantis"?"Atlantis":"OpenTaco"} applies ${bo(t[e==="atlantis"?1:2])} by default, terragucci ${bo(t[3])}; the other way is ${bo(n[3])}`}function n$e(){return{roots:[],afterMerge:!1,requirements:new Map,projectsWithRequirements:0,projects:0,versions:new Map,tofu:[],env:new Map,envConflicts:new Set,named:new Map,dependsOn:[],terragrunt:!1}}function nK(e,t,n,r,o){if(typeof r!="string"||!/^[A-Za-z_][A-Za-z0-9_]*$/.test(r)||typeof o!="string"){t.unmapped(n,"Custom steps","it sets no fixed value, so it stays out of `env`");return}let s=e.env.get(r);if(s&&s.value!==o){e.envConflicts.add(r),t.unmapped(n,"Custom steps",`${r} is set to two values (in ${s.key} too), and \`env\` holds one`);return}s||e.env.set(r,{value:o,key:n})}function rK(e,t,n,r){oo(r).forEach((o,s)=>{let i=`${n}[${s}]`;if(typeof o=="string"){o==="policy_check"?t.unmapped(i,"Policy","set `policy:` and keep the Rego in the repo"):(o==="import"||o==="state_rm")&&t.leftOut(i,"`import` or `state rm` from a comment");return}if(!Me(o))return t.unknown(i);for(let[a,c]of Object.entries(o)){let u=`${i}.${a}`;a==="env"?Me(c)?nK(e,t,u,c.name,c.value):t.unmapped(u,"Custom steps"):a==="run"||a==="multienv"?t.unmapped(u,"Custom steps"):Me(c)&&c.extra_args!==void 0?t.leftOut(`${u}.extra_args`,"Flags at run time"):a==="policy_check"?t.unmapped(u,"Policy","set `policy:` and keep the Rego in the repo"):a==="import"||a==="state_rm"?t.leftOut(u,"`import` or `state rm` from a comment"):["init","plan","apply","show"].includes(a)||t.unknown(u)}})}function oK(e,t,n){typeof t.name=="string"&&n!==void 0&&!e.named.has(t.name)&&e.named.set(t.name,n)}function r$e(e,t,n,r){if(!e.dependsOn.length)return;if(e.terragrunt){for(let u of e.dependsOn)t.unmapped(u.at,"Order","in a Terragrunt repo the units' dependency blocks order them");return}let o=[];for(let u of e.dependsOn){let l=oo(u.names),d=l.filter(p=>typeof p!="string"||!e.named.has(p));d.length&&t.unmapped(`${u.at}: ${d.map(String).join(", ")}`,"Order",`${d.map(p=>JSON.stringify(p)).join(", ")} ${d.length===1?"names":"name"} no project`);for(let p of l)typeof p=="string"&&e.named.has(p)&&e.named.get(p)!==u.root&&o.push({at:u.at,d:u.root,u:e.named.get(p)})}let{after:s,fromReads:i,cycle:a}=jp(o.map(u=>[u.d,u.u]),r);Object.keys(s).length&&(n.waves={...n.waves,after:s});let c=new Map;for(let u of o)c.set(u.at,[...c.get(u.at)??[],u]);for(let[u,l]of c){let d=f=>f.map(g=>`${g.d} after ${g.u}`).join(", "),p=l.filter(f=>!i.has(`${f.d}\0${f.u}`));a&&p.length?t.unmapped(u,"Order",`${d(p)} ${p.length===1?"is":"are"} not kept: with the terraform_remote_state reads they make a cycle, ${a.join(" after ")}`):p.length?t.mapped(u,"Order",`waves.after: ${d(p)}`):t.covered(u,"Order",`the terraform_remote_state reads already order ${d(l)}`)}}function sK(e,t,n,r){let o=oo(r);o.length&&e.projectsWithRequirements++;for(let s of o)typeof s=="string"&&l0[s]?e.requirements.set(s,[...e.requirements.get(s)??[],n]):t.unknown(`${n}: ${String(s)}`)}var o$e=new Set(["name","dir","workspace","terraform_version","autoplan","apply_requirements","plan_requirements","import_requirements","execution_order_group","depends_on","workflow","repo_locks","custom_policy_check"]);function s$e(e,t,n){for(let o of Object.keys(e))["version","projects","workflows","automerge","parallel_plan","parallel_apply","autodiscover","abort_on_execution_order_fail","repo_locks"].includes(o)||n.unknown(o);e.autodiscover!==void 0&&n.covered("autodiscover","Which directories"),e.abort_on_execution_order_fail!==void 0&&n.unmapped("abort_on_execution_order_fail","Order"),e.automerge===!0&&(t.autoMerge="automerge"),e.repo_locks!==void 0&&d0(t,n,"repo_locks",e.repo_locks);let r=oo(e.projects);t.projects=r.length,r.forEach((o,s)=>{if(!Me(o))return n.unknown(`projects[${s}]`);let i=xo(o.dir??"."),a=`projects[${typeof o.name=="string"?o.name:i??s}]`;for(let c of Object.keys(o))o$e.has(c)||n.unknown(`${a}.${c}`);i===void 0?n.unmapped(`${a}.dir`,"Which directories",`${JSON.stringify(o.dir)} is not a directory inside the repo`):t.roots.includes(i)||t.roots.push(i),o.name!==void 0&&n.covered("projects[].name","Project name"),o.workspace!==void 0&&o.workspace!=="default"&&n.unmapped(`${a}.workspace`,"Workspace",`workspace ${String(o.workspace)} is not planned`),typeof o.terraform_version=="string"&&t.versions.set(o.terraform_version.replace(/^v/,""),[...t.versions.get(o.terraform_version.replace(/^v/,""))??[],a]),Me(o.autoplan)&&(o.autoplan.when_modified!==void 0&&n.covered("projects[].autoplan.when_modified","What triggers a plan"),o.autoplan.enabled===!1&&n.unmapped(`${a}.autoplan.enabled`,"What triggers a plan","no key turns the plan off for a root")),sK(t,n,`${a}.apply_requirements`,o.apply_requirements),o.plan_requirements!==void 0&&n.unmapped(`${a}.plan_requirements`,"Plan requirements"),o.import_requirements!==void 0&&n.leftOut(`${a}.import_requirements`,"`import` or `state rm` from a comment"),o.execution_order_group!==void 0&&n.unmapped(`${a}.execution_order_group`,"Order"),oK(t,o,i),o.depends_on!==void 0&&i!==void 0&&t.dependsOn.push({at:`${a}.depends_on`,root:i,names:o.depends_on}),o.repo_locks!==void 0&&d0(t,n,`${a}.repo_locks`,o.repo_locks),o.custom_policy_check!==void 0&&n.unmapped(`${a}.custom_policy_check`,"Policy")});for(let o of["parallel_plan","parallel_apply"])e[o]===!1?n.mapped(o,"Concurrency","parallelism: 1"):e[o]!==void 0&&n.covered(o,"Concurrency");if(Me(e.workflows)){for(let[o,s]of Object.entries(e.workflows))if(Me(s)){n.unmapped(`workflows.${o}`,"Custom steps");for(let[i,a]of Object.entries(s)){let c=`workflows.${o}.${i}`;i==="import"||i==="state_rm"?n.leftOut(c,"`import` or `state rm` from a comment"):i==="policy_check"?n.unmapped(c,"Policy","set `policy:` and keep the Rego in the repo"):i==="plan"||i==="apply"?rK(t,n,`${c}.steps`,Me(a)?a.steps:void 0):n.unknown(c)}}}}function d0(e,t,n,r){r===!0||Me(r)&&r.mode==="on_plan"?e.planLocks=n:t.covered(n,"Lock at plan time")}var i$e=new Set(["name","dir","workspace","terragrunt","opentofu","workflow","include_patterns","exclude_patterns","depends_on","aws_role_to_assume","apply_requirements"]);function a$e(e,t,n){for(let o of Object.keys(e))["projects","generate_projects","workflows","auto_merge","pr_locks"].includes(o)||n.unknown(o);e.auto_merge===!0&&(t.autoMerge="auto_merge"),e.pr_locks!==void 0&&d0(t,n,"pr_locks",e.pr_locks);let r=oo(e.projects);if(t.projects=r.length,r.forEach((o,s)=>{if(!Me(o))return n.unknown(`projects[${s}]`);let i=xo(o.dir??"."),a=`projects[${typeof o.name=="string"?o.name:i??s}]`;for(let c of Object.keys(o))i$e.has(c)||n.unknown(`${a}.${c}`);i===void 0?n.unmapped(`${a}.dir`,"Which directories",`${JSON.stringify(o.dir)} is not a directory inside the repo`):t.roots.includes(i)||t.roots.push(i),o.name!==void 0&&n.covered("projects[].name","Project name"),o.workspace!==void 0&&o.workspace!=="default"&&n.unmapped(`${a}.workspace`,"Workspace",`workspace ${String(o.workspace)} is not planned`),o.opentofu===!0&&t.tofu.push(a),o.terragrunt!==void 0&&n.covered("projects[].terragrunt","Terragrunt"),o.include_patterns!==void 0&&n.covered("projects[].include_patterns","What triggers a plan"),o.exclude_patterns!==void 0&&n.covered("projects[].exclude_patterns","What triggers a plan"),o.terragrunt===!0&&(t.terragrunt=!0),oK(t,o,i),o.depends_on!==void 0&&i!==void 0&&t.dependsOn.push({at:`${a}.depends_on`,root:i,names:o.depends_on}),o.aws_role_to_assume!==void 0&&n.unmapped(`${a}.aws_role_to_assume`,"Cloud credentials","name a read-only plan role and an apply role under `oidc`"),sK(t,n,`${a}.apply_requirements`,o.apply_requirements)}),Me(e.generate_projects)){let o=e.generate_projects,s=o.blocks!==void 0?oo(o.blocks).map((i,a)=>({b:i,at:`generate_projects.blocks[${a}]`})):[{b:o,at:"generate_projects"}];for(let{b:i,at:a}of s)if(Me(i)){for(let c of oo(i.include)){let u=xo(c);u===void 0?n.unmapped(`${a}.include`,"Which directories",`${JSON.stringify(c)} is not a glob inside the repo`):t.roots.includes(u)||(t.roots.push(u),n.mapped(`${a}.include`,"Which directories",`roots glob ${u}`))}i.exclude!==void 0&&n.unmapped(`${a}.exclude`,"Which directories","roots lists what to plan; narrow the globs instead"),(i.terragrunt_parsing!==void 0||i.terragrunt===!0)&&n.covered(`${a}.terragrunt_parsing`,"Terragrunt");for(let c of Object.keys(i))["include","exclude","terragrunt_parsing","terragrunt","blocks"].includes(c)||n.unknown(`${a}.${c}`)}}if(Me(e.workflows)){for(let[o,s]of Object.entries(e.workflows))if(Me(s))for(let[i,a]of Object.entries(s)){let c=`workflows.${o}.${i}`;if(i==="plan"||i==="apply")n.unmapped(c,"Custom steps"),rK(t,n,`${c}.steps`,Me(a)?a.steps:void 0);else if(i==="env_vars"&&Me(a))for(let[u,l]of Object.entries(a))oo(l).forEach((d,p)=>{let f=`${c}.${u}[${p}]`;Me(d)?nK(t,n,f,d.name,d.value):n.unknown(f)});else if(i==="workflow_configuration"&&Me(a))for(let[u,l]of Object.entries(a)){let d=`${c}.${u}`;u==="on_pull_request_pushed"?n.covered(d,"What triggers a plan"):u==="on_pull_request_closed"?n.covered(d,"Release locks on close"):u==="on_commit_to_default"?(f=>oo(l).some(g=>typeof g=="string"&&g.trim()===f))("digger apply")?(t.afterMerge=!0,n.covered(d,"Apply on merge")):n.covered(d,"Release locks on close"):n.unknown(d)}else n.unknown(c)}}}function c$e(e,t,n={}){if(!Me(t))throw new S(`the ${e$e[e]} config is not a map of settings`);if(e==="spacelift")return s0(t,[],n);if(e==="env0")return r0({discovery:t,flows:[],resources:[]},n);let r=new Ei,o=n$e();e==="atlantis"?s$e(t,o,r):a$e(t,o,r);let s={};o.roots.length&&(s.roots=[...o.roots].sort(),r.mapped("projects[].dir","Which directories",`roots: ${s.roots.join(", ")}`));let i=[...o.versions.keys()];i.length===1?(s.binary="terraform",s.version=i[0],r.mapped("projects[].terraform_version","Binary version",`binary: terraform, version: ${i[0]}`)):i.length>1&&r.unmapped("projects[].terraform_version","Binary version",`the projects pin ${i.join(", ")}, and the pipeline runs one; each root's required_version pin is read instead`),o.tofu.length&&(o.tofu.length===o.projects?(s.binary="tofu",r.mapped("projects[].opentofu","Binary version","binary: tofu")):r.unmapped("projects[].opentofu","Binary version",`only ${o.tofu.join(", ")} run OpenTofu, and the pipeline runs one binary`)),r$e(o,r,s,n.repo?.reads),e==="atlantis"&&(t.parallel_plan===!1||t.parallel_apply===!1)&&(s.parallelism=1);let a=n.applyWhen!==void 0,c=n.applyWhen??(o.afterMerge?"merge":"pull-request"),u=t$e(e);c==="pull-request"&&n.forge==="gitlab"?(c="merge",r.own("when it applies","unmapped",u,`left at apply.when: merge: ${us.comments}; and ${us.token}`)):c==="pull-request"?(s.apply={when:c},r.own("when it applies","mapped",u,a?"apply.when: pull-request, from --apply-when":"apply.when: pull-request, to keep applying before merge; --apply-when merge applies after merge instead")):r.own("when it applies","default",u,a?"apply.when: merge, from --apply-when":"apply.when: merge, terragucci's default");let l=Cn.filter(p=>[...o.requirements.keys()].some(f=>l0[f].to.includes(p)));for(let[p,f]of o.requirements){let{row:g,to:m}=l0[p],h=f.length===1?f[0]:`projects[].apply_requirements: ${p}`;c==="pull-request"?r.mapped(h,g,`apply.requires: ${m.join(", ")}`):r.unmapped(h,g,"applying after merge, a branch protection rule on the default branch does this job")}if(c==="pull-request"&&s.apply&&(l.length?(s.apply.requires=l,o.projectsWithRequirements<o.projects&&r.own("projects[].apply_requirements","mapped",`apply.requires covers every root: ${l.join(", ")}`,`${o.projectsWithRequirements} of ${o.projects} projects set apply_requirements, and terragucci keeps one list`)):r.own("projects[].apply_requirements","default",`no project sets apply_requirements, so apply.requires is left unset and every requirement applies: ${Cn.join(", ")}`)),o.autoMerge){let p=o.autoMerge;c!=="pull-request"||!s.apply?r.unmapped(p,"Merge after apply","apply.when is merge here, so a person merges"):n.forge==="forgejo"?r.unmapped(p,"Merge after apply","on Forgejo apply.merge: auto also needs apply.merge_token_env, the secret holding a token that may merge, which this command cannot name; add both"):s.apply.requires&&!s.apply.requires.includes("approved")?r.unmapped(p,"Merge after apply","apply.merge: auto merges only an approved head, and apply.requires leaves out approved"):(s.apply.merge="auto",r.mapped(p,"Merge after apply","apply.merge: auto"))}o.planLocks&&(n.forge==="gitlab"?r.unmapped(o.planLocks,"Lock at plan time",Df):(s.locks="plan",r.mapped(o.planLocks,"Lock at plan time","locks: plan")));let d=[...o.env].filter(([p])=>!o.envConflicts.has(p));if(d.length){s.env=Object.fromEntries(d.map(([p,f])=>[p,f.value]));for(let[p,f]of d)r.mapped(f.key,"Custom steps",`env: ${p}`)}return Ni(s,"terragucci.yml"),{settings:s,notes:r.list}}function u$e(e){let t=wt(e),n=wn(e,t);try{return{roots:t,layers:tc(e,t),reads:n}}catch{return{roots:t,layers:[t],reads:new Map}}}function iK(e,t,n={}){if(t==="terragrunt-scale"){if(n.applyWhen)throw new S("import terragrunt-scale takes no --apply-when: Pipelines applies after merge, terragucci's default");let p=YJ(e,n.file??c0[t][0]);return u0(e,t,p.from,p.settings,p.notes,p.missing,n)}let r=n.forge??Kr(e)?.value,o={...r?{forge:r}:{},...n.applyWhen?{applyWhen:n.applyWhen}:{},repo:u$e(e)};if(t==="spacelift"||t==="env0"){let p=n.file?Dp(e,QJ(e,n.file)):void 0,f=(t==="spacelift"?KJ:BJ)(e,{...o,...p!==void 0?{file:p}:{}}),g=f.missing??(f.settings.roots??[]).filter(m=>wt(e,[m]).length===0);return u0(e,t,f.read.join(", "),f.settings,f.notes,g,n)}let s=n.file?QJ(e,n.file):c0[t].map(p=>eK(e,p)).find(p=>XJ(p));if(!s||!XJ(s))throw new S(`import ${t} reads ${n.file??c0[t].join(" or ")}, and there is none in ${e}`);let i;try{i=uo(Xve(s,"utf-8"))}catch(p){throw new S(`${Dp(e,s)} is not YAML: ${p.message}`)}let{settings:a,notes:c,missing:u}=c$e(t,i,o),l=Dp(e,s)||s,d=u??(a.roots??[]).filter(p=>wt(e,[p]).length===0);return u0(e,t,l,a,c,d,n)}function u0(e,t,n,r,o,s,i){let a=`# Written by terragucci import ${t} from ${n}. What became of each setting: ${tK[t]}
948
+ `).attrs.get("x")??{t:"raw",text:p}}catch{return{t:"raw",text:p}}}let d=p=>{let f={type:"",labels:[],attrs:new Map,blocks:[]};for(;;){if(r(!0),t>=e.length)return p||n("a block has no closing }"),f;if(e[t]==="}")return p&&n("a } closes no block"),t++,f;let g=o();if(r(!1),e[t]==="="){t++,f.attrs.set(g,u());continue}let m=[];for(;e[t]!=="{";)e[t]==='"'?m.push(Cp(i())??n("a block label is not a plain string")):m.push(o()),r(!1);t++;let h=d(!1);f.blocks.push({...h,type:g,labels:m})}};return d(!0)}function Cp(e){return e.t==="str"&&e.parts.every(t=>typeof t=="string")?e.parts.join(""):void 0}function Np(e){switch(e.t){case"str":return`"${e.parts.map(t=>typeof t=="string"?t:`\${${Np(t)}}`).join("")}"`;case"lit":return String(e.v);case"list":return`[${e.items.map(Np).join(", ")}]`;case"obj":return`{ ${e.entries.map(([t,n])=>`${t} = ${Np(n)}`).join(", ")} }`;case"ref":return e.path.join(".");case"raw":return e.text}}var Z2=".gruntwork";function Jve(e){let t=e.trim().split("/").filter(o=>o!==""&&o!==".");if(e.trim().startsWith("/")||t.includes(".."))return;if(!t.length)return["**"];let n=t[t.length-1],r=t.join("/");return n==="**"?[r]:n==="*"?[`${r}*`]:[r,`${r}/**`]}var Kve=new Set(["environment","aws","repository","annotation"]),Vve=new Set(["account_id","plan_iam_role_arn","apply_iam_role_arn","region","session_duration"]),Zve={opentofu:"tofu",terraform:"terraform"};function Yve(e){let t=new Ei(Iv),n=e.dir??Z2,r=[];for(let p of e.global)try{r.push({file:p.path,body:a0(p.text)})}catch(f){throw new S(`${p.path} is not HCL this import reads: ${f.message}`)}let o=new Map;for(let{file:p,body:f}of r)for(let g of f.blocks.filter(m=>m.type==="aws"))for(let m of g.blocks){let h=`${p}: aws.accounts.${m.labels[0]??""}`;if(m.type!=="accounts"||m.labels.length!==1){t.unknown(`${p}: aws.${m.type}`);continue}let y=m.attrs.get("path"),w=y&&Cp(y);if(w===void 0){t.scale("unmapped",`${h}.path`,"Accounts","its path is not a plain string, so the accounts it names are not read");continue}let k=[Kc(".",w),Kc(n,w)].map($=>$.split("\\").join("/")).map($=>({x:$,text:$.startsWith("..")?void 0:e.read($)})).find($=>$.text!==void 0);if(!k){t.scale("unmapped",`${h}.path`,"Accounts",`${w} is not in the repo, so the roles that name its accounts are not read`);continue}let v;try{v=uo(k.text)}catch($){throw new S(`${k.x} is not YAML: ${$.message}`)}if(!Me(v))throw new S(`${k.x} is not a map of accounts`);o.set(m.labels[0],v),t.scale("default",h,"Accounts",`read ${k.x}`)}let s=p=>{if(p){if(p.t==="str"){let f=p.parts.map(g=>typeof g=="string"?g:s(g));return f.every(g=>g!==void 0)?f.join(""):void 0}if(p.t==="ref"&&p.path.length===5&&p.path[0]==="aws"&&p.path[1]==="accounts"){let f=o.get(p.path[2])?.[p.path[3]],g=Me(f)?f[p.path[4]]:void 0;return typeof g=="string"||typeof g=="number"?String(g):void 0}}},i=(p,f)=>{if(!p){t.scale("unmapped",f,"Roles","it has no authentication block");return}for(let m of p.attrs.keys())t.unknown(`${f}.authentication.${m}`);if(!p.blocks.length)return t.scale("default",`${f}.authentication`,"No authentication"),"none";let g;for(let m of p.blocks){let h=`${f}.authentication.${m.type}`;if(m.type==="azure_oidc"||m.type==="gcp_oidc"||m.type==="custom"){t.scale("unmapped",h,"Other clouds");continue}if(m.type!=="aws_oidc"){t.unknown(h);continue}for(let k of m.attrs.keys())Vve.has(k)||t.unknown(`${h}.${k}`);let y=s(m.attrs.get("plan_iam_role_arn")),w=s(m.attrs.get("apply_iam_role_arn")),b=["plan_iam_role_arn","apply_iam_role_arn"].filter(k=>s(m.attrs.get(k))===void 0);if(b.length){let k=b.map(v=>`${v} = ${m.attrs.has(v)?Np(m.attrs.get(v)):"(unset)"}`).join("; ");t.scale("unmapped",h,"Roles",`${k}: only Pipelines can evaluate it, so set this pair in terragrunt.credentials by hand`);continue}if(y===w){t.scale("unmapped",h,"Roles",`plan and apply are both ${y}; plan runs pull-request code, so give it a read-only role of its own and add the pair by hand`);continue}g={plan:y,apply:w}}return g},a=[],c=(p,f,g)=>{let m=[];for(let h of p){let y=a.find(([w])=>w===h);if(y&&(y[1].plan!==f.plan||y[1].apply!==f.apply)){t.scale("unmapped",`${g}: ${h}`,"Roles",`${h} already takes ${y[1].plan} and ${y[1].apply}`);continue}y||a.push([h,f]),m.push(h)}m.length&&t.scale("mapped",g,"Roles",`terragrunt.credentials ${m.map(h=>`"${h}"`).join(", ")}: plan ${f.plan}, apply ${f.apply}`)};for(let{unit:p,file:f}of[...e.units].sort((g,m)=>g.unit.localeCompare(m.unit))){let g;try{g=a0(f.text)}catch(m){throw new S(`${f.path} is not HCL this import reads: ${m.message}`)}for(let m of g.attrs.keys())t.unknown(`${f.path}: ${m}`);for(let m of g.blocks){if(m.type!=="unit"){t.unknown(`${f.path}: ${m.type}`);continue}let h=`${f.path}: unit`,y=m.blocks.find(b=>b.type==="authentication");for(let b of m.blocks)b.type!=="authentication"&&t.unknown(`${h}.${b.type}`);if(y&&!y.blocks.length&&!y.attrs.size){t.scale("unmapped",`${h}.authentication`,"No authentication",`${p} still takes the roles of the environment it is in, if any`);continue}let w=i(y,h);w&&w!=="none"&&c([p],w,`${h}.authentication.aws_oidc`)}}let u;for(let{file:p,body:f}of r){for(let g of f.attrs.keys())t.unknown(`${p}: ${g}`);for(let g of f.blocks){if(!Kve.has(g.type)){t.unknown(`${p}: ${g.type}`);continue}if(g.type==="environment"){let m=`${p}: environment.${g.labels[0]??""}`,y=g.blocks.find(k=>k.type==="filter")?.attrs.get("paths"),w=[];if(y?.t==="list")for(let k of y.items){let v=Cp(k),$=v===void 0?void 0:Jve(v);$===void 0?t.scale("unmapped",`${m}.filter.paths`,"Roles",`${Np(k)} is not a path inside the repo`):w.push(...$)}else t.scale("unmapped",`${m}.filter`,"Roles","it has no filter.paths list");let b=i(g.blocks.find(k=>k.type==="authentication"),m);b&&b!=="none"&&w.length&&c(w,b,`${m}.authentication.aws_oidc`)}else if(g.type==="repository"){for(let[m,h]of g.attrs){let y=`${p}: repository.${m}`,w=m==="tf_binary"?Zve[Cp(h)??""]:void 0;w?(u=w,t.scale("mapped",y,"Binary",`binary: ${w}`)):m==="tf_binary"?t.scale("unmapped",y,"Binary",`${Np(h)} is not opentofu or terraform`):t.scale("unmapped",y,"Other settings")}for(let m of g.blocks)t.scale("unmapped",`${p}: repository.${m.type}${m.labels.length?`.${m.labels[0]}`:""}`,"Other settings")}else g.type==="annotation"&&t.scale("unmapped",`${p}: annotation.${g.labels[0]??""}`,"Other settings")}}t.own("when it applies","default","Pipelines plans on the pull request and applies after merge","apply.when: merge, terragucci's default");let l={};u&&(l.binary=u),a.length&&(l.terragrunt={credentials:Object.fromEntries(a)}),Ni(l,"terragucci.yml");let d=e.unitPaths?a.map(([p])=>p).filter(p=>!e.unitPaths.some(f=>Pe(p,f))):[];return{settings:l,notes:t.list,missing:d}}function Y2(e,t=Z2){let n=Hve(e,t);if(!Sh(n)||!V2(n).isDirectory())throw new S(`import terragrunt-scale reads the ${t} directory, and there is none in ${e}`);let r=Wve(e,n).split("\\").join("/")||".",o=Bve(n).filter(c=>c.endsWith(".hcl")).sort();if(!o.length){let c=Sh(Kc(n,"config.yml"))||Sh(Kc(n,"config.yaml"));throw new S(c?`${r} holds the legacy config.yml, which names no roles; Pipelines' HCL configuration does (environment blocks), or set terragrunt.credentials by hand`:`${r} holds no .hcl files`)}if(!vd(e))throw new S(`${e} has no root.hcl, terragrunt.hcl or terragrunt.stack.hcl; import terragrunt-scale writes roles by unit, for a Terragrunt repo`);let s=Dn(e).map(c=>c.path),i=c=>{let u=Kc(e,c);return Sh(u)&&V2(u).isFile()?i0(u,"utf-8"):void 0};return{...Yve({dir:r,global:o.map(c=>({path:`${r}/${c}`,text:i0(Kc(n,c),"utf-8")})),units:s.filter(c=>Sh(Kc(e,c,"gruntwork.hcl"))).map(c=>({unit:c,file:{path:`${c}/gruntwork.hcl`,text:i0(Kc(e,c,"gruntwork.hcl"),"utf-8")}})),read:i,unitPaths:s}),from:r}}var p0=["atlantis","digger","terragrunt-scale","spacelift","env0"],c0={atlantis:["atlantis.yaml","atlantis.yml"],digger:["digger.yml","digger.yaml"],"terragrunt-scale":[".gruntwork"],spacelift:$k,env0:bk},e$e={atlantis:"Atlantis",digger:"OpenTaco","terragrunt-scale":"Terragrunt Scale",spacelift:"Spacelift",env0:"env zero"},tK={atlantis:_m,digger:_m,"terragrunt-scale":Iv,spacelift:hk,env0:hk},l0={approved:{row:"Required approval",to:["approved"]},mergeable:{row:"Checks green",to:["mergeable","checks"]},undiverged:{row:"Up to date",to:["undiverged"]}};function t$e(e){let[t,n]=Az;return`${e==="atlantis"?"Atlantis":"OpenTaco"} applies ${bo(t[e==="atlantis"?1:2])} by default, terragucci ${bo(t[3])}; the other way is ${bo(n[3])}`}function n$e(){return{roots:[],afterMerge:!1,requirements:new Map,projectsWithRequirements:0,projects:0,versions:new Map,tofu:[],env:new Map,envConflicts:new Set,named:new Map,dependsOn:[],terragrunt:!1}}function nK(e,t,n,r,o){if(typeof r!="string"||!/^[A-Za-z_][A-Za-z0-9_]*$/.test(r)||typeof o!="string"){t.unmapped(n,"Custom steps","it sets no fixed value, so it stays out of `env`");return}let s=e.env.get(r);if(s&&s.value!==o){e.envConflicts.add(r),t.unmapped(n,"Custom steps",`${r} is set to two values (in ${s.key} too), and \`env\` holds one`);return}s||e.env.set(r,{value:o,key:n})}function rK(e,t,n,r){oo(r).forEach((o,s)=>{let i=`${n}[${s}]`;if(typeof o=="string"){o==="policy_check"?t.unmapped(i,"Policy","set `policy:` and keep the Rego in the repo"):(o==="import"||o==="state_rm")&&t.leftOut(i,"`import` or `state rm` from a comment");return}if(!Me(o))return t.unknown(i);for(let[a,c]of Object.entries(o)){let u=`${i}.${a}`;a==="env"?Me(c)?nK(e,t,u,c.name,c.value):t.unmapped(u,"Custom steps"):a==="run"||a==="multienv"?t.unmapped(u,"Custom steps"):Me(c)&&c.extra_args!==void 0?t.leftOut(`${u}.extra_args`,"Flags at run time"):a==="policy_check"?t.unmapped(u,"Policy","set `policy:` and keep the Rego in the repo"):a==="import"||a==="state_rm"?t.leftOut(u,"`import` or `state rm` from a comment"):["init","plan","apply","show"].includes(a)||t.unknown(u)}})}function oK(e,t,n){typeof t.name=="string"&&n!==void 0&&!e.named.has(t.name)&&e.named.set(t.name,n)}function r$e(e,t,n,r){if(!e.dependsOn.length)return;if(e.terragrunt){for(let u of e.dependsOn)t.unmapped(u.at,"Order","in a Terragrunt repo the units' dependency blocks order them");return}let o=[];for(let u of e.dependsOn){let l=oo(u.names),d=l.filter(p=>typeof p!="string"||!e.named.has(p));d.length&&t.unmapped(`${u.at}: ${d.map(String).join(", ")}`,"Order",`${d.map(p=>JSON.stringify(p)).join(", ")} ${d.length===1?"names":"name"} no project`);for(let p of l)typeof p=="string"&&e.named.has(p)&&e.named.get(p)!==u.root&&o.push({at:u.at,d:u.root,u:e.named.get(p)})}let{after:s,fromReads:i,cycle:a}=jp(o.map(u=>[u.d,u.u]),r);Object.keys(s).length&&(n.waves={...n.waves,after:s});let c=new Map;for(let u of o)c.set(u.at,[...c.get(u.at)??[],u]);for(let[u,l]of c){let d=f=>f.map(g=>`${g.d} after ${g.u}`).join(", "),p=l.filter(f=>!i.has(`${f.d}\0${f.u}`));a&&p.length?t.unmapped(u,"Order",`${d(p)} ${p.length===1?"is":"are"} not kept: with the terraform_remote_state reads they make a cycle, ${a.join(" after ")}`):p.length?t.mapped(u,"Order",`waves.after: ${d(p)}`):t.covered(u,"Order",`the terraform_remote_state reads already order ${d(l)}`)}}function sK(e,t,n,r){let o=oo(r);o.length&&e.projectsWithRequirements++;for(let s of o)typeof s=="string"&&l0[s]?e.requirements.set(s,[...e.requirements.get(s)??[],n]):t.unknown(`${n}: ${String(s)}`)}var o$e=new Set(["name","dir","workspace","terraform_version","autoplan","apply_requirements","plan_requirements","import_requirements","execution_order_group","depends_on","workflow","repo_locks","custom_policy_check"]);function s$e(e,t,n){for(let o of Object.keys(e))["version","projects","workflows","automerge","parallel_plan","parallel_apply","autodiscover","abort_on_execution_order_fail","repo_locks"].includes(o)||n.unknown(o);e.autodiscover!==void 0&&n.covered("autodiscover","Which directories"),e.abort_on_execution_order_fail!==void 0&&n.unmapped("abort_on_execution_order_fail","Order"),e.automerge===!0&&(t.autoMerge="automerge"),e.repo_locks!==void 0&&d0(t,n,"repo_locks",e.repo_locks);let r=oo(e.projects);t.projects=r.length,r.forEach((o,s)=>{if(!Me(o))return n.unknown(`projects[${s}]`);let i=xo(o.dir??"."),a=`projects[${typeof o.name=="string"?o.name:i??s}]`;for(let c of Object.keys(o))o$e.has(c)||n.unknown(`${a}.${c}`);i===void 0?n.unmapped(`${a}.dir`,"Which directories",`${JSON.stringify(o.dir)} is not a directory inside the repo`):t.roots.includes(i)||t.roots.push(i),o.name!==void 0&&n.covered("projects[].name","Project name"),o.workspace!==void 0&&o.workspace!=="default"&&n.unmapped(`${a}.workspace`,"Workspace",`workspace ${String(o.workspace)} is not planned`),typeof o.terraform_version=="string"&&t.versions.set(o.terraform_version.replace(/^v/,""),[...t.versions.get(o.terraform_version.replace(/^v/,""))??[],a]),Me(o.autoplan)&&(o.autoplan.when_modified!==void 0&&n.covered("projects[].autoplan.when_modified","What triggers a plan"),o.autoplan.enabled===!1&&n.unmapped(`${a}.autoplan.enabled`,"What triggers a plan","no key turns the plan off for a root")),sK(t,n,`${a}.apply_requirements`,o.apply_requirements),o.plan_requirements!==void 0&&n.unmapped(`${a}.plan_requirements`,"Plan requirements"),o.import_requirements!==void 0&&n.leftOut(`${a}.import_requirements`,"`import` or `state rm` from a comment"),o.execution_order_group!==void 0&&n.unmapped(`${a}.execution_order_group`,"Order"),oK(t,o,i),o.depends_on!==void 0&&i!==void 0&&t.dependsOn.push({at:`${a}.depends_on`,root:i,names:o.depends_on}),o.repo_locks!==void 0&&d0(t,n,`${a}.repo_locks`,o.repo_locks),o.custom_policy_check!==void 0&&n.unmapped(`${a}.custom_policy_check`,"Policy")});for(let o of["parallel_plan","parallel_apply"])e[o]===!1?n.mapped(o,"Concurrency","parallelism: 1"):e[o]!==void 0&&n.covered(o,"Concurrency");if(Me(e.workflows)){for(let[o,s]of Object.entries(e.workflows))if(Me(s)){n.unmapped(`workflows.${o}`,"Custom steps");for(let[i,a]of Object.entries(s)){let c=`workflows.${o}.${i}`;i==="import"||i==="state_rm"?n.leftOut(c,"`import` or `state rm` from a comment"):i==="policy_check"?n.unmapped(c,"Policy","set `policy:` and keep the Rego in the repo"):i==="plan"||i==="apply"?rK(t,n,`${c}.steps`,Me(a)?a.steps:void 0):n.unknown(c)}}}}function d0(e,t,n,r){r===!0||Me(r)&&r.mode==="on_plan"?e.planLocks=n:t.covered(n,"Lock at plan time")}var i$e=new Set(["name","dir","workspace","terragrunt","opentofu","workflow","include_patterns","exclude_patterns","depends_on","aws_role_to_assume","apply_requirements"]);function a$e(e,t,n){for(let o of Object.keys(e))["projects","generate_projects","workflows","auto_merge","pr_locks"].includes(o)||n.unknown(o);e.auto_merge===!0&&(t.autoMerge="auto_merge"),e.pr_locks!==void 0&&d0(t,n,"pr_locks",e.pr_locks);let r=oo(e.projects);if(t.projects=r.length,r.forEach((o,s)=>{if(!Me(o))return n.unknown(`projects[${s}]`);let i=xo(o.dir??"."),a=`projects[${typeof o.name=="string"?o.name:i??s}]`;for(let c of Object.keys(o))i$e.has(c)||n.unknown(`${a}.${c}`);i===void 0?n.unmapped(`${a}.dir`,"Which directories",`${JSON.stringify(o.dir)} is not a directory inside the repo`):t.roots.includes(i)||t.roots.push(i),o.name!==void 0&&n.covered("projects[].name","Project name"),o.workspace!==void 0&&o.workspace!=="default"&&n.unmapped(`${a}.workspace`,"Workspace",`workspace ${String(o.workspace)} is not planned`),o.opentofu===!0&&t.tofu.push(a),o.terragrunt!==void 0&&n.covered("projects[].terragrunt","Terragrunt"),o.include_patterns!==void 0&&n.covered("projects[].include_patterns","What triggers a plan"),o.exclude_patterns!==void 0&&n.covered("projects[].exclude_patterns","What triggers a plan"),o.terragrunt===!0&&(t.terragrunt=!0),oK(t,o,i),o.depends_on!==void 0&&i!==void 0&&t.dependsOn.push({at:`${a}.depends_on`,root:i,names:o.depends_on}),o.aws_role_to_assume!==void 0&&n.unmapped(`${a}.aws_role_to_assume`,"Cloud credentials","name a read-only plan role and an apply role under `oidc`"),sK(t,n,`${a}.apply_requirements`,o.apply_requirements)}),Me(e.generate_projects)){let o=e.generate_projects,s=o.blocks!==void 0?oo(o.blocks).map((i,a)=>({b:i,at:`generate_projects.blocks[${a}]`})):[{b:o,at:"generate_projects"}];for(let{b:i,at:a}of s)if(Me(i)){for(let c of oo(i.include)){let u=xo(c);u===void 0?n.unmapped(`${a}.include`,"Which directories",`${JSON.stringify(c)} is not a glob inside the repo`):t.roots.includes(u)||(t.roots.push(u),n.mapped(`${a}.include`,"Which directories",`roots glob ${u}`))}i.exclude!==void 0&&n.unmapped(`${a}.exclude`,"Which directories","roots lists what to plan; narrow the globs instead"),(i.terragrunt_parsing!==void 0||i.terragrunt===!0)&&n.covered(`${a}.terragrunt_parsing`,"Terragrunt");for(let c of Object.keys(i))["include","exclude","terragrunt_parsing","terragrunt","blocks"].includes(c)||n.unknown(`${a}.${c}`)}}if(Me(e.workflows)){for(let[o,s]of Object.entries(e.workflows))if(Me(s))for(let[i,a]of Object.entries(s)){let c=`workflows.${o}.${i}`;if(i==="plan"||i==="apply")n.unmapped(c,"Custom steps"),rK(t,n,`${c}.steps`,Me(a)?a.steps:void 0);else if(i==="env_vars"&&Me(a))for(let[u,l]of Object.entries(a))oo(l).forEach((d,p)=>{let f=`${c}.${u}[${p}]`;Me(d)?nK(t,n,f,d.name,d.value):n.unknown(f)});else if(i==="workflow_configuration"&&Me(a))for(let[u,l]of Object.entries(a)){let d=`${c}.${u}`;u==="on_pull_request_pushed"?n.covered(d,"What triggers a plan"):u==="on_pull_request_closed"?n.covered(d,"Release locks on close"):u==="on_commit_to_default"?(f=>oo(l).some(g=>typeof g=="string"&&g.trim()===f))("digger apply")?(t.afterMerge=!0,n.covered(d,"Apply on merge")):n.covered(d,"Release locks on close"):n.unknown(d)}else n.unknown(c)}}}function c$e(e,t,n={}){if(!Me(t))throw new S(`the ${e$e[e]} config is not a map of settings`);if(e==="spacelift")return s0(t,[],n);if(e==="env0")return r0({discovery:t,flows:[],resources:[]},n);let r=new Ei,o=n$e();e==="atlantis"?s$e(t,o,r):a$e(t,o,r);let s={};o.roots.length&&(s.roots=[...o.roots].sort(),r.mapped("projects[].dir","Which directories",`roots: ${s.roots.join(", ")}`));let i=[...o.versions.keys()];i.length===1?(s.binary="terraform",s.version=i[0],r.mapped("projects[].terraform_version","Binary version",`binary: terraform, version: ${i[0]}`)):i.length>1&&r.unmapped("projects[].terraform_version","Binary version",`the projects pin ${i.join(", ")}, and the pipeline runs one; each root's required_version pin is read instead`),o.tofu.length&&(o.tofu.length===o.projects?(s.binary="tofu",r.mapped("projects[].opentofu","Binary version","binary: tofu")):r.unmapped("projects[].opentofu","Binary version",`only ${o.tofu.join(", ")} run OpenTofu, and the pipeline runs one binary`)),r$e(o,r,s,n.repo?.reads),e==="atlantis"&&(t.parallel_plan===!1||t.parallel_apply===!1)&&(s.parallelism=1);let a=n.applyWhen!==void 0,c=n.applyWhen??(o.afterMerge?"merge":"pull-request"),u=t$e(e);c==="pull-request"&&n.forge==="gitlab"?(c="merge",r.own("when it applies","unmapped",u,`left at apply.when: merge: ${us.comments}; and ${us.token}`)):c==="pull-request"?(s.apply={when:c},r.own("when it applies","mapped",u,a?"apply.when: pull-request, from --apply-when":"apply.when: pull-request, to keep applying before merge; --apply-when merge applies after merge instead")):r.own("when it applies","default",u,a?"apply.when: merge, from --apply-when":"apply.when: merge, terragucci's default");let l=Cn.filter(p=>[...o.requirements.keys()].some(f=>l0[f].to.includes(p)));for(let[p,f]of o.requirements){let{row:g,to:m}=l0[p],h=f.length===1?f[0]:`projects[].apply_requirements: ${p}`;c==="pull-request"?r.mapped(h,g,`apply.requires: ${m.join(", ")}`):r.unmapped(h,g,"applying after merge, a branch protection rule on the default branch does this job")}if(c==="pull-request"&&s.apply&&(l.length?(s.apply.requires=l,o.projectsWithRequirements<o.projects&&r.own("projects[].apply_requirements","mapped",`apply.requires covers every root: ${l.join(", ")}`,`${o.projectsWithRequirements} of ${o.projects} projects set apply_requirements, and terragucci keeps one list`)):r.own("projects[].apply_requirements","default",`no project sets apply_requirements, so apply.requires is left unset and every requirement applies: ${Cn.join(", ")}`)),o.autoMerge){let p=o.autoMerge;c!=="pull-request"||!s.apply?r.unmapped(p,"Merge after apply","apply.when is merge here, so a person merges"):n.forge==="forgejo"?r.unmapped(p,"Merge after apply","on Forgejo apply.merge: auto also needs apply.merge_token_env, the secret holding a token that may merge, which this command cannot name; add both"):s.apply.requires&&!s.apply.requires.includes("approved")?r.unmapped(p,"Merge after apply","apply.merge: auto merges only an approved head, and apply.requires leaves out approved"):(s.apply.merge="auto",r.mapped(p,"Merge after apply","apply.merge: auto"))}o.planLocks&&(n.forge==="gitlab"?r.unmapped(o.planLocks,"Lock at plan time",Df):(s.locks="plan",r.mapped(o.planLocks,"Lock at plan time","locks: plan")));let d=[...o.env].filter(([p])=>!o.envConflicts.has(p));if(d.length){s.env=Object.fromEntries(d.map(([p,f])=>[p,f.value]));for(let[p,f]of d)r.mapped(f.key,"Custom steps",`env: ${p}`)}return Ni(s,"terragucci.yml"),{settings:s,notes:r.list}}function u$e(e){let t=wt(e),n=wn(e,t);try{return{roots:t,layers:tc(e,t),reads:n}}catch{return{roots:t,layers:[t],reads:new Map}}}function iK(e,t,n={}){if(t==="terragrunt-scale"){if(n.applyWhen)throw new S("import terragrunt-scale takes no --apply-when: Pipelines applies after merge, terragucci's default");let p=Y2(e,n.file??c0[t][0]);return u0(e,t,p.from,p.settings,p.notes,p.missing,n)}let r=n.forge??Kr(e)?.value,o={...r?{forge:r}:{},...n.applyWhen?{applyWhen:n.applyWhen}:{},repo:u$e(e)};if(t==="spacelift"||t==="env0"){let p=n.file?Dp(e,Q2(e,n.file)):void 0,f=(t==="spacelift"?K2:B2)(e,{...o,...p!==void 0?{file:p}:{}}),g=f.missing??(f.settings.roots??[]).filter(m=>wt(e,[m]).length===0);return u0(e,t,f.read.join(", "),f.settings,f.notes,g,n)}let s=n.file?Q2(e,n.file):c0[t].map(p=>eK(e,p)).find(p=>X2(p));if(!s||!X2(s))throw new S(`import ${t} reads ${n.file??c0[t].join(" or ")}, and there is none in ${e}`);let i;try{i=uo(Xve(s,"utf-8"))}catch(p){throw new S(`${Dp(e,s)} is not YAML: ${p.message}`)}let{settings:a,notes:c,missing:u}=c$e(t,i,o),l=Dp(e,s)||s,d=u??(a.roots??[]).filter(p=>wt(e,[p]).length===0);return u0(e,t,l,a,c,d,n)}function u0(e,t,n,r,o,s,i){let a=`# Written by terragucci import ${t} from ${n}. What became of each setting: ${tK[t]}
949
949
  ${Object.keys(r).length?xr(r,0).trim():"{}"}
950
950
  `,c=fe(e),u;if(!i.dryRun){if(c&&!i.force)throw new S(`${Dp(e,c)} is already here; --force replaces it, or --dry-run prints what would be written`);let l=c??eK(e,"terragucci.yml");if(c&&!/\.ya?ml$/.test(c))throw new S(`${Dp(e,c)} is not YAML; move it aside first`);Qve(l,a),u=Dp(e,l)}return{source:t,from:n,settings:r,notes:o,yaml:a,missing:s,...u?{wrote:u}:{}}}var l$e={mapped:"Written to terragucci.yml",default:"Done by terragucci with no key",unmapped:"Not mapped","left-out":"Left out on purpose"};function aK(e){let t=[];t.push(e.wrote?`read ${e.from}, wrote ${e.wrote}:`:`read ${e.from}; dry run, nothing written. terragucci.yml would be:`),t.push("",...e.yaml.trimEnd().split(`
951
951
  `).map(n=>` ${n}`));for(let n of["mapped","default","unmapped","left-out"]){let r=e.notes.filter(o=>o.kind===n);if(r.length){t.push("",`${l$e[n]}:`);for(let o of r){let s=o.detail?`${o.detail}. `:"",i=o.row?`The guide: ${o.text}`:o.text;t.push(` ${o.key}${o.row?` (${o.row})`:""}: ${s}${i}`)}}}if(e.missing.length){let n=e.source==="terragrunt-scale"?`No unit matches ${e.missing.join(", ")}; check those filter paths.`:e.source==="spacelift"?`No directory with Terraform files matches ${e.missing.join(", ")}; check those stacks' project_root.`:e.source==="env0"?`No directory with Terraform files matches ${e.missing.join(", ")}; check those environments' templates.`:`No directory with Terraform files matches ${e.missing.join(", ")}; check those projects' dir.`;t.push("",n)}return t.push("",`Next: \`npx terragucci init\` writes the pipeline for these settings. Every mapping: ${tK[e.source]}`),t.join(`
@@ -1160,7 +1160,7 @@ ${b.notes}`).join(`
1160
1160
  `)),h={text:w||"no published module",data:y}}else if(r==="version-bump"){let{suggestions:y,proposals:w}=await r4(t,a,{module:n.module,since:n.since,env:o,fetch:n.decideFetch}),b=await dk(t,a,w,{mode:u,env:o,fetch:n.fetch});h={text:[...y.map(v=>`${v.module} ${v.last}${v.version?` -> ${v.version}`:""}: ${v.note}`),...b.map(Z0)].join(`
1161
1161
  `)||"no module",data:y,proposals:b}}else if(r==="description"){let y=Xc(t,n.report??"terragucci-report");m(Da(vr(y,"report.json")),"--report, the directory terragucci stage tf-plan wrote");let w=n.title!==void 0||n.description!==void 0?{title:n.title??"",description:n.description??""}:eRe(o)??await tRe(o,n.fetch??globalThis.fetch);if(!w||!w.title&&!w.description)return{event:r,response:c,skipped:"no pull request title or description to read",text:"no pull request title or description to read; the note is unchanged"};let b=await JV({dir:y,...w,decide:a.decide,write:u==="apply",env:o,fetch:n.decideFetch}),k="";if(b.files.length>0&&a.reports?.bucket)try{let v=JSON.parse(Zp(vr(y,"report.json"),"utf-8")),$=await qG(dr(a.reports,o,n.s3Fetch),y,v,b.files,a.reports.prefix);k=`
1162
1162
  copied ${b.files.join(", ")} to the bucket under ${$[0].slice(0,$[0].lastIndexOf("/"))}`}catch(v){k=`
1163
- the bucket's copy was not updated: ${v.message}`}h={text:b.text+k,data:b.record}}else{let y=await yJ(t,{mode:u,config:n.config,env:o,...n.fetch?{fetch:n.fetch}:{}});h={text:wJ(y),data:y,...YP(y)?{exit:YP(y)}:{}}}return{event:r,response:c,...h}}var Dk="terragucci_imports.tf",Vp="terragucci_generated.tf";function nRe(e,t){if(t&&typeof t!="string")return t;let n=Xc(e,t??vr("terragucci-report",vm));if(!Da(n)){if(t)throw new S(`--attributions ${t}: no such file`);return}return JSON.parse(Zp(n,"utf-8"))}async function rRe(e,t,n,r,o,s){if(t.length===0)throw new S("no root matches");if(r.length&&t.length!==1)throw new S(`--import needs --root to name one root; ${t.length} match`);let i={codified:[],left:[],files:new Map,imports:[],attributions:[],routed:[],notes:[]};for(let a of t){let c=vr(e,a),u=(...p)=>{let f=Y0(n,[`-chdir=${c}`,p[0],"-no-color",...p.slice(1)],{encoding:"utf-8",maxBuffer:536870912,env:Ve(o)});if(f.status!==0&&p[0]!=="query"&&!p.some(g=>g.startsWith("-generate")))throw new S(`${a}: ${n} ${p[0]} failed:
1163
+ the bucket's copy was not updated: ${v.message}`}h={text:b.text+k,data:b.record}}else{let y=await y2(t,{mode:u,config:n.config,env:o,...n.fetch?{fetch:n.fetch}:{}});h={text:w2(y),data:y,...YP(y)?{exit:YP(y)}:{}}}return{event:r,response:c,...h}}var Dk="terragucci_imports.tf",Vp="terragucci_generated.tf";function nRe(e,t){if(t&&typeof t!="string")return t;let n=Xc(e,t??vr("terragucci-report",vm));if(!Da(n)){if(t)throw new S(`--attributions ${t}: no such file`);return}return JSON.parse(Zp(n,"utf-8"))}async function rRe(e,t,n,r,o,s){if(t.length===0)throw new S("no root matches");if(r.length&&t.length!==1)throw new S(`--import needs --root to name one root; ${t.length} match`);let i={codified:[],left:[],files:new Map,imports:[],attributions:[],routed:[],notes:[]};for(let a of t){let c=vr(e,a),u=(...p)=>{let f=Y0(n,[`-chdir=${c}`,p[0],"-no-color",...p.slice(1)],{encoding:"utf-8",maxBuffer:536870912,env:Ve(o)});if(f.status!==0&&p[0]!=="query"&&!p.some(g=>g.startsWith("-generate")))throw new S(`${a}: ${n} ${p[0]} failed:
1164
1164
  ${Mk(f.stderr||f.stdout)}`);return f},l=vr(c,".terragucci-drift.tfplan");try{u("init","-input=false");let p=ob(n)&&ms(e,[a]).length>0;u("plan",...p?[]:["-refresh-only"],"-input=false","-lock=false",`-out=${l}`);let f=JSON.parse(u("show","-json",l).stdout),g=nl(p?Dv(f):f);if(s){let h=s.known?.[a]??await ep(a,g,s),y=VT(h.attributions);g=ZT(g,y.leave),i.attributions.push(...h.attributions),i.routed.push(...y.lines.map(w=>`${a}: ${w.slice(2)}`).map(w=>`- ${w}`)),i.notes.push(...h.notes.filter(w=>!i.notes.includes(w)))}let m=Av(a,c,g);i.codified.push(...m.codified),i.left.push(...m.left);for(let[h,y]of m.files)i.files.set(a4.join(a,h),y)}finally{i4(l,{force:!0})}let d=n==="terraform"&&Ez(c);if(!(!r.length&&!d)){for(let p of[Dk,Vp])if(Da(vr(c,p)))throw new S(`${a}/${p} exists; merge or remove it first`);try{r.length&&u4(vr(c,Dk),Rz(r));let p=d?u("query",`-generate-config-out=${Vp}`):u("plan","-input=false","-lock=false",`-generate-config-out=${Vp}`);if(!Da(vr(c,Vp)))throw new S(`${a}: ${n} generated no config:
1165
1165
  ${Mk(p.stderr||p.stdout)}`);for(let f of[Dk,Vp])Da(vr(c,f))&&i.files.set(a4.join(a,f),Zp(vr(c,f),"utf-8"));i.imports.push(...r.length?r.map(f=>`${a}: ${f.address} (${f.id})`):[`${a}: what terraform query lists`])}finally{for(let p of[Dk,Vp])i4(vr(c,p),{force:!0})}}}return i}async function oRe(e,t,n,r,o,s,i){if(n.imports?.length)throw new S("--import writes import blocks into a root's own files, and a Terragrunt unit's resources are in its module; import into a unit with an import block in its module, through a reviewed change");let a=MV(t,r,{...n.terragrunt?{terragrunt:n.terragrunt}:{},...n.terragruntExec?{exec:n.terragruntExec}:{}}),{units:c,from:u}=await LV(e,a,{...n.report?{report:n.report}:{},...n.root?{root:n.root}:{},...o.terragrunt?.exclude?{exclude:o.terragrunt.exclude}:{}});await s.prepareRoots({roots:c,binary:t,terragrunt:a.terragrunt,exec:a.exec});let l={codified:[],left:[],files:new Map,imports:[],attributions:[],routed:[],notes:[]};if(c.length===0)return l;l.notes.push(`${c.length} unit${c.length===1?"":"s"} from ${u}: ${c.join(", ")}`);for(let d of c){let p=await GV(e,d,a);if(p.length===0)continue;if(i){let g=i.known?.[d]??await ep(d,p,i),m=VT(g.attributions);p=ZT(p,m.leave),l.attributions.push(...g.attributions),l.routed.push(...m.lines.map(h=>`${d}: ${h.slice(2)}`).map(h=>`- ${h}`)),l.notes.push(...g.notes.filter(h=>!l.notes.includes(h)))}let f=await UV(e,d,p,a,l.files,s.sourceOf(d));l.codified.push(...f.codified),l.left.push(...f.left)}return l}async function sRe(e,t,n,r,o,s){let i=o.branch??s.GITHUB_HEAD_REF??s.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME;if(!i)throw new S("respond fmt needs --branch, the pull request's branch");if(r==="apply"&&i===await lc(o.fetch??globalThis.fetch,vh(e,t,s)))throw new S(`${i} is the default branch; fmt commits only to a pull request's branch`);no(e,["fetch","-q","origin",i]);let a=QP(e,"FETCH_HEAD");try{let c=Y0(n,["fmt","-recursive","-list=true",...r==="apply"?[]:["-check"]],{cwd:a.dir,encoding:"utf-8",env:Ve(s)});if(c.error)throw new S(`respond fmt could not run ${n}: ${c.error.message}`);let u=c.stdout.split(`
1166
1166
  `).filter(Boolean);if(c.status!==0&&u.length===0)throw new S(`${i}: ${n} fmt failed:
@@ -1298,9 +1298,9 @@ Exit codes: 0 done; 1 one or more projects or roots failed; 2 a usage or config
1298
1298
 
1299
1299
  init, reconcile, plan, stage, rollout, respond, config check, config identities, forge check and query take --json: one envelope on stdout.
1300
1300
 
1301
- Docs: https://intentius.io/terragucci/`;async function X9(e){try{let t=fe(e);if(!t)return{};let n=et(e,ue(await Re(t)));return{...n.after?{after:n.after}:{},...n.kind==="synth"?{synth:!0}:{}}}catch{return{}}}function Q9(e){if(e!==void 0&&!Do.includes(e))throw new S(`--approval must be one of ${Do.join(", ")}`);return e}function eY(e,t){let n=e===void 0?void 0:e==="none"?[]:e.split(",");if(n?.some(r=>!Cn.includes(r)))throw new S(`${t}'s --requires is a comma-separated list of ${Cn.join(", ")}, or none`);return n}function tY(e){let t=Number(e);if(!Number.isInteger(t)||t<1)throw new S(`--parallelism is ${JSON.stringify(e)}; use a whole number of 1 or more`);return t}function BI(e,t){let n=Number(t);if(!Number.isInteger(n)||n<1)throw new S(`--${e} is ${JSON.stringify(t)}; use a whole number of 1 or more`);return n}function FOe(e){let[t="",...n]=e,r={},o=[];for(let s=0;s<n.length;s++){let i=n[s];if(i.startsWith("--")){let[a,c]=i.slice(2).split("=",2);c!==void 0?r[a]=c:n[s+1]!==void 0&&!n[s+1].startsWith("--")&&!["force","dry-run","json","terragrunt","rest","again","poll","plan-notes","no-cost","check","approval-status","review","stand-down","publish","no-open","units","self-hosted","no-roots","require-clouds"].includes(a)?r[a]=n[++s]:r[a]=!0}else o.push(i)}return{cmd:t,flags:r,args:o}}function zOe(e){if(e!=="wait"&&e!=="refuse")throw new S("--on-held must be wait or refuse");return e}function P(e,t){let n=e[t];if(n===!0)throw new S(`--${t} needs a value`);return n}function UOe(e,t){let n=new Set(["aws"]),r=e.projects?Object.entries(e.projects).map(([o,s])=>({host:po(o).host,s:{...e.defaults,...s}})):[{host:void 0,s:e}];for(let{host:o,s}of r){let i=s.forge??(o?ir(o):t?Kr(t)?.value:void 0);i&&n.add(i)}return[...n]}async function nY(e){let{cmd:t,flags:n,args:r}=FOe(e),o=process.cwd(),s=n.json===!0,i=a=>(console.log(JSON.stringify(a,null,2)),a.exit);try{if(s&&!NJ.includes(t))throw new S(`--json is not available on ${t||"help"}`);switch(t){case"init":{let a=P(n,"forge"),c=P(n,"binary");if(a&&!Wr.includes(a))throw new S(`--forge must be one of ${Wr.join(", ")}`);if(c&&!od.includes(c))throw new S(`--binary must be one of ${od.join(", ")}`);let u=Q9(P(n,"approval")),l=P(n,"signer"),d=await ik(o,{forge:a,binary:c,...u?{approval:u}:{},...l?{signer:l}:{},force:n.force===!0,dryRun:n["dry-run"]===!0});return s?i(Xo("init",0,B2(o,d,n["dry-run"]===!0))):(console.log(U2(o,d,n["dry-run"]===!0)),n["dry-run"]===!0&&console.log("dry run: nothing was written"),0)}case"import":{let[a,c,u]=r;if(gK.includes(a)){if(c!==void 0)throw new S(`import ${a} reads the platform's API, not a file: give --hostname${a==="scalr"?"":" and --organization"}`);let f=P(n,"forge");if(f&&!Wr.includes(f))throw new S(`--forge must be one of ${Wr.join(", ")}`);let g=P(n,"hostname")??(a==="hcp"?"app.terraform.io":void 0);if(!g||!/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:\d+)?$/i.test(g))throw new S(`import ${a} needs --hostname, the host name of ${a==="scalr"?"the Scalr account, such as acme.scalr.io":"the OTF server"}`);let m=P(n,"organization");if(a!=="scalr"&&!m)throw new S(`import ${a} needs --organization, the organization whose workspaces it reads`);if(a==="scalr"&&m)throw new S("import scalr reads the account the host names; --environment narrows it to one environment");let h=P(n,"environment");if(a!=="scalr"&&h)throw new S(`--environment is Scalr's; import ${a} reads one --organization`);let y=P(n,"repo");if(y!==void 0&&!/^[^/\s]+(\/[^/\s]+)+$/.test(y))throw new S("--repo must be the repo as the VCS connection names it, owner/name");let w=await mK(o,()=>a==="scalr"?lK(g,h,process.env):cK(a,g,m,process.env),{...y?{repo:y}:{},...f?{forge:f}:{},force:n.force===!0,dryRun:n["dry-run"]===!0});return console.log(hK(w)),0}if(!p0.includes(a))throw new S(`import reads ${p0.join(", ")}: \`terragucci import atlantis [atlantis.yaml]\`, \`terragucci import digger [digger.yml]\`, \`terragucci import terragrunt-scale [.gruntwork]\`, \`terragucci import spacelift [.spacelift/config.yml]\` or \`terragucci import env0 [env0-discovery.yml]\``);if(u!==void 0)throw new S("import reads one file");let l=P(n,"forge");if(l&&!Wr.includes(l))throw new S(`--forge must be one of ${Wr.join(", ")}`);let d=P(n,"apply-when");if(d&&!id.includes(d))throw new S(`--apply-when must be one of ${id.join(", ")}`);let p=iK(o,a,{...c?{file:c}:{},...l?{forge:l}:{},...d?{applyWhen:d}:{},force:n.force===!0,dryRun:n["dry-run"]===!0});return console.log(aK(p)),0}case"reconcile":{let a=P(n,"config")??fe(o);if(!a)throw new S("reconcile needs --config <file>");let c=cd(P(n,"mode")??"dry-run"),u=await H2(await Re(Oe(a)),{mode:c,project:P(n,"project")}),l=u.some(d=>d.status==="failed")?1:0;return s?i(Xo("reconcile",l,{mode:c,projects:u})):(console.log(q2(u,c)),l)}case"generate":{let a=P(n,"config")??fe(o),c=ue(a?await Re(Oe(a)):{});if(n.check===!0){let d=EB(o,c);return xp(o,d),d.ok?0:1}let u=qm(o,c);if(u.foreign.length)throw new S(u.foreign.join("; "));let l=n["dry-run"]===!0;if(!l)for(let d of u.files)d.status!=="unchanged"&&(d.status==="removed"?LOe(d.path):V9(d.path,d.content));return console.log(TB(o,u,l)),0}case"estate":{let a=P(n,"config")??fe(o),c=P(n,"link-hours"),u=c===void 0?void 0:Math.round(Number(c)*3600);if(c!==void 0&&!(Number(c)>0))throw new S(`--link-hours is ${JSON.stringify(c)}; use a number of hours, up to 168`);let l=P(n,"bucket"),d=await eV(o,a?await Re(Oe(a)):{},{...P(n,"out")?{out:P(n,"out")}:{},...u!==void 0?{linkSeconds:u}:{},...l?{reports:{bucket:l,...P(n,"bucket-endpoint")?{endpoint:P(n,"bucket-endpoint")}:{},...P(n,"bucket-prefix")?{prefix:P(n,"bucket-prefix")}:{}}}:{}});return console.log(tV(d,o)),d.unreadable.length?1:0}case"audit":{let a=P(n,"config")??fe(o),c=P(n,"link-hours"),u=c===void 0?void 0:Math.round(Number(c)*3600);if(c!==void 0&&!(Number(c)>0))throw new S(`--link-hours is ${JSON.stringify(c)}; use a number of hours, up to 168`);let l=P(n,"bucket"),d=n.check===!0,p=await oV(o,a?await Re(Oe(a)):{},{...d?{check:d}:{},...P(n,"out")?{out:P(n,"out")}:{},...u!==void 0?{linkSeconds:u}:{},...l?{reports:{bucket:l,...P(n,"bucket-endpoint")?{endpoint:P(n,"bucket-endpoint")}:{},...P(n,"bucket-prefix")?{prefix:P(n,"bucket-prefix")}:{}}}:{}});return console.log(sV(p,o,d)),p.unreadable.length||d&&p.added.length?1:0}case"plan":{let a=await bK(o,{root:P(n,"root"),project:P(n,"project"),config:P(n,"config")},s?()=>{}:console.log),c=a.every(u=>u.ok)?0:1;return s?i(Xo("plan",c,{roots:a})):c}case"stage":{if(r[0]==="tf-apply"){if(s)throw new S("--json is not available on stage tf-apply");let l=P(n,"layers");if(!l||!P(n,"wave"))throw new S("stage tf-apply needs --wave <n> and --layers <a,b;c>");return await LH(o,{wave:Number(P(n,"wave")),layers:Ic(l),canary:(P(n,"canary")??"").split(",").filter(Boolean),binary:P(n,"binary")??"tofu",gate:P(n,"gate")??"on-destructive",...P(n,"approval")?{approval:Q9(P(n,"approval"))}:{},...P(n,"config")?{config:P(n,"config")}:{},...P(n,"parallelism")?{parallelism:tY(P(n,"parallelism"))}:{},...n.terragrunt===!0?{terragrunt:!0}:{},...n.rest===!0?{rest:!0}:{},...P(n,"base")?{base:P(n,"base")}:{},...P(n,"shares")?{shares:BI("shares",P(n,"shares"))}:{},...P(n,"share")?{share:BI("share",P(n,"share"))}:{},...P(n,"decided")?{decided:P(n,"decided")}:{},...P(n,"branches")?{branches:jH(P(n,"branches"))}:{},...P(n,"branch")?{branch:P(n,"branch")}:{},...P(n,"groups")?{groups:IH(P(n,"groups"))}:{},...P(n,"group")?{group:P(n,"group")}:{},...P(n,"on-held")?{onHeld:zOe(P(n,"on-held"))}:{},...n["stand-down"]===!0?{standDown:!0}:{}})}let a=await ZB(r[0]??"",o,{root:P(n,"root"),project:P(n,"project"),config:P(n,"config"),out:P(n,"out"),reportUrl:P(n,"report-url"),...P(n,"layers")?{layers:Ic(P(n,"layers"))}:{},...P(n,"binary")?{binary:P(n,"binary")}:{},...P(n,"forge")?{forge:P(n,"forge")}:{},...P(n,"canary")?{canary:P(n,"canary").split(",").filter(Boolean)}:{},...n.terragrunt===!0?{terragrunt:!0}:{},...n["no-cost"]===!0?{noCost:!0}:{},...P(n,"base")?{base:P(n,"base")}:{},...P(n,"parallelism")?{parallelism:tY(P(n,"parallelism"))}:{},...P(n,"bucket")?{reports:{bucket:P(n,"bucket"),...P(n,"bucket-endpoint")?{endpoint:P(n,"bucket-endpoint")}:{},...P(n,"bucket-prefix")?{prefix:P(n,"bucket-prefix")}:{},...P(n,"bucket-url")?{url:P(n,"bucket-url")}:{}}}:{}},s?()=>{}:console.error),c=a.failed?1:0;r[0]==="tf-plan"&&(await Zv(o,{binary:P(n,"binary")??a.report.run.binary??"tofu",planOnly:!0,log:s?console.error:console.log})).code!==0&&(c=1);let u={html:`${a.dir}/report.html`,json:`${a.dir}/report.json`,note:`${a.dir}/note.md`};return s?i(Xo("stage",c,{stage:r[0],change_set:a.report.change_set,files:u,uploaded:a.uploaded??null,...a.issue?{issue:a.issue}:{}})):(console.log(Ub(a.report)),console.log(`report: ${eS(o,u.html)||u.html}`),a.uploaded&&console.log(`copied to the bucket under ${a.uploaded.prefix}; index rewritten at ${a.uploaded.indexes.join(" and ")}`),a.report.run.report_url&&console.log(`served at ${a.report.run.report_url}`),a.report.run.trace_id&&console.log(`trace: ${a.report.run.trace_url??a.report.run.trace_id}`),c)}case"check-root":{let a=r[0];if(!a)throw new S("usage: terragucci check-root <dir> [--binary <b>] [--config <file>] [--base <ref>]");let c=P(n,"config")??fe(o),u=ue(c?await Re(Oe(c)):{}),l=await Zd(o,u.modules,P(n,"base")??ah(process.env),c?{config:Oe(c)}:{}),d=await lq(P(n,"binary")??"tofu",a,o,l?{pins:l}:{});return xp(o,d),d.ok?0:1}case"check-sources":{let a=n.units===!0,c=r[0];if(a===!!c)throw new S("usage: terragucci check-sources <dir>|--units [--config <file>] [--base <ref>]");let u=P(n,"config")??fe(o),l=ue(u?await Re(Oe(u)):{}),d=await kc(o,l.sources,P(n,"base")??ah(process.env),u?{config:Oe(u)}:{},{afterInit:!1});if(!d)return 0;let p=a?Dn(o,l.terragrunt?.exclude).map(m=>m.path):[c],f=[];for(let m of p)f.push(dq(m,await d.before(m)));let g={ok:f.every(m=>m.ok),log:f.flatMap(m=>m.log),report:f.flatMap(m=>m.report)};return xp(o,g),g.ok?0:1}case"check-job":{let a=P(n,"forge"),c=P(n,"environment"),u=n.branches===void 0?void 0:P(n,"branches");if(!a||!Wr.includes(a)||!c||u===void 0)throw new S("usage: terragucci check-job --forge github|gitlab|forgejo --environment <name> --branches <b>[,<b>...] [--forge-environment <name>] [--runner-tags <t>[,<t>...]] [--self-hosted]");let l=f=>(f??"").split(",").map(g=>g.trim()).filter(g=>g!==""),d={forge:a,environment:c,branches:l(u),...P(n,"forge-environment")?{forgeEnvironment:P(n,"forge-environment")}:{},...P(n,"runner-tags")?{runnerTags:l(P(n,"runner-tags"))}:{},...n["self-hosted"]===!0?{selfHosted:!0}:{}},p=mM(d,fM(d,await gM(d)));for(let f of p.out)console.log(f);for(let f of p.err)console.error(f);return p.ok?0:1}case"check-pins":{let a=P(n,"config")??fe(o),c=ue(a?await Re(Oe(a)):{}),u=await Zd(o,c.modules,P(n,"base")??ah(process.env),a?{config:Oe(a)}:{});if(!u)return 0;let l=await uq(Dn(o,c.terragrunt?.exclude).map(d=>d.path),u);return xp(o,l),l.ok?0:1}case"check-policy":{let a=await fq(o,{...P(n,"config")?{config:P(n,"config")}:{},...P(n,"base")?{base:P(n,"base")}:{}});return xp(o,a),a.ok?0:1}case"atmos":{if(r[0]!=="write")throw new S("usage: terragucci atmos write");for(let a of await tD(o))console.log(a);return 0}case"terramate":{if(r[0]!=="generate")throw new S("usage: terragucci terramate generate");for(let a of await tL(o))console.log(a);return 0}case"oidc-trade":{let a=P(n,"out");if(!a)throw new S("usage: terragucci oidc-trade --out <file> [--reports-role <arn>]");for(let c of await uV(a,process.env,{reportsRole:P(n,"reports-role")}))console.error(`terragucci: ${c}`);return 0}case"auth-provider":return console.log(JSON.stringify(HN(o,process.env))),0;case"binary":{let a=r[0];if(!a)throw new S("usage: terragucci binary <root> [--binary <b>] [--config <file>]");let c=P(n,"config")??fe(o),u=ue(c?await Re(Oe(c)):{}),d=await new jr(o,P(n,"binary")??u.binary??"tofu",u.version).resolve(a);return console.error(`${a}: ${Vr(d)}`),console.log(d.path),0}case"install":{let[a,c]=r;if(!a||!c||!["tofu","terraform","terragrunt","choudoufu","infracost","cosign","atmos","terramate"].includes(a))throw new S("usage: terragucci install tofu|terraform|terragrunt|choudoufu|infracost|cosign|atmos|terramate <version>");return Bd(),console.log(await Wd(a,c)),0}case"publish":{let a=P(n,"config")??fe(o),c=ue(a?await Re(Oe(a)):{}),u=await SK(o,c,{dryRun:n["dry-run"]===!0});return console.log(RK(u)),u.some(l=>l.status==="refused")?1:0}case"verify-release":{let[a,c]=r;if(!a||!c)throw new S("usage: terragucci verify-release <module> <version> [--config <file>]");let u=P(n,"config")??fe(o),l=await xK(o,ue(u?await Re(Oe(u)):{}),a,c);return console.log(EK(l)),l.every(d=>d.verified)?0:1}case"respond":{let a=r[0]??"",c=P(n,"config")??fe(o);if(a==="rollout"&&(r.length>1||n.provider!==void 0)){let p=c?await Re(Oe(c)):{};if(Kn(p.projects?ue(p.defaults??{}):ue(p),"rollout")!=="off")return nY(["rollout",...e.slice(e.indexOf("rollout")+1)])}let u=p=>P(n,p),l=u("log"),d=await l4(a,o,{...Object.fromEntries(["config","project","base","out","report","approved","current","root","binary","branch","module","version","title","description","since","attributions"].map(p=>[p,u(p)])),mode:u("mode")??"dry-run",...u("wave")?{wave:Number(u("wave"))}:{},...l?{log:X_(l==="-"?0:Oe(o,l),"utf-8")}:{},...u("platform")?{platforms:u("platform").split(",")}:{},imports:e.flatMap((p,f)=>p==="--import"?[e[f+1]??""]:p.startsWith("--import=")?[p.slice(9)]:[]).map(xz)});return s?i(Xo("respond",d.exit??0,d)):(console.log(d.text),d.exit??0)}case"comment":{let a=P(n,"layers"),c=P(n,"out"),u=P(n,"forge")??"github",l=P(n,"agent")??"off",d=P(n,"policy-dir")??"policy";if(!["off","on","run","push"].includes(l))throw new S("comment's --agent is off, on, run or push");if(u==="gitlab"&&l==="run"&&n.poll!==!0){let g=P(n,"out"),m=P(n,"prompt");if(!g||!m)throw new S("comment --forge gitlab --agent run needs --out <file> and --prompt <file>");let h=await FO($o(process.env,process.env.TG_TOKEN,fetch),process.env);return Rm(Oe(o,g),h),h.go&&h.ask&&pA(Oe(o,m),{ask:h.ask,pr:h.pr,head:h.head,user:h.user,policyDir:d,what:`merge request !${h.pr}`}),h.fail?(console.error(`terragucci comment: failed, no agent: ${h.reason}`),1):(console.log(`terragucci comment: ${h.go?"":"no agent: "}${h.reason}`),0)}if(u==="gitlab"&&l==="push"&&n.poll!==!0){let g=P(n,"change");if(!g)throw new S("comment --agent push needs --change <dir>");let m=await UW({change:Oe(o,g),policyDir:d});return console.log(`terragucci comment: ${m.pushed?"":"nothing pushed: "}${m.reason}`),m.fail?1:0}if(n.poll===!0||u==="gitlab"){if(u!=="gitlab"||n.poll!==!0)throw new S("comment --poll is GitLab's: run it as comment --forge gitlab --poll --layers <a,b;c>");if(!a)throw new S("comment --forge gitlab --poll needs --layers <a,b;c>");let g=P(n,"when")??"merge";if(g!=="merge"&&g!=="pull-request")throw new S("comment's --when is merge or pull-request");let m=eY(P(n,"requires"),"comment");if(l!=="off"&&l!=="on")throw new S("comment --forge gitlab --poll's --agent is off or on");let h=await a2({layers:Ic(a),when:g,...m?{requires:m}:{},...n["plan-notes"]===!0?{planNotes:!0}:{},...l==="on"?{agent:!0}:{},...n.review===!0?{review:!0}:{}});for(let y of h.plans??[])console.log(`terragucci comment: !${y.mr} plan: ${y.reason}`);for(let y of h.reviews??[])console.log(`terragucci comment: !${y.mr} review: ${y.reason}`);for(let y of h.outcomes)console.log(`terragucci comment: !${y.mr} note ${y.note}: ${y.ran?"":"nothing run: "}${y.reason}`);return h.outcomes.length===0&&!h.fail&&console.log("terragucci comment: no new /terragucci notes"),h.fail&&console.error(`terragucci comment: failed: ${h.fail}`),h.fail||h.outcomes.some(y=>y.fail)||(h.plans??[]).some(y=>y.fail)||(h.reviews??[]).some(y=>y.fail)?1:0}if(l==="push"){let g=P(n,"change");if(!g)throw new S("comment --agent push needs --change <dir>");let m=await Fz({change:Oe(o,g),policyDir:d});return console.log(`terragucci comment: ${m.pushed?"":"nothing pushed: "}${m.reason}`),m.fail?1:0}let p=P(n,"prompt");if(l==="run"?!c||!p:!a||!c)throw new S(l==="run"?"comment --agent run needs --out <file> and --prompt <file>":"comment needs --layers <a,b;c> and --out <file>");if(u!=="github"&&u!=="forgejo")throw new S("comment's --forge is github or forgejo");let f=await jz({layers:a?Ic(a):[],forge:u,agent:l});return Rm(Oe(o,c),f),f.go&&f.ask&&p&&pA(Oe(o,p),{ask:f.ask,pr:f.pr,head:f.head,user:f.user,policyDir:d}),f.fail?(console.error(`terragucci comment: failed, no re-plan: ${f.reason}`),1):(console.log(`terragucci comment: ${f.go?"":l==="run"?"no agent: ":"no re-plan: "}${f.reason}`),0)}case"comment-apply":{let a=P(n,"layers"),c=P(n,"out"),u=P(n,"forge")??"github",l=P(n,"canary"),d=P(n,"when")??"merge";if(!a||!c)throw new S("comment-apply needs --layers <a,b;c> and --out <file>");if(u!=="github"&&u!=="forgejo"&&u!=="gitlab")throw new S("comment-apply's --forge is github, forgejo or gitlab");if(d!=="merge"&&d!=="pull-request")throw new S("comment-apply's --when is merge or pull-request");let p=eY(P(n,"requires"),"comment-apply"),{after:f,synth:g}=n.terragrunt===!0?{}:await X9(o);if(u==="gitlab"){if(d!=="pull-request")throw new S("comment-apply --forge gitlab is apply before merge's: pass --when pull-request");let h=await ZH({layers:Ic(a),...l?{canary:l.split(",")}:{},...f?{after:f}:{},...p?{requires:p}:{},...g?{synth:g}:{},...n.terragrunt===!0?{terragrunt:!0}:{}});return Rm(Oe(o,c),h),h.fail?(console.error(`terragucci comment-apply: failed, nothing applied: ${h.reason}`),1):(console.log(`terragucci comment-apply: ${h.go?"":"nothing applied: "}${h.reason}`),0)}let m=await uW({layers:Ic(a),forge:u,when:d,...l?{canary:l.split(",")}:{},...f?{after:f}:{},...p?{requires:p}:{},...g?{synth:g}:{},...n.terragrunt===!0?{terragrunt:!0}:{},...n.again===!0?{again:!0}:{}});return Rm(Oe(o,c),m),m.fail?(console.error(`terragucci comment-apply: failed, nothing applied: ${m.reason}`),1):(console.log(`terragucci comment-apply: ${m.go?"":"nothing applied: "}${m.reason}`),0)}case"pr-lock":{let a=P(n,"layers"),c=P(n,"forge")??"github",u=P(n,"when")??"merge";if(!a)throw new S("pr-lock needs --layers <a,b;c>");if(c!=="github"&&c!=="forgejo")throw new S("pr-lock's --forge is github or forgejo");if(u!=="merge"&&u!=="pull-request")throw new S("pr-lock's --when is merge or pull-request");let{synth:l}=n.terragrunt===!0?{}:await X9(o),d=await pW({layers:Ic(a),forge:c,when:u,...l?{synth:l}:{},...n.terragrunt===!0?{terragrunt:!0}:{}});return d.fail?(console.error(`terragucci pr-lock: failed: ${d.reason}`),1):(console.log(`terragucci pr-lock: ${d.reason}`),0)}case"pr-merge":{let a=Number(P(n,"pr")),c=P(n,"sha"),u=P(n,"forge")??"github";if(!Number.isInteger(a)||a<1||!c)throw new S("pr-merge needs --pr <n> and --sha <sha>");if(u!=="github"&&u!=="forgejo"&&u!=="gitlab")throw new S("pr-merge's --forge is github, forgejo or gitlab");try{return console.log(`terragucci pr-merge: ${u==="gitlab"?await YH({pr:a,sha:c}):await fW({pr:a,sha:c,forge:u})}`),0}catch(l){if(l instanceof S)throw l;return console.error(`terragucci pr-merge: not merged: ${l.message}`),1}}case"approve":{let a=n.sign===!0?!0:P(n,"sign"),c=n.plan===!0?"":P(n,"plan");if(IJ(r)){if(r.length>2)throw new S(`approve ${r[0]} takes one ${r[0]==="ephemeral"?"pull request":"root"}, not ${r.slice(1).join(" ")}`);return(await CJ(o,{kind:r[0],target:r[1],...c!==void 0?{plan:c}:{},...a!==void 0?{sign:a}:{},...P(n,"actor")?{actor:P(n,"actor")}:{},dryRun:n["dry-run"]===!0})).code}return(await AJ(o,{...n["no-resume"]===!0?{resume:!1}:{},...r[0]?{wave:r[0]}:{},...c!==void 0?{plan:c}:{},...a!==void 0?{sign:a}:{},...P(n,"actor")?{actor:P(n,"actor")}:{},dryRun:n["dry-run"]===!0})).code}case"override":{let a=n.sign===!0?!0:P(n,"sign"),c=e.flatMap((l,d)=>l==="--rule"?[e[d+1]??""]:l.startsWith("--rule=")?[l.slice(7)]:[]).flatMap(l=>l.split(","));if(c.some(l=>l===""||l.startsWith("--")))throw new S("--rule needs a rule id, such as main.deny_public_bucket");return(await PJ(o,{root:r[0]??"",rules:c,reason:P(n,"reason")??"",...a!==void 0?{sign:a}:{},...P(n,"actor")?{actor:P(n,"actor")}:{},dryRun:n["dry-run"]===!0})).code}case"state":{if(r[0]!=="export"||!r[1])throw new S("state takes: state export <root> [--version <id>] [--out <file>] [--actor <name>]");return(await x2(o,{root:r[1],...P(n,"version")?{version:P(n,"version")}:{},...P(n,"out")?{out:P(n,"out")}:{},...P(n,"actor")?{actor:P(n,"actor")}:{},...P(n,"binary")?{binary:P(n,"binary")}:{},...P(n,"config")?{config:P(n,"config")}:{}})).code}case"migrate":{if(r[0]!=="revert"||!r[1])throw new S("migrate takes: migrate revert <migration>");let a=PU(o,r[1]);return console.log(`wrote ${a}: it puts back each state ${r[1]} wrote, to the version it recorded before`),console.log(`revert the code of ${r[1]} in the same change; the plan job proves the revert and wave 1 waits for its approval`),0}case"unlock-state":{if(!r[0]||r.length>1)throw new S("unlock-state takes one root: unlock-state <root>");return(await TJ(o,r[0],{...P(n,"binary")?{binary:P(n,"binary")}:{},...P(n,"config")?{config:P(n,"config")}:{},...P(n,"actor")?{actor:P(n,"actor")}:{},log:c=>console.log(`terragucci unlock-state: ${c}`)})).code}case"ephemeral":{let a=r[0],c={...P(n,"binary")?{binary:P(n,"binary")}:{},...P(n,"config")?{config:P(n,"config")}:{},...P(n,"base")?{base:P(n,"base")}:{},...P(n,"actor")?{actor:P(n,"actor")}:{},log:d=>console.log(`terragucci ephemeral: ${d}`)};if(a==="sweep")return await wB(o,c);if(a!=="up"&&a!=="down")throw new S("ephemeral takes up, down or sweep");let u=BI("pr",P(n,"pr")??"");if(a==="up")return await mB(o,{...c,pr:u,...P(n,"head")?{head:P(n,"head")}:{}});let l=P(n,"reason");if(l!=="closed"&&l!=="expired")throw new S("ephemeral down takes --reason closed or --reason expired");return await hB(o,{...c,pr:u,reason:l})}case"resume":{let a=P(n,"forge")??(process.env.GITLAB_CI==="true"?"gitlab":process.env.GITEA_ACTIONS==="true"||process.env.FORGEJO_ACTIONS==="true"?"forgejo":"github");if(a!=="github"&&a!=="forgejo"&&a!=="gitlab")throw new S("resume's --forge is github, forgejo or gitlab");let c=P(n,"out"),u=process.env.TG_SHA||MOe("git",["rev-parse","HEAD"],{cwd:o,encoding:"utf-8"}).stdout.trim();if(a==="gitlab")return await w2(o,process.env);let l=ft(o),d;try{d=await pH(o,l)}catch(f){console.log(`terragucci resume: the decision point questions could not be read, so no wave waiting on one is resumed: ${f.message}`)}let p=await y2({ledger:l,migrations:ft(o,ga),forge:a,sha:u,env:process.env,...d?{questions:d}:{}});if(c&&V9(Oe(o,c),p.kind==="apply"?`TG_SHA=${p.sha}
1301
+ Docs: https://intentius.io/terragucci/`;async function X9(e){try{let t=fe(e);if(!t)return{};let n=et(e,ue(await Re(t)));return{...n.after?{after:n.after}:{},...n.kind==="synth"?{synth:!0}:{}}}catch{return{}}}function Q9(e){if(e!==void 0&&!Do.includes(e))throw new S(`--approval must be one of ${Do.join(", ")}`);return e}function eY(e,t){let n=e===void 0?void 0:e==="none"?[]:e.split(",");if(n?.some(r=>!Cn.includes(r)))throw new S(`${t}'s --requires is a comma-separated list of ${Cn.join(", ")}, or none`);return n}function tY(e){let t=Number(e);if(!Number.isInteger(t)||t<1)throw new S(`--parallelism is ${JSON.stringify(e)}; use a whole number of 1 or more`);return t}function BI(e,t){let n=Number(t);if(!Number.isInteger(n)||n<1)throw new S(`--${e} is ${JSON.stringify(t)}; use a whole number of 1 or more`);return n}function FOe(e){let[t="",...n]=e,r={},o=[];for(let s=0;s<n.length;s++){let i=n[s];if(i.startsWith("--")){let[a,c]=i.slice(2).split("=",2);c!==void 0?r[a]=c:n[s+1]!==void 0&&!n[s+1].startsWith("--")&&!["force","dry-run","json","terragrunt","rest","again","poll","plan-notes","no-cost","check","approval-status","review","stand-down","publish","no-open","units","self-hosted","no-roots","require-clouds"].includes(a)?r[a]=n[++s]:r[a]=!0}else o.push(i)}return{cmd:t,flags:r,args:o}}function zOe(e){if(e!=="wait"&&e!=="refuse")throw new S("--on-held must be wait or refuse");return e}function P(e,t){let n=e[t];if(n===!0)throw new S(`--${t} needs a value`);return n}function UOe(e,t){let n=new Set(["aws"]),r=e.projects?Object.entries(e.projects).map(([o,s])=>({host:po(o).host,s:{...e.defaults,...s}})):[{host:void 0,s:e}];for(let{host:o,s}of r){let i=s.forge??(o?ir(o):t?Kr(t)?.value:void 0);i&&n.add(i)}return[...n]}async function nY(e){let{cmd:t,flags:n,args:r}=FOe(e),o=process.cwd(),s=n.json===!0,i=a=>(console.log(JSON.stringify(a,null,2)),a.exit);try{if(s&&!N2.includes(t))throw new S(`--json is not available on ${t||"help"}`);switch(t){case"init":{let a=P(n,"forge"),c=P(n,"binary");if(a&&!Wr.includes(a))throw new S(`--forge must be one of ${Wr.join(", ")}`);if(c&&!od.includes(c))throw new S(`--binary must be one of ${od.join(", ")}`);let u=Q9(P(n,"approval")),l=P(n,"signer"),d=await ik(o,{forge:a,binary:c,...u?{approval:u}:{},...l?{signer:l}:{},force:n.force===!0,dryRun:n["dry-run"]===!0});return s?i(Xo("init",0,BJ(o,d,n["dry-run"]===!0))):(console.log(UJ(o,d,n["dry-run"]===!0)),n["dry-run"]===!0&&console.log("dry run: nothing was written"),0)}case"import":{let[a,c,u]=r;if(gK.includes(a)){if(c!==void 0)throw new S(`import ${a} reads the platform's API, not a file: give --hostname${a==="scalr"?"":" and --organization"}`);let f=P(n,"forge");if(f&&!Wr.includes(f))throw new S(`--forge must be one of ${Wr.join(", ")}`);let g=P(n,"hostname")??(a==="hcp"?"app.terraform.io":void 0);if(!g||!/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:\d+)?$/i.test(g))throw new S(`import ${a} needs --hostname, the host name of ${a==="scalr"?"the Scalr account, such as acme.scalr.io":"the OTF server"}`);let m=P(n,"organization");if(a!=="scalr"&&!m)throw new S(`import ${a} needs --organization, the organization whose workspaces it reads`);if(a==="scalr"&&m)throw new S("import scalr reads the account the host names; --environment narrows it to one environment");let h=P(n,"environment");if(a!=="scalr"&&h)throw new S(`--environment is Scalr's; import ${a} reads one --organization`);let y=P(n,"repo");if(y!==void 0&&!/^[^/\s]+(\/[^/\s]+)+$/.test(y))throw new S("--repo must be the repo as the VCS connection names it, owner/name");let w=await mK(o,()=>a==="scalr"?lK(g,h,process.env):cK(a,g,m,process.env),{...y?{repo:y}:{},...f?{forge:f}:{},force:n.force===!0,dryRun:n["dry-run"]===!0});return console.log(hK(w)),0}if(!p0.includes(a))throw new S(`import reads ${p0.join(", ")}: \`terragucci import atlantis [atlantis.yaml]\`, \`terragucci import digger [digger.yml]\`, \`terragucci import terragrunt-scale [.gruntwork]\`, \`terragucci import spacelift [.spacelift/config.yml]\` or \`terragucci import env0 [env0-discovery.yml]\``);if(u!==void 0)throw new S("import reads one file");let l=P(n,"forge");if(l&&!Wr.includes(l))throw new S(`--forge must be one of ${Wr.join(", ")}`);let d=P(n,"apply-when");if(d&&!id.includes(d))throw new S(`--apply-when must be one of ${id.join(", ")}`);let p=iK(o,a,{...c?{file:c}:{},...l?{forge:l}:{},...d?{applyWhen:d}:{},force:n.force===!0,dryRun:n["dry-run"]===!0});return console.log(aK(p)),0}case"reconcile":{let a=P(n,"config")??fe(o);if(!a)throw new S("reconcile needs --config <file>");let c=cd(P(n,"mode")??"dry-run"),u=await HJ(await Re(Oe(a)),{mode:c,project:P(n,"project")}),l=u.some(d=>d.status==="failed")?1:0;return s?i(Xo("reconcile",l,{mode:c,projects:u})):(console.log(qJ(u,c)),l)}case"generate":{let a=P(n,"config")??fe(o),c=ue(a?await Re(Oe(a)):{});if(n.check===!0){let d=EB(o,c);return xp(o,d),d.ok?0:1}let u=qm(o,c);if(u.foreign.length)throw new S(u.foreign.join("; "));let l=n["dry-run"]===!0;if(!l)for(let d of u.files)d.status!=="unchanged"&&(d.status==="removed"?LOe(d.path):V9(d.path,d.content));return console.log(TB(o,u,l)),0}case"estate":{let a=P(n,"config")??fe(o),c=P(n,"link-hours"),u=c===void 0?void 0:Math.round(Number(c)*3600);if(c!==void 0&&!(Number(c)>0))throw new S(`--link-hours is ${JSON.stringify(c)}; use a number of hours, up to 168`);let l=P(n,"bucket"),d=await eV(o,a?await Re(Oe(a)):{},{...P(n,"out")?{out:P(n,"out")}:{},...u!==void 0?{linkSeconds:u}:{},...l?{reports:{bucket:l,...P(n,"bucket-endpoint")?{endpoint:P(n,"bucket-endpoint")}:{},...P(n,"bucket-prefix")?{prefix:P(n,"bucket-prefix")}:{}}}:{}});return console.log(tV(d,o)),d.unreadable.length?1:0}case"audit":{let a=P(n,"config")??fe(o),c=P(n,"link-hours"),u=c===void 0?void 0:Math.round(Number(c)*3600);if(c!==void 0&&!(Number(c)>0))throw new S(`--link-hours is ${JSON.stringify(c)}; use a number of hours, up to 168`);let l=P(n,"bucket"),d=n.check===!0,p=await oV(o,a?await Re(Oe(a)):{},{...d?{check:d}:{},...P(n,"out")?{out:P(n,"out")}:{},...u!==void 0?{linkSeconds:u}:{},...l?{reports:{bucket:l,...P(n,"bucket-endpoint")?{endpoint:P(n,"bucket-endpoint")}:{},...P(n,"bucket-prefix")?{prefix:P(n,"bucket-prefix")}:{}}}:{}});return console.log(sV(p,o,d)),p.unreadable.length||d&&p.added.length?1:0}case"plan":{let a=await bK(o,{root:P(n,"root"),project:P(n,"project"),config:P(n,"config")},s?()=>{}:console.log),c=a.every(u=>u.ok)?0:1;return s?i(Xo("plan",c,{roots:a})):c}case"stage":{if(r[0]==="tf-apply"){if(s)throw new S("--json is not available on stage tf-apply");let l=P(n,"layers");if(!l||!P(n,"wave"))throw new S("stage tf-apply needs --wave <n> and --layers <a,b;c>");return await LH(o,{wave:Number(P(n,"wave")),layers:Ic(l),canary:(P(n,"canary")??"").split(",").filter(Boolean),binary:P(n,"binary")??"tofu",gate:P(n,"gate")??"on-destructive",...P(n,"approval")?{approval:Q9(P(n,"approval"))}:{},...P(n,"config")?{config:P(n,"config")}:{},...P(n,"parallelism")?{parallelism:tY(P(n,"parallelism"))}:{},...n.terragrunt===!0?{terragrunt:!0}:{},...n.rest===!0?{rest:!0}:{},...P(n,"base")?{base:P(n,"base")}:{},...P(n,"shares")?{shares:BI("shares",P(n,"shares"))}:{},...P(n,"share")?{share:BI("share",P(n,"share"))}:{},...P(n,"decided")?{decided:P(n,"decided")}:{},...P(n,"branches")?{branches:jH(P(n,"branches"))}:{},...P(n,"branch")?{branch:P(n,"branch")}:{},...P(n,"groups")?{groups:IH(P(n,"groups"))}:{},...P(n,"group")?{group:P(n,"group")}:{},...P(n,"on-held")?{onHeld:zOe(P(n,"on-held"))}:{},...n["stand-down"]===!0?{standDown:!0}:{}})}let a=await ZB(r[0]??"",o,{root:P(n,"root"),project:P(n,"project"),config:P(n,"config"),out:P(n,"out"),reportUrl:P(n,"report-url"),...P(n,"layers")?{layers:Ic(P(n,"layers"))}:{},...P(n,"binary")?{binary:P(n,"binary")}:{},...P(n,"forge")?{forge:P(n,"forge")}:{},...P(n,"canary")?{canary:P(n,"canary").split(",").filter(Boolean)}:{},...n.terragrunt===!0?{terragrunt:!0}:{},...n["no-cost"]===!0?{noCost:!0}:{},...P(n,"base")?{base:P(n,"base")}:{},...P(n,"parallelism")?{parallelism:tY(P(n,"parallelism"))}:{},...P(n,"bucket")?{reports:{bucket:P(n,"bucket"),...P(n,"bucket-endpoint")?{endpoint:P(n,"bucket-endpoint")}:{},...P(n,"bucket-prefix")?{prefix:P(n,"bucket-prefix")}:{},...P(n,"bucket-url")?{url:P(n,"bucket-url")}:{}}}:{}},s?()=>{}:console.error),c=a.failed?1:0;r[0]==="tf-plan"&&(await Zv(o,{binary:P(n,"binary")??a.report.run.binary??"tofu",planOnly:!0,log:s?console.error:console.log})).code!==0&&(c=1);let u={html:`${a.dir}/report.html`,json:`${a.dir}/report.json`,note:`${a.dir}/note.md`};return s?i(Xo("stage",c,{stage:r[0],change_set:a.report.change_set,files:u,uploaded:a.uploaded??null,...a.issue?{issue:a.issue}:{}})):(console.log(Ub(a.report)),console.log(`report: ${eS(o,u.html)||u.html}`),a.uploaded&&console.log(`copied to the bucket under ${a.uploaded.prefix}; index rewritten at ${a.uploaded.indexes.join(" and ")}`),a.report.run.report_url&&console.log(`served at ${a.report.run.report_url}`),a.report.run.trace_id&&console.log(`trace: ${a.report.run.trace_url??a.report.run.trace_id}`),c)}case"check-root":{let a=r[0];if(!a)throw new S("usage: terragucci check-root <dir> [--binary <b>] [--config <file>] [--base <ref>]");let c=P(n,"config")??fe(o),u=ue(c?await Re(Oe(c)):{}),l=await Zd(o,u.modules,P(n,"base")??ah(process.env),c?{config:Oe(c)}:{}),d=await lq(P(n,"binary")??"tofu",a,o,l?{pins:l}:{});return xp(o,d),d.ok?0:1}case"check-sources":{let a=n.units===!0,c=r[0];if(a===!!c)throw new S("usage: terragucci check-sources <dir>|--units [--config <file>] [--base <ref>]");let u=P(n,"config")??fe(o),l=ue(u?await Re(Oe(u)):{}),d=await kc(o,l.sources,P(n,"base")??ah(process.env),u?{config:Oe(u)}:{},{afterInit:!1});if(!d)return 0;let p=a?Dn(o,l.terragrunt?.exclude).map(m=>m.path):[c],f=[];for(let m of p)f.push(dq(m,await d.before(m)));let g={ok:f.every(m=>m.ok),log:f.flatMap(m=>m.log),report:f.flatMap(m=>m.report)};return xp(o,g),g.ok?0:1}case"check-job":{let a=P(n,"forge"),c=P(n,"environment"),u=n.branches===void 0?void 0:P(n,"branches");if(!a||!Wr.includes(a)||!c||u===void 0)throw new S("usage: terragucci check-job --forge github|gitlab|forgejo --environment <name> --branches <b>[,<b>...] [--forge-environment <name>] [--runner-tags <t>[,<t>...]] [--self-hosted]");let l=f=>(f??"").split(",").map(g=>g.trim()).filter(g=>g!==""),d={forge:a,environment:c,branches:l(u),...P(n,"forge-environment")?{forgeEnvironment:P(n,"forge-environment")}:{},...P(n,"runner-tags")?{runnerTags:l(P(n,"runner-tags"))}:{},...n["self-hosted"]===!0?{selfHosted:!0}:{}},p=mM(d,fM(d,await gM(d)));for(let f of p.out)console.log(f);for(let f of p.err)console.error(f);return p.ok?0:1}case"check-pins":{let a=P(n,"config")??fe(o),c=ue(a?await Re(Oe(a)):{}),u=await Zd(o,c.modules,P(n,"base")??ah(process.env),a?{config:Oe(a)}:{});if(!u)return 0;let l=await uq(Dn(o,c.terragrunt?.exclude).map(d=>d.path),u);return xp(o,l),l.ok?0:1}case"check-policy":{let a=await fq(o,{...P(n,"config")?{config:P(n,"config")}:{},...P(n,"base")?{base:P(n,"base")}:{}});return xp(o,a),a.ok?0:1}case"atmos":{if(r[0]!=="write")throw new S("usage: terragucci atmos write");for(let a of await tD(o))console.log(a);return 0}case"terramate":{if(r[0]!=="generate")throw new S("usage: terragucci terramate generate");for(let a of await tL(o))console.log(a);return 0}case"oidc-trade":{let a=P(n,"out");if(!a)throw new S("usage: terragucci oidc-trade --out <file> [--reports-role <arn>]");for(let c of await uV(a,process.env,{reportsRole:P(n,"reports-role")}))console.error(`terragucci: ${c}`);return 0}case"auth-provider":return console.log(JSON.stringify(HN(o,process.env))),0;case"binary":{let a=r[0];if(!a)throw new S("usage: terragucci binary <root> [--binary <b>] [--config <file>]");let c=P(n,"config")??fe(o),u=ue(c?await Re(Oe(c)):{}),d=await new jr(o,P(n,"binary")??u.binary??"tofu",u.version).resolve(a);return console.error(`${a}: ${Vr(d)}`),console.log(d.path),0}case"install":{let[a,c]=r;if(!a||!c||!["tofu","terraform","terragrunt","choudoufu","infracost","cosign","atmos","terramate"].includes(a))throw new S("usage: terragucci install tofu|terraform|terragrunt|choudoufu|infracost|cosign|atmos|terramate <version>");return Bd(),console.log(await Wd(a,c)),0}case"publish":{let a=P(n,"config")??fe(o),c=ue(a?await Re(Oe(a)):{}),u=await SK(o,c,{dryRun:n["dry-run"]===!0});return console.log(RK(u)),u.some(l=>l.status==="refused")?1:0}case"verify-release":{let[a,c]=r;if(!a||!c)throw new S("usage: terragucci verify-release <module> <version> [--config <file>]");let u=P(n,"config")??fe(o),l=await xK(o,ue(u?await Re(Oe(u)):{}),a,c);return console.log(EK(l)),l.every(d=>d.verified)?0:1}case"respond":{let a=r[0]??"",c=P(n,"config")??fe(o);if(a==="rollout"&&(r.length>1||n.provider!==void 0)){let p=c?await Re(Oe(c)):{};if(Kn(p.projects?ue(p.defaults??{}):ue(p),"rollout")!=="off")return nY(["rollout",...e.slice(e.indexOf("rollout")+1)])}let u=p=>P(n,p),l=u("log"),d=await l4(a,o,{...Object.fromEntries(["config","project","base","out","report","approved","current","root","binary","branch","module","version","title","description","since","attributions"].map(p=>[p,u(p)])),mode:u("mode")??"dry-run",...u("wave")?{wave:Number(u("wave"))}:{},...l?{log:X_(l==="-"?0:Oe(o,l),"utf-8")}:{},...u("platform")?{platforms:u("platform").split(",")}:{},imports:e.flatMap((p,f)=>p==="--import"?[e[f+1]??""]:p.startsWith("--import=")?[p.slice(9)]:[]).map(xz)});return s?i(Xo("respond",d.exit??0,d)):(console.log(d.text),d.exit??0)}case"comment":{let a=P(n,"layers"),c=P(n,"out"),u=P(n,"forge")??"github",l=P(n,"agent")??"off",d=P(n,"policy-dir")??"policy";if(!["off","on","run","push"].includes(l))throw new S("comment's --agent is off, on, run or push");if(u==="gitlab"&&l==="run"&&n.poll!==!0){let g=P(n,"out"),m=P(n,"prompt");if(!g||!m)throw new S("comment --forge gitlab --agent run needs --out <file> and --prompt <file>");let h=await FO($o(process.env,process.env.TG_TOKEN,fetch),process.env);return Rm(Oe(o,g),h),h.go&&h.ask&&pA(Oe(o,m),{ask:h.ask,pr:h.pr,head:h.head,user:h.user,policyDir:d,what:`merge request !${h.pr}`}),h.fail?(console.error(`terragucci comment: failed, no agent: ${h.reason}`),1):(console.log(`terragucci comment: ${h.go?"":"no agent: "}${h.reason}`),0)}if(u==="gitlab"&&l==="push"&&n.poll!==!0){let g=P(n,"change");if(!g)throw new S("comment --agent push needs --change <dir>");let m=await UW({change:Oe(o,g),policyDir:d});return console.log(`terragucci comment: ${m.pushed?"":"nothing pushed: "}${m.reason}`),m.fail?1:0}if(n.poll===!0||u==="gitlab"){if(u!=="gitlab"||n.poll!==!0)throw new S("comment --poll is GitLab's: run it as comment --forge gitlab --poll --layers <a,b;c>");if(!a)throw new S("comment --forge gitlab --poll needs --layers <a,b;c>");let g=P(n,"when")??"merge";if(g!=="merge"&&g!=="pull-request")throw new S("comment's --when is merge or pull-request");let m=eY(P(n,"requires"),"comment");if(l!=="off"&&l!=="on")throw new S("comment --forge gitlab --poll's --agent is off or on");let h=await aJ({layers:Ic(a),when:g,...m?{requires:m}:{},...n["plan-notes"]===!0?{planNotes:!0}:{},...l==="on"?{agent:!0}:{},...n.review===!0?{review:!0}:{}});for(let y of h.plans??[])console.log(`terragucci comment: !${y.mr} plan: ${y.reason}`);for(let y of h.reviews??[])console.log(`terragucci comment: !${y.mr} review: ${y.reason}`);for(let y of h.outcomes)console.log(`terragucci comment: !${y.mr} note ${y.note}: ${y.ran?"":"nothing run: "}${y.reason}`);return h.outcomes.length===0&&!h.fail&&console.log("terragucci comment: no new /terragucci notes"),h.fail&&console.error(`terragucci comment: failed: ${h.fail}`),h.fail||h.outcomes.some(y=>y.fail)||(h.plans??[]).some(y=>y.fail)||(h.reviews??[]).some(y=>y.fail)?1:0}if(l==="push"){let g=P(n,"change");if(!g)throw new S("comment --agent push needs --change <dir>");let m=await Fz({change:Oe(o,g),policyDir:d});return console.log(`terragucci comment: ${m.pushed?"":"nothing pushed: "}${m.reason}`),m.fail?1:0}let p=P(n,"prompt");if(l==="run"?!c||!p:!a||!c)throw new S(l==="run"?"comment --agent run needs --out <file> and --prompt <file>":"comment needs --layers <a,b;c> and --out <file>");if(u!=="github"&&u!=="forgejo")throw new S("comment's --forge is github or forgejo");let f=await jz({layers:a?Ic(a):[],forge:u,agent:l});return Rm(Oe(o,c),f),f.go&&f.ask&&p&&pA(Oe(o,p),{ask:f.ask,pr:f.pr,head:f.head,user:f.user,policyDir:d}),f.fail?(console.error(`terragucci comment: failed, no re-plan: ${f.reason}`),1):(console.log(`terragucci comment: ${f.go?"":l==="run"?"no agent: ":"no re-plan: "}${f.reason}`),0)}case"comment-apply":{let a=P(n,"layers"),c=P(n,"out"),u=P(n,"forge")??"github",l=P(n,"canary"),d=P(n,"when")??"merge";if(!a||!c)throw new S("comment-apply needs --layers <a,b;c> and --out <file>");if(u!=="github"&&u!=="forgejo"&&u!=="gitlab")throw new S("comment-apply's --forge is github, forgejo or gitlab");if(d!=="merge"&&d!=="pull-request")throw new S("comment-apply's --when is merge or pull-request");let p=eY(P(n,"requires"),"comment-apply"),{after:f,synth:g}=n.terragrunt===!0?{}:await X9(o);if(u==="gitlab"){if(d!=="pull-request")throw new S("comment-apply --forge gitlab is apply before merge's: pass --when pull-request");let h=await ZH({layers:Ic(a),...l?{canary:l.split(",")}:{},...f?{after:f}:{},...p?{requires:p}:{},...g?{synth:g}:{},...n.terragrunt===!0?{terragrunt:!0}:{}});return Rm(Oe(o,c),h),h.fail?(console.error(`terragucci comment-apply: failed, nothing applied: ${h.reason}`),1):(console.log(`terragucci comment-apply: ${h.go?"":"nothing applied: "}${h.reason}`),0)}let m=await uW({layers:Ic(a),forge:u,when:d,...l?{canary:l.split(",")}:{},...f?{after:f}:{},...p?{requires:p}:{},...g?{synth:g}:{},...n.terragrunt===!0?{terragrunt:!0}:{},...n.again===!0?{again:!0}:{}});return Rm(Oe(o,c),m),m.fail?(console.error(`terragucci comment-apply: failed, nothing applied: ${m.reason}`),1):(console.log(`terragucci comment-apply: ${m.go?"":"nothing applied: "}${m.reason}`),0)}case"pr-lock":{let a=P(n,"layers"),c=P(n,"forge")??"github",u=P(n,"when")??"merge";if(!a)throw new S("pr-lock needs --layers <a,b;c>");if(c!=="github"&&c!=="forgejo")throw new S("pr-lock's --forge is github or forgejo");if(u!=="merge"&&u!=="pull-request")throw new S("pr-lock's --when is merge or pull-request");let{synth:l}=n.terragrunt===!0?{}:await X9(o),d=await pW({layers:Ic(a),forge:c,when:u,...l?{synth:l}:{},...n.terragrunt===!0?{terragrunt:!0}:{}});return d.fail?(console.error(`terragucci pr-lock: failed: ${d.reason}`),1):(console.log(`terragucci pr-lock: ${d.reason}`),0)}case"pr-merge":{let a=Number(P(n,"pr")),c=P(n,"sha"),u=P(n,"forge")??"github";if(!Number.isInteger(a)||a<1||!c)throw new S("pr-merge needs --pr <n> and --sha <sha>");if(u!=="github"&&u!=="forgejo"&&u!=="gitlab")throw new S("pr-merge's --forge is github, forgejo or gitlab");try{return console.log(`terragucci pr-merge: ${u==="gitlab"?await YH({pr:a,sha:c}):await fW({pr:a,sha:c,forge:u})}`),0}catch(l){if(l instanceof S)throw l;return console.error(`terragucci pr-merge: not merged: ${l.message}`),1}}case"approve":{let a=n.sign===!0?!0:P(n,"sign"),c=n.plan===!0?"":P(n,"plan");if(I2(r)){if(r.length>2)throw new S(`approve ${r[0]} takes one ${r[0]==="ephemeral"?"pull request":"root"}, not ${r.slice(1).join(" ")}`);return(await C2(o,{kind:r[0],target:r[1],...c!==void 0?{plan:c}:{},...a!==void 0?{sign:a}:{},...P(n,"actor")?{actor:P(n,"actor")}:{},dryRun:n["dry-run"]===!0})).code}return(await A2(o,{...n["no-resume"]===!0?{resume:!1}:{},...r[0]?{wave:r[0]}:{},...c!==void 0?{plan:c}:{},...a!==void 0?{sign:a}:{},...P(n,"actor")?{actor:P(n,"actor")}:{},dryRun:n["dry-run"]===!0})).code}case"override":{let a=n.sign===!0?!0:P(n,"sign"),c=e.flatMap((l,d)=>l==="--rule"?[e[d+1]??""]:l.startsWith("--rule=")?[l.slice(7)]:[]).flatMap(l=>l.split(","));if(c.some(l=>l===""||l.startsWith("--")))throw new S("--rule needs a rule id, such as main.deny_public_bucket");return(await P2(o,{root:r[0]??"",rules:c,reason:P(n,"reason")??"",...a!==void 0?{sign:a}:{},...P(n,"actor")?{actor:P(n,"actor")}:{},dryRun:n["dry-run"]===!0})).code}case"state":{if(r[0]!=="export"||!r[1])throw new S("state takes: state export <root> [--version <id>] [--out <file>] [--actor <name>]");return(await xJ(o,{root:r[1],...P(n,"version")?{version:P(n,"version")}:{},...P(n,"out")?{out:P(n,"out")}:{},...P(n,"actor")?{actor:P(n,"actor")}:{},...P(n,"binary")?{binary:P(n,"binary")}:{},...P(n,"config")?{config:P(n,"config")}:{}})).code}case"migrate":{if(r[0]!=="revert"||!r[1])throw new S("migrate takes: migrate revert <migration>");let a=PU(o,r[1]);return console.log(`wrote ${a}: it puts back each state ${r[1]} wrote, to the version it recorded before`),console.log(`revert the code of ${r[1]} in the same change; the plan job proves the revert and wave 1 waits for its approval`),0}case"unlock-state":{if(!r[0]||r.length>1)throw new S("unlock-state takes one root: unlock-state <root>");return(await T2(o,r[0],{...P(n,"binary")?{binary:P(n,"binary")}:{},...P(n,"config")?{config:P(n,"config")}:{},...P(n,"actor")?{actor:P(n,"actor")}:{},log:c=>console.log(`terragucci unlock-state: ${c}`)})).code}case"ephemeral":{let a=r[0],c={...P(n,"binary")?{binary:P(n,"binary")}:{},...P(n,"config")?{config:P(n,"config")}:{},...P(n,"base")?{base:P(n,"base")}:{},...P(n,"actor")?{actor:P(n,"actor")}:{},log:d=>console.log(`terragucci ephemeral: ${d}`)};if(a==="sweep")return await wB(o,c);if(a!=="up"&&a!=="down")throw new S("ephemeral takes up, down or sweep");let u=BI("pr",P(n,"pr")??"");if(a==="up")return await mB(o,{...c,pr:u,...P(n,"head")?{head:P(n,"head")}:{}});let l=P(n,"reason");if(l!=="closed"&&l!=="expired")throw new S("ephemeral down takes --reason closed or --reason expired");return await hB(o,{...c,pr:u,reason:l})}case"resume":{let a=P(n,"forge")??(process.env.GITLAB_CI==="true"?"gitlab":process.env.GITEA_ACTIONS==="true"||process.env.FORGEJO_ACTIONS==="true"?"forgejo":"github");if(a!=="github"&&a!=="forgejo"&&a!=="gitlab")throw new S("resume's --forge is github, forgejo or gitlab");let c=P(n,"out"),u=process.env.TG_SHA||MOe("git",["rev-parse","HEAD"],{cwd:o,encoding:"utf-8"}).stdout.trim();if(a==="gitlab")return await wJ(o,process.env);let l=ft(o),d;try{d=await pH(o,l)}catch(f){console.log(`terragucci resume: the decision point questions could not be read, so no wave waiting on one is resumed: ${f.message}`)}let p=await yJ({ledger:l,migrations:ft(o,ga),forge:a,sha:u,env:process.env,...d?{questions:d}:{}});if(c&&V9(Oe(o,c),p.kind==="apply"?`TG_SHA=${p.sha}
1302
1302
  TG_PR=${p.pr??""}
1303
- `:""),p.kind==="none")console.log(`terragucci resume: nothing to resume: ${p.why}`);else{for(let f of p.waves)console.log(f.migration?`terragucci resume: migration ${f.migration}, which wave 1 runs, was approved by ${f.by} for ${f.digest}`:f.stopped?`terragucci resume: the apply of wave ${f.wave}, approved by ${f.by} for ${f.digest}, stopped before it finished; the wave applies the rest`:`terragucci resume: wave ${f.wave} was approved by ${f.by} for ${f.digest}`);console.log(`terragucci resume: applying the waves again at ${p.sha.slice(0,8)}; each gate decides`)}return 0}case"notify":{if(r[0]==="drift"){for(let g of await E4(x4(Oe(o,P(n,"report")??"terragucci-report"))))console.log(`terragucci notify: ${g}`);return 0}let a=r[0];if(!aj.includes(a))throw new S(`terragucci notify takes one of ${[...aj,"drift"].join(", ")}`);let c=Number(P(n,"wave"));if(!Number.isInteger(c)||c<1)throw new S("terragucci notify needs --wave <n>");let u=P(n,"outcome"),l=u&&J9(Oe(o,u))?X_(Oe(o,u),"utf-8"):void 0,d=P(n,"outcome-json"),p=d?b4(Oe(o,d)):void 0,f=P(n,"report");for(let g of await R4(v4(a,c,{...l?{outcome:l}:{},...p?{result:p}:{},reportDir:Oe(o,f??"terragucci-report")})))console.log(`terragucci notify: ${g}`);return 0}case"drift-agent":{let a=r[0],c=P(n,"policy-dir")??"policy";if(a==="prompt"){let u=P(n,"report"),l=P(n,"out");if(!u||!l)throw new S("drift-agent prompt needs --report <dir> and --out <file>");let d=Hz({report:Oe(o,u),out:Oe(o,l),policyDir:c});return console.log(`terragucci drift-agent: wrote the prompt for drift issue #${d.issue}, ${d.roots} root${d.roots===1?"":"s"} drifted`),0}if(a==="push"){let u=P(n,"change");if(!u)throw new S("drift-agent push needs --change <dir>");let l=P(n,"forge")??"github";if(l!=="github"&&l!=="forgejo")throw new S("drift-agent push's --forge is github or forgejo");let d=await qz({change:Oe(o,u),policyDir:c,forge:l});return console.log(`terragucci drift-agent: ${d.reason}`),d.fail?1:0}throw new S("drift-agent is drift-agent prompt or drift-agent push")}case"query":{let[a,c]=r;if(a===void 0||c!==void 0)throw new S('query takes one statement, quoted: terragucci query "SELECT * FROM inventory"');let u=P(n,"config")??fe(o),l=P(n,"bucket"),d=await M9(u?await Re(Oe(u)):{},a,{...l?{reports:{bucket:l,...P(n,"bucket-endpoint")?{endpoint:P(n,"bucket-endpoint")}:{},...P(n,"bucket-prefix")?{prefix:P(n,"bucket-prefix")}:{}}}:{}});return s?i(Xo("query",0,d)):(console.log(L9(d)),0)}case"mcp":{let a=P(n,"config")??fe(o),c=P(n,"bucket");return await I9({cwd:o,config:a?await Re(Oe(a)):{},...c?{reports:{bucket:c,...P(n,"bucket-endpoint")?{endpoint:P(n,"bucket-endpoint")}:{},...P(n,"bucket-prefix")?{prefix:P(n,"bucket-prefix")}:{}}}:{}}),0}case"view":{if(r.length)throw new S("view takes no arguments: terragucci view [--export <dir> [--publish]] [--reports <dir|s3://<bucket>/<prefix>|https://...>] [--project <host/path>] [--port <n>] [--no-open]");let a=P(n,"export");if(n.publish===!0&&a===void 0)throw new S("--publish uploads the exported view; give --export <dir> too");let c=P(n,"port")===void 0?void 0:Number(P(n,"port"));if(c!==void 0&&(!Number.isInteger(c)||c<1||c>65535))throw new S("terragucci view --port takes a port number");if(c!==void 0&&a!==void 0)throw new S("--port is for serving; --export writes files and serves nothing");let u=W9(o),l=P(n,"reports"),d=P(n,"project")??H9(u,process.env),p=l===void 0?P(n,"config")??fe(o)??(u!==o?fe(u):void 0):void 0,f=z9(l===void 0?F9(p?await Re(Oe(p)):{},d):void 0,l,o);return n.publish===!0&&Y_(f),await B9({root:u,source:f,project:d,...c!==void 0?{port:c}:{},open:n["no-open"]!==!0,...a!==void 0?{export:Oe(o,a),publish:n.publish===!0}:{}},q9())}case"relay":{let a=Number(P(n,"port")??process.env.PORT??8080);if(!Number.isInteger(a)||a<1||a>65535)throw new S("terragucci relay --port takes a port number");return await N4({port:a}),await new Promise(()=>{})}case"post":{let a=P(n,"forge")??process.env.TG_FORGE??"github";if(a!=="github"&&a!=="gitlab"&&a!=="forgejo")throw new S("post's --forge is github, gitlab or forgejo");let c=await d2(r[0]??"",r.slice(1),{forge:a});return c&&console.log(c),0}case"plan-note":{let a=P(n,"forge")??"github";if(a!=="github"&&a!=="forgejo")throw new S("plan-note's --forge is github or forgejo");let c=P(n,"report")??"terragucci-report",u=P(n,"root"),l=await xW({forge:a,report:Oe(o,c),planResult:P(n,"plan-result")??"",...u?{root:u}:{},...n["approval-status"]===!0?{approval:!0}:{},...P(n,"agent-login")?{agentLogin:P(n,"agent-login")}:{}});for(let d of l)console.log(d);return 0}case"review":{let a=r[0],c=P(n,"forge");if(c!==void 0&&!["github","forgejo","gitlab"].includes(c))throw new S("review's --forge is github, forgejo or gitlab");if(a==="prompt"&&c==="gitlab"){let u=Oe(o,P(n,"report")??"terragucci-report"),{written:l,report:d}=await WW({report:u,instructions:P(n,"instructions")??Xm});return console.log(`terragucci review: ${d}`),console.log(`terragucci review: wrote the prompt for merge request !${l.pr} at ${l.head.slice(0,8)}; instructions: ${l.instructions==="default"?"the default branch's":"none on the default branch"}${l.changed?", which this merge request changes":""}`),0}if(a==="post"&&c==="gitlab"){let u=P(n,"dir");if(!u)throw new S("review post needs --dir <dir>");let l=Oe(o,u),d=await HW({dir:l,read:p=>J9(Z9(l,p))?X_(Z9(l,p),"utf-8"):""});return console.log(`terragucci review: ${d.reason}`),d.posted?0:1}if(a==="prompt"){let u=Oe(o,P(n,"report")??"terragucci-report"),l;try{l=JSON.parse(X_(process.env.GITHUB_EVENT_PATH??"","utf-8"))}catch(m){throw new S(`review prompt reads the pull request from the event file, and could not (${m.message})`)}let d=to(process.env),{subject:p,run:f}=await MW(l,d);console.log(`terragucci review: ${await LW(d,x$(process.env),p,f,u)}`);let g=_$({report:u,instructions:P(n,"instructions")??Xm,pull:p});return console.log(`terragucci review: wrote the prompt for pull request ${g.pr} at ${g.head.slice(0,8)}; instructions: ${g.instructions==="default"?"the default branch's":"none on the default branch"}${g.changed?", which this pull request changes":""}`),0}if(a==="post"){let u=P(n,"dir");if(!u)throw new S("review post needs --dir <dir>");let l=await NW({dir:Oe(o,u)});return console.log(`terragucci review: ${l.reason}`),0}throw new S("review is review prompt --report <dir> [--instructions <path>] or review post --dir <dir>")}case"approval-status":{let a=P(n,"forge")??"github";if(a!=="github"&&a!=="forgejo")throw new S("approval-status's --forge is github or forgejo");let c=P(n,"report"),u=P(n,"agent-login"),l=await h$({forge:a,...c?{report:Oe(o,c)}:{},...u?{agentLogin:u}:{},log:d=>console.log(`terragucci approval-status: ${d}`)});return console.log(`terragucci approval-status: ${l.state}: ${l.description}`),0}case"rollout":{let a=await ZP(o,bJ(r,n)),c=lJ(a);return s?i(Xo("rollout",c,a)):(console.log(XP(a)),c)}case"profiles":{let a=P(n,"config")??fe(o);return console.log(UOe(a?await Re(Oe(a)):{},a?void 0:o).join(" ")),0}case"config":{if(r[0]==="identities"){let g=P(n,"config")??fe(o);if(!g)throw new S("no terragucci config here; pass --config <file>");let m=await Mh(Oe(g),eS(o,Oe(g))||g,{...P(n,"project")?{project:P(n,"project")}:{},...P(n,"forge")?{forge:P(n,"forge")}:{},...P(n,"repo")?{repo:P(n,"repo")}:{},...P(n,"default-branch")?{defaultBranch:P(n,"default-branch")}:{},...P(n,"owner-id")?{ownerId:P(n,"owner-id")}:{},...P(n,"repo-id")?{repoId:P(n,"repo-id")}:{},noRoots:n["no-roots"]===!0});return s?i(Xo("config identities",0,m)):(console.log(fV(m)),0)}if(r[0]!=="check")throw new S("usage: terragucci config check|identities [--config <file>] [--json]");let a=P(n,"config")??fe(o);if(!a)throw new S("no terragucci config here; pass --config <file>");let c=[],u=[],l=[],d,p;try{let g=await Re(Oe(a),"check"),m=o2(g);if(m.length)throw new S(`${a} does not match terragucci.schema.json`,m.map(v=>`${v} (terragucci.schema.json)`));let h=Q_(Oe(a)),y=g.projects?void 0:et(h,ue(g));y&&c.push(...y.problems("config"));for(let v of Object.keys(g.projects??{}))try{ot(g,v)}catch($){if(!($ instanceof S))throw $;c.push(...$.problems??[$.message])}if(y&&c.length===0&&g.environments){let v=y.kind==="terragrunt"?Dn(h,g.terragrunt?.exclude).map($=>$.path):y.kind==="atmos"?Hi(await Wi(h)).map($=>$.path):wt(h,g.roots);c.push(...wD(v,g))}let w=Zs(g.oidc,g.environments),b=w?["aws","gcp","azure"].filter(v=>v==="aws"?w.roles:w[v]?.roles):[];if(y&&c.length===0&&w&&b.length>0&&(y.kind==="atmos"||y.engine==="per-root")){let v=y.kind==="atmos"?Hi(await Wi(h)):void 0,$=v?v.map(R=>R.path):wt(h,g.roots),_=v?{...rg(v),via:"!terraform.state"}:void 0,E=b.map(R=>XL(h,$,w,_,R));d={roles:E.flatMap(R=>R.roles),warnings:E.flatMap(R=>R.warnings)},u=d.warnings}y&&c.length===0&&y.engine==="per-root"&&y.kind!=="atmos"&&u.push(...uD(h,wt(h,g.roots))),!g.projects&&g.terragrunt?.credentials&&u.push(...QL(g.terragrunt.credentials));let k=!!(g.environments||g.oidc?.roles||g.oidc?.gcp?.roles||g.oidc?.azure?.roles);if(y&&c.length===0&&y.kind!=="terragrunt"&&k)try{let v=y.kind==="atmos"?Hi(await Wi(h)).map(R=>R.path):wt(h,g.roots),$=g.forge??Kr(h)?.value,_=sc(g,v),E=dM(_,$);E&&l.push({rule:$x,message:E}),l.push(...Vq(_,g,$))}catch{}for(let v of[g.agent,...Object.values(g.projects??{}).map($=>$.agent)]){let $=Vy(v);$&&!u.includes($)&&u.push($)}p=Yu(Q_(Oe(a)),g),c.push(...EA(Q_(Oe(a)))),!g.forge&&(g.apply?.when==="pull-request"||g.agent?.comment||g.review?.agent)&&Kr(Q_(Oe(a)))?.value==="gitlab"&&c.push(...Nf(g,"config"))}catch(g){if(!(g instanceof S))throw g;c=g.problems??[g.message]}let f=eS(o,Oe(a))||a;if(s)return i(Xo("config check",c.length?2:0,{file:f,ok:c.length===0,problems:c,...u.length?{warnings:u}:{},...l.length&&c.length===0?{tips:l}:{},...d&&c.length===0?{state_access:d.roles}:{},...p&&c.length===0?{approval:p}:{}}));if(c.length===0){if(console.log(`${f}: ok`),p&&console.log(`approval: ${p.mode} (${p.source})${p.note?`
1303
+ `:""),p.kind==="none")console.log(`terragucci resume: nothing to resume: ${p.why}`);else{for(let f of p.waves)console.log(f.migration?`terragucci resume: migration ${f.migration}, which wave 1 runs, was approved by ${f.by} for ${f.digest}`:f.stopped?`terragucci resume: the apply of wave ${f.wave}, approved by ${f.by} for ${f.digest}, stopped before it finished; the wave applies the rest`:`terragucci resume: wave ${f.wave} was approved by ${f.by} for ${f.digest}`);console.log(`terragucci resume: applying the waves again at ${p.sha.slice(0,8)}; each gate decides`)}return 0}case"notify":{if(r[0]==="drift"){for(let g of await E4(x4(Oe(o,P(n,"report")??"terragucci-report"))))console.log(`terragucci notify: ${g}`);return 0}let a=r[0];if(!aj.includes(a))throw new S(`terragucci notify takes one of ${[...aj,"drift"].join(", ")}`);let c=Number(P(n,"wave"));if(!Number.isInteger(c)||c<1)throw new S("terragucci notify needs --wave <n>");let u=P(n,"outcome"),l=u&&J9(Oe(o,u))?X_(Oe(o,u),"utf-8"):void 0,d=P(n,"outcome-json"),p=d?b4(Oe(o,d)):void 0,f=P(n,"report");for(let g of await R4(v4(a,c,{...l?{outcome:l}:{},...p?{result:p}:{},reportDir:Oe(o,f??"terragucci-report")})))console.log(`terragucci notify: ${g}`);return 0}case"drift-agent":{let a=r[0],c=P(n,"policy-dir")??"policy";if(a==="prompt"){let u=P(n,"report"),l=P(n,"out");if(!u||!l)throw new S("drift-agent prompt needs --report <dir> and --out <file>");let d=Hz({report:Oe(o,u),out:Oe(o,l),policyDir:c});return console.log(`terragucci drift-agent: wrote the prompt for drift issue #${d.issue}, ${d.roots} root${d.roots===1?"":"s"} drifted`),0}if(a==="push"){let u=P(n,"change");if(!u)throw new S("drift-agent push needs --change <dir>");let l=P(n,"forge")??"github";if(l!=="github"&&l!=="forgejo")throw new S("drift-agent push's --forge is github or forgejo");let d=await qz({change:Oe(o,u),policyDir:c,forge:l});return console.log(`terragucci drift-agent: ${d.reason}`),d.fail?1:0}throw new S("drift-agent is drift-agent prompt or drift-agent push")}case"query":{let[a,c]=r;if(a===void 0||c!==void 0)throw new S('query takes one statement, quoted: terragucci query "SELECT * FROM inventory"');let u=P(n,"config")??fe(o),l=P(n,"bucket"),d=await M9(u?await Re(Oe(u)):{},a,{...l?{reports:{bucket:l,...P(n,"bucket-endpoint")?{endpoint:P(n,"bucket-endpoint")}:{},...P(n,"bucket-prefix")?{prefix:P(n,"bucket-prefix")}:{}}}:{}});return s?i(Xo("query",0,d)):(console.log(L9(d)),0)}case"mcp":{let a=P(n,"config")??fe(o),c=P(n,"bucket");return await I9({cwd:o,config:a?await Re(Oe(a)):{},...c?{reports:{bucket:c,...P(n,"bucket-endpoint")?{endpoint:P(n,"bucket-endpoint")}:{},...P(n,"bucket-prefix")?{prefix:P(n,"bucket-prefix")}:{}}}:{}}),0}case"view":{if(r.length)throw new S("view takes no arguments: terragucci view [--export <dir> [--publish]] [--reports <dir|s3://<bucket>/<prefix>|https://...>] [--project <host/path>] [--port <n>] [--no-open]");let a=P(n,"export");if(n.publish===!0&&a===void 0)throw new S("--publish uploads the exported view; give --export <dir> too");let c=P(n,"port")===void 0?void 0:Number(P(n,"port"));if(c!==void 0&&(!Number.isInteger(c)||c<1||c>65535))throw new S("terragucci view --port takes a port number");if(c!==void 0&&a!==void 0)throw new S("--port is for serving; --export writes files and serves nothing");let u=W9(o),l=P(n,"reports"),d=P(n,"project")??H9(u,process.env),p=l===void 0?P(n,"config")??fe(o)??(u!==o?fe(u):void 0):void 0,f=z9(l===void 0?F9(p?await Re(Oe(p)):{},d):void 0,l,o);return n.publish===!0&&Y_(f),await B9({root:u,source:f,project:d,...c!==void 0?{port:c}:{},open:n["no-open"]!==!0,...a!==void 0?{export:Oe(o,a),publish:n.publish===!0}:{}},q9())}case"relay":{let a=Number(P(n,"port")??process.env.PORT??8080);if(!Number.isInteger(a)||a<1||a>65535)throw new S("terragucci relay --port takes a port number");return await N4({port:a}),await new Promise(()=>{})}case"post":{let a=P(n,"forge")??process.env.TG_FORGE??"github";if(a!=="github"&&a!=="gitlab"&&a!=="forgejo")throw new S("post's --forge is github, gitlab or forgejo");let c=await dJ(r[0]??"",r.slice(1),{forge:a});return c&&console.log(c),0}case"plan-note":{let a=P(n,"forge")??"github";if(a!=="github"&&a!=="forgejo")throw new S("plan-note's --forge is github or forgejo");let c=P(n,"report")??"terragucci-report",u=P(n,"root"),l=await xW({forge:a,report:Oe(o,c),planResult:P(n,"plan-result")??"",...u?{root:u}:{},...n["approval-status"]===!0?{approval:!0}:{},...P(n,"agent-login")?{agentLogin:P(n,"agent-login")}:{}});for(let d of l)console.log(d);return 0}case"review":{let a=r[0],c=P(n,"forge");if(c!==void 0&&!["github","forgejo","gitlab"].includes(c))throw new S("review's --forge is github, forgejo or gitlab");if(a==="prompt"&&c==="gitlab"){let u=Oe(o,P(n,"report")??"terragucci-report"),{written:l,report:d}=await WW({report:u,instructions:P(n,"instructions")??Xm});return console.log(`terragucci review: ${d}`),console.log(`terragucci review: wrote the prompt for merge request !${l.pr} at ${l.head.slice(0,8)}; instructions: ${l.instructions==="default"?"the default branch's":"none on the default branch"}${l.changed?", which this merge request changes":""}`),0}if(a==="post"&&c==="gitlab"){let u=P(n,"dir");if(!u)throw new S("review post needs --dir <dir>");let l=Oe(o,u),d=await HW({dir:l,read:p=>J9(Z9(l,p))?X_(Z9(l,p),"utf-8"):""});return console.log(`terragucci review: ${d.reason}`),d.posted?0:1}if(a==="prompt"){let u=Oe(o,P(n,"report")??"terragucci-report"),l;try{l=JSON.parse(X_(process.env.GITHUB_EVENT_PATH??"","utf-8"))}catch(m){throw new S(`review prompt reads the pull request from the event file, and could not (${m.message})`)}let d=to(process.env),{subject:p,run:f}=await MW(l,d);console.log(`terragucci review: ${await LW(d,x$(process.env),p,f,u)}`);let g=_$({report:u,instructions:P(n,"instructions")??Xm,pull:p});return console.log(`terragucci review: wrote the prompt for pull request ${g.pr} at ${g.head.slice(0,8)}; instructions: ${g.instructions==="default"?"the default branch's":"none on the default branch"}${g.changed?", which this pull request changes":""}`),0}if(a==="post"){let u=P(n,"dir");if(!u)throw new S("review post needs --dir <dir>");let l=await NW({dir:Oe(o,u)});return console.log(`terragucci review: ${l.reason}`),0}throw new S("review is review prompt --report <dir> [--instructions <path>] or review post --dir <dir>")}case"approval-status":{let a=P(n,"forge")??"github";if(a!=="github"&&a!=="forgejo")throw new S("approval-status's --forge is github or forgejo");let c=P(n,"report"),u=P(n,"agent-login"),l=await h$({forge:a,...c?{report:Oe(o,c)}:{},...u?{agentLogin:u}:{},log:d=>console.log(`terragucci approval-status: ${d}`)});return console.log(`terragucci approval-status: ${l.state}: ${l.description}`),0}case"rollout":{let a=await ZP(o,b2(r,n)),c=l2(a);return s?i(Xo("rollout",c,a)):(console.log(XP(a)),c)}case"profiles":{let a=P(n,"config")??fe(o);return console.log(UOe(a?await Re(Oe(a)):{},a?void 0:o).join(" ")),0}case"config":{if(r[0]==="identities"){let g=P(n,"config")??fe(o);if(!g)throw new S("no terragucci config here; pass --config <file>");let m=await Mh(Oe(g),eS(o,Oe(g))||g,{...P(n,"project")?{project:P(n,"project")}:{},...P(n,"forge")?{forge:P(n,"forge")}:{},...P(n,"repo")?{repo:P(n,"repo")}:{},...P(n,"default-branch")?{defaultBranch:P(n,"default-branch")}:{},...P(n,"owner-id")?{ownerId:P(n,"owner-id")}:{},...P(n,"repo-id")?{repoId:P(n,"repo-id")}:{},noRoots:n["no-roots"]===!0});return s?i(Xo("config identities",0,m)):(console.log(fV(m)),0)}if(r[0]!=="check")throw new S("usage: terragucci config check|identities [--config <file>] [--json]");let a=P(n,"config")??fe(o);if(!a)throw new S("no terragucci config here; pass --config <file>");let c=[],u=[],l=[],d,p;try{let g=await Re(Oe(a),"check"),m=oJ(g);if(m.length)throw new S(`${a} does not match terragucci.schema.json`,m.map(v=>`${v} (terragucci.schema.json)`));let h=Q_(Oe(a)),y=g.projects?void 0:et(h,ue(g));y&&c.push(...y.problems("config"));for(let v of Object.keys(g.projects??{}))try{ot(g,v)}catch($){if(!($ instanceof S))throw $;c.push(...$.problems??[$.message])}if(y&&c.length===0&&g.environments){let v=y.kind==="terragrunt"?Dn(h,g.terragrunt?.exclude).map($=>$.path):y.kind==="atmos"?Hi(await Wi(h)).map($=>$.path):wt(h,g.roots);c.push(...wD(v,g))}let w=Zs(g.oidc,g.environments),b=w?["aws","gcp","azure"].filter(v=>v==="aws"?w.roles:w[v]?.roles):[];if(y&&c.length===0&&w&&b.length>0&&(y.kind==="atmos"||y.engine==="per-root")){let v=y.kind==="atmos"?Hi(await Wi(h)):void 0,$=v?v.map(R=>R.path):wt(h,g.roots),_=v?{...rg(v),via:"!terraform.state"}:void 0,E=b.map(R=>XL(h,$,w,_,R));d={roles:E.flatMap(R=>R.roles),warnings:E.flatMap(R=>R.warnings)},u=d.warnings}y&&c.length===0&&y.engine==="per-root"&&y.kind!=="atmos"&&u.push(...uD(h,wt(h,g.roots))),!g.projects&&g.terragrunt?.credentials&&u.push(...QL(g.terragrunt.credentials));let k=!!(g.environments||g.oidc?.roles||g.oidc?.gcp?.roles||g.oidc?.azure?.roles);if(y&&c.length===0&&y.kind!=="terragrunt"&&k)try{let v=y.kind==="atmos"?Hi(await Wi(h)).map(R=>R.path):wt(h,g.roots),$=g.forge??Kr(h)?.value,_=sc(g,v),E=dM(_,$);E&&l.push({rule:$x,message:E}),l.push(...Vq(_,g,$))}catch{}for(let v of[g.agent,...Object.values(g.projects??{}).map($=>$.agent)]){let $=Vy(v);$&&!u.includes($)&&u.push($)}p=Yu(Q_(Oe(a)),g),c.push(...EA(Q_(Oe(a)))),!g.forge&&(g.apply?.when==="pull-request"||g.agent?.comment||g.review?.agent)&&Kr(Q_(Oe(a)))?.value==="gitlab"&&c.push(...Nf(g,"config"))}catch(g){if(!(g instanceof S))throw g;c=g.problems??[g.message]}let f=eS(o,Oe(a))||a;if(s)return i(Xo("config check",c.length?2:0,{file:f,ok:c.length===0,problems:c,...u.length?{warnings:u}:{},...l.length&&c.length===0?{tips:l}:{},...d&&c.length===0?{state_access:d.roles}:{},...p&&c.length===0?{approval:p}:{}}));if(c.length===0){if(console.log(`${f}: ok`),p&&console.log(`approval: ${p.mode} (${p.source})${p.note?`
1304
1304
  note: ${p.note}`:""}`),d){console.log("state access:");for(let g of d.roles)console.log(` ${g.role} (${g.cloud?`${g.cloud}, `:""}${g.stage}, ${g.environment}): ${g.states.length?g.states.join(", "):"no state named in code"}${g.reads.length?`; reads ${g.reads.join(", ")}`:""}`)}for(let g of l)console.log(`tip (${g.rule}): ${g.message}`)}else console.error(`${f}: ${c.length} problem(s)
1305
1305
  ${c.join(`
1306
1306
  `)}`);return u.length&&console.error(`${f}: ${u.length} warning(s)