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