Skip to content

presets: add zephyr#4038

Open
pi0 wants to merge 17 commits intomainfrom
feat/zephyr-preset
Open

presets: add zephyr#4038
pi0 wants to merge 17 commits intomainfrom
feat/zephyr-preset

Conversation

@pi0
Copy link
Member

@pi0 pi0 commented Feb 17, 2026

This PR adds a built-in preset for Zephyr Cloud

(co-authored with @Nsttt based on ZephyrCloudIO/zephyr-packages#358)

Checklist:

  • Working hello world
  • Fix static asseets
  • Support zephyr.provider config
    • Config schema
    • Pull on first build
  • Lazy import zephyr-agent SDK with prompt to install

@vercel
Copy link

vercel bot commented Feb 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nitro.build Ready Ready Preview, Comment Mar 2, 2026 8:30am

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new Zephyr Nitro preset (preset, types, runtime), deployment utilities, CLI deploy integration, package dependency for zephyr-agent, and unit tests; registers the preset in aggregated exports.

Changes

Cohort / File(s) Summary
Package manifest
package.json
Added zephyr-agent to dependencies (^0.1.13), declared it in peerDependencies (^0.1.13) and marked optional in peerDependenciesMeta.
Preset registry & types
src/presets/_all.gen.ts, src/presets/_types.gen.ts
Imported and registered the zephyr preset; extended PresetOptions to include zephyr?: ZephyrOptions, added "zephyr" to presetsWithConfig, PresetName, and PresetNameInput.
Zephyr preset & types
src/presets/zephyr/preset.ts, src/presets/zephyr/types.ts
New zephyr preset defined via defineNitroPreset with build hooks, output/publicDir, rollup/wasm settings, optional compiled hook to deploy, and ZephyrOptions (deployOnBuild?: boolean).
Runtime
src/presets/zephyr/runtime/server.ts
Added Cloudflare Worker runtime server module exporting a default handler wired to Nitro runtime helpers.
Deployment utilities
src/presets/zephyr/utils.ts
New uploadNitroOutputToZephyr and helpers: dynamic zephyr-agent loading, asset discovery/filtering, SSR entrypoint resolution, engine construction, upload flow, and exported ZephyrProvider/LOGGER_TAG.
CLI integration
src/cli/commands/deploy.ts
When buildInfo.preset === "zephyr", calls uploadNitroOutputToZephyr, logs deployment result, and handles NITRO_INTERNAL_ZEPHYR_SKIP_DEPLOY_ON_BUILD during prebuild flows; minor error/exit handling adjusted.
Tests
test/unit/zephyr-preset.test.ts
New unit tests for the Zephyr preset: preset config, build hooks, deploy-on-build behavior, env-based skip, and mocked uploadNitroOutputToZephyr interactions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title follows conventional commits format with 'presets:' scope and clearly describes the main change (adding zephyr preset).
Description check ✅ Passed The description is directly related to the changeset, detailing implementation of a Zephyr Cloud preset with specific completed features and configuration options.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/zephyr-preset

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pi0 pi0 requested a review from a team February 17, 2026 11:17
@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 17, 2026

Open in StackBlitz

npm i https://pkg.pr.new/nitro@4038

commit: 3c3fbcd

@pi0 pi0 marked this pull request as ready for review February 26, 2026 01:40
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
test/unit/zephyr-preset.test.ts (1)

11-36: Consider adding a test for the compiled hook deployment wiring.

Current coverage validates build:before, but not compiled behavior (uploadNitroOutputToZephyr call path and publicDir resolution). A focused test here would catch deploy-time regressions earlier.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/zephyr-preset.test.ts` around lines 11 - 36, Add a unit test that
invokes the zephyr preset's compiled hook to validate deployment wiring: locate
the preset via zephyrPresets and its hooks (hooks["compiled"]) and call it with
a nitro-like object that stubs options (including output.dir and
output.publicDir scenarios), a logger, and a mocked uploadNitroOutputToZephyr
function; assert that uploadNitroOutputToZephyr is called with the resolved
server output path and the correct publicDir resolution, and that logger methods
are used as expected to capture deploy-time messages.
src/presets/zephyr/utils.ts (1)

98-109: Provider validation is duplicated and stateful; simplify to a single stateless check.

parsePulledProvider already enforces the supported provider set, so the later provider !== "cloudflare" branch is redundant today. Also, module-level pulledProvider couples separate invocations in the same process.

Refactor sketch
-let pulledProvider: ZephyrProvider | undefined;
@@
-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 resolveProvider(appPlatform: unknown): ZephyrProvider {
+  return parsePulledProvider(appPlatform);
+}
@@
-  const provider = resolveProvider(appConfig?.PLATFORM);
-  if (provider !== "cloudflare") {
-    throw new TypeError(
-      `[${LOGGER_TAG}] Zephyr PLATFORM "${provider}" is not supported yet by this Nitro preset. Supported today: cloudflare. See https://docs.zephyr-cloud.io`
-    );
-  }
+  resolveProvider(appConfig?.PLATFORM);

