alchemy 2.0.0-beta.78 → 2.0.0-beta.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/bin/cli.js +25 -65
  2. package/bin/exec.js +1 -1
  3. package/bin/tsconfig.json +5 -0
  4. package/lib/Cloudflare/Workers/InferEnv.d.ts +3 -1
  5. package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
  6. package/lib/Cloudflare/Workers/Worker.d.ts +5 -2
  7. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  8. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  9. package/lib/Prisma/Providers.d.ts +4 -1
  10. package/lib/Prisma/Providers.d.ts.map +1 -1
  11. package/lib/Prisma/Providers.js +7 -2
  12. package/lib/Prisma/Providers.js.map +1 -1
  13. package/lib/Prisma/Website/Artifact.d.ts +52 -0
  14. package/lib/Prisma/Website/Artifact.d.ts.map +1 -0
  15. package/lib/Prisma/Website/Artifact.js +285 -0
  16. package/lib/Prisma/Website/Artifact.js.map +1 -0
  17. package/lib/Prisma/Website/Astro.d.ts +62 -0
  18. package/lib/Prisma/Website/Astro.d.ts.map +1 -0
  19. package/lib/Prisma/Website/Astro.js +50 -0
  20. package/lib/Prisma/Website/Astro.js.map +1 -0
  21. package/lib/Prisma/Website/Foldkit.d.ts +30 -0
  22. package/lib/Prisma/Website/Foldkit.d.ts.map +1 -0
  23. package/lib/Prisma/Website/Foldkit.js +30 -0
  24. package/lib/Prisma/Website/Foldkit.js.map +1 -0
  25. package/lib/Prisma/Website/FrameworkSite.d.ts +82 -0
  26. package/lib/Prisma/Website/FrameworkSite.d.ts.map +1 -0
  27. package/lib/Prisma/Website/FrameworkSite.js +96 -0
  28. package/lib/Prisma/Website/FrameworkSite.js.map +1 -0
  29. package/lib/Prisma/Website/Nextjs.d.ts +35 -0
  30. package/lib/Prisma/Website/Nextjs.d.ts.map +1 -0
  31. package/lib/Prisma/Website/Nextjs.js +37 -0
  32. package/lib/Prisma/Website/Nextjs.js.map +1 -0
  33. package/lib/Prisma/Website/Nuxt.d.ts +37 -0
  34. package/lib/Prisma/Website/Nuxt.d.ts.map +1 -0
  35. package/lib/Prisma/Website/Nuxt.js +37 -0
  36. package/lib/Prisma/Website/Nuxt.js.map +1 -0
  37. package/lib/Prisma/Website/Octane.d.ts +35 -0
  38. package/lib/Prisma/Website/Octane.d.ts.map +1 -0
  39. package/lib/Prisma/Website/Octane.js +36 -0
  40. package/lib/Prisma/Website/Octane.js.map +1 -0
  41. package/lib/Prisma/Website/ReactRouter.d.ts +35 -0
  42. package/lib/Prisma/Website/ReactRouter.d.ts.map +1 -0
  43. package/lib/Prisma/Website/ReactRouter.js +36 -0
  44. package/lib/Prisma/Website/ReactRouter.js.map +1 -0
  45. package/lib/Prisma/Website/SolidStart.d.ts +37 -0
  46. package/lib/Prisma/Website/SolidStart.d.ts.map +1 -0
  47. package/lib/Prisma/Website/SolidStart.js +37 -0
  48. package/lib/Prisma/Website/SolidStart.js.map +1 -0
  49. package/lib/Prisma/Website/StaticSite.d.ts +57 -0
  50. package/lib/Prisma/Website/StaticSite.d.ts.map +1 -0
  51. package/lib/Prisma/Website/StaticSite.js +118 -0
  52. package/lib/Prisma/Website/StaticSite.js.map +1 -0
  53. package/lib/Prisma/Website/SvelteKit.d.ts +37 -0
  54. package/lib/Prisma/Website/SvelteKit.d.ts.map +1 -0
  55. package/lib/Prisma/Website/SvelteKit.js +37 -0
  56. package/lib/Prisma/Website/SvelteKit.js.map +1 -0
  57. package/lib/Prisma/Website/TanStackStart.d.ts +35 -0
  58. package/lib/Prisma/Website/TanStackStart.d.ts.map +1 -0
  59. package/lib/Prisma/Website/TanStackStart.js +36 -0
  60. package/lib/Prisma/Website/TanStackStart.js.map +1 -0
  61. package/lib/Prisma/Website/Vite.d.ts +42 -0
  62. package/lib/Prisma/Website/Vite.d.ts.map +1 -0
  63. package/lib/Prisma/Website/Vite.js +38 -0
  64. package/lib/Prisma/Website/Vite.js.map +1 -0
  65. package/lib/Prisma/Website/Vocs.d.ts +37 -0
  66. package/lib/Prisma/Website/Vocs.d.ts.map +1 -0
  67. package/lib/Prisma/Website/Vocs.js +38 -0
  68. package/lib/Prisma/Website/Vocs.js.map +1 -0
  69. package/lib/Prisma/Website/Waku.d.ts +44 -0
  70. package/lib/Prisma/Website/Waku.d.ts.map +1 -0
  71. package/lib/Prisma/Website/Waku.js +38 -0
  72. package/lib/Prisma/Website/Waku.js.map +1 -0
  73. package/lib/Prisma/Website/index.d.ts +15 -0
  74. package/lib/Prisma/Website/index.d.ts.map +1 -0
  75. package/lib/Prisma/Website/index.js +14 -0
  76. package/lib/Prisma/Website/index.js.map +1 -0
  77. package/lib/Prisma/index.d.ts +1 -0
  78. package/lib/Prisma/index.d.ts.map +1 -1
  79. package/lib/Prisma/index.js +1 -0
  80. package/lib/Prisma/index.js.map +1 -1
  81. package/lib/State/HttpStateApi.d.ts +11 -11
  82. package/lib/Website/Server.d.ts.map +1 -1
  83. package/lib/Website/Server.js +18 -8
  84. package/lib/Website/Server.js.map +1 -1
  85. package/package.json +6 -6
  86. package/src/Cloudflare/Workers/InferEnv.ts +5 -3
  87. package/src/Cloudflare/Workers/Worker.ts +25 -5
  88. package/src/Prisma/Providers.ts +11 -0
  89. package/src/Prisma/Website/Artifact.ts +429 -0
  90. package/src/Prisma/Website/Astro.ts +72 -0
  91. package/src/Prisma/Website/Foldkit.ts +34 -0
  92. package/src/Prisma/Website/FrameworkSite.ts +204 -0
  93. package/src/Prisma/Website/Nextjs.ts +41 -0
  94. package/src/Prisma/Website/Nuxt.ts +44 -0
  95. package/src/Prisma/Website/Octane.ts +40 -0
  96. package/src/Prisma/Website/ReactRouter.ts +40 -0
  97. package/src/Prisma/Website/SolidStart.ts +44 -0
  98. package/src/Prisma/Website/StaticSite.ts +149 -0
  99. package/src/Prisma/Website/SvelteKit.ts +44 -0
  100. package/src/Prisma/Website/TanStackStart.ts +40 -0
  101. package/src/Prisma/Website/Vite.ts +50 -0
  102. package/src/Prisma/Website/Vocs.ts +45 -0
  103. package/src/Prisma/Website/Waku.ts +52 -0
  104. package/src/Prisma/Website/index.ts +19 -0
  105. package/src/Prisma/index.ts +1 -0
  106. package/src/Website/Server.ts +21 -8
