UNPKG

@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.43 kB
import{writeFileSync as b,existsSync as S,cpSync as I,statSync as N,lstatSync as M,readdirSync as _,mkdirSync as O,readFileSync as x}from"node:fs";import{join as f,sep as B,relative as K,resolve as E,dirname as V}from"node:path";import{SDK_TARGETS as G,SDK_LANGUAGES as P,generateSdk as Q,isTypedSchema as J}from"@lunora/codegen";import{LunoraError as d}from"@lunora/errors";import{d as q}from"../packem_shared/command-B6vu47iR.mjs";import{r as k,a as H,b as W,s as X}from"../packem_shared/resolve-Bdc9KEUv.mjs";import{r as U}from"../packem_shared/cli-manifest-DoQ_3bzs.mjs";const $="gh:anolilab/lunora/sdks",v="lunora-transport.json",z=/_test\.|Tests?\./u,A=t=>t.startsWith("test_")||z.test(t),C=(t,n="")=>_(t).toSorted((e,r)=>e.localeCompare(r)).flatMap(e=>{const r=f(t,e),a=n.length>0?`${n}/${e}`:e;return N(r).isDirectory()?C(r,a):[a]}),F=(t,n)=>{const e=M(n);if(e.isSymbolicLink())throw new d("INTERNAL",`refusing to vendor "${K(t,n)}" — it is a symlink, not a regular file`);if(e.isDirectory())for(const r of _(n))A(r)||F(t,f(n,r))},R=(t,n,e,r)=>{const a=f(t,e);if(!S(a))throw new d("NOT_FOUND",`transport is missing ${e}`);F(t,a);const s=f(n,r);return I(a,s,{filter:o=>!A(o.slice(o.lastIndexOf(B)+1)),force:!0,recursive:!0}),N(s).isDirectory()?C(s).map(o=>`${r}/${o}`):[r]},L=(t,n)=>n.vendor.every(e=>S(f(t,e.from))),Y=async(t,n)=>{const{language:e,logger:r,outputDirectory:a,registryOptions:s,target:o}=n,{cleanup:l,directory:h}=await W(e,{...s,ref:t},$);try{if(!L(h,o)){r.warn(`${$}/${e} does not exist at ${t}.`);return}return{files:o.vendor.flatMap(c=>R(h,a,c.from,c.to)).toSorted((c,u)=>c.localeCompare(u)),ref:t,source:`${$}/${e}`,versionMatched:t===k()}}finally{l()}},Z=(t,n)=>{const{language:e,outputDirectory:r,target:a}=n,s=f(t,e);if(!S(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(!L(s,a)){const o=a.vendor.map(l=>l.from).join(", ");throw new d("NOT_FOUND",`${s} is not a ${e} transport — expected it to contain ${o}`)}return{files:a.vendor.flatMap(o=>R(s,r,o.from,o.to)).toSorted((o,l)=>o.localeCompare(l)),ref:"local",source:s,versionMatched:!1}},ee=async t=>{const{allowUnsafeSource:n,from:e,language:r,logger:a,outputDirectory:s,ref:o,source:l,target:h}=t;if(e!==void 0)return Z(e,{language:r,outputDirectory:s,target:h});const c={allowUnsafeSource:n,logger:a,names:[],source:l},u=async i=>Y(await H(i,a),{language:r,logger:a,outputDirectory:s,registryOptions:c,target:h});if(o!==void 0&&o.length>0){const i=await u(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 g=k(),y=await u(g).catch(i=>{const w=i instanceof Error?i.message.split(` `)[0]??i.message:String(i);a.warn(`could not fetch the transport at ${g} (${w}).`)});if(y!==void 0)return y;const m=await u(void 0);if(m===void 0)throw new d("NOT_FOUND",`no ${r} transport at ${g} 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 ${g}, used ${m.ref}. The vendored wire protocol may not be the one this surface was generated against; ${v} records what was actually copied.`),m},te=(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};b(f(t,v),`${JSON.stringify(r,void 0,4)} `,"utf8")},re=f("lunora","_generated","openrpc.json"),ne=t=>{if(!S(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(x(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},fe=q(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=G[s];if(o===void 0)throw new d("BAD_REQUEST",`unsupported --lang "${s}" — expected one of: ${P.join(" | ")}`);const l=X("sdk generate",{allowUnsafeSource:r.allowUnsafeSource,logger:e,names:[],source:r.source});if(l!==void 0)throw new d("BAD_REQUEST",l);const h=E(n,r.spec??re),c=E(n,r.out??f("sdk",s)),u=ne(h);u.methods.every(p=>p["x-lunora-function-kind"]==="stream")&&e.warn(`${h} declares no methods — writing an empty SDK.`);const{files:g,undeclared:y,unrepresentable:m}=await Q(u,o);O(c,{recursive:!0});const i=await ee({allowUnsafeSource:r.allowUnsafeSource,from:r.from,language:s,logger:e,outputDirectory:c,ref:r.ref,source:r.source,target:o});te(c,s,i);for(const[p,j]of Object.entries(g)){const D=f(c,p);O(V(D),{recursive:!0}),b(D,j,"utf8")}const w=u.methods.filter(p=>p["x-lunora-function-kind"]!=="stream"),T=w.filter(p=>!J(p.result?.schema)).length;return e.success(`Generated ${s} SDK for ${String(w.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."),y.length>0&&e.warn(`${String(y.length)} schema(s) are not expressible as a named ${s} model, so those arguments or returns stay untyped: ${y.join(", ")}`),m.length>0&&e.warn(`${String(m.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: ${m.join(", ")}`),T>0&&e.warn(`${String(T)} of ${String(w.length)} function(s) return an untyped result — declare \`.output()\` on them for typed returns.`),{code:0}});export{fe as execute};