Also applies to: 185-189

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/presets/zephyr/utils.ts` around lines 98 - 109, The resolveProvider
function uses a module-level pulledProvider and duplicate validation; remove the
stateful pulledProvider usage and simplify resolveProvider to be stateless by
returning the result of parsePulledProvider(appPlatform) directly (i.e., call
parsePulledProvider and return its value), removing the pulledProvider
assignment and the subsequent equality check/TypeError; also apply the same
simplification to the similar code block referenced around lines 185-189 so both
sites rely solely on parsePulledProvider for validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 181: Replace the wildcard dependency for "zephyr-agent" in package.json
with a pinned semver (e.g. "^0.1.13") and update the lockfile; then verify that
the pinned release exposes the internal APIs used (readDirRecursiveWithContents,
buildAssetsMap, zeBuildDashData, ZephyrEngine.create) by checking the package
source or installed types and run a full build/test to confirm compatibility—if
any API differs, adjust calls in src/presets/zephyr/utils.ts to match the pinned
version or choose a compatible semver range.

---

Nitpick comments:
In `@src/presets/zephyr/utils.ts`:
- Around line 98-109: The resolveProvider function uses a module-level
pulledProvider and duplicate validation; remove the stateful pulledProvider
usage and simplify resolveProvider to be stateless by returning the result of
parsePulledProvider(appPlatform) directly (i.e., call parsePulledProvider and
return its value), removing the pulledProvider assignment and the subsequent
equality check/TypeError; also apply the same simplification to the similar code
block referenced around lines 185-189 so both sites rely solely on
parsePulledProvider for validation.

In `@test/unit/zephyr-preset.test.ts`:
- Around line 11-36: Add a unit test that invokes the zephyr preset's compiled
hook to validate deployment wiring: locate the preset via zephyrPresets and its
hooks (hooks["compiled"]) and call it with a nitro-like object that stubs
options (including output.dir and output.publicDir scenarios), a logger, and a
mocked uploadNitroOutputToZephyr function; assert that uploadNitroOutputToZephyr
is called with the resolved server output path and the correct publicDir
resolution, and that logger methods are used as expected to capture deploy-time
messages.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b954d0a and d3da97a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • package.json
  • src/presets/_all.gen.ts
  • src/presets/_types.gen.ts
  • src/presets/zephyr/preset.ts
  • src/presets/zephyr/runtime/server.ts
  • src/presets/zephyr/utils.ts
  • test/unit/zephyr-preset.test.ts

@Nsttt
Copy link

Nsttt commented Mar 1, 2026

Update: Zephyr preset now supports both build-time and deploy-time flows, with declarative opt-out.

Usage:

import { defineConfig } from "nitro";

export default defineConfig({
  preset: "zephyr",
  zephyr: {
    deployOnBuild: false, // optional; default true
  },
});

Behavior:

  • deployOnBuild omitted/true: nitro build deploys immediately and prints Zephyr URL.
  • deployOnBuild: false: nitro build skips deploy but still generates deploy artifacts.
  • nitro deploy --prebuilt: deploys from generated .zephyr/deploy.mjs and prints Zephyr URL.

Generated contract in output dir:

  • client/
  • server/

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
test/unit/zephyr-preset.test.ts (1)

4-5: Switch test path operations from node:path to pathe.

This test file imports join and resolve from node:path on line 4-5 and uses them throughout. Use pathe instead for cross-platform path handling consistency with the repository guideline.

Proposed fix
-import { join, resolve } from "node:path";
+import { join, resolve } from "pathe";

Affected usages: lines 159, 164, 166, 180.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/zephyr-preset.test.ts` around lines 4 - 5, Replace node:path
imports with pathe: update the import statement that currently reads "import {
join, resolve } from 'node:path'" to import from "pathe" instead, and ensure all
usages of join and resolve in this test (e.g., where test paths are built around
tmpdir and fixtures inside the zephyr-preset.test.ts file) continue to call the
same function names; no other behavioral changes are needed. Keep tmpdir import
from "node:os" as-is and run the unit tests to confirm cross-platform paths now
use pathe.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/presets/zephyr/preset.ts`:
- Around line 54-61: Capture the return value from uploadNitroOutputToZephyr
(e.g., const deploymentUrl = await uploadNitroOutputToZephyr(...)) and include
that URL in the success log so the deployed Zephyr URL is printed after
build-time deploy; update the nitro.logger.success call (or add a subsequent
nitro.logger.info) to reference deploymentUrl and provide a clear message like
"Zephyr deployment succeeded: <deploymentUrl>" using the existing LOGGER_TAG.

In `@src/presets/zephyr/utils.ts`:
- Around line 84-87: The current meta loading uses .catch(() => ({})) which
hides real read/parse errors; replace that with an explicit try/catch around
reading and JSON.parse of metaPath (references: metaPath, meta, outputDir) so
that if readFile throws ENOENT you return {} (treat missing file as
recoverable/warn), but for any other error (including JSON.parse errors) rethrow
or propagate the error so invalid states fail loudly; optionally log a warning
when returning {} for a missing file.
- Around line 1-4: Replace the node:path import with pathe's resolve to keep
path operations cross-platform: remove "resolve" import from "node:path" and
import { resolve } from "pathe" (alongside the existing normalize import), then
ensure every call site that uses resolve in this module (the preset utility
functions that build file paths) uses the pathe resolve import. Keep all other
logic unchanged so normalize and resolve come from pathe for consistent behavior
across platforms.

---

Nitpick comments:
In `@test/unit/zephyr-preset.test.ts`:
- Around line 4-5: Replace node:path imports with pathe: update the import
statement that currently reads "import { join, resolve } from 'node:path'" to
import from "pathe" instead, and ensure all usages of join and resolve in this
test (e.g., where test paths are built around tmpdir and fixtures inside the
zephyr-preset.test.ts file) continue to call the same function names; no other
behavioral changes are needed. Keep tmpdir import from "node:os" as-is and run
the unit tests to confirm cross-platform paths now use pathe.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3da97a and 0f6def5.

📒 Files selected for processing (5)
  • src/presets/_types.gen.ts
  • src/presets/zephyr/preset.ts
  • src/presets/zephyr/types.ts
  • src/presets/zephyr/utils.ts
  • test/unit/zephyr-preset.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/presets/_types.gen.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/presets/zephyr/utils.ts (1)

180-183: Remove unreachable fallback.

opts.rootDir is a required parameter in the function signature, so || process.cwd() is unreachable dead code.

♻️ Suggested fix
   const zephyrEngine = await zephyrAgent.ZephyrEngine.create({
     builder: "unknown",
-    context: opts.rootDir || process.cwd(),
+    context: opts.rootDir,
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/presets/zephyr/utils.ts` around lines 180 - 183, The ZephyrEngine
creation contains an unreachable fallback using process.cwd(); update the call
to zephyrAgent.ZephyrEngine.create to pass opts.rootDir directly for the context
parameter (remove the "|| process.cwd()" fallback) since opts.rootDir is
required; adjust any related doc/comments or assumptions around context if
needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cli/commands/deploy.ts`:
- Around line 42-53: The Zephyr deploy branch in the CLI omits publicDir and
baseURL when calling uploadNitroOutputToZephyr, causing inconsistent asset path
resolution compared to the preset hook; update the call inside the
buildInfo.preset === "zephyr" branch to pass the same publicDir and baseURL
values used at build time (alongside rootDir and outputDir) so
uploadNitroOutputToZephyr (and resolveAssetPath) receives the correct
parameters.

---

Nitpick comments:
In `@src/presets/zephyr/utils.ts`:
- Around line 180-183: The ZephyrEngine creation contains an unreachable
fallback using process.cwd(); update the call to zephyrAgent.ZephyrEngine.create
to pass opts.rootDir directly for the context parameter (remove the "||
process.cwd()" fallback) since opts.rootDir is required; adjust any related
doc/comments or assumptions around context if needed.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f6def5 and 8e5adae.

📒 Files selected for processing (4)
  • src/cli/commands/deploy.ts
  • src/presets/zephyr/preset.ts
  • src/presets/zephyr/utils.ts
  • test/unit/zephyr-preset.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/presets/zephyr/preset.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/presets/zephyr/utils.ts (1)

185-186: Consider capturing or logging the resolved provider.

resolveProvider(appConfig?.PLATFORM) is called purely for validation (to throw if unsupported), but the resolved provider value is discarded. This is fine for validation, but capturing it could be useful for future logging or conditional logic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/presets/zephyr/utils.ts` around lines 185 - 186, The call to
resolveProvider(appConfig?.PLATFORM) only validates the platform but discards
the returned provider; update the code where
zephyrEngine.application_configuration is awaited to capture the resolved value
(e.g., assign to a local like resolvedProvider from
resolveProvider(appConfig?.PLATFORM)) and then use or log that variable for
future conditional logic or debugging (refer to resolveProvider and the
appConfig variable retrieved from zephyrEngine.application_configuration);
ensure any existing behavior (throwing on unsupported platforms) remains
unchanged.
test/unit/zephyr-preset.test.ts (1)

