@piwitests/reporter 0.61.0 → 0.62.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 +369 -223
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1794 -266
- package/dist/internal/capture/attachments.d.ts +24 -1
- package/dist/internal/capture/attachments.js +21 -2
- package/dist/internal/capture/capture-fixtures.d.ts +114 -6
- package/dist/internal/capture/capture-fixtures.js +1597 -176
- package/package.json +1 -1
- package/templates/skills/apply-locator-healing/SKILL.md +2 -2
- package/templates/skills/fix-a-reported-bug/SKILL.md +11 -8
- package/templates/skills/investigate-failure/SKILL.md +3 -3
- package/templates/skills/setup-piwi/SKILL.md +11 -8
- package/templates/skills/stabilize-flaky-tests/SKILL.md +3 -3
- package/templates/skills/write-the-missing-test/SKILL.md +13 -10
package/dist/cli/index.js
CHANGED
|
@@ -647,11 +647,11 @@ function spawnPlaywright(playwrightArgs2, env, options = {}) {
|
|
|
647
647
|
let fd = output === "inherit" ? null : fs3.openSync(output.file, "a");
|
|
648
648
|
const stdio = fd === null ? "inherit" : ["ignore", fd, fd];
|
|
649
649
|
const child = (0, import_node_child_process.spawn)(invocation.file, invocation.args, { stdio, env, cwd });
|
|
650
|
-
return new Promise((
|
|
650
|
+
return new Promise((resolve11) => {
|
|
651
651
|
const done = (code) => {
|
|
652
652
|
if (fd !== null) fs3.closeSync(fd);
|
|
653
653
|
fd = null;
|
|
654
|
-
|
|
654
|
+
resolve11(code);
|
|
655
655
|
};
|
|
656
656
|
child.on("error", (err) => {
|
|
657
657
|
console.error(`${label}: could not start Playwright \u2014 ${err.message}`);
|
|
@@ -788,9 +788,8 @@ async function runAi(argv, env = process.env) {
|
|
|
788
788
|
}
|
|
789
789
|
|
|
790
790
|
// src/cli/bug.ts
|
|
791
|
-
var
|
|
792
|
-
var
|
|
793
|
-
var import_node_child_process2 = require("child_process");
|
|
791
|
+
var fs7 = __toESM(require("fs"));
|
|
792
|
+
var path10 = __toESM(require("path"));
|
|
794
793
|
|
|
795
794
|
// src/internal/config/env.ts
|
|
796
795
|
var path8 = __toESM(require("path"));
|
|
@@ -870,6 +869,13 @@ function readDotEnv(filePath2) {
|
|
|
870
869
|
}
|
|
871
870
|
}
|
|
872
871
|
|
|
872
|
+
// src/internal/support/ci.ts
|
|
873
|
+
function detectCi(env) {
|
|
874
|
+
return Boolean(
|
|
875
|
+
env.JENKINS_URL || env.GITHUB_ACTIONS || env.GITLAB_CI || env.CIRCLECI || env.TRAVIS || env.TF_BUILD || env.BITBUCKET_BUILD_NUMBER || env.TEAMCITY_VERSION || env.CODEBUILD_BUILD_ID || env.bamboo_buildKey || env.CI && env.CI.toLowerCase() !== "false" && env.CI !== "0"
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
|
|
873
879
|
// src/internal/config/env.ts
|
|
874
880
|
var DEFAULTS = {
|
|
875
881
|
projectName: "default-project",
|
|
@@ -1007,6 +1013,82 @@ var ENV_FALLBACK_SPECS = [
|
|
|
1007
1013
|
{ option: "pickLocatorOnFailure", env: PIWI_ENV_KEYS.pickLocatorOnFailure, kind: "bool" },
|
|
1008
1014
|
{ option: "outputFile", env: PIWI_ENV_KEYS.outputFile, kind: "string" }
|
|
1009
1015
|
];
|
|
1016
|
+
var DESKTOP_APP_MODES = ["prefer", "fallback", "off"];
|
|
1017
|
+
function desktopAppMode(option, envValue) {
|
|
1018
|
+
const fromEnv = envValue?.trim().toLowerCase();
|
|
1019
|
+
if (DESKTOP_APP_MODES.includes(fromEnv)) return fromEnv;
|
|
1020
|
+
if (DESKTOP_APP_MODES.includes(option)) return option;
|
|
1021
|
+
return "fallback";
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// src/internal/support/connection.ts
|
|
1025
|
+
var fs6 = __toESM(require("fs"));
|
|
1026
|
+
var path9 = __toESM(require("path"));
|
|
1027
|
+
var import_node_child_process2 = require("child_process");
|
|
1028
|
+
var CONFIG_NAMES = ["playwright.config.ts", "playwright.config.js", "playwright.config.mjs", "playwright.config.cjs"];
|
|
1029
|
+
function findPlaywrightRoot(from, root) {
|
|
1030
|
+
const top = root ? path9.resolve(root) : null;
|
|
1031
|
+
let dir = path9.resolve(from);
|
|
1032
|
+
for (; ; ) {
|
|
1033
|
+
if (CONFIG_NAMES.some((name) => fs6.existsSync(path9.join(dir, name)))) return dir;
|
|
1034
|
+
const parent = path9.dirname(dir);
|
|
1035
|
+
if (dir === top || parent === dir) return null;
|
|
1036
|
+
dir = parent;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
function gitRoot(cwd) {
|
|
1040
|
+
try {
|
|
1041
|
+
const out = (0, import_node_child_process2.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
|
|
1042
|
+
cwd,
|
|
1043
|
+
encoding: "utf-8",
|
|
1044
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1045
|
+
}).trim();
|
|
1046
|
+
return out ? path9.resolve(out) : null;
|
|
1047
|
+
} catch {
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
function connectionFolders(cwd) {
|
|
1052
|
+
const root = gitRoot(cwd);
|
|
1053
|
+
const dirs = [findPlaywrightRoot(cwd, root) ?? cwd, cwd, root];
|
|
1054
|
+
return [...new Set(dirs.filter((dir) => dir !== null).map((dir) => path9.resolve(dir)))];
|
|
1055
|
+
}
|
|
1056
|
+
function projectOf(source) {
|
|
1057
|
+
const project = source[PIWI_ENV_KEYS.projectName];
|
|
1058
|
+
return project ? { [PIWI_ENV_KEYS.projectName]: project } : {};
|
|
1059
|
+
}
|
|
1060
|
+
function readDotEnvConnection(dirs) {
|
|
1061
|
+
const files = dirs.map((dir) => readDotEnv(path9.join(dir, ".env")));
|
|
1062
|
+
const withUrl = files.find((file) => file[PIWI_ENV_KEYS.serverUrl]);
|
|
1063
|
+
const vars = {};
|
|
1064
|
+
if (withUrl) {
|
|
1065
|
+
vars[PIWI_ENV_KEYS.serverUrl] = withUrl[PIWI_ENV_KEYS.serverUrl];
|
|
1066
|
+
if (withUrl[PIWI_ENV_KEYS.apiKey]) vars[PIWI_ENV_KEYS.apiKey] = withUrl[PIWI_ENV_KEYS.apiKey];
|
|
1067
|
+
}
|
|
1068
|
+
const project = files.find((file) => file[PIWI_ENV_KEYS.projectName])?.[PIWI_ENV_KEYS.projectName];
|
|
1069
|
+
if (project) vars[PIWI_ENV_KEYS.projectName] = project;
|
|
1070
|
+
return { vars, anyKey: files.some((file) => file[PIWI_ENV_KEYS.apiKey]) };
|
|
1071
|
+
}
|
|
1072
|
+
function resolveCliConnection(flags, env, dirs, command = "piwi") {
|
|
1073
|
+
const { vars: dotEnv, anyKey } = readDotEnvConnection(dirs);
|
|
1074
|
+
const mode = desktopAppMode(void 0, env[PIWI_ENV_KEYS.desktopApp]);
|
|
1075
|
+
const preferLocally = mode === "prefer" && !flags.serverUrl && !detectCi(env);
|
|
1076
|
+
const urlSet = Boolean(flags.serverUrl || env[PIWI_ENV_KEYS.serverUrl] || dotEnv[PIWI_ENV_KEYS.serverUrl]);
|
|
1077
|
+
const keySet = Boolean(flags.apiKey || env[PIWI_ENV_KEYS.apiKey] || anyKey);
|
|
1078
|
+
const readDesktop = mode !== "off" && (preferLocally || urlSet || !keySet);
|
|
1079
|
+
const desktop = readDesktop ? readDesktopConfig(env[PIWI_DESKTOP_CONFIG_ENV] || defaultDesktopConfigPath()) : null;
|
|
1080
|
+
const preferDesktop = desktop !== null && preferLocally;
|
|
1081
|
+
const connection = resolvePiwiConnection(
|
|
1082
|
+
preferDesktop ? { flags, env: projectOf(env), dotEnv: projectOf(dotEnv), desktop } : { flags, env, dotEnv, desktop }
|
|
1083
|
+
);
|
|
1084
|
+
if (connection && flags.apiKey && connection.apiKey !== flags.apiKey) {
|
|
1085
|
+
const from = !preferDesktop && dotEnv[PIWI_ENV_KEYS.serverUrl] ? "a .env file" : "the desktop app";
|
|
1086
|
+
console.error(
|
|
1087
|
+
`${command}: --api-key is not sent to ${connection.serverUrl}, which comes from ${from}: a key goes only to the URL given with it. Pass --server-url to name the instance the key is for.`
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
return connection;
|
|
1091
|
+
}
|
|
1010
1092
|
|
|
1011
1093
|
// src/cli/bug.ts
|
|
1012
1094
|
var EXIT_OK2 = 0;
|
|
@@ -1029,8 +1111,8 @@ Options:
|
|
|
1029
1111
|
--no-run With --write or --out: write it without running it
|
|
1030
1112
|
--force Replace an existing file
|
|
1031
1113
|
--run-mode The spec without test.fail(), as a reproduction runs it
|
|
1032
|
-
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL)
|
|
1033
|
-
--api-key <key> API key (env PIWI_API_KEY)
|
|
1114
|
+
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
1115
|
+
--api-key <key> API key (env PIWI_API_KEY, .env)
|
|
1034
1116
|
|
|
1035
1117
|
Exit codes: 0 printed or written (and, when run, the test behaved as its
|
|
1036
1118
|
test.fail() expects: the bug reproduces), 1 the run did not (the bug may be
|
|
@@ -1044,7 +1126,7 @@ function readOption2(argv, name) {
|
|
|
1044
1126
|
const value = argv[index + 1];
|
|
1045
1127
|
return value !== void 0 && !value.startsWith("--") ? value : void 0;
|
|
1046
1128
|
}
|
|
1047
|
-
function parseBugArgs(argv, env) {
|
|
1129
|
+
function parseBugArgs(argv, env, dirs = []) {
|
|
1048
1130
|
let idText;
|
|
1049
1131
|
for (let i = 0; i < argv.length; i++) {
|
|
1050
1132
|
const token = argv[i];
|
|
@@ -1060,8 +1142,13 @@ function parseBugArgs(argv, env) {
|
|
|
1060
1142
|
for (const flag of VALUE_FLAGS) {
|
|
1061
1143
|
if (argv.includes(flag) && readOption2(argv, flag) === void 0) throw new Error(`${flag} expects a value`);
|
|
1062
1144
|
}
|
|
1063
|
-
const
|
|
1064
|
-
|
|
1145
|
+
const connection = resolveCliConnection(
|
|
1146
|
+
{ serverUrl: readOption2(argv, "--server-url"), apiKey: readOption2(argv, "--api-key") },
|
|
1147
|
+
env,
|
|
1148
|
+
dirs,
|
|
1149
|
+
"piwi bug"
|
|
1150
|
+
);
|
|
1151
|
+
if (!connection) throw new Error("No dashboard \u2014 pass --server-url or set PIWI_DASHBOARD_URL (environment or .env)");
|
|
1065
1152
|
const out = readOption2(argv, "--out") ?? null;
|
|
1066
1153
|
const write = argv.includes("--write") || out !== null;
|
|
1067
1154
|
return {
|
|
@@ -1071,8 +1158,8 @@ function parseBugArgs(argv, env) {
|
|
|
1071
1158
|
run: write && !argv.includes("--no-run"),
|
|
1072
1159
|
force: argv.includes("--force"),
|
|
1073
1160
|
mode: argv.includes("--run-mode") ? "run" : "commit",
|
|
1074
|
-
serverUrl,
|
|
1075
|
-
apiKey:
|
|
1161
|
+
serverUrl: connection.serverUrl,
|
|
1162
|
+
apiKey: connection.apiKey
|
|
1076
1163
|
};
|
|
1077
1164
|
}
|
|
1078
1165
|
async function fetchBugSpec(args, specDir) {
|
|
@@ -1094,35 +1181,23 @@ async function fetchBugSpec(args, specDir) {
|
|
|
1094
1181
|
if (typeof body.code !== "string" || typeof body.path !== "string") throw new Error("the dashboard sent no spec");
|
|
1095
1182
|
return { code: body.code, path: body.path, warnings: Array.isArray(body.warnings) ? body.warnings : [] };
|
|
1096
1183
|
}
|
|
1097
|
-
function repositoryRoot(cwd) {
|
|
1098
|
-
try {
|
|
1099
|
-
const out = (0, import_node_child_process2.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
|
|
1100
|
-
cwd,
|
|
1101
|
-
encoding: "utf-8",
|
|
1102
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
1103
|
-
}).trim();
|
|
1104
|
-
return out ? path9.resolve(out) : null;
|
|
1105
|
-
} catch {
|
|
1106
|
-
return null;
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
1184
|
function realFolder(dir) {
|
|
1110
1185
|
const rest = [];
|
|
1111
1186
|
let current = dir;
|
|
1112
|
-
while (!
|
|
1113
|
-
rest.unshift(
|
|
1114
|
-
current =
|
|
1187
|
+
while (!fs7.existsSync(current) && path10.dirname(current) !== current) {
|
|
1188
|
+
rest.unshift(path10.basename(current));
|
|
1189
|
+
current = path10.dirname(current);
|
|
1115
1190
|
}
|
|
1116
|
-
return
|
|
1191
|
+
return path10.join(fs7.realpathSync(current), ...rest);
|
|
1117
1192
|
}
|
|
1118
1193
|
function folderInRepository(root, dir) {
|
|
1119
|
-
const relative9 =
|
|
1120
|
-
if (relative9.startsWith("..") ||
|
|
1121
|
-
return relative9.split(
|
|
1194
|
+
const relative9 = path10.relative(realFolder(root), realFolder(dir));
|
|
1195
|
+
if (relative9.startsWith("..") || path10.isAbsolute(relative9)) return void 0;
|
|
1196
|
+
return relative9.split(path10.sep).join("/");
|
|
1122
1197
|
}
|
|
1123
1198
|
function fileFilter(file) {
|
|
1124
1199
|
const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1125
|
-
return `${escape(
|
|
1200
|
+
return `${escape(path10.basename(path10.dirname(file)))}[\\\\/]${escape(path10.basename(file))}$`;
|
|
1126
1201
|
}
|
|
1127
1202
|
function runPlaywright(file, cwd, id, env) {
|
|
1128
1203
|
return spawnPlaywright([fileFilter(file)], { ...env, ...originEnv("bug", id) }, { cwd, command: "piwi bug" });
|
|
@@ -1134,18 +1209,18 @@ async function runBug(argv, env = process.env, cwd = process.cwd()) {
|
|
|
1134
1209
|
}
|
|
1135
1210
|
let args;
|
|
1136
1211
|
try {
|
|
1137
|
-
args = parseBugArgs(argv, env);
|
|
1212
|
+
args = parseBugArgs(argv, env, readOption2(argv, "--server-url") ? [] : connectionFolders(cwd));
|
|
1138
1213
|
} catch (e) {
|
|
1139
1214
|
console.error(`piwi bug: ${e.message}
|
|
1140
1215
|
|
|
1141
1216
|
${USAGE2}`);
|
|
1142
1217
|
return EXIT_ERROR2;
|
|
1143
1218
|
}
|
|
1144
|
-
const root = args.write ?
|
|
1145
|
-
const outFile = args.out !== null ?
|
|
1219
|
+
const root = args.write ? gitRoot(cwd) : null;
|
|
1220
|
+
const outFile = args.out !== null ? path10.resolve(cwd, args.out) : null;
|
|
1146
1221
|
let spec;
|
|
1147
1222
|
try {
|
|
1148
|
-
spec = await fetchBugSpec(args, outFile && root ? folderInRepository(root,
|
|
1223
|
+
spec = await fetchBugSpec(args, outFile && root ? folderInRepository(root, path10.dirname(outFile)) : void 0);
|
|
1149
1224
|
} catch (e) {
|
|
1150
1225
|
console.error(`piwi bug: ${e.message}`);
|
|
1151
1226
|
return EXIT_ERROR2;
|
|
@@ -1160,16 +1235,16 @@ ${USAGE2}`);
|
|
|
1160
1235
|
if (!outFile && !root) {
|
|
1161
1236
|
console.error("piwi bug: not in a git repository \u2014 the bugs folder is read from the working directory");
|
|
1162
1237
|
}
|
|
1163
|
-
const file = outFile ??
|
|
1238
|
+
const file = outFile ?? path10.resolve(root ?? cwd, spec.path);
|
|
1164
1239
|
try {
|
|
1165
|
-
|
|
1166
|
-
|
|
1240
|
+
fs7.mkdirSync(path10.dirname(file), { recursive: true });
|
|
1241
|
+
fs7.writeFileSync(file, spec.code, { flag: args.force ? "w" : "wx" });
|
|
1167
1242
|
} catch (e) {
|
|
1168
1243
|
const exists = e.code === "EEXIST";
|
|
1169
1244
|
console.error(`piwi bug: ${exists ? `${file} exists \u2014 pass --force to replace it` : e.message}`);
|
|
1170
1245
|
return EXIT_ERROR2;
|
|
1171
1246
|
}
|
|
1172
|
-
const shown =
|
|
1247
|
+
const shown = path10.relative(cwd, file) || file;
|
|
1173
1248
|
console.error(`piwi bug: wrote ${shown}`);
|
|
1174
1249
|
if (!args.run) return EXIT_OK2;
|
|
1175
1250
|
const code = await runPlaywright(file, cwd, args.id, env);
|
|
@@ -1187,8 +1262,8 @@ ${USAGE2}`);
|
|
|
1187
1262
|
}
|
|
1188
1263
|
|
|
1189
1264
|
// src/cli/codegen.ts
|
|
1190
|
-
var
|
|
1191
|
-
var
|
|
1265
|
+
var fs8 = __toESM(require("fs"));
|
|
1266
|
+
var path11 = __toESM(require("path"));
|
|
1192
1267
|
|
|
1193
1268
|
// ../core/src/recording.ts
|
|
1194
1269
|
var ASSERTION_MATCHERS = [
|
|
@@ -1879,6 +1954,44 @@ function locatorForStep(step, index, ctx) {
|
|
|
1879
1954
|
}
|
|
1880
1955
|
return `${ctx.page}.${chosen.text}`;
|
|
1881
1956
|
}
|
|
1957
|
+
function escapedInTemplate(code) {
|
|
1958
|
+
return code < 32 && code !== 9 || code === 127 || code === 8232 || code === 8233;
|
|
1959
|
+
}
|
|
1960
|
+
function templateText(text) {
|
|
1961
|
+
let out = "";
|
|
1962
|
+
for (let i = 0; i < text.length; i++) {
|
|
1963
|
+
const c = text[i];
|
|
1964
|
+
const code = c.charCodeAt(0);
|
|
1965
|
+
if (c === "\\" || c === "`" || c === "$" && text[i + 1] === "{") out += `\\${c}`;
|
|
1966
|
+
else if (escapedInTemplate(code)) out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
1967
|
+
else out += c;
|
|
1968
|
+
}
|
|
1969
|
+
return out;
|
|
1970
|
+
}
|
|
1971
|
+
function ariaSnapshotLiteral(snapshot, indent) {
|
|
1972
|
+
const lines = snapshot.split(/\r\n?|\n/).filter((line) => line.trim());
|
|
1973
|
+
const common = Math.min(...lines.map((line) => line.length - line.replace(/^ +/, "").length));
|
|
1974
|
+
return ["`", ...lines.map((line) => `${indent} ${templateText(line.slice(common).trimEnd())}`), `${indent}\``];
|
|
1975
|
+
}
|
|
1976
|
+
function renderAriaSnapshotStep(step, index, ctx, not) {
|
|
1977
|
+
const snapshot = step.assertion?.expected ?? "";
|
|
1978
|
+
if (!snapshot.trim()) {
|
|
1979
|
+
ctx.warnings.push({
|
|
1980
|
+
step: index,
|
|
1981
|
+
code: "incomplete-assertion",
|
|
1982
|
+
detail: "toMatchAriaSnapshot",
|
|
1983
|
+
message: "toMatchAriaSnapshot needs the ARIA snapshot to match."
|
|
1984
|
+
});
|
|
1985
|
+
return [` // Step ${index + 1}: toMatchAriaSnapshot without a snapshot.`];
|
|
1986
|
+
}
|
|
1987
|
+
const [open, ...rest] = ariaSnapshotLiteral(snapshot, " ");
|
|
1988
|
+
const close = rest.pop();
|
|
1989
|
+
return [
|
|
1990
|
+
` await expect(${locatorForStep(step, index, ctx)})${not}.toMatchAriaSnapshot(${open}`,
|
|
1991
|
+
...rest,
|
|
1992
|
+
`${close});`
|
|
1993
|
+
];
|
|
1994
|
+
}
|
|
1882
1995
|
function renderAssertStep(step, index, ctx) {
|
|
1883
1996
|
const assertion = step.action === "assertVisible" ? null : step.assertion;
|
|
1884
1997
|
if (step.action === "assert" && !assertion) {
|
|
@@ -1889,6 +2002,7 @@ function renderAssertStep(step, index, ctx) {
|
|
|
1889
2002
|
const not = assertion?.negated ? ".not" : "";
|
|
1890
2003
|
const lines = [];
|
|
1891
2004
|
if (assertion?.note) lines.push(` // ${commentText(assertion.note)}`);
|
|
2005
|
+
if (matcher === "toMatchAriaSnapshot") return [...lines, ...renderAriaSnapshotStep(step, index, ctx, not)];
|
|
1892
2006
|
let arg = "";
|
|
1893
2007
|
if (VALUE_MATCHERS.has(matcher)) {
|
|
1894
2008
|
if (assertion?.expected == null) {
|
|
@@ -2779,9 +2893,9 @@ Code:
|
|
|
2779
2893
|
--tag <tag> Add a tag; repeat for more
|
|
2780
2894
|
|
|
2781
2895
|
Project (optional: your function catalog, and the locators your tests already use):
|
|
2782
|
-
--project <name|id> Project (env PIWI_PROJECT_NAME)
|
|
2783
|
-
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL)
|
|
2784
|
-
--api-key <key> API key (env PIWI_API_KEY)
|
|
2896
|
+
--project <name|id> Project (env PIWI_PROJECT_NAME, .env)
|
|
2897
|
+
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
2898
|
+
--api-key <key> API key (env PIWI_API_KEY, .env)
|
|
2785
2899
|
--offline Do not contact the dashboard
|
|
2786
2900
|
|
|
2787
2901
|
Exit codes: 0 ok, 2 the steps could not be read or the spec could not be written.
|
|
@@ -2821,12 +2935,22 @@ function findFile(argv) {
|
|
|
2821
2935
|
}
|
|
2822
2936
|
return void 0;
|
|
2823
2937
|
}
|
|
2824
|
-
function parseCodegenArgs(argv, env) {
|
|
2938
|
+
function parseCodegenArgs(argv, env, dirs = []) {
|
|
2825
2939
|
const file = findFile(argv);
|
|
2826
2940
|
if (!file) throw new Error("No steps file \u2014 usage: piwi codegen <steps.json>");
|
|
2827
2941
|
for (const flag of VALUE_FLAGS2) {
|
|
2828
2942
|
if (argv.includes(flag) && readOption3(argv, flag) === void 0) throw new Error(`${flag} expects a value`);
|
|
2829
2943
|
}
|
|
2944
|
+
const connection = resolveCliConnection(
|
|
2945
|
+
{
|
|
2946
|
+
serverUrl: readOption3(argv, "--server-url"),
|
|
2947
|
+
apiKey: readOption3(argv, "--api-key"),
|
|
2948
|
+
project: readOption3(argv, "--project")
|
|
2949
|
+
},
|
|
2950
|
+
env,
|
|
2951
|
+
dirs,
|
|
2952
|
+
"piwi codegen"
|
|
2953
|
+
);
|
|
2830
2954
|
return {
|
|
2831
2955
|
file,
|
|
2832
2956
|
out: readOption3(argv, "--out") ?? null,
|
|
@@ -2840,9 +2964,9 @@ function parseCodegenArgs(argv, env) {
|
|
|
2840
2964
|
fail: argv.includes("--fail") || argv.includes("--fail-reason"),
|
|
2841
2965
|
failReason: readOption3(argv, "--fail-reason") ?? null,
|
|
2842
2966
|
tags: readAll2(argv, "--tag"),
|
|
2843
|
-
project: readOption3(argv, "--project") ?? env.PIWI_PROJECT_NAME ?? "",
|
|
2844
|
-
serverUrl:
|
|
2845
|
-
apiKey:
|
|
2967
|
+
project: connection?.project ?? readOption3(argv, "--project") ?? env.PIWI_PROJECT_NAME ?? "",
|
|
2968
|
+
serverUrl: connection?.serverUrl ?? "",
|
|
2969
|
+
apiKey: connection?.apiKey ?? null,
|
|
2846
2970
|
offline: argv.includes("--offline")
|
|
2847
2971
|
};
|
|
2848
2972
|
}
|
|
@@ -2901,14 +3025,15 @@ async function loadProjectContext(args) {
|
|
|
2901
3025
|
);
|
|
2902
3026
|
return { catalog, suiteLocators };
|
|
2903
3027
|
}
|
|
2904
|
-
async function runCodegen(argv, env = process.env) {
|
|
3028
|
+
async function runCodegen(argv, env = process.env, cwd = process.cwd()) {
|
|
2905
3029
|
if (argv.includes("-h") || argv.includes("--help")) {
|
|
2906
3030
|
console.log(USAGE3);
|
|
2907
3031
|
return EXIT_OK3;
|
|
2908
3032
|
}
|
|
2909
3033
|
let args;
|
|
2910
3034
|
try {
|
|
2911
|
-
|
|
3035
|
+
const given = readOption3(argv, "--server-url") && (readOption3(argv, "--project") || env.PIWI_PROJECT_NAME);
|
|
3036
|
+
args = parseCodegenArgs(argv, env, given ? [] : connectionFolders(cwd));
|
|
2912
3037
|
} catch (e) {
|
|
2913
3038
|
console.error(`piwi codegen: ${e.message}
|
|
2914
3039
|
|
|
@@ -2920,7 +3045,7 @@ ${USAGE3}`);
|
|
|
2920
3045
|
if (bugId) {
|
|
2921
3046
|
if (!args.serverUrl) {
|
|
2922
3047
|
console.error(
|
|
2923
|
-
"piwi codegen: bug:<id> reads the report from the dashboard \u2014 pass --server-url or set PIWI_DASHBOARD_URL"
|
|
3048
|
+
"piwi codegen: bug:<id> reads the report from the dashboard \u2014 pass --server-url or set PIWI_DASHBOARD_URL (environment or .env)"
|
|
2924
3049
|
);
|
|
2925
3050
|
return EXIT_ERROR3;
|
|
2926
3051
|
}
|
|
@@ -2933,8 +3058,8 @@ ${USAGE3}`);
|
|
|
2933
3058
|
}
|
|
2934
3059
|
} else {
|
|
2935
3060
|
try {
|
|
2936
|
-
if (
|
|
2937
|
-
text =
|
|
3061
|
+
if (fs8.statSync(args.file).size > MAX_STEPS_FILE_BYTES) throw new Error("is too large to be a steps file");
|
|
3062
|
+
text = fs8.readFileSync(args.file, "utf-8");
|
|
2938
3063
|
} catch (e) {
|
|
2939
3064
|
const reason = e.code === "ENOENT" ? "does not exist" : e.message;
|
|
2940
3065
|
console.error(`piwi codegen: ${args.file} ${reason}`);
|
|
@@ -2966,8 +3091,8 @@ ${USAGE3}`);
|
|
|
2966
3091
|
return EXIT_OK3;
|
|
2967
3092
|
}
|
|
2968
3093
|
try {
|
|
2969
|
-
|
|
2970
|
-
|
|
3094
|
+
fs8.mkdirSync(path11.dirname(args.out), { recursive: true });
|
|
3095
|
+
fs8.writeFileSync(args.out, result.code, { flag: args.force ? "w" : "wx" });
|
|
2971
3096
|
} catch (e) {
|
|
2972
3097
|
const exists = e.code === "EEXIST";
|
|
2973
3098
|
console.error(`piwi codegen: ${exists ? `${args.out} exists \u2014 pass --force to replace it` : e.message}`);
|
|
@@ -2981,7 +3106,7 @@ ${USAGE3}`);
|
|
|
2981
3106
|
}
|
|
2982
3107
|
|
|
2983
3108
|
// src/cli/coverage.ts
|
|
2984
|
-
var
|
|
3109
|
+
var path16 = __toESM(require("path"));
|
|
2985
3110
|
|
|
2986
3111
|
// ../core/src/codeowners.ts
|
|
2987
3112
|
function patternParts(pattern) {
|
|
@@ -3105,7 +3230,7 @@ function errorMessage(error) {
|
|
|
3105
3230
|
}
|
|
3106
3231
|
|
|
3107
3232
|
// src/internal/support/test-paths.ts
|
|
3108
|
-
var
|
|
3233
|
+
var path12 = __toESM(require("path"));
|
|
3109
3234
|
|
|
3110
3235
|
// src/internal/collect/metadata-collector.ts
|
|
3111
3236
|
var DETACHED_HEAD = "HEAD";
|
|
@@ -3138,23 +3263,23 @@ function normalizeRef(ref) {
|
|
|
3138
3263
|
|
|
3139
3264
|
// src/internal/coverage-upload/git-inventory.ts
|
|
3140
3265
|
var import_node_child_process5 = require("child_process");
|
|
3141
|
-
var
|
|
3142
|
-
var
|
|
3266
|
+
var fs10 = __toESM(require("fs"));
|
|
3267
|
+
var path14 = __toESM(require("path"));
|
|
3143
3268
|
|
|
3144
3269
|
// src/internal/collect/blob-ids.ts
|
|
3145
3270
|
var import_node_child_process4 = require("child_process");
|
|
3146
3271
|
var crypto2 = __toESM(require("crypto"));
|
|
3147
|
-
var
|
|
3148
|
-
var
|
|
3272
|
+
var fs9 = __toESM(require("fs"));
|
|
3273
|
+
var path13 = __toESM(require("path"));
|
|
3149
3274
|
function gitHashObjects(root, paths) {
|
|
3150
|
-
return new Promise((
|
|
3275
|
+
return new Promise((resolve11) => {
|
|
3151
3276
|
const child = (0, import_node_child_process4.execFile)(
|
|
3152
3277
|
"git",
|
|
3153
3278
|
["hash-object", "--stdin-paths"],
|
|
3154
3279
|
{ cwd: root, encoding: "utf8", timeout: 3e4, windowsHide: true },
|
|
3155
3280
|
(error, stdout) => {
|
|
3156
3281
|
const ids = error ? [] : stdout.split("\n").filter(Boolean);
|
|
3157
|
-
|
|
3282
|
+
resolve11(ids.length === paths.length ? ids : null);
|
|
3158
3283
|
}
|
|
3159
3284
|
);
|
|
3160
3285
|
child.stdin?.on("error", () => {
|
|
@@ -3168,12 +3293,12 @@ function gitHashObjects(root, paths) {
|
|
|
3168
3293
|
var MAX_GIT_OUTPUT = 512 * 1024 * 1024;
|
|
3169
3294
|
var GIT_BATCH = 500;
|
|
3170
3295
|
function git(cwd, args) {
|
|
3171
|
-
return new Promise((
|
|
3296
|
+
return new Promise((resolve11, reject) => {
|
|
3172
3297
|
(0, import_node_child_process5.execFile)(
|
|
3173
3298
|
"git",
|
|
3174
3299
|
args,
|
|
3175
3300
|
{ cwd, encoding: "utf8", maxBuffer: MAX_GIT_OUTPUT, timeout: 12e4, windowsHide: true },
|
|
3176
|
-
(error, stdout) => error ? reject(error) :
|
|
3301
|
+
(error, stdout) => error ? reject(error) : resolve11(stdout)
|
|
3177
3302
|
);
|
|
3178
3303
|
});
|
|
3179
3304
|
}
|
|
@@ -3212,7 +3337,7 @@ async function readInventory(root, warn = () => {
|
|
|
3212
3337
|
const changed = [];
|
|
3213
3338
|
for (const file of (await git(root, ["diff", "--name-only", "-z", "HEAD"]).catch(() => "")).split("\0")) {
|
|
3214
3339
|
if (!file || !inventory.has(file)) continue;
|
|
3215
|
-
if (
|
|
3340
|
+
if (fs10.existsSync(path14.join(root, file))) changed.push(file);
|
|
3216
3341
|
else inventory.delete(file);
|
|
3217
3342
|
}
|
|
3218
3343
|
const ids = await hashPaths(root, changed);
|
|
@@ -3231,8 +3356,8 @@ async function readInventory(root, warn = () => {
|
|
|
3231
3356
|
}
|
|
3232
3357
|
|
|
3233
3358
|
// src/internal/coverage-upload/report-files.ts
|
|
3234
|
-
var
|
|
3235
|
-
var
|
|
3359
|
+
var fs11 = __toESM(require("fs"));
|
|
3360
|
+
var path15 = __toESM(require("path"));
|
|
3236
3361
|
|
|
3237
3362
|
// ../core/src/coverage.ts
|
|
3238
3363
|
var MAX_LINE = 1e6;
|
|
@@ -3744,14 +3869,14 @@ function globSegmentRegExp(segment) {
|
|
|
3744
3869
|
function expandPattern(pattern, cwd) {
|
|
3745
3870
|
const normalized = pattern.replace(/\\/g, "/");
|
|
3746
3871
|
if (!/[*?]/.test(normalized)) {
|
|
3747
|
-
const file =
|
|
3748
|
-
return
|
|
3872
|
+
const file = path15.resolve(cwd, normalized);
|
|
3873
|
+
return fs11.existsSync(file) && fs11.statSync(file).isFile() ? [file] : [];
|
|
3749
3874
|
}
|
|
3750
|
-
const absolute =
|
|
3875
|
+
const absolute = path15.isAbsolute(normalized) || /^[A-Za-z]:\//.test(normalized);
|
|
3751
3876
|
const segments = normalized.split("/");
|
|
3752
3877
|
const firstWild = segments.findIndex((s) => /[*?]/.test(s));
|
|
3753
3878
|
const baseParts = segments.slice(0, firstWild);
|
|
3754
|
-
const base = absolute ? baseParts.join("/") || "/" :
|
|
3879
|
+
const base = absolute ? baseParts.join("/") || "/" : path15.resolve(cwd, ...baseParts);
|
|
3755
3880
|
const rest = segments.slice(firstWild).filter((s) => s !== "");
|
|
3756
3881
|
const out = [];
|
|
3757
3882
|
const walk = (dir, at) => {
|
|
@@ -3759,14 +3884,14 @@ function expandPattern(pattern, cwd) {
|
|
|
3759
3884
|
const segment = rest[at];
|
|
3760
3885
|
let entries;
|
|
3761
3886
|
try {
|
|
3762
|
-
entries =
|
|
3887
|
+
entries = fs11.readdirSync(dir, { withFileTypes: true });
|
|
3763
3888
|
} catch {
|
|
3764
3889
|
return;
|
|
3765
3890
|
}
|
|
3766
3891
|
if (segment === "**") {
|
|
3767
3892
|
walk(dir, at + 1);
|
|
3768
3893
|
for (const entry of entries) {
|
|
3769
|
-
if (entry.isDirectory() && !SKIPPED_DIRS.has(entry.name)) walk(
|
|
3894
|
+
if (entry.isDirectory() && !SKIPPED_DIRS.has(entry.name)) walk(path15.join(dir, entry.name), at);
|
|
3770
3895
|
}
|
|
3771
3896
|
return;
|
|
3772
3897
|
}
|
|
@@ -3774,7 +3899,7 @@ function expandPattern(pattern, cwd) {
|
|
|
3774
3899
|
const last = at === rest.length - 1;
|
|
3775
3900
|
for (const entry of entries) {
|
|
3776
3901
|
if (!match.test(entry.name)) continue;
|
|
3777
|
-
const full =
|
|
3902
|
+
const full = path15.join(dir, entry.name);
|
|
3778
3903
|
if (last && entry.isFile()) out.push(full);
|
|
3779
3904
|
else if (!last && entry.isDirectory() && (!SKIPPED_DIRS.has(entry.name) || segment === entry.name)) {
|
|
3780
3905
|
walk(full, at + 1);
|
|
@@ -3787,7 +3912,7 @@ function expandPattern(pattern, cwd) {
|
|
|
3787
3912
|
async function readReport(file, format) {
|
|
3788
3913
|
let reader = format ? new CoverageReportReader(format) : null;
|
|
3789
3914
|
let head = "";
|
|
3790
|
-
for await (const chunk of
|
|
3915
|
+
for await (const chunk of fs11.createReadStream(file, { encoding: "utf8", highWaterMark: 1024 * 1024 })) {
|
|
3791
3916
|
if (!reader) {
|
|
3792
3917
|
head += chunk;
|
|
3793
3918
|
if (head.length < 4096) continue;
|
|
@@ -3809,18 +3934,18 @@ async function readReport(file, format) {
|
|
|
3809
3934
|
return reader.end();
|
|
3810
3935
|
}
|
|
3811
3936
|
function reportBases(report, reportFile, options) {
|
|
3812
|
-
if (options.root) return [
|
|
3813
|
-
const dir =
|
|
3937
|
+
if (options.root) return [path15.resolve(options.cwd, options.root)];
|
|
3938
|
+
const dir = path15.dirname(path15.resolve(reportFile));
|
|
3814
3939
|
const sources = report.sources.map(
|
|
3815
|
-
(source) =>
|
|
3940
|
+
(source) => path15.isAbsolute(source) || /^[A-Za-z]:[\\/]/.test(source) ? source : path15.resolve(dir, source)
|
|
3816
3941
|
);
|
|
3817
|
-
return [...sources, options.cwd, dir,
|
|
3942
|
+
return [...sources, options.cwd, dir, path15.dirname(dir), options.repoRoot];
|
|
3818
3943
|
}
|
|
3819
3944
|
function resolveReport(report, reportFile, options, into) {
|
|
3820
3945
|
const bases = reportBases(report, reportFile, options);
|
|
3821
3946
|
for (const file of report.files) {
|
|
3822
3947
|
const candidates = reportPathCandidates(file.path, { repoRoot: options.repoRoot, bases });
|
|
3823
|
-
const match = candidates.find((candidate) => options.inventory.has(candidate)) ?? candidates.find((candidate) =>
|
|
3948
|
+
const match = candidates.find((candidate) => options.inventory.has(candidate)) ?? candidates.find((candidate) => fs11.existsSync(path15.join(options.repoRoot, candidate)));
|
|
3824
3949
|
if (!match) {
|
|
3825
3950
|
into.unmatched.push(file.path);
|
|
3826
3951
|
continue;
|
|
@@ -3860,9 +3985,9 @@ Suite:
|
|
|
3860
3985
|
--kind <kind> unit (the default) or e2e
|
|
3861
3986
|
|
|
3862
3987
|
Connection:
|
|
3863
|
-
--project <name|id> Project (env PIWI_PROJECT_NAME)
|
|
3864
|
-
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL)
|
|
3865
|
-
--api-key <key> API key (env PIWI_API_KEY)
|
|
3988
|
+
--project <name|id> Project (env PIWI_PROJECT_NAME, .env)
|
|
3989
|
+
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
3990
|
+
--api-key <key> API key (env PIWI_API_KEY, .env)
|
|
3866
3991
|
--branch <name> The branch (env PIWI_BRANCH, the CI's, else git's)
|
|
3867
3992
|
|
|
3868
3993
|
Other:
|
|
@@ -3882,7 +4007,7 @@ var VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
|
3882
4007
|
"--api-key",
|
|
3883
4008
|
"--branch"
|
|
3884
4009
|
]);
|
|
3885
|
-
function parseCoverageUploadArgs(argv, env) {
|
|
4010
|
+
function parseCoverageUploadArgs(argv, env, dirs = []) {
|
|
3886
4011
|
const values = /* @__PURE__ */ new Map();
|
|
3887
4012
|
const patterns2 = [];
|
|
3888
4013
|
const flags = /* @__PURE__ */ new Set();
|
|
@@ -3913,11 +4038,19 @@ function parseCoverageUploadArgs(argv, env) {
|
|
|
3913
4038
|
if (format !== void 0 && format !== "cobertura" && format !== "lcov" && format !== "piwi") {
|
|
3914
4039
|
throw new Error("--format is cobertura, lcov or piwi");
|
|
3915
4040
|
}
|
|
3916
|
-
const
|
|
4041
|
+
const connection = resolveCliConnection(
|
|
4042
|
+
{ serverUrl: values.get("--server-url"), apiKey: values.get("--api-key"), project: values.get("--project") },
|
|
4043
|
+
env,
|
|
4044
|
+
dirs,
|
|
4045
|
+
"piwi coverage upload"
|
|
4046
|
+
);
|
|
4047
|
+
const serverUrl = connection?.serverUrl ?? "";
|
|
3917
4048
|
const dryRun = flags.has("--dry-run");
|
|
3918
|
-
if (!serverUrl && !dryRun)
|
|
3919
|
-
|
|
3920
|
-
|
|
4049
|
+
if (!serverUrl && !dryRun)
|
|
4050
|
+
throw new Error("No dashboard URL \u2014 pass --server-url or set PIWI_DASHBOARD_URL (environment or .env)");
|
|
4051
|
+
const project = connection?.project ?? values.get("--project") ?? env.PIWI_PROJECT_NAME ?? "";
|
|
4052
|
+
if (!project && !dryRun)
|
|
4053
|
+
throw new Error("No project \u2014 pass --project or set PIWI_PROJECT_NAME (environment or .env)");
|
|
3921
4054
|
return {
|
|
3922
4055
|
patterns: patterns2,
|
|
3923
4056
|
suite,
|
|
@@ -3926,7 +4059,7 @@ function parseCoverageUploadArgs(argv, env) {
|
|
|
3926
4059
|
...values.get("--root") ? { root: values.get("--root") } : {},
|
|
3927
4060
|
project,
|
|
3928
4061
|
serverUrl,
|
|
3929
|
-
apiKey:
|
|
4062
|
+
apiKey: connection?.apiKey ?? null,
|
|
3930
4063
|
...values.get("--branch") ? { branch: values.get("--branch") } : {},
|
|
3931
4064
|
dryRun,
|
|
3932
4065
|
json: flags.has("--json")
|
|
@@ -4045,7 +4178,7 @@ async function uploadCoverage(args, env, cwd, warn = warnOnStderr) {
|
|
|
4045
4178
|
commit: checkout.commit,
|
|
4046
4179
|
branch,
|
|
4047
4180
|
suite: args.suite,
|
|
4048
|
-
reports: reports.map((report) =>
|
|
4181
|
+
reports: reports.map((report) => path16.relative(cwd, report) || report),
|
|
4049
4182
|
files: files.length,
|
|
4050
4183
|
unmatched: resolved.unmatched.length,
|
|
4051
4184
|
outOfScope: resolved.outOfScope,
|
|
@@ -4092,7 +4225,7 @@ ${USAGE4}`);
|
|
|
4092
4225
|
}
|
|
4093
4226
|
let args;
|
|
4094
4227
|
try {
|
|
4095
|
-
args = parseCoverageUploadArgs(rest, env);
|
|
4228
|
+
args = parseCoverageUploadArgs(rest, env, [cwd]);
|
|
4096
4229
|
} catch (error) {
|
|
4097
4230
|
console.error(`piwi coverage upload: ${errorMessage(error)}`);
|
|
4098
4231
|
return EXIT_ERROR4;
|
|
@@ -4115,7 +4248,7 @@ ${USAGE4}`);
|
|
|
4115
4248
|
|
|
4116
4249
|
// src/cli/flake.ts
|
|
4117
4250
|
var import_node_child_process6 = require("child_process");
|
|
4118
|
-
var
|
|
4251
|
+
var fs12 = __toESM(require("fs"));
|
|
4119
4252
|
var os2 = __toESM(require("os"));
|
|
4120
4253
|
var path17 = __toESM(require("path"));
|
|
4121
4254
|
|
|
@@ -4318,19 +4451,6 @@ function withinBudget(startedAt, now, budgetMs) {
|
|
|
4318
4451
|
return now - startedAt < budgetMs;
|
|
4319
4452
|
}
|
|
4320
4453
|
|
|
4321
|
-
// src/internal/support/connection.ts
|
|
4322
|
-
var path16 = __toESM(require("path"));
|
|
4323
|
-
function resolveCliConnection(flags, env, dirs) {
|
|
4324
|
-
let dotEnv = {};
|
|
4325
|
-
for (const dir of dirs) dotEnv = { ...readDotEnv(path16.join(dir, ".env")), ...dotEnv };
|
|
4326
|
-
return resolvePiwiConnection({
|
|
4327
|
-
flags,
|
|
4328
|
-
env,
|
|
4329
|
-
dotEnv,
|
|
4330
|
-
desktop: readDesktopConfig(env[PIWI_DESKTOP_CONFIG_ENV] || defaultDesktopConfigPath())
|
|
4331
|
-
});
|
|
4332
|
-
}
|
|
4333
|
-
|
|
4334
4454
|
// src/cli/flake.ts
|
|
4335
4455
|
var USAGE5 = `
|
|
4336
4456
|
piwi flake \u2014 make a flaky test fail on demand, then prove the fix
|
|
@@ -4373,7 +4493,7 @@ Options:
|
|
|
4373
4493
|
--json Print the results as JSON instead of text
|
|
4374
4494
|
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
4375
4495
|
--api-key <key> Reporter API key (env PIWI_API_KEY, .env)
|
|
4376
|
-
--project <name|id> The Piwi project, for a file:line test (env PIWI_PROJECT_NAME)
|
|
4496
|
+
--project <name|id> The Piwi project, for a file:line test (env PIWI_PROJECT_NAME, .env)
|
|
4377
4497
|
|
|
4378
4498
|
Exit codes: 0 an arm reproduced the failure (verify: the fix held),
|
|
4379
4499
|
1 nothing reproduced (verify: it still fails, or too few runs),
|
|
@@ -4521,8 +4641,8 @@ function playwrightRunner(dir, log) {
|
|
|
4521
4641
|
now: () => Date.now(),
|
|
4522
4642
|
async runBatch(plan, arm, repeat, batch) {
|
|
4523
4643
|
const planFile = path17.join(dir, `${arm.id}.plan.json`);
|
|
4524
|
-
if (!
|
|
4525
|
-
|
|
4644
|
+
if (!fs12.existsSync(planFile)) {
|
|
4645
|
+
fs12.writeFileSync(
|
|
4526
4646
|
planFile,
|
|
4527
4647
|
JSON.stringify({
|
|
4528
4648
|
version: FLAKE_PLAN_VERSION,
|
|
@@ -4534,14 +4654,14 @@ function playwrightRunner(dir, log) {
|
|
|
4534
4654
|
);
|
|
4535
4655
|
}
|
|
4536
4656
|
const resultsFile = path17.join(dir, `${arm.id}.${batch}.jsonl`);
|
|
4537
|
-
|
|
4657
|
+
fs12.writeFileSync(resultsFile, "");
|
|
4538
4658
|
const env = {
|
|
4539
4659
|
...process.env,
|
|
4540
4660
|
[PIWI_FLAKE_ENV.plan]: planFile,
|
|
4541
4661
|
[PIWI_FLAKE_ENV.results]: resultsFile
|
|
4542
4662
|
};
|
|
4543
4663
|
delete env[PIWI_PROBE_ENV.flag];
|
|
4544
|
-
|
|
4664
|
+
fs12.appendFileSync(log, `
|
|
4545
4665
|
\u2500\u2500 ${arm.id} batch ${batch} \u2500\u2500
|
|
4546
4666
|
`);
|
|
4547
4667
|
await spawnPlaywright(playwrightArgs(plan.test, arm, repeat), env, {
|
|
@@ -4554,7 +4674,7 @@ function playwrightRunner(dir, log) {
|
|
|
4554
4674
|
}
|
|
4555
4675
|
function readResults(file) {
|
|
4556
4676
|
const lines = [];
|
|
4557
|
-
for (const line of
|
|
4677
|
+
for (const line of fs12.readFileSync(file, "utf8").split("\n")) {
|
|
4558
4678
|
if (!line.trim()) continue;
|
|
4559
4679
|
try {
|
|
4560
4680
|
lines.push(JSON.parse(line));
|
|
@@ -4718,7 +4838,7 @@ function printHeader(plan, commit, estimate, budgetMs) {
|
|
|
4718
4838
|
async function loadPlan(args, commit) {
|
|
4719
4839
|
if (args.planFile) {
|
|
4720
4840
|
try {
|
|
4721
|
-
const raw = JSON.parse(
|
|
4841
|
+
const raw = JSON.parse(fs12.readFileSync(args.planFile, "utf8"));
|
|
4722
4842
|
const plan = "plan" in raw && raw.plan ? raw.plan : raw;
|
|
4723
4843
|
if (plan.version !== 1 || !plan.control || !Array.isArray(plan.arms)) throw new Error("not a flake-lab plan");
|
|
4724
4844
|
if (args.verify !== (plan.kind === "verify")) {
|
|
@@ -4733,7 +4853,8 @@ async function loadPlan(args, commit) {
|
|
|
4733
4853
|
const connection = resolveCliConnection(
|
|
4734
4854
|
{ serverUrl: args.serverUrl, apiKey: args.apiKey, project: args.project },
|
|
4735
4855
|
process.env,
|
|
4736
|
-
[process.cwd()]
|
|
4856
|
+
[process.cwd()],
|
|
4857
|
+
args.verify ? "piwi flake verify" : "piwi flake"
|
|
4737
4858
|
);
|
|
4738
4859
|
if (!connection) {
|
|
4739
4860
|
throw new FlakeError(
|
|
@@ -4812,7 +4933,7 @@ async function runFlake(argv, runner) {
|
|
|
4812
4933
|
if (!args.json) {
|
|
4813
4934
|
printHeader(plan, commit, estimate, args.budgetMs);
|
|
4814
4935
|
}
|
|
4815
|
-
const dir =
|
|
4936
|
+
const dir = fs12.mkdtempSync(path17.join(os2.tmpdir(), "piwi-flake-"));
|
|
4816
4937
|
const log = path17.join(dir, "playwright.log");
|
|
4817
4938
|
let session;
|
|
4818
4939
|
try {
|
|
@@ -4821,8 +4942,8 @@ async function runFlake(argv, runner) {
|
|
|
4821
4942
|
});
|
|
4822
4943
|
} catch (error) {
|
|
4823
4944
|
console.error(`piwi flake: ${error.message}`);
|
|
4824
|
-
if (
|
|
4825
|
-
const tail =
|
|
4945
|
+
if (fs12.existsSync(log)) {
|
|
4946
|
+
const tail = fs12.readFileSync(log, "utf8").trimEnd().split("\n").slice(-LOG_TAIL_LINES).join("\n");
|
|
4826
4947
|
console.error(`
|
|
4827
4948
|
Playwright's output (${log}):
|
|
4828
4949
|
${tail}`);
|
|
@@ -4895,7 +5016,7 @@ ${tail}`);
|
|
|
4895
5016
|
verifyCommand: plan.kind === "reproduce" && verdict === "reproduced" ? verifyCommand : null,
|
|
4896
5017
|
exitCode: code
|
|
4897
5018
|
};
|
|
4898
|
-
|
|
5019
|
+
fs12.rmSync(dir, { recursive: true, force: true });
|
|
4899
5020
|
if (args.json) {
|
|
4900
5021
|
console.log(JSON.stringify(report, null, 2));
|
|
4901
5022
|
return code;
|
|
@@ -4910,7 +5031,7 @@ ${tail}`);
|
|
|
4910
5031
|
}
|
|
4911
5032
|
|
|
4912
5033
|
// src/cli/gate.ts
|
|
4913
|
-
var
|
|
5034
|
+
var fs13 = __toESM(require("fs"));
|
|
4914
5035
|
|
|
4915
5036
|
// ../core/src/gate.ts
|
|
4916
5037
|
function formatGateResult(result) {
|
|
@@ -4956,8 +5077,8 @@ Where the run comes from (first match wins):
|
|
|
4956
5077
|
(default) ./piwi-run.json, if it exists
|
|
4957
5078
|
|
|
4958
5079
|
Connection:
|
|
4959
|
-
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL)
|
|
4960
|
-
--api-key <key> API key (env PIWI_API_KEY)
|
|
5080
|
+
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
5081
|
+
--api-key <key> API key (env PIWI_API_KEY, .env)
|
|
4961
5082
|
|
|
4962
5083
|
Policy (at least one rule or warn-only flag is required):
|
|
4963
5084
|
--require-tag <tags> Comma-separated; every test carrying the tag must pass
|
|
@@ -5006,16 +5127,22 @@ function readCount(argv, name) {
|
|
|
5006
5127
|
}
|
|
5007
5128
|
function readRunIdFromFile(path25) {
|
|
5008
5129
|
try {
|
|
5009
|
-
const parsed = JSON.parse(
|
|
5130
|
+
const parsed = JSON.parse(fs13.readFileSync(path25, "utf-8"));
|
|
5010
5131
|
const runId = Number(parsed.runId);
|
|
5011
5132
|
return Number.isFinite(runId) && runId > 0 ? runId : null;
|
|
5012
5133
|
} catch {
|
|
5013
5134
|
return null;
|
|
5014
5135
|
}
|
|
5015
5136
|
}
|
|
5016
|
-
function parseGateArgs(argv, env) {
|
|
5017
|
-
const
|
|
5018
|
-
|
|
5137
|
+
function parseGateArgs(argv, env, dirs = []) {
|
|
5138
|
+
const connection = resolveCliConnection(
|
|
5139
|
+
{ serverUrl: readOption4(argv, "--server-url"), apiKey: readOption4(argv, "--api-key") },
|
|
5140
|
+
env,
|
|
5141
|
+
dirs,
|
|
5142
|
+
"piwi gate"
|
|
5143
|
+
);
|
|
5144
|
+
if (!connection)
|
|
5145
|
+
throw new Error("No dashboard URL \u2014 pass --server-url or set PIWI_DASHBOARD_URL (environment or .env)");
|
|
5019
5146
|
let runId = Number(readOption4(argv, "--run-id") ?? NaN);
|
|
5020
5147
|
if (!Number.isFinite(runId) || runId <= 0) {
|
|
5021
5148
|
const candidate = readOption4(argv, "--from-file") ?? env.PIWI_OUTPUT_FILE ?? "piwi-run.json";
|
|
@@ -5040,8 +5167,8 @@ function parseGateArgs(argv, env) {
|
|
|
5040
5167
|
maxNewLeaks: readCount(argv, "--max-new-leaks")
|
|
5041
5168
|
};
|
|
5042
5169
|
return {
|
|
5043
|
-
serverUrl,
|
|
5044
|
-
apiKey:
|
|
5170
|
+
serverUrl: connection.serverUrl,
|
|
5171
|
+
apiKey: connection.apiKey,
|
|
5045
5172
|
runId,
|
|
5046
5173
|
policy,
|
|
5047
5174
|
maxUncoveredChanges: readCount(argv, "--max-uncovered-changes"),
|
|
@@ -5066,14 +5193,14 @@ async function requestGate(args) {
|
|
|
5066
5193
|
}
|
|
5067
5194
|
return await res.json();
|
|
5068
5195
|
}
|
|
5069
|
-
async function runGate(argv, env = process.env) {
|
|
5196
|
+
async function runGate(argv, env = process.env, cwd = process.cwd()) {
|
|
5070
5197
|
if (argv.includes("-h") || argv.includes("--help")) {
|
|
5071
5198
|
console.log(USAGE6);
|
|
5072
5199
|
return EXIT_OK5;
|
|
5073
5200
|
}
|
|
5074
5201
|
let args;
|
|
5075
5202
|
try {
|
|
5076
|
-
args = parseGateArgs(argv, env);
|
|
5203
|
+
args = parseGateArgs(argv, env, [cwd]);
|
|
5077
5204
|
} catch (e) {
|
|
5078
5205
|
console.error(`piwi gate: ${e.message}
|
|
5079
5206
|
`);
|
|
@@ -5097,7 +5224,7 @@ async function runGate(argv, env = process.env) {
|
|
|
5097
5224
|
}
|
|
5098
5225
|
|
|
5099
5226
|
// src/cli/init.ts
|
|
5100
|
-
var
|
|
5227
|
+
var fs16 = __toESM(require("fs"));
|
|
5101
5228
|
var path20 = __toESM(require("path"));
|
|
5102
5229
|
|
|
5103
5230
|
// src/cli/report.ts
|
|
@@ -5115,9 +5242,9 @@ function formatStep(result) {
|
|
|
5115
5242
|
}
|
|
5116
5243
|
|
|
5117
5244
|
// src/cli/detect.ts
|
|
5118
|
-
var
|
|
5245
|
+
var fs14 = __toESM(require("fs"));
|
|
5119
5246
|
var path18 = __toESM(require("path"));
|
|
5120
|
-
var
|
|
5247
|
+
var CONFIG_NAMES2 = [
|
|
5121
5248
|
"playwright.config.ts",
|
|
5122
5249
|
"playwright.config.mts",
|
|
5123
5250
|
"playwright.config.cts",
|
|
@@ -5136,7 +5263,7 @@ var REPORTER_PACKAGE = "@piwitests/reporter";
|
|
|
5136
5263
|
function readPackageJson(root) {
|
|
5137
5264
|
const pkgPath = path18.join(root, "package.json");
|
|
5138
5265
|
try {
|
|
5139
|
-
return { pkg: JSON.parse(
|
|
5266
|
+
return { pkg: JSON.parse(fs14.readFileSync(pkgPath, "utf-8")), pkgPath };
|
|
5140
5267
|
} catch {
|
|
5141
5268
|
return { pkg: null, pkgPath: null };
|
|
5142
5269
|
}
|
|
@@ -5145,14 +5272,14 @@ function detectPackageManager(root, pkg) {
|
|
|
5145
5272
|
const declared = pkg?.packageManager?.split("@")[0];
|
|
5146
5273
|
if (declared === "pnpm" || declared === "yarn" || declared === "bun" || declared === "npm") return declared;
|
|
5147
5274
|
for (const [file, manager] of LOCKFILES) {
|
|
5148
|
-
if (
|
|
5275
|
+
if (fs14.existsSync(path18.join(root, file))) return manager;
|
|
5149
5276
|
}
|
|
5150
5277
|
return "npm";
|
|
5151
5278
|
}
|
|
5152
5279
|
function findConfig(root) {
|
|
5153
|
-
for (const name of
|
|
5280
|
+
for (const name of CONFIG_NAMES2) {
|
|
5154
5281
|
const candidate = path18.join(root, name);
|
|
5155
|
-
if (
|
|
5282
|
+
if (fs14.existsSync(candidate)) return candidate;
|
|
5156
5283
|
}
|
|
5157
5284
|
return null;
|
|
5158
5285
|
}
|
|
@@ -5313,7 +5440,7 @@ function ensureGitignoreEntry(existing, entry = ".env") {
|
|
|
5313
5440
|
}
|
|
5314
5441
|
|
|
5315
5442
|
// src/cli/skills.ts
|
|
5316
|
-
var
|
|
5443
|
+
var fs15 = __toESM(require("fs"));
|
|
5317
5444
|
var path19 = __toESM(require("path"));
|
|
5318
5445
|
|
|
5319
5446
|
// ../core/src/skill-stamp.ts
|
|
@@ -5381,7 +5508,7 @@ var DEFAULT_SKILLS_DIR = path19.join(".claude", "skills");
|
|
|
5381
5508
|
function findTemplatesDir(fromDir) {
|
|
5382
5509
|
let dir = fromDir;
|
|
5383
5510
|
for (let i = 0; i < 6; i++) {
|
|
5384
|
-
if (
|
|
5511
|
+
if (fs15.existsSync(path19.join(dir, "templates", "skills"))) return path19.join(dir, "templates");
|
|
5385
5512
|
const parent = path19.dirname(dir);
|
|
5386
5513
|
if (parent === dir) break;
|
|
5387
5514
|
dir = parent;
|
|
@@ -5400,7 +5527,7 @@ function readFrontMatter(markdown) {
|
|
|
5400
5527
|
}
|
|
5401
5528
|
function readTemplatesVersion(templatesDir) {
|
|
5402
5529
|
try {
|
|
5403
|
-
const pkg = JSON.parse(
|
|
5530
|
+
const pkg = JSON.parse(fs15.readFileSync(path19.join(templatesDir, "..", "package.json"), "utf-8"));
|
|
5404
5531
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
5405
5532
|
} catch {
|
|
5406
5533
|
return "0.0.0";
|
|
@@ -5413,8 +5540,8 @@ function listSkills(templatesDir, slugs = ALL_SKILLS) {
|
|
|
5413
5540
|
const infos = [];
|
|
5414
5541
|
for (const slug of slugs) {
|
|
5415
5542
|
const file = templatePath(templatesDir, slug);
|
|
5416
|
-
if (!
|
|
5417
|
-
const front = readFrontMatter(
|
|
5543
|
+
if (!fs15.existsSync(file)) continue;
|
|
5544
|
+
const front = readFrontMatter(fs15.readFileSync(file, "utf-8"));
|
|
5418
5545
|
infos.push({ slug, name: front.name ?? slug, description: front.description ?? "" });
|
|
5419
5546
|
}
|
|
5420
5547
|
return infos;
|
|
@@ -5426,16 +5553,16 @@ function installSkills(opts) {
|
|
|
5426
5553
|
const source = templatePath(opts.templatesDir, slug);
|
|
5427
5554
|
const relDest = path19.join(opts.skillsDir, slug, "SKILL.md");
|
|
5428
5555
|
const dest = path19.join(opts.root, relDest);
|
|
5429
|
-
if (!
|
|
5556
|
+
if (!fs15.existsSync(source)) {
|
|
5430
5557
|
results.push({ step, status: "error", detail: `no template found for "${slug}"` });
|
|
5431
5558
|
continue;
|
|
5432
5559
|
}
|
|
5433
5560
|
const version = opts.version ?? readTemplatesVersion(opts.templatesDir);
|
|
5434
|
-
const contents = stampSkill(
|
|
5435
|
-
const exists =
|
|
5561
|
+
const contents = stampSkill(fs15.readFileSync(source, "utf-8"), version);
|
|
5562
|
+
const exists = fs15.existsSync(dest);
|
|
5436
5563
|
let detail = "installed skill";
|
|
5437
5564
|
if (exists) {
|
|
5438
|
-
const installed =
|
|
5565
|
+
const installed = fs15.readFileSync(dest, "utf-8");
|
|
5439
5566
|
const state = classifyInstalledSkill(installed, contents);
|
|
5440
5567
|
const from = readSkillStamp(installed).version;
|
|
5441
5568
|
if (state === "current") {
|
|
@@ -5457,8 +5584,8 @@ function installSkills(opts) {
|
|
|
5457
5584
|
}
|
|
5458
5585
|
}
|
|
5459
5586
|
if (!opts.dryRun) {
|
|
5460
|
-
|
|
5461
|
-
|
|
5587
|
+
fs15.mkdirSync(path19.dirname(dest), { recursive: true });
|
|
5588
|
+
fs15.writeFileSync(dest, contents);
|
|
5462
5589
|
}
|
|
5463
5590
|
results.push({ step, file: relDest, status: exists ? "updated" : "created", detail });
|
|
5464
5591
|
}
|
|
@@ -5552,7 +5679,7 @@ function runSkills(argv, templatesDir, cwd = process.cwd()) {
|
|
|
5552
5679
|
|
|
5553
5680
|
// src/cli/init.ts
|
|
5554
5681
|
var DEFAULT_SERVER_URL = "http://localhost:3000";
|
|
5555
|
-
var
|
|
5682
|
+
var DESKTOP_APP_MODES2 = ["prefer", "fallback", "off"];
|
|
5556
5683
|
var USAGE8 = `
|
|
5557
5684
|
piwi init \u2014 wire a Playwright project up to a Piwi Dashboard
|
|
5558
5685
|
|
|
@@ -5628,7 +5755,7 @@ function parseInitArgs(argv, env, cwd) {
|
|
|
5628
5755
|
}
|
|
5629
5756
|
function readFileOr(file, fallback) {
|
|
5630
5757
|
try {
|
|
5631
|
-
return
|
|
5758
|
+
return fs16.readFileSync(file, "utf-8");
|
|
5632
5759
|
} catch {
|
|
5633
5760
|
return fallback;
|
|
5634
5761
|
}
|
|
@@ -5682,7 +5809,7 @@ function stepConfig(project, opts) {
|
|
|
5682
5809
|
projectName: opts.projectName,
|
|
5683
5810
|
desktopApp
|
|
5684
5811
|
});
|
|
5685
|
-
if (edit.status === "updated" && !opts.dryRun)
|
|
5812
|
+
if (edit.status === "updated" && !opts.dryRun) fs16.writeFileSync(project.configPath, edit.text);
|
|
5686
5813
|
if (edit.status === "already" && desktopApp && !new RegExp(`\\bdesktopApp\\s*:\\s*['"\`]${desktopApp}['"\`]`).test(source)) {
|
|
5687
5814
|
return {
|
|
5688
5815
|
step,
|
|
@@ -5697,7 +5824,7 @@ function stepFixtures(project, opts) {
|
|
|
5697
5824
|
const step = "fixtures";
|
|
5698
5825
|
const rel = path20.join("tests", project.configLang === "js" ? "fixtures.js" : "fixtures.ts");
|
|
5699
5826
|
const dest = path20.join(project.root, rel);
|
|
5700
|
-
if (
|
|
5827
|
+
if (fs16.existsSync(dest)) {
|
|
5701
5828
|
const current = readFileOr(dest, "");
|
|
5702
5829
|
if (/piwiFixtures/.test(current))
|
|
5703
5830
|
return { step, file: rel, status: "already", detail: "capture fixtures already set up" };
|
|
@@ -5709,8 +5836,8 @@ function stepFixtures(project, opts) {
|
|
|
5709
5836
|
};
|
|
5710
5837
|
}
|
|
5711
5838
|
if (!opts.dryRun) {
|
|
5712
|
-
|
|
5713
|
-
|
|
5839
|
+
fs16.mkdirSync(path20.dirname(dest), { recursive: true });
|
|
5840
|
+
fs16.writeFileSync(dest, fixturesContents());
|
|
5714
5841
|
}
|
|
5715
5842
|
return {
|
|
5716
5843
|
step,
|
|
@@ -5726,7 +5853,7 @@ function stepEnv(project, opts) {
|
|
|
5726
5853
|
["PIWI_DASHBOARD_URL", opts.serverUrl],
|
|
5727
5854
|
["PIWI_API_KEY", ""]
|
|
5728
5855
|
]);
|
|
5729
|
-
if (example.added.length && !opts.dryRun)
|
|
5856
|
+
if (example.added.length && !opts.dryRun) fs16.writeFileSync(examplePath, example.text);
|
|
5730
5857
|
results.push({
|
|
5731
5858
|
step: "env",
|
|
5732
5859
|
file: ".env.example",
|
|
@@ -5738,7 +5865,7 @@ function stepEnv(project, opts) {
|
|
|
5738
5865
|
const entries = [["PIWI_API_KEY", opts.apiKey]];
|
|
5739
5866
|
if (opts.serverUrl) entries.unshift(["PIWI_DASHBOARD_URL", opts.serverUrl]);
|
|
5740
5867
|
const env = upsertEnvKeys(readFileOr(envPath, ""), entries);
|
|
5741
|
-
if (env.added.length && !opts.dryRun)
|
|
5868
|
+
if (env.added.length && !opts.dryRun) fs16.writeFileSync(envPath, env.text);
|
|
5742
5869
|
results.push({
|
|
5743
5870
|
step: "env",
|
|
5744
5871
|
file: ".env",
|
|
@@ -5747,7 +5874,7 @@ function stepEnv(project, opts) {
|
|
|
5747
5874
|
});
|
|
5748
5875
|
const gitignorePath = path20.join(project.root, ".gitignore");
|
|
5749
5876
|
const gitignore = ensureGitignoreEntry(readFileOr(gitignorePath, ""));
|
|
5750
|
-
if (gitignore.added && !opts.dryRun)
|
|
5877
|
+
if (gitignore.added && !opts.dryRun) fs16.writeFileSync(gitignorePath, gitignore.text);
|
|
5751
5878
|
results.push({
|
|
5752
5879
|
step: "gitignore",
|
|
5753
5880
|
file: ".gitignore",
|
|
@@ -5790,8 +5917,8 @@ async function runInit(argv, env = process.env, cwd = process.cwd()) {
|
|
|
5790
5917
|
return 0;
|
|
5791
5918
|
}
|
|
5792
5919
|
const opts = parseInitArgs(argv, env, cwd);
|
|
5793
|
-
if (opts.desktopApp !== null && !
|
|
5794
|
-
console.error(`--desktop-app must be one of ${
|
|
5920
|
+
if (opts.desktopApp !== null && !DESKTOP_APP_MODES2.includes(opts.desktopApp)) {
|
|
5921
|
+
console.error(`--desktop-app must be one of ${DESKTOP_APP_MODES2.join(", ")} (got "${opts.desktopApp}")`);
|
|
5795
5922
|
return 1;
|
|
5796
5923
|
}
|
|
5797
5924
|
const project = detectProject(opts.root);
|
|
@@ -5848,7 +5975,7 @@ Piwi setup${opts.dryRun ? " (dry run \u2014 nothing written)" : ""} for "${opts.
|
|
|
5848
5975
|
}
|
|
5849
5976
|
|
|
5850
5977
|
// src/cli/select.ts
|
|
5851
|
-
var
|
|
5978
|
+
var fs18 = __toESM(require("fs"));
|
|
5852
5979
|
var path22 = __toESM(require("path"));
|
|
5853
5980
|
var import_node_child_process7 = require("child_process");
|
|
5854
5981
|
|
|
@@ -6694,7 +6821,7 @@ function diffImpactInput(files) {
|
|
|
6694
6821
|
}
|
|
6695
6822
|
|
|
6696
6823
|
// src/cli/add-reporter.ts
|
|
6697
|
-
var
|
|
6824
|
+
var fs17 = __toESM(require("fs"));
|
|
6698
6825
|
var path21 = __toESM(require("path"));
|
|
6699
6826
|
var import_node_module2 = require("module");
|
|
6700
6827
|
var REPORTER_PACKAGE2 = "@piwitests/reporter";
|
|
@@ -6738,7 +6865,7 @@ function resolveConfigPath(cwd, playwrightArgs2) {
|
|
|
6738
6865
|
function readConfigSource(configPath) {
|
|
6739
6866
|
if (!configPath) return null;
|
|
6740
6867
|
try {
|
|
6741
|
-
return
|
|
6868
|
+
return fs17.readFileSync(configPath, "utf-8");
|
|
6742
6869
|
} catch {
|
|
6743
6870
|
return null;
|
|
6744
6871
|
}
|
|
@@ -6773,9 +6900,9 @@ Usage:
|
|
|
6773
6900
|
npx @piwitests/reporter run impact --base <ref> run the tests your diff impacts
|
|
6774
6901
|
|
|
6775
6902
|
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)
|
|
6903
|
+
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
6904
|
+
--api-key <key> API key (env PIWI_API_KEY, .env)
|
|
6905
|
+
--project <name|id> Project (env PIWI_PROJECT_NAME, .env)
|
|
6779
6906
|
|
|
6780
6907
|
Selection:
|
|
6781
6908
|
--format <fmt> args (file:line, default) | grep | files | json
|
|
@@ -6832,12 +6959,22 @@ function parseDuration2(raw) {
|
|
|
6832
6959
|
const ms = Math.round(value * factor);
|
|
6833
6960
|
return ms > 0 ? ms : null;
|
|
6834
6961
|
}
|
|
6835
|
-
function parseSelectArgs(argv, env) {
|
|
6962
|
+
function parseSelectArgs(argv, env, dirs = [], command = "piwi select") {
|
|
6836
6963
|
const dashIndex = argv.indexOf("--");
|
|
6837
6964
|
const own = dashIndex === -1 ? argv : argv.slice(0, dashIndex);
|
|
6838
6965
|
const extra = dashIndex === -1 ? [] : argv.slice(dashIndex + 1);
|
|
6839
|
-
const
|
|
6840
|
-
|
|
6966
|
+
const connection = resolveCliConnection(
|
|
6967
|
+
{
|
|
6968
|
+
serverUrl: readOption7(own, "--server-url"),
|
|
6969
|
+
apiKey: readOption7(own, "--api-key"),
|
|
6970
|
+
project: readOption7(own, "--project")
|
|
6971
|
+
},
|
|
6972
|
+
env,
|
|
6973
|
+
dirs,
|
|
6974
|
+
command
|
|
6975
|
+
);
|
|
6976
|
+
if (!connection)
|
|
6977
|
+
throw new Error("No dashboard URL \u2014 pass --server-url or set PIWI_DASHBOARD_URL (environment or .env)");
|
|
6841
6978
|
const key = findKey(own);
|
|
6842
6979
|
if (!key) throw new Error("No selection key \u2014 usage: piwi select <key>");
|
|
6843
6980
|
const budgetRaw = readOption7(own, "--budget");
|
|
@@ -6851,9 +6988,9 @@ function parseSelectArgs(argv, env) {
|
|
|
6851
6988
|
throw new Error(`--shard expects an "i/n" spec like 2/4, got "${shard}"`);
|
|
6852
6989
|
}
|
|
6853
6990
|
return {
|
|
6854
|
-
serverUrl,
|
|
6855
|
-
apiKey:
|
|
6856
|
-
project:
|
|
6991
|
+
serverUrl: connection.serverUrl,
|
|
6992
|
+
apiKey: connection.apiKey,
|
|
6993
|
+
project: connection.project,
|
|
6857
6994
|
key,
|
|
6858
6995
|
format: readOption7(own, "--format") ?? "args",
|
|
6859
6996
|
budgetMs,
|
|
@@ -6872,7 +7009,7 @@ function cacheKey(args) {
|
|
|
6872
7009
|
}
|
|
6873
7010
|
function readCache(args) {
|
|
6874
7011
|
try {
|
|
6875
|
-
const store = JSON.parse(
|
|
7012
|
+
const store = JSON.parse(fs18.readFileSync(CACHE_FILE, "utf-8"));
|
|
6876
7013
|
return store[cacheKey(args)] ?? null;
|
|
6877
7014
|
} catch {
|
|
6878
7015
|
return null;
|
|
@@ -6882,12 +7019,12 @@ function writeCache(args, resolution) {
|
|
|
6882
7019
|
try {
|
|
6883
7020
|
let store = {};
|
|
6884
7021
|
try {
|
|
6885
|
-
store = JSON.parse(
|
|
7022
|
+
store = JSON.parse(fs18.readFileSync(CACHE_FILE, "utf-8"));
|
|
6886
7023
|
} catch {
|
|
6887
7024
|
}
|
|
6888
7025
|
store[cacheKey(args)] = resolution;
|
|
6889
|
-
|
|
6890
|
-
|
|
7026
|
+
fs18.mkdirSync(path22.dirname(CACHE_FILE), { recursive: true });
|
|
7027
|
+
fs18.writeFileSync(CACHE_FILE, JSON.stringify(store, null, 2));
|
|
6891
7028
|
} catch {
|
|
6892
7029
|
}
|
|
6893
7030
|
}
|
|
@@ -6979,7 +7116,7 @@ async function runSelect(argv, env = process.env) {
|
|
|
6979
7116
|
}
|
|
6980
7117
|
let args;
|
|
6981
7118
|
try {
|
|
6982
|
-
args = parseSelectArgs(argv, env);
|
|
7119
|
+
args = parseSelectArgs(argv, env, [process.cwd()]);
|
|
6983
7120
|
} catch (e) {
|
|
6984
7121
|
console.error(`piwi select: ${e.message}
|
|
6985
7122
|
`);
|
|
@@ -7070,7 +7207,7 @@ async function runRun(argv, env = process.env) {
|
|
|
7070
7207
|
}
|
|
7071
7208
|
let args;
|
|
7072
7209
|
try {
|
|
7073
|
-
args = parseSelectArgs(argv, env);
|
|
7210
|
+
args = parseSelectArgs(argv, env, [process.cwd()], "piwi run");
|
|
7074
7211
|
} catch (e) {
|
|
7075
7212
|
console.error(`piwi run: ${e.message}
|
|
7076
7213
|
`);
|
|
@@ -7120,7 +7257,7 @@ async function runRun(argv, env = process.env) {
|
|
|
7120
7257
|
}
|
|
7121
7258
|
|
|
7122
7259
|
// src/cli/preflight.ts
|
|
7123
|
-
var
|
|
7260
|
+
var fs19 = __toESM(require("fs"));
|
|
7124
7261
|
var path23 = __toESM(require("path"));
|
|
7125
7262
|
var import_node_child_process8 = require("child_process");
|
|
7126
7263
|
|
|
@@ -7186,8 +7323,8 @@ Actions:
|
|
|
7186
7323
|
|
|
7187
7324
|
Connection:
|
|
7188
7325
|
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
7189
|
-
--api-key <key> API key (env PIWI_API_KEY)
|
|
7190
|
-
--project <name|id> Project (env PIWI_PROJECT_NAME)
|
|
7326
|
+
--api-key <key> API key (env PIWI_API_KEY, .env)
|
|
7327
|
+
--project <name|id> Project (env PIWI_PROJECT_NAME, .env)
|
|
7191
7328
|
|
|
7192
7329
|
Output:
|
|
7193
7330
|
--json Print the breaks, the edits and the impact as JSON
|
|
@@ -7244,16 +7381,6 @@ function parsePreflightArgs(argv) {
|
|
|
7244
7381
|
extra: dashIndex === -1 ? [] : argv.slice(dashIndex + 1)
|
|
7245
7382
|
};
|
|
7246
7383
|
}
|
|
7247
|
-
var CONFIG_NAMES2 = ["playwright.config.ts", "playwright.config.js", "playwright.config.mjs", "playwright.config.cjs"];
|
|
7248
|
-
function findPlaywrightRoot(from) {
|
|
7249
|
-
let dir = path23.resolve(from);
|
|
7250
|
-
for (; ; ) {
|
|
7251
|
-
if (CONFIG_NAMES2.some((name) => fs18.existsSync(path23.join(dir, name)))) return dir;
|
|
7252
|
-
const parent = path23.dirname(dir);
|
|
7253
|
-
if (parent === dir) return null;
|
|
7254
|
-
dir = parent;
|
|
7255
|
-
}
|
|
7256
|
-
}
|
|
7257
7384
|
function git3(cwd, args) {
|
|
7258
7385
|
return (0, import_node_child_process8.execFileSync)("git", args, {
|
|
7259
7386
|
cwd,
|
|
@@ -7271,7 +7398,12 @@ function tryGit(cwd, args) {
|
|
|
7271
7398
|
}
|
|
7272
7399
|
var TEST_FILE = /(?:^|\/)[^/]+\.(?:spec|test)\.[cm]?[jt]sx?$/;
|
|
7273
7400
|
function resolveConnection(args, env, dirs) {
|
|
7274
|
-
return resolveCliConnection(
|
|
7401
|
+
return resolveCliConnection(
|
|
7402
|
+
{ serverUrl: args.serverUrl, apiKey: args.apiKey, project: args.project },
|
|
7403
|
+
env,
|
|
7404
|
+
dirs,
|
|
7405
|
+
"piwi preflight"
|
|
7406
|
+
);
|
|
7275
7407
|
}
|
|
7276
7408
|
function translationLookup(repoRoot, base, locale) {
|
|
7277
7409
|
const files = (tryGit(repoRoot, ["ls-files"]) ?? "").split("\n").filter((f) => f && isTranslationFile(f)).sort((a, b) => Number(prefersLocale(b, locale)) - Number(prefersLocale(a, locale)) || a.localeCompare(b));
|
|
@@ -7298,7 +7430,7 @@ function prefersLocale(file, locale) {
|
|
|
7298
7430
|
}
|
|
7299
7431
|
function readText(file) {
|
|
7300
7432
|
try {
|
|
7301
|
-
return
|
|
7433
|
+
return fs19.readFileSync(file, "utf-8");
|
|
7302
7434
|
} catch {
|
|
7303
7435
|
return void 0;
|
|
7304
7436
|
}
|
|
@@ -7309,7 +7441,7 @@ function cacheKey2(connection, branch) {
|
|
|
7309
7441
|
}
|
|
7310
7442
|
function readCache2(root, key) {
|
|
7311
7443
|
try {
|
|
7312
|
-
const store = JSON.parse(
|
|
7444
|
+
const store = JSON.parse(fs19.readFileSync(path23.join(root, CACHE_FILE2), "utf-8"));
|
|
7313
7445
|
return store[key] ?? null;
|
|
7314
7446
|
} catch {
|
|
7315
7447
|
return null;
|
|
@@ -7320,12 +7452,12 @@ function writeCache2(root, key, index) {
|
|
|
7320
7452
|
try {
|
|
7321
7453
|
let store = {};
|
|
7322
7454
|
try {
|
|
7323
|
-
store = JSON.parse(
|
|
7455
|
+
store = JSON.parse(fs19.readFileSync(file, "utf-8"));
|
|
7324
7456
|
} catch {
|
|
7325
7457
|
}
|
|
7326
7458
|
store[key] = { savedAt: (/* @__PURE__ */ new Date()).toISOString(), index };
|
|
7327
|
-
|
|
7328
|
-
|
|
7459
|
+
fs19.mkdirSync(path23.dirname(file), { recursive: true });
|
|
7460
|
+
fs19.writeFileSync(file, JSON.stringify(store));
|
|
7329
7461
|
} catch {
|
|
7330
7462
|
}
|
|
7331
7463
|
}
|
|
@@ -7384,7 +7516,7 @@ function applyEdits(breaks, replacementsOf) {
|
|
|
7384
7516
|
}
|
|
7385
7517
|
const edited = [];
|
|
7386
7518
|
for (const [file, edits] of byFile) {
|
|
7387
|
-
const text =
|
|
7519
|
+
const text = fs19.readFileSync(file, "utf-8");
|
|
7388
7520
|
const eol = text.includes("\r\n") ? "\r\n" : "\n";
|
|
7389
7521
|
const lines = text.split(/\r?\n/);
|
|
7390
7522
|
for (const e of edits) {
|
|
@@ -7394,7 +7526,7 @@ function applyEdits(breaks, replacementsOf) {
|
|
|
7394
7526
|
}
|
|
7395
7527
|
const next = lines.join(eol);
|
|
7396
7528
|
if (next !== text) {
|
|
7397
|
-
|
|
7529
|
+
fs19.writeFileSync(file, next);
|
|
7398
7530
|
edited.push(file);
|
|
7399
7531
|
}
|
|
7400
7532
|
}
|
|
@@ -7505,7 +7637,7 @@ async function runPreflight(argv, env = process.env, cwd = process.cwd()) {
|
|
|
7505
7637
|
console.error("piwi preflight: not inside a git repository");
|
|
7506
7638
|
return EXIT_ERROR8;
|
|
7507
7639
|
}
|
|
7508
|
-
const testRoot = path23.resolve(cwd, args.testRoot ?? findPlaywrightRoot(cwd) ?? cwd);
|
|
7640
|
+
const testRoot = path23.resolve(cwd, args.testRoot ?? findPlaywrightRoot(cwd, repoRoot) ?? cwd);
|
|
7509
7641
|
let files;
|
|
7510
7642
|
try {
|
|
7511
7643
|
files = parseUnifiedDiff(
|
|
@@ -7631,7 +7763,7 @@ async function runPreflight(argv, env = process.env, cwd = process.cwd()) {
|
|
|
7631
7763
|
}
|
|
7632
7764
|
|
|
7633
7765
|
// src/cli/probe.ts
|
|
7634
|
-
var
|
|
7766
|
+
var fs20 = __toESM(require("fs"));
|
|
7635
7767
|
var os3 = __toESM(require("os"));
|
|
7636
7768
|
var path24 = __toESM(require("path"));
|
|
7637
7769
|
var EXIT_OK8 = 0;
|
|
@@ -7643,9 +7775,9 @@ Usage:
|
|
|
7643
7775
|
npx @piwitests/reporter probe [options] [-- <playwright args>]
|
|
7644
7776
|
|
|
7645
7777
|
Options:
|
|
7646
|
-
--server-url <url> Dashboard URL (or PIWI_DASHBOARD_URL)
|
|
7647
|
-
--api-key <key> Reporter API key (or PIWI_API_KEY)
|
|
7648
|
-
--project <name|id> Project name or id (or PIWI_PROJECT_NAME)
|
|
7778
|
+
--server-url <url> Dashboard URL (or PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
7779
|
+
--api-key <key> Reporter API key (or PIWI_API_KEY, .env)
|
|
7780
|
+
--project <name|id> Project name or id (or PIWI_PROJECT_NAME, .env)
|
|
7649
7781
|
--budget <n> Max pairs to probe this run (default the server's)
|
|
7650
7782
|
|
|
7651
7783
|
Everything after \`--\` is passed to \`playwright test\`.
|
|
@@ -7666,13 +7798,21 @@ async function runProbe(argv) {
|
|
|
7666
7798
|
return EXIT_OK8;
|
|
7667
7799
|
}
|
|
7668
7800
|
const env = process.env;
|
|
7669
|
-
const
|
|
7670
|
-
|
|
7671
|
-
|
|
7801
|
+
const connection = resolveCliConnection(
|
|
7802
|
+
{
|
|
7803
|
+
serverUrl: readOption9(own, "--server-url"),
|
|
7804
|
+
apiKey: readOption9(own, "--api-key"),
|
|
7805
|
+
project: readOption9(own, "--project")
|
|
7806
|
+
},
|
|
7807
|
+
env,
|
|
7808
|
+
[process.cwd()],
|
|
7809
|
+
"piwi probe"
|
|
7810
|
+
);
|
|
7811
|
+
if (!connection) {
|
|
7812
|
+
console.error("piwi probe: no dashboard URL \u2014 pass --server-url or set PIWI_DASHBOARD_URL (environment or .env)");
|
|
7672
7813
|
return EXIT_ERROR9;
|
|
7673
7814
|
}
|
|
7674
|
-
const
|
|
7675
|
-
const project = readOption9(own, "--project") ?? env.PIWI_PROJECT_NAME ?? "";
|
|
7815
|
+
const { serverUrl, apiKey, project } = connection;
|
|
7676
7816
|
const budget = readOption9(own, "--budget");
|
|
7677
7817
|
let projectId;
|
|
7678
7818
|
try {
|
|
@@ -7697,12 +7837,12 @@ async function runProbe(argv) {
|
|
|
7697
7837
|
return EXIT_OK8;
|
|
7698
7838
|
}
|
|
7699
7839
|
console.error(`piwi probe: ${plan.items.length} pair(s) to probe (budget ${plan.budget}).`);
|
|
7700
|
-
const dir =
|
|
7840
|
+
const dir = fs20.mkdtempSync(path24.join(os3.tmpdir(), "piwi-probe-"));
|
|
7701
7841
|
const planFile = path24.join(dir, "plan.json");
|
|
7702
7842
|
const resultsFile = path24.join(dir, "results.jsonl");
|
|
7703
7843
|
const runOutputFile = path24.join(dir, "run.json");
|
|
7704
|
-
|
|
7705
|
-
|
|
7844
|
+
fs20.writeFileSync(planFile, JSON.stringify(plan));
|
|
7845
|
+
fs20.writeFileSync(resultsFile, "");
|
|
7706
7846
|
const grep = plan.items.map((i) => i.testTitle).filter(Boolean).map(escapeRegex2).join("|");
|
|
7707
7847
|
const pwArgs = [...playwrightArgs2];
|
|
7708
7848
|
if (grep && !playwrightArgs2.includes("--grep") && !playwrightArgs2.includes("-g")) pwArgs.push("--grep", grep);
|
|
@@ -7716,7 +7856,7 @@ async function runProbe(argv) {
|
|
|
7716
7856
|
[PIWI_ENV_KEYS.outputFile]: runOutputFile
|
|
7717
7857
|
};
|
|
7718
7858
|
await spawnPlaywright(pwArgs, childEnv, { command: "piwi probe" });
|
|
7719
|
-
const lines =
|
|
7859
|
+
const lines = fs20.readFileSync(resultsFile, "utf8").split("\n").filter(Boolean);
|
|
7720
7860
|
const results = [];
|
|
7721
7861
|
for (const line of lines) {
|
|
7722
7862
|
try {
|
|
@@ -7730,7 +7870,7 @@ async function runProbe(argv) {
|
|
|
7730
7870
|
}
|
|
7731
7871
|
let runId = null;
|
|
7732
7872
|
try {
|
|
7733
|
-
const out = JSON.parse(
|
|
7873
|
+
const out = JSON.parse(fs20.readFileSync(runOutputFile, "utf8"));
|
|
7734
7874
|
if (typeof out.runId === "number") runId = out.runId;
|
|
7735
7875
|
} catch {
|
|
7736
7876
|
}
|
|
@@ -7755,7 +7895,7 @@ async function runProbe(argv) {
|
|
|
7755
7895
|
}
|
|
7756
7896
|
|
|
7757
7897
|
// src/cli/quality-report.ts
|
|
7758
|
-
var
|
|
7898
|
+
var fs21 = __toESM(require("fs"));
|
|
7759
7899
|
var EXIT_OK9 = 0;
|
|
7760
7900
|
var EXIT_VERDICT = 1;
|
|
7761
7901
|
var EXIT_ERROR10 = 2;
|
|
@@ -7768,8 +7908,8 @@ Usage:
|
|
|
7768
7908
|
npx @piwitests/reporter report [options]
|
|
7769
7909
|
|
|
7770
7910
|
Connection:
|
|
7771
|
-
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL)
|
|
7772
|
-
--api-key <key> API key (env PIWI_API_KEY)
|
|
7911
|
+
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
7912
|
+
--api-key <key> API key (env PIWI_API_KEY, .env)
|
|
7773
7913
|
|
|
7774
7914
|
What to report:
|
|
7775
7915
|
--project <names|ids> Comma-separated projects (default: every project you can see)
|
|
@@ -7801,9 +7941,15 @@ function normalizePeriod(raw) {
|
|
|
7801
7941
|
const days = raw.trim().match(/^(\d+)d$/);
|
|
7802
7942
|
return days ? `last-${days[1]}d` : raw.trim();
|
|
7803
7943
|
}
|
|
7804
|
-
function parseReportArgs(argv, env) {
|
|
7805
|
-
const
|
|
7806
|
-
|
|
7944
|
+
function parseReportArgs(argv, env, dirs = []) {
|
|
7945
|
+
const connection = resolveCliConnection(
|
|
7946
|
+
{ serverUrl: readOption10(argv, "--server-url"), apiKey: readOption10(argv, "--api-key") },
|
|
7947
|
+
env,
|
|
7948
|
+
dirs,
|
|
7949
|
+
"piwi report"
|
|
7950
|
+
);
|
|
7951
|
+
if (!connection)
|
|
7952
|
+
throw new Error("No dashboard URL \u2014 pass --server-url or set PIWI_DASHBOARD_URL (environment or .env)");
|
|
7807
7953
|
const format = (readOption10(argv, "--format") ?? "md").toLowerCase();
|
|
7808
7954
|
if (!FORMATS.includes(format)) {
|
|
7809
7955
|
throw new Error(`--format must be one of ${FORMATS.join(", ")}, got "${format}"`);
|
|
@@ -7823,8 +7969,8 @@ function parseReportArgs(argv, env) {
|
|
|
7823
7969
|
const output = readOption10(argv, "--output") ?? null;
|
|
7824
7970
|
if (format === "pdf" && !output) throw new Error("A PDF needs --output <file>");
|
|
7825
7971
|
return {
|
|
7826
|
-
serverUrl,
|
|
7827
|
-
apiKey:
|
|
7972
|
+
serverUrl: connection.serverUrl,
|
|
7973
|
+
apiKey: connection.apiKey,
|
|
7828
7974
|
projects: (readOption10(argv, "--project") ?? "").split(",").map((p) => p.trim()).filter(Boolean),
|
|
7829
7975
|
period: normalizePeriod(readOption10(argv, "--period") ?? "30d"),
|
|
7830
7976
|
compare: readOption10(argv, "--compare") ?? null,
|
|
@@ -7868,14 +8014,14 @@ async function runQualityReport(argv) {
|
|
|
7868
8014
|
return EXIT_OK9;
|
|
7869
8015
|
}
|
|
7870
8016
|
try {
|
|
7871
|
-
const args = parseReportArgs(argv, process.env);
|
|
8017
|
+
const args = parseReportArgs(argv, process.env, [process.cwd()]);
|
|
7872
8018
|
const projectIds = [];
|
|
7873
8019
|
for (const project of args.projects) {
|
|
7874
8020
|
projectIds.push(await resolveProjectId({ serverUrl: args.serverUrl, apiKey: args.apiKey, project, key: "" }));
|
|
7875
8021
|
}
|
|
7876
8022
|
const res = await fetchReport(args, reportQuery(args, projectIds, args.format));
|
|
7877
8023
|
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
7878
|
-
if (args.output)
|
|
8024
|
+
if (args.output) fs21.writeFileSync(args.output, bytes);
|
|
7879
8025
|
else process.stdout.write(bytes);
|
|
7880
8026
|
if (!args.failOn) return EXIT_OK9;
|
|
7881
8027
|
const bundle = args.format === "json" ? JSON.parse(new TextDecoder().decode(bytes)) : await (await fetchReport(args, reportQuery(args, projectIds, "json"))).json();
|