@@ -0,0 +1,429 @@
1
+ import { makeNodeServeEntrySource } from "@alchemy.run/frontend-frameworks/core";
2
+ import * as Effect from "effect/Effect";
3
+ import * as FileSystem from "effect/FileSystem";
4
+ import * as Path from "effect/Path";
5
+ import type { PlatformError } from "effect/PlatformError";
6
+ import { createRequire } from "node:module";
7
+ import { createPhysicalName } from "../../PhysicalName.ts";
8
+ import * as Provider from "../../Provider.ts";
9
+ import { Resource } from "../../Resource.ts";
10
+ import { initialCwd } from "../../Util/Node.ts";
11
+ import { packSiteExtraFiles } from "../../Website/packExtraFiles.ts";
12
+ import { createComputeArchive } from "../ComputeArchive.ts";
13
+
14
+ /** Resolved framework output to stage after Website.Server has built it. */
15
+ export interface WebsiteArtifactProps {
16
+ /** Application root, relative to the initial working directory. */
17
+ root: string;
18
+ /** Build output directory, relative to the initial working directory. */
19
+ distDir: string;
20
+ /** Node serve entry, relative to the initial working directory. */
21
+ serverEntry?: string;
22
+ /** Generate the shared static-file server instead of tracing a framework entry. */
23
+ static?: {
24
+ /** Unmatched-path behavior. @default "none" */
25
+ notFoundHandling?: "none" | "spa" | "404-page";
26
+ /** Extensionless HTML handling. @default "none" */
27
+ htmlHandling?: "none" | "drop-trailing-slash";
28
+ /** HTML file served for misses, relative to the static output. @default "404.html" */
29
+ errorPage?: string;
30
+ };
31
+ /** Next.js keeps its output and configuration beside the serve entry. */
32
+ layout?: "output" | "next";
33
+ /** Build output fingerprint; keeps the build dependency explicit. */
34
+ buildHash?: string;
35
+ }
36
+
37
+ /** Internal, filesystem-only artifact lifecycle; never creates cloud resources. */
38
+ export interface WebsiteArtifact extends Resource<
39
+ "Prisma.WebsiteArtifact",
40
+ WebsiteArtifactProps,
41
+ {
42
+ /** Private directory owned by this artifact resource. */
43
+ directory: string;
44
+ /** Verified Compute tarball path. */
45
+ artifactPath: string;
46
+ /** SHA-256 of the tarball uploaded by Compute. */
47
+ hash: string;
48
+ }
49
+ > {}
50
+
51
+ export const WebsiteArtifact = Resource<WebsiteArtifact>(
52
+ "Prisma.WebsiteArtifact",
53
+ );
54
+
55
+ const MAX_ENTRIES = 50_000;
56
+ const MAX_BYTES = 256 * 1024 * 1024;
57
+ const MAX_FILE_BYTES = 128 * 1024 * 1024;
58
+ const excluded = (relative: string) =>
59
+ relative
60
+ .split(/[\\/]/)
61
+ .some((part) =>
62
+ /^(?:\.git|\.alchemy|\.env(?:\..*)?|\.envrc|\.npmrc|\.yarnrc.*|\.netrc|\.pypirc|\.aws|\.ssh)$/.test(
63
+ part,
64
+ ),
65
+ );
66
+ const javascript = (file: string) => /\.[cm]?[jt]sx?$/.test(file);
67
+ const nextExcluded = (relative: string) =>
68
+ /^\.next\/(?:cache|standalone|types|diagnostics|dev)(?:\/|$)/.test(
69
+ relative,
70
+ ) || /^\.next\/trace(?:-|$)/.test(relative);
71
+
72
+ const fail = (message: string) => Effect.fail(new Error(message));
73
+
74
+ /**
75
+ * Stage real build files and NFT's reachable runtime graph without relocating
76
+ * individual modules. Keeping their relative layout preserves import.meta.url,
77
+ * package scopes, pnpm links, and dynamically loaded framework chunks.
78
+ * The caller owns the surrounding Scope and must archive before it closes.
79
+ */
80
+ export const stageWebsiteArtifact = Effect.fn(function* (
81
+ props: WebsiteArtifactProps,
82
+ ) {
83
+ const fs = yield* FileSystem.FileSystem;
84
+ const path = yield* Path.Path;
85
+ const root = yield* fs.realPath(path.resolve(initialCwd, props.root));
86
+ const dist = yield* fs.realPath(path.resolve(initialCwd, props.distDir));
87
+ const entry =
88
+ props.serverEntry === undefined
89
+ ? undefined
90
+ : yield* fs.realPath(path.resolve(initialCwd, props.serverEntry));
91
+ if (entry === undefined && props.static === undefined) {
92
+ return yield* fail(
93
+ "A website artifact requires a Node server entry or static serving options.",
94
+ );
95
+ }
96
+ if (entry !== undefined && (yield* fs.stat(entry)).type !== "File") {
97
+ return yield* fail(`Website server entry is not a file: ${entry}`);
98
+ }
99
+ if (props.layout !== "next" && dist === root) {
100
+ return yield* fail(
101
+ "Website output must be a dedicated build directory, not the application root.",
102
+ );
103
+ }
104
+ const files = new Set<string>(entry === undefined ? [] : [entry]);
105
+ const seeds = new Set<string>(entry === undefined ? [] : [entry]);
106
+ const manifests: string[] = [];
107
+ let visited = 0;
108
+ const collect: (
109
+ source: string,
110
+ ancestors?: ReadonlySet<string>,
111
+ runtimePackage?: boolean,
112
+ boundary?: string,
113
+ ) => Effect.Effect<void, Error | PlatformError> = Effect.fn(function* (
114
+ source,
115
+ ancestors = new Set(),
116
+ runtimePackage = false,
117
+ boundary,
118
+ ) {
119
+ if (++visited > MAX_ENTRIES)
120
+ return yield* fail(
121
+ "Website artifact exceeds the 50,000-entry safety limit.",
122
+ );
123
+ const relative = path.relative(root, source).replaceAll("\\", "/");
124
+ if (
125
+ excluded(relative) ||
126
+ (runtimePackage && /(?:\.map|\.d\.ts)$/.test(source)) ||
127
+ (props.layout === "next" && nextExcluded(relative))
128
+ )
129
+ return;
130
+ const real = yield* fs.realPath(source);
131
+ const relativeToBoundary = path.relative(boundary ?? real, real);
132
+ if (
133
+ relativeToBoundary === ".." ||
134
+ relativeToBoundary.startsWith(`..${path.sep}`) ||
135
+ path.isAbsolute(relativeToBoundary)
136
+ ) {
137
+ return yield* fail(
138
+ `Website output symlink escapes its selected directory: ${source}`,
139
+ );
140
+ }
141
+ const stat = yield* fs.stat(real);
142
+ if (stat.type === "Directory") {
143
+ if (ancestors.has(real))
144
+ return yield* fail(`Cyclic website output symlink: ${source}`);
145
+ const next = new Set([...ancestors, real]);
146
+ for (const name of yield* fs.readDirectory(source)) {
147
+ // Runtime dependencies are traced, never copied wholesale.
148
+ if (name === "node_modules") continue;
149
+ yield* collect(
150
+ path.join(source, name),
151
+ next,
152
+ runtimePackage,
153
+ boundary ?? real,
154
+ );
155
+ }
156
+ } else if (stat.type === "File") {
157
+ files.add(source);
158
+ if (!runtimePackage && props.static === undefined && javascript(source))
159
+ seeds.add(source);
160
+ if (props.layout === "next" && source.endsWith(".nft.json"))
161
+ manifests.push(source);
162
+ } else {
163
+ return yield* fail(`Unsupported website output file: ${source}`);
164
+ }
165
+ });
166
+
167
+ const extras = yield* packSiteExtraFiles(
168
+ props.layout === "next" ? root : dist,
169
+ props.layout === "next" ? "next" : "client",
170
+ );
171
+ if (
172
+ props.layout === "next" &&
173
+ !(yield* fs.exists(path.join(root, ".next", "BUILD_ID")))
174
+ ) {
175
+ return yield* fail(
176
+ "Next.js output is missing .next/BUILD_ID; run the production build before packaging.",
177
+ );
178
+ }
179
+ for (const extra of extras ?? []) yield* collect(extra.source);
180
+ // Also include config extensions not currently listed by packSiteExtraFiles.
181
+ if (props.layout === "next") {
182
+ for (const name of ["next.config.mts", "next.config.cts"]) {
183
+ const config = path.join(root, name);
184
+ if (yield* fs.exists(config)) yield* collect(config);
185
+ }
186
+ }
187
+
188
+ if (props.layout === "next") {
189
+ const nextPackage = yield* Effect.try(() =>
190
+ createRequire(path.join(root, "package.json")).resolve(
191
+ "next/package.json",
192
+ ),
193
+ );
194
+ const nextRoot = path.dirname(nextPackage);
195
+ // Next's custom server resolves internal modules through runtime alias
196
+ // tables that NFT cannot evaluate. Keep that package, not node_modules.
197
+ yield* collect(nextRoot, undefined, true);
198
+ yield* collect(path.join(nextRoot, "dist", "compiled", "webpack"));
199
+ }
200
+
201
+ for (const manifest of manifests) {
202
+ const text = yield* fs.readFileString(manifest);
203
+ const parsed = yield* Effect.try(
204
+ () => JSON.parse(text) as { files?: unknown },
205
+ );
206
+ if (
207
+ !Array.isArray(parsed.files) ||
208
+ !parsed.files.every((file): file is string => typeof file === "string")
209
+ ) {
210
+ return yield* fail(`Invalid Next.js trace manifest: ${manifest}`);
211
+ }
212
+ for (const file of parsed.files) {
213
+ const source = path.resolve(path.dirname(manifest), file);
214
+ if (excluded(path.relative(root, source)))
215
+ return yield* fail(
216
+ `A Next.js runtime dependency is a sensitive file: ${source}`,
217
+ );
218
+ files.add(source);
219
+ if (javascript(source)) seeds.add(source);
220
+ }
221
+ }
222
+
223
+ const traceBase = path.parse(root).root;
224
+ const trace =
225
+ seeds.size === 0
226
+ ? { fileList: new Set<string>() }
227
+ : yield* Effect.tryPromise({
228
+ try: () =>
229
+ import("@vercel/nft").then(({ nodeFileTrace }) =>
230
+ nodeFileTrace([...seeds], {
231
+ base: traceBase,
232
+ processCwd: root,
233
+ conditions: ["node", "production"],
234
+ analysis: {
235
+ emitGlobs: true,
236
+ computeFileReferences: true,
237
+ evaluatePureExpressions: true,
238
+ },
239
+ }),
240
+ ),
241
+ catch: (cause) =>
242
+ new Error(
243
+ "Failed to trace website runtime dependencies. Install @vercel/nft in the deployment workspace.",
244
+ { cause },
245
+ ),
246
+ }).pipe(Effect.timeout("90 seconds"));
247
+ for (const file of trace.fileList) files.add(path.resolve(traceBase, file));
248
+ if (files.size > MAX_ENTRIES)
249
+ return yield* fail(
250
+ "Website dependency trace exceeds the 50,000-entry safety limit.",
251
+ );
252
+
253
+ const links = new Map<string, string>();
254
+ const selected = new Set<string>();
255
+ for (const file of files) {
256
+ if (excluded(path.relative(root, file)))
257
+ return yield* fail(
258
+ `A website runtime dependency is a sensitive file: ${file}`,
259
+ );
260
+ const link = yield* fs
261
+ .readLink(file)
262
+ .pipe(Effect.catch(() => Effect.succeed(undefined)));
263
+ if (link !== undefined) {
264
+ const target = yield* fs.realPath(file);
265
+ links.set(file, target);
266
+ if ((yield* fs.stat(target)).type === "File")
267
+ selected.add(yield* fs.realPath(target));
268
+ } else {
269
+ const stat = yield* fs.stat(file);
270
+ if (stat.type !== "File")
271
+ return yield* fail(
272
+ `A traced runtime dependency is not a file: ${file}`,
273
+ );
274
+ selected.add(file);
275
+ }
276
+ }
277
+ let base = root;
278
+ const within = (directory: string, file: string) => {
279
+ const relative = path.relative(directory, file);
280
+ return (
281
+ relative !== ".." &&
282
+ !relative.startsWith(`..${path.sep}`) &&
283
+ !path.isAbsolute(relative)
284
+ );
285
+ };
286
+ for (const file of [...selected, ...links.keys(), ...links.values()]) {
287
+ while (!within(base, file)) base = path.dirname(base);
288
+ }
289
+ const directory = yield* fs.makeTempDirectoryScoped({
290
+ prefix: "alchemy-prisma-website-",
291
+ });
292
+ // pnpm's peer-qualified store names exceed ustar's 100-byte link field.
293
+ // Shorten only store-directory names; package scopes and links stay intact.
294
+ const stores = new Map(
295
+ [
296
+ ...new Set(
297
+ [...selected, ...links.keys(), ...links.values()].flatMap((file) => {
298
+ const parts = file.split(path.sep);
299
+ return parts.flatMap((part, index) =>
300
+ part === ".pnpm" && parts[index + 1] !== undefined
301
+ ? [parts[index + 1]!]
302
+ : [],
303
+ );
304
+ }),
305
+ ),
306
+ ]
307
+ .sort()
308
+ .map((name, index) => [name, `p${index.toString(36)}`]),
309
+ );
310
+ const staged = (file: string) => {
311
+ const parts = path.relative(base, file).split(path.sep);
312
+ return path.join(
313
+ directory,
314
+ "files",
315
+ ...parts.map((part, index) =>
316
+ parts[index - 1] === ".pnpm" ? (stores.get(part) ?? part) : part,
317
+ ),
318
+ );
319
+ };
320
+ let bytes = 0;
321
+ for (const file of [...selected].sort()) {
322
+ const stat = yield* fs.stat(file);
323
+ const size = Number(stat.size);
324
+ bytes += size;
325
+ if (size > MAX_FILE_BYTES || bytes > MAX_BYTES)
326
+ return yield* fail(
327
+ "Website artifact exceeds Prisma Compute's file or total byte safety limit.",
328
+ );
329
+ const dest = staged(file);
330
+ yield* fs.makeDirectory(path.dirname(dest), { recursive: true });
331
+ yield* fs.copyFile(file, dest);
332
+ yield* fs.chmod(dest, stat.mode & 0o777);
333
+ }
334
+ for (const [file, target] of links) {
335
+ // NFT also reports resolution-only links with no retained runtime files.
336
+ if (!(yield* fs.exists(staged(target)))) continue;
337
+ const dest = staged(file);
338
+ if (yield* fs.exists(dest)) continue;
339
+ yield* fs.makeDirectory(path.dirname(dest), { recursive: true });
340
+ yield* fs.symlink(path.relative(path.dirname(dest), staged(target)), dest);
341
+ }
342
+ if (props.static !== undefined) {
343
+ const client = path.relative(directory, staged(dist)).replaceAll("\\", "/");
344
+ yield* fs.writeFileString(
345
+ path.join(directory, "server.mjs"),
346
+ makeNodeServeEntrySource({
347
+ ...props.static,
348
+ clientDirExpression: `fileURLToPath(new URL(${JSON.stringify(`./${client}/`)}, import.meta.url))`,
349
+ }),
350
+ );
351
+ return { directory, entrypoint: "server.mjs", requiredFiles: [] };
352
+ }
353
+ if (entry === undefined) return yield* fail("Missing website server entry.");
354
+ const entryRelative = path
355
+ .relative(directory, staged(entry))
356
+ .replaceAll("\\", "/");
357
+ const cwdRelative = path
358
+ .relative(directory, staged(root))
359
+ .replaceAll("\\", "/");
360
+ yield* fs.makeDirectory(staged(root), { recursive: true });
361
+ yield* fs.writeFileString(
362
+ path.join(directory, "server.mjs"),
363
+ [
364
+ 'import { fileURLToPath } from "node:url";',
365
+ `process.chdir(fileURLToPath(new URL(${JSON.stringify(`./${cwdRelative}/`)}, import.meta.url)));`,
366
+ `await import(new URL(${JSON.stringify(`./${entryRelative}`)}, import.meta.url).href);`,
367
+ "",
368
+ ].join("\n"),
369
+ );
370
+ return {
371
+ directory,
372
+ entrypoint: "server.mjs",
373
+ requiredFiles: [entryRelative],
374
+ };
375
+ });
376
+
377
+ export const WebsiteArtifactProvider = () =>
378
+ Provider.effect(
379
+ WebsiteArtifact,
380
+ Effect.gen(function* () {
381
+ const fs = yield* FileSystem.FileSystem;
382
+ const path = yield* Path.Path;
383
+ return {
384
+ list: () => Effect.succeed([]),
385
+ // Traced dependencies can change independently of the framework's hash.
386
+ diff: () => Effect.succeed({ action: "update" as const }),
387
+ reconcile: Effect.fn(function* ({ id, news }) {
388
+ const name = yield* createPhysicalName({ id, maxLength: 80 });
389
+ const directory = path.join(
390
+ initialCwd,
391
+ ".alchemy",
392
+ "prisma-websites",
393
+ name,
394
+ );
395
+ return yield* Effect.scoped(
396
+ Effect.gen(function* () {
397
+ const staged = yield* stageWebsiteArtifact(news);
398
+ const archive = yield* createComputeArchive({
399
+ ...staged,
400
+ output: "file",
401
+ });
402
+ return yield* Effect.gen(function* () {
403
+ yield* fs.makeDirectory(directory, { recursive: true });
404
+ const artifactPath = path.join(
405
+ directory,
406
+ `${archive.sha256}.tar.gz`,
407
+ );
408
+ yield* fs.copyFile(archive.path, artifactPath);
409
+ for (const old of yield* fs.readDirectory(directory)) {
410
+ if (
411
+ old !== path.basename(artifactPath) &&
412
+ /^[a-f0-9]{64}\.tar\.gz$/.test(old)
413
+ ) {
414
+ yield* fs.remove(path.join(directory, old), {
415
+ force: true,
416
+ });
417
+ }
418
+ }
419
+ return { directory, artifactPath, hash: archive.sha256 };
420
+ }).pipe(Effect.ensuring(archive.cleanup));
421
+ }),
422
+ );
423
+ }),
424
+ delete: Effect.fn(function* ({ output }) {
425
+ yield* fs.remove(output.directory, { recursive: true, force: true });
426
+ }),
427
+ };
428
+ }),
429
+ );
@@ -0,0 +1,72 @@
1
+ import * as Namespace from "../../Namespace.ts";
2
+ import { makeFrameworkSite, type FrameworkSiteProps } from "./FrameworkSite.ts";
3
+
4
+ /** Configuration for a Prisma Astro website. */
5
+ export interface AstroProps extends FrameworkSiteProps {
6
+ /** Serializable overrides merged over astro.config.*; the Node target owns adapter. */
7
+ astro?: {
8
+ /** Canonical deployment URL. */
9
+ site?: string;
10
+ /** Deployment base path. */
11
+ base?: string;
12
+ /** Render on demand or prerender every page. @default "server" */
13
+ output?: "server" | "static";
14
+ /** Source directory relative to rootDir. @default "./src" */
15
+ srcDir?: string;
16
+ /** Static passthrough directory. @default "./public" */
17
+ publicDir?: string;
18
+ /** Production output directory. @default "./dist" */
19
+ outDir?: string;
20
+ /** Route trailing-slash policy. */
21
+ trailingSlash?: "always" | "never" | "ignore";
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Astro SSR and prerendered pages on Prisma Compute. The Node target owns the adapter; omit adapter from astro.config.*. Set astro.output to "static" to prerender every page.
27
+ *
28
+ * Native framework development and HMR run without Prisma cloud resources.
29
+ * Apply `Alchemy.remote()` to use the live Compute deployment during dev.
30
+ * Packaging requires the optional `@vercel/nft` peer dependency.
31
+ *
32
+ * ### Creating a Website
33
+ * **Example:** Astro application
34
+ * ```typescript
35
+ * const site = yield* Prisma.Website.Astro("Web", {
36
+ * rootDir: "./app",
37
+ * });
38
+ * ```
39
+ *
40
+ * ### Deployment Configuration
41
+ * **Example:** Existing project and custom hostname
42
+ * ```typescript
43
+ * const site = yield* Prisma.Website.Astro("Web", {
44
+ * project,
45
+ * domain: "www.example.com",
46
+ * env: { API_BASE: "https://api.example.com" },
47
+ * compute: { destroyOldDeployment: true },
48
+ * });
49
+ * ```
50
+ *
51
+ * ### Static Output
52
+ * **Example:** Prerendered documentation with a 404 page
53
+ * ```typescript
54
+ * const docs = yield* Prisma.Website.Astro("Docs", {
55
+ * rootDir: "./docs",
56
+ * astro: { output: "static" },
57
+ * assets: { notFoundHandling: "404-page" },
58
+ * });
59
+ * ```
60
+ *
61
+ * @resource
62
+ * @product Website
63
+ */
64
+ export const Astro = (id: string, props: AstroProps = {}) =>
65
+ makeFrameworkSite(id, props, {
66
+ framework: "@alchemy.run/frontend-frameworks/astro",
67
+ target: "@alchemy.run/frontend-frameworks/astro/node",
68
+ options: {
69
+ astro: { ...props.astro, output: props.astro?.output ?? "server" },
70
+ },
71
+ notFoundHandling: "none",
72
+ }).pipe(Namespace.push(id));
@@ -0,0 +1,34 @@
1
+ import { Vite, type ViteProps } from "./Vite.ts";
2
+
3
+ /** Configuration for a client-only Foldkit website. */
4
+ export interface FoldkitProps extends ViteProps {}
5
+
6
+ /**
7
+ * Deploy a Foldkit Vite application to Prisma Compute with SPA routing.
8
+ * The project's Foldkit Vite plugin drives the build; native Vite HMR runs
9
+ * during development without creating cloud resources.
10
+ *
11
+ * ### Creating a Website
12
+ * **Example:** Foldkit application
13
+ * ```typescript
14
+ * const site = yield* Prisma.Website.Foldkit("Web", {
15
+ * rootDir: "./app",
16
+ * });
17
+ * ```
18
+ *
19
+ * ### Multi-Page Routing
20
+ * **Example:** Override the default SPA fallback
21
+ * ```typescript
22
+ * const site = yield* Prisma.Website.Foldkit("Web", {
23
+ * assets: { notFoundHandling: "404-page" },
24
+ * });
25
+ * ```
26
+ *
27
+ * @resource
28
+ * @product Website
29
+ */
30
+ export const Foldkit = (id: string, props: FoldkitProps = {}) =>
31
+ Vite(id, {
32
+ ...props,
33
+ assets: { notFoundHandling: "single-page-application", ...props.assets },
34
+ });