11-185: Consider adding error scenario tests.

The test suite covers happy paths well. Consider adding tests for error conditions:

  • When uploadNitroOutputToZephyr throws (e.g., no deployable assets, missing entrypoint)
  • When deployment succeeds but deploymentUrl is null

This would improve resilience against regressions in error handling.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/zephyr-preset.test.ts` around lines 11 - 185, Add negative-path
unit tests for the zephyr preset: mock uploadNitroOutputToZephyr to throw and
assert hooks.compiled calls result in nitro.logger.error being called with a
descriptive failure message (and nitro.logger.success not called), and mock
uploadNitroOutputToZephyr to resolve with deploymentUrl: null and assert
hooks.compiled results in nitro.logger.error or nitro.logger.info being called
appropriately (and success not called); use the same vi.doMock pattern as
existing tests to replace uploadNitroOutputToZephyr and invoke
preset.hooks!.compiled with a nitro object (including logger stubs) to assert
the error-handling behavior of the compiled hook.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/presets/zephyr/utils.ts`:
- Around line 185-186: The call to resolveProvider(appConfig?.PLATFORM) only
validates the platform but discards the returned provider; update the code where
zephyrEngine.application_configuration is awaited to capture the resolved value
(e.g., assign to a local like resolvedProvider from
resolveProvider(appConfig?.PLATFORM)) and then use or log that variable for
future conditional logic or debugging (refer to resolveProvider and the
appConfig variable retrieved from zephyrEngine.application_configuration);
ensure any existing behavior (throwing on unsupported platforms) remains
unchanged.

