@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) • 6.3 kB
JavaScript
import{writeFileSync as E,existsSync as w,cpSync as j,statSync as b,lstatSync as I,readdirSync as N,mkdirSync as D,readFileSync as M}from"node:fs";import{join as u,sep as x,relative as B,resolve as O,dirname as K}from"node:path";import{SDK_TARGETS as V,SDK_LANGUAGES as G,generateSdk as P,isTypedSchema as Q}from"@lunora/codegen";import{LunoraError as d}from"@lunora/errors";import{d as J}from"../packem_shared/command-DwC11Cwe.mjs";import{r as _,a as U,b as q,c as H,s as W}from"../packem_shared/resolve-DZAGzg67.mjs";const $="gh:anolilab/lunora/sdks",v="lunora-transport.json",X=/_test\.|Tests?\./u,k=t=>t.startsWith("test_")||X.test(t),A=(t,n="")=>N(t).toSorted((e,r)=>e.localeCompare(r)).flatMap(e=>{const r=u(t,e),a=n.length>0?`${n}/${e}`:e;return b(r).isDirectory()?A(r,a):[a]}),C=(t,n)=>{const e=I(n);if(e.isSymbolicLink())throw new d("INTERNAL",`refusing to vendor "${B(t,n)}" — it is a symlink, not a regular file`);if(e.isDirectory())for(const r of N(n))k(r)||C(t,u(n,r))},F=(t,n,e,r)=>{const a=u(t,e);if(!w(a))throw new d("NOT_FOUND",`transport is missing ${e}`);C(t,a);const s=u(n,r);return j(a,s,{filter:o=>!k(o.slice(o.lastIndexOf(x)+1)),force:!0,recursive:!0}),b(s).isDirectory()?A(s).map(o=>`${r}/${o}`):[r]},R=(t,n)=>n.vendor.every(e=>w(u(t,e.from))),z=async(t,n)=>{const{language:e,logger:r,outputDirectory:a,registryOptions:s,target:o}=n,{cleanup:f,directory:h}=await H(e,{...s,ref:t},$);try{if(!R(h,o)){r.warn(`${$}/${e} does not exist at ${t}.`);return}return{files:o.vendor.flatMap(c=>F(h,a,c.from,c.to)).toSorted((c,l)=>c.localeCompare(l)),ref:t,source:`${$}/${e}`,versionMatched:t===_()}}finally{f()}},Y=(t,n)=>{const{language:e,outputDirectory:r,target:a}=n,s=u(t,e);if(!w(s))throw new d("NOT_FOUND",`no transport at ${s} — --from must point at a directory of per-language transports (the repo's \`sdks/\`)`);if(!R(s,a)){const o=a.vendor.map(f=>f.from).join(", ");throw new d("NOT_FOUND",`${s} is not a ${e} transport — expected it to contain ${o}`)}return{files:a.vendor.flatMap(o=>F(s,r,o.from,o.to)).toSorted((o,f)=>o.localeCompare(f)),ref:"local",source:s,versionMatched:!1}},Z=async t=>{const{allowUnsafeSource:n,from:e,language:r,logger:a,outputDirectory:s,ref:o,source:f,target:h}=t;if(e!==void 0)return Y(e,{language:r,outputDirectory:s,target:h});const c={allowUnsafeSource:n,logger:a,names:[],source:f},l=async i=>z(await q(i,a),{language:r,logger:a,outputDirectory:s,registryOptions:c,target:h});if(o!==void 0&&o.length>0){const i=await l(o);if(i===void 0)throw new d("NOT_FOUND",`--ref ${o} carries no ${r} transport — pick a ref that does, or pass --from <dir> to copy a local one`);return i}const m=_(),g=await l(m).catch(i=>{const y=i instanceof Error?i.message.split(`
`)[0]??i.message:String(i);a.warn(`could not fetch the transport at ${m} (${y}).`)});if(g!==void 0)return g;const p=await l(void 0);if(p===void 0)throw new d("NOT_FOUND",`no ${r} transport at ${m} or on the release branch. This language may not be released yet — pass --from <dir> to copy one from a checkout, or --ref <tag> to name a ref that has it.`);return a.warn(`FALLBACK: the transport is NOT version-matched to this CLI (${U()}). Wanted ${m}, used ${p.ref}. The vendored wire protocol may not be the one this surface was generated against; ${v} records what was actually copied.`),p},ee=(t,n,e)=>{const r={cliVersion:U(),files:e.files,language:n,note:e.versionMatched?"Vendored by `lunora sdk generate` at the ref matching the CLI that generated the surface beside it. Regenerate with a newer CLI to upgrade.":"Vendored by `lunora sdk generate` from a ref that is NOT this CLI's release tag, so the transport and the generated surface may be different protocol vintages.",ref:e.ref,source:e.source,versionMatched:e.versionMatched};E(u(t,v),`${JSON.stringify(r,void 0,4)}
`,"utf8")},te=u("lunora","_generated","openrpc.json"),re=t=>{if(!w(t))throw new d("NOT_FOUND",`no OpenRPC document at ${t} — run \`lunora codegen --api-spec openrpc\` (or \`--api-spec both\`) first, or pass --spec`);let n;try{n=JSON.parse(M(t,"utf8"))}catch(e){throw new d("BAD_REQUEST",`${t} is not valid JSON: ${e instanceof Error?e.message:String(e)}`)}if(n===null||typeof n!="object"||!Array.isArray(n.methods))throw new d("BAD_REQUEST",`${t} is not an OpenRPC document (no \`methods\` array)`);return n},de=J(async({argument:t,cwd:n,logger:e,options:r})=>{const a=t[0]??"generate";if(a!=="generate")throw new d("BAD_REQUEST",`unknown subcommand "${a}" — the only one is \`lunora sdk generate\``);const s=r.lang??"python",o=V[s];if(o===void 0)throw new d("BAD_REQUEST",`unsupported --lang "${s}" — expected one of: ${G.join(" | ")}`);const f=W("sdk generate",{allowUnsafeSource:r.allowUnsafeSource,logger:e,names:[],source:r.source});if(f!==void 0)throw new d("BAD_REQUEST",f);const h=O(n,r.spec??te),c=O(n,r.out??u("sdk",s)),l=re(h);l.methods.length===0&&e.warn(`${h} declares no methods — writing an empty SDK.`);const{files:m,undeclared:g,unrepresentable:p}=await P(l,o);D(c,{recursive:!0});const i=await Z({allowUnsafeSource:r.allowUnsafeSource,from:r.from,language:s,logger:e,outputDirectory:c,ref:r.ref,source:r.source,target:o});ee(c,s,i);for(const[S,L]of Object.entries(m)){const T=u(c,S);D(K(T),{recursive:!0}),E(T,L,"utf8")}const y=l.methods.filter(S=>!Q(S.result?.schema)).length;return e.success(`Generated ${s} SDK for ${String(l.methods.length)} function(s) → ${c}`),e.info(`Vendored the ${s} transport (${String(i.files.length)} file(s)) from ${i.source} @ ${i.ref}${i.versionMatched?" — version-matched to this CLI":""}; see ${v}.`),e.info(o.requires.length>0?`Install in the consuming project: ${o.requires.join(", ")}.`:"Nothing to install — the transport is vendored and needs only the standard library."),g.length>0&&e.warn(`${String(g.length)} schema(s) are not expressible as a named ${s} model, so those arguments or returns stay untyped: ${g.join(", ")}`),p.length>0&&e.warn(`${String(p.length)} function(s) take or return a \`v.bigint()\`/\`v.bytes()\`, which no generated model can carry — their parameters stay untyped, so pass wire values directly: ${p.join(", ")}`),y>0&&e.warn(`${String(y)} of ${String(l.methods.length)} function(s) return an untyped result — declare \`.output()\` on them for typed returns.`),{code:0}});export{de as execute};