1import * as Namespace from "../../Namespace.ts";
2import {
3 makeFrameworkSite,
4 staticConfigFromAssets,
5 type FrameworkSiteProps,
6} from "./FrameworkSite.ts";
7
8/** The framework-integration package that drives the Astro build. */
9export const ASTRO_FRAMEWORK_SPECIFIER =
10 "@alchemy.run/frontend-frameworks/astro";
11
12/** The Node container deploy target for the Astro build. */
13export const ASTRO_NODE_TARGET_SPECIFIER =
14 "@alchemy.run/frontend-frameworks/astro/node";
15
16export interface AstroProps extends FrameworkSiteProps {
17 /**
18 * Serializable Astro config merged OVER the project's own
19 * `astro.config.*` (which loads natively). `adapter` is owned by the
20 * Node deploy target and may not be set here.
21 */
22 astro?: {
23 /** The full URL the site deploys to (`Astro.site`). */
24 site?: string;
25 /** Base path the site deploys under. */
26 base?: string;
27 /**
28 * Astro output target. `"server"` renders pages on demand;
29 * `"static"` prerenders every page at build time and deploys
30 * assets-only (no SSR unit).
31 * @default "server"
32 */
33 output?: "server" | "static";
34 /** Source directory, relative to `rootDir`. @default "./src" */
35 srcDir?: string;
36 /** Public (static passthrough) directory. @default "./public" */
37 publicDir?: string;
38 /** Build output directory. @default "./dist" */
39 outDir?: string;
40 /** Trailing-slash handling for routes. */
41 trailingSlash?: "always" | "never" | "ignore";
42 };
43}
44
45/**
46 * Deploy an [Astro](https://astro.build) application to a Hetzner Cloud
47 * Server: the Node adapter bundle as a systemd unit on port 3000, static
48 * assets baked into the unit. Pages render on demand by default
49 * (`output: "server"`); `astro: { output: "static" }` prerenders every
50 * page and deploys assets-only.
51 *
52 * The build runs through `@alchemy.run/frontend-frameworks/astro` with the
53 * `@alchemy.run/frontend-frameworks/astro/node` deploy target. Your
54 * `astro.config.*` must not declare an adapter.
55 *
56 *
57 * ### Creating Astro Sites
58 * **Example:** Basic Astro App
59 * ```typescript
60 * const site = yield* Hetzner.Website.Astro("Web", {
61 * rootDir: "./app",
62 * });
63 * ```
64 *
65 * **Example:** Custom Domain
66 * ```typescript
67 * const site = yield* Hetzner.Website.Astro("Web", {
68 * rootDir: "./app",
69 * domain: "app.example.com",
70 * zone,
71 * });
72 * ```
73 *
74 * ### Static Sites
75 * **Example:** Fully Static Astro Site
76 * ```typescript
77 * const site = yield* Hetzner.Website.Astro("Docs", {
78 * rootDir: "./docs",
79 * astro: { output: "static" },
80 * assets: { notFoundHandling: "404-page" },
81 * });
82 * ```
83 *
84 * @resource
85 * @product Website
86 */
87export const Astro = (id: string, props: AstroProps = {}) => {
88 const output = props.astro?.output ?? "server";
89 return makeFrameworkSite(id, props, {
90 name: "Astro",
91 framework: ASTRO_FRAMEWORK_SPECIFIER,
92 target: ASTRO_NODE_TARGET_SPECIFIER,
93 options: { astro: { ...props.astro, output } },
94 static:
95 output === "static" ? staticConfigFromAssets(props.assets) : undefined,
96 }).pipe(Namespace.push(id));
97};