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 Node
20 * 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 in the
29 * container; individual pages opt into prerendering with
30 * `export const prerender = true`. `"static"` prerenders every page at
31 * build time and deploys assets-only (no framework handler).
32 * @default "server"
33 */
34 output?: "server" | "static";
35 /** Source directory, relative to `rootDir`. @default "./src" */
36 srcDir?: string;
37 /** Public (static passthrough) directory. @default "./public" */
38 publicDir?: string;
39 /** Build output directory. @default "./dist" */
40 outDir?: string;
41 /** Trailing-slash handling for routes. */
42 trailingSlash?: "always" | "never" | "ignore";
43 };
44}
45
46/**
47 * Deploy an [Astro](https://astro.build) application to Railway: the Node
48 * SSR bundle (assets first, then the Astro fetch handler) on one
49 * `Railway.Service`. Pages render on demand by default
50 * (`output: "server"`); pages that `export const prerender = true` are
51 * prerendered at build time and served as static files. With
52 * `astro: { output: "static" }` every page is prerendered and the deploy
53 * is assets-only.
54 *
55 * The build runs through `@alchemy.run/frontend-frameworks/astro` with the
56 * `@alchemy.run/frontend-frameworks/astro/node` deploy target (a Node
57 * adapter is injected — your `astro.config.*` must not declare one).
58 *
59 * During `alchemy dev` the site is Astro's own dev server and no cloud
60 * resources are created. `Alchemy.remote()` opts back into the live
61 * Service path.
62 *
63 * ### Creating Astro Sites
64 * **Example:** Basic Astro App
65 * ```typescript
66 * const site = yield* Railway.Website.Astro("Web", {
67 * rootDir: "./app",
68 * });
69 * ```
70 *
71 * ### Static Sites
72 * **Example:** Fully Static Astro Site
73 * ```typescript
74 * const site = yield* Railway.Website.Astro("Docs", {
75 * rootDir: "./docs",
76 * astro: { output: "static" },
77 * errorPage: "404.html",
78 * });
79 * ```
80 *
81 * ### Server Configuration
82 * **Example:** Process Environment
83 * ```typescript
84 * const site = yield* Railway.Website.Astro("Web", {
85 * rootDir: "./app",
86 * env: {
87 * API_BASE: "https://api.example.com",
88 * },
89 * });
90 * ```
91 *
92 * @resource
93 * @product Website
94 */
95export const Astro = (id: string, props: AstroProps = {}) => {
96 const output = props.astro?.output ?? "server";
97 return makeFrameworkSite(id, props, {
98 name: "Astro",
99 framework: ASTRO_FRAMEWORK_SPECIFIER,
100 target: ASTRO_NODE_TARGET_SPECIFIER,
101 options: { astro: { ...props.astro, output } },
102 static:
103 output === "static" ? staticConfigFromAssets(props.assets) : undefined,
104 }).pipe(Namespace.push(id));
105};