Skip to content

Commit 1d50bec

Browse files
committed
feat: merge nitro instances into singular on vercel builds
Signed-off-by: Rihan Arfan <me@file.properties>
1 parent c7582d8 commit 1d50bec

21 files changed

Lines changed: 293 additions & 1707 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eve": patch
3+
---
4+
5+
Vercel builds now run a single Nitro build. The workflow flow function is emitted through Nitro's per-route `functionRules` (queue trigger, `maxDuration: "max"`, precondition guard) instead of a second standalone Nitro build that was copied and retargeted into the output, making `eve build` on Vercel roughly twice as fast.

docs/channels/slack.mdx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ export default slackChannel({
4040

4141
If eve cannot find an authenticated Vercel session, `eve channels add slack` asks whether to set up Vercel Connect or use portable environment credentials. The portable option adds the required names to `.env.example`. Before deploying, appropriate values must be supplied to your runtime environment.
4242

43-
4443
### Deploy
4544

4645
Deploy once the trigger destination and channel file are ready:

packages/eve/src/internal/application/build-workspace.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export interface ApplicationBuildWorkspace {
1616
};
1717
readonly nitro: {
1818
readonly buildDir: string;
19-
readonly surfaceOutputDir: string;
2019
};
2120
readonly publication: {
2221
readonly output: {
@@ -59,7 +58,6 @@ export async function createApplicationBuildWorkspace(
5958
},
6059
nitro: {
6160
buildDir: join(rootDir, "nitro"),
62-
surfaceOutputDir: join(rootDir, "nitro-output"),
6361
},
6462
publication: {
6563
output: {

packages/eve/src/internal/application/paths.ts

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
33
import { join, resolve } from "node:path";
44

55
import { workflowEntryReference } from "#execution/workflow-runtime.js";
6-
import type { NitroBuildSurface } from "#internal/nitro/host/types.js";
76
import {
87
resolveInstalledPackageInfo,
98
resolvePackageRoot,
@@ -45,33 +44,14 @@ export function isVercelBuildEnvironment(): boolean {
4544
/**
4645
* Resolves the programmatic Nitro build directory for an app.
4746
*/
48-
export function resolveNitroBuildDirectory(
49-
appRoot: string,
50-
surface: NitroBuildSurface = "all",
51-
): string {
52-
const rootDirectory = join(appRoot, ".eve", "nitro");
53-
54-
if (surface === "all") {
55-
return rootDirectory;
56-
}
57-
58-
return join(rootDirectory, surface);
47+
export function resolveNitroBuildDirectory(appRoot: string): string {
48+
return join(appRoot, ".eve", "nitro");
5949
}
6050

6151
export function resolveApplicationHostArtifactsDirectory(appRoot: string): string {
6252
return join(appRoot, ".eve", "host");
6353
}
6454

65-
/**
66-
* Resolves the staged Nitro output directory for one isolated build surface.
67-
*/
68-
export function resolveNitroSurfaceOutputDirectory(
69-
appRoot: string,
70-
surface: Exclude<NitroBuildSurface, "all">,
71-
): string {
72-
return join(appRoot, ".eve", "nitro-output", surface);
73-
}
74-
7555
/**
7656
* Resolves the package-owned Workflow DevKit bundle directory for a target app.
7757
*

packages/eve/src/internal/nitro/host/build-application.scenario.test.ts

Lines changed: 32 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,6 @@ const resolveDiscoveryProjectMock = vi.fn(async (appRoot: string) => ({
6868
layout: "nested" as const,
6969
}));
7070
const runVercelBuildPrewarmMock = vi.fn(async () => undefined);
71-
const workflowBuilderBuildVercelOutputMock = vi.fn(async (_options: unknown) => undefined);
72-
const workflowBuilderConstructors: unknown[] = [];
7371

7472
vi.mock("nitro/builder", () => ({
7573
build: buildNitroMock,
@@ -94,18 +92,6 @@ vi.mock("./vercel-build-prewarm.js", () => ({
9492
runVercelBuildPrewarm: runVercelBuildPrewarmMock,
9593
}));
9694

97-
vi.mock("../../workflow-bundle/builder.js", () => ({
98-
WorkflowBundleBuilder: class WorkflowBundleBuilder {
99-
constructor(options: unknown) {
100-
workflowBuilderConstructors.push(options);
101-
}
102-
103-
async buildVercelOutput(options: unknown): Promise<void> {
104-
await workflowBuilderBuildVercelOutputMock(options);
105-
}
106-
},
107-
}));
108-
10995
const createScratchDirectory = useTemporaryDirectories();
11096
const DEPLOYABLE_BUILD_OPTIONS = { skipVercelSandboxPrewarm: false } as const;
11197

@@ -177,7 +163,6 @@ describe("buildApplication", () => {
177163
beforeEach(() => {
178164
vi.resetModules();
179165
vi.clearAllMocks();
180-
workflowBuilderConstructors.length = 0;
181166
});
182167

183168
afterEach(() => {
@@ -223,7 +208,6 @@ describe("buildApplication", () => {
223208
2,
224209
)}\n`,
225210
);
226-
expect(workflowBuilderBuildVercelOutputMock).not.toHaveBeenCalled();
227211
expect(runVercelBuildPrewarmMock).not.toHaveBeenCalled();
228212
await expect(
229213
readFile(join(appRoot, ".eve", "compile", "compiled-agent-manifest.json"), "utf8"),
@@ -265,7 +249,7 @@ describe("buildApplication", () => {
265249
expect(profile.phases).toEqual(
266250
expect.arrayContaining([
267251
expect.objectContaining({ name: "host.prepare" }),
268-
expect.objectContaining({ name: "nitro.all.bundle" }),
252+
expect.objectContaining({ name: "nitro.bundle" }),
269253
expect.objectContaining({ name: "output.publish" }),
270254
expect.objectContaining({ name: "workspace.remove" }),
271255
]),
@@ -384,27 +368,17 @@ describe("buildApplication", () => {
384368
await expect(readFile(summaryPath, "utf8")).resolves.toBe("last-good-summary\n");
385369
});
386370

387-
it("builds isolated Vercel Nitro surfaces and stitches workflow functions", async () => {
371+
it("builds one Vercel Nitro output and normalizes eve functions", async () => {
388372
vi.stubEnv("VERCEL", "1");
389373
const appRoot = await createScratchDirectory("eve-build-application-vercel-");
390374
const profilePath = join(appRoot, ".eve", "profiles", "vercel-build.json");
391-
const stableFlowOutputDir = join(appRoot, ".eve", "nitro-output", "flow");
392-
const staleFlowOutputPath = join(stableFlowOutputDir, "stale-flow.txt");
393375

394376
prepareProductionApplicationHostMock.mockImplementationOnce(prepareHostBuildWorkspace);
395377
createProductionApplicationNitroMock.mockImplementation(
396378
async (_preparedHost: PreparedApplicationHost, options: { outputDir: string }) =>
397379
createNitroStub(options.outputDir),
398380
);
399-
await mkdir(stableFlowOutputDir, { recursive: true });
400-
await Promise.all([
401-
writeFile(
402-
join(stableFlowOutputDir, "eve-cache.json"),
403-
`${JSON.stringify({ eveVersion: "old" })}\n`,
404-
),
405-
writeFile(staleFlowOutputPath, "stale\n"),
406-
mkdir(join(appRoot, ".vercel", "output"), { recursive: true }),
407-
]);
381+
await mkdir(join(appRoot, ".vercel", "output"), { recursive: true });
408382
await writeFile(
409383
join(appRoot, ".vercel", "output", "config.json"),
410384
`${JSON.stringify(
@@ -437,20 +411,14 @@ describe("buildApplication", () => {
437411
});
438412

439413
expect(outputDir).toBe(join(appRoot, ".vercel", "output"));
440-
expect(createProductionApplicationNitroMock).toHaveBeenCalledTimes(2);
441-
expect(createProductionApplicationNitroMock.mock.calls.map((call) => call[1]?.surface)).toEqual(
442-
["app", "flow"],
443-
);
444-
const flowOutputDir = createProductionApplicationNitroMock.mock.calls.find(
445-
(call) => call[1]?.surface === "flow",
446-
)?.[1]?.outputDir;
447-
expect(flowOutputDir).toEqual(expect.stringContaining(join(appRoot, ".eve", "builds")));
448-
expect(workflowBuilderConstructors).toHaveLength(1);
449-
expect(workflowBuilderBuildVercelOutputMock).toHaveBeenCalledWith({
450-
flowNitroOutputDir: flowOutputDir,
451-
outputDir: expect.stringContaining(join(appRoot, ".eve", "builds")),
452-
runtime: "nodejs24.x",
453-
});
414+
expect(createProductionApplicationNitroMock).toHaveBeenCalledTimes(1);
415+
expect(createProductionApplicationNitroMock).toHaveBeenCalledWith(
416+
expect.objectContaining({ appRoot }),
417+
{
418+
buildDir: expect.stringContaining(join(appRoot, ".eve", "builds")),
419+
outputDir: expect.stringContaining(join(appRoot, ".eve", "builds")),
420+
},
421+
);
454422
const nestedFunctionStats = await lstat(
455423
join(appRoot, ".vercel", "output", "functions", "eve", "v1", "health.func"),
456424
);
@@ -491,7 +459,6 @@ describe("buildApplication", () => {
491459
src: "^/eve/v1/session/(?<sessionId>[^/]+)/stream$",
492460
},
493461
]);
494-
await expect(readFile(staleFlowOutputPath, "utf8")).resolves.toBe("stale\n");
495462
expect(runVercelBuildPrewarmMock).toHaveBeenCalledWith(
496463
expect.objectContaining({
497464
appRoot,
@@ -533,20 +500,18 @@ describe("buildApplication", () => {
533500

534501
expect(outputDir).toBe(join(appRoot, ".vercel", "output"));
535502
expect(runVercelBuildPrewarmMock).not.toHaveBeenCalled();
536-
expect(workflowBuilderBuildVercelOutputMock).toHaveBeenCalledTimes(1);
503+
expect(buildNitroMock).toHaveBeenCalledTimes(1);
537504
});
538505

539506
it("normalizes eve function output behind a non-Next host service", async () => {
540507
vi.stubEnv("VERCEL", "1");
541508
const appRoot = await createScratchDirectory("eve-build-application-vercel-nuxt-");
542-
const flowOutputDir = join(appRoot, ".eve", "nitro-output", "flow");
543509

544510
prepareProductionApplicationHostMock.mockImplementationOnce(prepareHostBuildWorkspace);
545511
createProductionApplicationNitroMock.mockImplementation(
546512
async (_preparedHost: PreparedApplicationHost, options: { outputDir: string }) =>
547513
createNitroStub(options.outputDir),
548514
);
549-
await mkdir(flowOutputDir, { recursive: true });
550515
await writeFile(
551516
join(appRoot, "vercel.json"),
552517
`${JSON.stringify(
@@ -705,40 +670,36 @@ describe("buildApplication", () => {
705670
});
706671
});
707672

708-
it("builds isolated Vercel Nitro surfaces from legacy root service config", async () => {
673+
it("normalizes eve function output from legacy root service config", async () => {
709674
vi.stubEnv("VERCEL", "1");
710675
const appRoot = await createScratchDirectory("eve-build-application-vercel-root-config-");
711-
const flowOutputDir = join(appRoot, ".eve", "nitro-output", "flow");
712676

713677
prepareProductionApplicationHostMock.mockImplementationOnce(prepareHostBuildWorkspace);
714678
createProductionApplicationNitroMock.mockImplementation(
715679
async (_preparedHost: PreparedApplicationHost, options: { outputDir: string }) =>
716680
createNitroStub(options.outputDir),
717681
);
718-
await Promise.all([
719-
mkdir(flowOutputDir, { recursive: true }),
720-
writeFile(
721-
join(appRoot, "vercel.json"),
722-
`${JSON.stringify(
723-
{
724-
experimentalServices: {
725-
eve: {
726-
entrypoint: ".",
727-
framework: "eve",
728-
routePrefix: "/_eve_internal/eve",
729-
},
730-
web: {
731-
entrypoint: ".",
732-
framework: "nextjs",
733-
routePrefix: "/",
734-
},
682+
await writeFile(
683+
join(appRoot, "vercel.json"),
684+
`${JSON.stringify(
685+
{
686+
experimentalServices: {
687+
eve: {
688+
entrypoint: ".",
689+
framework: "eve",
690+
routePrefix: "/_eve_internal/eve",
691+
},
692+
web: {
693+
entrypoint: ".",
694+
framework: "nextjs",
695+
routePrefix: "/",
735696
},
736697
},
737-
null,
738-
2,
739-
)}\n`,
740-
),
741-
]);
698+
},
699+
null,
700+
2,
701+
)}\n`,
702+
);
742703

743704
const { buildApplication } = await import("#internal/nitro/host/build-application.js");
744705
const outputDir = await buildApplication(appRoot, DEPLOYABLE_BUILD_OPTIONS);

0 commit comments

Comments
 (0)