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