| 1 | import type { InputProps } from "../../Input.ts"; |
| 2 | import * as Namespace from "../../Namespace.ts"; |
| 3 | import { makeFrameworkSite, type FrameworkSiteProps } from "./FrameworkSite.ts"; |
| 4 | |
| 5 | /** The framework-integration package that drives the Astro build. */ |
| 6 | export const ASTRO_FRAMEWORK_SPECIFIER = |
| 7 | "@alchemy.run/frontend-frameworks/astro"; |
| 8 | |
| 9 | /** The AWS Lambda deploy target for the Astro build. */ |
| 10 | export const ASTRO_AWS_TARGET_SPECIFIER = |
| 11 | "@alchemy.run/frontend-frameworks/astro/aws"; |
| 12 | |
| 13 | export interface AstroProps extends FrameworkSiteProps { |
| 14 | // Astro is configured in YOUR `astro.config.*`, which loads natively — |
| 15 | // this resource only deploys it. The `astro` bag is a deploy-time |
| 16 | // overlay merged OVER the file for values a config file cannot hold |
| 17 | // (per-stage values, other resources' Outputs). |
| 18 | /** |
| 19 | * Deploy-time Astro config overrides, merged OVER your natively-loaded |
| 20 | * `astro.config.*` (values here win). Use it for values that vary per |
| 21 | * stage or derive from other resources' Outputs — everything else |
| 22 | * belongs in the config file. |
| 23 | */ |
| 24 | astro?: { |
| 25 | /** Deployed URL origin (astro's `site`). */ |
| 26 | site?: string; |
| 27 | /** Base path the site is served from (astro's `base`). */ |
| 28 | base?: string; |
| 29 | /** |
| 30 | * Astro output target — a deploy-topology decision (whether a |
| 31 | * server function exists). `"server"` renders pages on demand in |
| 32 | * the Lambda; individual pages opt into prerendering with |
| 33 | * `export const prerender = true`. `"static"` prerenders every |
| 34 | * page at build time and deploys assets-only (no Lambda). |
| 35 | * Supersedes a file-level `output`. |
| 36 | * @default "server" |
| 37 | */ |
| 38 | output?: "server" | "static"; |
| 39 | /** Source directory (astro's `srcDir`). */ |
| 40 | srcDir?: string; |
| 41 | /** Public assets directory (astro's `publicDir`). */ |
| 42 | publicDir?: string; |
| 43 | /** Build output directory (astro's `outDir`). */ |
| 44 | outDir?: string; |
| 45 | /** Trailing-slash handling (astro's `trailingSlash`). */ |
| 46 | trailingSlash?: "always" | "never" | "ignore"; |
| 47 | }; |
| 48 | /** |
| 49 | * Path to an alternate astro config file, relative to `rootDir`. |
| 50 | * Defaults to astro's own config discovery. |
| 51 | */ |
| 52 | config?: string; |
| 53 | /** |
| 54 | * Serve the built error page (e.g. astro's `404.html`) for requests that |
| 55 | * match no uploaded file. Only applies to `astro: { output: "static" }` |
| 56 | * sites — a server-backed site forwards misses to the Lambda instead. |
| 57 | */ |
| 58 | errorPage?: string; |
| 59 | /** |
| 60 | * Answer misses with the index page (200) instead of a 404. Only applies |
| 61 | * to `astro: { output: "static" }` sites. |
| 62 | */ |
| 63 | spa?: boolean; |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * Deploy an [Astro](https://astro.build) application to AWS: the server |
| 68 | * bundle on a streaming Lambda Function URL, static assets (prerendered |
| 69 | * pages included) in S3, and a CloudFront distribution whose edge router |
| 70 | * serves uploaded files from S3 and forwards everything else to the server. |
| 71 | * |
| 72 | * The build runs through `@alchemy.run/frontend-frameworks/astro` with the |
| 73 | * `@alchemy.run/frontend-frameworks/astro/aws` deploy target (a wrangler-free |
| 74 | * AWS Lambda adapter is injected — your `astro.config.*` must not declare |
| 75 | * one) — the package must be installed in your project. |
| 76 | * |
| 77 | * Pages render on demand by default (`output: "server"`); pages that |
| 78 | * `export const prerender = true` are prerendered at build time and served |
| 79 | * from S3. With `astro: { output: "static" }` every page is prerendered |
| 80 | * and the deploy is assets-only — no Lambda. |
| 81 | * |
| 82 | * Your `astro.config.*` is the home for Astro configuration and loads |
| 83 | * natively; the `astro` prop is a deploy-time override bag merged OVER |
| 84 | * the file for values that vary per stage or derive from other |
| 85 | * resources' Outputs. `config` points at an alternate config file |
| 86 | * (relative to `rootDir`). |
| 87 | * |
| 88 | * ### Creating Astro Sites |
| 89 | * **Example:** Basic Astro App |
| 90 | * ```typescript |
| 91 | * const site = yield* AWS.Website.Astro("Web", { |
| 92 | * rootDir: "./app", |
| 93 | * }); |
| 94 | * ``` |
| 95 | * |
| 96 | * **Example:** Per-stage site URL override |
| 97 | * ```typescript |
| 98 | * const site = yield* AWS.Website.Astro("Blog", { |
| 99 | * rootDir: "./blog", |
| 100 | * astro: { site: "https://blog.example.com" }, |
| 101 | * }); |
| 102 | * ``` |
| 103 | * |
| 104 | * **Example:** Custom Domain |
| 105 | * ```typescript |
| 106 | * const site = yield* AWS.Website.Astro("Web", { |
| 107 | * rootDir: "./app", |
| 108 | * domain: { |
| 109 | * name: "app.example.com", |
| 110 | * hostedZoneId: zone.hostedZoneId, |
| 111 | * }, |
| 112 | * }); |
| 113 | * ``` |
| 114 | * |
| 115 | * ### Static Sites |
| 116 | * **Example:** Fully Static Astro Site |
| 117 | * ```typescript |
| 118 | * const site = yield* AWS.Website.Astro("Docs", { |
| 119 | * rootDir: "./docs", |
| 120 | * astro: { output: "static" }, |
| 121 | * errorPage: "404.html", |
| 122 | * }); |
| 123 | * ``` |
| 124 | * |
| 125 | * ### Server Configuration |
| 126 | * **Example:** Tune The Server Function |
| 127 | * ```typescript |
| 128 | * const site = yield* AWS.Website.Astro("Web", { |
| 129 | * rootDir: "./app", |
| 130 | * memorySize: 2048, |
| 131 | * env: { |
| 132 | * API_BASE: api.url, |
| 133 | * }, |
| 134 | * }); |
| 135 | * ``` |
| 136 | * |
| 137 | * @resource |
| 138 | */ |
| 139 | export const Astro = (id: string, props: InputProps<AstroProps> = {}) => { |
| 140 | const p = props as AstroProps; |
| 141 | // Server output is the documented default: astro's own zero-config |
| 142 | // default is `"static"`. The inline config merges OVER the project's |
| 143 | // `astro.config.*`, so an explicit file-level `output` is superseded; |
| 144 | // opt into a fully prerendered site with `astro: { output: "static" }`. |
| 145 | const output = p.astro?.output ?? "server"; |
| 146 | return makeFrameworkSite(id, props, { |
| 147 | name: "Astro", |
| 148 | framework: ASTRO_FRAMEWORK_SPECIFIER, |
| 149 | target: ASTRO_AWS_TARGET_SPECIFIER, |
| 150 | options: { astro: { ...p.astro, output }, config: p.config }, |
| 151 | static: |
| 152 | output === "static" ? { spa: p.spa, errorPage: p.errorPage } : undefined, |
| 153 | }).pipe(Namespace.push(id)); |
| 154 | }; |