UNPKG

@lunora/cli

Version:

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

14 lines (12 loc) 11.3 kB
import{existsSync as $,readFileSync as k,writeFileSync as y,mkdirSync as R,lstatSync as _}from"node:fs";import{join as g,relative as T,dirname as b}from"@visulima/path";import{EXIT_CODE as m}from"./EXIT_CODE-08cwt3MK.mjs";import{p as V,d as M,e as q,s as j,f as B,g as J,h as H,j as X,k as P,c as Y,l as z}from"./resolve-DUCSc7jQ.mjs";import{s as u,c as Q,b as Z}from"./catalog-Cbzph90w.mjs";import{findWranglerFile as K,readWranglerJsonc as ee}from"@lunora/config/cloudflare";import{LunoraError as N}from"@lunora/errors";import{insertSchemaExtension as re}from"./insertSchemaExtension-DuiV6cba.mjs";import{createHash as te}from"node:crypto";import{installArgsFor as ne,detectPackageManager as se}from"@lunora/config";const A=1,oe=".lunora-registry.json",G=e=>g(e,"lunora",oe),ie=e=>typeof e!="object"||e===null||!("items"in e)?!1:typeof e.items=="object"&&e.items!==null,x=e=>te("sha256").update(e).digest("hex"),ae=e=>{const t=G(e);if(!$(t))return{items:{},version:A};try{const n=JSON.parse(k(t,"utf8"));if(ie(n))return{items:n.items,version:A}}catch{}return{items:{},version:A}},ce=(e,t)=>{y(G(e),`${JSON.stringify(t,void 0,2)} `,"utf8")},C=(e,t,n,o)=>{const r=e.items[t]??{files:{}};e.items[t]=r,r.files[n]=x(o)},de=(e,t,n)=>e.items[t]?.files[n],F=3,I=e=>e===""?[]:e.split(` `),ue=(e,t)=>{const n=I(e),o=I(t);let r=0;for(;r<n.length&&r<o.length&&n[r]===o[r];)r+=1;let s=n.length,i=o.length;for(;s>r&&i>r&&n[s-1]===o[i-1];)s-=1,i-=1;if(r===s&&r===i)return[];const a=[];for(let c=Math.max(0,r-F);c<r;c+=1)a.push(` ${n[c]??""}`);for(let c=r;c<s;c+=1)a.push(`- ${n[c]??""}`);for(let c=r;c<i;c+=1)a.push(`+ ${o[c]??""}`);for(let c=i;c<Math.min(o.length,i+F);c+=1)a.push(` ${o[c]??""}`);return a},fe=/\.[cm]?[jt]sx?$/u,L=(e,t,n,o)=>{const r=g(e,t.from);if(_(r).isSymbolicLink())throw new N("INTERNAL",`registry item "${o}": refusing to read "${t.from}" — it is a symlink, not a regular file`);const s=k(r,"utf8");return n&&fe.test(t.to)?q(s):s},le=e=>`import { defineSchema } from "${e?"lunorash/server":"@lunora/server"}"; export default defineSchema({}); `,me=(e,t,n,o,r,s,i)=>{const a=g(o,"lunora","schema.ts");if(s)return r.info(`~ would merge .extend(${t}.extension) into lunora/schema.ts (and create ${e.to} if absent)`),{kind:"skipped",path:a};const c=g(o,e.to);$(c)||(R(b(c),{recursive:!0}),y(c,L(n,e,i,t),"utf8"));const d=$(a)?k(a,"utf8"):le(i),f=re(d,t);if(f.ok)return R(b(a),{recursive:!0}),y(a,f.text,"utf8"),r.success(`merged .extend(${t}.extension) into lunora/schema.ts`),{kind:"written",path:a};if(f.reason==="already-applied")return r.warn(`lunora/schema.ts already extends "${t}" — skipping`),{kind:"skipped",path:a};throw f.reason==="invalid-identifier"?new N("INTERNAL",`schema-extension item "${t}" is not a valid JS identifier — it is spliced into lunora/schema.ts as \`import { ${t} }\` / \`.extend(${t}.extension)\`. Rename the item to a valid identifier (no leading digit, no "-").`):new N("INTERNAL",`schema-extension merge failed for "${t}": ${f.reason}`)},pe=(e,t,n,o,r)=>{const s=ue(t,n);if(s.length===0){r.info(`= ${e.to} (unchanged)`);return}r.info(`${o?"~":"+"} ${e.to}`);for(const i of s)r.info(` ${i}`)},he=(e,t,n,o,r,s,i,a)=>{const c=g(o,e.to),d=L(n,e,a,t),f=$(c),l=f?k(c,"utf8"):"",p=S=>(R(b(c),{recursive:!0}),y(c,d,"utf8"),C(s,t,e.to,d),r.success(`${S}: ${e.to}`),{kind:"written",path:c});if(i.diff)return pe(e,l,d,f,r),{kind:"skipped",path:c};if(!f)return p("write");const v=x(l);if(v===x(d))return C(s,t,e.to,d),r.warn(`skip (exists): ${e.to}`),{kind:"skipped",path:c};if(i.overwrite)return p("overwrite");const h=de(s,t,e.to);return h===void 0?(r.warn(`skip (exists, untracked): ${e.to} — refusing to overwrite a file lunora didn't add (use --overwrite to force)`),{kind:"skipped",path:c}):h===v?p("update"):h===x(d)?(r.warn(`skip (locally edited): ${e.to}`),{kind:"skipped",path:c}):(y(`${c}.new`,d,"utf8"),r.warn(`conflict: ${e.to} has local edits and an upstream update — wrote ${e.to}.new (use --overwrite to take theirs)`),{kind:"skipped",path:c})},ge=(e,t,n,o,r,s,i={},a=!1)=>e.merge==="schema-extension"?me(e,t,n,o,r,i.diff===!0,a):he(e,t,n,o,r,s,i,a),U=["src/server.ts","src/server/index.ts","src/server/index.tsx","src/index.ts","src/worker.ts"],$e=e=>{const t=K(e);if(t===void 0)return;const{parsed:n}=ee(t);return typeof n?.main=="string"?n.main:void 0},ye=e=>{const t=$e(e),n=t===void 0?U:[t,...U];for(const o of n){const r=g(e,o);if(!$(r))continue;const s=k(r,"utf8");if(!s.includes("createShardDO(")){if(o===t)break;continue}return{entryPath:r,main:o,source:s}}},ve=(e,t,n)=>{const o=T(b(e),g(t,"lunora",n)).replaceAll("\\","/");return o.startsWith(".")?o:`./${o}`},we=(e,t)=>{for(const n of e){const r=`Add \`export * from "${`./lunora/${n.module}.js`}"\` to your worker entry`,s=n.comment?` (${n.comment})`:"";t.warn(`${r}${s}`)}return 0},ke=(e,t,n,o)=>{const r=[];for(const s of e){const i=ve(t,n,s.module),a=i.replaceAll(/[.*+?^${}()|[\]\\]/g,String.raw`\$&`);new RegExp(String.raw`export\s+\*\s+from\s+["']${a}\.js["']`,"u").test(o)||(s.comment&&r.push(` // ${s.comment}`),r.push(`export * from "${i}.js";`))}return r},Se=(e,t,n,o)=>{if(e.length===0)return 0;const r=ye(t);if(r===void 0)return we(e,n);const s=ke(e,r.entryPath,t,r.source);if(s.length===0)return 0;if(o){for(const a of s)a!==""&&n.info(`~ entrypoint: ${a}`);return s.length}const i=r.source.endsWith(` `)?"":` `;return y(r.entryPath,`${r.source}${i}${s.join(` `)} `,"utf8"),n.success(`wrote ${String(s.length)} entrypoint re-export(s) to ${T(t,r.entryPath)}`),s.length},D=(e,t,n,o={})=>{const r=[],s=[],i=[],a=[],c=ae(t),d=V(t);try{for(const{directory:f,manifest:l}of e){for(const v of l.files){const h=ge(v,l.name,f,t,n,c,o,d);(h.kind==="written"?r:s).push(h.path)}if(l.entrypointReexports!==void 0&&Se(l.entrypointReexports,t,n,o.diff===!0),o.diff)continue;const p=M(l,t,n,d,o.pinnedVersions);i.push(...p.deps),a.push(...p.bindings)}}finally{!o.diff&&Object.keys(c.items).length>0&&ce(t,c)}return{bindings:a,deps:i,skipped:s,written:r}},E=()=>({bindings:[],code:0,deps:[],skipped:[],written:[]}),De=(e,t,n,o,r)=>e.bindings?{...e,bindings:e.bindings.map(s=>{if(s.path[0]!==t||!Array.isArray(s.value))return s;const i=s.value;return{...s,value:i.map(a=>typeof a=="object"&&a!==null&&a[n.key]===n.value?{...a,[o]:r}:a)}})}:e,W=(e,t,n)=>{const o=t.title??t.description;e.info(`plan: ${u(t.name)}${o?` — ${u(o)}`:""}`);for(const r of t.files)e.info(` file ${u(r.to)} (${u(r.merge)})`);for(const[r,s]of Object.entries(t.deps??{}))e.info(` dep ${u(r)}@${u(P(s,n,r))}`);for(const[r,s]of Object.entries(t.devDependencies??{}))e.info(` dev ${u(r)}@${u(P(s,n,r))}`);for(const r of t.bindings??[])e.info(` bind ${u(r.path.join("."))} = ${u(JSON.stringify(r.value))}`);for(const r of t.envVars??[]){const s=r.secret?" (secret)":` = ${u(JSON.stringify(r.value??""))}`;e.info(` env ${u(r.name)}${s}`)}for(const r of t.entrypointReexports??[]){const s=`./lunora/${u(r.module)}`,i=r.comment?` // ${u(r.comment)}`:"";e.info(` entry ${s}${i}`)}},xe=e=>e.map(({manifest:t})=>({bindings:(t.bindings??[]).map(n=>({path:n.path.join("."),value:n.value})),deps:Object.keys(t.deps??{}),devDependencies:Object.keys(t.devDependencies??{}),entrypointReexports:(t.entrypointReexports??[]).map(n=>({module:n.module,...n.comment?{comment:n.comment}:{}})),envVars:(t.envVars??[]).map(n=>({name:n.name,...n.secret?{secret:!0}:{value:n.value??""}})),files:t.files.map(n=>({merge:n.merge,to:n.to})),name:t.name,requires:t.requires??[],title:t.title})),be=e=>{try{const{args:t,command:n}=ne(se(e));return`${n} ${t.join(" ")}`}catch{return"<your-package-manager> install"}},Ee=(e,t,n,o,r,s)=>{r.success(`add complete: ${String(n)} written, ${String(o)} skipped`),r.info("next steps:"),r.info(" lunora codegen # regenerate _generated/ so the new tables/functions appear"),t.length>0&&r.info(` ${be(s)} # install newly-added dependencies`);for(const{manifest:i}of e)i.docs&&r.info(`${u(i.name)}: ${u(i.docs)}`)},Ae=async e=>{const t=E(),n=j("list",e);if(n)return e.logger.error(n),{...t,code:m.USAGE,error:n};const{logger:o}=e;let r=()=>{};try{const s=await X(e);r=s.cleanup;const i=Q(s.root);if(e.format==="json")return{...t,data:{items:i}};o.info(`available registry items (${String(i.length)}):`);for(const a of i)o.info(` ${a.name}${a.description?` — ${a.description}`:""}`);return t}catch(s){const i=u(`list failed: ${s instanceof Error?s.message:String(s)}`);return o.error(i),{...t,code:1,error:i}}finally{r()}},Te=async e=>{const t=e.cwd??process.cwd(),n=E();if(e.list)return Ae(e);const{logger:o}=e;if(e.names.length===0){const i="add requires at least one item name. Usage: lunora registry add <name> [...names]";return o.error(i),{...n,code:m.USAGE,error:i}}const r=j("add",e);if(r)return o.error(r),{...n,code:m.USAGE,error:r};let s=[];try{const{cleanups:i,items:a}=await B(e.names,e);s=i;const{transformManifest:c}=e,d=c?a.map(w=>({...w,manifest:c(w.manifest)})):a,f=await J(d.map(w=>w.manifest));for(const{manifest:w}of d)W(o,w,f);const l={items:xe(d)};if(e.dryRun)return o.info("dry-run: stopping before any files are written"),{...n,data:l};if(e.diff)return D(d,t,o,{diff:!0}),o.info("diff: preview only — re-run without --diff to apply"),{...n,data:l};const p=await H(d,e);if(!p.ok)return p.reason==="declined"?{...n,code:m.CANCELLED,data:l,error:"registry add: the package.json change was declined"}:{...n,code:m.USAGE,data:l,error:"registry add: stdin is not a TTY — re-run with --yes to confirm"};const{bindings:v,deps:h,skipped:S,written:O}=D(d,t,o,{overwrite:e.overwrite,pinnedVersions:f});return Ee(d,h,O.length,S.length,o,t),{bindings:v,code:0,data:l,deps:h,skipped:S,written:O}}catch(i){const a=u(`add failed: ${i instanceof Error?i.message:String(i)}`);return o.error(a),{...n,code:1,error:a}}finally{for(const i of s)i()}},Je=async e=>{const t=E();if(e.names.length===0){const r="view requires an item name. Usage: lunora registry view <name>";return e.logger.error(r),{...t,code:m.USAGE,error:r}}const n=j("view",e);if(n)return e.logger.error(n),{...t,code:m.USAGE,error:n};const o=[];try{for(const r of e.names){const{cleanup:s,directory:i}=await Y(r,e);o.push(s);const a=z(i,r);W(e.logger,a,await J([a]));for(const c of a.files){e.logger.info(`--- ${u(c.to)} (${u(c.merge)}) ---`);const d=L(i,c,!1,r);for(const f of d.split(` `))e.logger.info(u(f))}}return t}catch(r){return e.logger.error(u(`view failed: ${r instanceof Error?r.message:String(r)}`)),{...t,code:1}}finally{for(const r of o)r()}},Ge=async e=>{const t=E(),n=e.from;if(n===void 0){const s="registry build requires --from <registry root>";return e.logger.error(s),{...t,code:m.USAGE,error:s}}if(!$(n)){const s=`registry root not found: ${n}`;return e.logger.error(s),{...t,code:m.NOT_FOUND,error:s}}const o=Z(n),r=e.out??g(n,"index.json");if(e.check){const s=$(r)?JSON.parse(k(r,"utf8")):{items:[]};if(JSON.stringify(s.items??[])!==JSON.stringify(o.items)){const a=`registry: ${r} is stale — run \`lunora registry build\` to regenerate it`;return e.logger.error(a),{...t,code:m.USAGE,error:a}}return e.logger.success(`registry: ${r} is up to date (${String(o.items.length)} items)`),t}return y(r,`${JSON.stringify({$schema:"./schema/registry.schema.json",...o},void 0,4)} `,"utf8"),e.logger.success(`registry: wrote ${r} (${String(o.items.length)} items)`),t};export{Ge as a,Je as b,Te as r,De as s};