UNPKG

@lunora/cli

Version:

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

20 lines (18 loc) 20.3 kB
import{existsSync as p,readFileSync as g,writeFileSync as $,mkdirSync as j,mkdtempSync as oe,rmSync as O}from"node:fs";import{dirname as w,join as l,relative as P}from"@visulima/path";import{p as se,l as ie}from"./detect-package-manager-DXDstphE.mjs";import{DEV_VARS_FILE as ae,parseDevVariableEntries as ce}from"@lunora/config";import{modify as W,applyEdits as U,parse as ue}from"jsonc-parser";import{fileURLToPath as de}from"node:url";import{LunoraError as y}from"@lunora/errors";import{o as fe}from"./tui-prompts-BU3irGxV.mjs";import{collectCatalog as le,buildRegistryIndex as pe}from"./buildRegistryIndex-DwySASBu.mjs";import{findWranglerFile as ge,readWranglerJsonc as me}from"@lunora/config/cloudflare";import{insertSchemaExtension as $e}from"./insertSchemaExtension-DZReBZ4_.mjs";import{createHash as he}from"node:crypto";import{tmpdir as ye}from"node:os";import{downloadTemplate as ve}from"giget";import we from"./parseManifest-x3WsxKHz.mjs";const be="alpha",z="main",ke=new Set(["alpha","beta","next"]),Se=/^[\w./@-]+$/,je=e=>!e.includes("..")&&Se.test(e),M=()=>{try{let e=w(de(import.meta.url));for(let t=0;t<6;t+=1){const r=l(e,"package.json");if(p(r)){const o=JSON.parse(g(r,"utf8"));if(o.name==="@lunora/cli"&&typeof o.version=="string")return o.version}const n=w(e);if(n===e)break;e=n}}catch{}return"0.0.0"},V=e=>{if(e==="0.0.0")return be;const t=e.split("+")[0]??e,r=t.indexOf("-");if(r!==-1){const[n]=t.slice(r+1).split(".");if(n!==void 0&&ke.has(n))return n}return z},B=e=>{if(e!==void 0&&e.length>0){if(!je(e))throw new y("INTERNAL",`invalid --ref "${e}" — a ref may contain letters, digits, ".", "_", "-", "/", "@" and must not contain "..".`);return e}return V(M())},x="anolilab/lunora",F=/^[0-9a-f]{40}$/iu,H=String.raw`\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)+)?(?:\+[0-9A-Za-z.-]+)?`,xe=new RegExp(String.raw`^v?${H}$`,"u"),Ne=new RegExp(String.raw`^(?:@[\w.-]+\/)?[\w.-]+@${H}$`,"u"),Oe=e=>F.test(e)||xe.test(e)||Ne.test(e),Ae=()=>{const e=process.env.GITHUB_TOKEN??process.env.GH_TOKEN;return e!==void 0&&e.length>0?{authorization:`Bearer ${e}`}:{}},Ee=async e=>{try{const t=await fetch(`https://api.github.com/repos/${x}/commits/${encodeURIComponent(e)}`,{headers:{accept:"application/vnd.github+json","user-agent":"lunora-cli",...Ae()},signal:AbortSignal.timeout(1e4)});if(!t.ok)return;const r=await t.json();return typeof r.sha=="string"&&F.test(r.sha)?r.sha:void 0}catch{return}},Re=async(e,t)=>{const r=B(e);if(Oe(r))return r;const n=await Ee(r);return n===void 0?(t.warn(`could not pin ${x}#${r} to a commit — fetching the UNPINNED branch (set GITHUB_TOKEN if rate-limited).`),r):(t.info(`pinned ${x}#${r}${n}`),n)},Te="latest",_e=(e=M())=>{const t=V(e);return t===z?Te:t},Je="https://registry.npmjs.org",De=()=>{const e=process.env.npm_config_registry,t=e!==void 0&&e.length>0?e:Je;return t.endsWith("/")?t.slice(0,-1):t},Ie=async(e,t)=>{try{const r=await fetch(`${De()}/${e.replaceAll("/","%2F")}`,{headers:{accept:"application/vnd.npm.install-v1+json"},signal:AbortSignal.timeout(1e4)});return r.ok?(await r.json())["dist-tags"]?.[t]:void 0}catch{return}},Lt=async(e,t)=>{const r=new Map;return await Promise.all([...new Set(e)].map(async n=>{const o=await Ie(n,t);o!==void 0&&r.set(n,o)})),r},C=e=>{if(!e.startsWith("workspace:"))return e;const t=e.slice(10);return t===""||t==="*"||t==="^"||t==="~"?_e():t},Le=new Set(["@lunora/client","@lunora/do","@lunora/ratelimit","@lunora/runtime","@lunora/server","@lunora/values"]),qe=/(['"])@lunora\/(client|do|ratelimit|runtime|server|values)(\/[^'"]*)?\1/gu,Pe=e=>{const t=l(e,"package.json");if(!p(t))return!1;try{const r=JSON.parse(g(t,"utf8"));return r.dependencies?.lunorash!==void 0||r.devDependencies?.lunorash!==void 0}catch{return!1}},We=e=>e.replaceAll(qe,(t,r,n,o)=>`${r}lunorash/${n}${o??""}${r}`),A=(e,t,r,n="dependencies",o=!1)=>{const s=Object.entries(e);if(s.length===0)return[];const i=l(t,"package.json");if(!p(i))return r.warn(`package.json not found at ${i} — skipping dependency updates`),[];let a=g(i,"utf8");const c=JSON.parse(a),u=[];for(const[d,f]of s){if(o&&Le.has(d)){r.info(`dep provided by the lunorash umbrella, skipping: ${d}`);continue}if(c.dependencies?.[d]!==void 0||c.devDependencies?.[d]!==void 0){r.info(`dep already present: ${d}`);continue}const m=W(a,[n,d],C(f),{formattingOptions:{insertSpaces:!0,tabSize:4}});a=U(a,m),u.push(d)}return u.length>0&&($(i,a,"utf8"),r.success(`added ${String(u.length)} ${n==="devDependencies"?"devDependency(ies)":"dependency(ies)"} to package.json: ${u.join(", ")}`)),u},Ue=(e,t,r)=>{if(e.length===0)return[];const n=l(t,ae),o=p(n)?g(n,"utf8"):"",s=new Set(ce(o).map(u=>u.key)),i=[],a=[],c=[];for(const u of e)u.secret&&a.push(u.name),!s.has(u.name)&&(u.description&&c.push(`# ${u.description}`),c.push(`${u.name}=${u.secret?"":u.value??""}`),i.push(u.name));if(i.length>0){const u=o===""||o.endsWith(` `)?o:`${o} `;$(n,`${u}${c.join(` `)} `,"utf8"),r.success(`scaffolded ${String(i.length)} env var(s) into .dev.vars: ${i.join(", ")}`)}return a.length>0&&r.info(`set secret value(s) locally in .dev.vars, then for production: ${a.map(u=>`wrangler secret put ${u}`).join("; ")}`),i},E=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"]),ze=["binding","name","queue","pattern"],R=e=>{if(!(typeof e!="object"||e===null))for(const t of ze){const r=e[t];if(typeof r=="string"&&r.length>0)return`${t}:${r}`}},Me=(e,t,r,n)=>{const o=new Set(e.map(a=>JSON.stringify(a))),s=new Set(e.map(a=>R(a)).filter(a=>a!==void 0)),i=[];for(const a of t){if(o.has(JSON.stringify(a)))continue;const c=R(a);if(c!==void 0&&s.has(c)){n.warn(`${c.replace(":"," ")} already exists in ${r} — keeping the project's entry and skipping the registry item's. Reconcile by hand if the item needs different settings.`);continue}c!==void 0&&s.add(c),i.push(a)}return i},T=e=>Array.isArray(e),Ve=(e,t)=>{let r=ue(e);for(const n of t){if(typeof r!="object"||r===null)return;r=r[n]}return r},Be=(e,t)=>{const r=e[0];return r!==void 0&&E.has(r)?!0:(t.warn(`skipping binding "${e.join(".")}" — only resource bindings (${[...E].join(", ")}) may be written, not exec/entrypoint keys`),!1)},G=Symbol("skip-binding"),Fe=(e,t,r)=>{const{value:n}=t;if(!T(n))return n;const o=Ve(e,t.path);if(!T(o))return n;const s=Me(o,n,t.path.join("."),r);return s.length===0?G:[...o,...s]},He=(e,t,r)=>{if(e.length===0)return[];const n=["wrangler.jsonc","wrangler.json"].map(i=>l(t,i)).find(i=>p(i));if(!n)return r.warn("wrangler.jsonc not found — skipping binding updates"),[];let o=g(n,"utf8");const s=[];for(const i of e){if(!Be(i.path,r))continue;const a=Fe(o,i,r);if(a===G)continue;const c=W(o,[...i.path],a,{formattingOptions:{insertSpaces:!0,tabSize:4}});c.length!==0&&(o=U(o,c),s.push(i.path.join(".")))}return s.length>0&&($(n,o,"utf8"),r.success(`applied ${String(s.length)} binding(s) to ${n}: ${s.join(", ")}`)),s},Ce=(e,t,r,n=!1)=>{const o=[],s=[];return e.deps&&o.push(...A(e.deps,t,r,"dependencies",n)),e.devDependencies&&o.push(...A(e.devDependencies,t,r,"devDependencies",n)),e.bindings&&s.push(...He(e.bindings,t,r)),e.envVars&&Ue(e.envVars,t,r),{bindings:s,deps:o}},Ge=async(e,t)=>{const r=e.some(({manifest:c})=>Object.keys(c.deps??{}).length>0||Object.keys(c.devDependencies??{}).length>0),n=e.some(({manifest:c})=>(c.bindings??[]).length>0),o=t.source!==void 0&&t.source.length>0;if(!r&&!n&&!o||t.yes)return!0;const s=[];r&&s.push("add dependencies to package.json"),n&&s.push("write wrangler.jsonc bindings"),o&&s.push(`come from a non-default source (${String(t.source)})`);const i=s.join(", ");if(!process.stdin.isTTY&&t.confirm===void 0)return t.logger.error(`add: stdin is not a TTY and the requested items ${i} — re-run with --yes to confirm`),!1;const a=await(t.confirm??fe)(`The requested items ${i}. Continue?`);return a||t.logger.info("add: aborted"),a},S=1,Ze=".lunora-registry.json",Z=e=>l(e,"lunora",Ze),Ke=e=>typeof e!="object"||e===null||!("items"in e)?!1:typeof e.items=="object"&&e.items!==null,b=e=>he("sha256").update(e).digest("hex"),Ye=e=>{const t=Z(e);if(!p(t))return{items:{},version:S};try{const r=JSON.parse(g(t,"utf8"));if(Ke(r))return{items:r.items,version:S}}catch{}return{items:{},version:S}},Qe=(e,t)=>{$(Z(e),`${JSON.stringify(t,void 0,2)} `,"utf8")},_=(e,t,r,n)=>{const o=e.items[t]??{files:{}};e.items[t]=o,o.files[r]=b(n)},Xe=(e,t,r)=>e.items[t]?.files[r],J=e=>e===""?[]:e.split(` `),et=(e,t)=>{const r=J(e),n=J(t);let o=0;for(;o<r.length&&o<n.length&&r[o]===n[o];)o+=1;let s=r.length,i=n.length;for(;s>o&&i>o&&r[s-1]===n[i-1];)s-=1,i-=1;if(o===s&&o===i)return[];const a=[];for(let c=Math.max(0,o-3);c<o;c+=1)a.push(` ${r[c]??""}`);for(let c=o;c<s;c+=1)a.push(`- ${r[c]??""}`);for(let c=o;c<i;c+=1)a.push(`+ ${n[c]??""}`);for(let c=i;c<Math.min(n.length,i+3);c+=1)a.push(` ${n[c]??""}`);return a},tt=/\.[cm]?[jt]sx?$/u,K=(e,t,r)=>{const n=g(l(e,t.from),"utf8");return r&&tt.test(t.to)?We(n):n},rt=(e,t,r,n,o,s,i)=>{const a=l(n,"lunora","schema.ts");if(s)return o.info(`~ would merge .extend(${t}.extension) into lunora/schema.ts (and create ${e.to} if absent)`),{kind:"skipped",path:a};const c=l(n,e.to);p(c)||(j(w(c),{recursive:!0}),$(c,K(r,e,i),"utf8"));const u=i?"lunorash/server":"@lunora/server",d=p(a)?g(a,"utf8"):`import { defineSchema } from "${u}"; export const schema = defineSchema({}); `,f=$e(d,t);if(f.ok)return j(w(a),{recursive:!0}),$(a,f.text,"utf8"),o.success(`merged .extend(${t}.extension) into lunora/schema.ts`),{kind:"written",path:a};if(f.reason==="already-applied")return o.warn(`lunora/schema.ts already extends "${t}" — skipping`),{kind:"skipped",path:a};throw f.reason==="invalid-identifier"?new y("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 y("INTERNAL",`schema-extension merge failed for "${t}": ${f.reason}`)},nt=(e,t,r,n,o)=>{const s=et(t,r);if(s.length===0){o.info(`= ${e.to} (unchanged)`);return}o.info(`${n?"~":"+"} ${e.to}`);for(const i of s)o.info(` ${i}`)},ot=(e,t,r,n,o,s,i,a)=>{const c=l(n,e.to),u=K(r,e,a),d=p(c),f=d?g(c,"utf8"):"",m=ne=>(j(w(c),{recursive:!0}),$(c,u,"utf8"),_(s,t,e.to,u),o.success(`${ne}: ${e.to}`),{kind:"written",path:c});if(i.diff)return nt(e,f,u,d,o),{kind:"skipped",path:c};if(!d)return m("write");const h=b(f);if(h===b(u))return _(s,t,e.to,u),o.warn(`skip (exists): ${e.to}`),{kind:"skipped",path:c};if(i.overwrite)return m("overwrite");const v=Xe(s,t,e.to);return v===void 0?(o.warn(`skip (exists, untracked): ${e.to} — refusing to overwrite a file lunora didn't add (use --overwrite to force)`),{kind:"skipped",path:c}):v===h?m("update"):v===b(u)?(o.warn(`skip (locally edited): ${e.to}`),{kind:"skipped",path:c}):($(`${c}.new`,u,"utf8"),o.warn(`conflict: ${e.to} has local edits and an upstream update — wrote ${e.to}.new (use --overwrite to take theirs)`),{kind:"skipped",path:c})},st=(e,t,r,n,o,s,i={},a=!1)=>e.merge==="schema-extension"?rt(e,t,r,n,o,i.diff===!0,a):ot(e,t,r,n,o,s,i,a),D=["src/server.ts","src/server/index.ts","src/server/index.tsx","src/index.ts","src/worker.ts"],it=e=>{const t=ge(e);if(t===void 0)return;const{parsed:r}=me(t);return typeof r?.main=="string"?r.main:void 0},at=e=>{const t=it(e),r=t===void 0?D:[t,...D];for(const n of r){const o=l(e,n);if(!p(o))continue;const s=g(o,"utf8");if(!s.includes("createShardDO(")){if(n===t)break;continue}return{entryPath:o,main:n,source:s}}},ct=(e,t,r)=>{const n=P(w(e),l(t,"lunora",r)).replaceAll("\\","/");return n.startsWith(".")?n:`./${n}`},ut=(e,t)=>{for(const r of e){const n=`Add \`export * from "${`./lunora/${r.module}.js`}"\` to your worker entry`,o=r.comment?` (${r.comment})`:"";t.warn(`${n}${o}`)}return 0},dt=(e,t,r,n)=>{const o=[];for(const s of e){const i=ct(t,r,s.module),a=i.replaceAll(/[.*+?^${}()|[\]\\]/g,String.raw`\$&`);new RegExp(String.raw`export\s+\*\s+from\s+["']${a}\.js["']`,"u").test(n)||(s.comment&&o.push(` // ${s.comment}`),o.push(`export * from "${i}.js";`))}return o},ft=(e,t,r,n)=>{if(e.length===0)return 0;const o=at(t);if(o===void 0)return ut(e,r);const s=dt(e,o.entryPath,t,o.source);if(s.length===0)return 0;if(n){for(const a of s)a!==""&&r.info(`~ entrypoint: ${a}`);return s.length}const i=o.source.endsWith(` `)?"":` `;return $(o.entryPath,`${o.source}${i}${s.join(` `)} `,"utf8"),r.success(`wrote ${String(s.length)} entrypoint re-export(s) to ${P(t,o.entryPath)}`),s.length},I=(e,t,r,n={})=>{const o=[],s=[],i=[],a=[],c=Ye(t),u=Pe(t);for(const{directory:d,manifest:f}of e){for(const h of f.files){const v=st(h,f.name,d,t,r,c,n,u);(v.kind==="written"?o:s).push(v.path)}if(f.entrypointReexports!==void 0&&ft(f.entrypointReexports,t,r,n.diff===!0),n.diff)continue;const m=Ce(f,t,r,u);i.push(...m.deps),a.push(...m.bindings)}return!n.diff&&Object.keys(c.items).length>0&&Qe(t,c),{bindings:a,deps:i,skipped:s,written:o}},Y="gh:anolilab/lunora/registry",L=/^[A-Za-z0-9][\w-]*$/u,lt=e=>{if(!L.test(e))throw new y("INTERNAL",`invalid registry item name "${e}" — names must match ${L.source} (letters, digits, "-", "_"; no path separators or "..")`)},pt=e=>e.includes("..")?!1:e.startsWith("gh:")||e.startsWith("github:")||e.startsWith("https://"),gt=e=>e.from===void 0&&e.source!==void 0&&e.source.length>0&&!e.allowUnsafeSource&&!pt(e.source),N=(e,t)=>gt(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,Q=async(e,t,r)=>{const n=oe(l(ye(),`lunora-${t}-fetch-`)),o=l(n,t);r.info(`fetching ${e}`);try{const s=await ve(e,{cwd:n,dir:o,force:!0,install:!1,silent:!0});return r.info(s.commit?`resolved ${s.source} @ ${s.commit}`:`resolved ${s.source}`),{cleanup:()=>{O(n,{force:!0,recursive:!0})},directory:o}}catch(s){throw O(n,{force:!0,recursive:!0}),s}},q=new WeakMap,X=async e=>{const t=q.get(e);if(t!==void 0)return t;const r=e.source!==void 0&&e.source.length>0?Promise.resolve(B(e.ref)):Re(e.ref,e.logger);return q.set(e,r),r},ee=async(e,t)=>{if(lt(e),t.from!==void 0){const n=l(t.from,e);if(!p(n))throw new y("INTERNAL",`registry item not found in local source: ${n}`);return{cleanup:()=>{},directory:n}}const r=t.source??Y;return Q(`${r}/${e}#${await X(t)}`,"item",t.logger)},mt=async e=>{if(e.from!==void 0){if(!p(e.from))throw new y("INTERNAL",`registry root not found: ${e.from}`);return{cleanup:()=>{},root:e.from}}const t=e.source??Y,{cleanup:r,directory:n}=await Q(`${t}#${await X(e)}`,"registry",e.logger);return{cleanup:r,root:n}},te=(e,t)=>{const r=JSON.parse(g(l(e,"registry.json"),"utf8"));return we(r,t)},$t=async(e,t)=>{const r=[],n=[],o=new Set,s=new Set,i=async a=>{if(o.has(a))return;if(s.has(a))throw new y("INTERNAL",`cyclic registry dependency detected at "${a}"`);s.add(a);const{cleanup:c,directory:u}=await ee(a,t);n.push(c);const d=te(u,a);for(const f of d.requires??[])await i(f);s.delete(a),o.add(a),r.push({directory:u,manifest:d})};try{for(const a of e)await i(a)}catch(a){for(const c of n)c();throw a}return{cleanups:n,items:r}},k=()=>({bindings:[],code:0,deps:[],skipped:[],written:[]}),qt=(e,t,r,n,o)=>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[r.key]===r.value?{...a,[n]:o}:a)}})}:e,re=(e,t)=>{const r=t.title??t.description;e.info(`plan: ${t.name}${r?` — ${r}`:""}`);for(const s of t.files)e.info(` file ${s.to} (${s.merge})`);const n=new Map,o=s=>{const i=n.get(s);if(i!==void 0)return i;const a=C(s);return n.set(s,a),a};for(const[s,i]of Object.entries(t.deps??{}))e.info(` dep ${s}@${o(i)}`);for(const[s,i]of Object.entries(t.devDependencies??{}))e.info(` dev ${s}@${o(i)}`);for(const s of t.bindings??[])e.info(` bind ${s.path.join(".")} = ${JSON.stringify(s.value)}`);for(const s of t.envVars??[]){const i=s.secret?" (secret)":` = ${JSON.stringify(s.value??"")}`;e.info(` env ${s.name}${i}`)}for(const s of t.entrypointReexports??[]){const i=`./lunora/${s.module}`,a=s.comment?` // ${s.comment}`:"";e.info(` entry ${i}${a}`)}},ht=e=>{const t=e.map(({manifest:r})=>({bindings:(r.bindings??[]).map(n=>({path:n.path.join("."),value:n.value})),deps:Object.keys(r.deps??{}),devDependencies:Object.keys(r.devDependencies??{}),entrypointReexports:(r.entrypointReexports??[]).map(n=>({module:n.module,...n.comment?{comment:n.comment}:{}})),envVars:(r.envVars??[]).map(n=>({name:n.name,...n.secret?{secret:!0}:{value:n.value??""}})),files:r.files.map(n=>({merge:n.merge,to:n.to})),name:r.name,requires:r.requires??[],title:r.title}));process.stdout.write(`${JSON.stringify({items:t},void 0,2)} `)},yt=e=>{try{const{args:t,command:r}=se(ie(e));return`${r} ${t.join(" ")}`}catch{return"<your-package-manager> install"}},vt=(e,t,r,n,o,s)=>{o.success(`add complete: ${String(r)} written, ${String(n)} skipped`),o.info("next steps:"),o.info(" lunora codegen # regenerate _generated/ so the new tables/functions appear"),t.length>0&&o.info(` ${yt(s)} # install newly-added dependencies`);for(const{manifest:i}of e)i.docs&&o.info(`${i.name}: ${i.docs}`)},wt=async e=>{const t=k(),r=N("list",e);if(r)return e.logger.error(r),{...t,code:1};let n=()=>{};try{const o=await mt(e);n=o.cleanup;const s=le(o.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(o){return e.logger.error(`list failed: ${o instanceof Error?o.message:String(o)}`),{...t,code:1}}finally{n()}},Pt=async e=>{const t=e.cwd??process.cwd(),r=k();if(e.list)return wt(e);if(e.names.length===0)return e.logger.error("add requires at least one item name. Usage: lunora registry add <name> [...names]"),{...r,code:1};const n=N("add",e);if(n)return e.logger.error(n),{...r,code:1};let o=[];try{const{cleanups:s,items:i}=await $t(e.names,e);o=s;const{transformManifest:a}=e,c=a?i.map(h=>({...h,manifest:a(h.manifest)})):i;for(const{manifest:h}of c)re(e.logger,h);if(e.json&&ht(c),e.dryRun)return e.logger.info("dry-run: stopping before any files are written"),r;if(e.diff)return I(c,t,e.logger,{diff:!0}),e.logger.info("diff: preview only — re-run without --diff to apply"),r;if(!await Ge(c,e))return{...r,code:1};const{bindings:u,deps:d,skipped:f,written:m}=I(c,t,e.logger,{overwrite:e.overwrite});return vt(c,d,m.length,f.length,e.logger,t),{bindings:u,code:0,deps:d,skipped:f,written:m}}catch(s){return e.logger.error(`add failed: ${s instanceof Error?s.message:String(s)}`),{...r,code:1}}finally{for(const s of o)s()}},Wt=async e=>{const t=k();if(e.names.length===0)return e.logger.error("view requires an item name. Usage: lunora registry view <name>"),{...t,code:1};const r=N("view",e);if(r)return e.logger.error(r),{...t,code:1};const n=[];try{for(const o of e.names){const{cleanup:s,directory:i}=await ee(o,e);n.push(s);const a=te(i,o);re(e.logger,a);for(const c of a.files){e.logger.info(`--- ${c.to} (${c.merge}) ---`);const u=g(l(i,c.from),"utf8");for(const d of u.split(` `))e.logger.info(d)}}return t}catch(o){return e.logger.error(`view failed: ${o instanceof Error?o.message:String(o)}`),{...t,code:1}}finally{for(const o of n)o()}},Ut=async e=>{const t=k(),r=e.from;if(r===void 0)return e.logger.error("registry build requires --from <registry root>"),{...t,code:1};if(!p(r))return e.logger.error(`registry root not found: ${r}`),{...t,code:1};const n=pe(r),o=e.out??l(r,"index.json");if(e.check){const s=p(o)?JSON.parse(g(o,"utf8")):{items:[]};return JSON.stringify(s.items??[])!==JSON.stringify(n.items)?(e.logger.error(`registry: ${o} is stale — run \`lunora registry build\` to regenerate it`),{...t,code:1}):(e.logger.success(`registry: ${o} is up to date (${String(n.items.length)} items)`),t)}return $(o,`${JSON.stringify({$schema:"./schema/registry.schema.json",...n},void 0,4)} `,"utf8"),e.logger.success(`registry: wrote ${o} (${String(n.items.length)} items)`),t};export{Wt as E,Ut as F,Pt as I,_e as L,Lt as P,Re as b,qt as d,B as g};