ttsc 0.26.1 → 0.27.0

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.
Files changed (119) hide show
  1. package/README.md +10 -1
  2. package/cmd/ttsc/api_transform.go +17 -3
  3. package/cmd/ttscgraph/auxiliary_identity_state_tracks_retargets_not_source_contents_test.go +85 -0
  4. package/cmd/ttscgraph/disk_realpath_other.go +16 -0
  5. package/cmd/ttscgraph/disk_realpath_windows.go +56 -0
  6. package/cmd/ttscgraph/serve.go +104 -35
  7. package/driver/diagnostics_preamble.go +284 -0
  8. package/driver/emit_plugin.go +71 -19
  9. package/driver/graph.go +64 -11
  10. package/driver/input_observation.go +273 -0
  11. package/driver/input_observation_test.go +149 -0
  12. package/driver/plugins.go +384 -4
  13. package/driver/program.go +81 -15
  14. package/driver/resolution_candidates.go +13 -5
  15. package/driver/rewrite.go +47 -4
  16. package/internal/graph/diagnostics.go +4 -3
  17. package/internal/graph/provenance.go +10 -1
  18. package/lib/TtscCompiler.d.ts +12 -11
  19. package/lib/TtscCompiler.js +44 -29
  20. package/lib/TtscCompiler.js.map +1 -1
  21. package/lib/compiler/internal/captureProcessOutput.js +2 -2
  22. package/lib/compiler/internal/captureProcessOutput.js.map +1 -1
  23. package/lib/compiler/internal/compileProjectInMemory.js +11 -3
  24. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  25. package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +2 -0
  26. package/lib/compiler/internal/resolveEmittedJavaScript.js +3 -0
  27. package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
  28. package/lib/compiler/internal/runBuild.d.ts +29 -1
  29. package/lib/compiler/internal/runBuild.js +117 -32
  30. package/lib/compiler/internal/runBuild.js.map +1 -1
  31. package/lib/compiler/internal/runSingleFileEmit.js +9 -2
  32. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  33. package/lib/compiler/internal/sharedHostHelpers.d.ts +28 -0
  34. package/lib/compiler/internal/sharedHostHelpers.js +39 -0
  35. package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
  36. package/lib/compiler/internal/startResidentTransform.js +12 -4
  37. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  38. package/lib/compiler/internal/transformProjectInMemory.d.ts +3 -0
  39. package/lib/compiler/internal/transformProjectInMemory.js +239 -35
  40. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  41. package/lib/flags/schema.js +33 -5
  42. package/lib/flags/schema.js.map +1 -1
  43. package/lib/internal/createCanonicalTempDirectory.d.ts +17 -0
  44. package/lib/internal/createCanonicalTempDirectory.js +42 -0
  45. package/lib/internal/createCanonicalTempDirectory.js.map +1 -0
  46. package/lib/internal/projectInputPathIdentity.d.ts +3 -0
  47. package/lib/internal/projectInputPathIdentity.js +11 -5
  48. package/lib/internal/projectInputPathIdentity.js.map +1 -1
  49. package/lib/internal/resolveNodeBinary.d.ts +14 -0
  50. package/lib/internal/resolveNodeBinary.js +150 -0
  51. package/lib/internal/resolveNodeBinary.js.map +1 -0
  52. package/lib/internal/resolveSafeCacheCleanupTargets.d.ts +15 -0
  53. package/lib/internal/resolveSafeCacheCleanupTargets.js +61 -0
  54. package/lib/internal/resolveSafeCacheCleanupTargets.js.map +1 -0
  55. package/lib/launcher/internal/assertNoSolutionBuild.d.ts +31 -0
  56. package/lib/launcher/internal/assertNoSolutionBuild.js +38 -0
  57. package/lib/launcher/internal/assertNoSolutionBuild.js.map +1 -0
  58. package/lib/launcher/internal/prepareExecution.d.ts +3 -0
  59. package/lib/launcher/internal/prepareExecution.js +73 -26
  60. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  61. package/lib/launcher/internal/runTtsc.js +27 -13
  62. package/lib/launcher/internal/runTtsc.js.map +1 -1
  63. package/lib/launcher/internal/runTtscserver.js +18 -3
  64. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  65. package/lib/launcher/internal/runTtsx.js +4 -0
  66. package/lib/launcher/internal/runTtsx.js.map +1 -1
  67. package/lib/launcher/internal/runtimeHooks.d.ts +37 -1
  68. package/lib/launcher/internal/runtimeHooks.js +660 -62
  69. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  70. package/lib/plugin/internal/buildSourcePlugin.d.ts +62 -8
  71. package/lib/plugin/internal/buildSourcePlugin.js +754 -72
  72. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  73. package/lib/plugin/internal/loadProjectPlugins.d.ts +17 -5
  74. package/lib/plugin/internal/loadProjectPlugins.js +1339 -77
  75. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  76. package/lib/register.d.ts +1 -0
  77. package/lib/register.js +46 -0
  78. package/lib/register.js.map +1 -0
  79. package/lib/structures/ITtscCompilerContext.d.ts +6 -3
  80. package/lib/structures/ITtscCompilerTransformation.d.ts +31 -0
  81. package/lib/structures/ITtscPlugin.d.ts +33 -0
  82. package/package.json +16 -9
  83. package/shim/checker/go.mod +9 -0
  84. package/shim/checker/go.sum +22 -0
  85. package/shim/checker/shim.go +75 -0
  86. package/shim/compiler/emit_sourcemap.go +56 -15
  87. package/shim/compiler/incremental.go +45 -8
  88. package/src/TtscCompiler.ts +55 -33
  89. package/src/compiler/internal/captureProcessOutput.ts +3 -2
  90. package/src/compiler/internal/compileProjectInMemory.ts +11 -3
  91. package/src/compiler/internal/resolveEmittedJavaScript.ts +6 -0
  92. package/src/compiler/internal/runBuild.ts +172 -36
  93. package/src/compiler/internal/runSingleFileEmit.ts +9 -2
  94. package/src/compiler/internal/sharedHostHelpers.ts +44 -0
  95. package/src/compiler/internal/startResidentTransform.ts +12 -3
  96. package/src/compiler/internal/transformProjectInMemory.ts +410 -39
  97. package/src/flags/schema.ts +35 -5
  98. package/src/internal/createCanonicalTempDirectory.ts +53 -0
  99. package/src/internal/projectInputPathIdentity.ts +17 -4
  100. package/src/internal/resolveNodeBinary.ts +173 -0
  101. package/src/internal/resolveSafeCacheCleanupTargets.ts +85 -0
  102. package/src/launcher/internal/assertNoSolutionBuild.ts +40 -0
  103. package/src/launcher/internal/prepareExecution.ts +78 -26
  104. package/src/launcher/internal/runTtsc.ts +26 -13
  105. package/src/launcher/internal/runTtscserver.ts +18 -3
  106. package/src/launcher/internal/runTtsx.ts +4 -0
  107. package/src/launcher/internal/runtimeHooks.ts +830 -66
  108. package/src/plugin/internal/buildSourcePlugin.ts +984 -79
  109. package/src/plugin/internal/loadProjectPlugins.ts +1718 -104
  110. package/src/register.ts +47 -0
  111. package/src/structures/ITtscCompilerContext.ts +6 -3
  112. package/src/structures/ITtscCompilerTransformation.ts +39 -0
  113. package/src/structures/ITtscPlugin.ts +36 -0
  114. package/tools/shim_audit/baseline.json +0 -1
  115. package/utility/host.go +9 -3
  116. package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +0 -5
  117. package/lib/internal/assertSafeExplicitCacheDirectory.js +0 -26
  118. package/lib/internal/assertSafeExplicitCacheDirectory.js.map +0 -1
  119. package/src/internal/assertSafeExplicitCacheDirectory.ts +0 -29
