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