supercov 0.0.43 → 0.0.45

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 (42) hide show
  1. package/README.md +23 -14
  2. package/docs/agent-loop.md +128 -39
  3. package/docs/assertion-agent.md +156 -0
  4. package/docs/assertion-evidence.md +9 -135
  5. package/docs/assertion-maps.md +252 -0
  6. package/docs/assertions.md +82 -0
  7. package/docs/cli.md +38 -23
  8. package/docs/coverage-model.md +18 -6
  9. package/docs/evidence.md +6 -6
  10. package/docs/getting-started.md +60 -72
  11. package/docs/performance.md +4 -4
  12. package/docs/troubleshooting.md +8 -8
  13. package/docs/verification.md +2 -2
  14. package/docs/workspace-isolation.md +1 -1
  15. package/package.json +34 -33
  16. package/runtime/javascript/nodeAssertAdapter.mjs +32 -8
  17. package/runtime/javascript/nodeTest.mjs +13 -5
  18. package/runtime/javascript/runnerEvidence.mjs +33 -11
  19. package/runtime/javascript/runtime.mjs +27 -23
  20. package/schemas/assertions.schema.json +276 -0
  21. package/analyzers/typescript/README.md +0 -55
  22. package/analyzers/typescript/bin/compiler-identity.mjs +0 -78
  23. package/analyzers/typescript/bin/identity.mjs +0 -67
  24. package/analyzers/typescript/bin/query.mjs +0 -29
  25. package/analyzers/typescript/dist/analyze.js +0 -3972
  26. package/analyzers/typescript/dist/archive.js +0 -309
  27. package/analyzers/typescript/dist/build-identity.json +0 -1
  28. package/analyzers/typescript/dist/compiler.js +0 -32
  29. package/analyzers/typescript/dist/frontend.js +0 -75
  30. package/analyzers/typescript/dist/native-frontend.js +0 -271
  31. package/analyzers/typescript/dist/pragmas.js +0 -143
  32. package/analyzers/typescript/dist/types.js +0 -1
  33. package/analyzers/typescript/package.json +0 -27
  34. package/analyzers/typescript/src/analyze.ts +0 -4538
  35. package/analyzers/typescript/src/archive.ts +0 -438
  36. package/analyzers/typescript/src/compiler.ts +0 -49
  37. package/analyzers/typescript/src/frontend.ts +0 -136
  38. package/analyzers/typescript/src/native-frontend.ts +0 -315
  39. package/analyzers/typescript/src/pragmas.ts +0 -218
  40. package/analyzers/typescript/src/types.ts +0 -45
  41. package/analyzers/typescript/tsconfig.json +0 -12
  42. package/docs/code-verification.md +0 -182
@@ -27,13 +27,24 @@ function localFile(file) {
27
27
  return relative(process.cwd(), absolute).split(sep).join("/");
28
28
  }
29
29
  export function runnerTestId(identity) {
30
- const key = [
30
+ const parts = [
31
31
  identity.runner,
32
32
  localFile(identity.file) ?? "unknown",
33
33
  identity.line ?? 0,
34
34
  identity.column ?? 0,
35
35
  identity.name,
36
- ].join("\0");
36
+ ];
37
+ // Preserve existing top-level, uniquely registered test IDs. Nested tests
38
+ // and repeated registrations need more than a shared source/name identity.
39
+ if (identity.parentTestId)
40
+ parts.push("parent", identity.parentTestId);
41
+ if (identity.registrationOrdinal)
42
+ parts.push("registration", identity.registrationOrdinal);
43
+ // Titles can themselves contain separator text. Domain-separate and encode
44
+ // the extended identity structurally so it cannot alias a literal title.
45
+ const key = identity.parentTestId || identity.registrationOrdinal
46
+ ? JSON.stringify(["registration-v2", ...parts])
47
+ : parts.join("\0");
37
48
  return `${identity.runner}:${createHash("sha256").update(key).digest("hex").slice(0, 24)}`;
38
49
  }
39
50
  export function runnerExecutionScope(identity) {
@@ -50,24 +61,35 @@ export function runnerExecutionScope(identity) {
50
61
  testId,
51
62
  testKey,
52
63
  retry,
53
- attemptId: `${testKey}-${retry}`,
64
+ // Different processes/worker threads can execute the same registration.
65
+ // Their files must not collide even though the stable test ID is shared.
66
+ attemptId: `${testKey}-${retry}-${processInstanceToken()}`,
54
67
  };
55
68
  }
