UNPKG

@lunora/cli

Version:

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

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