UNPKG

@lunora/cli

Version:

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

6 lines (5 loc) • 10.4 kB
import{existsSync as p,readFileSync as g,writeFileSync as k,mkdtempSync as F,rmSync as w}from"node:fs";import{tmpdir as G}from"node:os";import{LunoraError as h}from"@lunora/errors";import{join as l}from"@visulima/path";import{downloadTemplate as W}from"giget";import{r as y}from"./cli-manifest-DoQ_3bzs.mjs";import{DEV_VARS_FILE as z,parseDevVariableEntries as H,writeDevVariablesFileAtomically as J}from"@lunora/config";import{modify as j,applyEdits as N,parse as K}from"jsonc-parser";import{b as q}from"./tui-prompts-B3YwUhGw.mjs";import Y from"./parseManifest-CwPTKdtS.mjs";const Z="alpha",T="main",X=new Set(["alpha","beta","next"]),Q=/^[\w./@-]+$/,ee=e=>!e.includes("..")&&Q.test(e),O=e=>{if(e==="0.0.0")return Z;const t=e.split("+")[0]??e,n=t.indexOf("-");if(n!==-1){const[s]=t.slice(n+1).split(".");if(s!==void 0&&X.has(s))return s}return T},D=e=>{if(e!==void 0&&e.length>0){if(!ee(e))throw new h("INTERNAL",`invalid --ref "${e}" — a ref may contain letters, digits, ".", "_", "-", "/", "@" and must not contain "..".`);return e}return O(y())},I="anolilab/lunora",L=/^[0-9a-f]{40}$/iu,B=String.raw`\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)+)?(?:\+[0-9A-Za-z.-]+)?`,te=new RegExp(String.raw`^v?${B}$`,"u"),ne=new RegExp(String.raw`^(?:@[\w.-]+\/)?[\w.-]+@${B}$`,"u"),se=e=>L.test(e)||te.test(e)||ne.test(e),re=()=>{const e=process.env.GITHUB_TOKEN??process.env.GH_TOKEN;return e!==void 0&&e.length>0?{authorization:`Bearer ${e}`}:{}},oe=async(e,t=I)=>{try{const n=await fetch(`https://api.github.com/repos/${t}/commits/${encodeURIComponent(e)}`,{headers:{accept:"application/vnd.github+json","user-agent":"lunora-cli",...re()},signal:AbortSignal.timeout(1e4)});if(!n.ok)return;const s=await n.json();return typeof s.sha=="string"&&L.test(s.sha)?s.sha:void 0}catch{return}},ie=async(e,t,n)=>{if(se(t))return t;const s=await oe(t,e);return s===void 0?(n.warn(`could not pin ${e}#${t} to a commit — fetching the UNPINNED branch (set GITHUB_TOKEN if rate-limited).`),t):(n.info(`pinned ${e}#${t} → ${s}`),s)},ae=async(e,t)=>ie(I,D(e),t),Me=(e=y())=>`@lunora/cli@${e}`,ce="latest",P=(e=y())=>{const t=O(e);return t===T?ce:t},de="https://registry.npmjs.org",ue=()=>{const e=process.env.npm_config_registry,t=e!==void 0&&e.length>0?e:de;return t.endsWith("/")?t.slice(0,-1):t},fe=async(e,t)=>{try{const n=await fetch(`${ue()}/${e.replaceAll("/","%2F")}`,{headers:{accept:"application/vnd.npm.install-v1+json"},signal:AbortSignal.timeout(1e4)});return n.ok?(await n.json())["dist-tags"]?.[t]:void 0}catch{return}},le=async(e,t)=>{const n=new Map;return await Promise.all([...new Set(e)].map(async s=>{const o=await fe(s,t);o!==void 0&&n.set(s,o)})),n},pe=e=>{if(!e.startsWith("workspace:"))return!1;const t=e.slice(10);return t===""||t==="*"||t==="^"||t==="~"},he=(e,t,n)=>{if(!e.startsWith("workspace:"))return e;const s=e.slice(10);return s===""||s==="*"||s==="^"||s==="~"?(n===void 0?void 0:t?.get(n))??P():s},Ue=async e=>{const t=new Set;for(const n of e)for(const s of[n.deps,n.devDependencies])for(const[o,r]of Object.entries(s??{}))pe(r)&&t.add(o);return t.size===0?new Map:le([...t],P())},ge=new Set(["@lunora/client","@lunora/do","@lunora/ratelimit","@lunora/runtime","@lunora/server","@lunora/values"]),me=/(['"])@lunora\/(client|do|ratelimit|runtime|server|values)(\/[^'"]*)?\1/gu,Fe=e=>e.replaceAll(me,(t,n,s,o)=>`${n}lunorash/${s}${o??""}${n}`),$=(e,t,n,s="dependencies",o=!1,r)=>{const a=Object.entries(e);if(a.length===0)return[];const i=l(t,"package.json");if(!p(i))return n.warn(`package.json not found at ${i} — skipping dependency updates`),[];let d=g(i,"utf8");const c=JSON.parse(d),f=[];for(const[u,M]of a){if(o&&ge.has(u)){n.info(`dep provided by the lunorash umbrella, skipping: ${u}`);continue}if(c.dependencies?.[u]!==void 0||c.devDependencies?.[u]!==void 0){n.info(`dep already present: ${u}`);continue}const U=j(d,[s,u],he(M,r,u),{formattingOptions:{insertSpaces:!0,tabSize:4}});d=N(d,U),f.push(u)}return f.length>0&&(k(i,d,"utf8"),n.success(`added ${String(f.length)} ${s==="devDependencies"?"devDependency(ies)":"dependency(ies)"} to package.json: ${f.join(", ")}`)),f},ye=(e,t,n)=>{if(e.length===0)return[];const s=l(t,z),o=p(s)?g(s,"utf8"):"",r=new Set(H(o).map(c=>c.key)),a=[],i=[],d=[];for(const c of e)c.secret&&i.push(c.name),!r.has(c.name)&&(c.description&&d.push(`# ${c.description}`),d.push(`${c.name}=${c.secret?"":c.value??""}`),a.push(c.name));if(a.length>0){const c=o===""||o.endsWith(` `)?o:`${o} `;J(s,`${c}${d.join(` `)} `),n.success(`scaffolded ${String(a.length)} env var(s) into .dev.vars: ${a.join(", ")}`)}return i.length>0&&n.info(`set secret value(s) locally in .dev.vars, then for production: ${i.map(c=>`wrangler secret put ${c}`).join("; ")}`),a},S=new Set(["ai","analytics_engine_datasets","browser","d1_databases","durable_objects","hyperdrive","kv_namespaces","mtls_certificates","queues","r2_buckets","send_email","services","vars","vectorize","version_metadata","workflows"]),ve=["binding","name","queue","pattern"],E=e=>{if(!(typeof e!="object"||e===null))for(const t of ve){const n=e[t];if(typeof n=="string"&&n.length>0)return`${t}:${n}`}},we=(e,t,n,s)=>{const o=new Set(e.map(i=>JSON.stringify(i))),r=new Set(e.map(i=>E(i)).filter(i=>i!==void 0)),a=[];for(const i of t){if(o.has(JSON.stringify(i)))continue;const d=E(i);if(d!==void 0&&r.has(d)){s.warn(`${d.replace(":"," ")} already exists in ${n} — keeping the project's entry and skipping the registry item's. Reconcile by hand if the item needs different settings.`);continue}d!==void 0&&r.add(d),a.push(i)}return a},b=e=>Array.isArray(e),R=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),$e=(e,t,n,s)=>{const o={};for(const[r,a]of Object.entries(t)){if(!Object.hasOwn(e,r)){o[r]=a;continue}JSON.stringify(e[r])!==JSON.stringify(a)&&s.warn(`${r} already exists in ${n} — keeping the project's value and skipping the registry item's. Reconcile by hand if the item needs different settings.`)}return o},Se=(e,t)=>{let n=K(e);for(const s of t){if(typeof n!="object"||n===null)return;n=n[s]}return n},Ee=(e,t)=>{const n=e[0];return n!==void 0&&S.has(n)?!0:(t.warn(`skipping binding "${e.join(".")}" — only resource bindings (${[...S].join(", ")}) may be written, not exec/entrypoint keys`),!1)},m=Symbol("skip-binding"),be=(e,t,n)=>{const{value:s}=t,o=t.path.join("."),r=Se(e,t.path);if(b(s)){if(!b(r))return s;const a=we(r,s,o,n);return a.length===0?m:[...r,...a]}if(R(s)){if(!R(r))return s;const a=$e(r,s,o,n);return Object.keys(a).length===0?m:{...r,...a}}return s},Re=(e,t,n)=>{if(e.length===0)return[];const o=["wrangler.jsonc","wrangler.json"].map(i=>l(t,i)).find(i=>p(i));if(!o)return n.warn("wrangler.jsonc not found — skipping binding updates"),[];let r=g(o,"utf8");const a=[];for(const i of e){if(!Ee(i.path,n))continue;const d=be(r,i,n);if(d===m)continue;const c=j(r,[...i.path],d,{formattingOptions:{insertSpaces:!0,tabSize:4}});c.length!==0&&(r=N(r,c),a.push(i.path.join(".")))}return a.length>0&&(k(o,r,"utf8"),n.success(`applied ${String(a.length)} binding(s) to ${o}: ${a.join(", ")}`)),a},Ge=(e,t,n,s=!1,o)=>{const r=[],a=[];return e.deps&&r.push(...$(e.deps,t,n,"dependencies",s,o)),e.devDependencies&&r.push(...$(e.devDependencies,t,n,"devDependencies",s,o)),e.bindings&&a.push(...Re(e.bindings,t,n)),e.envVars&&ye(e.envVars,t,n),{bindings:a,deps:r}},v=e=>e.source!==void 0&&e.source.length>0||e.from!==void 0&&e.from.length>0,We=async(e,t)=>{const n=e.some(({manifest:c})=>Object.keys(c.deps??{}).length>0||Object.keys(c.devDependencies??{}).length>0),s=e.some(({manifest:c})=>(c.bindings??[]).length>0),o=v(t);if(!n&&!s&&!o||t.yes)return{ok:!0};const r=[];n&&r.push("add dependencies to package.json"),s&&r.push("write wrangler.jsonc bindings"),o&&r.push(`come from a custom registry source (${String(t.from??t.source)})`);const a=r.join(", ");return!process.stdin.isTTY&&t.confirm===void 0?(t.logger.error(`add: stdin is not a TTY and the requested items ${a} — re-run with --yes to confirm`),{ok:!1,reason:"non-interactive"}):await(t.confirm??q)(`The requested items ${a}. Continue?`)?{ok:!0}:(t.logger.info("add: aborted"),{ok:!1,reason:"declined"})},V="gh:anolilab/lunora/registry",_=/^[A-Za-z0-9][\w-]*$/u,_e=e=>{if(!_.test(e))throw new h("INTERNAL",`invalid registry item name "${e}" — names must match ${_.source} (letters, digits, "-", "_"; no path separators or "..")`)},Ae=e=>e.includes("..")?!1:e.startsWith("gh:")||e.startsWith("github:")||e.startsWith("https://"),ke=e=>v(e)&&e.from===void 0&&!e.allowUnsafeSource&&!Ae(e.source??""),ze=(e,t)=>ke(t)?`${e}: refusing --source ${String(t.source)} — only gh:, github:, or https:// sources are allowed (and may not contain ".."). Re-run with --allow-unsafe-source if you really want this.`:void 0,C=async(e,t,n)=>{const s=F(l(G(),`lunora-${t}-fetch-`)),o=l(s,t);n.info(`fetching ${e}`);try{const r=await W(e,{cwd:s,dir:o,force:!0,install:!1,silent:!0});return n.info(r.commit?`resolved ${r.source} @ ${r.commit}`:`resolved ${r.source}`),{cleanup:()=>{w(s,{force:!0,recursive:!0})},directory:o}}catch(r){throw w(s,{force:!0,recursive:!0}),r}},A=new WeakMap,x=async e=>{const t=A.get(e);if(t!==void 0)return t;const n=v(e)?Promise.resolve(D(e.ref)):ae(e.ref,e.logger);return A.set(e,n),n},je=async(e,t,n=V)=>{if(_e(e),t.from!==void 0){const o=l(t.from,e);if(!p(o))throw new h("INTERNAL",`registry item not found in local source: ${o}`);return{cleanup:()=>{},directory:o}}const s=t.source??n;return C(`${s}/${e}#${await x(t)}`,"item",t.logger)},He=async e=>{if(e.from!==void 0){if(!p(e.from))throw new h("INTERNAL",`registry root not found: ${e.from}`);return{cleanup:()=>{},root:e.from}}const t=e.source??V,{cleanup:n,directory:s}=await C(`${t}#${await x(e)}`,"registry",e.logger);return{cleanup:n,root:s}},Ne=(e,t)=>{const n=JSON.parse(g(l(e,"registry.json"),"utf8"));return Y(n,t)},Je=async(e,t)=>{const n=[],s=[],o=new Set,r=new Set,a=async i=>{if(o.has(i))return;if(r.has(i))throw new h("INTERNAL",`cyclic registry dependency detected at "${i}"`);r.add(i);const{cleanup:d,directory:c}=await je(i,t);s.push(d);const f=Ne(c,i);for(const u of f.requires??[])await a(u);r.delete(i),o.add(i),n.push({directory:c,manifest:f})};try{for(const i of e)await a(i)}catch(i){for(const d of s)d();throw i}return{cleanups:s,items:n}};export{ae as a,je as b,Ge as c,Fe as d,Je as e,Ue as f,We as g,He as h,v as i,he as j,Ne as k,le as l,ie as m,P as n,D as o,Me as r,ze as s};