56
- export function callerLocation(ignored) {
57
- const lines = new Error().stack?.split("\n").slice(2) ?? [];
58
- for (const entry of lines) {
59
- if (ignored.test(entry) || entry.includes("node:internal"))
60
- continue;
61
- const match = /(?:\(|at\s+)(file:\/\/[^:)]+|(?:[A-Za-z]:)?[^():]+):(\d+):(\d+)\)?$/.exec(entry.trim());
69
+ export function callerLocation(boundary = callerLocation) {
70
+ // Omit the registration wrapper by identity. A user file may have the same
71
+ // basename as a runtime adapter. Parse the location suffix, not individual
72
+ // path segments: spaces, parentheses and Unicode are valid filenames.
73
+ try {
74
+ const error = {};
75
+ Error.captureStackTrace(error, boundary);
76
+ if (typeof error.stack !== "string")
77
+ return {};
78
+ const entry = error.stack.split("\n")[1]?.trim();
79
+ const match = entry && /(?:^at (?:async )?| \()((?:file:\/\/|\/|[A-Za-z]:[\\/]|\\\\).*):(\d+):(\d+)\)?$/.exec(entry);
62
80
  if (!match)
63
- continue;
81
+ return {};
64
82
  return {
65
83
  file: match[1],
66
84
  line: Number(match[2]),
67
85
  column: Number(match[3]),
68
86
  };
69
87
  }
70
- return {};
88
+ catch {
89
+ // Custom formatters must not prevent registration. Missing provenance
90
+ // is explicit, never replaced with an unrelated deeper caller.
91
+ return {};
92
+ }
71
93
  }
72
94
  export function readScopedServerEvidence(scope, evidencePath = serverEvidencePath(scope)) {
73
95
  try {
@@ -144,9 +144,6 @@ function createState() {
144
144
  decisions: /* @__PURE__ */ new Map(),
145
145
  hits: /* @__PURE__ */ new Set(),
146
146
  events: [],
147
- // Position of the test-file statement currently executing ("file:line:column"), set by the
148
- // instrumented test module; production hits recorded meanwhile are attributed to it.
149
- testStatement: void 0,
150
147
  // Per value-position logical expression (`a && b`, `a || b`, `a ?? b`): the distinct
151
148
  // (right operand evaluated?, result truthy?) pairs seen, keyed by branch id.
152
149
  logicals: /* @__PURE__ */ new Map(),
@@ -317,7 +314,6 @@ function resetCoverage(testId2) {
317
314
  state.decisions.clear();
318
315
  state.hits.clear();
319
316
  state.events.length = 0;
320
- state.testStatement = void 0;
321
317
  state.logicals.clear();
322
318
  state.eventKeys.clear();
323
319
  state.probeV2ContextEpochs.clear();
@@ -721,9 +717,11 @@ function withCoverageCarrier(carrier, callback) {
721
717
  function assertionPhaseState(scope) {
722
718
  const key = attemptKey(scope);
723
719
  const existing = state.assertionPhases.get(key);
724
- if (existing)
720
+ if (existing) {
721
+ existing.phaseIds ??= new Set(existing.phases.filter(phase => phase.kind === "assertion").map(phase => phase.id));
725
722
  return existing;
726
- const created = { counter: 0, phases: [] };
723
+ }
724
+ const created = { counter: 0, phases: [], phaseIds: new Set() };
727
725
  state.assertionPhases.set(key, created);
728
726
  return created;
729
727
  }
@@ -753,9 +751,13 @@ function withNodeAssertionPhase(operation, source, callback) {
753
751
  const scope = context.scope;
754
752
  if (!scope)
755
753
  return callback();
756
- const existing = context.phaseId ? assertionPhaseState(scope).phases.find((phase2) => phase2.id === context.phaseId && phase2.kind === "assertion") : void 0;
754
+ const existing = context.phaseId && assertionPhaseState(scope).phaseIds.has(context.phaseId);
757
755
  if (existing)
758
756
  return callback();
757
+ // A lexical occurrence already identifies its source. Stack fallback is lazy
758
+ // and qualified so transformed coordinates cannot impersonate original ones.
759
+ if (typeof source === "function")
760
+ source = source();
759
761
  const bridged = (_a8 = runtimeGlobal.__SUPERCOV_ASSERTION_PHASE_BRIDGE__) == null ? void 0 : _a8.call(runtimeGlobal, operation, source, callback);
760
762
  if (bridged == null ? void 0 : bridged.handled)
761
763
  return bridged.value;
@@ -768,6 +770,7 @@ function withNodeAssertionPhase(operation, source, callback) {
768
770
  startedAtMs: Date.now()
769
771
  });
770
772
  attempt.phases.push(phase);
773
+ attempt.phaseIds.add(phase.id);
771
774
  try {
772
775
  const result = withCoverageCarrier({ version: 1, scope, phaseId: phase.id }, callback);
773
776
  if (result && typeof result.then === "function")
@@ -785,6 +788,13 @@ function withNodeAssertionPhase(operation, source, callback) {
785
788
  throw cleanInstrumentationStack(error);
786
789
  }
787
790
  }
791
+ // Callee binding preserves receiver, getter/evaluation order, spreads and the
792
+ // original await/yield placement. Only the matcher call opens the phase.
793
+ function bindNodeAssertionPhase(operation, source, target, property = null) {
794
+ const callback = property === null ? target : target[property];
795
+ const receiver = property === null ? undefined : target;
796
+ return (...args) => withNodeAssertionPhase(operation, source, () => Reflect.apply(callback, receiver, args));
797
+ }
788
798
  function takeNodeAssertionPhases(scope) {
789
799
  var _a8, _b;
790
800
  const key = attemptKey(scope);
@@ -978,31 +988,25 @@ function recordBrowserEvent(event) {
978
988
  state.events.push(event);
979
989
  return true;
980
990
  }
981
- function testStatement(source) {
982
- state.testStatement = typeof source === "string" && source.length > 0 ? source : void 0;
983
- }
984
- function statementFields() {
985
- return state.testStatement ? { statementId: state.testStatement } : {};
986
- }
987
991
  function coverageHit(id) {
988
992
  state.hits.add(id);
989
993
  const timestampMs = Date.now();
990
994
  const phaseId = currentPhaseId();
991
995
  if (isBrowser) {
992
- if (recordBrowserEvent(__spreadProps(__spreadValues(__spreadValues({
996
+ if (recordBrowserEvent(__spreadProps(__spreadValues({
993
997
  type: "hit",
994
998
  id,
995
999
  timestampMs
996
- }, phaseId ? { phaseId } : {}), statementFields()), {
1000
+ }, phaseId ? { phaseId } : {}), {
997
1001
  environment: "browser"
998
1002
  })))
999
1003
  persistBrowser();
1000
1004
  } else {
1001
- appendServer(__spreadValues(__spreadValues({
1005
+ appendServer(__spreadValues({
1002
1006
  type: "hit",
1003
1007
  id,
1004
1008
  timestampMs
1005
- }, phaseId ? { phaseId } : {}), statementFields()));
1009
+ }, phaseId ? { phaseId } : {}));
1006
1010
  }
1007
1011
  }
1008
1012
  function registerProbeV2(definition) {
@@ -1237,22 +1241,22 @@ function mcdcEnd(frame, value) {
1237
1241
  const timestampMs = Date.now();
1238
1242
  const phaseId = currentPhaseId();
1239
1243
  if (isBrowser) {
1240
- if (recordBrowserEvent(__spreadProps(__spreadValues(__spreadValues({
1244
+ if (recordBrowserEvent(__spreadProps(__spreadValues({
1241
1245
  type: "decision",
1242
1246
  id: decision.meta.id,
1243
1247
  vector,
1244
1248
  timestampMs
1245
- }, phaseId ? { phaseId } : {}), statementFields()), {
1249
+ }, phaseId ? { phaseId } : {}), {
1246
1250
  environment: "browser"
1247
1251
  })))
1248
1252
  persistBrowser();
1249
1253
  } else {
1250
- appendServer(__spreadValues(__spreadValues({
1254
+ appendServer(__spreadValues({
1251
1255
  type: "decision",
1252
1256
  meta: decision.meta,
1253
1257
  vector,
1254
1258
  timestampMs
1255
- }, phaseId ? { phaseId } : {}), statementFields()));
1259
+ }, phaseId ? { phaseId } : {}));
1256
1260
  }
1257
1261
  return value;
1258
1262
  }
@@ -1293,12 +1297,12 @@ const directRuntimeApi = {
1293
1297
  selectionEnd,
1294
1298
  selectionRight,
1295
1299
  takeNodeAssertionPhases,
1296
- testStatement,
1297
1300
  tryBegin,
1298
1301
  tryCatch,
1299
1302
  tryEnd,
1300
1303
  withCoverageCarrier,
1301
1304
  withNodeAssertionPhase,
1305
+ bindNodeAssertionPhase,
1302
1306
  withRequestPhase,
1303
1307
  writeExclusiveBackgroundRecord
1304
1308
  };
@@ -1343,12 +1347,12 @@ export {
1343
1347
  selectionEnd,
1344
1348
  selectionRight,
1345
1349
  takeNodeAssertionPhases,
1346
- testStatement,
1347
1350
  tryBegin,
1348
1351
  tryCatch,
1349
1352
  tryEnd,
1350
1353
  withCoverageCarrier,
1351
1354
  withNodeAssertionPhase,
1355
+ bindNodeAssertionPhase,
1352
1356
  withRequestPhase,
1353
1357
  writeExclusiveBackgroundRecord
1354
1358
  };
@@ -0,0 +1,276 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "AssertionMap",
4
+ "type": "object",
5
+ "properties": {
6
+ "schemaVersion": {
7
+ "type": "integer",
8
+ "format": "uint32",
9
+ "minimum": 2,
10
+ "maximum": 2
11
+ },
12
+ "assertions": {
13
+ "type": "array",
14
+ "items": {
15
+ "$ref": "#/$defs/Assertion"
16
+ }
17
+ },
18
+ "changeAssessments": {
19
+ "type": "array",
20
+ "items": {
21
+ "$ref": "#/$defs/ChangeAssessment"
22
+ }
23
+ },
24
+ "retiredAssertions": {
25
+ "type": "array",
26
+ "items": {
27
+ "$ref": "#/$defs/Retired"
28
+ }
29
+ }
30
+ },
31
+ "additionalProperties": false,
32
+ "required": [
33
+ "schemaVersion",
34
+ "assertions"
35
+ ],
36
+ "$defs": {
37
+ "Assertion": {
38
+ "type": "object",
39
+ "properties": {
40
+ "id": {
41
+ "type": "string"
42
+ },
43
+ "at": {
44
+ "$ref": "#/$defs/Anchor"
45
+ },
46
+ "questions": {
47
+ "type": "array",
48
+ "items": {
49
+ "type": "string"
50
+ }
51
+ },
52
+ "observes": {
53
+ "type": "array",
54
+ "items": {
55
+ "type": "string"
56
+ },
57
+ "default": []
58
+ },
59
+ "flows": {
60
+ "type": "array",
61
+ "items": {
62
+ "$ref": "#/$defs/Flow"
63
+ },
64
+ "default": []
65
+ }
66
+ },
67
+ "additionalProperties": false,
68
+ "required": [
69
+ "id",
70
+ "at"
71
+ ]
72
+ },
73
+ "Anchor": {
74
+ "description": "One-based lines and UTF-8 byte columns, for every language. Text is exact.",
75
+ "type": "object",
76
+ "properties": {
77
+ "file": {
78
+ "type": "string"
79
+ },
80
+ "line": {
81
+ "type": "integer",
82
+ "format": "uint",
83
+ "minimum": 0
84
+ },
85
+ "column": {
86
+ "type": "integer",
87
+ "format": "uint",
88
+ "minimum": 0
89
+ },
90
+ "text": {
91
+ "type": "string"
92
+ }
93
+ },
94
+ "additionalProperties": false,
95
+ "required": [
96
+ "file",
97
+ "line",
98
+ "column",
99
+ "text"
100
+ ]
101
+ },
102
+ "Flow": {
103
+ "type": "object",
104
+ "properties": {
105
+ "id": {
106
+ "type": "string"
107
+ },
108
+ "basis": {
109
+ "type": [
110
+ "string",
111
+ "null"
112
+ ],
113
+ "pattern": "^scov2:[0-9a-f]{64}$"
114
+ },
115
+ "explanation": {
116
+ "type": "string"
117
+ },
118
+ "appliesTo": {
119
+ "type": "array",
120
+ "items": {
121
+ "$ref": "#/$defs/TestSelector"
122
+ }
123
+ },
124
+ "nodes": {
125
+ "type": "array",
126
+ "items": {
127
+ "$ref": "#/$defs/Node"
128
+ }
129
+ },
130
+ "edges": {
131
+ "type": "array",
132
+ "items": {
133
+ "$ref": "#/$defs/Edge"
134
+ },
135
+ "default": []
136
+ },
137
+ "countsAsAsserted": {
138
+ "type": "array",
139
+ "items": {
140
+ "type": "string"
141
+ }
142
+ },
143
+ "watch": {
144
+ "type": "array",
145
+ "items": {
146
+ "type": "string"
147
+ }
148
+ },
149
+ "questions": {
150
+ "type": "array",
151
+ "items": {
152
+ "type": "string"
153
+ }
154
+ }
155
+ },
156
+ "additionalProperties": false,
157
+ "required": [
158
+ "id",
159
+ "basis",
160
+ "explanation",
161
+ "appliesTo",
162
+ "nodes",
163
+ "countsAsAsserted",
164
+ "watch"
165
+ ]
166
+ },
167
+ "TestSelector": {
168
+ "type": "object",
169
+ "properties": {
170
+ "file": {
171
+ "type": "string"
172
+ },
173
+ "name": {
174
+ "type": "string"
175
+ }
176
+ },
177
+ "additionalProperties": false,
178
+ "required": [
179
+ "file",
180
+ "name"
181
+ ]
182
+ },
183
+ "Node": {
184
+ "type": "object",
185
+ "properties": {
186
+ "id": {
187
+ "type": "string"
188
+ },
189
+ "at": {
190
+ "$ref": "#/$defs/Anchor"
191
+ },
192
+ "role": {
193
+ "type": "string"
194
+ },
195
+ "meaning": {
196
+ "type": "string"
197
+ }
198
+ },
199
+ "additionalProperties": false,
200
+ "required": [
201
+ "id",
202
+ "at"
203
+ ]
204
+ },
205
+ "Edge": {
206
+ "type": "object",
207
+ "properties": {
208
+ "from": {
209
+ "type": "string"
210
+ },
211
+ "to": {
212
+ "type": "string"
213
+ },
214
+ "kind": {
215
+ "type": "string"
216
+ },
217
+ "basis": {
218
+ "type": "string"
219
+ }
220
+ },
221
+ "additionalProperties": false,
222
+ "required": [
223
+ "from",
224
+ "to",
225
+ "kind"
226
+ ]
227
+ },
228
+ "ChangeAssessment": {
229
+ "type": "object",
230
+ "properties": {
231
+ "id": {
232
+ "type": "string"
233
+ },
234
+ "basis": {
235
+ "type": [
236
+ "string",
237
+ "null"
238
+ ],
239
+ "pattern": "^scov2:[0-9a-f]{64}$"
240
+ },
241
+ "affectedFlows": {
242
+ "type": "array",
243
+ "items": {
244
+ "type": "string"
245
+ }
246
+ },
247
+ "explanation": {
248
+ "type": "string"
249
+ }
250
+ },
251
+ "additionalProperties": false,
252
+ "required": [
253
+ "id",
254
+ "basis",
255
+ "affectedFlows",
256
+ "explanation"
257
+ ]
258
+ },
259
+ "Retired": {
260
+ "type": "object",
261
+ "properties": {
262
+ "assertion": {
263
+ "$ref": "#/$defs/Assertion"
264
+ },
265
+ "reason": {
266
+ "type": "string"
267
+ }
268
+ },
269
+ "additionalProperties": false,
270
+ "required": [
271
+ "assertion",
272
+ "reason"
273
+ ]
274
+ }
275
+ }
276
+ }
@@ -1,55 +0,0 @@
1
- # JS/TS assertion analyzer (internal)
2
-
3
- This private package is the post-run analysis component used by Supercov's
4
- `runs <run> assertions` command. It is bundled in the npm distribution, not
5
- published as a separate CLI or SDK.
6
-
7
- See [Assertion evidence](../../docs/assertion-evidence.md) for public commands,
8
- pragma syntax, report interpretation and supported scope.
9
-
10
- ## Product boundary
11
-
12
- - `bin/query.mjs` is the CLI-to-analyzer JSON transport. It accepts an ordinary
13
- archive representation prepared by Rust, validates identities and returns facts.
14
- - `bin/identity.mjs` checks the analyzer source/build identity; its direct
15
- invocation writes that identity during the build.
16
- - `bin/compiler-identity.mjs` fingerprints the project's compiler implementation.
17
- - `src/archive.ts` validates the archive protocol, source positions and attempt
18
- ownership, then constructs query-local evidence in memory.
19
- - `src/analyze.ts` extracts source-flow facts and exact assertion witnesses.
20
- The Rust engine joins those facts into candidate classifications.
21
- - `src/frontend.ts` and `src/native-frontend.ts` implement the legacy and native
22
- TypeScript compiler boundaries. `src/pragmas.ts` validates optional hints.
23
-
24
- There is no converted-input command, on-disk research input, V8 remapping,
25
- mutation runner or diagnostic-ablation option in the product interface.
26
- Source files ship to support build-identity verification; tests and research
27
- tools do not ship. Assertion candidates remain unverified, not safety proofs.
28
- Only executable JavaScript and its build identity ship from `dist`; there is no
29
- public SDK declaration surface or generated source-map payload.
30
-
31
- ## Build and test
32
-
33
- From this directory:
34
-
35
- ```sh
36
- npm ci --ignore-scripts
37
- npm test
38
- ```
39
-
40
- The build uses pinned TypeScript 5.8.3 explicitly. Project analysis uses the
41
- project's compiler; native 7.0.2 has a separate frontend and identity checks.
42
- Native compiler processes close after both successful and failed queries.
43
-
44
- From the repository root, exercise real Node/Vitest archives and the installed
45
- npm package:
46
-
47
- ```sh
48
- npm run test:asserted-integration
49
- npm run test:asserted-package
50
- ```
51
-
52
- Controlled unit fixtures test parser, flow and witness rules. The ordinary-archive
53
- integration tests execute real suites and explicit behavioral counterexamples.
54
- Compiler-path and identity regressions run across supported CI platforms.
55
- Historical external-cohort research is not part of the default suite or product.
@@ -1,78 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { readFileSync, readdirSync } from "node:fs";
3
- import { dirname, resolve } from "node:path";
4
- import { createRequire } from "node:module";
5
- import { pathToFileURL } from "node:url";
6
- import { projectCompilerPath } from "../dist/compiler.js";
7
-
8
- /** Hash the native implementation as well as its JS client; version.cjs alone is not the compiler. */
9
- export function compilerIdentity(projectRoot) {
10
- return compilerIdentityFromEntry(projectCompilerPath(projectRoot));
11
- }
12
-
13
- export function compilerIdentityFromEntry(entry) {
14
- const require = createRequire(pathToFileURL(entry));
15
- const { version } = require(entry);
16
- const entrySha256 = createHash("sha256")
17
- .update(readFileSync(entry))
18
- .digest("hex");
19
- if (version !== "7.0.2")
20
- return { backend: "typescript-legacy", version, sha256: entrySha256 };
21
- const packageRoot = dirname(require.resolve("typescript/package.json"));
22
- const platform = `@typescript/typescript-${process.platform}-${process.arch}`;
23
- let nativeRoot;
24
- try {
25
- nativeRoot = dirname(require.resolve(`${platform}/package.json`));
26
- } catch (cause) {
27
- throw new Error(
28
- `TypeScript 7's native compiler dependency ${platform} is missing. Install the project's optional TypeScript dependencies before recapturing tests.`,
29
- { cause },
30
- );
31
- }
32
- const nativeMetadata = JSON.parse(
33
- readFileSync(resolve(nativeRoot, "package.json"), "utf8"),
34
- );
35
- if (nativeMetadata.name !== platform || nativeMetadata.version !== version)
36
- throw new Error(
37
- `TypeScript native package identity mismatch: expected ${platform}@${version}`,
38
- );
39
- const executable = resolve(
40
- nativeRoot,
41
- "lib",
42
- process.platform === "win32" ? "tsc.exe" : "tsc",
43
- );
44
- const nativeSha256 = createHash("sha256")
45
- .update(readFileSync(executable))
46
- .digest("hex");
47
- const hash = createHash("sha256");
48
- let files = 0;
49
- function walk(root, relative = "") {
50
- for (const file of readdirSync(resolve(root, relative), {
51
- withFileTypes: true,
52
- }).sort((a, b) => a.name.localeCompare(b.name))) {
53
- if (file.name === "node_modules") continue;
54
- const path = relative ? `${relative}/${file.name}` : file.name;
55
- if (file.isDirectory()) walk(root, path);
56
- else if (file.isFile()) {
57
- hash
58
- .update(path)
59
- .update("\0")
60
- .update(readFileSync(resolve(root, path)))
61
- .update("\0");
62
- files++;
63
- } else throw new Error(`Unsupported compiler package entry: ${path}`);
64
- }
65
- }
66
- hash.update("typescript-package\0");
67
- walk(packageRoot);
68
- hash.update("native-package\0");
69
- walk(nativeRoot);
70
- return {
71
- backend: "typescript-native-7",
72
- version,
73
- sha256: hash.digest("hex"),
74
- nativePackage: platform,
75
- nativeSha256,
76
- files,
77
- };
78
- }