| 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 | */ |
| 28 | import * as Effect from "effect/Effect"; |
| 29 | import { runBuildChild } from "../core/BuildChild.ts"; |
| 30 | import { DeployTargetError, makeDeployTarget } from "../core/index.ts"; |
| 31 | import { make, type NuxtTarget, type NuxtTargetConfig } from "./Nuxt.ts"; |
| 32 | |
| 33 | /** The nitro deployment preset this target builds with. */ |
| 34 | export 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 | */ |
| 45 | export const NITRO_HANDLER_SPECIFIER = |
| 46 | "nitropack/presets/aws-lambda/runtime/aws-lambda-streaming"; |
| 47 | |
| 48 | /** AWS-specific knobs carried on the shared {@link NuxtTargetConfig}. */ |
| 49 | export 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 | |
| 60 | const 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 | */ |
| 69 | const 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 | */ |
| 105 | export interface NuxtAwsBuildChildConfig { |
| 106 | readonly rootDir: string; |
| 107 | /** The (JSON-serializable) target config the parent was created with. */ |
| 108 | readonly config: NuxtAwsTargetConfig; |
| 109 | } |
| 110 | |
| 111 | export 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 | */ |
| 130 | export 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 | */ |
| 150 | export const target = makeAwsTarget; |
| 151 | |
| 152 | export default makeAwsTarget; |