@lunora/cli
Version:
The Lunora CLI: init, dev, deploy, codegen, migrate, seed, doctor, insights, logs, registry, and the rest of the project commands
8 lines (7 loc) • 25.3 kB
JavaScript
import{join as z,dirname as J}from"node:path";import{discoverMigrations as X,readServiceBindings as Q,runCodegen as Z}from"@lunora/codegen";import{readLinkedProject as I,execArgsFor as L,detectPackageManager as b,discoverContainerInfo as U,COMPOSED_WORKER_ENTRY as P,writeLinkedProject as ee,discoverSchemaInfo as re,DEV_VARS_FILE as w,parseDevVariableEntries as O,isMintableSecretKey as x,packageNamesFromBindings as te,inferLunoraBindings as F,resolveSchemaDirectory as W,requiredSecrets as ne,generateSecretValue as ae,toolchainExecArgs as R,upsertDevVariableLine as oe,writeDevVariablesFileAtomically as ie,resolveDeployDriver as A,planToolchainInvocation as M,runPostCodegenHook as se}from"@lunora/config";import{findWranglerFile as B,readWranglerJsonc as ce,mergeWranglerEnvironment as le,warnCloudflareCliConfigOnce as de,validateWranglerProject as ue,reconcileWranglerBindings as ge,reconcileWranglerCompatibilityDate as me,reconcileWranglerCrons as fe,describePreservedCrons as he}from"@lunora/config/cloudflare";import{Spinner as ve}from"@visulima/spinner";import{r as pe,a as ye,e as we}from"../packem_shared/platform-diagnostics-Cn2g6Jh-.mjs";import{p as ke,a as be}from"../packem_shared/deploy-target-DCWwiuAe.mjs";import{w as $e}from"../packem_shared/binding-manifest-file-CQ1eQYy1.mjs";import{d as Se}from"../packem_shared/command-CAXl2A1S.mjs";import{r as C}from"../packem_shared/wrangler-name-Dsk5K1f-.mjs";import{EXIT_CODE as h}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";import{containerBuildTag as De}from"@lunora/container";import{a as Ee,i as Re}from"../packem_shared/docker-DpVxvYpL.mjs";import{defaultSpawner as $}from"../packem_shared/createRecordingSpawner-CYhdPBD5.mjs";import{r as V}from"../packem_shared/resolve-target-C_ZloTvd.mjs";import{p as Ae,H as Ce,a as je,r as Pe}from"../packem_shared/health-probe-Bw9JFfIy.mjs";import{existsSync as p,readFileSync as k,writeFileSync as H}from"node:fs";import{join as y}from"@visulima/path";import{Project as xe}from"ts-morph";import{m as Te,e as _e,i as Ne}from"../packem_shared/secret-key-DsMeUoEg.mjs";import{runMigrateDataCommand as Ie}from"../packem_shared/runMigrateGenerateCommand-Ii_WMZEA.mjs";import{c as Le}from"../packem_shared/tui-prompts-B3YwUhGw.mjs";import{l as Ue}from"../packem_shared/wrangler-secrets-Bqi6kLuq.mjs";const Oe=e=>{const{cwd:r,env:a,logger:t,mintedSecretsFile:n}=e;try{const o=I(r),i=o?.workerName??C(r),s=e.url??o?.workerUrl;t.success("deploy complete"),t.info(` worker: ${i??"(see wrangler output above)"}`),a!==void 0&&t.info(` env: ${a}`),s===void 0?t.info(" url: run `lunora link --url <https://your-worker>` to record it"):t.info(` url: ${s}`),n!==void 0&&t.info(` secrets: generated value(s) recorded in ${n}`),e.logsAvailable&&t.info(" logs: lunora logs")}catch{}},Fe=async e=>{if(e.targets.length===0)return{builtTags:[],code:0};if(!(e.railpackAvailable??Ee)()){const t="deploy blocked: a container uses `image: { build }` (Railpack), but Railpack isn't ready. Install the `railpack` CLI and start a BuildKit instance, e.g. `docker run --rm --privileged -d --name buildkit moby/buildkit` then `export BUILDKIT_HOST=docker-container://buildkit`. Alternatively switch the container's `image` to a Dockerfile path or a pre-built registry reference.";return e.logger.error(t),{builtTags:[],code:1,error:t}}const r=e.spawner??$,a=[];for(const t of e.targets){const n=De(t.exportName),o={args:["build",t.buildDir,"--name",n],command:"railpack",cwd:e.cwd},i=L(b(e.cwd),"wrangler",["containers","push",n]),s={args:i.args,command:i.command,cwd:e.cwd};e.logger.info(`railpack: building "${t.exportName}" → ${n} from ${t.buildDir}`);const c=await r(o);if(c.code!==0){const l=`railpack build failed for container "${t.exportName}" (${t.buildDir})`;return e.logger.error(l),{builtTags:a,code:c.code,error:l}}e.logger.info(`railpack: pushing ${n} to the Cloudflare Registry`);const d=await r(s);if(d.code!==0){const l=`wrangler containers push failed for "${t.exportName}" (${n})`;return e.logger.error(l),{builtTags:a,code:d.code,error:l}}a.push(n)}return{builtTags:a,code:0}},We=e=>{const a=[B(e),z(e,"package.json")].filter(t=>t!==void 0&&p(t)).map(t=>[t,k(t,"utf8")]);return()=>{for(const[t,n]of a)k(t,"utf8")!==n&&H(t,n,"utf8")}},G="<replace-with-d1-create-id>",T=e=>Array.isArray(e)?e.filter(r=>r!=null):void 0,j=(e,r)=>{const a=B(e);if(!a)return;const{parsed:t}=ce(a);if(t===void 0)return;const{error:n,merged:o}=le(t,r),i=n===void 0?o:t,{containers:s,d1_databases:c,vars:d}=i;return{containers:T(s),d1_databases:T(c),vars:typeof d=="object"&&d!==null&&!Array.isArray(d)?d:void 0}},Me=/^127(?:\.\d{1,3}){3}$/,Be=e=>{try{const{hostname:r}=new URL(e);return r==="localhost"||Me.test(r)||r==="::1"||r==="[::1]"}catch{return!1}},Ve=e=>e.startsWith("./")||e.startsWith("../")||e.startsWith("/")||e.includes("Dockerfile"),He=(e,r,a,t="deploy",n)=>{const o=(j(e,n)?.containers??[]).filter(s=>typeof s?.image=="string"&&Ve(s.image));if(o.length===0||a())return;const i=`${t} blocked: wrangler.jsonc declares ${String(o.length)} container(s) built from a local Dockerfile, but no Docker-compatible engine is available. Start Docker (or Colima), or point the container's \`image\` at a pre-built registry reference. Note: container images must target linux/amd64.`;return r.error(i),i},K=e=>p(y(e,P))?P:void 0,Ge=(e,r,a="deploy")=>{for(const t of U(e,"lunora").containers){const n=t.schedulingPolicy===void 0?t.image:void 0,i=[...n?.kind==="dockerfile"?[{field:"image",path:n.dockerfilePath}]:[],...Object.entries(t.schedulingPolicy==="durable_object"?t.images??{}:{}).flatMap(([s,c])=>c.kind==="dockerfile"?[{field:`images["${s}"]`,path:c.dockerfilePath}]:[])].find(s=>!p(y(e,s.path)));if(i!==void 0){const s=`${a} blocked: container "${t.exportName}" references a Dockerfile at "${i.path}" that does not exist. Create it or fix the \`${i.field}\` path in lunora/containers.ts.`;return r.error(s),s}if(n?.kind==="build"&&!p(y(e,n.buildDir))){const s=`${a} blocked: container "${t.exportName}" references a Railpack build directory "${n.buildDir}" that does not exist. Create it or fix the \`image.build\` path in lunora/containers.ts.`;return r.error(s),s}}},Ke=(e,r)=>(j(e,r)?.d1_databases??[]).find(a=>a.database_id===G)?.binding,qe=e=>{if(!e.migrate||e.dryRun||e.preview)return;if(e.migrateUrl===void 0){const a="--migrate requires --migrate-url <https://your-worker> — the deploy target URL is only known after wrangler runs, and this gate runs before it; refusing to default to localhost";return e.logger.error(a),a}if(e.migrateYes!==!0){const a="--migrate runs production data migrations after deploy. Re-run with --migrate-yes to confirm.";return e.logger.error(a),a}const r=e.migrateToken??process.env.LUNORA_ADMIN_TOKEN;if(r===void 0||r===""){const a="admin token required for --migrate — pass --migrate-token or set LUNORA_ADMIN_TOKEN";return e.logger.error(a),a}},Ye=(e,r,a="deploy",t)=>{const n=Ke(e,t);if(n===void 0)return;const o=`${a} blocked: the "${n}" D1 binding has a placeholder database_id ("${G}"). Run \`wrangler d1 create <name>\` to create the database, then replace the placeholder in wrangler.jsonc with the real id before deploying.`;return r.error(o),o},ze=(e,r,a="deploy",t)=>{const n=j(e,t)?.vars;if(!n)return;const o=Object.entries(n).filter(([,s])=>typeof s=="string"&&Be(s)).map(([s])=>s).toSorted((s,c)=>s.localeCompare(c));if(o.length===0)return;const i=`${a} blocked: ${o.join(", ")} in wrangler.jsonc point at localhost. A deployed Worker can't reach a loopback address, so this silently breaks scheduled-job dispatch, reverse cross-shard relations, and auth callbacks. Set each to the deployed worker's public URL (or move it to a secret with \`wrangler secret put\`) before deploying.`;return r.error(i),i},Je=(e,r,a)=>{const t=Ye(e,r.logger,a,r.env);if(t!==void 0)return{code:h.USAGE,error:t};const n=ze(e,r.logger,a,r.env);if(n!==void 0)return{code:h.USAGE,error:n};const o=Ge(e,r.logger,a);if(o!==void 0)return{code:h.USAGE,error:o};const i=He(e,r.logger,r.dockerAvailable??Re,a,r.env);return i===void 0?void 0:{code:h.MISSING_DEPENDENCY,error:i}},Xe=/https?:\/\/[^\s"'<>]+\.workers\.dev[^\s"'<>]*/u,Qe=/https:\/\/[^\s"'<>]+/u,Ze=e=>{if(e===void 0)return;const r=Xe.exec(e);return r?r[0]:Qe.exec(e)?.[0]},_=e=>e===void 0?"":` (--env ${e})`,er=({cwd:e,env:r,logger:a,now:t,url:n})=>{if(n!==void 0)try{const o=I(e);if(o!==void 0){if(o.workerUrl===n&&o.env===r)return;a.warn(`link: .lunora/project.json records ${o.workerUrl??"(no url)"}${_(o.env)}, but this deploy published ${n}${_(r)}. Keeping the recorded value — run \`lunora link --url ${n}${r===void 0?"":` --env ${r}`}\` to update it.`);return}const i=(t??(()=>new Date().toISOString()))();ee(e,{env:r,linkedAt:i,workerName:C(e),workerUrl:n}),a.success(`link: recorded ${n} in .lunora/project.json (run \`lunora link\` to change)`)}catch{}},rr=async(e,r)=>{const a=new xe({skipAddingFilesFromTsConfig:!0}),t=y(r,"lunora");let n;try{n=X(a,t)}catch(o){const i=o instanceof Error?o.message:String(o);return e.logger.warn(`--migrate: could not discover migrations (${i}); skipping`),0}if(n.length===0)return e.logger.info("--migrate: no data migrations declared in lunora/"),0;e.logger.info(`--migrate: running ${String(n.length)} migration(s) against deployed worker`);for(const o of n){e.logger.info(`--migrate: up "${o.id}" (table "${o.table}")`);const i={cwd:r,fetchImpl:e.fetchImpl,id:o.id,logger:e.logger,prod:!0,subcommand:"up",token:e.migrateToken,url:e.migrateUrl,yes:e.migrateYes===!0},s=await Ie(i);if(s.code!==0)return e.logger.error(`--migrate: migration "${o.id}" failed — see output above`),s.code;e.logger.success(`--migrate: "${o.id}" applied`)}return 0},tr=async(e,r)=>{const{info:a}=re(r,"lunora"),t=a?.vectorMetadata??[];if(t.length===0)return;const n=[];for(const s of t){const c=Te(s.kind);if(c===void 0){e.logger.warn(`vector index "${s.index}" declares metadata "${s.property}", whose column type cannot be filtered on in Vectorize — it is stored with each vector but no filter will match it.`);continue}n.push({index:s.index,property:s.property,type:c})}if(n.length===0)return;const i=(await _e({cwd:r,entries:n,exec:L(b(r),"wrangler",[]),logger:e.logger,spawner:e.spawner??$})).filter(s=>s.status!=="failed").length;i>0&&e.logger.success(`vectorize metadata indexes ready: ${String(i)}/${String(n.length)}`)},N=5,nr=2e3,ar=async(e,r,a)=>{if(e.healthCheck!==!0)return;const t=a??V({cwd:r,env:e.env});if(t===void 0){const o="--health-check: the deploy succeeded, but no URL to probe could be resolved — wrangler's output carried none and this checkout has no link for this environment. Run `lunora link --url <https://your-worker>` and re-deploy, or drop --health-check.";return e.logger.error(o),{error:o,ok:!1,url:""}}const n=await Ae({attempts:N,baseUrl:t,delayMs:nr,fetchImpl:e.healthFetch,paths:[Ce,je],sleep:e.healthSleep});return n.error===void 0?(e.logger.success(`health check ok (${n.url})`),{ok:!0,url:n.url}):(e.logger.error(`--health-check: the deploy succeeded, but the new version did not answer after ${String(N)} attempt(s) — ${n.error}`),{error:n.error,ok:!1,url:n.url})},or=async(e,r,a,t,n,o)=>{await tr(e,r);const i=e.migrate?await rr(e,r):0;return i===0&&n?.(),{code:i,descriptor:a,mintedSecretsFile:o,validation:t}},ir=async({cwd:e,descriptor:r,mintedSecretsFile:a,options:t,reblessSchemaBaseline:n,stdout:o,validation:i})=>{const s={deployedAt:new Date().toISOString(),dryRun:t.dryRun===!0,env:t.env,preview:t.preview===!0,url:t.dryRun===!0?void 0:Ze(o),workerName:C(e)};if(t.dryRun===!0||t.preview===!0)return t.healthCheck===!0&&t.logger.warn(`--health-check skipped: ${t.dryRun===!0?"a dry run publishes nothing":"a preview version serves no live traffic"}`),{code:0,deployment:s,descriptor:r,mintedSecretsFile:a,validation:i};t.temporary!==!0&&er({cwd:e,env:t.env,logger:t.logger,url:s.url});const c=await ar(t,e,s.url);return c?.error!==void 0?{code:h.UNAVAILABLE,deployment:s,descriptor:r,healthCheck:c,mintedSecretsFile:a,validation:i}:{...await or(t,e,r,i,n,a),deployment:s,healthCheck:c}},sr=(e,r,a)=>{const t=y(e,w);if(!p(t))return;let n;try{n=O(k(t,"utf8")).length}catch{return}if(n===0)return;const o=a.toolchain?.secretPut!==void 0;r.warn(o?`Note: \`lunora deploy\` does not push secrets. ${w} has ${String(n)} key(s); if you changed them, run \`lunora env push --yes\` to update the deployed secrets.`:`Note: ${a.name} deploys read no secrets. ${w} has ${String(n)} key(s) that only the dev server sees; put the values the deployed worker needs in wrangler \`vars\`.`)},cr=async e=>{let r=[];try{r=te(await F({projectRoot:e,schemaDir:W(e)}))}catch{}const a=ne(r).map(n=>n.key);let t=[];try{const n=y(e,w);p(n)&&(t=O(k(n,"utf8")).map(o=>o.key).filter(o=>Ne(o)))}catch{}return[...new Set([...a,...t])]},lr=async(e,r,a,t)=>{const{logger:n}=r,o=r.spawner??$,i=b(e),s=r.env===void 0?"":` --env ${r.env}`,c=t.toolchain?.secretPut;if(c===void 0)return n.error(`deploy target "${t.id}" has no secret store; cannot push secrets`),{minted:[],ok:!1};const d=[];for(const l of a){const m=ae(),u=c({environment:r.env,key:l,temporary:r.temporary}),g=R(i,u),f=await o({args:g.args,command:g.command,cwd:e,input:m});if(f.code!==0)return n.error(`failed to push secret ${l} (exit ${String(f.code)}); set it manually with \`wrangler secret put ${l}${s}\`.`),{minted:d,ok:!1};d.push({key:l,value:m}),n.success(`generated + pushed ${l}`)}return{minted:d,ok:!0}},dr=/^[\w-]+$/u,D=[".dev.vars",".dev.vars.*","!.dev.vars.example"],ur=/\r?\n/u,gr=(e,r)=>{const a=y(e,".gitignore");try{const t=p(a)?k(a,"utf8"):"",n=new Set(t.split(ur).map(c=>c.trim())),o=D.filter(c=>!n.has(c));if(o.length===0)return;const i=o.some(c=>!c.startsWith("!"))?[...o.filter(c=>!c.startsWith("!")),...D.filter(c=>c.startsWith("!"))]:o,s=t.length>0&&!t.endsWith(`
`)?`
`:"";H(a,`${t}${s}
# Lunora — never commit a minted secret
${i.join(`
`)}
`,"utf8"),r.info(`.gitignore: added ${o.join(", ")} so the recorded secret cannot be committed`)}catch(t){r.warn(`could not update .gitignore (${t instanceof Error?t.message:String(t)}) — add \`${D.join("` and `")}\` by hand before committing.`)}},mr=(e,r,a)=>{if(a.length===0)return;const t=a.map(s=>s.key).join(", ");if(r.env!==void 0&&!dr.test(r.env)){r.logger.warn(`${t}: minted and pushed for --env ${r.env}, but "${r.env}" isn't a safe filename fragment — the value could not be recorded. Capture it manually if you need it again.`);return}const n=r.env===void 0?w:`${w}.${r.env}`;gr(e,r.logger);const o=y(e,n);let i=p(o)?k(o,"utf8"):"";for(const s of a)i=oe(i,s.key,s.value);return ie(o,i),r.logger.success(r.env===void 0?`${t}: value(s) recorded in ${n}`:`${t}: value(s) recorded in ${n} (kept separate from ${w} so \`lunora dev\` and a plain \`lunora env push\` don't inherit the --env ${r.env} value)`),n},fr=async(e,r,a,t)=>{if(r.dryRun===!0||r.preview===!0)return{};const{logger:n}=r,o=r.env===void 0?"":` --env ${r.env}`;let i;try{i=await(r.secretLister??Ue)({cwd:e,env:r.env,temporary:r.temporary})}catch{return{}}if(!i.ok)return{};const s=new Set(i.names),d=(await cr(e)).filter(u=>!s.has(u));if(d.length===0)return{};if(!a)return{error:`missing required secret(s) on the deploy target: ${d.join(", ")}. Set them with \`wrangler secret put <KEY>${o}\` (or \`lunora env generate --set\` then \`lunora env push --yes${o}\`), then re-deploy.`};for(const u of d.filter(g=>!x(g)))n.warn(`required secret ${u} is not set on the target — set it with: wrangler secret put ${u}${o}`);const l=d.filter(u=>x(u));if(l.length===0)return{};if(await(r.secretConfirm??Le())(`${String(l.length)} required secret(s) not set on the target (${l.join(", ")}). Generate strong values and push them now?`)){const{minted:u,ok:g}=await lr(e,r,l,t),f=mr(e,r,u);return g?{mintedSecretsFile:f}:{error:"failed to push required secret(s) — set them manually and re-deploy",mintedSecretsFile:f}}return n.warn(`${String(l.length)} required secret(s) not set on the target: ${l.join(", ")}. Generate + push with \`lunora env generate --set\` then \`lunora env push --yes${o}\`.`),{}},q=e=>e.format==="json"?!1:e.interactive!==void 0?e.interactive:process.stdout.isTTY&&!process.env.CI,hr=async(e,r)=>{if(r.dryRun===!0)return;const a=U(e,"lunora").containers.flatMap(n=>n.schedulingPolicy===void 0&&n.image.kind==="build"?[{buildDir:n.image.buildDir,exportName:n.exportName}]:[]);if(a.length===0)return;const t=await Fe({cwd:e,logger:r.logger,railpackAvailable:r.railpackAvailable,spawner:r.spawner,targets:a});return t.code===0?void 0:t.error??"railpack build failed"},vr=(e,r,a)=>{if(a!==void 0)try{const t=fe(e,a);t.changed&&r.success(`synced ${String(a.length)} cron trigger(s) → ${t.wranglerPath??"wrangler.jsonc"}`);for(const o of t.warnings)r.warn(o);const n=he(t.preserved);n!==void 0&&r.info(n)}catch(t){const n=t instanceof Error?t.message:String(t);r.warn(`cron trigger sync skipped: ${n}`)}},pr=async(e,r,a,t,n)=>{try{A(t);const o=await F({projectRoot:e,schemaDir:W(e)}),i=ge(e,o,n),s=i.wranglerPath??"wrangler.jsonc";if(i.added.length>0&&r.success(`provisioned bindings: ${i.added.join(", ")} → ${s}`),i.updated.length>0&&r.success(`updated bindings: ${i.updated.join(", ")} → ${s}`),i.devConfig!==void 0){const{added:c,path:d,updated:l}=i.devConfig;r.success(`dev sidecar bindings: ${[...c,...l].join(", ")} → ${d}`)}for(const c of i.warnings)r.warn(c)}catch(o){const i=o instanceof Error?o.message:String(o);r.warn(`binding inference skipped: ${i}`)}try{const o=me(e);o.changed&&r.success(`bumped compatibility_date to ${o.date??"unknown"} (Workers Cache enabled) → ${o.wranglerPath??"wrangler.jsonc"}`)}catch(o){const i=o instanceof Error?o.message:String(o);r.warn(`compatibility date sync skipped: ${i}`)}vr(e,r,a)},yr=async(e,r,a,t,n,o,i,s)=>{let c;r?(c=new ve({name:"dots"}),c.start("running codegen")):a.info("running codegen");try{const d=Z({apiSpec:t,projectRoot:e,target:n});c?.succeed("codegen complete"),c||a.success("codegen complete");const l=ye(d.platformDiagnostics,a);if(l.errors.length>0)return{error:l.errors.join("; ")};const{errorAdvisories:m,names:u,shouldBlock:g}=we(d.advisories,s);if(g){const v=`${m.length.toString()} ERROR-level ${m.length===1?"advisory":"advisories"} (${u.join(", ")}). Pass --no-strict-advisories to downgrade this to a warning and continue.`;return a.error(v),{error:v}}const f=await se({cwd:e,logger:a,spawner:o,stdoutToStderr:i});return f.error!==void 0?{error:f.error}:{result:d}}catch(d){c?.failed("codegen failed");const l=d instanceof Error?d.message:String(d);return a.error(`codegen failed: ${l}`),{error:`codegen failed: ${l}`}}},wr=(e,r)=>({dryRun:r.dryRun,entry:K(e),environment:r.env,outDir:r.outDir,preview:r.preview,temporary:r.temporary}),Y=(e,r,a)=>{const{toolchain:t}=a;if(t===void 0)throw new Error(`deploy target "${a.id}" has no command-line toolchain`);const n=wr(e,r);return M(a,e,"deploy",o=>t.deploy({...n,configPath:o}))},kr=(e,r,a)=>{const t=Y(e,r,a),n=K(e);return n!==void 0&&r.logger.info(`class-B composition: deploying ${n} (overrides wrangler main)`),r.temporary&&r.logger.info("temporary account: deploying to a short-lived Cloudflare account (~60min); wrangler will print a claim URL"),r.dryRun&&r.logger.info("dry run: validating + bundling without publishing"),r.outDir!==void 0&&r.logger.info(`build artifact: emitting bundle to ${r.outDir}`),t.commit(),t.projection!==void 0&&t.projection.dropped.length>0&&r.logger.warn(`${a.name} ignores these wrangler keys, so they were left out of ${t.projection.configPath}: ${t.projection.dropped.join(", ")}`),t.command},E=(e,r)=>({code:h.USAGE,descriptor:void 0,error:e,validation:{problems:[],wranglerPath:void 0},...r}),br=(e,r)=>{for(const a of e.report?.warnings??[])r.warn(`wrangler.jsonc: ${a}`);if(e.problems.length===0)return!1;r.error("wrangler.jsonc validation failed:");for(const a of e.problems)r.error(` - ${a}`);return!0},$r=(e,r,a)=>{const t=r.format==="json",n=r.dryRun!==!0,o=kr(e,r,a),i=R(b(e),o);return{args:i.args,captureStdout:n&&!t,captureStdoutSilently:n&&t,command:i.command,cwd:e,stdoutToStderr:t&&!n}},Sr=async(e,r)=>{const a=e.cwd??process.cwd(),t=q(e),n=pe(e),o={problems:[],wranglerPath:void 0},i=be(a,e.target);if(i.target===void 0){const u=i.error??"unknown deploy target";return e.logger.error(u),{error:u,validation:o}}const{target:s}=i;try{Y(a,e,A(s))}catch(u){const g=u instanceof Error?u.message:String(u);return e.logger.error(g),{error:g,target:s,validation:o}}let c;if(!e.skipCodegen){const u=await yr(a,t,e.logger,e.apiSpec,s,e.spawner,e.format==="json",n);if(u.error!==void 0)return{error:u.error,target:s,validation:o};c=u.result}let d;if(c!==void 0){const u=Pe({allowDrift:e.allowSchemaDrift===!0,codegen:c,command:r,logger:e.logger,updateBaseline:e.updateSchemaBaseline===!0});if(u.blocked)return{error:`schema drift gate blocked ${r}`,schemaDrift:{blocked:!0,reason:u.reason},target:s,validation:o};d=u.rebless}await pr(a,e.logger,c?.cronTriggers,s,e.env);const l=Je(a,e,r);if(l!==void 0)return{code:l.code,error:l.error,target:s,validation:o};const m=ue({environment:e.env,projectRoot:a});return br(m,e.logger)?{error:"wrangler validation failed",target:s,validation:m}:{codegen:c,reblessSchemaBaseline:d,target:s,validation:m}},Dr=e=>{if(e.skipServices===!0)return"--skip-services";if(e.preview===!0)return"--preview uploads the app version only";if(e.temporary===!0)return"--temporary deploys the app to a throwaway account"},Er=async(e,r,a,t)=>{const{error:n,services:o}=Q(e);if(n!==void 0)return n;if(o.length===0)return;const{toolchain:i}=a,s=i===void 0?`target "${a.id}" has no deploy command`:Dr(r);if(s!==void 0||i===void 0){const l=o.map(m=>m.worker).join(", ");r.logger.warn(`services not deployed (${s??""}): ${l} — deploy them yourself if the app calls anything new`);return}const c=b(e),d=[...new Map(o.map(l=>[l.wranglerPath,l])).values()];for(const l of d){const m=J(l.wranglerPath);let u;try{u=M(a,m,"deploy",v=>i.deploy({configPath:v??l.wranglerPath,dryRun:r.dryRun===!0,environment:r.env}))}catch(v){return`service ${l.name} (${l.worker}): ${v instanceof Error?v.message:String(v)}`}u.commit();const g=R(c,u.command);r.logger.info(`deploying service ${l.name} (${l.worker}) via ${g.command} ${g.args.join(" ")}`);const f=await t({args:g.args,command:g.command,cwd:m,stdoutToStderr:r.format==="json"});if(f.code!==0)return`service ${l.name} (${l.worker}): ${g.command} ${g.args.join(" ")} exited ${String(f.code)} — stopping before the app`}},Rr=async e=>{const r=e.cwd??process.cwd(),a=q(e),t=await Sr(e,e.commandName??"deploy");if(t.error!==void 0){if(t.validation.problems.length>0)return{code:h.USAGE,descriptor:void 0,error:t.error,validation:t.validation};const S=t.schemaDrift===void 0?void 0:{schemaDrift:t.schemaDrift};return E(t.error,{...S,...t.code===void 0?{}:{code:t.code}})}const{reblessSchemaBaseline:n,validation:o}=t,i=A(t.target),s=qe(e);if(s!==void 0)return E(s);const c=i.toolchain?.prebuildsContainerImages===!0?await hr(r,e):void 0;if(c!==void 0)return E(c);sr(r,e.logger,i),de(r,S=>{e.logger.warn(S)});const{error:d,mintedSecretsFile:l}=await fr(r,e,a,i);if(d!==void 0)return e.logger.error(d),{code:h.USAGE,descriptor:void 0,error:d,mintedSecretsFile:l,validation:o};const m=e.spawner??$,u=await Er(r,e,i,m);if(u!==void 0)return{code:h.FAILURE,descriptor:void 0,error:u,mintedSecretsFile:l,validation:o};const g=$r(r,e,i);e.logger.info(`deploying via ${g.command} ${g.args.join(" ")}`);const f=await m(g);return g.captureStdoutSilently===!0&&f.stdout!==void 0&&f.stdout!==""&&process.stderr.write(f.stdout),f.code!==0?{code:f.code,descriptor:g,mintedSecretsFile:l,validation:o}:{...await ir({cwd:r,descriptor:g,mintedSecretsFile:l,options:e,reblessSchemaBaseline:n,stdout:f.stdout,validation:o}),logsAvailable:i.toolchain?.tail!==void 0}},Ar=async e=>{const{logger:r}=e,a=e.dryRun===!0?We(e.cwd??process.cwd()):void 0;let t;try{if(t=await Rr({...e,logger:r}),t.code===0&&e.emitBindings!==void 0){const{error:n}=$e({destination:e.emitBindings,logger:r,projectRoot:e.cwd??process.cwd()});n!==void 0&&(r.error(n),t={...t,code:h.USAGE})}}finally{a?.()}if(e.format==="json")return t;if(t.code===0&&e.dryRun!==!0&&e.preview!==!0)Oe({cwd:e.cwd??process.cwd(),env:e.env,logger:e.logger,logsAvailable:t.logsAvailable===!0,mintedSecretsFile:t.mintedSecretsFile,url:t.deployment?.url});else if(t.code===0&&e.preview===!0){const n=t.deployment?.url;e.logger.success(n===void 0?"preview version uploaded":`preview version uploaded — ${n}`)}return t},Xr=Se(async({cwd:e,format:r,logger:a,options:t})=>{const n=await Ar({allowSchemaDrift:t.allowSchemaDrift===!0,apiSpec:ke(t.apiSpec),cwd:e,dryRun:t.dryRun===!0,env:t.env,format:r,healthCheck:t.healthCheck===!0,logger:a,migrate:t.migrate===!0,migrateToken:t.migrateToken,migrateUrl:V({cwd:e,env:t.env,url:t.migrateUrl}),migrateYes:t.migrateYes===!0,preview:t.preview===!0,skipCodegen:t.prebuilt===!0,skipServices:t.skipServices===!0,strictAdvisories:t.strictAdvisories,target:t.target,temporary:t.temporary===!0,updateSchemaBaseline:t.updateSchemaBaseline===!0});return{code:n.code,data:{deployment:n.deployment,healthCheck:n.healthCheck,mintedSecretsFile:n.mintedSecretsFile,schemaDrift:n.schemaDrift,validation:n.validation},error:n.error}});export{Xr as execute,pr as provisionBindings,Ar as runDeployCommand,Sr as runPreDeployPipeline};