From 579f8d399e12c188517f93d2e4ef3bd6f0d0b753 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 17 Feb 2026 12:12:35 +0100 Subject: [PATCH 1/8] presets: add `zephyr` --- package.json | 9 +- playground/nitro.config.ts | 1 + playground/public/test.txt | 1 + playground/server.ts | 4 +- pnpm-lock.yaml | 192 ++++++++++++++++++++++++--- src/presets/_all.gen.ts | 2 + src/presets/_types.gen.ts | 4 +- src/presets/zephyr/preset.ts | 47 +++++++ src/presets/zephyr/runtime/server.ts | 8 ++ src/presets/zephyr/utils.ts | 87 ++++++++++++ 10 files changed, 335 insertions(+), 20 deletions(-) create mode 100644 playground/public/test.txt create mode 100644 src/presets/zephyr/preset.ts create mode 100644 src/presets/zephyr/runtime/server.ts create mode 100644 src/presets/zephyr/utils.ts diff --git a/package.json b/package.json index fc76e69109..6aa5ccd1c8 100644 --- a/package.json +++ b/package.json @@ -168,7 +168,8 @@ "wrangler": "^4.65.0", "xml2js": "^0.6.2", "youch": "^4.1.0-beta.14", - "youch-core": "^0.3.3" + "youch-core": "^0.3.3", + "zephyr-agent": "^0.1.10" }, "peerDependencies": { "dotenv": "*", @@ -176,7 +177,8 @@ "jiti": "^2.6.1", "rollup": "^4.57.1", "vite": "^7 || ^8 || >=8.0.0-0", - "xml2js": "^0.6.2" + "xml2js": "^0.6.2", + "zephyr-agent": "*" }, "peerDependenciesMeta": { "dotenv": { @@ -196,6 +198,9 @@ }, "jiti": { "optional": true + }, + "zephyr-agent": { + "optional": true } }, "resolutions": { diff --git a/playground/nitro.config.ts b/playground/nitro.config.ts index 4d63605a93..6e09c04eb7 100644 --- a/playground/nitro.config.ts +++ b/playground/nitro.config.ts @@ -2,4 +2,5 @@ import { defineConfig } from "nitro"; export default defineConfig({ serverDir: "./server", + preset: "zephyr", }); diff --git a/playground/public/test.txt b/playground/public/test.txt new file mode 100644 index 0000000000..4fff881e87 --- /dev/null +++ b/playground/public/test.txt @@ -0,0 +1 @@ +Test File diff --git a/playground/server.ts b/playground/server.ts index 7d810949a2..20e55cdb61 100644 --- a/playground/server.ts +++ b/playground/server.ts @@ -1,5 +1,7 @@ export default { fetch(req: Request) { - return new Response("Hello from Nitro playground!"); + return new Response("Hello from Nitro playground! test.txt", { + headers: { "Content-Type": "text/html" }, + }); }, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index df170aed34..5bc5033cdf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,7 +96,7 @@ importers: version: 6.0.3(rollup@4.57.1) '@scalar/api-reference': specifier: ^1.44.20 - version: 1.44.20(axios@1.13.5)(jwt-decode@4.0.0)(tailwindcss@4.1.18)(typescript@5.9.3) + version: 1.44.20(axios@1.13.5(debug@4.4.3))(jwt-decode@4.0.0)(tailwindcss@4.1.18)(typescript@5.9.3) '@types/aws-lambda': specifier: ^8.10.160 version: 8.10.160 @@ -319,6 +319,9 @@ importers: youch-core: specifier: ^0.3.3 version: 0.3.3 + zephyr-agent: + specifier: ^0.1.10 + version: 0.1.10(https-proxy-agent@7.0.6) examples/api-routes: devDependencies: @@ -2756,6 +2759,9 @@ packages: '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@toon-format/toon@0.9.0': + resolution: {integrity: sha512-BaMhGh1+/z8ceDrF2xL9Drd42hijbUJlivm/1goPR26RgCYsqlMkHbg48hx9a5UjZC7oZqxWPJ6ju5qvALi6Ag==} + '@trpc/client@11.10.0': resolution: {integrity: sha512-h0s2AwDtuhS8INRb4hlo4z3RKCkarWqlOy+3ffJgrlDxzzW6aLUN+9nDrcN4huPje1Em15tbCOqhIc6oaKYTRw==} peerDependencies: @@ -3268,6 +3274,11 @@ packages: avvio@9.2.0: resolution: {integrity: sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ==} + axios-retry@4.5.0: + resolution: {integrity: sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==} + peerDependencies: + axios: 0.x || 1.x + axios@1.13.5: resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} @@ -3459,6 +3470,10 @@ packages: chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} @@ -3963,6 +3978,10 @@ packages: resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} engines: {node: '>=18.0.0'} + eventsource@4.1.0: + resolution: {integrity: sha512-2GuF51iuHX6A9xdTccMTsNb7VO0lHZihApxhvQzJB5A03DvHDd2FQepodbMaztPBmBcE/ox7o2gqaxGhYB9LhQ==} + engines: {node: '>=20.0.0'} + exact-mirror@0.2.7: resolution: {integrity: sha512-+MeEmDcLA4o/vjK2zujgk+1VTxPR4hdp23qLqkWfStbECtAq9gmsvQa3LW6z/0GXZyHJobrCnmy1cdeE7BjsYg==} peerDependencies: @@ -4173,6 +4192,12 @@ packages: resolution: {integrity: sha512-T2qUpKBHeUTwHcIhydgnJzhL0Hj785ms+JkxaaWQH9SDM/llXeewnOkfJcFShAHjWI+26hOChwUfCoupaXLm8g==} hasBin: true + git-up@7.0.0: + resolution: {integrity: sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==} + + git-url-parse@15.0.0: + resolution: {integrity: sha512-5reeBufLi+i4QD3ZFftcJs9jC26aULFLBU23FeKM/b1rI0K6ofIeAblmDVO7Ht22zTDE9+CkJ3ZVb0CgJmz3UQ==} + github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -4418,6 +4443,10 @@ packages: is-buffer@1.1.6: resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + is-ci@4.1.0: + resolution: {integrity: sha512-Ab9bQDQ11lWootZUI5qxgN2ZXwxNI5hTwnsvOc1wyxQ7zQ8OkEDw79mI0+9jI3x432NfwbVRru+3noJfXF6lSQ==} + hasBin: true + is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -4506,6 +4535,13 @@ packages: resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==} engines: {node: '>=12'} + is-retry-allowed@2.2.0: + resolution: {integrity: sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==} + engines: {node: '>=10'} + + is-ssh@1.4.1: + resolution: {integrity: sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -4570,6 +4606,9 @@ packages: joi@17.13.3: resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + jose@5.10.0: + resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} + jose@6.1.3: resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} @@ -5192,6 +5231,10 @@ packages: node-html-parser@6.1.13: resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} + node-persist@4.0.4: + resolution: {integrity: sha512-8sPAz/7tw1mCCc8xBG4f0wi+flHkSSgQeX998iQ75Pu27evA6UUWCjSE7xnrYTg2q33oU5leJ061EKPDv6BocQ==} + engines: {node: '>=10.12.0'} + node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -5308,6 +5351,10 @@ packages: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + p-timeout@3.2.0: resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} engines: {node: '>=8'} @@ -5338,6 +5385,12 @@ packages: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} + parse-path@7.1.0: + resolution: {integrity: sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==} + + parse-url@8.1.0: + resolution: {integrity: sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==} + parse5-htmlparser2-tree-adapter@7.1.0: resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} @@ -5463,12 +5516,18 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} + proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + protocols@2.0.2: + resolution: {integrity: sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -5646,6 +5705,10 @@ packages: resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} engines: {node: '>=10'} + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -6710,6 +6773,10 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + yoctocolors@2.1.2: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} @@ -6723,6 +6790,14 @@ packages: youch@4.1.0-beta.14: resolution: {integrity: sha512-VqcHA/HqOxaBMjBQCYz1h8jYdAAeLm6cVLmefijJjMY4aovOfKkqMry7amNX3JiN4hXArb7ZVYBNpjEVkV3r/A==} + zephyr-agent@0.1.10: + resolution: {integrity: sha512-wL9RDvemIEs3jh0LI0nloI0dbefFTDMuFDM+HEV1A1k6pLJ/xZ6gLmGp+by8idnCxH4+snDR5ITSbyetQa5vOQ==} + peerDependencies: + https-proxy-agent: ^7.0.6 + + zephyr-edge-contract@0.1.10: + resolution: {integrity: sha512-bAqXtBWljq4p13ko+oqo0FiLxK8Cqf382lxUHOEWaU3zn/ErQ20o40QVm+hXVwDFvovupI4vMQLmuGPUYF45NA==} + zhead@2.2.4: resolution: {integrity: sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==} @@ -8142,10 +8217,10 @@ snapshots: '@sagold/json-pointer': 5.1.2 ebnf: 1.9.1 - '@scalar/agent-chat@0.5.11(axios@1.13.5)(jwt-decode@4.0.0)(tailwindcss@4.1.18)(typescript@5.9.3)': + '@scalar/agent-chat@0.5.11(axios@1.13.5(debug@4.4.3))(jwt-decode@4.0.0)(tailwindcss@4.1.18)(typescript@5.9.3)': dependencies: '@ai-sdk/vue': 3.0.33(vue@3.5.28(typescript@5.9.3))(zod@4.3.6) - '@scalar/api-client': 2.27.1(axios@1.13.5)(jwt-decode@4.0.0)(tailwindcss@4.1.18)(typescript@5.9.3) + '@scalar/api-client': 2.27.1(axios@1.13.5(debug@4.4.3))(jwt-decode@4.0.0)(tailwindcss@4.1.18)(typescript@5.9.3) '@scalar/components': 0.19.4(typescript@5.9.3) '@scalar/helpers': 0.2.12 '@scalar/icons': 0.5.3(typescript@5.9.3) @@ -8182,7 +8257,7 @@ snapshots: dependencies: zod: 4.3.6 - '@scalar/api-client@2.27.1(axios@1.13.5)(jwt-decode@4.0.0)(tailwindcss@4.1.18)(typescript@5.9.3)': + '@scalar/api-client@2.27.1(axios@1.13.5(debug@4.4.3))(jwt-decode@4.0.0)(tailwindcss@4.1.18)(typescript@5.9.3)': dependencies: '@headlessui/tailwindcss': 0.2.2(tailwindcss@4.1.18) '@headlessui/vue': 1.7.23(vue@3.5.28(typescript@5.9.3)) @@ -8209,7 +8284,7 @@ snapshots: '@scalar/workspace-store': 0.32.1(typescript@5.9.3) '@types/har-format': 1.2.16 '@vueuse/core': 13.9.0(vue@3.5.28(typescript@5.9.3)) - '@vueuse/integrations': 13.9.0(axios@1.13.5)(focus-trap@7.8.0)(fuse.js@7.1.0)(jwt-decode@4.0.0)(vue@3.5.28(typescript@5.9.3)) + '@vueuse/integrations': 13.9.0(axios@1.13.5(debug@4.4.3))(focus-trap@7.8.0)(fuse.js@7.1.0)(jwt-decode@4.0.0)(vue@3.5.28(typescript@5.9.3)) focus-trap: 7.8.0 fuse.js: 7.1.0 js-base64: 3.7.8 @@ -8240,11 +8315,11 @@ snapshots: - typescript - universal-cookie - '@scalar/api-reference@1.44.20(axios@1.13.5)(jwt-decode@4.0.0)(tailwindcss@4.1.18)(typescript@5.9.3)': + '@scalar/api-reference@1.44.20(axios@1.13.5(debug@4.4.3))(jwt-decode@4.0.0)(tailwindcss@4.1.18)(typescript@5.9.3)': dependencies: '@headlessui/vue': 1.7.23(vue@3.5.28(typescript@5.9.3)) - '@scalar/agent-chat': 0.5.11(axios@1.13.5)(jwt-decode@4.0.0)(tailwindcss@4.1.18)(typescript@5.9.3) - '@scalar/api-client': 2.27.1(axios@1.13.5)(jwt-decode@4.0.0)(tailwindcss@4.1.18)(typescript@5.9.3) + '@scalar/agent-chat': 0.5.11(axios@1.13.5(debug@4.4.3))(jwt-decode@4.0.0)(tailwindcss@4.1.18)(typescript@5.9.3) + '@scalar/api-client': 2.27.1(axios@1.13.5(debug@4.4.3))(jwt-decode@4.0.0)(tailwindcss@4.1.18)(typescript@5.9.3) '@scalar/code-highlight': 0.2.3 '@scalar/components': 0.19.4(typescript@5.9.3) '@scalar/helpers': 0.2.12 @@ -8839,6 +8914,8 @@ snapshots: '@tokenizer/token@0.3.0': {} + '@toon-format/toon@0.9.0': {} + '@trpc/client@11.10.0(@trpc/server@11.10.0(typescript@5.9.3))(typescript@5.9.3)': dependencies: '@trpc/server': 11.10.0(typescript@5.9.3) @@ -9251,13 +9328,13 @@ snapshots: '@vueuse/shared': 13.9.0(vue@3.5.28(typescript@5.9.3)) vue: 3.5.28(typescript@5.9.3) - '@vueuse/integrations@13.9.0(axios@1.13.5)(focus-trap@7.8.0)(fuse.js@7.1.0)(jwt-decode@4.0.0)(vue@3.5.28(typescript@5.9.3))': + '@vueuse/integrations@13.9.0(axios@1.13.5(debug@4.4.3))(focus-trap@7.8.0)(fuse.js@7.1.0)(jwt-decode@4.0.0)(vue@3.5.28(typescript@5.9.3))': dependencies: '@vueuse/core': 13.9.0(vue@3.5.28(typescript@5.9.3)) '@vueuse/shared': 13.9.0(vue@3.5.28(typescript@5.9.3)) vue: 3.5.28(typescript@5.9.3) optionalDependencies: - axios: 1.13.5 + axios: 1.13.5(debug@4.4.3) focus-trap: 7.8.0 fuse.js: 7.1.0 jwt-decode: 4.0.0 @@ -9404,9 +9481,14 @@ snapshots: '@fastify/error': 4.2.0 fastq: 1.20.1 - axios@1.13.5: + axios-retry@4.5.0(axios@1.13.5(debug@4.4.3)): dependencies: - follow-redirects: 1.15.11 + axios: 1.13.5(debug@4.4.3) + is-retry-allowed: 2.2.0 + + axios@1.13.5(debug@4.4.3): + dependencies: + follow-redirects: 1.15.11(debug@4.4.3) form-data: 4.0.5 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -9654,6 +9736,8 @@ snapshots: chownr@1.1.4: {} + ci-info@4.4.0: {} + citty@0.1.6: dependencies: consola: 3.4.2 @@ -10085,6 +10169,10 @@ snapshots: eventsource-parser@3.0.6: {} + eventsource@4.1.0: + dependencies: + eventsource-parser: 3.0.6 + exact-mirror@0.2.7(@sinclair/typebox@0.34.48): optionalDependencies: '@sinclair/typebox': 0.34.48 @@ -10258,7 +10346,9 @@ snapshots: dependencies: tabbable: 6.4.0 - follow-redirects@1.15.11: {} + follow-redirects@1.15.11(debug@4.4.3): + optionalDependencies: + debug: 4.4.3 foreground-child@3.3.1: dependencies: @@ -10352,6 +10442,15 @@ snapshots: giget@3.1.2: {} + git-up@7.0.0: + dependencies: + is-ssh: 1.4.1 + parse-url: 8.1.0 + + git-url-parse@15.0.0: + dependencies: + git-up: 7.0.0 + github-from-package@0.0.0: {} github-slugger@2.0.0: {} @@ -10596,7 +10695,7 @@ snapshots: http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.11 + follow-redirects: 1.15.11(debug@4.4.3) requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -10660,6 +10759,10 @@ snapshots: is-buffer@1.1.6: {} + is-ci@4.1.0: + dependencies: + ci-info: 4.4.0 + is-core-module@2.16.1: dependencies: hasown: 2.0.2 @@ -10722,6 +10825,12 @@ snapshots: is-regexp@3.1.0: {} + is-retry-allowed@2.2.0: {} + + is-ssh@1.4.1: + dependencies: + protocols: 2.0.2 + is-stream@2.0.1: {} is-stream@4.0.1: {} @@ -10781,6 +10890,8 @@ snapshots: '@sideway/formula': 3.0.1 '@sideway/pinpoint': 2.0.0 + jose@5.10.0: {} + jose@6.1.3: {} js-base64@3.7.8: {} @@ -11499,6 +11610,10 @@ snapshots: css-select: 5.2.2 he: 1.2.0 + node-persist@4.0.4: + dependencies: + p-limit: 3.1.0 + node-releases@2.0.27: {} normalize-path@3.0.0: {} @@ -11673,6 +11788,10 @@ snapshots: p-finally@1.0.0: {} + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + p-timeout@3.2.0: dependencies: p-finally: 1.0.0 @@ -11696,6 +11815,14 @@ snapshots: parse-ms@4.0.0: {} + parse-path@7.1.0: + dependencies: + protocols: 2.0.2 + + parse-url@8.1.0: + dependencies: + parse-path: 7.1.0 + parse5-htmlparser2-tree-adapter@7.1.0: dependencies: domhandler: 5.0.3 @@ -11836,10 +11963,18 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + property-information@7.1.0: {} proto-list@1.2.4: {} + protocols@2.0.2: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -12065,6 +12200,8 @@ snapshots: ret@0.5.0: {} + retry@0.12.0: {} + reusify@1.1.0: {} rfdc@1.4.1: {} @@ -13014,7 +13151,7 @@ snapshots: wait-on@7.2.0: dependencies: - axios: 1.13.5 + axios: 1.13.5(debug@4.4.3) joi: 17.13.3 lodash: 4.17.23 minimist: 1.2.8 @@ -13145,6 +13282,8 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yocto-queue@0.1.0: {} + yoctocolors@2.1.2: {} youch-core@0.3.3: @@ -13168,6 +13307,29 @@ snapshots: cookie-es: 2.0.0 youch-core: 0.3.3 + zephyr-agent@0.1.10(https-proxy-agent@7.0.6): + dependencies: + '@toon-format/toon': 0.9.0 + axios: 1.13.5(debug@4.4.3) + axios-retry: 4.5.0(axios@1.13.5(debug@4.4.3)) + debug: 4.4.3 + eventsource: 4.1.0 + git-url-parse: 15.0.0 + https-proxy-agent: 7.0.6 + is-ci: 4.1.0 + jose: 5.10.0 + node-persist: 4.0.4 + open: 10.2.0 + proper-lockfile: 4.1.2 + tslib: 2.8.1 + zephyr-edge-contract: 0.1.10 + transitivePeerDependencies: + - supports-color + + zephyr-edge-contract@0.1.10: + dependencies: + tslib: 2.8.1 + zhead@2.2.4: {} zimmerframe@1.1.4: {} diff --git a/src/presets/_all.gen.ts b/src/presets/_all.gen.ts index 0aabd48fde..18730bb704 100644 --- a/src/presets/_all.gen.ts +++ b/src/presets/_all.gen.ts @@ -26,6 +26,7 @@ import _stormkit from "./stormkit/preset.ts"; import _vercel from "./vercel/preset.ts"; import _winterjs from "./winterjs/preset.ts"; import _zeabur from "./zeabur/preset.ts"; +import _zephyr from "./zephyr/preset.ts"; import _zerops from "./zerops/preset.ts"; export default [ @@ -55,5 +56,6 @@ export default [ ..._vercel, ..._winterjs, ..._zeabur, + ..._zephyr, ..._zerops, ] as const; diff --git a/src/presets/_types.gen.ts b/src/presets/_types.gen.ts index 974925a21a..b6f6f9291f 100644 --- a/src/presets/_types.gen.ts +++ b/src/presets/_types.gen.ts @@ -20,6 +20,6 @@ export interface PresetOptions { export const presetsWithConfig = ["awsAmplify","awsLambda","azure","cloudflare","firebase","netlify","vercel"] as const; -export type PresetName = "alwaysdata" | "aws-amplify" | "aws-lambda" | "azure-swa" | "base-worker" | "bun" | "cleavr" | "cloudflare-dev" | "cloudflare-durable" | "cloudflare-module" | "cloudflare-pages" | "cloudflare-pages-static" | "deno" | "deno-deploy" | "deno-server" | "digital-ocean" | "firebase-app-hosting" | "flight-control" | "genezio" | "github-pages" | "gitlab-pages" | "heroku" | "iis-handler" | "iis-node" | "koyeb" | "netlify" | "netlify-edge" | "netlify-static" | "nitro-dev" | "nitro-prerender" | "node" | "node-cluster" | "node-middleware" | "node-server" | "platform-sh" | "render-com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-static" | "winterjs" | "zeabur" | "zeabur-static" | "zerops" | "zerops-static"; +export type PresetName = "alwaysdata" | "aws-amplify" | "aws-lambda" | "azure-swa" | "base-worker" | "bun" | "cleavr" | "cloudflare-dev" | "cloudflare-durable" | "cloudflare-module" | "cloudflare-pages" | "cloudflare-pages-static" | "deno" | "deno-deploy" | "deno-server" | "digital-ocean" | "firebase-app-hosting" | "flight-control" | "genezio" | "github-pages" | "gitlab-pages" | "heroku" | "iis-handler" | "iis-node" | "koyeb" | "netlify" | "netlify-edge" | "netlify-static" | "nitro-dev" | "nitro-prerender" | "node" | "node-cluster" | "node-middleware" | "node-server" | "platform-sh" | "render-com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-static" | "winterjs" | "zeabur" | "zeabur-static" | "zephyr`" | "zerops" | "zerops-static"; -export type PresetNameInput = "alwaysdata" | "aws-amplify" | "awsAmplify" | "aws_amplify" | "aws-lambda" | "awsLambda" | "aws_lambda" | "azure-swa" | "azureSwa" | "azure_swa" | "base-worker" | "baseWorker" | "base_worker" | "bun" | "cleavr" | "cloudflare-dev" | "cloudflareDev" | "cloudflare_dev" | "cloudflare-durable" | "cloudflareDurable" | "cloudflare_durable" | "cloudflare-module" | "cloudflareModule" | "cloudflare_module" | "cloudflare-pages" | "cloudflarePages" | "cloudflare_pages" | "cloudflare-pages-static" | "cloudflarePagesStatic" | "cloudflare_pages_static" | "deno" | "deno-deploy" | "denoDeploy" | "deno_deploy" | "deno-server" | "denoServer" | "deno_server" | "digital-ocean" | "digitalOcean" | "digital_ocean" | "firebase-app-hosting" | "firebaseAppHosting" | "firebase_app_hosting" | "flight-control" | "flightControl" | "flight_control" | "genezio" | "github-pages" | "githubPages" | "github_pages" | "gitlab-pages" | "gitlabPages" | "gitlab_pages" | "heroku" | "iis-handler" | "iisHandler" | "iis_handler" | "iis-node" | "iisNode" | "iis_node" | "koyeb" | "netlify" | "netlify-edge" | "netlifyEdge" | "netlify_edge" | "netlify-static" | "netlifyStatic" | "netlify_static" | "nitro-dev" | "nitroDev" | "nitro_dev" | "nitro-prerender" | "nitroPrerender" | "nitro_prerender" | "node" | "node-cluster" | "nodeCluster" | "node_cluster" | "node-middleware" | "nodeMiddleware" | "node_middleware" | "node-server" | "nodeServer" | "node_server" | "platform-sh" | "platformSh" | "platform_sh" | "render-com" | "renderCom" | "render_com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-static" | "vercelStatic" | "vercel_static" | "winterjs" | "zeabur" | "zeabur-static" | "zeaburStatic" | "zeabur_static" | "zerops" | "zerops-static" | "zeropsStatic" | "zerops_static" | (string & {}); +export type PresetNameInput = "alwaysdata" | "aws-amplify" | "awsAmplify" | "aws_amplify" | "aws-lambda" | "awsLambda" | "aws_lambda" | "azure-swa" | "azureSwa" | "azure_swa" | "base-worker" | "baseWorker" | "base_worker" | "bun" | "cleavr" | "cloudflare-dev" | "cloudflareDev" | "cloudflare_dev" | "cloudflare-durable" | "cloudflareDurable" | "cloudflare_durable" | "cloudflare-module" | "cloudflareModule" | "cloudflare_module" | "cloudflare-pages" | "cloudflarePages" | "cloudflare_pages" | "cloudflare-pages-static" | "cloudflarePagesStatic" | "cloudflare_pages_static" | "deno" | "deno-deploy" | "denoDeploy" | "deno_deploy" | "deno-server" | "denoServer" | "deno_server" | "digital-ocean" | "digitalOcean" | "digital_ocean" | "firebase-app-hosting" | "firebaseAppHosting" | "firebase_app_hosting" | "flight-control" | "flightControl" | "flight_control" | "genezio" | "github-pages" | "githubPages" | "github_pages" | "gitlab-pages" | "gitlabPages" | "gitlab_pages" | "heroku" | "iis-handler" | "iisHandler" | "iis_handler" | "iis-node" | "iisNode" | "iis_node" | "koyeb" | "netlify" | "netlify-edge" | "netlifyEdge" | "netlify_edge" | "netlify-static" | "netlifyStatic" | "netlify_static" | "nitro-dev" | "nitroDev" | "nitro_dev" | "nitro-prerender" | "nitroPrerender" | "nitro_prerender" | "node" | "node-cluster" | "nodeCluster" | "node_cluster" | "node-middleware" | "nodeMiddleware" | "node_middleware" | "node-server" | "nodeServer" | "node_server" | "platform-sh" | "platformSh" | "platform_sh" | "render-com" | "renderCom" | "render_com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-static" | "vercelStatic" | "vercel_static" | "winterjs" | "zeabur" | "zeabur-static" | "zeaburStatic" | "zeabur_static" | "zephyr`" | "zerops" | "zerops-static" | "zeropsStatic" | "zerops_static" | (string & {}); diff --git a/src/presets/zephyr/preset.ts b/src/presets/zephyr/preset.ts new file mode 100644 index 0000000000..7a62152752 --- /dev/null +++ b/src/presets/zephyr/preset.ts @@ -0,0 +1,47 @@ +import { defineNitroPreset } from "../_utils/preset.ts"; +import { createUploadRunner } from "./utils.ts"; +import type { Nitro } from "nitro/types"; + +const zephyr = defineNitroPreset( + { + extends: "standard", + entry: "./standard/runtime/server", + serveStatic: "inline", + hooks: { + "build:before"() { + // TODO: Determine preset here.... + }, + compiled: async (nitro: Nitro) => { + const startTime = Date.now(); + const { ZephyrEngine } = await import("zephyr-agent"); + const { zephyr_engine_defer, zephyr_defer_create } = ZephyrEngine.defer_create(); + let initialized = false; + + const initEngine = () => { + if (initialized) return; + initialized = true; + zephyr_defer_create({ + builder: "unknown", + context: nitro.options.rootDir, + }); + }; + + const runUpload = createUploadRunner({ + nitro, + zephyrEngineDefer: zephyr_engine_defer, + initEngine, + }); + + await runUpload(); + + const endTime = Date.now(); + console.log(`Zephyr deploy completed in ${(endTime - startTime) / 1000}s`); + }, + }, + }, + { + name: "zephyr" as const, + } +); + +export default [zephyr] as const; diff --git a/src/presets/zephyr/runtime/server.ts b/src/presets/zephyr/runtime/server.ts new file mode 100644 index 0000000000..a67e6bb575 --- /dev/null +++ b/src/presets/zephyr/runtime/server.ts @@ -0,0 +1,8 @@ +import "#nitro/virtual/polyfills"; +import { useNitroApp } from "nitro/app"; + +const nitroApp = useNitroApp(); + +export default { + fetch: nitroApp.fetch, +}; diff --git a/src/presets/zephyr/utils.ts b/src/presets/zephyr/utils.ts new file mode 100644 index 0000000000..7d9e8921a2 --- /dev/null +++ b/src/presets/zephyr/utils.ts @@ -0,0 +1,87 @@ +import type { Nitro } from "nitro/types"; +import { resolve, normalize, relative } from "pathe"; +import { existsSync } from "node:fs"; +import { + type ZephyrEngine, + buildAssetsMap, + handleGlobalError, + readDirRecursiveWithContents, + zeBuildDashData, + ze_log, +} from "zephyr-agent"; + +export function createUploadRunner({ + nitro, + zephyrEngineDefer, + initEngine, +}: { + nitro: Nitro; + zephyrEngineDefer: Promise; + initEngine: () => void; +}) { + let uploadCalled = false; + return async () => { + if (uploadCalled) return; + uploadCalled = true; + initEngine(); + + try { + const zephyr_engine = await zephyrEngineDefer; + ze_log.upload("Nuxt build done. Preparing Zephyr upload..."); + + const serverDir = resolve(nitro.options.output.dir, nitro.options.output.serverDir); + + const publicDir = resolve(nitro.options.output.dir, nitro.options.output.publicDir); + + let entrypoint: string | undefined = resolve(serverDir, "index.mjs"); + + if (!existsSync(entrypoint)) { + entrypoint = undefined; + } + + const snapshotType = entrypoint ? "ssr" : "csr"; + + ze_log.upload(`Zephyr upload starting. snapshotType=${snapshotType} output=${publicDir}`); + if (entrypoint) { + ze_log.upload(`Zephyr entrypoint: ${entrypoint}`); + } + + zephyr_engine.env.ssr = snapshotType === "ssr"; + zephyr_engine.buildProperties.output = publicDir; + zephyr_engine.buildProperties.baseHref = nitro.options.baseURL; + + const files = await readDirRecursiveWithContents(nitro.options.output.dir); + if (!files.length) { + ze_log.upload(`No build output found in ${publicDir}`); + return; + } + + const assets: Record = files.reduce( + (memo, file) => { + const relativePath = normalize(file.relativePath); + memo[relativePath] = file.content; + return memo; + }, + {} as Record + ); + + const assetsMap = buildAssetsMap( + assets, + (asset) => asset, + () => "buffer" + ); + + await zephyr_engine.upload_assets({ + assetsMap, + buildStats: await zeBuildDashData(zephyr_engine), + snapshotType, + entrypoint: relative(nitro.options.rootDir, entrypoint || ""), + }); + + await zephyr_engine.build_finished(); + ze_log.upload("Zephyr upload complete."); + } catch (error) { + handleGlobalError(error); + } + }; +} From 4194ee693c4623c0ae92e51f2c2c62efe1ec9020 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 11:17:37 +0000 Subject: [PATCH 2/8] chore: apply automated updates --- src/presets/_types.gen.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/presets/_types.gen.ts b/src/presets/_types.gen.ts index b6f6f9291f..b88572846f 100644 --- a/src/presets/_types.gen.ts +++ b/src/presets/_types.gen.ts @@ -20,6 +20,6 @@ export interface PresetOptions { export const presetsWithConfig = ["awsAmplify","awsLambda","azure","cloudflare","firebase","netlify","vercel"] as const; -export type PresetName = "alwaysdata" | "aws-amplify" | "aws-lambda" | "azure-swa" | "base-worker" | "bun" | "cleavr" | "cloudflare-dev" | "cloudflare-durable" | "cloudflare-module" | "cloudflare-pages" | "cloudflare-pages-static" | "deno" | "deno-deploy" | "deno-server" | "digital-ocean" | "firebase-app-hosting" | "flight-control" | "genezio" | "github-pages" | "gitlab-pages" | "heroku" | "iis-handler" | "iis-node" | "koyeb" | "netlify" | "netlify-edge" | "netlify-static" | "nitro-dev" | "nitro-prerender" | "node" | "node-cluster" | "node-middleware" | "node-server" | "platform-sh" | "render-com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-static" | "winterjs" | "zeabur" | "zeabur-static" | "zephyr`" | "zerops" | "zerops-static"; +export type PresetName = "alwaysdata" | "aws-amplify" | "aws-lambda" | "azure-swa" | "base-worker" | "bun" | "cleavr" | "cloudflare-dev" | "cloudflare-durable" | "cloudflare-module" | "cloudflare-pages" | "cloudflare-pages-static" | "deno" | "deno-deploy" | "deno-server" | "digital-ocean" | "firebase-app-hosting" | "flight-control" | "genezio" | "github-pages" | "gitlab-pages" | "heroku" | "iis-handler" | "iis-node" | "koyeb" | "netlify" | "netlify-edge" | "netlify-static" | "nitro-dev" | "nitro-prerender" | "node" | "node-cluster" | "node-middleware" | "node-server" | "platform-sh" | "render-com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-static" | "winterjs" | "zeabur" | "zeabur-static" | "zephyr" | "zerops" | "zerops-static"; -export type PresetNameInput = "alwaysdata" | "aws-amplify" | "awsAmplify" | "aws_amplify" | "aws-lambda" | "awsLambda" | "aws_lambda" | "azure-swa" | "azureSwa" | "azure_swa" | "base-worker" | "baseWorker" | "base_worker" | "bun" | "cleavr" | "cloudflare-dev" | "cloudflareDev" | "cloudflare_dev" | "cloudflare-durable" | "cloudflareDurable" | "cloudflare_durable" | "cloudflare-module" | "cloudflareModule" | "cloudflare_module" | "cloudflare-pages" | "cloudflarePages" | "cloudflare_pages" | "cloudflare-pages-static" | "cloudflarePagesStatic" | "cloudflare_pages_static" | "deno" | "deno-deploy" | "denoDeploy" | "deno_deploy" | "deno-server" | "denoServer" | "deno_server" | "digital-ocean" | "digitalOcean" | "digital_ocean" | "firebase-app-hosting" | "firebaseAppHosting" | "firebase_app_hosting" | "flight-control" | "flightControl" | "flight_control" | "genezio" | "github-pages" | "githubPages" | "github_pages" | "gitlab-pages" | "gitlabPages" | "gitlab_pages" | "heroku" | "iis-handler" | "iisHandler" | "iis_handler" | "iis-node" | "iisNode" | "iis_node" | "koyeb" | "netlify" | "netlify-edge" | "netlifyEdge" | "netlify_edge" | "netlify-static" | "netlifyStatic" | "netlify_static" | "nitro-dev" | "nitroDev" | "nitro_dev" | "nitro-prerender" | "nitroPrerender" | "nitro_prerender" | "node" | "node-cluster" | "nodeCluster" | "node_cluster" | "node-middleware" | "nodeMiddleware" | "node_middleware" | "node-server" | "nodeServer" | "node_server" | "platform-sh" | "platformSh" | "platform_sh" | "render-com" | "renderCom" | "render_com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-static" | "vercelStatic" | "vercel_static" | "winterjs" | "zeabur" | "zeabur-static" | "zeaburStatic" | "zeabur_static" | "zephyr`" | "zerops" | "zerops-static" | "zeropsStatic" | "zerops_static" | (string & {}); +export type PresetNameInput = "alwaysdata" | "aws-amplify" | "awsAmplify" | "aws_amplify" | "aws-lambda" | "awsLambda" | "aws_lambda" | "azure-swa" | "azureSwa" | "azure_swa" | "base-worker" | "baseWorker" | "base_worker" | "bun" | "cleavr" | "cloudflare-dev" | "cloudflareDev" | "cloudflare_dev" | "cloudflare-durable" | "cloudflareDurable" | "cloudflare_durable" | "cloudflare-module" | "cloudflareModule" | "cloudflare_module" | "cloudflare-pages" | "cloudflarePages" | "cloudflare_pages" | "cloudflare-pages-static" | "cloudflarePagesStatic" | "cloudflare_pages_static" | "deno" | "deno-deploy" | "denoDeploy" | "deno_deploy" | "deno-server" | "denoServer" | "deno_server" | "digital-ocean" | "digitalOcean" | "digital_ocean" | "firebase-app-hosting" | "firebaseAppHosting" | "firebase_app_hosting" | "flight-control" | "flightControl" | "flight_control" | "genezio" | "github-pages" | "githubPages" | "github_pages" | "gitlab-pages" | "gitlabPages" | "gitlab_pages" | "heroku" | "iis-handler" | "iisHandler" | "iis_handler" | "iis-node" | "iisNode" | "iis_node" | "koyeb" | "netlify" | "netlify-edge" | "netlifyEdge" | "netlify_edge" | "netlify-static" | "netlifyStatic" | "netlify_static" | "nitro-dev" | "nitroDev" | "nitro_dev" | "nitro-prerender" | "nitroPrerender" | "nitro_prerender" | "node" | "node-cluster" | "nodeCluster" | "node_cluster" | "node-middleware" | "nodeMiddleware" | "node_middleware" | "node-server" | "nodeServer" | "node_server" | "platform-sh" | "platformSh" | "platform_sh" | "render-com" | "renderCom" | "render_com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-static" | "vercelStatic" | "vercel_static" | "winterjs" | "zeabur" | "zeabur-static" | "zeaburStatic" | "zeabur_static" | "zephyr" | "zerops" | "zerops-static" | "zeropsStatic" | "zerops_static" | (string & {}); From 90707d4903f75dd344c59726c2f3a08ced163d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Tue, 17 Feb 2026 17:48:27 +0100 Subject: [PATCH 3/8] feat: zephyr preset that pulls cloud info from zephyr --- src/presets/zephyr/preset.ts | 81 +++++---- src/presets/zephyr/utils.ts | 303 +++++++++++++++++++++++--------- test/unit/zephyr-preset.test.ts | 61 +++++++ 3 files changed, 333 insertions(+), 112 deletions(-) create mode 100644 test/unit/zephyr-preset.test.ts diff --git a/src/presets/zephyr/preset.ts b/src/presets/zephyr/preset.ts index 7a62152752..ea05fe7202 100644 --- a/src/presets/zephyr/preset.ts +++ b/src/presets/zephyr/preset.ts @@ -1,41 +1,62 @@ import { defineNitroPreset } from "../_utils/preset.ts"; -import { createUploadRunner } from "./utils.ts"; import type { Nitro } from "nitro/types"; +import { + DEFAULT_BASE_PRESET, + LOGGER_TAG, + ZEPHYR_PLATFORM, + createZephyrMetadataPlugin, + resolveBundlerOutputDir, + toError, + uploadNitroOutputToZephyr, +} from "./utils.ts"; const zephyr = defineNitroPreset( { - extends: "standard", - entry: "./standard/runtime/server", - serveStatic: "inline", + extends: DEFAULT_BASE_PRESET, hooks: { - "build:before"() { - // TODO: Determine preset here.... + "build:before": (nitro: Nitro) => { + nitro.logger.info( + `[${LOGGER_TAG}] PLATFORM=${ZEPHYR_PLATFORM}; using preset \`${DEFAULT_BASE_PRESET}\`.` + ); + }, + "rollup:before": (nitro: Nitro, config) => { + const outputDir = resolveBundlerOutputDir(nitro, config); + const plugin = createZephyrMetadataPlugin(nitro, outputDir); + + if (!Array.isArray(config.plugins)) { + config.plugins = [plugin]; + return; + } + + config.plugins.push(plugin); }, compiled: async (nitro: Nitro) => { - const startTime = Date.now(); - const { ZephyrEngine } = await import("zephyr-agent"); - const { zephyr_engine_defer, zephyr_defer_create } = ZephyrEngine.defer_create(); - let initialized = false; - - const initEngine = () => { - if (initialized) return; - initialized = true; - zephyr_defer_create({ - builder: "unknown", - context: nitro.options.rootDir, - }); - }; - - const runUpload = createUploadRunner({ - nitro, - zephyrEngineDefer: zephyr_engine_defer, - initEngine, - }); - - await runUpload(); - - const endTime = Date.now(); - console.log(`Zephyr deploy completed in ${(endTime - startTime) / 1000}s`); + const deployOutputDir = nitro.options.output.dir; + nitro.logger.info( + `[${LOGGER_TAG}] Uploading Nitro output to Zephyr from ${deployOutputDir}.` + ); + + try { + const { deploymentUrl, entrypoint } = await uploadNitroOutputToZephyr( + nitro, + deployOutputDir + ); + + if (entrypoint) { + nitro.logger.info(`[${LOGGER_TAG}] Zephyr SSR entrypoint: ${entrypoint}.`); + } + + if (deploymentUrl) { + nitro.logger.success(`[${LOGGER_TAG}] Zephyr deployment URL: ${deploymentUrl}`); + return; + } + + nitro.logger.success( + `[${LOGGER_TAG}] Zephyr deploy completed but no deployment URL was returned.` + ); + } catch (error) { + throw toError(error); + } }, }, }, diff --git a/src/presets/zephyr/utils.ts b/src/presets/zephyr/utils.ts index 7d9e8921a2..fa34fb8a95 100644 --- a/src/presets/zephyr/utils.ts +++ b/src/presets/zephyr/utils.ts @@ -1,87 +1,226 @@ -import type { Nitro } from "nitro/types"; -import { resolve, normalize, relative } from "pathe"; -import { existsSync } from "node:fs"; -import { - type ZephyrEngine, - buildAssetsMap, - handleGlobalError, - readDirRecursiveWithContents, - zeBuildDashData, - ze_log, -} from "zephyr-agent"; - -export function createUploadRunner({ - nitro, - zephyrEngineDefer, - initEngine, -}: { - nitro: Nitro; - zephyrEngineDefer: Promise; - initEngine: () => void; -}) { - let uploadCalled = false; - return async () => { - if (uploadCalled) return; - uploadCalled = true; - initEngine(); - - try { - const zephyr_engine = await zephyrEngineDefer; - ze_log.upload("Nuxt build done. Preparing Zephyr upload..."); - - const serverDir = resolve(nitro.options.output.dir, nitro.options.output.serverDir); - - const publicDir = resolve(nitro.options.output.dir, nitro.options.output.publicDir); - - let entrypoint: string | undefined = resolve(serverDir, "index.mjs"); - - if (!existsSync(entrypoint)) { - entrypoint = undefined; - } - - const snapshotType = entrypoint ? "ssr" : "csr"; - - ze_log.upload(`Zephyr upload starting. snapshotType=${snapshotType} output=${publicDir}`); - if (entrypoint) { - ze_log.upload(`Zephyr entrypoint: ${entrypoint}`); - } - - zephyr_engine.env.ssr = snapshotType === "ssr"; - zephyr_engine.buildProperties.output = publicDir; - zephyr_engine.buildProperties.baseHref = nitro.options.baseURL; - - const files = await readDirRecursiveWithContents(nitro.options.output.dir); - if (!files.length) { - ze_log.upload(`No build output found in ${publicDir}`); - return; - } - - const assets: Record = files.reduce( - (memo, file) => { - const relativePath = normalize(file.relativePath); - memo[relativePath] = file.content; - return memo; - }, - {} as Record - ); +import type { Nitro, RollupConfig } from "nitro/types"; +import type { Plugin } from "rollup"; +import { dirname, isAbsolute, join, normalize, relative } from "pathe"; - const assetsMap = buildAssetsMap( - assets, - (asset) => asset, - () => "buffer" - ); +export const LOGGER_TAG = "zephyr-nitro-preset"; +export const ZEPHYR_PLATFORM = "cloudflare"; +export const PLATFORM_PRESET_MAP = { + cloudflare: "cloudflare-module", +} as const; +export const DEFAULT_BASE_PRESET = PLATFORM_PRESET_MAP[ZEPHYR_PLATFORM]; + +const DEFAULT_METADATA_FILE = ".zephyr/nitro-build.json"; +const DEFAULT_DEPLOY_TARGET = "web"; +const DEFAULT_DEPLOY_SSR = true; +const SKIP_DEPLOY_PATTERNS = [/\.map$/i, /node_modules\//i, /\.git\//i, /\.DS_Store$/i]; + +interface ZephyrNitroBuildMetadata { + generatedBy: "zephyr-nitro-preset"; + preset: string; + outputDir: string; +} + +interface DirectoryAsset { + content: Buffer; +} + +function normalizeMetadataFile(metadataFile: string): string { + if (isAbsolute(metadataFile)) { + throw new TypeError( + `[${LOGGER_TAG}] \`metadataFile\` must be relative when using the bundler lifecycle.` + ); + } + + const normalized = normalize(metadataFile).replace(/\\/g, "/").replace(/^\/+/, ""); + if (!normalized || normalized === ".") { + throw new TypeError(`[${LOGGER_TAG}] \`metadataFile\` must point to a file path.`); + } + + return normalized; +} + +function toPosixPath(filePath: string): string { + return filePath.replace(/\\/g, "/"); +} + +function shouldSkipDeployAsset(filePath: string): boolean { + return SKIP_DEPLOY_PATTERNS.some((pattern) => pattern.test(filePath)); +} - await zephyr_engine.upload_assets({ - assetsMap, - buildStats: await zeBuildDashData(zephyr_engine), - snapshotType, - entrypoint: relative(nitro.options.rootDir, entrypoint || ""), - }); - - await zephyr_engine.build_finished(); - ze_log.upload("Zephyr upload complete."); - } catch (error) { - handleGlobalError(error); +function normalizeEntrypoint(entrypoint: string): string { + let normalized = entrypoint.trim().replace(/\\/g, "/"); + while (normalized.startsWith("./")) { + normalized = normalized.slice(2); + } + while (normalized.startsWith("/")) { + normalized = normalized.slice(1); + } + return normalized; +} + +function resolveDeployEntrypoint(assets: Record): string | undefined { + const candidates = ["index.mjs", "index.js", "server/index.mjs", "server/index.js"]; + for (const candidate of candidates) { + if (Object.prototype.hasOwnProperty.call(assets, candidate)) { + return candidate; + } + } + + return undefined; +} + +function toImportSpecifier(fromFile: string, toFile: string): string { + const relativePath = relative(dirname(fromFile), toFile); + if (!relativePath) { + return "./"; + } + return relativePath.startsWith(".") ? relativePath : `./${relativePath}`; +} + +function createCloudflareEntrypointWrapper(entrypoint: string): { + wrapperPath: string; + source: string; +} { + const wrapperPath = ".zephyr/entrypoint.mjs"; + const importPath = toImportSpecifier(wrapperPath, entrypoint); + const source = `import nitroHandler from '${importPath}'; + +const base = nitroHandler?.default ?? nitroHandler; + +export default { + ...base, + async fetch(request, env = {}, context) { + if (typeof base?.fetch === 'function') { + return base.fetch(request, env ?? {}, context); } + + if (typeof base === 'function') { + return base(request, env ?? {}, context); + } + + throw new TypeError('[${LOGGER_TAG}] Invalid Nitro Cloudflare entrypoint export.'); + }, +}; +`; + + return { wrapperPath, source }; +} + +function createZephyrNitroMetadata(nitro: Nitro, outputDir: string): ZephyrNitroBuildMetadata { + return { + generatedBy: "zephyr-nitro-preset", + preset: nitro.options.preset, + outputDir, + }; +} + +function createZephyrNitroMetadataAsset(nitro: Nitro, outputDir: string) { + const fileName = normalizeMetadataFile(DEFAULT_METADATA_FILE); + return { + type: "asset" as const, + fileName, + source: `${JSON.stringify(createZephyrNitroMetadata(nitro, outputDir), null, 2)}\n`, }; } + +export function resolveBundlerOutputDir(nitro: Nitro, config: RollupConfig): string { + const output = Array.isArray(config.output) ? config.output[0] : config.output; + return output?.dir || nitro.options.output.serverDir || nitro.options.output.dir; +} + +export function createZephyrMetadataPlugin(nitro: Nitro, outputDir: string): Plugin { + const metadataPath = normalizeMetadataFile(DEFAULT_METADATA_FILE); + const emittedMetadataPath = join(outputDir, metadataPath); + + return { + name: "zephyr-nitro-metadata", + generateBundle() { + const asset = createZephyrNitroMetadataAsset(nitro, outputDir); + this.emitFile(asset); + nitro.logger.success( + `[${LOGGER_TAG}] Emitted Nitro metadata asset at ${emittedMetadataPath}.` + ); + }, + }; +} + +export async function uploadNitroOutputToZephyr( + nitro: Nitro, + outputDir: string +): Promise<{ deploymentUrl: string | null; entrypoint?: string }> { + const zephyrAgent = await import("zephyr-agent"); + const files = await zephyrAgent.readDirRecursiveWithContents(outputDir); + + const assets = files.reduce>((memo, file) => { + const relativePath = toPosixPath(file.relativePath); + if (shouldSkipDeployAsset(relativePath)) { + return memo; + } + + memo[relativePath] = { + content: file.content, + }; + return memo; + }, {}); + + if (Object.keys(assets).length === 0) { + throw new TypeError(`[${LOGGER_TAG}] No deployable assets found in ${outputDir}.`); + } + + let entrypoint = resolveDeployEntrypoint(assets); + + if (DEFAULT_DEPLOY_SSR && entrypoint) { + const { wrapperPath, source } = createCloudflareEntrypointWrapper( + normalizeEntrypoint(entrypoint) + ); + assets[wrapperPath] = { + content: Buffer.from(source, "utf8"), + }; + entrypoint = wrapperPath; + } + + const assetsMap = zephyrAgent.buildAssetsMap( + assets, + (asset: DirectoryAsset) => asset.content, + () => "buffer" + ); + + const zephyrEngine = await zephyrAgent.ZephyrEngine.create({ + builder: "unknown", + context: nitro.options.rootDir || process.cwd(), + }); + + const appConfig = await zephyrEngine.application_configuration; + if (appConfig?.PLATFORM !== ZEPHYR_PLATFORM) { + throw new TypeError( + `[${LOGGER_TAG}] Unsupported Zephyr PLATFORM "${appConfig?.PLATFORM}". Expected "${ZEPHYR_PLATFORM}".` + ); + } + + zephyrEngine.env.target = DEFAULT_DEPLOY_TARGET; + zephyrEngine.env.ssr = DEFAULT_DEPLOY_SSR; + + const buildStats = await zephyrAgent.zeBuildDashData(zephyrEngine); + let deploymentUrl: string | null = null; + + await zephyrEngine.upload_assets({ + assetsMap, + buildStats, + snapshotType: DEFAULT_DEPLOY_SSR ? "ssr" : "csr", + entrypoint, + hooks: { + onDeployComplete(deploymentInfo) { + deploymentUrl = deploymentInfo.url; + }, + }, + }); + + return { deploymentUrl, entrypoint }; +} + +export function toError(error: unknown): Error { + if (error instanceof Error) { + return error; + } + return new TypeError(`[${LOGGER_TAG}] ${String(error)}`); +} diff --git a/test/unit/zephyr-preset.test.ts b/test/unit/zephyr-preset.test.ts new file mode 100644 index 0000000000..5b2e2f37f5 --- /dev/null +++ b/test/unit/zephyr-preset.test.ts @@ -0,0 +1,61 @@ +import { describe, expect, it, vi } from "vitest"; +import zephyrPresets from "../../src/presets/zephyr/preset.ts"; + +describe("zephyr preset", () => { + it("extends cloudflare-module", () => { + const [preset] = zephyrPresets; + expect(preset.extends).toBe("cloudflare-module"); + }); + + it("emits zephyr metadata asset in rollup:before", async () => { + const [preset] = zephyrPresets; + const hooks = preset.hooks!; + + const nitro = { + options: { + preset: "zephyr", + output: { + dir: "/tmp/zephyr-output", + serverDir: "/tmp/zephyr-output/server", + }, + }, + logger: { + info: vi.fn(), + success: vi.fn(), + }, + } as any; + + const config = { + output: { + dir: "/tmp/zephyr-output/server", + }, + plugins: [], + } as any; + + await hooks["build:before"]?.(nitro); + await hooks["rollup:before"]?.(nitro, config); + + expect(config.plugins).toHaveLength(1); + const plugin = config.plugins[0]; + const emitFile = vi.fn(); + + await plugin.generateBundle.call({ emitFile }); + + expect(emitFile).toHaveBeenCalledWith({ + type: "asset", + fileName: ".zephyr/nitro-build.json", + source: expect.stringContaining('"generatedBy": "zephyr-nitro-preset"'), + }); + expect(emitFile).toHaveBeenCalledWith({ + type: "asset", + fileName: ".zephyr/nitro-build.json", + source: expect.stringContaining('"outputDir": "/tmp/zephyr-output/server"'), + }); + expect(nitro.logger.info).toHaveBeenCalledWith( + "[zephyr-nitro-preset] PLATFORM=cloudflare; using preset `cloudflare-module`." + ); + expect(nitro.logger.success).toHaveBeenCalledWith( + "[zephyr-nitro-preset] Emitted Nitro metadata asset at /tmp/zephyr-output/server/.zephyr/nitro-build.json." + ); + }); +}); From 7915de57689e3ca0c2594b0bf88cd50a30bc319e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Tue, 17 Feb 2026 17:53:00 +0100 Subject: [PATCH 4/8] chore: cleanup and final touches --- src/presets/zephyr/preset.ts | 31 +++++-------------------------- src/presets/zephyr/utils.ts | 8 +------- test/unit/zephyr-preset.test.ts | 14 ++++++++------ 3 files changed, 14 insertions(+), 39 deletions(-) diff --git a/src/presets/zephyr/preset.ts b/src/presets/zephyr/preset.ts index ea05fe7202..e79b4b65b6 100644 --- a/src/presets/zephyr/preset.ts +++ b/src/presets/zephyr/preset.ts @@ -3,7 +3,6 @@ import type { Nitro } from "nitro/types"; import { DEFAULT_BASE_PRESET, LOGGER_TAG, - ZEPHYR_PLATFORM, createZephyrMetadataPlugin, resolveBundlerOutputDir, toError, @@ -15,9 +14,9 @@ const zephyr = defineNitroPreset( extends: DEFAULT_BASE_PRESET, hooks: { "build:before": (nitro: Nitro) => { - nitro.logger.info( - `[${LOGGER_TAG}] PLATFORM=${ZEPHYR_PLATFORM}; using preset \`${DEFAULT_BASE_PRESET}\`.` - ); + // Zephyr deploy flow replaces Nitro preset command hints (wrangler). + delete nitro.options.commands.preview; + delete nitro.options.commands.deploy; }, "rollup:before": (nitro: Nitro, config) => { const outputDir = resolveBundlerOutputDir(nitro, config); @@ -31,29 +30,9 @@ const zephyr = defineNitroPreset( config.plugins.push(plugin); }, compiled: async (nitro: Nitro) => { - const deployOutputDir = nitro.options.output.dir; - nitro.logger.info( - `[${LOGGER_TAG}] Uploading Nitro output to Zephyr from ${deployOutputDir}.` - ); - try { - const { deploymentUrl, entrypoint } = await uploadNitroOutputToZephyr( - nitro, - deployOutputDir - ); - - if (entrypoint) { - nitro.logger.info(`[${LOGGER_TAG}] Zephyr SSR entrypoint: ${entrypoint}.`); - } - - if (deploymentUrl) { - nitro.logger.success(`[${LOGGER_TAG}] Zephyr deployment URL: ${deploymentUrl}`); - return; - } - - nitro.logger.success( - `[${LOGGER_TAG}] Zephyr deploy completed but no deployment URL was returned.` - ); + await uploadNitroOutputToZephyr(nitro, nitro.options.output.dir); + nitro.logger.success(`[${LOGGER_TAG}] Zephyr deployment succeeded.`); } catch (error) { throw toError(error); } diff --git a/src/presets/zephyr/utils.ts b/src/presets/zephyr/utils.ts index fa34fb8a95..eac7d0f5c7 100644 --- a/src/presets/zephyr/utils.ts +++ b/src/presets/zephyr/utils.ts @@ -1,6 +1,6 @@ import type { Nitro, RollupConfig } from "nitro/types"; import type { Plugin } from "rollup"; -import { dirname, isAbsolute, join, normalize, relative } from "pathe"; +import { dirname, isAbsolute, normalize, relative } from "pathe"; export const LOGGER_TAG = "zephyr-nitro-preset"; export const ZEPHYR_PLATFORM = "cloudflare"; @@ -129,17 +129,11 @@ export function resolveBundlerOutputDir(nitro: Nitro, config: RollupConfig): str } export function createZephyrMetadataPlugin(nitro: Nitro, outputDir: string): Plugin { - const metadataPath = normalizeMetadataFile(DEFAULT_METADATA_FILE); - const emittedMetadataPath = join(outputDir, metadataPath); - return { name: "zephyr-nitro-metadata", generateBundle() { const asset = createZephyrNitroMetadataAsset(nitro, outputDir); this.emitFile(asset); - nitro.logger.success( - `[${LOGGER_TAG}] Emitted Nitro metadata asset at ${emittedMetadataPath}.` - ); }, }; } diff --git a/test/unit/zephyr-preset.test.ts b/test/unit/zephyr-preset.test.ts index 5b2e2f37f5..1a18ab814a 100644 --- a/test/unit/zephyr-preset.test.ts +++ b/test/unit/zephyr-preset.test.ts @@ -18,6 +18,10 @@ describe("zephyr preset", () => { dir: "/tmp/zephyr-output", serverDir: "/tmp/zephyr-output/server", }, + commands: { + preview: "npx wrangler --cwd ./ dev", + deploy: "npx wrangler --cwd ./ deploy", + }, }, logger: { info: vi.fn(), @@ -34,6 +38,8 @@ describe("zephyr preset", () => { await hooks["build:before"]?.(nitro); await hooks["rollup:before"]?.(nitro, config); + expect(nitro.options.commands.preview).toBeUndefined(); + expect(nitro.options.commands.deploy).toBeUndefined(); expect(config.plugins).toHaveLength(1); const plugin = config.plugins[0]; @@ -51,11 +57,7 @@ describe("zephyr preset", () => { fileName: ".zephyr/nitro-build.json", source: expect.stringContaining('"outputDir": "/tmp/zephyr-output/server"'), }); - expect(nitro.logger.info).toHaveBeenCalledWith( - "[zephyr-nitro-preset] PLATFORM=cloudflare; using preset `cloudflare-module`." - ); - expect(nitro.logger.success).toHaveBeenCalledWith( - "[zephyr-nitro-preset] Emitted Nitro metadata asset at /tmp/zephyr-output/server/.zephyr/nitro-build.json." - ); + expect(nitro.logger.info).not.toHaveBeenCalled(); + expect(nitro.logger.success).not.toHaveBeenCalled(); }); }); From f096648d1acff832504548b57923e46c04cd2c0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Tue, 17 Feb 2026 18:03:13 +0100 Subject: [PATCH 5/8] chore: tighten types and async loading of zephyr-agent --- src/presets/zephyr/utils.ts | 128 +++++++++++++++++++++++++++++++++--- 1 file changed, 120 insertions(+), 8 deletions(-) diff --git a/src/presets/zephyr/utils.ts b/src/presets/zephyr/utils.ts index eac7d0f5c7..2a6a457ebb 100644 --- a/src/presets/zephyr/utils.ts +++ b/src/presets/zephyr/utils.ts @@ -1,18 +1,19 @@ import type { Nitro, RollupConfig } from "nitro/types"; import type { Plugin } from "rollup"; -import { dirname, isAbsolute, normalize, relative } from "pathe"; +import { dirname, isAbsolute, normalize, relative, resolve } from "pathe"; export const LOGGER_TAG = "zephyr-nitro-preset"; -export const ZEPHYR_PLATFORM = "cloudflare"; -export const PLATFORM_PRESET_MAP = { +const PROVIDER_PRESET_MAP = { cloudflare: "cloudflare-module", } as const; -export const DEFAULT_BASE_PRESET = PLATFORM_PRESET_MAP[ZEPHYR_PLATFORM]; +export type ZephyrProvider = keyof typeof PROVIDER_PRESET_MAP; +export const DEFAULT_BASE_PRESET = PROVIDER_PRESET_MAP.cloudflare; const DEFAULT_METADATA_FILE = ".zephyr/nitro-build.json"; const DEFAULT_DEPLOY_TARGET = "web"; const DEFAULT_DEPLOY_SSR = true; const SKIP_DEPLOY_PATTERNS = [/\.map$/i, /node_modules\//i, /\.git\//i, /\.DS_Store$/i]; +let pulledProvider: ZephyrProvider | undefined; interface ZephyrNitroBuildMetadata { generatedBy: "zephyr-nitro-preset"; @@ -24,6 +25,115 @@ interface DirectoryAsset { content: Buffer; } +interface ZephyrEngineLike { + application_configuration: Promise<{ + PLATFORM?: string; + }>; + env: { + target: string; + ssr?: boolean; + }; + upload_assets: (props: { + assetsMap: Record; + buildStats: unknown; + snapshotType: "ssr" | "csr"; + entrypoint?: string; + hooks?: { + onDeployComplete?: (deploymentInfo: { url: string }) => void; + }; + }) => Promise; +} + +interface ZephyrAgentModule { + readDirRecursiveWithContents: ( + dirPath: string + ) => Promise>; + buildAssetsMap: ( + assets: Record, + extractBuffer: (asset: T) => Buffer | string | undefined, + getAssetType: (asset: T) => string + ) => Record; + zeBuildDashData: (engine: any) => Promise; + ZephyrEngine: { + create: (options: { builder: "unknown"; context: string }) => Promise; + }; +} + +function isZephyrProvider(provider: unknown): provider is ZephyrProvider { + return typeof provider === "string" && provider in PROVIDER_PRESET_MAP; +} + +function parsePulledProvider(platform: unknown): ZephyrProvider { + if (!isZephyrProvider(platform)) { + throw new TypeError( + `[${LOGGER_TAG}] Zephyr PLATFORM "${String(platform)}" is not supported yet by this Nitro preset. Supported today: cloudflare. See https://docs.zephyr-cloud.io` + ); + } + return platform; +} + +function normalizeBaseURL(baseURL: string): string { + let normalized = baseURL.trim().replace(/\\/g, "/"); + while (normalized.startsWith("./")) { + normalized = normalized.slice(2); + } + while (normalized.startsWith("/")) { + normalized = normalized.slice(1); + } + while (normalized.endsWith("/")) { + normalized = normalized.slice(0, -1); + } + return normalized; +} + +function resolveAssetPath( + file: { fullPath: string; relativePath: string }, + publicDir: string, + baseURL: string +): string { + const fullPath = toPosixPath(file.fullPath); + const publicRoot = toPosixPath(publicDir).replace(/\/+$/, ""); + + if (fullPath.startsWith(`${publicRoot}/`)) { + const staticRelative = fullPath.slice(publicRoot.length + 1); + const basePath = normalizeBaseURL(baseURL); + return basePath ? `${basePath}/${staticRelative}` : staticRelative; + } + + return toPosixPath(file.relativePath); +} + +async function loadZephyrAgent(): Promise { + try { + return (await import("zephyr-agent")) as unknown as ZephyrAgentModule; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + if ( + message.includes("Cannot find package 'zephyr-agent'") || + message.includes("Cannot find module 'zephyr-agent'") || + message.includes("ERR_MODULE_NOT_FOUND") + ) { + throw new TypeError( + `[${LOGGER_TAG}] Missing optional dependency \`zephyr-agent\`. Install with \`pnpm add -D zephyr-agent\`.` + ); + } + throw error; + } +} + +function resolveProvider(appPlatform: unknown): ZephyrProvider { + const pulled = parsePulledProvider(appPlatform); + if (!pulledProvider) { + pulledProvider = pulled; + } + if (pulledProvider !== pulled) { + throw new TypeError( + `[${LOGGER_TAG}] Zephyr PLATFORM changed from "${pulledProvider}" to "${pulled}" within the same process.` + ); + } + return pulledProvider; +} + function normalizeMetadataFile(metadataFile: string): string { if (isAbsolute(metadataFile)) { throw new TypeError( @@ -142,11 +252,12 @@ export async function uploadNitroOutputToZephyr( nitro: Nitro, outputDir: string ): Promise<{ deploymentUrl: string | null; entrypoint?: string }> { - const zephyrAgent = await import("zephyr-agent"); + const zephyrAgent = await loadZephyrAgent(); + const publicDir = resolve(outputDir, nitro.options.output.publicDir); const files = await zephyrAgent.readDirRecursiveWithContents(outputDir); const assets = files.reduce>((memo, file) => { - const relativePath = toPosixPath(file.relativePath); + const relativePath = resolveAssetPath(file, publicDir, nitro.options.baseURL); if (shouldSkipDeployAsset(relativePath)) { return memo; } @@ -185,9 +296,10 @@ export async function uploadNitroOutputToZephyr( }); const appConfig = await zephyrEngine.application_configuration; - if (appConfig?.PLATFORM !== ZEPHYR_PLATFORM) { + const provider = resolveProvider(appConfig?.PLATFORM); + if (provider !== "cloudflare") { throw new TypeError( - `[${LOGGER_TAG}] Unsupported Zephyr PLATFORM "${appConfig?.PLATFORM}". Expected "${ZEPHYR_PLATFORM}".` + `[${LOGGER_TAG}] Zephyr PLATFORM "${provider}" is not supported yet by this Nitro preset. Supported today: cloudflare. See https://docs.zephyr-cloud.io` ); } From e3bd861ee61e5e7b6dbc9c9c6b850219693db1e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor?= Date: Thu, 19 Feb 2026 18:51:30 +0100 Subject: [PATCH 6/8] feat: make compatible with public paths --- src/presets/zephyr/preset.ts | 44 +++++---- src/presets/zephyr/runtime/server.ts | 10 +- src/presets/zephyr/utils.ts | 136 ++++----------------------- test/unit/zephyr-preset.test.ts | 42 ++------- 4 files changed, 51 insertions(+), 181 deletions(-) diff --git a/src/presets/zephyr/preset.ts b/src/presets/zephyr/preset.ts index e79b4b65b6..314d6c650d 100644 --- a/src/presets/zephyr/preset.ts +++ b/src/presets/zephyr/preset.ts @@ -1,33 +1,31 @@ import { defineNitroPreset } from "../_utils/preset.ts"; import type { Nitro } from "nitro/types"; -import { - DEFAULT_BASE_PRESET, - LOGGER_TAG, - createZephyrMetadataPlugin, - resolveBundlerOutputDir, - toError, - uploadNitroOutputToZephyr, -} from "./utils.ts"; +import { unenvCfExternals, unenvCfNodeCompat } from "../cloudflare/unenv/preset.ts"; +import { LOGGER_TAG, toError, uploadNitroOutputToZephyr } from "./utils.ts"; const zephyr = defineNitroPreset( { - extends: DEFAULT_BASE_PRESET, + extends: "base-worker", + entry: "./zephyr/runtime/server", + output: { + publicDir: "{{ output.dir }}/client/{{ baseURL }}", + }, + exportConditions: ["node"], + minify: false, + rollupConfig: { + output: { + format: "esm", + exports: "named", + inlineDynamicImports: false, + }, + }, + wasm: { + lazy: false, + esmImport: true, + }, hooks: { "build:before": (nitro: Nitro) => { - // Zephyr deploy flow replaces Nitro preset command hints (wrangler). - delete nitro.options.commands.preview; - delete nitro.options.commands.deploy; - }, - "rollup:before": (nitro: Nitro, config) => { - const outputDir = resolveBundlerOutputDir(nitro, config); - const plugin = createZephyrMetadataPlugin(nitro, outputDir); - - if (!Array.isArray(config.plugins)) { - config.plugins = [plugin]; - return; - } - - config.plugins.push(plugin); + nitro.options.unenv.push(unenvCfExternals, unenvCfNodeCompat); }, compiled: async (nitro: Nitro) => { try { diff --git a/src/presets/zephyr/runtime/server.ts b/src/presets/zephyr/runtime/server.ts index a67e6bb575..91e3e309d9 100644 --- a/src/presets/zephyr/runtime/server.ts +++ b/src/presets/zephyr/runtime/server.ts @@ -1,8 +1,6 @@ import "#nitro/virtual/polyfills"; -import { useNitroApp } from "nitro/app"; +import { createHandler } from "../../cloudflare/runtime/_module-handler.ts"; -const nitroApp = useNitroApp(); - -export default { - fetch: nitroApp.fetch, -}; +export default createHandler({ + fetch() {}, +}); diff --git a/src/presets/zephyr/utils.ts b/src/presets/zephyr/utils.ts index 2a6a457ebb..2444c6480a 100644 --- a/src/presets/zephyr/utils.ts +++ b/src/presets/zephyr/utils.ts @@ -1,26 +1,17 @@ -import type { Nitro, RollupConfig } from "nitro/types"; -import type { Plugin } from "rollup"; -import { dirname, isAbsolute, normalize, relative, resolve } from "pathe"; +import type { Nitro } from "nitro/types"; +import { resolve } from "pathe"; export const LOGGER_TAG = "zephyr-nitro-preset"; const PROVIDER_PRESET_MAP = { - cloudflare: "cloudflare-module", + cloudflare: true, } as const; export type ZephyrProvider = keyof typeof PROVIDER_PRESET_MAP; -export const DEFAULT_BASE_PRESET = PROVIDER_PRESET_MAP.cloudflare; -const DEFAULT_METADATA_FILE = ".zephyr/nitro-build.json"; const DEFAULT_DEPLOY_TARGET = "web"; const DEFAULT_DEPLOY_SSR = true; const SKIP_DEPLOY_PATTERNS = [/\.map$/i, /node_modules\//i, /\.git\//i, /\.DS_Store$/i]; let pulledProvider: ZephyrProvider | undefined; -interface ZephyrNitroBuildMetadata { - generatedBy: "zephyr-nitro-preset"; - preset: string; - outputDir: string; -} - interface DirectoryAsset { content: Buffer; } @@ -97,7 +88,7 @@ function resolveAssetPath( if (fullPath.startsWith(`${publicRoot}/`)) { const staticRelative = fullPath.slice(publicRoot.length + 1); const basePath = normalizeBaseURL(baseURL); - return basePath ? `${basePath}/${staticRelative}` : staticRelative; + return basePath ? `client/${basePath}/${staticRelative}` : `client/${staticRelative}`; } return toPosixPath(file.relativePath); @@ -134,21 +125,6 @@ function resolveProvider(appPlatform: unknown): ZephyrProvider { return pulledProvider; } -function normalizeMetadataFile(metadataFile: string): string { - if (isAbsolute(metadataFile)) { - throw new TypeError( - `[${LOGGER_TAG}] \`metadataFile\` must be relative when using the bundler lifecycle.` - ); - } - - const normalized = normalize(metadataFile).replace(/\\/g, "/").replace(/^\/+/, ""); - if (!normalized || normalized === ".") { - throw new TypeError(`[${LOGGER_TAG}] \`metadataFile\` must point to a file path.`); - } - - return normalized; -} - function toPosixPath(filePath: string): string { return filePath.replace(/\\/g, "/"); } @@ -157,19 +133,17 @@ function shouldSkipDeployAsset(filePath: string): boolean { return SKIP_DEPLOY_PATTERNS.some((pattern) => pattern.test(filePath)); } -function normalizeEntrypoint(entrypoint: string): string { - let normalized = entrypoint.trim().replace(/\\/g, "/"); - while (normalized.startsWith("./")) { - normalized = normalized.slice(2); - } - while (normalized.startsWith("/")) { - normalized = normalized.slice(1); - } - return normalized; -} - function resolveDeployEntrypoint(assets: Record): string | undefined { - const candidates = ["index.mjs", "index.js", "server/index.mjs", "server/index.js"]; + const candidates = [ + "server/index.js", + "server/index.mjs", + "server/server.js", + "server/server.mjs", + "server/_worker.js", + "server/_worker.mjs", + "index.mjs", + "index.js", + ]; for (const candidate of candidates) { if (Object.prototype.hasOwnProperty.call(assets, candidate)) { return candidate; @@ -179,75 +153,6 @@ function resolveDeployEntrypoint(assets: Record): string return undefined; } -function toImportSpecifier(fromFile: string, toFile: string): string { - const relativePath = relative(dirname(fromFile), toFile); - if (!relativePath) { - return "./"; - } - return relativePath.startsWith(".") ? relativePath : `./${relativePath}`; -} - -function createCloudflareEntrypointWrapper(entrypoint: string): { - wrapperPath: string; - source: string; -} { - const wrapperPath = ".zephyr/entrypoint.mjs"; - const importPath = toImportSpecifier(wrapperPath, entrypoint); - const source = `import nitroHandler from '${importPath}'; - -const base = nitroHandler?.default ?? nitroHandler; - -export default { - ...base, - async fetch(request, env = {}, context) { - if (typeof base?.fetch === 'function') { - return base.fetch(request, env ?? {}, context); - } - - if (typeof base === 'function') { - return base(request, env ?? {}, context); - } - - throw new TypeError('[${LOGGER_TAG}] Invalid Nitro Cloudflare entrypoint export.'); - }, -}; -`; - - return { wrapperPath, source }; -} - -function createZephyrNitroMetadata(nitro: Nitro, outputDir: string): ZephyrNitroBuildMetadata { - return { - generatedBy: "zephyr-nitro-preset", - preset: nitro.options.preset, - outputDir, - }; -} - -function createZephyrNitroMetadataAsset(nitro: Nitro, outputDir: string) { - const fileName = normalizeMetadataFile(DEFAULT_METADATA_FILE); - return { - type: "asset" as const, - fileName, - source: `${JSON.stringify(createZephyrNitroMetadata(nitro, outputDir), null, 2)}\n`, - }; -} - -export function resolveBundlerOutputDir(nitro: Nitro, config: RollupConfig): string { - const output = Array.isArray(config.output) ? config.output[0] : config.output; - return output?.dir || nitro.options.output.serverDir || nitro.options.output.dir; -} - -export function createZephyrMetadataPlugin(nitro: Nitro, outputDir: string): Plugin { - return { - name: "zephyr-nitro-metadata", - generateBundle() { - const asset = createZephyrNitroMetadataAsset(nitro, outputDir); - this.emitFile(asset); - }, - }; -} - export async function uploadNitroOutputToZephyr( nitro: Nitro, outputDir: string @@ -272,16 +177,11 @@ export async function uploadNitroOutputToZephyr( throw new TypeError(`[${LOGGER_TAG}] No deployable assets found in ${outputDir}.`); } - let entrypoint = resolveDeployEntrypoint(assets); - - if (DEFAULT_DEPLOY_SSR && entrypoint) { - const { wrapperPath, source } = createCloudflareEntrypointWrapper( - normalizeEntrypoint(entrypoint) + const entrypoint = resolveDeployEntrypoint(assets); + if (DEFAULT_DEPLOY_SSR && !entrypoint) { + throw new TypeError( + `[${LOGGER_TAG}] Could not detect SSR entrypoint in ${outputDir}. Expected one of: server/index.js, server/index.mjs, server/server.js, server/server.mjs, server/_worker.js, server/_worker.mjs, index.mjs, index.js.` ); - assets[wrapperPath] = { - content: Buffer.from(source, "utf8"), - }; - entrypoint = wrapperPath; } const assetsMap = zephyrAgent.buildAssetsMap( diff --git a/test/unit/zephyr-preset.test.ts b/test/unit/zephyr-preset.test.ts index 1a18ab814a..49915e721a 100644 --- a/test/unit/zephyr-preset.test.ts +++ b/test/unit/zephyr-preset.test.ts @@ -2,12 +2,13 @@ import { describe, expect, it, vi } from "vitest"; import zephyrPresets from "../../src/presets/zephyr/preset.ts"; describe("zephyr preset", () => { - it("extends cloudflare-module", () => { + it("extends base-worker", () => { const [preset] = zephyrPresets; - expect(preset.extends).toBe("cloudflare-module"); + expect(preset.extends).toBe("base-worker"); + expect(preset.output?.publicDir).toBe("{{ output.dir }}/client/{{ baseURL }}"); }); - it("emits zephyr metadata asset in rollup:before", async () => { + it("adds cloudflare unenv presets", async () => { const [preset] = zephyrPresets; const hooks = preset.hooks!; @@ -18,10 +19,7 @@ describe("zephyr preset", () => { dir: "/tmp/zephyr-output", serverDir: "/tmp/zephyr-output/server", }, - commands: { - preview: "npx wrangler --cwd ./ dev", - deploy: "npx wrangler --cwd ./ deploy", - }, + unenv: [], }, logger: { info: vi.fn(), @@ -29,34 +27,10 @@ describe("zephyr preset", () => { }, } as any; - const config = { - output: { - dir: "/tmp/zephyr-output/server", - }, - plugins: [], - } as any; - await hooks["build:before"]?.(nitro); - await hooks["rollup:before"]?.(nitro, config); - expect(nitro.options.commands.preview).toBeUndefined(); - expect(nitro.options.commands.deploy).toBeUndefined(); - - expect(config.plugins).toHaveLength(1); - const plugin = config.plugins[0]; - const emitFile = vi.fn(); - - await plugin.generateBundle.call({ emitFile }); - - expect(emitFile).toHaveBeenCalledWith({ - type: "asset", - fileName: ".zephyr/nitro-build.json", - source: expect.stringContaining('"generatedBy": "zephyr-nitro-preset"'), - }); - expect(emitFile).toHaveBeenCalledWith({ - type: "asset", - fileName: ".zephyr/nitro-build.json", - source: expect.stringContaining('"outputDir": "/tmp/zephyr-output/server"'), - }); + expect(nitro.options.unenv).toHaveLength(2); + expect(nitro.options.unenv[0].meta?.name).toBe("nitro:cloudflare-externals"); + expect(nitro.options.unenv[1].meta?.name).toBe("nitro:cloudflare-node-compat"); expect(nitro.logger.info).not.toHaveBeenCalled(); expect(nitro.logger.success).not.toHaveBeenCalled(); }); From fcce0b666ff16f407e023b92cc5973fbdc671494 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 26 Feb 2026 02:09:16 +0100 Subject: [PATCH 7/8] update lock --- pnpm-lock.yaml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a40667416b..4cece77372 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -321,8 +321,8 @@ importers: specifier: ^0.3.3 version: 0.3.3 zephyr-agent: - specifier: ^0.1.10 - version: 0.1.10(https-proxy-agent@7.0.6) + specifier: ^0.1.13 + version: 0.1.13(https-proxy-agent@7.0.6) examples/api-routes: devDependencies: @@ -6615,13 +6615,13 @@ packages: youch@4.1.0-beta.10: resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} - zephyr-agent@0.1.10: - resolution: {integrity: sha512-wL9RDvemIEs3jh0LI0nloI0dbefFTDMuFDM+HEV1A1k6pLJ/xZ6gLmGp+by8idnCxH4+snDR5ITSbyetQa5vOQ==} + zephyr-agent@0.1.13: + resolution: {integrity: sha512-M6c0RlLXw4Asup8+RMoze9P9o8Oi5W9jX29bFLJbqschd0Bxzgh3RA8h+hFYB53E4hANVhJXegdyKAaAD/S8QA==} peerDependencies: https-proxy-agent: ^7.0.6 - zephyr-edge-contract@0.1.10: - resolution: {integrity: sha512-bAqXtBWljq4p13ko+oqo0FiLxK8Cqf382lxUHOEWaU3zn/ErQ20o40QVm+hXVwDFvovupI4vMQLmuGPUYF45NA==} + zephyr-edge-contract@0.1.13: + resolution: {integrity: sha512-S3vauLHCc45wACTP+8SxqFS5RyL9VqA0ETi0bzVdXj/XEvfpDqQqLauF0XDOxdHH/I8ZwCaL0egEv2pMqoRBug==} zimmerframe@1.1.4: resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} @@ -9253,7 +9253,7 @@ snapshots: '@fastify/error': 4.2.0 fastq: 1.20.1 - axios-retry@4.5.0(axios@1.13.5): + axios-retry@4.5.0(axios@1.13.5(debug@4.4.3)): dependencies: axios: 1.13.5(debug@4.4.3) is-retry-allowed: 2.2.0 @@ -13003,11 +13003,11 @@ snapshots: cookie: 1.1.1 youch-core: 0.3.3 - zephyr-agent@0.1.10(https-proxy-agent@7.0.6): + zephyr-agent@0.1.13(https-proxy-agent@7.0.6): dependencies: '@toon-format/toon': 0.9.0 axios: 1.13.5(debug@4.4.3) - axios-retry: 4.5.0(axios@1.13.5) + axios-retry: 4.5.0(axios@1.13.5(debug@4.4.3)) debug: 4.4.3 eventsource: 4.1.0 git-url-parse: 15.0.0 @@ -13018,11 +13018,11 @@ snapshots: open: 10.2.0 proper-lockfile: 4.1.2 tslib: 2.8.1 - zephyr-edge-contract: 0.1.10 + zephyr-edge-contract: 0.1.13 transitivePeerDependencies: - supports-color - zephyr-edge-contract@0.1.10: + zephyr-edge-contract@0.1.13: dependencies: tslib: 2.8.1 From d3da97a474aa3c7704ad3c95b078ac2247130a61 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 26 Feb 2026 02:28:49 +0100 Subject: [PATCH 8/8] update --- playground/nitro.config.ts | 1 - playground/public/test.txt | 1 - playground/server.ts | 4 +- src/presets/zephyr/preset.ts | 15 +++++-- src/presets/zephyr/utils.ts | 82 +++++++++++++----------------------- 5 files changed, 43 insertions(+), 60 deletions(-) delete mode 100644 playground/public/test.txt diff --git a/playground/nitro.config.ts b/playground/nitro.config.ts index 6e09c04eb7..4d63605a93 100644 --- a/playground/nitro.config.ts +++ b/playground/nitro.config.ts @@ -2,5 +2,4 @@ import { defineConfig } from "nitro"; export default defineConfig({ serverDir: "./server", - preset: "zephyr", }); diff --git a/playground/public/test.txt b/playground/public/test.txt deleted file mode 100644 index 4fff881e87..0000000000 --- a/playground/public/test.txt +++ /dev/null @@ -1 +0,0 @@ -Test File diff --git a/playground/server.ts b/playground/server.ts index 20e55cdb61..7d810949a2 100644 --- a/playground/server.ts +++ b/playground/server.ts @@ -1,7 +1,5 @@ export default { fetch(req: Request) { - return new Response("Hello from Nitro playground! test.txt", { - headers: { "Content-Type": "text/html" }, - }); + return new Response("Hello from Nitro playground!"); }, }; diff --git a/src/presets/zephyr/preset.ts b/src/presets/zephyr/preset.ts index 314d6c650d..81e389ccc9 100644 --- a/src/presets/zephyr/preset.ts +++ b/src/presets/zephyr/preset.ts @@ -1,7 +1,8 @@ import { defineNitroPreset } from "../_utils/preset.ts"; import type { Nitro } from "nitro/types"; import { unenvCfExternals, unenvCfNodeCompat } from "../cloudflare/unenv/preset.ts"; -import { LOGGER_TAG, toError, uploadNitroOutputToZephyr } from "./utils.ts"; +import { LOGGER_TAG, uploadNitroOutputToZephyr } from "./utils.ts"; +import { resolve } from "pathe"; const zephyr = defineNitroPreset( { @@ -29,10 +30,18 @@ const zephyr = defineNitroPreset( }, compiled: async (nitro: Nitro) => { try { - await uploadNitroOutputToZephyr(nitro, nitro.options.output.dir); + await uploadNitroOutputToZephyr({ + rootDir: nitro.options.rootDir, + baseURL: nitro.options.baseURL, + outputDir: nitro.options.output.dir, + publicDir: resolve(nitro.options.output.dir, nitro.options.output.publicDir), + }); nitro.logger.success(`[${LOGGER_TAG}] Zephyr deployment succeeded.`); } catch (error) { - throw toError(error); + if (error instanceof Error) { + throw error; + } + throw new TypeError(`[${LOGGER_TAG}] ${String(error)}`); } }, }, diff --git a/src/presets/zephyr/utils.ts b/src/presets/zephyr/utils.ts index 2444c6480a..6b819bb521 100644 --- a/src/presets/zephyr/utils.ts +++ b/src/presets/zephyr/utils.ts @@ -1,11 +1,11 @@ -import type { Nitro } from "nitro/types"; -import { resolve } from "pathe"; +import { normalize } from "pathe"; +import { importDep } from "../../utils/dep.ts"; export const LOGGER_TAG = "zephyr-nitro-preset"; -const PROVIDER_PRESET_MAP = { - cloudflare: true, -} as const; -export type ZephyrProvider = keyof typeof PROVIDER_PRESET_MAP; + +export type ZephyrProvider = "cloudflare" | (string & {}); + +const SUPPORTED_PROVIDERS = new Set(["cloudflare"]); const DEFAULT_DEPLOY_TARGET = "web"; const DEFAULT_DEPLOY_SSR = true; @@ -51,7 +51,7 @@ interface ZephyrAgentModule { } function isZephyrProvider(provider: unknown): provider is ZephyrProvider { - return typeof provider === "string" && provider in PROVIDER_PRESET_MAP; + return SUPPORTED_PROVIDERS.has(provider as string); } function parsePulledProvider(platform: unknown): ZephyrProvider { @@ -63,6 +63,9 @@ function parsePulledProvider(platform: unknown): ZephyrProvider { return platform; } +/** + * Strips leading `./` and `/`, trailing `/`, and normalizes backslashes to get a bare path segment. + */ function normalizeBaseURL(baseURL: string): string { let normalized = baseURL.trim().replace(/\\/g, "/"); while (normalized.startsWith("./")) { @@ -82,34 +85,14 @@ function resolveAssetPath( publicDir: string, baseURL: string ): string { - const fullPath = toPosixPath(file.fullPath); - const publicRoot = toPosixPath(publicDir).replace(/\/+$/, ""); - + const fullPath = normalize(file.fullPath); + const publicRoot = normalize(publicDir).replace(/\/+$/, ""); if (fullPath.startsWith(`${publicRoot}/`)) { const staticRelative = fullPath.slice(publicRoot.length + 1); const basePath = normalizeBaseURL(baseURL); return basePath ? `client/${basePath}/${staticRelative}` : `client/${staticRelative}`; } - - return toPosixPath(file.relativePath); -} - -async function loadZephyrAgent(): Promise { - try { - return (await import("zephyr-agent")) as unknown as ZephyrAgentModule; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - if ( - message.includes("Cannot find package 'zephyr-agent'") || - message.includes("Cannot find module 'zephyr-agent'") || - message.includes("ERR_MODULE_NOT_FOUND") - ) { - throw new TypeError( - `[${LOGGER_TAG}] Missing optional dependency \`zephyr-agent\`. Install with \`pnpm add -D zephyr-agent\`.` - ); - } - throw error; - } + return normalize(file.relativePath); } function resolveProvider(appPlatform: unknown): ZephyrProvider { @@ -125,10 +108,6 @@ function resolveProvider(appPlatform: unknown): ZephyrProvider { return pulledProvider; } -function toPosixPath(filePath: string): string { - return filePath.replace(/\\/g, "/"); -} - function shouldSkipDeployAsset(filePath: string): boolean { return SKIP_DEPLOY_PATTERNS.some((pattern) => pattern.test(filePath)); } @@ -153,16 +132,22 @@ function resolveDeployEntrypoint(assets: Record): string return undefined; } -export async function uploadNitroOutputToZephyr( - nitro: Nitro, - outputDir: string -): Promise<{ deploymentUrl: string | null; entrypoint?: string }> { - const zephyrAgent = await loadZephyrAgent(); - const publicDir = resolve(outputDir, nitro.options.output.publicDir); - const files = await zephyrAgent.readDirRecursiveWithContents(outputDir); +export async function uploadNitroOutputToZephyr(opts: { + rootDir: string; + baseURL: string; + outputDir: string; + publicDir: string; +}): Promise<{ deploymentUrl: string | null; entrypoint?: string }> { + const zephyrAgent = await importDep({ + id: "zephyr-agent", + reason: "deploying to Zephyr", + dir: opts.rootDir, + }); + + const files = await zephyrAgent.readDirRecursiveWithContents(opts.outputDir); const assets = files.reduce>((memo, file) => { - const relativePath = resolveAssetPath(file, publicDir, nitro.options.baseURL); + const relativePath = resolveAssetPath(file, opts.publicDir, opts.baseURL); if (shouldSkipDeployAsset(relativePath)) { return memo; } @@ -174,13 +159,13 @@ export async function uploadNitroOutputToZephyr( }, {}); if (Object.keys(assets).length === 0) { - throw new TypeError(`[${LOGGER_TAG}] No deployable assets found in ${outputDir}.`); + throw new TypeError(`[${LOGGER_TAG}] No deployable assets found in ${opts.outputDir}.`); } const entrypoint = resolveDeployEntrypoint(assets); if (DEFAULT_DEPLOY_SSR && !entrypoint) { throw new TypeError( - `[${LOGGER_TAG}] Could not detect SSR entrypoint in ${outputDir}. Expected one of: server/index.js, server/index.mjs, server/server.js, server/server.mjs, server/_worker.js, server/_worker.mjs, index.mjs, index.js.` + `[${LOGGER_TAG}] Could not detect SSR entrypoint in ${opts.outputDir}. Expected one of: server/index.js, server/index.mjs, server/server.js, server/server.mjs, server/_worker.js, server/_worker.mjs, index.mjs, index.js.` ); } @@ -192,7 +177,7 @@ export async function uploadNitroOutputToZephyr( const zephyrEngine = await zephyrAgent.ZephyrEngine.create({ builder: "unknown", - context: nitro.options.rootDir || process.cwd(), + context: opts.rootDir || process.cwd(), }); const appConfig = await zephyrEngine.application_configuration; @@ -223,10 +208,3 @@ export async function uploadNitroOutputToZephyr( return { deploymentUrl, entrypoint }; } - -export function toError(error: unknown): Error { - if (error instanceof Error) { - return error; - } - return new TypeError(`[${LOGGER_TAG}] ${String(error)}`); -}