@piwitests/reporter 0.59.0 → 0.61.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/dist/cli/index.js +1520 -1307
- package/dist/global-setup-module.js +203 -63
- package/dist/index.d.ts +36 -2
- package/dist/index.js +1973 -584
- package/dist/internal/capture/attachments.d.ts +3 -0
- package/dist/internal/capture/attachments.js +4 -1
- package/dist/internal/capture/capture-fixtures.js +1363 -373
- package/dist/internal/capture/pick-on-failure.d.ts +6 -3
- package/dist/internal/capture/pick-on-failure.js +49 -30
- package/package.json +5 -5
- package/templates/skills/run-the-right-tests/SKILL.md +19 -8
- package/templates/skills/setup-piwi/SKILL.md +4 -1
package/dist/cli/index.js
CHANGED
|
@@ -788,10 +788,13 @@ async function runAi(argv, env = process.env) {
|
|
|
788
788
|
}
|
|
789
789
|
|
|
790
790
|
// src/cli/bug.ts
|
|
791
|
-
var
|
|
792
|
-
var
|
|
791
|
+
var fs6 = __toESM(require("fs"));
|
|
792
|
+
var path9 = __toESM(require("path"));
|
|
793
793
|
var import_node_child_process2 = require("child_process");
|
|
794
794
|
|
|
795
|
+
// src/internal/config/env.ts
|
|
796
|
+
var path8 = __toESM(require("path"));
|
|
797
|
+
|
|
795
798
|
// src/internal/config/desktop.ts
|
|
796
799
|
var fs4 = __toESM(require("fs"));
|
|
797
800
|
var os = __toESM(require("os"));
|
|
@@ -799,6 +802,14 @@ var path7 = __toESM(require("path"));
|
|
|
799
802
|
function defaultDesktopConfigPath() {
|
|
800
803
|
return path7.join(os.homedir(), ".piwi", "desktop.json");
|
|
801
804
|
}
|
|
805
|
+
function processAlive(pid) {
|
|
806
|
+
try {
|
|
807
|
+
process.kill(pid, 0);
|
|
808
|
+
return true;
|
|
809
|
+
} catch (error) {
|
|
810
|
+
return error.code === "EPERM";
|
|
811
|
+
}
|
|
812
|
+
}
|
|
802
813
|
function readDesktopConfig(filePath2) {
|
|
803
814
|
try {
|
|
804
815
|
const parsed = JSON.parse(fs4.readFileSync(filePath2, "utf8"));
|
|
@@ -806,12 +817,59 @@ function readDesktopConfig(filePath2) {
|
|
|
806
817
|
const token = parsed?.token;
|
|
807
818
|
if (typeof url !== "string" || !url) return null;
|
|
808
819
|
if (typeof token !== "string" || !token) return null;
|
|
820
|
+
if (Number.isInteger(parsed?.pid) && parsed.pid > 0 && !processAlive(parsed.pid)) return null;
|
|
809
821
|
return { url, token };
|
|
810
822
|
} catch {
|
|
811
823
|
return null;
|
|
812
824
|
}
|
|
813
825
|
}
|
|
814
826
|
|
|
827
|
+
// src/internal/config/dotenv.ts
|
|
828
|
+
var fs5 = __toESM(require("fs"));
|
|
829
|
+
|
|
830
|
+
// ../core/src/dotenv.ts
|
|
831
|
+
function parseDotEnv(text) {
|
|
832
|
+
const out = {};
|
|
833
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
834
|
+
const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(raw);
|
|
835
|
+
if (!m) continue;
|
|
836
|
+
let value = m[2];
|
|
837
|
+
const quote3 = value[0];
|
|
838
|
+
if ((quote3 === '"' || quote3 === "'") && value.length > 1 && value.endsWith(quote3)) {
|
|
839
|
+
value = value.slice(1, -1);
|
|
840
|
+
if (quote3 === '"') value = value.replace(/\\n/g, "\n").replace(/\\"/g, '"');
|
|
841
|
+
} else {
|
|
842
|
+
value = value.replace(/\s+#.*$/, "");
|
|
843
|
+
}
|
|
844
|
+
out[m[1]] = value;
|
|
845
|
+
}
|
|
846
|
+
return out;
|
|
847
|
+
}
|
|
848
|
+
function resolvePiwiConnection(sources) {
|
|
849
|
+
const own = {
|
|
850
|
+
serverUrl: sources.flags?.serverUrl || sources.env?.PIWI_DASHBOARD_URL,
|
|
851
|
+
apiKey: sources.flags?.apiKey || sources.env?.PIWI_API_KEY
|
|
852
|
+
};
|
|
853
|
+
const workspace = { serverUrl: sources.dotEnv?.PIWI_DASHBOARD_URL, apiKey: sources.dotEnv?.PIWI_API_KEY };
|
|
854
|
+
const project = sources.flags?.project || sources.env?.PIWI_PROJECT_NAME || sources.dotEnv?.PIWI_PROJECT_NAME || "";
|
|
855
|
+
const desktop = sources.desktop ?? null;
|
|
856
|
+
const trim = (url) => url.replace(/\/+$/, "");
|
|
857
|
+
const layer = own.serverUrl ? own : workspace.serverUrl ? workspace : null;
|
|
858
|
+
const serverUrl = layer ? trim(layer.serverUrl) : desktop ? trim(desktop.url) : "";
|
|
859
|
+
if (!serverUrl) return null;
|
|
860
|
+
const desktopKey = desktop && trim(desktop.url) === serverUrl ? desktop.token : null;
|
|
861
|
+
return { serverUrl, apiKey: layer?.apiKey || desktopKey || null, project };
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// src/internal/config/dotenv.ts
|
|
865
|
+
function readDotEnv(filePath2) {
|
|
866
|
+
try {
|
|
867
|
+
return parseDotEnv(fs5.readFileSync(filePath2, "utf-8"));
|
|
868
|
+
} catch {
|
|
869
|
+
return {};
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
815
873
|
// src/internal/config/env.ts
|
|
816
874
|
var DEFAULTS = {
|
|
817
875
|
projectName: "default-project",
|
|
@@ -848,6 +906,7 @@ var DEFAULTS = {
|
|
|
848
906
|
};
|
|
849
907
|
var PIWI_ENV_KEYS = {
|
|
850
908
|
serverUrl: "PIWI_DASHBOARD_URL",
|
|
909
|
+
desktopApp: "PIWI_DESKTOP_APP",
|
|
851
910
|
projectName: "PIWI_PROJECT_NAME",
|
|
852
911
|
verbose: "PIWI_VERBOSE",
|
|
853
912
|
apiKey: "PIWI_API_KEY",
|
|
@@ -873,6 +932,7 @@ var PIWI_ENV_KEYS = {
|
|
|
873
932
|
codeReachRoots: "PIWI_CODE_REACH_ROOTS",
|
|
874
933
|
coverageBrowser: "PIWI_COVERAGE_BROWSER",
|
|
875
934
|
coverageServer: "PIWI_COVERAGE_SERVER",
|
|
935
|
+
coverageCss: "PIWI_COVERAGE_CSS",
|
|
876
936
|
externalIdPattern: "PIWI_EXTERNAL_ID_PATTERN",
|
|
877
937
|
externalIdOnDuplicate: "PIWI_EXTERNAL_ID_ON_DUPLICATE",
|
|
878
938
|
captureServerTraces: "PIWI_CAPTURE_SERVER_TRACES",
|
|
@@ -1041,7 +1101,7 @@ function repositoryRoot(cwd) {
|
|
|
1041
1101
|
encoding: "utf-8",
|
|
1042
1102
|
stdio: ["ignore", "pipe", "ignore"]
|
|
1043
1103
|
}).trim();
|
|
1044
|
-
return out ?
|
|
1104
|
+
return out ? path9.resolve(out) : null;
|
|
1045
1105
|
} catch {
|
|
1046
1106
|
return null;
|
|
1047
1107
|
}
|
|
@@ -1049,20 +1109,20 @@ function repositoryRoot(cwd) {
|
|
|
1049
1109
|
function realFolder(dir) {
|
|
1050
1110
|
const rest = [];
|
|
1051
1111
|
let current = dir;
|
|
1052
|
-
while (!
|
|
1053
|
-
rest.unshift(
|
|
1054
|
-
current =
|
|
1112
|
+
while (!fs6.existsSync(current) && path9.dirname(current) !== current) {
|
|
1113
|
+
rest.unshift(path9.basename(current));
|
|
1114
|
+
current = path9.dirname(current);
|
|
1055
1115
|
}
|
|
1056
|
-
return
|
|
1116
|
+
return path9.join(fs6.realpathSync(current), ...rest);
|
|
1057
1117
|
}
|
|
1058
1118
|
function folderInRepository(root, dir) {
|
|
1059
|
-
const relative9 =
|
|
1060
|
-
if (relative9.startsWith("..") ||
|
|
1061
|
-
return relative9.split(
|
|
1119
|
+
const relative9 = path9.relative(realFolder(root), realFolder(dir));
|
|
1120
|
+
if (relative9.startsWith("..") || path9.isAbsolute(relative9)) return void 0;
|
|
1121
|
+
return relative9.split(path9.sep).join("/");
|
|
1062
1122
|
}
|
|
1063
1123
|
function fileFilter(file) {
|
|
1064
1124
|
const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1065
|
-
return `${escape(
|
|
1125
|
+
return `${escape(path9.basename(path9.dirname(file)))}[\\\\/]${escape(path9.basename(file))}$`;
|
|
1066
1126
|
}
|
|
1067
1127
|
function runPlaywright(file, cwd, id, env) {
|
|
1068
1128
|
return spawnPlaywright([fileFilter(file)], { ...env, ...originEnv("bug", id) }, { cwd, command: "piwi bug" });
|
|
@@ -1082,10 +1142,10 @@ ${USAGE2}`);
|
|
|
1082
1142
|
return EXIT_ERROR2;
|
|
1083
1143
|
}
|
|
1084
1144
|
const root = args.write ? repositoryRoot(cwd) : null;
|
|
1085
|
-
const outFile = args.out !== null ?
|
|
1145
|
+
const outFile = args.out !== null ? path9.resolve(cwd, args.out) : null;
|
|
1086
1146
|
let spec;
|
|
1087
1147
|
try {
|
|
1088
|
-
spec = await fetchBugSpec(args, outFile && root ? folderInRepository(root,
|
|
1148
|
+
spec = await fetchBugSpec(args, outFile && root ? folderInRepository(root, path9.dirname(outFile)) : void 0);
|
|
1089
1149
|
} catch (e) {
|
|
1090
1150
|
console.error(`piwi bug: ${e.message}`);
|
|
1091
1151
|
return EXIT_ERROR2;
|
|
@@ -1100,16 +1160,16 @@ ${USAGE2}`);
|
|
|
1100
1160
|
if (!outFile && !root) {
|
|
1101
1161
|
console.error("piwi bug: not in a git repository \u2014 the bugs folder is read from the working directory");
|
|
1102
1162
|
}
|
|
1103
|
-
const file = outFile ??
|
|
1163
|
+
const file = outFile ?? path9.resolve(root ?? cwd, spec.path);
|
|
1104
1164
|
try {
|
|
1105
|
-
|
|
1106
|
-
|
|
1165
|
+
fs6.mkdirSync(path9.dirname(file), { recursive: true });
|
|
1166
|
+
fs6.writeFileSync(file, spec.code, { flag: args.force ? "w" : "wx" });
|
|
1107
1167
|
} catch (e) {
|
|
1108
1168
|
const exists = e.code === "EEXIST";
|
|
1109
1169
|
console.error(`piwi bug: ${exists ? `${file} exists \u2014 pass --force to replace it` : e.message}`);
|
|
1110
1170
|
return EXIT_ERROR2;
|
|
1111
1171
|
}
|
|
1112
|
-
const shown =
|
|
1172
|
+
const shown = path9.relative(cwd, file) || file;
|
|
1113
1173
|
console.error(`piwi bug: wrote ${shown}`);
|
|
1114
1174
|
if (!args.run) return EXIT_OK2;
|
|
1115
1175
|
const code = await runPlaywright(file, cwd, args.id, env);
|
|
@@ -1127,8 +1187,8 @@ ${USAGE2}`);
|
|
|
1127
1187
|
}
|
|
1128
1188
|
|
|
1129
1189
|
// src/cli/codegen.ts
|
|
1130
|
-
var
|
|
1131
|
-
var
|
|
1190
|
+
var fs7 = __toESM(require("fs"));
|
|
1191
|
+
var path10 = __toESM(require("path"));
|
|
1132
1192
|
|
|
1133
1193
|
// ../core/src/recording.ts
|
|
1134
1194
|
var ASSERTION_MATCHERS = [
|
|
@@ -2168,7 +2228,7 @@ function renderSpec(session, options = {}) {
|
|
|
2168
2228
|
if (viewport) bodyLines.push(viewportLine(viewport, page));
|
|
2169
2229
|
stepStarts[pos] = bodyLines.length;
|
|
2170
2230
|
if (step.action === "goto") {
|
|
2171
|
-
bodyLines.push(...renderRawStep(step, pos, ctx));
|
|
2231
|
+
if (!(options.continuePage && pos === 0)) bodyLines.push(...renderRawStep(step, pos, ctx));
|
|
2172
2232
|
sawGoto = true;
|
|
2173
2233
|
pos++;
|
|
2174
2234
|
continue;
|
|
@@ -2187,10 +2247,10 @@ function renderSpec(session, options = {}) {
|
|
|
2187
2247
|
if (!callAt(pos + 1)) checkNextPage(pos);
|
|
2188
2248
|
pos++;
|
|
2189
2249
|
}
|
|
2190
|
-
const start = viewportAt.get(0);
|
|
2250
|
+
const start = options.continuePage ? void 0 : viewportAt.get(0);
|
|
2191
2251
|
const opening = [
|
|
2192
2252
|
...start ? [viewportLine(start, page)] : [],
|
|
2193
|
-
...!sawGoto && session.startUrl ? [` await ${page}.goto(${quote(urlForCode(session.startUrl, ctx))});`] : []
|
|
2253
|
+
...!sawGoto && !options.continuePage && session.startUrl ? [` await ${page}.goto(${quote(urlForCode(session.startUrl, ctx))});`] : []
|
|
2194
2254
|
];
|
|
2195
2255
|
bodyLines.unshift(...opening);
|
|
2196
2256
|
const bodyOffset = opening.length;
|
|
@@ -2261,6 +2321,8 @@ var STEPS_LIMITS = {
|
|
|
2261
2321
|
/** Titles, names, texts, locators. */
|
|
2262
2322
|
textLength: 500,
|
|
2263
2323
|
viewports: 50,
|
|
2324
|
+
/** The options a manual step's instruction names. */
|
|
2325
|
+
options: 50,
|
|
2264
2326
|
/** The widest and tallest viewport, in CSS pixels. */
|
|
2265
2327
|
viewportSize: 1e4,
|
|
2266
2328
|
/** The browser's zoom factors a viewport may name, as browsers allow them. */
|
|
@@ -2345,43 +2407,43 @@ var Checker = class {
|
|
|
2345
2407
|
constructor() {
|
|
2346
2408
|
this.errors = [];
|
|
2347
2409
|
}
|
|
2348
|
-
fail(
|
|
2349
|
-
if (this.errors.length < 20) this.errors.push(`${
|
|
2410
|
+
fail(path25, message) {
|
|
2411
|
+
if (this.errors.length < 20) this.errors.push(`${path25}: ${message}`);
|
|
2350
2412
|
}
|
|
2351
|
-
text(v,
|
|
2413
|
+
text(v, path25, max, nullable) {
|
|
2352
2414
|
if (v == null) {
|
|
2353
|
-
if (!nullable) this.fail(
|
|
2415
|
+
if (!nullable) this.fail(path25, "is required");
|
|
2354
2416
|
return null;
|
|
2355
2417
|
}
|
|
2356
2418
|
if (typeof v !== "string") {
|
|
2357
|
-
this.fail(
|
|
2419
|
+
this.fail(path25, "must be a string");
|
|
2358
2420
|
return null;
|
|
2359
2421
|
}
|
|
2360
|
-
if (v.length > max) this.fail(
|
|
2422
|
+
if (v.length > max) this.fail(path25, `is longer than ${max} characters`);
|
|
2361
2423
|
return v;
|
|
2362
2424
|
}
|
|
2363
|
-
number(v,
|
|
2425
|
+
number(v, path25) {
|
|
2364
2426
|
if (typeof v !== "number" || !Number.isFinite(v)) {
|
|
2365
|
-
this.fail(
|
|
2427
|
+
this.fail(path25, "must be a number");
|
|
2366
2428
|
return 0;
|
|
2367
2429
|
}
|
|
2368
2430
|
return v;
|
|
2369
2431
|
}
|
|
2370
2432
|
};
|
|
2371
|
-
function checkTarget(c, v,
|
|
2433
|
+
function checkTarget(c, v, path25) {
|
|
2372
2434
|
if (v == null) return null;
|
|
2373
2435
|
if (!isObject(v)) {
|
|
2374
|
-
c.fail(
|
|
2436
|
+
c.fail(path25, "must be an object");
|
|
2375
2437
|
return null;
|
|
2376
2438
|
}
|
|
2377
2439
|
const alternativesIn = v.alternatives ?? [];
|
|
2378
2440
|
const alternatives = [];
|
|
2379
|
-
if (!Array.isArray(alternativesIn)) c.fail(`${
|
|
2441
|
+
if (!Array.isArray(alternativesIn)) c.fail(`${path25}.alternatives`, "must be a list");
|
|
2380
2442
|
else if (alternativesIn.length > STEPS_LIMITS.alternatives)
|
|
2381
|
-
c.fail(`${
|
|
2443
|
+
c.fail(`${path25}.alternatives`, `holds more than ${STEPS_LIMITS.alternatives}`);
|
|
2382
2444
|
else {
|
|
2383
2445
|
alternativesIn.forEach((alt, i) => {
|
|
2384
|
-
const at = `${
|
|
2446
|
+
const at = `${path25}.alternatives[${i}]`;
|
|
2385
2447
|
if (!isObject(alt)) return c.fail(at, "must be an object");
|
|
2386
2448
|
const locator = c.text(alt.locator, `${at}.locator`, STEPS_LIMITS.textLength, false);
|
|
2387
2449
|
const safe = locator ? safeLocator(locator) : null;
|
|
@@ -2395,49 +2457,108 @@ function checkTarget(c, v, path24) {
|
|
|
2395
2457
|
});
|
|
2396
2458
|
}
|
|
2397
2459
|
return {
|
|
2398
|
-
tagName: c.text(v.tagName, `${
|
|
2399
|
-
role: c.text(v.role, `${
|
|
2400
|
-
accessibleName: c.text(v.accessibleName, `${
|
|
2401
|
-
testId: c.text(v.testId, `${
|
|
2402
|
-
text: c.text(v.text, `${
|
|
2460
|
+
tagName: c.text(v.tagName, `${path25}.tagName`, 64, false) ?? "",
|
|
2461
|
+
role: c.text(v.role, `${path25}.role`, 64, true),
|
|
2462
|
+
accessibleName: c.text(v.accessibleName, `${path25}.accessibleName`, STEPS_LIMITS.textLength, true),
|
|
2463
|
+
testId: c.text(v.testId, `${path25}.testId`, STEPS_LIMITS.textLength, true),
|
|
2464
|
+
text: c.text(v.text, `${path25}.text`, STEPS_LIMITS.textLength, true),
|
|
2403
2465
|
alternatives
|
|
2404
2466
|
};
|
|
2405
2467
|
}
|
|
2406
|
-
function checkAssertion(c, v,
|
|
2468
|
+
function checkAssertion(c, v, path25) {
|
|
2407
2469
|
if (!isObject(v)) {
|
|
2408
|
-
c.fail(
|
|
2470
|
+
c.fail(path25, "is required on an assert step");
|
|
2409
2471
|
return void 0;
|
|
2410
2472
|
}
|
|
2411
2473
|
const matcher = v.matcher;
|
|
2412
2474
|
if (!ASSERTION_MATCHERS.includes(matcher)) {
|
|
2413
|
-
c.fail(`${
|
|
2475
|
+
c.fail(`${path25}.matcher`, `must be one of ${ASSERTION_MATCHERS.join(", ")}`);
|
|
2414
2476
|
return void 0;
|
|
2415
2477
|
}
|
|
2416
|
-
const expected = c.text(v.expected, `${
|
|
2478
|
+
const expected = c.text(v.expected, `${path25}.expected`, STEPS_LIMITS.valueLength, !VALUE_MATCHERS.has(matcher));
|
|
2417
2479
|
if (matcher === "toHaveCount" && expected != null && !/^\d{1,6}$/.test(expected)) {
|
|
2418
|
-
c.fail(`${
|
|
2480
|
+
c.fail(`${path25}.expected`, "must be a whole number of elements on a toHaveCount assertion");
|
|
2419
2481
|
}
|
|
2420
2482
|
return {
|
|
2421
2483
|
matcher,
|
|
2422
2484
|
expected: VALUE_MATCHERS.has(matcher) ? expected : null,
|
|
2423
|
-
actual: c.text(v.actual, `${
|
|
2485
|
+
actual: c.text(v.actual, `${path25}.actual`, STEPS_LIMITS.valueLength, true),
|
|
2424
2486
|
negated: v.negated === true,
|
|
2425
|
-
note: c.text(v.note, `${
|
|
2487
|
+
note: c.text(v.note, `${path25}.note`, STEPS_LIMITS.valueLength, true)
|
|
2426
2488
|
};
|
|
2427
2489
|
}
|
|
2428
|
-
function checkManual(c, v,
|
|
2490
|
+
function checkManual(c, v, path25) {
|
|
2429
2491
|
if (!isObject(v)) {
|
|
2430
|
-
c.fail(
|
|
2492
|
+
c.fail(path25, "is required on a manual step");
|
|
2431
2493
|
return void 0;
|
|
2432
2494
|
}
|
|
2433
|
-
const description = c.text(v.description, `${
|
|
2434
|
-
if (description != null && !description.trim()) c.fail(`${
|
|
2495
|
+
const description = c.text(v.description, `${path25}.description`, STEPS_LIMITS.valueLength, false);
|
|
2496
|
+
if (description != null && !description.trim()) c.fail(`${path25}.description`, "must not be empty");
|
|
2497
|
+
const instruction = readInstruction(v.instruction);
|
|
2435
2498
|
return {
|
|
2436
2499
|
description: description ?? "",
|
|
2437
|
-
location: c.text(v.location, `${
|
|
2438
|
-
call: c.text(v.call, `${
|
|
2500
|
+
location: c.text(v.location, `${path25}.location`, STEPS_LIMITS.textLength, true),
|
|
2501
|
+
call: c.text(v.call, `${path25}.call`, STEPS_LIMITS.textLength, true),
|
|
2502
|
+
...instruction ? { instruction } : {}
|
|
2439
2503
|
};
|
|
2440
2504
|
}
|
|
2505
|
+
var isText = (v) => typeof v === "string" && v.length <= STEPS_LIMITS.valueLength;
|
|
2506
|
+
var isTextOrNull = (v) => v == null || isText(v);
|
|
2507
|
+
var isCount = (v) => Number.isInteger(v) && v >= 0;
|
|
2508
|
+
var INSTRUCTION_FIELDS = {
|
|
2509
|
+
locator: isText,
|
|
2510
|
+
call: isText,
|
|
2511
|
+
matcher: isText,
|
|
2512
|
+
what: isText,
|
|
2513
|
+
expected: isTextOrNull,
|
|
2514
|
+
received: isTextOrNull,
|
|
2515
|
+
x: (v) => typeof v === "number" && Number.isFinite(v),
|
|
2516
|
+
y: (v) => typeof v === "number" && Number.isFinite(v),
|
|
2517
|
+
count: isCount,
|
|
2518
|
+
limit: isCount,
|
|
2519
|
+
options: (v) => Array.isArray(v) && v.length > 0 && v.length <= STEPS_LIMITS.options && v.every((o) => isText(o) || isCount(o))
|
|
2520
|
+
};
|
|
2521
|
+
var CHECK_VALUES = ["expected", "received"];
|
|
2522
|
+
var INSTRUCTION_KINDS = {
|
|
2523
|
+
goBack: [],
|
|
2524
|
+
goForward: [],
|
|
2525
|
+
reload: [],
|
|
2526
|
+
gotoUnknown: [],
|
|
2527
|
+
setContent: [],
|
|
2528
|
+
keyboardType: [],
|
|
2529
|
+
mouseAt: ["x", "y", "call"],
|
|
2530
|
+
mouseOrKeyboard: ["call"],
|
|
2531
|
+
failedCall: ["call"],
|
|
2532
|
+
locatorCall: ["locator", "call"],
|
|
2533
|
+
fillUnknown: ["locator"],
|
|
2534
|
+
fillPartial: ["locator"],
|
|
2535
|
+
pressUnknown: ["locator"],
|
|
2536
|
+
selectOptionUnknown: ["locator"],
|
|
2537
|
+
setInputFiles: ["locator"],
|
|
2538
|
+
dragTo: ["locator"],
|
|
2539
|
+
drop: ["locator"],
|
|
2540
|
+
focus: ["locator"],
|
|
2541
|
+
blur: ["locator"],
|
|
2542
|
+
dispatchEvent: ["locator"],
|
|
2543
|
+
scrollIntoViewIfNeeded: ["locator"],
|
|
2544
|
+
selectOptions: ["locator", "options"],
|
|
2545
|
+
expectPoll: ["matcher", ...CHECK_VALUES],
|
|
2546
|
+
expectValue: ["what", ...CHECK_VALUES],
|
|
2547
|
+
expectLocator: ["locator", "matcher", ...CHECK_VALUES],
|
|
2548
|
+
expectURL: CHECK_VALUES,
|
|
2549
|
+
leadingSteps: ["count", "limit"]
|
|
2550
|
+
};
|
|
2551
|
+
function readInstruction(v) {
|
|
2552
|
+
if (!isObject(v) || typeof v.kind !== "string" || !Object.prototype.hasOwnProperty.call(INSTRUCTION_KINDS, v.kind))
|
|
2553
|
+
return null;
|
|
2554
|
+
const kind = v.kind;
|
|
2555
|
+
const out = { kind };
|
|
2556
|
+
for (const field of INSTRUCTION_KINDS[kind]) {
|
|
2557
|
+
if (!INSTRUCTION_FIELDS[field](v[field])) return null;
|
|
2558
|
+
out[field] = v[field] ?? null;
|
|
2559
|
+
}
|
|
2560
|
+
return out;
|
|
2561
|
+
}
|
|
2441
2562
|
function checkViewports(c, v, stepCount) {
|
|
2442
2563
|
if (v == null) return [];
|
|
2443
2564
|
if (!Array.isArray(v)) {
|
|
@@ -2476,43 +2597,43 @@ function checkViewports(c, v, stepCount) {
|
|
|
2476
2597
|
});
|
|
2477
2598
|
return out;
|
|
2478
2599
|
}
|
|
2479
|
-
function checkStep(c, v,
|
|
2600
|
+
function checkStep(c, v, path25) {
|
|
2480
2601
|
if (!isObject(v)) {
|
|
2481
|
-
c.fail(
|
|
2602
|
+
c.fail(path25, "must be an object");
|
|
2482
2603
|
return null;
|
|
2483
2604
|
}
|
|
2484
2605
|
const action = v.action;
|
|
2485
2606
|
if (!ACTIONS.has(action)) {
|
|
2486
|
-
c.fail(`${
|
|
2607
|
+
c.fail(`${path25}.action`, "is not a known action");
|
|
2487
2608
|
return null;
|
|
2488
2609
|
}
|
|
2489
2610
|
const step = {
|
|
2490
2611
|
action: action === "assertVisible" ? "assert" : action,
|
|
2491
|
-
target: checkTarget(c, v.target, `${
|
|
2492
|
-
value: c.text(v.value, `${
|
|
2612
|
+
target: checkTarget(c, v.target, `${path25}.target`),
|
|
2613
|
+
value: c.text(v.value, `${path25}.value`, STEPS_LIMITS.valueLength, true),
|
|
2493
2614
|
redacted: v.redacted === true,
|
|
2494
|
-
pageUrl: c.text(v.pageUrl, `${
|
|
2495
|
-
timestamp: v.timestamp == null ? 0 : c.number(v.timestamp, `${
|
|
2615
|
+
pageUrl: c.text(v.pageUrl, `${path25}.pageUrl`, STEPS_LIMITS.valueLength, false) ?? "",
|
|
2616
|
+
timestamp: v.timestamp == null ? 0 : c.number(v.timestamp, `${path25}.timestamp`)
|
|
2496
2617
|
};
|
|
2497
2618
|
if (step.redacted) step.value = null;
|
|
2498
2619
|
if (action === "hover" || action === "dblclick") {
|
|
2499
2620
|
step.value = null;
|
|
2500
|
-
if (!step.target) c.fail(`${
|
|
2621
|
+
if (!step.target) c.fail(`${path25}.target`, `is required on a ${action} step`);
|
|
2501
2622
|
}
|
|
2502
|
-
if (action === "setInputFiles" && !step.target) c.fail(`${
|
|
2623
|
+
if (action === "setInputFiles" && !step.target) c.fail(`${path25}.target`, "is required on a setInputFiles step");
|
|
2503
2624
|
if (action === "dragTo") {
|
|
2504
2625
|
step.value = null;
|
|
2505
|
-
if (!step.target) c.fail(`${
|
|
2506
|
-
step.dropTarget = checkTarget(c, v.dropTarget, `${
|
|
2507
|
-
if (!step.dropTarget) c.fail(`${
|
|
2626
|
+
if (!step.target) c.fail(`${path25}.target`, "is required on a dragTo step");
|
|
2627
|
+
step.dropTarget = checkTarget(c, v.dropTarget, `${path25}.dropTarget`);
|
|
2628
|
+
if (!step.dropTarget) c.fail(`${path25}.dropTarget`, "is required on a dragTo step");
|
|
2508
2629
|
}
|
|
2509
2630
|
if (action === "assertVisible") {
|
|
2510
2631
|
step.assertion = { matcher: "toBeVisible", expected: null, actual: null, negated: false, note: null };
|
|
2511
2632
|
} else if (action === "assert") {
|
|
2512
|
-
step.assertion = checkAssertion(c, v.assertion, `${
|
|
2633
|
+
step.assertion = checkAssertion(c, v.assertion, `${path25}.assertion`);
|
|
2513
2634
|
} else if (action === "manual") {
|
|
2514
2635
|
step.value = null;
|
|
2515
|
-
step.manual = checkManual(c, v.manual, `${
|
|
2636
|
+
step.manual = checkManual(c, v.manual, `${path25}.manual`);
|
|
2516
2637
|
}
|
|
2517
2638
|
return step;
|
|
2518
2639
|
}
|
|
@@ -2592,13 +2713,14 @@ async function fetchResolution(options, projectId) {
|
|
|
2592
2713
|
}
|
|
2593
2714
|
return await res.json();
|
|
2594
2715
|
}
|
|
2595
|
-
async function fetchImpact(options, projectId, changedFiles) {
|
|
2716
|
+
async function fetchImpact(options, projectId, changedFiles, changes = []) {
|
|
2596
2717
|
const url = `${options.serverUrl}/api/projects/${projectId}/selections/impact`;
|
|
2597
2718
|
const res = await fetch(url, {
|
|
2598
2719
|
method: "POST",
|
|
2599
2720
|
headers: { "Content-Type": "application/json", ...authHeaders(options.apiKey) },
|
|
2600
2721
|
body: JSON.stringify({
|
|
2601
2722
|
changedFiles,
|
|
2723
|
+
...changes.length > 0 ? { changes } : {},
|
|
2602
2724
|
format: options.format ?? "args",
|
|
2603
2725
|
shard: options.shard ?? void 0,
|
|
2604
2726
|
order: options.order ?? void 0
|
|
@@ -2811,8 +2933,8 @@ ${USAGE3}`);
|
|
|
2811
2933
|
}
|
|
2812
2934
|
} else {
|
|
2813
2935
|
try {
|
|
2814
|
-
if (
|
|
2815
|
-
text =
|
|
2936
|
+
if (fs7.statSync(args.file).size > MAX_STEPS_FILE_BYTES) throw new Error("is too large to be a steps file");
|
|
2937
|
+
text = fs7.readFileSync(args.file, "utf-8");
|
|
2816
2938
|
} catch (e) {
|
|
2817
2939
|
const reason = e.code === "ENOENT" ? "does not exist" : e.message;
|
|
2818
2940
|
console.error(`piwi codegen: ${args.file} ${reason}`);
|
|
@@ -2844,8 +2966,8 @@ ${USAGE3}`);
|
|
|
2844
2966
|
return EXIT_OK3;
|
|
2845
2967
|
}
|
|
2846
2968
|
try {
|
|
2847
|
-
|
|
2848
|
-
|
|
2969
|
+
fs7.mkdirSync(path10.dirname(args.out), { recursive: true });
|
|
2970
|
+
fs7.writeFileSync(args.out, result.code, { flag: args.force ? "w" : "wx" });
|
|
2849
2971
|
} catch (e) {
|
|
2850
2972
|
const exists = e.code === "EEXIST";
|
|
2851
2973
|
console.error(`piwi codegen: ${exists ? `${args.out} exists \u2014 pass --force to replace it` : e.message}`);
|
|
@@ -2859,7 +2981,7 @@ ${USAGE3}`);
|
|
|
2859
2981
|
}
|
|
2860
2982
|
|
|
2861
2983
|
// src/cli/coverage.ts
|
|
2862
|
-
var
|
|
2984
|
+
var path15 = __toESM(require("path"));
|
|
2863
2985
|
|
|
2864
2986
|
// ../core/src/codeowners.ts
|
|
2865
2987
|
function patternParts(pattern) {
|
|
@@ -2897,10 +3019,10 @@ function pathPatternMatcher(pattern) {
|
|
|
2897
3019
|
const { segments, floating } = patternParts(pattern);
|
|
2898
3020
|
if (floating) {
|
|
2899
3021
|
const only = segments[0];
|
|
2900
|
-
return (
|
|
3022
|
+
return (path25) => path25.split("/").some((part) => segmentMatches(only, part));
|
|
2901
3023
|
}
|
|
2902
|
-
return (
|
|
2903
|
-
const parts =
|
|
3024
|
+
return (path25) => {
|
|
3025
|
+
const parts = path25.split("/");
|
|
2904
3026
|
let at = new Uint8Array(parts.length);
|
|
2905
3027
|
at[0] = 1;
|
|
2906
3028
|
for (let i = 0; i < segments.length; i++) {
|
|
@@ -2966,8 +3088,8 @@ function parseCoverageScope(raw) {
|
|
|
2966
3088
|
function compileCoverageScope(scope) {
|
|
2967
3089
|
const include = scope.include.map(pathPatternMatcher);
|
|
2968
3090
|
const exclude = scope.exclude.map(pathPatternMatcher);
|
|
2969
|
-
return (
|
|
2970
|
-
const file =
|
|
3091
|
+
return (path25) => {
|
|
3092
|
+
const file = path25.replace(/\\/g, "/").replace(/^\.?\//, "");
|
|
2971
3093
|
if (include.length > 0 && !include.some((matches) => matches(file))) return false;
|
|
2972
3094
|
return !exclude.some((matches) => matches(file));
|
|
2973
3095
|
};
|
|
@@ -2983,7 +3105,7 @@ function errorMessage(error) {
|
|
|
2983
3105
|
}
|
|
2984
3106
|
|
|
2985
3107
|
// src/internal/support/test-paths.ts
|
|
2986
|
-
var
|
|
3108
|
+
var path11 = __toESM(require("path"));
|
|
2987
3109
|
|
|
2988
3110
|
// src/internal/collect/metadata-collector.ts
|
|
2989
3111
|
var DETACHED_HEAD = "HEAD";
|
|
@@ -3016,14 +3138,14 @@ function normalizeRef(ref) {
|
|
|
3016
3138
|
|
|
3017
3139
|
// src/internal/coverage-upload/git-inventory.ts
|
|
3018
3140
|
var import_node_child_process5 = require("child_process");
|
|
3019
|
-
var
|
|
3020
|
-
var
|
|
3141
|
+
var fs9 = __toESM(require("fs"));
|
|
3142
|
+
var path13 = __toESM(require("path"));
|
|
3021
3143
|
|
|
3022
3144
|
// src/internal/collect/blob-ids.ts
|
|
3023
3145
|
var import_node_child_process4 = require("child_process");
|
|
3024
3146
|
var crypto2 = __toESM(require("crypto"));
|
|
3025
|
-
var
|
|
3026
|
-
var
|
|
3147
|
+
var fs8 = __toESM(require("fs"));
|
|
3148
|
+
var path12 = __toESM(require("path"));
|
|
3027
3149
|
function gitHashObjects(root, paths) {
|
|
3028
3150
|
return new Promise((resolve10) => {
|
|
3029
3151
|
const child = (0, import_node_child_process4.execFile)(
|
|
@@ -3090,7 +3212,7 @@ async function readInventory(root, warn = () => {
|
|
|
3090
3212
|
const changed = [];
|
|
3091
3213
|
for (const file of (await git(root, ["diff", "--name-only", "-z", "HEAD"]).catch(() => "")).split("\0")) {
|
|
3092
3214
|
if (!file || !inventory.has(file)) continue;
|
|
3093
|
-
if (
|
|
3215
|
+
if (fs9.existsSync(path13.join(root, file))) changed.push(file);
|
|
3094
3216
|
else inventory.delete(file);
|
|
3095
3217
|
}
|
|
3096
3218
|
const ids = await hashPaths(root, changed);
|
|
@@ -3109,8 +3231,8 @@ async function readInventory(root, warn = () => {
|
|
|
3109
3231
|
}
|
|
3110
3232
|
|
|
3111
3233
|
// src/internal/coverage-upload/report-files.ts
|
|
3112
|
-
var
|
|
3113
|
-
var
|
|
3234
|
+
var fs10 = __toESM(require("fs"));
|
|
3235
|
+
var path14 = __toESM(require("path"));
|
|
3114
3236
|
|
|
3115
3237
|
// ../core/src/coverage.ts
|
|
3116
3238
|
var MAX_LINE = 1e6;
|
|
@@ -3189,7 +3311,7 @@ var FileLines = class {
|
|
|
3189
3311
|
this.functions.set(key, { start, end: last, ran });
|
|
3190
3312
|
}
|
|
3191
3313
|
}
|
|
3192
|
-
toFile(
|
|
3314
|
+
toFile(path25) {
|
|
3193
3315
|
const executable = [];
|
|
3194
3316
|
const lines = [];
|
|
3195
3317
|
const push = (ranges, line) => {
|
|
@@ -3209,7 +3331,7 @@ var FileLines = class {
|
|
|
3209
3331
|
if (didRun) ran.push([start, end]);
|
|
3210
3332
|
}
|
|
3211
3333
|
return {
|
|
3212
|
-
path:
|
|
3334
|
+
path: path25,
|
|
3213
3335
|
executable,
|
|
3214
3336
|
lines,
|
|
3215
3337
|
functions: normalizeFunctionRanges(ran),
|
|
@@ -3221,16 +3343,16 @@ var ReportFiles = class {
|
|
|
3221
3343
|
constructor() {
|
|
3222
3344
|
this.byPath = /* @__PURE__ */ new Map();
|
|
3223
3345
|
}
|
|
3224
|
-
of(
|
|
3225
|
-
let file = this.byPath.get(
|
|
3346
|
+
of(path25) {
|
|
3347
|
+
let file = this.byPath.get(path25);
|
|
3226
3348
|
if (!file) {
|
|
3227
3349
|
file = new FileLines();
|
|
3228
|
-
this.byPath.set(
|
|
3350
|
+
this.byPath.set(path25, file);
|
|
3229
3351
|
}
|
|
3230
3352
|
return file;
|
|
3231
3353
|
}
|
|
3232
3354
|
toFiles() {
|
|
3233
|
-
return [...this.byPath].map(([
|
|
3355
|
+
return [...this.byPath].map(([path25, file]) => file.toFile(path25));
|
|
3234
3356
|
}
|
|
3235
3357
|
};
|
|
3236
3358
|
var XML_ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
|
|
@@ -3569,23 +3691,23 @@ function normalizeSlashPath(raw) {
|
|
|
3569
3691
|
}
|
|
3570
3692
|
return (drive || (absolute ? "/" : "")) + out.join("/");
|
|
3571
3693
|
}
|
|
3572
|
-
function isAbsolutePath(
|
|
3573
|
-
return
|
|
3694
|
+
function isAbsolutePath(path25) {
|
|
3695
|
+
return path25.startsWith("/") || /^[A-Za-z]:\//.test(path25);
|
|
3574
3696
|
}
|
|
3575
|
-
function relativeTo(
|
|
3697
|
+
function relativeTo(path25, root) {
|
|
3576
3698
|
const drive = /^[A-Za-z]:/.test(root);
|
|
3577
3699
|
const base = root.endsWith("/") ? root : `${root}/`;
|
|
3578
|
-
const head =
|
|
3700
|
+
const head = path25.slice(0, base.length);
|
|
3579
3701
|
const inside = drive ? head.toLowerCase() === base.toLowerCase() : head === base;
|
|
3580
3702
|
if (!inside) return null;
|
|
3581
|
-
const relative9 =
|
|
3703
|
+
const relative9 = path25.slice(base.length);
|
|
3582
3704
|
return relative9 || null;
|
|
3583
3705
|
}
|
|
3584
3706
|
function reportPathCandidates(raw, { repoRoot, bases }) {
|
|
3585
3707
|
const slash = (value) => value.replace(/\\/g, "/");
|
|
3586
3708
|
const root = normalizeSlashPath(slash(repoRoot));
|
|
3587
|
-
const
|
|
3588
|
-
if (!root || !
|
|
3709
|
+
const path25 = slash(raw.trim());
|
|
3710
|
+
if (!root || !path25 || path25.includes("\0")) return [];
|
|
3589
3711
|
const out = [];
|
|
3590
3712
|
const add = (absolute) => {
|
|
3591
3713
|
const normalized = normalizeSlashPath(absolute);
|
|
@@ -3593,12 +3715,12 @@ function reportPathCandidates(raw, { repoRoot, bases }) {
|
|
|
3593
3715
|
const relative9 = normalized.startsWith("/_/") ? normalized.slice(3) : relativeTo(normalized, root);
|
|
3594
3716
|
if (relative9 && !out.includes(relative9)) out.push(relative9);
|
|
3595
3717
|
};
|
|
3596
|
-
if (isAbsolutePath(
|
|
3597
|
-
add(
|
|
3718
|
+
if (isAbsolutePath(path25)) {
|
|
3719
|
+
add(path25);
|
|
3598
3720
|
} else {
|
|
3599
3721
|
for (const base of bases) {
|
|
3600
3722
|
const dir = slash(base);
|
|
3601
|
-
if (isAbsolutePath(dir)) add(`${dir}/${
|
|
3723
|
+
if (isAbsolutePath(dir)) add(`${dir}/${path25}`);
|
|
3602
3724
|
}
|
|
3603
3725
|
}
|
|
3604
3726
|
return out;
|
|
@@ -3622,14 +3744,14 @@ function globSegmentRegExp(segment) {
|
|
|
3622
3744
|
function expandPattern(pattern, cwd) {
|
|
3623
3745
|
const normalized = pattern.replace(/\\/g, "/");
|
|
3624
3746
|
if (!/[*?]/.test(normalized)) {
|
|
3625
|
-
const file =
|
|
3626
|
-
return
|
|
3747
|
+
const file = path14.resolve(cwd, normalized);
|
|
3748
|
+
return fs10.existsSync(file) && fs10.statSync(file).isFile() ? [file] : [];
|
|
3627
3749
|
}
|
|
3628
|
-
const absolute =
|
|
3750
|
+
const absolute = path14.isAbsolute(normalized) || /^[A-Za-z]:\//.test(normalized);
|
|
3629
3751
|
const segments = normalized.split("/");
|
|
3630
3752
|
const firstWild = segments.findIndex((s) => /[*?]/.test(s));
|
|
3631
3753
|
const baseParts = segments.slice(0, firstWild);
|
|
3632
|
-
const base = absolute ? baseParts.join("/") || "/" :
|
|
3754
|
+
const base = absolute ? baseParts.join("/") || "/" : path14.resolve(cwd, ...baseParts);
|
|
3633
3755
|
const rest = segments.slice(firstWild).filter((s) => s !== "");
|
|
3634
3756
|
const out = [];
|
|
3635
3757
|
const walk = (dir, at) => {
|
|
@@ -3637,14 +3759,14 @@ function expandPattern(pattern, cwd) {
|
|
|
3637
3759
|
const segment = rest[at];
|
|
3638
3760
|
let entries;
|
|
3639
3761
|
try {
|
|
3640
|
-
entries =
|
|
3762
|
+
entries = fs10.readdirSync(dir, { withFileTypes: true });
|
|
3641
3763
|
} catch {
|
|
3642
3764
|
return;
|
|
3643
3765
|
}
|
|
3644
3766
|
if (segment === "**") {
|
|
3645
3767
|
walk(dir, at + 1);
|
|
3646
3768
|
for (const entry of entries) {
|
|
3647
|
-
if (entry.isDirectory() && !SKIPPED_DIRS.has(entry.name)) walk(
|
|
3769
|
+
if (entry.isDirectory() && !SKIPPED_DIRS.has(entry.name)) walk(path14.join(dir, entry.name), at);
|
|
3648
3770
|
}
|
|
3649
3771
|
return;
|
|
3650
3772
|
}
|
|
@@ -3652,7 +3774,7 @@ function expandPattern(pattern, cwd) {
|
|
|
3652
3774
|
const last = at === rest.length - 1;
|
|
3653
3775
|
for (const entry of entries) {
|
|
3654
3776
|
if (!match.test(entry.name)) continue;
|
|
3655
|
-
const full =
|
|
3777
|
+
const full = path14.join(dir, entry.name);
|
|
3656
3778
|
if (last && entry.isFile()) out.push(full);
|
|
3657
3779
|
else if (!last && entry.isDirectory() && (!SKIPPED_DIRS.has(entry.name) || segment === entry.name)) {
|
|
3658
3780
|
walk(full, at + 1);
|
|
@@ -3665,7 +3787,7 @@ function expandPattern(pattern, cwd) {
|
|
|
3665
3787
|
async function readReport(file, format) {
|
|
3666
3788
|
let reader = format ? new CoverageReportReader(format) : null;
|
|
3667
3789
|
let head = "";
|
|
3668
|
-
for await (const chunk of
|
|
3790
|
+
for await (const chunk of fs10.createReadStream(file, { encoding: "utf8", highWaterMark: 1024 * 1024 })) {
|
|
3669
3791
|
if (!reader) {
|
|
3670
3792
|
head += chunk;
|
|
3671
3793
|
if (head.length < 4096) continue;
|
|
@@ -3687,18 +3809,18 @@ async function readReport(file, format) {
|
|
|
3687
3809
|
return reader.end();
|
|
3688
3810
|
}
|
|
3689
3811
|
function reportBases(report, reportFile, options) {
|
|
3690
|
-
if (options.root) return [
|
|
3691
|
-
const dir =
|
|
3812
|
+
if (options.root) return [path14.resolve(options.cwd, options.root)];
|
|
3813
|
+
const dir = path14.dirname(path14.resolve(reportFile));
|
|
3692
3814
|
const sources = report.sources.map(
|
|
3693
|
-
(source) =>
|
|
3815
|
+
(source) => path14.isAbsolute(source) || /^[A-Za-z]:[\\/]/.test(source) ? source : path14.resolve(dir, source)
|
|
3694
3816
|
);
|
|
3695
|
-
return [...sources, options.cwd, dir,
|
|
3817
|
+
return [...sources, options.cwd, dir, path14.dirname(dir), options.repoRoot];
|
|
3696
3818
|
}
|
|
3697
3819
|
function resolveReport(report, reportFile, options, into) {
|
|
3698
3820
|
const bases = reportBases(report, reportFile, options);
|
|
3699
3821
|
for (const file of report.files) {
|
|
3700
3822
|
const candidates = reportPathCandidates(file.path, { repoRoot: options.repoRoot, bases });
|
|
3701
|
-
const match = candidates.find((candidate) => options.inventory.has(candidate)) ?? candidates.find((candidate) =>
|
|
3823
|
+
const match = candidates.find((candidate) => options.inventory.has(candidate)) ?? candidates.find((candidate) => fs10.existsSync(path14.join(options.repoRoot, candidate)));
|
|
3702
3824
|
if (!match) {
|
|
3703
3825
|
into.unmatched.push(file.path);
|
|
3704
3826
|
continue;
|
|
@@ -3923,7 +4045,7 @@ async function uploadCoverage(args, env, cwd, warn = warnOnStderr) {
|
|
|
3923
4045
|
commit: checkout.commit,
|
|
3924
4046
|
branch,
|
|
3925
4047
|
suite: args.suite,
|
|
3926
|
-
reports: reports.map((report) =>
|
|
4048
|
+
reports: reports.map((report) => path15.relative(cwd, report) || report),
|
|
3927
4049
|
files: files.length,
|
|
3928
4050
|
unmatched: resolved.unmatched.length,
|
|
3929
4051
|
outOfScope: resolved.outOfScope,
|
|
@@ -3995,7 +4117,7 @@ ${USAGE4}`);
|
|
|
3995
4117
|
var import_node_child_process6 = require("child_process");
|
|
3996
4118
|
var fs11 = __toESM(require("fs"));
|
|
3997
4119
|
var os2 = __toESM(require("os"));
|
|
3998
|
-
var
|
|
4120
|
+
var path17 = __toESM(require("path"));
|
|
3999
4121
|
|
|
4000
4122
|
// ../core/src/error-parse.ts
|
|
4001
4123
|
var ANSI_RE = new RegExp("\\u001B\\[[0-9;]*m", "g");
|
|
@@ -4197,52 +4319,10 @@ function withinBudget(startedAt, now, budgetMs) {
|
|
|
4197
4319
|
}
|
|
4198
4320
|
|
|
4199
4321
|
// src/internal/support/connection.ts
|
|
4200
|
-
var
|
|
4201
|
-
var path15 = __toESM(require("path"));
|
|
4202
|
-
|
|
4203
|
-
// ../core/src/dotenv.ts
|
|
4204
|
-
function parseDotEnv(text) {
|
|
4205
|
-
const out = {};
|
|
4206
|
-
for (const raw of text.split(/\r?\n/)) {
|
|
4207
|
-
const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(raw);
|
|
4208
|
-
if (!m) continue;
|
|
4209
|
-
let value = m[2];
|
|
4210
|
-
const quote3 = value[0];
|
|
4211
|
-
if ((quote3 === '"' || quote3 === "'") && value.length > 1 && value.endsWith(quote3)) {
|
|
4212
|
-
value = value.slice(1, -1);
|
|
4213
|
-
if (quote3 === '"') value = value.replace(/\\n/g, "\n").replace(/\\"/g, '"');
|
|
4214
|
-
} else {
|
|
4215
|
-
value = value.replace(/\s+#.*$/, "");
|
|
4216
|
-
}
|
|
4217
|
-
out[m[1]] = value;
|
|
4218
|
-
}
|
|
4219
|
-
return out;
|
|
4220
|
-
}
|
|
4221
|
-
function resolvePiwiConnection(sources) {
|
|
4222
|
-
const own = {
|
|
4223
|
-
serverUrl: sources.flags?.serverUrl || sources.env?.PIWI_DASHBOARD_URL,
|
|
4224
|
-
apiKey: sources.flags?.apiKey || sources.env?.PIWI_API_KEY
|
|
4225
|
-
};
|
|
4226
|
-
const workspace = { serverUrl: sources.dotEnv?.PIWI_DASHBOARD_URL, apiKey: sources.dotEnv?.PIWI_API_KEY };
|
|
4227
|
-
const project = sources.flags?.project || sources.env?.PIWI_PROJECT_NAME || sources.dotEnv?.PIWI_PROJECT_NAME || "";
|
|
4228
|
-
const desktop = sources.desktop ?? null;
|
|
4229
|
-
const trim = (url) => url.replace(/\/+$/, "");
|
|
4230
|
-
const layer = own.serverUrl ? own : workspace.serverUrl ? workspace : null;
|
|
4231
|
-
const serverUrl = layer ? trim(layer.serverUrl) : desktop ? trim(desktop.url) : "";
|
|
4232
|
-
if (!serverUrl) return null;
|
|
4233
|
-
const desktopKey = desktop && trim(desktop.url) === serverUrl ? desktop.token : null;
|
|
4234
|
-
return { serverUrl, apiKey: layer?.apiKey || desktopKey || null, project };
|
|
4235
|
-
}
|
|
4236
|
-
|
|
4237
|
-
// src/internal/support/connection.ts
|
|
4322
|
+
var path16 = __toESM(require("path"));
|
|
4238
4323
|
function resolveCliConnection(flags, env, dirs) {
|
|
4239
4324
|
let dotEnv = {};
|
|
4240
|
-
for (const dir of dirs) {
|
|
4241
|
-
try {
|
|
4242
|
-
dotEnv = { ...parseDotEnv(fs10.readFileSync(path15.join(dir, ".env"), "utf-8")), ...dotEnv };
|
|
4243
|
-
} catch {
|
|
4244
|
-
}
|
|
4245
|
-
}
|
|
4325
|
+
for (const dir of dirs) dotEnv = { ...readDotEnv(path16.join(dir, ".env")), ...dotEnv };
|
|
4246
4326
|
return resolvePiwiConnection({
|
|
4247
4327
|
flags,
|
|
4248
4328
|
env,
|
|
@@ -4440,7 +4520,7 @@ function playwrightRunner(dir, log) {
|
|
|
4440
4520
|
return {
|
|
4441
4521
|
now: () => Date.now(),
|
|
4442
4522
|
async runBatch(plan, arm, repeat, batch) {
|
|
4443
|
-
const planFile =
|
|
4523
|
+
const planFile = path17.join(dir, `${arm.id}.plan.json`);
|
|
4444
4524
|
if (!fs11.existsSync(planFile)) {
|
|
4445
4525
|
fs11.writeFileSync(
|
|
4446
4526
|
planFile,
|
|
@@ -4453,7 +4533,7 @@ function playwrightRunner(dir, log) {
|
|
|
4453
4533
|
})
|
|
4454
4534
|
);
|
|
4455
4535
|
}
|
|
4456
|
-
const resultsFile =
|
|
4536
|
+
const resultsFile = path17.join(dir, `${arm.id}.${batch}.jsonl`);
|
|
4457
4537
|
fs11.writeFileSync(resultsFile, "");
|
|
4458
4538
|
const env = {
|
|
4459
4539
|
...process.env,
|
|
@@ -4732,8 +4812,8 @@ async function runFlake(argv, runner) {
|
|
|
4732
4812
|
if (!args.json) {
|
|
4733
4813
|
printHeader(plan, commit, estimate, args.budgetMs);
|
|
4734
4814
|
}
|
|
4735
|
-
const dir = fs11.mkdtempSync(
|
|
4736
|
-
const log =
|
|
4815
|
+
const dir = fs11.mkdtempSync(path17.join(os2.tmpdir(), "piwi-flake-"));
|
|
4816
|
+
const log = path17.join(dir, "playwright.log");
|
|
4737
4817
|
let session;
|
|
4738
4818
|
try {
|
|
4739
4819
|
session = await runSession(runner ?? playwrightRunner(dir, log), plan, args, (result) => {
|
|
@@ -4924,9 +5004,9 @@ function readCount(argv, name) {
|
|
|
4924
5004
|
if (!Number.isFinite(n) || n < 0) throw new Error(`${name} expects a non-negative number, got "${raw}"`);
|
|
4925
5005
|
return Math.floor(n);
|
|
4926
5006
|
}
|
|
4927
|
-
function readRunIdFromFile(
|
|
5007
|
+
function readRunIdFromFile(path25) {
|
|
4928
5008
|
try {
|
|
4929
|
-
const parsed = JSON.parse(fs12.readFileSync(
|
|
5009
|
+
const parsed = JSON.parse(fs12.readFileSync(path25, "utf-8"));
|
|
4930
5010
|
const runId = Number(parsed.runId);
|
|
4931
5011
|
return Number.isFinite(runId) && runId > 0 ? runId : null;
|
|
4932
5012
|
} catch {
|
|
@@ -5018,7 +5098,7 @@ async function runGate(argv, env = process.env) {
|
|
|
5018
5098
|
|
|
5019
5099
|
// src/cli/init.ts
|
|
5020
5100
|
var fs15 = __toESM(require("fs"));
|
|
5021
|
-
var
|
|
5101
|
+
var path20 = __toESM(require("path"));
|
|
5022
5102
|
|
|
5023
5103
|
// src/cli/report.ts
|
|
5024
5104
|
var STATUS_MARK = {
|
|
@@ -5036,7 +5116,7 @@ function formatStep(result) {
|
|
|
5036
5116
|
|
|
5037
5117
|
// src/cli/detect.ts
|
|
5038
5118
|
var fs13 = __toESM(require("fs"));
|
|
5039
|
-
var
|
|
5119
|
+
var path18 = __toESM(require("path"));
|
|
5040
5120
|
var CONFIG_NAMES = [
|
|
5041
5121
|
"playwright.config.ts",
|
|
5042
5122
|
"playwright.config.mts",
|
|
@@ -5054,7 +5134,7 @@ var LOCKFILES = [
|
|
|
5054
5134
|
];
|
|
5055
5135
|
var REPORTER_PACKAGE = "@piwitests/reporter";
|
|
5056
5136
|
function readPackageJson(root) {
|
|
5057
|
-
const pkgPath =
|
|
5137
|
+
const pkgPath = path18.join(root, "package.json");
|
|
5058
5138
|
try {
|
|
5059
5139
|
return { pkg: JSON.parse(fs13.readFileSync(pkgPath, "utf-8")), pkgPath };
|
|
5060
5140
|
} catch {
|
|
@@ -5065,13 +5145,13 @@ function detectPackageManager(root, pkg) {
|
|
|
5065
5145
|
const declared = pkg?.packageManager?.split("@")[0];
|
|
5066
5146
|
if (declared === "pnpm" || declared === "yarn" || declared === "bun" || declared === "npm") return declared;
|
|
5067
5147
|
for (const [file, manager] of LOCKFILES) {
|
|
5068
|
-
if (fs13.existsSync(
|
|
5148
|
+
if (fs13.existsSync(path18.join(root, file))) return manager;
|
|
5069
5149
|
}
|
|
5070
5150
|
return "npm";
|
|
5071
5151
|
}
|
|
5072
5152
|
function findConfig(root) {
|
|
5073
5153
|
for (const name of CONFIG_NAMES) {
|
|
5074
|
-
const candidate =
|
|
5154
|
+
const candidate = path18.join(root, name);
|
|
5075
5155
|
if (fs13.existsSync(candidate)) return candidate;
|
|
5076
5156
|
}
|
|
5077
5157
|
return null;
|
|
@@ -5082,14 +5162,14 @@ function langOf(configPath) {
|
|
|
5082
5162
|
}
|
|
5083
5163
|
function suggestProjectName(root, pkg) {
|
|
5084
5164
|
const fromPkg = pkg?.name?.replace(/^@[^/]+\//, "").trim();
|
|
5085
|
-
return fromPkg ||
|
|
5165
|
+
return fromPkg || path18.basename(root) || "default-project";
|
|
5086
5166
|
}
|
|
5087
5167
|
function hasReporter(pkg) {
|
|
5088
5168
|
if (!pkg) return false;
|
|
5089
5169
|
return Boolean(pkg.dependencies?.[REPORTER_PACKAGE] || pkg.devDependencies?.[REPORTER_PACKAGE]);
|
|
5090
5170
|
}
|
|
5091
5171
|
function detectProject(root) {
|
|
5092
|
-
const absRoot =
|
|
5172
|
+
const absRoot = path18.resolve(root);
|
|
5093
5173
|
const { pkg, pkgPath } = readPackageJson(absRoot);
|
|
5094
5174
|
const configPath = findConfig(absRoot);
|
|
5095
5175
|
return {
|
|
@@ -5118,7 +5198,10 @@ function installCommand(manager) {
|
|
|
5118
5198
|
|
|
5119
5199
|
// src/cli/configure.ts
|
|
5120
5200
|
var REPORTER_IMPORT = "import { wrapConfig } from '@piwitests/reporter'";
|
|
5121
|
-
|
|
5201
|
+
function manualHint(opts) {
|
|
5202
|
+
const options = optionsLiteral(opts).replace(/\s+/g, " ");
|
|
5203
|
+
return `Could not find \`export default defineConfig(...)\`. Wrap your exported config by hand: import { wrapConfig } from '@piwitests/reporter', then export default wrapConfig(defineConfig({ ... }), ${options}).`;
|
|
5204
|
+
}
|
|
5122
5205
|
var quote2 = quoteJsString;
|
|
5123
5206
|
function matchClosingParen(src, openIndex) {
|
|
5124
5207
|
let depth = 0;
|
|
@@ -5157,6 +5240,7 @@ function optionsLiteral(opts) {
|
|
|
5157
5240
|
const lines = [];
|
|
5158
5241
|
if (opts.serverUrl) lines.push(` serverUrl: ${quote2(opts.serverUrl)},`);
|
|
5159
5242
|
lines.push(` projectName: ${quote2(opts.projectName)},`);
|
|
5243
|
+
if (opts.desktopApp) lines.push(` desktopApp: ${quote2(opts.desktopApp)},`);
|
|
5160
5244
|
return `{
|
|
5161
5245
|
${lines.join("\n")}
|
|
5162
5246
|
}`;
|
|
@@ -5180,11 +5264,11 @@ function wrapPlaywrightConfig(source, opts) {
|
|
|
5180
5264
|
return { text: source, status: "already", detail: "Piwi reporter is already wired into the config" };
|
|
5181
5265
|
}
|
|
5182
5266
|
const match = /export\s+default\s+defineConfig\s*\(/.exec(source);
|
|
5183
|
-
if (!match) return { text: source, status: "manual", detail:
|
|
5267
|
+
if (!match) return { text: source, status: "manual", detail: manualHint(opts) };
|
|
5184
5268
|
const openParen = match.index + match[0].length - 1;
|
|
5185
5269
|
const closeParen = matchClosingParen(source, openParen);
|
|
5186
5270
|
const callStart = source.indexOf("defineConfig", match.index);
|
|
5187
|
-
if (closeParen === -1 || callStart === -1) return { text: source, status: "manual", detail:
|
|
5271
|
+
if (closeParen === -1 || callStart === -1) return { text: source, status: "manual", detail: manualHint(opts) };
|
|
5188
5272
|
const callExpr = source.slice(callStart, closeParen + 1);
|
|
5189
5273
|
let before = withReporterImport(source.slice(0, match.index));
|
|
5190
5274
|
const after = source.slice(closeParen + 1);
|
|
@@ -5230,7 +5314,7 @@ function ensureGitignoreEntry(existing, entry = ".env") {
|
|
|
5230
5314
|
|
|
5231
5315
|
// src/cli/skills.ts
|
|
5232
5316
|
var fs14 = __toESM(require("fs"));
|
|
5233
|
-
var
|
|
5317
|
+
var path19 = __toESM(require("path"));
|
|
5234
5318
|
|
|
5235
5319
|
// ../core/src/skill-stamp.ts
|
|
5236
5320
|
var SKILL_VERSION_KEY = "piwi-version";
|
|
@@ -5293,16 +5377,16 @@ var WORKFLOW_SKILLS = [
|
|
|
5293
5377
|
"fix-a-reported-bug"
|
|
5294
5378
|
];
|
|
5295
5379
|
var ALL_SKILLS = [SETUP_SKILL, ...WORKFLOW_SKILLS];
|
|
5296
|
-
var DEFAULT_SKILLS_DIR =
|
|
5380
|
+
var DEFAULT_SKILLS_DIR = path19.join(".claude", "skills");
|
|
5297
5381
|
function findTemplatesDir(fromDir) {
|
|
5298
5382
|
let dir = fromDir;
|
|
5299
5383
|
for (let i = 0; i < 6; i++) {
|
|
5300
|
-
if (fs14.existsSync(
|
|
5301
|
-
const parent =
|
|
5384
|
+
if (fs14.existsSync(path19.join(dir, "templates", "skills"))) return path19.join(dir, "templates");
|
|
5385
|
+
const parent = path19.dirname(dir);
|
|
5302
5386
|
if (parent === dir) break;
|
|
5303
5387
|
dir = parent;
|
|
5304
5388
|
}
|
|
5305
|
-
return
|
|
5389
|
+
return path19.resolve(fromDir, "..", "..", "templates");
|
|
5306
5390
|
}
|
|
5307
5391
|
function readFrontMatter(markdown) {
|
|
5308
5392
|
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(markdown);
|
|
@@ -5316,14 +5400,14 @@ function readFrontMatter(markdown) {
|
|
|
5316
5400
|
}
|
|
5317
5401
|
function readTemplatesVersion(templatesDir) {
|
|
5318
5402
|
try {
|
|
5319
|
-
const pkg = JSON.parse(fs14.readFileSync(
|
|
5403
|
+
const pkg = JSON.parse(fs14.readFileSync(path19.join(templatesDir, "..", "package.json"), "utf-8"));
|
|
5320
5404
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
5321
5405
|
} catch {
|
|
5322
5406
|
return "0.0.0";
|
|
5323
5407
|
}
|
|
5324
5408
|
}
|
|
5325
5409
|
function templatePath(templatesDir, slug) {
|
|
5326
|
-
return
|
|
5410
|
+
return path19.join(templatesDir, "skills", slug, "SKILL.md");
|
|
5327
5411
|
}
|
|
5328
5412
|
function listSkills(templatesDir, slugs = ALL_SKILLS) {
|
|
5329
5413
|
const infos = [];
|
|
@@ -5340,8 +5424,8 @@ function installSkills(opts) {
|
|
|
5340
5424
|
for (const slug of opts.slugs) {
|
|
5341
5425
|
const step = `skill:${slug}`;
|
|
5342
5426
|
const source = templatePath(opts.templatesDir, slug);
|
|
5343
|
-
const relDest =
|
|
5344
|
-
const dest =
|
|
5427
|
+
const relDest = path19.join(opts.skillsDir, slug, "SKILL.md");
|
|
5428
|
+
const dest = path19.join(opts.root, relDest);
|
|
5345
5429
|
if (!fs14.existsSync(source)) {
|
|
5346
5430
|
results.push({ step, status: "error", detail: `no template found for "${slug}"` });
|
|
5347
5431
|
continue;
|
|
@@ -5373,7 +5457,7 @@ function installSkills(opts) {
|
|
|
5373
5457
|
}
|
|
5374
5458
|
}
|
|
5375
5459
|
if (!opts.dryRun) {
|
|
5376
|
-
fs14.mkdirSync(
|
|
5460
|
+
fs14.mkdirSync(path19.dirname(dest), { recursive: true });
|
|
5377
5461
|
fs14.writeFileSync(dest, contents);
|
|
5378
5462
|
}
|
|
5379
5463
|
results.push({ step, file: relDest, status: exists ? "updated" : "created", detail });
|
|
@@ -5446,7 +5530,7 @@ function runSkills(argv, templatesDir, cwd = process.cwd()) {
|
|
|
5446
5530
|
}
|
|
5447
5531
|
const results = installSkills({
|
|
5448
5532
|
templatesDir,
|
|
5449
|
-
root:
|
|
5533
|
+
root: path19.resolve(readOption5(rest, "--cwd") ?? cwd),
|
|
5450
5534
|
skillsDir: readOption5(rest, "--dir") ?? DEFAULT_SKILLS_DIR,
|
|
5451
5535
|
slugs: requested.length ? requested : ALL_SKILLS,
|
|
5452
5536
|
force: rest.includes("--force"),
|
|
@@ -5468,6 +5552,7 @@ function runSkills(argv, templatesDir, cwd = process.cwd()) {
|
|
|
5468
5552
|
|
|
5469
5553
|
// src/cli/init.ts
|
|
5470
5554
|
var DEFAULT_SERVER_URL = "http://localhost:3000";
|
|
5555
|
+
var DESKTOP_APP_MODES = ["prefer", "fallback", "off"];
|
|
5471
5556
|
var USAGE8 = `
|
|
5472
5557
|
piwi init \u2014 wire a Playwright project up to a Piwi Dashboard
|
|
5473
5558
|
|
|
@@ -5484,9 +5569,14 @@ What it does (each step is idempotent and safe to re-run):
|
|
|
5484
5569
|
Options:
|
|
5485
5570
|
--server-url <url> Dashboard URL to write into the config (env PIWI_DASHBOARD_URL,
|
|
5486
5571
|
default ${DEFAULT_SERVER_URL})
|
|
5572
|
+
--no-server-url Write no URL into the config: the reporter finds the Piwi desktop
|
|
5573
|
+
app running on this machine, or reads PIWI_DASHBOARD_URL
|
|
5487
5574
|
--project <name> Project name to report under (default: your package/folder name)
|
|
5488
|
-
--
|
|
5489
|
-
|
|
5575
|
+
--desktop-app <mode> Write desktopApp into the config: "prefer" sends local runs to the
|
|
5576
|
+
Piwi desktop app while it runs, and CI runs to --server-url
|
|
5577
|
+
--api-key <key> API key to write into .env (env PIWI_API_KEY), where the reporter
|
|
5578
|
+
reads it. Omit to keep it a .env.example placeholder you fill in
|
|
5579
|
+
yourself.
|
|
5490
5580
|
--cwd <path> Project root to operate on (default: current directory)
|
|
5491
5581
|
--skills <list> Comma-separated skills to install, or "all" / "none"
|
|
5492
5582
|
(default: ${WORKFLOW_SKILLS.join(", ")})
|
|
@@ -5517,13 +5607,15 @@ function resolveSkillSlugs(raw) {
|
|
|
5517
5607
|
return raw.split(",").map((slug) => slug.trim()).filter(Boolean);
|
|
5518
5608
|
}
|
|
5519
5609
|
function parseInitArgs(argv, env, cwd) {
|
|
5520
|
-
const root =
|
|
5610
|
+
const root = path20.resolve(readOption6(argv, "--cwd") ?? cwd);
|
|
5521
5611
|
const detected = detectProject(root);
|
|
5522
5612
|
return {
|
|
5523
5613
|
root,
|
|
5524
|
-
serverUrl: (readOption6(argv, "--server-url") ?? env.PIWI_DASHBOARD_URL ?? DEFAULT_SERVER_URL).replace(/\/$/, ""),
|
|
5614
|
+
serverUrl: argv.includes("--no-server-url") ? "" : (readOption6(argv, "--server-url") ?? env.PIWI_DASHBOARD_URL ?? DEFAULT_SERVER_URL).replace(/\/$/, ""),
|
|
5525
5615
|
projectName: readOption6(argv, "--project") ?? detected.suggestedProjectName,
|
|
5526
|
-
|
|
5616
|
+
desktopApp: argv.includes("--desktop-app") ? readOption6(argv, "--desktop-app") ?? "" : null,
|
|
5617
|
+
// The desktop app needs no key, and a key with no URL keeps the reporter from finding it.
|
|
5618
|
+
apiKey: argv.includes("--no-server-url") ? null : readOption6(argv, "--api-key") ?? env.PIWI_API_KEY ?? null,
|
|
5527
5619
|
skillsDir: readOption6(argv, "--skills-dir") ?? DEFAULT_SKILLS_DIR,
|
|
5528
5620
|
skillSlugs: resolveSkillSlugs(readOption6(argv, "--skills")),
|
|
5529
5621
|
install: !argv.includes("--no-install"),
|
|
@@ -5582,18 +5674,29 @@ function stepConfig(project, opts) {
|
|
|
5582
5674
|
status: "manual",
|
|
5583
5675
|
detail: "No playwright.config found \u2014 create one, then re-run `npx @piwitests/reporter init`"
|
|
5584
5676
|
};
|
|
5585
|
-
const rel =
|
|
5586
|
-
const
|
|
5677
|
+
const rel = path20.relative(project.root, project.configPath) || path20.basename(project.configPath);
|
|
5678
|
+
const source = readFileOr(project.configPath, "");
|
|
5679
|
+
const desktopApp = opts.desktopApp ?? void 0;
|
|
5680
|
+
const edit = wrapPlaywrightConfig(source, {
|
|
5587
5681
|
serverUrl: opts.serverUrl,
|
|
5588
|
-
projectName: opts.projectName
|
|
5682
|
+
projectName: opts.projectName,
|
|
5683
|
+
desktopApp
|
|
5589
5684
|
});
|
|
5590
5685
|
if (edit.status === "updated" && !opts.dryRun) fs15.writeFileSync(project.configPath, edit.text);
|
|
5686
|
+
if (edit.status === "already" && desktopApp && !new RegExp(`\\bdesktopApp\\s*:\\s*['"\`]${desktopApp}['"\`]`).test(source)) {
|
|
5687
|
+
return {
|
|
5688
|
+
step,
|
|
5689
|
+
file: rel,
|
|
5690
|
+
status: "manual",
|
|
5691
|
+
detail: `The Piwi reporter is already wired in \u2014 add \`desktopApp: '${desktopApp}'\` to its options`
|
|
5692
|
+
};
|
|
5693
|
+
}
|
|
5591
5694
|
return { step, file: rel, status: edit.status, detail: edit.detail };
|
|
5592
5695
|
}
|
|
5593
5696
|
function stepFixtures(project, opts) {
|
|
5594
5697
|
const step = "fixtures";
|
|
5595
|
-
const rel =
|
|
5596
|
-
const dest =
|
|
5698
|
+
const rel = path20.join("tests", project.configLang === "js" ? "fixtures.js" : "fixtures.ts");
|
|
5699
|
+
const dest = path20.join(project.root, rel);
|
|
5597
5700
|
if (fs15.existsSync(dest)) {
|
|
5598
5701
|
const current = readFileOr(dest, "");
|
|
5599
5702
|
if (/piwiFixtures/.test(current))
|
|
@@ -5606,7 +5709,7 @@ function stepFixtures(project, opts) {
|
|
|
5606
5709
|
};
|
|
5607
5710
|
}
|
|
5608
5711
|
if (!opts.dryRun) {
|
|
5609
|
-
fs15.mkdirSync(
|
|
5712
|
+
fs15.mkdirSync(path20.dirname(dest), { recursive: true });
|
|
5610
5713
|
fs15.writeFileSync(dest, fixturesContents());
|
|
5611
5714
|
}
|
|
5612
5715
|
return {
|
|
@@ -5618,7 +5721,7 @@ function stepFixtures(project, opts) {
|
|
|
5618
5721
|
}
|
|
5619
5722
|
function stepEnv(project, opts) {
|
|
5620
5723
|
const results = [];
|
|
5621
|
-
const examplePath =
|
|
5724
|
+
const examplePath = path20.join(project.root, ".env.example");
|
|
5622
5725
|
const example = upsertEnvKeys(readFileOr(examplePath, ""), [
|
|
5623
5726
|
["PIWI_DASHBOARD_URL", opts.serverUrl],
|
|
5624
5727
|
["PIWI_API_KEY", ""]
|
|
@@ -5631,11 +5734,10 @@ function stepEnv(project, opts) {
|
|
|
5631
5734
|
detail: example.added.length ? `Recorded ${example.added.join(", ")}` : "connection template already present"
|
|
5632
5735
|
});
|
|
5633
5736
|
if (opts.apiKey) {
|
|
5634
|
-
const envPath =
|
|
5635
|
-
const
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
]);
|
|
5737
|
+
const envPath = path20.join(project.root, ".env");
|
|
5738
|
+
const entries = [["PIWI_API_KEY", opts.apiKey]];
|
|
5739
|
+
if (opts.serverUrl) entries.unshift(["PIWI_DASHBOARD_URL", opts.serverUrl]);
|
|
5740
|
+
const env = upsertEnvKeys(readFileOr(envPath, ""), entries);
|
|
5639
5741
|
if (env.added.length && !opts.dryRun) fs15.writeFileSync(envPath, env.text);
|
|
5640
5742
|
results.push({
|
|
5641
5743
|
step: "env",
|
|
@@ -5643,7 +5745,7 @@ function stepEnv(project, opts) {
|
|
|
5643
5745
|
status: env.added.length ? "updated" : "already",
|
|
5644
5746
|
detail: env.added.length ? `Wrote ${env.added.join(", ")} (keep .env out of git)` : "already set"
|
|
5645
5747
|
});
|
|
5646
|
-
const gitignorePath =
|
|
5748
|
+
const gitignorePath = path20.join(project.root, ".gitignore");
|
|
5647
5749
|
const gitignore = ensureGitignoreEntry(readFileOr(gitignorePath, ""));
|
|
5648
5750
|
if (gitignore.added && !opts.dryRun) fs15.writeFileSync(gitignorePath, gitignore.text);
|
|
5649
5751
|
results.push({
|
|
@@ -5657,16 +5759,26 @@ function stepEnv(project, opts) {
|
|
|
5657
5759
|
}
|
|
5658
5760
|
function nextSteps(opts, steps) {
|
|
5659
5761
|
const out = [];
|
|
5660
|
-
if (!opts.apiKey) {
|
|
5762
|
+
if (!opts.apiKey && opts.serverUrl) {
|
|
5661
5763
|
out.push(
|
|
5662
|
-
"If your dashboard has authentication on, create an API key (Settings \u2192 Account \u2192 API keys)
|
|
5764
|
+
"If your dashboard has authentication on, create an API key (Settings \u2192 Account \u2192 API keys) and put it in .env as PIWI_API_KEY: the reporter reads it from the .env next to your Playwright config. Keep .env out of git; in CI, set PIWI_API_KEY as a secret."
|
|
5663
5765
|
);
|
|
5664
5766
|
}
|
|
5665
5767
|
if (steps.some((s) => s.status === "manual")) {
|
|
5666
5768
|
out.push("Finish the steps marked [manual] above \u2014 each one carries the exact change to make.");
|
|
5667
5769
|
}
|
|
5668
5770
|
out.push("In your specs, import `test` (and `expect`) from your fixtures file instead of `@playwright/test`.");
|
|
5669
|
-
|
|
5771
|
+
if (!opts.serverUrl) {
|
|
5772
|
+
out.push(
|
|
5773
|
+
"Run `npx playwright test` \u2014 the run appears in the Piwi desktop app while it is running. Elsewhere, set PIWI_DASHBOARD_URL to the dashboard the runs go to."
|
|
5774
|
+
);
|
|
5775
|
+
} else if (opts.desktopApp === "prefer") {
|
|
5776
|
+
out.push(
|
|
5777
|
+
`Run \`npx playwright test\` \u2014 the run appears in the Piwi desktop app while it is running, and at ${opts.serverUrl} on CI or with the app closed.`
|
|
5778
|
+
);
|
|
5779
|
+
} else {
|
|
5780
|
+
out.push(`Run \`npx playwright test\` \u2014 the run should appear at ${opts.serverUrl}.`);
|
|
5781
|
+
}
|
|
5670
5782
|
out.push(
|
|
5671
5783
|
"For a machine-readable check, set PIWI_OUTPUT_FILE=piwi-run.json and read runUrl from that file after the run."
|
|
5672
5784
|
);
|
|
@@ -5678,6 +5790,10 @@ async function runInit(argv, env = process.env, cwd = process.cwd()) {
|
|
|
5678
5790
|
return 0;
|
|
5679
5791
|
}
|
|
5680
5792
|
const opts = parseInitArgs(argv, env, cwd);
|
|
5793
|
+
if (opts.desktopApp !== null && !DESKTOP_APP_MODES.includes(opts.desktopApp)) {
|
|
5794
|
+
console.error(`--desktop-app must be one of ${DESKTOP_APP_MODES.join(", ")} (got "${opts.desktopApp}")`);
|
|
5795
|
+
return 1;
|
|
5796
|
+
}
|
|
5681
5797
|
const project = detectProject(opts.root);
|
|
5682
5798
|
const steps = [];
|
|
5683
5799
|
if (!opts.skillsOnly) {
|
|
@@ -5707,9 +5823,10 @@ async function runInit(argv, env = process.env, cwd = process.cwd()) {
|
|
|
5707
5823
|
project: {
|
|
5708
5824
|
root: project.root,
|
|
5709
5825
|
packageManager: project.packageManager,
|
|
5710
|
-
configPath: project.configPath ?
|
|
5826
|
+
configPath: project.configPath ? path20.relative(project.root, project.configPath) : null,
|
|
5711
5827
|
projectName: opts.projectName,
|
|
5712
|
-
serverUrl: opts.serverUrl
|
|
5828
|
+
serverUrl: opts.serverUrl,
|
|
5829
|
+
desktopApp: opts.desktopApp
|
|
5713
5830
|
},
|
|
5714
5831
|
steps,
|
|
5715
5832
|
nextSteps: guidance
|
|
@@ -5732,1197 +5849,1280 @@ Piwi setup${opts.dryRun ? " (dry run \u2014 nothing written)" : ""} for "${opts.
|
|
|
5732
5849
|
|
|
5733
5850
|
// src/cli/select.ts
|
|
5734
5851
|
var fs17 = __toESM(require("fs"));
|
|
5735
|
-
var
|
|
5852
|
+
var path22 = __toESM(require("path"));
|
|
5736
5853
|
var import_node_child_process7 = require("child_process");
|
|
5737
5854
|
|
|
5738
|
-
// src/
|
|
5739
|
-
var
|
|
5740
|
-
var
|
|
5741
|
-
var
|
|
5742
|
-
var
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
function playwrightSupportsAddReporter(version) {
|
|
5746
|
-
const match = version ? /^(\d+)\.(\d+)/.exec(version) : null;
|
|
5747
|
-
if (!match) return false;
|
|
5748
|
-
const major = Number(match[1]);
|
|
5749
|
-
const minor = Number(match[2]);
|
|
5750
|
-
return major > ADD_REPORTER_MIN.major || major === ADD_REPORTER_MIN.major && minor >= ADD_REPORTER_MIN.minor;
|
|
5855
|
+
// ../core/src/diff-anchors.ts
|
|
5856
|
+
var RENAME_FROM = `rename from `;
|
|
5857
|
+
var RENAME_TO = "rename to ";
|
|
5858
|
+
var HUNK_HEADER = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
|
|
5859
|
+
var INDEX_LINE = /^index ([0-9a-f]+)\.\.([0-9a-f]+)(?: |$)/i;
|
|
5860
|
+
function blobOf(id) {
|
|
5861
|
+
return /^0+$/.test(id) ? void 0 : id.toLowerCase();
|
|
5751
5862
|
}
|
|
5752
|
-
function
|
|
5753
|
-
|
|
5863
|
+
function stripPrefix(path25) {
|
|
5864
|
+
const unquoted = path25.startsWith('"') && path25.endsWith('"') ? path25.slice(1, -1) : path25;
|
|
5865
|
+
return unquoted.replace(/^[ab]\//, "");
|
|
5754
5866
|
}
|
|
5755
|
-
function
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5867
|
+
function parseUnifiedDiff(text) {
|
|
5868
|
+
const files = [];
|
|
5869
|
+
let file = null;
|
|
5870
|
+
let hunk = null;
|
|
5871
|
+
let oldLine = 0;
|
|
5872
|
+
let newLine = 0;
|
|
5873
|
+
let oldLeft = 0;
|
|
5874
|
+
let newLeft = 0;
|
|
5875
|
+
const current = () => {
|
|
5876
|
+
if (!file) {
|
|
5877
|
+
file = { path: "", status: "modified", hunks: [] };
|
|
5878
|
+
files.push(file);
|
|
5879
|
+
}
|
|
5880
|
+
return file;
|
|
5766
5881
|
};
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5882
|
+
for (const line of text.split(/\r?\n/)) {
|
|
5883
|
+
if (hunk) {
|
|
5884
|
+
if (line.startsWith("-") && oldLeft > 0) {
|
|
5885
|
+
hunk.removed.push({ line: oldLine++, text: line.slice(1) });
|
|
5886
|
+
oldLeft--;
|
|
5887
|
+
} else if (line.startsWith("+") && newLeft > 0) {
|
|
5888
|
+
hunk.added.push({ line: newLine++, text: line.slice(1) });
|
|
5889
|
+
newLeft--;
|
|
5890
|
+
} else if ((line.startsWith(" ") || line === "") && oldLeft > 0 && newLeft > 0) {
|
|
5891
|
+
oldLine++;
|
|
5892
|
+
newLine++;
|
|
5893
|
+
oldLeft--;
|
|
5894
|
+
newLeft--;
|
|
5895
|
+
} else if (!line.startsWith("\\")) hunk = null;
|
|
5896
|
+
if (hunk && oldLeft === 0 && newLeft === 0) hunk = null;
|
|
5897
|
+
if (hunk || !line.startsWith("@@")) continue;
|
|
5776
5898
|
}
|
|
5899
|
+
const header = HUNK_HEADER.exec(line);
|
|
5900
|
+
if (header) {
|
|
5901
|
+
oldLine = Number(header[1]);
|
|
5902
|
+
newLine = Number(header[3]);
|
|
5903
|
+
oldLeft = header[2] === void 0 ? 1 : Number(header[2]);
|
|
5904
|
+
newLeft = header[4] === void 0 ? 1 : Number(header[4]);
|
|
5905
|
+
hunk = { oldStart: oldLine, newStart: newLine, removed: [], added: [] };
|
|
5906
|
+
current().hunks.push(hunk);
|
|
5907
|
+
if (oldLeft === 0 && newLeft === 0) hunk = null;
|
|
5908
|
+
continue;
|
|
5909
|
+
}
|
|
5910
|
+
if (line.startsWith("diff --git ")) {
|
|
5911
|
+
const m = /^diff --git (\S+|"[^"]+") (\S+|"[^"]+")$/.exec(line);
|
|
5912
|
+
file = { path: m ? stripPrefix(m[2]) : "", status: "modified", hunks: [] };
|
|
5913
|
+
files.push(file);
|
|
5914
|
+
} else if (line.startsWith("--- ")) {
|
|
5915
|
+
const f = file && !file.hunks.length ? file : (file = null, current());
|
|
5916
|
+
const old = line.slice(4).trim();
|
|
5917
|
+
if (old === "/dev/null") f.status = "added";
|
|
5918
|
+
else if (!f.path) f.path = stripPrefix(old);
|
|
5919
|
+
} else if (line.startsWith("+++ ")) {
|
|
5920
|
+
const next = line.slice(4).trim();
|
|
5921
|
+
if (next === "/dev/null") current().status = "deleted";
|
|
5922
|
+
else current().path = stripPrefix(next);
|
|
5923
|
+
} else if (line.startsWith(RENAME_FROM)) {
|
|
5924
|
+
current().oldPath = line.slice(RENAME_FROM.length);
|
|
5925
|
+
current().status = "renamed";
|
|
5926
|
+
} else if (line.startsWith(RENAME_TO)) current().path = line.slice(RENAME_TO.length);
|
|
5927
|
+
else if (INDEX_LINE.test(line)) {
|
|
5928
|
+
const [, oldId, newId] = INDEX_LINE.exec(line);
|
|
5929
|
+
const f = current();
|
|
5930
|
+
const oldBlob = blobOf(oldId);
|
|
5931
|
+
const newBlob = blobOf(newId);
|
|
5932
|
+
if (oldBlob) f.oldBlob = oldBlob;
|
|
5933
|
+
if (newBlob) f.newBlob = newBlob;
|
|
5934
|
+
} else if (line.startsWith("new file mode")) current().status = "added";
|
|
5935
|
+
else if (line.startsWith("deleted file mode")) current().status = "deleted";
|
|
5777
5936
|
}
|
|
5778
|
-
return
|
|
5779
|
-
}
|
|
5780
|
-
function readConfigSource(configPath) {
|
|
5781
|
-
if (!configPath) return null;
|
|
5782
|
-
try {
|
|
5783
|
-
return fs16.readFileSync(configPath, "utf-8");
|
|
5784
|
-
} catch {
|
|
5785
|
-
return null;
|
|
5786
|
-
}
|
|
5937
|
+
return files.filter((f) => f.path);
|
|
5787
5938
|
}
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5939
|
+
var DEFAULT_TRANSLATION_GLOBS = ["**/{locales,locale,i18n,lang,translations}/**", "**/*.resx"];
|
|
5940
|
+
var ANCHOR_ATTRIBUTES = [
|
|
5941
|
+
"id",
|
|
5942
|
+
"name",
|
|
5943
|
+
"aria-label",
|
|
5944
|
+
"placeholder",
|
|
5945
|
+
"alt",
|
|
5946
|
+
"title",
|
|
5947
|
+
"role",
|
|
5948
|
+
"value",
|
|
5949
|
+
"label"
|
|
5950
|
+
];
|
|
5951
|
+
var TRANSLATION_EXTENSIONS = /\.(json|ya?ml|properties|po|resx)$/i;
|
|
5952
|
+
var MARKUP_FILE = /\.(vue|svelte|html?|cshtml|razor|hbs|handlebars|jsx|tsx|astro|njk|liquid|erb|php)$/i;
|
|
5953
|
+
var MIN_LENGTH = 2;
|
|
5954
|
+
var MAX_LENGTH = 200;
|
|
5955
|
+
function expandBraces(glob) {
|
|
5956
|
+
const m = /\{([^{}]*)\}/.exec(glob);
|
|
5957
|
+
if (!m) return [glob];
|
|
5958
|
+
return m[1].split(",").flatMap((alt) => expandBraces(glob.slice(0, m.index) + alt + glob.slice(m.index + m[0].length)));
|
|
5959
|
+
}
|
|
5960
|
+
function pathGlobToRegExp(glob) {
|
|
5961
|
+
const parts = expandBraces(glob).map((g) => {
|
|
5962
|
+
let re = "";
|
|
5963
|
+
for (let i = 0; i < g.length; i++) {
|
|
5964
|
+
const c = g[i];
|
|
5965
|
+
if (c === "*") {
|
|
5966
|
+
if (g[i + 1] === "*") {
|
|
5967
|
+
i++;
|
|
5968
|
+
if (g[i + 1] === "/") {
|
|
5969
|
+
i++;
|
|
5970
|
+
re += "(?:.*/)?";
|
|
5971
|
+
} else re += ".*";
|
|
5972
|
+
} else re += "[^/]*";
|
|
5973
|
+
} else if (c === "?") re += "[^/]";
|
|
5974
|
+
else if ("\\^$.|+()[]{}".includes(c)) re += "\\" + c;
|
|
5975
|
+
else re += c;
|
|
5796
5976
|
}
|
|
5797
|
-
|
|
5798
|
-
}
|
|
5799
|
-
return
|
|
5977
|
+
return re;
|
|
5978
|
+
});
|
|
5979
|
+
return new RegExp(`^(?:${parts.join("|")})$`);
|
|
5800
5980
|
}
|
|
5801
|
-
function
|
|
5802
|
-
const
|
|
5803
|
-
|
|
5981
|
+
function isTranslationFile(path25, globs = DEFAULT_TRANSLATION_GLOBS) {
|
|
5982
|
+
const posix = path25.replace(/\\/g, "/");
|
|
5983
|
+
if (!TRANSLATION_EXTENSIONS.test(posix)) return false;
|
|
5984
|
+
return globs.some((g) => pathGlobToRegExp(g).test(posix));
|
|
5804
5985
|
}
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
piwi select / piwi run \u2014 run a saved selection of tests
|
|
5811
|
-
|
|
5812
|
-
Usage:
|
|
5813
|
-
npx @piwitests/reporter select <key> [options] print the Playwright args
|
|
5814
|
-
npx @piwitests/reporter run <key> [options] [-- <playwright args>]
|
|
5815
|
-
npx @piwitests/reporter run impact --base <ref> run the tests your diff impacts
|
|
5816
|
-
|
|
5817
|
-
Connection:
|
|
5818
|
-
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL)
|
|
5819
|
-
--api-key <key> API key (env PIWI_API_KEY)
|
|
5820
|
-
--project <name|id> Project (env PIWI_PROJECT_NAME)
|
|
5821
|
-
|
|
5822
|
-
Selection:
|
|
5823
|
-
--format <fmt> args (file:line, default) | grep | files | json
|
|
5824
|
-
--budget <duration> Cap total time, e.g. 5m, 90s, 300000 (ms)
|
|
5825
|
-
--shard <i/n> Keep only shard i of n, balanced by duration, lock-aware
|
|
5826
|
-
--fail-fast Order the least-reliable tests first (fail-fast)
|
|
5827
|
-
--base <ref> For "impact": the ref to diff the working tree against
|
|
5828
|
-
|
|
5829
|
-
Behavior:
|
|
5830
|
-
--strict Fail (exit 2) instead of falling back when unreachable
|
|
5831
|
-
--pkg-runner <cmd> Package runner for the printed command (default npx)
|
|
5832
|
-
--json Print the full resolution as JSON (select only)
|
|
5833
|
-
-h, --help Show this help
|
|
5834
|
-
|
|
5835
|
-
Exit codes: 0 ok, 1 the test run failed (run only), 2 could not resolve.
|
|
5836
|
-
`.trim();
|
|
5837
|
-
var IMPACT_KEY = "impact";
|
|
5838
|
-
var VALUE_FLAGS3 = /* @__PURE__ */ new Set([
|
|
5839
|
-
"--server-url",
|
|
5840
|
-
"--api-key",
|
|
5841
|
-
"--project",
|
|
5842
|
-
"--format",
|
|
5843
|
-
"--budget",
|
|
5844
|
-
"--shard",
|
|
5845
|
-
"--base",
|
|
5846
|
-
"--pkg-runner"
|
|
5847
|
-
]);
|
|
5848
|
-
function readOption7(argv, name) {
|
|
5849
|
-
const withEquals = argv.find((arg) => arg.startsWith(`${name}=`));
|
|
5850
|
-
if (withEquals) return withEquals.slice(name.length + 1);
|
|
5851
|
-
const index = argv.indexOf(name);
|
|
5852
|
-
if (index === -1) return void 0;
|
|
5853
|
-
const value = argv[index + 1];
|
|
5854
|
-
return value && !value.startsWith("-") ? value : void 0;
|
|
5986
|
+
function keepable(value) {
|
|
5987
|
+
const v = value.trim();
|
|
5988
|
+
if (v.length < MIN_LENGTH || v.length > MAX_LENGTH) return false;
|
|
5989
|
+
if (/^[-+]?[\d.,]+$/.test(v)) return false;
|
|
5990
|
+
return new RegExp("\\p{L}", "u").test(v);
|
|
5855
5991
|
}
|
|
5856
|
-
function
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5992
|
+
function readQuoted(text, start) {
|
|
5993
|
+
const quote3 = text[start];
|
|
5994
|
+
let value = "";
|
|
5995
|
+
for (let i = start + 1; i < text.length; i++) {
|
|
5996
|
+
const ch = text[i];
|
|
5997
|
+
if (ch === "\\" && i + 1 < text.length) {
|
|
5998
|
+
const next = text[i + 1];
|
|
5999
|
+
value += next === "n" ? "\n" : next === "t" ? " " : next;
|
|
6000
|
+
i++;
|
|
5861
6001
|
continue;
|
|
5862
6002
|
}
|
|
5863
|
-
return
|
|
6003
|
+
if (quote3 === "`" && ch === "$" && text[i + 1] === "{") return null;
|
|
6004
|
+
if (ch === quote3) return { value, end: i + 1 };
|
|
6005
|
+
value += ch;
|
|
5864
6006
|
}
|
|
5865
|
-
return
|
|
6007
|
+
return null;
|
|
5866
6008
|
}
|
|
5867
|
-
function
|
|
5868
|
-
|
|
5869
|
-
if (!match) return null;
|
|
5870
|
-
const value = Number(match[1]);
|
|
5871
|
-
const unit = match[2];
|
|
5872
|
-
const factor = unit === "h" ? 36e5 : unit === "m" ? 6e4 : unit === "s" ? 1e3 : 1;
|
|
5873
|
-
const ms = Math.round(value * factor);
|
|
5874
|
-
return ms > 0 ? ms : null;
|
|
6009
|
+
function escapeRegExp(s) {
|
|
6010
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5875
6011
|
}
|
|
5876
|
-
function
|
|
5877
|
-
const
|
|
5878
|
-
const
|
|
5879
|
-
const
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
if (
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
6012
|
+
function attributeTokens(text, attributes, line, spans) {
|
|
6013
|
+
const tokens = [];
|
|
6014
|
+
const names = attributes.map(escapeRegExp).join("|");
|
|
6015
|
+
const re = new RegExp(
|
|
6016
|
+
`(?<![\\w-])(?:v-bind:|:|\\[(?:attr\\.)?)?(["']?)(${names})\\1\\]?\\s*(=|:)\\s*(\\{\\s*)?(["'\`])`,
|
|
6017
|
+
"g"
|
|
6018
|
+
);
|
|
6019
|
+
let m;
|
|
6020
|
+
while ((m = re.exec(text)) !== null) {
|
|
6021
|
+
const bound = m[0].startsWith(":") || m[0].startsWith("v-bind:") || m[0].startsWith("[");
|
|
6022
|
+
const quoteAt = m.index + m[0].length - 1;
|
|
6023
|
+
const outer = readQuoted(text, quoteAt);
|
|
6024
|
+
if (!outer) continue;
|
|
6025
|
+
let value = outer.value;
|
|
6026
|
+
if (bound) {
|
|
6027
|
+
const inner = /^\s*(['"`])(.*)\1\s*$/.exec(value);
|
|
6028
|
+
if (!inner) continue;
|
|
6029
|
+
value = inner[2];
|
|
6030
|
+
}
|
|
6031
|
+
spans.push({ start: m.index, end: outer.end });
|
|
6032
|
+
re.lastIndex = outer.end;
|
|
6033
|
+
if (!keepable(value) || value.includes("${")) continue;
|
|
6034
|
+
const attribute = m[2];
|
|
6035
|
+
tokens.push({ kind: "attribute", group: `attribute:${attribute}`, value, attribute, line });
|
|
5893
6036
|
}
|
|
5894
|
-
return
|
|
5895
|
-
serverUrl,
|
|
5896
|
-
apiKey: readOption7(own, "--api-key") ?? env.PIWI_API_KEY ?? null,
|
|
5897
|
-
project: readOption7(own, "--project") ?? env.PIWI_PROJECT_NAME ?? "",
|
|
5898
|
-
key,
|
|
5899
|
-
format: readOption7(own, "--format") ?? "args",
|
|
5900
|
-
budgetMs,
|
|
5901
|
-
shard,
|
|
5902
|
-
order: own.includes("--fail-fast") ? "failureLikelihood" : null,
|
|
5903
|
-
base: readOption7(own, "--base") ?? null,
|
|
5904
|
-
strict: own.includes("--strict"),
|
|
5905
|
-
pkgRunner: readOption7(own, "--pkg-runner") ?? "npx",
|
|
5906
|
-
json: own.includes("--json"),
|
|
5907
|
-
extra
|
|
5908
|
-
};
|
|
5909
|
-
}
|
|
5910
|
-
var CACHE_FILE = path21.join(".piwi", "selection-cache.json");
|
|
5911
|
-
function cacheKey(args) {
|
|
5912
|
-
return `${args.project.toLowerCase()}:${args.key}:${args.format}:${args.budgetMs ?? 0}:${args.shard ?? ""}:${args.order ?? ""}`;
|
|
6037
|
+
return tokens;
|
|
5913
6038
|
}
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
6039
|
+
var TRANSLATION_CALL = /(?:(?<![\w$.])(?:\$?t|i18n\.t|translate|formatMessage)\s*\(\s*(?:\{\s*id\s*:\s*)?(['"`])([\w.:-]+)\1|\b\w*[Ll]ocalizer\s*\[\s*"([\w.:-]+)"\s*\])/g;
|
|
6040
|
+
function translationRefTokens(text, line, side, lookup, spans) {
|
|
6041
|
+
const tokens = [];
|
|
6042
|
+
for (const m of text.matchAll(TRANSLATION_CALL)) {
|
|
6043
|
+
spans.push({ start: m.index, end: m.index + m[0].length });
|
|
6044
|
+
const key = m[2] ?? m[3];
|
|
6045
|
+
const value = lookup?.(key, side);
|
|
6046
|
+
if (value === void 0 || !keepable(value)) continue;
|
|
6047
|
+
tokens.push({ kind: "translation", group: "translation-ref", value, key, line });
|
|
5920
6048
|
}
|
|
6049
|
+
return tokens;
|
|
5921
6050
|
}
|
|
5922
|
-
function
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
6051
|
+
function inSpans(spans, at) {
|
|
6052
|
+
return spans.some((s) => at >= s.start && at < s.end);
|
|
6053
|
+
}
|
|
6054
|
+
var NON_TEXT_LITERAL = /^(?:\.{0,2}\/|[a-z][\w+.-]*:\/\/|#[\w-]+$|@\/|~\/)|\.(?:m?[jt]sx?|vue|svelte|css|scss|json|png|svg|jpe?g)$/i;
|
|
6055
|
+
function literalTokens(text, line, spans, markup) {
|
|
6056
|
+
const tokens = [];
|
|
6057
|
+
if (/^\s*(?:import\b|export\s+\*|\/\/|\*|\/\*|#include|using\s)/.test(text)) return tokens;
|
|
6058
|
+
for (let i = 0; i < text.length; i++) {
|
|
6059
|
+
const ch = text[i];
|
|
6060
|
+
if (ch !== '"' && ch !== "'" && ch !== "`") continue;
|
|
6061
|
+
const lit = readQuoted(text, i);
|
|
6062
|
+
if (!lit) {
|
|
6063
|
+
if (ch === "'") continue;
|
|
6064
|
+
break;
|
|
5928
6065
|
}
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
6066
|
+
const start = i;
|
|
6067
|
+
i = lit.end - 1;
|
|
6068
|
+
if (inSpans(spans, start)) continue;
|
|
6069
|
+
const before = text.slice(0, start);
|
|
6070
|
+
if (markup && /[\w:@.[\]-]=\s*$/.test(before)) continue;
|
|
6071
|
+
if (/\b(?:from|require|import)\s*\(?\s*$/.test(before)) continue;
|
|
6072
|
+
const value = lit.value;
|
|
6073
|
+
if (!keepable(value) || NON_TEXT_LITERAL.test(value)) continue;
|
|
6074
|
+
if (/^[\w-]+(?:\.[\w-]+)+$/.test(value) && !/\s/.test(value)) continue;
|
|
6075
|
+
tokens.push({ kind: "literal", group: "literal", value, line });
|
|
5933
6076
|
}
|
|
6077
|
+
return tokens;
|
|
5934
6078
|
}
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
} catch (e) {
|
|
5942
|
-
if (args.strict) throw e;
|
|
5943
|
-
const cached = readCache(args);
|
|
5944
|
-
if (cached) {
|
|
5945
|
-
console.error(`piwi: dashboard unreachable, using cached resolution \u2014 ${e.message}`);
|
|
5946
|
-
return { resolution: cached, fromCache: true };
|
|
5947
|
-
}
|
|
5948
|
-
return { error: e };
|
|
5949
|
-
}
|
|
6079
|
+
var CODE_LINE = /^(?:import|export|const|let|var|function|return|if|else|for|while|switch|case|default|class|interface|type|await|async|new|throw|try|catch|public|private|protected|static|using|namespace|@\w+\s*\()\b/;
|
|
6080
|
+
function textSegment(raw, line) {
|
|
6081
|
+
const value = raw.replace(/ /g, " ").replace(/\s+/g, " ").trim();
|
|
6082
|
+
if (!keepable(value)) return null;
|
|
6083
|
+
if (/[{}]|@[\w(]|<%|%>/.test(value)) return null;
|
|
6084
|
+
return { kind: "text", group: "text", value, line };
|
|
5950
6085
|
}
|
|
5951
|
-
function
|
|
5952
|
-
|
|
6086
|
+
function textTokens(text, line, markup) {
|
|
6087
|
+
const tokens = [];
|
|
6088
|
+
if (!markup) return tokens;
|
|
6089
|
+
const trimmed = text.trim();
|
|
6090
|
+
if (!trimmed) return tokens;
|
|
6091
|
+
if (!/[<>]/.test(trimmed)) {
|
|
6092
|
+
if (CODE_LINE.test(trimmed) || /[;=(){}[\]"'`]|^\/\/|^\*|^\/\*|^<!--|[,:]$/.test(trimmed)) return tokens;
|
|
6093
|
+
const token = textSegment(trimmed, line);
|
|
6094
|
+
return token ? [token] : tokens;
|
|
6095
|
+
}
|
|
6096
|
+
for (const m of text.matchAll(/>([^<>]+)</g)) {
|
|
6097
|
+
const token = textSegment(m[1], line);
|
|
6098
|
+
if (token) tokens.push(token);
|
|
6099
|
+
}
|
|
6100
|
+
const lead = /^\s*([^<>]+)<\//.exec(text);
|
|
6101
|
+
if (lead && !/[=;(){}]/.test(lead[1])) {
|
|
6102
|
+
const token = textSegment(lead[1], line);
|
|
6103
|
+
if (token) tokens.push(token);
|
|
6104
|
+
}
|
|
6105
|
+
const tail = />([^<>]+)$/.exec(text);
|
|
6106
|
+
if (tail && !/[=;(){}"']/.test(tail[1])) {
|
|
6107
|
+
const token = textSegment(tail[1], line);
|
|
6108
|
+
if (token) tokens.push(token);
|
|
6109
|
+
}
|
|
6110
|
+
return tokens;
|
|
5953
6111
|
}
|
|
5954
|
-
function
|
|
5955
|
-
const
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
}
|
|
5959
|
-
return
|
|
5960
|
-
...new Set(
|
|
5961
|
-
out.split("\n").map((line) => line.trim()).filter(Boolean)
|
|
5962
|
-
)
|
|
5963
|
-
];
|
|
6112
|
+
function unquoteYaml(raw) {
|
|
6113
|
+
const v = raw.trim();
|
|
6114
|
+
if (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")) {
|
|
6115
|
+
return v.slice(1, -1).replace(/\\"/g, '"').replace(/''/g, "'");
|
|
6116
|
+
}
|
|
6117
|
+
return v.replace(/\s+#.*$/, "");
|
|
5964
6118
|
}
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
return fetchImpact(args, projectId, files);
|
|
6119
|
+
function decodeXml2(text) {
|
|
6120
|
+
return text.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
5968
6121
|
}
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
6122
|
+
function parseTranslationFile(path25, content) {
|
|
6123
|
+
const lines = content.split(/\r?\n/);
|
|
6124
|
+
const out = [];
|
|
6125
|
+
const ext = /\.([a-z]+)$/i.exec(path25)?.[1]?.toLowerCase();
|
|
6126
|
+
if (ext === "json") {
|
|
6127
|
+
const stack = [];
|
|
6128
|
+
const path26 = (key) => [...stack.map((f) => f.key), key].join(".");
|
|
6129
|
+
lines.forEach((text, i) => {
|
|
6130
|
+
const t = text.trim();
|
|
6131
|
+
const top = stack[stack.length - 1];
|
|
6132
|
+
const pair = /^"((?:[^"\\]|\\.)*)"\s*:\s*(.*)$/.exec(t);
|
|
6133
|
+
if (pair) {
|
|
6134
|
+
const key = JSON.parse(`"${pair[1]}"`);
|
|
6135
|
+
const rest = pair[2];
|
|
6136
|
+
const str = /^"((?:[^"\\]|\\.)*)"/.exec(rest);
|
|
6137
|
+
if (str) out.push({ key: path26(key), value: JSON.parse(`"${str[1]}"`), line: i + 1 });
|
|
6138
|
+
else if (/^[[{]\s*$/.test(rest)) stack.push({ key, array: rest.startsWith("["), next: 0 });
|
|
6139
|
+
return;
|
|
6140
|
+
}
|
|
6141
|
+
if (top?.array) {
|
|
6142
|
+
const str = /^"((?:[^"\\]|\\.)*)"\s*,?$/.exec(t);
|
|
6143
|
+
if (str) {
|
|
6144
|
+
out.push({ key: path26(top.next++), value: JSON.parse(`"${str[1]}"`), line: i + 1 });
|
|
6145
|
+
return;
|
|
6146
|
+
}
|
|
6147
|
+
if (/^[[{]\s*$/.test(t)) {
|
|
6148
|
+
stack.push({ key: top.next++, array: t === "[", next: 0 });
|
|
6149
|
+
return;
|
|
6150
|
+
}
|
|
6151
|
+
}
|
|
6152
|
+
if (/^[\]}]/.test(t)) stack.pop();
|
|
6153
|
+
});
|
|
6154
|
+
return out;
|
|
5985
6155
|
}
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
6156
|
+
if (ext === "yaml" || ext === "yml") {
|
|
6157
|
+
const stack = [];
|
|
6158
|
+
lines.forEach((text, i) => {
|
|
6159
|
+
const m = /^(\s*)(?:"([^"]+)"|'([^']+)'|([^\s:#][^:#]*?))\s*:(?:\s+(.*))?$/.exec(text);
|
|
6160
|
+
if (!m) return;
|
|
6161
|
+
const indent = m[1].length;
|
|
6162
|
+
const key = (m[2] ?? m[3] ?? m[4]).trim();
|
|
6163
|
+
while (stack.length && stack[stack.length - 1].indent >= indent) stack.pop();
|
|
6164
|
+
const raw = m[5]?.trim();
|
|
6165
|
+
if (!raw || raw.startsWith("#") || raw === "|" || raw === ">") {
|
|
6166
|
+
stack.push({ indent, key });
|
|
6167
|
+
return;
|
|
6168
|
+
}
|
|
6169
|
+
out.push({ key: [...stack.map((s) => s.key), key].join("."), value: unquoteYaml(raw), line: i + 1 });
|
|
6170
|
+
});
|
|
6171
|
+
return out;
|
|
5993
6172
|
}
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
console.error(USAGE9);
|
|
6001
|
-
return EXIT_ERROR7;
|
|
6173
|
+
if (ext === "properties") {
|
|
6174
|
+
lines.forEach((text, i) => {
|
|
6175
|
+
const m = /^\s*([^#!\s][^=:]*?)\s*[=:]\s*(.*)$/.exec(text);
|
|
6176
|
+
if (m && !m[2].endsWith("\\")) out.push({ key: m[1], value: m[2], line: i + 1 });
|
|
6177
|
+
});
|
|
6178
|
+
return out;
|
|
6002
6179
|
}
|
|
6003
|
-
if (
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6180
|
+
if (ext === "po") {
|
|
6181
|
+
let msgid = null;
|
|
6182
|
+
lines.forEach((text, i) => {
|
|
6183
|
+
const id = /^msgid\s+"(.*)"\s*$/.exec(text);
|
|
6184
|
+
if (id) {
|
|
6185
|
+
msgid = id[1].replace(/\\"/g, '"');
|
|
6186
|
+
return;
|
|
6187
|
+
}
|
|
6188
|
+
const str = /^msgstr(?:\[\d+\])?\s+"(.*)"\s*$/.exec(text);
|
|
6189
|
+
if (str && msgid) out.push({ key: msgid, value: str[1].replace(/\\"/g, '"'), line: i + 1 });
|
|
6190
|
+
});
|
|
6191
|
+
return out;
|
|
6009
6192
|
}
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6193
|
+
if (ext === "resx") {
|
|
6194
|
+
let name = null;
|
|
6195
|
+
lines.forEach((text, i) => {
|
|
6196
|
+
const data = /<data\s+name="([^"]+)"/.exec(text);
|
|
6197
|
+
if (data) name = decodeXml2(data[1]);
|
|
6198
|
+
const value = /<value>(.*)<\/value>/.exec(text);
|
|
6199
|
+
if (value && name) {
|
|
6200
|
+
out.push({ key: name, value: decodeXml2(value[1]), line: i + 1 });
|
|
6201
|
+
name = null;
|
|
6202
|
+
}
|
|
6203
|
+
});
|
|
6204
|
+
return out;
|
|
6017
6205
|
}
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6206
|
+
return out;
|
|
6207
|
+
}
|
|
6208
|
+
function translationLineToken(path25, text, line) {
|
|
6209
|
+
const ext = /\.([a-z]+)$/i.exec(path25)?.[1]?.toLowerCase();
|
|
6210
|
+
let key;
|
|
6211
|
+
let value;
|
|
6212
|
+
if (ext === "json") {
|
|
6213
|
+
const pair = /^\s*"((?:[^"\\]|\\.)*)"\s*:\s*"((?:[^"\\]|\\.)*)"/.exec(text);
|
|
6214
|
+
if (pair) {
|
|
6215
|
+
key = JSON.parse(`"${pair[1]}"`);
|
|
6216
|
+
value = JSON.parse(`"${pair[2]}"`);
|
|
6217
|
+
} else {
|
|
6218
|
+
const item = /^\s*"((?:[^"\\]|\\.)*)"\s*,?\s*$/.exec(text);
|
|
6219
|
+
if (item) value = JSON.parse(`"${item[1]}"`);
|
|
6220
|
+
}
|
|
6221
|
+
} else if (ext === "yaml" || ext === "yml") {
|
|
6222
|
+
const m = /^\s*(?:-\s+)?(?:"([^"]+)"|'([^']+)'|([^\s:#][^:#]*?))\s*:\s+(.+)$/.exec(text);
|
|
6223
|
+
if (m) {
|
|
6224
|
+
key = (m[1] ?? m[2] ?? m[3]).trim();
|
|
6225
|
+
value = unquoteYaml(m[4]);
|
|
6226
|
+
}
|
|
6227
|
+
} else if (ext === "properties") {
|
|
6228
|
+
const m = /^\s*([^#!\s][^=:]*?)\s*[=:]\s*(.*)$/.exec(text);
|
|
6229
|
+
if (m) {
|
|
6230
|
+
key = m[1];
|
|
6231
|
+
value = m[2];
|
|
6232
|
+
}
|
|
6233
|
+
} else if (ext === "po") {
|
|
6234
|
+
const m = /^msg(id|str(?:\[\d+\])?)\s+"(.*)"\s*$/.exec(text);
|
|
6235
|
+
if (m) {
|
|
6236
|
+
value = m[2].replace(/\\"/g, '"');
|
|
6237
|
+
if (m[1] === "id") key = value;
|
|
6238
|
+
}
|
|
6239
|
+
} else if (ext === "resx") {
|
|
6240
|
+
const m = /<value>(.*)<\/value>/.exec(text);
|
|
6241
|
+
if (m) value = decodeXml2(m[1]);
|
|
6022
6242
|
}
|
|
6023
|
-
if (
|
|
6024
|
-
|
|
6025
|
-
return EXIT_OK6;
|
|
6243
|
+
if (value === void 0 || !keepable(value)) return null;
|
|
6244
|
+
return { kind: "translation", group: `translation:${key ?? ""}`, value, key, line };
|
|
6026
6245
|
}
|
|
6027
|
-
function
|
|
6028
|
-
const
|
|
6029
|
-
|
|
6030
|
-
return
|
|
6246
|
+
function lineTokens(path25, text, line, side, attributes, options) {
|
|
6247
|
+
const spans = [];
|
|
6248
|
+
const markup = MARKUP_FILE.test(path25);
|
|
6249
|
+
return [
|
|
6250
|
+
...attributeTokens(text, attributes, line, spans),
|
|
6251
|
+
...translationRefTokens(text, line, side, options.translations, spans),
|
|
6252
|
+
...literalTokens(text, line, spans, markup),
|
|
6253
|
+
...textTokens(text, line, markup)
|
|
6254
|
+
];
|
|
6031
6255
|
}
|
|
6032
|
-
function
|
|
6033
|
-
|
|
6256
|
+
function keysByLine(path25, side, options) {
|
|
6257
|
+
const content = options.readFile?.(path25, side);
|
|
6258
|
+
if (content === void 0) return null;
|
|
6259
|
+
return new Map(parseTranslationFile(path25, content).map((e) => [e.line, e.key]));
|
|
6034
6260
|
}
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6261
|
+
function tokenId(t) {
|
|
6262
|
+
return `${t.group}\0${t.value}`;
|
|
6263
|
+
}
|
|
6264
|
+
function hunkAnchors(file, hunk, removed, added) {
|
|
6265
|
+
const addedLeft = /* @__PURE__ */ new Map();
|
|
6266
|
+
for (const t of added) addedLeft.set(tokenId(t), [...addedLeft.get(tokenId(t)) ?? [], t]);
|
|
6267
|
+
const gone = [];
|
|
6268
|
+
for (const t of removed) {
|
|
6269
|
+
const same = addedLeft.get(tokenId(t));
|
|
6270
|
+
if (same?.length) same.shift();
|
|
6271
|
+
else gone.push(t);
|
|
6046
6272
|
}
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
);
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6273
|
+
const fresh = [...addedLeft.values()].flat();
|
|
6274
|
+
const anchors = [];
|
|
6275
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6276
|
+
for (const t of gone) {
|
|
6277
|
+
const peers = gone.filter((g) => g.group === t.group);
|
|
6278
|
+
const replacements = fresh.filter((a) => a.group === t.group);
|
|
6279
|
+
const rename = peers.length === 1 && replacements.length === 1 ? replacements[0] : void 0;
|
|
6280
|
+
const anchor = {
|
|
6281
|
+
file,
|
|
6282
|
+
line: rename ? rename.line : Math.max(1, hunk.newStart),
|
|
6283
|
+
oldLine: t.line,
|
|
6284
|
+
kind: t.kind,
|
|
6285
|
+
before: t.value
|
|
6286
|
+
};
|
|
6287
|
+
if (t.attribute) anchor.attribute = t.attribute;
|
|
6288
|
+
if (t.key) anchor.key = t.key;
|
|
6289
|
+
if (rename) anchor.after = rename.value;
|
|
6290
|
+
const id = `${anchor.kind}\0${anchor.attribute ?? ""}\0${anchor.key ?? ""}\0${anchor.before}\0${anchor.after ?? ""}`;
|
|
6291
|
+
if (seen.has(id)) continue;
|
|
6292
|
+
seen.add(id);
|
|
6293
|
+
anchors.push(anchor);
|
|
6068
6294
|
}
|
|
6069
|
-
|
|
6070
|
-
...env,
|
|
6071
|
-
PIWI_SELECTION: IMPACT_KEY,
|
|
6072
|
-
PIWI_SELECTION_VERSION: "0",
|
|
6073
|
-
PIWI_SELECTION_HASH: impact.resolvedHash,
|
|
6074
|
-
PIWI_SELECTION_COUNT: String(impact.estimate.count),
|
|
6075
|
-
...shardEnv(args)
|
|
6076
|
-
};
|
|
6077
|
-
console.error(`piwi run: impact \u2192 ${impact.estimate.count} test(s)`);
|
|
6078
|
-
return spawnPlaywrightForRun(args.pkgRunner, [...impact.materialization.args, ...args.extra], runEnv);
|
|
6295
|
+
return anchors;
|
|
6079
6296
|
}
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6297
|
+
function extractDiffAnchors(files, options = {}) {
|
|
6298
|
+
const attributes = [.../* @__PURE__ */ new Set([...options.testIdAttributes ?? ["data-testid"], ...ANCHOR_ATTRIBUTES])];
|
|
6299
|
+
const globs = options.translationGlobs ?? DEFAULT_TRANSLATION_GLOBS;
|
|
6300
|
+
const anchors = [];
|
|
6301
|
+
for (const file of files) {
|
|
6302
|
+
if (options.isTestFile?.(file.path)) continue;
|
|
6303
|
+
const translation = isTranslationFile(file.path, globs);
|
|
6304
|
+
let oldKeys;
|
|
6305
|
+
let newKeys;
|
|
6306
|
+
for (const hunk of file.hunks) {
|
|
6307
|
+
let removed;
|
|
6308
|
+
let added;
|
|
6309
|
+
if (translation) {
|
|
6310
|
+
oldKeys ??= keysByLine(file.oldPath ?? file.path, "old", options);
|
|
6311
|
+
newKeys ??= keysByLine(file.path, "new", options);
|
|
6312
|
+
const read = (lines, keys) => lines.flatMap((l) => {
|
|
6313
|
+
const token = translationLineToken(file.path, l.text, l.line);
|
|
6314
|
+
if (!token) return [];
|
|
6315
|
+
const full = keys?.get(l.line);
|
|
6316
|
+
if (full) {
|
|
6317
|
+
token.key = full;
|
|
6318
|
+
token.group = `translation:${full}`;
|
|
6319
|
+
}
|
|
6320
|
+
return [token];
|
|
6321
|
+
});
|
|
6322
|
+
removed = read(hunk.removed, oldKeys);
|
|
6323
|
+
added = read(hunk.added, newKeys);
|
|
6324
|
+
} else {
|
|
6325
|
+
removed = hunk.removed.flatMap((l) => lineTokens(file.path, l.text, l.line, "old", attributes, options));
|
|
6326
|
+
added = hunk.added.flatMap((l) => lineTokens(file.path, l.text, l.line, "new", attributes, options));
|
|
6327
|
+
}
|
|
6328
|
+
anchors.push(...hunkAnchors(file.path, hunk, removed, added));
|
|
6102
6329
|
}
|
|
6103
|
-
return runRunImpact(args, env);
|
|
6104
6330
|
}
|
|
6105
|
-
|
|
6106
|
-
try {
|
|
6107
|
-
outcome = await resolveWithCache(args);
|
|
6108
|
-
} catch (e) {
|
|
6109
|
-
console.error(`piwi run: ${e.message}`);
|
|
6110
|
-
return EXIT_ERROR7;
|
|
6111
|
-
}
|
|
6112
|
-
if ("error" in outcome) {
|
|
6113
|
-
console.error(`piwi run: ${outcome.error.message} \u2014 no cached resolution, running the full suite`);
|
|
6114
|
-
return spawnPlaywrightForRun(args.pkgRunner, args.extra, env);
|
|
6115
|
-
}
|
|
6116
|
-
const { resolution } = outcome;
|
|
6117
|
-
printWarnings(resolution);
|
|
6118
|
-
if (resolution.estimate.count === 0) {
|
|
6119
|
-
console.error(`piwi run: "${args.key}" resolved to 0 tests`);
|
|
6120
|
-
return EXIT_ERROR7;
|
|
6121
|
-
}
|
|
6122
|
-
const runEnv = {
|
|
6123
|
-
...env,
|
|
6124
|
-
PIWI_SELECTION: resolution.key ?? args.key,
|
|
6125
|
-
PIWI_SELECTION_VERSION: String(resolution.version ?? 0),
|
|
6126
|
-
PIWI_SELECTION_HASH: resolution.resolvedHash,
|
|
6127
|
-
PIWI_SELECTION_COUNT: String(resolution.estimate.count),
|
|
6128
|
-
...shardEnv(args)
|
|
6129
|
-
};
|
|
6130
|
-
console.error(
|
|
6131
|
-
`piwi run: ${args.key} \u2192 ${resolution.estimate.count} tests${resolution.materialization.format !== args.format ? ` (materialized as ${resolution.materialization.format})` : ""}`
|
|
6132
|
-
);
|
|
6133
|
-
return spawnPlaywrightForRun(args.pkgRunner, [...resolution.materialization.args, ...args.extra], runEnv);
|
|
6331
|
+
return anchors;
|
|
6134
6332
|
}
|
|
6135
6333
|
|
|
6136
|
-
// src/
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6334
|
+
// ../core/src/locator-text-match.ts
|
|
6335
|
+
function normalizeWhiteSpace(text) {
|
|
6336
|
+
return text.replace(/[]/g, "").trim().replace(/\s+/g, " ");
|
|
6337
|
+
}
|
|
6338
|
+
function textMatcher(value, exact) {
|
|
6339
|
+
if (typeof value !== "string") {
|
|
6340
|
+
const re = value;
|
|
6341
|
+
return { matcher: (text) => re.test(text.full), kind: "regex" };
|
|
6342
|
+
}
|
|
6343
|
+
const needle = normalizeWhiteSpace(value);
|
|
6344
|
+
if (exact) return { matcher: (text) => text.normalized === needle, kind: "strict" };
|
|
6345
|
+
const lower = needle.toLowerCase();
|
|
6346
|
+
return { matcher: (text) => (text.lower ??= text.normalized.toLowerCase()).includes(lower), kind: "lax" };
|
|
6347
|
+
}
|
|
6348
|
+
function attributeMatcher(value, exact) {
|
|
6349
|
+
if (typeof value !== "string") return (actual) => !!actual.match(value);
|
|
6350
|
+
if (exact) return (actual) => actual === value;
|
|
6351
|
+
const lower = value.toLowerCase();
|
|
6352
|
+
return (actual) => actual.toLowerCase().includes(lower);
|
|
6353
|
+
}
|
|
6354
|
+
function nameMatcher(expected, exact) {
|
|
6355
|
+
if (typeof expected !== "string") return (name) => !!name.match(expected);
|
|
6356
|
+
const wanted = normalizeWhiteSpace(expected);
|
|
6357
|
+
if (exact) return (name) => name === wanted;
|
|
6358
|
+
const upper = wanted.toUpperCase();
|
|
6359
|
+
return (name) => name.toUpperCase().includes(upper);
|
|
6360
|
+
}
|
|
6361
|
+
function textMatches(value, exact, text) {
|
|
6362
|
+
return textMatcher(value, exact).matcher({ full: text, normalized: normalizeWhiteSpace(text) });
|
|
6363
|
+
}
|
|
6364
|
+
function nameMatches(value, exact, name) {
|
|
6365
|
+
return nameMatcher(value, exact)(normalizeWhiteSpace(name));
|
|
6366
|
+
}
|
|
6367
|
+
function attributeMatches(value, exact, actual) {
|
|
6368
|
+
return attributeMatcher(value, exact)(actual);
|
|
6369
|
+
}
|
|
6140
6370
|
|
|
6141
|
-
// ../core/src/
|
|
6142
|
-
var
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
function stripPrefix(path24) {
|
|
6146
|
-
const unquoted = path24.startsWith('"') && path24.endsWith('"') ? path24.slice(1, -1) : path24;
|
|
6147
|
-
return unquoted.replace(/^[ab]\//, "");
|
|
6371
|
+
// ../core/src/locator-break.ts
|
|
6372
|
+
var TEXT_KINDS = /* @__PURE__ */ new Set(["text", "translation", "literal"]);
|
|
6373
|
+
function isText2(anchor) {
|
|
6374
|
+
return TEXT_KINDS.has(anchor.kind);
|
|
6148
6375
|
}
|
|
6149
|
-
function
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
const current = () => {
|
|
6158
|
-
if (!file) {
|
|
6159
|
-
file = { path: "", status: "modified", hunks: [] };
|
|
6160
|
-
files.push(file);
|
|
6161
|
-
}
|
|
6162
|
-
return file;
|
|
6163
|
-
};
|
|
6164
|
-
for (const line of text.split(/\r?\n/)) {
|
|
6165
|
-
if (hunk) {
|
|
6166
|
-
if (line.startsWith("-") && oldLeft > 0) {
|
|
6167
|
-
hunk.removed.push({ line: oldLine++, text: line.slice(1) });
|
|
6168
|
-
oldLeft--;
|
|
6169
|
-
} else if (line.startsWith("+") && newLeft > 0) {
|
|
6170
|
-
hunk.added.push({ line: newLine++, text: line.slice(1) });
|
|
6171
|
-
newLeft--;
|
|
6172
|
-
} else if ((line.startsWith(" ") || line === "") && oldLeft > 0 && newLeft > 0) {
|
|
6173
|
-
oldLine++;
|
|
6174
|
-
newLine++;
|
|
6175
|
-
oldLeft--;
|
|
6176
|
-
newLeft--;
|
|
6177
|
-
} else if (!line.startsWith("\\")) hunk = null;
|
|
6178
|
-
if (hunk && oldLeft === 0 && newLeft === 0) hunk = null;
|
|
6179
|
-
if (hunk || !line.startsWith("@@")) continue;
|
|
6180
|
-
}
|
|
6181
|
-
const header = HUNK_HEADER.exec(line);
|
|
6182
|
-
if (header) {
|
|
6183
|
-
oldLine = Number(header[1]);
|
|
6184
|
-
newLine = Number(header[3]);
|
|
6185
|
-
oldLeft = header[2] === void 0 ? 1 : Number(header[2]);
|
|
6186
|
-
newLeft = header[4] === void 0 ? 1 : Number(header[4]);
|
|
6187
|
-
hunk = { oldStart: oldLine, newStart: newLine, removed: [], added: [] };
|
|
6188
|
-
current().hunks.push(hunk);
|
|
6189
|
-
if (oldLeft === 0 && newLeft === 0) hunk = null;
|
|
6190
|
-
continue;
|
|
6191
|
-
}
|
|
6192
|
-
if (line.startsWith("diff --git ")) {
|
|
6193
|
-
const m = /^diff --git (\S+|"[^"]+") (\S+|"[^"]+")$/.exec(line);
|
|
6194
|
-
file = { path: m ? stripPrefix(m[2]) : "", status: "modified", hunks: [] };
|
|
6195
|
-
files.push(file);
|
|
6196
|
-
} else if (line.startsWith("--- ")) {
|
|
6197
|
-
const f = file && !file.hunks.length ? file : (file = null, current());
|
|
6198
|
-
const old = line.slice(4).trim();
|
|
6199
|
-
if (old === "/dev/null") f.status = "added";
|
|
6200
|
-
else if (!f.path) f.path = stripPrefix(old);
|
|
6201
|
-
} else if (line.startsWith("+++ ")) {
|
|
6202
|
-
const next = line.slice(4).trim();
|
|
6203
|
-
if (next === "/dev/null") current().status = "deleted";
|
|
6204
|
-
else current().path = stripPrefix(next);
|
|
6205
|
-
} else if (line.startsWith(RENAME_FROM)) {
|
|
6206
|
-
current().oldPath = line.slice(RENAME_FROM.length);
|
|
6207
|
-
current().status = "renamed";
|
|
6208
|
-
} else if (line.startsWith(RENAME_TO)) current().path = line.slice(RENAME_TO.length);
|
|
6209
|
-
else if (line.startsWith("new file mode")) current().status = "added";
|
|
6210
|
-
else if (line.startsWith("deleted file mode")) current().status = "deleted";
|
|
6376
|
+
function hasAttribute(anchor, names) {
|
|
6377
|
+
return anchor.kind === "attribute" && !!anchor.attribute && names.includes(anchor.attribute);
|
|
6378
|
+
}
|
|
6379
|
+
function toRegExp(arg) {
|
|
6380
|
+
try {
|
|
6381
|
+
return new RegExp(arg.source, arg.flags.replace(/[gy]/g, ""));
|
|
6382
|
+
} catch {
|
|
6383
|
+
return null;
|
|
6211
6384
|
}
|
|
6212
|
-
return files.filter((f) => f.path);
|
|
6213
6385
|
}
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
"id",
|
|
6217
|
-
"name",
|
|
6218
|
-
"aria-label",
|
|
6219
|
-
"placeholder",
|
|
6220
|
-
"alt",
|
|
6221
|
-
"title",
|
|
6222
|
-
"role",
|
|
6223
|
-
"value",
|
|
6224
|
-
"label"
|
|
6225
|
-
];
|
|
6226
|
-
var TRANSLATION_EXTENSIONS = /\.(json|ya?ml|properties|po|resx)$/i;
|
|
6227
|
-
var MARKUP_FILE = /\.(vue|svelte|html?|cshtml|razor|hbs|handlebars|jsx|tsx|astro|njk|liquid|erb|php)$/i;
|
|
6228
|
-
var MIN_LENGTH = 2;
|
|
6229
|
-
var MAX_LENGTH = 200;
|
|
6230
|
-
function expandBraces(glob) {
|
|
6231
|
-
const m = /\{([^{}]*)\}/.exec(glob);
|
|
6232
|
-
if (!m) return [glob];
|
|
6233
|
-
return m[1].split(",").flatMap((alt) => expandBraces(glob.slice(0, m.index) + alt + glob.slice(m.index + m[0].length)));
|
|
6386
|
+
function optionsOf(arg) {
|
|
6387
|
+
return new Map(arg?.type === "object" ? arg.entries : []);
|
|
6234
6388
|
}
|
|
6235
|
-
function
|
|
6236
|
-
const
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6389
|
+
function isExact(opts) {
|
|
6390
|
+
const exact = opts.get("exact");
|
|
6391
|
+
return exact?.type === "boolean" && exact.value;
|
|
6392
|
+
}
|
|
6393
|
+
function escapeRegExp2(s) {
|
|
6394
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6395
|
+
}
|
|
6396
|
+
function renameValue(value, before, after, exact) {
|
|
6397
|
+
const v = normalizeWhiteSpace(value);
|
|
6398
|
+
const b = normalizeWhiteSpace(before);
|
|
6399
|
+
if (exact ? v === b : v.toLowerCase() === b.toLowerCase()) return after;
|
|
6400
|
+
const re = new RegExp(escapeRegExp2(b).replace(/ /g, "\\s+"), exact ? "" : "i");
|
|
6401
|
+
if (!re.test(value)) return null;
|
|
6402
|
+
return value.replace(re, () => after);
|
|
6403
|
+
}
|
|
6404
|
+
function withArg(call, index, arg) {
|
|
6405
|
+
return { method: call.method, args: call.args.map((a, i) => i === index ? arg : a) };
|
|
6406
|
+
}
|
|
6407
|
+
function withOption(call, index, key, arg) {
|
|
6408
|
+
const obj = call.args[index];
|
|
6409
|
+
if (obj?.type !== "object") return call;
|
|
6410
|
+
return withArg(call, index, {
|
|
6411
|
+
type: "object",
|
|
6412
|
+
entries: obj.entries.map(([k, v]) => [k, k === key ? arg : v])
|
|
6253
6413
|
});
|
|
6254
|
-
return new RegExp(`^(?:${parts.join("|")})$`);
|
|
6255
6414
|
}
|
|
6256
|
-
function
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6415
|
+
function stringSlot(arg, exact, rule, accepts, replace) {
|
|
6416
|
+
if (arg?.type === "regex") {
|
|
6417
|
+
const re = toRegExp(arg);
|
|
6418
|
+
return re ? { value: re, exact, rule, accepts } : null;
|
|
6419
|
+
}
|
|
6420
|
+
if (arg?.type !== "string") return null;
|
|
6421
|
+
const value = arg.value;
|
|
6422
|
+
return {
|
|
6423
|
+
value,
|
|
6424
|
+
exact,
|
|
6425
|
+
rule,
|
|
6426
|
+
accepts,
|
|
6427
|
+
rewrite: (after, before) => replace({ type: "string", value: renameValue(value, before, after, exact) ?? value }),
|
|
6428
|
+
literal: (after, before) => [value, renameValue(value, before, after, exact) ?? value]
|
|
6429
|
+
};
|
|
6260
6430
|
}
|
|
6261
|
-
function
|
|
6262
|
-
const
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6431
|
+
function cssSlots(selector, testIds, replace) {
|
|
6432
|
+
const slots = [];
|
|
6433
|
+
const slot = (value, names, render, at, len) => {
|
|
6434
|
+
const edited = (after) => selector.slice(0, at) + render(after) + selector.slice(at + len);
|
|
6435
|
+
slots.push({
|
|
6436
|
+
value,
|
|
6437
|
+
exact: true,
|
|
6438
|
+
rule: "exact",
|
|
6439
|
+
accepts: (a) => hasAttribute(a, names),
|
|
6440
|
+
rewrite: (after) => replace(edited(after)),
|
|
6441
|
+
literal: (after) => [selector, edited(after)]
|
|
6442
|
+
});
|
|
6443
|
+
};
|
|
6444
|
+
for (const m of selector.matchAll(/#((?:[\w-]|\\.)+)/g)) {
|
|
6445
|
+
slot(
|
|
6446
|
+
m[1].replace(/\\(.)/g, "$1"),
|
|
6447
|
+
["id"],
|
|
6448
|
+
(v) => `#${v.replace(/[^\w-]/g, (c) => `\\${c}`)}`,
|
|
6449
|
+
m.index,
|
|
6450
|
+
m[0].length
|
|
6451
|
+
);
|
|
6452
|
+
}
|
|
6453
|
+
for (const m of selector.matchAll(/\[\s*([\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\]\s]+))\s*\]/g)) {
|
|
6454
|
+
const attr = m[1];
|
|
6455
|
+
const names = testIds.includes(attr) ? testIds : attr === "id" || attr === "name" ? [attr] : null;
|
|
6456
|
+
if (!names) continue;
|
|
6457
|
+
const quote3 = m[2] !== void 0 ? '"' : m[3] !== void 0 ? "'" : "";
|
|
6458
|
+
const value = m[2] ?? m[3] ?? m[4];
|
|
6459
|
+
slot(value, [attr], (v) => `[${attr}=${quote3}${v}${quote3}]`, m.index, m[0].length);
|
|
6460
|
+
}
|
|
6461
|
+
return slots;
|
|
6266
6462
|
}
|
|
6267
|
-
function
|
|
6268
|
-
const
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6463
|
+
function directSlots(call, testIds) {
|
|
6464
|
+
const first = call.args[0];
|
|
6465
|
+
const opts = optionsOf(call.args[1]);
|
|
6466
|
+
const exact = isExact(opts);
|
|
6467
|
+
const slots = [];
|
|
6468
|
+
const argSlot = (rule, accepts, ex = exact) => stringSlot(first, ex, rule, accepts, (next) => withArg(call, 0, next));
|
|
6469
|
+
const hasTextSlot = (index) => {
|
|
6470
|
+
const hasText = optionsOf(call.args[index]).get("hasText");
|
|
6471
|
+
if (!hasText) return null;
|
|
6472
|
+
return stringSlot(hasText, false, "text", isText2, (next) => withOption(call, index, "hasText", next));
|
|
6473
|
+
};
|
|
6474
|
+
const textOr = (attribute) => (a) => a.kind === "translation" || a.kind === "literal" || hasAttribute(a, [attribute]);
|
|
6475
|
+
switch (call.method) {
|
|
6476
|
+
case "getByTestId":
|
|
6477
|
+
slots.push(argSlot("exact", (a) => hasAttribute(a, testIds), true));
|
|
6478
|
+
break;
|
|
6479
|
+
case "getByRole":
|
|
6480
|
+
slots.push(
|
|
6481
|
+
stringSlot(
|
|
6482
|
+
opts.get("name"),
|
|
6483
|
+
exact,
|
|
6484
|
+
"name",
|
|
6485
|
+
(a) => isText2(a) || hasAttribute(a, ["aria-label", "title", "alt", "value"]),
|
|
6486
|
+
(next) => withOption(call, 1, "name", next)
|
|
6487
|
+
)
|
|
6488
|
+
);
|
|
6489
|
+
break;
|
|
6490
|
+
case "getByText":
|
|
6491
|
+
slots.push(argSlot("text", isText2));
|
|
6492
|
+
break;
|
|
6493
|
+
case "getByLabel":
|
|
6494
|
+
slots.push(argSlot("text", (a) => isText2(a) || hasAttribute(a, ["aria-label"])));
|
|
6495
|
+
break;
|
|
6496
|
+
case "getByPlaceholder":
|
|
6497
|
+
slots.push(argSlot("text", textOr("placeholder")));
|
|
6498
|
+
break;
|
|
6499
|
+
case "getByAltText":
|
|
6500
|
+
slots.push(argSlot("text", textOr("alt")));
|
|
6501
|
+
break;
|
|
6502
|
+
case "getByTitle":
|
|
6503
|
+
slots.push(argSlot("text", textOr("title")));
|
|
6504
|
+
break;
|
|
6505
|
+
case "locator":
|
|
6506
|
+
if (first?.type === "string") {
|
|
6507
|
+
slots.push(...cssSlots(first.value, testIds, (next) => withArg(call, 0, { type: "string", value: next })));
|
|
6508
|
+
}
|
|
6509
|
+
slots.push(hasTextSlot(1));
|
|
6510
|
+
break;
|
|
6511
|
+
case "filter":
|
|
6512
|
+
slots.push(hasTextSlot(0));
|
|
6513
|
+
break;
|
|
6281
6514
|
}
|
|
6282
|
-
return
|
|
6515
|
+
return slots.filter((slot) => !!slot);
|
|
6283
6516
|
}
|
|
6284
|
-
function
|
|
6285
|
-
|
|
6517
|
+
function nestedChains(call) {
|
|
6518
|
+
const out = [];
|
|
6519
|
+
call.args.forEach((arg, index) => {
|
|
6520
|
+
if (arg.type === "chain")
|
|
6521
|
+
out.push({ chain: arg.chain, put: (c) => withArg(call, index, { type: "chain", chain: c }) });
|
|
6522
|
+
if (arg.type === "object") {
|
|
6523
|
+
for (const [key, v] of arg.entries) {
|
|
6524
|
+
if (v.type === "chain") {
|
|
6525
|
+
out.push({ chain: v.chain, put: (c) => withOption(call, index, key, { type: "chain", chain: c }) });
|
|
6526
|
+
}
|
|
6527
|
+
}
|
|
6528
|
+
}
|
|
6529
|
+
});
|
|
6530
|
+
return out;
|
|
6286
6531
|
}
|
|
6287
|
-
function
|
|
6288
|
-
const
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
let m;
|
|
6295
|
-
while ((m = re.exec(text)) !== null) {
|
|
6296
|
-
const bound = m[0].startsWith(":") || m[0].startsWith("v-bind:") || m[0].startsWith("[");
|
|
6297
|
-
const quoteAt = m.index + m[0].length - 1;
|
|
6298
|
-
const outer = readQuoted(text, quoteAt);
|
|
6299
|
-
if (!outer) continue;
|
|
6300
|
-
let value = outer.value;
|
|
6301
|
-
if (bound) {
|
|
6302
|
-
const inner = /^\s*(['"`])(.*)\1\s*$/.exec(value);
|
|
6303
|
-
if (!inner) continue;
|
|
6304
|
-
value = inner[2];
|
|
6532
|
+
function chainSlots(chain, testIds) {
|
|
6533
|
+
const out = [];
|
|
6534
|
+
chain.calls.forEach((call, i) => {
|
|
6535
|
+
const replaceAt = (next) => ({ calls: chain.calls.map((c, j) => j === i ? next : c) });
|
|
6536
|
+
for (const slot of directSlots(call, testIds)) {
|
|
6537
|
+
const rewrite = slot.rewrite;
|
|
6538
|
+
out.push({ ...slot, rewrite: rewrite && ((after, before) => replaceAt(rewrite(after, before))) });
|
|
6305
6539
|
}
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6540
|
+
for (const n of nestedChains(call)) {
|
|
6541
|
+
for (const slot of chainSlots(n.chain, testIds)) {
|
|
6542
|
+
const rewrite = slot.rewrite;
|
|
6543
|
+
out.push({ ...slot, rewrite: rewrite && ((after, before) => replaceAt(n.put(rewrite(after, before)))) });
|
|
6544
|
+
}
|
|
6545
|
+
}
|
|
6546
|
+
});
|
|
6547
|
+
return out;
|
|
6313
6548
|
}
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
tokens.push({ kind: "translation", group: "translation-ref", value, key, line });
|
|
6549
|
+
function slotMatches(slot, text) {
|
|
6550
|
+
switch (slot.rule) {
|
|
6551
|
+
case "exact":
|
|
6552
|
+
return attributeMatches(slot.value, true, text);
|
|
6553
|
+
case "name":
|
|
6554
|
+
return nameMatches(slot.value, slot.exact, text);
|
|
6555
|
+
case "text":
|
|
6556
|
+
return textMatches(slot.value, slot.exact, text);
|
|
6323
6557
|
}
|
|
6324
|
-
return tokens;
|
|
6325
6558
|
}
|
|
6326
|
-
|
|
6327
|
-
|
|
6559
|
+
var filePath = (path25) => path25.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
6560
|
+
function sameFilePath(a, b) {
|
|
6561
|
+
const x = filePath(a);
|
|
6562
|
+
const y = filePath(b);
|
|
6563
|
+
return x === y || x.endsWith("/" + y) || y.endsWith("/" + x);
|
|
6328
6564
|
}
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
const
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6565
|
+
function reachOfIndex(index) {
|
|
6566
|
+
if (!index.reach.some((r) => r.origin === "client")) return void 0;
|
|
6567
|
+
const filesOf = /* @__PURE__ */ new Map();
|
|
6568
|
+
for (const r of index.reach) {
|
|
6569
|
+
const file = index.files[r.file];
|
|
6570
|
+
if (file === void 0) continue;
|
|
6571
|
+
for (const t of r.tests) {
|
|
6572
|
+
const id = index.tests[t]?.id;
|
|
6573
|
+
if (id === void 0) continue;
|
|
6574
|
+
const files = filesOf.get(id);
|
|
6575
|
+
if (files) files.push(file);
|
|
6576
|
+
else filesOf.set(id, [file]);
|
|
6340
6577
|
}
|
|
6341
|
-
const start = i;
|
|
6342
|
-
i = lit.end - 1;
|
|
6343
|
-
if (inSpans(spans, start)) continue;
|
|
6344
|
-
const before = text.slice(0, start);
|
|
6345
|
-
if (markup && /[\w:@.[\]-]=\s*$/.test(before)) continue;
|
|
6346
|
-
if (/\b(?:from|require|import)\s*\(?\s*$/.test(before)) continue;
|
|
6347
|
-
const value = lit.value;
|
|
6348
|
-
if (!keepable(value) || NON_TEXT_LITERAL.test(value)) continue;
|
|
6349
|
-
if (/^[\w-]+(?:\.[\w-]+)+$/.test(value) && !/\s/.test(value)) continue;
|
|
6350
|
-
tokens.push({ kind: "literal", group: "literal", value, line });
|
|
6351
6578
|
}
|
|
6352
|
-
return
|
|
6579
|
+
return (testId, file) => (filesOf.get(testId) ?? []).some((reached) => sameFilePath(reached, file));
|
|
6353
6580
|
}
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
const value = raw.replace(/ /g, " ").replace(/\s+/g, " ").trim();
|
|
6357
|
-
if (!keepable(value)) return null;
|
|
6358
|
-
if (/[{}]|@[\w(]|<%|%>/.test(value)) return null;
|
|
6359
|
-
return { kind: "text", group: "text", value, line };
|
|
6581
|
+
function callSiteFile(callSite) {
|
|
6582
|
+
return callSite.replace(/:\d+(?::\d+)?$/, "");
|
|
6360
6583
|
}
|
|
6361
|
-
function
|
|
6362
|
-
const
|
|
6363
|
-
|
|
6364
|
-
const trimmed = text.trim();
|
|
6365
|
-
if (!trimmed) return tokens;
|
|
6366
|
-
if (!/[<>]/.test(trimmed)) {
|
|
6367
|
-
if (CODE_LINE.test(trimmed) || /[;=(){}[\]"'`]|^\/\/|^\*|^\/\*|^<!--|[,:]$/.test(trimmed)) return tokens;
|
|
6368
|
-
const token = textSegment(trimmed, line);
|
|
6369
|
-
return token ? [token] : tokens;
|
|
6370
|
-
}
|
|
6371
|
-
for (const m of text.matchAll(/>([^<>]+)</g)) {
|
|
6372
|
-
const token = textSegment(m[1], line);
|
|
6373
|
-
if (token) tokens.push(token);
|
|
6374
|
-
}
|
|
6375
|
-
const lead = /^\s*([^<>]+)<\//.exec(text);
|
|
6376
|
-
if (lead && !/[=;(){}]/.test(lead[1])) {
|
|
6377
|
-
const token = textSegment(lead[1], line);
|
|
6378
|
-
if (token) tokens.push(token);
|
|
6379
|
-
}
|
|
6380
|
-
const tail = />([^<>]+)$/.exec(text);
|
|
6381
|
-
if (tail && !/[=;(){}"']/.test(tail[1])) {
|
|
6382
|
-
const token = textSegment(tail[1], line);
|
|
6383
|
-
if (token) tokens.push(token);
|
|
6384
|
-
}
|
|
6385
|
-
return tokens;
|
|
6584
|
+
function callSiteLine(callSite) {
|
|
6585
|
+
const m = /:(\d+)(?::\d+)?$/.exec(callSite);
|
|
6586
|
+
return m ? Number(m[1]) : null;
|
|
6386
6587
|
}
|
|
6387
|
-
function
|
|
6388
|
-
const
|
|
6389
|
-
|
|
6390
|
-
|
|
6588
|
+
function locatorCallSiteFiles(index) {
|
|
6589
|
+
const files = /* @__PURE__ */ new Set();
|
|
6590
|
+
for (const entry of index.locators) {
|
|
6591
|
+
for (const use of entry.uses) for (const site of use.callSites) files.add(callSiteFile(site));
|
|
6391
6592
|
}
|
|
6392
|
-
return
|
|
6393
|
-
}
|
|
6394
|
-
function decodeXml2(text) {
|
|
6395
|
-
return text.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
6593
|
+
return files;
|
|
6396
6594
|
}
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
const
|
|
6400
|
-
const
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
const raw = m[5]?.trim();
|
|
6440
|
-
if (!raw || raw.startsWith("#") || raw === "|" || raw === ">") {
|
|
6441
|
-
stack.push({ indent, key });
|
|
6442
|
-
return;
|
|
6443
|
-
}
|
|
6444
|
-
out.push({ key: [...stack.map((s) => s.key), key].join("."), value: unquoteYaml(raw), line: i + 1 });
|
|
6445
|
-
});
|
|
6446
|
-
return out;
|
|
6447
|
-
}
|
|
6448
|
-
if (ext === "properties") {
|
|
6449
|
-
lines.forEach((text, i) => {
|
|
6450
|
-
const m = /^\s*([^#!\s][^=:]*?)\s*[=:]\s*(.*)$/.exec(text);
|
|
6451
|
-
if (m && !m[2].endsWith("\\")) out.push({ key: m[1], value: m[2], line: i + 1 });
|
|
6452
|
-
});
|
|
6453
|
-
return out;
|
|
6454
|
-
}
|
|
6455
|
-
if (ext === "po") {
|
|
6456
|
-
let msgid = null;
|
|
6457
|
-
lines.forEach((text, i) => {
|
|
6458
|
-
const id = /^msgid\s+"(.*)"\s*$/.exec(text);
|
|
6459
|
-
if (id) {
|
|
6460
|
-
msgid = id[1].replace(/\\"/g, '"');
|
|
6461
|
-
return;
|
|
6462
|
-
}
|
|
6463
|
-
const str = /^msgstr(?:\[\d+\])?\s+"(.*)"\s*$/.exec(text);
|
|
6464
|
-
if (str && msgid) out.push({ key: msgid, value: str[1].replace(/\\"/g, '"'), line: i + 1 });
|
|
6465
|
-
});
|
|
6466
|
-
return out;
|
|
6467
|
-
}
|
|
6468
|
-
if (ext === "resx") {
|
|
6469
|
-
let name = null;
|
|
6470
|
-
lines.forEach((text, i) => {
|
|
6471
|
-
const data = /<data\s+name="([^"]+)"/.exec(text);
|
|
6472
|
-
if (data) name = decodeXml2(data[1]);
|
|
6473
|
-
const value = /<value>(.*)<\/value>/.exec(text);
|
|
6474
|
-
if (value && name) {
|
|
6475
|
-
out.push({ key: name, value: decodeXml2(value[1]), line: i + 1 });
|
|
6476
|
-
name = null;
|
|
6595
|
+
var CONFIDENCE_RANK = { likely: 0, possible: 1 };
|
|
6596
|
+
function predictLocatorBreaks(anchors, index, options = {}) {
|
|
6597
|
+
const testIds = options.testIdAttributes ?? index.testIdAttributes ?? ["data-testid"];
|
|
6598
|
+
const siteFiles = [...locatorCallSiteFiles(index)];
|
|
6599
|
+
const live = anchors.filter((a) => !siteFiles.some((f) => sameFilePath(f, a.file)));
|
|
6600
|
+
if (!live.length) return [];
|
|
6601
|
+
const breaks = [];
|
|
6602
|
+
for (const entry of index.locators) {
|
|
6603
|
+
const chain = tryParseLocatorChain(entry.locator);
|
|
6604
|
+
if (!chain) continue;
|
|
6605
|
+
const slots = chainSlots(chain, testIds);
|
|
6606
|
+
if (!slots.length) continue;
|
|
6607
|
+
for (const anchor of live) {
|
|
6608
|
+
const hit = slots.filter(
|
|
6609
|
+
(slot) => slot.accepts(anchor) && slotMatches(slot, anchor.before) && (anchor.after === void 0 || !slotMatches(slot, anchor.after))
|
|
6610
|
+
);
|
|
6611
|
+
if (!hit.length) continue;
|
|
6612
|
+
const tests = [...new Set(entry.uses.map((u) => u.test))].map((t) => index.tests[t]).filter((t) => !!t);
|
|
6613
|
+
const found = {
|
|
6614
|
+
anchor,
|
|
6615
|
+
locator: entry.locator,
|
|
6616
|
+
entry,
|
|
6617
|
+
uses: entry.uses,
|
|
6618
|
+
tests,
|
|
6619
|
+
confidence: confidenceOf(anchor, tests, options.reach)
|
|
6620
|
+
};
|
|
6621
|
+
if (anchor.after !== void 0 && hit.every((slot) => slot.rewrite)) {
|
|
6622
|
+
let rewritten = chain;
|
|
6623
|
+
const replacements = [];
|
|
6624
|
+
for (const slot of hit) {
|
|
6625
|
+
const again = chainSlots(rewritten, testIds).find(
|
|
6626
|
+
(s) => s.rule === slot.rule && String(s.value) === String(slot.value) && s.rewrite
|
|
6627
|
+
);
|
|
6628
|
+
if (!again?.rewrite) continue;
|
|
6629
|
+
rewritten = again.rewrite(anchor.after, anchor.before);
|
|
6630
|
+
if (slot.literal) replacements.push(slot.literal(anchor.after, anchor.before));
|
|
6631
|
+
}
|
|
6632
|
+
const rendered = renderLocatorChain(rewritten);
|
|
6633
|
+
if (rendered !== entry.locator) {
|
|
6634
|
+
found.rewrite = rendered;
|
|
6635
|
+
found.replacements = replacements.filter(([a, b]) => a !== b);
|
|
6636
|
+
}
|
|
6477
6637
|
}
|
|
6478
|
-
|
|
6479
|
-
return out;
|
|
6480
|
-
}
|
|
6481
|
-
return out;
|
|
6482
|
-
}
|
|
6483
|
-
function translationLineToken(path24, text, line) {
|
|
6484
|
-
const ext = /\.([a-z]+)$/i.exec(path24)?.[1]?.toLowerCase();
|
|
6485
|
-
let key;
|
|
6486
|
-
let value;
|
|
6487
|
-
if (ext === "json") {
|
|
6488
|
-
const pair = /^\s*"((?:[^"\\]|\\.)*)"\s*:\s*"((?:[^"\\]|\\.)*)"/.exec(text);
|
|
6489
|
-
if (pair) {
|
|
6490
|
-
key = JSON.parse(`"${pair[1]}"`);
|
|
6491
|
-
value = JSON.parse(`"${pair[2]}"`);
|
|
6492
|
-
} else {
|
|
6493
|
-
const item = /^\s*"((?:[^"\\]|\\.)*)"\s*,?\s*$/.exec(text);
|
|
6494
|
-
if (item) value = JSON.parse(`"${item[1]}"`);
|
|
6495
|
-
}
|
|
6496
|
-
} else if (ext === "yaml" || ext === "yml") {
|
|
6497
|
-
const m = /^\s*(?:-\s+)?(?:"([^"]+)"|'([^']+)'|([^\s:#][^:#]*?))\s*:\s+(.+)$/.exec(text);
|
|
6498
|
-
if (m) {
|
|
6499
|
-
key = (m[1] ?? m[2] ?? m[3]).trim();
|
|
6500
|
-
value = unquoteYaml(m[4]);
|
|
6501
|
-
}
|
|
6502
|
-
} else if (ext === "properties") {
|
|
6503
|
-
const m = /^\s*([^#!\s][^=:]*?)\s*[=:]\s*(.*)$/.exec(text);
|
|
6504
|
-
if (m) {
|
|
6505
|
-
key = m[1];
|
|
6506
|
-
value = m[2];
|
|
6507
|
-
}
|
|
6508
|
-
} else if (ext === "po") {
|
|
6509
|
-
const m = /^msg(id|str(?:\[\d+\])?)\s+"(.*)"\s*$/.exec(text);
|
|
6510
|
-
if (m) {
|
|
6511
|
-
value = m[2].replace(/\\"/g, '"');
|
|
6512
|
-
if (m[1] === "id") key = value;
|
|
6638
|
+
breaks.push(found);
|
|
6513
6639
|
}
|
|
6514
|
-
} else if (ext === "resx") {
|
|
6515
|
-
const m = /<value>(.*)<\/value>/.exec(text);
|
|
6516
|
-
if (m) value = decodeXml2(m[1]);
|
|
6517
6640
|
}
|
|
6518
|
-
|
|
6519
|
-
|
|
6641
|
+
return breaks.sort(
|
|
6642
|
+
(a, b) => CONFIDENCE_RANK[a.confidence] - CONFIDENCE_RANK[b.confidence] || b.tests.length - a.tests.length
|
|
6643
|
+
);
|
|
6520
6644
|
}
|
|
6521
|
-
function
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
return
|
|
6525
|
-
|
|
6526
|
-
...translationRefTokens(text, line, side, options.translations, spans),
|
|
6527
|
-
...literalTokens(text, line, spans, markup),
|
|
6528
|
-
...textTokens(text, line, markup)
|
|
6529
|
-
];
|
|
6645
|
+
function confidenceOf(anchor, tests, reach) {
|
|
6646
|
+
if (anchor.kind === "literal") return "possible";
|
|
6647
|
+
if (!reach) return "likely";
|
|
6648
|
+
if (anchor.kind === "translation" && /\.(?:json|ya?ml|properties|po|resx)$/i.test(anchor.file)) return "likely";
|
|
6649
|
+
return tests.some((t) => reach(t.id, anchor.file)) ? "likely" : "possible";
|
|
6530
6650
|
}
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6651
|
+
|
|
6652
|
+
// ../core/src/suspect-lines.ts
|
|
6653
|
+
function deletionPoints(hunk) {
|
|
6654
|
+
const added = hunk.added.map(({ line }) => line).sort((a, b) => a - b);
|
|
6655
|
+
const addedLines = new Set(added);
|
|
6656
|
+
const points = [];
|
|
6657
|
+
let previous = -1;
|
|
6658
|
+
hunk.removed.forEach(({ line }, removedBefore) => {
|
|
6659
|
+
const opensRun = line !== previous + 1;
|
|
6660
|
+
previous = line;
|
|
6661
|
+
if (!opensRun) return;
|
|
6662
|
+
let before;
|
|
6663
|
+
if (line === hunk.oldStart && added.length === 0) before = line === 1 ? 0 : hunk.newStart;
|
|
6664
|
+
else {
|
|
6665
|
+
before = hunk.newStart + (line - 1 - hunk.oldStart) - removedBefore;
|
|
6666
|
+
for (const addedLine of added) {
|
|
6667
|
+
if (addedLine > before) break;
|
|
6668
|
+
before++;
|
|
6669
|
+
}
|
|
6670
|
+
}
|
|
6671
|
+
if (!addedLines.has(before + 1)) points.push(before);
|
|
6672
|
+
});
|
|
6673
|
+
return points;
|
|
6535
6674
|
}
|
|
6536
|
-
function
|
|
6537
|
-
|
|
6675
|
+
function hunkHeadLines(hunk) {
|
|
6676
|
+
const lines = hunk.added.map(({ line }) => [line, line]);
|
|
6677
|
+
for (const before of deletionPoints(hunk)) lines.push(before >= 1 ? [before, before + 1] : [1, 1]);
|
|
6678
|
+
return normalizeLineRanges(lines);
|
|
6538
6679
|
}
|
|
6539
|
-
function
|
|
6540
|
-
|
|
6541
|
-
for (const t of added) addedLeft.set(tokenId(t), [...addedLeft.get(tokenId(t)) ?? [], t]);
|
|
6542
|
-
const gone = [];
|
|
6543
|
-
for (const t of removed) {
|
|
6544
|
-
const same = addedLeft.get(tokenId(t));
|
|
6545
|
-
if (same?.length) same.shift();
|
|
6546
|
-
else gone.push(t);
|
|
6547
|
-
}
|
|
6548
|
-
const fresh = [...addedLeft.values()].flat();
|
|
6549
|
-
const anchors = [];
|
|
6550
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6551
|
-
for (const t of gone) {
|
|
6552
|
-
const peers = gone.filter((g) => g.group === t.group);
|
|
6553
|
-
const replacements = fresh.filter((a) => a.group === t.group);
|
|
6554
|
-
const rename = peers.length === 1 && replacements.length === 1 ? replacements[0] : void 0;
|
|
6555
|
-
const anchor = {
|
|
6556
|
-
file,
|
|
6557
|
-
line: rename ? rename.line : Math.max(1, hunk.newStart),
|
|
6558
|
-
oldLine: t.line,
|
|
6559
|
-
kind: t.kind,
|
|
6560
|
-
before: t.value
|
|
6561
|
-
};
|
|
6562
|
-
if (t.attribute) anchor.attribute = t.attribute;
|
|
6563
|
-
if (t.key) anchor.key = t.key;
|
|
6564
|
-
if (rename) anchor.after = rename.value;
|
|
6565
|
-
const id = `${anchor.kind}\0${anchor.attribute ?? ""}\0${anchor.key ?? ""}\0${anchor.before}\0${anchor.after ?? ""}`;
|
|
6566
|
-
if (seen.has(id)) continue;
|
|
6567
|
-
seen.add(id);
|
|
6568
|
-
anchors.push(anchor);
|
|
6569
|
-
}
|
|
6570
|
-
return anchors;
|
|
6680
|
+
function hunkBaseLines(hunk) {
|
|
6681
|
+
return hunkHeadLines({ oldStart: hunk.newStart, newStart: hunk.oldStart, removed: hunk.added, added: hunk.removed });
|
|
6571
6682
|
}
|
|
6572
|
-
function
|
|
6573
|
-
const
|
|
6574
|
-
const
|
|
6575
|
-
const anchors = [];
|
|
6683
|
+
function diffImpactInput(files) {
|
|
6684
|
+
const changedFiles = /* @__PURE__ */ new Set();
|
|
6685
|
+
const changes = [];
|
|
6576
6686
|
for (const file of files) {
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
let removed;
|
|
6583
|
-
let added;
|
|
6584
|
-
if (translation) {
|
|
6585
|
-
oldKeys ??= keysByLine(file.oldPath ?? file.path, "old", options);
|
|
6586
|
-
newKeys ??= keysByLine(file.path, "new", options);
|
|
6587
|
-
const read = (lines, keys) => lines.flatMap((l) => {
|
|
6588
|
-
const token = translationLineToken(file.path, l.text, l.line);
|
|
6589
|
-
if (!token) return [];
|
|
6590
|
-
const full = keys?.get(l.line);
|
|
6591
|
-
if (full) {
|
|
6592
|
-
token.key = full;
|
|
6593
|
-
token.group = `translation:${full}`;
|
|
6594
|
-
}
|
|
6595
|
-
return [token];
|
|
6596
|
-
});
|
|
6597
|
-
removed = read(hunk.removed, oldKeys);
|
|
6598
|
-
added = read(hunk.added, newKeys);
|
|
6599
|
-
} else {
|
|
6600
|
-
removed = hunk.removed.flatMap((l) => lineTokens(file.path, l.text, l.line, "old", attributes, options));
|
|
6601
|
-
added = hunk.added.flatMap((l) => lineTokens(file.path, l.text, l.line, "new", attributes, options));
|
|
6602
|
-
}
|
|
6603
|
-
anchors.push(...hunkAnchors(file.path, hunk, removed, added));
|
|
6604
|
-
}
|
|
6687
|
+
changedFiles.add(file.path);
|
|
6688
|
+
if (file.oldPath) changedFiles.add(file.oldPath);
|
|
6689
|
+
if (file.status !== "modified" || !file.oldBlob) continue;
|
|
6690
|
+
const lines = normalizeLineRanges(file.hunks.flatMap(hunkBaseLines));
|
|
6691
|
+
if (lines.length > 0) changes.push({ filePath: file.path, blob: file.oldBlob, lines });
|
|
6605
6692
|
}
|
|
6606
|
-
return
|
|
6693
|
+
return { changedFiles: [...changedFiles], changes };
|
|
6607
6694
|
}
|
|
6608
6695
|
|
|
6609
|
-
//
|
|
6610
|
-
|
|
6611
|
-
|
|
6696
|
+
// src/cli/add-reporter.ts
|
|
6697
|
+
var fs16 = __toESM(require("fs"));
|
|
6698
|
+
var path21 = __toESM(require("path"));
|
|
6699
|
+
var import_node_module2 = require("module");
|
|
6700
|
+
var REPORTER_PACKAGE2 = "@piwitests/reporter";
|
|
6701
|
+
var ADD_REPORTER_FLAG = "--add-reporter";
|
|
6702
|
+
var ADD_REPORTER_MIN = { major: 1, minor: 63 };
|
|
6703
|
+
function playwrightSupportsAddReporter(version) {
|
|
6704
|
+
const match = version ? /^(\d+)\.(\d+)/.exec(version) : null;
|
|
6705
|
+
if (!match) return false;
|
|
6706
|
+
const major = Number(match[1]);
|
|
6707
|
+
const minor = Number(match[2]);
|
|
6708
|
+
return major > ADD_REPORTER_MIN.major || major === ADD_REPORTER_MIN.major && minor >= ADD_REPORTER_MIN.minor;
|
|
6612
6709
|
}
|
|
6613
|
-
function
|
|
6614
|
-
|
|
6615
|
-
const re = value;
|
|
6616
|
-
return { matcher: (text) => re.test(text.full), kind: "regex" };
|
|
6617
|
-
}
|
|
6618
|
-
const needle = normalizeWhiteSpace(value);
|
|
6619
|
-
if (exact) return { matcher: (text) => text.normalized === needle, kind: "strict" };
|
|
6620
|
-
const lower = needle.toLowerCase();
|
|
6621
|
-
return { matcher: (text) => (text.lower ??= text.normalized.toLowerCase()).includes(lower), kind: "lax" };
|
|
6710
|
+
function configHasPiwiReporter(source) {
|
|
6711
|
+
return new RegExp(REPORTER_PACKAGE2.replace(/[/\\]/g, "\\$&")).test(source);
|
|
6622
6712
|
}
|
|
6623
|
-
function
|
|
6624
|
-
if (
|
|
6625
|
-
if (
|
|
6626
|
-
|
|
6627
|
-
|
|
6713
|
+
function decideAddReporter(configSource, playwrightVersion) {
|
|
6714
|
+
if (configSource === null || configHasPiwiReporter(configSource)) return { args: [], log: null };
|
|
6715
|
+
if (playwrightSupportsAddReporter(playwrightVersion)) {
|
|
6716
|
+
return {
|
|
6717
|
+
args: [ADD_REPORTER_FLAG, REPORTER_PACKAGE2],
|
|
6718
|
+
log: `piwi run: the Playwright config has no Piwi reporter \u2014 appending ${ADD_REPORTER_FLAG} ${REPORTER_PACKAGE2}`
|
|
6719
|
+
};
|
|
6720
|
+
}
|
|
6721
|
+
return {
|
|
6722
|
+
args: [],
|
|
6723
|
+
log: `piwi run: the Playwright config has no Piwi reporter and Playwright ${playwrightVersion ?? "unknown"} predates ${ADD_REPORTER_FLAG} (1.63) \u2014 results will not reach the dashboard`
|
|
6724
|
+
};
|
|
6628
6725
|
}
|
|
6629
|
-
function
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6726
|
+
function resolveConfigPath(cwd, playwrightArgs2) {
|
|
6727
|
+
for (let i = 0; i < playwrightArgs2.length; i++) {
|
|
6728
|
+
const arg = playwrightArgs2[i];
|
|
6729
|
+
if (arg === "--config" || arg === "-c") {
|
|
6730
|
+
const next = playwrightArgs2[i + 1];
|
|
6731
|
+
if (next) return path21.resolve(cwd, next);
|
|
6732
|
+
} else if (arg.startsWith("--config=")) {
|
|
6733
|
+
return path21.resolve(cwd, arg.slice("--config=".length));
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
return detectProject(cwd).configPath;
|
|
6635
6737
|
}
|
|
6636
|
-
function
|
|
6637
|
-
|
|
6738
|
+
function readConfigSource(configPath) {
|
|
6739
|
+
if (!configPath) return null;
|
|
6740
|
+
try {
|
|
6741
|
+
return fs16.readFileSync(configPath, "utf-8");
|
|
6742
|
+
} catch {
|
|
6743
|
+
return null;
|
|
6744
|
+
}
|
|
6638
6745
|
}
|
|
6639
|
-
function
|
|
6640
|
-
|
|
6746
|
+
function readInstalledPlaywrightVersion(cwd) {
|
|
6747
|
+
try {
|
|
6748
|
+
const require2 = (0, import_node_module2.createRequire)(path21.join(cwd, "noop.js"));
|
|
6749
|
+
for (const id of ["@playwright/test/package.json", "playwright/package.json"]) {
|
|
6750
|
+
try {
|
|
6751
|
+
return require2(id).version ?? null;
|
|
6752
|
+
} catch {
|
|
6753
|
+
}
|
|
6754
|
+
}
|
|
6755
|
+
} catch {
|
|
6756
|
+
}
|
|
6757
|
+
return null;
|
|
6641
6758
|
}
|
|
6642
|
-
function
|
|
6643
|
-
|
|
6759
|
+
function computeAddReporterArgs(cwd, playwrightArgs2) {
|
|
6760
|
+
const source = readConfigSource(resolveConfigPath(cwd, playwrightArgs2));
|
|
6761
|
+
return decideAddReporter(source, readInstalledPlaywrightVersion(cwd));
|
|
6644
6762
|
}
|
|
6645
6763
|
|
|
6646
|
-
//
|
|
6647
|
-
var
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6764
|
+
// src/cli/select.ts
|
|
6765
|
+
var EXIT_OK6 = 0;
|
|
6766
|
+
var EXIT_ERROR7 = 2;
|
|
6767
|
+
var USAGE9 = `
|
|
6768
|
+
piwi select / piwi run \u2014 run a saved selection of tests
|
|
6769
|
+
|
|
6770
|
+
Usage:
|
|
6771
|
+
npx @piwitests/reporter select <key> [options] print the Playwright args
|
|
6772
|
+
npx @piwitests/reporter run <key> [options] [-- <playwright args>]
|
|
6773
|
+
npx @piwitests/reporter run impact --base <ref> run the tests your diff impacts
|
|
6774
|
+
|
|
6775
|
+
Connection:
|
|
6776
|
+
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL)
|
|
6777
|
+
--api-key <key> API key (env PIWI_API_KEY)
|
|
6778
|
+
--project <name|id> Project (env PIWI_PROJECT_NAME)
|
|
6779
|
+
|
|
6780
|
+
Selection:
|
|
6781
|
+
--format <fmt> args (file:line, default) | grep | files | json
|
|
6782
|
+
--budget <duration> Cap total time, e.g. 5m, 90s, 300000 (ms)
|
|
6783
|
+
--shard <i/n> Keep only shard i of n, balanced by duration, lock-aware
|
|
6784
|
+
--fail-fast Order the least-reliable tests first (fail-fast)
|
|
6785
|
+
--base <ref> For "impact": the ref to diff the working tree against
|
|
6786
|
+
|
|
6787
|
+
Behavior:
|
|
6788
|
+
--strict Fail (exit 2) instead of falling back when unreachable
|
|
6789
|
+
--pkg-runner <cmd> Package runner for the printed command (default npx)
|
|
6790
|
+
--json Print the full resolution as JSON (select only)
|
|
6791
|
+
-h, --help Show this help
|
|
6792
|
+
|
|
6793
|
+
Exit codes: 0 ok, 1 the test run failed (run only), 2 could not resolve.
|
|
6794
|
+
`.trim();
|
|
6795
|
+
var IMPACT_KEY = "impact";
|
|
6796
|
+
var NOTHING_TO_RUN_ARGS = ["--pass-with-no-tests", "--grep-invert", "."];
|
|
6797
|
+
var VALUE_FLAGS3 = /* @__PURE__ */ new Set([
|
|
6798
|
+
"--server-url",
|
|
6799
|
+
"--api-key",
|
|
6800
|
+
"--project",
|
|
6801
|
+
"--format",
|
|
6802
|
+
"--budget",
|
|
6803
|
+
"--shard",
|
|
6804
|
+
"--base",
|
|
6805
|
+
"--pkg-runner"
|
|
6806
|
+
]);
|
|
6807
|
+
function readOption7(argv, name) {
|
|
6808
|
+
const withEquals = argv.find((arg) => arg.startsWith(`${name}=`));
|
|
6809
|
+
if (withEquals) return withEquals.slice(name.length + 1);
|
|
6810
|
+
const index = argv.indexOf(name);
|
|
6811
|
+
if (index === -1) return void 0;
|
|
6812
|
+
const value = argv[index + 1];
|
|
6813
|
+
return value && !value.startsWith("-") ? value : void 0;
|
|
6653
6814
|
}
|
|
6654
|
-
function
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6815
|
+
function findKey(argv) {
|
|
6816
|
+
for (let i = 0; i < argv.length; i++) {
|
|
6817
|
+
const tok = argv[i];
|
|
6818
|
+
if (tok.startsWith("-")) {
|
|
6819
|
+
if (!tok.includes("=") && VALUE_FLAGS3.has(tok)) i++;
|
|
6820
|
+
continue;
|
|
6821
|
+
}
|
|
6822
|
+
return tok;
|
|
6659
6823
|
}
|
|
6824
|
+
return void 0;
|
|
6660
6825
|
}
|
|
6661
|
-
function
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
const
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6670
|
-
}
|
|
6671
|
-
function renameValue(value, before, after, exact) {
|
|
6672
|
-
const v = normalizeWhiteSpace(value);
|
|
6673
|
-
const b = normalizeWhiteSpace(before);
|
|
6674
|
-
if (exact ? v === b : v.toLowerCase() === b.toLowerCase()) return after;
|
|
6675
|
-
const re = new RegExp(escapeRegExp2(b).replace(/ /g, "\\s+"), exact ? "" : "i");
|
|
6676
|
-
if (!re.test(value)) return null;
|
|
6677
|
-
return value.replace(re, () => after);
|
|
6678
|
-
}
|
|
6679
|
-
function withArg(call, index, arg) {
|
|
6680
|
-
return { method: call.method, args: call.args.map((a, i) => i === index ? arg : a) };
|
|
6681
|
-
}
|
|
6682
|
-
function withOption(call, index, key, arg) {
|
|
6683
|
-
const obj = call.args[index];
|
|
6684
|
-
if (obj?.type !== "object") return call;
|
|
6685
|
-
return withArg(call, index, {
|
|
6686
|
-
type: "object",
|
|
6687
|
-
entries: obj.entries.map(([k, v]) => [k, k === key ? arg : v])
|
|
6688
|
-
});
|
|
6826
|
+
function parseDuration2(raw) {
|
|
6827
|
+
const match = raw.trim().match(/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/);
|
|
6828
|
+
if (!match) return null;
|
|
6829
|
+
const value = Number(match[1]);
|
|
6830
|
+
const unit = match[2];
|
|
6831
|
+
const factor = unit === "h" ? 36e5 : unit === "m" ? 6e4 : unit === "s" ? 1e3 : 1;
|
|
6832
|
+
const ms = Math.round(value * factor);
|
|
6833
|
+
return ms > 0 ? ms : null;
|
|
6689
6834
|
}
|
|
6690
|
-
function
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6835
|
+
function parseSelectArgs(argv, env) {
|
|
6836
|
+
const dashIndex = argv.indexOf("--");
|
|
6837
|
+
const own = dashIndex === -1 ? argv : argv.slice(0, dashIndex);
|
|
6838
|
+
const extra = dashIndex === -1 ? [] : argv.slice(dashIndex + 1);
|
|
6839
|
+
const serverUrl = (readOption7(own, "--server-url") ?? env.PIWI_DASHBOARD_URL ?? "").replace(/\/$/, "");
|
|
6840
|
+
if (!serverUrl) throw new Error("No dashboard URL \u2014 pass --server-url or set PIWI_DASHBOARD_URL");
|
|
6841
|
+
const key = findKey(own);
|
|
6842
|
+
if (!key) throw new Error("No selection key \u2014 usage: piwi select <key>");
|
|
6843
|
+
const budgetRaw = readOption7(own, "--budget");
|
|
6844
|
+
let budgetMs = null;
|
|
6845
|
+
if (budgetRaw !== void 0) {
|
|
6846
|
+
budgetMs = parseDuration2(budgetRaw);
|
|
6847
|
+
if (budgetMs === null) throw new Error(`--budget expects a duration like 5m or 90s, got "${budgetRaw}"`);
|
|
6848
|
+
}
|
|
6849
|
+
const shard = readOption7(own, "--shard") ?? null;
|
|
6850
|
+
if (shard !== null && !/^\d+\s*\/\s*\d+$/.test(shard)) {
|
|
6851
|
+
throw new Error(`--shard expects an "i/n" spec like 2/4, got "${shard}"`);
|
|
6694
6852
|
}
|
|
6695
|
-
if (arg?.type !== "string") return null;
|
|
6696
|
-
const value = arg.value;
|
|
6697
6853
|
return {
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6854
|
+
serverUrl,
|
|
6855
|
+
apiKey: readOption7(own, "--api-key") ?? env.PIWI_API_KEY ?? null,
|
|
6856
|
+
project: readOption7(own, "--project") ?? env.PIWI_PROJECT_NAME ?? "",
|
|
6857
|
+
key,
|
|
6858
|
+
format: readOption7(own, "--format") ?? "args",
|
|
6859
|
+
budgetMs,
|
|
6860
|
+
shard,
|
|
6861
|
+
order: own.includes("--fail-fast") ? "failureLikelihood" : null,
|
|
6862
|
+
base: readOption7(own, "--base") ?? null,
|
|
6863
|
+
strict: own.includes("--strict"),
|
|
6864
|
+
pkgRunner: readOption7(own, "--pkg-runner") ?? "npx",
|
|
6865
|
+
json: own.includes("--json"),
|
|
6866
|
+
extra
|
|
6704
6867
|
};
|
|
6705
6868
|
}
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
const edited = (after) => selector.slice(0, at) + render(after) + selector.slice(at + len);
|
|
6710
|
-
slots.push({
|
|
6711
|
-
value,
|
|
6712
|
-
exact: true,
|
|
6713
|
-
rule: "exact",
|
|
6714
|
-
accepts: (a) => hasAttribute(a, names),
|
|
6715
|
-
rewrite: (after) => replace(edited(after)),
|
|
6716
|
-
literal: (after) => [selector, edited(after)]
|
|
6717
|
-
});
|
|
6718
|
-
};
|
|
6719
|
-
for (const m of selector.matchAll(/#((?:[\w-]|\\.)+)/g)) {
|
|
6720
|
-
slot(
|
|
6721
|
-
m[1].replace(/\\(.)/g, "$1"),
|
|
6722
|
-
["id"],
|
|
6723
|
-
(v) => `#${v.replace(/[^\w-]/g, (c) => `\\${c}`)}`,
|
|
6724
|
-
m.index,
|
|
6725
|
-
m[0].length
|
|
6726
|
-
);
|
|
6727
|
-
}
|
|
6728
|
-
for (const m of selector.matchAll(/\[\s*([\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\]\s]+))\s*\]/g)) {
|
|
6729
|
-
const attr = m[1];
|
|
6730
|
-
const names = testIds.includes(attr) ? testIds : attr === "id" || attr === "name" ? [attr] : null;
|
|
6731
|
-
if (!names) continue;
|
|
6732
|
-
const quote3 = m[2] !== void 0 ? '"' : m[3] !== void 0 ? "'" : "";
|
|
6733
|
-
const value = m[2] ?? m[3] ?? m[4];
|
|
6734
|
-
slot(value, [attr], (v) => `[${attr}=${quote3}${v}${quote3}]`, m.index, m[0].length);
|
|
6735
|
-
}
|
|
6736
|
-
return slots;
|
|
6869
|
+
var CACHE_FILE = path22.join(".piwi", "selection-cache.json");
|
|
6870
|
+
function cacheKey(args) {
|
|
6871
|
+
return `${args.project.toLowerCase()}:${args.key}:${args.format}:${args.budgetMs ?? 0}:${args.shard ?? ""}:${args.order ?? ""}`;
|
|
6737
6872
|
}
|
|
6738
|
-
function
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
const hasTextSlot = (index) => {
|
|
6745
|
-
const hasText = optionsOf(call.args[index]).get("hasText");
|
|
6746
|
-
if (!hasText) return null;
|
|
6747
|
-
return stringSlot(hasText, false, "text", isText, (next) => withOption(call, index, "hasText", next));
|
|
6748
|
-
};
|
|
6749
|
-
const textOr = (attribute) => (a) => a.kind === "translation" || a.kind === "literal" || hasAttribute(a, [attribute]);
|
|
6750
|
-
switch (call.method) {
|
|
6751
|
-
case "getByTestId":
|
|
6752
|
-
slots.push(argSlot("exact", (a) => hasAttribute(a, testIds), true));
|
|
6753
|
-
break;
|
|
6754
|
-
case "getByRole":
|
|
6755
|
-
slots.push(
|
|
6756
|
-
stringSlot(
|
|
6757
|
-
opts.get("name"),
|
|
6758
|
-
exact,
|
|
6759
|
-
"name",
|
|
6760
|
-
(a) => isText(a) || hasAttribute(a, ["aria-label", "title", "alt", "value"]),
|
|
6761
|
-
(next) => withOption(call, 1, "name", next)
|
|
6762
|
-
)
|
|
6763
|
-
);
|
|
6764
|
-
break;
|
|
6765
|
-
case "getByText":
|
|
6766
|
-
slots.push(argSlot("text", isText));
|
|
6767
|
-
break;
|
|
6768
|
-
case "getByLabel":
|
|
6769
|
-
slots.push(argSlot("text", (a) => isText(a) || hasAttribute(a, ["aria-label"])));
|
|
6770
|
-
break;
|
|
6771
|
-
case "getByPlaceholder":
|
|
6772
|
-
slots.push(argSlot("text", textOr("placeholder")));
|
|
6773
|
-
break;
|
|
6774
|
-
case "getByAltText":
|
|
6775
|
-
slots.push(argSlot("text", textOr("alt")));
|
|
6776
|
-
break;
|
|
6777
|
-
case "getByTitle":
|
|
6778
|
-
slots.push(argSlot("text", textOr("title")));
|
|
6779
|
-
break;
|
|
6780
|
-
case "locator":
|
|
6781
|
-
if (first?.type === "string") {
|
|
6782
|
-
slots.push(...cssSlots(first.value, testIds, (next) => withArg(call, 0, { type: "string", value: next })));
|
|
6783
|
-
}
|
|
6784
|
-
slots.push(hasTextSlot(1));
|
|
6785
|
-
break;
|
|
6786
|
-
case "filter":
|
|
6787
|
-
slots.push(hasTextSlot(0));
|
|
6788
|
-
break;
|
|
6873
|
+
function readCache(args) {
|
|
6874
|
+
try {
|
|
6875
|
+
const store = JSON.parse(fs17.readFileSync(CACHE_FILE, "utf-8"));
|
|
6876
|
+
return store[cacheKey(args)] ?? null;
|
|
6877
|
+
} catch {
|
|
6878
|
+
return null;
|
|
6789
6879
|
}
|
|
6790
|
-
return slots.filter((slot) => !!slot);
|
|
6791
6880
|
}
|
|
6792
|
-
function
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
for (const [key, v] of arg.entries) {
|
|
6799
|
-
if (v.type === "chain") {
|
|
6800
|
-
out.push({ chain: v.chain, put: (c) => withOption(call, index, key, { type: "chain", chain: c }) });
|
|
6801
|
-
}
|
|
6802
|
-
}
|
|
6881
|
+
function writeCache(args, resolution) {
|
|
6882
|
+
try {
|
|
6883
|
+
let store = {};
|
|
6884
|
+
try {
|
|
6885
|
+
store = JSON.parse(fs17.readFileSync(CACHE_FILE, "utf-8"));
|
|
6886
|
+
} catch {
|
|
6803
6887
|
}
|
|
6804
|
-
|
|
6805
|
-
|
|
6888
|
+
store[cacheKey(args)] = resolution;
|
|
6889
|
+
fs17.mkdirSync(path22.dirname(CACHE_FILE), { recursive: true });
|
|
6890
|
+
fs17.writeFileSync(CACHE_FILE, JSON.stringify(store, null, 2));
|
|
6891
|
+
} catch {
|
|
6892
|
+
}
|
|
6806
6893
|
}
|
|
6807
|
-
function
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
const
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
}
|
|
6894
|
+
async function resolveWithCache(args) {
|
|
6895
|
+
try {
|
|
6896
|
+
const projectId = await resolveProjectId(args);
|
|
6897
|
+
const resolution = await fetchResolution(args, projectId);
|
|
6898
|
+
writeCache(args, resolution);
|
|
6899
|
+
return { resolution, fromCache: false };
|
|
6900
|
+
} catch (e) {
|
|
6901
|
+
if (args.strict) throw e;
|
|
6902
|
+
const cached = readCache(args);
|
|
6903
|
+
if (cached) {
|
|
6904
|
+
console.error(`piwi: dashboard unreachable, using cached resolution \u2014 ${e.message}`);
|
|
6905
|
+
return { resolution: cached, fromCache: true };
|
|
6820
6906
|
}
|
|
6907
|
+
return { error: e };
|
|
6908
|
+
}
|
|
6909
|
+
}
|
|
6910
|
+
function printWarnings(resolution) {
|
|
6911
|
+
for (const w of resolution.warnings) console.error(`piwi: warning [${w.code}] ${w.message}`);
|
|
6912
|
+
}
|
|
6913
|
+
function git2(args) {
|
|
6914
|
+
return (0, import_node_child_process7.execFileSync)("git", ["-c", "core.quotePath=false", ...args], {
|
|
6915
|
+
encoding: "utf-8",
|
|
6916
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
6917
|
+
maxBuffer: 256 * 1024 * 1024
|
|
6821
6918
|
});
|
|
6822
|
-
return out;
|
|
6823
6919
|
}
|
|
6824
|
-
function
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6920
|
+
function gitChange(base) {
|
|
6921
|
+
const names = git2(["diff", "--name-only", base]).split("\n").map((line) => line.trim()).filter(Boolean);
|
|
6922
|
+
let fromDiff = { changedFiles: [], changes: [] };
|
|
6923
|
+
try {
|
|
6924
|
+
fromDiff = diffImpactInput(
|
|
6925
|
+
parseUnifiedDiff(
|
|
6926
|
+
git2([
|
|
6927
|
+
"diff",
|
|
6928
|
+
"--full-index",
|
|
6929
|
+
"--unified=0",
|
|
6930
|
+
"--no-color",
|
|
6931
|
+
"--no-ext-diff",
|
|
6932
|
+
"--src-prefix=a/",
|
|
6933
|
+
"--dst-prefix=b/",
|
|
6934
|
+
base,
|
|
6935
|
+
"--"
|
|
6936
|
+
])
|
|
6937
|
+
)
|
|
6938
|
+
);
|
|
6939
|
+
} catch {
|
|
6832
6940
|
}
|
|
6941
|
+
return { changedFiles: [.../* @__PURE__ */ new Set([...names, ...fromDiff.changedFiles])], changes: fromDiff.changes };
|
|
6833
6942
|
}
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
const y = filePath(b);
|
|
6838
|
-
return x === y || x.endsWith("/" + y) || y.endsWith("/" + x);
|
|
6943
|
+
async function loadImpact(args, projectId) {
|
|
6944
|
+
const { changedFiles, changes } = gitChange(args.base);
|
|
6945
|
+
return fetchImpact(args, projectId, changedFiles, changes);
|
|
6839
6946
|
}
|
|
6840
|
-
function
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6947
|
+
function lineLevelNote(impact) {
|
|
6948
|
+
const files = impact.impact.lineLevelFiles ?? 0;
|
|
6949
|
+
return files > 0 ? `, ${files} file(s) narrowed by line coverage` : "";
|
|
6950
|
+
}
|
|
6951
|
+
async function runSelectImpact(args) {
|
|
6952
|
+
let impact;
|
|
6953
|
+
try {
|
|
6954
|
+
const projectId = await resolveProjectId(args);
|
|
6955
|
+
impact = await loadImpact(args, projectId);
|
|
6956
|
+
} catch (e) {
|
|
6957
|
+
console.error(`piwi select: ${e.message}`);
|
|
6958
|
+
return EXIT_ERROR7;
|
|
6959
|
+
}
|
|
6960
|
+
printWarnings(impact);
|
|
6961
|
+
console.error(
|
|
6962
|
+
`piwi select: ${impact.impact.changedFiles} changed file(s) \u2192 ${impact.estimate.count} impacted test(s)${impact.impact.widened ? " (widened to full suite)" : lineLevelNote(impact)}`
|
|
6963
|
+
);
|
|
6964
|
+
if (args.json) {
|
|
6965
|
+
console.log(JSON.stringify(impact, null, 2));
|
|
6966
|
+
return EXIT_OK6;
|
|
6967
|
+
}
|
|
6968
|
+
if (impact.impact.widened) console.log("");
|
|
6969
|
+
else if (impact.estimate.count === 0) {
|
|
6970
|
+
console.error(`piwi select: no tests impacted \u2014 printing arguments that run none`);
|
|
6971
|
+
console.log(NOTHING_TO_RUN_ARGS.join(" "));
|
|
6972
|
+
} else console.log(impact.materialization.args.join(" "));
|
|
6973
|
+
return EXIT_OK6;
|
|
6974
|
+
}
|
|
6975
|
+
async function runSelect(argv, env = process.env) {
|
|
6976
|
+
if (argv.includes("-h") || argv.includes("--help")) {
|
|
6977
|
+
console.log(USAGE9);
|
|
6978
|
+
return EXIT_OK6;
|
|
6979
|
+
}
|
|
6980
|
+
let args;
|
|
6981
|
+
try {
|
|
6982
|
+
args = parseSelectArgs(argv, env);
|
|
6983
|
+
} catch (e) {
|
|
6984
|
+
console.error(`piwi select: ${e.message}
|
|
6985
|
+
`);
|
|
6986
|
+
console.error(USAGE9);
|
|
6987
|
+
return EXIT_ERROR7;
|
|
6988
|
+
}
|
|
6989
|
+
if (args.key === IMPACT_KEY) {
|
|
6990
|
+
if (!args.base) {
|
|
6991
|
+
console.error("piwi select: impact needs a base ref \u2014 pass --base <ref> (e.g. --base origin/main)");
|
|
6992
|
+
return EXIT_ERROR7;
|
|
6852
6993
|
}
|
|
6994
|
+
return runSelectImpact(args);
|
|
6853
6995
|
}
|
|
6854
|
-
|
|
6996
|
+
let resolution;
|
|
6997
|
+
try {
|
|
6998
|
+
const projectId = await resolveProjectId(args);
|
|
6999
|
+
resolution = await fetchResolution(args, projectId);
|
|
7000
|
+
} catch (e) {
|
|
7001
|
+
console.error(`piwi select: ${e.message}`);
|
|
7002
|
+
return EXIT_ERROR7;
|
|
7003
|
+
}
|
|
7004
|
+
printWarnings(resolution);
|
|
7005
|
+
if (resolution.estimate.count === 0) {
|
|
7006
|
+
console.error(`piwi select: "${args.key}" resolved to 0 tests`);
|
|
7007
|
+
return EXIT_ERROR7;
|
|
7008
|
+
}
|
|
7009
|
+
if (args.json) console.log(JSON.stringify(resolution, null, 2));
|
|
7010
|
+
else console.log(resolution.materialization.args.join(" "));
|
|
7011
|
+
return EXIT_OK6;
|
|
6855
7012
|
}
|
|
6856
|
-
function
|
|
6857
|
-
|
|
7013
|
+
function spawnPlaywrightForRun(pkgRunner, playwrightArgs2, env) {
|
|
7014
|
+
const decision = computeAddReporterArgs(process.cwd(), playwrightArgs2);
|
|
7015
|
+
if (decision.log) console.error(decision.log);
|
|
7016
|
+
return spawnPlaywright([...decision.args, ...playwrightArgs2], env, { runner: pkgRunner, command: "piwi run" });
|
|
6858
7017
|
}
|
|
6859
|
-
function
|
|
6860
|
-
|
|
6861
|
-
return m ? Number(m[1]) : null;
|
|
7018
|
+
function shardEnv(args) {
|
|
7019
|
+
return args.shard ? { [PIWI_SHARD_ENV]: args.shard } : {};
|
|
6862
7020
|
}
|
|
6863
|
-
function
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
7021
|
+
async function runRunImpact(args, env) {
|
|
7022
|
+
let projectId;
|
|
7023
|
+
try {
|
|
7024
|
+
projectId = await resolveProjectId(args);
|
|
7025
|
+
} catch (e) {
|
|
7026
|
+
if (args.strict) {
|
|
7027
|
+
console.error(`piwi run: ${e.message}`);
|
|
7028
|
+
return EXIT_ERROR7;
|
|
7029
|
+
}
|
|
7030
|
+
console.error(`piwi run: ${e.message} \u2014 running the full suite`);
|
|
7031
|
+
return spawnPlaywrightForRun(args.pkgRunner, args.extra, env);
|
|
6867
7032
|
}
|
|
6868
|
-
|
|
7033
|
+
let impact;
|
|
7034
|
+
try {
|
|
7035
|
+
impact = await loadImpact(args, projectId);
|
|
7036
|
+
} catch (e) {
|
|
7037
|
+
if (args.strict) {
|
|
7038
|
+
console.error(`piwi run: ${e.message}`);
|
|
7039
|
+
return EXIT_ERROR7;
|
|
7040
|
+
}
|
|
7041
|
+
console.error(`piwi run: ${e.message} \u2014 running the full suite`);
|
|
7042
|
+
return spawnPlaywrightForRun(args.pkgRunner, args.extra, env);
|
|
7043
|
+
}
|
|
7044
|
+
printWarnings(impact);
|
|
7045
|
+
if (impact.impact.widened) {
|
|
7046
|
+
console.error(
|
|
7047
|
+
`piwi run: impact widened to the full suite (${impact.impact.unmappedSourceFiles.length} changed file(s) not fully mapped to tests)`
|
|
7048
|
+
);
|
|
7049
|
+
return spawnPlaywrightForRun(args.pkgRunner, args.extra, env);
|
|
7050
|
+
}
|
|
7051
|
+
if (impact.estimate.count === 0) {
|
|
7052
|
+
console.error(`piwi run: no tests impacted by ${impact.impact.changedFiles} changed file(s) \u2014 nothing to run`);
|
|
7053
|
+
return EXIT_OK6;
|
|
7054
|
+
}
|
|
7055
|
+
const runEnv = {
|
|
7056
|
+
...env,
|
|
7057
|
+
PIWI_SELECTION: IMPACT_KEY,
|
|
7058
|
+
PIWI_SELECTION_VERSION: "0",
|
|
7059
|
+
PIWI_SELECTION_HASH: impact.resolvedHash,
|
|
7060
|
+
PIWI_SELECTION_COUNT: String(impact.estimate.count),
|
|
7061
|
+
...shardEnv(args)
|
|
7062
|
+
};
|
|
7063
|
+
console.error(`piwi run: impact \u2192 ${impact.estimate.count} test(s)${lineLevelNote(impact)}`);
|
|
7064
|
+
return spawnPlaywrightForRun(args.pkgRunner, [...impact.materialization.args, ...args.extra], runEnv);
|
|
6869
7065
|
}
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
uses: entry.uses,
|
|
6893
|
-
tests,
|
|
6894
|
-
confidence: confidenceOf(anchor, tests, options.reach)
|
|
6895
|
-
};
|
|
6896
|
-
if (anchor.after !== void 0 && hit.every((slot) => slot.rewrite)) {
|
|
6897
|
-
let rewritten = chain;
|
|
6898
|
-
const replacements = [];
|
|
6899
|
-
for (const slot of hit) {
|
|
6900
|
-
const again = chainSlots(rewritten, testIds).find(
|
|
6901
|
-
(s) => s.rule === slot.rule && String(s.value) === String(slot.value) && s.rewrite
|
|
6902
|
-
);
|
|
6903
|
-
if (!again?.rewrite) continue;
|
|
6904
|
-
rewritten = again.rewrite(anchor.after, anchor.before);
|
|
6905
|
-
if (slot.literal) replacements.push(slot.literal(anchor.after, anchor.before));
|
|
6906
|
-
}
|
|
6907
|
-
const rendered = renderLocatorChain(rewritten);
|
|
6908
|
-
if (rendered !== entry.locator) {
|
|
6909
|
-
found.rewrite = rendered;
|
|
6910
|
-
found.replacements = replacements.filter(([a, b]) => a !== b);
|
|
6911
|
-
}
|
|
6912
|
-
}
|
|
6913
|
-
breaks.push(found);
|
|
7066
|
+
async function runRun(argv, env = process.env) {
|
|
7067
|
+
if (argv.includes("-h") || argv.includes("--help")) {
|
|
7068
|
+
console.log(USAGE9);
|
|
7069
|
+
return EXIT_OK6;
|
|
7070
|
+
}
|
|
7071
|
+
let args;
|
|
7072
|
+
try {
|
|
7073
|
+
args = parseSelectArgs(argv, env);
|
|
7074
|
+
} catch (e) {
|
|
7075
|
+
console.error(`piwi run: ${e.message}
|
|
7076
|
+
`);
|
|
7077
|
+
console.error(USAGE9);
|
|
7078
|
+
return EXIT_ERROR7;
|
|
7079
|
+
}
|
|
7080
|
+
if (args.format === "json") {
|
|
7081
|
+
console.error("piwi run: --format json cannot be run; use args, grep or files");
|
|
7082
|
+
return EXIT_ERROR7;
|
|
7083
|
+
}
|
|
7084
|
+
if (args.key === IMPACT_KEY) {
|
|
7085
|
+
if (!args.base) {
|
|
7086
|
+
console.error("piwi run: impact needs a base ref \u2014 pass --base <ref> (e.g. --base origin/main)");
|
|
7087
|
+
return EXIT_ERROR7;
|
|
6914
7088
|
}
|
|
7089
|
+
return runRunImpact(args, env);
|
|
6915
7090
|
}
|
|
6916
|
-
|
|
6917
|
-
|
|
7091
|
+
let outcome;
|
|
7092
|
+
try {
|
|
7093
|
+
outcome = await resolveWithCache(args);
|
|
7094
|
+
} catch (e) {
|
|
7095
|
+
console.error(`piwi run: ${e.message}`);
|
|
7096
|
+
return EXIT_ERROR7;
|
|
7097
|
+
}
|
|
7098
|
+
if ("error" in outcome) {
|
|
7099
|
+
console.error(`piwi run: ${outcome.error.message} \u2014 no cached resolution, running the full suite`);
|
|
7100
|
+
return spawnPlaywrightForRun(args.pkgRunner, args.extra, env);
|
|
7101
|
+
}
|
|
7102
|
+
const { resolution } = outcome;
|
|
7103
|
+
printWarnings(resolution);
|
|
7104
|
+
if (resolution.estimate.count === 0) {
|
|
7105
|
+
console.error(`piwi run: "${args.key}" resolved to 0 tests`);
|
|
7106
|
+
return EXIT_ERROR7;
|
|
7107
|
+
}
|
|
7108
|
+
const runEnv = {
|
|
7109
|
+
...env,
|
|
7110
|
+
PIWI_SELECTION: resolution.key ?? args.key,
|
|
7111
|
+
PIWI_SELECTION_VERSION: String(resolution.version ?? 0),
|
|
7112
|
+
PIWI_SELECTION_HASH: resolution.resolvedHash,
|
|
7113
|
+
PIWI_SELECTION_COUNT: String(resolution.estimate.count),
|
|
7114
|
+
...shardEnv(args)
|
|
7115
|
+
};
|
|
7116
|
+
console.error(
|
|
7117
|
+
`piwi run: ${args.key} \u2192 ${resolution.estimate.count} tests${resolution.materialization.format !== args.format ? ` (materialized as ${resolution.materialization.format})` : ""}`
|
|
6918
7118
|
);
|
|
7119
|
+
return spawnPlaywrightForRun(args.pkgRunner, [...resolution.materialization.args, ...args.extra], runEnv);
|
|
6919
7120
|
}
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
}
|
|
7121
|
+
|
|
7122
|
+
// src/cli/preflight.ts
|
|
7123
|
+
var fs18 = __toESM(require("fs"));
|
|
7124
|
+
var path23 = __toESM(require("path"));
|
|
7125
|
+
var import_node_child_process8 = require("child_process");
|
|
6926
7126
|
|
|
6927
7127
|
// ../core/src/locator-edit.ts
|
|
6928
7128
|
function escapeForQuote(value, quote3) {
|
|
@@ -7046,15 +7246,15 @@ function parsePreflightArgs(argv) {
|
|
|
7046
7246
|
}
|
|
7047
7247
|
var CONFIG_NAMES2 = ["playwright.config.ts", "playwright.config.js", "playwright.config.mjs", "playwright.config.cjs"];
|
|
7048
7248
|
function findPlaywrightRoot(from) {
|
|
7049
|
-
let dir =
|
|
7249
|
+
let dir = path23.resolve(from);
|
|
7050
7250
|
for (; ; ) {
|
|
7051
|
-
if (CONFIG_NAMES2.some((name) => fs18.existsSync(
|
|
7052
|
-
const parent =
|
|
7251
|
+
if (CONFIG_NAMES2.some((name) => fs18.existsSync(path23.join(dir, name)))) return dir;
|
|
7252
|
+
const parent = path23.dirname(dir);
|
|
7053
7253
|
if (parent === dir) return null;
|
|
7054
7254
|
dir = parent;
|
|
7055
7255
|
}
|
|
7056
7256
|
}
|
|
7057
|
-
function
|
|
7257
|
+
function git3(cwd, args) {
|
|
7058
7258
|
return (0, import_node_child_process8.execFileSync)("git", args, {
|
|
7059
7259
|
cwd,
|
|
7060
7260
|
encoding: "utf-8",
|
|
@@ -7064,7 +7264,7 @@ function git2(cwd, args) {
|
|
|
7064
7264
|
}
|
|
7065
7265
|
function tryGit(cwd, args) {
|
|
7066
7266
|
try {
|
|
7067
|
-
return
|
|
7267
|
+
return git3(cwd, args);
|
|
7068
7268
|
} catch {
|
|
7069
7269
|
return void 0;
|
|
7070
7270
|
}
|
|
@@ -7079,7 +7279,7 @@ function translationLookup(repoRoot, base, locale) {
|
|
|
7079
7279
|
const build = (side) => {
|
|
7080
7280
|
const map = /* @__PURE__ */ new Map();
|
|
7081
7281
|
for (const file of files) {
|
|
7082
|
-
const content = side === "new" ? readText(
|
|
7282
|
+
const content = side === "new" ? readText(path23.join(repoRoot, file)) : tryGit(repoRoot, ["show", `${base}:${file}`]);
|
|
7083
7283
|
if (content === void 0) continue;
|
|
7084
7284
|
for (const entry of parseTranslationFile(file, content)) {
|
|
7085
7285
|
if (!map.has(entry.key)) map.set(entry.key, entry.value);
|
|
@@ -7103,20 +7303,20 @@ function readText(file) {
|
|
|
7103
7303
|
return void 0;
|
|
7104
7304
|
}
|
|
7105
7305
|
}
|
|
7106
|
-
var CACHE_FILE2 =
|
|
7306
|
+
var CACHE_FILE2 = path23.join(".piwi", "locator-index.json");
|
|
7107
7307
|
function cacheKey2(connection, branch) {
|
|
7108
7308
|
return `${connection.serverUrl}|${connection.project}|${branch ?? ""}`;
|
|
7109
7309
|
}
|
|
7110
7310
|
function readCache2(root, key) {
|
|
7111
7311
|
try {
|
|
7112
|
-
const store = JSON.parse(fs18.readFileSync(
|
|
7312
|
+
const store = JSON.parse(fs18.readFileSync(path23.join(root, CACHE_FILE2), "utf-8"));
|
|
7113
7313
|
return store[key] ?? null;
|
|
7114
7314
|
} catch {
|
|
7115
7315
|
return null;
|
|
7116
7316
|
}
|
|
7117
7317
|
}
|
|
7118
7318
|
function writeCache2(root, key, index) {
|
|
7119
|
-
const file =
|
|
7319
|
+
const file = path23.join(root, CACHE_FILE2);
|
|
7120
7320
|
try {
|
|
7121
7321
|
let store = {};
|
|
7122
7322
|
try {
|
|
@@ -7124,7 +7324,7 @@ function writeCache2(root, key, index) {
|
|
|
7124
7324
|
} catch {
|
|
7125
7325
|
}
|
|
7126
7326
|
store[key] = { savedAt: (/* @__PURE__ */ new Date()).toISOString(), index };
|
|
7127
|
-
fs18.mkdirSync(
|
|
7327
|
+
fs18.mkdirSync(path23.dirname(file), { recursive: true });
|
|
7128
7328
|
fs18.writeFileSync(file, JSON.stringify(store));
|
|
7129
7329
|
} catch {
|
|
7130
7330
|
}
|
|
@@ -7141,7 +7341,7 @@ function planSites(b, testRoot) {
|
|
|
7141
7341
|
const sites = [...new Set(b.uses.flatMap((u) => u.callSites))];
|
|
7142
7342
|
return sites.map((callSite) => {
|
|
7143
7343
|
if (!b.rewrite || !b.replacements?.length) return { callSite, action: "none" };
|
|
7144
|
-
const file =
|
|
7344
|
+
const file = path23.resolve(testRoot, callSiteFile(callSite));
|
|
7145
7345
|
const lineNo = callSiteLine(callSite);
|
|
7146
7346
|
const text = readText(file);
|
|
7147
7347
|
if (text === void 0 || lineNo === null) return { callSite, action: "elsewhere" };
|
|
@@ -7305,10 +7505,22 @@ async function runPreflight(argv, env = process.env, cwd = process.cwd()) {
|
|
|
7305
7505
|
console.error("piwi preflight: not inside a git repository");
|
|
7306
7506
|
return EXIT_ERROR8;
|
|
7307
7507
|
}
|
|
7308
|
-
const testRoot =
|
|
7508
|
+
const testRoot = path23.resolve(cwd, args.testRoot ?? findPlaywrightRoot(cwd) ?? cwd);
|
|
7309
7509
|
let files;
|
|
7310
7510
|
try {
|
|
7311
|
-
files = parseUnifiedDiff(
|
|
7511
|
+
files = parseUnifiedDiff(
|
|
7512
|
+
git3(repoRoot, [
|
|
7513
|
+
"diff",
|
|
7514
|
+
"--full-index",
|
|
7515
|
+
"--unified=0",
|
|
7516
|
+
"--no-color",
|
|
7517
|
+
"--no-ext-diff",
|
|
7518
|
+
"--src-prefix=a/",
|
|
7519
|
+
"--dst-prefix=b/",
|
|
7520
|
+
args.base,
|
|
7521
|
+
"--"
|
|
7522
|
+
])
|
|
7523
|
+
);
|
|
7312
7524
|
} catch (e) {
|
|
7313
7525
|
console.error(`piwi preflight: could not diff against ${args.base} \u2014 ${e.message.split("\n")[0]}`);
|
|
7314
7526
|
return EXIT_ERROR8;
|
|
@@ -7342,7 +7554,7 @@ async function runPreflight(argv, env = process.env, cwd = process.cwd()) {
|
|
|
7342
7554
|
testIdAttributes: index.testIdAttributes ?? void 0,
|
|
7343
7555
|
isTestFile: (file) => TEST_FILE.test(file),
|
|
7344
7556
|
translations: translationLookup(repoRoot, args.base, args.locale),
|
|
7345
|
-
readFile: (file, side) => side === "new" ? readText(
|
|
7557
|
+
readFile: (file, side) => side === "new" ? readText(path23.join(repoRoot, file)) : tryGit(repoRoot, ["show", `${args.base}:${file}`])
|
|
7346
7558
|
});
|
|
7347
7559
|
const found = predictLocatorBreaks(anchors, index, { reach: await loadReach(connection, projectId, args.branch) });
|
|
7348
7560
|
const replacementsOf = /* @__PURE__ */ new Map();
|
|
@@ -7360,14 +7572,15 @@ async function runPreflight(argv, env = process.env, cwd = process.cwd()) {
|
|
|
7360
7572
|
return pb;
|
|
7361
7573
|
});
|
|
7362
7574
|
let impact = null;
|
|
7363
|
-
const changed = files
|
|
7575
|
+
const { changedFiles: changed, changes } = diffImpactInput(files);
|
|
7364
7576
|
const breakSpecs = [...new Set(breaks.flatMap((b) => b.tests.map((t) => t.file)))];
|
|
7365
7577
|
if (projectId !== null && (changed.length || args.run && breakSpecs.length)) {
|
|
7366
7578
|
try {
|
|
7367
7579
|
impact = await fetchImpact(
|
|
7368
7580
|
{ ...connection, key: "impact", format: "args", pkgRunner: args.pkgRunner },
|
|
7369
7581
|
projectId,
|
|
7370
|
-
args.run ? [.../* @__PURE__ */ new Set([...changed, ...breakSpecs])] : changed
|
|
7582
|
+
args.run ? [.../* @__PURE__ */ new Set([...changed, ...breakSpecs])] : changed,
|
|
7583
|
+
changes
|
|
7371
7584
|
);
|
|
7372
7585
|
} catch (e) {
|
|
7373
7586
|
console.error(`piwi preflight: could not ask which tests reach the changed files \u2014 ${e.message}`);
|
|
@@ -7387,8 +7600,8 @@ async function runPreflight(argv, env = process.env, cwd = process.cwd()) {
|
|
|
7387
7600
|
if (args.json) console.log(JSON.stringify(result, null, 2));
|
|
7388
7601
|
else {
|
|
7389
7602
|
const rel = (file) => {
|
|
7390
|
-
const abs =
|
|
7391
|
-
const r =
|
|
7603
|
+
const abs = path23.isAbsolute(file) ? file : path23.resolve(testRoot, file);
|
|
7604
|
+
const r = path23.relative(cwd, abs).split(path23.sep).join("/");
|
|
7392
7605
|
return r.startsWith("..") ? file : r || file;
|
|
7393
7606
|
};
|
|
7394
7607
|
console.log(renderPreflight(result, rel));
|
|
@@ -7420,7 +7633,7 @@ async function runPreflight(argv, env = process.env, cwd = process.cwd()) {
|
|
|
7420
7633
|
// src/cli/probe.ts
|
|
7421
7634
|
var fs19 = __toESM(require("fs"));
|
|
7422
7635
|
var os3 = __toESM(require("os"));
|
|
7423
|
-
var
|
|
7636
|
+
var path24 = __toESM(require("path"));
|
|
7424
7637
|
var EXIT_OK8 = 0;
|
|
7425
7638
|
var EXIT_ERROR9 = 2;
|
|
7426
7639
|
var USAGE11 = `
|
|
@@ -7484,10 +7697,10 @@ async function runProbe(argv) {
|
|
|
7484
7697
|
return EXIT_OK8;
|
|
7485
7698
|
}
|
|
7486
7699
|
console.error(`piwi probe: ${plan.items.length} pair(s) to probe (budget ${plan.budget}).`);
|
|
7487
|
-
const dir = fs19.mkdtempSync(
|
|
7488
|
-
const planFile =
|
|
7489
|
-
const resultsFile =
|
|
7490
|
-
const runOutputFile =
|
|
7700
|
+
const dir = fs19.mkdtempSync(path24.join(os3.tmpdir(), "piwi-probe-"));
|
|
7701
|
+
const planFile = path24.join(dir, "plan.json");
|
|
7702
|
+
const resultsFile = path24.join(dir, "results.jsonl");
|
|
7703
|
+
const runOutputFile = path24.join(dir, "run.json");
|
|
7491
7704
|
fs19.writeFileSync(planFile, JSON.stringify(plan));
|
|
7492
7705
|
fs19.writeFileSync(resultsFile, "");
|
|
7493
7706
|
const grep = plan.items.map((i) => i.testTitle).filter(Boolean).map(escapeRegex2).join("|");
|