ttsc 0.19.1 → 0.19.2

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 (37) hide show
  1. package/README.md +68 -186
  2. package/cmd/graphdump/main.go +21 -1
  3. package/cmd/ttsc/api_compile.go +3 -13
  4. package/cmd/ttsc/api_transform.go +13 -0
  5. package/cmd/ttscgraph/dump.go +40 -1
  6. package/cmd/ttscgraph/serve.go +194 -25
  7. package/cmd/ttscgraph/serve_error_response_still_names_its_mode_test.go +73 -0
  8. package/cmd/ttscgraph/serve_protocol_fails_the_stream_on_an_unaddressable_request_line_test.go +43 -0
  9. package/cmd/ttscgraph/serve_protocol_version_matches_the_typescript_client_test.go +48 -0
  10. package/cmd/ttscgraph/serve_snapshot_proves_its_program_without_a_second_read_test.go +109 -0
  11. package/driver/graph.go +133 -0
  12. package/internal/graph/diagnostics.go +6 -5
  13. package/internal/graph/dump.go +69 -17
  14. package/internal/graph/dump_diagnostics_ride_the_generation_that_built_it_test.go +79 -0
  15. package/internal/graph/dump_evidence_skips_leading_trivia_test.go +1 -1
  16. package/internal/graph/dump_is_a_function_of_the_source_test.go +1 -1
  17. package/internal/graph/dump_never_serializes_a_list_as_null_test.go +76 -0
  18. package/internal/graph/dump_schema_version_matches_the_typescript_client_test.go +48 -0
  19. package/internal/graph/dump_serializes_the_full_graph_test.go +21 -7
  20. package/internal/graph/node_modifiers_emit_union_strings_test.go +1 -1
  21. package/internal/graph/provenance.go +312 -0
  22. package/lib/TtscCompiler.js +9 -4
  23. package/lib/TtscCompiler.js.map +1 -1
  24. package/lib/compiler/internal/transformProjectInMemory.d.ts +4 -0
  25. package/lib/compiler/internal/transformProjectInMemory.js +67 -12
  26. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  27. package/lib/structures/ITtscCompilerTransformation.d.ts +98 -0
  28. package/package.json +8 -8
  29. package/shim/compiler/incremental.go +58 -0
  30. package/shim/core/shim.go +5 -0
  31. package/src/TtscCompiler.ts +19 -4
  32. package/src/compiler/internal/transformProjectInMemory.ts +106 -12
  33. package/src/structures/ITtscCompilerTransformation.ts +107 -0
  34. package/tools/shim_audit/baseline.json +2 -1
  35. package/tools/shim_audit/main.go +17 -0
  36. package/utility/host.go +17 -15
  37. package/cmd/ttscgraph/serve_protocol_rejects_invalid_request_line_test.go +0 -43
package/README.md CHANGED
@@ -9,135 +9,104 @@ A `typescript-go` toolchain for compiler-powered plugins and type-safe execution
9
9
  - **`ttsc`**: build, check, and transform.
10
10
  - **`ttsx`**: execute TypeScript with type checking.