@@ -5,12 +5,27 @@
5
5
  // tsgo's Program.Emit has no hook to inject a custom transformer, so ttsc's
6
6
  // driver assembles the per-file emit pipeline by hand (see GetSourceFilesToEmit
7
7
  // / GetScriptTransformers / GetOutputPathsFor). That hand-assembly must also
8
- // reproduce the source-map step the emitter would otherwise run: a bare
9
- // printer.Write with a nil generator drops the `.js.map` entirely. This file
10
- // ports internal/compiler/emitter.go's `printSourceFile` source-map branch so a
11
- // `sourceMap` / `inlineSourceMap` build that goes through a plugin transform
12
- // produces the same map (and `//# sourceMappingURL=` trailer) a plain build
13
- // does. Keep it in sync with that emitter source when the pin is bumped.
8
+ // reproduce every step the emitter would otherwise run: building the printer's
9
+ // options from the compiler options, the source-map branch a bare printer.Write
10
+ // with a nil generator drops entirely, the `emitBOM` byte-order mark, and the
11
+ // `WriteFileData` the emitter hands its writeFile callback. This file ports
12
+ // internal/compiler/emitter.go's `emitJSFile` PrinterOptions construction and
13
+ // the whole of its `printSourceFile` so a build that goes through a plugin
14
+ // transform honors the same compiler options — and produces the same map (and
15
+ // `//# sourceMappingURL=` trailer, and the same first bytes) — a plain build
16
+ // does.
17
+ //
18
+ // Keep it in sync with that emitter source when the pin is bumped. Anything
19
+ // `emitJSFile` sets and this file omits takes the Go zero value, which silently
20
+ // turns the option off for every project that emits through a plugin transform,
21
+ // and anything `printSourceFile` does around the printer that this file omits
22
+ // is missing from the plugin lane's output even when the option set matches.
23
+ // `printer_options_field_set_matches_pinned_emitter_test.go` fails when the pin
24
+ // changes the PrinterOptions field set,
25
+ // `write_file_data_field_set_matches_pinned_emitter_test.go` fails when it
26
+ // changes the WriteFileData field set, and
27
+ // `emit_plugin_transform_matches_plain_emit_for_printer_options_test.go` fails
28
+ // when a forwarded option stops matching the plain emit.
14
29
  package compiler
