@skyramp/mcp 0.4.3-rc.1 → 0.4.3
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/build/recommendation/answers.d.ts +5 -3
- package/build/recommendation/answers.js +5 -2
- package/build/recommendation/verifierContracts.d.ts +8 -0
- package/build/recommendation/verifierContracts.js +13 -0
- package/build/recommendation/verifiers/coverage.js +143 -4
- package/build/tools/code-refactor/removed-modules.d.ts +33 -0
- package/build/tools/code-refactor/removed-modules.js +61 -0
- package/build/tools/code-refactor/reuse-outcome.d.ts +27 -1
- package/build/tools/code-refactor/reuse-record-store.d.ts +5 -0
- package/build/tools/code-refactor/reuse-record-store.js +15 -1
- package/build/tools/code-refactor/reuse-state.d.ts +17 -4
- package/build/tools/code-refactor/reuse-state.js +67 -9
- package/build/tools/code-refactor/utils-verify-gates.js +39 -15
- package/build/tools/submitReportTool.js +47 -18
- package/build/tools/test-management/registerTestPlanTool.js +1 -1
- package/build/types/ReuseOutcome.d.ts +27 -0
- package/build/utils/utils-verify/allow.d.ts +1 -1
- package/build/utils/utils-verify/allow.js +15 -0
- package/build/utils/utils-verify/changed-lines.d.ts +48 -0
- package/build/utils/utils-verify/changed-lines.js +106 -0
- package/build/utils/utils-verify/head.d.ts +32 -7
- package/build/utils/utils-verify/head.js +55 -14
- package/build/utils/utils-verify/typecheck.d.ts +25 -4
- package/build/utils/utils-verify/typecheck.js +95 -13
- package/build/utils/utils-verify/verify.d.ts +24 -5
- package/build/utils/utils-verify/verify.js +156 -8
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +4 -2
- package/node_modules/playwright/lib/mcp/skyramp/snapshotLocators.js +246 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +221 -233
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +21 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.ZYuEFHsi.js → index.uiPTFncY.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +3 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
|
+
import { readFileSync } from "fs";
|
|
2
3
|
import { fileURLToPath } from "url";
|
|
3
4
|
let tsModule;
|
|
4
5
|
/** The compiler, loaded on first use and once per process. Loaded statically it
|
|
@@ -41,8 +42,61 @@ const UNRESOLVED_MODULE_CODES = new Set([2307, 2792]);
|
|
|
41
42
|
* exit was to rewrite working code. Every other package still resolves from the
|
|
42
43
|
* repository first, because every other package is the repository's to pin. */
|
|
43
44
|
const EXECUTOR_PROVIDED_PACKAGES = ["@skyramp/skyramp"];
|
|
45
|
+
/** The PEERS of the executor-provided packages — what their type declarations
|
|
46
|
+
* import and expect the host to install (`@skyramp/skyramp` declares
|
|
47
|
+
* `@playwright/test`, whose `Expect` types its `expect`). Read from each
|
|
48
|
+
* package's own manifest under `mcpRoot`, once per root: the SDK already states
|
|
49
|
+
* the list, and a second copy here would need a test to keep it in step. A peer
|
|
50
|
+
* is resolved from the REPOSITORY first for every file that imports it, the
|
|
51
|
+
* MCP's own copy second — including from inside the MCP's SDK declarations.
|
|
52
|
+
* Otherwise the SDK's copy came from the MCP's install while the spec's own
|
|
53
|
+
* import came from the repository's, and a repository pinned to another
|
|
54
|
+
* Playwright version had two `Locator` identities: every `expect(locator)` in
|
|
55
|
+
* it typed as the base matchers, and four assertion lines that pre-dated the
|
|
56
|
+
* run became blocking `type-error`s against a valid module — which the run then
|
|
57
|
+
* deleted. The customer's `tsc` has one node_modules and never sees the split;
|
|
58
|
+
* the executor runs the repository's Playwright, so the repository's copy is
|
|
59
|
+
* the truth. */
|
|
60
|
+
const peersByRoot = new Map();
|
|
61
|
+
function executorPeerPackages(mcpRoot) {
|
|
62
|
+
let peers = peersByRoot.get(mcpRoot);
|
|
63
|
+
if (peers)
|
|
64
|
+
return peers;
|
|
65
|
+
peers = [];
|
|
66
|
+
for (const pkg of EXECUTOR_PROVIDED_PACKAGES) {
|
|
67
|
+
const manifest = installedManifest(pkg, mcpRoot);
|
|
68
|
+
if (manifest)
|
|
69
|
+
peers.push(...Object.keys(manifest.peerDependencies ?? {}));
|
|
70
|
+
// No manifest reachable from this root: the package resolves from nowhere
|
|
71
|
+
// and its imports are reported as unresolved gaps, so there is no peer to
|
|
72
|
+
// route.
|
|
73
|
+
}
|
|
74
|
+
peersByRoot.set(mcpRoot, peers);
|
|
75
|
+
return peers;
|
|
76
|
+
}
|
|
77
|
+
/** The package's manifest as Node would find it from `from`: the nearest
|
|
78
|
+
* `node_modules/<pkg>` walking UP from there. The MCP's dependencies are not
|
|
79
|
+
* always nested under its own root — `npm install @skyramp/mcp`, testbot's
|
|
80
|
+
* default, hoists them BESIDE `@skyramp/mcp` — and a read at one fixed path
|
|
81
|
+
* found nothing on that layout, left the peer list empty, and brought the two
|
|
82
|
+
* Playwright identities back exactly where customers run. */
|
|
83
|
+
function installedManifest(pkg, from) {
|
|
84
|
+
for (let dir = from;; dir = path.dirname(dir)) {
|
|
85
|
+
try {
|
|
86
|
+
return JSON.parse(readFileSync(path.join(dir, "node_modules", pkg, "package.json"), "utf8"));
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
if (path.dirname(dir) === dir)
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const isPackageOrSubpath = (specifier, packages) => packages.some((p) => specifier === p || specifier.startsWith(`${p}/`));
|
|
44
95
|
function executorProvides(specifier) {
|
|
45
|
-
return
|
|
96
|
+
return isPackageOrSubpath(specifier, EXECUTOR_PROVIDED_PACKAGES);
|
|
97
|
+
}
|
|
98
|
+
function executorPeer(specifier, mcpRoot) {
|
|
99
|
+
return isPackageOrSubpath(specifier, executorPeerPackages(mcpRoot));
|
|
46
100
|
}
|
|
47
101
|
export const AMBIENT_DECLARATION_CODES = new Set([
|
|
48
102
|
2304, 2311, 2318, 2468, 2503, 2552, 2580, 2581, 2582, 2583, 2584, 2591, 2592,
|
|
@@ -211,26 +265,51 @@ function findConfigs(ts, files) {
|
|
|
211
265
|
* an error, and its diagnostics are dropped (everything typed by it is `any` and
|
|
212
266
|
* was not checked — the gap is stated instead).
|
|
213
267
|
*/
|
|
214
|
-
function runPass(ts, files, options, mcpRoot) {
|
|
268
|
+
function runPass(ts, files, options, mcpRoot, repoDir, baseline) {
|
|
215
269
|
const own = new Set(files.map((f) => path.normalize(f)));
|
|
216
270
|
const unresolved = new Set();
|
|
217
271
|
const host = ts.createCompilerHost(options, true);
|
|
272
|
+
if (baseline) {
|
|
273
|
+
// A baseline file is read from the content given, never from disk; a hidden
|
|
274
|
+
// one does not exist to the compiler, so an import of it is unresolved and
|
|
275
|
+
// typed `any`, as it was before the run wrote it.
|
|
276
|
+
const contentOf = (f) => baseline.contents.get(path.normalize(f));
|
|
277
|
+
const hidden = (f) => baseline.hidden.has(path.normalize(f));
|
|
278
|
+
const { fileExists, readFile, getSourceFile } = host;
|
|
279
|
+
host.fileExists = (f) => hidden(f) ? false : contentOf(f) !== undefined || fileExists(f);
|
|
280
|
+
host.readFile = (f) => hidden(f) ? undefined : (contentOf(f) ?? readFile(f));
|
|
281
|
+
host.getSourceFile = (f, lang, onError, shouldCreate) => {
|
|
282
|
+
if (hidden(f))
|
|
283
|
+
return undefined;
|
|
284
|
+
const content = contentOf(f);
|
|
285
|
+
return content === undefined
|
|
286
|
+
? getSourceFile(f, lang, onError, shouldCreate)
|
|
287
|
+
: ts.createSourceFile(f, content, lang, true);
|
|
288
|
+
};
|
|
289
|
+
}
|
|
218
290
|
// Global types (`@types/*` pulled in automatically) are looked up from the current
|
|
219
291
|
// directory upward. That must be the repository, not wherever the MCP process
|
|
220
292
|
// happens to run: the MCP's own `@types/jest` made a bare `expect` compile here
|
|
221
293
|
// that the customer's `tsc` cannot see.
|
|
222
|
-
const repoDir = path.dirname(files[0]);
|
|
223
294
|
host.getCurrentDirectory = () => repoDir;
|
|
224
295
|
// A containing file that does not exist is fine for resolution: only its
|
|
225
296
|
// directory is read, and node_modules is looked up from there.
|
|
226
297
|
const anchor = path.join(mcpRoot, "__skyramp_typecheck_anchor__.ts");
|
|
298
|
+
// The same device for the repository: a peer imported from INSIDE the MCP's
|
|
299
|
+
// install must still resolve from the repository first, and the containing
|
|
300
|
+
// file's directory would find the MCP's copy.
|
|
301
|
+
const repoAnchor = path.join(repoDir, "__skyramp_typecheck_anchor__.ts");
|
|
227
302
|
host.resolveModuleNameLiterals = (literals, containingFile, _r, opts) => literals.map((literal) => {
|
|
228
303
|
const delivered = own.has(path.normalize(containingFile));
|
|
229
|
-
// The executor's own packages are read from the MCP's install first;
|
|
230
|
-
//
|
|
304
|
+
// The executor's own packages are read from the MCP's install first; their
|
|
305
|
+
// peers from the repository first wherever the import sits, so both sides of
|
|
306
|
+
// an SDK type meet ONE copy; everything else from the repository first, the
|
|
307
|
+
// MCP second.
|
|
231
308
|
const [first, second] = executorProvides(literal.text)
|
|
232
309
|
? [anchor, containingFile]
|
|
233
|
-
:
|
|
310
|
+
: executorPeer(literal.text, mcpRoot)
|
|
311
|
+
? [repoAnchor, anchor]
|
|
312
|
+
: [containingFile, anchor];
|
|
234
313
|
let resolved = ts.resolveModuleName(literal.text, first, opts, host);
|
|
235
314
|
if (!resolved.resolvedModule) {
|
|
236
315
|
const other = ts.resolveModuleName(literal.text, second, opts, host);
|
|
@@ -281,15 +360,17 @@ const findingKey = (f) => `${f.file}\0${f.line}\0${f.code}\0${f.message}`;
|
|
|
281
360
|
* every repository whether or not `typescript` is installed there.
|
|
282
361
|
*
|
|
283
362
|
* Two passes over the same files. The PERMISSIVE pass has every strictness flag off:
|
|
284
|
-
* an error it reports fails under any configuration
|
|
285
|
-
*
|
|
286
|
-
*
|
|
363
|
+
* an error it reports fails under any configuration (`errors`) — the verifier
|
|
364
|
+
* blocks on it unless the repository already had it at HEAD, which the same
|
|
365
|
+
* function establishes when called with a `baseline`. The SETTINGS pass runs the
|
|
366
|
+
* repository's own tsconfig, when one reaches the files; what it adds is an error
|
|
367
|
+
* only under those settings and is advisory (`settingErrors`).
|
|
287
368
|
* The executor transpiles without typechecking, so neither pass has run before this
|
|
288
369
|
* one, and a delivered file that fails here reaches the customer's own `tsc` first.
|
|
289
370
|
*
|
|
290
371
|
* Never throws: a compiler failure is a result that says the check did not run.
|
|
291
372
|
*/
|
|
292
|
-
export async function typecheckDelivered(files, mcpRoot = mcpPackageRoot()) {
|
|
373
|
+
export async function typecheckDelivered(files, { mcpRoot = mcpPackageRoot(), anchor, baseline } = {}) {
|
|
293
374
|
const result = {
|
|
294
375
|
ran: false,
|
|
295
376
|
errors: [],
|
|
@@ -310,15 +391,16 @@ export async function typecheckDelivered(files, mcpRoot = mcpPackageRoot()) {
|
|
|
310
391
|
// import is unresolved here, typed `any`, and its unconditional error then
|
|
311
392
|
// surfaces only in the settings pass — downgraded to an advisory.
|
|
312
393
|
const base = (reaching ?? nearest)?.parsed.options ?? {};
|
|
313
|
-
const
|
|
394
|
+
const repoDir = path.dirname(anchor ?? files[0]);
|
|
395
|
+
const floor = runPass(ts, files, permissiveOptions(ts, base), mcpRoot, repoDir, baseline);
|
|
314
396
|
result.ran = true;
|
|
315
397
|
result.errors = floor.findings.filter((f) => !AMBIENT_DECLARATION_CODES.has(f.code));
|
|
316
398
|
result.ambientErrors = floor.findings.filter((f) => AMBIENT_DECLARATION_CODES.has(f.code));
|
|
317
399
|
result.unresolved = [...floor.unresolved].sort();
|
|
318
|
-
if (reaching) {
|
|
400
|
+
if (reaching && !baseline) {
|
|
319
401
|
result.tsconfig = reaching.path;
|
|
320
402
|
const seen = new Set([...floor.findings, ...floor.unresolvedFindings].map(findingKey));
|
|
321
|
-
const settings = runPass(ts, files, settingsOptions(reaching.parsed.options), mcpRoot);
|
|
403
|
+
const settings = runPass(ts, files, settingsOptions(reaching.parsed.options), mcpRoot, repoDir);
|
|
322
404
|
// An import only the repository's resolution refuses is its `tsc`'s error,
|
|
323
405
|
// not a gap: the permissive pass resolved it, so the module exists.
|
|
324
406
|
result.settingErrors = [
|
|
@@ -24,6 +24,13 @@ export interface UtilsVerifyResult {
|
|
|
24
24
|
ok: boolean;
|
|
25
25
|
/** Utils files located for this spec (absolute). Empty = nothing was written or imported. */
|
|
26
26
|
utilsFiles: string[];
|
|
27
|
+
/** Those of `utilsFiles` that HEAD holds — an earlier run's or the customer's. */
|
|
28
|
+
incumbentModules: string[];
|
|
29
|
+
/** Those of `utilsFiles` git could not answer for (outside a repository, or a
|
|
30
|
+
* git failure). Neither an incumbent nor this run's: a caller that states
|
|
31
|
+
* "this run created it" must not say so for these. The rest of `utilsFiles`
|
|
32
|
+
* this run created. */
|
|
33
|
+
headUnknownModules: string[];
|
|
27
34
|
/** Helpers defined across the utils files. */
|
|
28
35
|
helpers: number;
|
|
29
36
|
/** Helper names the delivered spec imports from those files. */
|
|
@@ -86,6 +93,13 @@ export interface UtilsVerifyResult {
|
|
|
86
93
|
* `tsconfig-type-error` advisories; this carries the rest (the tsconfig read, the
|
|
87
94
|
* modules it could not resolve). Absent for Python and JavaScript. */
|
|
88
95
|
typecheck?: UtilsTypecheck;
|
|
96
|
+
/** What the attribution of compiler errors to this run established — with
|
|
97
|
+
* `typecheck`, TypeScript only. `ran` is whether the HEAD compile ran, so an
|
|
98
|
+
* error the repository already had could be excused; when it did not, `reason`
|
|
99
|
+
* says why and every error blocked as this run's. `headUnknown` lists the
|
|
100
|
+
* delivered files git gave no HEAD answer for: each is judged as this run's
|
|
101
|
+
* in full, and the pass says so rather than hide it. */
|
|
102
|
+
attribution?: TypeAttribution;
|
|
89
103
|
/** Modules the spec imports that this pass READ and does not CHECK: the
|
|
90
104
|
* repository's own modules (tracked at HEAD, no Skyramp header) on a path with no
|
|
91
105
|
* in-house checks — integration, today — and a module outside the repository.
|
|
@@ -110,6 +124,11 @@ export interface UtilsVerifyResult {
|
|
|
110
124
|
* Absent when the spec imports no such module. */
|
|
111
125
|
inHouse?: InHouseVerification;
|
|
112
126
|
}
|
|
127
|
+
export interface TypeAttribution {
|
|
128
|
+
ran: boolean;
|
|
129
|
+
reason?: string;
|
|
130
|
+
headUnknown: string[];
|
|
131
|
+
}
|
|
113
132
|
export interface InHouseVerification {
|
|
114
133
|
/** Absolute paths of the in-house modules read. */
|
|
115
134
|
modules: string[];
|
|
@@ -177,17 +196,17 @@ export interface VerifyUtilsParams {
|
|
|
177
196
|
* returned. See status-once.ts.
|
|
178
197
|
* - `type-error` (TypeScript): the delivered module or spec fails the compiler with
|
|
179
198
|
* every strictness flag off — an error under every configuration (a path parameter
|
|
180
|
-
* typed `unknown`, request options built as a record and handed to `sendRequest`)
|
|
181
|
-
*
|
|
182
|
-
* ADVISORY
|
|
199
|
+
* typed `unknown`, request options built as a record and handed to `sendRequest`)
|
|
200
|
+
* — and this run CAUSED it: an error the file already had at HEAD on an
|
|
201
|
+
* unchanged line is the `inherited-type-error` ADVISORY, and an error only the
|
|
202
|
+
* repository's own tsconfig raises is the `tsconfig-type-error` ADVISORY. See
|
|
203
|
+
* typecheckDelivered and inheritedTypeError.
|
|
183
204
|
*
|
|
184
205
|
* Analyses the utils files' INTERNAL invariants — unlike the POM verifier, which
|
|
185
206
|
* checks a spec's cross-file references — so it reads the utils files, not the spec,
|
|
186
207
|
* except to count what the spec imports.
|
|
187
208
|
*/
|
|
188
209
|
export declare function verifyUtils(params: VerifyUtilsParams): Promise<UtilsVerifyResult>;
|
|
189
|
-
/** The compared counts as one phrase, shared by the assertion-loss detail and the
|
|
190
|
-
* PASSED line's note so the two cannot drift. */
|
|
191
210
|
export declare function describeAssertionCount(c: {
|
|
192
211
|
baseline: number;
|
|
193
212
|
delivered: number;
|
|
@@ -20,7 +20,8 @@ import { describeFixedSleeps, fixedSleepReplacement } from "./fixed-sleep.js";
|
|
|
20
20
|
import { singleImporterHelpers } from "./importers.js";
|
|
21
21
|
import { assertsStatusEquality, defaultedStatusParam, pinnedStatusLiteral, restatedStatusAssertions, } from "./status-once.js";
|
|
22
22
|
import { STATUS_ONCE_RULE } from "../../prompts/shared-helper-policy.js";
|
|
23
|
-
import { contentAtHead } from "./head.js";
|
|
23
|
+
import { addedSinceHead, changedSinceHead, contentAtHead, readAtHead, } from "./head.js";
|
|
24
|
+
import { alignLines, BaselineErrors, baselineErrorKey, inheritedTypeError, } from "./changed-lines.js";
|
|
24
25
|
import { incumbentSignatureViolations } from "./incumbent.js";
|
|
25
26
|
import { helperFamilyFor, helperFamilyOf, scenarioNameViolation, utilsSpecFor, utilsSpecForFile, } from "./language-spec.js";
|
|
26
27
|
/** What the compiler says about a name defined twice, which `helper-redefined`
|
|
@@ -52,9 +53,11 @@ const REDECLARATION_CODES = new Set([2393, 2323, 2451]);
|
|
|
52
53
|
* returned. See status-once.ts.
|
|
53
54
|
* - `type-error` (TypeScript): the delivered module or spec fails the compiler with
|
|
54
55
|
* every strictness flag off — an error under every configuration (a path parameter
|
|
55
|
-
* typed `unknown`, request options built as a record and handed to `sendRequest`)
|
|
56
|
-
*
|
|
57
|
-
* ADVISORY
|
|
56
|
+
* typed `unknown`, request options built as a record and handed to `sendRequest`)
|
|
57
|
+
* — and this run CAUSED it: an error the file already had at HEAD on an
|
|
58
|
+
* unchanged line is the `inherited-type-error` ADVISORY, and an error only the
|
|
59
|
+
* repository's own tsconfig raises is the `tsconfig-type-error` ADVISORY. See
|
|
60
|
+
* typecheckDelivered and inheritedTypeError.
|
|
58
61
|
*
|
|
59
62
|
* Analyses the utils files' INTERNAL invariants — unlike the POM verifier, which
|
|
60
63
|
* checks a spec's cross-file references — so it reads the utils files, not the spec,
|
|
@@ -102,6 +105,8 @@ export async function verifyUtils(params) {
|
|
|
102
105
|
const result = {
|
|
103
106
|
ok: true,
|
|
104
107
|
utilsFiles,
|
|
108
|
+
incumbentModules: [],
|
|
109
|
+
headUnknownModules: [],
|
|
105
110
|
helpers: 0,
|
|
106
111
|
imported: 0,
|
|
107
112
|
importedNames: [],
|
|
@@ -136,6 +141,11 @@ export async function verifyUtils(params) {
|
|
|
136
141
|
* kept: both the status advisory's scope and the restatement's remediation
|
|
137
142
|
* turn on it. */
|
|
138
143
|
const incumbentsOfFile = new Map();
|
|
144
|
+
/** Per utils file, the module at HEAD — `undefined` for a module HEAD does not
|
|
145
|
+
* hold. The compiler's attribution reads it: an error the module already had at
|
|
146
|
+
* HEAD is not this run's. */
|
|
147
|
+
const headContentOfFile = new Map();
|
|
148
|
+
const contentOfFile = new Map();
|
|
139
149
|
const allHelpers = [];
|
|
140
150
|
const helpersByFile = new Map();
|
|
141
151
|
const allAllows = [];
|
|
@@ -170,10 +180,17 @@ export async function verifyUtils(params) {
|
|
|
170
180
|
// helpers this run writes, so a defaulted status on an incumbent is not advised
|
|
171
181
|
// — and an incumbent's signature is held to what it was (incumbent.ts). Absent
|
|
172
182
|
// for a module this run created, or outside git: then every helper is this run's.
|
|
173
|
-
const
|
|
183
|
+
const atHead = await readAtHead(file);
|
|
184
|
+
const baseline = "content" in atHead ? atHead.content : undefined;
|
|
185
|
+
if (baseline !== undefined)
|
|
186
|
+
result.incumbentModules.push(file);
|
|
187
|
+
else if ("unknown" in atHead)
|
|
188
|
+
result.headUnknownModules.push(file);
|
|
174
189
|
const headHelpers = baseline === undefined ? [] : parseUtilsFile(baseline, fileSpec);
|
|
175
190
|
const incumbents = new Set(headHelpers.map((h) => h.name));
|
|
176
191
|
incumbentsOfFile.set(file, incumbents);
|
|
192
|
+
headContentOfFile.set(file, baseline);
|
|
193
|
+
contentOfFile.set(file, content);
|
|
177
194
|
pending.push(...incumbentSignatureViolations(headHelpers, helpers, file));
|
|
178
195
|
for (const h of helpers) {
|
|
179
196
|
// A static reading of the signature. An unannotated parameter is an error only
|
|
@@ -431,8 +448,10 @@ export async function verifyUtils(params) {
|
|
|
431
448
|
// The compiler over the delivered files: the shared module(s) and the spec that
|
|
432
449
|
// imports them. The executor transpiles TypeScript without typechecking it, so
|
|
433
450
|
// this is the first typecheck the files meet before the customer's own. An error
|
|
434
|
-
// under the permissive settings fails under every configuration and blocks
|
|
435
|
-
//
|
|
451
|
+
// under the permissive settings fails under every configuration and blocks when
|
|
452
|
+
// this run caused it — one the file already had at HEAD advises (see the
|
|
453
|
+
// attribution below); one only the repository's tsconfig raises advises (see
|
|
454
|
+
// typecheckDelivered).
|
|
436
455
|
// Over the TypeScript files only, by extension: a JavaScript module is not
|
|
437
456
|
// typechecked (its spec would be `checkJs`, which no delivered file relies on),
|
|
438
457
|
// and a JavaScript spec is not compiled beside a TypeScript module.
|
|
@@ -444,7 +463,7 @@ export async function verifyUtils(params) {
|
|
|
444
463
|
const realSpec = await realpath(params.testFile);
|
|
445
464
|
const specFile = realSpec && isTs(realSpec) ? realSpec : undefined;
|
|
446
465
|
const files = specFile ? [...tsUtils, specFile] : tsUtils;
|
|
447
|
-
const tc = await typecheckDelivered(files);
|
|
466
|
+
const tc = await typecheckDelivered(files, { anchor: specFile });
|
|
448
467
|
result.typecheck = tc;
|
|
449
468
|
const subjectOf = (file, line) => file === specFile
|
|
450
469
|
? path.basename(specFile).replace(/\W/g, "_")
|
|
@@ -459,11 +478,96 @@ export async function verifyUtils(params) {
|
|
|
459
478
|
const redefined = new Set(pending
|
|
460
479
|
.filter((v) => v.kind === "helper-redefined")
|
|
461
480
|
.map((v) => `${v.file}\0${v.helper}`));
|
|
481
|
+
// The files as the repository held them BEFORE this run — each at HEAD —
|
|
482
|
+
// compiled once more, so that an error is excused only when the repository
|
|
483
|
+
// already had it on the same line: attribution by CAUSE. A file HEAD does not
|
|
484
|
+
// hold (a module or a spec this run created) is hidden from that compile and
|
|
485
|
+
// every error in it is this run's — the generator's included: a line any
|
|
486
|
+
// pass of this run wrote is the run's to fix, and only what the customer or
|
|
487
|
+
// an earlier run committed is not.
|
|
488
|
+
const baselineContents = new Map();
|
|
489
|
+
const hidden = new Set();
|
|
490
|
+
const attribution = {
|
|
491
|
+
ran: false,
|
|
492
|
+
headUnknown: [...result.headUnknownModules],
|
|
493
|
+
};
|
|
494
|
+
result.attribution = attribution;
|
|
495
|
+
for (const file of tsUtils) {
|
|
496
|
+
const head = headContentOfFile.get(file);
|
|
497
|
+
if (head === undefined)
|
|
498
|
+
hidden.add(file);
|
|
499
|
+
else
|
|
500
|
+
baselineContents.set(file, head);
|
|
501
|
+
}
|
|
502
|
+
if (specFile) {
|
|
503
|
+
const specAtHead = await readAtHead(specFile);
|
|
504
|
+
if ("content" in specAtHead)
|
|
505
|
+
baselineContents.set(specFile, specAtHead.content);
|
|
506
|
+
else {
|
|
507
|
+
hidden.add(specFile);
|
|
508
|
+
if ("unknown" in specAtHead)
|
|
509
|
+
attribution.headUnknown.push(specFile);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// Every OTHER file the program may reach — a page object, an in-house helper,
|
|
513
|
+
// a config module — as HEAD held it too: one the run changed is served from
|
|
514
|
+
// HEAD, one the run added does not exist. Read from the working tree, a
|
|
515
|
+
// page-object edit put its error into both compiles and it was excused as
|
|
516
|
+
// inherited. Only the extensions the compiler reads; nothing under
|
|
517
|
+
// node_modules, which the run does not edit.
|
|
518
|
+
// Nothing to attribute on a clean compile: the HEAD compile — a full second
|
|
519
|
+
// program build — is skipped, and the skip is stated so the field is never
|
|
520
|
+
// read as "attribution ran and excused nothing".
|
|
521
|
+
const baselineErrors = new BaselineErrors();
|
|
522
|
+
if (tc.errors.length === 0)
|
|
523
|
+
attribution.reason = "the delivered files compiled clean";
|
|
524
|
+
else if (baselineContents.size === 0)
|
|
525
|
+
attribution.reason =
|
|
526
|
+
"no delivered file exists at HEAD, so there is nothing to inherit from";
|
|
527
|
+
else if (!(await addBaselineForChangedFiles(root, baselineContents, hidden)))
|
|
528
|
+
attribution.reason =
|
|
529
|
+
"the files this run changed could not be listed, so no file could be read at HEAD";
|
|
530
|
+
else {
|
|
531
|
+
const before = await typecheckDelivered([...baselineContents.keys()], {
|
|
532
|
+
anchor: specFile,
|
|
533
|
+
baseline: { contents: baselineContents, hidden },
|
|
534
|
+
});
|
|
535
|
+
if (!before.ran) {
|
|
536
|
+
// The delivered compile ran and this one did not: something in the HEAD
|
|
537
|
+
// content or the host. Stated, never silent — the old errors then block
|
|
538
|
+
// as this run's, which is the outcome attribution exists to prevent.
|
|
539
|
+
attribution.reason = `the HEAD compile did not run: ${before.reason ?? "unknown cause"}`;
|
|
540
|
+
logger.warning("The HEAD compile did not run — no type error will be excused as inherited", { testFile: params.testFile, reason: before.reason });
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
attribution.ran = true;
|
|
544
|
+
for (const e of before.errors)
|
|
545
|
+
baselineErrors.add(baselineErrorKey(e.file, e.line, e.code, e.message));
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const unchangedOf = new Map();
|
|
549
|
+
for (const [file, before] of baselineContents) {
|
|
550
|
+
const now = file === specFile ? specContent : contentOfFile.get(file);
|
|
551
|
+
if (now !== undefined)
|
|
552
|
+
unchangedOf.set(file, alignLines(before, now));
|
|
553
|
+
}
|
|
554
|
+
const inherited = inheritedTypeError((file) => unchangedOf.get(file), baselineErrors);
|
|
462
555
|
for (const e of tc.errors) {
|
|
463
556
|
const helper = subjectOf(e.file, e.line);
|
|
464
557
|
if (REDECLARATION_CODES.has(e.code) &&
|
|
465
558
|
redefined.has(`${e.file}\0${helper}`))
|
|
466
559
|
continue;
|
|
560
|
+
const why = inherited(e.file, e.line, e.code, e.message);
|
|
561
|
+
if (why !== undefined) {
|
|
562
|
+
result.advisories.push({
|
|
563
|
+
kind: "inherited-type-error",
|
|
564
|
+
file: e.file,
|
|
565
|
+
helper,
|
|
566
|
+
line: e.line,
|
|
567
|
+
detail: `\`TS${e.code}: ${e.message}\` — ${why}`,
|
|
568
|
+
});
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
467
571
|
pending.push({
|
|
468
572
|
kind: "type-error",
|
|
469
573
|
file: e.file,
|
|
@@ -686,6 +790,50 @@ function remediation(helper, literal, incumbent) {
|
|
|
686
790
|
}
|
|
687
791
|
/** The compared counts as one phrase, shared by the assertion-loss detail and the
|
|
688
792
|
* PASSED line's note so the two cannot drift. */
|
|
793
|
+
/** Extensions the compiler reads as source. */
|
|
794
|
+
const COMPILED_EXTENSIONS = new Set([
|
|
795
|
+
".ts",
|
|
796
|
+
".tsx",
|
|
797
|
+
".mts",
|
|
798
|
+
".cts",
|
|
799
|
+
".js",
|
|
800
|
+
".jsx",
|
|
801
|
+
".mjs",
|
|
802
|
+
".cjs",
|
|
803
|
+
".json",
|
|
804
|
+
]);
|
|
805
|
+
/**
|
|
806
|
+
* Add to the baseline every compiled file in the repository that differs from
|
|
807
|
+
* HEAD and is not already accounted for: changed files served from HEAD, added
|
|
808
|
+
* files hidden. Returns whether the listing succeeded — a baseline that cannot
|
|
809
|
+
* be completed must excuse NOTHING, since any file it reads from disk may carry
|
|
810
|
+
* the run's own edit; the caller then compiles no baseline and every error
|
|
811
|
+
* blocks, as before attribution existed.
|
|
812
|
+
*/
|
|
813
|
+
async function addBaselineForChangedFiles(root, contents, hidden) {
|
|
814
|
+
if (!root)
|
|
815
|
+
return true; // outside git nothing has a HEAD; nothing is excused
|
|
816
|
+
const compiled = (f) => COMPILED_EXTENSIONS.has(path.extname(f)) &&
|
|
817
|
+
!f.split(path.sep).includes("node_modules");
|
|
818
|
+
try {
|
|
819
|
+
for (const file of await changedSinceHead(root, { filter: "M" })) {
|
|
820
|
+
if (!compiled(file) || contents.has(file) || hidden.has(file))
|
|
821
|
+
continue;
|
|
822
|
+
const head = await contentAtHead(file, root);
|
|
823
|
+
if (head === undefined)
|
|
824
|
+
return false; // git did not answer: excuse nothing
|
|
825
|
+
contents.set(file, head);
|
|
826
|
+
}
|
|
827
|
+
for (const file of await addedSinceHead(root))
|
|
828
|
+
if (compiled(file) && !contents.has(file))
|
|
829
|
+
hidden.add(file);
|
|
830
|
+
return true;
|
|
831
|
+
}
|
|
832
|
+
catch (err) {
|
|
833
|
+
logger.warning("Could not list the files this run changed — no type error will be excused as inherited", { root, error: String(err) });
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
689
837
|
export function describeAssertionCount(c) {
|
|
690
838
|
return `${c.baseline} assertions at the modularization hand-out, ${c.delivered} delivered (${c.inSpec} in the test, ${c.inHelpers} in the helpers it imports and calls)`;
|
|
691
839
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const SKYRAMP_IMAGE_VERSION = "v1.3.
|
|
2
|
-
export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.
|
|
3
|
-
export declare const WORKER_DOCKER_IMAGE = "skyramp/worker:v1.3.
|
|
1
|
+
export declare const SKYRAMP_IMAGE_VERSION = "v1.3.47";
|
|
2
|
+
export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.47";
|
|
3
|
+
export declare const WORKER_DOCKER_IMAGE = "skyramp/worker:v1.3.47";
|
|
4
4
|
export declare const WORKER_CONTROL_PORT = 35142;
|
package/build/utils/versions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const SKYRAMP_IMAGE_VERSION = "v1.3.
|
|
1
|
+
export const SKYRAMP_IMAGE_VERSION = "v1.3.47";
|
|
2
2
|
export const EXECUTOR_DOCKER_IMAGE = `skyramp/executor:${SKYRAMP_IMAGE_VERSION}`;
|
|
3
3
|
export const WORKER_DOCKER_IMAGE = `skyramp/worker:${SKYRAMP_IMAGE_VERSION}`;
|
|
4
4
|
// Control port the Skyramp worker listens on (SDK `CONTAINER_PORT`).
|
|
@@ -88,10 +88,12 @@ Requested path: ${outputZip}` }],
|
|
|
88
88
|
counter++;
|
|
89
89
|
outputZip = `${base}_${counter}${ext}`;
|
|
90
90
|
}
|
|
91
|
-
const { jsonl: jsonlContent, actionCount, skipped } = (0, import_skyRampExport.buildJsonlContent)(ctx.trackedActions, "chromium", ctx.harPath);
|
|
91
|
+
const { jsonl: jsonlContent, actionCount, skipped, simplified } = (0, import_skyRampExport.buildJsonlContent)(ctx.trackedActions, "chromium", ctx.harPath);
|
|
92
92
|
await (0, import_skyRampExport.writeSkyrampZip)(outputZip, jsonlContent, ctx.harPath);
|
|
93
93
|
const skippedNote = skipped.length ? `
|
|
94
94
|
Skipped (not exportable): ${[...new Set(skipped)].join(", ")}` : "";
|
|
95
|
+
const simplifiedNote = simplified.length ? `
|
|
96
|
+
Exported with a simplified locator (a part the trace format cannot carry was dropped, so the step may match more elements at replay): ${[...new Set(simplified)].join("; ")}` : "";
|
|
95
97
|
const dropped = ctx.droppedActions ?? [];
|
|
96
98
|
const droppedNote = dropped.length ? `
|
|
97
99
|
Failed & dropped (not recorded): ${summarizeDropped(dropped)}` : "";
|
|
@@ -102,7 +104,7 @@ Failed & dropped (not recorded): ${summarizeDropped(dropped)}` : "";
|
|
|
102
104
|
Skyramp zip written to ${outputZip}
|
|
103
105
|
|
|
104
106
|
Contains:
|
|
105
|
-
- skyramp_playwright.txt (${actionCount} actions)${skippedNote}${droppedNote}`
|
|
107
|
+
- skyramp_playwright.txt (${actionCount} actions)${skippedNote}${simplifiedNote}${droppedNote}`
|
|
106
108
|
}]
|
|
107
109
|
};
|
|
108
110
|
};
|