Open History to see the last commit
1// @effect-diagnostics nodeBuiltinImport:off -- Resolves the real CLI entrypoint for subprocess tests.
2import * as NodeURL from "node:url";
3import * as NodeServices from "@effect/platform-node/NodeServices";
4import { expect, it } from "@effect/vitest";
5import * as Effect from "effect/Effect";
6import * as FileSystem from "effect/FileSystem";
7import * as Stream from "effect/Stream";
8import { ChildProcess, ChildProcessSpawner } from "effect/process";
9
10it.live("loads the CLI with its cache settings only when the command needs it", () =>
11 Effect.gen(function* () {
12 const fs = yield* FileSystem.FileSystem;
13 const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
14 const root = yield* fs.makeTempDirectoryScoped({ prefix: "t3-cli-cache-test-" });
15 const report = `
16 import { getCompileCacheDir, registerHooks } from 'node:module';
17 registerHooks({ resolve(specifier, context, nextResolve) {
18 if (specifier === './binMain.ts') console.error('LOADED_CLI_MAIN');
19 if (specifier === './binCli.ts') console.error('CACHE=' + (getCompileCacheDir() ?? 'disabled'));
20 return nextResolve(specifier, context);
21 } });
22 `;
23 const run = Effect.fn("cliCompileCache.test.run")(function* (
24 env: Record<string, string>,
25 args: ReadonlyArray<string> = ["--help"],
26 ) {
27 const child = yield* spawner.spawn(
28 ChildProcess.make(
29 process.execPath,
30 [
31 "--experimental-strip-types",
32 "--import",
33 `data:text/javascript,${encodeURIComponent(report)}`,
34 NodeURL.fileURLToPath(new URL("./bin.ts", import.meta.url)),
35 ...args,
36 ],
37 {
38 env: {
39 XDG_CACHE_HOME: `${root}/cache`,
40 T3CODE_HOME: `${root}/home`,
41 NODE_COMPILE_CACHE: undefined,
42 NODE_DISABLE_COMPILE_CACHE: undefined,
43 APPIMAGE: "",
44 ...env,
45 },
46 },
47 ),
48 );
49 return yield* Effect.all(
50 {
51 stdout: child.stdout.pipe(Stream.decodeText(), Stream.mkString),
52 stderr: child.stderr.pipe(Stream.decodeText(), Stream.mkString),
53 exitCode: child.exitCode.pipe(Effect.map(Number)),
54 },
55 { concurrency: "unbounded" },
56 );
57 }, Effect.scoped);
58 const normal = yield* run({});
59 expect(normal.exitCode).toBe(0);
60 expect(normal.stderr).toContain("LOADED_CLI_MAIN");
61 expect(normal.stderr.replaceAll("\\", "/")).toContain(
62 `CACHE=${root}/cache/t3code/compile-cache`.replaceAll("\\", "/"),
63 );
64 const launcher = yield* run({ T3CODE_HOME: "" }, ["__service-launcher"]);
65 expect(launcher.exitCode).toBe(1);
66 expect(launcher.stderr).toContain("[service-launcher] T3CODE_HOME is required");
67 expect(launcher.stderr).not.toMatch(/LOADED_CLI_MAIN|CACHE=/);
68 const launcherHelp = yield* run({ T3CODE_HOME: "" }, ["__service-launcher", "--help"]);
69 expect(launcherHelp.exitCode).toBe(0);
70 expect(launcherHelp.stderr).toContain("LOADED_CLI_MAIN");
71 expect(launcherHelp.stdout).toContain("t3 __service-launcher [flags]");
72 const launcherFlags = yield* run({ T3CODE_HOME: "" }, [
73 "__service-launcher",
74 "--log-level",
75 "debug",
76 ]);
77 expect(launcherFlags.exitCode).toBe(1);
78 expect(launcherFlags.stderr).toContain("LOADED_CLI_MAIN");
79 expect(launcherFlags.stderr).toContain("[service-launcher] T3CODE_HOME is required");
80 const appImage = yield* run({ APPIMAGE: `${root}/T3Code.AppImage` });
81 expect(appImage.exitCode).toBe(0);
82 expect(appImage.stderr).toContain("CACHE=disabled");
83 const existing = yield* run({ NODE_COMPILE_CACHE: `${root}/explicit` });
84 expect(existing.exitCode).toBe(0);
85 expect(existing.stderr.replaceAll("\\", "/")).toContain(
86 `${root}/explicit`.replaceAll("\\", "/"),
87 );
88 const disabled = yield* run({ NODE_DISABLE_COMPILE_CACHE: "1" });
89 expect(disabled.exitCode).toBe(0);
90 expect(disabled.stderr).toContain("CACHE=disabled");
91 yield* fs.writeFileString(`${root}/blocked`, "not a directory");
92 const unavailable = yield* run({ XDG_CACHE_HOME: `${root}/blocked` });
93 expect(unavailable.exitCode).toBe(0);
94 expect(unavailable.stderr).toContain("CACHE=disabled");
95 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
96);