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