ttsc 0.16.12 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/TtscCompiler.d.ts +7 -7
- package/lib/TtscCompiler.js +21 -25
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/launcher/internal/resolveCacheDir.d.ts +4 -3
- package/lib/launcher/internal/resolveCacheDir.js +4 -3
- package/lib/launcher/internal/resolveCacheDir.js.map +1 -1
- package/lib/launcher/internal/runTtsc.js +140 -12
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/plugin/internal/buildSourcePlugin.d.ts +38 -19
- package/lib/plugin/internal/buildSourcePlugin.js +284 -76
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/package.json +8 -8
- package/src/TtscCompiler.ts +28 -27
- package/src/launcher/internal/resolveCacheDir.ts +4 -3
- package/src/launcher/internal/runTtsc.ts +184 -13
- package/src/plugin/internal/buildSourcePlugin.ts +360 -89
|
@@ -5,8 +5,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.buildSourcePlugin = buildSourcePlugin;
|
|
7
7
|
exports.resolvePluginCacheRoot = resolvePluginCacheRoot;
|
|
8
|
-
exports.
|
|
9
|
-
exports.
|
|
8
|
+
exports.resolveSourceBuildCachePaths = resolveSourceBuildCachePaths;
|
|
9
|
+
exports.resolveCleanTargets = resolveCleanTargets;
|
|
10
|
+
exports.legacyGlobalCacheTargets = legacyGlobalCacheTargets;
|
|
11
|
+
exports.isPathWithin = isPathWithin;
|
|
10
12
|
exports.computeCacheKey = computeCacheKey;
|
|
11
13
|
const node_child_process_1 = require("node:child_process");
|
|
12
14
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
@@ -106,15 +108,36 @@ const CONTRIB_DIRNAME = "contrib";
|
|
|
106
108
|
// runtimeHooks.ts.
|
|
107
109
|
const PLUGIN_BUILD_LOCK_STEAL_MS = 600_000;
|
|
108
110
|
const PLUGIN_BUILD_LOCK_POLL_MS = 50;
|
|
109
|
-
|
|
111
|
+
// The default cache lives INSIDE the workspace, at
|
|
112
|
+
// `<workspaceRoot>/node_modules/.cache/ttsc`, so `rm -rf node_modules` (or
|
|
113
|
+
// deleting the repo) reclaims every compiled plugin binary and Go object file.
|
|
114
|
+
// This is the `find-cache-dir` convention (Babel, webpack, ESLint, Nuxt, …): a
|
|
115
|
+
// disposable build cache under `node_modules/.cache/<tool>`. ttsc keeps NO
|
|
116
|
+
// global (`~/.cache`) cache — a machine-wide cache silently grew to hundreds of
|
|
117
|
+
// GB across tsgo/plugin version bumps, so it was removed outright. See
|
|
118
|
+
// resolveSourceBuildCacheRoot for the (override → workspace-local) priority.
|
|
119
|
+
const NODE_MODULES_DIRNAME = "node_modules";
|
|
120
|
+
const LOCAL_CACHE_PARENT_DIRNAME = ".cache";
|
|
121
|
+
const TTSC_CACHE_DIRNAME = "ttsc";
|
|
110
122
|
const PLUGIN_CACHE_DIRNAME = "plugins";
|
|
123
|
+
const GO_BUILD_CACHE_DIRNAME = "go-build";
|
|
124
|
+
// Directories whose presence marks a monorepo/workspace root, so every package
|
|
125
|
+
// in the workspace shares ONE cache and a plugin builds once, not once per
|
|
126
|
+
// package. `package.json` with a `workspaces` field (yarn/npm/bun) is checked
|
|
127
|
+
// separately in isWorkspaceRootDir.
|
|
128
|
+
const WORKSPACE_ROOT_MARKER_FILES = ["pnpm-workspace.yaml"];
|
|
111
129
|
const CACHE_LAST_USED_FILE = ".last-used";
|
|
112
130
|
const CACHE_GC_MARKER_FILE = ".gc-last-run";
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
131
|
+
// The plugin binary cache is content-keyed, so a project that bumps tsgo/typia
|
|
132
|
+
// many times leaves one stale entry per superseded key. An opportunistic GC
|
|
133
|
+
// (once/day) evicts entries unused for 30 days and, past a 2 GB ceiling, the
|
|
134
|
+
// least-recently-used down to 80%. It is scoped to the resolved cache root only
|
|
135
|
+
// — ttsc never scans a shared or global location.
|
|
136
|
+
const PLUGIN_CACHE_GC_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
137
|
+
const PLUGIN_CACHE_ENTRY_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
138
|
+
const PLUGIN_CACHE_MAX_BYTES = 2 * 1024 * 1024 * 1024;
|
|
139
|
+
const PLUGIN_CACHE_TARGET_BYTES = Math.floor(PLUGIN_CACHE_MAX_BYTES * 0.8);
|
|
140
|
+
const PLUGIN_CACHE_PROTECTED_AGE_MS = 60 * 60 * 1000;
|
|
118
141
|
/** Build one Go source plugin into a cached executable. */
|
|
119
142
|
function buildSourcePlugin(opts) {
|
|
120
143
|
const { dir, entry, source } = resolveSourceBuildTarget(opts);
|
|
@@ -131,8 +154,9 @@ function buildSourcePlugin(opts) {
|
|
|
131
154
|
ttscVersion: opts.ttscVersion,
|
|
132
155
|
tsgoVersion: opts.tsgoVersion,
|
|
133
156
|
});
|
|
134
|
-
const
|
|
135
|
-
|
|
157
|
+
const paths = resolveSourceBuildCachePaths(opts.baseDir, opts.cacheDir);
|
|
158
|
+
maybePrunePluginCache(paths, opts.cacheDir);
|
|
159
|
+
const cacheDir = node_path_1.default.join(paths.pluginRoot, key);
|
|
136
160
|
const binaryName = process.platform === "win32" ? "plugin.exe" : "plugin";
|
|
137
161
|
const binaryPath = node_path_1.default.join(cacheDir, binaryName);
|
|
138
162
|
if (node_fs_1.default.existsSync(binaryPath)) {
|
|
@@ -149,6 +173,7 @@ function buildSourcePlugin(opts) {
|
|
|
149
173
|
goBinary,
|
|
150
174
|
key,
|
|
151
175
|
label: opts.label ?? "source plugin",
|
|
176
|
+
goBuildCacheRoot: paths.goBuildRoot,
|
|
152
177
|
overlayDirs,
|
|
153
178
|
pluginName: opts.pluginName,
|
|
154
179
|
quiet: opts.quiet === true,
|
|
@@ -180,7 +205,7 @@ function compileSourcePlugin(opts) {
|
|
|
180
205
|
}
|
|
181
206
|
writeGoWork(scratchDir, opts.overlayDirs, opts.goBinary, opts.pluginName);
|
|
182
207
|
const scratchBinaryName = process.platform === "win32" ? ".ttsc-plugin.exe" : ".ttsc-plugin";
|
|
183
|
-
runGoBuild(scratchDir, opts.entry, scratchBinaryName, opts.pluginName, opts.goBinary);
|
|
208
|
+
runGoBuild(scratchDir, opts.entry, scratchBinaryName, opts.pluginName, opts.goBinary, opts.goBuildCacheRoot);
|
|
184
209
|
const builtBinary = node_path_1.default.join(scratchDir, scratchBinaryName);
|
|
185
210
|
publishBuiltBinary(builtBinary, opts.binaryPath);
|
|
186
211
|
touchCacheEntry(opts.cacheDir);
|
|
@@ -525,7 +550,7 @@ function readGoModInfo(dir, goBinary, pluginName) {
|
|
|
525
550
|
if (!node_fs_1.default.existsSync(node_path_1.default.join(dir, "go.mod"))) {
|
|
526
551
|
return emptyGoModInfo();
|
|
527
552
|
}
|
|
528
|
-
const result = (
|
|
553
|
+
const result = spawnGoTool(goBinary, ["mod", "edit", "-json"], {
|
|
529
554
|
cwd: dir,
|
|
530
555
|
encoding: "utf8",
|
|
531
556
|
env: goBuildEnv(goBinary),
|
|
@@ -585,12 +610,12 @@ function isTtscManagedModulePath(modulePath) {
|
|
|
585
610
|
modulePath === TSGO_GO_MODULE_PATH ||
|
|
586
611
|
modulePath.startsWith("github.com/microsoft/typescript-go/shim/"));
|
|
587
612
|
}
|
|
588
|
-
function runGoBuild(cwd, entry, binaryName, pluginName, goBinary) {
|
|
613
|
+
function runGoBuild(cwd, entry, binaryName, pluginName, goBinary, goBuildCacheRoot) {
|
|
589
614
|
ensureExecutableGoToolchain(goBinary);
|
|
590
|
-
const result = (
|
|
615
|
+
const result = spawnGoTool(goBinary, ["build", "-o", binaryName, entry], {
|
|
591
616
|
cwd,
|
|
592
617
|
encoding: "utf8",
|
|
593
|
-
env: goBuildEnv(goBinary),
|
|
618
|
+
env: goBuildEnv(goBinary, goBuildCacheRoot),
|
|
594
619
|
maxBuffer: 1024 * 1024 * 64,
|
|
595
620
|
windowsHide: true,
|
|
596
621
|
});
|
|
@@ -609,9 +634,30 @@ function goToolchainNotFoundMessage(pluginName) {
|
|
|
609
634
|
`Reinstall ttsc with optional dependencies so the bundled Go compiler is present, ` +
|
|
610
635
|
`or set TTSC_GO_BINARY to an absolute path.`);
|
|
611
636
|
}
|
|
612
|
-
function
|
|
637
|
+
function spawnGoTool(goBinary, args, options) {
|
|
638
|
+
const shell = shouldSpawnGoToolThroughShell(goBinary);
|
|
639
|
+
// Under shell:true (a Windows .cmd/.bat wrapper) Node does not quote the
|
|
640
|
+
// command, so a wrapper at a path containing a space would be split by cmd.
|
|
641
|
+
// Quote it here; the go arguments ttsc passes never contain spaces, and
|
|
642
|
+
// quoting a space-free path is harmless.
|
|
643
|
+
return (0, node_child_process_1.spawnSync)(shell ? `"${goBinary}"` : goBinary, [...args], {
|
|
644
|
+
...options,
|
|
645
|
+
shell,
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
function shouldSpawnGoToolThroughShell(goBinary) {
|
|
649
|
+
return process.platform === "win32" && /\.(?:bat|cmd)$/i.test(goBinary);
|
|
650
|
+
}
|
|
651
|
+
function goBuildEnv(goBinary, goBuildCacheRoot) {
|
|
613
652
|
const env = { ...process.env };
|
|
614
653
|
env.GOWORK = "auto";
|
|
654
|
+
// Only the actual `go build` needs ttsc's GOCACHE; read-only metadata spawns
|
|
655
|
+
// (`go mod edit`, `go env`, `go version`) call goBuildEnv with no cache root
|
|
656
|
+
// and inherit the ambient GOCACHE, which they never write to anyway. GOCACHE
|
|
657
|
+
// is not part of the plugin cache key, so this cannot affect it.
|
|
658
|
+
if (goBuildCacheRoot) {
|
|
659
|
+
env.GOCACHE = goBuildCacheRoot;
|
|
660
|
+
}
|
|
615
661
|
const goRoot = inferGoRoot(goBinary);
|
|
616
662
|
if (goRoot && !env.GOROOT) {
|
|
617
663
|
env.GOROOT = goRoot;
|
|
@@ -712,77 +758,225 @@ function walkForGoMod(dir, out) {
|
|
|
712
758
|
/**
|
|
713
759
|
* Resolve the directory where compiled plugin binaries are cached.
|
|
714
760
|
*
|
|
715
|
-
*
|
|
761
|
+
* Delegates to {@link resolveSourceBuildCachePaths}; kept as a thin accessor for
|
|
762
|
+
* callers (and tests) that only need the plugin-binary root. Triggers the
|
|
763
|
+
* opportunistic project-cache GC as a side effect for the default location.
|
|
764
|
+
*/
|
|
765
|
+
function resolvePluginCacheRoot(projectRoot, cacheDir) {
|
|
766
|
+
const paths = resolveSourceBuildCachePaths(projectRoot, cacheDir);
|
|
767
|
+
maybePrunePluginCache(paths, cacheDir);
|
|
768
|
+
return paths.pluginRoot;
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Resolve all source-plugin build cache directories for one invocation.
|
|
772
|
+
*
|
|
773
|
+
* `pluginRoot` stores compiled plugin binaries; `goBuildRoot` is the Go object
|
|
774
|
+
* cache passed as `GOCACHE` while ttsc builds those binaries. Both live under a
|
|
775
|
+
* single `root`, so persisting one directory covers the whole source-build
|
|
776
|
+
* cache without depending on ttsc internals.
|
|
777
|
+
*/
|
|
778
|
+
function resolveSourceBuildCachePaths(projectRoot, cacheDir, env = process.env) {
|
|
779
|
+
const root = resolveSourceBuildCacheRoot(projectRoot, cacheDir, env);
|
|
780
|
+
const goBuild = resolveGoBuildCacheRoot(root, projectRoot, env);
|
|
781
|
+
return {
|
|
782
|
+
root,
|
|
783
|
+
pluginRoot: node_path_1.default.join(root, PLUGIN_CACHE_DIRNAME),
|
|
784
|
+
goBuildRoot: goBuild.root,
|
|
785
|
+
goBuildRootSource: goBuild.source,
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Resolve the cache root for one invocation.
|
|
790
|
+
*
|
|
791
|
+
* Priority:
|
|
716
792
|
*
|
|
717
|
-
* 1. `cacheDir` option (resolved relative to `projectRoot`)
|
|
718
|
-
* 2. `TTSC_CACHE_DIR` environment variable
|
|
719
|
-
* 3.
|
|
793
|
+
* 1. Explicit `cacheDir` option (resolved relative to `projectRoot`);
|
|
794
|
+
* 2. `TTSC_CACHE_DIR` environment variable (resolved absolute);
|
|
795
|
+
* 3. `<workspaceRoot>/node_modules/.cache/ttsc` — project-local by default.
|
|
720
796
|
*
|
|
721
|
-
*
|
|
722
|
-
*
|
|
723
|
-
*
|
|
797
|
+
* There is deliberately NO global (`~/.cache`) fallback: the cache is scoped to
|
|
798
|
+
* the workspace so it can never accumulate machine-wide, and `rm -rf
|
|
799
|
+
* node_modules` reclaims it.
|
|
724
800
|
*/
|
|
725
|
-
function
|
|
801
|
+
function resolveSourceBuildCacheRoot(projectRoot, cacheDir, env) {
|
|
726
802
|
if (cacheDir) {
|
|
727
|
-
return node_path_1.default.resolve(projectRoot, cacheDir
|
|
803
|
+
return node_path_1.default.resolve(projectRoot, cacheDir);
|
|
728
804
|
}
|
|
729
|
-
if (
|
|
730
|
-
|
|
805
|
+
if (env.TTSC_CACHE_DIR) {
|
|
806
|
+
// Anchor a relative TTSC_CACHE_DIR to the project root (not the process
|
|
807
|
+
// cwd) so a programmatic host whose cwd differs from the project still
|
|
808
|
+
// resolves — and later cleans — the same cache. Absolute values pass
|
|
809
|
+
// through path.resolve unchanged.
|
|
810
|
+
return node_path_1.default.resolve(projectRoot, env.TTSC_CACHE_DIR);
|
|
731
811
|
}
|
|
732
|
-
|
|
733
|
-
maybePruneGlobalPluginCache(root);
|
|
734
|
-
return root;
|
|
812
|
+
return node_path_1.default.join(resolveWorkspaceRoot(projectRoot), NODE_MODULES_DIRNAME, LOCAL_CACHE_PARENT_DIRNAME, TTSC_CACHE_DIRNAME);
|
|
735
813
|
}
|
|
736
814
|
/**
|
|
737
|
-
*
|
|
815
|
+
* Resolve the monorepo/workspace root for `projectRoot` so every package shares
|
|
816
|
+
* one cache and a plugin builds once per workspace, not once per package.
|
|
738
817
|
*
|
|
739
|
-
*
|
|
740
|
-
*
|
|
741
|
-
* `
|
|
818
|
+
* Walks up from `projectRoot` and returns, in order of preference: the NEAREST
|
|
819
|
+
* ancestor that is a workspace root (holds `pnpm-workspace.yaml`, or a
|
|
820
|
+
* `package.json` with a `workspaces` field); else the nearest ancestor that
|
|
821
|
+
* already contains a `node_modules` directory; else `projectRoot` itself.
|
|
822
|
+
*
|
|
823
|
+
* Nearest (not highest) so an unrelated ancestor that happens to declare
|
|
824
|
+
* `workspaces` — for example a `package.json` in the user's home directory —
|
|
825
|
+
* cannot pull the cache above the project's real monorepo root.
|
|
742
826
|
*/
|
|
743
|
-
function
|
|
744
|
-
|
|
827
|
+
function resolveWorkspaceRoot(projectRoot) {
|
|
828
|
+
let dir = node_path_1.default.resolve(projectRoot);
|
|
829
|
+
let nearestNodeModulesOwner = null;
|
|
830
|
+
for (;;) {
|
|
831
|
+
if (isWorkspaceRootDir(dir)) {
|
|
832
|
+
return dir;
|
|
833
|
+
}
|
|
834
|
+
if (nearestNodeModulesOwner === null &&
|
|
835
|
+
node_fs_1.default.existsSync(node_path_1.default.join(dir, NODE_MODULES_DIRNAME))) {
|
|
836
|
+
nearestNodeModulesOwner = dir;
|
|
837
|
+
}
|
|
838
|
+
const parent = node_path_1.default.dirname(dir);
|
|
839
|
+
if (parent === dir) {
|
|
840
|
+
break;
|
|
841
|
+
}
|
|
842
|
+
dir = parent;
|
|
843
|
+
}
|
|
844
|
+
return nearestNodeModulesOwner ?? node_path_1.default.resolve(projectRoot);
|
|
845
|
+
}
|
|
846
|
+
function isWorkspaceRootDir(dir) {
|
|
847
|
+
for (const marker of WORKSPACE_ROOT_MARKER_FILES) {
|
|
848
|
+
if (node_fs_1.default.existsSync(node_path_1.default.join(dir, marker))) {
|
|
849
|
+
return true;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return packageJsonDeclaresWorkspaces(node_path_1.default.join(dir, "package.json"));
|
|
853
|
+
}
|
|
854
|
+
function packageJsonDeclaresWorkspaces(packageJsonPath) {
|
|
855
|
+
let text;
|
|
856
|
+
try {
|
|
857
|
+
text = node_fs_1.default.readFileSync(packageJsonPath, "utf8");
|
|
858
|
+
}
|
|
859
|
+
catch {
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
try {
|
|
863
|
+
const workspaces = JSON.parse(text)
|
|
864
|
+
.workspaces;
|
|
865
|
+
// A real workspace root declares a NON-EMPTY package list (an array, or an
|
|
866
|
+
// object with a `packages` array). Ignore `false`, `[]`, `null`, or `{}` so
|
|
867
|
+
// a disabled or empty declaration on an unrelated ancestor cannot hijack
|
|
868
|
+
// the cache location.
|
|
869
|
+
if (Array.isArray(workspaces)) {
|
|
870
|
+
return workspaces.length > 0;
|
|
871
|
+
}
|
|
872
|
+
if (workspaces !== null && typeof workspaces === "object") {
|
|
873
|
+
const packages = workspaces.packages;
|
|
874
|
+
return Array.isArray(packages) && packages.length > 0;
|
|
875
|
+
}
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
878
|
+
catch {
|
|
879
|
+
return false;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
function resolveGoBuildCacheRoot(root, projectRoot, env) {
|
|
883
|
+
if (env.TTSC_GO_CACHE_DIR) {
|
|
884
|
+
// Anchor a relative TTSC_GO_CACHE_DIR to the project root, matching
|
|
885
|
+
// TTSC_CACHE_DIR, so the build and a later `clean` from a different cwd
|
|
886
|
+
// agree on the directory. Absolute values pass through unchanged.
|
|
887
|
+
return {
|
|
888
|
+
root: node_path_1.default.resolve(projectRoot, env.TTSC_GO_CACHE_DIR),
|
|
889
|
+
source: "TTSC_GO_CACHE_DIR",
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
if (env.GOCACHE && env.GOCACHE.length > 0) {
|
|
893
|
+
return {
|
|
894
|
+
root: env.GOCACHE,
|
|
895
|
+
source: "GOCACHE",
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
return {
|
|
899
|
+
root: node_path_1.default.join(root, GO_BUILD_CACHE_DIRNAME),
|
|
900
|
+
source: "ttsc-cache",
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
function maybePrunePluginCache(paths, cacheDir) {
|
|
904
|
+
// GC only the default (workspace-local) location. When the user pins an
|
|
905
|
+
// explicit `cacheDir`/`TTSC_CACHE_DIR`, they own its lifetime, so ttsc must
|
|
906
|
+
// not delete entries out from under them.
|
|
907
|
+
if (!cacheDir && !process.env.TTSC_CACHE_DIR) {
|
|
908
|
+
prunePluginCacheRoot(paths.pluginRoot);
|
|
909
|
+
}
|
|
745
910
|
}
|
|
746
911
|
/**
|
|
747
|
-
* Return
|
|
912
|
+
* Return every directory `ttsc clean` should remove for `projectRoot`.
|
|
748
913
|
*
|
|
749
|
-
* Covers
|
|
750
|
-
*
|
|
751
|
-
* project-local
|
|
914
|
+
* Covers the resolved cache root (which holds `plugins/` and, when ttsc-owned,
|
|
915
|
+
* `go-build/`), a ttsc-owned Go build cache that lives OUTSIDE that root
|
|
916
|
+
* (`TTSC_GO_CACHE_DIR`), and the two legacy project-local caches. A
|
|
917
|
+
* user-provided `GOCACHE` is never removed. Pure over `env`, so the CLI passes
|
|
918
|
+
* `process.env` and a programmatic caller can pass an injected environment.
|
|
752
919
|
*/
|
|
753
|
-
function
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
920
|
+
function resolveCleanTargets(projectRoot, cacheDir, env = process.env) {
|
|
921
|
+
const paths = resolveSourceBuildCachePaths(projectRoot, cacheDir, env);
|
|
922
|
+
// Remove ttsc-OWNED directories only, never the parent cache root.
|
|
923
|
+
const targets = [paths.pluginRoot];
|
|
924
|
+
// ttsc's nested `<root>/go-build` is only safe to delete when we are certain
|
|
925
|
+
// the root belongs to ttsc: the default `node_modules/.cache/ttsc`, or a root
|
|
926
|
+
// the user explicitly named `ttsc`. Under a shared root (e.g.
|
|
927
|
+
// `TTSC_CACHE_DIR=~/.cache`) a bare `<root>/go-build` could be the user's
|
|
928
|
+
// machine-wide GOCACHE, so it must never be removed by name.
|
|
929
|
+
const isTtscOwnedRoot = (!cacheDir && !env.TTSC_CACHE_DIR) ||
|
|
930
|
+
node_path_1.default.basename(paths.root) === TTSC_CACHE_DIRNAME;
|
|
931
|
+
if (isTtscOwnedRoot) {
|
|
932
|
+
targets.push(node_path_1.default.join(paths.root, GO_BUILD_CACHE_DIRNAME));
|
|
933
|
+
}
|
|
934
|
+
// An explicit TTSC_GO_CACHE_DIR is a ttsc-dedicated external cache; a
|
|
935
|
+
// user-provided GOCACHE (source "GOCACHE") is never removed.
|
|
936
|
+
if (paths.goBuildRootSource === "TTSC_GO_CACHE_DIR") {
|
|
937
|
+
targets.push(paths.goBuildRoot);
|
|
938
|
+
}
|
|
939
|
+
targets.push(node_path_1.default.join(projectRoot, NODE_MODULES_DIRNAME, ".ttsc"));
|
|
940
|
+
targets.push(node_path_1.default.join(projectRoot, ".ttsc"));
|
|
941
|
+
return targets;
|
|
759
942
|
}
|
|
760
|
-
|
|
943
|
+
/**
|
|
944
|
+
* Machine-global cache directories created by pre-0.17 ttsc releases (XDG /
|
|
945
|
+
* AppData / Library / `~/.cache`). ttsc no longer writes to any of these, but
|
|
946
|
+
* an upgraded machine can still hold a multi-GB orphaned cache here, so `ttsc
|
|
947
|
+
* clean` offers them for removal to reclaim that disk. Each entry is the whole
|
|
948
|
+
* `<userCacheRoot>/ttsc` directory (both its `plugins` and `go-build`), which
|
|
949
|
+
* was entirely ttsc-owned in those releases and is safe to remove.
|
|
950
|
+
*/
|
|
951
|
+
function legacyGlobalCacheTargets() {
|
|
952
|
+
const roots = new Set();
|
|
953
|
+
const home = node_os_1.default.homedir();
|
|
761
954
|
const xdg = process.env.XDG_CACHE_HOME;
|
|
762
955
|
if (xdg && node_path_1.default.isAbsolute(xdg)) {
|
|
763
|
-
|
|
956
|
+
roots.add(node_path_1.default.join(xdg, TTSC_CACHE_DIRNAME));
|
|
764
957
|
}
|
|
765
958
|
if (process.platform === "win32") {
|
|
766
959
|
const local = process.env.LOCALAPPDATA;
|
|
767
960
|
if (local && node_path_1.default.isAbsolute(local)) {
|
|
768
|
-
|
|
961
|
+
roots.add(node_path_1.default.join(local, TTSC_CACHE_DIRNAME));
|
|
769
962
|
}
|
|
770
|
-
const home = node_os_1.default.homedir();
|
|
771
963
|
if (home) {
|
|
772
|
-
|
|
964
|
+
roots.add(node_path_1.default.join(home, "AppData", "Local", TTSC_CACHE_DIRNAME));
|
|
773
965
|
}
|
|
774
966
|
}
|
|
775
|
-
if (process.platform === "darwin") {
|
|
776
|
-
|
|
777
|
-
if (home) {
|
|
778
|
-
return node_path_1.default.join(home, "Library", "Caches");
|
|
779
|
-
}
|
|
967
|
+
else if (process.platform === "darwin" && home) {
|
|
968
|
+
roots.add(node_path_1.default.join(home, "Library", "Caches", TTSC_CACHE_DIRNAME));
|
|
780
969
|
}
|
|
781
|
-
const home = node_os_1.default.homedir();
|
|
782
970
|
if (home) {
|
|
783
|
-
|
|
971
|
+
roots.add(node_path_1.default.join(home, ".cache", TTSC_CACHE_DIRNAME));
|
|
784
972
|
}
|
|
785
|
-
return
|
|
973
|
+
return [...roots];
|
|
974
|
+
}
|
|
975
|
+
/** Report whether `child` equals `parent` or is nested beneath it. */
|
|
976
|
+
function isPathWithin(child, parent) {
|
|
977
|
+
const rel = node_path_1.default.relative(parent, child);
|
|
978
|
+
return (rel === "" ||
|
|
979
|
+
(rel !== ".." && !rel.startsWith(`..${node_path_1.default.sep}`) && !node_path_1.default.isAbsolute(rel)));
|
|
786
980
|
}
|
|
787
981
|
function resolveGoCompiler() {
|
|
788
982
|
const explicit = process.env.TTSC_GO_BINARY;
|
|
@@ -942,7 +1136,7 @@ function computeGoCompilerIdentity(goBinary, resolved) {
|
|
|
942
1136
|
if (!node_fs_1.default.existsSync(resolved)) {
|
|
943
1137
|
return "missing";
|
|
944
1138
|
}
|
|
945
|
-
const version = (
|
|
1139
|
+
const version = spawnGoTool(goBinary, ["version"], {
|
|
946
1140
|
encoding: "utf8",
|
|
947
1141
|
windowsHide: true,
|
|
948
1142
|
});
|
|
@@ -967,14 +1161,28 @@ function resolveExecutableIdentityPath(binary) {
|
|
|
967
1161
|
return resolveRealPath(candidate);
|
|
968
1162
|
}
|
|
969
1163
|
if (process.platform === "win32") {
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1164
|
+
// Probe every PATHEXT extension, not just `.exe`, so a compiler backed by
|
|
1165
|
+
// a `.cmd`/`.bat` wrapper resolves to its real file and is hashed into the
|
|
1166
|
+
// cache key. Otherwise the wrapper reads as "missing" and a change to it
|
|
1167
|
+
// would not invalidate the cached plugin binary.
|
|
1168
|
+
for (const ext of windowsExecutableExtensions()) {
|
|
1169
|
+
const executable = `${candidate}${ext}`;
|
|
1170
|
+
if (node_fs_1.default.existsSync(executable)) {
|
|
1171
|
+
return resolveRealPath(executable);
|
|
1172
|
+
}
|
|
973
1173
|
}
|
|
974
1174
|
}
|
|
975
1175
|
}
|
|
976
1176
|
return binary;
|
|
977
1177
|
}
|
|
1178
|
+
function windowsExecutableExtensions() {
|
|
1179
|
+
const pathext = process.env.PATHEXT;
|
|
1180
|
+
const raw = pathext && pathext.length > 0 ? pathext : ".COM;.EXE;.BAT;.CMD";
|
|
1181
|
+
return raw
|
|
1182
|
+
.split(";")
|
|
1183
|
+
.map((ext) => ext.trim().toLowerCase())
|
|
1184
|
+
.filter((ext) => ext.length > 0);
|
|
1185
|
+
}
|
|
978
1186
|
function readPathEnvironment() {
|
|
979
1187
|
return process.env.PATH ?? process.env.Path ?? "";
|
|
980
1188
|
}
|
|
@@ -1003,7 +1211,7 @@ function hashGoBuildEnvironment(hash, goBinary, cwd) {
|
|
|
1003
1211
|
function resolveGoBuildEnvironment(goBinary, cwd) {
|
|
1004
1212
|
const values = new Map();
|
|
1005
1213
|
if (goBinary !== undefined) {
|
|
1006
|
-
const result = (
|
|
1214
|
+
const result = spawnGoTool(goBinary, ["env", "-json", ...GO_BUILD_ENV_KEYS], {
|
|
1007
1215
|
cwd,
|
|
1008
1216
|
encoding: "utf8",
|
|
1009
1217
|
env: goBuildEnv(goBinary),
|
|
@@ -1169,47 +1377,47 @@ function touchCacheEntry(cacheDir) {
|
|
|
1169
1377
|
// Cache hits must not fail because metadata touch failed.
|
|
1170
1378
|
}
|
|
1171
1379
|
}
|
|
1172
|
-
function
|
|
1380
|
+
function prunePluginCacheRoot(root) {
|
|
1173
1381
|
try {
|
|
1174
1382
|
node_fs_1.default.mkdirSync(root, { recursive: true });
|
|
1175
1383
|
const marker = node_path_1.default.join(root, CACHE_GC_MARKER_FILE);
|
|
1176
1384
|
const now = Date.now();
|
|
1177
1385
|
const lastRun = readTimestamp(marker);
|
|
1178
|
-
if (lastRun !== null && now - lastRun <
|
|
1386
|
+
if (lastRun !== null && now - lastRun < PLUGIN_CACHE_GC_INTERVAL_MS) {
|
|
1179
1387
|
return;
|
|
1180
1388
|
}
|
|
1181
1389
|
node_fs_1.default.writeFileSync(marker, `${now}\n`);
|
|
1182
|
-
|
|
1390
|
+
prunePluginCacheEntries(root, now);
|
|
1183
1391
|
}
|
|
1184
1392
|
catch {
|
|
1185
|
-
//
|
|
1393
|
+
// Plugin-cache GC is opportunistic; builds still proceed when it fails.
|
|
1186
1394
|
}
|
|
1187
1395
|
}
|
|
1188
|
-
function
|
|
1189
|
-
const entries =
|
|
1396
|
+
function prunePluginCacheEntries(root, now) {
|
|
1397
|
+
const entries = collectPluginCacheEntries(root, now);
|
|
1190
1398
|
for (const entry of entries) {
|
|
1191
|
-
if (now - entry.lastUsedAt <=
|
|
1399
|
+
if (now - entry.lastUsedAt <= PLUGIN_CACHE_ENTRY_MAX_AGE_MS) {
|
|
1192
1400
|
continue;
|
|
1193
1401
|
}
|
|
1194
1402
|
removeCacheEntry(entry);
|
|
1195
1403
|
}
|
|
1196
|
-
const remaining =
|
|
1404
|
+
const remaining = collectPluginCacheEntries(root, now);
|
|
1197
1405
|
let total = remaining.reduce((sum, entry) => sum + entry.size, 0);
|
|
1198
|
-
if (total <=
|
|
1406
|
+
if (total <= PLUGIN_CACHE_MAX_BYTES) {
|
|
1199
1407
|
return;
|
|
1200
1408
|
}
|
|
1201
1409
|
for (const entry of remaining.sort((a, b) => a.lastUsedAt - b.lastUsedAt)) {
|
|
1202
|
-
if (total <=
|
|
1410
|
+
if (total <= PLUGIN_CACHE_TARGET_BYTES) {
|
|
1203
1411
|
return;
|
|
1204
1412
|
}
|
|
1205
|
-
if (now - entry.lastUsedAt <=
|
|
1413
|
+
if (now - entry.lastUsedAt <= PLUGIN_CACHE_PROTECTED_AGE_MS) {
|
|
1206
1414
|
continue;
|
|
1207
1415
|
}
|
|
1208
1416
|
removeCacheEntry(entry);
|
|
1209
1417
|
total -= entry.size;
|
|
1210
1418
|
}
|
|
1211
1419
|
}
|
|
1212
|
-
function
|
|
1420
|
+
function collectPluginCacheEntries(root, now) {
|
|
1213
1421
|
const entries = [];
|
|
1214
1422
|
let dirents;
|
|
1215
1423
|
try {
|