1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as cloudrun from "@distilled.cloud/gcp/run_v2";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as FileSystem from "effect/FileSystem";
7import * as Path from "effect/Path";
8import { cloneFixture } from "../../Cloudflare/Utils/Fixture.ts";
9import { expectUrlContains } from "../../Cloudflare/Utils/Http.ts";
10import { dockerAvailable } from "../bindingHost.ts";
11import {
12 assertSiteGone,
13 cloudflareFixture,
14 cloudRunUrl,
15 liveOptions,
16 logLevel,
17 serviceIdentity,
18 tempRoot,
19} from "./site.ts";
20
21const { test } = Test.make({ providers: GCP.providers() });
22
23test.provider.skipIf(!dockerAvailable)(
24 "Vite SPA: deploy v1, SPA fallback, update to v2 in place, destroy, gone",
25 (stack) =>
26 Effect.gen(function* () {
27 yield* stack.destroy();
28 const fs = yield* FileSystem.FileSystem;
29 const path = yield* Path.Path;
30
31 const rootDir = yield* cloneFixture(
32 cloudflareFixture("vite-spa-fixture"),
33 {
34 prefix: "alchemy-vite-gcp-",
35 tempRoot,
36 entries: ["index.html", "package.json", "src"],
37 },
38 );
39 const index = path.join(rootDir, "index.html");
40 const original = yield* fs.readFileString(index);
41 const writeVersion = (version: string) =>
42 fs.writeFileString(
43 index,
44 original.replaceAll(
45 "Vite SPA fixture",
46 `Vite SPA fixture ${version}`,
47 ),
48 );
49 const deploy = () =>
50 stack.deploy(
51 Effect.gen(function* () {
52 const site = yield* GCP.Website.Vite("Web", {
53 rootDir,
54 memo: { include: ["index.html", "src/**", "package.json"] },
55 tags: { suite: "website" },
56 });
57 return { site };
58 }),
59 );
60
61 yield* writeVersion("v1");
62 const first = yield* deploy();
63 const url = first.site.url as string;
64 expect(url).toMatch(cloudRunUrl);
65 const service = serviceIdentity(first.site.service!);
66 yield* expectUrlContains(`${url}/`, "Vite SPA fixture v1", {
67 timeout: "180 seconds",
68 label: "vite v1 index",
69 });
70 // SPA fallback: an unknown route serves index.html.
71 yield* expectUrlContains(`${url}/counter/42`, "Vite SPA fixture v1", {
72 timeout: "30 seconds",
73 label: "vite spa deep link",
74 });
75
76 const observed = yield* cloudrun.getProjectsLocationsServices({
77 name: service.name,
78 });
79 expect(observed.invokerIamDisabled).toBe(true);
80 expect(observed.labels?.suite).toBe("website");
81
82 yield* writeVersion("v2");
83 const second = yield* deploy();
84 expect(second.site.url).toBe(url);
85 expect(second.site.service!.name).toBe(service.name);
86 expect(second.site.service!.latestReadyRevision).not.toBe(
87 first.site.service!.latestReadyRevision,
88 );
89 yield* expectUrlContains(`${url}/`, "Vite SPA fixture v2", {
90 timeout: "60 seconds",
91 label: "vite v2 index",
92 });
93
94 yield* stack.destroy();
95 yield* assertSiteGone(service);
96 }).pipe(logLevel),
97 liveOptions(1_200_000),
98);