UNPKG

@lunora/cli

Version:

The Lunora CLI: init, dev, deploy, codegen, run, reset, and migrate commands

9 lines (8 loc) 4.8 kB
import{createWriteStream as T,createReadStream as x}from"node:fs";import{unlink as k,stat as I}from"node:fs/promises";import{LunoraError as S}from"@lunora/errors";import{a as O}from"./admin-url-DfVehV3e.mjs";const A="/_lunora/admin/export",E="/_lunora/admin/import",_=500,L=r=>{if(r===void 0)return;const t=r.split(",").map(o=>o.trim()).filter(o=>o.length>0);return t.length>0?t:void 0},N=async(r,t)=>{r.write(t)||await new Promise(o=>{r.once("drain",o)})},R=async(r,t)=>{const o=r.getReader(),u=new TextDecoder;let s=0,g=0,e="",a=!1;try{for(;!a;){const c=await o.read();if(a=c.done,c.value===void 0)continue;s+=c.value.length,e+=u.decode(c.value,{stream:!0});let l=e.indexOf(` `);for(;l!==-1;){g+=1;const n=`${e.slice(0,l)} `;await N(t,n),e=e.slice(l+1),l=e.indexOf(` `)}}return e.length>0&&(g+=1,await N(t,`${e} `)),{bytes:s,rows:g}}finally{o.releaseLock()}},P=async(r,t)=>{if(t!==void 0){r.destroy();try{await k(t)}catch{}}},M=async r=>{if(r.prod&&r.url===void 0)return r.logger.error("--prod requires an explicit --url (refusing to export from the implicit localhost worker)"),{bytes:0,code:1,rows:0};const t=r.token??process.env.LUNORA_ADMIN_TOKEN;if(!t)return r.logger.error("admin token required — pass --token or set LUNORA_ADMIN_TOKEN"),{bytes:0,code:1,rows:0};const o=O(r.url,r.logger);if(o===void 0)return{bytes:0,code:1,rows:0};const u=`${o}${A}`,s=L(r.tables),g=r.fetchImpl??globalThis.fetch;if(typeof g!="function")throw new TypeError("no fetch implementation available — pass fetchImpl or run on Node >= 18");r.logger.info(`POST ${u} -> export${s?` (tables: ${s.join(",")})`:""}`);const e=await g(u,{body:JSON.stringify(s?{tables:s}:{}),headers:{authorization:`Bearer ${t}`,"content-type":"application/json"},method:"POST"});if(!e.ok){const d=await e.text();return r.logger.error(`export failed: HTTP ${String(e.status)}: ${d}`),{bytes:0,code:1,rows:0}}if(!e.body)return r.logger.error("export response carried no body"),{bytes:0,code:1,rows:0};const a=r.out===void 0||r.out==="-"?void 0:r.out,c=a===void 0?process.stdout:T(a,{encoding:"utf8"});let l,n;try{({bytes:l,rows:n}=await R(e.body,c))}catch(d){throw await P(c,a),d}return a!==void 0&&(await new Promise((d,w)=>{c.end(h=>{h?w(h):d()})}),r.logger.success(`wrote ${String(n)} rows to ${a} (${String(l)} bytes)`)),{bytes:l,code:0,rows:n}},U=async r=>{if(r.prod&&r.url===void 0){r.logger.error("--prod requires an explicit --url (refusing to import to the implicit localhost worker)");return}if(r.prod&&r.yes!==!0){r.logger.error("import --prod bulk-writes production. Re-run with --yes to confirm.");return}const t=r.token??process.env.LUNORA_ADMIN_TOKEN;if(!t){r.logger.error("admin token required — pass --token or set LUNORA_ADMIN_TOKEN");return}try{if(!(await I(r.file)).isFile()){r.logger.error(`not a file: ${r.file}`);return}}catch(s){const g=s instanceof Error?s.message:String(s);r.logger.error(`failed to stat ${r.file}: ${g}`);return}const o=O(r.url,r.logger);if(o===void 0)return;const u=r.fetchImpl??globalThis.fetch;if(typeof u!="function")throw new TypeError("no fetch implementation available — pass fetchImpl or run on Node >= 18");return{fetchImpl:u,requestUrl:`${o}${E}`,token:t}},C=async r=>{const t=await U(r);if(t===void 0)return{body:void 0,code:1,inserted:0};const{fetchImpl:o,requestUrl:u,token:s}=t,g=r.batchSize??_;r.logger.info(`POST ${u} -> import ${r.file}`);const e=x(r.file,{encoding:"utf8"}),a={},c=[];let l=0,n="",d=[],w=0;const h=async()=>{if(d.length===0)return;const p=d.join(` `);d=[];const f=await o(u,{body:p,headers:{authorization:`Bearer ${s}`,"content-type":"application/x-ndjson"},method:"POST"});if(!f.ok){const m=await f.text().catch(()=>"<no body>");throw new S("INTERNAL",`import batch failed (HTTP ${String(f.status)}): ${m}`)}const i=await f.json();if(i.inserted)for(const[m,y]of Object.entries(i.inserted))a[m]=(a[m]??0)+y;Array.isArray(i.errors)&&c.push(...i.errors),typeof i.conflicts=="number"&&(l+=i.conflicts)},b=p=>{const f=p.trim();if(f.length===0)return;if(w+=1,r.table===void 0){d.push(f);return}let i;try{i=JSON.parse(f)}catch(m){const y=m instanceof Error?m.message:String(m);throw new S("INTERNAL",`invalid JSON on line ${String(w)}: ${y}`,{cause:m})}d.push(JSON.stringify({doc:i,table:r.table}))};for await(const p of e){const f=typeof p=="string"?p:p.toString("utf8");n+=f;let i=n.indexOf(` `);for(;i!==-1;)b(n.slice(0,i)),n=n.slice(i+1),i=n.indexOf(` `),d.length>=g&&await h()}n.length>0&&b(n),await h();const $=Object.values(a).reduce((p,f)=>p+f,0),v={conflicts:l,errors:c,inserted:a};return r.logger.info(JSON.stringify(v,void 0,2)),r.logger.success(`imported ${String($)} rows (${String(l)} conflicts, ${String(c.length)} errors)`),{body:v,code:c.length>0?1:0,inserted:$}};export{_ as DEFAULT_IMPORT_BATCH_SIZE,M as runExportCommand,C as runImportCommand};