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) 48 kB
import{createReadStream as N}from"node:fs";import{open as De,stat as x,readdir as A,realpath as X,readFile as y,mkdir as C,writeFile as ce,appendFile as We}from"node:fs/promises";import{l as qe}from"./admin-token-BKmc3AUm.mjs";import{u as He}from"./admin-url-Ca-KI3d_.mjs";import{join as m,resolve as L,basename as p,dirname as le,relative as Ye,sep as Ke}from"node:path";import{createInterface as j}from"node:readline";import Ge from"adm-zip";import{e as de}from"./path-containment-CcTCKfzV.mjs";import{Transform as Xe}from"node:stream";import{createInflateRaw as Ze,crc32 as Qe}from"node:zlib";import{LunoraError as f}from"@lunora/errors";import{parse as et}from"csv-parse/sync";import{parse as ue}from"csv-parse";import{STORAGE_UPLOAD_MAX_BODY_BYTES as tt}from"@lunora/runtime";import{n as z,e as rt,t as nt}from"./shared-Ce9bKz5c.mjs";import{createHash as ot}from"node:crypto";const it=67324752,E=30,st=26,at=28,Z=0,ct=8,lt=async(e,t)=>{const r=await De(e,"r");try{const n=Buffer.alloc(E),{bytesRead:o}=await r.read(n,0,E,t);if(o<E||n.readUInt32LE(0)!==it)throw new Error(`${e}: no local file header at offset ${String(t)} — the archive is truncated or corrupt`);return t+E+n.readUInt16LE(st)+n.readUInt16LE(at)}finally{await r.close()}},Q=e=>{let t=0;return new Xe({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,o){t=Qe(r,t),o(void 0,r)}})},dt=async(e,t)=>{const{compressedSize:r,encrypted:n,method:o,offset:i}=t.header;if(n)throw new Error(`${t.entryName} is encrypted — decrypt the archive before importing`);if(o!==Z&&o!==ct)throw new Error(`${t.entryName} uses unsupported compression method ${String(o)} — 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 s=await lt(e,i),a=N(e,{end:s+r-1,start:s});if(o===Z){const d=Q(t);return a.on("error",u=>d.destroy(u)),a.pipe(d)}const c=Ze(),l=Q(t);return a.on("error",d=>c.destroy(d)),c.on("error",d=>l.destroy(d)),a.pipe(c).pipe(l)},S="_storage",fe=e=>e.startsWith("_"),ut=e=>{for(const t of e.getEntries()){const r=t.entryName.replaceAll("\\","/").split("/");if(r.length>=2&&r[r.length-2]==="_storage")return r.slice(0,-1).join("/")}return"_storage"},ft=async e=>{const t=await x(e).catch(()=>{});if(t?.isDirectory())return{kind:"directory",root:e};if(t?.isFile()&&e.toLowerCase().endsWith(".zip")){const r=new Ge(e);return{kind:"zip",storagePrefix:ut(r),zip:r,zipPath:e}}},gt=async e=>{const t=[];for(const r of await A(e,{withFileTypes:!0})){if(!r.isDirectory())continue;const n=m(e,r.name,"documents.jsonl");(await x(n).catch(()=>{}))?.isFile()&&t.push({file:n,table:r.name})}return t},ht=e=>{const t=[];for(const r of e.getEntries()){if(r.isDirectory)continue;const n=r.entryName.replaceAll("\\","/"),o=n.split("/");o.length>=2&&o[o.length-1]==="documents.jsonl"&&t.push({file:n,table:o[o.length-2]})}return t},pt=async e=>{const t=e.kind==="directory"?await gt(e.root):ht(e.zip);return t.length>0?t.toSorted((r,n)=>r.table.localeCompare(n.table)):void 0},P=async function*(e,t){if(e.kind==="directory"){for await(const i of j({crlfDelay:Number.POSITIVE_INFINITY,input:N(t.file,{encoding:"utf8"})}))yield i;return}const r=e.zip.getEntry(t.file);if(r===null)throw new Error(`missing ${t.file} in archive`);const n=await dt(e.zipPath,r);if(n===void 0)return;const o=j({crlfDelay:Number.POSITIVE_INFINITY,input:n});try{for await(const i of o)yield i}finally{o.close(),n.destroy()}},mt=async(e,t)=>{if(e.kind==="directory"){const n=await X(m(e.root,"_storage")),o=await X(L(n,t)).catch(()=>{});if(o===void 0||!de(n,o))throw new Error(`blob ${t} resolves outside the snapshot's _storage directory`);return y(o)}const r=e.zip.readFile(`${e.storagePrefix}/${t}`);if(r===null)throw new Error(`missing blob ${t} in archive`);return Buffer.from(r)},$t=e=>{const t={conflicts:0,errors:[],inserted:{},received:0,warnings:[]};let r=[],n=0;const o=s=>{for(const[a,c]of Object.entries(s.inserted??{}))t.inserted[a]=(t.inserted[a]??0)+c;t.errors.push(...s.errors??[]),t.conflicts+=s.conflicts??0,t.received+=s.received??0;for(const a of s.warnings??[])t.warnings.includes(a)||t.warnings.push(a)},i=async()=>{if(r.length===0)return;const s=r.join(` `);r=[],n=0;const a=await e.fetchImpl(e.requestUrl,{body:s,headers:{authorization:`Bearer ${e.token}`,"content-type":"application/x-ndjson"},method:"POST"});if(!a.ok){const c=await a.text().catch(()=>"<no body>");throw new f("INTERNAL",`import batch failed (HTTP ${String(a.status)}): ${c}`)}o(await a.json())};return{flush:i,push:async s=>{const a=Buffer.byteLength(s)+1;r.length>0&&n+a>e.maxBatchBytes&&await i(),r.push(s),n+=a,r.length>=e.batchSize&&await i()},totals:t}},ge=(e,t,r,n)=>{const o=[],i=[];let s=0;const a=d=>n?.[r]?.includes(d)===!0,c=(d,u,g=!1)=>{if(Array.isArray(d))return d.map(h=>c(h,u,g));if(d!==null&&typeof d=="object"){const h=d;if(typeof h.$storage=="string"){const b=h.$storage,w=t.get(b);return w===void 0?(i.push({column:u,storageId:b,table:r}),d):(s+=1,w)}return Object.fromEntries(Object.entries(h).map(([b,w])=>[b,c(w,u)]))}return typeof d=="string"&&t.has(d)?a(u)?(s+=1,t.get(d)??d):(o.push({column:u,storageId:d,table:r}),d):(g&&typeof d=="string"&&d.length>0&&a(u)&&n!==void 0&&i.push({column:u,storageId:d,table:r}),d)},l=Object.fromEntries(Object.entries(e).map(([d,u])=>[d,c(u,d,!0)]));return{ambiguous:o,document:l,rewritten:s,unmigrated:i}},wt=e=>{const{remapDocument:t,report:r,storageColumns:n,storageIdMap:o,table:i}=e,s=(c,l)=>{let d;try{d=JSON.parse(c)}catch(u){const g=u instanceof Error?u.message:String(u);throw new f("INTERNAL",`invalid JSON on line ${String(l)}: ${g}`,{cause:u})}return JSON.stringify({doc:d,table:i})},a=(c,l)=>{let d;try{d=JSON.parse(c)}catch(u){throw new f("INTERNAL",`line ${String(l)}: import envelope is not valid JSON — ${u instanceof Error?u.message:String(u)}`,{cause:u})}if(typeof d.table!="string")throw new f("INTERNAL",`line ${String(l)}: import envelope is missing a string \`table\``);if(d.doc!==null&&typeof d.doc=="object"&&!Array.isArray(d.doc)){let u=d.doc;if(o!==void 0){const g=ge(u,o,d.table,n);u=g.document,r.rewritten+=g.rewritten,r.ambiguous.push(...g.ambiguous),r.unmigrated.push(...g.unmigrated)}d.doc=t===void 0?u:t(u,d.table)}return JSON.stringify(d)};return(c,l)=>{const d=c.trim();if(d.length!==0)return i!==void 0?s(d,l):o===void 0&&t===void 0?d:a(d,l)}},he=async(e,t,r,n)=>{const o=await n(e).catch(()=>{});if(o===void 0)throw new f("INTERNAL",`${e} is not a readable directory`);const i=new Map;for(const[l,d]of Object.entries(t?.tables??{}))d.file!==void 0&&i.set(p(d.file),l);const s=o.filter(l=>l.isFile()&&r.matches(l.name)&&!r.authFiles.has(l.name)).map(l=>({file:m(e,l.name),table:i.get(l.name)??r.tableNameOf(l.name)}));if(s.length===0)throw new f("INTERNAL",`${e} ${r.emptyMessage}`);const a=new Set(s.map(l=>p(l.file))),c=[...i].filter(([l])=>!a.has(l));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(([l,d])=>`\`${l}\` (table \`${d}\`)`).join(", ")}. Check the name and that the file is one this source reads.`);return s.toSorted((l,d)=>l.table.localeCompare(d.table))},pe=async function*(e,t,r,n){for(const o of e){r.has(o.table)||r.set(o.table,0),t.info(`reading ${p(o.file)}${o.table}`);for await(const i of n(o))r.set(o.table,(r.get(o.table)??0)+1),yield`${JSON.stringify({doc:i,table:o.table})} `}},me=["timestamp-ms","timestamp-iso","json","bytea-base64","int8-string","number","boolean","text-array"],yt=e=>me.includes(e),bt=/^\\x([\dA-Fa-f]*)$/,ee=/^[+-]?\d+$/,$e=/[+-]\d{2}$/,we=/(?:Z|[+-]\d{2}:\d{2})$/i,ye=/^\+/,vt=/e/i,Nt=/e/i,St=e=>{const[t="0",r="0"]=e.split(Nt),n=Number(r),o=t.startsWith("-"),[i="0",s=""]=(o?t.slice(1):t).replace(ye,"").split("."),a=`${i}${s}`,c=i.length+n;let l;return c<=0?l=`0.${"0".repeat(-c)}${a}`:c>=a.length?l=`${a}${"0".repeat(c-a.length)}`:l=`${a.slice(0,c)}.${a.slice(c)}`,o?`-${l}`:l},Tt=/^0+(?=\d)/,te=e=>{const t=(vt.test(e.trim())?St(e.trim()):e.trim()).replace(ye,""),r=t.startsWith("-"),[n="0",o=""]=(r?t.slice(1):t).split("."),i=n.replace(Tt,"");let s=o.length;for(;s>0&&o[s-1]==="0";)s-=1;const a=o.slice(0,s),c=a.length>0?`${i}.${a}`:i;return r&&Number(c)!==0?`-${c}`:c},$=(e,t,r,n)=>{throw new f("INTERNAL",`column \`${e}\`: cannot reshape ${JSON.stringify(r)} as \`${t}\` — ${n}`)},re=(e,t,r)=>{const n=r.includes("T")?r:r.replace(" ","T"),o=$e.test(n)?`${n}:00`:n,i=we.test(o)?o:`${o}Z`,s=Date.parse(i);return Number.isNaN(s)&&$(e,t,r,"not a date Postgres or ISO-8601 syntax can express"),s},It=(e,t)=>{(!t.startsWith("{")||!t.endsWith("}"))&&$(e,"text-array",t,"not a Postgres array literal (expected `{…}`)");const r=t.slice(1,-1);if(r.length===0)return[];r.includes("{")&&$(e,"text-array",t,"nested arrays are not supported — map the column to `json` instead");const n=[];let o="",i=!1,s=!1,a=!1;const c=()=>{n.push(!s&&o==="NULL"?null:o),o="",s=!1};for(const l of r)a?(o+=l,a=!1):l==="\\"?a=!0:l==='"'?(i=!i,s=!0):l===","&&!i?c():o+=l;return c(),n},be=(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:$(e,t,r,"not a Postgres boolean literal");case"bytea-base64":{const n=bt.exec(r);if(n===null)return $(e,t,r,"not `bytea` hex output (expected a leading `\\x`) — set `bytea_output = 'hex'` before dumping");const o=n[1];return o.length%2!==0?$(e,t,r,"has an odd number of hex digits, so the dump is truncated — re-export the column"):Buffer.from(o,"hex").toString("base64")}case"int8-string":return ee.test(r)?r:$(e,t,r,"not an integer");case"json":try{return JSON.parse(r)}catch(n){return $(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 $(e,t,r,"not a finite number");if(ee.test(r)){if(!Number.isSafeInteger(n))return $(e,t,r,"exceeds Number.MAX_SAFE_INTEGER — map this column to `int8-string` to keep it lossless")}else if(te(String(n))!==te(r))return $(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 It(e,r);case"timestamp-iso":{re(e,t,r);const n=r.includes("T")?r:r.replace(" ","T"),o=$e.test(n)?`${n}:00`:n;return we.test(o)?o:`${o}Z`}case"timestamp-ms":return re(e,t,r);default:return r}},Et="auth.",kt=new Set(["confirmation_token","email_change_token_current","email_change_token_new","encrypted_password","password","password_hash","passwordhash","reauthentication_token","recovery_token","salt"]),xt=String.raw`\N`,ve=(e,t)=>t.header?e:!t.quoting&&e.length===0||e===xt?null:e,At=e=>new Set([e?.auth?.file,e?.auth?.identitiesFile].filter(t=>t!==void 0).map(t=>p(t))),Rt=async(e,t)=>he(e,t,{authFiles:At(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(Et),tableNameOf:r=>p(r,".csv")},async r=>A(r,{withFileTypes:!0})),Ot=(e,t,r)=>{const n=t?.idColumn??"id",o=t?.types??{},i={};let s=!1;for(const[a,c]of Object.entries(e)){if(kt.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 l=o[a],d=l===void 0?c:be(a,l,c);if(a===n){if(c===null)throw new f("INTERNAL",`id column \`${n}\` is NULL — every row needs an id to preserve`);i._id=c,s=!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.`);i[a]=d}}if(!s)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 i},Lt=async function*(e,t){const r=t?.tables?.[e.table],n=N(e.file).pipe(ue({cast:ve,columns:!0,relaxColumnCountLess:!1,skipEmptyLines:!0}));let o=0;const i=n[Symbol.asyncIterator]();for(;;){let s,a;try{if(s=await i.next(),s.done===!0)return;o+=1,a=Ot(s.value,r,e.table)}catch(c){throw new f("INTERNAL",`${p(e.file)} row ${String(o+1)}: ${c instanceof Error?c.message:String(c)}`,{cause:c})}yield a}},jt=async function*(e,t,r,n,o){yield*Te("supabase",o,t,r,n),yield*pe(e,r,n,i=>Lt(i,t))},_t=/^\d+$/,Ct=/[+-]\d{2}$/,_=e=>{if(e==null)return;if(typeof e=="number")return e;const t=Number(e);if(Number.isFinite(t)&&_t.test(e))return t;const r=e.includes("T")?e:e.replace(" ","T"),n=Date.parse(Ct.test(r)?`${r}:00`:r);return Number.isNaN(n)?void 0:n},Ne=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=Ne(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]}},Pt=e=>{const t=Ne(e);if(t!==void 0){for(const r of["avatar_url","picture"])if(typeof t[r]=="string"&&t[r].length>0)return t[r]}},Ut=(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,o={_id:r,email:e.email??null,emailVerified:typeof e.email_confirmed_at=="string"&&e.email_confirmed_at.length>0,id:r},i=zt(n),s=Pt(n),a=_(e.created_at),c=_(e.updated_at);return i!==void 0&&(o.name=i),s!==void 0&&(o.image=s),a!==void 0&&(o.createdAt=a),c!==void 0&&(o.updatedAt=c),{accounts:t.map(l=>{const d=typeof l.provider=="string"?l.provider:"unknown",u=typeof l.provider_id=="string"?l.provider_id:r,g=`${r}:${d}:${u}`;return{_id:g,accountId:u,id:g,providerId:d,userId:r}}),user:o}},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=_(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,s=o.rawId??o.federatedId??t,a=`${t}:${i}:${s}`;return{_id:a,accountId:s,id:a,providerId:i,userId:t}}),user:r}},Ft=(e,t)=>{const r=new Map,n=[],o=[];for(const{accounts:i,user:s}of e){const a=typeof s.email=="string"?s.email.toLowerCase():void 0;if(a!==void 0&&a.length>0){const c=r.get(a);c===void 0?r.set(a,String(s._id)):n.push(`${a} (ids ${c} and ${String(s._id)})`)}o.push(`${JSON.stringify({doc:s,table:"user"})} `),t.set("user",(t.get("user")??0)+1);for(const c of i)o.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 o},U=(e,t)=>m(e,p(t)),Se=async e=>{const t=await y(e,"utf8");return et(t,{cast:ve,columns:!0,skipEmptyLines:!0})},Vt=async(e,t)=>{const r=new Map;if(t===void 0)return r;for(const n of await Se(U(e,t))){const o=n.user_id;typeof o=="string"&&r.set(o,[...r.get(o)??[],n])}return r},Mt=async(e,t)=>{const r=t.auth?.file;if(r===void 0)return[];const n=await Se(U(e,r)),o=await Vt(e,t.auth?.identitiesFile);return n.map(i=>Ut(i,o.get(i.id??"")??[]))},Bt=async(e,t)=>{const r=t.auth?.file;if(r===void 0)return[];const n=U(e,r);let o;try{o=JSON.parse(await y(n,"utf8"))}catch(s){throw new f("INTERNAL",`${p(n)}: invalid JSON — ${s instanceof Error?s.message:String(s)}`,{cause:s})}const i=Array.isArray(o)?o:o.users??[];if(!Array.isArray(i))throw new f("INTERNAL",`${p(n)}: expected \`{ users: [...] }\` from \`firebase auth:export\`, or a bare array`);return i.map(s=>Jt(s))},Te=async function*(e,t,r,n,o){if(r?.auth?.file===void 0)return;const i=e==="supabase"?await Mt(t,r):await Bt(t,r);n.info(`auth: ${String(i.length)} user(s) — passwords are never migrated; users reset via "forgot password"`);for(const s of Ft(i,o))yield s},ne=/\.(?:nd)?json$/i,Ie=e=>{const t=e.split("/").filter(r=>r.length>0);return t[t.length-1]??e},Dt=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},Wt=(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")},qt=(e,t)=>{const r=typeof e=="string"?Date.parse(e):Dt(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},Ht=(e,t)=>typeof e=="string"?e:Wt(e,t),Ee=(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 qt(e.timestampValue,t);if(e.bytesValue!==void 0)return Ht(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 Ie(e.referenceValue);if(e.arrayValue!==void 0)return(e.arrayValue.values??[]).map((r,n)=>Ee(r,`${t}[${String(n)}]`));if(e.mapValue!==void 0)return ke(e.mapValue.fields??{},t);throw new f("INTERNAL",`${t}: unrecognised Firestore value ${JSON.stringify(e).slice(0,80)}`)},Yt=new Set(["createTime","fields","name","readTime","updateTime"]),Kt=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},Gt=e=>{if(typeof e.name=="string")return!0;const{fields:t}=e;return typeof t!="object"||t===null?!1:Object.keys(e).every(r=>Yt.has(r))&&!Kt(t)},ke=(e,t)=>Object.fromEntries(Object.entries(e).map(([r,n])=>[r,Ee(n,`${t}.${r}`)])),Xt=(e,t,r,n)=>{const o=Gt(e),i=ke(o?e.fields??{}:e,n),s=e.name??e.__name__,a=typeof s=="string"?Ie(s):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[l,d]of Object.entries(c)){const u=i[l];if(u!==void 0){if(u!==null&&typeof u!="boolean"&&typeof u!="number"&&typeof u!="string")throw new f("INTERNAL",`${n}.${l}: a \`${d}\` reshape needs a scalar, but this field decoded to an object or array`);i[l]=be(l,d,u===null?null:String(u))}}return{...i,_id:a}},Zt=async(e,t)=>he(e,t,{authFiles:new Set(t?.auth?.file===void 0?[]:[p(t.auth.file)]),emptyMessage:"holds no .json/.ndjson collection files",matches:r=>ne.test(r),tableNameOf:r=>r.replace(ne,"")},async r=>A(r,{withFileTypes:!0})),Qt=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 o=n.trim();if(o.length!==0)try{yield{raw:JSON.parse(o)}}catch(i){throw new f("INTERNAL",`${p(e)} line ${String(r)}: invalid JSON — ${i instanceof Error?i.message:String(i)}`,{cause:i})}}}finally{t.close()}},er=async e=>{const t=await y(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,o])=>({fallbackId:n,raw:o}));throw new f("INTERNAL",`${p(e)}: expected an object, an array, or \`{ documents: [...] }\``)},tr=async function*(e,t){const r=t?.tables?.[e.table],n=e.file.toLowerCase().endsWith(".ndjson")?Qt(e.file):await er(e.file);let o=0;for await(const i of n)yield Xt(i.raw,i.fallbackId,r,`${e.table}[${String(o)}]`),o+=1},rr=async function*(e,t,r,n,o){yield*Te("firebase",o,t,r,n),yield*pe(e,r,n,i=>tr(i,t))},I=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),J=(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},xe=(e,t,r)=>{const n=e[t];if(n!==void 0&&(!Array.isArray(n)||n.some(o=>typeof o!="string")))throw new f("INTERNAL",`${r}: \`${t}\` must be an array of column names`);return n},nr=(e,t)=>{const r=J(e,t),n=T(r,"file",t),o=T(r,"idColumn",t),i=xe(r,"storageColumns",t),{types:s}=r;if(s!==void 0){if(!I(s))throw new f("INTERNAL",`${t}.types must be an object of column → reshape`);for(const[a,c]of Object.entries(s))if(!yt(c))throw new f("INTERNAL",`${t}.types.${a}: unknown reshape ${JSON.stringify(c)} — expected one of ${me.join(", ")}`)}return{file:n,idColumn:o,storageColumns:i,types:s}},or=(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`)}},ir=(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,nr(n,`${t}: tables.${r}`)]))}},sr=(e,t)=>{const r=J(e,t),n=T(r,"keyPrefix",t),{auth:o,tables:i}=r;return or(o,t),{auth:o,keyPrefix:n,tables:ir(i,t)}},Ae=e=>m("lunora",`import-${e}.json`),ar=async(e,t,r)=>{const n=Ae(t),o=m(e,n);let i;try{i=await y(o,"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 s;try{s=JSON.parse(i)}catch(a){throw new f("INTERNAL",`${o}: invalid JSON — ${a instanceof Error?a.message:String(a)}`,{cause:a})}return sr(s,o)},cr=8,lr=24*1048576,dr=1e3,ur=/^[\dA-F]{64}$/i,fr=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},gr=/^[\d+/A-Z]{43}=$/i,hr=e=>ur.test(e)?e.toLowerCase():gr.test(e)?Buffer.from(e,"base64").toString("hex"):void 0,pr=(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 o=hr(r.sha256);if(o===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"||!fr(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:o,size:r.size}},F=async(e,t,r)=>{const n=[];try{let o=0;for await(const i of P(e,t)){const s=i.trim();o+=1,s.length>0&&n.push(pr(s,`_storage/documents.jsonl line ${String(o)}`))}}catch(o){const i=o instanceof Error?o.message:String(o);throw r.error(`failed to read _storage metadata: ${i}`),o}return n},Re=tt,R=e=>e.bucket===void 0?"":`&bucket=${encodeURIComponent(e.bucket)}`,V=async(e,t)=>{const r=[];let n;do{const o=`${e.baseUrl}${z}?prefix=${encodeURIComponent(t)}&limit=${String(dr)}${n===void 0?"":`&cursor=${encodeURIComponent(n)}`}${R(e)}`,i=await e.fetchImpl(o,{headers:{authorization:`Bearer ${e.token}`},method:"GET"});if(!i.ok){const c=await i.text().catch(()=>"<no body>");throw new f("INTERNAL",`storage list failed (HTTP ${String(i.status)}): ${c}`)}const s=await i.json();r.push(...s.objects??[]);const a=s.truncated===!0?s.cursor:void 0;if(a!==void 0&&a===n)throw new f("INTERNAL","storage list did not advance its cursor — refusing to page forever");n=a}while(n!==void 0);return r},mr=async(e,t,r,n)=>{const o=`${e.baseUrl}${z}?key=${encodeURIComponent(t)}&expectedSha256=${n.sha256}&expectedSize=${String(n.size)}${R(e)}`,i=await e.fetchImpl(o,{body:new Uint8Array(r),headers:{authorization:`Bearer ${e.token}`,"content-type":n.contentType??"application/octet-stream"},method:"PUT"});if(!i.ok){const a=await i.text().catch(()=>"<no body>");throw new f("INTERNAL",`blob upload failed (HTTP ${String(i.status)}): ${a}`)}const s=await i.json();if(s.sha256!==n.sha256)throw new f("INTERNAL",`blob upload verification failed: expected ${n.sha256}, got ${s.sha256??"none"}`);return t},$r=async(e,t)=>(await e.fetchImpl(`${e.baseUrl}${z}?key=${encodeURIComponent(t)}${R(e)}`,{headers:{authorization:`Bearer ${e.token}`},method:"DELETE"}).catch(()=>{}))?.ok===!0,wr=async(e,t,r,n,o)=>{const i=`${e.baseUrl}${rt}?key=${encodeURIComponent(t)}&method=PUT&contentType=${encodeURIComponent(n.contentType??"application/octet-stream")}${R(e)}`,s=await e.fetchImpl(i,{headers:{authorization:`Bearer ${e.token}`},method:"GET"});if(!s.ok){const h=await s.text().catch(()=>"<no body>");throw new f("INTERNAL",`blob ${t} is ${String(n.size)} bytes, above the ${String(Re)}-byte verified-upload cap, and no signed PUT URL could be minted (HTTP ${String(s.status)}): ${h}`)}const{url:a}=await s.json(),c=await e.fetchImpl(a,{body:new Uint8Array(r),headers:{"content-type":n.contentType??"application/octet-stream"},method:"PUT"});if(!c.ok){const h=await c.text().catch(()=>"<no body>");throw new f("INTERNAL",`signed PUT failed (HTTP ${String(c.status)}): ${h}`)}const l=(await V(e,t)).find(h=>h.key===t);if(l===void 0)throw new f("INTERNAL",`post-upload verification failed: blob not found at key ${t}`);const d=l.size!==void 0&&l.size!==n.size,u=l.sha256!==void 0&&l.sha256.toLowerCase()!==n.sha256;if(d||u){const h=await $r(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")}${h?" (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&&o.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},Oe=async(e,t,r,n,o)=>{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<=Re?mr(e,t,r,n):wr(e,t,r,n,o)},yr=e=>{const t=[];let r=[],n=0;for(const o of e)r.length>0&&(r.length>=cr||n+o.size>lr)&&(t.push(r),r=[],n=0),r.push(o),n+=o.size;return r.length>0&&t.push(r),t},br=async(e,t,r,n,o)=>{const i=await F(t,r,o),s=new Map,a=await V(e,n),c=new Map(a.map(u=>[u.key,u])),l=[];for(const u of i){const g=`${n}${u.sha256}`;c.get(g)?.size===u.size?s.set(u.id,g):l.push(u)}o.info(`migrating ${String(l.length)} storage blobs${s.size>0?` (${String(s.size)} already present)`:""}...`);const d=async u=>{try{const g=await mt(t,u.id);s.set(u.id,await Oe(e,`${n}${u.sha256}`,g,u,o))}catch(g){const h=g instanceof Error?g.message:String(g);throw o.error(`failed to upload blob ${u.id}: ${h}`),g}};for(const u of yr(l)){const g=(await Promise.allSettled(u.map(h=>d(h)))).find(h=>h.status==="rejected");if(g!==void 0)throw g.reason}return o.success(`migrated ${String(l.length)} storage blobs`),s};async function*vr(e,t,r){let n=0;for await(const o of P(e,t)){const i=o.trim();if(n+=1,i.length===0)continue;let s;try{s=JSON.parse(i)}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:s,table:t.table})} `}}async function*Nr(e,t,r,n,o){for(const i of t){if(fe(i.table)){i.table===S&&!n&&r.warn(`skipping "${S}" — those rows describe stored files, and their blobs were not migrated. Re-run with --with-storage to upload them and rewrite the references.`);continue}o.has(i.table)||o.set(i.table,0),yield*vr(e,i,o)}}const Pn=["firebase","supabase"],Sr=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 ar(r,t,e.logger),o=Object.values(n?.tables??{}).some(i=>(i.storageColumns??[]).length>0);return e.verify===!0&&e.withStorage!==!0&&o?(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 Rt(e.file,n)}:{collections:await Zt(e.file,n),kind:"firebase",mapping:n}},Tr=async e=>{if(!await x(e.file).then(()=>!0,()=>!1))return!1;for(const[t,r]of[["--scan",e.scan],["--verify",e.verify],["--with-storage",e.withStorage]])if(r===!0)return e.logger.error(`${t} requires a Convex export directory or .zip snapshot — ${e.file} is not one.`),!0;return!1},Ir=async(e,t,r)=>{const n=t.find(i=>i.table===S);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 o=await F(e,n,r.logger);return o.length>0?(r.logger.error(`--verify on an export carrying ${String(o.length)} stored file(s) requires --with-storage — otherwise every file reference stays unmigrated and only row counts would be checked.`),!0):!1},Er=async(e,t)=>{if(e.from==="supabase"||e.from==="firebase")return Sr(e,e.from,t);const r=await ft(e.file),n=r===void 0?void 0:await pt(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 Tr(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 Ir(r,n,e)?{kind:"invalid"}:{kind:"convex",snapshot:r,tables:n}},k=m("lunora","import-convex.json"),kr=(e,t)=>{const r=J(e,t),n=T(r,"keyPrefix",t),o=r.storageColumns;if(o!==void 0){if(!I(o))throw new f("INTERNAL",`${t}: \`storageColumns\` must be an object of table → column names`);for(const i of Object.keys(o))xe(o,i,`${t}: storageColumns`)}return{keyPrefix:n,storageColumns:o}},xr=async(e,t)=>{const r=m(e,k);let n;try{n=await y(r,"utf8")}catch(i){if(i.code==="ENOENT"){t.info(`no ${k} found — rewriting only self-describing { $storage } refs (run with --scan to generate one)`);return}throw i}let o;try{o=JSON.parse(n)}catch(i){throw new f("INTERNAL",`${r}: invalid JSON — ${i instanceof Error?i.message:String(i)}`,{cause:i})}return kr(o,r)},Ar=(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})}},Rr=async(e,t,r)=>{const n=new Map([...r].map(s=>[s,s])),o=[];let i=0;for await(const s of P(e,t)){const a=s.trim();if(i+=1,a.length===0)continue;const{ambiguous:c}=ge(Ar(a,t.table,i),n,t.table);for(const{column:l}of c)o.includes(l)||o.push(l)}return o},Or=async(e,t,r)=>{const n={};for(const o of t){if(fe(o.table))continue;const i=await Rr(e,o,r);i.length>0&&(n[o.table]=i)}return n},Lr=async(e,t,r)=>{const n=m(t,k),o=`${JSON.stringify(e,void 0,4)} `;await C(m(t,"lunora"),{recursive:!0});try{await ce(n,o,{encoding:"utf8",flag:"wx"}),r.success(`wrote candidate mapping to ${n} — review it, then re-run without --scan`)}catch(i){if(i.code!=="EEXIST")throw i;r.warn(`${n} already exists — leaving it untouched. Candidate mapping:`),r.info(o)}},jr=async(e,t,r,n)=>{const o=t.find(c=>c.table===S);if(o===void 0){n.error("no `_storage` table in this export — re-export with `npx convex export --include-file-storage`");return}const i=await F(e,o,n),s=new Set(i.map(c=>c.id));n.info(`found ${String(s.size)} storage ids`);const a={keyPrefix:"",storageColumns:await Or(e,t,s)};return await Lr(a,r,n),a},v=20,_r=(e,t,r)=>{let n=0;if(r.conflicts===0)for(const[o,i]of t){const s=r.inserted[o]??0;s<i&&(n+=1,e.error(`verify: ${o} inserted ${String(s)} of ${String(i)} source rows (${String(i-s)} missing)`))}else{const o=[...t.values()].reduce((s,a)=>s+a,0),i=Object.values(r.inserted).reduce((s,a)=>s+a,0)+r.conflicts;i<o&&(n+=1,e.error(`verify: ${String(i)} of ${String(o)} source rows accounted for across all tables (${String(o-i)} 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},oe=(e,t,r)=>{const n=new Set;for(const o of t){const i=`${o.table} ${o.column} ${o.storageId}`;n.has(i)||(n.add(i),n.size<=v&&e.warn(r(o)))}n.size>v&&e.warn(`… and ${String(n.size-v)} more`)},Cr=(e,t,r)=>(e.info(`storage refs: ${String(t.rewritten)} rewritten, ${String(t.unmigrated.length)} unmigrated, ${String(t.ambiguous.length)} ambiguous`),oe(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\``),oe(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),zr=(e,t,r)=>{for(const n of[...t].slice(0,v))e.warn(`storage path never transferred: ${n} — left as-is`);return t.size>v&&e.warn(`… and ${String(t.size-v)} more untransferred storage paths`),r&&t.size>0},ie=200,Pr=/^\\x[\dA-Fa-f]*$/,Ur=/^[+-]?\d+$/,Jr=/^[+-]?\d+\.\d+$/,Fr=/^\{.*\}$/,Vr=/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}/,Mr=[["bytea-base64",e=>Pr.test(e)],["timestamp-ms",e=>Vr.test(e)],[e=>e.some(t=>!Number.isSafeInteger(Number(t)))?"int8-string":void 0,e=>Ur.test(e)],["boolean",e=>e==="f"||e==="t"],["number",e=>Jr.test(e)],[e=>e.every(t=>Fr.test(t))?"text-array":"json",e=>e.startsWith("{")||e.startsWith("[")]],Br=e=>{if(e.length!==0){for(const[t,r]of Mr)if(e.every(n=>r(n)))return typeof t=="function"?t(e):t}},Dr=(e,t)=>{for(const[r,n]of Object.entries(e)){if(n.length===0)continue;const o=t.get(r);o===void 0?t.set(r,[n]):o.push(n)}},Wr=async e=>{const t=new Map,r=N(e).pipe(ue({columns:!0,skipEmptyLines:!0,toLine:ie+1}));let n=0;for await(const i of r)if(Dr(i,t),n+=1,n>=ie)break;r.destroy();const o={};for(const[i,s]of t){const a=Br(s);a!==void 0&&(o[i]=a)}return o},Le=async(e,t,r,n)=>{const o=Ae(r),i=m(t,o),s=`${JSON.stringify(e,void 0,4)} `;await C(le(i),{recursive:!0});try{await ce(i,s,{encoding:"utf8",flag:"wx"}),n.success(`wrote candidate mapping to ${i} — review the inferred types, then re-run without --scan`)}catch(a){if(a.code!=="EEXIST")throw a;n.warn(`${i} already exists — leaving it untouched. Candidate mapping:`),n.info(s)}},qr=async(e,t,r)=>{const n={};for(const i of e){const s=await Wr(i.file);r.info(`${p(i.file)}${i.table}: ${String(Object.keys(s).length)} column(s) need a reshape`),n[i.table]={file:p(i.file),idColumn:"id",...Object.keys(s).length>0?{types:s}:{}}}const o={keyPrefix:"",tables:n};return await Le(o,t,"supabase",r),o},Hr=async(e,t,r)=>{const n={};for(const i of e)n[i.table]={file:p(i.file),storageColumns:[]};r.info(`found ${String(e.length)} collection(s) — Firestore values are self-describing, so only storage columns need declaring`);const o={keyPrefix:"",tables:n};return await Le(o,t,"firebase",r),o},M=e=>m("lunora",`.import-storage-${e}.ndjson`),Yr=async(e,t,r)=>{const n=new Map;let o;try{o=await y(m(e,M(t)),"utf8")}catch(s){if(s.code==="ENOENT")return n;throw s}let i=0;for(const s of o.split(` `)){const a=s.trim();if(a.length!==0)try{const c=JSON.parse(a);typeof c.path=="string"&&typeof c.key=="string"?n.set(c.path,c):i+=1}catch{i+=1}}return n.size>0&&r.info(`resuming: ${String(n.size)} object(s) already transferred${i>0?` (${String(i)} unreadable checkpoint line(s) ignored)`:""}`),n},Kr=async(e,t,r)=>{const n=m(e,M(t));await C(le(n),{recursive:!0}),await We(n,`${JSON.stringify(r)} `,"utf8")},je=100,Gr=e=>e.split("/").map(t=>encodeURIComponent(t)).join("/"),Xr=async(e,t,r,n,o)=>{const i=await r(`${e.url}/storage/v1/object/list/${encodeURIComponent(t)}`,{body:JSON.stringify({limit:je,offset:o,prefix:n}),headers:{authorization:`Bearer ${e.serviceKey}`,"content-type":"application/json"},method:"POST"});if(!i.ok){const s=await i.text().catch(()=>"<no body>");throw new f("INTERNAL",`Supabase storage list failed for bucket ${t} (HTTP ${String(i.status)}): ${s}`)}return await i.json()},_e=async(e,t,r,n="")=>{const o=[];let i=0;for(;;){const s=await Xr(e,t,r,n,i);for(const a of s){const c=n===""?a.name:`${n}/${a.name}`;a.id===null||a.id===void 0?o.push(...await _e(e,t,r,c)):o.push({contentType:a.metadata?.mimetype,name:c})}if(s.length<je)return o;i+=s.length}},Zr=async(e,t,r)=>{const n=await t(`${e.url}/storage/v1/bucket`,{headers:{authorization:`Bearer ${e.serviceKey}`},method:"GET"});if(!n.ok){const s=await n.text().catch(()=>"<no body>");throw new f("INTERNAL",`Supabase bucket list failed (HTTP ${String(n.status)}): ${s} — check the project URL and that the key is the service-role key, not the anon key`)}const o=await n.json(),i=[];for(const s of o){const a=await _e(e,s.name,t);r.info(`supabase bucket ${s.name}: ${String(a.length)} object(s)`);for(const c of a){const l=`${s.name}/${c.name}`;i.push({contentType:c.contentType,bytes:async()=>{const d=await t(`${e.url}/storage/v1/object/${encodeURIComponent(s.name)}/${Gr(c.name)}`,{headers:{authorization:`Bearer ${e.serviceKey}`},method:"GET"});if(!d.ok){const u=await d.text().catch(()=>"<no body>");throw new f("INTERNAL",`Supabase download failed for ${l} (HTTP ${String(d.status)}): ${u}`)}if(d.arrayBuffer===void 0)throw new f("INTERNAL","the fetch implementation cannot read response bytes, which the storage transfer requires");return Buffer.from(await d.arrayBuffer())},path:l})}}return i},Qr=async e=>{const t=L(e),r=[],n=async o=>{const i=await A(o,{withFileTypes:!0}).catch(()=>{});if(i===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 s of i){const a=L(o,s.name);if(!de(t,a))throw new f("INTERNAL",`${s.name} resolves outside ${e} — refusing to upload it`);s.isDirectory()?await n(a):s.isFile()&&r.push({bytes:async()=>y(a),path:Ye(t,a).split(Ke).join("/")})}};return await n(t),r},en=async(e,t,r,n,o)=>{const i=await t.bytes(),s=ot("sha256").update(i).digest("hex"),a=`${r.keyPrefix}${s}`;if(n.get(a)?.size!==i.length){const c={contentType:t.contentType,id:t.path,sha256:s,size:i.length};await Oe(e,a,i,c,o)}return await Kr(r.cwd,r.source,{key:a,path:t.path,size:i.length}),a},tn=(e,t)=>{const r=e>500?100:25;return n=>{if(n===e||n%r===0){const o=e===0?100:Math.round(n/e*100);t.info(`transferred ${String(n)}/${String(e)} object(s) (${String(o)}%)`)}}},se=async(e,t,r,n)=>{const o=new Map,i=await Yr(r.cwd,r.source,n),s=await V(e,r.keyPrefix),a=new Map(s.map(d=>[d.key,d]));i.size>0&&s.length===0&&n.warn(`the checkpoint records ${String(i.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=tn(t.length,n);let l=0;n.info(`transferring ${String(t.length)} object(s) to R2...`);for(const d of t){const u=i.get(d.path);if(u!==void 0&&a.has(u.key)){o.set(d.path,u.key),l+=1,c(l);continue}try{const g=await en(e,d,r,a,n);o.set(d.path,g)}catch(g){const h=g instanceof Error?g.message:String(g);throw n.error(`failed transferring ${d.path} after ${String(l)} object(s): ${h}`),n.error(`progress is saved — re-run the same command to continue from here (delete ${M(r.source)} to start over)`),g}l+=1,c(l)}return n.success(`transferred ${String(t.length)} object(s) to R2`),o},ae=/^\/+/,rn=/\/storage\/v1\/object\/(?:public\/|sign\/|authenticated\/)?/,nn=/[#?]/u,on=e=>{const t=new Map(e),r=new Map;for(const[n,o]of e){const i=n.indexOf("/");if(i===-1)continue;const s=n.slice(i+1);s.length===0||e.has(s)||r.set(s,r.has(s)?void 0:o)}for(const[n,o]of r)o!==void 0&&t.set(n,o);return t},sn=e=>{const t=rn.exec(e);if(t===null)return;const r=e.slice(t.index+t[0].length).split(nn)[0]??"";if(r.length!==0)try{return decodeURIComponent(r)}catch{return r}},an=(e,t)=>{const r=t.get(e);if(r!==void 0)return r;const n=e.replace(ae,""),o=t.get(n);if(o!==void 0)return o;const i=sn(e);return i===void 0?void 0:t.get(i)??t.get(i.replace(ae,""))},cn=500,ln=9e5,dn=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}if(e.prod&&e.yes!==!0){e.logger.error("import --prod bulk-writes production. Re-run with --yes to confirm.");return}const t=He(e.url,e.logger,e.cwd);if(t===void 0)return;const{token:r}=qe({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 o=await x(e.file);if(!o.isFile()&&!o.isDirectory()){e.logger.error(`not a file or directory: ${e.file}`);return}}catch(o){const i=o instanceof Error?o.message:String(o);e.logger.error(`failed to stat ${e.file}: ${i}`);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}${nt}`,token:r}},un=(e,t,r)=>({conflicts:e.conflicts,errors:e.errors,inserted:e.inserted,received:e.received,...t===void 0?{}:{storage:{ambiguous:r.ambiguous,blobs:t.size,rewritten:r.rewritten,unmigrated:r.unmigrated}},...e.warnings.length>0?{warnings:e.warnings}:{}}),fn=(e,t,r,n)=>{switch(e.kind){case"convex":return Nr(e.snapshot,e.tables,t.logger,r,n);case"firebase":return rr(e.collections,e.mapping,t.logger,n,t.file);case"supabase":return jt(e.tables,e.mapping,t.logger,n,t.file);default:return N(t.file,{encoding:"utf8"})}},gn=async(e,t,r)=>{switch(e.kind){case"convex":return jr(e.snapshot,e.tables,t,r);case"firebase":return Hr(e.collections,t,r);case"supabase":return qr(e.tables,t,r);default:{r.error("--scan needs a Convex, Supabase, or Firebase source.");return}}},hn=async(e,t,r,n)=>{const o=t.mapping?.keyPrefix??"";if(r.storageDir!==void 0)return se(e,await Qr(r.storageDir),{cwd:n,keyPrefix:o,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 i=process.env.SUPABASE_URL,s=process.env.SUPABASE_SERVICE_ROLE_KEY;if(i===void 0||s===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=i.length;for(;a>0&&i[a-1]==="/";)a-=1;const c=i.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 l=await Zr({serviceKey:s,url:c},e.fetchImpl,r.logger);return se(e,l,{cwd:n,keyPrefix:o,source:t.kind},r.logger)},pn=(e,t,r,n,o)=>{const i=n.mapping?.tables?.[t]?.storageColumns??[];if(i.length===0)return e;const s={...e};for(const a of i){const c=s[a];if(typeof c!="string"||c.length===0)continue;const l=an(c,r);l===void 0?o.add(`${t}.${a}: ${c}`):s[a]=l}return s},mn=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:on(r);return wt({remapDocument:n===void 0||t===void 0?void 0:(o,i)=>pn(o,i,n,t,e.unresolvedPaths),report:e.report,storageColumns:e.storageColumns,storageIdMap:e.storageIdMap,table:e.table})},$n=async(e,t,r,n)=>{let o="",i=0;const s=async a=>{i+=1;const c=t(a,i);c!==void 0&&await r.push(c)};try{for await(const a of e){o+=typeof a=="string"?a:a.toString("utf8");let c=o.indexOf(` `);for(;c!==-1;)await s(o.slice(0,c)),o=o.slice(c+1),c=o.indexOf(` `)}o.length>0&&await s(o),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}},wn=async(e,t,r,n)=>{const o=await xr(r,n),i=t.tables.find(a=>a.table===S);if(i===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 s=await br(e,t.snapshot,i,o?.keyPrefix??"",n);return n.info(`storage map: ${String(s.size)} blobs mapped`),{mapping:o,storageIdMap:s}},yn=async(e,t,r,n)=>{if(r.withStorage!==!0)return{};if(t.kind==="convex")return wn(e,t,n,r.logger);if(t.kind!=="supabase"&&t.kind!=="firebase")return{};try{const o=await hn(e,t,r,n);return o===void 0?void 0:{transferredPaths:o}}catch{r.logger.error("no rows were imported — fix the transfer and re-run; it will resume where it stopped");return}},bn=(e,t)=>{for(const o of t.warnings)e.warn(o);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)},Un=async e=>{const t=e.cwd??process.cwd(),r=await Er(e,t);if(r.kind==="invalid")return{body:void 0,code:1,inserted:0};if(e.scan===!0)return{body:void 0,code:await gn(r,t,e.logger)===void 0?1:0,inserted:0};const n=await dn(e);if(n===void 0)return{body:void 0,code:1,inserted:0};const{baseUrl:o,fetchImpl:i,requestUrl:s,token:a}=n,c=e.batchSize??cn,l=await yn({baseUrl:o,fetchImpl:i,token:a},r,e,t);if(l===void 0)return{body:void 0,code:1,inserted:0};const{mapping:d,storageIdMap:u,transferredPaths:g}=l,h=new Set,b=d?.storageColumns,w={ambiguous:[],rewritten:0,unmigrated:[]};e.logger.info(r.kind==="convex"?`POST ${s} -> import Convex export ${e.file} (${String(r.tables.length)} tables)`:`POST ${s} -> import ${e.file}`);const B=new Map,Ce=fn(r,e,u!==void 0,B),O=$t({batchSize:c,fetchImpl:i,maxBatchBytes:ln,requestUrl:s,token:a}),ze=mn({report:w,source:r,storageColumns:b,storageIdMap:u,table:e.table,transferredPaths:g,unresolvedPaths:h}),D=await $n(Ce,ze,O,e.logger),{conflicts:W,errors:q,inserted:H,received:Pe,warnings:Ue}=O.totals,Je=e.verify===!0&&D===void 0?_r(e.logger,B,{conflicts:W,inserted:H}):0,Fe=u!==void 0&&Cr(e.logger,w,e.verify===!0),Ve=zr(e.logger,h,e.verify===!0),Y=Object.values(H).reduce((Me,Be)=>Me+Be,0),K=un(O.totals,u,w),G=D!==void 0||q.length>0||Je>0||Fe||Ve;return e.logger.info(JSON.stringify(K,void 0,2)),bn(e.logger,{conflicts:W,errorCount:q.length,failed:G,insertedTotal:Y,received:Pe,warnings:Ue}),{body:K,code:G?1:0,inserted:Y}};export{cn as R,Un as S,Re as a,Oe as b,R as c,V as m,Pn as w};