1import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
2import * as NodeServices from "@effect/platform-node/NodeServices";
3import { nodeFileTrace } from "@vercel/nft";
4import * as Effect from "effect/Effect";
5import * as FileSystem from "effect/FileSystem";
6import * as Path from "effect/Path";
7import type { TraceInput } from "./Trace.ts";
8
9NodeRuntime.runMain(
10 Effect.gen(function* () {
11 const fs = yield* FileSystem.FileSystem;
12 const path = yield* Path.Path;
13 const inputPath = process.argv[2];
14 const outputPath = process.argv[3];
15 if (!inputPath || !outputPath)
16 return yield* Effect.fail(new Error("Missing dependency trace paths"));
17 const source = yield* fs.readFileString(inputPath);
18 const input = yield* Effect.try(() => JSON.parse(source) as TraceInput);
19 const trace = (seeds: string[], emitGlobs: boolean) =>
20 Effect.tryPromise(() =>
21 nodeFileTrace(seeds, {
22 base: input.base,
23 processCwd: input.root,
24 conditions: ["node", "production"],
25 // Next's manifests already trace its runtime without dev bundlers.
26 ignore: input.next
27 ? (file) =>
28 file.replaceAll("\\", "/").includes("/node_modules/next/")
29 : undefined,
30 analysis: {
31 emitGlobs,
32 computeFileReferences: true,
33 evaluatePureExpressions: true,
34 },
35 }),
36 );
37 const result = yield* trace(input.seeds, true);
38 const stateFiles = [...result.fileList].filter((file) =>
39 file.split(/[\\/]/).includes(".alchemy"),
40 );
41 if (stateFiles.length > 0) {
42 // NFT's asset globs can sweep up unrelated build/state directories.
43 // Trace without globs to retain explicit state dependencies: packaging
44 // must still reject those, including references through symlinks.
45 // Include modules discovered by a wildcard import too: their explicit
46 // references to state must not disappear with the wildcard expansion.
47 const explicit = yield* trace(
48 [
49 ...new Set([
50 ...input.seeds,
51 ...[...result.fileList]
52 .filter(
53 (file) =>
54 /\.[cm]?[jt]sx?$/.test(file) &&
55 !file.split(/[\\/]/).includes(".alchemy"),
56 )
57 .map((file) => path.resolve(input.base, file)),
58 ]),
59 ],
60 false,
61 );
62 for (const file of stateFiles) {
63 if (!explicit.fileList.has(file)) result.fileList.delete(file);
64 }
65 }
66 yield* fs.writeFileString(outputPath, JSON.stringify([...result.fileList]));
67 }).pipe(Effect.provide(NodeServices.layer)),
68);