1import "vite-plus/test/config";
2import { defineConfig, mergeConfig } from "vite-plus";
3
4import baseConfig from "../../vite.config.ts";
5import { loadRepoEnv } from "../../scripts/lib/public-config.ts";
6import packageJson from "./package.json" with { type: "json" };
7import { WeightedShardSequencer } from "./src/testUtils/weightedShardSequencer.ts";
8
9// The bundle used to inline only workspace packages, leaving every third-party
10// runtime dep external. External deps must exist on the real filesystem (the WSL
11// backend runs plain `wsl.exe -- node`, which cannot read inside an asar), so the
12// desktop build unpacked `**\/node_modules\/**` wholesale: 13,875 loose files to
13// support 20 native binaries. NSIS install time tracks file count, not bytes.
14//
15// Inverted here — bundle everything except the packages that genuinely cannot be
16// inlined. See scripts/lib/cli-external-packages.ts for what earns an exemption.
17import {
18 isExternalCliDependency,
19 shouldBundleCliDependency,
20} from "../../scripts/lib/cli-external-packages.ts";
21
22export { shouldBundleCliDependency };
23
24const repoEnv = loadRepoEnv();
25const cliBuildChannel = /^[^-+]+-(?:nightly|preview)\./.test(packageJson.version)
26 ? "nightly"
27 : "latest";
28
29// `build:exe` wraps the same bundle in a Node single-executable. tsdown's exe
30// step refuses multi-chunk output and counts the sourcemap as a chunk, and the
31// executable needs a host Node that supports `--build-sea` (25.7+), so this is
32// a separate mode rather than a second entry in the default build.
33const packExecutable = process.env.T3CODE_PACK_EXE === "1";
34// `<platform>-<arch>` in nodejs.org naming (darwin-x64, linux-arm64, win-x64).
35// When set, tsdown injects the bundle into a downloaded Node of that target
36// instead of the host Node, which is how the arm64 macOS runner produces the
37// x64 archive. Cross-building is safe because the code cache is off.
38//
39// The Node inside the executable is pinned here rather than taken from the
40// build host, so every archive of a release embeds the same runtime no matter
41// which Node happens to run the build.
42const SEA_NODE_VERSION = "26.8.2";
43const SEA_TARGETS = {
44 "darwin-arm64": { platform: "darwin", arch: "arm64" },
45 "darwin-x64": { platform: "darwin", arch: "x64" },
46 "linux-arm64": { platform: "linux", arch: "arm64" },
47 "linux-x64": { platform: "linux", arch: "x64" },
48 "win-arm64": { platform: "win", arch: "arm64" },
49 "win-x64": { platform: "win", arch: "x64" },
50} as const;
51const packExecutableTarget = process.env.T3CODE_PACK_EXE_TARGET?.trim();
52if (packExecutableTarget && !Object.hasOwn(SEA_TARGETS, packExecutableTarget)) {
53 throw new Error(
54 `T3CODE_PACK_EXE_TARGET must be one of ${Object.keys(SEA_TARGETS).join(", ")}, got "${packExecutableTarget}".`,
55 );
56}
57const packExecutableTargets = packExecutableTarget
58 ? [
59 {
60 ...SEA_TARGETS[packExecutableTarget as keyof typeof SEA_TARGETS],
61 nodeVersion: SEA_NODE_VERSION,
62 },
63 ]
64 : undefined;
65
66export default mergeConfig(
67 baseConfig,
68 defineConfig({
69 run: {
70 tasks: {
71 build: {
72 command: "node scripts/cli.ts build",
73 dependsOn: ["@t3tools/web#build"],
74 cache: false,
75 },
76 },
77 },
78 pack: {
79 // The executable embeds one entry; the history worker becomes a hidden
80 // subcommand there instead of a sibling script.
81 entry: packExecutable ? ["src/bin.ts"] : ["src/bin.ts", "src/claude-history-worker.ts"],
82 outDir: packExecutable ? "dist-exe" : "dist",
83 sourcemap: !packExecutable,
84 clean: true,
85 ...(packExecutable
86 ? {
87 exe: {
88 fileName: "t3",
89 outDir: "dist-exe",
90 ...(packExecutableTargets ? { targets: packExecutableTargets } : {}),
91 // Node's SEA docs: `import()` does not work when useCodeCache is
92 // true, and the server reaches several modules that way. The
93 // cache is also platform-bound, so leaving it off keeps the
94 // build correct on any host.
95 seaConfig: { useCodeCache: false },
96 },
97 }
98 : {}),
99 deps: {
100 // Both halves are required. `alwaysBundle` forces the JS dependencies in
101 // (declared deps are external by default, which is what this change is
102 // undoing). `neverBundle` forces the native packages out: returning
103 // false from `alwaysBundle` only means "no opinion", so a transitive
104 // dependency would still be bundled — which silently inlined native
105 // loaders such as node-gyp-build, losing native acceleration.
106 alwaysBundle: shouldBundleCliDependency,
107 neverBundle: (id: string) => isExternalCliDependency(id),
108 onlyBundle: false,
109 },
110 banner: {
111 js: "#!/usr/bin/env node\n",
112 },
113 define: {
114 __T3CODE_BUILD_CHANNEL__: JSON.stringify(cliBuildChannel),
115 __T3CODE_BUILD_RELAY_URL__: JSON.stringify(repoEnv.T3CODE_RELAY_URL?.trim() ?? ""),
116 __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__: JSON.stringify(
117 repoEnv.T3CODE_CLERK_PUBLISHABLE_KEY?.trim() ?? "",
118 ),
119 __T3CODE_BUILD_CLERK_CLI_OAUTH_CLIENT_ID__: JSON.stringify(
120 repoEnv.T3CODE_CLERK_CLI_OAUTH_CLIENT_ID?.trim() ?? "",
121 ),
122 },
123 },
124 test: {
125 // The server suite exercises sqlite, git, temp worktrees, and orchestration
126 // runtimes heavily. Running files in parallel introduces load-sensitive flakes.
127 fileParallelism: false,
128 // CI runs the suite as `--shard` runs of equal recorded duration.
129 sequence: { sequencer: WeightedShardSequencer },
130 // Appended to the root setup, which mergeConfig concatenates.
131 setupFiles: ["./src/testUtils/gitConfig.setup.ts"],
132 // Server integration tests exercise sqlite, git, and orchestration together.
133 // Under package-wide runs they can exceed the default budget on loaded CI hosts.
134 hookTimeout: 120_000,
135 testTimeout: 120_000,
136 },
137 }),
138);