UNPKG

@lunora/cli

Version:

The Lunora CLI: init, dev, deploy, codegen, migrate, seed, doctor, insights, logs, registry, and the rest of the project commands

4 lines (3 loc) 7.66 kB
import{existsSync as h,readFileSync as S}from"node:fs";import{join as V}from"node:path";import{DEV_VARS_FILE as d,DEV_VARS_KEY_PATTERN as k,writeDevVariablesFileAtomically as $,removeDevVariableLine as R,upsertDevVariableLine as P,isPlaceholderValue as A,detectPackageManager as N,resolveDeployDriver as U,resolveProjectTarget as z,execArgsFor as M,generateSecretValue as Y,DEV_VARS_EXAMPLE_FILE as p,parseDevVariableEntries as _,packageNamesFromBindings as j,inferLunoraBindings as x,requiredSecrets as C,isMintableSecretKey as b}from"@lunora/config";import{d as q}from"../packem_shared/command-DwC11Cwe.mjs";import{defaultSpawner as F}from"../packem_shared/createRecordingSpawner-DJlNacZ6.mjs";import{l as T}from"../packem_shared/wrangler-secrets-CCdt0mUl.mjs";const Z=/[\r\n]/u,B=/["\\]/u,D=r=>new Map(_(r).map(s=>[s.key,s])),y=r=>h(r)?S(r,"utf8"):"",L=r=>r.length<=4?"****":`${r.slice(0,4)}${"*".repeat(Math.min(8,r.length-4))}`,f=r=>D(y(r)),K=r=>r.env??(r.prod===!0?"production":void 0),I=r=>{const s=f(r.devVariablesPath);if(s.size===0)return r.logger.info(`${d}: (empty)`),{code:0,descriptors:[]};for(const t of s.values())r.logger.info(`${t.key}=${L(t.value)}`);return{code:0,descriptors:[]}},O=r=>{const{devVariablesPath:s,logger:t,options:e}=r;if(!e.key)return t.error("env get requires a key. Usage: lunora env get <KEY>"),{code:1,descriptors:[]};const o=f(s).get(e.key);return o?(process.stdout.write(`${o.value} `),{code:0,descriptors:[]}):(t.error(`env: ${e.key} is not set in ${d}`),{code:1,descriptors:[]})},G=r=>{const{devVariablesPath:s,logger:t,options:e}=r;if(!e.key)return t.error("env set requires a key. Usage: lunora env set <KEY> <VALUE>"),{code:1,descriptors:[]};if(!k.test(e.key))return t.error(`env: invalid key "${e.key}" — must match [A-Za-z_][A-Za-z0-9_]*`),{code:1,descriptors:[]};if(e.value===void 0)return t.error("env set requires a value. Usage: lunora env set <KEY> <VALUE>"),{code:1,descriptors:[]};if(Z.test(e.value))return t.error(`env: value for "${e.key}" contains a newline, which .dev.vars cannot represent`),{code:1,descriptors:[]};if(B.test(e.value))return t.error(`env: value for "${e.key}" contains a double-quote or backslash, which .dev.vars cannot round-trip`),{code:1,descriptors:[]};const o=y(s);return $(s,P(o,e.key,e.value)),t.success(`env: set ${e.key} (${L(e.value)}) in ${d}`),{code:0,descriptors:[]}},H=r=>{const{devVariablesPath:s,logger:t,options:e}=r;if(!e.key)return t.error("env unset requires a key. Usage: lunora env unset <KEY>"),{code:1,descriptors:[]};if(!k.test(e.key))return t.error(`env: invalid key "${e.key}" — must match [A-Za-z_][A-Za-z0-9_]*`),{code:1,descriptors:[]};const o=y(s);return D(o).has(e.key)?($(s,R(o,e.key)),t.success(`env: unset ${e.key} in ${d}`),{code:0,descriptors:[]}):(t.warn(`env: ${e.key} was not set in ${d}`),{code:0,descriptors:[]})},W=async r=>{const{devVariablesPath:s,logger:t,options:e}=r;if(!e.yes)return t.error("env push uploads secrets to Cloudflare. Re-run with --yes to confirm."),{code:1,descriptors:[]};const o=f(s);if(o.size===0)return t.warn(`${d}: nothing to push (empty)`),{code:0,descriptors:[]};const i=[...o.values()].filter(n=>A(n.value)).map(n=>n.key);if(i.length>0)return t.error(`env push: refusing to upload placeholder value(s) for ${i.join(", ")} — run \`lunora env doctor\` to review, and \`lunora env generate --set\` (or \`lunora env set <KEY> <VALUE>\`) to fill them, then re-run.`),{code:1,descriptors:[]};const c=e.spawner??F,l=e.cwd??process.cwd(),v=N(l),a=[],u=K(e);for(const n of o.values()){const m=U(z(l)).toolchain?.secretPut({environment:u,key:n.key,temporary:e.temporary});if(m===void 0)return t.error("deploy target has no command-line toolchain; cannot push secrets"),{code:1,descriptors:[]};const E=M(v,m.tool,m.args),w={args:E.args,command:E.command,cwd:l,input:n.value};a.push(w),t.info(`pushing ${n.key} -> wrangler secret${u===void 0?"":` (${u})`}`);const g=await c(w);if(g.code!==0)return t.error(`env push: failed at ${n.key} (exit ${String(g.code)})`),{code:g.code,descriptors:a}}return t.success(`env: pushed ${String(o.size)} secret(s)`),{code:0,descriptors:a}},X=r=>(r.options.secretLister??T)({cwd:r.cwd,env:K(r.options),temporary:r.options.temporary}),J=async r=>{const{devVariablesPath:s,logger:t}=r,e=await X(r);if(!e.ok)return t.error(`env diff: ${e.error??"failed to list remote secrets"}`),{code:1,descriptors:[]};const o=new Set(f(s).keys()),i=new Set(e.names),c=[...o].filter(a=>!i.has(a)).toSorted((a,u)=>a.localeCompare(u)),l=e.names.filter(a=>!o.has(a)),v=[...o].filter(a=>i.has(a)).toSorted((a,u)=>a.localeCompare(u));for(const a of c)t.info(`local only (run \`lunora env push\`): ${a}`);for(const a of l)t.info(`remote only (in Cloudflare, not ${d}): ${a}`);return t.info(`in both: ${String(v.length)} secret(s)`),c.length===0&&l.length===0&&t.success("env diff: local and remote secret names match"),{code:0,descriptors:[]}},Q=r=>{const{cwd:s,devVariablesPath:t,logger:e}=r,o=V(s,p);if(!h(o))return e.info(`env doctor: no ${p} to check against — nothing to validate.`),{code:0,descriptors:[]};const i=_(S(o,"utf8")).map(n=>n.key),c=f(t);if(!h(t))return e.error(`env doctor: ${d} is missing. Run \`lunora dev\` to scaffold it, or \`lunora env set <KEY> <VALUE>\`.`),e.info(`expected (from ${p}): ${i.join(", ")}`),{code:1,descriptors:[]};const l=i.filter(n=>!c.has(n)),v=[...c.values()].filter(n=>A(n.value)).map(n=>n.key),a=new Set(i),u=[...c.keys()].filter(n=>!a.has(n));for(const n of l)e.error(`missing: ${n} is in ${p} but not ${d}`);for(const n of v)e.error(`unset: ${n} still has a placeholder value`);for(const n of u)e.info(`extra: ${n} is set locally but not listed in ${p}`);return l.length===0&&v.length===0?(e.success(`env doctor: ${d} looks good (${String(c.size)} var(s)).`),{code:0,descriptors:[]}):{code:1,descriptors:[]}},ee=async r=>{let s=[];try{s=j(await x({projectRoot:r.cwd}))}catch{}const t=C(s).map(o=>o.key).filter(o=>b(o)),e=[...f(r.devVariablesPath).keys()].filter(o=>b(o));return[...new Set([...t,...e])]},re=async r=>{const{devVariablesPath:s,logger:t,options:e}=r;let o;if(e.key===void 0){if(o=await ee(r),o.length===0)return t.info("env generate: no locally-generatable secrets for this project. Name one explicitly: lunora env generate <KEY>"),{code:0,descriptors:[]}}else{if(!k.test(e.key))return t.error(`env: invalid key "${e.key}" — must match [A-Za-z_][A-Za-z0-9_]*`),{code:1,descriptors:[]};o=[e.key]}const i=o.map(c=>({key:c,value:Y()}));if(e.set===!0){let c=y(s);for(const l of i)c=P(c,l.key,l.value);return $(s,c),t.success(`env: generated ${String(i.length)} secret(s) into ${d}: ${i.map(l=>l.key).join(", ")}`),{code:0,descriptors:[]}}for(const c of i)process.stdout.write(`${c.key}=${c.value} `);return{code:0,descriptors:[]}},te=async r=>{const s=r.cwd??process.cwd(),t={cwd:s,devVariablesPath:V(s,d),logger:r.logger,options:r};switch(r.subcommand){case"diff":return J(t);case"doctor":return Q(t);case"generate":return re(t);case"get":return O(t);case"list":return I(t);case"push":return W(t);case"set":return G(t);case"unset":return H(t);default:return r.logger.error(`env: unknown subcommand "${r.subcommand}"`),{code:1,descriptors:[]}}},oe=new Set(["diff","doctor","generate","get","list","push","set","unset"]),se=r=>typeof r=="string"&&oe.has(r),ue=q(({argument:r,cwd:s,logger:t,options:e})=>{const o=r[0];return se(o)?te({cwd:s,env:e.env,key:r[1],logger:t,prod:e.prod===!0,set:e.set===!0,subcommand:o,temporary:e.temporary===!0,value:r[2],yes:e.yes===!0}):(t.error(`env: unknown subcommand "${o??""}" — expected list | get | set | unset | push | diff | doctor | generate`),{code:1})});export{ue as execute,te as runEnvCommand};