11
11
  - [**`@ttsc/lint`**](https://github.com/samchon/ttsc/tree/master/packages/lint): lint violations as compiler errors.
12
- - [**`@ttsc/graph`**](https://github.com/samchon/ttsc/tree/master/packages/graph): MCP code graph that reduce token usage.
12
+ - [**`@ttsc/graph`**](https://github.com/samchon/ttsc/tree/master/packages/graph): MCP code graph that reduces agent token usage.
13
13
  - **plugin support**: compiler-powered libraries, such as `typia`.
14
14
 
15
15
  ## Setup
16
16
 
17
- ### Install
18
-
19
- Install `ttsc`, `@ttsc/lint`, and the native TypeScript compiler:
17
+ `ttsc` is a drop-in replacement for `tsc`. It reads the same `tsconfig.json`, takes the same flags, and emits the same JavaScript, then runs your plugins in the pass that type-checks the project.
20
18
 
21
19
  ```bash
22
- npm install -D ttsc @ttsc/lint typescript
20
+ npm install -D ttsc typescript
23
21
  ```
24
22
 
25
- ### Commands
26
-
27
- Run TypeScript directly with `ttsx` (CLI command):
28
-
29
23
  ```bash
30
- npx ttsx src/index.ts
24
+ npx ttsx src/index.ts # run a file, type-checked first
25
+ npx ttsc # build
26
+ npx ttsc --noEmit # check only
27
+ npx ttsc --watch # rebuild on save
31
28
  ```
32
29
 
33
- Build, check, or watch the project with `ttsc`:
30
+ `ttsx` runs a file the way `tsx` or `ts-node` does, but it type-checks the whole project first, so a type error stops the run before anything executes.
34
31
 
35
- ```bash
36
- npx ttsc
37
- npx ttsc --noEmit
38
- npx ttsc --watch
39
- ```
32
+ That covers the CLI. The integrations each have a short guide:
40
33
 
41
- Rewrite source files in place with the `@ttsc/lint` format rules:
34
+ - [`@ttsc/unplugin`](https://ttsc.dev/docs/setup/unplugin): Vite, Rollup, Rolldown, esbuild, webpack, Rspack, Next.js, Turbopack, Farm, and Bun.
35
+ - [`@ttsc/metro`](https://ttsc.dev/docs/setup/metro): React Native and Expo.
36
+ - [`@ttsc/vscode`](https://ttsc.dev/docs/setup/vscode): live editor diagnostics.
42
37
 
43
- ```bash
44
- npx ttsc format
45
- ```
38
+ ## Lint
46
39
 
47
- ### Bundlers
40
+ Lint and format at compiler speed: up to 800x faster than ESLint.
48
41
 
49
- Use `@ttsc/unplugin` when a bundler owns your build.
42
+ `@ttsc/lint` replaces ESLint and Prettier with rules that run inside the type-check. It shares one AST pass with the compiler, so linting and formatting add almost nothing to the build you already run.
50
43
 
51
- It runs `ttsc` plugins inside supported bundlers.
44
+ Configuration is a single file. Each rule takes `"error"`, `"warning"`, or `"off"`, and the `format` block mirrors `.prettierrc`.
52
45
 
53
46
  ```bash
54
- npm install -D ttsc @ttsc/lint typescript
55
- npm install -D @ttsc/unplugin
47
+ npm install -D @ttsc/lint
56
48
  ```
57
49
 
58
- Minimal Vite setup:
59
-
60
50
  ```ts
61
- // vite.config.ts
62
- import ttsc from "@ttsc/unplugin/vite";
63
- import { defineConfig } from "vite";
64
-
65
- export default defineConfig({
66
- plugins: [ttsc()],
67
- });
68
- ```
69
-
70
- Supported bundlers:
71
-
72
- - Vite
73
- - Rollup
74
- - Rolldown
75
- - esbuild
76
- - Webpack
77
- - Rspack
78
- - Next.js
79
- - Turbopack
80
- - Farm
81
- - Bun
82
-
83
- See [`@ttsc/unplugin`](https://github.com/samchon/ttsc/tree/master/packages/unplugin) for full setup and adapter options.
51
+ // lint.config.ts
52
+ import type { ITtscLintConfig } from "@ttsc/lint";
84
53
 
85
- ### React Native / Expo
86
-
87
- React Native and Expo bundle with Metro, so the `ttsc` CLI and `@ttsc/unplugin` never run. `@ttsc/metro` is a Metro transformer that runs `ttsc` plugins on each TypeScript file, then hands the result to your existing Expo or React-Native Babel transformer.
88
-
89
- ```bash
90
- npm install -D ttsc @ttsc/lint typescript
91
- npm install -D @ttsc/metro
54
+ export default {
55
+ rules: {
56
+ "no-var": "error",
57
+ "prefer-const": "error",
58
+ "typescript/no-explicit-any": "warning",
59
+ },
60
+ format: {
61
+ printWidth: 100,
62
+ singleQuote: true,
63
+ trailingComma: "all",
64
+ },
65
+ } satisfies ITtscLintConfig;
92
66
  ```
93
67
 
94
- Wrap your Metro config (CommonJS, the standard for `metro.config.js`):
68
+ Violations surface as compiler diagnostics, in the same stream as type errors, so the CI step that already runs `ttsc --noEmit` gates lint without a second job:
95
69
 
96
- ```js
97
- // metro.config.js (Expo)
98
- const { getDefaultConfig } = require("expo/metro-config");
99
- const { withTtsc } = require("@ttsc/metro");
100
-
101
- module.exports = withTtsc(getDefaultConfig(__dirname));
70
+ ```ts
71
+ // src/index.ts
72
+ var count = 3;
73
+ let total = count;
102
74
  ```
103
75
 
104
- For bare React Native, wrap `getDefaultConfig` from `@react-native/metro-config` instead. See [`@ttsc/metro`](https://github.com/samchon/ttsc/tree/master/packages/metro) for options and the v1 caveats.
105
-
106
- ### VS Code Extension
76
+ ```text
77
+ $ npx ttsc --noEmit
78
+ src/index.ts:2:5 - error TS17397: [prefer-const] Use const instead of let.
107
79
 
108
- Install the VS Code extension for live TypeScript-Go editor features plus saved-state ttsc plugin diagnostics and actions.
80
+ 2 let total = count;
81
+ ~~~~~~~~~~~~~
109
82
 
110
- Install it from the VS Code Marketplace by searching `ttsc`, or run:
83
+ src/index.ts:1:1 - error TS11966: [no-var] Unexpected var, use let or const instead.
111
84
 
112
- ```bash
113
- npx @ttsc/vscode
85
+ 1 var count = 3;
86
+ ~~~~~~~~~~~~~~
114
87
  ```
115
88
 
116
- Then turn on format-on-save in `.vscode/settings.json`:
89
+ Clean the project in place:
117
90
 
118
- ```jsonc
119
- "[typescript][typescriptreact]": {
120
- "editor.defaultFormatter": "samchon.ttsc",
121
- "editor.formatOnSave": true
122
- }
91
+ ```bash
92
+ npx ttsc fix # lint autofixes + format edits
93
+ npx ttsc format # format edits only, never changes behavior
123
94
  ```
124
95
 
125
- Lint fixes stay off-save by default; opt in with `"editor.codeActionsOnSave": { "source.fixAll.ttsc": "explicit" }`.
96
+ The rule catalog and every `format` key are in the [Lint & Format guide](https://ttsc.dev/docs/lint).
126
97
 
127
- See [`@ttsc/vscode`](https://github.com/samchon/ttsc/tree/master/packages/vscode) for requirements and settings.
98
+ ## Graph
128
99
 
129
- ### Coding Agents (MCP)
100
+ Your coding agent answers from the compiler, spending roughly 90% fewer tokens.
130
101
 
131
- `@ttsc/graph` gives a coding agent a checker-resolved graph of your project, over MCP.
102
+ `@ttsc/graph` is an MCP server that gives the agent a compiler-resolved graph of your project: what calls what, what a change would touch, and where to start reading. The agent asks the type checker instead of grepping and re-reading files.
132
103
 
133
- It answers what relates to a symbol and what a change affects, straight from the type checker, so the agent stops grepping and re-reading files.
104
+ Register it with your MCP client. For Claude Code, a `.mcp.json` in the project root:
134
105
 
135
106
  ```bash
136
107
  npm install -D ttsc @ttsc/graph typescript
137
108
  ```
138
109
 
139
- Point your agent's MCP client at it. For Claude Code:
140
-
141
110
  ```json
142
111
  {
143
112
  "mcpServers": {
@@ -149,119 +118,32 @@ Point your agent's MCP client at it. For Claude Code:
149
118
  }
150
119
  ```
151
120
 
152
- On codegraph's own agent-cost benchmark, Claude agents answer reading zero files, cutting tokens by 77% to 86% and tool calls by 94% to 95%. See [`@ttsc/graph`](https://github.com/samchon/ttsc/tree/master/packages/graph) and the [benchmark](https://ttsc.dev/docs/benchmark/graph).
121
+ On the agent-cost benchmark, Claude agents answer reading zero files, cutting tokens by roughly 90% and tool calls by 93% to 96%. The design and per-repository numbers are in the [Code Graph guide](https://ttsc.dev/docs/graph) and the [benchmark](https://ttsc.dev/docs/benchmark/graph).
153
122
 
154
- Your agent picks the tools up from the MCP handshake and uses them on its own. See [Setup](https://ttsc.dev/docs/setup#coding-agents-ttscgraph) for the full walk-through.
123
+ ![Median tokens on the shared onboarding question, lower is better](https://ttsc.dev/benchmark/svg/graph-common-codex-gpt-5.6-sol.svg)
155
124
 
156
125
  ## Plugins
157
126
 
158
- Plugins let libraries add compile-time checks, transforms, and type-driven code generation to normal `ttsc` and `ttsx` runs.
127
+ A plugin hooks the compile to add checks, transforms, or type-driven code generation, all driven by the types the checker has already resolved. It runs on every `ttsc` build and `ttsx` run, with no extra step.
159
128
 
160
- ```bash
161
- # compile
162
- npx ttsc
163
-
164
- # execute
165
- npx ttsx src/index.ts
166
- ```
167
-
168
- ### Transform Example
169
-
170
- A transform uses TypeScript types to generate JavaScript before runtime.
129
+ [typia](https://typia.io) is the canonical one. Ask it for a validator of any type, and the transform writes the implementation at build time:
171
130
 
172
131
  ```ts
173
- import typia, { tags } from "typia";
174
- import { v4 } from "uuid";
175
-
176
- const matched: boolean = typia.is<IMember>({
177
- id: v4(),
178
- email: "samchon.github@gmail.com",
179
- age: 30,
180
- });
181
- console.log(matched); // true
182
-
183
- interface IMember {
184
- id: string & tags.Format<"uuid">;
185
- email: string & tags.Format<"email">;
186
- age: number &
187
- tags.Type<"uint32"> &
188
- tags.ExclusiveMinimum<19> &
189
- tags.Maximum<100>;
190
- }
191
- ```
192
-
193
- The transform replaces `typia.is<IMember>()` with dedicated JavaScript checks at build time:
194
-
195
- ```js
196
132
  import typia from "typia";
197
- import * as __typia_transform__isFormatEmail from "typia/lib/internal/_isFormatEmail";
198
- import * as __typia_transform__isFormatUuid from "typia/lib/internal/_isFormatUuid";
199
- import * as __typia_transform__isTypeUint32 from "typia/lib/internal/_isTypeUint32";
200
- import { v4 } from "uuid";
201
-
202
- const matched = (() => {
203
- const _io0 = (input) =>
204
- "string" === typeof input.id &&
205
- __typia_transform__isFormatUuid._isFormatUuid(input.id) &&
206
- "string" === typeof input.email &&
207
- __typia_transform__isFormatEmail._isFormatEmail(input.email) &&
208
- "number" === typeof input.age &&
209
- __typia_transform__isTypeUint32._isTypeUint32(input.age) &&
210
- 19 < input.age &&
211
- input.age <= 100;
212
- return (input) => "object" === typeof input && null !== input && _io0(input);
213
- })()({
214
- id: v4(),
215
- email: "samchon.github@gmail.com",
216
- age: 30,
217
- });
218
- console.log(matched); // true
219
- ```
220
-
221
- ## Programmatic API
222
-
223
- Embed `ttsc` from another Node tool with the `TtscCompiler` class:
224
-
225
- ```ts
226
- import { TtscCompiler } from "ttsc";
227
133
 
228
- const compiler = new TtscCompiler({ cwd: "./project" });
229
- const result = compiler.compile();
230
-
231
- if (result.type === "success") {
232
- for (const [path, text] of Object.entries(result.output)) {
233
- // path is project-relative ("dist/index.js", "dist/index.d.ts", ...)
234
- console.log(path, text.length);
235
- }
236
- } else if (result.type === "failure") {
237
- for (const d of result.diagnostics) {
238
- console.error(`${d.file}:${d.line}:${d.character} ${d.messageText}`);
239
- }
240
- }
134
+ export const isStringArray = typia.createIs<string[]>();
241
135
  ```
242
136
 
243
- When one process transforms a project's files repeatedly (a watch server, an editor, a codegen tool), `TtscService` keeps the compiler host warm instead of recompiling per call. It compiles the project once, then answers per-file transform requests, and reflects in-memory edits through `updateFile`:
137
+ No schema, no decorator. The call compiles to a plain function:
244
138
 
245
- ```ts
246
- import { TtscService } from "ttsc";
247
-
248
- const service = new TtscService({ cwd: "./project" });
249
- try {
250
- const code = await service.transformFile("src/index.ts");
251
- await service.updateFile("src/index.ts", "export const x: number = 2;\n");
252
- const next = await service.transformFile("src/index.ts");
253
- } finally {
254
- service.dispose();
255
- }
139
+ ```js
140
+ export const isStringArray = (() => {
141
+ return (input) =>
142
+ Array.isArray(input) && input.every((elem) => "string" === typeof elem);
143
+ })();
256
144
  ```
257
145
 
258
- `TtscService` runs through the linked-plugin host, so the project must declare at least one transform-stage plugin.
259
-
260
- See the [Programmatic API guide](https://ttsc.dev/docs/ttsc/api) for the full lifecycle, plugin overrides, and patterns. For browser embedding, see [`@ttsc/wasm`](https://github.com/samchon/ttsc/tree/master/packages/wasm) and the higher-level [`@ttsc/playground`](https://github.com/samchon/ttsc/tree/master/packages/playground) package.
261
-
262
- ### List of Plugins
263
-
264
- `ttsc` ships a few small utility plugins in this repository.
146
+ Utility plugins shipped in this repository:
265
147
 
266
148
  - [`@ttsc/banner`](https://github.com/samchon/ttsc/tree/master/packages/banner): adds `@packageDocumentation` JSDoc banners.
267
149
  - [`@ttsc/lint`](https://github.com/samchon/ttsc/tree/master/packages/lint): lints and formats TypeScript source.
@@ -271,13 +153,13 @@ See the [Programmatic API guide](https://ttsc.dev/docs/ttsc/api) for the full li
271
153
  - [`@ttsc/unplugin`](https://github.com/samchon/ttsc/tree/master/packages/unplugin): runs `ttsc` plugins inside bundlers supported by `unplugin`.
272
154
  - [`@ttsc/metro`](https://github.com/samchon/ttsc/tree/master/packages/metro): runs `ttsc` plugins inside Metro for React Native and Expo.
273
155
 
274
- Plugin authors should start from the [`Guide Documents`](https://ttsc.dev/docs).
275
-
276
- Ecosystem plugins are listed below; PRs adding `ttsc` plugins are welcome.
156
+ Ecosystem plugins; PRs adding yours are welcome:
277
157
 
278
158
  - [`nestia`](https://github.com/samchon/nestia): generates NestJS routes, OpenAPI, and SDKs.
279
159
  - [`typia`](https://github.com/samchon/typia): generates validators, serializers, and type-driven runtime code.
280
160
 
161
+ To write your own, start from [Plugin Development](https://ttsc.dev/docs/development).
162
+
281
163
  ## Sponsors
282
164
 
283
165
  [![Sponsors](https://raw.githubusercontent.com/samchon/sponsor-images/refs/heads/master/public/circle.svg)](https://github.com/sponsors/samchon)
@@ -50,7 +50,27 @@ func run() int {
50
50
 
51
51
  g := graph.Build(prog)
52
52
  ignored := graph.GitIgnoredFiles(root, g)
53
- data, err := graph.MarshalDump(g, root, *tsconfig, ignored, graph.SourceTexts(prog), *pretty)
53
+ texts := graph.SourceTexts(prog)
54
+ // The viewer pipeline reduces nodes and edges and never asks whether the build
55
+ // universe moved or whether the disk still matches, so this tool pays for
56
+ // neither. It names its producer and digests what the checker read, and it
57
+ // declares exactly that and no more: a reader learns the universe and the disk
58
+ // digests are absent, rather than reading an empty universe as "nothing
59
+ // changed" or an empty diskDigest as "the file could not be read". The shipped
60
+ // `ttscgraph dump` is the one that proves the whole contract.
61
+ data, err := graph.MarshalDump(g, root, *tsconfig, ignored, texts, graph.DumpOrigin{
62
+ Provenance: graph.NewProvenance(
63
+ root,
64
+ // No version: this tool is built from the tree on demand and never
65
+ // stamped, and an invented one would be worse than an absent one.
66
+ graph.Producer{Tool: "graphdump", Typescript: graph.TypescriptVersion()},
67
+ []string{graph.CapabilitySourceDigests},
68
+ nil,
69
+ nil,
70
+ texts,
71
+ nil,
72
+ ),
73
+ }, *pretty)
54
74
  if err != nil {
55
75
  fmt.Fprintf(os.Stderr, "graphdump: %v\n", err)
56
76
  return 1
@@ -10,8 +10,6 @@ import (
10
10
  "encoding/json"
11
11
  "flag"
12
12
  "fmt"
13
- "path/filepath"
14
- "strings"
15
13
 
16
14
  shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
17
15
 
@@ -146,16 +144,8 @@ func toAPICompileDiagnostic(diag driver.Diagnostic) apiCompileDiagnostic {
146
144
  // generated file. Files inside cwd use a slash-separated relative path (the
147
145
  // API contract). Files outside cwd — rare, e.g. a monorepo output rooted
148
146
  // above the project — are returned as an absolute slash path instead.
147
+ // Delegates to the driver so every envelope section (typescript, graph)
148
+ // shares one key implementation and consumers can join sections by key.
149
149
  func apiOutputKey(cwd, fileName string) string {
150
- if rel, err := filepath.Rel(cwd, fileName); err == nil && !isOutsideRelativePath(rel) {
151
- return filepath.ToSlash(rel)
152
- }
153
- return filepath.ToSlash(fileName)
154
- }
155
-
156
- // isOutsideRelativePath reports whether the result of filepath.Rel points
157
- // above the base directory (starts with ".."), meaning the file lives outside
158
- // the project root and cannot safely be expressed as a project-relative path.
159
- func isOutsideRelativePath(rel string) bool {
160
- return rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator))
150
+ return driver.TransformOutputKey(cwd, fileName)
161
151
  }
@@ -20,6 +20,12 @@ type apiTransformResult struct {
20
20
  // Program facade, keyed the same way api-compile keys emitted files.
21
21
  Diagnostics []apiCompileDiagnostic `json:"diagnostics,omitempty"`
22
22
  TypeScript map[string]string `json:"typescript"`
23
+ // Graph is the host-owned reference graph of the loaded program (direct
24
+ // resolved reference edges, global-scope files, tsconfig extends chain),
25
+ // keyed like TypeScript. Consumers use it to register every file whose
26
+ // content can influence a transformed module, so bundler caches invalidate
27
+ // soundly without per-plugin reporting.
28
+ Graph *driver.TransformGraph `json:"graph,omitempty"`
23
29
  }
24
30
 
25
31
  // runAPITransform implements the `api-transform` sub-command. It loads the
@@ -63,8 +69,14 @@ func runAPITransform(args []string) int {
63
69
  return 2
64
70
  }
65
71
  typescript := map[string]string{}
72
+ var graph *driver.TransformGraph
66
73
  if prog != nil {
67
74
  defer prog.Close()
75
+ // Compute the reference graph before SourceFiles() runs linked plugin
76
+ // hooks: those mutate parsed ASTs in place, and the graph must describe
77
+ // the original source's resolved references — the transform's inputs —
78
+ // not the mutated output.
79
+ graph = driver.NewTransformGraph(prog, cwd)
68
80
  for _, file := range prog.SourceFiles() {
69
81
  typescript[apiOutputKey(cwd, file.FileName())] = file.Text()
70
82
  }
@@ -74,6 +86,7 @@ func runAPITransform(args []string) int {
74
86
  result := apiTransformResult{
75
87
  Diagnostics: make([]apiCompileDiagnostic, 0, len(diags)),
76
88
  TypeScript: typescript,
89
+ Graph: graph,
77
90
  }
78
91
  for _, diag := range diags {
79
92
  result.Diagnostics = append(result.Diagnostics, toAPICompileDiagnostic(diag))
@@ -59,6 +59,12 @@ func runDump(args []string) int {
59
59
 
60
60
  g := graph.Build(prog)
61
61
  ignored := graph.GitIgnoredFiles(cwd, g)
62
+ texts := graph.SourceTexts(prog)
63
+ origin, err := dumpOrigin(prog, cwd, texts)
64
+ if err != nil {
65
+ fmt.Fprintf(stderr, "ttscgraph: %v\n", err)
66
+ return 1
67
+ }
62
68
  // Stream the document out instead of marshalling it into one byte slice and
63
69
  // then copying that slice into a string: on VS Code the dump is 323 MB, so
64
70
  // the string conversion alone was a second full copy of it, for nothing. The
@@ -69,7 +75,8 @@ func runDump(args []string) int {
69
75
  cwd,
70
76
  tsconfig,
71
77
  ignored,
72
- graph.SourceTexts(prog),
78
+ texts,
79
+ origin,
73
80
  *prettyFlag,
74
81
  ); err != nil {
75
82
  fmt.Fprintf(stderr, "ttscgraph: %v\n", err)
@@ -77,3 +84,35 @@ func runDump(args []string) int {
77
84
  }
78
85
  return 0
79
86
  }
87
+
88
+ // dumpOrigin assembles the one-shot command's snapshot evidence. A dump written
89
+ // to a file outlives the process that made it and is read by tooling that never
90
+ // saw the project, so it carries the same provenance a served snapshot does:
91
+ // without it the file is a pile of facts with no way to tell which program, or
92
+ // which day, they describe.
93
+ func dumpOrigin(prog *driver.Program, cwd string, texts map[string]string) (graph.DumpOrigin, error) {
94
+ configs, err := parsedConfigs(prog)
95
+ if err != nil {
96
+ return graph.DumpOrigin{}, err
97
+ }
98
+ configHashes, err := hashFiles(configFiles(configs))
99
+ if err != nil {
100
+ return graph.DumpOrigin{}, err
101
+ }
102
+ _, diskDigests, err := hashProgramSources(prog)
103
+ if err != nil {
104
+ return graph.DumpOrigin{}, err
105
+ }
106
+ return graph.DumpOrigin{
107
+ Provenance: graph.NewProvenance(
108
+ cwd,
109
+ serveProducer(),
110
+ fullSnapshotCapabilities,
111
+ fileDigests(configHashes),
112
+ rootFileEntries(projectRootFilesFromConfigs(configs, false)),
113
+ texts,
114
+ diskDigests,
115
+ ),
116
+ Diagnostics: graph.NewDiagnostics(prog, cwd),
117
+ }, nil
118
+ }