ttsc 0.23.0 → 0.24.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.
- package/cmd/ttscgraph/serve.go +264 -48
- package/cmd/ttscgraph/serve_early_error_encode_failure_is_observable_test.go +40 -0
- package/cmd/ttscgraph/serve_graph_snapshot_version_matches_typescript_client_test.go +32 -0
- package/cmd/ttscgraph/serve_phase_trace_accounts_for_load_failure_test.go +45 -0
- package/cmd/ttscgraph/serve_phase_trace_is_opt_in_and_payload_free_test.go +52 -0
- package/cmd/ttscgraph/serve_protocol_negotiates_incremental_shards_test.go +44 -0
- package/cmd/ttscgraph/serve_session_reloads_commonjs_package_entry_for_esm_test.go +2 -2
- package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +46 -20
- package/cmd/ttscgraph/serve_session_retries_failed_dump_test.go +9 -9
- package/cmd/ttscgraph/serve_shards.go +1390 -0
- package/cmd/ttscgraph/serve_shards_abandoned_generation_converges_on_retry_test.go +92 -0
- package/cmd/ttscgraph/serve_shards_delete_removed_source_test.go +57 -0
- package/cmd/ttscgraph/serve_shards_external_endpoints_test.go +53 -0
- package/cmd/ttscgraph/serve_shards_keep_source_distributed_dependencies_at_the_boundary_test.go +108 -0
- package/cmd/ttscgraph/serve_shards_match_full_dump_oracle_test.go +88 -0
- package/cmd/ttscgraph/serve_shards_merge_pending_and_new_edits_test.go +50 -0
- package/cmd/ttscgraph/serve_shards_prune_unreferenced_external_endpoints_test.go +65 -0
- package/cmd/ttscgraph/serve_shards_publish_created_source_test.go +92 -0
- package/cmd/ttscgraph/serve_shards_rebuild_declaration_edit_test.go +43 -0
- package/cmd/ttscgraph/serve_shards_refresh_cross_file_implementation_test.go +63 -0
- package/cmd/ttscgraph/serve_shards_refresh_dependent_diagnostics_test.go +80 -0
- package/cmd/ttscgraph/serve_shards_rename_moves_source_identity_test.go +95 -0
- package/cmd/ttscgraph/serve_shards_retry_preserves_committed_generation_test.go +48 -0
- package/cmd/ttscgraph/serve_shards_reuse_unaffected_sources_test.go +121 -0
- package/driver/declaration_shape.go +85 -0
- package/driver/program.go +39 -10
- package/internal/graph/build.go +62 -10
- package/internal/graph/build_files_reuses_committed_cross_file_endpoints_test.go +87 -0
- package/internal/graph/build_keeps_source_distributed_dependencies_at_the_external_boundary_test.go +74 -0
- package/internal/graph/dump.go +84 -43
- package/internal/graph/dump_path_mapper_caches_alias_resolution_test.go +29 -0
- package/internal/graph/dump_paths.go +134 -11
- package/internal/graph/dump_paths_canonicalize_windows_short_root_test.go +39 -0
- package/internal/graph/dump_rejects_provenance_only_path_errors_test.go +32 -0
- package/internal/graph/dump_serializes_the_full_graph_test.go +2 -2
- package/internal/graph/dump_symlink_project_publishes_matching_base_test.go +82 -0
- package/internal/graph/edges.go +58 -16
- package/internal/graph/exports.go +9 -2
- package/internal/graph/graph.go +47 -7
- package/internal/graph/graph_kind_contracts_match_their_producers_test.go +121 -0
- package/internal/graph/node_modifiers_emit_union_strings_test.go +1 -1
- package/internal/graph/provenance.go +11 -1
- package/internal/graph/resolve.go +1 -4
- package/internal/graph/source.go +23 -0
- package/internal/graph/wire_node_ids_share_alias_resolution_test.go +29 -0
- package/package.json +8 -8
|
@@ -0,0 +1,1390 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"crypto/sha256"
|
|
5
|
+
"encoding/hex"
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"fmt"
|
|
8
|
+
"maps"
|
|
9
|
+
"path/filepath"
|
|
10
|
+
"sort"
|
|
11
|
+
"strings"
|
|
12
|
+
"time"
|
|
13
|
+
|
|
14
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
15
|
+
shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
|
|
16
|
+
shimtspath "github.com/microsoft/typescript-go/shim/tspath"
|
|
17
|
+
|
|
18
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
19
|
+
"github.com/samchon/ttsc/packages/ttsc/internal/graph"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
const graphSnapshotProtocolVersion = 1
|
|
23
|
+
|
|
24
|
+
// serveGraphSnapshot is one atomic native shard transaction. Manifest is the
|
|
25
|
+
// complete committed generation; Upserts and Deletes are the delta from
|
|
26
|
+
// BaseGeneration. The consumer validates and normalizes it into its own Graph
|
|
27
|
+
// Snapshot Protocol before publication.
|
|
28
|
+
type serveGraphSnapshot struct {
|
|
29
|
+
ProtocolVersion int `json:"protocolVersion"`
|
|
30
|
+
SchemaVersion int `json:"schemaVersion"`
|
|
31
|
+
Project string `json:"project"`
|
|
32
|
+
Tsconfig string `json:"tsconfig"`
|
|
33
|
+
Producer graph.Producer `json:"producer"`
|
|
34
|
+
Capabilities []string `json:"capabilities"`
|
|
35
|
+
Universe graph.Universe `json:"universe"`
|
|
36
|
+
Sequence int `json:"sequence"`
|
|
37
|
+
Generation string `json:"generation"`
|
|
38
|
+
BaseSequence int `json:"baseSequence,omitempty"`
|
|
39
|
+
BaseGeneration string `json:"baseGeneration,omitempty"`
|
|
40
|
+
Upserts []serveGraphShardUpsert `json:"upserts"`
|
|
41
|
+
Deletes []string `json:"deletes"`
|
|
42
|
+
Manifest []serveGraphShardReference `json:"manifest"`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type serveGraphShardUpsert struct {
|
|
46
|
+
Digest string `json:"digest"`
|
|
47
|
+
Shard serveGraphShard `json:"shard"`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type serveGraphShardReference struct {
|
|
51
|
+
Key string `json:"key"`
|
|
52
|
+
Digest string `json:"digest"`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// serveGraphShard owns one compiler source's outgoing facts or one config
|
|
56
|
+
// input. Source and Config are both absent only on the external-node and
|
|
57
|
+
// fileless-diagnostic metadata shards.
|
|
58
|
+
type serveGraphShard struct {
|
|
59
|
+
Key string `json:"key"`
|
|
60
|
+
Source *graph.SourceDigest `json:"source,omitempty"`
|
|
61
|
+
Config *graph.FileDigest `json:"config,omitempty"`
|
|
62
|
+
Nodes []graph.DumpNode `json:"nodes"`
|
|
63
|
+
Edges []graph.DumpEdge `json:"edges"`
|
|
64
|
+
Diagnostics []graph.Diagnostic `json:"diagnostics"`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type committedServeGraphShard struct {
|
|
68
|
+
digest string
|
|
69
|
+
shard serveGraphShard
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type serveGraphStore struct {
|
|
73
|
+
sequence int
|
|
74
|
+
generation string
|
|
75
|
+
shards map[string]committedServeGraphShard
|
|
76
|
+
nodes map[string]*graph.Node
|
|
77
|
+
provenance graph.Provenance
|
|
78
|
+
wireProvenance graph.Provenance
|
|
79
|
+
wireSources map[string]string
|
|
80
|
+
identity serveGraphIdentity
|
|
81
|
+
resolutionDigests map[string]string
|
|
82
|
+
sourceKeys map[string]string
|
|
83
|
+
dumpNodeFiles map[string]string
|
|
84
|
+
reverseDependencies map[string][]string
|
|
85
|
+
sourceExternal map[string]map[string]bool
|
|
86
|
+
externalNodes map[string]graph.DumpNode
|
|
87
|
+
externalReferences map[string]int
|
|
88
|
+
externalNodeWireIDs map[string]string
|
|
89
|
+
implementationSources map[string]map[string]bool
|
|
90
|
+
nodeOwners map[string]string
|
|
91
|
+
incomingEdges map[string]map[string]int
|
|
92
|
+
// extractedFiles records the exact authored closure read for this generation.
|
|
93
|
+
// It supports phase/closure evidence without retaining an older generation.
|
|
94
|
+
extractedFiles []string
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
type serveGraphIdentity struct {
|
|
98
|
+
project string
|
|
99
|
+
target string
|
|
100
|
+
universe string
|
|
101
|
+
producer graph.Producer
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
func (s *graphSession) SnapshotShards() (*serveGraphSnapshot, string, bool, error) {
|
|
105
|
+
snapshot, mode, changed, _, _, err := s.snapshotShardsWithTiming()
|
|
106
|
+
return snapshot, mode, changed, err
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
func (s *graphSession) snapshotShardsWithTiming() (*serveGraphSnapshot, string, bool, time.Duration, time.Duration, error) {
|
|
110
|
+
semanticStarted := time.Now()
|
|
111
|
+
change, err := s.nextChange(true)
|
|
112
|
+
semanticDuration := time.Since(semanticStarted)
|
|
113
|
+
if err != nil {
|
|
114
|
+
return nil, "", false, semanticDuration, 0, err
|
|
115
|
+
}
|
|
116
|
+
if change == nil {
|
|
117
|
+
return nil, serveModeUnchanged, false, semanticDuration, 0, nil
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
exportStarted := time.Now()
|
|
121
|
+
snapshot, store, err := s.buildShardSnapshot(change)
|
|
122
|
+
exportDuration := time.Since(exportStarted)
|
|
123
|
+
if err != nil {
|
|
124
|
+
s.pending = change
|
|
125
|
+
return nil, "", false, semanticDuration, exportDuration, err
|
|
126
|
+
}
|
|
127
|
+
s.graphStore = store
|
|
128
|
+
s.pending = nil
|
|
129
|
+
return snapshot, change.mode, true, semanticDuration, exportDuration, nil
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
func (s *graphSession) buildShardSnapshot(change *graphChange) (*serveGraphSnapshot, *serveGraphStore, error) {
|
|
133
|
+
if change.full || s.graphStore == nil {
|
|
134
|
+
return s.buildFullShardSnapshot()
|
|
135
|
+
}
|
|
136
|
+
return s.buildIncrementalShardSnapshot(change)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
func (s *graphSession) buildFullShardSnapshot() (*serveGraphSnapshot, *serveGraphStore, error) {
|
|
140
|
+
program := s.compiler.Program()
|
|
141
|
+
built := graph.Build(program)
|
|
142
|
+
texts := graph.SourceTexts(program)
|
|
143
|
+
provenance := graph.NewProvenance(
|
|
144
|
+
serveProducer(),
|
|
145
|
+
fullSnapshotCapabilities,
|
|
146
|
+
s.configDigests,
|
|
147
|
+
s.roots,
|
|
148
|
+
texts,
|
|
149
|
+
s.diskDigests,
|
|
150
|
+
)
|
|
151
|
+
identity, wireProvenance, wireSources, err := newServeGraphIdentity(s.cwd, s.tsconfig, provenance)
|
|
152
|
+
if err != nil {
|
|
153
|
+
return nil, nil, err
|
|
154
|
+
}
|
|
155
|
+
facts, err := graph.NewDumpFacts(
|
|
156
|
+
built,
|
|
157
|
+
s.cwd,
|
|
158
|
+
graph.GitIgnoredFiles(s.cwd, built),
|
|
159
|
+
texts,
|
|
160
|
+
)
|
|
161
|
+
if err != nil {
|
|
162
|
+
return nil, nil, err
|
|
163
|
+
}
|
|
164
|
+
resolutionDigests, err := serveGraphResolutionDigests(program, s.cwd)
|
|
165
|
+
if err != nil {
|
|
166
|
+
return nil, nil, err
|
|
167
|
+
}
|
|
168
|
+
shards, sourceKeys, dumpNodeFiles, externalNodes, sourceExternal, err := partitionServeGraphFacts(
|
|
169
|
+
identity,
|
|
170
|
+
provenance,
|
|
171
|
+
facts,
|
|
172
|
+
graph.NewDiagnostics(program),
|
|
173
|
+
nil,
|
|
174
|
+
nil,
|
|
175
|
+
resolutionDigests,
|
|
176
|
+
)
|
|
177
|
+
if err != nil {
|
|
178
|
+
return nil, nil, err
|
|
179
|
+
}
|
|
180
|
+
externalReferences := map[string]int{}
|
|
181
|
+
for _, targets := range sourceExternal {
|
|
182
|
+
for id := range targets {
|
|
183
|
+
externalReferences[id]++
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if err := installExternalShard(shards, identity, externalNodes, externalReferences); err != nil {
|
|
187
|
+
return nil, nil, err
|
|
188
|
+
}
|
|
189
|
+
externalNodeWireIDs, err := serveGraphExternalNodeWireIDs(s.cwd, built.Nodes)
|
|
190
|
+
if err != nil {
|
|
191
|
+
return nil, nil, err
|
|
192
|
+
}
|
|
193
|
+
store := &serveGraphStore{
|
|
194
|
+
shards: map[string]committedServeGraphShard{},
|
|
195
|
+
nodes: maps.Clone(built.Nodes),
|
|
196
|
+
provenance: provenance,
|
|
197
|
+
wireProvenance: wireProvenance,
|
|
198
|
+
wireSources: wireSources,
|
|
199
|
+
identity: identity,
|
|
200
|
+
resolutionDigests: resolutionDigests,
|
|
201
|
+
sourceKeys: sourceKeys,
|
|
202
|
+
dumpNodeFiles: dumpNodeFiles,
|
|
203
|
+
reverseDependencies: reverseGraphDependencies(program),
|
|
204
|
+
sourceExternal: sourceExternal,
|
|
205
|
+
externalNodes: externalNodes,
|
|
206
|
+
externalReferences: externalReferences,
|
|
207
|
+
externalNodeWireIDs: externalNodeWireIDs,
|
|
208
|
+
implementationSources: cloneServeGraphStringSets(built.ImplementationSources),
|
|
209
|
+
extractedFiles: sortedSelectedFiles(authoredGraphFiles(program)),
|
|
210
|
+
}
|
|
211
|
+
snapshot, committed, nodeOwners, incomingEdges, err := commitServeGraphSnapshot(
|
|
212
|
+
identity,
|
|
213
|
+
wireProvenance,
|
|
214
|
+
s.graphStore,
|
|
215
|
+
shards,
|
|
216
|
+
nil,
|
|
217
|
+
true,
|
|
218
|
+
)
|
|
219
|
+
if err != nil {
|
|
220
|
+
return nil, nil, err
|
|
221
|
+
}
|
|
222
|
+
store.sequence = snapshot.Sequence
|
|
223
|
+
store.generation = snapshot.Generation
|
|
224
|
+
store.shards = committed
|
|
225
|
+
store.nodeOwners = nodeOwners
|
|
226
|
+
store.incomingEdges = incomingEdges
|
|
227
|
+
return snapshot, store, nil
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
func (s *graphSession) buildIncrementalShardSnapshot(change *graphChange) (*serveGraphSnapshot, *serveGraphStore, error) {
|
|
231
|
+
prior := s.graphStore
|
|
232
|
+
program := s.compiler.Program()
|
|
233
|
+
selected := invalidatedGraphFiles(program, prior.reverseDependencies, change.files, change.publicFiles)
|
|
234
|
+
if len(selected) == 0 {
|
|
235
|
+
// A changed declaration or virtual input outside the authored graph can
|
|
236
|
+
// alter external endpoints and global types. Rebuild when no exact source
|
|
237
|
+
// owner can be established instead of publishing an empty semantic delta.
|
|
238
|
+
return s.buildCompleteShardFallback(change)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Re-export edges can stamp a declaration owned by a forward dependency.
|
|
242
|
+
// Include both the prior and next targets in the same replacement set, then
|
|
243
|
+
// rebuild once more so every selected declaration is fresh before commit.
|
|
244
|
+
addPriorExportTargets(selected, prior)
|
|
245
|
+
expandPriorImplementationSources(selected, prior)
|
|
246
|
+
var partial *graph.Graph
|
|
247
|
+
for {
|
|
248
|
+
files := sortedSelectedFiles(selected)
|
|
249
|
+
partial = graph.BuildFiles(program, files, prior.nodes)
|
|
250
|
+
expanded := false
|
|
251
|
+
for id := range partial.ExportedTargets {
|
|
252
|
+
file := graph.NodeFile(id)
|
|
253
|
+
if file != "" && !selected[file] && graphSourceFile(program, file) != nil {
|
|
254
|
+
selected[file] = true
|
|
255
|
+
expanded = true
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
for id := range partial.ImplementationSources {
|
|
259
|
+
file := graph.NodeFile(id)
|
|
260
|
+
if file != "" && !selected[file] && graphSourceFile(program, file) != nil {
|
|
261
|
+
selected[file] = true
|
|
262
|
+
expanded = true
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if !expanded {
|
|
266
|
+
break
|
|
267
|
+
}
|
|
268
|
+
expandPriorImplementationSources(selected, prior)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
projectionFiles := maps.Clone(selected)
|
|
272
|
+
for _, node := range partial.Nodes {
|
|
273
|
+
projectionFiles[node.File] = true
|
|
274
|
+
if node.ImplementationFile != "" {
|
|
275
|
+
projectionFiles[node.ImplementationFile] = true
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
for _, sources := range partial.ImplementationSources {
|
|
279
|
+
for file := range sources {
|
|
280
|
+
projectionFiles[file] = true
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
texts := graph.SourceTextsForFiles(program, sortedSelectedFiles(projectionFiles))
|
|
284
|
+
provenance, wireProvenance, ok := advanceServeGraphProvenance(
|
|
285
|
+
prior.provenance,
|
|
286
|
+
prior.wireProvenance,
|
|
287
|
+
prior.wireSources,
|
|
288
|
+
program,
|
|
289
|
+
change.files,
|
|
290
|
+
s.diskDigests,
|
|
291
|
+
)
|
|
292
|
+
if !ok {
|
|
293
|
+
return s.buildCompleteShardFallback(change)
|
|
294
|
+
}
|
|
295
|
+
identity := prior.identity
|
|
296
|
+
facts, err := graph.NewDumpFacts(
|
|
297
|
+
partial,
|
|
298
|
+
s.cwd,
|
|
299
|
+
graph.GitIgnoredFiles(s.cwd, partial),
|
|
300
|
+
texts,
|
|
301
|
+
)
|
|
302
|
+
if err != nil {
|
|
303
|
+
return nil, nil, err
|
|
304
|
+
}
|
|
305
|
+
selectedFiles := sortedSelectedFiles(selected)
|
|
306
|
+
selectedSources := make([]*shimast.SourceFile, 0, len(selectedFiles))
|
|
307
|
+
for _, file := range selectedFiles {
|
|
308
|
+
source := graphSourceFile(program, file)
|
|
309
|
+
if source == nil {
|
|
310
|
+
return s.buildCompleteShardFallback(change)
|
|
311
|
+
}
|
|
312
|
+
selectedSources = append(selectedSources, source)
|
|
313
|
+
}
|
|
314
|
+
replacements, replacementSourceKeys, replacementDumpNodeFiles, discoveredExternal, replacementExternal, err := partitionServeGraphFacts(
|
|
315
|
+
identity,
|
|
316
|
+
provenance,
|
|
317
|
+
facts,
|
|
318
|
+
graph.NewDiagnosticsForFiles(program, selectedSources),
|
|
319
|
+
selected,
|
|
320
|
+
prior.externalNodes,
|
|
321
|
+
prior.resolutionDigests,
|
|
322
|
+
)
|
|
323
|
+
if err != nil {
|
|
324
|
+
return nil, nil, err
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
nextShards := maps.Clone(prior.shards)
|
|
328
|
+
nextSourceExternal := maps.Clone(prior.sourceExternal)
|
|
329
|
+
nextExternalNodes := maps.Clone(prior.externalNodes)
|
|
330
|
+
nextExternalReferences := maps.Clone(prior.externalReferences)
|
|
331
|
+
nextSourceKeys := maps.Clone(prior.sourceKeys)
|
|
332
|
+
nextDumpNodeFiles := maps.Clone(prior.dumpNodeFiles)
|
|
333
|
+
for file := range selected {
|
|
334
|
+
key := nextSourceKeys[file]
|
|
335
|
+
if old := nextSourceExternal[key]; old != nil {
|
|
336
|
+
for id := range old {
|
|
337
|
+
nextExternalReferences[id]--
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
delete(nextSourceExternal, key)
|
|
341
|
+
delete(nextShards, key)
|
|
342
|
+
delete(nextSourceKeys, file)
|
|
343
|
+
}
|
|
344
|
+
for id, file := range nextDumpNodeFiles {
|
|
345
|
+
if selected[file] {
|
|
346
|
+
delete(nextDumpNodeFiles, id)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
for file, key := range replacementSourceKeys {
|
|
350
|
+
nextSourceKeys[file] = key
|
|
351
|
+
}
|
|
352
|
+
for id, file := range replacementDumpNodeFiles {
|
|
353
|
+
nextDumpNodeFiles[id] = file
|
|
354
|
+
}
|
|
355
|
+
for key, targets := range replacementExternal {
|
|
356
|
+
nextSourceExternal[key] = targets
|
|
357
|
+
for id := range targets {
|
|
358
|
+
nextExternalReferences[id]++
|
|
359
|
+
if node, ok := discoveredExternal[id]; ok {
|
|
360
|
+
nextExternalNodes[id] = node
|
|
361
|
+
} else if node, ok := prior.externalNodes[id]; ok {
|
|
362
|
+
nextExternalNodes[id] = node
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
for id, count := range nextExternalReferences {
|
|
367
|
+
if count <= 0 {
|
|
368
|
+
delete(nextExternalReferences, id)
|
|
369
|
+
delete(nextExternalNodes, id)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
nextRaw := rawServeShards(nextShards)
|
|
374
|
+
for key, shard := range replacements {
|
|
375
|
+
nextRaw[key] = shard
|
|
376
|
+
}
|
|
377
|
+
if err := installExternalShard(nextRaw, identity, nextExternalNodes, nextExternalReferences); err != nil {
|
|
378
|
+
return nil, nil, err
|
|
379
|
+
}
|
|
380
|
+
dirty := make(map[string]bool, len(replacements)+1)
|
|
381
|
+
for key := range replacements {
|
|
382
|
+
dirty[key] = true
|
|
383
|
+
}
|
|
384
|
+
dirty[externalServeGraphShardKey(identity)] = true
|
|
385
|
+
|
|
386
|
+
nextNodes := maps.Clone(prior.nodes)
|
|
387
|
+
for id, node := range nextNodes {
|
|
388
|
+
if selected[node.File] {
|
|
389
|
+
delete(nextNodes, id)
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
nextImplementationSources := cloneServeGraphStringSets(prior.implementationSources)
|
|
394
|
+
for id := range nextImplementationSources {
|
|
395
|
+
if selected[graph.NodeFile(id)] {
|
|
396
|
+
delete(nextImplementationSources, id)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
for id, sources := range partial.ImplementationSources {
|
|
400
|
+
nextImplementationSources[id] = maps.Clone(sources)
|
|
401
|
+
}
|
|
402
|
+
for id, node := range partial.Nodes {
|
|
403
|
+
nextNodes[id] = node
|
|
404
|
+
}
|
|
405
|
+
nextExternalNodeWireIDs := maps.Clone(prior.externalNodeWireIDs)
|
|
406
|
+
changedExternalNodeWireIDs, err := serveGraphExternalNodeWireIDs(s.cwd, partial.Nodes)
|
|
407
|
+
if err != nil {
|
|
408
|
+
return nil, nil, err
|
|
409
|
+
}
|
|
410
|
+
for id, wireID := range changedExternalNodeWireIDs {
|
|
411
|
+
nextExternalNodeWireIDs[id] = wireID
|
|
412
|
+
}
|
|
413
|
+
for id, node := range nextNodes {
|
|
414
|
+
if node.External {
|
|
415
|
+
wireID, exists := nextExternalNodeWireIDs[id]
|
|
416
|
+
if !exists {
|
|
417
|
+
return nil, nil, fmt.Errorf("ttscgraph: external node %s has no wire identity", id)
|
|
418
|
+
}
|
|
419
|
+
if nextExternalReferences[wireID] <= 0 {
|
|
420
|
+
delete(nextNodes, id)
|
|
421
|
+
delete(nextExternalNodeWireIDs, id)
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
snapshot, committed, nodeOwners, incomingEdges, err := commitServeGraphSnapshot(
|
|
427
|
+
identity,
|
|
428
|
+
wireProvenance,
|
|
429
|
+
prior,
|
|
430
|
+
nextRaw,
|
|
431
|
+
dirty,
|
|
432
|
+
false,
|
|
433
|
+
)
|
|
434
|
+
if err != nil {
|
|
435
|
+
return nil, nil, err
|
|
436
|
+
}
|
|
437
|
+
store := &serveGraphStore{
|
|
438
|
+
sequence: snapshot.Sequence,
|
|
439
|
+
generation: snapshot.Generation,
|
|
440
|
+
shards: committed,
|
|
441
|
+
nodes: nextNodes,
|
|
442
|
+
provenance: provenance,
|
|
443
|
+
wireProvenance: wireProvenance,
|
|
444
|
+
wireSources: maps.Clone(prior.wireSources),
|
|
445
|
+
identity: identity,
|
|
446
|
+
resolutionDigests: prior.resolutionDigests,
|
|
447
|
+
sourceKeys: nextSourceKeys,
|
|
448
|
+
dumpNodeFiles: nextDumpNodeFiles,
|
|
449
|
+
reverseDependencies: prior.reverseDependencies,
|
|
450
|
+
sourceExternal: nextSourceExternal,
|
|
451
|
+
externalNodes: nextExternalNodes,
|
|
452
|
+
externalReferences: nextExternalReferences,
|
|
453
|
+
externalNodeWireIDs: nextExternalNodeWireIDs,
|
|
454
|
+
implementationSources: nextImplementationSources,
|
|
455
|
+
nodeOwners: nodeOwners,
|
|
456
|
+
incomingEdges: incomingEdges,
|
|
457
|
+
extractedFiles: append([]string{}, selectedFiles...),
|
|
458
|
+
}
|
|
459
|
+
return snapshot, store, nil
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
func (s *graphSession) buildCompleteShardFallback(change *graphChange) (*serveGraphSnapshot, *serveGraphStore, error) {
|
|
463
|
+
change.mode = serveModeRebuild
|
|
464
|
+
change.full = true
|
|
465
|
+
return s.buildFullShardSnapshot()
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
func commitServeGraphSnapshot(
|
|
469
|
+
identity serveGraphIdentity,
|
|
470
|
+
provenance graph.Provenance,
|
|
471
|
+
prior *serveGraphStore,
|
|
472
|
+
shards map[string]serveGraphShard,
|
|
473
|
+
dirty map[string]bool,
|
|
474
|
+
forceAll bool,
|
|
475
|
+
) (*serveGraphSnapshot, map[string]committedServeGraphShard, map[string]string, map[string]map[string]int, error) {
|
|
476
|
+
manifest := make([]serveGraphShardReference, 0, len(shards))
|
|
477
|
+
current := make(map[string]committedServeGraphShard, len(shards))
|
|
478
|
+
keys := make([]string, 0, len(shards))
|
|
479
|
+
for key := range shards {
|
|
480
|
+
keys = append(keys, key)
|
|
481
|
+
}
|
|
482
|
+
sort.Strings(keys)
|
|
483
|
+
for _, key := range keys {
|
|
484
|
+
if !forceAll && prior != nil && !dirty[key] {
|
|
485
|
+
committed, exists := prior.shards[key]
|
|
486
|
+
if !exists {
|
|
487
|
+
return nil, nil, nil, nil, fmt.Errorf("ttscgraph: new graph shard %s was not marked dirty", key)
|
|
488
|
+
}
|
|
489
|
+
current[key] = committed
|
|
490
|
+
manifest = append(manifest, serveGraphShardReference{Key: key, Digest: committed.digest})
|
|
491
|
+
continue
|
|
492
|
+
}
|
|
493
|
+
shard := shards[key]
|
|
494
|
+
normalizeServeGraphShard(&shard)
|
|
495
|
+
digest, err := serveGraphShardDigest(shard)
|
|
496
|
+
if err != nil {
|
|
497
|
+
return nil, nil, nil, nil, err
|
|
498
|
+
}
|
|
499
|
+
current[key] = committedServeGraphShard{digest: digest, shard: shard}
|
|
500
|
+
manifest = append(manifest, serveGraphShardReference{Key: key, Digest: digest})
|
|
501
|
+
}
|
|
502
|
+
var nodeOwners map[string]string
|
|
503
|
+
var incomingEdges map[string]map[string]int
|
|
504
|
+
var err error
|
|
505
|
+
if forceAll || prior == nil {
|
|
506
|
+
nodeOwners, incomingEdges, err = indexServeGraphShards(current, provenance.Universe)
|
|
507
|
+
} else {
|
|
508
|
+
nodeOwners, incomingEdges, err = updateServeGraphShardIndex(prior, current, dirty, provenance.Universe)
|
|
509
|
+
}
|
|
510
|
+
if err != nil {
|
|
511
|
+
return nil, nil, nil, nil, err
|
|
512
|
+
}
|
|
513
|
+
generation, err := digestJSON(struct {
|
|
514
|
+
Tsconfig string `json:"tsconfig"`
|
|
515
|
+
Producer graph.Producer `json:"producer"`
|
|
516
|
+
Capabilities []string `json:"capabilities"`
|
|
517
|
+
Universe graph.Universe `json:"universe"`
|
|
518
|
+
Manifest []serveGraphShardReference `json:"manifest"`
|
|
519
|
+
}{
|
|
520
|
+
Tsconfig: identity.target,
|
|
521
|
+
Producer: provenance.Producer,
|
|
522
|
+
Capabilities: provenance.Capabilities,
|
|
523
|
+
Universe: provenance.Universe,
|
|
524
|
+
Manifest: manifest,
|
|
525
|
+
})
|
|
526
|
+
if err != nil {
|
|
527
|
+
return nil, nil, nil, nil, err
|
|
528
|
+
}
|
|
529
|
+
sequence := 1
|
|
530
|
+
if prior != nil {
|
|
531
|
+
sequence = prior.sequence + 1
|
|
532
|
+
}
|
|
533
|
+
snapshot := &serveGraphSnapshot{
|
|
534
|
+
ProtocolVersion: graphSnapshotProtocolVersion,
|
|
535
|
+
SchemaVersion: graph.DumpSchemaVersion,
|
|
536
|
+
Project: identity.project,
|
|
537
|
+
Tsconfig: identity.target,
|
|
538
|
+
Producer: provenance.Producer,
|
|
539
|
+
Capabilities: append([]string{}, provenance.Capabilities...),
|
|
540
|
+
Universe: provenance.Universe,
|
|
541
|
+
Sequence: sequence,
|
|
542
|
+
Generation: generation,
|
|
543
|
+
Upserts: []serveGraphShardUpsert{},
|
|
544
|
+
Deletes: []string{},
|
|
545
|
+
Manifest: manifest,
|
|
546
|
+
}
|
|
547
|
+
if prior != nil {
|
|
548
|
+
snapshot.BaseSequence = prior.sequence
|
|
549
|
+
snapshot.BaseGeneration = prior.generation
|
|
550
|
+
for key := range prior.shards {
|
|
551
|
+
if _, exists := current[key]; !exists {
|
|
552
|
+
snapshot.Deletes = append(snapshot.Deletes, key)
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
sort.Strings(snapshot.Deletes)
|
|
556
|
+
}
|
|
557
|
+
for _, key := range keys {
|
|
558
|
+
value := current[key]
|
|
559
|
+
if forceAll || prior == nil || prior.shards[key].digest != value.digest {
|
|
560
|
+
snapshot.Upserts = append(snapshot.Upserts, serveGraphShardUpsert{
|
|
561
|
+
Digest: value.digest,
|
|
562
|
+
Shard: value.shard,
|
|
563
|
+
})
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return snapshot, current, nodeOwners, incomingEdges, nil
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
func validateServeGraphShardHeaders(shards map[string]committedServeGraphShard, universe graph.Universe) error {
|
|
570
|
+
sources := map[string]string{}
|
|
571
|
+
configs := map[string]string{}
|
|
572
|
+
for key, committed := range shards {
|
|
573
|
+
if key == "" || strings.ContainsRune(key, '\x00') || committed.shard.Key != key {
|
|
574
|
+
return fmt.Errorf("ttscgraph: invalid graph shard identity: %q", key)
|
|
575
|
+
}
|
|
576
|
+
if committed.shard.Source != nil && committed.shard.Config != nil {
|
|
577
|
+
return fmt.Errorf("ttscgraph: shard %s claims both a source and a config input", key)
|
|
578
|
+
}
|
|
579
|
+
if committed.shard.Config != nil && (len(committed.shard.Nodes) != 0 || len(committed.shard.Edges) != 0) {
|
|
580
|
+
return fmt.Errorf("ttscgraph: config shard %s unexpectedly owns graph facts", key)
|
|
581
|
+
}
|
|
582
|
+
if committed.shard.Config != nil {
|
|
583
|
+
if _, exists := configs[committed.shard.Config.File]; exists {
|
|
584
|
+
return fmt.Errorf("ttscgraph: config input %s has more than one shard", committed.shard.Config.File)
|
|
585
|
+
}
|
|
586
|
+
configs[committed.shard.Config.File] = committed.shard.Config.Digest
|
|
587
|
+
}
|
|
588
|
+
if committed.shard.Source != nil {
|
|
589
|
+
if owner, exists := sources[committed.shard.Source.File]; exists {
|
|
590
|
+
return fmt.Errorf("ttscgraph: source input %s is owned by both %s and %s", committed.shard.Source.File, owner, key)
|
|
591
|
+
}
|
|
592
|
+
sources[committed.shard.Source.File] = key
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if len(configs) != len(universe.Configs) {
|
|
596
|
+
return fmt.Errorf("ttscgraph: config shards do not cover the build universe")
|
|
597
|
+
}
|
|
598
|
+
for _, config := range universe.Configs {
|
|
599
|
+
if configs[config.File] != config.Digest {
|
|
600
|
+
return fmt.Errorf("ttscgraph: config shard disagrees with universe input %s", config.File)
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return nil
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
func indexServeGraphShards(
|
|
607
|
+
shards map[string]committedServeGraphShard,
|
|
608
|
+
universe graph.Universe,
|
|
609
|
+
) (map[string]string, map[string]map[string]int, error) {
|
|
610
|
+
if err := validateServeGraphShardHeaders(shards, universe); err != nil {
|
|
611
|
+
return nil, nil, err
|
|
612
|
+
}
|
|
613
|
+
owners := map[string]string{}
|
|
614
|
+
incoming := map[string]map[string]int{}
|
|
615
|
+
for key, committed := range shards {
|
|
616
|
+
if err := validateServeGraphShardContents(key, committed.shard); err != nil {
|
|
617
|
+
return nil, nil, err
|
|
618
|
+
}
|
|
619
|
+
for _, node := range committed.shard.Nodes {
|
|
620
|
+
if owner, exists := owners[node.ID]; exists {
|
|
621
|
+
return nil, nil, fmt.Errorf("ttscgraph: node %s is owned by both %s and %s", node.ID, owner, key)
|
|
622
|
+
}
|
|
623
|
+
owners[node.ID] = key
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
for key, committed := range shards {
|
|
627
|
+
for _, edge := range committed.shard.Edges {
|
|
628
|
+
if owners[edge.From] != key {
|
|
629
|
+
return nil, nil, fmt.Errorf("ttscgraph: shard %s does not own edge source %s", key, edge.From)
|
|
630
|
+
}
|
|
631
|
+
if _, exists := owners[edge.To]; !exists {
|
|
632
|
+
return nil, nil, fmt.Errorf("ttscgraph: edge target is absent from committed shards: %s", edge.To)
|
|
633
|
+
}
|
|
634
|
+
addServeGraphIncomingEdge(incoming, edge.To, key)
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return owners, incoming, nil
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
func updateServeGraphShardIndex(
|
|
641
|
+
prior *serveGraphStore,
|
|
642
|
+
current map[string]committedServeGraphShard,
|
|
643
|
+
dirty map[string]bool,
|
|
644
|
+
universe graph.Universe,
|
|
645
|
+
) (map[string]string, map[string]map[string]int, error) {
|
|
646
|
+
if err := validateServeGraphShardHeaders(current, universe); err != nil {
|
|
647
|
+
return nil, nil, err
|
|
648
|
+
}
|
|
649
|
+
owners := maps.Clone(prior.nodeOwners)
|
|
650
|
+
incoming := cloneServeGraphIncomingEdges(prior.incomingEdges)
|
|
651
|
+
touched := maps.Clone(dirty)
|
|
652
|
+
for key := range prior.shards {
|
|
653
|
+
if _, exists := current[key]; !exists {
|
|
654
|
+
touched[key] = true
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
removedNodes := map[string]bool{}
|
|
658
|
+
for key := range touched {
|
|
659
|
+
committed, exists := prior.shards[key]
|
|
660
|
+
if !exists {
|
|
661
|
+
continue
|
|
662
|
+
}
|
|
663
|
+
for _, edge := range committed.shard.Edges {
|
|
664
|
+
removeServeGraphIncomingEdge(incoming, edge.To, key)
|
|
665
|
+
}
|
|
666
|
+
for _, node := range committed.shard.Nodes {
|
|
667
|
+
if owners[node.ID] == key {
|
|
668
|
+
delete(owners, node.ID)
|
|
669
|
+
removedNodes[node.ID] = true
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
for key := range dirty {
|
|
674
|
+
committed, exists := current[key]
|
|
675
|
+
if !exists {
|
|
676
|
+
return nil, nil, fmt.Errorf("ttscgraph: dirty graph shard %s is absent from the next generation", key)
|
|
677
|
+
}
|
|
678
|
+
if err := validateServeGraphShardContents(key, committed.shard); err != nil {
|
|
679
|
+
return nil, nil, err
|
|
680
|
+
}
|
|
681
|
+
for _, node := range committed.shard.Nodes {
|
|
682
|
+
if owner, exists := owners[node.ID]; exists {
|
|
683
|
+
return nil, nil, fmt.Errorf("ttscgraph: node %s is owned by both %s and %s", node.ID, owner, key)
|
|
684
|
+
}
|
|
685
|
+
owners[node.ID] = key
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
for key := range dirty {
|
|
689
|
+
for _, edge := range current[key].shard.Edges {
|
|
690
|
+
if owners[edge.From] != key {
|
|
691
|
+
return nil, nil, fmt.Errorf("ttscgraph: shard %s does not own edge source %s", key, edge.From)
|
|
692
|
+
}
|
|
693
|
+
if _, exists := owners[edge.To]; !exists {
|
|
694
|
+
return nil, nil, fmt.Errorf("ttscgraph: edge target is absent from committed shards: %s", edge.To)
|
|
695
|
+
}
|
|
696
|
+
addServeGraphIncomingEdge(incoming, edge.To, key)
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
for id := range removedNodes {
|
|
700
|
+
if _, exists := owners[id]; exists {
|
|
701
|
+
continue
|
|
702
|
+
}
|
|
703
|
+
if len(incoming[id]) != 0 {
|
|
704
|
+
return nil, nil, fmt.Errorf("ttscgraph: removed node %s is still referenced by an unchanged shard", id)
|
|
705
|
+
}
|
|
706
|
+
delete(incoming, id)
|
|
707
|
+
}
|
|
708
|
+
return owners, incoming, nil
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
func validateServeGraphShardContents(key string, shard serveGraphShard) error {
|
|
712
|
+
if shard.Source != nil {
|
|
713
|
+
for _, node := range shard.Nodes {
|
|
714
|
+
if node.External || node.File != shard.Source.File {
|
|
715
|
+
return fmt.Errorf("ttscgraph: source shard %s owns node %s from %s", key, node.ID, node.File)
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
for _, diagnostic := range shard.Diagnostics {
|
|
719
|
+
if diagnostic.File != shard.Source.File {
|
|
720
|
+
return fmt.Errorf("ttscgraph: source shard %s owns diagnostic from %s", key, diagnostic.File)
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return nil
|
|
724
|
+
}
|
|
725
|
+
if len(shard.Edges) != 0 {
|
|
726
|
+
return fmt.Errorf("ttscgraph: non-source shard %s unexpectedly owns edges", key)
|
|
727
|
+
}
|
|
728
|
+
if shard.Config != nil {
|
|
729
|
+
for _, diagnostic := range shard.Diagnostics {
|
|
730
|
+
if diagnostic.File != shard.Config.File {
|
|
731
|
+
return fmt.Errorf("ttscgraph: config shard %s owns diagnostic from %s", key, diagnostic.File)
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return nil
|
|
735
|
+
}
|
|
736
|
+
for _, node := range shard.Nodes {
|
|
737
|
+
if !node.External {
|
|
738
|
+
return fmt.Errorf("ttscgraph: metadata shard %s owns authored node %s", key, node.ID)
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
for _, diagnostic := range shard.Diagnostics {
|
|
742
|
+
if diagnostic.File != "" {
|
|
743
|
+
return fmt.Errorf("ttscgraph: metadata shard %s owns diagnostic from %s", key, diagnostic.File)
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return nil
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
func cloneServeGraphIncomingEdges(input map[string]map[string]int) map[string]map[string]int {
|
|
750
|
+
cloned := make(map[string]map[string]int, len(input))
|
|
751
|
+
for id, owners := range input {
|
|
752
|
+
cloned[id] = maps.Clone(owners)
|
|
753
|
+
}
|
|
754
|
+
return cloned
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
func cloneServeGraphStringSets(input map[string]map[string]bool) map[string]map[string]bool {
|
|
758
|
+
cloned := make(map[string]map[string]bool, len(input))
|
|
759
|
+
for key, values := range input {
|
|
760
|
+
cloned[key] = maps.Clone(values)
|
|
761
|
+
}
|
|
762
|
+
return cloned
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
func serveGraphExternalNodeWireIDs(project string, nodes map[string]*graph.Node) (map[string]string, error) {
|
|
766
|
+
ids := []string{}
|
|
767
|
+
for id, node := range nodes {
|
|
768
|
+
if node.External {
|
|
769
|
+
ids = append(ids, id)
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
sort.Strings(ids)
|
|
773
|
+
return graph.WireNodeIDs(project, ids)
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
func addServeGraphIncomingEdge(incoming map[string]map[string]int, target, owner string) {
|
|
777
|
+
owners := incoming[target]
|
|
778
|
+
if owners == nil {
|
|
779
|
+
owners = map[string]int{}
|
|
780
|
+
incoming[target] = owners
|
|
781
|
+
}
|
|
782
|
+
owners[owner]++
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
func removeServeGraphIncomingEdge(incoming map[string]map[string]int, target, owner string) {
|
|
786
|
+
owners := incoming[target]
|
|
787
|
+
if owners == nil {
|
|
788
|
+
return
|
|
789
|
+
}
|
|
790
|
+
if owners[owner] <= 1 {
|
|
791
|
+
delete(owners, owner)
|
|
792
|
+
} else {
|
|
793
|
+
owners[owner]--
|
|
794
|
+
}
|
|
795
|
+
if len(owners) == 0 {
|
|
796
|
+
delete(incoming, target)
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
func partitionServeGraphFacts(
|
|
801
|
+
identity serveGraphIdentity,
|
|
802
|
+
provenance graph.Provenance,
|
|
803
|
+
facts graph.DumpFacts,
|
|
804
|
+
diagnostics []graph.Diagnostic,
|
|
805
|
+
selected map[string]bool,
|
|
806
|
+
knownExternal map[string]graph.DumpNode,
|
|
807
|
+
resolutionDigests map[string]string,
|
|
808
|
+
) (
|
|
809
|
+
map[string]serveGraphShard,
|
|
810
|
+
map[string]string,
|
|
811
|
+
map[string]string,
|
|
812
|
+
map[string]graph.DumpNode,
|
|
813
|
+
map[string]map[string]bool,
|
|
814
|
+
error,
|
|
815
|
+
) {
|
|
816
|
+
shards := map[string]serveGraphShard{}
|
|
817
|
+
sourceKeys := map[string]string{}
|
|
818
|
+
relativeToPhysical := map[string]string{}
|
|
819
|
+
for _, source := range provenance.Sources {
|
|
820
|
+
if selected != nil && !selected[source.File] {
|
|
821
|
+
continue
|
|
822
|
+
}
|
|
823
|
+
relative, err := serveGraphFile(identity.project, source.File)
|
|
824
|
+
if err != nil {
|
|
825
|
+
return nil, nil, nil, nil, nil, err
|
|
826
|
+
}
|
|
827
|
+
sourceCopy := source
|
|
828
|
+
sourceCopy.File = relative
|
|
829
|
+
key := sourceServeGraphShardKey(identity, relative, source, resolutionDigests[source.File])
|
|
830
|
+
shards[key] = emptyServeGraphShard(key, &sourceCopy)
|
|
831
|
+
sourceKeys[source.File] = key
|
|
832
|
+
relativeToPhysical[relative] = source.File
|
|
833
|
+
}
|
|
834
|
+
if selected == nil {
|
|
835
|
+
for _, config := range provenance.Universe.Configs {
|
|
836
|
+
relative, err := serveGraphFile(identity.project, config.File)
|
|
837
|
+
if err != nil {
|
|
838
|
+
return nil, nil, nil, nil, nil, err
|
|
839
|
+
}
|
|
840
|
+
configCopy := config
|
|
841
|
+
configCopy.File = relative
|
|
842
|
+
key := configServeGraphShardKey(identity, relative, config.Digest)
|
|
843
|
+
shard := emptyServeGraphShard(key, nil)
|
|
844
|
+
shard.Config = &configCopy
|
|
845
|
+
shards[key] = shard
|
|
846
|
+
if _, exists := sourceKeys[config.File]; !exists {
|
|
847
|
+
sourceKeys[config.File] = key
|
|
848
|
+
}
|
|
849
|
+
relativeToPhysical[relative] = config.File
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
metadataKey := metadataServeGraphShardKey(identity)
|
|
853
|
+
shards[metadataKey] = emptyServeGraphShard(metadataKey, nil)
|
|
854
|
+
|
|
855
|
+
dumpNodeFiles := map[string]string{}
|
|
856
|
+
externalNodes := map[string]graph.DumpNode{}
|
|
857
|
+
for _, node := range facts.Nodes {
|
|
858
|
+
if node.External {
|
|
859
|
+
externalNodes[node.ID] = node
|
|
860
|
+
continue
|
|
861
|
+
}
|
|
862
|
+
physical, ok := relativeToPhysical[node.File]
|
|
863
|
+
if !ok {
|
|
864
|
+
return nil, nil, nil, nil, nil, fmt.Errorf("ttscgraph: node source is absent from shard manifest: %s", node.File)
|
|
865
|
+
}
|
|
866
|
+
dumpNodeFiles[node.ID] = physical
|
|
867
|
+
key := sourceKeys[physical]
|
|
868
|
+
shard := shards[key]
|
|
869
|
+
shard.Nodes = append(shard.Nodes, node)
|
|
870
|
+
shards[key] = shard
|
|
871
|
+
}
|
|
872
|
+
sourceExternal := map[string]map[string]bool{}
|
|
873
|
+
for _, edge := range facts.Edges {
|
|
874
|
+
physical, ok := dumpNodeFiles[edge.From]
|
|
875
|
+
if !ok {
|
|
876
|
+
return nil, nil, nil, nil, nil, fmt.Errorf("ttscgraph: edge source node is absent from shard facts: %s", edge.From)
|
|
877
|
+
}
|
|
878
|
+
key := sourceKeys[physical]
|
|
879
|
+
shard := shards[key]
|
|
880
|
+
shard.Edges = append(shard.Edges, edge)
|
|
881
|
+
shards[key] = shard
|
|
882
|
+
_, discovered := externalNodes[edge.To]
|
|
883
|
+
_, committed := knownExternal[edge.To]
|
|
884
|
+
if discovered || committed {
|
|
885
|
+
targets := sourceExternal[key]
|
|
886
|
+
if targets == nil {
|
|
887
|
+
targets = map[string]bool{}
|
|
888
|
+
sourceExternal[key] = targets
|
|
889
|
+
}
|
|
890
|
+
targets[edge.To] = true
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
for _, diagnostic := range diagnostics {
|
|
894
|
+
if diagnostic.File == "" {
|
|
895
|
+
shard := shards[metadataKey]
|
|
896
|
+
shard.Diagnostics = append(shard.Diagnostics, diagnostic)
|
|
897
|
+
shards[metadataKey] = shard
|
|
898
|
+
continue
|
|
899
|
+
}
|
|
900
|
+
key := sourceKeys[diagnostic.File]
|
|
901
|
+
shard, ok := shards[key]
|
|
902
|
+
if !ok {
|
|
903
|
+
if selected != nil {
|
|
904
|
+
// Config and stable program diagnostics are repeated by tsgo's
|
|
905
|
+
// per-file diagnostic API. Their owning shards are outside this
|
|
906
|
+
// replacement set and remain committed unchanged.
|
|
907
|
+
continue
|
|
908
|
+
}
|
|
909
|
+
return nil, nil, nil, nil, nil, fmt.Errorf("ttscgraph: diagnostic source is absent from shard manifest: %s", diagnostic.File)
|
|
910
|
+
}
|
|
911
|
+
normalized := diagnostic
|
|
912
|
+
relative, err := serveGraphFile(identity.project, diagnostic.File)
|
|
913
|
+
if err != nil {
|
|
914
|
+
return nil, nil, nil, nil, nil, err
|
|
915
|
+
}
|
|
916
|
+
normalized.File = relative
|
|
917
|
+
shard.Diagnostics = append(shard.Diagnostics, normalized)
|
|
918
|
+
shards[key] = shard
|
|
919
|
+
}
|
|
920
|
+
return shards, sourceKeys, dumpNodeFiles, externalNodes, sourceExternal, nil
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
func installExternalShard(
|
|
924
|
+
shards map[string]serveGraphShard,
|
|
925
|
+
identity serveGraphIdentity,
|
|
926
|
+
externalNodes map[string]graph.DumpNode,
|
|
927
|
+
references map[string]int,
|
|
928
|
+
) error {
|
|
929
|
+
key := externalServeGraphShardKey(identity)
|
|
930
|
+
shard := emptyServeGraphShard(key, nil)
|
|
931
|
+
for id, count := range references {
|
|
932
|
+
if count > 0 {
|
|
933
|
+
node, ok := externalNodes[id]
|
|
934
|
+
if !ok {
|
|
935
|
+
return fmt.Errorf("ttscgraph: external edge target is absent from committed endpoints: %s", id)
|
|
936
|
+
}
|
|
937
|
+
shard.Nodes = append(shard.Nodes, node)
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
shards[key] = shard
|
|
941
|
+
return nil
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
func normalizeServeGraphShard(shard *serveGraphShard) {
|
|
945
|
+
if shard.Nodes == nil {
|
|
946
|
+
shard.Nodes = []graph.DumpNode{}
|
|
947
|
+
}
|
|
948
|
+
if shard.Edges == nil {
|
|
949
|
+
shard.Edges = []graph.DumpEdge{}
|
|
950
|
+
}
|
|
951
|
+
if shard.Diagnostics == nil {
|
|
952
|
+
shard.Diagnostics = []graph.Diagnostic{}
|
|
953
|
+
}
|
|
954
|
+
sort.Slice(shard.Nodes, func(i, j int) bool { return shard.Nodes[i].ID < shard.Nodes[j].ID })
|
|
955
|
+
sort.Slice(shard.Edges, func(i, j int) bool {
|
|
956
|
+
if shard.Edges[i].From != shard.Edges[j].From {
|
|
957
|
+
return shard.Edges[i].From < shard.Edges[j].From
|
|
958
|
+
}
|
|
959
|
+
if shard.Edges[i].To != shard.Edges[j].To {
|
|
960
|
+
return shard.Edges[i].To < shard.Edges[j].To
|
|
961
|
+
}
|
|
962
|
+
return shard.Edges[i].Kind < shard.Edges[j].Kind
|
|
963
|
+
})
|
|
964
|
+
sort.Slice(shard.Diagnostics, func(i, j int) bool {
|
|
965
|
+
if shard.Diagnostics[i].File != shard.Diagnostics[j].File {
|
|
966
|
+
return shard.Diagnostics[i].File < shard.Diagnostics[j].File
|
|
967
|
+
}
|
|
968
|
+
if shard.Diagnostics[i].Line != shard.Diagnostics[j].Line {
|
|
969
|
+
return shard.Diagnostics[i].Line < shard.Diagnostics[j].Line
|
|
970
|
+
}
|
|
971
|
+
if shard.Diagnostics[i].Column != shard.Diagnostics[j].Column {
|
|
972
|
+
return shard.Diagnostics[i].Column < shard.Diagnostics[j].Column
|
|
973
|
+
}
|
|
974
|
+
return shard.Diagnostics[i].Code < shard.Diagnostics[j].Code
|
|
975
|
+
})
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
func emptyServeGraphShard(key string, source *graph.SourceDigest) serveGraphShard {
|
|
979
|
+
return serveGraphShard{
|
|
980
|
+
Key: key,
|
|
981
|
+
Source: source,
|
|
982
|
+
Nodes: []graph.DumpNode{},
|
|
983
|
+
Edges: []graph.DumpEdge{},
|
|
984
|
+
Diagnostics: []graph.Diagnostic{},
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
func serveGraphShardDigest(shard serveGraphShard) (string, error) {
|
|
989
|
+
return digestJSON(shard)
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
func digestJSON(value any) (string, error) {
|
|
993
|
+
encoded, err := json.Marshal(value)
|
|
994
|
+
if err != nil {
|
|
995
|
+
return "", fmt.Errorf("ttscgraph: encode shard digest: %w", err)
|
|
996
|
+
}
|
|
997
|
+
sum := sha256.Sum256(encoded)
|
|
998
|
+
return hex.EncodeToString(sum[:]), nil
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
func newServeGraphIdentity(project, tsconfig string, provenance graph.Provenance) (serveGraphIdentity, graph.Provenance, map[string]string, error) {
|
|
1002
|
+
if !filepath.IsAbs(project) {
|
|
1003
|
+
return serveGraphIdentity{}, graph.Provenance{}, nil, fmt.Errorf("ttscgraph: project root must be absolute: %s", project)
|
|
1004
|
+
}
|
|
1005
|
+
wireProject, err := graph.WireProject(project)
|
|
1006
|
+
if err != nil {
|
|
1007
|
+
return serveGraphIdentity{}, graph.Provenance{}, nil, err
|
|
1008
|
+
}
|
|
1009
|
+
target := tsconfig
|
|
1010
|
+
if !filepath.IsAbs(target) {
|
|
1011
|
+
target = filepath.Join(project, target)
|
|
1012
|
+
}
|
|
1013
|
+
target, err = serveGraphFile(project, filepath.Clean(target))
|
|
1014
|
+
if err != nil {
|
|
1015
|
+
return serveGraphIdentity{}, graph.Provenance{}, nil, err
|
|
1016
|
+
}
|
|
1017
|
+
normalized, wireSources, err := normalizeServeGraphProvenance(project, provenance)
|
|
1018
|
+
if err != nil {
|
|
1019
|
+
return serveGraphIdentity{}, graph.Provenance{}, nil, err
|
|
1020
|
+
}
|
|
1021
|
+
universe, err := digestJSON(normalized.Universe)
|
|
1022
|
+
if err != nil {
|
|
1023
|
+
return serveGraphIdentity{}, graph.Provenance{}, nil, err
|
|
1024
|
+
}
|
|
1025
|
+
return serveGraphIdentity{
|
|
1026
|
+
project: wireProject,
|
|
1027
|
+
target: target,
|
|
1028
|
+
universe: universe,
|
|
1029
|
+
producer: provenance.Producer,
|
|
1030
|
+
}, normalized, wireSources, nil
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
func normalizeServeGraphProvenance(project string, provenance graph.Provenance) (graph.Provenance, map[string]string, error) {
|
|
1034
|
+
normalized := provenance
|
|
1035
|
+
normalized.Capabilities = append([]string{}, provenance.Capabilities...)
|
|
1036
|
+
normalized.Sources = make([]graph.SourceDigest, 0, len(provenance.Sources))
|
|
1037
|
+
wireSources := make(map[string]string, len(provenance.Sources))
|
|
1038
|
+
sourceOwners := map[string]string{}
|
|
1039
|
+
for _, source := range provenance.Sources {
|
|
1040
|
+
file, err := serveGraphFile(project, source.File)
|
|
1041
|
+
if err != nil {
|
|
1042
|
+
return graph.Provenance{}, nil, err
|
|
1043
|
+
}
|
|
1044
|
+
if previous, exists := sourceOwners[file]; exists {
|
|
1045
|
+
return graph.Provenance{}, nil, fmt.Errorf("ttscgraph: source paths %q and %q collide at wire identity %q", previous, source.File, file)
|
|
1046
|
+
}
|
|
1047
|
+
sourceOwners[file] = source.File
|
|
1048
|
+
wireSources[source.File] = file
|
|
1049
|
+
normalized.Sources = append(normalized.Sources, graph.SourceDigest{
|
|
1050
|
+
File: file,
|
|
1051
|
+
Checker: source.Checker,
|
|
1052
|
+
Disk: source.Disk,
|
|
1053
|
+
})
|
|
1054
|
+
}
|
|
1055
|
+
normalized.Universe.Configs = make([]graph.FileDigest, 0, len(provenance.Universe.Configs))
|
|
1056
|
+
configOwners := map[string]string{}
|
|
1057
|
+
for _, config := range provenance.Universe.Configs {
|
|
1058
|
+
file, err := serveGraphFile(project, config.File)
|
|
1059
|
+
if err != nil {
|
|
1060
|
+
return graph.Provenance{}, nil, err
|
|
1061
|
+
}
|
|
1062
|
+
if previous, exists := configOwners[file]; exists {
|
|
1063
|
+
return graph.Provenance{}, nil, fmt.Errorf("ttscgraph: config paths %q and %q collide at wire identity %q", previous, config.File, file)
|
|
1064
|
+
}
|
|
1065
|
+
configOwners[file] = config.File
|
|
1066
|
+
normalized.Universe.Configs = append(normalized.Universe.Configs, graph.FileDigest{File: file, Digest: config.Digest})
|
|
1067
|
+
}
|
|
1068
|
+
normalized.Universe.Roots = make([]graph.RootFile, 0, len(provenance.Universe.Roots))
|
|
1069
|
+
for _, root := range provenance.Universe.Roots {
|
|
1070
|
+
config, err := serveGraphFile(project, root.Config)
|
|
1071
|
+
if err != nil {
|
|
1072
|
+
return graph.Provenance{}, nil, err
|
|
1073
|
+
}
|
|
1074
|
+
file, err := serveGraphFile(project, root.File)
|
|
1075
|
+
if err != nil {
|
|
1076
|
+
return graph.Provenance{}, nil, err
|
|
1077
|
+
}
|
|
1078
|
+
normalized.Universe.Roots = append(normalized.Universe.Roots, graph.RootFile{Config: config, File: file})
|
|
1079
|
+
}
|
|
1080
|
+
sort.Slice(normalized.Sources, func(i, j int) bool { return normalized.Sources[i].File < normalized.Sources[j].File })
|
|
1081
|
+
sort.Slice(normalized.Universe.Configs, func(i, j int) bool {
|
|
1082
|
+
return normalized.Universe.Configs[i].File < normalized.Universe.Configs[j].File
|
|
1083
|
+
})
|
|
1084
|
+
sort.Slice(normalized.Universe.Roots, func(i, j int) bool {
|
|
1085
|
+
if normalized.Universe.Roots[i].Config != normalized.Universe.Roots[j].Config {
|
|
1086
|
+
return normalized.Universe.Roots[i].Config < normalized.Universe.Roots[j].Config
|
|
1087
|
+
}
|
|
1088
|
+
return normalized.Universe.Roots[i].File < normalized.Universe.Roots[j].File
|
|
1089
|
+
})
|
|
1090
|
+
return normalized, wireSources, nil
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
func advanceServeGraphProvenance(
|
|
1094
|
+
previous graph.Provenance,
|
|
1095
|
+
previousWire graph.Provenance,
|
|
1096
|
+
wireSources map[string]string,
|
|
1097
|
+
program *driver.Program,
|
|
1098
|
+
changed []string,
|
|
1099
|
+
diskDigests map[string]string,
|
|
1100
|
+
) (graph.Provenance, graph.Provenance, bool) {
|
|
1101
|
+
next := previous
|
|
1102
|
+
next.Capabilities = append([]string{}, previous.Capabilities...)
|
|
1103
|
+
next.Sources = append([]graph.SourceDigest{}, previous.Sources...)
|
|
1104
|
+
next.Universe.Configs = append([]graph.FileDigest{}, previous.Universe.Configs...)
|
|
1105
|
+
next.Universe.Roots = append([]graph.RootFile{}, previous.Universe.Roots...)
|
|
1106
|
+
nextWire := previousWire
|
|
1107
|
+
nextWire.Capabilities = append([]string{}, previousWire.Capabilities...)
|
|
1108
|
+
nextWire.Sources = append([]graph.SourceDigest{}, previousWire.Sources...)
|
|
1109
|
+
nextWire.Universe.Configs = append([]graph.FileDigest{}, previousWire.Universe.Configs...)
|
|
1110
|
+
nextWire.Universe.Roots = append([]graph.RootFile{}, previousWire.Universe.Roots...)
|
|
1111
|
+
if len(nextWire.Sources) != len(next.Sources) {
|
|
1112
|
+
return graph.Provenance{}, graph.Provenance{}, false
|
|
1113
|
+
}
|
|
1114
|
+
positions := make(map[string]int, len(next.Sources))
|
|
1115
|
+
for index, source := range next.Sources {
|
|
1116
|
+
positions[source.File] = index
|
|
1117
|
+
}
|
|
1118
|
+
wirePositions := make(map[string]int, len(nextWire.Sources))
|
|
1119
|
+
for index, source := range nextWire.Sources {
|
|
1120
|
+
wirePositions[source.File] = index
|
|
1121
|
+
}
|
|
1122
|
+
for _, file := range changed {
|
|
1123
|
+
source := program.SourceFile(file)
|
|
1124
|
+
if source == nil {
|
|
1125
|
+
return graph.Provenance{}, graph.Provenance{}, false
|
|
1126
|
+
}
|
|
1127
|
+
physical := source.FileName()
|
|
1128
|
+
index, ok := positions[physical]
|
|
1129
|
+
if !ok {
|
|
1130
|
+
return graph.Provenance{}, graph.Provenance{}, false
|
|
1131
|
+
}
|
|
1132
|
+
updated := graph.SourceDigest{
|
|
1133
|
+
File: physical,
|
|
1134
|
+
Checker: graph.Digest(sha256.Sum256([]byte(source.Text()))),
|
|
1135
|
+
Disk: diskDigests[physical],
|
|
1136
|
+
}
|
|
1137
|
+
wireFile, ok := wireSources[physical]
|
|
1138
|
+
if !ok {
|
|
1139
|
+
return graph.Provenance{}, graph.Provenance{}, false
|
|
1140
|
+
}
|
|
1141
|
+
wireIndex, ok := wirePositions[wireFile]
|
|
1142
|
+
if !ok {
|
|
1143
|
+
return graph.Provenance{}, graph.Provenance{}, false
|
|
1144
|
+
}
|
|
1145
|
+
next.Sources[index] = updated
|
|
1146
|
+
nextWire.Sources[wireIndex].Checker = updated.Checker
|
|
1147
|
+
nextWire.Sources[wireIndex].Disk = updated.Disk
|
|
1148
|
+
}
|
|
1149
|
+
return next, nextWire, true
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
func sourceServeGraphShardKey(identity serveGraphIdentity, file string, source graph.SourceDigest, resolutionDigest string) string {
|
|
1153
|
+
prefix := "1"
|
|
1154
|
+
if strings.HasPrefix(file, "bundled:///") {
|
|
1155
|
+
prefix = "2"
|
|
1156
|
+
}
|
|
1157
|
+
return prefix + ":source:" + serveGraphShardKey(identity, file, source.Checker, source.Disk, resolutionDigest)
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
func configServeGraphShardKey(identity serveGraphIdentity, file, digest string) string {
|
|
1161
|
+
return "3:config:" + serveGraphShardKey(identity, file, digest)
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
func externalServeGraphShardKey(identity serveGraphIdentity) string {
|
|
1165
|
+
return "0:external:" + serveGraphShardKey(identity, "external")
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
func metadataServeGraphShardKey(identity serveGraphIdentity) string {
|
|
1169
|
+
return "0:metadata:" + serveGraphShardKey(identity, "metadata")
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
func serveGraphShardKey(identity serveGraphIdentity, values ...string) string {
|
|
1173
|
+
coordinates := []any{
|
|
1174
|
+
graphSnapshotProtocolVersion,
|
|
1175
|
+
identity.producer.Tool,
|
|
1176
|
+
identity.producer.Version,
|
|
1177
|
+
identity.producer.Typescript,
|
|
1178
|
+
identity.target,
|
|
1179
|
+
identity.universe,
|
|
1180
|
+
}
|
|
1181
|
+
for _, value := range values {
|
|
1182
|
+
coordinates = append(coordinates, value)
|
|
1183
|
+
}
|
|
1184
|
+
encoded, _ := json.Marshal(coordinates)
|
|
1185
|
+
return string(encoded)
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
func serveGraphFile(project, file string) (string, error) {
|
|
1189
|
+
relative, err := graph.WirePath(project, file)
|
|
1190
|
+
if err != nil {
|
|
1191
|
+
return "", fmt.Errorf("ttscgraph: relativize shard source %s: %w", file, err)
|
|
1192
|
+
}
|
|
1193
|
+
return relative, nil
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
func rawServeShards(shards map[string]committedServeGraphShard) map[string]serveGraphShard {
|
|
1197
|
+
out := make(map[string]serveGraphShard, len(shards))
|
|
1198
|
+
for key, shard := range shards {
|
|
1199
|
+
out[key] = shard.shard
|
|
1200
|
+
}
|
|
1201
|
+
return out
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
func invalidatedGraphFiles(
|
|
1205
|
+
program *driver.Program,
|
|
1206
|
+
reverse map[string][]string,
|
|
1207
|
+
changed []string,
|
|
1208
|
+
publicChanged []string,
|
|
1209
|
+
) map[string]bool {
|
|
1210
|
+
authored := authoredGraphFiles(program)
|
|
1211
|
+
selected := map[string]bool{}
|
|
1212
|
+
for _, file := range changed {
|
|
1213
|
+
source := graphSourceFile(program, file)
|
|
1214
|
+
if source == nil {
|
|
1215
|
+
return nil
|
|
1216
|
+
}
|
|
1217
|
+
if shimcompiler.FileAffectsGlobalScope(source) {
|
|
1218
|
+
return authored
|
|
1219
|
+
}
|
|
1220
|
+
selected[file] = true
|
|
1221
|
+
}
|
|
1222
|
+
pending := append([]string{}, publicChanged...)
|
|
1223
|
+
for len(pending) > 0 {
|
|
1224
|
+
file := pending[0]
|
|
1225
|
+
pending = pending[1:]
|
|
1226
|
+
source := graphSourceFile(program, file)
|
|
1227
|
+
if source == nil {
|
|
1228
|
+
return nil
|
|
1229
|
+
}
|
|
1230
|
+
if shimcompiler.FileAffectsGlobalScope(source) {
|
|
1231
|
+
return authored
|
|
1232
|
+
}
|
|
1233
|
+
selected[file] = true
|
|
1234
|
+
for _, dependent := range reverse[file] {
|
|
1235
|
+
if !selected[dependent] {
|
|
1236
|
+
selected[dependent] = true
|
|
1237
|
+
pending = append(pending, dependent)
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return selected
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
func authoredGraphFiles(program *driver.Program) map[string]bool {
|
|
1245
|
+
authored := map[string]bool{}
|
|
1246
|
+
for _, file := range program.SourceFiles() {
|
|
1247
|
+
if !graph.IsWorkspaceSourceFile(file) {
|
|
1248
|
+
continue
|
|
1249
|
+
}
|
|
1250
|
+
authored[file.FileName()] = true
|
|
1251
|
+
}
|
|
1252
|
+
return authored
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
func reverseGraphDependencies(program *driver.Program) map[string][]string {
|
|
1256
|
+
reverseSets := map[string]map[string]bool{}
|
|
1257
|
+
authored := map[string]bool{}
|
|
1258
|
+
authoredByPath := map[string]string{}
|
|
1259
|
+
for _, file := range program.SourceFiles() {
|
|
1260
|
+
if !graph.IsWorkspaceSourceFile(file) {
|
|
1261
|
+
continue
|
|
1262
|
+
}
|
|
1263
|
+
authored[file.FileName()] = true
|
|
1264
|
+
authoredByPath[string(file.Path())] = file.FileName()
|
|
1265
|
+
}
|
|
1266
|
+
add := func(target, dependent string) {
|
|
1267
|
+
if !authored[target] || target == dependent {
|
|
1268
|
+
return
|
|
1269
|
+
}
|
|
1270
|
+
values := reverseSets[target]
|
|
1271
|
+
if values == nil {
|
|
1272
|
+
values = map[string]bool{}
|
|
1273
|
+
reverseSets[target] = values
|
|
1274
|
+
}
|
|
1275
|
+
values[dependent] = true
|
|
1276
|
+
}
|
|
1277
|
+
for _, source := range program.SourceFiles() {
|
|
1278
|
+
if !graph.IsWorkspaceSourceFile(source) {
|
|
1279
|
+
continue
|
|
1280
|
+
}
|
|
1281
|
+
for _, referencedPath := range shimcompiler.GetReferencedFilePaths(program.TSProgram, source) {
|
|
1282
|
+
if target, ok := authoredByPath[referencedPath]; ok {
|
|
1283
|
+
add(target, source.FileName())
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
reverse := map[string][]string{}
|
|
1288
|
+
for target, dependents := range reverseSets {
|
|
1289
|
+
values := make([]string, 0, len(dependents))
|
|
1290
|
+
for dependent := range dependents {
|
|
1291
|
+
values = append(values, dependent)
|
|
1292
|
+
}
|
|
1293
|
+
sort.Strings(values)
|
|
1294
|
+
reverse[target] = values
|
|
1295
|
+
}
|
|
1296
|
+
return reverse
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
func serveGraphResolutionDigests(program *driver.Program, project string) (map[string]string, error) {
|
|
1300
|
+
digests := map[string]string{}
|
|
1301
|
+
for _, source := range program.TSProgram.SourceFiles() {
|
|
1302
|
+
raw := shimcompiler.GetReferencedFilePaths(program.TSProgram, source)
|
|
1303
|
+
references := make([]string, 0, len(raw))
|
|
1304
|
+
for _, reference := range raw {
|
|
1305
|
+
target := program.TSProgram.GetSourceFileByPath(shimtspath.Path(reference))
|
|
1306
|
+
if target == nil {
|
|
1307
|
+
continue
|
|
1308
|
+
}
|
|
1309
|
+
file, err := serveGraphFile(project, target.FileName())
|
|
1310
|
+
if err != nil {
|
|
1311
|
+
return nil, err
|
|
1312
|
+
}
|
|
1313
|
+
references = append(references, file)
|
|
1314
|
+
}
|
|
1315
|
+
sort.Strings(references)
|
|
1316
|
+
digest, _ := digestJSON(references)
|
|
1317
|
+
digests[source.FileName()] = digest
|
|
1318
|
+
}
|
|
1319
|
+
return digests, nil
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
func graphSourceFile(program *driver.Program, file string) *shimast.SourceFile {
|
|
1323
|
+
for _, source := range program.SourceFiles() {
|
|
1324
|
+
if graph.IsWorkspaceSourceFile(source) && source.FileName() == file {
|
|
1325
|
+
return source
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
return nil
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
func sortedSelectedFiles(selected map[string]bool) []string {
|
|
1332
|
+
files := make([]string, 0, len(selected))
|
|
1333
|
+
for file := range selected {
|
|
1334
|
+
files = append(files, file)
|
|
1335
|
+
}
|
|
1336
|
+
sort.Strings(files)
|
|
1337
|
+
return files
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
func addPriorExportTargets(selected map[string]bool, prior *serveGraphStore) {
|
|
1341
|
+
for file := range maps.Clone(selected) {
|
|
1342
|
+
shard, ok := prior.shards[prior.sourceKeys[file]]
|
|
1343
|
+
if !ok {
|
|
1344
|
+
continue
|
|
1345
|
+
}
|
|
1346
|
+
for _, edge := range shard.shard.Edges {
|
|
1347
|
+
if edge.Kind != "exports" {
|
|
1348
|
+
continue
|
|
1349
|
+
}
|
|
1350
|
+
target, ok := prior.dumpNodeFiles[edge.To]
|
|
1351
|
+
if ok {
|
|
1352
|
+
selected[target] = true
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
func expandPriorImplementationSources(selected map[string]bool, prior *serveGraphStore) {
|
|
1359
|
+
for {
|
|
1360
|
+
expanded := false
|
|
1361
|
+
for id, sources := range prior.implementationSources {
|
|
1362
|
+
owner := graph.NodeFile(id)
|
|
1363
|
+
affected := selected[owner]
|
|
1364
|
+
if !affected {
|
|
1365
|
+
for source := range sources {
|
|
1366
|
+
if selected[source] {
|
|
1367
|
+
affected = true
|
|
1368
|
+
break
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
if !affected {
|
|
1373
|
+
continue
|
|
1374
|
+
}
|
|
1375
|
+
if owner != "" && !selected[owner] {
|
|
1376
|
+
selected[owner] = true
|
|
1377
|
+
expanded = true
|
|
1378
|
+
}
|
|
1379
|
+
for source := range sources {
|
|
1380
|
+
if !selected[source] {
|
|
1381
|
+
selected[source] = true
|
|
1382
|
+
expanded = true
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
if !expanded {
|
|
1387
|
+
return
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|