1import * as Effect from "effect/Effect";
2import * as FileSystem from "effect/FileSystem";
3import * as Path from "effect/Path";
4import type { ExtraFile } from "../Util/extraFiles.ts";
5
6const nextConfigFiles = [
7 "next.config.js",
8 "next.config.mjs",
9 "next.config.cjs",
10 "next.config.ts",
11] as const;
12
13const exists = (target: string) =>
14 Effect.gen(function* () {
15 const fs = yield* FileSystem.FileSystem;
16 return yield* fs.exists(target).pipe(Effect.orElseSucceed(() => false));
17 });
18
19/**
20 * Extra files baked into a container/unit next to the Node serve entry.
21 *
22 * - `"client"`: the build's dist directory at the image/unit root.
23 * - `"next"`: `.next`, `public/`, and `next.config.*` from `from` (the
24 * Next.js app root — for the node target that is also `distDirectory`).
25 */
26export const packSiteExtraFiles = (
27 from: string,
28 mode: "client" | "next",
29): Effect.Effect<
30 ExtraFile[] | undefined,
31 never,
32 FileSystem.FileSystem | Path.Path
33> =>
34 Effect.gen(function* () {
35 if (mode === "client") {
36 return [{ source: from, dest: "." }];
37 }
38 const path = yield* Path.Path;
39 const nextDir = path.join(from, ".next");
40 const publicDir = path.join(from, "public");
41 const configPaths = nextConfigFiles.map((name) => ({
42 name,
43 target: path.join(from, name),
44 }));
45 const [hasNext, hasPublic, ...configHits] = yield* Effect.all(
46 [
47 exists(nextDir),
48 exists(publicDir),
49 ...configPaths.map((file) => exists(file.target)),
50 ],
51 { concurrency: "unbounded" },
52 );
53 const files: ExtraFile[] = [];
54 if (hasNext) files.push({ source: nextDir, dest: ".next" });
55 if (hasPublic) files.push({ source: publicDir, dest: "public" });
56 configPaths.forEach((file, i) => {
57 if (configHits[i] === true) {
58 files.push({ source: file.target, dest: file.name });
59 }
60 });
61 return files.length > 0 ? files : undefined;
62 });