@lunora/cli
Version:
The Lunora CLI: init, dev, deploy, codegen, migrate, seed, doctor, insights, logs, registry, and the rest of the project commands
16 lines (14 loc) • 10.9 kB
JavaScript
import{existsSync as h,readFileSync as v,writeFileSync as $,mkdirSync as b,lstatSync as M}from"node:fs";import{join as g,relative as F,dirname as k}from"@visulima/path";import{p as W,d as D,e as T,s as E,f as _,g as q,h as V,j as B,c as H,k as U}from"./resolve-B9ReA3FV.mjs";import{s as d,c as G,b as X}from"./catalog-Cbzph90w.mjs";import{findWranglerFile as Y,readWranglerJsonc as z}from"@lunora/config/cloudflare";import{LunoraError as R}from"@lunora/errors";import{insertSchemaExtension as Q}from"./insertSchemaExtension-DuiV6cba.mjs";import{createHash as Z}from"node:crypto";import{installArgsFor as K,detectPackageManager as ee}from"@lunora/config";const S=1,te=".lunora-registry.json",I=e=>g(e,"lunora",te),re=e=>typeof e!="object"||e===null||!("items"in e)?!1:typeof e.items=="object"&&e.items!==null,w=e=>Z("sha256").update(e).digest("hex"),ne=e=>{const t=I(e);if(!h(t))return{items:{},version:S};try{const n=JSON.parse(v(t,"utf8"));if(re(n))return{items:n.items,version:S}}catch{}return{items:{},version:S}},se=(e,t)=>{$(I(e),`${JSON.stringify(t,void 0,2)}
`,"utf8")},N=(e,t,n,o)=>{const r=e.items[t]??{files:{}};e.items[t]=r,r.files[n]=w(o)},oe=(e,t,n)=>e.items[t]?.files[n],A=3,L=e=>e===""?[]:e.split(`
`),ie=(e,t)=>{const n=L(e),o=L(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 c=[];for(let a=Math.max(0,r-A);a<r;a+=1)c.push(` ${n[a]??""}`);for(let a=r;a<s;a+=1)c.push(`- ${n[a]??""}`);for(let a=r;a<i;a+=1)c.push(`+ ${o[a]??""}`);for(let a=i;a<Math.min(o.length,i+A);a+=1)c.push(` ${o[a]??""}`);return c},ae=/\.[cm]?[jt]sx?$/u,j=(e,t,n,o)=>{const r=g(e,t.from);if(M(r).isSymbolicLink())throw new R("INTERNAL",`registry item "${o}": refusing to read "${t.from}" — it is a symlink, not a regular file`);const s=v(r,"utf8");return n&&ae.test(t.to)?T(s):s},ce=(e,t,n,o,r,s,i)=>{const c=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:c};const a=g(o,e.to);h(a)||(b(k(a),{recursive:!0}),$(a,j(n,e,i,t),"utf8"));const f=i?"lunorash/server":"@lunora/server",l=h(c)?v(c,"utf8"):`import { defineSchema } from "${f}";
export const schema = defineSchema({});
`,u=Q(l,t);if(u.ok)return b(k(c),{recursive:!0}),$(c,u.text,"utf8"),r.success(`merged .extend(${t}.extension) into lunora/schema.ts`),{kind:"written",path:c};if(u.reason==="already-applied")return r.warn(`lunora/schema.ts already extends "${t}" — skipping`),{kind:"skipped",path:c};throw u.reason==="invalid-identifier"?new R("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 R("INTERNAL",`schema-extension merge failed for "${t}": ${u.reason}`)},de=(e,t,n,o,r)=>{const s=ie(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}`)},ue=(e,t,n,o,r,s,i,c)=>{const a=g(o,e.to),f=j(n,e,c,t),l=h(a),u=l?v(a,"utf8"):"",m=J=>(b(k(a),{recursive:!0}),$(a,f,"utf8"),N(s,t,e.to,f),r.success(`${J}: ${e.to}`),{kind:"written",path:a});if(i.diff)return de(e,u,f,l,r),{kind:"skipped",path:a};if(!l)return m("write");const p=w(u);if(p===w(f))return N(s,t,e.to,f),r.warn(`skip (exists): ${e.to}`),{kind:"skipped",path:a};if(i.overwrite)return m("overwrite");const y=oe(s,t,e.to);return y===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:a}):y===p?m("update"):y===w(f)?(r.warn(`skip (locally edited): ${e.to}`),{kind:"skipped",path:a}):($(`${a}.new`,f,"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:a})},fe=(e,t,n,o,r,s,i={},c=!1)=>e.merge==="schema-extension"?ce(e,t,n,o,r,i.diff===!0,c):ue(e,t,n,o,r,s,i,c),O=["src/server.ts","src/server/index.ts","src/server/index.tsx","src/index.ts","src/worker.ts"],le=e=>{const t=Y(e);if(t===void 0)return;const{parsed:n}=z(t);return typeof n?.main=="string"?n.main:void 0},me=e=>{const t=le(e),n=t===void 0?O:[t,...O];for(const o of n){const r=g(e,o);if(!h(r))continue;const s=v(r,"utf8");if(!s.includes("createShardDO(")){if(o===t)break;continue}return{entryPath:r,main:o,source:s}}},pe=(e,t,n)=>{const o=F(k(e),g(t,"lunora",n)).replaceAll("\\","/");return o.startsWith(".")?o:`./${o}`},ge=(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},he=(e,t,n,o)=>{const r=[];for(const s of e){const i=pe(t,n,s.module),c=i.replaceAll(/[.*+?^${}()|[\]\\]/g,String.raw`\$&`);new RegExp(String.raw`export\s+\*\s+from\s+["']${c}\.js["']`,"u").test(o)||(s.comment&&r.push(`
// ${s.comment}`),r.push(`export * from "${i}.js";`))}return r},$e=(e,t,n,o)=>{if(e.length===0)return 0;const r=me(t);if(r===void 0)return ge(e,n);const s=he(e,r.entryPath,t,r.source);if(s.length===0)return 0;if(o){for(const c of s)c!==""&&n.info(`~ entrypoint: ${c}`);return s.length}const i=r.source.endsWith(`
`)?"":`
`;return $(r.entryPath,`${r.source}${i}${s.join(`
`)}
`,"utf8"),n.success(`wrote ${String(s.length)} entrypoint re-export(s) to ${F(t,r.entryPath)}`),s.length},P=(e,t,n,o={})=>{const r=[],s=[],i=[],c=[],a=ne(t),f=W(t);try{for(const{directory:l,manifest:u}of e){for(const p of u.files){const y=fe(p,u.name,l,t,n,a,o,f);(y.kind==="written"?r:s).push(y.path)}if(u.entrypointReexports!==void 0&&$e(u.entrypointReexports,t,n,o.diff===!0),o.diff)continue;const m=D(u,t,n,f);i.push(...m.deps),c.push(...m.bindings)}}finally{!o.diff&&Object.keys(a.items).length>0&&se(t,a)}return{bindings:c,deps:i,skipped:s,written:r}},x=()=>({bindings:[],code:0,deps:[],skipped:[],written:[]}),Oe=(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(c=>typeof c=="object"&&c!==null&&c[n.key]===n.value?{...c,[o]:r}:c)}})}:e,C=(e,t)=>{const n=t.title??t.description;e.info(`plan: ${d(t.name)}${n?` — ${d(n)}`:""}`);for(const s of t.files)e.info(` file ${d(s.to)} (${d(s.merge)})`);const o=new Map,r=s=>{const i=o.get(s);if(i!==void 0)return i;const c=B(s);return o.set(s,c),c};for(const[s,i]of Object.entries(t.deps??{}))e.info(` dep ${d(s)}@${d(r(i))}`);for(const[s,i]of Object.entries(t.devDependencies??{}))e.info(` dev ${d(s)}@${d(r(i))}`);for(const s of t.bindings??[])e.info(` bind ${d(s.path.join("."))} = ${d(JSON.stringify(s.value))}`);for(const s of t.envVars??[]){const i=s.secret?" (secret)":` = ${d(JSON.stringify(s.value??""))}`;e.info(` env ${d(s.name)}${i}`)}for(const s of t.entrypointReexports??[]){const i=`./lunora/${d(s.module)}`,c=s.comment?` // ${d(s.comment)}`:"";e.info(` entry ${i}${c}`)}},ye=e=>{const t=e.map(({manifest:n})=>({bindings:(n.bindings??[]).map(o=>({path:o.path.join("."),value:o.value})),deps:Object.keys(n.deps??{}),devDependencies:Object.keys(n.devDependencies??{}),entrypointReexports:(n.entrypointReexports??[]).map(o=>({module:o.module,...o.comment?{comment:o.comment}:{}})),envVars:(n.envVars??[]).map(o=>({name:o.name,...o.secret?{secret:!0}:{value:o.value??""}})),files:n.files.map(o=>({merge:o.merge,to:o.to})),name:n.name,requires:n.requires??[],title:n.title}));process.stdout.write(`${JSON.stringify({items:t},void 0,2)}
`)},ve=e=>{try{const{args:t,command:n}=K(ee(e));return`${n} ${t.join(" ")}`}catch{return"<your-package-manager> install"}},we=(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(` ${ve(s)} # install newly-added dependencies`);for(const{manifest:i}of e)i.docs&&r.info(`${d(i.name)}: ${d(i.docs)}`)},ke=async e=>{const t=x(),n=E("list",e);if(n)return e.logger.error(n),{...t,code:1};let o=()=>{};try{const r=await V(e);o=r.cleanup;const s=G(r.root);if(e.json)return process.stdout.write(`${JSON.stringify(s,void 0,2)}
`),t;e.logger.info(`available registry items (${String(s.length)}):`);for(const i of s)e.logger.info(` ${i.name}${i.description?` — ${i.description}`:""}`);return t}catch(r){return e.logger.error(d(`list failed: ${r instanceof Error?r.message:String(r)}`)),{...t,code:1}}finally{o()}},Pe=async e=>{const t=e.cwd??process.cwd(),n=x();if(e.list)return ke(e);if(e.names.length===0)return e.logger.error("add requires at least one item name. Usage: lunora registry add <name> [...names]"),{...n,code:1};const o=E("add",e);if(o)return e.logger.error(o),{...n,code:1};let r=[];try{const{cleanups:s,items:i}=await _(e.names,e);r=s;const{transformManifest:c}=e,a=c?i.map(p=>({...p,manifest:c(p.manifest)})):i;for(const{manifest:p}of a)C(e.logger,p);if(e.json&&ye(a),e.dryRun)return e.logger.info("dry-run: stopping before any files are written"),n;if(e.diff)return P(a,t,e.logger,{diff:!0}),e.logger.info("diff: preview only — re-run without --diff to apply"),n;if(!await q(a,e))return{...n,code:1};const{bindings:f,deps:l,skipped:u,written:m}=P(a,t,e.logger,{overwrite:e.overwrite});return we(a,l,m.length,u.length,e.logger,t),{bindings:f,code:0,deps:l,skipped:u,written:m}}catch(s){return e.logger.error(d(`add failed: ${s instanceof Error?s.message:String(s)}`)),{...n,code:1}}finally{for(const s of r)s()}},Fe=async e=>{const t=x();if(e.names.length===0)return e.logger.error("view requires an item name. Usage: lunora registry view <name>"),{...t,code:1};const n=E("view",e);if(n)return e.logger.error(n),{...t,code:1};const o=[];try{for(const r of e.names){const{cleanup:s,directory:i}=await H(r,e);o.push(s);const c=U(i,r);C(e.logger,c);for(const a of c.files){e.logger.info(`--- ${d(a.to)} (${d(a.merge)}) ---`);const f=j(i,a,!1,r);for(const l of f.split(`
`))e.logger.info(d(l))}}return t}catch(r){return e.logger.error(d(`view failed: ${r instanceof Error?r.message:String(r)}`)),{...t,code:1}}finally{for(const r of o)r()}},Ie=async e=>{const t=x(),n=e.from;if(n===void 0)return e.logger.error("registry build requires --from <registry root>"),{...t,code:1};if(!h(n))return e.logger.error(`registry root not found: ${n}`),{...t,code:1};const o=X(n),r=e.out??g(n,"index.json");if(e.check){const s=h(r)?JSON.parse(v(r,"utf8")):{items:[]};return JSON.stringify(s.items??[])!==JSON.stringify(o.items)?(e.logger.error(`registry: ${r} is stale — run \`lunora registry build\` to regenerate it`),{...t,code:1}):(e.logger.success(`registry: ${r} is up to date (${String(o.items.length)} items)`),t)}return $(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{Ie as a,Fe as b,Pe as r,Oe as s};