In `@test/unit/zephyr-preset.test.ts`:
- Around line 11-185: Add negative-path unit tests for the zephyr preset: mock
uploadNitroOutputToZephyr to throw and assert hooks.compiled calls result in
nitro.logger.error being called with a descriptive failure message (and
nitro.logger.success not called), and mock uploadNitroOutputToZephyr to resolve
with deploymentUrl: null and assert hooks.compiled results in nitro.logger.error
or nitro.logger.info being called appropriately (and success not called); use
the same vi.doMock pattern as existing tests to replace
uploadNitroOutputToZephyr and invoke preset.hooks!.compiled with a nitro object
(including logger stubs) to assert the error-handling behavior of the compiled
hook.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e5adae and 26dd329.

📒 Files selected for processing (5)
  • package.json
  • src/cli/commands/deploy.ts
  • src/presets/zephyr/preset.ts
  • src/presets/zephyr/utils.ts
  • test/unit/zephyr-preset.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/presets/zephyr/preset.ts
  • package.json
  • src/cli/commands/deploy.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/cli/commands/deploy.ts (1)

42-53: Consider wrapping the Zephyr upload in try/catch for consistent error handling.

The uploadNitroOutputToZephyr function can throw (e.g., no assets to deploy, SSR entrypoint not detected). Other error paths in this file (lines 39-40, 55-58) use consola.error + process.exit(1) for cleaner output. Currently, a thrown error will bubble up and terminate with a stack trace.

♻️ Proposed refactor for consistent error handling
     if (buildInfo.preset === "zephyr") {
+      try {
         const { deploymentUrl } = await uploadNitroOutputToZephyr({
           rootDir,
           outputDir,
         });
         if (deploymentUrl) {
           consola.success(`[${LOGGER_TAG}] Zephyr deployment succeeded: ${deploymentUrl}`);
         } else {
           consola.success(`[${LOGGER_TAG}] Zephyr deployment succeeded.`);
         }
         return;
+      } catch (error) {
+        consola.error(`[${LOGGER_TAG}] Zephyr deployment failed:`, error instanceof Error ? error.message : error);
+        process.exit(1);
+      }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/commands/deploy.ts` around lines 42 - 53, The Zephyr deployment call
in the buildInfo.preset === "zephyr" branch should be wrapped in a try/catch so
thrown errors are handled consistently; inside the try call
uploadNitroOutputToZephyr({ rootDir, outputDir }) as currently done and on
success log via consola.success, and in the catch call
consola.error(`[${LOGGER_TAG}] Zephyr deployment failed: ${error.message ||
error}`) followed by process.exit(1) to match other error paths (refer to the
uploadNitroOutputToZephyr invocation and the surrounding branch).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/cli/commands/deploy.ts`:
- Around line 42-53: The Zephyr deployment call in the buildInfo.preset ===
"zephyr" branch should be wrapped in a try/catch so thrown errors are handled
consistently; inside the try call uploadNitroOutputToZephyr({ rootDir, outputDir
}) as currently done and on success log via consola.success, and in the catch
call consola.error(`[${LOGGER_TAG}] Zephyr deployment failed: ${error.message ||
error}`) followed by process.exit(1) to match other error paths (refer to the
uploadNitroOutputToZephyr invocation and the surrounding branch).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 26dd329 and fc4e331.

📒 Files selected for processing (1)
  • src/cli/commands/deploy.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants