UNPKG

@lunora/cli

Version:

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

14 lines • 54 kB
import{createReadStream as E}from"node:fs";import{open as st,stat as L,readdir as x,realpath as ae,readFile as v,mkdir as J,writeFile as $e,appendFile as ot}from"node:fs/promises";import{t as it,r as at}from"./admin-token-VdUnvnKW.mjs";import{r as ct,a as dt}from"./admin-url-DJB8P6Jh.mjs";import{EXIT_CODE as $,isRefusal as lt}from"./EXIT_CODE-DVjUgIaG.mjs";import{join as w,resolve as F,basename as p,dirname as Se,relative as ut,sep as ft}from"node:path";import{createInterface as M}from"node:readline";import ht from"adm-zip";import{i as ve}from"./path-containment-CgxYZggb.mjs";import{Transform as gt}from"node:stream";import{createInflateRaw as mt,crc32 as pt}from"node:zlib";import{LunoraError as f}from"@lunora/errors";import{parse as yt}from"csv-parse/sync";import{parse as Ne}from"csv-parse";import{STORAGE_UPLOAD_MAX_BODY_BYTES as wt}from"@lunora/runtime";import{S as V,a as bt,I as $t}from"./shared-D-zCOmgY.mjs";import{createHash as St}from"node:crypto";const vt=67324752,O=30,Nt=26,Et=28,ce=0,Tt=8,It=async(e,t)=>{const r=await st(e,"r");try{const n=Buffer.alloc(O),{bytesRead:s}=await r.read(n,0,O,t);if(s<O||n.readUInt32LE(0)!==vt)throw new Error(`${e}: no local file header at offset ${String(t)} — the archive is truncated or corrupt`);return t+O+n.readUInt16LE(Nt)+n.readUInt16LE(Et)}finally{await r.close()}},de=e=>{let t=0;return new gt({flush(r){if(t===e.header.crc){r();return}r(new Error(`${e.entryName} failed its CRC check — the archive is corrupt or truncated (expected ${String(e.header.crc)}, read ${String(t)})`))},transform(r,n,s){t=pt(r,t),s(void 0,r)}})},At=async(e,t)=>{const{compressedSize:r,encrypted:n,method:s,offset:o}=t.header;if(n)throw new Error(`${t.entryName} is encrypted — decrypt the archive before importing`);if(s!==ce&&s!==Tt)throw new Error(`${t.entryName} uses unsupported compression method ${String(s)} — re-create the archive with standard deflate`);if(r===0){if(t.header.size>0||t.header.crc!==0)throw new Error(`${t.entryName} declares 0 compressed bytes but ${String(t.header.size)} uncompressed with CRC ${String(t.header.crc)} — the archive is corrupt`);return}const i=await It(e,o),a=E(e,{end:i+r-1,start:i});if(s===ce){const l=de(t);return a.on("error",u=>l.destroy(u)),a.pipe(l)}const c=mt(),d=de(t);return a.on("error",l=>c.destroy(l)),c.on("error",l=>d.destroy(l)),a.pipe(c).pipe(d)},N="_storage",Ee=e=>e.startsWith("_"),Rt=e=>{for(const t of e.getEntries()){const n=t.entryName.replaceAll("\\","/").split("/");if(n.length>=2&&n[n.length-2]==="_storage")return n.slice(0,-1).join("/")}return"_storage"},_t=async e=>{const t=await L(e).catch(()=>{});if(t?.isDirectory())return{kind:"directory",root:e};if(t?.isFile()&&e.toLowerCase().endsWith(".zip")){const r=new ht(e);return{kind:"zip",storagePrefix:Rt(r),zip:r,zipPath:e}}},Ot=async e=>{const t=[];for(const r of await x(e,{withFileTypes:!0})){if(!r.isDirectory())continue;const n=w(e,r.name,"documents.jsonl");(await L(n).catch(()=>{}))?.isFile()&&t.push({file:n,table:r.name})}return t},kt=e=>{const t=[];for(const r of e.getEntries()){if(r.isDirectory)continue;const n=r.entryName.replaceAll("\\","/"),s=n.split("/");s.length>=2&&s[s.length-1]==="documents.jsonl"&&t.push({file:n,table:s[s.length-2]})}return t},Lt=async e=>{const t=e.kind==="directory"?await Ot(e.root):kt(e.zip);return t.length>0?t.toSorted((r,n)=>r.table.localeCompare(n.table)):void 0},H=async function*(e,t){if(e.kind==="directory"){for await(const o of M({crlfDelay:Number.POSITIVE_INFINITY,input:E(t.file,{encoding:"utf8"})}))yield o;return}const r=e.zip.getEntry(t.file);if(r===null)throw new Error(`missing ${t.file} in archive`);const n=await At(e.zipPath,r);if(n===void 0)return;const s=M({crlfDelay:Number.POSITIVE_INFINITY,input:n});try{for await(const o of s)yield o}finally{s.close(),n.destroy()}},xt=async(e,t)=>{if(e.kind==="directory"){const n=await ae(w(e.root,"_storage")),s=await ae(F(n,t)).catch(()=>{});if(s===void 0||!ve(n,s))throw new Error(`blob ${t} resolves outside the snapshot's _storage directory`);return v(s)}const r=e.zip.readFile(`${e.storagePrefix}/${t}`);if(r===null)throw new Error(`missing blob ${t} in archive`);return Buffer.from(r)},j=207,Ct=e=>{const t={conflicts:0,errors:[],failed:[],inserted:{},received:0,replaced:0,warnings:[]};let r=[],n=0;const s=a=>{for(const[c,d]of Object.entries(a.inserted??{}))t.inserted[c]=(t.inserted[c]??0)+d;t.errors.push(...a.errors??[]),t.failed.push(...a.failed??[]),t.conflicts+=a.conflicts??0,t.received+=a.received??0,t.replaced+=a.replaced??0;for(const c of a.warnings??[])t.warnings.includes(c)||t.warnings.push(c)},o=async()=>{if(r.length===0)return;const a=r.join(`
`);r=[],n=0;const c=await e.fetchImpl(e.requestUrl,{body:a,headers:{authorization:`Bearer ${e.token}`,"content-type":"application/x-ndjson"},method:"POST"});if(!c.ok&&c.status!==j){const l=await c.text().catch(()=>"<no body>");throw new f("INTERNAL",`import batch failed (HTTP ${String(c.status)}): ${l}`)}const d=await c.json();s(d),c.status===j&&(d.failed??[]).length===0&&t.failed.push({message:`the endpoint answered ${String(j)} without naming the failed shards`,shardKey:"<unknown>",timedOut:!1})};return{flush:o,push:async a=>{const c=Buffer.byteLength(a)+1;r.length>0&&n+c>e.maxBatchBytes&&await o(),r.push(a),n+=c,r.length>=e.batchSize&&await o()},totals:t}},Te=(e,t,r,n)=>{const s=[],o=[];let i=0;const a=l=>n?.[r]?.includes(l)===!0,c=(l,u,h=!1)=>{if(Array.isArray(l))return l.map(g=>c(g,u,h));if(l!==null&&typeof l=="object"){const g=l;if(typeof g.$storage=="string"){const m=g.$storage,y=t.get(m);return y===void 0?(o.push({column:u,storageId:m,table:r}),l):(i+=1,y)}return Object.fromEntries(Object.entries(g).map(([m,y])=>[m,c(y,u)]))}return typeof l=="string"&&t.has(l)?a(u)?(i+=1,t.get(l)??l):(s.push({column:u,storageId:l,table:r}),l):(h&&typeof l=="string"&&l.length>0&&a(u)&&n!==void 0&&o.push({column:u,storageId:l,table:r}),l)},d=Object.fromEntries(Object.entries(e).map(([l,u])=>[l,c(u,l,!0)]));return{ambiguous:s,document:d,rewritten:i,unmigrated:o}},le=(e,t,r)=>{for(const n of r){const s=`${n.table}\0${n.column}\0${n.storageId}`;t.has(s)||(t.add(s),e.push(n))}},Ut=e=>{const{remapDocument:t,report:r,storageColumns:n,storageIdMap:s,table:o}=e,i=new Set,a=new Set,c=(u,h)=>{let g;try{g=JSON.parse(u)}catch(m){const y=m instanceof Error?m.message:String(m);throw new f("INTERNAL",`invalid JSON on line ${String(h)}: ${y}`,{cause:m})}return JSON.stringify({doc:g,table:o})},d=(u,h)=>{let g;try{g=JSON.parse(u)}catch(y){throw new f("INTERNAL",`line ${String(h)}: import envelope is not valid JSON — ${y instanceof Error?y.message:String(y)}`,{cause:y})}const m=g===null||typeof g!="object"||Array.isArray(g)?void 0:g;if(m===void 0||typeof m.table!="string")throw new f("INTERNAL",`line ${String(h)}: import envelope is missing a string \`table\``);return m},l=u=>{if(u.doc!==null&&typeof u.doc=="object"&&!Array.isArray(u.doc)){let h=u.doc;if(s!==void 0){const g=Te(h,s,u.table,n);h=g.document,r.rewritten+=g.rewritten,le(r.ambiguous,i,g.ambiguous),le(r.unmigrated,a,g.unmigrated)}return JSON.stringify({...u,doc:t===void 0?h:t(h,u.table)})}return JSON.stringify(u)};return(u,h)=>{const g=u.trim();if(g.length===0)return;if(o!==void 0)return c(g,h);const m=d(g,h);return s===void 0&&t===void 0?g:l(m)}},Ie=async(e,t,r,n)=>{const s=await n(e).catch(()=>{});if(s===void 0)throw new f("INTERNAL",`${e} is not a readable directory`);const o=new Map;for(const[d,l]of Object.entries(t?.tables??{}))l.file!==void 0&&o.set(p(l.file),d);const i=s.filter(d=>d.isFile()&&r.matches(d.name)&&!r.authFiles.has(d.name)).map(d=>({file:w(e,d.name),table:o.get(d.name)??r.tableNameOf(d.name)}));if(i.length===0)throw new f("INTERNAL",`${e} ${r.emptyMessage}`);const a=new Set(i.map(d=>p(d.file))),c=[...o].filter(([d])=>!a.has(d));if(c.length>0)throw new f("INTERNAL",`${e}: the mapping names ${String(c.length)} file(s) that are not importable from this directory — ${c.map(([d,l])=>`\`${d}\` (table \`${l}\`)`).join(", ")}. Check the name and that the file is one this source reads.`);return i.toSorted((d,l)=>d.table.localeCompare(l.table))},Ae=async function*(e,t,r,n){for(const s of e){r.has(s.table)||r.set(s.table,0),t.info(`reading ${p(s.file)} → ${s.table}`);for await(const o of n(s))r.set(s.table,(r.get(s.table)??0)+1),yield`${JSON.stringify({doc:o,table:s.table})}
`}},Re=["timestamp-ms","timestamp-iso","json","bytea-base64","int8-string","number","boolean","text-array"],Pt=e=>Re.includes(e),jt=/^\\x([\dA-Fa-f]*)$/,ue=/^[+-]?\d+$/,_e=/[+-]\d{2}$/,Oe=/(?:Z|[+-]\d{2}:\d{2})$/i,ke=/^\+/,Ft=/e/i,Mt=/e/i,Dt=e=>{const[t="0",r="0"]=e.split(Mt),n=Number(r),s=t.startsWith("-"),[o="0",i=""]=(s?t.slice(1):t).replace(ke,"").split("."),a=`${o}${i}`,c=o.length+n;let d;return c<=0?d=`0.${"0".repeat(-c)}${a}`:c>=a.length?d=`${a}${"0".repeat(c-a.length)}`:d=`${a.slice(0,c)}.${a.slice(c)}`,s?`-${d}`:d},zt=/^0+(?=\d)/,fe=e=>{const r=(Ft.test(e.trim())?Dt(e.trim()):e.trim()).replace(ke,""),n=r.startsWith("-"),[s="0",o=""]=(n?r.slice(1):r).split("."),i=s.replace(zt,"");let a=o.length;for(;a>0&&o[a-1]==="0";)a-=1;const c=o.slice(0,a),d=c.length>0?`${i}.${c}`:i;return n&&Number(d)!==0?`-${d}`:d},b=(e,t,r,n)=>{throw new f("INTERNAL",`column \`${e}\`: cannot reshape ${JSON.stringify(r)} as \`${t}\` — ${n}`)},he=(e,t,r)=>{const n=r.includes("T")?r:r.replace(" ","T"),s=_e.test(n)?`${n}:00`:n,o=Oe.test(s)?s:`${s}Z`,i=Date.parse(o);return Number.isNaN(i)&&b(e,t,r,"not a date Postgres or ISO-8601 syntax can express"),i},Bt=(e,t)=>{(!t.startsWith("{")||!t.endsWith("}"))&&b(e,"text-array",t,"not a Postgres array literal (expected `{…}`)");const r=t.slice(1,-1);if(r.length===0)return[];r.includes("{")&&b(e,"text-array",t,"nested arrays are not supported — map the column to `json` instead");const n=[];let s="",o=!1,i=!1,a=!1;const c=()=>{n.push(!i&&s==="NULL"?null:s),s="",i=!1};for(const d of r)a?(s+=d,a=!1):d==="\\"?a=!0:d==='"'?(o=!o,i=!0):d===","&&!o?c():s+=d;return c(),n},Le=(e,t,r)=>{if(r===null)return null;switch(t){case"boolean":return["1","t","TRUE","true"].includes(r)?!0:["0","f","FALSE","false"].includes(r)?!1:b(e,t,r,"not a Postgres boolean literal");case"bytea-base64":{const n=jt.exec(r);if(n===null)return b(e,t,r,"not `bytea` hex output (expected a leading `\\x`) — set `bytea_output = 'hex'` before dumping");const s=n[1];return s.length%2!==0?b(e,t,r,"has an odd number of hex digits, so the dump is truncated — re-export the column"):Buffer.from(s,"hex").toString("base64")}case"int8-string":return ue.test(r)?r:b(e,t,r,"not an integer");case"json":try{return JSON.parse(r)}catch(n){return b(e,t,r,`invalid JSON — ${n instanceof Error?n.message:String(n)}`)}case"number":{const n=Number(r);if(r.trim().length===0||!Number.isFinite(n))return b(e,t,r,"not a finite number");if(ue.test(r)){if(!Number.isSafeInteger(n))return b(e,t,r,"exceeds Number.MAX_SAFE_INTEGER — map this column to `int8-string` to keep it lossless")}else if(fe(String(n))!==fe(r))return b(e,t,r,"has more precision than a JS number holds — map this column to `int8-string` or `json` to keep it lossless");return n}case"text-array":return Bt(e,r);case"timestamp-iso":{he(e,t,r);const n=r.includes("T")?r:r.replace(" ","T"),s=_e.test(n)?`${n}:00`:n;return Oe.test(s)?s:`${s}Z`}case"timestamp-ms":return he(e,t,r);default:return r}},Jt="auth.",Vt=new Set(["confirmation_token","email_change_token_current","email_change_token_new","encrypted_password","password","password_hash","passwordhash","reauthentication_token","recovery_token","salt"]),Ht=String.raw`\N`,xe=(e,t)=>t.header?e:!t.quoting&&e.length===0||e===Ht?null:e,Gt=e=>new Set([e?.auth?.file,e?.auth?.identitiesFile].filter(t=>t!==void 0).map(t=>p(t))),qt=async(e,t)=>Ie(e,t,{authFiles:Gt(t),emptyMessage:"holds no .csv files — export each table with `COPY <table> TO STDOUT WITH CSV HEADER` first",matches:r=>r.toLowerCase().endsWith(".csv")&&!r.toLowerCase().startsWith(Jt),tableNameOf:r=>p(r,".csv")},async r=>x(r,{withFileTypes:!0})),Wt=(e,t,r)=>{const n=t?.idColumn??"id",s=t?.types??{},o={};let i=!1;for(const[a,c]of Object.entries(e)){if(Vt.has(a.toLowerCase()))throw new f("INTERNAL",`${r}.${a} is credential material — this looks like an auth dump being imported as a table. Name it under \`auth\` in the mapping instead; passwords are never migrated.`);const d=s[a],l=d===void 0?c:Le(a,d,c);if(a===n){if(c===null)throw new f("INTERNAL",`id column \`${n}\` is NULL — every row needs an id to preserve`);o._id=c,i=!0}else{if(a==="_id")throw new f("INTERNAL",`${r}: source column \`_id\` collides with the reserved id field. Rename it in the source, or map it via \`tables.${r}.idColumn\` if it IS the id.`);o[a]=l}}if(!i)throw new f("INTERNAL",`${r}: no \`${n}\` column to preserve as the id (columns present: ${Object.keys(e).join(", ")}). Set \`tables.${r}.idColumn\` in the mapping.`);return o},Kt=async function*(e,t){const r=t?.tables?.[e.table],n=E(e.file).pipe(Ne({cast:xe,columns:!0,relaxColumnCountLess:!1,skipEmptyLines:!0}));let s=0;const o=n[Symbol.asyncIterator]();for(;;){let i,a;try{if(i=await o.next(),i.done===!0)return;s+=1,a=Wt(i.value,r,e.table)}catch(c){throw new f("INTERNAL",`${p(e.file)} row ${String(s+1)}: ${c instanceof Error?c.message:String(c)}`,{cause:c})}yield a}},Yt=async function*(e,t,r,n,s){yield*Pe("supabase",s,t,r,n),yield*Ae(e,r,n,o=>Kt(o,t))},Xt=/^\d+$/,Zt=/[+-]\d{2}$/,D=e=>{if(e==null)return;if(typeof e=="number")return e;const t=Number(e);if(Number.isFinite(t)&&Xt.test(e))return t;const r=e.includes("T")?e:e.replace(" ","T"),n=Date.parse(Zt.test(r)?`${r}:00`:r);return Number.isNaN(n)?void 0:n},Ce=e=>{if(typeof e=="string")try{const t=JSON.parse(e);return t!==null&&typeof t=="object"?t:void 0}catch{return}return e!==null&&typeof e=="object"?e:void 0},Qt=e=>{const t=Ce(e);if(t!==void 0){for(const r of["name","full_name","user_name","preferred_username"])if(typeof t[r]=="string"&&t[r].length>0)return t[r]}},er=e=>{const t=Ce(e);if(t!==void 0){for(const r of["avatar_url","picture"])if(typeof t[r]=="string"&&t[r].length>0)return t[r]}},tr=(e,t)=>{const{id:r}=e;if(typeof r!="string"||r.length===0)throw new f("INTERNAL","auth row is missing `id` — every user needs an id to preserve");const n=e.raw_user_meta_data,s={_id:r,email:e.email??null,emailVerified:typeof e.email_confirmed_at=="string"&&e.email_confirmed_at.length>0,id:r},o=Qt(n),i=er(n),a=D(e.created_at),c=D(e.updated_at);return o!==void 0&&(s.name=o),i!==void 0&&(s.image=i),a!==void 0&&(s.createdAt=a),c!==void 0&&(s.updatedAt=c),{accounts:t.map(l=>{const u=typeof l.provider=="string"?l.provider:"unknown",h=typeof l.provider_id=="string"?l.provider_id:r,g=`${r}:${u}:${h}`;return{_id:g,accountId:h,id:g,providerId:u,userId:r}}),user:s}},rr=e=>{const t=e.localId;if(typeof t!="string"||t.length===0)throw new f("INTERNAL","auth row is missing `localId` — every user needs an id to preserve");const r={_id:t,email:e.email??null,emailVerified:e.emailVerified===!0,id:t},n=D(e.createdAt);return typeof e.displayName=="string"&&(r.name=e.displayName),typeof e.photoUrl=="string"&&(r.image=e.photoUrl),n!==void 0&&(r.createdAt=n),{accounts:(e.providerUserInfo??[]).filter(o=>typeof o.providerId=="string"&&o.providerId!=="password").map(o=>{const i=o.providerId,a=o.rawId??o.federatedId??t,c=`${t}:${i}:${a}`;return{_id:c,accountId:a,id:c,providerId:i,userId:t}}),user:r}},nr=(e,t)=>{const r=new Map,n=[],s=[];for(const{accounts:o,user:i}of e){const a=typeof i.email=="string"?i.email.toLowerCase():void 0;if(a!==void 0&&a.length>0){const c=r.get(a);c===void 0?r.set(a,String(i._id)):n.push(`${a} (ids ${c} and ${String(i._id)})`)}s.push(`${JSON.stringify({doc:i,table:"user"})}
`),t.set("user",(t.get("user")??0)+1);for(const c of o)s.push(`${JSON.stringify({doc:c,table:"account"})}
`),t.set("account",(t.get("account")??0)+1)}if(n.length>0)throw new f("INTERNAL",`auth import found ${String(n.length)} duplicate email(s), which would merge distinct users: ${n.slice(0,10).join("; ")}${n.length>10?" …":""}`);return s},G=(e,t)=>w(e,p(t)),Ue=async e=>{const t=await v(e,"utf8");return yt(t,{cast:xe,columns:!0,skipEmptyLines:!0})},sr=async(e,t)=>{const r=new Map;if(t===void 0)return r;for(const n of await Ue(G(e,t))){const s=n.user_id;typeof s=="string"&&r.set(s,[...r.get(s)??[],n])}return r},or=async(e,t)=>{const r=t.auth?.file;if(r===void 0)return[];const n=await Ue(G(e,r)),s=await sr(e,t.auth?.identitiesFile);return n.map(o=>tr(o,s.get(o.id??"")??[]))},ir=async(e,t)=>{const r=t.auth?.file;if(r===void 0)return[];const n=G(e,r);let s;try{s=JSON.parse(await v(n,"utf8"))}catch(i){throw new f("INTERNAL",`${p(n)}: invalid JSON — ${i instanceof Error?i.message:String(i)}`,{cause:i})}const o=Array.isArray(s)?s:s.users??[];if(!Array.isArray(o))throw new f("INTERNAL",`${p(n)}: expected \`{ users: [...] }\` from \`firebase auth:export\`, or a bare array`);return o.map(i=>rr(i))},Pe=async function*(e,t,r,n,s){if(r?.auth?.file===void 0)return;const o=e==="supabase"?await or(t,r):await ir(t,r);n.info(`auth: ${String(o.length)} user(s) — passwords are never migrated; users reset via "forgot password"`);for(const i of nr(o,s))yield i},ge=/\.(?:nd)?json$/i,je=e=>{const t=e.split("/").filter(r=>r.length>0);return t[t.length-1]??e},ar=e=>{const t=Number(e.seconds??0),r=e.nanos??0;return Number.isFinite(t)&&Number.isFinite(r)?t*1e3+Math.floor(r/1e6):Number.NaN},cr=(e,t)=>{const r=Array.isArray(e)?e:e.data;if(!Array.isArray(r))throw new f("INTERNAL",`${t}: \`bytesValue\` ${JSON.stringify(e)} is neither base64 nor a byte array`);return Buffer.from(r).toString("base64")},dr=(e,t)=>{const r=typeof e=="string"?Date.parse(e):ar(e);if(Number.isNaN(r))throw new f("INTERNAL",`${t}: \`timestampValue\` ${JSON.stringify(e)} is neither an RFC-3339 string nor a \`{ seconds, nanos }\` protobuf timestamp`);return r},lr=(e,t)=>typeof e=="string"?e:cr(e,t),Fe=(e,t)=>{if("nullValue"in e)return null;if(e.stringValue!==void 0)return e.stringValue;if(e.booleanValue!==void 0)return e.booleanValue;if(e.integerValue!==void 0){const r=String(e.integerValue),n=Number(r);return Number.isSafeInteger(n)?n:r}if(e.doubleValue!==void 0)return Number(e.doubleValue);if(e.timestampValue!==void 0)return dr(e.timestampValue,t);if(e.bytesValue!==void 0)return lr(e.bytesValue,t);if(e.geoPointValue!==void 0)return{latitude:e.geoPointValue.latitude??0,longitude:e.geoPointValue.longitude??0};if(e.referenceValue!==void 0)return je(e.referenceValue);if(e.arrayValue!==void 0)return(e.arrayValue.values??[]).map((r,n)=>Fe(r,`${t}[${String(n)}]`));if(e.mapValue!==void 0)return z(e.mapValue.fields??{},t);throw new f("INTERNAL",`${t}: unrecognised Firestore value ${JSON.stringify(e).slice(0,80)}`)},ur=new Set(["createTime","fields","name","readTime","updateTime"]),fr=e=>{if(typeof e!="object"||e===null||Array.isArray(e))return!1;const t=Object.keys(e);return t.length===1&&t[0]?.endsWith("Value")===!0},hr=e=>{if(typeof e.name=="string")return!0;const{fields:t}=e;return typeof t!="object"||t===null?!1:Object.keys(e).every(r=>ur.has(r))&&!fr(t)},z=(e,t)=>Object.fromEntries(Object.entries(e).map(([r,n])=>[r,Fe(n,`${t}.${r}`)])),gr=(e,t,r,n)=>{const s=hr(e),o=z(s?e.fields??{}:e,n),i=e.name??e.__name__,a=typeof i=="string"?je(i):t;if(a===void 0)throw new f("INTERNAL",`${n}: no document id — expected a \`name\`/\`__name__\` resource path, or a document keyed by its id`);const c=r?.types??{};for(const[d,l]of Object.entries(c)){const u=o[d];if(u!==void 0){if(u!==null&&typeof u!="boolean"&&typeof u!="number"&&typeof u!="string")throw new f("INTERNAL",`${n}.${d}: a \`${l}\` reshape needs a scalar, but this field decoded to an object or array`);o[d]=Le(d,l,u===null?null:String(u))}}return{...o,_id:a}},mr=async(e,t)=>Ie(e,t,{authFiles:new Set(t?.auth?.file===void 0?[]:[p(t.auth.file)]),emptyMessage:"holds no .json/.ndjson collection files",matches:r=>ge.test(r),tableNameOf:r=>r.replace(ge,"")},async r=>x(r,{withFileTypes:!0})),pr=async function*(e){const t=M({crlfDelay:Number.POSITIVE_INFINITY,input:E(e,"utf8")});let r=0;try{for await(const n of t){r+=1;const s=n.trim();if(s.length!==0)try{yield{raw:JSON.parse(s)}}catch(o){throw new f("INTERNAL",`${p(e)} line ${String(r)}: invalid JSON — ${o instanceof Error?o.message:String(o)}`,{cause:o})}}}finally{t.close()}},yr=async e=>{const t=await v(e,"utf8");let r;try{r=JSON.parse(t)}catch(n){throw new f("INTERNAL",`${p(e)}: invalid JSON — ${n instanceof Error?n.message:String(n)}`,{cause:n})}if(r!==null&&typeof r=="object"&&Array.isArray(r.documents))return r.documents.map(n=>({raw:n}));if(Array.isArray(r))return r.map(n=>({raw:n}));if(r!==null&&typeof r=="object")return Object.entries(r).map(([n,s])=>({fallbackId:n,raw:s}));throw new f("INTERNAL",`${p(e)}: expected an object, an array, or \`{ documents: [...] }\``)},wr=async function*(e,t){const r=t?.tables?.[e.table],n=e.file.toLowerCase().endsWith(".ndjson")?pr(e.file):await yr(e.file);let s=0;for await(const o of n)yield gr(o.raw,o.fallbackId,r,`${e.table}[${String(s)}]`),s+=1},br=async function*(e,t,r,n,s){yield*Pe("firebase",s,t,r,n),yield*Ae(e,r,n,o=>wr(o,t))},I=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),q=(e,t)=>{if(!I(e))throw new f("INTERNAL",`${t}: expected a JSON object`);return e},T=(e,t,r)=>{const n=e[t];if(n!==void 0&&typeof n!="string")throw new f("INTERNAL",`${r}: \`${t}\` must be a string`);return n},Me=(e,t,r)=>{const n=e[t];if(n!==void 0&&(!Array.isArray(n)||n.some(s=>typeof s!="string")))throw new f("INTERNAL",`${r}: \`${t}\` must be an array of column names`);return n},$r=(e,t)=>{const r=q(e,t),n=T(r,"file",t),s=T(r,"idColumn",t),o=Me(r,"storageColumns",t),{types:i}=r;if(i!==void 0){if(!I(i))throw new f("INTERNAL",`${t}.types must be an object of column → reshape`);for(const[a,c]of Object.entries(i))if(!Pt(c))throw new f("INTERNAL",`${t}.types.${a}: unknown reshape ${JSON.stringify(c)} — expected one of ${Re.join(", ")}`)}return{file:n,idColumn:s,storageColumns:o,types:i}},Sr=(e,t)=>{if(e!==void 0){if(!I(e))throw new f("INTERNAL",`${t}: \`auth\` must be an object`);for(const r of["file","identitiesFile"])T(e,r,`${t}: auth`)}},vr=(e,t)=>{if(e!==void 0){if(!I(e))throw new f("INTERNAL",`${t}: \`tables\` must be an object of table → mapping`);return Object.fromEntries(Object.entries(e).map(([r,n])=>[r,$r(n,`${t}: tables.${r}`)]))}},Nr=(e,t)=>{const r=q(e,t),n=T(r,"keyPrefix",t),{auth:s,tables:o}=r;return Sr(s,t),{auth:s,keyPrefix:n,tables:vr(o,t)}},De=e=>w("lunora",`import-${e}.json`),Er=async(e,t,r)=>{const n=De(t),s=w(e,n);let o;try{o=await v(s,"utf8")}catch(a){if(a.code==="ENOENT"){r.info(`no ${n} found — every column is copied through untouched (run with --scan to generate one)`);return}throw a}let i;try{i=JSON.parse(o)}catch(a){throw new f("INTERNAL",`${s}: invalid JSON — ${a instanceof Error?a.message:String(a)}`,{cause:a})}return Nr(i,s)},Tr=async e=>{const t=[];let r;for(;;){const n=await e(r);if(t.push(...Array.isArray(n.records)?n.records:[]),n.truncated!==!0||typeof n.cursor!="string"||n.cursor.length===0)return t;if(n.cursor===r)throw new Error("collectPages: the list did not advance its cursor — refusing to page forever");r=n.cursor}},Ir=8,Ar=24*1048576,Rr=1e3,_r=/^[\dA-F]{64}$/i,Or=e=>{for(let t=0;t<e.length;t+=1){const r=e.codePointAt(t)??0;if(r<32||r===127)return!1}return!0},kr=/^[\d+/A-Z]{43}=$/i,Lr=e=>_r.test(e)?e.toLowerCase():kr.test(e)?Buffer.from(e,"base64").toString("hex"):void 0,xr=(e,t)=>{const r=JSON.parse(e),n=r._id;if(typeof n!="string"||n.length===0||n.includes("/")||n.includes("\\"))throw new f("INTERNAL",`${t}: \`_id\` must be a path-free non-empty string`);if(typeof r.sha256!="string")throw new f("INTERNAL",`${t}: \`sha256\` is missing — re-export with \`npx convex export --include-file-storage\``);const s=Lr(r.sha256);if(s===void 0)throw new f("INTERNAL",`${t}: \`sha256\` is neither base16 nor base64 SHA-256 (${r.sha256})`);if(typeof r.size!="number"||!Number.isInteger(r.size)||r.size<0)throw new f("INTERNAL",`${t}: \`size\` must be a non-negative integer`);if(r.contentType!==void 0&&(typeof r.contentType!="string"||!Or(r.contentType)))throw new f("INTERNAL",`${t}: \`contentType\` must be a string with no control characters`);return{contentType:typeof r.contentType=="string"?r.contentType:void 0,id:n,sha256:s,size:r.size}},C=async(e,t,r)=>{const n=[];try{let s=0;for await(const o of H(e,t)){const i=o.trim();s+=1,i.length>0&&n.push(xr(i,`_storage/documents.jsonl line ${String(s)}`))}}catch(s){const o=s instanceof Error?s.message:String(s);throw r.error(`failed to read _storage metadata: ${o}`),s}return n},ze=wt,U=e=>e.bucket===void 0?"":`&bucket=${encodeURIComponent(e.bucket)}`,W=async(e,t)=>await Tr(async r=>{const n=`${e.baseUrl}${V}?prefix=${encodeURIComponent(t)}&limit=${String(Rr)}${r===void 0?"":`&cursor=${encodeURIComponent(r)}`}${U(e)}`,s=await e.fetchImpl(n,{headers:{authorization:`Bearer ${e.token}`},method:"GET"});if(!s.ok){const i=await s.text().catch(()=>"<no body>");throw new f("INTERNAL",`storage list failed (HTTP ${String(s.status)}): ${i}`)}const o=await s.json();return{cursor:o.cursor,records:o.objects,truncated:o.truncated}}),Cr=async(e,t,r,n)=>{const s=`${e.baseUrl}${V}?key=${encodeURIComponent(t)}&expectedSha256=${n.sha256}&expectedSize=${String(n.size)}${U(e)}`,o=await e.fetchImpl(s,{body:new Uint8Array(r),headers:{authorization:`Bearer ${e.token}`,"content-type":n.contentType??"application/octet-stream"},method:"PUT"});if(!o.ok){const a=await o.text().catch(()=>"<no body>");throw new f("INTERNAL",`blob upload failed (HTTP ${String(o.status)}): ${a}`)}const i=await o.json();if(i.sha256!==n.sha256)throw new f("INTERNAL",`blob upload verification failed: expected ${n.sha256}, got ${i.sha256??"none"}`);return t},Ur=async(e,t)=>(await e.fetchImpl(`${e.baseUrl}${V}?key=${encodeURIComponent(t)}${U(e)}`,{headers:{authorization:`Bearer ${e.token}`},method:"DELETE"}).catch(()=>{}))?.ok===!0,Pr=async(e,t,r,n,s)=>{const o=`${e.baseUrl}${bt}?key=${encodeURIComponent(t)}&method=PUT&contentType=${encodeURIComponent(n.contentType??"application/octet-stream")}${U(e)}`,i=await e.fetchImpl(o,{headers:{authorization:`Bearer ${e.token}`},method:"GET"});if(!i.ok){const m=await i.text().catch(()=>"<no body>");throw new f("INTERNAL",`blob ${t} is ${String(n.size)} bytes, above the ${String(ze)}-byte verified-upload cap, and no signed PUT URL could be minted (HTTP ${String(i.status)}): ${m}`)}const{url:a}=await i.json(),c=await e.fetchImpl(a,{body:new Uint8Array(r),headers:{"content-type":n.contentType??"application/octet-stream"},method:"PUT"});if(!c.ok){const m=await c.text().catch(()=>"<no body>");throw new f("INTERNAL",`signed PUT failed (HTTP ${String(c.status)}): ${m}`)}const l=(await W(e,t)).find(m=>m.key===t);if(l===void 0)throw new f("INTERNAL",`post-upload verification failed: blob not found at key ${t}`);const u=l.size!==void 0&&l.size!==n.size,h=l.sha256!==void 0&&l.sha256.toLowerCase()!==n.sha256;if(u||h){const m=await Ur(e,t);throw new f("INTERNAL",`post-upload verification failed: expected sha256=${n.sha256} size=${String(n.size)}, got sha256=${l.sha256??"none"} size=${String(l.size??"none")}${m?" (the object was removed)":` — AND the object could not be removed: delete ${t} by hand before re-running, or the next run will treat it as already migrated`}`)}const g=[l.size===void 0?"size":void 0,l.sha256===void 0?"sha256":void 0].filter(Boolean);return g.length>0&&s.warn(`blob ${t} went through the signed-PUT path and the host reports no ${g.join(" or ")} for it — that much of the write is unverified`),t},Be=async(e,t,r,n,s)=>{if(r.length!==n.size)throw new f("INTERNAL",`blob ${n.id} is ${String(r.length)} bytes on disk but the export declares ${String(n.size)}`);return r.length<=ze?Cr(e,t,r,n):Pr(e,t,r,n,s)},jr=e=>{const t=[];let r=[],n=0;for(const s of e)r.length>0&&(r.length>=Ir||n+s.size>Ar)&&(t.push(r),r=[],n=0),r.push(s),n+=s.size;return r.length>0&&t.push(r),t},B=(e,t)=>`${e}${t.sha256}`,Fr=async(e,t,r,n)=>{const s=await C(e,t,n);return new Map(s.map(o=>[o.id,B(r,o)]))},Mr=async(e,t,r,n,s)=>{const o=await C(t,r,s),i=new Map,a=await W(e,n),c=new Map(a.map(u=>[u.key,u])),d=[];for(const u of o){const h=B(n,u);c.get(h)?.size===u.size?i.set(u.id,h):d.push(u)}s.info(`migrating ${String(d.length)} storage blobs${i.size>0?` (${String(i.size)} already present)`:""}...`);const l=async u=>{try{const h=await xt(t,u.id);i.set(u.id,await Be(e,B(n,u),h,u,s))}catch(h){const g=h instanceof Error?h.message:String(h);throw s.error(`failed to upload blob ${u.id}: ${g}`),h}};for(const u of jr(d)){const g=(await Promise.allSettled(u.map(m=>l(m)))).find(m=>m.status==="rejected");if(g!==void 0)throw g.reason}return s.success(`migrated ${String(d.length)} storage blobs`),i};async function*Dr(e,t,r){let n=0;for await(const s of H(e,t)){const o=s.trim();if(n+=1,o.length===0)continue;let i;try{i=JSON.parse(o)}catch(a){throw new f("INTERNAL",`${t.table}/documents.jsonl line ${String(n)}: invalid JSON — ${a instanceof Error?a.message:String(a)}`,{cause:a})}r.set(t.table,(r.get(t.table)??0)+1),yield`${JSON.stringify({doc:i,table:t.table})}
`}}async function*zr(e,t,r,n,s){for(const o of t){if(Ee(o.table)){o.table===N&&!n&&r.warn(`skipping "${N}" — those rows describe stored files, and their blobs were not migrated. Re-run with --with-storage to upload them and rewrite the references.`);continue}s.has(o.table)||s.set(o.table,0),yield*Dr(e,o,s)}}const fs=["firebase","supabase"],Br=async(e,t,r)=>{if(e.withStorage===!0&&t==="firebase"&&e.storageDir===void 0)return e.logger.error("--with-storage on Firebase needs --storage-dir — download the bucket first with `gcloud storage cp -r gs://<bucket> <dir>`."),{kind:"invalid"};if(e.table!==void 0)return e.logger.error(`--table cannot be combined with --from ${t} — each row's table comes from its source file.`),{kind:"invalid"};const n=await Er(r,t,e.logger),s=Object.values(n?.tables??{}).some(o=>(o.storageColumns??[]).length>0);return e.verify===!0&&e.withStorage!==!0&&s?(e.logger.error("--verify with `storageColumns` declared requires --with-storage — otherwise every storage path stays unmigrated and only row counts would be checked."),{kind:"invalid"}):t==="supabase"?{kind:"supabase",mapping:n,tables:await qt(e.file,n)}:{collections:await mr(e.file,n),kind:"firebase",mapping:n}},Jr=async e=>{if(!await L(e.file).then(()=>!0,()=>!1))return!1;for(const[r,n]of[["--scan",e.scan],["--verify",e.verify],["--with-storage",e.withStorage]])if(n===!0)return e.logger.error(`${r} requires a Convex export directory or .zip snapshot — ${e.file} is not one.`),!0;return!1},Vr=async(e,t,r)=>{const n=t.find(o=>o.table===N);if(n===void 0)return r.logger.error("--verify cannot check file references: this export has no `_storage` table, so it was taken without `--include-file-storage`. Re-export with that flag and pass --with-storage, or drop --verify."),!0;const s=await C(e,n,r.logger);return s.length>0?(r.logger.error(`--verify on an export carrying ${String(s.length)} stored file(s) requires --with-storage — otherwise every file reference stays unmigrated and only row counts would be checked.`),!0):!1},Hr=async(e,t)=>{if(e.from==="supabase"||e.from==="firebase")return Br(e,e.from,t);const r=await _t(e.file),n=r===void 0?void 0:await Lt(r);return r!==void 0&&n===void 0?(e.logger.error(`${e.file} is a ${r.kind==="zip"?".zip":"directory"} but holds no <table>/documents.jsonl — expected a \`npx convex export --path\` snapshot, or pass an NDJSON file.`),{kind:"invalid"}):r===void 0||n===void 0?await Jr(e)?{kind:"invalid"}:{kind:"ndjson"}:e.table!==void 0?(e.logger.error("--table cannot be combined with a Convex export directory — each row's table comes from its source directory."),{kind:"invalid"}):e.verify===!0&&e.withStorage!==!0&&await Vr(r,n,e)?{kind:"invalid"}:{kind:"convex",snapshot:r,tables:n}},k=w("lunora","import-convex.json"),Gr=(e,t)=>{const r=q(e,t),n=T(r,"keyPrefix",t),s=r.storageColumns;if(s!==void 0){if(!I(s))throw new f("INTERNAL",`${t}: \`storageColumns\` must be an object of table → column names`);for(const o of Object.keys(s))Me(s,o,`${t}: storageColumns`)}return{keyPrefix:n,storageColumns:s}},qr=async(e,t)=>{const r=w(e,k);let n;try{n=await v(r,"utf8")}catch(o){if(o.code==="ENOENT"){t.info(`no ${k} found — rewriting only self-describing { $storage } refs (run with --scan to generate one)`);return}throw o}let s;try{s=JSON.parse(n)}catch(o){throw new f("INTERNAL",`${r}: invalid JSON — ${o instanceof Error?o.message:String(o)}`,{cause:o})}return Gr(s,r)},Wr=(e,t,r)=>{try{return JSON.parse(e)}catch(n){throw new f("INTERNAL",`${t}/documents.jsonl line ${String(r)}: invalid JSON — ${n instanceof Error?n.message:String(n)}`,{cause:n})}},Kr=async(e,t,r)=>{const n=new Map([...r].map(i=>[i,i])),s=[];let o=0;for await(const i of H(e,t)){const a=i.trim();if(o+=1,a.length===0)continue;const{ambiguous:c}=Te(Wr(a,t.table,o),n,t.table);for(const{column:d}of c)s.includes(d)||s.push(d)}return s},Yr=async(e,t,r)=>{const n={};for(const s of t){if(Ee(s.table))continue;const o=await Kr(e,s,r);o.length>0&&(n[s.table]=o)}return n},Xr=async(e,t,r)=>{const n=w(t,k),s=`${JSON.stringify(e,void 0,4)}
`;await J(w(t,"lunora"),{recursive:!0});try{await $e(n,s,{encoding:"utf8",flag:"wx"}),r.success(`wrote candidate mapping to ${n} — review it, then re-run without --scan`)}catch(o){if(o.code!=="EEXIST")throw o;r.warn(`${n} already exists — leaving it untouched. Candidate mapping:`),r.info(s)}},Zr=async(e,t,r,n)=>{const s=t.find(c=>c.table===N);if(s===void 0){n.error("no `_storage` table in this export — re-export with `npx convex export --include-file-storage`");return}const o=await C(e,s,n),i=new Set(o.map(c=>c.id));n.info(`found ${String(i.size)} storage ids`);const a={keyPrefix:"",storageColumns:await Yr(e,t,i)};return await Xr(a,r,n),a},S=20,Qr=(e,t,r)=>{let n=0;if(r.conflicts===0)for(const[s,o]of t){const i=r.inserted[s]??0;i<o&&(n+=1,e.error(`verify: ${s} inserted ${String(i)} of ${String(o)} source rows (${String(o-i)} missing)`))}else{const s=[...t.values()].reduce((i,a)=>i+a,0),o=Object.values(r.inserted).reduce((i,a)=>i+a,0)+r.conflicts;o<s&&(n+=1,e.error(`verify: ${String(o)} of ${String(s)} source rows accounted for across all tables (${String(s-o)} missing; ${String(r.conflicts)} already present)`))}return n>0?e.error(`verify: ${String(n)} row-parity check(s) failed`):e.success("verify: all source rows accounted for"),n},me=(e,t,r)=>{for(const n of t.slice(0,S))e.warn(r(n));t.length>S&&e.warn(`… and ${String(t.length-S)} more`)},Je=(e,t,r)=>(e.info(`storage refs: ${String(t.rewritten)} rewritten, ${String(t.unmigrated.length)} unmigrated, ${String(t.ambiguous.length)} ambiguous`),me(e,t.unmigrated,n=>`unmigrated storage reference ${n.table}.${n.column}: ${n.storageId} has no exported blob — re-export with \`npx convex export --include-file-storage\``),me(e,t.ambiguous,n=>`unrewritten storage id in ${n.table}.${n.column}: ${n.storageId} — if that column holds storage references, add it to ${k} and re-import`),r&&t.unmigrated.length>0?(e.error(`verify: ${String(t.unmigrated.length)} storage reference(s) resolved to no migrated blob`),!0):!1),en=(e,t,r)=>{for(const n of[...t].slice(0,S))e.warn(`storage path never transferred: ${n} — left as-is`);return t.size>S&&e.warn(`… and ${String(t.size-S)} more untransferred storage paths`),r&&t.size>0},pe=200,tn=/^\\x[\dA-Fa-f]*$/,rn=/^[+-]?\d+$/,nn=/^[+-]?\d+\.\d+$/,sn=/^\{.*\}$/,on=/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}/,an=[["bytea-base64",e=>tn.test(e)],["timestamp-ms",e=>on.test(e)],[e=>e.some(t=>!Number.isSafeInteger(Number(t)))?"int8-string":void 0,e=>rn.test(e)],["boolean",e=>e==="f"||e==="t"],["number",e=>nn.test(e)],[e=>e.every(t=>sn.test(t))?"text-array":"json",e=>e.startsWith("{")||e.startsWith("[")]],cn=e=>{if(e.length!==0){for(const[t,r]of an)if(e.every(n=>r(n)))return typeof t=="function"?t(e):t}},dn=(e,t)=>{for(const[r,n]of Object.entries(e)){if(n.length===0)continue;const s=t.get(r);s===void 0?t.set(r,[n]):s.push(n)}},ln=async e=>{const t=new Map,r=E(e).pipe(Ne({columns:!0,skipEmptyLines:!0,toLine:pe+1}));let n=0;for await(const o of r)if(dn(o,t),n+=1,n>=pe)break;r.destroy();const s={};for(const[o,i]of t){const a=cn(i);a!==void 0&&(s[o]=a)}return s},Ve=async(e,t,r,n)=>{const s=De(r),o=w(t,s),i=`${JSON.stringify(e,void 0,4)}
`;await J(Se(o),{recursive:!0});try{await $e(o,i,{encoding:"utf8",flag:"wx"}),n.success(`wrote candidate mapping to ${o} — review the inferred types, then re-run without --scan`)}catch(a){if(a.code!=="EEXIST")throw a;n.warn(`${o} already exists — leaving it untouched. Candidate mapping:`),n.info(i)}},un=async(e,t,r)=>{const n={};for(const o of e){const i=await ln(o.file);r.info(`${p(o.file)} → ${o.table}: ${String(Object.keys(i).length)} column(s) need a reshape`),n[o.table]={file:p(o.file),idColumn:"id",...Object.keys(i).length>0?{types:i}:{}}}const s={keyPrefix:"",tables:n};return await Ve(s,t,"supabase",r),s},fn=async(e,t,r)=>{const n={};for(const o of e)n[o.table]={file:p(o.file),storageColumns:[]};r.info(`found ${String(e.length)} collection(s) — Firestore values are self-describing, so only storage columns need declaring`);const s={keyPrefix:"",tables:n};return await Ve(s,t,"firebase",r),s},K=e=>w("lunora",`.import-storage-${e}.ndjson`),hn=async(e,t,r)=>{const n=new Map;let s;try{s=await v(w(e,K(t)),"utf8")}catch(i){if(i.code==="ENOENT")return n;throw i}let o=0;for(const i of s.split(`
`)){const a=i.trim();if(a.length!==0)try{const c=JSON.parse(a);typeof c.path=="string"&&typeof c.key=="string"?n.set(c.path,c):o+=1}catch{o+=1}}return n.size>0&&r.info(`resuming: ${String(n.size)} object(s) already transferred${o>0?` (${String(o)} unreadable checkpoint line(s) ignored)`:""}`),n},gn=async(e,t,r)=>{const n=w(e,K(t));await J(Se(n),{recursive:!0}),await ot(n,`${JSON.stringify(r)}
`,"utf8")},He=100,mn=e=>e.split("/").map(t=>encodeURIComponent(t)).join("/"),pn=async(e,t,r,n,s)=>{const o=await r(`${e.url}/storage/v1/object/list/${encodeURIComponent(t)}`,{body:JSON.stringify({limit:He,offset:s,prefix:n}),headers:{authorization:`Bearer ${e.serviceKey}`,"content-type":"application/json"},method:"POST"});if(!o.ok){const i=await o.text().catch(()=>"<no body>");throw new f("INTERNAL",`Supabase storage list failed for bucket ${t} (HTTP ${String(o.status)}): ${i}`)}return await o.json()},Ge=async(e,t,r,n="")=>{const s=[];let o=0;for(;;){const i=await pn(e,t,r,n,o);for(const a of i){const c=n===""?a.name:`${n}/${a.name}`;a.id===null||a.id===void 0?s.push(...await Ge(e,t,r,c)):s.push({contentType:a.metadata?.mimetype,name:c})}if(i.length<He)return s;o+=i.length}},yn=async(e,t,r)=>{const n=await t(`${e.url}/storage/v1/bucket`,{headers:{authorization:`Bearer ${e.serviceKey}`},method:"GET"});if(!n.ok){const i=await n.text().catch(()=>"<no body>");throw new f("INTERNAL",`Supabase bucket list failed (HTTP ${String(n.status)}): ${i} — check the project URL and that the key is the service-role key, not the anon key`)}const s=await n.json(),o=[];for(const i of s){const a=await Ge(e,i.name,t);r.info(`supabase bucket ${i.name}: ${String(a.length)} object(s)`);for(const c of a){const d=`${i.name}/${c.name}`;o.push({contentType:c.contentType,bytes:async()=>{const l=await t(`${e.url}/storage/v1/object/${encodeURIComponent(i.name)}/${mn(c.name)}`,{headers:{authorization:`Bearer ${e.serviceKey}`},method:"GET"});if(!l.ok){const u=await l.text().catch(()=>"<no body>");throw new f("INTERNAL",`Supabase download failed for ${d} (HTTP ${String(l.status)}): ${u}`)}if(l.arrayBuffer===void 0)throw new f("INTERNAL","the fetch implementation cannot read response bytes, which the storage transfer requires");return Buffer.from(await l.arrayBuffer())},path:d})}}return o},wn=async e=>{const t=F(e),r=[],n=async s=>{const o=await x(s,{withFileTypes:!0}).catch(()=>{});if(o===void 0)throw new f("INTERNAL",`${e} is not a readable directory — download the bucket first with \`gcloud storage cp -r gs://<bucket> <dir>\``);for(const i of o){const a=F(s,i.name);if(!ve(t,a))throw new f("INTERNAL",`${i.name} resolves outside ${e} — refusing to upload it`);i.isDirectory()?await n(a):i.isFile()&&r.push({bytes:async()=>v(a),path:ut(t,a).split(ft).join("/")})}};return await n(t),r},bn=async(e,t,r,n,s)=>{const o=await t.bytes(),i=St("sha256").update(o).digest("hex"),a=`${r.keyPrefix}${i}`;if(n.get(a)?.size!==o.length){const c={contentType:t.contentType,id:t.path,sha256:i,size:o.length};await Be(e,a,o,c,s)}return await gn(r.cwd,r.source,{key:a,path:t.path,size:o.length}),a},$n=(e,t)=>{const r=e>500?100:25;return n=>{if(n===e||n%r===0){const s=e===0?100:Math.round(n/e*100);t.info(`transferred ${String(n)}/${String(e)} object(s) (${String(s)}%)`)}}},ye=async(e,t,r,n)=>{const s=new Map,o=await hn(r.cwd,r.source,n),i=await W(e,r.keyPrefix),a=new Map(i.map(l=>[l.key,l]));o.size>0&&i.length===0&&n.warn(`the checkpoint records ${String(o.size)} transferred object(s) but the target holds none under \`${r.keyPrefix}\` — re-transferring (a different deployment, a wiped bucket, or a changed keyPrefix)`);const c=$n(t.length,n);let d=0;n.info(`transferring ${String(t.length)} object(s) to R2...`);for(const l of t){const u=o.get(l.path);if(u!==void 0&&a.has(u.key)){s.set(l.path,u.key),d+=1,c(d);continue}try{const h=await bn(e,l,r,a,n);s.set(l.path,h)}catch(h){const g=h instanceof Error?h.message:String(h);throw n.error(`failed transferring ${l.path} after ${String(d)} object(s): ${g}`),n.error(`progress is saved — re-run the same command to continue from here (delete ${K(r.source)} to start over)`),h}d+=1,c(d)}return n.success(`transferred ${String(t.length)} object(s) to R2`),s},we=/^\/+/,Sn=/\/storage\/v1\/object\/(?:public\/|sign\/|authenticated\/)?/,vn=/[#?]/u,Nn=e=>{const t=new Map(e),r=new Map;for(const[n,s]of e){const o=n.indexOf("/");if(o===-1)continue;const i=n.slice(o+1);i.length===0||e.has(i)||r.set(i,r.has(i)?void 0:s)}for(const[n,s]of r)s!==void 0&&t.set(n,s);return t},En=e=>{const t=Sn.exec(e);if(t===null)return;const r=e.slice(t.index+t[0].length).split(vn)[0]??"";if(r.length!==0)try{return decodeURIComponent(r)}catch{return r}},Tn=(e,t)=>{const r=t.get(e);if(r!==void 0)return r;const n=e.replace(we,""),s=t.get(n);if(s!==void 0)return s;const o=En(e);return o===void 0?void 0:t.get(o)??t.get(o.replace(we,""))},In=500,An=9e5,Rn=async e=>{if(e.prod&&e.url===void 0)return e.logger.error("--prod requires an explicit --url (refusing to import to the implicit localhost worker)"),{refused:$.USAGE};const t=ct(e.url,e.logger,e.cwd);if(t===void 0)return{refused:$.USAGE};if(it({prod:e.prod,url:t})&&e.yes!==!0)return e.logger.error(`import bulk-writes ${t}, which is not local. Re-run with --yes to confirm.`),{refused:$.USAGE};const{token:r}=at({cwd:e.cwd??process.cwd(),token:e.token,url:t});if(!r)return e.logger.error("admin token required — pass --token, set LUNORA_ADMIN_TOKEN, or add it to .dev.vars (local targets only)"),{refused:$.AUTH};try{const s=await L(e.file);if(!s.isFile()&&!s.isDirectory())return e.logger.error(`not a file or directory: ${e.file}`),{refused:$.USAGE}}catch(s){const o=s instanceof Error?s.message:String(s);return e.logger.error(`failed to stat ${e.file}: ${o}`),{refused:$.NOT_FOUND}}const n=e.fetchImpl??dt;return{baseUrl:t,fetchImpl:n,requestUrl:`${t}${$t}`,token:r}},qe=(e,t,r)=>({conflicts:e.conflicts,errors:e.errors,...e.failed.length>0?{failed:e.failed}:{},inserted:e.inserted,received:e.received,replaced:e.replaced,...t===void 0?{}:{storage:{ambiguous:r.ambiguous.slice(0,S),ambiguousTotal:r.ambiguous.length,blobs:t.size,rewritten:r.rewritten,unmigrated:r.unmigrated.slice(0,S),unmigratedTotal:r.unmigrated.length}},...e.warnings.length>0?{warnings:e.warnings}:{}}),_n=(e,t,r,n)=>{switch(e.kind){case"convex":return zr(e.snapshot,e.tables,t.logger,r,n);case"firebase":return br(e.collections,e.mapping,t.logger,n,t.file);case"supabase":return Yt(e.tables,e.mapping,t.logger,n,t.file);default:return E(t.file,{encoding:"utf8"})}},On=async(e,t,r)=>{switch(e.kind){case"convex":return Zr(e.snapshot,e.tables,t,r);case"firebase":return fn(e.collections,t,r);case"supabase":return un(e.tables,t,r);default:{r.error("--scan needs a Convex, Supabase, or Firebase source.");return}}},kn=async(e,t,r,n)=>{const s=t.mapping?.keyPrefix??"";if(r.storageDir!==void 0)return ye(e,await wn(r.storageDir),{cwd:n,keyPrefix:s,source:t.kind},r.logger);if(t.kind==="firebase"){r.logger.error("--with-storage needs --storage-dir for a Firebase source: download the bucket first (`gcloud storage cp -r gs://<bucket> ./storage`), then point --storage-dir at it.");return}const o=process.env.SUPABASE_URL,i=process.env.SUPABASE_SERVICE_ROLE_KEY;if(o===void 0||i===void 0){r.logger.error("--with-storage needs SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in the environment (the service-role key, not the anon key), or --storage-dir pointing at an already-downloaded bucket.");return}let a=o.length;for(;a>0&&o[a-1]==="/";)a-=1;const c=o.slice(0,a);if(!c.startsWith("https://")){r.logger.error(`SUPABASE_URL must be https:// — refusing to send the service-role key over ${c.split(":")[0]??"an unknown scheme"}.`);return}const d=await yn({serviceKey:i,url:c},e.fetchImpl,r.logger);return ye(e,d,{cwd:n,keyPrefix:s,source:t.kind},r.logger)},Ln=(e,t,r,n,s)=>{const o=n.mapping?.tables?.[t]?.storageColumns??[];if(o.length===0)return e;const i={...e};for(const a of o){const c=i[a];if(typeof c!="string"||c.length===0)continue;const d=Tn(c,r);d===void 0?s.add(`${t}.${a}: ${c}`):i[a]=d}return i},xn=e=>{const t=e.source.kind==="supabase"||e.source.kind==="firebase"?e.source:void 0,{transferredPaths:r}=e,n=r===void 0||t===void 0?void 0:Nn(r);return Ut({remapDocument:n===void 0||t===void 0?void 0:(s,o)=>Ln(s,o,n,t,e.unresolvedPaths),report:e.report,storageColumns:e.storageColumns,storageIdMap:e.storageIdMap,table:e.table})},Cn=async(e,t,r,n)=>{let s="",o=0;const i=async a=>{o+=1;const c=t(a,o);c!==void 0&&await r.push(c)};try{for await(const a of e){s+=typeof a=="string"?a:a.toString("utf8");let c=s.indexOf(`
`);for(;c!==-1;)await i(s.slice(0,c)),s=s.slice(c+1),c=s.indexOf(`
`)}s.length>0&&await i(s),await r.flush();return}catch(a){const c=a instanceof Error?a.message:String(a);return r.totals.received===0?(n.error(`import failed before any batch was acknowledged: ${c}`),n.error("re-run the same command once the worker is reachable (existing rows conflict rather than duplicate)")):(n.error(`import failed part-way through: ${c}`),n.error("the rows below had already been written — re-run the same command to resume (existing rows conflict rather than duplicate)")),a}},Un=async(e,t,r)=>{if(t.withStorage!==!0||e.kind!=="convex")return{};const n=await qr(r,t.logger),s=e.tables.find(o=>o.table===N);if(s===void 0){t.logger.error("--with-storage requires a Convex export with a `_storage` metadata table — re-export with `npx convex export --include-file-storage`.");return}return{mapping:n,storageIdMap:await Fr(e.snapshot,s,n?.keyPrefix??"",t.logger)}},Pn=async(e,t,r,n)=>{const s=t.tables.find(i=>i.table===N);if(s===void 0)return;const o=await Mr(e,t.snapshot,s,r.mapping?.keyPrefix??"",n);return n.info(`storage map: ${String(o.size)} blobs mapped`),{mapping:r.mapping,storageIdMap:o}},jn=async(e,t,r,n,s)=>{if(r.withStorage!==!0)return{};if(t.kind==="convex")return Pn(e,t,s,r.logger);if(t.kind!=="supabase"&&t.kind!=="firebase")return{};try{const o=await kn(e,t,r,n);return o===void 0?void 0:{transferredPaths:o}}catch(o){r.logger.error(`storage transfer failed: ${o instanceof Error?o.message:String(o)}`),r.logger.error("no rows were imported — fix the error above and re-run; objects that did transfer are checkpointed and will be skipped");return}},Fn=(e,t)=>{for(const o of t.warnings)e.warn(o);const r=t.received-t.insertedTotal-t.replaced-t.conflicts-t.errorCount;r>0&&e.warn(`${String(r)} of ${String(t.received)} rows were neither inserted, replaced, conflicted, nor reported as errors`);const n=t.replaced>0?`${String(t.replaced)} replaced, `:"",s=`imported ${String(t.insertedTotal+t.replaced)} of ${String(t.received)} rows (${n}${String(t.conflicts)} conflicts, ${String(t.errorCount)} errors)`;t.failed?e.error(s):e.success(s)},Mn=async(e,t,r)=>await On(e,t,r.logger)===void 0?{body:void 0,code:$.USAGE,error:"import --scan: the export could not be scanned",inserted:0}:{body:void 0,code:0,inserted:0},Dn="GLOBAL_DRY_RUN_UNSUPPORTED",zn={error:()=>{},info:()=>{},success:()=>{},warn:()=>{}},Bn=e=>({...e,logger:zn}),be=async(e,t,r,n)=>{const s=new Set,o={ambiguous:[],rewritten:0,unmigrated:[]},i=new Map,a=_n(t,e,r.storageIdMap!==void 0,i),c=Ct({batchSize:n.batchSize,fetchImpl:n.fetchImpl,maxBatchBytes:An,requestUrl:n.requestUrl,token:n.token}),d=xn({report:o,source:t,storageColumns:r.mapping?.storageColumns,storageIdMap:r.storageIdMap,table:e.table,transferredPaths:r.transferredPaths,unresolvedPaths:s}),l=await Cn(a,d,c,e.logger);return{batcher:c,remapReport:o,sourceRows:i,streamFailure:l,unresolvedPaths:s}},Y=e=>e instanceof Error?e.message:typeof e=="string"?e:"an unknown failure",We=e=>{const{conflicts:t,inserted:r}=e.batcher.totals;return t>0||Object.values(r).some(n=>n>0)},Jn=(e,t,r,n)=>{const s=qe(t.batcher.totals,n,t.remapReport),o=We(t);if(e.format!=="json"&&e.logger.info(JSON.stringify(s,void 0,2)),t.streamFailure!==void 0){const c=Y(t.streamFailure);e.logger.error(t.batcher.totals.received===0?`import failed before any batch was acknowledged: ${c}`:`import failed while checking the rows: ${c}`),e.logger.error(o?"rows may already be written by the check; check the shard before running the command again":"no rows were written; re-run the same command once the worker is reachable")}for(const c of t.batcher.totals.failed)e.logger.error(`import: shard "${c.shardKey}" was never reached${c.timedOut?" (timed out)":""} — no rows were written: ${c.message}`);const i=Object.values(t.batcher.totals.inserted).reduce((c,d)=>c+d,0),a=o?`import: ${e.file} was refused, and rows may already be written: ${r}`:`import: ${e.file} was not written: ${r}`;return{body:s,code:1,data:{file:e.file,inserted:i,summary:s},error:a,inserted:i}},Vn=(e,t)=>{if(e.streamFailure!==void 0)return`the check stopped early: ${Y(e.streamFailure)}`;const{errors:r,failed:n}=e.batcher.totals;if(We(e))return"this worker wrote rows during its check, so it cannot check an import before writing, and some rows may already be written";if(n.length>0)return`${String(n.length)} shard(s) could not be reached to check`;const s=r.filter(i=>i.code!==Dn);if(s.length>0)return`${String(s.length)} row(s) would be refused`;const o=e.remapReport.unmigrated.length;return t&&o>0?`${String(o)} storage reference(s) resolve to no exported blob`:void 0},Hn=(e,t)=>{e.verify===!0&&t.remapReport.unmigrated.length>0&&Je(e.logger,t.remapReport,!0)},Gn=(e,t)=>{for(const r of t)e.error(`import: shard "${r.shardKey}" was never reached${r.timedOut?" (timed out)":""} — its rows were NOT written: ${r.message}`)},qn=async(e,t)=>{try{const r=await e.fetchImpl(`${e.requestUrl}?dryRun=true${t?"&onConflict=replace":""}`,{body:`
`,headers:{authorization:`Bearer ${e.token}`,"content-type":"application/x-ndjson"},method:"POST"});if(!r.ok)return`the worker at ${e.requestUrl} answered HTTP ${String(r.status)} to the check`;const n=await r.json();return n.dryRun!==!0?`the worker at ${e.requestUrl} predates ?dryRun=true, so it would write the file while checking it; deploy the current runtime and import again`:t&&n.onConflict!=="replace"?`the worker at ${e.requestUrl} predates ?onConflict=replace, so --replace would overwrite nothing; deploy the current runtime and import again`:void 0}catch(r){return`the worker at ${e.requestUrl} did not answer the check: ${Y(r)}`}},Wn=(e,t)=>{const r=t?"onConflict=replace":void 0;return{check:`${e}?dryRun=true${r===void 0?"":`&${r}`}`,write:r===void 0?e:`${e}?${r}`}},hs=async e=>{const t=e.cwd??process.cwd(),r=await Hr(e,t);if(r.kind==="invalid")return{body:void 0,code:$.USAGE,error:`import: could not read ${e.file}`,inserted:0};if(e.scan===!0)return Mn(r,t,e);const n=await Rn(e);if(lt(n))return{body:void 0,code:n.refused,error:"import: could not resolve the worker URL and admin token",inserted:0};const{baseUrl:s,fetchImpl:o,token:i}=n,a=e.replace===!0,c=Wn(n.requestUrl,a),{write:d}=c,u={batchSize:e.batchSize??In,fetchImpl:o,requestUrl:n.requestUrl,token:i},h=await qn(u,a);if(h!==void 0)return{body:void 0,code:1,error:`import: ${e.file} was not written: ${h}`,inserted:0};const g=await Un(r,e,t);if(g===void 0)return{body:void 0,code:1,error:"import: the storage phase failed — no rows were written",inserted:0};const m=await be(Bn(e),r,g,{...u,requestUrl:c.check}),y=Vn(m,e.verify===!0);if(y!==void 0)return Hn(e,m),Jn(e,m,y,void 0);const P=await jn({baseUrl:s,fetchImpl:o,token:i},r,e,t,g);if(P===void 0)return{body:void 0,code:1,error:"import: the storage phase failed — no rows were written",inserted:0};const{storageIdMap:X}=P;e.logger.info(r.kind==="convex"?`POST ${d} -> import Convex export ${e.file} (${String(r.tables.length)} tables)`:`POST ${d} -> import ${e.file}`);const{batcher:Z,remapReport:Q,sourceRows:Ke,streamFailure:ee,unresolvedPaths:Ye}=await be(e,r,P,{...u,requestUrl:d}),{conflicts:te,errors:re,failed:ne,inserted:se,received:Xe,replaced:A,warnings:Ze}=Z.totals,Qe=e.verify===!0&&ee===void 0?Qr(e.logger,Ke,{conflicts:te+A,inserted:se}):0,et=X!==void 0&&Je(e.logger,Q,e.verify===!0),tt=en(e.logger,Ye,e.verify===!0),R=Object.values(se).reduce((rt,nt)=>rt+nt,0),_=qe(Z.totals,X,Q);Gn(e.logger,ne);const oe=ee!==void 0||re.length>0||ne.length>0||Qe>0||et||tt;e.format!=="json"&&e.logger.info(JSON.stringify(_,void 0,2)),Fn(e.logger,{conflicts:te,errorCount:re.length,failed:oe,insertedTotal:R,received:Xe,replaced:A,warnings:Ze});const ie={file:e.file,inserted:R,summary:_};return oe?{body:_,code:1,data:ie,error:`import: ${e.file} did not import cleanly`,inserted:R,replaced:A}:{body:_,code:0,data:ie,inserted:R,replaced:A}};export{In as D,fs as I,ze as M,U as b,W as l,hs as r,Be as u};