1import type { InputProps } from "../../Input.ts";
2import * as Namespace from "../../Namespace.ts";
3import { makeFrameworkSite } from "./FrameworkSite.ts";
4import {
5 VITE_AWS_TARGET_SPECIFIER,
6 VITE_FRAMEWORK_SPECIFIER,
7 viteFrameworkOptions,
8 type ViteProps,
9} from "./Vite.ts";
10
11/**
12 * Props for {@link Foldkit}. A Foldkit app is a plain Vite project, so the
13 * surface is {@link ViteProps} — `rootDir`, `vite`, `domain`, `spa`,
14 * `errorPage`, and the rest of the shared website props.
15 */
16export interface FoldkitProps extends ViteProps {}
17
18/**
19 * Deploy a [Foldkit](https://foldkit.dev) app to AWS: the client build in
20 * S3 behind a CloudFront distribution. Foldkit is an Elm-architecture
21 * frontend framework built on Effect, and its apps are client-only Vite
22 * projects — so the deployment is assets-only and never creates a server
23 * function.
24 *
25 * The Foldkit Vite plugin lives in your project's own `vite.config.*`,
26 * which loads natively. The build runs through
27 * `@alchemy.run/frontend-frameworks/vite` with the
28 * `@alchemy.run/frontend-frameworks/vite/aws` deploy target — the package
29 * must be installed in your project. Input files are content-hashed so
30 * unchanged projects skip the build and deploy entirely.
31 *
32 * Foldkit apps route on the client, so `spa` defaults on: unmatched paths
33 * serve `index.html` with a `200` and the Foldkit runtime resolves the
34 * route once the app boots.
35 *
36 * During `alchemy dev` the site is Vite's own dev server — Foldkit's HMR
37 * and devtools wiring work unchanged — and no AWS resources are created.
38 * `Alchemy.remote()` opts back into the full deployment.
39 *
40 * ### Creating Foldkit Sites
41 * **Example:** Basic Foldkit App
42 * ```typescript
43 * const site = yield* AWS.Website.Foldkit("Web");
44 * ```
45 *
46 * **Example:** Project in a Subdirectory
47 * ```typescript
48 * const site = yield* AWS.Website.Foldkit("Web", {
49 * rootDir: "applications/web",
50 * });
51 * ```
52 *
53 * **Example:** Custom Domain
54 * ```typescript
55 * const site = yield* AWS.Website.Foldkit("Web", {
56 * domain: {
57 * name: "app.example.com",
58 * hostedZoneId: zone.hostedZoneId,
59 * },
60 * });
61 * ```
62 *
63 * ### Deep Links
64 * A deep link like `/counter/42` arrives at the edge as a request for a
65 * file that does not exist. `spa` is on by default so the shell is served
66 * instead of a 404. An app that ships a real 404 page opts out with
67 * `errorPage` — the two are mutually exclusive.
68 *
69 * **Example:** Serving a Real 404 Page
70 * ```typescript
71 * const site = yield* AWS.Website.Foldkit("Web", {
72 * spa: false,
73 * errorPage: "404.html",
74 * });
75 * ```
76 *
77 * ### Sharing a Router
78 * **Example:** Serve Through an Existing AWS.Website.Router
79 * ```typescript
80 * const router = yield* AWS.Website.Router("Router", {});
81 * const site = yield* AWS.Website.Foldkit("Web", {
82 * domain: { router },
83 * });
84 * ```
85 *
86 * ### Build Configuration
87 * Vite configuration (the Foldkit plugin included) lives in your
88 * project's own `vite.config.*`. The `vite` bag holds deploy-time
89 * overrides merged over that file, and `config` selects an alternate
90 * config file.
91 *
92 * **Example:** Deploy-Time Base Path Override
93 * ```typescript
94 * const site = yield* AWS.Website.Foldkit("Web", {
95 * vite: { base: "/app/" },
96 * });
97 * ```
98 *
99 * ### Local Development
100 * **Example:** Foldkit's Vite Dev Server Under `alchemy dev`
101 * ```typescript
102 * // `alchemy dev` starts `vite` programmatically: site.url is the local
103 * // dev server (Foldkit HMR included); no bucket or distribution is
104 * // created.
105 * const site = yield* AWS.Website.Foldkit("Web");
106 * ```
107 *
108 * @resource
109 */
110export const Foldkit = (id: string, props: InputProps<FoldkitProps> = {}) => {
111 const p = props as FoldkitProps;
112 return makeFrameworkSite(id, props, {
113 name: "Foldkit",
114 framework: VITE_FRAMEWORK_SPECIFIER,
115 target: VITE_AWS_TARGET_SPECIFIER,
116 options: viteFrameworkOptions(p),
117 // Foldkit is client-only: the whole deployable output is the client
118 // build, so the deploy never creates a server function. Foldkit routes
119 // on the client, so `spa` defaults on — but yields to an explicit
120 // `errorPage` (the two are mutually exclusive downstream).
121 static: {
122 spa: p.spa ?? (p.errorPage === undefined ? true : undefined),
123 errorPage: p.errorPage,
124 },
125 }).pipe(Namespace.push(id));
126};