UNPKG

@lunora/cli

Version:

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

13 lines (12 loc) 49.1 kB
import{createReadStream as N}from"node:fs";import{open as We,stat as R,readdir as O,realpath as te,readFile as S,mkdir as F,writeFile as he,appendFile as Ye}from"node:fs/promises";import{t as Ke,r as Xe}from"./admin-token-VdUnvnKW.mjs";import{r as qe}from"./admin-url-BhF5ufg1.mjs";import{join as b,resolve as C,basename as p,dirname as ge,relative as Ze,sep as Qe}from"node:path";import{createInterface as j}from"node:readline";import et from"adm-zip";import{i as me}from"./path-containment-CgxYZggb.mjs";import{Transform as tt}from"node:stream";import{createInflateRaw as rt,crc32 as nt}from"node:zlib";import{LunoraError as f}from"@lunora/errors";import{parse as st}from"csv-parse/sync";import{parse as pe}from"csv-parse";import{STORAGE_UPLOAD_MAX_BODY_BYTES as ot}from"@lunora/runtime";import{S as M,a as it,I as at}from"./shared-D-zCOmgY.mjs";import{createHash as ct}from"node:crypto";const dt=67324752,A=30,lt=26,ut=28,re=0,ft=8,ht=async(e,t)=>{const r=await We(e,"r");try{const n=Buffer.alloc(A),{bytesRead:s}=await r.read(n,0,A,t);if(s<A||n.readUInt32LE(0)!==dt)throw new Error(`${e}: no local file header at offset ${String(t)} — the archive is truncated or corrupt`);return t+A+n.readUInt16LE(lt)+n.readUInt16LE(ut)}finally{await r.close()}},ne=e=>{let t=0;return new tt({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=nt(r,t),s(void 0,r)}})},gt=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!==re&&s!==ft)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 ht(e,o),a=N(e,{end:i+r-1,start:i});if(s===re){const l=ne(t);return a.on("error",u=>l.destroy(u)),a.pipe(l)}const c=rt(),d=ne(t);return a.on("error",l=>c.destroy(l)),c.on("error",l=>d.destroy(l)),a.pipe(c).pipe(d)},E="_storage",ye=e=>e.startsWith("_"),mt=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"},pt=async e=>{const t=await R(e).catch(()=>{});if(t?.isDirectory())return{kind:"directory",root:e};if(t?.isFile()&&e.toLowerCase().endsWith(".zip")){const r=new et(e);return{kind:"zip",storagePrefix:mt(r),zip:r,zipPath:e}}},yt=async e=>{const t=[];for(const r of await O(e,{withFileTypes:!0})){if(!r.isDirectory())continue;const n=b(e,r.name,"documents.jsonl");(await R(n).catch(()=>{}))?.isFile()&&t.push({file:n,table:r.name})}return t},bt=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},wt=async e=>{const t=e.kind==="directory"?await yt(e.root):bt(e.zip);return t.length>0?t.toSorted((r,n)=>r.table.localeCompare(n.table)):void 0},z=async function*(e,t){if(e.kind==="directory"){for await(const o of j({crlfDelay:Number.POSITIVE_INFINITY,input:N(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 gt(e.zipPath,r);if(n===void 0)return;const s=j({crlfDelay:Number.POSITIVE_INFINITY,input:n});try{for await(const o of s)yield o}finally{s.close(),n.destroy()}},$t=async(e,t)=>{if(e.kind==="directory"){const n=await te(b(e.root,"_storage")),s=await te(C(n,t)).catch(()=>{});if(s===void 0||!me(n,s))throw new Error(`blob ${t} resolves outside the snapshot's _storage directory`);return S(s)}const r=e.zip.readFile(`${e.storagePrefix}/${t}`);if(r===null)throw new Error(`missing blob ${t} in archive`);return Buffer.from(r)},x=207,St=e=>{const t={conflicts:0,errors:[],failed:[],inserted:{},received: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;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!==x){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===x&&(d.failed??[]).length===0&&t.failed.push({message:`the endpoint answered ${String(x)} 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}},be=(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}},se=(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))}},vt=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=be(h,s,u.table,n);h=g.document,r.rewritten+=g.rewritten,se(r.ambiguous,i,g.ambiguous),se(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)}},we=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:b(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))},$e=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})} `}},Se=["timestamp-ms","timestamp-iso","json","bytea-base64","int8-string","number","boolean","text-array"],Nt=e=>Se.includes(e),Et=/^\\x([\dA-Fa-f]*)$/,oe=/^[+-]?\d+$/,ve=/[+-]\d{2}$/,Ne=/(?:Z|[+-]\d{2}:\d{2})$/i,Ee=/^\+/,Tt=/e/i,It=/e/i,At=e=>{const[t="0",r="0"]=e.split(It),n=Number(r),s=t.startsWith("-"),[o="0",i=""]=(s?t.slice(1):t).replace(Ee,"").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},_t=/^0+(?=\d)/,ie=e=>{const r=(Tt.test(e.trim())?At(e.trim()):e.trim()).replace(Ee,""),n=r.startsWith("-"),[s="0",o=""]=(n?r.slice(1):r).split("."),i=s.replace(_t,"");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},w=(e,t,r,n)=>{throw new f("INTERNAL",`column \`${e}\`: cannot reshape ${JSON.stringify(r)} as \`${t}\` — ${n}`)},ae=(e,t,r)=>{const n=r.includes("T")?r:r.replace(" ","T"),s=ve.test(n)?`${n}:00`:n,o=Ne.test(s)?s:`${s}Z`,i=Date.parse(o);return Number.isNaN(i)&&w(e,t,r,"not a date Postgres or ISO-8601 syntax can express"),i},Rt=(e,t)=>{(!t.startsWith("{")||!t.endsWith("}"))&&w(e,"text-array",t,"not a Postgres array literal (expected `{…}`)");const r=t.slice(1,-1);if(r.length===0)return[];r.includes("{")&&w(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},Te=(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:w(e,t,r,"not a Postgres boolean literal");case"bytea-base64":{const n=Et.exec(r);if(n===null)return w(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?w(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 oe.test(r)?r:w(e,t,r,"not an integer");case"json":try{return JSON.parse(r)}catch(n){return w(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 w(e,t,r,"not a finite number");if(oe.test(r)){if(!Number.isSafeInteger(n))return w(e,t,r,"exceeds Number.MAX_SAFE_INTEGER — map this column to `int8-string` to keep it lossless")}else if(ie(String(n))!==ie(r))return w(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 Rt(e,r);case"timestamp-iso":{ae(e,t,r);const n=r.includes("T")?r:r.replace(" ","T"),s=ve.test(n)?`${n}:00`:n;return Ne.test(s)?s:`${s}Z`}case"timestamp-ms":return ae(e,t,r);default:return r}},Ot="auth.",Lt=new Set(["confirmation_token","email_change_token_current","email_change_token_new","encrypted_password","password","password_hash","passwordhash","reauthentication_token","recovery_token","salt"]),kt=String.raw`\N`,Ie=(e,t)=>t.header?e:!t.quoting&&e.length===0||e===kt?null:e,xt=e=>new Set([e?.auth?.file,e?.auth?.identitiesFile].filter(t=>t!==void 0).map(t=>p(t))),Ct=async(e,t)=>we(e,t,{authFiles:xt(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(Ot),tableNameOf:r=>p(r,".csv")},async r=>O(r,{withFileTypes:!0})),jt=(e,t,r)=>{const n=t?.idColumn??"id",s=t?.types??{},o={};let i=!1;for(const[a,c]of Object.entries(e)){if(Lt.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:Te(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},Pt=async function*(e,t){const r=t?.tables?.[e.table],n=N(e.file).pipe(pe({cast:Ie,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=jt(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}},Ut=async function*(e,t,r,n,s){yield*Re("supabase",s,t,r,n),yield*$e(e,r,n,o=>Pt(o,t))},Ft=/^\d+$/,Mt=/[+-]\d{2}$/,P=e=>{if(e==null)return;if(typeof e=="number")return e;const t=Number(e);if(Number.isFinite(t)&&Ft.test(e))return t;const r=e.includes("T")?e:e.replace(" ","T"),n=Date.parse(Mt.test(r)?`${r}:00`:r);return Number.isNaN(n)?void 0:n},Ae=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},zt=e=>{const t=Ae(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]}},Dt=e=>{const t=Ae(e);if(t!==void 0){for(const r of["avatar_url","picture"])if(typeof t[r]=="string"&&t[r].length>0)return t[r]}},Bt=(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=zt(n),i=Dt(n),a=P(e.created_at),c=P(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}},Jt=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=P(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}},Vt=(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},D=(e,t)=>b(e,p(t)),_e=async e=>{const t=await S(e,"utf8");return st(t,{cast:Ie,columns:!0,skipEmptyLines:!0})},Ht=async(e,t)=>{const r=new Map;if(t===void 0)return r;for(const n of await _e(D(e,t))){const s=n.user_id;typeof s=="string"&&r.set(s,[...r.get(s)??[],n])}return r},Gt=async(e,t)=>{const r=t.auth?.file;if(r===void 0)return[];const n=await _e(D(e,r)),s=await Ht(e,t.auth?.identitiesFile);return n.map(o=>Bt(o,s.get(o.id??"")??[]))},Wt=async(e,t)=>{const r=t.auth?.file;if(r===void 0)return[];const n=D(e,r);let s;try{s=JSON.parse(await S(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=>Jt(i))},Re=async function*(e,t,r,n,s){if(r?.auth?.file===void 0)return;const o=e==="supabase"?await Gt(t,r):await Wt(t,r);n.info(`auth: ${String(o.length)} user(s) — passwords are never migrated; users reset via "forgot password"`);for(const i of Vt(o,s))yield i},ce=/\.(?:nd)?json$/i,Oe=e=>{const t=e.split("/").filter(r=>r.length>0);return t[t.length-1]??e},Yt=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},Kt=(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")},Xt=(e,t)=>{const r=typeof e=="string"?Date.parse(e):Yt(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},qt=(e,t)=>typeof e=="string"?e:Kt(e,t),Le=(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 Xt(e.timestampValue,t);if(e.bytesValue!==void 0)return qt(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 Oe(e.referenceValue);if(e.arrayValue!==void 0)return(e.arrayValue.values??[]).map((r,n)=>Le(r,`${t}[${String(n)}]`));if(e.mapValue!==void 0)return U(e.mapValue.fields??{},t);throw new f("INTERNAL",`${t}: unrecognised Firestore value ${JSON.stringify(e).slice(0,80)}`)},Zt=new Set(["createTime","fields","name","readTime","updateTime"]),Qt=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},er=e=>{if(typeof e.name=="string")return!0;const{fields:t}=e;return typeof t!="object"||t===null?!1:Object.keys(e).every(r=>Zt.has(r))&&!Qt(t)},U=(e,t)=>Object.fromEntries(Object.entries(e).map(([r,n])=>[r,Le(n,`${t}.${r}`)])),tr=(e,t,r,n)=>{const s=er(e),o=U(s?e.fields??{}:e,n),i=e.name??e.__name__,a=typeof i=="string"?Oe(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]=Te(d,l,u===null?null:String(u))}}return{...o,_id:a}},rr=async(e,t)=>we(e,t,{authFiles:new Set(t?.auth?.file===void 0?[]:[p(t.auth.file)]),emptyMessage:"holds no .json/.ndjson collection files",matches:r=>ce.test(r),tableNameOf:r=>r.replace(ce,"")},async r=>O(r,{withFileTypes:!0})),nr=async function*(e){const t=j({crlfDelay:Number.POSITIVE_INFINITY,input:N(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()}},sr=async e=>{const t=await S(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: [...] }\``)},or=async function*(e,t){const r=t?.tables?.[e.table],n=e.file.toLowerCase().endsWith(".ndjson")?nr(e.file):await sr(e.file);let s=0;for await(const o of n)yield tr(o.raw,o.fallbackId,r,`${e.table}[${String(s)}]`),s+=1},ir=async function*(e,t,r,n,s){yield*Re("firebase",s,t,r,n),yield*$e(e,r,n,o=>or(o,t))},I=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),B=(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},ke=(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},ar=(e,t)=>{const r=B(e,t),n=T(r,"file",t),s=T(r,"idColumn",t),o=ke(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(!Nt(c))throw new f("INTERNAL",`${t}.types.${a}: unknown reshape ${JSON.stringify(c)} — expected one of ${Se.join(", ")}`)}return{file:n,idColumn:s,storageColumns:o,types:i}},cr=(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`)}},dr=(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,ar(n,`${t}: tables.${r}`)]))}},lr=(e,t)=>{const r=B(e,t),n=T(r,"keyPrefix",t),{auth:s,tables:o}=r;return cr(s,t),{auth:s,keyPrefix:n,tables:dr(o,t)}},xe=e=>b("lunora",`import-${e}.json`),ur=async(e,t,r)=>{const n=xe(t),s=b(e,n);let o;try{o=await S(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 lr(i,s)},fr=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}},hr=8,gr=24*1048576,mr=1e3,pr=/^[\dA-F]{64}$/i,yr=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},br=/^[\d+/A-Z]{43}=$/i,wr=e=>pr.test(e)?e.toLowerCase():br.test(e)?Buffer.from(e,"base64").toString("hex"):void 0,$r=(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=wr(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"||!yr(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}},J=async(e,t,r)=>{const n=[];try{let s=0;for await(const o of z(e,t)){const i=o.trim();s+=1,i.length>0&&n.push($r(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},Ce=ot,L=e=>e.bucket===void 0?"":`&bucket=${encodeURIComponent(e.bucket)}`,V=async(e,t)=>await fr(async r=>{const n=`${e.baseUrl}${M}?prefix=${encodeURIComponent(t)}&limit=${String(mr)}${r===void 0?"":`&cursor=${encodeURIComponent(r)}`}${L(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}}),Sr=async(e,t,r,n)=>{const s=`${e.baseUrl}${M}?key=${encodeURIComponent(t)}&expectedSha256=${n.sha256}&expectedSize=${String(n.size)}${L(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},vr=async(e,t)=>(await e.fetchImpl(`${e.baseUrl}${M}?key=${encodeURIComponent(t)}${L(e)}`,{headers:{authorization:`Bearer ${e.token}`},method:"DELETE"}).catch(()=>{}))?.ok===!0,Nr=async(e,t,r,n,s)=>{const o=`${e.baseUrl}${it}?key=${encodeURIComponent(t)}&method=PUT&contentType=${encodeURIComponent(n.contentType??"application/octet-stream")}${L(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(Ce)}-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 V(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 vr(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},je=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<=Ce?Sr(e,t,r,n):Nr(e,t,r,n,s)},Er=e=>{const t=[];let r=[],n=0;for(const s of e)r.length>0&&(r.length>=hr||n+s.size>gr)&&(t.push(r),r=[],n=0),r.push(s),n+=s.size;return r.length>0&&t.push(r),t},Tr=async(e,t,r,n,s)=>{const o=await J(t,r,s),i=new Map,a=await V(e,n),c=new Map(a.map(u=>[u.key,u])),d=[];for(const u of o){const h=`${n}${u.sha256}`;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 $t(t,u.id);i.set(u.id,await je(e,`${n}${u.sha256}`,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 Er(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*Ir(e,t,r){let n=0;for await(const s of z(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*Ar(e,t,r,n,s){for(const o of t){if(ye(o.table)){o.table===E&&!n&&r.warn(`skipping "${E}" — 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*Ir(e,o,s)}}const Bn=["firebase","supabase"],_r=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 ur(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 Ct(e.file,n)}:{collections:await rr(e.file,n),kind:"firebase",mapping:n}},Rr=async e=>{if(!await R(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},Or=async(e,t,r)=>{const n=t.find(o=>o.table===E);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 J(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},Lr=async(e,t)=>{if(e.from==="supabase"||e.from==="firebase")return _r(e,e.from,t);const r=await pt(e.file),n=r===void 0?void 0:await wt(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 Rr(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 Or(r,n,e)?{kind:"invalid"}:{kind:"convex",snapshot:r,tables:n}},_=b("lunora","import-convex.json"),kr=(e,t)=>{const r=B(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))ke(s,o,`${t}: storageColumns`)}return{keyPrefix:n,storageColumns:s}},xr=async(e,t)=>{const r=b(e,_);let n;try{n=await S(r,"utf8")}catch(o){if(o.code==="ENOENT"){t.info(`no ${_} 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 kr(s,r)},Cr=(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})}},jr=async(e,t,r)=>{const n=new Map([...r].map(i=>[i,i])),s=[];let o=0;for await(const i of z(e,t)){const a=i.trim();if(o+=1,a.length===0)continue;const{ambiguous:c}=be(Cr(a,t.table,o),n,t.table);for(const{column:d}of c)s.includes(d)||s.push(d)}return s},Pr=async(e,t,r)=>{const n={};for(const s of t){if(ye(s.table))continue;const o=await jr(e,s,r);o.length>0&&(n[s.table]=o)}return n},Ur=async(e,t,r)=>{const n=b(t,_),s=`${JSON.stringify(e,void 0,4)} `;await F(b(t,"lunora"),{recursive:!0});try{await he(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)}},Fr=async(e,t,r,n)=>{const s=t.find(c=>c.table===E);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 J(e,s,n),i=new Set(o.map(c=>c.id));n.info(`found ${String(i.size)} storage ids`);const a={keyPrefix:"",storageColumns:await Pr(e,t,i)};return await Ur(a,r,n),a},$=20,Mr=(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},de=(e,t,r)=>{for(const n of t.slice(0,$))e.warn(r(n));t.length>$&&e.warn(`… and ${String(t.length-$)} more`)},zr=(e,t,r)=>(e.info(`storage refs: ${String(t.rewritten)} rewritten, ${String(t.unmigrated.length)} unmigrated, ${String(t.ambiguous.length)} ambiguous`),de(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\``),de(e,t.ambiguous,n=>`unrewritten storage id in ${n.table}.${n.column}: ${n.storageId} — if that column holds storage references, add it to ${_} 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),Dr=(e,t,r)=>{for(const n of[...t].slice(0,$))e.warn(`storage path never transferred: ${n} — left as-is`);return t.size>$&&e.warn(`… and ${String(t.size-$)} more untransferred storage paths`),r&&t.size>0},le=200,Br=/^\\x[\dA-Fa-f]*$/,Jr=/^[+-]?\d+$/,Vr=/^[+-]?\d+\.\d+$/,Hr=/^\{.*\}$/,Gr=/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}/,Wr=[["bytea-base64",e=>Br.test(e)],["timestamp-ms",e=>Gr.test(e)],[e=>e.some(t=>!Number.isSafeInteger(Number(t)))?"int8-string":void 0,e=>Jr.test(e)],["boolean",e=>e==="f"||e==="t"],["number",e=>Vr.test(e)],[e=>e.every(t=>Hr.test(t))?"text-array":"json",e=>e.startsWith("{")||e.startsWith("[")]],Yr=e=>{if(e.length!==0){for(const[t,r]of Wr)if(e.every(n=>r(n)))return typeof t=="function"?t(e):t}},Kr=(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)}},Xr=async e=>{const t=new Map,r=N(e).pipe(pe({columns:!0,skipEmptyLines:!0,toLine:le+1}));let n=0;for await(const o of r)if(Kr(o,t),n+=1,n>=le)break;r.destroy();const s={};for(const[o,i]of t){const a=Yr(i);a!==void 0&&(s[o]=a)}return s},Pe=async(e,t,r,n)=>{const s=xe(r),o=b(t,s),i=`${JSON.stringify(e,void 0,4)} `;await F(ge(o),{recursive:!0});try{await he(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)}},qr=async(e,t,r)=>{const n={};for(const o of e){const i=await Xr(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 Pe(s,t,"supabase",r),s},Zr=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 Pe(s,t,"firebase",r),s},H=e=>b("lunora",`.import-storage-${e}.ndjson`),Qr=async(e,t,r)=>{const n=new Map;let s;try{s=await S(b(e,H(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},en=async(e,t,r)=>{const n=b(e,H(t));await F(ge(n),{recursive:!0}),await Ye(n,`${JSON.stringify(r)} `,"utf8")},Ue=100,tn=e=>e.split("/").map(t=>encodeURIComponent(t)).join("/"),rn=async(e,t,r,n,s)=>{const o=await r(`${e.url}/storage/v1/object/list/${encodeURIComponent(t)}`,{body:JSON.stringify({limit:Ue,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()},Fe=async(e,t,r,n="")=>{const s=[];let o=0;for(;;){const i=await rn(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 Fe(e,t,r,c)):s.push({contentType:a.metadata?.mimetype,name:c})}if(i.length<Ue)return s;o+=i.length}},nn=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 Fe(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)}/${tn(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},sn=async e=>{const t=C(e),r=[],n=async s=>{const o=await O(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=C(s,i.name);if(!me(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()=>S(a),path:Ze(t,a).split(Qe).join("/")})}};return await n(t),r},on=async(e,t,r,n,s)=>{const o=await t.bytes(),i=ct("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 je(e,a,o,c,s)}return await en(r.cwd,r.source,{key:a,path:t.path,size:o.length}),a},an=(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)}%)`)}}},ue=async(e,t,r,n)=>{const s=new Map,o=await Qr(r.cwd,r.source,n),i=await V(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=an(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 on(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 ${H(r.source)} to start over)`),h}d+=1,c(d)}return n.success(`transferred ${String(t.length)} object(s) to R2`),s},fe=/^\/+/,cn=/\/storage\/v1\/object\/(?:public\/|sign\/|authenticated\/)?/,dn=/[#?]/u,ln=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},un=e=>{const t=cn.exec(e);if(t===null)return;const r=e.slice(t.index+t[0].length).split(dn)[0]??"";if(r.length!==0)try{return decodeURIComponent(r)}catch{return r}},fn=(e,t)=>{const r=t.get(e);if(r!==void 0)return r;const n=e.replace(fe,""),s=t.get(n);if(s!==void 0)return s;const o=un(e);return o===void 0?void 0:t.get(o)??t.get(o.replace(fe,""))},hn=500,gn=9e5,mn=async e=>{if(e.prod&&e.url===void 0){e.logger.error("--prod requires an explicit --url (refusing to import to the implicit localhost worker)");return}const t=qe(e.url,e.logger,e.cwd);if(t===void 0)return;if(Ke({prod:e.prod,url:t})&&e.yes!==!0){e.logger.error(`import bulk-writes ${t}, which is not local. Re-run with --yes to confirm.`);return}const{token:r}=Xe({cwd:e.cwd??process.cwd(),token:e.token,url:t});if(!r){e.logger.error("admin token required — pass --token, set LUNORA_ADMIN_TOKEN, or add it to .dev.vars (local targets only)");return}try{const s=await R(e.file);if(!s.isFile()&&!s.isDirectory()){e.logger.error(`not a file or directory: ${e.file}`);return}}catch(s){const o=s instanceof Error?s.message:String(s);e.logger.error(`failed to stat ${e.file}: ${o}`);return}const n=e.fetchImpl??globalThis.fetch;if(typeof n!="function")throw new TypeError("no fetch implementation available — pass fetchImpl or run on Node >= 18");return{baseUrl:t,fetchImpl:n,requestUrl:`${t}${at}`,token:r}},pn=(e,t,r)=>({conflicts:e.conflicts,errors:e.errors,...e.failed.length>0?{failed:e.failed}:{},inserted:e.inserted,received:e.received,...t===void 0?{}:{storage:{ambiguous:r.ambiguous.slice(0,$),ambiguousTotal:r.ambiguous.length,blobs:t.size,rewritten:r.rewritten,unmigrated:r.unmigrated.slice(0,$),unmigratedTotal:r.unmigrated.length}},...e.warnings.length>0?{warnings:e.warnings}:{}}),yn=(e,t,r,n)=>{switch(e.kind){case"convex":return Ar(e.snapshot,e.tables,t.logger,r,n);case"firebase":return ir(e.collections,e.mapping,t.logger,n,t.file);case"supabase":return Ut(e.tables,e.mapping,t.logger,n,t.file);default:return N(t.file,{encoding:"utf8"})}},bn=async(e,t,r)=>{switch(e.kind){case"convex":return Fr(e.snapshot,e.tables,t,r);case"firebase":return Zr(e.collections,t,r);case"supabase":return qr(e.tables,t,r);default:{r.error("--scan needs a Convex, Supabase, or Firebase source.");return}}},wn=async(e,t,r,n)=>{const s=t.mapping?.keyPrefix??"";if(r.storageDir!==void 0)return ue(e,await sn(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 nn({serviceKey:i,url:c},e.fetchImpl,r.logger);return ue(e,d,{cwd:n,keyPrefix:s,source:t.kind},r.logger)},$n=(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=fn(c,r);d===void 0?s.add(`${t}.${a}: ${c}`):i[a]=d}return i},Sn=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:ln(r);return vt({remapDocument:n===void 0||t===void 0?void 0:(s,o)=>$n(s,o,n,t,e.unresolvedPaths),report:e.report,storageColumns:e.storageColumns,storageIdMap:e.storageIdMap,table:e.table})},vn=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){return n.error(`import failed part-way through: ${a instanceof Error?a.message:String(a)}`),n.error("the rows below had already been written — re-run the same command to resume (existing rows conflict rather than duplicate)"),a}},Nn=async(e,t,r,n)=>{const s=await xr(r,n),o=t.tables.find(a=>a.table===E);if(o===void 0){n.error("--with-storage requires a Convex export with a `_storage` metadata table — re-export with `npx convex export --include-file-storage`.");return}const i=await Tr(e,t.snapshot,o,s?.keyPrefix??"",n);return n.info(`storage map: ${String(i.size)} blobs mapped`),{mapping:s,storageIdMap:i}},En=async(e,t,r,n)=>{if(r.withStorage!==!0)return{};if(t.kind==="convex")return Nn(e,t,n,r.logger);if(t.kind!=="supabase"&&t.kind!=="firebase")return{};try{const s=await wn(e,t,r,n);return s===void 0?void 0:{transferredPaths:s}}catch(s){r.logger.error(`storage transfer failed: ${s instanceof Error?s.message:String(s)}`),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}},Tn=(e,t)=>{for(const s of t.warnings)e.warn(s);const r=t.received-t.insertedTotal-t.conflicts-t.errorCount;r>0&&e.warn(`${String(r)} of ${String(t.received)} rows were neither inserted, conflicted, nor reported as errors`);const n=`imported ${String(t.insertedTotal)} of ${String(t.received)} rows (${String(t.conflicts)} conflicts, ${String(t.errorCount)} errors)`;t.failed?e.error(n):e.success(n)},Jn=async e=>{const t=e.cwd??process.cwd(),r=await Lr(e,t);if(r.kind==="invalid")return{body:void 0,code:1,inserted:0};if(e.scan===!0)return{body:void 0,code:await bn(r,t,e.logger)===void 0?1:0,inserted:0};const n=await mn(e);if(n===void 0)return{body:void 0,code:1,inserted:0};const{baseUrl:s,fetchImpl:o,requestUrl:i,token:a}=n,c=e.batchSize??hn,d=await En({baseUrl:s,fetchImpl:o,token:a},r,e,t);if(d===void 0)return{body:void 0,code:1,inserted:0};const{mapping:l,storageIdMap:u,transferredPaths:h}=d,g=new Set,m=l?.storageColumns,y={ambiguous:[],rewritten:0,unmigrated:[]};e.logger.info(r.kind==="convex"?`POST ${i} -> import Convex export ${e.file} (${String(r.tables.length)} tables)`:`POST ${i} -> import ${e.file}`);const G=new Map,Me=yn(r,e,u!==void 0,G),k=St({batchSize:c,fetchImpl:o,maxBatchBytes:gn,requestUrl:i,token:a}),ze=Sn({report:y,source:r,storageColumns:m,storageIdMap:u,table:e.table,transferredPaths:h,unresolvedPaths:g}),W=await vn(Me,ze,k,e.logger),{conflicts:Y,errors:K,failed:X,inserted:q,received:De,warnings:Be}=k.totals,Je=e.verify===!0&&W===void 0?Mr(e.logger,G,{conflicts:Y,inserted:q}):0,Ve=u!==void 0&&zr(e.logger,y,e.verify===!0),He=Dr(e.logger,g,e.verify===!0),Z=Object.values(q).reduce((v,Ge)=>v+Ge,0),Q=pn(k.totals,u,y);for(const v of X)e.logger.error(`import: shard "${v.shardKey}" was never reached${v.timedOut?" (timed out)":""} — its rows were NOT written: ${v.message}`);const ee=W!==void 0||K.length>0||X.length>0||Je>0||Ve||He;return e.logger.info(JSON.stringify(Q,void 0,2)),Tn(e.logger,{conflicts:Y,errorCount:K.length,failed:ee,insertedTotal:Z,received:De,warnings:Be}),{body:Q,code:ee?1:0,inserted:Z}};export{hn as D,Bn as I,Ce as M,L as b,V as l,Jn as r,je as u};