1/**
2 * `@alchemy.run/frontend-frameworks/nuxt/aws` — the AWS Lambda deploy
3 * target for the Nuxt integration.
4 *
5 * Nuxt's AWS story is nitro's `aws-lambda` preset: `.output/server` is a
6 * self-contained Node ESM bundle whose entry exports a Lambda `handler`, and
7 * `.output/public` holds the static assets (prerendered pages included) for
8 * the CDN. What this target owns:
9 *
10 * - **`nitroPreset`** — `"aws-lambda"`, enforced as the last word on the
11 * resolved nitro config.
12 * - **`configureNitro`** — enables response streaming by default
13 * (`awsLambda.streaming: true`): the emitted handler wraps
14 * `awslambda.streamifyResponse` and expects a Lambda Function URL with
15 * `invokeMode: RESPONSE_STREAM`. Set `streaming: false` on the target
16 * config for the buffered APIGW-style handler. Also wires the user-entry
17 * seam: a configured `main` becomes nitro's `entry`.
18 * - **`entry`** — surfaces `config.main` as the generic user-entry carriage
19 * (a module that re-exports nitro's runtime handler and adds its own
20 * exports).
21 * - **`bundle`** — Node resolve conditions and the `@aws-sdk/` externals
22 * (the Lambda Node.js runtime ships SDK v3) for callers that post-process
23 * server code.
24 *
25 * No `devPlatform`: `dev` runs Nuxt's own Node dev server, which is already
26 * the AWS Lambda programming model (plain Node).
27 */
28import * as Effect from "effect/Effect";
29import { runBuildChild } from "../core/BuildChild.ts";
30import { DeployTargetError, makeDeployTarget } from "../core/index.ts";
31import { make, type NuxtTarget, type NuxtTargetConfig } from "./Nuxt.ts";
32
33/** The nitro deployment preset this target builds with. */
34export const NITRO_PRESET = "aws-lambda";
35
36/**
37 * The importable specifier of nitro's aws-lambda streaming runtime handler —
38 * the module a USER entry re-exports to wrap the framework's handler:
39 *
40 * ```ts
41 * // lambda-entry.mjs
42 * export { handler } from "nitropack/presets/aws-lambda/runtime/aws-lambda-streaming";
43 * ```
44 */
45export const NITRO_HANDLER_SPECIFIER =
46 "nitropack/presets/aws-lambda/runtime/aws-lambda-streaming";
47
48/** AWS-specific knobs carried on the shared {@link NuxtTargetConfig}. */
49export interface NuxtAwsTargetConfig extends NuxtTargetConfig {
50 /**
51 * Whether the emitted Lambda handler streams its response
52 * (`awslambda.streamifyResponse`, requires a Function URL with
53 * `invokeMode: RESPONSE_STREAM`). The buffered handler answers
54 * APIGW/Function-URL events with a complete response object.
55 * @default true
56 */
57 readonly streaming?: boolean | undefined;
58}
59
60const fail = (message: string, cause?: unknown) =>
61 new DeployTargetError({ platform: "aws", message, cause });
62
63/**
64 * The adapter-driven target — the shape the framework's regular nitro
65 * build pipeline consumes. Used directly in the build child (where
66 * `cwd === root` holds); {@link makeAwsTarget} wraps it with the wholesale
67 * `build` hook that spawns the child.
68 */
69const makeAwsAdapterTarget = (config: NuxtAwsTargetConfig = {}): NuxtTarget =>
70 makeDeployTarget({
71 platform: "aws",
72 config,
73 bundle: {
74 conditions: ["node", "import", "module"],
75 external: ["@aws-sdk/"],
76 },
77 entry: config.main !== undefined ? { main: config.main } : undefined,
78 nitroPreset: NITRO_PRESET,
79 configureNitro: (nitroConfig, _context) => {
80 const awsLambda =
81 nitroConfig.awsLambda !== null &&
82 typeof nitroConfig.awsLambda === "object"
83 ? (nitroConfig.awsLambda as Record<string, unknown>)
84 : {};
85 nitroConfig.awsLambda = {
86 ...awsLambda,
87 streaming: config.streaming ?? true,
88 };
89 // NOTE: the user entry (context.entry) is deliberately NOT wired here —
90 // the framework package sets it on the nitro INSTANCE at `nitro:init`
91 // (a config-level entry would leak into the prerenderer's Node-preset
92 // clone). See the matching note in ./cloudflare.ts.
93 },
94 });
95
96/**
97 * `loadNuxt` executes the user's `nuxt.config.ts` and modules, which may
98 * read the cwd, mutate `process.env`, or `process.chdir` — none of which
99 * may touch the engine process (many deploys share one event loop). This
100 * target's wholesale `build` therefore runs the framework in a disposable
101 * child process whose working directory IS the project root (see
102 * `core/BuildChild.ts`). The shared `core/BuildChildRunner` entry imports
103 * this module in the child and calls the exported {@link buildInChild}.
104 */
105export interface NuxtAwsBuildChildConfig {
106 readonly rootDir: string;
107 /** The (JSON-serializable) target config the parent was created with. */
108 readonly config: NuxtAwsTargetConfig;
109}
110
111export const buildInChild = (config: NuxtAwsBuildChildConfig) =>
112 Effect.gen(function* () {
113 const framework = yield* make({
114 root: config.rootDir,
115 // The adapter-only target: no wholesale `build` hook, so the child
116 // runs the regular nitro build pipeline (no recursion).
117 target: makeAwsAdapterTarget(config.config),
118 compatibilityDate: config.config.compatibilityDate,
119 compatibilityFlags: config.config.compatibilityFlags,
120 main: config.config.main,
121 nuxt: config.config.nuxt,
122 });
123 return yield* framework.build({ root: config.rootDir });
124 });
125
126/**
127 * Create the AWS Lambda {@link NuxtTarget}. See the module doc for the
128 * seams.
129 */
130export const makeAwsTarget = (
131 config: NuxtAwsTargetConfig = {},
132): NuxtTarget => ({
133 ...makeAwsAdapterTarget(config),
134 build: (context) =>
135 runBuildChild({
136 module: import.meta.url,
137 rootDir: context.root,
138 framework: "nuxt",
139 config: {
140 rootDir: context.root,
141 config,
142 } satisfies NuxtAwsBuildChildConfig,
143 }).pipe(Effect.mapError((error) => fail(error.message, error.cause))),
144});
145
146/**
147 * The deploy-target module contract (`resolveDeployTarget` accepts the
148 * default export — or the named `target` export — as a value or factory).
149 */
150export const target = makeAwsTarget;
151
152export default makeAwsTarget;