15
30
 
16
31
  import (
@@ -25,24 +40,43 @@ import (
25
40
 
26
41
  // PrintedFile is the rendered output of one source file in the plugin-transform
27
42
  // emit path. JS is the JavaScript text, already carrying a trailing
28
- // `//# sourceMappingURL=` comment when a map was produced. MapText/MapPath are
29
- // the external source-map file and its path; both are empty when no external map
30
- // is written (source maps disabled, or an inline map encoded into the JS).
43
+ // `//# sourceMappingURL=` comment when a map was produced and a leading UTF-8
44
+ // byte order mark when `emitBOM` is on. MapText/MapPath are the external
45
+ // source-map file and its path; both are empty when no external map is written
46
+ // (source maps disabled, or an inline map encoded into the JS).
31
47
  type PrintedFile struct {
32
48
  JS string
33
49
  MapText string
34
50
  MapPath string
51
+ // SourceMapUrlPos is the offset of the `//# sourceMappingURL=` trailer in JS,
52
+ // or -1 when no trailer was written — the value tsgo's emitter reports as
53
+ // WriteFileData.SourceMapUrlPos so a caller can locate and rewrite the
54
+ // trailer without re-scanning the text. Like the emitter's, it is the printer
55
+ // writer's text position, taken BEFORE the `emitBOM` mark is prepended, so a
56
+ // BOM build's offset is three bytes short of the trailer's position in the
57
+ // written file. That is the pinned emitter's own behavior
58
+ // (internal/compiler/emitter.go::printSourceFile computes sourceMapUrlPos
59
+ // from the writer and only then calls AddUTF8ByteOrderMark), and this lane
60
+ // exists to match it: "correcting" the offset here would make a plugin build
61
+ // disagree with the plain build of the same project.
62
+ SourceMapUrlPos int
35
63
  }
36
64
 
37
65
  // PrintFileWithSourceMap renders sourceFile through a printer built from options
38
66
  // and emitContext, optionally generating a source map, mirroring
39
- // emitter.printSourceFile for the single-file plugin-transform path. When
67
+ // emitter.emitJSFile and emitter.printSourceFile for the single-file
68
+ // plugin-transform path. The PrinterOptions below are emitJSFile's, field for
69
+ // field: `removeComments`, `newLine`, `noEmitHelpers`, `sourceMap`,
70
+ // `inlineSourceMap`, `inlineSources`, and `target`. When
40
71
  // `sourceMap`/`inlineSourceMap` is enabled (and the file is not JSON) it builds a
41
72
  // sourcemap.Generator, feeds it to the printer so positions are recorded,
42
- // appends the sourceMappingURL trailer, and returns the external map text/path
43
- // (or encodes the map inline). host supplies the same directory/casing context
44
- // tsgo's emitter reads. With source maps off it is byte-for-byte the prior
45
- // bare-printer behavior.
73
+ // appends the sourceMappingURL trailer, records its offset, and returns the
74
+ // external map text/path (or encodes the map inline). `emitBOM` prepends the
75
+ // UTF-8 byte order mark to the JavaScript afterwards, exactly where
76
+ // printSourceFile does it — outside PrinterOptions, which is why forwarding the
77
+ // whole options struct never reached it. The external map is written without a
78
+ // mark, matching the emitter's own `writeText(sourceMapFilePath, ..., nil)`.
79
+ // host supplies the same directory/casing context tsgo's emitter reads.
46
80
  func PrintFileWithSourceMap(
47
81
  emitContext *innerprinter.EmitContext,
48
82
  node *innerast.Node,
@@ -53,10 +87,13 @@ func PrintFileWithSourceMap(
53
87
  sourceMapFilePath string,
54
88
  ) PrintedFile {
55
89
  printer := innerprinter.NewPrinter(innerprinter.PrinterOptions{
90
+ RemoveComments: options.RemoveComments.IsTrue(),
56
91
  NewLine: options.NewLine,
92
+ NoEmitHelpers: options.NoEmitHelpers.IsTrue(),
57
93
  SourceMap: options.SourceMap.IsTrue(),
58
94
  InlineSourceMap: options.InlineSourceMap.IsTrue(),
59
95
  InlineSources: options.InlineSources.IsTrue(),
96
+ Target: options.Target,
60
97
  }, innerprinter.PrintHandlers{}, emitContext)
61
98
  writer := innerprinter.NewTextWriter(options.NewLine.GetNewLineCharacter(), 0)
62
99
 
@@ -78,7 +115,7 @@ func PrintFileWithSourceMap(
78
115
 
79
116
  printer.Write(node, sourceFile, writer, generator)
80
117
 
81
- result := PrintedFile{}
118
+ result := PrintedFile{SourceMapUrlPos: -1}
82
119
  if generator != nil {
83
120
  url := sourceMappingURL(options, generator, host, jsFilePath, sourceMapFilePath, sourceFile)
84
121
  if len(url) > 0 {
@@ -89,6 +126,7 @@ func PrintFileWithSourceMap(
89
126
  writer.RawWrite("\n")
90
127
  }
91
128
  }
129
+ result.SourceMapUrlPos = writer.GetTextPos()
92
130
  writer.WriteComment("//# sourceMappingURL=")
93
131
  writer.WriteComment(url)
94
132
  }
@@ -100,6 +138,9 @@ func PrintFileWithSourceMap(
100
138
  writer.WriteLine()
101
139
  }
102
140
  result.JS = writer.String()
141
+ if options.EmitBOM.IsTrue() {
142
+ result.JS = innerstringutil.AddUTF8ByteOrderMark(result.JS)
143
+ }
103
144
  return result
104
145
  }
105
146
 
@@ -1,13 +1,20 @@
1
1
  // gen_shims:hand-maintained
2
2
  //
3
- // Reference-graph parts of tsgo's own incremental engine (`tsc --incremental`
4
- // semantics), linked from internal/execute/incremental so ttsc's transform
5
- // envelopes can carry the same language-semantic input bound the compiler
6
- // itself uses for invalidation: per-file direct resolved references and the
7
- // files that contribute to the global scope.
3
+ // Parts of tsgo's own incremental engine (`tsc --incremental` semantics),
4
+ // exposed from internal/execute/incremental for the two things a ttsc host
5
+ // needs from it:
6
+ //
7
+ // - the reference graph, so transform envelopes can carry the same
8
+ // language-semantic input bound the compiler itself uses for invalidation:
9
+ // per-file direct resolved references and the files that contribute to the
10
+ // global scope;
11
+ // - the build-information emit, so a host that constructs its Program
12
+ // in-process still writes the `.tsbuildinfo` an `incremental` or
13
+ // `composite` project asked for.
8
14
  package compiler
9
15
 
10
16
  import (
17
+ "context"
11
18
  _ "unsafe"
12
19
 
13
20
  innerast "github.com/microsoft/typescript-go/internal/ast"
@@ -15,11 +22,41 @@ import (
15
22
  innercompiler "github.com/microsoft/typescript-go/internal/compiler"
16
23
  "github.com/microsoft/typescript-go/internal/tspath"
17
24
 
18
- // The linknamed symbols below live in the incremental package; the blank
19
- // import compiles it into every shim consumer so the references resolve.
20
- _ "github.com/microsoft/typescript-go/internal/execute/incremental"
25
+ // Imported for EmitFreshWithBuildInfo below, and for the linknamed symbols
26
+ // further down: compiling the package into every shim consumer is what makes
27
+ // those references resolve.
28
+ "github.com/microsoft/typescript-go/internal/execute/incremental"
21
29
  )
22
30
 
31
+ // EmitFreshWithBuildInfo runs a full emit through tsgo's own incremental
32
+ // program, so an `incremental` or `composite` project gets the `.tsbuildinfo`
33
+ // tsgo would have written, in the exact format and location
34
+ // `outputpaths.GetBuildInfoFileName` resolves from the compiler options.
35
+ //
36
+ // This is the emit half of `tsc.go::performIncrementalCompilation`, which the
37
+ // tsgo CLI takes whenever `CompilerOptions.IsIncremental()`. A host that builds
38
+ // its Program in-process (ttsc's driver, and therefore every plugin sidecar
39
+ // emitting through it) never reaches that CLI path, so without this the build
40
+ // information is silently dropped even though the options parsed cleanly.
41
+ //
42
+ // "Fresh" is the load-bearing word: no previous snapshot is supplied, so
43
+ // `programToSnapshot` marks every file changed and this emits the whole program
44
+ // exactly as `Program.Emit` does, then writes the build info. A ttsc plugin's
45
+ // output is not a pure function of the source text a build info records — it
46
+ // also depends on the plugin binary, its config file, and its contributors —
47
+ // so reusing a previous snapshot to skip a file would serve stale transformed
48
+ // output. Producing the record is sound; consuming it needs plugin identity in
49
+ // the invalidation key first.
50
+ func EmitFreshWithBuildInfo(ctx context.Context, program *Program, options EmitOptions) *EmitResult {
51
+ incrementalProgram := incremental.NewProgram(
52
+ program,
53
+ nil,
54
+ incremental.CreateHost(program.Host()),
55
+ false,
56
+ )
57
+ return incrementalProgram.Emit(ctx, options)
58
+ }
59
+
23
60
  //go:linkname incrementalGetReferencedFiles github.com/microsoft/typescript-go/internal/execute/incremental.getReferencedFiles
24
61
  func incrementalGetReferencedFiles(program *innercompiler.Program, file *innerast.SourceFile) *collections.Set[tspath.Path]
25
62
 
@@ -6,7 +6,10 @@ import { resolveProjectConfig } from "./compiler/internal/project/resolveProject
6
6
  import { resolveBinary } from "./compiler/internal/resolveBinary";
7
7
  import { createProcessDiagnostic } from "./compiler/internal/runBuild";
8
8
  import { transformProjectInMemory } from "./compiler/internal/transformProjectInMemory";
9
- import { assertSafeExplicitCacheDirectory } from "./internal/assertSafeExplicitCacheDirectory";
9
+ import {
10
+ type SafeCacheCleanupTarget,
11
+ resolveSafeCacheCleanupTargets,
12
+ } from "./internal/resolveSafeCacheCleanupTargets";
10
13
  import { resolveCleanTargets } from "./plugin/internal/buildSourcePlugin";
11
14
  import { loadProjectPlugins } from "./plugin/internal/loadProjectPlugins";
12
15
  import type { ITtscCompilerContext } from "./structures/ITtscCompilerContext";
@@ -86,15 +89,16 @@ export class TtscCompiler {
86
89
  /**
87
90
  * Remove compiled cache artifacts for this compiler instance.
88
91
  *
89
- * Removes the resolved cache root (which holds both the plugin binaries and,
90
- * when ttsc-owned, the Go build cache), a ttsc-owned Go build cache that
91
- * lives outside that root (`TTSC_GO_CACHE_DIR`), and the two legacy
92
- * project-local caches. A user-provided `GOCACHE` is never removed. The cache
92
+ * Removes an explicit `cacheDir` wholesale. Otherwise removes the plugin
93
+ * binary subdirectory, a safely identified ttsc-owned Go build cache
94
+ * (including an external `TTSC_GO_CACHE_DIR`), and the two legacy
95
+ * project-local caches. A user-provided `GOCACHE` is never removed. Every
96
+ * resolved deletion target is validated before the first removal. The cache
93
97
  * location comes from this instance's `cacheDir` and environment
94
98
  * (`TTSC_CACHE_DIR` / `TTSC_GO_CACHE_DIR`), defaulting to
95
- * `<workspaceRoot>/node_modules/.cache/ttsc`. An explicit cache directory
96
- * that equals or contains the project, or names a filesystem root, is
97
- * rejected before any directory is removed.
99
+ * `<workspaceRoot>/node_modules/.cache/ttsc`. Any resolved cache target that
100
+ * equals or contains the project, or names a filesystem root, is rejected
101
+ * before any directory is removed.
98
102
  *
99
103
  * @returns Cache directories that were removed.
100
104
  */
@@ -105,31 +109,35 @@ export class TtscCompiler {
105
109
  path.join(projectRoot, ".ttsc"),
106
110
  ];
107
111
  const explicitCacheDir = this.resolveCacheDir();
112
+ let targets: string[];
108
113
  if (explicitCacheDir !== undefined) {
109
- assertSafeExplicitCacheDirectory(projectRoot, explicitCacheDir);
110
114
  // An explicit constructor `cacheDir` names the cache directory for this
111
115
  // instance — exactly like `ttsc clean --cache-dir X` on the CLI — so
112
116
  // remove it wholesale plus the legacy project-local caches. This keeps
113
117
  // the programmatic and CLI clean contracts identical for an explicit
114
118
  // cache dir.
115
- return removeExistingDirectories([explicitCacheDir, ...legacyTargets]);
116
- }
117
- // Default / `context.env.TTSC_CACHE_DIR`: resolve the cache root and the Go
118
- // build cache (`TTSC_GO_CACHE_DIR`) from this instance's effective
119
- // environment — the same `{ ...process.env, ...context.env }` that
120
- // prepare()/compile()/transform() build with — then remove only the
121
- // ttsc-owned subdirectories, so a possibly-shared root is never deleted and
122
- // a user-provided `GOCACHE` is never touched. Using the effective env (not
123
- // ambient `process.env`) makes clean() remove exactly the artifacts this
124
- // instance owns, including a `TTSC_GO_CACHE_DIR` supplied only in
125
- // `context.env`.
126
- return removeExistingDirectories(
127
- resolveCleanTargets(
119
+ targets = [explicitCacheDir, ...legacyTargets];
120
+ } else {
121
+ // Default / `context.env.TTSC_CACHE_DIR`: resolve the cache root and the Go
122
+ // build cache (`TTSC_GO_CACHE_DIR`) from this instance's effective
123
+ // environment — the same `{ ...process.env, ...context.env }` that
124
+ // prepare()/compile()/transform() build with — then remove only the
125
+ // ttsc-owned subdirectories, so a possibly-shared root is never deleted and
126
+ // a user-provided `GOCACHE` is never touched. Using the effective env (not
127
+ // ambient `process.env`) makes clean() remove exactly the artifacts this
128
+ // instance owns, including a `TTSC_GO_CACHE_DIR` supplied only in
129
+ // `context.env`.
130
+ targets = resolveCleanTargets(
128
131
  projectRoot,
129
132
  this.resolvePluginCacheDir(),
130
133
  this.resolveEffectiveEnv(),
131
- ),
132
- );
134
+ );
135
+ }
136
+ // Validate the complete deletion set before removing the first directory.
137
+ // This includes an environment-selected TTSC_GO_CACHE_DIR and project-local
138
+ // legacy locations, not only an explicit constructor cacheDir.
139
+ const safeTargets = resolveSafeCacheCleanupTargets(projectRoot, targets);
140
+ return removeExistingDirectories(safeTargets);
133
141
  }
134
142
 
135
143
  /**
@@ -234,24 +242,29 @@ export class TtscCompiler {
234
242
  * The effective environment for this instance's source-plugin builds and
235
243
  * clean targets: `context.env` merged over `process.env`, matching the
236
244
  * documented {@link ITtscCompilerContext.env} contract that child compiler,
237
- * native-plugin, and native-host processes already receive. Returned as a
238
- * fresh object so callers never mutate the shared `process.env`; when no
239
- * `context.env` was supplied this is a plain copy of `process.env`, so CLI /
240
- * default behavior is unchanged.
245
+ * native-plugin, native-host, and isolated descriptor processes already
246
+ * receive. Returned as a fresh object so callers never mutate the shared
247
+ * `process.env`; when no `context.env` was supplied this is a plain copy of
248
+ * `process.env`, so CLI / default behavior is unchanged.
241
249
  */
242
250
  private resolveEffectiveEnv(): NodeJS.ProcessEnv {
243
251
  return { ...process.env, ...this.context.env };
244
252
  }
245
253
  }
246
254
 
247
- function removeExistingDirectories(directories: readonly string[]): string[] {
255
+ function removeExistingDirectories(
256
+ directories: readonly SafeCacheCleanupTarget[],
257
+ ): string[] {
248
258
  const removed: string[] = [];
249
- for (const directory of [...new Set(directories)]) {
250
- if (!fs.existsSync(directory)) {
259
+ const visited = new Set<string>();
260
+ for (const directory of directories) {
261
+ if (visited.has(directory.path)) continue;
262
+ visited.add(directory.path);
263
+ if (!directory.exists || !fs.existsSync(directory.path)) {
251
264
  continue;
252
265
  }
253
- fs.rmSync(directory, { recursive: true, force: true });
254
- removed.push(directory);
266
+ fs.rmSync(directory.path, { recursive: true, force: true });
267
+ removed.push(directory.requestedPath);
255
268
  }
256
269
  return removed;
257
270
  }
@@ -265,6 +278,9 @@ interface ProjectTransformation {
265
278
  dependencies?: Record<string, string[]>;
266
279
  dependenciesComplete?: string[];
267
280
  graph?: ITtscCompilerTransformation.IReferenceGraph;
281
+ hostInputHashes?: Record<string, string | null>;
282
+ hostInputRealpaths?: Record<string, string | null>;
283
+ hostInputs?: string[];
268
284
  result: TtscBuildResult;
269
285
  typescript: Record<string, string>;
270
286
  volatile?: string[];
@@ -382,6 +398,9 @@ function toCompilerTransformation(
382
398
  dependencies,
383
399
  dependenciesComplete,
384
400
  graph,
401
+ hostInputHashes,
402
+ hostInputRealpaths,
403
+ hostInputs,
385
404
  result,
386
405
  typescript,
387
406
  volatile,
@@ -390,6 +409,9 @@ function toCompilerTransformation(
390
409
  ...(dependencies === undefined ? {} : { dependencies }),
391
410
  ...(dependenciesComplete === undefined ? {} : { dependenciesComplete }),
392
411
  ...(graph === undefined ? {} : { graph }),
412
+ ...(hostInputHashes === undefined ? {} : { hostInputHashes }),
413
+ ...(hostInputRealpaths === undefined ? {} : { hostInputRealpaths }),
414
+ ...(hostInputs === undefined ? {} : { hostInputs }),
393
415
  ...(volatile === undefined ? {} : { volatile }),
394
416
  };
395
417
  if (result.status === 0 && !hasErrorDiagnostics(result.diagnostics)) {
@@ -1,7 +1,8 @@
1
1
  import fs from "node:fs";
2
- import os from "node:os";
3
2
  import path from "node:path";
4
3
 
4
+ import { createCanonicalTempDirectory } from "../../internal/createCanonicalTempDirectory";
5
+
5
6
  export interface CapturedProcessOutput {
6
7
  /** Close the descriptors and remove the backing files. */
7
8
  dispose(): void;
@@ -32,7 +33,7 @@ export interface CapturedProcessOutput {
32
33
  * bytes.
33
34
  */
34
35
  export function captureProcessOutput(): CapturedProcessOutput {
35
- const directory = fs.mkdtempSync(path.join(os.tmpdir(), "ttsc-spawn-"));
36
+ const directory = createCanonicalTempDirectory("ttsc-spawn-");
36
37
  const stdoutPath = path.join(directory, "stdout");
37
38
  const stderrPath = path.join(directory, "stderr");
38
39
  const stdoutFd = fs.openSync(stdoutPath, "w+");
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
- import os from "node:os";
3
2
  import path from "node:path";
4
3
 
4
+ import { createCanonicalTempDirectory } from "../../internal/createCanonicalTempDirectory";
5
5
  import { hasProjectPluginEntries } from "../../plugin/internal/loadProjectPlugins";
6
6
  import type { ITtscCompilerContext } from "../../structures/ITtscCompilerContext";
7
7
  import type { ITtscCompilerDiagnostic } from "../../structures/ITtscCompilerDiagnostic";
@@ -11,6 +11,7 @@ import { buildNativeCompiler } from "./buildNativeCompiler";
11
11
  import { isOutsideRelativePath, packageRootDir } from "./paths";
12
12
  import { readProjectConfig } from "./project/readProjectConfig";
13
13
  import { runBuild } from "./runBuild";
14
+ import { inheritedSidecarEnv } from "./sharedHostHelpers";
14
15
  import { outputText, spawnNative } from "./spawnNative";
15
16
 
16
17
  /**
@@ -50,7 +51,7 @@ export function compileProjectInMemory(options: ITtscCompilerContext): {
50
51
  ["api-compile", "--cwd", project.root, "--tsconfig", tsconfig],
51
52
  {
52
53
  cwd: project.root,
53
- env: { ...process.env, ...options.env },
54
+ env: inheritedSidecarEnv(options.env),
54
55
  },
55
56
  );
56
57
  if (res.error) {
@@ -110,7 +111,7 @@ function compileProjectWithPlugins(
110
111
  output: Record<string, string>;
111
112
  result: TtscBuildResult;
112
113
  } {
113
- const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ttsc-api-output-"));
114
+ const tempRoot = createCanonicalTempDirectory("ttsc-api-output-");
114
115
  const tempOutDir = path.join(tempRoot, "out");
115
116
  try {
116
117
  const result = runBuild({
@@ -119,6 +120,13 @@ function compileProjectWithPlugins(
119
120
  emit: true,
120
121
  forceListEmittedFiles: true,
121
122
  outDir: tempOutDir,
123
+ // The temp directory is an `outDir` this lane injected so the emit can be
124
+ // read back as strings; the project need not declare one at all. tsgo
125
+ // answers an inferred common source directory with TS5011 as soon as any
126
+ // `outDir` is in play, so pin the root it would infer. The keys
127
+ // `outputKeyMapper` builds are unchanged by it — that root is exactly the
128
+ // layout tsgo already lays the emit out against (issue #1172).
129
+ pinInferredRootDir: true,
122
130
  quiet: true,
123
131
  resolvedProject: project,
124
132
  structuredDiagnostics: true,
@@ -20,6 +20,8 @@ import { isOutsideRelativePath } from "./paths";
20
20
  * Returns `null` when no matching output file is found on disk.
21
21
  */
22
22
  export function resolveEmittedJavaScript(options: {
23
+ /** Skip trailing-stem recovery when exact source ownership is required. */
24
+ allowStemFallback?: boolean;
23
25
  /** Pre-computed list of emitted paths; when absent `outDir` is scanned. */
24
26
  emittedFiles?: readonly string[];
25
27
  outDir: string;
@@ -45,6 +47,10 @@ export function resolveEmittedJavaScript(options: {
45
47
  }
46
48
  }
47
49
 
50
+ if (options.allowStemFallback === false) {
51
+ return null;
52
+ }
53
+
48
54
  // Score the pre-computed emit list first (cheap). When it yields nothing —
49
55
  // because the list is incomplete (a native transform host such as typia emits
50
56
  // without printing the `--listEmittedFiles` lines) or because the emit landed