@lunora/cli 1.0.0-alpha.485 → 1.0.0-alpha.486
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.
|
@@ -2,7 +2,7 @@ import{mkdirSync as Re,writeFileSync as X,existsSync as b,readFileSync as E,read
|
|
|
2
2
|
${r.join(`
|
|
3
3
|
`)}`},Un=(e,r)=>p(e,".lunora",r===void 0?"architecture.json":`architecture.${r}.json`),Bn=e=>{if(b(e))try{const r=JSON.parse(E(e,"utf8"));return r.version===1&&Array.isArray(r.modules)&&Array.isArray(r.edges)?r:void 0}catch{return}},Wn=e=>{const{cwd:r,environment:t,logger:n}=e,o=Un(r,t),s=Bn(o),i=e.current??(s===void 0?void 0:Nn);if(i===void 0)return;const a=s===void 0?void 0:Mn(Fn(s,i));return a!==void 0&&n.info(a),()=>{try{Re(R(o),{recursive:!0}),X(o,`${JSON.stringify(i,void 0,2)}
|
|
4
4
|
`,"utf8")}catch(c){n.warn(`could not record the deployed architecture at ${o}: ${c instanceof Error?c.message:String(c)}`)}}},Hn=e=>{const{cwd:r,env:t,logger:n,mintedSecretsFile:o}=e;try{const s=Ce(r),i=s?.workerName??Ie(r),a=e.url??s?.workerUrl;n.success("deploy complete"),n.info(` worker: ${i??"(see wrangler output above)"}`),t!==void 0&&n.info(` env: ${t}`),a===void 0?n.info(" url: run `lunora link --url <https://your-worker>` to record it"):n.info(` url: ${a}`),o!==void 0&&n.info(` secrets: generated value(s) recorded in ${o}`),e.logsAvailable&&n.info(" logs: lunora logs")}catch{}},qn=/\/index$/u,Gn=/[^\dA-Za-z]/gu,Vn=e=>e.replace(qn,"").split("/").map(r=>r.replaceAll(Gn,"_")),Kn=e=>Vn(e).join("_"),zn=/[^a-z0-9-]/gu,Jn=/-$/u,Ye=7,Yn=(e,r)=>{const t=e.toLowerCase(),n=t.replaceAll(zn,"-");if(n===t&&n.length<=r)return n;const o=wn("sha256").update(t).digest("hex").slice(0,Ye-1);return`${n.slice(0,r-Ye).replace(Jn,"")}-${o}`},Xn=32,Dr=15e3,Qn=5432,Zn=3306,eo=/[^A-Za-z0-9]/gu,Y=e=>`LUNORA_${e.replaceAll(eo,"_").toUpperCase()}_URL`,ro=e=>{const r=new Map;for(const t of e){const n=Y(t),o=r.get(n);if(o!==void 0)return{bindings:[o,t],variable:n};r.set(n,t)}},to=(e,r)=>Yn(`${e}-${r}`,Xn),Xe=e=>e==="postgresql"?"postgres":e==="mariadb"?"mysql":e,no=e=>e==="mysql"?Zn:Qn,oo=e=>{try{const r=new URL(e),t=r.protocol.slice(0,-1),n=decodeURIComponent(r.pathname.slice(1));return!["mariadb","mysql","postgres","postgresql"].includes(t)||r.hostname===""||n===""?void 0:{database:n,host:r.hostname,password:decodeURIComponent(r.password),port:r.port===""?no(Xe(t)):Number(r.port),scheme:Xe(t),user:decodeURIComponent(r.username)}}catch{return}},so=(e,r)=>{const t=typeof r?.password=="string"?r.password:"",n=t===""?[]:[t,JSON.stringify(t).slice(1,-1),encodeURIComponent(t)];let o=e;for(const s of n)o=o.replaceAll(s,"<redacted>");return o.split(" ").map(s=>s.includes("://")?"<url>":s).join(" ")},Or=e=>(Array.isArray(e)?e:[]).flatMap((r,t)=>{const{binding:n,id:o}=typeof r=="object"&&r!==null?r:{};return typeof n=="string"&&o===Er?[{binding:n,index:t}]:[]}),se=e=>{let r="";try{const{errors:t}=JSON.parse(e.text);r=(t??[]).flatMap(n=>typeof n.message=="string"?[n.message]:[]).slice(0,2).join("; ")}catch{}return r===""?`HTTP ${String(e.status)}`:`HTTP ${String(e.status)}: ${r}`},io=e=>typeof e=="string"||typeof e=="number"?e.toString():void 0,ao=["scheme","host","port","database","user"],co=e=>e==="postgresql"?"postgres":e,Qe=(e,r)=>{const t=io(r);return e==="scheme"?co(t):t},Ze=50,lo=async(e,r)=>{for(let t=1;t<=Ze;t+=1){const n=await e(t===1?"/hyperdrive/configs":`/hyperdrive/configs?page=${String(t)}`);if(!n.ok)throw new Error(`listing Hyperdrive configs failed (${se(n)})`);if(!Array.isArray(n.body.result))throw new TypeError("the listing of Hyperdrive configs had no result list");const o=n.body.result.find(i=>i.name===r),s=typeof n.body.result_info=="object"&&n.body.result_info!==null?n.body.result_info:{};if(o!==void 0||typeof s.total_pages!="number"||t>=s.total_pages)return o}throw new Error(`the listing of Hyperdrive configs has more than ${String(Ze)} pages, so "${r}" can't be shown to be absent`)},uo=async(e,r,t,n)=>{const o=n?{caching:{disabled:!0}}:{},s=await lo(e,r);if(typeof s?.id=="string"){const c=ao.filter(d=>Qe(d,s.origin?.[d])!==Qe(d,t[d]));if(c.length>0)throw new Error(`a Hyperdrive config named "${r}" already exists for a different origin, or one that can't be confirmed (${c.join(", ")}). Delete it, or rename the Worker, so the binding does not reach the wrong database.`);const l=await e(`/hyperdrive/configs/${encodeURIComponent(s.id)}`,{body:JSON.stringify({origin:t,...o}),method:"PATCH"});if(!l.ok)throw new Error(`updating its credentials failed (${se(l)})`);return{id:s.id,reused:!0}}const i=await e("/hyperdrive/configs",{body:JSON.stringify({name:r,origin:t,...o}),method:"POST"});if(!i.ok)throw new Error(`creating it failed (${se(i)})`);const{id:a}=typeof i.body.result=="object"&&i.body.result!==null?i.body.result:{};if(typeof a!="string")throw new TypeError("the response carried no id");return{id:a,reused:!1}},fo=(e,r,t,n)=>r===void 0&&n?`${Y(e)} is set but is not a usable connection string for the "${e}" Hyperdrive binding: it needs a postgres://, postgresql:// or mysql:// scheme, a host and a database name, with any special characters percent-encoded.`:r===void 0?`the "${e}" Hyperdrive binding has no config yet. Set ${Y(e)} to its postgres:// or mysql:// connection string and Lunora creates the config, or run \`wrangler hyperdrive create\` and put its id in wrangler.jsonc.`:t?void 0:`creating the "${e}" Hyperdrive config needs CLOUDFLARE_API_TOKEN (Hyperdrive: Edit) and CLOUDFLARE_ACCOUNT_ID, or run \`wrangler hyperdrive create\` and put its id in wrangler.jsonc.`,go=async(e,r)=>{const t=to(e.workerName,e.binding),{id:n,reused:o}=await uo(async(s,i)=>Ir({accountId:e.accountId,apiToken:e.token,...e.fetch===void 0?{}:{fetch:e.fetch},init:{...i,signal:AbortSignal.timeout(Dr)},path:s}),t,e.origin,e.disableCaching);return e.logger.success(`${o?"reusing":"created"} Hyperdrive config "${t}" for ${e.binding} → ${e.wranglerPath}`),{id:n,text:Ar(r,[...e.path,e.index,"id"],n)}},ho=e=>e===void 0||e===""?void 0:e,Oe=e=>{const r=p(R(e),".wrangler","deploy","config.json");try{const{configPath:t}=JSON.parse(E(r,"utf8"));return typeof t=="string"?_(R(r),t):void 0}catch{return}},mo=(e,r,t,n,o)=>{const s=Oe(e);if(s===void 0||s===e)return;if(n.built!==n.deployed){o.warn(`the build output ${s} was made for ${n.built===void 0?"the default environment":`"${n.built}"`}, not ${n.deployed===void 0?"the default environment":`"${n.deployed}"`}, so the "${r}" Hyperdrive id was not written there. Build with CLOUDFLARE_ENV set to the environment you deploy, then deploy again.`);return}let i;try{i=I(s)}catch{return}const a=Or(i.parsed?.hyperdrive).find(c=>c.binding===r);a!==void 0&&X(s,Ar(i.text,["hyperdrive",a.index,"id"],t),"utf8")},po=(e,r)=>{const t=typeof e.name=="string"&&e.name!==""?e.name:"lunora";if(r===void 0)return t;const n=e.env?.[r]?.name;return typeof n=="string"&&n!==""?n:`${t}-${r}`},fe=(e,r,t)=>{const n=t??D(e),o=n===void 0?void 0:I(n).parsed;if(n===void 0||o===void 0)return;const s=r===void 0?void 0:o.env?.[r],i=Or(r===void 0?o.hyperdrive:s?.hyperdrive);return i.length===0?void 0:{accountId:s?.account_id??o.account_id,entries:i,path:r===void 0?["hyperdrive"]:["env",r,"hyperdrive"],workerName:po(o,r),wranglerPath:n}},vo=(e,r,t)=>{const n=ro(e.map(({binding:o})=>o));if(n!==void 0){const o=`deploy blocked: the Hyperdrive bindings ${n.bindings.map(s=>`"${s}"`).join(" and ")} both read ${n.variable} and would share one config. Rename one so their names differ in letters or digits, not only in punctuation.`;return t.logger.error(o),o}for(const{binding:o,origin:s,supplied:i}of e){const a=fo(o,s,r,i);if(a!==void 0&&t.dryRun===!0)t.logger.warn(`dry run: ${a} A real deploy stops here.`);else if(a!==void 0){const c=`deploy blocked: ${a}`;return t.logger.error(c),c}}},yo=async(e,r,t,n)=>{let o;try{o=await e(`/hyperdrive/configs/${encodeURIComponent(t)}`)}catch(i){n.warn(`could not check whether the "${r}" Hyperdrive config has query caching off (${i instanceof Error?i.message:String(i)}).`);return}if(!o.ok){n.warn(`could not check whether the "${r}" Hyperdrive config has query caching off (${se(o)}).`);return}const{caching:s}=typeof o.body.result=="object"&&o.body.result!==null?o.body.result:{};s?.disabled!==!0&&n.warn(`the "${r}" Hyperdrive config has query caching on, so .global() tables would read rows up to 75 s old. Run \`wrangler hyperdrive update ${t} --caching-disabled\`.`)},wo=async(e,r)=>{const t=e.wranglerPath??D(e.cwd),n=t===void 0?void 0:I(t).parsed,o=e.wranglerEnvironment===void 0?n:n?.env?.[e.wranglerEnvironment],{accountId:s,token:i}=_r(r,o?.account_id??n?.account_id);if(s===void 0||i===void 0)return;const a=async(c,l)=>Ir({accountId:s,apiToken:i,...e.fetch===void 0?{}:{fetch:e.fetch},init:{...l,signal:AbortSignal.timeout(Dr)},path:c});for(const c of Array.isArray(o?.hyperdrive)?o.hyperdrive:[]){const{binding:l,id:d}=typeof c=="object"&&c!==null?c:{};typeof l=="string"&&typeof d=="string"&&d!==Er&&await yo(a,l,d,e.logger)}},ko=async e=>{const r=e.environment??process.env;e.disableCaching===!0&&e.checkOnly!==!0&&e.dryRun!==!0&&await wo(e,r);const t=fe(e.cwd,e.wranglerEnvironment,e.wranglerPath);if(t===void 0)return;const{entries:n,path:o,wranglerPath:s}=t,{accountId:i,token:a}=_r(r,t.accountId),c=n.map(({binding:d})=>{const u=r[Y(d)]??"";return{binding:d,origin:oo(u),supplied:u!==""}}),l=vo(c,a!==void 0&&i!==void 0,e);if(l!==void 0)return l;if(!(e.dryRun===!0||e.checkOnly===!0))for(const[d,{binding:u,index:f}]of n.entries()){const g=c[d]?.origin;if(!(g===void 0||a===void 0||i===void 0))try{const{id:h,text:v}=await go({accountId:i,binding:u,disableCaching:e.disableCaching===!0,fetch:e.fetch,index:f,logger:e.logger,origin:g,path:o,token:a,workerName:t.workerName,wranglerPath:s},E(s,"utf8"));X(s,v,"utf8"),mo(s,u,h,{built:ho(r.CLOUDFLARE_ENV),deployed:e.wranglerEnvironment},e.logger)}catch(h){const v=h instanceof Error?h.message:String(h),m=`deploy blocked: the "${u}" Hyperdrive config could not be set up: ${so(v,c[d]?.origin)}`;return e.logger.error(m),m}}},bo=(e,r)=>{const t=fe(e,r.env)?.entries[0]?.binding;if(t===void 0)return;const n=`prepare blocked: the "${t}" Hyperdrive binding still has its placeholder id. Run \`lunora deploy\` with ${Y(t)} set to create the config, or put the id from \`wrangler hyperdrive create\` in wrangler.jsonc.`;return r.logger.error(n),n},$o=(e,r)=>{const t=fe(e,r.env)?.entries[0]?.binding;if(t===void 0)return;const n=`deploy blocked: the "${t}" Hyperdrive binding has no config, and a --temporary deploy cannot create one: it would be made in your account while the Worker goes to a temporary one, which a temporary account has no database for. Deploy without --temporary.`;return r.logger.error(n),n},H=async(e,r,t,n,o="create",s)=>{if(n!=="cloudflare")return;if(t==="prepare")return bo(e,r);if(r.temporary===!0)return $o(e,r);const i=r.clientBuildEnvironment===void 0?void 0:{...process.env,CLOUDFLARE_ENV:r.clientBuildEnvironment},a=Pe(e,O(e),r.discoveryCache);return a.error!==void 0&&r.logger.warn(`could not read the schema (${a.error}), so Hyperdrive configs are set up with query caching off.`),ko({checkOnly:o==="check",cwd:e,disableCaching:a.error!==void 0||a.info?.hasHyperdriveGlobalTable===!0,dryRun:r.dryRun,environment:i,logger:r.logger,wranglerEnvironment:r.env,wranglerPath:s})},So=(e,r,t)=>fe(e,r,t)!==void 0,pe=[".ts",".tsx",".mts",".js",".mjs"],Eo=/^[a-z]+:/u,er=/\.[cm]?js$/u,Ao=e=>!e.startsWith(".")&&!e.startsWith("/")&&!e.startsWith("#")&&!Eo.test(e),Ro=e=>e.split("/").slice(0,e.startsWith("@")?2:1).join("/"),To=e=>e.split(x).some(r=>r==="_generated"||r==="node_modules"),Co=e=>e.split(x).includes("node_modules"),rr=(e,r)=>{const t=_(R(e),r),n=er.test(t)?t.replace(er,""):void 0;return[t,...n===void 0?[]:pe.map(s=>`${n}${s}`),...pe.map(s=>`${t}${s}`),...pe.map(s=>p(t,`index${s}`))].find(s=>b(s)&&Te(s).isFile())},Po=/^lunora\.config\.[cm]?[jt]s$/u,be=e=>{if(typeof e=="string")return e;if(typeof e=="object"&&e!==null)for(const[r,t]of Object.entries(e)){const n=r==="types"?void 0:be(t);if(n!==void 0)return n}},_o=(e,r)=>{let t;try{t=JSON.parse(E(p(e,"package.json"),"utf8")).imports}catch{return}const n=be(t?.[r]);if(n!==void 0)return n;for(const[o,s]of Object.entries(t??{})){const[i="",a=""]=o.split("*"),c=be(s);if(c!==void 0&&o.includes("*")&&r.startsWith(i)&&r.endsWith(a)&&r.length>=o.length-1){const l=r.slice(i.length,r.length-a.length);return c.replaceAll("*",()=>l)}}},Io=(e,r)=>{const t=p(e,r,"_generated"),n=K(e).find(o=>Po.test(o));return[...b(t)?K(t).filter(o=>o.endsWith(".ts")).map(o=>p(t,o)):[],...n===void 0?[]:[p(e,n)]]},jo=e=>{const r=w.findConfigFile(e,n=>w.sys.fileExists(n));if(r===void 0)return!1;const t=w.readConfigFile(r,n=>w.sys.readFile(n));return w.parseJsonConfigFileContent(t.config??{},w.sys,R(r)).options.verbatimModuleSyntax===!0},tr=e=>e!==void 0&&(w.isNamedImports(e)||w.isNamedExports(e))&&e.elements.length>0&&e.elements.every(r=>r.isTypeOnly),Do=(e,r)=>{if(w.isImportDeclaration(e)){const{importClause:t}=e;return!(t?.phaseModifier===w.SyntaxKind.TypeKeyword||!r&&t?.name===void 0&&tr(t?.namedBindings))&&w.isStringLiteral(e.moduleSpecifier)?e.moduleSpecifier.text:void 0}if(w.isExportDeclaration(e))return!(e.isTypeOnly||!r&&tr(e.exportClause))&&e.moduleSpecifier!==void 0&&w.isStringLiteral(e.moduleSpecifier)?e.moduleSpecifier.text:void 0},Oo=e=>{if(!w.isCallExpression(e)||e.expression.kind!==w.SyntaxKind.ImportKeyword)return;const[r]=e.arguments;return r!==void 0&&w.isStringLiteralLike(r)?r.text:void 0},Lo=(e,r)=>{const t=w.createSourceFile(e,E(e,"utf8"),w.ScriptTarget.Latest,!1),n=[],o=[],s=i=>{const a=Oo(i),c=a??Do(i,r);c?.startsWith(".")||c?.startsWith("#")?o.push(c):a!==void 0&&Ao(a)&&n.push(a),w.forEachChild(i,s)};return s(t),{dynamicPackages:n,relative:o}},No=(e,r)=>{for(let t=e;;t=R(t)){if(b(p(t,"node_modules",r,"package.json")))return!0;if(R(t)===t)return!1}},xo=(e,r,t)=>{const n=jo(e),o=new Set,s=new Set,i=r!==void 0&&b(_(e,r))?[_(e,r)]:Io(e,t);for(let a=i.shift();a!==void 0;a=i.shift())if(!o.has(a)&&!Co(L(e,a))){o.add(a);const{dynamicPackages:c,relative:l}=Lo(a,n);if(!To(L(e,a)))for(const d of c)s.add(d);i.push(...l.flatMap(d=>{if(!d.startsWith("#"))return rr(a,d)??[];const u=_o(e,d);return u===void 0?[]:rr(p(e,"package.json"),u)??[]}))}return[...s].filter(a=>No(e,Ro(a))).toSorted((a,c)=>a.localeCompare(c))},Fo=18787,nr=3,$e=12e4,Lr=5e3,W=10,Nr=new Set([".js",".mjs",".mts",".ts",".tsx"]),Mo=/\(void 0\)|is not a function|before initialization|of undefined/u,Uo=e=>typeof e=="object"&&e!==null&&typeof e.functionPath=="string"&&(e.deferred===void 0||["agent","module","queue","workflow"].includes(e.deferred))&&typeof e.message=="string",Bo=e=>{const r=O(e),t=new Map;let n;try{n=K(p(e,r),{recursive:!0,encoding:"utf8"})}catch{return t}for(const o of n){const s=yt(o);Nr.has(s)&&!o.split(x).some(i=>i==="_generated"||i==="node_modules")&&t.set(Kn(o.slice(0,-s.length).split(x).join("/")),p(r,o))}return t},or=e=>Mo.test(e)?`
|
|
5
|
-
hint: if this module is part of a circular import, an import it reads is still undefined while it evaluates`:"",Wo=(e,r)=>{const t=Bo(e),n=new Map,o=[];for(const{deferred:s,functionPath:i,message:a}of r){if(s!==void 0){o.push(` ${i.slice(s.length+1)} (${s}): ${a}${or(a)}`);continue}const c=i.lastIndexOf(":"),l=i.slice(0,c),d=`${l}\0${a}`,u=n.get(d)??{exports:[],message:a,namespace:l};u.exports.push(i.slice(c+1)),n.set(d,u)}return[...[...n.values()].map(({exports:s,message:i,namespace:a})=>` ${t.get(a)??a} (${s.join(", ")}): ${i}${or(i)}`),...o]},te=e=>{for(const r of Nr)for(const t of[`${e}${r}`,p(e,`index${r}`)])if(b(t))return t},Ho=(e,r)=>{const t=p(e,r,"_generated"),n=a=>{try{return E(p(t,a),"utf8")}catch{return""}},o=n("app.ts"),s=[],i=(a,c)=>{c!==void 0&&s.push({file:c,kind:a,label:L(e,c).split(x).join("/")})};return o.includes('import("./queues.js")')&&i("queue",te(p(t,"queues"))),n("workflows.
|
|
5
|
+
hint: if this module is part of a circular import, an import it reads is still undefined while it evaluates`:"",Wo=(e,r)=>{const t=Bo(e),n=new Map,o=[];for(const{deferred:s,functionPath:i,message:a}of r){if(s!==void 0){o.push(` ${i.slice(s.length+1)} (${s}): ${a}${or(a)}`);continue}const c=i.lastIndexOf(":"),l=i.slice(0,c),d=`${l}\0${a}`,u=n.get(d)??{exports:[],message:a,namespace:l};u.exports.push(i.slice(c+1)),n.set(d,u)}return[...[...n.values()].map(({exports:s,message:i,namespace:a})=>` ${t.get(a)??a} (${s.join(", ")}): ${i}${or(i)}`),...o]},te=e=>{for(const r of Nr)for(const t of[`${e}${r}`,p(e,`index${r}`)])if(b(t))return t},Ho=(e,r)=>{const t=p(e,r,"_generated"),n=a=>{try{return E(p(t,a),"utf8")}catch{return""}},o=n("app.ts"),s=[],i=(a,c)=>{c!==void 0&&s.push({file:c,kind:a,label:L(e,c).split(x).join("/")})};return o.includes('import("./queues.js")')&&i("queue",te(p(t,"queues"))),n("workflows.ts").includes('import("../workflows.js")')&&i("workflow",te(p(e,r,"workflows"))),(n("agents.ts").includes('import("../agents.js")')||o.includes('import("../agents.js")'))&&i("agent",te(p(e,r,"agents"))),s},qo=/\.[cm]?js$/u,Go=(e,r)=>{const t=[],n=o=>{if(w.isCallExpression(o)&&o.expression.kind===w.SyntaxKind.ImportKeyword){const[s]=o.arguments;s!==void 0&&w.isStringLiteralLike(s)&&t.push(s.text)}w.forEachChild(o,n)};return n(w.createSourceFile(e,r,w.ScriptTarget.Latest,!1,w.ScriptKind.TS)),t},Vo=(e,r,t)=>{const n=p(e,r,"_generated"),o=new Set(t.map(a=>a.file)),s=[];let i;try{i=K(n).filter(a=>a.endsWith(".ts")&&a!=="functions.ts")}catch{return s}for(const a of i.toSorted((c,l)=>c.localeCompare(l)))for(const c of Go(p(n,a),E(p(n,a),"utf8"))){const l=c.startsWith("."),d=l?te(_(n,c.replace(qo,""))):c;d!==void 0&&!o.has(d)&&(o.add(d),s.push({file:d,kind:"module",label:l?L(e,d).split(x).join("/"):c}))}return s};class j extends Error{skipped;constructor(r,t=!1){super(r),this.skipped=t}}const sr=/^[$_\p{ID_Start}][$\u200C\u200D\p{ID_Continue}]*$/u,xr=(e,r,t)=>{const n=t??D(e),o=n===void 0?void 0:I(n).parsed;if(o===void 0)return{assetsDirectory:void 0,durableObjectClasses:[],entrypointClasses:[],hasContainers:!1,main:void 0,workflowClasses:[]};const{merged:s}=ue(o,r),i=d=>[...new Set(d.flatMap(u=>u?.script_name===void 0&&typeof u?.class_name=="string"&&sr.test(u.class_name)?[u.class_name]:[]))],a=s.exports??{},c=d=>Object.entries(a).flatMap(([u,f])=>f?.type===d&&(f.state===void 0||f.state==="created")&&sr.test(u)?[u]:[]),l=s.assets?.directory;return{assetsDirectory:l===void 0||n===void 0?void 0:_(R(n),l),durableObjectClasses:[...new Set([...i(s.durable_objects?.bindings??[]),...c("durable-object")])],entrypointClasses:c("worker"),hasContainers:(s.containers??[]).length>0,main:s.main,workflowClasses:[...new Set([...i(s.workflows??[]),...c("workflow")])]}},Ko=({composedEntry:e,configPath:r,cwd:t,environment:n,toolchain:o})=>{if(o.devServer!=="workerd")return{skip:"this deploy target does not run on workerd"};const{assetsDirectory:s,durableObjectClasses:i,entrypointClasses:a,hasContainers:c,main:l,workflowClasses:d}=xr(t,n,r),u=e??l,f=r??D(t);let g;if(f!==void 0&&Oe(f)!==void 0)g={stubs:{durableObjects:i,entrypoints:a,workflows:d}};else if(typeof u=="string"&&!u.startsWith("virtual:")&&b(_(t,u)))g={main:u};else return{skip:"the Worker entry is not a file on disk"};const h=s!==void 0&&!b(s);if(c)return{skip:"the project declares containers, which `wrangler dev` would build locally before the real container build"};const v=Kt(t,process.env,n,r);return v.length>0?{skip:`the Hyperdrive binding ${v.join(", ")} has no local database for wrangler dev`}:{base:g,configFile:f,missingAssets:h}},zo=/The Workers runtime failed to start|Build failed with \d+ error/u,Jo=/\[ERROR\]|Uncaught/u,ir=e=>{const r=e.map(o=>bn(o).trim()).filter(o=>o!==""),t=r.filter(o=>Jo.test(o)),n=t.length>0?t.slice(0,10):r.slice(-10);return n.length===0?"":`
|
|
6
6
|
${n.map(o=>` ${o}`).join(`
|
|
7
7
|
`)}`},Le=async e=>{const{deadline:r,output:t,pending:n,port:o,query:s,startupFailed:i}=e,a=await fetch(`http://127.0.0.1:${String(o)}/_lunora/__smoke_load?${s}`,{signal:AbortSignal.any([AbortSignal.timeout(Math.max(0,r-Date.now())),i])}).catch(d=>{throw i.aborted?new j(`the smoke Worker's runtime did not start, so nothing was evaluated:${ir(t)}
|
|
8
8
|
Fix the entry, or pass --no-smoke-load to ship anyway.`):d instanceof Error&&d.name==="TimeoutError"?new j(n===void 0?`the smoke Worker accepted the connection but answered no request within ${String($e/1e3)}s, so its runtime did not start:${ir(t)}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/cli",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.486",
|
|
4
4
|
"description": "The Lunora CLI: init, dev, deploy, codegen, migrate, seed, doctor, insights, logs, registry, and the rest of the project commands",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"@lunora/d1": "^1.0.0-alpha.233",
|
|
65
65
|
"@lunora/errors": ">=1.0.0-alpha.51 <2.0.0-0",
|
|
66
66
|
"@lunora/mcp": "^1.0.0-alpha.291",
|
|
67
|
-
"@lunora/runtime": "^1.0.0-alpha.
|
|
67
|
+
"@lunora/runtime": "^1.0.0-alpha.278",
|
|
68
68
|
"@lunora/seed": "^1.0.0-alpha.282",
|
|
69
69
|
"@lunora/wire": "1.0.0-alpha.6",
|
|
70
70
|
"@visulima/cerebro": "4.0.1",
|
|
@@ -105,10 +105,20 @@ For the auth flows, `lunora add auth-emails` adds ready-made templates.
|
|
|
105
105
|
throws `` `queue` binding is required for mailer.queue() ``, except under
|
|
106
106
|
capture, where it sends straight to the capture inbox so dev flows keep working.
|
|
107
107
|
A consumer builds its mailer with `createMailerFromEnv` too, drains the queue
|
|
108
|
-
with `consumeQueuedSend(mailer, message.body)` and then calls
|
|
109
|
-
for each message
|
|
108
|
+
with `consumeQueuedSend(mailer, message.body, { attempts: message.attempts, enqueuedAt: message.timestamp, sent: env.SENT })` and then calls
|
|
109
|
+
`message.ack()` for each message; `sent` (a KV namespace) lets a redelivered message whose key it
|
|
110
|
+
already recorded ack without a second send. That narrows duplicates rather than preventing them: KV
|
|
111
|
+
is eventually consistent, and a crash between the provider accepting the send and the record being
|
|
112
|
+
written resends it. `enqueuedAt` makes a send that may have landed non-retryable once the message is
|
|
113
|
+
23 hours old, since Resend drops a repeated key for only 24 hours, and a redelivery that old whose
|
|
114
|
+
earlier delivery may have landed (the store's per-delivery marks, or `attempts` without a store) is refused
|
|
115
|
+
before it reaches the provider. On a throw, branch on `isRetryableMailError(error)`: retry with
|
|
110
116
|
`message.retry()` when it is true, and ack and log when it is false, since a refused
|
|
111
|
-
address or malformed body never sends and a retry only dead-letters it.
|
|
117
|
+
address or malformed body never sends and a retry only dead-letters it. A send
|
|
118
|
+
that timed out or hit Resend's 409 for a key still in flight (`isOutcomeUnknownMailError(error)`)
|
|
119
|
+
may have been delivered: it is retryable only through Resend with an idempotency key (Resend drops
|
|
120
|
+
the repeat), and then retried with `message.retry({ delaySeconds: 180 })` so the first attempt has
|
|
121
|
+
finished; otherwise it is acked and reported instead of resent. With a queue bound in dev, the message still goes through the
|
|
112
122
|
queue and the consumer's mailer captures it into the inbox. Queues deliver at least once: with
|
|
113
123
|
per-message acks a later throw retries only the unacknowledged messages, while
|
|
114
124
|
without them a single throw resends the whole batch. The full wiring is in the "Queueing"
|