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