1/**
2 * The Astro-specific deploy-target contract.
3 *
4 * framework-core's generic `DeployTarget` covers the platform-neutral seams
5 * (`bundle`, `build`, `finish`, `serve`, opaque `config`). Astro needs one
6 * framework-specific hook on top: the Astro *integration* (the adapter) that
7 * gets pinned into the in-memory `AstroInlineConfig` for both `dev` and
8 * `build`. Everything platform-specific — which vite plugins the adapter
9 * injects, which server entrypoint it selects, how dev SSR executes — lives
10 * inside that integration, so the framework half of this package never has to
11 * know the platform.
12 *
13 * The Cloudflare implementation ships at `@alchemy.run/frontend-frameworks/astro/cloudflare`;
14 * a future platform (AWS, ...) implements the same interface as a new subpath
15 * without any change to the framework module.
16 */
17import {
18 isDeployTarget,
19 type BuildOutput,
20 type DeployTarget,
21 type DeployTargetBuildContext,
22 type DeployTargetError,
23 type DeployTargetInput,
24 type DeployTargetServices,
25 type ServerEntryChunk,
26} from "../core/index.ts";
27import type { AstroInlineConfig, AstroIntegration } from "astro";
28import type * as Effect from "effect/Effect";
29
30/**
31 * The context an Astro target's wholesale `build` takeover receives: the
32 * generic build context plus the inline Astro overlay the framework was
33 * constructed with (`AstroFrameworkOptions.astro`) — carried so wholesale
34 * targets that re-run the framework in a child process (the AWS target)
35 * can reconstruct it. Only JSON-serializable fields survive that process
36 * boundary.
37 */
38export interface AstroTargetBuildContext extends DeployTargetBuildContext {
39 readonly astro?: AstroInlineConfig | undefined;
40}
41
42export interface AstroTarget<Config = unknown> extends DeployTarget<Config> {
43 /**
44 * Build the Astro adapter integration pinned into the inline config
45 * (`AstroInlineConfig.adapter`). Called once per `dev`/`build` operation.
46 */
47 readonly integration: () => AstroIntegration;
48 /**
49 * Optional *wholesale* build takeover (see `DeployTarget.build`), with
50 * the Astro-specific {@link AstroTargetBuildContext}.
51 */
52 readonly build?:
53 | ((
54 context: AstroTargetBuildContext,
55 ) => Effect.Effect<BuildOutput, DeployTargetError, DeployTargetServices>)
56 | undefined;
57 /**
58 * Optional entry-chunk pin for the build-output collector: astro's page
59 * modules are rolldown inputs of the `ssr` environment, so it emits many
60 * entry chunks. A target whose adapter entry is not otherwise
61 * identifiable (e.g. the AWS target's `serverEntrypoint`, which astro
62 * renames to `config.build.serverEntry`) selects the chunk that becomes
63 * `serverModules[0]` here. The Cloudflare target does not need it — its
64 * vite plugin wraps the worker entry with a recognizable facade.
65 */
66 readonly selectServerEntry?:
67 | ((chunk: ServerEntryChunk) => boolean)
68 | undefined;
69}
70
71/**
72 * How a target reaches the Astro framework module: a target value, a
73 * `(config) => AstroTarget` factory, or a module specifier resolved from the
74 * *project's* `node_modules` (whose `default` — or named `target` — export is
75 * the value or factory).
76 */
77export type AstroTargetInput<Config = unknown> = DeployTargetInput<
78 AstroTarget<Config>,
79 Config
80>;
81
82/**
83 * The default target module: the Cloudflare implementation shipped by this
84 * package, resolved from the project's own dependency tree.
85 */
86export const DEFAULT_TARGET_SPECIFIER =
87 "@alchemy.run/frontend-frameworks/astro/cloudflare";
88
89/** Structural guard: a `DeployTarget` carrying the Astro `integration` hook. */
90export const isAstroTarget = (value: unknown): value is AstroTarget =>
91 isDeployTarget(value) &&
92 typeof (value as { integration?: unknown }).integration === "function";