@ait-co/devtools 0.1.131 → 0.1.133
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/{attach-orchestrator-ca34r46y.js → attach-orchestrator-CE0S09YU.js} +110 -5
- package/dist/attach-orchestrator-CE0S09YU.js.map +1 -0
- package/dist/{attach-orchestrator-BiaYiNOr.js → attach-orchestrator-CMoDuG2A.js} +110 -5
- package/dist/attach-orchestrator-CMoDuG2A.js.map +1 -0
- package/dist/cell-BIdSQb9T.js +201 -0
- package/dist/cell-BIdSQb9T.js.map +1 -0
- package/dist/{cell-Cw4fVOl2.js → cell-D1y4shoV.js} +1 -21
- package/dist/cell-D1y4shoV.js.map +1 -0
- package/dist/cell-DHA578lX.js +200 -0
- package/dist/cell-DHA578lX.js.map +1 -0
- package/dist/{debug-server-qRSRG7YZ.js → debug-server-CS1BS2BX.js} +21 -3
- package/dist/debug-server-CS1BS2BX.js.map +1 -0
- package/dist/{debug-server-BSbfnFR5.js → debug-server-DmzuK2c6.js} +26 -9
- package/dist/debug-server-DmzuK2c6.js.map +1 -0
- package/dist/{debug-server-CD4zYBq4.js → debug-server-DnNCoNiR.js} +7 -7
- package/dist/{debug-server-CD4zYBq4.js.map → debug-server-DnNCoNiR.js.map} +1 -1
- package/dist/mcp/cli.js +180 -8
- package/dist/mcp/cli.js.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/panel/index.js +1 -1
- package/dist/{pool-BaSCZALv.d.ts → pool-x28Wv1ty.d.ts} +16 -2
- package/dist/{pool-BaSCZALv.d.ts.map → pool-x28Wv1ty.d.ts.map} +1 -1
- package/dist/{qr-http-server-DCbjORFa.js → qr-http-server-C_DTp9WU.js} +3 -2
- package/dist/{qr-http-server-DCbjORFa.js.map → qr-http-server-C_DTp9WU.js.map} +1 -1
- package/dist/{qr-http-server-BxoRp2w7.cjs → qr-http-server-DX_9UA-p.cjs} +3 -2
- package/dist/qr-http-server-DX_9UA-p.cjs.map +1 -0
- package/dist/{qr-http-server-BdNGPRoL.js → qr-http-server-DeRrKp_R.js} +3 -2
- package/dist/{qr-http-server-BdNGPRoL.js.map → qr-http-server-DeRrKp_R.js.map} +1 -1
- package/dist/{qr-http-server-cKbciY5k.js → qr-http-server-FVn61xAq.js} +3 -2
- package/dist/{qr-http-server-Bq9zR7-z.js.map → qr-http-server-FVn61xAq.js.map} +1 -1
- package/dist/{qr-http-server-Bq9zR7-z.js → qr-http-server-V0xuU5TJ.js} +3 -2
- package/dist/{qr-http-server-cKbciY5k.js.map → qr-http-server-V0xuU5TJ.js.map} +1 -1
- package/dist/{qr-http-server-CV6TA9ZN.cjs → qr-http-server-iH_Hh_si.cjs} +3 -2
- package/dist/qr-http-server-iH_Hh_si.cjs.map +1 -0
- package/dist/{relay-factory-Cloog1jb.js → relay-factory-BQfmK7RP.js} +14 -8
- package/dist/relay-factory-BQfmK7RP.js.map +1 -0
- package/dist/{relay-secret-store-CFc9n0OA.js → relay-secret-store-CLEGyHou.js} +2 -2
- package/dist/{relay-secret-store-CFc9n0OA.js.map → relay-secret-store-CLEGyHou.js.map} +1 -1
- package/dist/{relay-secret-store-DzxDsK6S.js → relay-secret-store-DGduVJhs.js} +2 -2
- package/dist/{relay-secret-store-DzxDsK6S.js.map → relay-secret-store-DGduVJhs.js.map} +1 -1
- package/dist/{relay-url-store-D3rY-GJJ.js → relay-url-store-B0X8TsGr.js} +2 -2
- package/dist/{relay-url-store-D3rY-GJJ.js.map → relay-url-store-B0X8TsGr.js.map} +1 -1
- package/dist/{relay-url-store-DGQ-HPQC.js → relay-url-store-CKW8RQzf.js} +2 -2
- package/dist/{relay-url-store-DGQ-HPQC.js.map → relay-url-store-CKW8RQzf.js.map} +1 -1
- package/dist/{relay-worker-o0M2ndyJ.d.ts → relay-worker-DtX_eszH.d.ts} +51 -2
- package/dist/relay-worker-DtX_eszH.d.ts.map +1 -0
- package/dist/{relay-worker-CCAHLC4a.js → relay-worker-MZq4fLv8.js} +27 -6
- package/dist/{relay-worker-CCAHLC4a.js.map → relay-worker-MZq4fLv8.js.map} +1 -1
- package/dist/rolldown-runtime-DGkTqVfb.js +15 -0
- package/dist/rolldown-runtime-DUslC3ob.js +14 -0
- package/dist/test-runner/bin.js +173 -119
- package/dist/test-runner/bin.js.map +1 -1
- package/dist/test-runner/config.d.ts +1 -1
- package/dist/test-runner/config.js +1 -1
- package/dist/test-runner/pool.d.ts +1 -1
- package/dist/test-runner/pool.js +1 -1
- package/dist/test-runner/pool.js.map +1 -1
- package/dist/test-runner/relay-factory.d.ts +14 -0
- package/dist/test-runner/relay-factory.d.ts.map +1 -1
- package/dist/test-runner/relay-factory.js +11 -5
- package/dist/test-runner/relay-factory.js.map +1 -1
- package/dist/test-runner/relay-worker.d.ts +2 -2
- package/dist/test-runner/relay-worker.js +2 -2
- package/dist/test-runner/report.d.ts +35 -4
- package/dist/test-runner/report.d.ts.map +1 -1
- package/dist/test-runner/report.js +42 -7
- package/dist/test-runner/report.js.map +1 -1
- package/dist/{totp-DvOYkYim.js → totp-CZLLKfOC.js} +2 -14
- package/dist/{totp-DvOYkYim.js.map → totp-CZLLKfOC.js.map} +1 -1
- package/dist/totp-DAxys-r0.js +199 -0
- package/dist/totp-DAxys-r0.js.map +1 -0
- package/dist/{tunnel-B2UCKAc7.cjs → tunnel-DP453L4e.cjs} +2 -2
- package/dist/{tunnel-B2UCKAc7.cjs.map → tunnel-DP453L4e.cjs.map} +1 -1
- package/dist/{tunnel-C-rQ34dM.js → tunnel-Djf-zmjJ.js} +2 -2
- package/dist/{tunnel-C-rQ34dM.js.map → tunnel-Djf-zmjJ.js.map} +1 -1
- package/dist/unplugin/index.cjs +1 -1
- package/dist/unplugin/index.js +1 -1
- package/dist/unplugin/tunnel.cjs +1 -1
- package/dist/unplugin/tunnel.js +1 -1
- package/package.json +1 -1
- package/dist/attach-orchestrator-BiaYiNOr.js.map +0 -1
- package/dist/attach-orchestrator-ca34r46y.js.map +0 -1
- package/dist/cell-Bi8MAPA8.js +0 -88
- package/dist/cell-Bi8MAPA8.js.map +0 -1
- package/dist/cell-Cw4fVOl2.js.map +0 -1
- package/dist/cell-rr6gL9AT.js +0 -89
- package/dist/cell-rr6gL9AT.js.map +0 -1
- package/dist/debug-server-BSbfnFR5.js.map +0 -1
- package/dist/debug-server-qRSRG7YZ.js.map +0 -1
- package/dist/log-CUik_tHp.js +0 -95
- package/dist/log-CUik_tHp.js.map +0 -1
- package/dist/qr-http-server-BxoRp2w7.cjs.map +0 -1
- package/dist/qr-http-server-CV6TA9ZN.cjs.map +0 -1
- package/dist/relay-factory-Cloog1jb.js.map +0 -1
- package/dist/relay-worker-o0M2ndyJ.d.ts.map +0 -1
package/dist/test-runner/bin.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { m as logInfo } from "../attach-orchestrator-CE0S09YU.js";
|
|
3
|
+
import { r as generateTotp } from "../totp-CZLLKfOC.js";
|
|
4
|
+
import { n as runPermissionPreflight } from "../cell-BIdSQb9T.js";
|
|
3
5
|
import * as path$1 from "node:path";
|
|
4
6
|
import path, { basename, isAbsolute, resolve } from "node:path";
|
|
5
7
|
import { parseArgs } from "node:util";
|
|
@@ -22,6 +24,23 @@ import { fileURLToPath } from "node:url";
|
|
|
22
24
|
* safe to import from the MCP daemon graph.
|
|
23
25
|
*/
|
|
24
26
|
/**
|
|
27
|
+
* Filename suffix that opts a test file into manual-variant scheduling
|
|
28
|
+
* (devtools#741). A file named `<name>.manual.ait.test.ts` is EXCLUDED from
|
|
29
|
+
* `discoverTestFiles`'s default output (so existing unattended runs are
|
|
30
|
+
* unaffected — the zero-diff-when-off constraint) and is only surfaced when
|
|
31
|
+
* the caller explicitly asks for manual files via {@link partitionManualTests}
|
|
32
|
+
* (wired from the CLI's `--manual-blocking` flag).
|
|
33
|
+
*
|
|
34
|
+
* This is the entire tagging contract for v1 — no separate manifest/config,
|
|
35
|
+
* just a filename convention. Documented here + in the CLI `--help` text
|
|
36
|
+
* (cli.ts USAGE) and the test-runner README.
|
|
37
|
+
*/
|
|
38
|
+
const MANUAL_TEST_SUFFIX = ".manual.ait.test.ts";
|
|
39
|
+
/** True when `file`'s basename ends with {@link MANUAL_TEST_SUFFIX}. */
|
|
40
|
+
function isManualTestFile(file) {
|
|
41
|
+
return basename(file).endsWith(MANUAL_TEST_SUFFIX);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
25
44
|
* Expands `patterns` (globs or plain paths) into a sorted, de-duplicated list of
|
|
26
45
|
* ABSOLUTE test file paths, resolved relative to `cwd`.
|
|
27
46
|
*
|
|
@@ -30,14 +49,46 @@ import { fileURLToPath } from "node:url";
|
|
|
30
49
|
* resolved against `cwd`. `bundleTestFile` requires an absolute path, so the
|
|
31
50
|
* absolute output feeds it directly.
|
|
32
51
|
*
|
|
52
|
+
* By default, files matching {@link MANUAL_TEST_SUFFIX} are EXCLUDED from the
|
|
53
|
+
* result (devtools#741) — blocking-UI tests opt in via that filename
|
|
54
|
+
* convention and must never appear in an unattended run unless the caller
|
|
55
|
+
* explicitly asks for them via `includeManual: true`. This keeps the
|
|
56
|
+
* default (flag-off) discovery path byte-for-byte identical to before this
|
|
57
|
+
* option existed.
|
|
58
|
+
*
|
|
33
59
|
* @param patterns Glob patterns or file paths (e.g. `['src/**\/*.ait.test.ts']`).
|
|
34
60
|
* @param cwd Base directory for relative patterns/results.
|
|
61
|
+
* @param opts `{ includeManual }` — when true, manual-tagged files are kept
|
|
62
|
+
* in the (still-sorted) output instead of being filtered out.
|
|
63
|
+
* Use {@link partitionManualTests} to separate + reorder them.
|
|
35
64
|
* @returns Sorted, de-duplicated absolute file paths. Empty when nothing matches.
|
|
36
65
|
*/
|
|
37
|
-
async function discoverTestFiles(patterns, cwd) {
|
|
66
|
+
async function discoverTestFiles(patterns, cwd, opts) {
|
|
38
67
|
const out = /* @__PURE__ */ new Set();
|
|
39
68
|
for await (const match of glob(patterns, { cwd })) out.add(isAbsolute(match) ? match : resolve(cwd, match));
|
|
40
|
-
|
|
69
|
+
const sorted = [...out].sort();
|
|
70
|
+
if (opts?.includeManual) return sorted;
|
|
71
|
+
return sorted.filter((f) => !isManualTestFile(f));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Splits an already-discovered file list into `{ regular, manual }`, each
|
|
75
|
+
* still sorted. Used by the CLI's `--manual-blocking` path to schedule manual
|
|
76
|
+
* files strictly AFTER every regular file (devtools#741) — regular files run
|
|
77
|
+
* first (and produce the unattended-shaped part of the report) and manual
|
|
78
|
+
* files run last, each preceded by a dashboard prompt.
|
|
79
|
+
*
|
|
80
|
+
* Pure partition — does not itself decide inclusion; call
|
|
81
|
+
* `discoverTestFiles(patterns, cwd, { includeManual: true })` first so manual
|
|
82
|
+
* files are present in `files` to partition.
|
|
83
|
+
*/
|
|
84
|
+
function partitionManualTests(files) {
|
|
85
|
+
const regular = [];
|
|
86
|
+
const manual = [];
|
|
87
|
+
for (const f of files) (isManualTestFile(f) ? manual : regular).push(f);
|
|
88
|
+
return {
|
|
89
|
+
regular,
|
|
90
|
+
manual
|
|
91
|
+
};
|
|
41
92
|
}
|
|
42
93
|
//#endregion
|
|
43
94
|
//#region src/test-runner/relay-factory.ts
|
|
@@ -65,12 +116,13 @@ function createRelayConnectionFactory(opts) {
|
|
|
65
116
|
let family;
|
|
66
117
|
let qrServer;
|
|
67
118
|
let phase = "active";
|
|
119
|
+
let manualPrompt = null;
|
|
68
120
|
return {
|
|
69
121
|
async open() {
|
|
70
|
-
const { prepareAttach, renderAndMaybeWait, mintAttachUrl } = await import("../attach-orchestrator-
|
|
71
|
-
const { injectDebugIndicator, injectGlobals } = await import("../cell-
|
|
72
|
-
const { loadRelaySecretReadOnly } = await import("../relay-secret-store-
|
|
73
|
-
const { bootRelayFamily, buildRelayVerifyAuth } = await import("../debug-server-
|
|
122
|
+
const { prepareAttach, renderAndMaybeWait, mintAttachUrl } = await import("../attach-orchestrator-CE0S09YU.js").then((n) => n.i);
|
|
123
|
+
const { injectDebugIndicator, injectGlobals } = await import("../cell-BIdSQb9T.js").then((n) => n.t);
|
|
124
|
+
const { loadRelaySecretReadOnly } = await import("../relay-secret-store-CLEGyHou.js");
|
|
125
|
+
const { bootRelayFamily, buildRelayVerifyAuth } = await import("../debug-server-DnNCoNiR.js");
|
|
74
126
|
await loadRelaySecretReadOnly({ projectRoot });
|
|
75
127
|
let resolveTunnelUp;
|
|
76
128
|
const tunnelReady = new Promise((resolve) => {
|
|
@@ -100,13 +152,14 @@ function createRelayConnectionFactory(opts) {
|
|
|
100
152
|
canOpenBrowser: () => !headless
|
|
101
153
|
};
|
|
102
154
|
try {
|
|
103
|
-
const { startQrHttpServer } = await import("../qr-http-server-
|
|
155
|
+
const { startQrHttpServer } = await import("../qr-http-server-C_DTp9WU.js");
|
|
104
156
|
const getDashboardState = () => ({
|
|
105
157
|
tunnel: attachDeps.getTunnelStatus(),
|
|
106
158
|
pages: null,
|
|
107
159
|
attachUrl: lastAttachParts ? mintAttachUrl(attachDeps, lastAttachParts) : null,
|
|
108
160
|
mode: "relay-dev",
|
|
109
|
-
phase
|
|
161
|
+
phase,
|
|
162
|
+
manualPrompt
|
|
110
163
|
});
|
|
111
164
|
qrServer = await startQrHttpServer(getDashboardState);
|
|
112
165
|
attachDeps.qrHttpServer = qrServer;
|
|
@@ -163,13 +216,17 @@ function createRelayConnectionFactory(opts) {
|
|
|
163
216
|
phase = next;
|
|
164
217
|
qrServer?.notifyStateChange();
|
|
165
218
|
},
|
|
219
|
+
onManualPrompt(next) {
|
|
220
|
+
manualPrompt = next;
|
|
221
|
+
qrServer?.notifyStateChange();
|
|
222
|
+
},
|
|
166
223
|
async close(connection) {
|
|
167
224
|
if (phase !== "complete") {
|
|
168
225
|
phase = "complete";
|
|
169
226
|
qrServer?.notifyStateChange();
|
|
170
227
|
}
|
|
171
228
|
try {
|
|
172
|
-
const { injectDebugIndicator } = await import("../cell-
|
|
229
|
+
const { injectDebugIndicator } = await import("../cell-BIdSQb9T.js").then((n) => n.t);
|
|
173
230
|
await injectDebugIndicator(connection, { state: "disconnected" });
|
|
174
231
|
} catch {}
|
|
175
232
|
family?.stop();
|
|
@@ -221,98 +278,6 @@ const RELAY_AUTH_REJECT_CLOSE_CODE = 4401;
|
|
|
221
278
|
*/
|
|
222
279
|
const RELAY_AUTH_REJECT_REASON = "totp-rejected";
|
|
223
280
|
//#endregion
|
|
224
|
-
//#region src/mcp/log.ts
|
|
225
|
-
/**
|
|
226
|
-
* Allowed field keys that may pass through to a log line.
|
|
227
|
-
* Unknown keys are dropped. Values are still redact-scanned.
|
|
228
|
-
*/
|
|
229
|
-
const ALLOWED_KEYS = new Set([
|
|
230
|
-
"ts",
|
|
231
|
-
"level",
|
|
232
|
-
"event",
|
|
233
|
-
"msg",
|
|
234
|
-
"port",
|
|
235
|
-
"totpEnabled",
|
|
236
|
-
"env",
|
|
237
|
-
"tool",
|
|
238
|
-
"deploymentId",
|
|
239
|
-
"errorKind",
|
|
240
|
-
"reason",
|
|
241
|
-
"prevTargetId",
|
|
242
|
-
"mode",
|
|
243
|
-
"fileCount",
|
|
244
|
-
"passed",
|
|
245
|
-
"failed",
|
|
246
|
-
"skipped"
|
|
247
|
-
]);
|
|
248
|
-
/**
|
|
249
|
-
* Patterns that match secret values.
|
|
250
|
-
* Match order matters — more-specific patterns first.
|
|
251
|
-
*
|
|
252
|
-
* #268 redact script covers: relay=wss://…, at=<TOTP>, _deploymentId=<uuid>.
|
|
253
|
-
* Here we extend to in-process value-level patterns used in server logs.
|
|
254
|
-
*/
|
|
255
|
-
const SECRET_PATTERNS = [
|
|
256
|
-
/^\d{6}$/,
|
|
257
|
-
/^(aitcc_|AITCC_)/i,
|
|
258
|
-
/^[A-Za-z0-9_-]+=.{4,}/,
|
|
259
|
-
/^wss:\/\//,
|
|
260
|
-
/(?:^|[?&])at=[A-Z0-9]{6}/i
|
|
261
|
-
];
|
|
262
|
-
/**
|
|
263
|
-
* Returns `true` when the string value matches any known-secret pattern.
|
|
264
|
-
* Only string values are tested — numbers/booleans are always safe.
|
|
265
|
-
*/
|
|
266
|
-
function isSecretValue(value) {
|
|
267
|
-
return SECRET_PATTERNS.some((re) => re.test(value));
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Redacts a single scalar value.
|
|
271
|
-
* - strings: return "***" if the value matches a secret pattern.
|
|
272
|
-
* - other: return as-is.
|
|
273
|
-
*/
|
|
274
|
-
function redactValue(value) {
|
|
275
|
-
if (typeof value === "string" && isSecretValue(value)) return "***";
|
|
276
|
-
return value;
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* Builds a safe log payload from raw fields.
|
|
280
|
-
*
|
|
281
|
-
* - Only keys in `ALLOWED_KEYS` are included.
|
|
282
|
-
* - String values are scanned for secret patterns and replaced with "***".
|
|
283
|
-
* - `ts` and `level` and `event` are always included (they are injected by the
|
|
284
|
-
* logger functions below, not by callers).
|
|
285
|
-
*/
|
|
286
|
-
function buildPayload(level, event, fields) {
|
|
287
|
-
const out = {
|
|
288
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
289
|
-
level,
|
|
290
|
-
event
|
|
291
|
-
};
|
|
292
|
-
for (const [key, value] of Object.entries(fields)) {
|
|
293
|
-
if (!ALLOWED_KEYS.has(key)) continue;
|
|
294
|
-
if (key === "ts" || key === "level" || key === "event") continue;
|
|
295
|
-
out[key] = redactValue(value);
|
|
296
|
-
}
|
|
297
|
-
return out;
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Writes a single JSON log line to stderr.
|
|
301
|
-
* MCP stdio transport uses stdout; all diagnostics go to stderr.
|
|
302
|
-
*/
|
|
303
|
-
function writeLog(level, event, fields = {}) {
|
|
304
|
-
const payload = buildPayload(level, event, fields);
|
|
305
|
-
process.stderr.write(`${JSON.stringify(payload)}\n`);
|
|
306
|
-
}
|
|
307
|
-
/** Log an informational structured event. */
|
|
308
|
-
function logInfo(event, fields = {}) {
|
|
309
|
-
writeLog("info", event, fields);
|
|
310
|
-
}
|
|
311
|
-
/** Log an error structured event. */
|
|
312
|
-
function logError(event, fields = {}) {
|
|
313
|
-
writeLog("error", event, fields);
|
|
314
|
-
}
|
|
315
|
-
//#endregion
|
|
316
281
|
//#region src/mcp/chii-connection.ts
|
|
317
282
|
/**
|
|
318
283
|
* Production `CdpConnection` backed by the local Chii relay.
|
|
@@ -1316,6 +1281,13 @@ async function injectAndRunBundle(connection, bundleCode, timeoutMs = DEFAULT_TI
|
|
|
1316
1281
|
//#endregion
|
|
1317
1282
|
//#region src/test-runner/relay-worker.ts
|
|
1318
1283
|
/**
|
|
1284
|
+
* Per-file evaluate timeout (ms) for manual-variant files (devtools#741) — a
|
|
1285
|
+
* human is expected to be tapping through a native sheet, so the timeout must
|
|
1286
|
+
* be far longer than the unattended default (60s, rpc.ts DEFAULT_TIMEOUT_MS).
|
|
1287
|
+
* Fixed constant (not configurable in v1) — documented here + CLI `--help`.
|
|
1288
|
+
*/
|
|
1289
|
+
const MANUAL_FILE_TIMEOUT_MS = 5 * 6e4;
|
|
1290
|
+
/**
|
|
1319
1291
|
* Sentinel string embedded in the error message by `injectAndRunBundle` when
|
|
1320
1292
|
* the per-file evaluate race hits the timeout. Used by the retry guard so only
|
|
1321
1293
|
* genuine timeouts get a second attempt — not bundle errors or parse failures.
|
|
@@ -1386,12 +1358,23 @@ async function runTestFilesOverRelay(connection, files, opts) {
|
|
|
1386
1358
|
process.stderr.write(`relay-worker: reconnect failed (${e instanceof Error ? e.message : String(e)}) — continuing, remaining files may fail\n`);
|
|
1387
1359
|
}
|
|
1388
1360
|
};
|
|
1361
|
+
const manualFiles = opts?.manualFiles;
|
|
1362
|
+
const manualTotal = manualFiles?.size ?? 0;
|
|
1363
|
+
let manualIndex = 0;
|
|
1364
|
+
let preflightPermissions;
|
|
1365
|
+
if (files.length > 0) preflightPermissions = await runPermissionPreflight(connection);
|
|
1389
1366
|
try {
|
|
1390
1367
|
for (const file of files) {
|
|
1391
1368
|
if (pendingReconnectCheck) {
|
|
1392
1369
|
await attemptReconnect("next-file");
|
|
1393
1370
|
pendingReconnectCheck = false;
|
|
1394
1371
|
}
|
|
1372
|
+
const isManual = manualFiles?.has(file) === true;
|
|
1373
|
+
if (isManual) {
|
|
1374
|
+
manualIndex += 1;
|
|
1375
|
+
opts?.onManualFile?.(file, manualIndex, manualTotal);
|
|
1376
|
+
}
|
|
1377
|
+
const fileTimeoutMs = isManual ? MANUAL_FILE_TIMEOUT_MS : opts?.timeoutMs;
|
|
1395
1378
|
let fileEntry;
|
|
1396
1379
|
try {
|
|
1397
1380
|
const { code } = await bundleTestFile(file, opts?.bundleOptions);
|
|
@@ -1417,7 +1400,7 @@ async function runTestFilesOverRelay(connection, files, opts) {
|
|
|
1417
1400
|
const attempt = async () => {
|
|
1418
1401
|
let rpcResult;
|
|
1419
1402
|
try {
|
|
1420
|
-
rpcResult = await injectAndRunBundle(connection, code,
|
|
1403
|
+
rpcResult = await injectAndRunBundle(connection, code, fileTimeoutMs);
|
|
1421
1404
|
} catch (e) {
|
|
1422
1405
|
return {
|
|
1423
1406
|
file,
|
|
@@ -1453,6 +1436,7 @@ async function runTestFilesOverRelay(connection, files, opts) {
|
|
|
1453
1436
|
};
|
|
1454
1437
|
}
|
|
1455
1438
|
if ("error" in fileEntry.result && isRelayDisconnectMessage(fileEntry.result.error)) pendingReconnectCheck = true;
|
|
1439
|
+
if (isManual) fileEntry.mode = "manual";
|
|
1456
1440
|
fileResults.push(fileEntry);
|
|
1457
1441
|
}
|
|
1458
1442
|
} finally {
|
|
@@ -1481,7 +1465,8 @@ async function runTestFilesOverRelay(connection, files, opts) {
|
|
|
1481
1465
|
duration: Date.now() - wallStart,
|
|
1482
1466
|
files: fileResults,
|
|
1483
1467
|
totals,
|
|
1484
|
-
captures
|
|
1468
|
+
captures,
|
|
1469
|
+
...preflightPermissions ? { preflight: { permissions: preflightPermissions } } : {}
|
|
1485
1470
|
};
|
|
1486
1471
|
}
|
|
1487
1472
|
/**
|
|
@@ -1564,11 +1549,14 @@ function serializeRelayReport(report, meta) {
|
|
|
1564
1549
|
startedAt: report.startedAt,
|
|
1565
1550
|
duration: report.duration,
|
|
1566
1551
|
totals: report.totals,
|
|
1552
|
+
...report.preflight ? { preflight: report.preflight } : {},
|
|
1567
1553
|
files: report.files.map((f) => {
|
|
1568
1554
|
const file = relativise(meta.projectRoot, f.file);
|
|
1555
|
+
const modeField = f.mode === "manual" ? { mode: "manual" } : {};
|
|
1569
1556
|
if ("error" in f.result) return {
|
|
1570
1557
|
file,
|
|
1571
|
-
error: f.result.error
|
|
1558
|
+
error: f.result.error,
|
|
1559
|
+
...modeField
|
|
1572
1560
|
};
|
|
1573
1561
|
return {
|
|
1574
1562
|
file,
|
|
@@ -1576,33 +1564,65 @@ function serializeRelayReport(report, meta) {
|
|
|
1576
1564
|
passed: f.result.passed,
|
|
1577
1565
|
failed: f.result.failed,
|
|
1578
1566
|
skipped: f.result.skipped,
|
|
1579
|
-
tests: f.result.tests
|
|
1567
|
+
tests: f.result.tests,
|
|
1568
|
+
...modeField
|
|
1580
1569
|
};
|
|
1581
1570
|
})
|
|
1582
1571
|
};
|
|
1583
1572
|
}
|
|
1584
1573
|
/**
|
|
1585
1574
|
* Writes the serialised report to `<dir>/<sdkLine>.<platform>.json`, creating
|
|
1586
|
-
* `dir` if needed. Returns the absolute path written.
|
|
1575
|
+
* `dir` if needed. Returns the absolute path(s) written.
|
|
1587
1576
|
*
|
|
1588
1577
|
* The cell-suffixed filename keeps 2.x and 3.0 (and per-platform) runs as
|
|
1589
1578
|
* distinct artifacts in the same directory; the same cell metadata is also baked
|
|
1590
1579
|
* into the body so a renamed/moved file still carries its provenance.
|
|
1591
1580
|
*
|
|
1581
|
+
* Manual-run provenance (devtools#741): when `report.files` contains ANY
|
|
1582
|
+
* `mode: 'manual'` entry (i.e. this run included `--manual-blocking` files),
|
|
1583
|
+
* the manual-tagged files are written to a SEPARATE
|
|
1584
|
+
* `<dir>/<sdkLine>.<platform>.manual.json` artifact instead of the standard
|
|
1585
|
+
* one — the standard `<sdkLine>.<platform>.json` filename is reserved for the
|
|
1586
|
+
* regular (unattended) files only, so a manual run's presence never mutates
|
|
1587
|
+
* what the unattended-baseline filename means. If ALL files in the run are
|
|
1588
|
+
* regular, only the standard artifact is written (today's behavior,
|
|
1589
|
+
* unchanged). If a run mixes both (regular files + `--manual-blocking`
|
|
1590
|
+
* scheduled last), BOTH artifacts are written — the manual one ALONGSIDE, not
|
|
1591
|
+
* replacing, the standard one.
|
|
1592
|
+
*
|
|
1592
1593
|
* SECRET-HANDLING: the written body contains no relay/secret fields (the schema
|
|
1593
1594
|
* has none). `dir`/`projectRoot` are local filesystem paths, never logged here.
|
|
1594
1595
|
*
|
|
1595
1596
|
* @param report - The core relay run report.
|
|
1596
1597
|
* @param dir - Output directory (created recursively if missing).
|
|
1597
1598
|
* @param meta - Cell axes + projectRoot.
|
|
1598
|
-
* @returns The absolute path
|
|
1599
|
+
* @returns The absolute path(s) written, in order: standard first (if any
|
|
1600
|
+
* regular files ran), then manual (if any manual files ran). At least one
|
|
1601
|
+
* path is always returned when `report.files` is non-empty.
|
|
1599
1602
|
*/
|
|
1600
1603
|
async function writeReportArtifact(report, dir, meta) {
|
|
1601
1604
|
const serialised = serializeRelayReport(report, meta);
|
|
1602
1605
|
await mkdir(dir, { recursive: true });
|
|
1603
|
-
const
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
+
const regularFiles = serialised.files.filter((f) => f.mode !== "manual");
|
|
1607
|
+
const manualFiles = serialised.files.filter((f) => f.mode === "manual");
|
|
1608
|
+
const written = [];
|
|
1609
|
+
if (regularFiles.length > 0 || serialised.files.length === 0) {
|
|
1610
|
+
const outFile = path.join(dir, `${meta.sdkLine}.${meta.platform}.json`);
|
|
1611
|
+
await writeFile(outFile, `${JSON.stringify({
|
|
1612
|
+
...serialised,
|
|
1613
|
+
files: regularFiles
|
|
1614
|
+
}, null, 2)}\n`, "utf8");
|
|
1615
|
+
written.push(outFile);
|
|
1616
|
+
}
|
|
1617
|
+
if (manualFiles.length > 0) {
|
|
1618
|
+
const manualOutFile = path.join(dir, `${meta.sdkLine}.${meta.platform}.manual.json`);
|
|
1619
|
+
await writeFile(manualOutFile, `${JSON.stringify({
|
|
1620
|
+
...serialised,
|
|
1621
|
+
files: manualFiles
|
|
1622
|
+
}, null, 2)}\n`, "utf8");
|
|
1623
|
+
written.push(manualOutFile);
|
|
1624
|
+
}
|
|
1625
|
+
return written;
|
|
1606
1626
|
}
|
|
1607
1627
|
/**
|
|
1608
1628
|
* Writes harvested `__AIT_CAPTURE__` lines to per-category files under `dir`,
|
|
@@ -1688,6 +1708,24 @@ OPTIONS
|
|
|
1688
1708
|
--headless Disable browser auto-open (text QR only)
|
|
1689
1709
|
--project-root <dir> Project root for .ait_relay secret lookup
|
|
1690
1710
|
(default: current working directory)
|
|
1711
|
+
--manual-blocking Run manual-tagged test files (*.manual.ait.test.ts)
|
|
1712
|
+
LAST, after all regular files, with a human present.
|
|
1713
|
+
Before each manual file, the QR dashboard is pushed
|
|
1714
|
+
a step-by-step Korean prompt naming the file + its
|
|
1715
|
+
progress (k/n), and the same line is printed to
|
|
1716
|
+
stdout. Manual files get a 5-minute per-file evaluate
|
|
1717
|
+
timeout (vs. --timeout for everything else) since a
|
|
1718
|
+
human is expected to tap through a native sheet
|
|
1719
|
+
(photo picker, permission dialog, fullscreen ad).
|
|
1720
|
+
Without this flag (default off), *.manual.ait.test.ts
|
|
1721
|
+
files are EXCLUDED from the glob expansion entirely —
|
|
1722
|
+
existing unattended runs are byte-for-byte unaffected.
|
|
1723
|
+
With --report-dir, a run that included manual files
|
|
1724
|
+
ALSO writes <sdkLine>.<platform>.manual.json
|
|
1725
|
+
alongside (never replacing) the standard report, and
|
|
1726
|
+
each manual file's report entry is stamped
|
|
1727
|
+
mode: 'manual' — never diff a manual run against an
|
|
1728
|
+
unattended baseline as if they were equivalent.
|
|
1691
1729
|
--help, -h Show this help message
|
|
1692
1730
|
|
|
1693
1731
|
DESCRIPTION
|
|
@@ -1836,7 +1874,8 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1836
1874
|
"report-dir": { type: "string" },
|
|
1837
1875
|
"no-qr-stdout": { type: "boolean" },
|
|
1838
1876
|
headless: { type: "boolean" },
|
|
1839
|
-
"project-root": { type: "string" }
|
|
1877
|
+
"project-root": { type: "string" },
|
|
1878
|
+
"manual-blocking": { type: "boolean" }
|
|
1840
1879
|
},
|
|
1841
1880
|
allowPositionals: true
|
|
1842
1881
|
});
|
|
@@ -1867,6 +1906,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1867
1906
|
const projectRoot = typeof vals["project-root"] === "string" ? vals["project-root"] : process.cwd();
|
|
1868
1907
|
const reportDir = typeof vals["report-dir"] === "string" ? vals["report-dir"] : void 0;
|
|
1869
1908
|
const suppressQr = shouldSuppressQr(vals["no-qr-stdout"] === true);
|
|
1909
|
+
const manualBlocking = vals["manual-blocking"] === true;
|
|
1870
1910
|
const cellSdkLine = typeof vals["cell-sdk-line"] === "string" ? vals["cell-sdk-line"] : void 0;
|
|
1871
1911
|
const cellPlatform = typeof vals["cell-platform"] === "string" ? vals["cell-platform"] : process.env.AIT_CELL_PLATFORM;
|
|
1872
1912
|
const hasCell = cellSdkLine !== void 0 || cellPlatform !== void 0;
|
|
@@ -1881,13 +1921,16 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1881
1921
|
process.exitCode = 1;
|
|
1882
1922
|
return;
|
|
1883
1923
|
}
|
|
1884
|
-
const
|
|
1885
|
-
if (
|
|
1924
|
+
const discovered = await discoverTestFiles(globs, process.cwd(), { includeManual: manualBlocking });
|
|
1925
|
+
if (discovered.length === 0) {
|
|
1886
1926
|
process.stderr.write(`devtools-test: no test files matched ${globs.join(", ")}\n`);
|
|
1887
1927
|
process.exitCode = 1;
|
|
1888
1928
|
return;
|
|
1889
1929
|
}
|
|
1890
|
-
|
|
1930
|
+
const { regular, manual } = partitionManualTests(discovered);
|
|
1931
|
+
const files = manualBlocking ? [...regular, ...manual] : regular;
|
|
1932
|
+
const manualFileSet = new Set(manual);
|
|
1933
|
+
process.stderr.write(manualBlocking && manual.length > 0 ? `devtools-test: found ${regular.length} regular + ${manual.length} manual (${MANUAL_TEST_SUFFIX}) test file(s)\n` : `devtools-test: found ${files.length} test file(s)\n`);
|
|
1891
1934
|
if (hasCell) process.stderr.write(`devtools-test: injecting __AIT_CELL__ = ${JSON.stringify(cell)}\n`);
|
|
1892
1935
|
const factory = createRelayConnectionFactory({
|
|
1893
1936
|
schemeUrl,
|
|
@@ -1917,15 +1960,26 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1917
1960
|
const report = await runWithConnection(connection, files, {
|
|
1918
1961
|
timeoutMs: evaluateTimeoutMs,
|
|
1919
1962
|
printSummary: true,
|
|
1920
|
-
collectCaptures: reportDir !== void 0
|
|
1963
|
+
collectCaptures: reportDir !== void 0,
|
|
1964
|
+
manualFiles: manualBlocking && manualFileSet.size > 0 ? manualFileSet : void 0,
|
|
1965
|
+
onManualFile: (file, index, total) => {
|
|
1966
|
+
const name = basename(file);
|
|
1967
|
+
process.stdout.write(`수동 단계: ${name} — 폰에서 네이티브 시트가 뜨면 안내에 따라 조작하세요 (${index}/${total})\n`);
|
|
1968
|
+
factory.onManualPrompt?.({
|
|
1969
|
+
file: name,
|
|
1970
|
+
index,
|
|
1971
|
+
total
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1921
1974
|
});
|
|
1975
|
+
if (manualBlocking && manualFileSet.size > 0) factory.onManualPrompt?.(null);
|
|
1922
1976
|
if (reportDir !== void 0) try {
|
|
1923
|
-
const
|
|
1977
|
+
const reportPaths = await writeReportArtifact(report, reportDir, {
|
|
1924
1978
|
sdkLine: cell.sdkLine,
|
|
1925
1979
|
platform: cell.platform,
|
|
1926
1980
|
projectRoot
|
|
1927
1981
|
});
|
|
1928
|
-
process.stderr.write(`devtools-test: wrote report ${reportPath}\n`);
|
|
1982
|
+
for (const reportPath of reportPaths) process.stderr.write(`devtools-test: wrote report ${reportPath}\n`);
|
|
1929
1983
|
const capturePaths = await writeCaptureArtifacts(report.captures, `${reportDir}/.ait-capture`, cell);
|
|
1930
1984
|
if (capturePaths.length > 0) process.stderr.write(`devtools-test: wrote ${capturePaths.length} capture file(s)\n`);
|
|
1931
1985
|
} catch (e) {
|
|
@@ -1966,6 +2020,6 @@ main().catch((e) => {
|
|
|
1966
2020
|
process.exitCode = 1;
|
|
1967
2021
|
});
|
|
1968
2022
|
//#endregion
|
|
1969
|
-
export {
|
|
2023
|
+
export { RELAY_AUTH_REJECT_CLOSE_CODE as n, RELAY_AUTH_REJECT_REASON as r, ChiiCdpConnection as t };
|
|
1970
2024
|
|
|
1971
2025
|
//# sourceMappingURL=bin.js.map
|