@lunora/cli 1.0.0-alpha.489 → 1.0.0-alpha.490
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +201 -104
- package/README.md +3 -3
- package/dist/index.d.mts +8 -3
- package/dist/index.d.ts +8 -3
- package/dist/packem_chunks/cli.mjs +2 -2
- package/dist/packem_chunks/dispatch.mjs +1 -1
- package/dist/packem_chunks/handler.mjs +15 -15
- package/dist/packem_chunks/handler10.mjs +1 -2
- package/dist/packem_chunks/handler11.mjs +1 -1
- package/dist/packem_chunks/handler12.mjs +1 -1
- package/dist/packem_chunks/handler13.mjs +3 -1
- package/dist/packem_chunks/handler14.mjs +1 -3
- package/dist/packem_chunks/handler15.mjs +1 -1
- package/dist/packem_chunks/handler16.mjs +1 -1
- package/dist/packem_chunks/handler17.mjs +1 -1
- package/dist/packem_chunks/handler18.mjs +1 -1
- package/dist/packem_chunks/handler19.mjs +1 -1
- package/dist/packem_chunks/handler20.mjs +1 -1
- package/dist/packem_chunks/handler24.mjs +1 -1
- package/dist/packem_chunks/handler29.mjs +1 -1
- package/dist/packem_chunks/handler3.mjs +1 -1
- package/dist/packem_chunks/handler4.mjs +1 -1
- package/dist/packem_chunks/handler5.mjs +3 -1
- package/dist/packem_chunks/handler6.mjs +1 -3
- package/dist/packem_chunks/handler7.mjs +1 -1
- package/dist/packem_chunks/handler8.mjs +1 -1
- package/dist/packem_chunks/handler9.mjs +2 -1
- package/dist/packem_chunks/runRpcCommand.mjs +1 -1
- package/dist/packem_shared/{resolve-target-BRvw8Pf7.mjs → resolve-target-C_ZloTvd.mjs} +1 -1
- package/dist/packem_shared/{runDeployCommand-D4hsYdg0.mjs → runDeployCommand-DhgZz8Sj.mjs} +1 -1
- package/dist/packem_shared/sql-u1MSmopg.mjs +3 -0
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{r as $}from"../packem_shared/admin-token-VdUnvnKW.mjs";import{r as S,a as p}from"../packem_shared/admin-url-DJB8P6Jh.mjs";import{d as k}from"../packem_shared/command-BpMv2n0o.mjs";import{EXIT_CODE as u,exitCodeForStatus as b}from"../packem_shared/EXIT_CODE-DQVhSE-1.mjs";import{a as y}from"../packem_shared/resolve-target-C_ZloTvd.mjs";const w="/_lunora/admin/shard-registry/prune",f=["prune"],d=(e,r,a)=>(e.error(r),{code:a,error:r}),g=(e,r,a)=>{for(const{shardKey:t,table:n}of r.released)e.info(`${a?"would release":"released"} ${t} (${n})`);for(const{message:t,shardKey:n,tables:l}of r.failed)e.error(`could not check ${n} (${l.join(", ")}) — kept: ${t}`);const s=`${String(r.released.length)} ${a?"would be released":"released"}, ${String(r.kept.length)} kept (still hold rows), ${String(r.failed.length)} unreachable`;r.failed.length>0?e.warn(`${s} — re-run to retry the unreachable shards`):e.success(s)},A=async e=>{const{logger:r}=e;if(!f.includes(e.subcommand))return d(r,`shards: unknown subcommand "${e.subcommand??""}" — expected ${f.join(" | ")}`,u.USAGE);if(e.prod&&e.url===void 0)return d(r,"--prod requires an explicit --url (refusing to prune the implicit localhost worker)",u.USAGE);const a=S(e.url,r,e.cwd);if(a===void 0)return{code:u.USAGE,error:"could not resolve a usable worker URL"};const{token:s}=$({cwd:e.cwd??process.cwd(),token:e.token,url:a});if(!s)return d(r,"admin token required — pass --token, set LUNORA_ADMIN_TOKEN, or add it to .dev.vars (local targets only)",u.AUTH);const t=`${a}${w}`,n=e.fetchImpl??p,l=e.tables?.split(",").map(h=>h.trim()).filter(h=>h!==""),i=e.dryRun===!0;r.info(`POST ${t} -> shards prune${i?" (dry run)":""}`);const c=await n(t,{body:JSON.stringify({dryRun:i,...l===void 0?{}:{tables:l}}),headers:{authorization:`Bearer ${s}`,"content-type":"application/json"},method:"POST"}),m=await c.text();if(!c.ok)return d(r,`shards prune failed: HTTP ${String(c.status)}: ${m}`,b(c.status));let o;try{o=JSON.parse(m)}catch{return d(r,`shards prune failed: worker returned non-JSON: ${m}`,1)}return e.format!=="json"&&g(r,o,i),o.failed.length>0?{code:1,error:`shards prune: ${String(o.failed.length)} shard(s) could not be checked`,result:o}:{code:0,result:o}},E=k(async({argument:e,cwd:r,format:a,logger:s,options:t})=>{const n=await A({cwd:r,dryRun:t.dryRun===!0,format:a,logger:s,prod:t.prod,subcommand:e[0],tables:t.tables,token:t.token,url:y({cwd:r,prod:t.prod===!0,url:t.url})});return{code:n.code,data:n.result,error:n.error}});export{E as execute,A as runShardsCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as
|
|
1
|
+
import{existsSync as h}from"node:fs";import{join as p}from"node:path";import{runCodegen as g}from"@lunora/codegen";import{resolveLazyFunctions as u}from"@lunora/config";import{validateWranglerProject as v,wranglerCodegenInputs as y}from"@lunora/config/cloudflare";import{a as w,e as S,r as k}from"../packem_shared/platform-diagnostics-Cn2g6Jh-.mjs";import{p as j}from"../packem_shared/api-spec-lYyq4vL6.mjs";import{r as E}from"../packem_shared/codegen-error-AmH54ofi.mjs";import{d as A}from"../packem_shared/command-BpMv2n0o.mjs";import{r as D}from"../packem_shared/deploy-target-resolve-BTCW1Iv4.mjs";import{EXIT_CODE as R}from"../packem_shared/EXIT_CODE-DQVhSE-1.mjs";import{r as $,p as b}from"../packem_shared/health-probe-By9T5x2y.mjs";import{defaultSpawner as F}from"../packem_shared/createRecordingSpawner-DchLi0gR.mjs";import{execArgsFor as U,detectPackageManager as x}from"@lunora/config/cli";const P=async(r,a,t)=>{if(!h(p(r,"tsconfig.json")))return{warning:"no tsconfig.json found — skipping TypeScript type-check"};const o=U(x(r),"tsc",["--noEmit","-p","tsconfig.json"]),e=await a({args:o.args,command:o.command,cwd:r,stdoutToStderr:t==="json"});return e.code===0?{}:{error:`type errors: tsc --noEmit exited ${String(e.code)}`}},T=async(r,a)=>{if(r.healthUrl===void 0||r.healthUrl==="")return;const t=await b({baseUrl:r.healthUrl,fetchImpl:r.healthFetch});if(t.error===void 0){a.success(`verify: health probe ok (${t.url})`);return}return t.error},z=(r,a)=>{const{errorAdvisories:t,names:o,shouldBlock:e}=S(r,k(a));if(e)return`${t.length.toString()} ERROR-level ${t.length===1?"advisory":"advisories"} (${o.join(", ")}). Pass --no-strict-advisories to downgrade this to a warning and continue.`},C=(r,a,t,o)=>{if(a.length===0&&t.length===0)return r.success("verify: project is valid"),{code:0,errors:[],warnings:[],wranglerPath:o};if(t.length>0){r.warn("verify: warnings:");for(const e of t)r.warn(` - ${e}`)}if(a.length>0){r.error("verify: errors:");for(const e of a){r.error(` - ${e}`);const s=E(e);s!==void 0&&r.error(s)}return{code:1,error:a[0],errors:a,warnings:t,wranglerPath:o}}return r.success("verify: project is valid (with warnings)"),{code:0,errors:[],warnings:t,wranglerPath:o}},H=async r=>{const a=r.cwd??process.cwd(),{logger:t}=r,o=v({environment:r.env,projectRoot:a}),e=[...o.report.errors],s=[...o.report.warnings];try{const n=D(a,r.target);if(n.target===void 0){const c=n.error??"unknown deploy target";return t.error(c),{code:R.USAGE,error:c,errors:[c],warnings:[],wranglerPath:void 0}}const i=g({apiSpec:r.apiSpec,lazyFunctions:u(r.lazyFunctions),dryRun:!0,projectRoot:a,target:n.target,...y(a)}),l=w(i.platformDiagnostics,t);e.push(...l.errors),s.push(...l.warnings);const f=z(i.advisories,r);f!==void 0&&e.push(f);const m=await $({allowDrift:r.allowSchemaDrift===!0,codegen:i,command:"verify",logger:t,readOnly:!0});m.blocked&&e.push(m.reason)}catch(n){const i=n instanceof Error?n.message:String(n);e.push(`codegen failed: ${i}`)}if(r.typecheck!==!1){const n=await P(a,r.spawner??F,r.format??"pretty");n.error!==void 0&&e.push(n.error),n.warning!==void 0&&s.push(n.warning)}const d=await T(r,t);return d!==void 0&&e.push(d),C(t,e,s,o.wranglerPath)},Q=A(async({cwd:r,format:a,logger:t,options:o})=>{const e=await H({allowSchemaDrift:o.allowSchemaDrift===!0,apiSpec:j(o.apiSpec),lazyFunctions:u(o.lazyFunctions),cwd:r,env:o.env,format:a,healthUrl:o.healthUrl,logger:t,strictAdvisories:o.strictAdvisories,target:o.target,typecheck:o.typecheck===!1?!1:void 0});return{code:e.code,data:{errors:e.errors,warnings:e.warnings,wranglerPath:e.wranglerPath},error:e.error}});export{Q as execute,H as runVerifyCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{readLiveDevServerState as c}from"@lunora/config";import{d as i}from"../packem_shared/command-BpMv2n0o.mjs";import{o as a}from"../packem_shared/open-url-EnKy--w-.mjs";const n="http://127.0.0.1:6173",d="/__lunora",u=/\/$/u,l=r=>{const e=c(r);return e===void 0?n:e.studioUrl!==void 0?e.studioUrl:e.mode==="vite"?`${e.url.replace(u,"")}${d}`:n},m=async r=>{const e=l(r.cwd??process.cwd()),{logger:t}=r;t.info(`opening ${e}`);try{await a(e,{opener:r.opener})}catch(o){const s=o instanceof Error?o.message:String(o);return t.error(`view: failed to open URL: ${s}`),{code:1,url:e}}return{code:0,url:e}},v=i(({cwd:r,logger:e})=>m({cwd:r,logger:e}));export{v as execute,m as runViewCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{d as f}from"../packem_shared/command-BpMv2n0o.mjs";import{EXIT_CODE as y}from"../packem_shared/EXIT_CODE-DQVhSE-1.mjs";import{runRpcCommand as l}from"./runRpcCommand.mjs";const i="__lunora_admin__:createWorkflowInstance",N="__lunora_admin__:sendWorkflowEvent",u="Usage: lunora workflow create <exportName> [--id <id>] [--params <json>] | lunora workflow send-event <exportName> <instanceId> <eventType> [--payload <json>]",m=r=>{if(r===void 0)return{value:void 0};try{return{value:JSON.parse(r)}}catch{return}},n=(r,e)=>(r.error(e),{body:void 0,code:y.USAGE,error:e,requestUrl:""}),s=r=>{const{body:e}=r;return typeof e=="object"&&e!==null&&"result"in e?{...r,body:e.result}:r},p=async r=>{const{logger:e}=r,d={cwd:r.cwd,fetchImpl:r.fetchImpl,format:r.format,logger:e,token:r.token,url:r.url};if(r.subcommand==="create"){const a=m(r.params);return r.exportName?a===void 0?n(e,"could not parse --params as JSON"):s(await l({...d,args:JSON.stringify({exportName:r.exportName,...r.id===void 0?{}:{id:r.id},params:a.value}),functionPath:i})):n(e,u)}if(r.subcommand==="send-event"){const a=m(r.payload);return!r.exportName||!r.id||!r.type?n(e,u):a===void 0?n(e,"could not parse --payload as JSON"):s(await l({...d,args:JSON.stringify({exportName:r.exportName,id:r.id,payload:a.value,type:r.type}),functionPath:N}))}return n(e,`workflow: unknown subcommand "${r.subcommand??""}". ${u}`)},k=f(async({argument:r,cwd:e,format:d,logger:a,options:t})=>{const c=r[0],o=await p({cwd:e,exportName:r[1],format:d,id:c==="send-event"?r[2]:t.id,logger:a,params:t.params,payload:t.payload,subcommand:c,token:t.token,type:r[3],url:t.url});return{code:o.code,data:o.body,error:o.error}});export{k as execute,p as runWorkflowCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{d as
|
|
1
|
+
import{r as x}from"../packem_shared/admin-token-VdUnvnKW.mjs";import{r as E,a as D}from"../packem_shared/admin-url-DJB8P6Jh.mjs";import{d as U}from"../packem_shared/command-BpMv2n0o.mjs";import{EXIT_CODE as i,exitCodeForStatus as T}from"../packem_shared/EXIT_CODE-DQVhSE-1.mjs";import{a as N}from"../packem_shared/resolve-target-C_ZloTvd.mjs";const S=["migrations"],p=e=>S.some(r=>r===e),l="/_lunora/admin/tokens",O=864e5,v=["create","list","revoke"],a=(e,r,t)=>(e.error(r),{code:t,error:r}),f=(e,r)=>e===void 0?r:new Date(e).toISOString(),$=e=>{const r=(e??"").split(",").map(n=>n.trim()).filter(n=>n!==""),t=r.filter(n=>!p(n));return t.length>0?{error:`unknown scope ${t.join(", ")} — expected ${S.join(", ")}`}:{scopes:[...new Set(r.filter(p))]}},g=(e,r,t)=>{if(e==="list")return{method:"GET",path:l};if(e==="revoke")return{method:"DELETE",path:`${l}?name=${encodeURIComponent(r.name??"")}`};const n=r.expiresDays===void 0?void 0:(r.now??Date.now)()+r.expiresDays*O;return{body:JSON.stringify({expiresAt:n,name:r.name,...t.length===0?{}:{scopes:t}}),method:"POST",path:l}},_=(e,r,t,n)=>{if(r==="create"){e.success(`created admin token "${String(t.name)}" — copy it now, it is not shown again:`),e.info(String(t.token)),Array.isArray(t.scopes)&&t.scopes.length>0&&e.info(`scopes: ${t.scopes.map(String).join(", ")}`);return}if(r==="revoke"){e.success(`revoked admin token "${String(n)}"`);return}const s=t.tokens??[];s.length===0&&e.info("no named admin tokens");for(const o of s){const c=o.scopes===void 0||o.scopes.length===0?"":` scopes ${o.scopes.join(",")}`;e.info(`${o.name} created ${f(o.createdAt,"?")} last used ${f(o.lastUsedAt,"never")} expires ${f(o.expiresAt,"never")}${c}${o.revoked===!0?" revoked (its row still holds the name: revoke it again to free it)":""}`)}},b=(e,r)=>{const{logger:t}=e;if(r!=="list"&&(e.name===void 0||e.name===""))return a(t,`admin-token ${r}: a token name is required`,i.USAGE);if(e.expiresDays!==void 0&&(!Number.isFinite(e.expiresDays)||e.expiresDays<=0))return a(t,"--expires-days must be a positive number",i.USAGE);if(e.scope!==void 0&&r!=="create")return a(t,"--scope applies to `admin-token create` only",i.USAGE);const n=$(e.scope);if("error"in n)return a(t,`admin-token create: ${n.error}`,i.USAGE);if(e.prod&&e.url===void 0)return a(t,"--prod requires an explicit --url",i.USAGE)},G=async e=>{const{logger:r}=e,t=v.find(y=>y===e.subcommand);if(t===void 0)return a(r,`admin-token: unknown subcommand "${e.subcommand??""}" — expected ${v.join(" | ")}`,i.USAGE);const n=b(e,t);if(n!==void 0)return n;const s=$(e.scope),o="scopes"in s?s.scopes:[],c=E(e.url,r,e.cwd);if(c===void 0)return{code:i.USAGE,error:"could not resolve a usable worker URL"};const{token:k}=x({cwd:e.cwd??process.cwd(),token:e.token,url:c});if(!k)return a(r,"root admin token required — pass --token, set LUNORA_ADMIN_TOKEN, or add it to .dev.vars (local targets only)",i.AUTH);const{body:A,method:h,path:w}=g(t,e,o),u=await(e.fetchImpl??D)(`${c}${w}`,{body:A,headers:{authorization:`Bearer ${k}`,"content-type":"application/json"},method:h}),m=await u.text();if(!u.ok)return a(r,`admin-token ${t} failed: HTTP ${String(u.status)}: ${m}`,T(u.status));let d;try{d=JSON.parse(m)}catch{return a(r,`admin-token ${t} failed: worker returned non-JSON: ${m}`,1)}return t==="create"&&(typeof d.token!="string"||d.token.length===0)?a(r,"admin-token create failed: the worker's answer did not include the created token",1):(e.format!=="json"&&_(r,t,d,e.name),{code:0,data:d})},q=U(async({argument:e,cwd:r,format:t,logger:n,options:s})=>{const o=await G({cwd:r,expiresDays:s.expiresDays,format:t,logger:n,name:e[1],prod:s.prod,scope:s.scope,subcommand:e[0],token:s.token,url:N({cwd:r,prod:s.prod===!0,url:s.url})});return{code:o.code,data:o.data,error:o.error}});export{q as execute,G as runAdminTokenCommand};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import z,{join as m,resolve as U}from"node:path";import{isInteractive as J,promptYesNo as W}from"@lunora/config";import{backupObjectKey as S,isBackupManifestEntry as G,normalizeBackupPrefix as Y,BACKUP_KEY_PREFIX as X,backupManifestKey as Q,isBackupManifestKey as V,backupObjectKeyOfManifest as j}from"@lunora/runtime";import{r as A,t as Z}from"../packem_shared/admin-token-VdUnvnKW.mjs";import{r as T,a as I}from"../packem_shared/admin-url-DJB8P6Jh.mjs";import{d as ee}from"../packem_shared/command-BpMv2n0o.mjs";import{EXIT_CODE as u,isRefusal as $,exitCodeForStatus as y}from"../packem_shared/EXIT_CODE-DQVhSE-1.mjs";import{
|
|
1
|
+
import z,{join as m,resolve as U}from"node:path";import{isInteractive as J,promptYesNo as W}from"@lunora/config";import{backupObjectKey as S,isBackupManifestEntry as G,normalizeBackupPrefix as Y,BACKUP_KEY_PREFIX as X,backupManifestKey as Q,isBackupManifestKey as V,backupObjectKeyOfManifest as j}from"@lunora/runtime";import{r as A,t as Z}from"../packem_shared/admin-token-VdUnvnKW.mjs";import{r as T,a as I}from"../packem_shared/admin-url-DJB8P6Jh.mjs";import{d as ee}from"../packem_shared/command-BpMv2n0o.mjs";import{EXIT_CODE as u,isRefusal as $,exitCodeForStatus as y}from"../packem_shared/EXIT_CODE-DQVhSE-1.mjs";import{a as re}from"../packem_shared/resolve-target-C_ZloTvd.mjs";import{runExportCommand as te}from"../packem_shared/runExportCommand-_PtkJJDT.mjs";import{b as ae,l as oe,M as _,u as ne,p as ie,a as se,r as ce}from"../packem_shared/import-3Y59Ry9F.mjs";import{readAndLogBody as p}from"./runRpcCommand.mjs";import{randomUUID as q,createHash as de}from"node:crypto";import{existsSync as N,createReadStream as ue,createWriteStream as le}from"node:fs";import{mkdir as fe,rm as k,rename as M,readFile as C,writeFile as me,realpath as R,mkdtemp as x}from"node:fs/promises";import{LunoraError as b}from"@lunora/errors";import{i as O}from"../packem_shared/path-containment-CgxYZggb.mjs";import{tmpdir as P}from"node:os";import{Readable as ge}from"node:stream";import{pipeline as he}from"node:stream/promises";const E="manifest.json",L=async e=>{const t=de("sha256");let r=0;for await(const a of ue(e)){const o=a;r+=o.byteLength,t.update(o)}return{bytes:r,sha256:t.digest("hex")}},B=async e=>{const t=m(e,E);if(!N(t))return[];let r;try{r=JSON.parse(await C(t,"utf8"))}catch(a){const o=a instanceof Error?a.message:String(a);throw new b("INTERNAL",`backup: ${t} exists but is not valid JSON (${o}) — refusing to overwrite it; fix or remove it manually`,{cause:a})}if(!Array.isArray(r))throw new TypeError(`backup: ${t} exists but is not a JSON array — refusing to overwrite it; fix or remove it manually`);return r.filter(a=>G(a))},we=async(e,t)=>{const r=m(e,`${E}.${q()}.tmp`);try{await me(r,`${JSON.stringify(t,void 0,2)}
|
|
2
2
|
`,"utf8"),await M(r,m(e,E))}catch(a){throw await k(r,{force:!0}),a}},be=async(e,t)=>{const r=U(e),a=U(r,t.file),o=()=>{throw new b("INTERNAL",`backup: entry ${t.id} points outside ${r} (${t.file}) — refusing to read it`)};if(O(r,a)||o(),!!N(a))return O(await R(r),await R(a))||o(),a},ke=e=>{const t=r=>S("",r);return{commit:async(r,a)=>{await M(a,m(e,r))},label:e,list:async()=>B(e),locate:t,materialize:async(r,a)=>{const o=i=>i===void 0?void 0:{path:i,release:()=>Promise.resolve()};return o(r===void 0?N(a)?a:void 0:await be(e,r))},record:async r=>{const a=await B(e);a.push(r),await we(e,a)},stage:async r=>{await fe(e,{recursive:!0});const a=m(e,`${t(r)}.${q()}.partial`);return{path:a,release:()=>k(a,{force:!0})}}}},ye="/_lunora/admin/storage/object",pe="/_lunora/admin/storage",D=8,$e=(e,t=z)=>{const r=t.basename(e);return r===""||r==="."||r===".."?"snapshot.ndjson":r},ve=e=>{const{context:t,logger:r}=e,a=Y(e.prefix??X),o=`bucket ${t.bucket??"(default)"} under ${a}`,i=ae(t),n=c=>`${t.baseUrl}${ye}?key=${encodeURIComponent(c)}${i}`,f=async c=>{const s=await t.fetchImpl(n(c),{headers:{authorization:`Bearer ${t.token}`},method:"GET"});if(!s.ok){const l=await s.text().catch(()=>"<no body>");throw new b("INTERNAL",`backup: could not read ${c} (HTTP ${String(s.status)}): ${l}`)}return s.text()},g=async c=>{try{const s=JSON.parse(await f(c));if(G(s))return s;r.warn(`backup: ignoring ${c} — not a backup manifest`)}catch(s){r.warn(`backup: ignoring ${c} — ${s instanceof Error?s.message:String(s)}`)}};return{commit:async(c,s,l)=>{if(l.bytes>_)throw new b("INTERNAL",`backup: the snapshot is ${String(l.bytes)} bytes, above the ${String(_)}-byte limit for a checksum-verified upload — nothing was written. Narrow it with \`--tables\`, or write it to a directory with \`--dir\` and move the file yourself (\`wrangler r2 object put\`).`);const d=await C(s);await ne(t,c,d,{contentType:"application/x-ndjson",id:c,sha256:l.sha256,size:l.bytes},r)},label:o,list:async()=>{const s=(await oe(t,a)).map(d=>d.key).filter(d=>V(d)),l=[];for(let d=0;d<s.length;d+=D){const h=await Promise.all(s.slice(d,d+D).map(async w=>g(w)));l.push(...h.filter(w=>w!==void 0))}return l.toSorted((d,h)=>d.id.localeCompare(h.id))},locate:c=>S(a,c),materialize:async(c,s)=>{const l=c?.file??s,d=await t.fetchImpl(n(l),{headers:{authorization:`Bearer ${t.token}`},method:"GET"});if(d.status===404)return;if(!d.ok||d.body===null){const v=await d.text().catch(()=>"<no body>");throw new b("INTERNAL",`backup: could not download ${l} (HTTP ${String(d.status)}): ${v}`)}const h=await x(m(P(),"lunora-backup-")),w=m(h,$e(l));try{await he(ge.from(d.body),le(w))}catch(v){throw await k(h,{force:!0,recursive:!0}),v}return{path:w,release:async()=>k(h,{force:!0,recursive:!0})}},record:async c=>{const s=Q(c.file),l=await t.fetchImpl(`${t.baseUrl}${pe}?key=${encodeURIComponent(s)}${i}`,{body:`${JSON.stringify(c,void 0,2)}
|
|
3
3
|
`,headers:{authorization:`Bearer ${t.token}`,"content-type":"application/json"},method:"PUT"});if(!l.ok){const d=await l.text().catch(()=>"<no body>");throw new b("INTERNAL",`backup: snapshot ${c.file} was written but its manifest was not (HTTP ${String(l.status)}): ${d}`)}},stage:async c=>{const s=await x(m(P(),"lunora-backup-"));return{path:m(s,S("",c)),release:async()=>k(s,{force:!0,recursive:!0})}}}},Se=".lunora-backups",F="/_lunora/admin/backup/retention",Ee="/_lunora/admin/backup/prune",Ae="/_lunora/admin/pitr",Te="__lunora_admin__:getPitrBookmark",Ie="__lunora_admin__:pitrRestore",Ne=async(e,t)=>{const r=(e.now??(()=>new Date))().toISOString(),a=await t.stage(r);try{const o=await te({cwd:e.cwd,fetchImpl:e.fetchImpl,logger:e.logger,out:a.path,prod:e.prod,tables:e.tables,token:e.token,url:e.url});if(o.code!==0)return{code:o.code};const i=t.locate(r),n=await L(a.path);await t.commit(i,a.path,n);const f={bytes:n.bytes,createdAt:r,file:i,id:r,rows:o.rows,sha256:n.sha256,tables:e.tables};return await t.record(f),e.logger.success(`backup created: ${i} (${o.rows.toString()} rows, ${n.bytes.toString()} bytes)`),{code:0,entry:f}}finally{await a.release().catch(o=>{e.logger.warn(`backup: could not clean up staging (${o instanceof Error?o.message:String(o)})`)})}},Ue=async(e,t,r)=>{if(t?.sha256===void 0)return r.error(t===void 0?"--verify needs a recorded checksum, and a snapshot restored by path/key has no manifest entry — restore it by its backup id instead":`--verify: backup ${t.id} carries no recorded checksum — it was taken by a release before checksums existed`),{code:u.USAGE};const a=await L(e);if(a.sha256!==t.sha256)return r.error(`--verify: ${t.file} does not match its recorded checksum (expected ${t.sha256}, got ${a.sha256}) — nothing was imported`),{code:1};r.success(`verified ${t.file} against its recorded sha256`)},_e=async(e,t)=>{const r=await t.list();if(r.length===0)return e.logger.info(`no backups found in ${t.label}`),{code:0,entries:[]};for(const a of r)e.logger.info(`${a.id} ${a.rows.toString()} rows ${a.bytes.toString()} bytes ${a.file}`);return{code:0,entries:r}},Re=async(e,t)=>{const{target:r}=e;if(r===void 0||r.length===0)return e.logger.error("restore requires a backup id or file path. Usage: lunora backup restore <id|file>"),{code:u.USAGE};const o=(await t.list()).find(n=>n.id===r),i=await t.materialize(o,r);if(i===void 0)return e.logger.error(`backup not found: ${r}`),{code:u.NOT_FOUND};try{if(e.verify===!0){const f=await Ue(i.path,o,e.logger);if(f!==void 0)return f}const n=await ce({cwd:e.cwd,fetchImpl:e.fetchImpl,file:i.path,format:e.format,logger:e.logger,dryRun:e.dryRun,mode:e.mode??"require-empty",prod:e.prod,token:e.token,url:e.url,yes:e.yes});return{code:n.code,entry:o,...n.body?.plan===void 0?{}:{plan:n.body.plan},restored:n.inserted+(n.replaced??0)}}finally{await i.release().catch(n=>{e.logger.warn(`backup: could not clean up the downloaded snapshot (${n instanceof Error?n.message:String(n)})`)})}},xe=e=>{if(e.prod&&e.url===void 0)return e.logger.error("--prod requires an explicit --url (refusing to target the implicit localhost worker)"),{refused:u.USAGE};if(e.restore===!0&&e.at===void 0&&e.bookmark===void 0)return e.logger.error("pitr --restore requires --at <time> or --bookmark <bookmark>"),{refused:u.USAGE};const t=T(e.url,e.logger,e.cwd);if(t===void 0)return{refused:u.USAGE};if(e.restore===!0&&Z({prod:e.prod,url:t})&&e.yes!==!0)return e.logger.error(`pitr --restore restores data in place at ${t}, which is not local. Re-run with --yes to confirm.`),{refused:u.USAGE};const{token:r}=A({cwd:e.cwd??process.cwd(),token:e.token,url:t});return r?{fetchImpl:e.adminFetch??I,requestUrl:`${t}${Ae}`,token:r}:(e.logger.error("admin token required — pass --token, set LUNORA_ADMIN_TOKEN, or add it to .dev.vars (local targets only)"),{refused:u.AUTH})},Oe=(e,t)=>{const r={};return e.at!==void 0&&(r.time=e.at),t&&e.bookmark!==void 0&&(r.bookmark=e.bookmark),t&&e.restart===!0&&(r.restart=!0),r},Pe=async e=>{const t=xe(e);if($(t))return{code:t.refused};const r=e.restore===!0,a=r?Ie:Te,o=Oe(e,r),i=r?"restore":"bookmark";e.logger.info(`POST ${t.requestUrl} -> pitr ${i}${e.shard===void 0?"":` (shard "${e.shard}")`}`);const n=await t.fetchImpl(t.requestUrl,{body:JSON.stringify({args:o,functionPath:a,shardKey:e.shard}),headers:{authorization:`Bearer ${t.token}`,"content-type":"application/json"},method:"POST"});return{body:await p(n,e.logger),code:n.ok?0:y(n.status)}},Be=(e,t)=>{if(e.bucket===void 0)return e.prefix!==void 0?(e.logger.error("--prefix applies only to an R2 destination — pass --bucket alongside it, or use --dir to point at a local directory."),{refused:u.USAGE}):ke(m(t,e.dir??Se));if(e.dir!==void 0)return e.logger.error("--dir applies only to a local destination and does not apply alongside --bucket — drop one."),{refused:u.USAGE};if(e.prod===!0&&e.url===void 0)return e.logger.error("--prod requires an explicit --url (refusing to target the implicit localhost worker)"),{refused:u.USAGE};const r=T(e.url,e.logger,e.cwd);if(r===void 0)return{refused:u.USAGE};const{token:a}=A({cwd:t,token:e.token,url:r});if(!a)return e.logger.error("admin token required — pass --token, set LUNORA_ADMIN_TOKEN, or add it to .dev.vars (local targets only)"),{refused:u.AUTH};const o=e.fetchImpl??I;return ve({context:{baseUrl:r,bucket:e.bucket==="default"?void 0:e.bucket,fetchImpl:o,token:a},logger:e.logger,prefix:e.prefix})},K=e=>{const t=[e.bucket===void 0?void 0:"--bucket",e.prefix===void 0?void 0:"--prefix",e.dir===void 0?void 0:"--dir"].filter(i=>i!==void 0);if(t.length>0)return e.logger.error(`${t.join(" / ")} ${t.length===1?"does":"do"} not apply here — retention and prune act on the store the worker itself is configured with (\`backupStore\`), not a destination you name.`),{refused:u.USAGE};if(e.prod===!0&&e.url===void 0)return e.logger.error("--prod requires an explicit --url (refusing to target the implicit localhost worker)"),{refused:u.USAGE};const r=T(e.url,e.logger,e.cwd);if(r===void 0)return{refused:u.USAGE};const{token:a}=A({cwd:e.cwd??process.cwd(),token:e.token,url:r});if(!a)return e.logger.error("admin token required — pass --token, set LUNORA_ADMIN_TOKEN, or add it to .dev.vars (local targets only)"),{refused:u.AUTH};const o=e.adminFetch??I;return{baseUrl:r,fetchImpl:o,token:a}},H=(e,t)=>{e.logger.info(`backup retention would keep the newest ${t.keep.toString()} of ${t.eligible.toString()} under ${t.prefix} and delete ${t.wouldDelete.length.toString()}`);for(const r of t.wouldDelete)e.logger.info(` ${j(r)}`)},De=async e=>{const t=K(e);if($(t))return{code:t.refused};const r=await t.fetchImpl(`${t.baseUrl}${F}`,{headers:{authorization:`Bearer ${t.token}`},method:"GET"});if(!r.ok)return await p(r,e.logger),{code:y(r.status)};const a=await r.json();return a.cron===void 0?(e.logger.info("no scheduled backup is configured (`backupCron`), so retention would delete nothing"),{code:0,preview:a}):a.keep<=0?(e.logger.info(`retention is off for cron "${a.cron}" (\`backupRetain\` unset) — every snapshot under ${a.prefix} is kept`),{code:0,preview:a}):(H(e,a),a.eligible===0&&e.logger.info(`no snapshot under ${a.prefix} was written by this cron, so retention has nothing of its own to remove`),{code:0,preview:a})},Ge=(e,t)=>{e.logger.success(`deleted ${t.deleted.length.toString()} backup(s)`);for(const r of t.deleted)e.logger.info(` ${j(r)}`);t.failed.length>0&&e.logger.warn(`${t.failed.length.toString()} could not be removed — the snapshot may be gone with its manifest left behind; run again to retry`),t.ignored>0&&e.logger.info(`${t.ignored.toString()} were already gone by the time the delete ran`),t.remaining>0&&e.logger.info(`${t.remaining.toString()} more are past the window — run \`lunora backup prune\` again`)},je=async e=>{const t=K(e);if($(t))return{code:t.refused};const r=await t.fetchImpl(`${t.baseUrl}${F}`,{headers:{authorization:`Bearer ${t.token}`},method:"GET"});if(!r.ok)return await p(r,e.logger),{code:y(r.status)};const a=await r.json();if(a.keep<=0||a.cron===void 0)return e.logger.error("backup prune needs a retention window: set `backupRetain` (and `backupCron`) on the worker. Without one there is nothing past the window to remove."),{code:u.USAGE,preview:a};if(a.wouldDelete.length===0)return e.logger.info(`nothing to prune — ${a.eligible.toString()} snapshot(s) under ${a.prefix} and retention keeps the newest ${a.keep.toString()}`),{code:0,preview:a};if(H(e,a),e.yes!==!0){const n=e.confirm??(J()?W:void 0);if(n===void 0)return e.logger.error("refusing to delete backups without confirmation — re-run with --yes (there is no undo for an object-store delete)"),{code:u.USAGE,preview:a};if(!await n(`delete ${a.wouldDelete.length.toString()} backup(s)? This cannot be undone. [y/N] `))return e.logger.info("nothing was deleted"),{code:0,preview:a}}const o=await t.fetchImpl(`${t.baseUrl}${Ee}`,{body:JSON.stringify({confirm:a.wouldDelete}),headers:{authorization:`Bearer ${t.token}`,"content-type":"application/json"},method:"POST"});if(!o.ok)return await p(o,e.logger),{code:y(o.status),preview:a};const i=await o.json();return Ge(e,i),{code:i.failed.length>0?1:0,deleted:i.deleted,preview:a}},qe=async(e,t)=>{try{if(e.subcommand==="pitr")return await Pe(e);if(e.subcommand==="retention")return await De(e);if(e.subcommand==="prune")return await je(e);const r=Be(e,t);return $(r)?{code:r.refused}:e.subcommand==="create"?await Ne(e,r):e.subcommand==="list"?await _e(e,r):await Re(e,r)}catch(r){return e.logger.error(r instanceof Error?r.message:String(r)),{code:1}}},Me=async e=>{const t=e.cwd??process.cwd(),r=await qe(e,t);return{...r,data:{subcommand:e.subcommand,...r.entries===void 0?{}:{entries:r.entries},...r.entry===void 0?{}:{entry:r.entry},...r.plan===void 0?{}:{plan:r.plan},...r.restored===void 0?{}:{restored:r.restored},...r.preview===void 0?{}:{preview:r.preview},...r.deleted===void 0?{}:{deleted:r.deleted},...r.body===void 0?{}:{result:r.body}}}},Ce=e=>e==="create"||e==="list"||e==="pitr"||e==="prune"||e==="restore"||e==="retention",sr=ee(async({argument:e,cwd:t,format:r,logger:a,options:o})=>{const i=e[0];if(!Ce(i)){const g=`backup: unknown subcommand "${i??""}" — expected create | list | restore | retention | prune | pitr`;return a.error(g),{code:u.USAGE,error:g}}const n=ie(o.mode,"require-empty");if(n===void 0){const g=`--mode ${String(o.mode)} is not a known mode. Expected one of: ${se.join(", ")}.`;return a.error(g),{code:u.USAGE,error:g}}const f=await Me({at:o.at,bookmark:o.bookmark,bucket:o.bucket,cwd:t,dir:o.dir,format:r,logger:a,prefix:o.prefix,dryRun:o.dryRun===!0,mode:n,prod:o.prod===!0,restart:o.restart===!0,restore:o.restore===!0,shard:o.shard,subcommand:i,tables:o.tables,target:e[1],token:o.token,url:re({cwd:t,prod:o.prod===!0,url:o.url}),verify:o.verify===!0,yes:o.yes===!0});return{code:f.code,data:f.data,error:f.error}});export{sr as execute,Me as runBackupCommand};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{randomUUID as V,createHash as gr,createHmac as yr}from"node:crypto";import{mkdirSync as $r,writeFileSync as ne,linkSync as xe,rmSync as F,statSync as Ce,unlinkSync as kr,readFileSync as L,existsSync as Q,mkdtempSync as Er}from"node:fs";import{tmpdir as br}from"node:os";import{join as ee,dirname as _r}from"node:path";import{SCHEMA_SNAPSHOT_FILENAME as Sr,readServiceBindings as Rr}from"@lunora/codegen";import{generateSecretValue as Ir}from"@lunora/config";import{readManifest as Ke,recordManifestKey as Or,findWranglerFile as He,readWranglerJsonc as $e,applyModify as Tr}from"@lunora/config/cloudflare";import{n as pe,r as Ar,c as Cr}from"../packem_shared/cloudflare-credentials-BVmZnDZk.mjs";import{d as jr}from"../packem_shared/command-BpMv2n0o.mjs";import{r as Ur}from"../packem_shared/deploy-target-resolve-BTCW1Iv4.mjs";import{EXIT_CODE as w}from"../packem_shared/EXIT_CODE-DQVhSE-1.mjs";import{c as oe,d as Nr,b as Lr,e as Dr,r as Pr}from"./handler.mjs";import{runSeedCommand as Fr}from"./
|
|
1
|
+
import{randomUUID as V,createHash as gr,createHmac as yr}from"node:crypto";import{mkdirSync as $r,writeFileSync as ne,linkSync as xe,rmSync as F,statSync as Ce,unlinkSync as kr,readFileSync as L,existsSync as Q,mkdtempSync as Er}from"node:fs";import{tmpdir as br}from"node:os";import{join as ee,dirname as _r}from"node:path";import{SCHEMA_SNAPSHOT_FILENAME as Sr,readServiceBindings as Rr}from"@lunora/codegen";import{generateSecretValue as Ir}from"@lunora/config";import{readManifest as Ke,recordManifestKey as Or,findWranglerFile as He,readWranglerJsonc as $e,applyModify as Tr}from"@lunora/config/cloudflare";import{n as pe,r as Ar,c as Cr}from"../packem_shared/cloudflare-credentials-BVmZnDZk.mjs";import{d as jr}from"../packem_shared/command-BpMv2n0o.mjs";import{r as Ur}from"../packem_shared/deploy-target-resolve-BTCW1Iv4.mjs";import{EXIT_CODE as w}from"../packem_shared/EXIT_CODE-DQVhSE-1.mjs";import{c as oe,d as Nr,b as Lr,e as Dr,r as Pr}from"./handler.mjs";import{runSeedCommand as Fr}from"./handler13.mjs";const Be=r=>{if(!Number.isInteger(r)||r<=0)return!1;try{return process.kill(r,0),!0}catch(e){return e.code==="EPERM"}},ve=r=>Number(L(r,"utf8")),je=r=>{try{return ve(r)}catch{return}},Mr=(r,e)=>{const t=`${r}.retire-${String(e)}`;try{xe(r,t)}catch{return!1}try{return ve(t)!==e||Be(ve(t))?!1:(Ce(r).ino===Ce(t).ino&&kr(r),!0)}finally{F(t,{force:!0})}},Wr=(r,e={})=>{const t=ee(r,".lunora","preview.lock"),n=`${t}.${String(process.pid)}.${V()}`;$r(_r(t),{recursive:!0}),ne(n,String(process.pid));try{for(let o=0;o<3;o+=1){try{return xe(n,t),{release:()=>{je(t)===process.pid&&F(t,{force:!0})}}}catch(i){if(i.code!=="EEXIST")throw i}const s=je(t);if(s!==void 0){if(Be(s))return{error:`another preview command is running in this checkout (pid ${String(s)}); wait for it, or remove ${t} if it is not`};if(e.afterReadingOwner?.(),!Mr(t,s))return{error:`another preview command is taking over the lock at ${t}; run this again in a moment. If none is running, remove ${t}.retire-${String(s)}`}}}return{error:`could not take the preview lock ${t}`}}finally{F(n,{force:!0})}},xr=[["SIGINT",130],["SIGTERM",143]],Kr=5e3,H=new Set,Ge=r=>process.exit(r);let Ve=Ge,Z=[],he;const Hr=async r=>{const e=[...H].map(async n=>{try{await n()}catch{}});H.clear();const t=new Promise(n=>{setTimeout(n,Kr).unref()});await Promise.race([Promise.all(e),t]),Ve(r)},Br=r=>(he??=Hr(r),he),ie=(r,e)=>(H.add(r),Z.length===0&&(Z=xr.map(([t,n])=>{const o=()=>{Br(n).catch(()=>{})};return process.on(t,o),[t,o]})),()=>{if(H.delete(r),H.size===0){for(const[t,n]of Z)process.off(t,n);Z=[],Ve=Ge,he=void 0}}),Gr=/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu,ke=r=>{const e=Ke(r)?.lunora?.appId;return typeof e=="string"&&Gr.test(e)?e.toLowerCase():void 0},Vr=r=>{const e=Ke(r);if(e===void 0||e.lunoraIsForeign||e.lunora?.appId!==void 0)return;const t=V();return Or(e,"appId",t),ke(r)===t?t:void 0},se=63,Ee=new Set(["$schema","account_id","alias","assets","base_dir","build","cache","compatibility_date","compatibility_flags","dev","env","find_additional_modules","jsx_factory","jsx_fragment","keep_names","keep_vars","limits","logpush","main","migrations","minify","name","no_bundle","observability","placement","preview_urls","routes","rules","send_metrics","triggers","tsconfig","upload_source_maps","workers_dev"]),Ye=["ai","browser","define","images","media","secrets","vars","version_metadata","worker_loaders"],Yr=new Set(["env","name","routes","triggers","workers_dev"]),Jr=new Set(["tail_consumers"]),Y={d1_databases:"d1",kv_namespaces:"kv",r2_buckets:"r2"},R=r=>r==null?!1:Array.isArray(r)?r.length>0:typeof r!="object"||Object.keys(r).length>0,I=r=>(Array.isArray(r)?r:[]).filter(e=>typeof e=="object"&&e!==null),Je=(r,e)=>{if(!(Ee.has(r)||Jr.has(r)||Ye.includes(r)||r in Y)){if(r==="route")return`"route": write it as "routes" so the preview can clear it — a preview must never claim production's hostname`;if(r==="durable_objects"){const t=I(e.bindings).find(n=>n.script_name!==void 0);return t===void 0?void 0:`"durable_objects": the "${String(t.name)}" binding reaches another Worker's Durable Objects (script_name "${String(t.script_name)}"), which the preview would share`}if(r==="exports"){const t=Object.entries(e).find(([,n])=>n?.type==="workflow");return t===void 0?void 0:`"exports": workflow "${t[0]}" has an account-wide name, so the preview would take over production's workflow`}return`"${r}": a preview cannot isolate this binding yet, and copying it would point the preview at production's resource`}},Ue="LUNORA_ORIGIN_URL",U="LUNORA_PREVIEW",M=r=>`${r.production}/${r.slug}`,Xr=/[^a-z0-9]+/u,Xe=r=>{const e=r.toLowerCase().split(Xr).filter(t=>t!=="").join("-");return e===""||e===r?e:`${e}-${gr("sha256").update(r).digest("hex").slice(0,6)}`},ae=(r,e)=>oe(`${r}-preview-${e}`,se),be=(r,e)=>{const t=typeof r.name=="string"?r.name:"",n=Xe(e);return t===""?{error:`wrangler.jsonc has no "name", which the preview Worker's name is derived from`}:n===""?{error:`"${e}" has no letter or digit to name a preview by`}:{names:{environment:oe(`preview-${n}`,se),production:t,slug:n,worker:ae(t,n)}}},J=(r,e,t)=>{const n=t?.vars?.[U],o=typeof n=="string"?n.lastIndexOf("/"):-1;if(typeof n!="string"||o<=0)return!1;const s=n.slice(0,o),i=n.slice(o+1);return Xe(i)===i&&M({production:s,slug:i})===n&&e===oe(`preview-${i}`,se)&&t?.name===ae(s,i)},ce=(r,e)=>{const t=Object.entries(r.env??{}).filter(([i,a])=>!J(r,i,a)),n=t.find(([i])=>i===e)?.[1];if(n===void 0)return{error:`wrangler.jsonc declares no environment "${e}"${t.length===0?", and no environments at all":`; the environments are: ${t.map(([i])=>i).join(", ")}`}`};const o=typeof r.name=="string"?r.name:"";return{config:{...Object.fromEntries(Object.entries(r).filter(([i])=>Ee.has(i)||i==="exports")),...n,env:r.env,name:typeof n.name=="string"?n.name:`${o}-${e}`}}},zr=r=>Object.entries(r.env??{}).filter(([e,t])=>!J(r,e,t)).filter(([,e])=>typeof e.account_id=="string"&&e.account_id!==""&&e.account_id!==r.account_id).map(([e])=>e),ze=(r,e)=>oe(`${r}-${e}`,se),Ne=(r,e)=>{const t=I(r==="durable_objects"?e?.bindings:e);return new Set(t.map(n=>String(r==="durable_objects"?n.name:n.binding)))},Qr=r=>Object.entries(r.env??{}).flatMap(([e,t])=>J(r,e,t)?[]:Object.entries(t).flatMap(([n,o])=>{const s=r[n];if(n in Y||n==="durable_objects"){const i=Ne(n,s),a=[...Ne(n,o)].filter(c=>!i.has(c));return a.length===0?[]:[`"env.${e}.${n}": binding ${a.map(c=>`"${c}"`).join(", ")} is declared only in this environment, and a preview is planned from the top-level config, which lacks it; pass --env ${e} to preview that environment`]}return Je(n,o)!==void 0&&R(o)&&!R(s)?[`"env.${e}.${n}": declared only in this environment, and a preview is planned from the top-level config, which has none; pass --env ${e} to preview that environment`]:[]})),Qe=(r,e)=>Object.entries(Y).flatMap(([t,n])=>I(r[t]).map(o=>{const s=String(o.binding);return{binding:s,kind:n,name:ze(e,s)}})),Ze=(r,e,t)=>{const n=be(r,e);if(n.error!==void 0)return{error:n.error};const{environment:o,worker:s}=n.names;if(r.env?.[o]!==void 0&&!J(r,o,r.env[o]))return{error:`wrangler.jsonc already declares "env.${o}"; a preview writes that block itself, so name the preview differently`};const i=Object.entries(r).flatMap(([f,l])=>{const m=R(l)?Je(f,l):void 0;return m===void 0?[]:[m]});i.push(...t===void 0?Qr(r):[]);const a=I(r.r2_buckets).find(f=>f.jurisdiction!==void 0);if(a!==void 0&&i.push(`"r2_buckets": the "${String(a.binding)}" bucket has a jurisdiction, which a preview bucket is not created in yet`),i.length>0)return{error:`this app cannot be previewed in isolation yet:
|
|
2
2
|
- ${i.join(`
|
|
3
3
|
- `)}`};const c=Object.keys(Y).find(f=>I(r[f]).some(l=>typeof l.binding!="string"||l.binding===""));if(c!==void 0)return{error:`"${c}": every entry needs a non-empty "binding"`};const d=Qe(r,s),u=d.find((f,l)=>d.findIndex(m=>m.kind===f.kind&&m.name===f.name)!==l);return u!==void 0?{error:`two bindings would share the preview resource name "${u.name}"; rename one of them (binding names differ only by case or punctuation)`}:{plan:{...n.names,...t===void 0?{}:{sourceEnv:t},resources:d}}},_e=(r,e)=>`${r}:${e}`,le=(r,e)=>Object.fromEntries(Object.entries(r).filter(([t])=>t!==e)),Zr=(r,e,t,n)=>{const o=String(e.binding),s=t.resources.find(a=>a.kind===r&&a.binding===o),i=n.get(_e(r,o));if(s===void 0||i===void 0)throw new Error(`no preview ${r} resource for the "${o}" binding`);return r==="d1"?{...le(e,"preview_database_id"),database_id:i,database_name:s.name}:r==="kv"?{...le(e,"preview_id"),id:i}:{...le(e,"preview_bucket_name"),bucket_name:i}},qr=(r,e,t)=>{if(t.sourceEnv!==void 0)for(const[n,o]of Object.entries(r))Ee.has(n)&&!(n in e)&&!Yr.has(n)&&R(o)&&(e[n]=o)},qe=(r,e,t,n={},o)=>{const s=r,i={name:e.worker,routes:[],triggers:{crons:[]},workers_dev:!0},a={[U]:M(e)};for(const d of Ye)R(s[d])&&(i[d]=s[d]);const c=R(r.vars)?{...r.vars}:{};Ue in c&&(c[Ue]=o??e.worker),i.vars={...c,...n,...a},qr(s,i,e),R(r.durable_objects)&&(i.durable_objects=r.durable_objects),R(s.exports)&&(i.exports=s.exports);for(const[d,u]of Object.entries(Y)){const f=I(s[d]);f.length>0&&(i[d]=f.map(l=>Zr(u,l,e,t)))}return i},et={analytics_engine_datasets:["dataset"],d1_databases:["database_id","database_name","preview_database_id"],hyperdrive:["id"],kv_namespaces:["id","preview_id"],r2_buckets:["bucket_name","preview_bucket_name"],vectorize:["index_name"]},rt={d1_databases:"d1",kv_namespaces:"kv",r2_buckets:"r2"},er=(r,e)=>`${r}:${e}`,tt=r=>{const e=Object.entries(et).flatMap(([t,n])=>I(r[t]).flatMap(o=>n.map(s=>[rt[t]??t,o[s]])));return[["worker",r.name],...e].flatMap(([t,n])=>typeof n=="string"&&n!==""?[er(t,n)]:[])},nt=r=>new Set([r,...Object.entries(r.env??{}).flatMap(([e,t])=>J(r,e,t)?[]:[t])].flatMap(e=>tt(e))),Se=(r,e)=>[{kind:"worker",value:r},...e.flatMap(t=>[...t.id===void 0?[]:[{kind:t.kind,value:t.id}],{kind:t.kind,value:t.name}])],B=(r,e)=>{const t=nt(r);return e.filter(n=>t.has(er(n.kind,n.value))).map(n=>n.value)},fe="lunora-preview-registry",re=50,de=r=>{try{const{errors:e}=JSON.parse(r.text),t=(e??[]).flatMap(n=>typeof n.message=="string"?[n.message]:[]).join("; ");return t===""?`HTTP ${String(r.status)}`:`HTTP ${String(r.status)}: ${t}`}catch{return`HTTP ${String(r.status)}`}},k=(r,e)=>{if(!r.ok)throw new Error(`${e} failed (${de(r)})`);return r.body},X=r=>typeof r.result=="object"&&r.result!==null?r.result:{},rr=async(r,e,t,n)=>{const o=e.includes("?")?"&":"?";for(let s=1;s<=re;s+=1){const i=k(await r(`${e}${o}per_page=100&page=${String(s)}`),`listing ${t}`);if(!Array.isArray(i.result))throw new TypeError(`the listing of ${t} had no result list`);const a=i.result.find(d=>n(d)),c=typeof i.result_info=="object"&&i.result_info!==null?i.result_info:{};if(a!==void 0||typeof c.total_pages!="number"||s>=c.total_pages)return a}throw new Error(`the listing of ${t} has more than ${String(re)} pages`)},me=async(r,e)=>{const t=await rr(r,"/storage/kv/namespaces","KV namespaces",n=>n.title===e);return typeof t?.id=="string"?t.id:void 0},tr=async(r,e)=>{const{id:t}=X(k(await r("/storage/kv/namespaces",{body:JSON.stringify({title:e}),method:"POST"}),`creating KV namespace "${e}"`));if(typeof t!="string")throw new TypeError(`creating KV namespace "${e}" returned no id`);return t},Re=async(r,e)=>{const t=await me(r,fe);if(t!==void 0||!e)return t;try{return await tr(r,fe)}catch(n){const o=await me(r,fe);if(o===void 0)throw n;return o}},D=(r,e)=>`${r}/${e}`,Ie=(r,e)=>`/storage/kv/namespaces/${encodeURIComponent(r)}/values/${encodeURIComponent(e)}`,ot=new Set(["d1","kv","r2"]),it=(r,e)=>{const t=e;return typeof t=="object"&&t!==null&&ot.has(t.kind)&&typeof t.binding=="string"&&typeof t.id=="string"&&(t.pending===void 0||t.pending===!0)&&t.name===ze(r,t.binding)&&(t.kind!=="r2"||t.id===t.name)},st=r=>{const e=r;return typeof e=="object"&&e!==null&&typeof e.repository=="string"&&typeof e.pullRequest=="string"},at=r=>{const e=r;return typeof e=="object"&&e!==null&&e.version===1&&typeof e.worker=="string"&&typeof e.production=="string"&&(e.appId===void 0||typeof e.appId=="string")&&(e.sourceEnv===void 0||typeof e.sourceEnv=="string")&&(e.origin===void 0||st(e.origin))&&Array.isArray(e.resources)&&e.resources.every(t=>it(e.worker,t))},ge=async(r,e,t)=>{const n=await r(Ie(e,t));if(!n.ok&&n.status===404)return;const o=k(n,`reading the preview record "${t}"`);if(!at(o))throw new TypeError(`the preview record "${t}" is not one this CLI wrote; refusing to act on it`);return o},j=async(r,e,t)=>{k(await r(Ie(e,D(t.appId??t.production,t.name)),{body:JSON.stringify(t),method:"PUT"}),"writing the preview record")},nr=async(r,e,t)=>{k(await r(Ie(e,D(t.appId??t.production,t.name)),{method:"DELETE"}),"removing the preview record")},Le=async(r,e,t)=>{const n=`${t}/`,o=[];let s="";for(let a=1;;a+=1){if(a>re)throw new Error(`the preview records of "${t}" span more than ${String(re)} pages`);const c=`prefix=${encodeURIComponent(n)}${s===""?"":`&cursor=${encodeURIComponent(s)}`}`,d=k(await r(`/storage/kv/namespaces/${encodeURIComponent(e)}/keys?${c}`),"listing preview records");o.push(...(Array.isArray(d.result)?d.result:[]).flatMap(f=>typeof f.name=="string"?[f.name]:[]));const u=typeof d.result_info=="object"&&d.result_info!==null?d.result_info:{};if(typeof u.cursor!="string"||u.cursor==="")break;s=u.cursor}const i=await Promise.all(o.map(async a=>{try{return{key:a,record:await ge(r,e,a)}}catch(c){if(c instanceof TypeError)return{key:a,unreadable:!0};throw c}}));return{records:i.flatMap(a=>a.record===void 0?[]:[a.record]),unreadable:i.flatMap(a=>a.unreadable===!0?[a.key]:[])}},W=async(r,e,t)=>{if(e==="d1"){const o=await rr(r,`/d1/database?name=${encodeURIComponent(t)}`,"D1 databases",s=>s.name===t);return typeof o?.uuid=="string"?o.uuid:void 0}if(e==="kv")return me(r,t);const n=await r(`/r2/buckets/${encodeURIComponent(t)}`);if(!(!n.ok&&n.status===404))return k(n,`looking up R2 bucket "${t}"`),t},or=async(r,e,t)=>{if(e==="kv")return tr(r,t);if(e==="r2")return k(await r("/r2/buckets",{body:JSON.stringify({name:t}),method:"POST"}),`creating R2 bucket "${t}"`),t;const{uuid:n}=X(k(await r("/d1/database",{body:JSON.stringify({name:t}),method:"POST"}),`creating D1 database "${t}"`));if(typeof n!="string")throw new TypeError(`creating D1 database "${t}" returned no id`);return n},q="lunora-preview-locks",G=1800,ct=/UNIQUE constraint failed|SQLITE_CONSTRAINT/iu,dt="CREATE TABLE IF NOT EXISTS lunora_preview_locks (app_id TEXT NOT NULL, name TEXT NOT NULL, owner_token TEXT NOT NULL, expires_at INTEGER NOT NULL, PRIMARY KEY (app_id, name))",ut="INSERT INTO lunora_preview_locks (app_id, name, owner_token, expires_at) VALUES (?, ?, ?, unixepoch() + ?)",lt="SELECT owner_token, expires_at, unixepoch() AS now FROM lunora_preview_locks WHERE app_id = ? AND name = ?",ft="UPDATE lunora_preview_locks SET owner_token = ?, expires_at = unixepoch() + ? WHERE app_id = ? AND name = ? AND owner_token = ? AND expires_at <= unixepoch()",wt="DELETE FROM lunora_preview_locks WHERE app_id = ? AND name = ? AND owner_token = ?",E=async(r,e,t,n)=>{const o=await r(`/d1/database/${encodeURIComponent(e)}/query`,{body:JSON.stringify({params:n,sql:t}),method:"POST"});if(!o.ok&&ct.test(o.text))return;const s=k(o,"using the preview lock database").result?.[0];return{changes:typeof s?.meta?.changes=="number"?s.meta.changes:0,rows:Array.isArray(s?.results)?s.results:[]}},pt=4,vt=50,ht=async r=>new Promise(e=>{setTimeout(e,r)}),z="lunora_preview_lock_owner",ir="lunora-preview-locks/v1",mt=`CREATE TABLE IF NOT EXISTS ${z} (marker TEXT NOT NULL)`,gt=`INSERT INTO ${z} (marker) VALUES (?)`,yt="SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?",$t=`SELECT marker FROM ${z}`,De=async(r,e)=>((await E(r,e,yt,[z]))?.rows.length??0)===0?!1:(await E(r,e,$t,[]))?.rows.some(o=>o.marker===ir)===!0,sr=async r=>{let e=await W(r,"d1",q),t=!1;if(e===void 0)try{e=await or(r,"d1",q),t=!0}catch(n){if(e=await W(r,"d1",q),e===void 0)throw n}if(t)await E(r,e,mt,[]),await E(r,e,gt,[ir]);else{let n=await De(r,e);for(let o=1;o<=pt&&!n;o+=1)await ht(vt*o),n=await De(r,e);if(!n)throw new Error(`a D1 database named "${q}" exists but was not made by lunora preview (it has no ${z} marker), so it is not used or written to. Rename it, or delete it if it is an empty leftover, then run this again`)}return await E(r,e,dt,[]),e},ar=async(r,e,t,n)=>{const o=V();for(let s=0;s<2;s+=1){if(await E(r,e,ut,[t,n,o,G])!==void 0)return{token:o};const a=(await E(r,e,lt,[t,n]))?.rows[0];if(a===void 0)continue;const c=Number(a.expires_at),d=Number.isFinite(c)&&c<=Number(a.now);return typeof a.owner_token!="string"||!d?{held:new Date(c*1e3).toISOString()}:(await E(r,e,ft,[o,G,t,n,a.owner_token]))?.changes===1?{token:o}:{held:"just now"}}return{held:"just now"}},cr="UPDATE lunora_preview_locks SET expires_at = unixepoch() + ? WHERE app_id = ? AND name = ? AND owner_token = ?",dr=(r,e,t,n,o,s,i=G*1e3/3)=>{let a=Date.now();const c=async()=>{try{(await E(r,e,cr,[G,t,n,o]))?.changes===1?a=Date.now():s()}catch{Date.now()-a>=2*i&&s()}},d=setInterval(()=>{c().catch(()=>{})},i);return d.unref(),()=>{clearInterval(d)}},ur=async(r,e,t,n,o)=>(await E(r,e,cr,[G,t,n,o]))?.changes===1,te=async(r,e,t,n,o)=>{try{await E(r,e,wt,[t,n,o])}catch{}},lr={d1:r=>`/d1/database/${encodeURIComponent(r)}`,kv:r=>`/storage/kv/namespaces/${encodeURIComponent(r)}`,r2:r=>`/r2/buckets/${encodeURIComponent(r)}`},Oe=async(r,e)=>{if(e.pending===!0)return"present";const t=await r(lr[e.kind](e.id));if(!t.ok&&t.status===404)return"missing";const n=X(k(t,`looking up ${e.kind} "${e.name}"`));return{d1:n.name,kv:n.title,r2:n.name}[e.kind]===e.name?"present":"changed"},Pe=10,kt=(r,e)=>{const t=`/r2/buckets/${encodeURIComponent(r)}/objects/`,n=`${t}${e.split("/").map(o=>encodeURIComponent(o)).join("/")}`;return e.split("/").some(o=>o==="."||o==="..")?{error:`the object key ${JSON.stringify(e)} has a "." or ".." segment, which cannot be addressed without leaving the bucket`}:new URL(`https://api.invalid/accounts/a${n}`).pathname.startsWith(`/accounts/a${t}`)?{path:n}:{error:`the object key ${JSON.stringify(e)} resolves outside bucket "${r}"`}},Et=async(r,e,t)=>{const n=kt(e,t);if(n.error!==void 0)throw new Error(`${n.error}; remove the object by hand, then delete the preview again`);const o=await r(n.path,{method:"DELETE"});if(!o.ok&&o.status!==404)throw new Error(`deleting object "${t}" failed (${de(o)})`)},bt=async(r,e)=>{let t="";for(;;){const n=await r(`/r2/buckets/${encodeURIComponent(e)}/objects?per_page=1000`);if(!n.ok&&n.status===404)return;const o=k(n,`listing the objects of R2 bucket "${e}"`),s=(Array.isArray(o.result)?o.result:[]).flatMap(a=>typeof a.key=="string"?[a.key]:[]);if(s.length===0)return;const i=`${String(s.length)}:${s[0]??""}:${s.at(-1)??""}`;if(i===t)throw new Error(`R2 bucket "${e}" still holds the same ${String(s.length)} objects after deleting them`);t=i;for(let a=0;a<s.length;a+=Pe)await Promise.all(s.slice(a,a+Pe).map(async c=>Et(r,e,c)))}},fr=async(r,e)=>{e.kind==="r2"&&await bt(r,e.id);const t=await r(lr[e.kind](e.id),{method:"DELETE"});if(!(t.ok||t.status===404))throw new Error(de(t))},_t=async(r,e)=>{const t=await r(`/workers/scripts/${encodeURIComponent(e)}?force=true`,{method:"DELETE"});if(!t.ok&&t.status!==404)throw new Error(de(t))},ue=async(r,e,t)=>{const n=await r(`/workers/scripts/${encodeURIComponent(e)}/settings`);if(!n.ok&&n.status===404)return{exists:!1};const{bindings:o}=X(k(n,`reading Worker "${e}"`)),s=(Array.isArray(o)?o:[]).find(i=>i.type==="plain_text"&&i.name===t)?.text;return typeof s=="string"?{exists:!0,marker:s}:{exists:!0}},St=async r=>{const{subdomain:e}=X(k(await r("/workers/subdomain"),"reading the workers.dev subdomain"));if(typeof e!="string"||e==="")throw new TypeError("the account has no workers.dev subdomain; register one in the Cloudflare dashboard (Workers & Pages) first");return e},Fe=["deploy","list","delete"],Rt=15e3,It=5,Ot=1e3,we="LUNORA_ADMIN_TOKEN",Tt="LUNORA_PREVIEW_SECRET_SEED",At={celld:"celld has no isolated previews: it has no Wrangler environments, and an app's Durable Objects and storage live in its fleet bucket. Deploy the copy as its own config, with CELLD_BUCKET naming a bucket of its own, and delete that bucket to remove it.",node:"the Node target has no `lunora deploy`, so there is nothing to preview: run a second process with its own database for a copy."},p=(r,e,t=w.USAGE)=>(r.error(e),{code:t,error:e}),C=r=>({environment:r.environment,name:r.name,resources:r.resources,url:r.url,worker:r.worker}),Ct=async(r,e)=>{if(r===void 0)return e();r.begin();try{return await e()}finally{r.end()}},wr=(r,e)=>{const t=pe(r.GITHUB_REPOSITORY)??pe(r.CI_PROJECT_PATH);return t===void 0?void 0:{pullRequest:e,repository:t}},jt=(r,e)=>e!==void 0&&r.origin?.repository===e.repository&&r.origin.pullRequest===e.pullRequest,Ut=r=>`the record for preview "${r}" was not made by a deploy of this pull request in this repository, so the identity the pull request's files name cannot be used to delete it; nothing was deleted`,Nt=(r,e)=>{const t=Se(e.worker,e.resources);return[...B(r.config,t),...r.alsoProduction===void 0?[]:B(r.alsoProduction,t)]},Lt=(r,e,t)=>{Object.assign(r,{origin:wr(e.environment??process.env,t)??r.origin})},Dt=r=>{const e=r.cwd??process.cwd(),t=Ur(e,r.target);if(t.target===void 0)return{error:t.error??"unknown deploy target"};if(t.target!=="cloudflare")return{error:`lunora preview: ${At[t.target]??`target "${t.target}" has no isolated previews.`}`};const n=He(e),o=n===void 0?void 0:$e(n).parsed;return n===void 0||o===void 0?{error:"lunora preview needs a readable wrangler.jsonc (or wrangler.json) in the project root"}:{context:{config:o,cwd:e,appId:ke(e),wranglerPath:n}}},pr=(r,e)=>{if(e.env!==void 0)return ce(r,e.env);const t=zr(r);return t.length>0&&pe((e.environment??process.env).CLOUDFLARE_ACCOUNT_ID)===void 0?{error:`environment(s) ${t.join(", ")} name an account_id other than the top level's, so the account to look in is not clear: pass --env <name> to use that environment's account, or set CLOUDFLARE_ACCOUNT_ID`}:{config:r}},Te=(r,e)=>{const{accountId:t,token:n}=Ar(r.environment??process.env,e.account_id);if(t===void 0||n===void 0)return{error:"lunora preview needs CLOUDFLARE_API_TOKEN (Workers Scripts, D1, Workers KV Storage and Workers R2 Storage: Edit) and CLOUDFLARE_ACCOUNT_ID (or `account_id` in wrangler.jsonc)"};const o=r.sleep??(async s=>new Promise(i=>{setTimeout(i,s)}));return{request:async(s,i)=>{for(let a=0;;a+=1){const c=await Cr({accountId:t,apiToken:n,...r.fetch===void 0?{}:{fetch:r.fetch},init:{...i,signal:AbortSignal.timeout(Rt)},path:s});if(c.ok||c.status!==429||a===It)return c;await o(Ot*2**a)}}}},Pt=r=>{const e={};for(const t of r){const n=t.indexOf("="),o=n===-1?"":t.slice(0,n);if(o==="")return{error:`--var ${t}: expected KEY=VALUE`};if(o===U)return{error:`--var ${o}: the preview sets ${o} itself; \`delete\` relies on it`};e[o]=t.slice(n+1)}return{variables:e}},$=r=>r instanceof Error?r.message:String(r),Ft=async(r,e,t,n)=>{const o=n.environment??process.env,s={},i=o[Tt],a=h=>i===void 0||i===""?Ir():yr("sha256",i).update(`${t}/${h}`).digest("hex");for(const h of n.secrets??[]){const y=o[h];if(y===void 0||y==="")return{error:`--secret ${h}: ${h} is not set in the environment, so there is no value to give the preview`};s[h]=y}const{fromLocal:c,fromPackages:d,mintableKeys:u}=await Dr(r,void 0);for(const h of[...d,...c,we])s[h]===void 0&&(u.has(h)||h===we)&&(s[h]=a(h));const f=[...new Set([...d,...c])].filter(h=>s[h]===void 0),m=(Array.isArray(e.secrets?.required)?e.secrets.required.filter(h=>typeof h=="string"):[]).filter(h=>s[h]===void 0);return m.length>0?{error:`the config requires ${m.join(", ")}, which a preview only gets when you pass ${m.map(h=>`--secret ${h}`).join(" ")}`}:{adminToken:s[we]??"",missing:f,values:s}},Mt=async(r,e,t,n)=>{const o=await Oe(r,t);if(o!=="present")throw new Error(`the record lists ${t.kind} "${t.name}" (${t.id}), which is ${o==="missing"?"gone":"now a different resource"}; run \`lunora preview delete ${e.slug}\`, then deploy again`);return n.info(`reusing preview ${t.kind} "${t.name}" for ${t.binding}`),t.id},Wt=3,xt=async(r,e,t,n)=>{let o;for(let s=1;s<=Wt;s+=1)try{await j(r,e,t);return}catch(i){o=i}try{await fr(r,n),t.resources.splice(t.resources.indexOf(n),1),await j(r,e,t).catch(()=>{})}catch{n.pending=!0}throw new Error(`${$(o)}; the ${n.kind} "${n.name}" just made could not be recorded, so it was removed again`)},Me=async(r,e,t,n,o)=>{let s=t.resources.find(i=>i.kind===n.kind&&i.binding===n.binding);if(s===void 0){if(await W(r,n.kind,n.name)!==void 0)throw new Error(`a ${n.kind} resource named "${n.name}" already exists but no preview record says this preview created it; refusing to bind or later delete it. Rename or remove it, then deploy again.`);s={binding:n.binding,id:n.kind==="r2"?n.name:"",kind:n.kind,name:n.name,pending:!0},t.resources.push(s),await j(r,e,t)}return s.id=await or(r,n.kind,n.name),delete s.pending,await xt(r,e,t,s),o.success(`created preview ${n.kind} "${n.name}" for ${n.binding}`),s.id},Kt=async(r,e,t,n,o,s,i)=>{const a=n.resources.find(c=>c.kind===o.kind&&c.binding===o.binding);if(i(),a?.pending===!0){if(await W(r,o.kind,o.name)!==void 0)throw new Error(`a ${o.kind} resource named "${o.name}" exists, but the record only claims it (a deploy was interrupted between creating it and confirming it), so nothing shows it is this preview's and it is left alone. If it is, delete it by hand, then deploy again`);return Me(r,e,n,o,s)}return a===void 0?Me(r,e,n,o,s):Mt(r,t,a,s)},Ht=async(r,e,t,n,o,s)=>{const i=new Map;for(const a of t.resources)i.set(_e(a.kind,a.binding),await Kt(r,e,t,n,a,o,s));return i},Ae=async(r,e,t)=>{const n=new Set((t?.resources??[]).map(i=>`${i.kind}:${i.name}`)),o=e.filter(i=>!n.has(`${i.kind}:${i.name}`));return(await Promise.all(o.map(async i=>await W(r,i.kind,i.name)===void 0?[]:[i]))).flat().map(i=>`${i.kind} "${i.name}"`)},vr=async(r,e,t,n,o,s,i=!0)=>{if(t!==void 0){const d=await ge(r,e,D(t,o));if(d!==void 0){if(d.appId!==t||d.name!==o)throw new Error(`the record at "${D(t,o)}" does not name this project and preview; refusing to act on it`);return d}}const a=await ge(r,e,D(n,o));if(a===void 0)return;if(a.appId!==void 0||a.production!==n||a.name!==o||a.worker!==ae(n,o))throw new Error(`the record at "${D(n,o)}" is not the one this CLI wrote for that Worker and preview; refusing to act on it`);if(t===void 0)throw new Error(`preview "${o}" is recorded under the Worker name, from before projects had an id. Add an app id to package.json (\`"lunora": { "appId": "${V()}" }\`), commit it, and run this again: it re-keys the record`);const c={...a,appId:t};return i&&(await j(r,e,c),await nr(r,e,a),s.warn(`preview "${o}" was recorded under the Worker name "${n}"; it is now recorded under project ${t}`)),c},Bt=async(r,e,t,n)=>{if(t===void 0&&n)return{code:w.USAGE,error:`a Worker named "${e.worker}" exists but no preview record says this preview deployed it; refusing to deploy over it`};if(t!==void 0&&t.worker!==e.worker)return{code:w.USAGE,error:`the record for preview "${e.slug}" names Worker "${t.worker}", not "${e.worker}" (the app was renamed since it was deployed); run \`lunora preview delete ${e.slug}\` first, then deploy it again`};const o=await Ae(r,e.resources,t);return o.length===0?void 0:{code:w.FAILURE,error:`${o.join(", ")} already exist(s) but no preview record read says this preview created it, so nothing was written or deployed. If a deploy of this preview just ran, its record may not have reached this location yet: run the deploy again. Otherwise rename or remove it, then deploy again.`}},Gt=r=>{const e=Er(ee(br(),"lunora-preview-")),t=ee(e,"secrets.json");return ne(t,JSON.stringify(r),{encoding:"utf8",mode:384}),{directory:e,path:t}},Vt=r=>({environment:r.environment,name:r.slug,resources:r.resources,worker:r.worker}),Yt=(r,e,t)=>{r.info(`dry run: preview "${e.slug}" would deploy Worker "${e.worker}" (env.${e.environment}, its own Durable Object namespace)`);for(const s of e.resources)r.info(` ${s.kind} "${s.name}" for ${s.binding} (created, or reused when this preview's record lists it)`);const{vars:n}=t,o=n===void 0?t:{...t,vars:Object.fromEntries(Object.keys(n).map(s=>[s,"<redacted>"]))};r.info(` env.${e.environment}: ${JSON.stringify(o)}`)},hr=(r,e)=>`another run is deploying or deleting preview "${r}" (its hold lapses at ${e}); wait for it. A run that crashed lets go by itself`,mr=(r,e,t)=>{const n=`the hold on preview "${e}" was lost while this run worked, so another run may have changed it too; check it with \`lunora preview list\` and run \`lunora preview delete ${e}\` if it looks wrong`;return r.error(n),{...t,code:t.code===w.SUCCESS?w.FAILURE:t.code,error:n}},Jt=async(r,e,t)=>{try{const n=await ue(r,e,U);return n.exists&&n.marker===t?"present":"absent"}catch{return"unknown"}},ye=(r,e)=>{if(r.lost)throw new Error(`the hold on preview "${e}" can no longer be vouched for, so this run stopped before changing anything more; check it with \`lunora preview list\`, then run again`)},Xt=async(r,e,t,n,o,s)=>{const{config:i,cwd:a,wranglerPath:c}=r,{logger:d}=n,{appId:u,hold:f,namespace:l,secrets:m,subdomain:h}=s;let y,_;const v=`https://${e.worker}.${h}.workers.dev`;try{const g=await ue(o,e.worker,U);if(g.exists&&g.marker!==M(e))return p(d,`a Worker named "${e.worker}" already exists and was not deployed as preview "${e.slug}"; refusing to deploy over it`);const b=new Date().toISOString(),T=await vr(o,l,u,e.production,e.slug,d),A=await Bt(o,e,T,g.exists);if(A!==void 0)return p(d,A.error,A.code);const N=B(r.committed??i,Se(e.worker,T?.resources??[]));if(N.length>0)return p(d,`preview "${e.slug}" would bind production's ${N.join(", ")}; refusing to deploy, nothing was created`);y=T??{createdAt:b,environment:e.environment,name:e.slug,production:e.production,appId:u,...e.sourceEnv===void 0?{}:{sourceEnv:e.sourceEnv},resources:[],updatedAt:b,version:1,worker:e.worker},Lt(y,n,e.slug),await f.verify(),await j(o,l,y),_=await Ht(o,l,e,y,d,()=>{ye(f,e.slug)})}catch(g){return p(d,`preview "${e.slug}": ${$(g)}`,w.FAILURE)}const O=B(r.committed??i,[..._].map(([g,b])=>({kind:g.slice(0,g.indexOf(":")),value:b})));if(O.length>0)return p(d,`preview "${e.slug}" would bind production's ${O.join(", ")}; refusing to deploy`);const x=L(c,"utf8");ne(c,Tr(x,["env",e.environment],qe(i,e,_,t,v)),"utf8");const S=Gt(m.values),K=ie(()=>{F(S.directory,{force:!0,recursive:!0})});try{ye(f,e.slug);const g=await Ct(r.baseline,async()=>Pr({allowSchemaDrift:!0,cwd:a,env:e.environment,fetchImpl:n.adminFetch,format:n.format,healthCheck:!0,healthFetch:n.healthFetch,healthSleep:n.healthSleep,interactive:!1,isolatedPreview:!0,logger:d,migrate:!0,migrateToken:m.adminToken,migrateUrl:v,migrateYes:!0,secretsFile:S.path,spawner:n.spawner,target:"cloudflare"})),b=g.code===0?"present":await Jt(o,e.worker,M(e));if(b==="present"?y.url=v:b==="absent"&&delete y.url,y.updatedAt=new Date().toISOString(),await f.verify(),await j(o,l,y),g.code!==0)return{code:g.code,data:{preview:C(y)},error:g.error??`preview "${e.slug}" did not deploy`}}catch(g){return p(d,`preview "${e.slug}": ${$(g)}`,w.FAILURE)}finally{F(S.directory,{force:!0,recursive:!0}),K()}if(n.seed===!0){const g=await Fr({cwd:a,fetchImpl:n.adminFetch,format:n.format,logger:d,prod:!0,token:m.adminToken,url:y.url,yes:!0});if(g.code!==0)return{code:g.code,data:{preview:C(y)},error:g.error??`seeding preview "${e.slug}" failed`}}return d.success(`preview "${e.slug}" is live at ${y.url??v} — remove it with \`lunora preview delete ${e.slug}\``),{code:w.SUCCESS,data:{preview:C(y)}}},zt=async(r,e,t,n,o)=>{const{appId:s,config:i,cwd:a}=r,{logger:c}=n;if(s===void 0)return p(c,"the project has no id to key the preview record by");const d=await Ft(a,i,e.worker,n);if(d.error!==void 0)return p(c,d.error);for(const v of d.missing)c.warn(`the preview has no ${v}: production's is never copied. Pass --secret ${v} with ${v} set in the environment to give it one.`);let u,f,l,m;try{f=await St(o),u=await Re(o,!0)??"",l=await sr(o);const v=await ar(o,l,s,e.slug);if(v.held!==void 0)return p(c,hr(e.slug,v.held));({token:m}=v)}catch(v){return p(c,`preview "${e.slug}": ${$(v)}`,w.FAILURE)}const h={lost:!1,verify:async()=>{if(!await ur(o,l,s,e.slug,m))throw h.lost=!0,new Error(`the hold on preview "${e.slug}" is no longer this run's, so its record was left as the other run has it`)}},y=dr(o,l,s,e.slug,m,()=>{h.lost=!0}),_=ie(async()=>{y(),await te(o,l,s,e.slug,m)});try{const v=await Xt(r,e,t,n,o,{appId:s,hold:h,namespace:u,secrets:d,subdomain:f});return h.lost?mr(c,e.slug,v):v}finally{y(),await te(o,l,s,e.slug,m),_()}},Qt=(r,e)=>{if(r.appId!==void 0)return{appId:r.appId};const t=e.environment??process.env,n='package.json has a "lunora.appId" that is not a UUID, or no `lunora` object this CLI can add one to';if(e.dryRun===!0)return e.logger.info(" app id: none yet; the first deploy writes one to package.json for you to commit"),{appId:void 0};if(t.CI!==void 0&&t.CI!==""&&t.CI!=="false")return{error:`package.json has no "lunora.appId", and a CI run cannot mint one (its checkout is discarded, so the preview could never be found again to delete). Commit \`"lunora": { "appId": "${V()}" }\` to package.json`};const o=Vr(r.cwd);return o===void 0?{error:n}:(e.logger.warn(`wrote an app id to package.json ("lunora.appId"); commit it so CI finds this project's previews`),{appId:o})},Zt=async(r,e,t)=>{const{cwd:n}=r,{logger:o}=e,s=Pt(e.vars??[]);if(s.error!==void 0)return p(o,s.error);const{variables:i}=s,a=Wr(n);if(a.error!==void 0)return p(o,a.error);const c=Qt(r,e);if(c.error!==void 0)return a.release(),p(o,c.error);let d=()=>{};const u=ee(n,"lunora",Sr);let f;try{f=Q(u)?L(u):void 0}catch(v){return a.release(),p(o,`could not read ${u}: ${$(v)}`)}let l=!1,m;const h={begin:()=>{l=!0},end:()=>{m=Q(u)?L(u):void 0,l=!1}},y=()=>{try{if(d(),f===void 0){const v=Q(u)?L(u):void 0;v!==void 0&&(l||m!==void 0&&v.equals(m))&&F(u,{force:!0})}else f.equals(Q(u)?L(u):Buffer.alloc(0))||ne(u,f)}finally{a.release()}},_=ie(y);try{d=Nr(n),await Lr(n,o,void 0,"cloudflare",void 0,void 0,!0);const{services:v}=Rr(n);if(v.length>0)return p(o,`this app cannot be previewed in isolation yet: it declares lunora.config services (${v.map(A=>A.name).join(", ")}), which a preview would share with production`);const O=$e(r.wranglerPath).parsed??r.config,x=e.env===void 0?{config:O}:ce(O,e.env);if(x.error!==void 0)return p(o,x.error);const{config:S}=x,K=Ze(S,t,e.env);if(K.error!==void 0)return p(o,K.error);const{plan:g}=K,b=B(O,Se(g.worker,g.resources));if(b.length>0)return p(o,`preview "${g.slug}" names collide with production's ${b.join(", ")}; name the preview differently`);if(e.dryRun===!0){const A=new Map(g.resources.map(N=>[_e(N.kind,N.binding),`<${N.name}>`]));return Yt(o,g,qe(S,g,A,i,`https://${g.worker}.<your-subdomain>.workers.dev`)),o.info(` secrets: every one Lunora can mint, fresh${(e.secrets??[]).length>0?`; from the environment: ${(e.secrets??[]).join(", ")}`:""}`),{code:w.SUCCESS,data:{plan:Vt(g)}}}const T=Te(e,S);return T.error!==void 0?p(o,T.error,w.AUTH):await zt({...r,baseline:h,committed:O,config:S,appId:c.appId},g,i,e,T.request)}finally{y(),_()}},qt=async(r,e,t)=>{try{if(e.pending===!0)return await W(r,e.kind,e.name)===void 0?!0:(t.error(`${e.kind} "${e.name}" exists, but the record only claims it (a deploy was interrupted before confirming it), so nothing shows it is this preview's: it was left alone. If it is, delete it by hand, then run \`lunora preview delete\` again`),!1);const n=await Oe(r,e);return n==="changed"?(t.error(`${e.kind} "${e.name}" (${e.id}): the recorded id now names a different resource, so it was left alone`),!1):(n==="present"&&(await fr(r,e),t.success(`deleted ${e.kind} "${e.name}"`)),!0)}catch(n){return t.error(`deleting ${e.kind} "${e.name}" failed (${$(n)}); it stays on record for the next \`lunora preview delete\``),!1}},en=async(r,e,t,n,o,s,i)=>{try{s();const u=await ue(r,t.worker,U);if(u.exists&&u.marker!==M({production:t.production,slug:t.name}))return p(o,`Worker "${t.worker}" was not deployed as preview "${t.name}"; refusing to delete anything`);u.exists&&(await _t(r,t.worker),o.success(`deleted Worker "${t.worker}"`))}catch(u){return p(o,`deleting Worker "${t.worker}" failed (${$(u)}); its resources were left in place`,w.FAILURE)}const a=[];for(const u of t.resources)s(),await qt(r,u,o)||a.push(u);const c={...t,resources:a,updatedAt:new Date().toISOString()};let d;try{d=await Ae(r,n?.resources??[],t),d.length===0&&(await i(),await(a.length===0?nr(r,e,t):j(r,e,c)))}catch(u){return p(o,`preview "${t.name}": ${$(u)}`,w.FAILURE)}return d.length>0?p(o,`${d.join(", ")} exist(s) under this preview's names but not in the record read (a deploy may have just recorded it); the record was left as it is — run \`lunora preview delete ${t.name}\` again. If that keeps failing, the resource was never recorded (a deploy stopped between creating and recording it): delete it by hand, then run delete once more`,w.FAILURE):a.length>0?{code:w.FAILURE,data:{preview:C(c)},error:`${String(a.length)} preview resource(s) could not be deleted`}:{code:w.SUCCESS,data:{deleted:C(t)}}},rn=async(r,e,t)=>{const n=[{config:e}];for(const s of Object.keys(e.env??{})){const i=ce(e,s);i.config!==void 0&&n.push({config:i.config,env:s})}const o=await Promise.all(n.map(async s=>{const{names:i}=be(s.config,t);if(i===void 0)return[];const a=Qe(s.config,i.worker),c=await ue(r,i.worker,U),d=await Ae(r,a,void 0);return[...c.exists&&c.marker===M(i)?[`Worker "${i.worker}" exists`]:[],...d.map(u=>`${u} exists`)]}));return[...new Set(o.flat())]},tn=(r,e)=>{const t=e.sourceEnv===void 0?{config:r}:ce(r,e.sourceEnv);return t.config===void 0?void 0:Ze({...t.config,name:e.production},e.name,e.sourceEnv).plan},nn=async(r,e,t,n)=>{const o=await rn(r,e,t);return o.length===0?void 0:p(n,`no record of preview "${t}" could be read, but ${o.join(" and ")}; nothing was deleted. If a deploy just ran, its record may not have reached this location yet: run delete again. Otherwise delete from a checkout whose package.json has the lunora.appId that deployed it`,w.FAILURE)},on=(r,e)=>{e.info(`dry run: would delete Worker "${r.worker}" and its Durable Objects`);for(const t of r.resources)e.info(` ${t.kind} "${t.name}" (${t.binding})`);return{code:w.SUCCESS,data:{plan:C(r)}}},sn=(r,e,t)=>e.worker===ae(e.production,e.name)&&e.appId===r.appId&&e.name===t,We=async(r,e,{hold:t,names:n,namespace:o,request:s})=>{const{config:i}=r,{logger:a}=e;let c;try{if(c=o===void 0?void 0:await vr(s,o,r.appId,n.production,n.slug,a,e.dryRun!==!0),o===void 0||c===void 0){const l=await nn(s,i,n.slug,a);if(l!==void 0)return l}}catch(l){return p(a,`preview "${n.slug}": ${$(l)}`,w.FAILURE)}if(o===void 0||c===void 0)return p(a,`no preview "${n.slug}" of "${n.production}" is on record; nothing was deleted`,w.NOT_FOUND);const d=Nt(r,c);if(!sn(r,c,n.slug)||d.length>0)return p(a,`the record for preview "${n.slug}" names ${d.length>0?`production's ${d.join(", ")}`:`Worker "${c.worker}", which is not the Worker of this preview`}; refusing to delete anything`);if(r.requireOrigin===!0&&!jt(c,wr(e.environment??process.env,n.slug)))return p(a,Ut(n.slug),w.FAILURE);let u;try{u=await Promise.all(c.resources.map(async l=>Oe(s,l)))}catch(l){return p(a,`preview "${n.slug}": ${$(l)}`,w.FAILURE)}const f=c.resources.filter((l,m)=>u[m]==="changed");return f.length>0?p(a,`the record for preview "${n.slug}" lists ${f.map(l=>`${l.kind} ${l.id}`).join(", ")}, which no longer name(s) the recorded resource; refusing to delete anything`):e.dryRun===!0?on(c,a):en(s,o,c,tn(i,c),a,()=>{ye(t,c.name)},t.verify)},P=async(r,e,t)=>{const{config:n}=r,{logger:o}=e,s=be(n,t);if(s.error!==void 0)return p(o,s.error);const{names:i}=s,a=pr(n,e);if(a.error!==void 0)return p(o,a.error,e.env===void 0?w.AUTH:w.USAGE);const c=Te(e,a.config);if(c.error!==void 0)return p(o,c.error,w.AUTH);const{request:d}=c;let u;try{u=await Re(d,!1)}catch(v){return p(o,`preview "${i.slug}": ${$(v)}`,w.FAILURE)}if(r.appId===void 0||u===void 0||e.dryRun===!0)return We(r,e,{hold:{lost:!1,verify:()=>Promise.resolve()},names:i,namespace:u,request:d});const{appId:f}=r;let l,m;try{l=await sr(d);const v=await ar(d,l,f,i.slug);if(v.held!==void 0)return p(o,hr(i.slug,v.held));({token:m}=v)}catch(v){return p(o,`preview "${i.slug}": ${$(v)}`,w.FAILURE)}const h={lost:!1,verify:async()=>{if(!await ur(d,l,f,i.slug,m))throw h.lost=!0,new Error(`the hold on preview "${i.slug}" is no longer this run's, so its record was left as the other run has it`)}},y=dr(d,l,f,i.slug,m,()=>{h.lost=!0}),_=ie(async()=>{y(),await te(d,l,f,i.slug,m)});try{const v=await We(r,e,{hold:h,names:i,namespace:u,request:d});return h.lost?mr(o,i.slug,v):v}catch(v){return p(o,`preview "${i.slug}": ${$(v)}`,w.FAILURE)}finally{y(),await te(d,l,f,i.slug,m),_()}},an=async(r,e)=>{const{config:t}=r,{logger:n}=e,o=typeof t.name=="string"?t.name:"",s=pr(t,e);if(s.error!==void 0)return p(n,s.error,e.env===void 0?w.AUTH:w.USAGE);const i=Te(e,s.config);if(i.error!==void 0)return p(n,i.error,w.AUTH);let a;try{const c=await Re(i.request,!1),d={records:[],unreadable:[]},u=c===void 0||r.appId===void 0?d:await Le(i.request,c,r.appId),f=c===void 0?d:await Le(i.request,c,o);a=[...u.records,...f.records],f.records.length>0&&n.warn(`${String(f.records.length)} preview(s) are recorded under the Worker name, from before projects had an id: ${f.records.map(l=>l.name).join(", ")}. ${r.appId===void 0?"Add an app id to package.json, then run":"Run"} \`lunora preview delete <name>\` (or deploy it again) to re-key each`);for(const l of[...u.unreadable,...f.unreadable])n.warn(`the registry key "${l}" is not a preview record this CLI wrote; it is not listed, and \`delete\` will refuse it`)}catch(c){return p(n,$(c),w.FAILURE)}a.length===0&&n.info(`no previews of "${o}" are on record`);for(const c of a)n.info(`${c.name} ${c.url??"(not deployed)"} Worker ${c.worker}, ${String(c.resources.length)} resource(s), updated ${c.updatedAt}`);return{code:w.SUCCESS,data:{previews:a.map(c=>C(c))}}},cn=/^[\dA-Za-z][\w-]{0,62}$/u,dn=r=>{const e=ke(r);if(e===void 0)return{error:'its package.json has no readable "lunora.appId"'};const t=He(r);let n;try{n=t===void 0?void 0:$e(t).parsed}catch{n=void 0}const o=n?.name;return n===void 0||typeof o!="string"||!cn.test(o)?{error:"its wrangler config has no readable, valid Worker name"}:{identity:{appId:e,config:n,production:o}}},un=async(r,e,t,n)=>{const o=[...e];if(o.some(i=>i.code===w.SUCCESS)){for(const[i,a]of o.entries())if(a.code!==w.SUCCESS&&a.code!==w.NOT_FOUND){const c=await P(r[i],t,n);o[i]=c.code===w.NOT_FOUND?{code:w.SUCCESS}:c}}const s=o.find(i=>i.code!==w.SUCCESS&&i.code!==w.NOT_FOUND);return s!==void 0?s:o.find(i=>i.code===w.SUCCESS&&i.data!==void 0)??o.find(i=>i.code===w.SUCCESS)??p(t.logger,`no preview "${n}" is on record under this checkout's project or under the pull request head's, whose id or Worker name differs; nothing was deleted`,w.FAILURE)},ln=async(r,e,t,n)=>{const o=dn(n);if(o.error!==void 0){e.logger.warn(`the pull request head's identity could not be read (${o.error}); looking for the preview under this checkout's only`);const l=await P(r,e,t);return l.code===w.NOT_FOUND?p(e.logger,`no preview "${t}" is on record under this checkout's project, and the pull request head's identity could not be read (${o.error}), so it cannot be ruled out under that; nothing was deleted`,w.FAILURE):l}const{appId:s,config:i,production:a}=o.identity,c={...r,alsoProduction:i};if(s===r.appId&&a===r.config.name)return P(c,e,t);const d={...c,appId:s,config:{...r.config,...typeof i.account_id=="string"?{account_id:i.account_id}:{},name:a},requireOrigin:!0},u=[c,d],f=[await P(c,e,t),await P(d,e,t)];return un(u,f,e,t)},fn=async r=>{const{logger:e,subcommand:t}=r;if(!Fe.includes(t))return p(e,`usage: lunora preview <${Fe.join(" | ")}> [name]`);const n=Dt(r);return n.error!==void 0?p(e,n.error):r.headDir!==void 0&&t!=="delete"?p(e,"--head-dir applies to `lunora preview delete` only"):t==="list"?an(n.context,r):r.name===void 0||r.name===""?p(e,`usage: lunora preview ${t??""} <name> — e.g. the pull request number`):t==="deploy"?Zt(n.context,r,r.name):r.headDir===void 0?P(n.context,r,r.name):ln(n.context,r,r.name,r.headDir)},Rn=jr(async({argument:r,cwd:e,format:t,logger:n,options:o})=>{const s=await fn({cwd:e,dryRun:o.dryRun===!0,env:o.env,format:t,headDir:o.headDir,logger:n,name:r[1],secrets:o.secret??[],seed:o.seed===!0,subcommand:r[0],target:o.target,vars:o.var});return{code:s.code,data:s.data,error:s.error}});export{Rn as execute,fn as runPreviewCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{mkdtempSync as p,rmSync as u,existsSync as m,readdirSync as $,statSync as g}from"node:fs";import{tmpdir as S}from"node:os";import{join as y,relative as w}from"node:path";import{d as z}from"../packem_shared/command-BpMv2n0o.mjs";import{EXIT_CODE as B}from"../packem_shared/EXIT_CODE-DQVhSE-1.mjs";import{defaultSpawner as F}from"../packem_shared/createRecordingSpawner-DchLi0gR.mjs";import{execArgsFor as h,detectPackageManager as x}from"@lunora/config/cli";const v=e=>{const t=[],n=o=>{for(const s of $(o)){const r=y(o,s),i=g(r);i.isDirectory()?n(r):i.isFile()&&t.push({path:w(e,r),sizeBytes:i.size})}};return n(e),t},l=e=>e<1024?`${String(e)} B`:e<1024*1024?`${(e/1024).toFixed(1)} KiB`:`${(e/(1024*1024)).toFixed(2)} MiB`,M=e=>{const t=v(e),n=t.toSorted((r,i)=>i.sizeBytes-r.sizeBytes),o=t.reduce((r,i)=>r+i.sizeBytes,0);return{generatedFiles:t.filter(r=>r.path.includes("_generated")),outdir:e,topModules:n.slice(0,10),totalBytes:o,totalFiles:t.length}},O=(e,t)=>{if(t.info(`outdir: ${e.outdir}`),t.info(`total: ${String(e.totalFiles)} files, ${l(e.totalBytes)}`),e.topModules.length>0){t.info(""),t.info("top modules by size:");for(const n of e.topModules)t.info(` ${l(n.sizeBytes).padStart(10," ")} ${n.path}`)}if(e.generatedFiles.length>0){t.info(""),t.info(`_generated/ files: ${String(e.generatedFiles.length)}`);for(const n of e.generatedFiles)t.info(` ${l(n.sizeBytes).padStart(10," ")} ${n.path}`)}},j=async e=>{const t=e.cwd??process.cwd(),n=e.format==="json",{logger:o}=e;let s,r,i=!1;if(e.inspectOnly)s=e.inspectOnly;else{s=p(y(S(),"lunora-analyze-")),i=!0;const a=h(x(t),"wrangler",["deploy","--dry-run","--outdir",s]);r={args:a.args,command:a.command,cwd:t,stdoutToStderr:n},o.info(`analyze: building via ${r.command} ${r.args.join(" ")}`);const d=await(e.spawner??F)(r);if(d.code!==0){const f=`analyze: wrangler dry-run failed (exit ${String(d.code)})`;return o.error(f),u(s,{force:!0,recursive:!0}),{code:d.code,descriptor:r,error:f,report:void 0}}}try{if(!m(s)){const c=`analyze: outdir not found at ${s}`;return o.error(c),{code:B.NOT_FOUND,descriptor:r,error:c,report:void 0}}const a=M(s);return n||O(a,o),{code:0,descriptor:r,report:a}}finally{if(i&&m(s))try{u(s,{force:!0,recursive:!0})}catch{}}},N=z(async({cwd:e,format:t,logger:n})=>{const o=await j({cwd:e,format:t,logger:n});return{code:o.code,data:o.report,error:o.error}});export{N as execute,j as runAnalyzeCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{d as s}from"../packem_shared/command-BpMv2n0o.mjs";import{o as c}from"../packem_shared/open-url-EnKy--w-.mjs";const n="https://lunora.sh/docs",l=r=>{let e=0,t=r.length;for(;e<t&&r[e]==="/";)e+=1;for(;t>e&&r[t-1]==="/";)t-=1;return r.slice(e,t)},i=r=>{if(!r||r.length===0)return n;const e=l(r);return e.length===0?n:`${n}/${e}`},a=async r=>{const e=i(r.section);r.logger.info(`opening ${e}`);try{await c(e,{opener:r.opener})}catch(t){const o=t instanceof Error?t.message:String(t);return r.logger.error(`docs: failed to open URL: ${o}`),{code:1,url:e}}return{code:0,url:e}},m=s(({argument:r,logger:e})=>a({logger:e,section:r[0]}));export{m as execute,a as runDocsCommand};
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
import{d as
|
|
1
|
+
import{existsSync as E,readFileSync as U}from"node:fs";import{join as P}from"node:path";import{DEV_VARS_FILE as m,DEV_VARS_KEY_PATTERN as b,writeDevVariablesFileAtomically as w,removeDevVariableLine as N,upsertDevVariableLine as D,isPlaceholderValue as S,resolveDeployDriver as K,resolveProjectTarget as j,generateSecretValue as z,DEV_VARS_EXAMPLE_FILE as g,parseDevVariableEntries as _,secretSourcesFromBindings as T,inferLunoraBindings as C,resolveSchemaDirectory as F,mintableSecretKeysFor as M,requiredSecrets as O}from"@lunora/config";import{d as Y}from"../packem_shared/command-BpMv2n0o.mjs";import{EXIT_CODE as d}from"../packem_shared/EXIT_CODE-DQVhSE-1.mjs";import{defaultSpawner as q}from"../packem_shared/createRecordingSpawner-DchLi0gR.mjs";import{l as Z}from"../packem_shared/wrangler-secrets-C9siMBwN.mjs";import{detectPackageManager as B,toolchainExecArgs as I}from"@lunora/config/cli";const X=/[\r\n]/u,H=/["\\]/u,G=e=>new Map(_(e).map(s=>[s.key,s])),h=e=>E(e)?U(e,"utf8"):"",L=e=>e.length<=4?"****":`${e.slice(0,4)}${"*".repeat(Math.min(8,e.length-4))}`,p=e=>G(h(e)),$=e=>e.env??(e.prod===!0?"production":void 0),W=e=>{const s=p(e.devVariablesPath),t={file:m,keys:[...s.keys()],subcommand:"list"};if(s.size===0)return e.logger.info(`${m}: (empty)`),{code:0,data:t,descriptors:[]};for(const r of s.values())e.logger.info(`${r.key}=${L(r.value)}`);return{code:0,data:t,descriptors:[]}},J=e=>{const{devVariablesPath:s,logger:t,options:r}=e;if(!r.key)return t.error("env get requires a key. Usage: lunora env get <KEY>"),{code:d.USAGE,descriptors:[]};const o=p(s).get(r.key);return o?(r.format!=="json"&&process.stdout.write(`${o.value}
|
|
2
|
+
`),{code:0,data:{key:r.key,subcommand:"get",value:o.value},descriptors:[]}):(t.error(`env: ${r.key} is not set in ${m}`),{code:d.NOT_FOUND,descriptors:[]})},Q=e=>{const{devVariablesPath:s,logger:t,options:r}=e;if(!r.key)return t.error("env set requires a key. Usage: lunora env set <KEY> <VALUE>"),{code:d.USAGE,descriptors:[]};if(!b.test(r.key))return t.error(`env: invalid key "${r.key}" — must match [A-Za-z_][A-Za-z0-9_]*`),{code:d.USAGE,descriptors:[]};if(r.value===void 0)return t.error("env set requires a value. Usage: lunora env set <KEY> <VALUE>"),{code:d.USAGE,descriptors:[]};if(X.test(r.value))return t.error(`env: value for "${r.key}" contains a newline, which .dev.vars cannot represent`),{code:d.USAGE,descriptors:[]};if(H.test(r.value))return t.error(`env: value for "${r.key}" contains a double-quote or backslash, which .dev.vars cannot round-trip`),{code:d.USAGE,descriptors:[]};const o=h(s);return w(s,D(o,r.key,r.value)),t.success(`env: set ${r.key} (${L(r.value)}) in ${m}`),{code:0,data:{key:r.key,subcommand:"set"},descriptors:[]}},x=e=>{const{devVariablesPath:s,logger:t,options:r}=e;if(!r.key)return t.error("env unset requires a key. Usage: lunora env unset <KEY>"),{code:d.USAGE,descriptors:[]};if(!b.test(r.key))return t.error(`env: invalid key "${r.key}" — must match [A-Za-z_][A-Za-z0-9_]*`),{code:d.USAGE,descriptors:[]};const o=h(s);return G(o).has(r.key)?(w(s,N(o,r.key)),t.success(`env: unset ${r.key} in ${m}`),{code:0,data:{key:r.key,removed:!0,subcommand:"unset"},descriptors:[]}):(t.warn(`env: ${r.key} was not set in ${m}`),{code:0,data:{key:r.key,removed:!1,subcommand:"unset"},descriptors:[]})},ee=async e=>{const{devVariablesPath:s,logger:t,options:r}=e;if(!r.yes)return t.error("env push uploads secrets to Cloudflare. Re-run with --yes to confirm."),{code:d.USAGE,descriptors:[]};const o=p(s);if(o.size===0)return t.warn(`${m}: nothing to push (empty)`),{code:0,data:{pushed:[],subcommand:"push",target:$(r)},descriptors:[]};const n=[...o.values()].filter(a=>S(a.value)).map(a=>a.key);if(n.length>0)return t.error(`env push: refusing to upload placeholder value(s) for ${n.join(", ")} — run \`lunora env doctor\` to review, and \`lunora env generate --set\` (or \`lunora env set <KEY> <VALUE>\`) to fill them, then re-run.`),{code:d.USAGE,descriptors:[]};const c=r.spawner??q,u=r.cwd??process.cwd(),l=B(u),i=[],v=$(r),f=K(j(u)),y=f.toolchain?.secretPut;if(y===void 0)return t.error(`deploy target "${f.id}" has no secret store; cannot push secrets`),{code:d.USAGE,descriptors:[]};for(const a of o.values()){const R=y({environment:v,key:a.key,temporary:r.temporary}),A=I(l,R),V={args:A.args,command:A.command,cwd:u,input:a.value};i.push(V),t.info(`pushing ${a.key} -> wrangler secret${v===void 0?"":` (${v})`}`);const k=await c(V);if(k.code!==0)return t.error(`env push: failed at ${a.key} (exit ${String(k.code)})`),{code:k.code,descriptors:i}}return t.success(`env: pushed ${String(o.size)} secret(s)`),{code:0,data:{pushed:[...o.keys()],subcommand:"push",target:v},descriptors:i}},re=e=>(e.options.secretLister??Z)({cwd:e.cwd,env:$(e.options),temporary:e.options.temporary}),te=async e=>{const{devVariablesPath:s,logger:t}=e,r=await re(e);if(!r.ok)return t.error(`env diff: ${r.error??"failed to list remote secrets"}`),{code:1,descriptors:[]};const o=new Set(p(s).keys()),n=new Set(r.names),c=[...o].filter(i=>!n.has(i)).toSorted((i,v)=>i.localeCompare(v)),u=r.names.filter(i=>!o.has(i)),l=[...o].filter(i=>n.has(i)).toSorted((i,v)=>i.localeCompare(v));for(const i of c)t.info(`local only (run \`lunora env push\`): ${i}`);for(const i of u)t.info(`remote only (in Cloudflare, not ${m}): ${i}`);return t.info(`in both: ${String(l.length)} secret(s)`),c.length===0&&u.length===0&&t.success("env diff: local and remote secret names match"),{code:0,data:{both:l,localOnly:c,remoteOnly:u,subcommand:"diff"},descriptors:[]}},se=e=>{const{cwd:s,devVariablesPath:t,logger:r}=e,o=P(s,g);if(!E(o))return r.info(`env doctor: no ${g} to check against — nothing to validate.`),{code:0,data:{extra:[],missing:[],ok:!0,placeholders:[],subcommand:"doctor"},descriptors:[]};const n=_(U(o,"utf8")).map(a=>a.key),c=p(t);if(!E(t))return r.error(`env doctor: ${m} is missing. Run \`lunora dev\` to scaffold it, or \`lunora env set <KEY> <VALUE>\`.`),r.info(`expected (from ${g}): ${n.join(", ")}`),{code:d.NOT_FOUND,descriptors:[]};const u=n.filter(a=>!c.has(a)),l=[...c.values()].filter(a=>S(a.value)).map(a=>a.key),i=new Set(n),v=[...c.keys()].filter(a=>!i.has(a));for(const a of u)r.error(`missing: ${a} is in ${g} but not ${m}`);for(const a of l)r.error(`unset: ${a} still has a placeholder value`);for(const a of v)r.info(`extra: ${a} is set locally but not listed in ${g}`);const f=u.length===0&&l.length===0,y={extra:v,missing:u,ok:f,placeholders:l,subcommand:"doctor"};return f?(r.success(`env doctor: ${m} looks good (${String(c.size)} var(s)).`),{code:0,data:y,descriptors:[]}):{code:1,data:y,descriptors:[]}},oe=async e=>{let s=[];try{s=T(await C({projectRoot:e.cwd,schemaDir:F(e.cwd)}))}catch{}const t=M(s),r=O(s).map(n=>n.key).filter(n=>t.has(n)),o=[...p(e.devVariablesPath).keys()].filter(n=>t.has(n));return[...new Set([...r,...o])]},ne=(e,s,t)=>{const r=p(e),o=n=>{const c=r.get(n);return c!==void 0&&c.value!==""&&!S(c.value)};return{live:s.filter(n=>o(n.key)).map(n=>n.key),writable:t?s:s.filter(n=>!o(n.key))}},ae=(e,s)=>{const{devVariablesPath:t,logger:r,options:o}=e,{live:n,writable:c}=ne(t,s,o.yes===!0);if(c.length===0)return r.error(`env generate --set: ${n.join(", ")} already hold(s) a live secret and rotating it is irreversible (outstanding signed URLs and bearers stop verifying). Re-run with --yes to rotate anyway.`),{code:d.USAGE,descriptors:[]};n.length>0&&r.warn(`env generate --set: left ${n.join(", ")} untouched (already set) — re-run with --yes to rotate them too`);let u=h(t);for(const l of c)u=D(u,l.key,l.value);return w(t,u),r.success(`env: generated ${String(c.length)} secret(s) into ${m}: ${c.map(l=>l.key).join(", ")}`),{code:0,data:{subcommand:"generate",written:c.map(l=>l.key)},descriptors:[]}},ce=async e=>{const{logger:s,options:t}=e;let r;if(t.key===void 0){if(r=await oe(e),r.length===0)return s.info("env generate: no locally-generatable secrets for this project. Name one explicitly: lunora env generate <KEY>"),{code:0,data:{secrets:[],subcommand:"generate"},descriptors:[]}}else{if(!b.test(t.key))return s.error(`env: invalid key "${t.key}" — must match [A-Za-z_][A-Za-z0-9_]*`),{code:d.USAGE,descriptors:[]};r=[t.key]}const o=r.map(n=>({key:n,value:z()}));if(t.set===!0)return ae(e,o);if(t.format!=="json")for(const n of o)process.stdout.write(`${n.key}=${n.value}
|
|
3
|
+
`);return{code:0,data:{secrets:o,subcommand:"generate"},descriptors:[]}},ie=async e=>{const{options:s}=e;switch(s.subcommand){case"diff":return te(e);case"doctor":return se(e);case"generate":return ce(e);case"get":return J(e);case"list":return W(e);case"push":return ee(e);case"set":return Q(e);case"unset":return x(e);default:return e.logger.error(`env: unknown subcommand "${s.subcommand}"`),{code:d.USAGE,descriptors:[]}}},de=async e=>{const s=e.cwd??process.cwd(),t={cwd:s,devVariablesPath:P(s,m),logger:e.logger,options:e};return ie(t)},ue=new Set(["diff","doctor","generate","get","list","push","set","unset"]),le=e=>typeof e=="string"&&ue.has(e),Ee=Y(({argument:e,cwd:s,format:t,logger:r,options:o})=>{const n=e[0];if(!le(n)){const c=`env: unknown subcommand "${n??""}" — expected list | get | set | unset | push | diff | doctor | generate`;return r.error(c),{code:d.USAGE,error:c}}return de({cwd:s,env:o.env,format:t,key:e[1],logger:r,prod:o.prod===!0,set:o.set===!0,subcommand:n,temporary:o.temporary===!0,value:e[2],yes:o.yes===!0})});export{Ee as execute,de as runEnvCommand};
|
|
@@ -1,3 +1 @@
|
|
|
1
|
-
import{
|
|
2
|
-
`),{code:0,data:{key:r.key,subcommand:"get",value:o.value},descriptors:[]}):(t.error(`env: ${r.key} is not set in ${m}`),{code:d.NOT_FOUND,descriptors:[]})},Q=e=>{const{devVariablesPath:s,logger:t,options:r}=e;if(!r.key)return t.error("env set requires a key. Usage: lunora env set <KEY> <VALUE>"),{code:d.USAGE,descriptors:[]};if(!b.test(r.key))return t.error(`env: invalid key "${r.key}" — must match [A-Za-z_][A-Za-z0-9_]*`),{code:d.USAGE,descriptors:[]};if(r.value===void 0)return t.error("env set requires a value. Usage: lunora env set <KEY> <VALUE>"),{code:d.USAGE,descriptors:[]};if(X.test(r.value))return t.error(`env: value for "${r.key}" contains a newline, which .dev.vars cannot represent`),{code:d.USAGE,descriptors:[]};if(H.test(r.value))return t.error(`env: value for "${r.key}" contains a double-quote or backslash, which .dev.vars cannot round-trip`),{code:d.USAGE,descriptors:[]};const o=h(s);return w(s,D(o,r.key,r.value)),t.success(`env: set ${r.key} (${L(r.value)}) in ${m}`),{code:0,data:{key:r.key,subcommand:"set"},descriptors:[]}},x=e=>{const{devVariablesPath:s,logger:t,options:r}=e;if(!r.key)return t.error("env unset requires a key. Usage: lunora env unset <KEY>"),{code:d.USAGE,descriptors:[]};if(!b.test(r.key))return t.error(`env: invalid key "${r.key}" — must match [A-Za-z_][A-Za-z0-9_]*`),{code:d.USAGE,descriptors:[]};const o=h(s);return G(o).has(r.key)?(w(s,N(o,r.key)),t.success(`env: unset ${r.key} in ${m}`),{code:0,data:{key:r.key,removed:!0,subcommand:"unset"},descriptors:[]}):(t.warn(`env: ${r.key} was not set in ${m}`),{code:0,data:{key:r.key,removed:!1,subcommand:"unset"},descriptors:[]})},ee=async e=>{const{devVariablesPath:s,logger:t,options:r}=e;if(!r.yes)return t.error("env push uploads secrets to Cloudflare. Re-run with --yes to confirm."),{code:d.USAGE,descriptors:[]};const o=p(s);if(o.size===0)return t.warn(`${m}: nothing to push (empty)`),{code:0,data:{pushed:[],subcommand:"push",target:$(r)},descriptors:[]};const n=[...o.values()].filter(a=>S(a.value)).map(a=>a.key);if(n.length>0)return t.error(`env push: refusing to upload placeholder value(s) for ${n.join(", ")} — run \`lunora env doctor\` to review, and \`lunora env generate --set\` (or \`lunora env set <KEY> <VALUE>\`) to fill them, then re-run.`),{code:d.USAGE,descriptors:[]};const c=r.spawner??q,u=r.cwd??process.cwd(),l=B(u),i=[],v=$(r),f=K(j(u)),y=f.toolchain?.secretPut;if(y===void 0)return t.error(`deploy target "${f.id}" has no secret store; cannot push secrets`),{code:d.USAGE,descriptors:[]};for(const a of o.values()){const R=y({environment:v,key:a.key,temporary:r.temporary}),A=I(l,R),V={args:A.args,command:A.command,cwd:u,input:a.value};i.push(V),t.info(`pushing ${a.key} -> wrangler secret${v===void 0?"":` (${v})`}`);const k=await c(V);if(k.code!==0)return t.error(`env push: failed at ${a.key} (exit ${String(k.code)})`),{code:k.code,descriptors:i}}return t.success(`env: pushed ${String(o.size)} secret(s)`),{code:0,data:{pushed:[...o.keys()],subcommand:"push",target:v},descriptors:i}},re=e=>(e.options.secretLister??Z)({cwd:e.cwd,env:$(e.options),temporary:e.options.temporary}),te=async e=>{const{devVariablesPath:s,logger:t}=e,r=await re(e);if(!r.ok)return t.error(`env diff: ${r.error??"failed to list remote secrets"}`),{code:1,descriptors:[]};const o=new Set(p(s).keys()),n=new Set(r.names),c=[...o].filter(i=>!n.has(i)).toSorted((i,v)=>i.localeCompare(v)),u=r.names.filter(i=>!o.has(i)),l=[...o].filter(i=>n.has(i)).toSorted((i,v)=>i.localeCompare(v));for(const i of c)t.info(`local only (run \`lunora env push\`): ${i}`);for(const i of u)t.info(`remote only (in Cloudflare, not ${m}): ${i}`);return t.info(`in both: ${String(l.length)} secret(s)`),c.length===0&&u.length===0&&t.success("env diff: local and remote secret names match"),{code:0,data:{both:l,localOnly:c,remoteOnly:u,subcommand:"diff"},descriptors:[]}},se=e=>{const{cwd:s,devVariablesPath:t,logger:r}=e,o=P(s,g);if(!E(o))return r.info(`env doctor: no ${g} to check against — nothing to validate.`),{code:0,data:{extra:[],missing:[],ok:!0,placeholders:[],subcommand:"doctor"},descriptors:[]};const n=_(U(o,"utf8")).map(a=>a.key),c=p(t);if(!E(t))return r.error(`env doctor: ${m} is missing. Run \`lunora dev\` to scaffold it, or \`lunora env set <KEY> <VALUE>\`.`),r.info(`expected (from ${g}): ${n.join(", ")}`),{code:d.NOT_FOUND,descriptors:[]};const u=n.filter(a=>!c.has(a)),l=[...c.values()].filter(a=>S(a.value)).map(a=>a.key),i=new Set(n),v=[...c.keys()].filter(a=>!i.has(a));for(const a of u)r.error(`missing: ${a} is in ${g} but not ${m}`);for(const a of l)r.error(`unset: ${a} still has a placeholder value`);for(const a of v)r.info(`extra: ${a} is set locally but not listed in ${g}`);const f=u.length===0&&l.length===0,y={extra:v,missing:u,ok:f,placeholders:l,subcommand:"doctor"};return f?(r.success(`env doctor: ${m} looks good (${String(c.size)} var(s)).`),{code:0,data:y,descriptors:[]}):{code:1,data:y,descriptors:[]}},oe=async e=>{let s=[];try{s=T(await C({projectRoot:e.cwd,schemaDir:F(e.cwd)}))}catch{}const t=M(s),r=O(s).map(n=>n.key).filter(n=>t.has(n)),o=[...p(e.devVariablesPath).keys()].filter(n=>t.has(n));return[...new Set([...r,...o])]},ne=(e,s,t)=>{const r=p(e),o=n=>{const c=r.get(n);return c!==void 0&&c.value!==""&&!S(c.value)};return{live:s.filter(n=>o(n.key)).map(n=>n.key),writable:t?s:s.filter(n=>!o(n.key))}},ae=(e,s)=>{const{devVariablesPath:t,logger:r,options:o}=e,{live:n,writable:c}=ne(t,s,o.yes===!0);if(c.length===0)return r.error(`env generate --set: ${n.join(", ")} already hold(s) a live secret and rotating it is irreversible (outstanding signed URLs and bearers stop verifying). Re-run with --yes to rotate anyway.`),{code:d.USAGE,descriptors:[]};n.length>0&&r.warn(`env generate --set: left ${n.join(", ")} untouched (already set) — re-run with --yes to rotate them too`);let u=h(t);for(const l of c)u=D(u,l.key,l.value);return w(t,u),r.success(`env: generated ${String(c.length)} secret(s) into ${m}: ${c.map(l=>l.key).join(", ")}`),{code:0,data:{subcommand:"generate",written:c.map(l=>l.key)},descriptors:[]}},ce=async e=>{const{logger:s,options:t}=e;let r;if(t.key===void 0){if(r=await oe(e),r.length===0)return s.info("env generate: no locally-generatable secrets for this project. Name one explicitly: lunora env generate <KEY>"),{code:0,data:{secrets:[],subcommand:"generate"},descriptors:[]}}else{if(!b.test(t.key))return s.error(`env: invalid key "${t.key}" — must match [A-Za-z_][A-Za-z0-9_]*`),{code:d.USAGE,descriptors:[]};r=[t.key]}const o=r.map(n=>({key:n,value:z()}));if(t.set===!0)return ae(e,o);if(t.format!=="json")for(const n of o)process.stdout.write(`${n.key}=${n.value}
|
|
3
|
-
`);return{code:0,data:{secrets:o,subcommand:"generate"},descriptors:[]}},ie=async e=>{const{options:s}=e;switch(s.subcommand){case"diff":return te(e);case"doctor":return se(e);case"generate":return ce(e);case"get":return J(e);case"list":return W(e);case"push":return ee(e);case"set":return Q(e);case"unset":return x(e);default:return e.logger.error(`env: unknown subcommand "${s.subcommand}"`),{code:d.USAGE,descriptors:[]}}},de=async e=>{const s=e.cwd??process.cwd(),t={cwd:s,devVariablesPath:P(s,m),logger:e.logger,options:e};return ie(t)},ue=new Set(["diff","doctor","generate","get","list","push","set","unset"]),le=e=>typeof e=="string"&&ue.has(e),Ee=Y(({argument:e,cwd:s,format:t,logger:r,options:o})=>{const n=e[0];if(!le(n)){const c=`env: unknown subcommand "${n??""}" — expected list | get | set | unset | push | diff | doctor | generate`;return r.error(c),{code:d.USAGE,error:c}}return de({cwd:s,env:o.env,format:t,key:e[1],logger:r,prod:o.prod===!0,set:o.set===!0,subcommand:n,temporary:o.temporary===!0,value:e[2],yes:o.yes===!0})});export{Ee as execute,de as runEnvCommand};
|
|
1
|
+
import{d}from"../packem_shared/command-BpMv2n0o.mjs";import{a as l}from"../packem_shared/resolve-target-C_ZloTvd.mjs";import{runExportCommand as m}from"../packem_shared/runExportCommand-_PtkJJDT.mjs";const k=d(({argument:o,cwd:e,format:t,logger:u,options:r})=>m({cwd:e,format:t,logger:u,out:o[0]??r.out,prod:r.prod===!0,tables:r.tables,token:r.token,url:l({cwd:e,prod:r.prod===!0,url:r.url})}));export{k as execute};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{d}from"../packem_shared/command-BpMv2n0o.mjs";import{
|
|
1
|
+
import{d as c}from"../packem_shared/command-BpMv2n0o.mjs";import{EXIT_CODE as t}from"../packem_shared/EXIT_CODE-DQVhSE-1.mjs";import{a as i}from"../packem_shared/resolve-target-C_ZloTvd.mjs";import{I as u,p as S,a as l,r as s}from"../packem_shared/import-3Y59Ry9F.mjs";const I=c(({argument:n,cwd:a,format:f,logger:o,options:r})=>{const d=n[0];if(!d){const e="import requires a path. Usage: lunora import <file.ndjson | convex-export-dir> [--table <name>]";return o.error(e),{code:t.USAGE,error:e}}if(r.from!==void 0&&!u.includes(r.from)){const e=`--from ${r.from} is not a known source. Expected one of: ${u.join(", ")}.`;return o.error(e),{code:t.USAGE,error:e}}const m=S(r.mode,"append");if(m===void 0){const e=`--mode ${String(r.mode)} is not a known mode. Expected one of: ${l.join(", ")}.`;return o.error(e),{code:t.USAGE,error:e}}return s({batchSize:r.batchSize,cwd:a,dryRun:r.dryRun===!0,file:d,format:f,from:r.from,logger:o,mode:m,prod:r.prod===!0,scan:r.scan===!0,storageDir:r.storageDir,table:r.table,token:r.token,url:i({cwd:a,prod:r.prod===!0,url:r.url}),verify:r.verify===!0,withStorage:r.withStorage===!0,yes:r.yes===!0})});export{I as execute};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{d as
|
|
1
|
+
import{readFileSync as g,existsSync as b}from"node:fs";import{join as y}from"node:path";import{discoverSchema as h}from"@lunora/codegen";import{readLinkedProject as v,readProjectManifest as $}from"@lunora/config";import{findWranglerFile as p}from"@lunora/config/cloudflare";import{parse as k}from"jsonc-parser";import{Project as w}from"ts-morph";import{w as j,d as m}from"../packem_shared/binding-manifest-file-BZyQFSTF.mjs";import{d as S}from"../packem_shared/command-BpMv2n0o.mjs";import{EXIT_CODE as f}from"../packem_shared/EXIT_CODE-DQVhSE-1.mjs";const d=(n,e)=>{if(n===null||typeof n!="object")return;const i=n[e];return typeof i=="string"&&i.length>0?i:void 0},x=(n,e)=>{if(n===null||typeof n!="object")return[];const i=n[e];return Array.isArray(i)?i:[]},E=(n,e)=>{const{manifest:i}=m(e);return{bindings:(i?.bindings??[]).map(r=>`${r.type}:${r.binding}`),compatibilityDate:d(n,"compatibility_date"),compatibilityFlags:x(n,"compatibility_flags").filter(r=>typeof r=="string"),main:d(n,"main"),name:d(n,"name")}},F=n=>({tables:n.tables.map(e=>{let i="root";return e.shardMode==="global"?i="global":typeof e.shardMode=="object"&&(i=`shardBy(${e.shardMode.field})`),{indexes:e.indexes.length,name:e.name,shard:i}}),vectorIndexes:n.vectorIndexes.length}),P=n=>{const e=$(n);if(e===void 0)return[];const i=["dependencies","devDependencies","peerDependencies","optionalDependencies"],r=new Map;for(const s of i){const a=e[s];if(!(a===null||typeof a!="object"))for(const[t,o]of Object.entries(a))t.startsWith("@lunora/")&&typeof o=="string"&&!r.has(t)&&r.set(t,o)}return[...r.entries()].toSorted(([s],[a])=>s.localeCompare(a)).map(([s,a])=>({name:s,version:a}))},M=n=>{const e=P(n),i=p(n);let r;if(i)try{r=E(k(g(i,"utf8")),n)}catch{r=void 0}const s=y(n,"lunora","schema.ts");let a,t;if(b(s))try{const o=new w({skipAddingFilesFromTsConfig:!0,useInMemoryFileSystem:!1});a=F(h(o,s))}catch(o){t=o instanceof Error?o.message:String(o)}return{link:v(n),lunoraPackages:e,projectRoot:n,schema:a,schemaError:t,wrangler:r,wranglerPath:i}},I=(n,e)=>{if(e.info(`project: ${n.projectRoot}`),e.info(""),e.info("@lunora/* packages:"),n.lunoraPackages.length===0)e.info(" (none found in package.json)");else for(const i of n.lunoraPackages)e.info(` ${i.name}@${i.version}`);if(e.info(""),n.wrangler?(e.info(`wrangler: ${n.wranglerPath??""}`),e.info(` name: ${n.wrangler.name??"<unset>"}`),e.info(` main: ${n.wrangler.main??"<unset>"}`),e.info(` compatibility_date: ${n.wrangler.compatibilityDate??"<unset>"}`),e.info(` compatibility_flags: ${n.wrangler.compatibilityFlags.join(", ")||"<none>"}`),e.info(` bindings: ${n.wrangler.bindings.join(", ")||"<none>"}`)):e.info("wrangler: (not found)"),e.info(""),n.link?(e.info(`link: ${n.link.workerName??"(unnamed)"} -> ${n.link.workerUrl??"<no url>"}`),n.link.env!==void 0&&e.info(` env: ${n.link.env}`)):e.info("link: (not linked — run `lunora link --url <https://your-worker>`)"),e.info(""),n.schemaError!==void 0)e.warn(`schema: parse error — ${n.schemaError}`);else if(n.schema){e.info(`schema: ${String(n.schema.tables.length)} table(s), ${String(n.schema.vectorIndexes)} vector index(es)`);for(const i of n.schema.tables)e.info(` ${i.name} [${i.shard}, ${String(i.indexes)} index(es)]`)}else e.info("schema: (no lunora/schema.ts)")},A=n=>{const{cwd:e,json:i,logger:r,out:s}=n;if(s!==void 0){const{error:o}=j({destination:s,logger:r,projectRoot:e});return o!==void 0?(r.error(o),{code:f.USAGE,error:o}):{code:0}}const{error:a,manifest:t}=m(e);if(t===void 0){const o=a??"could not derive the binding manifest";return r.error(o),{code:f.USAGE,error:o}}if(i)return{code:0,data:t};if(t.bindings.length===0)r.info("No bindings declared.");else{const o=Math.max(...t.bindings.map(c=>c.type.length));r.info(`${String(t.bindings.length)} binding(s):`);for(const c of t.bindings){const l=[c.resource,c.className,c.resourceId].filter(u=>u!==void 0).join(" ");r.info(` ${c.type.padEnd(o)} ${c.binding}${l===""?"":` ${l}`}`)}}return t.crons.length>0&&r.info(`${String(t.crons.length)} cron trigger(s): ${t.crons.join(", ")}`),t.vars.length>0&&r.info(`${String(t.vars.length)} var(s): ${t.vars.join(", ")}`),t.unknown.length>0&&r.warn(`not modelled by the manifest: ${t.unknown.join(", ")} — anything they bind must be provisioned by hand.`),{code:0,data:t}},D=n=>{const e=n.cwd??process.cwd(),i=n.format==="json",{logger:r}=n;if(n.bindings===!0)return A({cwd:e,json:i,logger:r,out:n.out});if(n.out!==void 0){const a="--out only applies with --bindings; the full info snapshot prints to stdout under --format json.";return r.error(a),{code:f.USAGE,error:a}}const s=M(e);return i||I(s,r),{code:0,data:s,snapshot:s}},H=S(({cwd:n,format:e,logger:i,options:r})=>D({bindings:r.bindings===!0,cwd:n,format:e,logger:i,out:r.out}));export{M as collectInfo,H as execute,D as runInfoCommand};
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{r as v}from"../packem_shared/admin-token-VdUnvnKW.mjs";import{r as M,a as y}from"../packem_shared/admin-url-DJB8P6Jh.mjs";import{d as x}from"../packem_shared/command-BpMv2n0o.mjs";import{EXIT_CODE as u,exitCodeForStatus as T}from"../packem_shared/EXIT_CODE-DQVhSE-1.mjs";import{a as I}from"../packem_shared/resolve-target-C_ZloTvd.mjs";const O="__lunora_admin__:getFunctionStats",_=10,f=(r,o)=>({calls:r.calls,conflicts:r.conflicts??0,errors:r.errors,lastErrorMessage:r.lastErrorMessage,maxDurationMs:r.maxDurationMs,meanDurationMs:r.calls===0?0:r.totalDurationMs/r.calls,path:r.path,rate:o}),k=(r,o)=>{const t=r.filter(e=>(e.conflicts??0)>0).map(e=>f(e,e.calls===0?0:(e.conflicts??0)/e.calls)).toSorted((e,s)=>s.rate-e.rate||s.conflicts-e.conflicts).slice(0,o),n=r.filter(e=>e.errors>0).map(e=>f(e,e.calls===0?0:e.errors/e.calls)).toSorted((e,s)=>s.rate-e.rate||s.errors-e.errors).slice(0,o),a=r.map(e=>f(e,0)).toSorted((e,s)=>s.maxDurationMs-e.maxDurationMs).slice(0,o);return{errorHotspots:n,latencyOutliers:a,totalFunctions:r.length,writeContention:t}},p=r=>`${(r*100).toFixed(1)}%`,$=r=>r<1e3?`${Math.round(r).toString()}ms`:`${(r/1e3).toFixed(2)}s`,m=(r,o,t,n)=>[r,...o.length===0?[` ${t}`]:o.map(a=>` ${n(a)}`)],E=r=>{const o=t=>t.lastErrorMessage?` — ${t.lastErrorMessage}`:"";return[`Insights over ${r.totalFunctions.toString()} function${r.totalFunctions===1?"":"s"}`,"",...m("Write-conflict hot-spots (OCC contention — candidates for sharding):",r.writeContention,"none — no write conflicts observed",t=>`${t.path} ${t.conflicts.toString()}/${t.calls.toString()} calls (${p(t.rate)})`),"",...m("Error hot-spots:",r.errorHotspots,"none — no errors observed",t=>`${t.path} ${t.errors.toString()}/${t.calls.toString()} calls (${p(t.rate)})${o(t)}`),"",...m("Latency outliers (slowest single call):",r.latencyOutliers,"none — no functions have run",t=>`${t.path} max ${$(t.maxDurationMs)}, mean ${$(t.meanDurationMs)} over ${t.calls.toString()} calls`)].join(`
|
|
2
|
+
`)},i=(r,o,t)=>(r.error(o),{code:t,error:o}),F=r=>r===void 0||!Number.isFinite(r)||r<=0?_:Math.floor(r),A=async r=>{const{logger:o}=r;if(r.prod&&r.url===void 0)return i(o,"--prod requires an explicit --url (refusing to report from the implicit localhost worker)",u.USAGE);const t=M(r.url,o,r.cwd);if(t===void 0)return{code:u.USAGE,error:"could not resolve a usable worker URL"};const{token:n}=v({cwd:r.cwd??process.cwd(),token:r.token,url:t});if(!n)return i(o,"admin token required — pass --token, set LUNORA_ADMIN_TOKEN, or add it to .dev.vars (local targets only)",u.AUTH);const a=`${t}/_lunora/rpc`,e=y,s={args:{},functionPath:O};r.shard!==void 0&&(s.shardKey=r.shard),o.info(`POST ${a} -> insights`);const l=await e(a,{body:JSON.stringify(s),headers:{authorization:`Bearer ${n}`,"content-type":"application/json"},method:"POST"}),c=await l.text();if(!l.ok)return i(o,`insights failed: HTTP ${String(l.status)}: ${c}`,T(l.status));let d;try{d=JSON.parse(c)}catch{return i(o,`insights failed: worker returned non-JSON: ${c}`,1)}const S=d.result??d,{functions:h}=S;if(!Array.isArray(h))return i(o,"insights failed: response carried no `functions` array",1);const g=k(h,F(r.limit));return r.format!=="json"&&o.info(E(g)),{code:0,report:g}},L=x(async({cwd:r,format:o,logger:t,options:n})=>{const a=n.limit===void 0?void 0:Number.parseInt(n.limit,10),e=await A({cwd:r,fetchImpl:void 0,format:o,limit:a,logger:t,prod:n.prod,shard:n.shard,token:n.token,url:I({cwd:r,prod:n.prod===!0,url:n.url})});return{code:e.code,data:e.report,error:e.error}});export{k as buildInsightsReport,L as execute,E as formatInsightsReport,A as runInsightsCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LunoraError as h}from"@lunora/errors";import{r as S,d as b}from"../packem_shared/admin-token-VdUnvnKW.mjs";import{r as _,b as N,a as U}from"../packem_shared/admin-url-DJB8P6Jh.mjs";import{d as P}from"../packem_shared/command-BpMv2n0o.mjs";import{EXIT_CODE as i,exitCodeForStatus as E}from"../packem_shared/EXIT_CODE-DQVhSE-1.mjs";import{
|
|
1
|
+
import{LunoraError as h}from"@lunora/errors";import{r as S,d as b}from"../packem_shared/admin-token-VdUnvnKW.mjs";import{r as _,b as N,a as U}from"../packem_shared/admin-url-DJB8P6Jh.mjs";import{d as P}from"../packem_shared/command-BpMv2n0o.mjs";import{EXIT_CODE as i,exitCodeForStatus as E}from"../packem_shared/EXIT_CODE-DQVhSE-1.mjs";import{r as I}from"../packem_shared/resolve-target-C_ZloTvd.mjs";const y=(r,t="--args")=>{if(r===void 0||r.length===0)return{};let e;try{e=JSON.parse(r)}catch(a){const s=a instanceof Error?a.message:String(a);throw new h("INTERNAL",`failed to parse ${t} as JSON: ${s}`,{cause:a})}if(typeof e!="object"||e===null||Array.isArray(e))throw new h("INTERNAL",`${t} must be a JSON object (e.g. '{"id":1}'), got ${Array.isArray(e)?"an array":typeof e}`);return e},O=/\/$/u,$="__lunora_admin__:runAs",T="__lunora_admin__:",k=async(r,t)=>{const e=await r.text();let a;try{a=JSON.parse(e)}catch{a=e}return t.info(JSON.stringify(a,void 0,2)),a},p=(r,t)=>{const e=t.body?.error?.code;t.runAs||t.status!==403||e!=="FORBIDDEN_SHARD"||r.info("this app authorizes per shard, so an anonymous call is denied — retry with --as <userId> to run as an authenticated user")},R=({args:r,claims:t,options:e,runAs:a})=>{const s=a?{args:{args:r,functionPath:e.functionPath,identity:t,userId:e.as},functionPath:$}:{args:r,functionPath:e.functionPath};return e.shard!==void 0&&(s.shardKey=e.shard),s},w=({cwd:r,logger:t,needsBearer:e,url:a})=>e?_(a,t,r):(I({cwd:r,url:a})??N(r)).replace(O,""),D=({args:r,claims:t},e)=>{try{return{args:y(r),claims:t===void 0?void 0:y(t,"--claims")}}catch(a){e.error(a instanceof Error?a.message:String(a));return}},J=async r=>{const t=r.cwd??process.cwd(),{logger:e}=r,a=r.as!==void 0&&r.as!=="";if(r.claims!==void 0&&!a){const d="--claims requires --as <userId> — extra identity claims only travel inside the admin-gated `runAs` dispatch.";return e.error(d),{body:void 0,code:i.USAGE,error:d,requestUrl:r.url??""}}const s=a||r.functionPath.startsWith(T),n=w({cwd:t,logger:e,needsBearer:s,url:r.url});if(n===void 0)return{body:void 0,code:i.USAGE,error:"could not resolve a usable worker URL",requestUrl:r.url??""};const o=`${n}/_lunora/rpc`,u=r.fetchImpl??U,m=D(r,e);if(m===void 0)return{body:void 0,code:i.USAGE,error:"could not parse --args / --claims as JSON",requestUrl:o};const{source:g,token:l}=s?S({cwd:t,token:r.token,url:n}):{};if(s&&l===void 0){const d="admin token required — pass --token, set LUNORA_ADMIN_TOKEN, or add it to .dev.vars (local targets only)";return e.error(d),{body:void 0,code:i.AUTH,error:d,requestUrl:o}}l!==void 0&&e.debug?.(`admin bearer from ${b(g)}`);const v={"content-type":"application/json",...l===void 0?{}:{authorization:`Bearer ${l}`}},A=R({args:m.args,claims:m.claims,options:r,runAs:a});e.info(`POST ${o} -> ${r.functionPath}${a?` (as ${r.as??""})`:""}`);const c=await u(o,{body:JSON.stringify(A),headers:v,method:"POST"}),f=await k(c,e);return p(e,{body:f,runAs:a,status:c.status}),{body:f,code:c.ok?0:E(c.status),error:c.ok?void 0:`${r.functionPath} failed: HTTP ${String(c.status)}`,requestUrl:o}},j=P(async({argument:r,cwd:t,format:e,logger:a,options:s})=>{const n=r[0];if(!n){const u="missing function path. Usage: lunora run <functionPath> [--args <json>]";return a.error(u),{code:i.USAGE,error:u}}const o=await J({args:s.args,as:s.as,claims:s.claims,cwd:t,format:e,functionPath:n,logger:a,shard:s.shard,token:s.token,url:s.url});return{code:o.code,data:{functionPath:n,requestUrl:o.requestUrl,result:o.body},error:o.error}});export{j as execute,k as readAndLogBody,J as runRpcCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readLinkedProject as n}from"@lunora/config";const v=({cwd:e,env:i,url:o})=>{if(o!==void 0&&o!=="")return o;const r=n(e);if(!(r===void 0||r.env!==i))return r.workerUrl},d=({cwd:e,prod:i,url:o})=>{if(o!==void 0&&o!=="")return o;if(!i)return;const r=n(e);if(!(r===void 0||r.env!==void 0&&r.env!=="production"))return r.workerUrl};export{
|
|
1
|
+
import{readLinkedProject as n}from"@lunora/config";const v=({cwd:e,env:i,url:o})=>{if(o!==void 0&&o!=="")return o;const r=n(e);if(!(r===void 0||r.env!==i))return r.workerUrl},d=({cwd:e,prod:i,url:o})=>{if(o!==void 0&&o!=="")return o;if(!i)return;const r=n(e);if(!(r===void 0||r.env!==void 0&&r.env!=="production"))return r.workerUrl};export{d as a,v as r};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./admin-token-VdUnvnKW.mjs";import"./admin-url-DJB8P6Jh.mjs";import"./platform-diagnostics-Cn2g6Jh-.mjs";import"./api-spec-lYyq4vL6.mjs";import{f as D,b as x,r as y,a as P,s as b}from"../packem_chunks/handler.mjs";import"./vectorize-metadata-DK61NmVx.mjs";import"./binding-manifest-file-BZyQFSTF.mjs";import"./command-BpMv2n0o.mjs";import"./deploy-target-resolve-BTCW1Iv4.mjs";import"./EXIT_CODE-DQVhSE-1.mjs";import"./resolve-target-
|
|
1
|
+
import"./admin-token-VdUnvnKW.mjs";import"./admin-url-DJB8P6Jh.mjs";import"./platform-diagnostics-Cn2g6Jh-.mjs";import"./api-spec-lYyq4vL6.mjs";import{f as D,b as x,r as y,a as P,s as b}from"../packem_chunks/handler.mjs";import"./vectorize-metadata-DK61NmVx.mjs";import"./binding-manifest-file-BZyQFSTF.mjs";import"./command-BpMv2n0o.mjs";import"./deploy-target-resolve-BTCW1Iv4.mjs";import"./EXIT_CODE-DQVhSE-1.mjs";import"./resolve-target-C_ZloTvd.mjs";import"./health-probe-By9T5x2y.mjs";import"./createRecordingSpawner-DchLi0gR.mjs";export{D as execute,x as provisionBindings,y as runDeployCommand,P as runPreDeployPipeline,b as smokeLoadDeferredModules};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{existsSync as R,readdirSync as E,readFileSync as O}from"node:fs";import{join as v}from"@visulima/path";import{r as A,t as I}from"./admin-token-VdUnvnKW.mjs";import{r as T,a as k}from"./admin-url-DJB8P6Jh.mjs";import{EXIT_CODE as u,exitCodeForStatus as f}from"./EXIT_CODE-DQVhSE-1.mjs";const w="/_lunora/admin/sql-migrations",F=new Set(["applied","baselined","ran"]),N=new Set(["applied","baselined","changed","pending","ran"]),q=e=>{const r=v(e,"lunora","migrations");return R(r)?E(r,{withFileTypes:!0}).filter(t=>t.isFile()&&t.name.endsWith(".sql")).map(t=>t.name).toSorted((t,n)=>+(t>n)-+(t<n)).map(t=>({name:t,sql:O(v(r,t),"utf8")})):[]},L=(e,r,t)=>{const n=r.skipped.length>0?`, ${String(r.skipped.length)} already in place`:"";if(e.info(`${r.state.padEnd(9)} ${r.name} (${String(r.statements.length)} statement(s)${n})`),t)for(const a of r.statements)e.info(` ${a.split(`
|
|
2
|
+
`).join(`
|
|
3
|
+
`)}`);(r.rootOnly?.length??0)>0&&r.state==="pending"&&e.warn(`${r.name} runs statements a migrations-scoped token may not run; it needs the root admin token.`),r.destructive.length>0&&r.state==="pending"&&e.warn(`${r.name} drops, renames, truncates or replaces data. It runs only with --allow-destructive.`)},D=async e=>{const r=await e.text();try{return JSON.parse(r)}catch{return r}},m=e=>{const r=e?.error?.code;return typeof r=="string"?r:void 0},_=(e,r)=>e?"plan":r?"baseline":"apply",Q=(e,r,t)=>{const n=T(e.url,e.logger,e.cwd);if(n===void 0)return{code:u.USAGE,files:[]};const{token:a}=A({cwd:r,token:e.token,url:n});if(!a){const o="admin token required — pass --token, set LUNORA_ADMIN_TOKEN, or add it to .dev.vars (local targets only)";return e.logger.error(o),{code:u.AUTH,error:o,files:[]}}if(!t&&I({prod:e.prod,url:n})&&!e.yes){const o=`migrate apply runs the SQL migrations against ${n}, which is not local. Re-run with --yes to confirm.`;return e.logger.error(o),{code:u.USAGE,error:o,files:[]}}return{baseUrl:n,token:a}},g=e=>Array.isArray(e)&&e.every(r=>typeof r=="string"),M=e=>{if(typeof e!="object"||e===null)return!1;const r=e;return typeof r.name=="string"&&typeof r.state=="string"&&N.has(r.state)&&g(r.destructive)&&g(r.skipped)&&g(r.statements)},C=e=>{const r=e?.refused;return Array.isArray(r)&&r.every(t=>typeof t=="string")?r:[]},x=(e,r)=>{const t=e?.files;if(!Array.isArray(t)||!t.every(M))return;const n=t.map(l=>l.name),a=new Set(n),o=r.map(l=>l.name);return a.size===n.length&&n.length===o.length&&o.every(l=>a.has(l))?t:void 0},G=(e,r)=>{const t=r===void 0?e.length-1:e.findIndex(n=>n.name===r);return new Set(e.slice(0,t+1).map(n=>n.name))},i=(e,r,t)=>(e.error(r),{code:t,error:r,files:[]}),P=(e,r)=>{const{baselineThrough:t}=e;if(e.baseline===!0&&t!==void 0)return i(e.logger,"pass either --baseline or --baseline-through, not both",u.USAGE);if(t!==void 0&&!r.some(n=>n.name===t))return i(e.logger,`--baseline-through ${t} names no file under lunora/migrations/`,u.USAGE)},j=(e,r,t,n)=>{if(e.status===404)return i(n,`the worker at ${t.baseUrl} has no ${w} route, so it predates \`migrate apply\`. Deploy a version of lunora that includes it, then re-run.`,u.NOT_FOUND);if(e.status===413)return i(n,`${t.baseUrl} refused the request: the SQL files in lunora/migrations/ together exceed the 8 MiB one request can carry. Split the files across deploys.`,f(e.status));if(e.status===400&&m(r)==="SQL_MIGRATIONS_NOT_CONFIGURED")return n.warn(`${t.baseUrl} has no D1 global plane, so the SQL files in lunora/migrations/ do not run there. Skipping them.`),{code:0,files:[]};if(m(r)==="MISCONFIGURED")return i(n,`${t.baseUrl} has a D1 binding but its worker options have no applySqlMigrations, so the SQL files in lunora/migrations/ cannot run. Pass applySqlMigrations built from applySqlMigrationFiles in @lunora/d1, then redeploy.`,f(e.status));if(m(r)==="ADMIN_SCOPE_REQUIRED")return i(n,`${t.baseUrl} refused migrate apply: this token has no migrations scope. Mint one with \`lunora admin-token create <name> --scope migrations\` and pass it as --token.`,f(e.status));if(m(r)==="MIGRATION_ROOT_REQUIRED")return i(n,`${t.baseUrl} refused migrate apply: a file runs statements that a migrations-scoped token may not run (a DROP, a row write, a trigger or a UNIQUE index). Run it with the root admin token; the dry run lists the statements.`,f(e.status));if(!e.ok)return n.info(JSON.stringify(r,void 0,2)),i(n,`migrate apply was refused by ${t.baseUrl} (HTTP ${String(e.status)}) — see the response above`,f(e.status))},B=(e,r,t)=>{if(t.dryRun){const s=e.filter(l=>l.state==="changed").map(l=>l.name);return s.length>0?i(t.logger,`${String(s.length)} SQL file(s) changed after they were applied: ${s.join(", ")}`,u.CONFLICT):t.refused.length>0?i(t.logger,`${String(t.refused.length)} SQL file(s) would be refused when applied: ${t.refused.join(", ")}. See the statements listed above.`,u.CONFLICT):void 0}const n=G(r,t.baselineThrough),a=e.filter(s=>n.has(s.name)&&!F.has(s.state)).map(s=>s.name);if(a.length===0)return;const o=a.length===1?"was":"were";return i(t.logger,`${a.join(", ")} ${o} not recorded by ${t.baseUrl}, so the run did not finish`,u.FAILURE)},V=async e=>{const r=e.cwd??process.cwd(),t=e.dryRun===!0,n=e.baseline===!0,{baselineThrough:a}=e,o=n||a!==void 0;if(e.prod&&e.url===void 0)return i(e.logger,"--prod requires an explicit --url (refusing to migrate the implicit localhost worker)",u.USAGE);const s=q(r),l=P(e,s);if(l!==void 0)return l;if(s.length===0)return e.logger.info("no SQL migration files under lunora/migrations/ — nothing to apply"),{code:0,files:[]};const d=Q(e,r,t);if("code"in d)return d;const p=`${d.baseUrl}${w}`,$=e.fetchImpl??k;e.logger.info(`POST ${p} -> ${_(t,o)} ${String(s.length)} SQL file(s)`);const y=await $(p,{body:JSON.stringify({baseline:n,baselineThrough:a,confirmDestructive:e.allowDestructive===!0,dryRun:t,files:s}),headers:{authorization:`Bearer ${d.token}`,"content-type":"application/json"},method:"POST"}),h=await D(y),S=j(y,h,d,e.logger);if(S!==void 0)return S;const c=x(h,s);if(c===void 0)return i(e.logger,`${d.baseUrl} answered ${t?"the plan":"the apply"} without a valid result for every SQL file — it may not support \`migrate apply\`, or a proxy answered for it`,u.FAILURE);for(const U of c)L(e.logger,U,t);const b=B(c,s,{baselineThrough:a,baseUrl:d.baseUrl,dryRun:t,logger:e.logger,refused:C(h)});return b!==void 0?{...b,files:c}:{code:0,files:c}};export{V as r};
|