1import type { Input } from "../../Input.ts";
2import type { Certificate } from "../ACM/Certificate.ts";
3import type { Distribution } from "../CloudFront/Distribution.ts";
4import type { Records } from "../Route53/Records.ts";
5import type { AssetFileOption } from "./AssetDeployment.ts";
6import type { Bucket } from "../S3/Bucket.ts";
7
8/**
9 * Same-stack resources a Router-attached site binds its concrete hostnames
10 * onto, so declaring `domain: { name, router }` on the site alone is enough
11 * for the hostname to be fully provisioned — distribution alias, certificate
12 * SAN, and Route 53 record. Populated by `AWS.Website.Router` when it owns a
13 * `domain`; absent on cross-stack references (bindings are same-stack), in
14 * which case the site registers KV host-matching only and the hostname must
15 * be covered by the Router's own `domain` configuration.
16 */
17export interface WebsiteRouterBindTargets {
18 /**
19 * The Router's CloudFront distribution — bound hostnames become
20 * distribution aliases.
21 */
22 distribution?: Distribution;
23 /**
24 * The Router's managed ACM certificate — bound hostnames become
25 * certificate SANs (a SAN change replaces the certificate, create-first).
26 * Absent when the Router uses a user-provided `domain.cert`, which must
27 * then already cover attached-site hostnames (e.g. a wildcard
28 * certificate).
29 */
30 certificate?: Certificate;
31 /**
32 * The Router's Route 53 alias record set — bound hostnames get A-alias
33 * records pointing at the distribution. Absent when the Router's domain
34 * sets `dns: false`. Without an explicit `hostedZoneId`, the set infers
35 * its zone from the first bound hostname.
36 */
37 records?: Records;
38}
39
40/**
41 * Structural slice of an `AWS.Website.Router` that a site attaches to via
42 * `domain.router` — satisfied by the Router's own return value.
43 */
44export interface WebsiteRouterRef {
45 kvStoreArn: Input<string>;
46 kvNamespace: Input<string>;
47 distributionId: Input<string>;
48 distributionArn: Input<string>;
49 url: Input<string>;
50 /**
51 * Same-stack bind targets for attached-site hostnames (see
52 * {@link WebsiteRouterBindTargets}). Cross-stack refs omit this — the
53 * attached site then falls back to KV host-matching only.
54 */
55 bindTargets?: WebsiteRouterBindTargets | undefined;
56}
57
58/**
59 * A standalone custom domain: the site (or Router) owns its own CloudFront
60 * distribution, and the domain is attached as a distribution alias with an
61 * ACM certificate and Route 53 records.
62 */
63export interface WebsiteStandaloneDomainProps {
64 /**
65 * The canonical hostname (e.g. `"www.example.com"`). Attached to the
66 * distribution as an alias — certificate and Route 53 records are managed
67 * automatically (see {@link cert} and {@link dns} to opt out).
68 *
69 * When set, `https://<name>` is the site's primary `url` output.
70 */
71 name: string;
72 /**
73 * Hosted zone used for Route 53 automation. Optional — when omitted,
74 * the most specific PUBLIC hosted zone in the account containing each
75 * hostname is inferred by walking its parent domains; the deploy fails
76 * actionably when no zone matches. Pass an explicit id to pin the zone
77 * (e.g. when several zones could match).
78 */
79 hostedZoneId?: string;
80 /**
81 * Additional hostnames that serve the site (e.g. `"example.com"`,
82 * `"docs.example.com"`). Each is attached as its own distribution alias,
83 * certificate SAN, and Route 53 record. Order matters: aliases follow
84 * `name` in the `urls` output.
85 */
86 aliases?: string[];
87 /**
88 * Hostnames that permanently redirect (HTTP 301, path and query
89 * preserved) to {@link name} — e.g. `"old.example.com"`. Each is attached
90 * as a distribution alias (for TLS) with a certificate SAN and Route 53
91 * record, and the generated viewer-request CloudFront Function issues the
92 * redirect. Redirect hostnames serve no content, so they appear in the
93 * `domain` output but never in `urls`.
94 */
95 redirects?: string[];
96 /**
97 * Existing ACM certificate ARN to use instead of creating one.
98 */
99 cert?: Input<string>;
100 /**
101 * Disable Route 53 automation. When set, no DNS records are created.
102 */
103 dns?: false;
104 /**
105 * Never set on a standalone domain — attach to a Router by setting
106 * {@link WebsiteRouterDomainProps.router}.
107 */
108 router?: undefined;
109 /**
110 * Never set on a standalone domain — `path` only applies to
111 * Router-attached sites ({@link WebsiteRouterDomainProps.path}).
112 */
113 path?: undefined;
114}
115
116/**
117 * A Router-attached domain: the site owns no distribution — it registers
118 * itself in the Router's KV store and is served through the Router's
119 * distribution, matched by host pattern and path prefix.
120 */
121export interface WebsiteRouterDomainProps {
122 /**
123 * Host pattern this site is served for on the Router — an exact hostname
124 * (`"docs.example.com"`) or a wildcard pattern (`"*.example.com"`). When
125 * omitted, the site matches any host on the Router.
126 *
127 * Pattern semantics exist ONLY in Router mode: a standalone
128 * {@link WebsiteStandaloneDomainProps.name} is always a concrete
129 * hostname.
130 *
131 * A concrete hostname on a same-stack Router that owns a `domain` is
132 * fully provisioned from this declaration alone: the site binds it onto
133 * the Router's distribution (alias), certificate (SAN), and Route 53
134 * record set (see {@link WebsiteRouterBindTargets}). Wildcard patterns
135 * bind nothing concrete — a wildcard-matched site still needs the
136 * Router's own `domain`/certificate to cover its hostnames (e.g. a
137 * wildcard alias on the Router). The same applies to cross-stack Router
138 * references, where bindings cannot flow.
139 */
140 name?: string;
141 /**
142 * Additional host patterns that serve the site through the Router. Each
143 * registers its own KV route entry. Requires {@link name}. Order
144 * matters: aliases follow `name` in the `urls` output.
145 */
146 aliases?: string[];
147 /**
148 * Exact hostnames that permanently redirect (HTTP 301, path and query
149 * preserved) to {@link name}. The Router's edge function issues the
150 * redirect when a matched request arrives on one of these hosts.
151 * Requires a concrete (non-wildcard) {@link name}. Redirect hostnames
152 * appear in the `domain` output but never in `urls`.
153 */
154 redirects?: string[];
155 /**
156 * The `AWS.Website.Router` to attach to (or any structural slice with
157 * its KV store, namespace, distribution, and URL outputs).
158 */
159 router: WebsiteRouterRef;
160 /**
161 * Path prefix the site is served under (e.g. `"/docs"`).
162 * @default "/"
163 */
164 path?: string;
165}
166
167/**
168 * A website's custom-domain configuration: either a standalone domain (the
169 * site owns its own CloudFront distribution) or a Router attachment (the
170 * site is served through an existing `AWS.Website.Router`).
171 */
172export type WebsiteDomainProps =
173 | WebsiteStandaloneDomainProps
174 | WebsiteRouterDomainProps;
175
176/**
177 * Accepted `domain` prop shape: a bare hostname string (shorthand for
178 * `{ name }`), a full config object, or `null` to explicitly clear.
179 */
180export type WebsiteDomainInput = string | WebsiteDomainProps | null;
181
182/**
183 * Normalize the accepted `domain` prop shapes (`string` shorthand, `null`
184 * clear) into the object form. A bare string is always a standalone
185 * canonical hostname, so the shorthand result satisfies any accepted
186 * domain shape.
187 * @internal
188 */
189export const normalizeWebsiteDomain = <D extends WebsiteDomainProps>(
190 domain: string | D | null | undefined,
191): D | undefined =>
192 domain == null
193 ? undefined
194 : typeof domain === "string"
195 ? ({ name: domain } as D)
196 : domain;
197
198export interface WebsiteRewrite {
199 /**
200 * Regex matched against the request URI.
201 */
202 regex: string;
203 /**
204 * Replacement path forwarded to the origin.
205 */
206 to: string;
207}
208
209export interface WebsiteEdgeInjection {
210 /**
211 * JavaScript injected into the generated CloudFront Function body.
212 */
213 injection: string;
214 /**
215 * Optional associated KeyValueStore ARN for the function.
216 */
217 keyValueStoreArn?: Input<string>;
218}
219
220export interface WebsiteEdgeProps {
221 /**
222 * Additional logic for viewer request handling.
223 */
224 viewerRequest?: WebsiteEdgeInjection;
225 /**
226 * Additional logic for viewer response handling.
227 */
228 viewerResponse?: WebsiteEdgeInjection;
229}
230
231export interface WebsiteInvalidationProps {
232 /**
233 * Wait for the CloudFront invalidation to finish.
234 * @default false
235 */
236 wait?: boolean;
237 /**
238 * Paths to invalidate.
239 * @default "all"
240 */
241 paths?: "all" | "versioned" | string[];
242}
243
244/**
245 * Character encoding appended as `charset` to inferred text-based content
246 * types (`none` omits the charset entirely).
247 */
248export type WebsiteTextEncoding =
249 | "utf-8"
250 | "iso-8859-1"
251 | "windows-1252"
252 | "ascii"
253 | "none";
254
255export interface StaticSiteBuildProps {
256 /**
257 * Command used to build the site before upload.
258 */
259 command: string;
260 /**
261 * Directory containing the build output, relative to `path`.
262 */
263 output: string;
264 /**
265 * Environment variables exposed to the build command.
266 */
267 env?: Record<string, Input<string>>;
268 /**
269 * Glob patterns of files to hash. Paths are relative to `path`.
270 * When the hash of matched files changes, the build will re-run.
271 *
272 * @default ["**\/*"] (all files, filtered by `exclude`)
273 * @example ["src/**", "package.json", "tsconfig.json"]
274 */
275 include?: string[];
276 /**
277 * Glob patterns to exclude from input hashing. Paths are relative to `path`.
278 *
279 * @default gitignore rules collected from the working directory up to the repo root
280 */
281 exclude?: string[];
282 /**
283 * Whether to include the nearest package-manager lockfile in the hash,
284 * even when it lives above the site directory (e.g. monorepo root).
285 *
286 * @default true when both `include` and `exclude` are unset; false otherwise
287 */
288 lockfile?: boolean;
289}
290
291export interface StaticSiteAssetsProps {
292 /**
293 * Existing bucket used for asset uploads.
294 * When a string bucket name is provided, bucket policies must be managed
295 * separately because Alchemy cannot bind to an external bucket resource.
296 */
297 bucket?: Bucket;
298 /**
299 * Optional path prefix inside the bucket.
300 */
301 path?: string;
302 /**
303 * Remove stale files under the bucket path prefix.
304 * @default true
305 */
306 purge?: boolean;
307 /**
308 * Additional route prefixes that should be served directly from the bucket.
309 */
310 routes?: string[];
311 /**
312 * Character encoding used for text-based assets.
313 * @default "utf-8"
314 */
315 textEncoding?: WebsiteTextEncoding;
316}
317
318/**
319 * Static-asset upload configuration for the website composites:
320 * {@link StaticSiteAssetsProps} plus per-file overrides.
321 */
322export interface WebsiteAssetsConfig extends StaticSiteAssetsProps {
323 /**
324 * Per-file overrides for content type and cache-control.
325 */
326 fileOptions?: AssetFileOption[];
327}
328
329export interface RouterUrlRouteProps {
330 /**
331 * Destination URL.
332 */
333 url: Input<string>;
334 /**
335 * Optional rewrite applied before forwarding.
336 */
337 rewrite?: WebsiteRewrite;
338 /**
339 * Optional origin override configuration.
340 */
341 origin?: Record<string, any>;
342 /**
343 * Origin protocol policy (used by SsrSite for server origins).
344 */
345 originProtocolPolicy?: string;
346}
347
348export interface RouterBucketRouteProps {
349 /**
350 * Bucket or bucket regional domain name served by the route.
351 */
352 bucket: Bucket | string;
353 /**
354 * Optional rewrite applied before forwarding.
355 */
356 rewrite?: WebsiteRewrite;
357 /**
358 * Optional origin override configuration.
359 */
360 origin?: Record<string, any>;
361 /**
362 * Optional CloudFront OAC to attach to the S3 origin (used by SsrSite).
363 */
364 originAccessControlId?: Input<string>;
365 /**
366 * Additional origin path prefix (used by SsrSite).
367 */
368 originPath?: Input<string>;
369 /**
370 * Version token for invalidation (used by SsrSite).
371 */
372 version?: Input<string>;
373}
374
375/**
376 * An inline route target: a destination URL string, a URL route with rewrite
377 * options, or an S3 bucket route.
378 */
379export type RouterRoute = string | RouterUrlRouteProps | RouterBucketRouteProps;
380
381export interface RouterProps {
382 /**
383 * Optional custom domain managed through Route 53. A string is shorthand
384 * for `{ name }`; `null` explicitly clears a previously set domain.
385 */
386 domain?: string | WebsiteStandaloneDomainProps | null;
387 /**
388 * Serve the Router at its CloudFront default domain
389 * (`https://dxxxx.cloudfront.net`). The default domain cannot be removed
390 * from a distribution, so `false` is emulated at the edge: the Router's
391 * viewer-request CloudFront Function 301s requests that arrive on the
392 * default domain to `https://<domain.name>` (path and query preserved),
393 * and the default domain is excluded from the `urls` output.
394 *
395 * Requires `domain` when `false` (the Router would be unreachable).
396 * @default true
397 */
398 cloudfrontUrl?: boolean;
399 /**
400 * Optional inline routes keyed by path pattern.
401 * Sites register lazily via the KV store; inline routes are for
402 * URL-based or bucket-based origins that aren't managed by StaticSite.
403 */
404 routes?: Record<string, RouterRoute>;
405 /**
406 * Optional edge behavior shared by the router's default behavior.
407 */
408 edge?: WebsiteEdgeProps;
409 /**
410 * Optional invalidation behavior for route updates.
411 * @default false
412 */
413 invalidation?: false | WebsiteInvalidationProps;
414 /**
415 * User-defined tags applied to created resources.
416 */
417 tags?: Record<string, string>;
418}
419
420/**
421 * Route targets returned by `SsrSite` for composition with
422 * `AWS.Website.Router` (used with `cdn: false`).
423 */
424export interface SsrSiteRouteTargets {
425 /**
426 * URL route pointing at the dynamic server origin.
427 */
428 server: RouterUrlRouteProps;
429 /**
430 * Optional bucket route for the static asset origin.
431 */
432 assets?: {
433 /**
434 * Path pattern the assets should be served under (e.g. `/_assets/*`).
435 */
436 pattern: string;
437 /**
438 * S3 bucket route for the assets, including the OAC and version token.
439 */
440 route: RouterBucketRouteProps;
441 };
442}