1import * as Effect from "effect/Effect";
2import type { PluginOption } from "vite";
3import { loadVinextModule } from "./Modules.ts";
4
5interface NextConfigModule {
6 PHASE_PRODUCTION_BUILD: string;
7 findVinextNextConfigInPlugins(
8 plugins: PluginOption[] | undefined,
9 ): Promise<unknown>;
10 resolveNextConfigInput(config: unknown, phase: string): Promise<unknown>;
11 loadNextConfig(root: string, phase: string): Promise<unknown>;
12 resolveNextConfig(
13 config: unknown,
14 root: string,
15 ): Promise<{
16 buildId: string;
17 output?: "standalone" | "export";
18 }>;
19 createRscCompatibilityId(config: unknown): string;
20}
21
22interface PrerenderConfigModule {
23 findVinextPrerenderConfigInPlugins(
24 plugins: PluginOption[] | undefined,
25 ): Promise<unknown>;
26 findVinextRouteRootConfigInPlugins(
27 plugins: PluginOption[] | undefined,
28 ): Promise<unknown>;
29 findVinextCacheConfigInPlugins(
30 plugins: PluginOption[] | undefined,
31 ): Promise<unknown>;
32 hasBuildIdentityResponseHeader(cache: unknown): boolean;
33 hasVerbatimResponseVary(cache: unknown): boolean;
34 hasUncachedRequestRouting(cache: unknown): boolean;
35 isConfiguredCdnResponsePolicyHeader(cache: unknown, name: string): boolean;
36}
37
38/** Keep native configuration objects intact when forwarding them between build phases. */
39export const loadVinextBuildConfig = Effect.fn(function* (
40 root: string,
41 plugins: PluginOption[] | undefined,
42) {
43 const next = yield* loadVinextModule<NextConfigModule>(
44 root,
45 "config/next-config.js",
46 );
47 const prerender = yield* loadVinextModule<PrerenderConfigModule>(
48 root,
49 "config/prerender.js",
50 );
51 return yield* Effect.tryPromise(async () => {
52 const input = await next.findVinextNextConfigInPlugins(plugins);
53 const raw = input
54 ? await next.resolveNextConfigInput(input, next.PHASE_PRODUCTION_BUILD)
55 : await next.loadNextConfig(root, next.PHASE_PRODUCTION_BUILD);
56 const nextConfig = await next.resolveNextConfig(raw, root);
57 const cache = await prerender.findVinextCacheConfigInPlugins(plugins);
58 return {
59 nextConfig,
60 rscCompatibilityId: next.createRscCompatibilityId(nextConfig),
61 prerenderConfig:
62 await prerender.findVinextPrerenderConfigInPlugins(plugins),
63 routeRootConfig:
64 await prerender.findVinextRouteRootConfigInPlugins(plugins),
65 buildIdentity: prerender.hasBuildIdentityResponseHeader(cache)
66 ? "response-header"
67 : undefined,
68 responseVary: prerender.hasVerbatimResponseVary(cache)
69 ? "verbatim"
70 : undefined,
71 requestRouting: prerender.hasUncachedRequestRouting(cache)
72 ? "uncached-stage"
73 : undefined,
74 isResponsePolicyHeader: (name: string) =>
75 prerender.isConfiguredCdnResponsePolicyHeader(cache, name),
76 };
77 });
78});
79
80export type VinextBuildConfig = Effect.Success<
81 ReturnType<typeof loadVinextBuildConfig>
82>;