1import * as NodeServices from "@effect/platform-node/NodeServices";
2import * as Effect from "effect/Effect";
3import * as FileSystem from "effect/FileSystem";
4import * as Path from "effect/Path";
5import type * as Scope from "effect/Scope";
6import { describe, expect, it } from "vitest";
7import {
8 NODE_BUNDLE_CONDITIONS,
9 NODE_SERVE_ENTRY_FILE_NAME,
10} from "../../core/NodeServe.ts";
11import type { NitroConfigSlice } from "../UserConfig.ts";
12import {
13 NITRO_HANDLER_SPECIFIER,
14 NITRO_PRESET,
15 makeNodeTarget,
16 target,
17} from "../node.ts";
18
19const runWithNode = <A, E>(
20 effect: Effect.Effect<A, E, FileSystem.FileSystem | Path.Path | Scope.Scope>,
21): Promise<A> =>
22 Effect.runPromise(
23 Effect.scoped(effect).pipe(
24 Effect.provide(NodeServices.layer),
25 ) as Effect.Effect<A, E>,
26 );
27
28describe("makeNodeTarget", () => {
29 it("declares the node platform, the node nitro preset, and a finish pass", () => {
30 const node = makeNodeTarget();
31 expect(node.platform).toBe("node");
32 expect(node.nitroPreset).toBe(NITRO_PRESET);
33 expect(NITRO_PRESET).toBe("node");
34 expect(NITRO_PRESET).not.toBe("aws-lambda");
35 expect(NITRO_PRESET).not.toBe("cloudflare_module");
36 expect(node.bundle?.conditions).toEqual([...NODE_BUNDLE_CONDITIONS]);
37 expect(node.bundle?.external ?? []).not.toContain("cloudflare:");
38 expect(node.bundle?.external ?? []).not.toContain("@aws-sdk/");
39 expect(node.build).toBeTypeOf("function");
40 expect(node.finish).toBeTypeOf("function");
41 });
42
43 it("does not wire aws-lambda streaming on the nitro config", () => {
44 const node = makeNodeTarget();
45 const config: NitroConfigSlice = {};
46 node.configureNitro?.(config, { root: "/project" });
47 expect(config.awsLambda).toBeUndefined();
48 expect(config.preset).toBeUndefined();
49 });
50
51 it("exports the node-listener handler specifier for user entries", () => {
52 expect(NITRO_HANDLER_SPECIFIER).toBe(
53 "nitropack/presets/node/runtime/node-listener",
54 );
55 expect(NITRO_HANDLER_SPECIFIER).not.toContain("aws-lambda");
56 });
57
58 it("exposes the named `target` module export as the factory", () => {
59 expect(target).toBe(makeNodeTarget);
60 });
61});
62
63describe("finish", () => {
64 it("wraps nitro's node-listener handler, not the aws-lambda handler", async () => {
65 const output = await runWithNode(
66 Effect.gen(function* () {
67 const fs = yield* FileSystem.FileSystem;
68 const path = yield* Path.Path;
69 const dir = yield* fs.makeTempDirectoryScoped({
70 prefix: "solidstart-node-finish-",
71 });
72 const serverDir = path.join(dir, "server");
73 const publicDir = path.join(dir, "public");
74 yield* fs.makeDirectory(serverDir, { recursive: true });
75 yield* fs.makeDirectory(publicDir, { recursive: true });
76 yield* fs.writeFileString(
77 path.join(serverDir, "index.mjs"),
78 "export const handler = () => {};",
79 );
80 const node = makeNodeTarget();
81 return yield* node.finish!(
82 {
83 distDirectory: dir,
84 clientDirectory: publicDir,
85 serverModules: [],
86 externalWorkspaces: new Set<string>(),
87 },
88 { root: dir, framework: "solidstart" },
89 );
90 }),
91 );
92 expect(output.serverModules?.[0]?.name).toBe(
93 `server/${NODE_SERVE_ENTRY_FILE_NAME}`,
94 );
95 const source = String(output.serverModules?.[0]?.content);
96 expect(source).toContain("/health");
97 expect(source).toContain("process.env.PORT");
98 expect(source).toContain(`import { handler } from "./index.mjs"`);
99 expect(source).not.toContain("aws-lambda");
100 expect(source).not.toContain("streamifyResponse");
101 expect(source).not.toContain("workerd");
102 });
103});