@desplega.ai/qa-use 2.14.1 → 2.15.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/README.md +23 -0
- package/dist/lib/env/index.d.ts +13 -0
- package/dist/lib/env/index.d.ts.map +1 -1
- package/dist/lib/env/index.js +35 -0
- package/dist/lib/env/index.js.map +1 -1
- package/dist/lib/env/localhost.d.ts +22 -0
- package/dist/lib/env/localhost.d.ts.map +1 -0
- package/dist/lib/env/localhost.js +49 -0
- package/dist/lib/env/localhost.js.map +1 -0
- package/dist/lib/env/paths.d.ts +27 -0
- package/dist/lib/env/paths.d.ts.map +1 -0
- package/dist/lib/env/paths.js +42 -0
- package/dist/lib/env/paths.js.map +1 -0
- package/dist/lib/env/sessions.d.ts +55 -0
- package/dist/lib/env/sessions.d.ts.map +1 -0
- package/dist/lib/env/sessions.js +128 -0
- package/dist/lib/env/sessions.js.map +1 -0
- package/dist/lib/tunnel/errors.d.ts +61 -0
- package/dist/lib/tunnel/errors.d.ts.map +1 -0
- package/dist/lib/tunnel/errors.js +152 -0
- package/dist/lib/tunnel/errors.js.map +1 -0
- package/dist/lib/tunnel/index.d.ts.map +1 -1
- package/dist/lib/tunnel/index.js +26 -11
- package/dist/lib/tunnel/index.js.map +1 -1
- package/dist/lib/tunnel/registry.d.ts +182 -0
- package/dist/lib/tunnel/registry.d.ts.map +1 -0
- package/dist/lib/tunnel/registry.js +561 -0
- package/dist/lib/tunnel/registry.js.map +1 -0
- package/dist/package.json +1 -1
- package/dist/src/cli/commands/browser/_detached.d.ts +27 -0
- package/dist/src/cli/commands/browser/_detached.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/_detached.js +422 -0
- package/dist/src/cli/commands/browser/_detached.js.map +1 -0
- package/dist/src/cli/commands/browser/close.d.ts +7 -0
- package/dist/src/cli/commands/browser/close.d.ts.map +1 -1
- package/dist/src/cli/commands/browser/close.js +101 -5
- package/dist/src/cli/commands/browser/close.js.map +1 -1
- package/dist/src/cli/commands/browser/create.d.ts +7 -0
- package/dist/src/cli/commands/browser/create.d.ts.map +1 -1
- package/dist/src/cli/commands/browser/create.js +233 -25
- package/dist/src/cli/commands/browser/create.js.map +1 -1
- package/dist/src/cli/commands/browser/index.d.ts.map +1 -1
- package/dist/src/cli/commands/browser/index.js +3 -0
- package/dist/src/cli/commands/browser/index.js.map +1 -1
- package/dist/src/cli/commands/browser/run.d.ts.map +1 -1
- package/dist/src/cli/commands/browser/run.js +13 -6
- package/dist/src/cli/commands/browser/run.js.map +1 -1
- package/dist/src/cli/commands/browser/status.d.ts +4 -0
- package/dist/src/cli/commands/browser/status.d.ts.map +1 -1
- package/dist/src/cli/commands/browser/status.js +85 -3
- package/dist/src/cli/commands/browser/status.js.map +1 -1
- package/dist/src/cli/commands/doctor.d.ts +45 -0
- package/dist/src/cli/commands/doctor.d.ts.map +1 -0
- package/dist/src/cli/commands/doctor.js +267 -0
- package/dist/src/cli/commands/doctor.js.map +1 -0
- package/dist/src/cli/commands/test/run.d.ts.map +1 -1
- package/dist/src/cli/commands/test/run.js +29 -18
- package/dist/src/cli/commands/test/run.js.map +1 -1
- package/dist/src/cli/commands/tunnel/close.d.ts +18 -0
- package/dist/src/cli/commands/tunnel/close.d.ts.map +1 -0
- package/dist/src/cli/commands/tunnel/close.js +154 -0
- package/dist/src/cli/commands/tunnel/close.js.map +1 -0
- package/dist/src/cli/commands/tunnel/index.d.ts +6 -0
- package/dist/src/cli/commands/tunnel/index.d.ts.map +1 -0
- package/dist/src/cli/commands/tunnel/index.js +17 -0
- package/dist/src/cli/commands/tunnel/index.js.map +1 -0
- package/dist/src/cli/commands/tunnel/ls.d.ts +10 -0
- package/dist/src/cli/commands/tunnel/ls.d.ts.map +1 -0
- package/dist/src/cli/commands/tunnel/ls.js +89 -0
- package/dist/src/cli/commands/tunnel/ls.js.map +1 -0
- package/dist/src/cli/commands/tunnel/start.d.ts +15 -0
- package/dist/src/cli/commands/tunnel/start.d.ts.map +1 -0
- package/dist/src/cli/commands/tunnel/start.js +65 -0
- package/dist/src/cli/commands/tunnel/start.js.map +1 -0
- package/dist/src/cli/commands/tunnel/status.d.ts +8 -0
- package/dist/src/cli/commands/tunnel/status.d.ts.map +1 -0
- package/dist/src/cli/commands/tunnel/status.js +58 -0
- package/dist/src/cli/commands/tunnel/status.js.map +1 -0
- package/dist/src/cli/generated/docs-content.d.ts +1 -1
- package/dist/src/cli/generated/docs-content.d.ts.map +1 -1
- package/dist/src/cli/generated/docs-content.js +157 -100
- package/dist/src/cli/generated/docs-content.js.map +1 -1
- package/dist/src/cli/index.js +8 -0
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/cli/lib/browser.d.ts +25 -9
- package/dist/src/cli/lib/browser.d.ts.map +1 -1
- package/dist/src/cli/lib/browser.js +73 -42
- package/dist/src/cli/lib/browser.js.map +1 -1
- package/dist/src/cli/lib/cli-entry.d.ts +40 -0
- package/dist/src/cli/lib/cli-entry.d.ts.map +1 -0
- package/dist/src/cli/lib/cli-entry.js +65 -0
- package/dist/src/cli/lib/cli-entry.js.map +1 -0
- package/dist/src/cli/lib/startup-sweep.d.ts +45 -0
- package/dist/src/cli/lib/startup-sweep.d.ts.map +1 -0
- package/dist/src/cli/lib/startup-sweep.js +246 -0
- package/dist/src/cli/lib/startup-sweep.js.map +1 -0
- package/dist/src/cli/lib/tunnel-banner.d.ts +33 -0
- package/dist/src/cli/lib/tunnel-banner.d.ts.map +1 -0
- package/dist/src/cli/lib/tunnel-banner.js +55 -0
- package/dist/src/cli/lib/tunnel-banner.js.map +1 -0
- package/dist/src/cli/lib/tunnel-error-hint.d.ts +20 -0
- package/dist/src/cli/lib/tunnel-error-hint.d.ts.map +1 -0
- package/dist/src/cli/lib/tunnel-error-hint.js +48 -0
- package/dist/src/cli/lib/tunnel-error-hint.js.map +1 -0
- package/dist/src/cli/lib/tunnel-option.d.ts +27 -0
- package/dist/src/cli/lib/tunnel-option.d.ts.map +1 -0
- package/dist/src/cli/lib/tunnel-option.js +77 -0
- package/dist/src/cli/lib/tunnel-option.js.map +1 -0
- package/dist/src/cli/lib/tunnel-resolve.d.ts +42 -0
- package/dist/src/cli/lib/tunnel-resolve.d.ts.map +1 -0
- package/dist/src/cli/lib/tunnel-resolve.js +72 -0
- package/dist/src/cli/lib/tunnel-resolve.js.map +1 -0
- package/lib/env/index.ts +51 -0
- package/lib/env/localhost.test.ts +63 -0
- package/lib/env/localhost.ts +51 -0
- package/lib/env/paths.ts +46 -0
- package/lib/env/sessions.test.ts +109 -0
- package/lib/env/sessions.ts +155 -0
- package/lib/tunnel/errors.test.ts +105 -0
- package/lib/tunnel/errors.ts +169 -0
- package/lib/tunnel/index.ts +26 -11
- package/lib/tunnel/registry.test.ts +420 -0
- package/lib/tunnel/registry.ts +646 -0
- package/package.json +1 -1
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qa-use doctor - Scan `~/.qa-use/sessions/*.json` and `~/.qa-use/tunnels/*.json`
|
|
3
|
+
* for orphaned entries (owning PID no longer alive), and reap them.
|
|
4
|
+
*
|
|
5
|
+
* Responsibilities:
|
|
6
|
+
* 1. Session files: for each, check `process.kill(pid, 0)`. If dead:
|
|
7
|
+
* - Remove the PID file.
|
|
8
|
+
* - Force-release the registry handle if the session referenced a tunnel target.
|
|
9
|
+
* - Best-effort backend session-end call.
|
|
10
|
+
* 2. Tunnel files: for each entry with `pid`, check pid liveness. If dead,
|
|
11
|
+
* zero refcount and run registry teardown (via `forceClose`).
|
|
12
|
+
* 3. `--dry-run` mode prints the reap plan without acting.
|
|
13
|
+
* 4. Exits non-zero when any action was needed (so CI/scripts can notice).
|
|
14
|
+
*
|
|
15
|
+
* This is the manual counterpart to the bounded startup sweep in
|
|
16
|
+
* `src/cli/lib/startup-sweep.ts` — `doctor` does more thorough work and is
|
|
17
|
+
* permitted to make network calls.
|
|
18
|
+
*/
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import { Command } from 'commander';
|
|
22
|
+
import { sessionsDir, tunnelsDir } from '../../../lib/env/paths.js';
|
|
23
|
+
import { isPidAlive, listSessionRecords, removeSessionRecord, } from '../../../lib/env/sessions.js';
|
|
24
|
+
import { tunnelRegistry } from '../../../lib/tunnel/registry.js';
|
|
25
|
+
import { createBrowserClient, loadConfig } from '../lib/config.js';
|
|
26
|
+
import { error, formatError, info, success, warning } from '../lib/output.js';
|
|
27
|
+
function listRawTunnelRecords() {
|
|
28
|
+
const dir = tunnelsDir();
|
|
29
|
+
let files;
|
|
30
|
+
try {
|
|
31
|
+
files = fs.readdirSync(dir);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const name of files) {
|
|
38
|
+
if (!name.endsWith('.json') || name.endsWith('.tmp'))
|
|
39
|
+
continue;
|
|
40
|
+
try {
|
|
41
|
+
const raw = fs.readFileSync(path.join(dir, name), 'utf8');
|
|
42
|
+
const parsed = JSON.parse(raw);
|
|
43
|
+
if (typeof parsed.id !== 'string' || typeof parsed.target !== 'string')
|
|
44
|
+
continue;
|
|
45
|
+
out.push(parsed);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
/* skip unreadable */
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
function findStaleSessions() {
|
|
54
|
+
const records = listSessionRecords();
|
|
55
|
+
const stale = [];
|
|
56
|
+
for (const record of records) {
|
|
57
|
+
if (!isPidAlive(record.pid)) {
|
|
58
|
+
stale.push({ id: record.id, pid: record.pid, target: record.target });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return stale;
|
|
62
|
+
}
|
|
63
|
+
function findStaleTunnels() {
|
|
64
|
+
const records = listRawTunnelRecords();
|
|
65
|
+
const stale = [];
|
|
66
|
+
for (const record of records) {
|
|
67
|
+
if (typeof record.pid !== 'number' || !isPidAlive(record.pid)) {
|
|
68
|
+
stale.push({
|
|
69
|
+
id: record.id ?? '?',
|
|
70
|
+
target: record.target ?? '?',
|
|
71
|
+
pid: record.pid ?? 0,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return stale;
|
|
76
|
+
}
|
|
77
|
+
async function reapSession(stale, backendClient) {
|
|
78
|
+
// Remove the PID file.
|
|
79
|
+
removeSessionRecord(stale.id);
|
|
80
|
+
// Force-release any tunnel owned by this session's target.
|
|
81
|
+
if (stale.target) {
|
|
82
|
+
try {
|
|
83
|
+
await tunnelRegistry.forceClose(stale.target);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
/* best-effort */
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Best-effort backend session-end call.
|
|
90
|
+
if (backendClient) {
|
|
91
|
+
try {
|
|
92
|
+
await backendClient.deleteSession(stale.id);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
/* best-effort */
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function reapTunnel(stale) {
|
|
100
|
+
// Force-close removes the on-disk record (by target-hash) and (if we own
|
|
101
|
+
// it) tears down the manager. For dead foreign PIDs this removes the
|
|
102
|
+
// file we expect. However, if the on-disk `id` was drifted from the
|
|
103
|
+
// canonical hash of `target` (e.g. legacy data, test fixtures), the
|
|
104
|
+
// file-by-id fallback below catches it.
|
|
105
|
+
if (stale.target && stale.target !== '?') {
|
|
106
|
+
try {
|
|
107
|
+
await tunnelRegistry.forceClose(stale.target);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
/* best-effort; fall through to manual unlink by id */
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Fallback: unlink by raw id. Safe because `forceClose` already removed
|
|
114
|
+
// whatever the canonical-hash file name was.
|
|
115
|
+
if (stale.id && stale.id !== '?') {
|
|
116
|
+
try {
|
|
117
|
+
fs.unlinkSync(path.join(tunnelsDir(), `${stale.id}.json`));
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
/* already gone */
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Internal entry point — exported for tests. Returns `{ reaped, plan }` so
|
|
126
|
+
* tests can assert the number of actions without parsing stdout.
|
|
127
|
+
*/
|
|
128
|
+
export async function runDoctor(options) {
|
|
129
|
+
const staleSessions = findStaleSessions();
|
|
130
|
+
const staleTunnels = findStaleTunnels();
|
|
131
|
+
if (options.dryRun) {
|
|
132
|
+
if (options.json) {
|
|
133
|
+
console.log(JSON.stringify({
|
|
134
|
+
dryRun: true,
|
|
135
|
+
staleSessions,
|
|
136
|
+
staleTunnels,
|
|
137
|
+
actionsTaken: 0,
|
|
138
|
+
}, null, 2));
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
if (staleSessions.length === 0 && staleTunnels.length === 0) {
|
|
142
|
+
console.log(success('Nothing to do'));
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
console.log(info('Dry run — no files removed.'));
|
|
146
|
+
if (staleSessions.length > 0) {
|
|
147
|
+
console.log(info(`Would reap ${staleSessions.length} stale session(s):`));
|
|
148
|
+
for (const s of staleSessions) {
|
|
149
|
+
console.log(` - ${s.id} (pid=${s.pid}, target=${s.target})`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (staleTunnels.length > 0) {
|
|
153
|
+
console.log(info(`Would reap ${staleTunnels.length} stale tunnel(s):`));
|
|
154
|
+
for (const t of staleTunnels) {
|
|
155
|
+
console.log(` - ${t.id} (pid=${t.pid}, target=${t.target})`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return { staleSessions, staleTunnels, actionsTaken: 0 };
|
|
161
|
+
}
|
|
162
|
+
// Non-dry-run: actually reap.
|
|
163
|
+
let backendClient = null;
|
|
164
|
+
if (staleSessions.length > 0) {
|
|
165
|
+
try {
|
|
166
|
+
const config = await loadConfig();
|
|
167
|
+
if (config.api_key) {
|
|
168
|
+
backendClient = createBrowserClient(config);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
/* best-effort — proceed without backend calls */
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
let actionsTaken = 0;
|
|
176
|
+
for (const s of staleSessions) {
|
|
177
|
+
await reapSession(s, backendClient);
|
|
178
|
+
actionsTaken += 1;
|
|
179
|
+
}
|
|
180
|
+
for (const t of staleTunnels) {
|
|
181
|
+
await reapTunnel(t);
|
|
182
|
+
actionsTaken += 1;
|
|
183
|
+
}
|
|
184
|
+
if (options.json) {
|
|
185
|
+
console.log(JSON.stringify({
|
|
186
|
+
dryRun: false,
|
|
187
|
+
staleSessions,
|
|
188
|
+
staleTunnels,
|
|
189
|
+
actionsTaken,
|
|
190
|
+
}, null, 2));
|
|
191
|
+
}
|
|
192
|
+
else if (actionsTaken === 0) {
|
|
193
|
+
console.log(success('Nothing to do'));
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
if (staleSessions.length > 0) {
|
|
197
|
+
console.log(success(`Reaped ${staleSessions.length} stale session(s)`));
|
|
198
|
+
for (const s of staleSessions) {
|
|
199
|
+
console.log(` - ${s.id} (pid=${s.pid})`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (staleTunnels.length > 0) {
|
|
203
|
+
console.log(success(`Reaped ${staleTunnels.length} stale tunnel(s)`));
|
|
204
|
+
for (const t of staleTunnels) {
|
|
205
|
+
console.log(` - ${t.id} (pid=${t.pid}, target=${t.target})`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return { staleSessions, staleTunnels, actionsTaken };
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Detect orphaned files that could not be reached. A warning — but not a
|
|
213
|
+
* failure — when seen in the wild.
|
|
214
|
+
*/
|
|
215
|
+
function checkTunnelsDirExists() {
|
|
216
|
+
try {
|
|
217
|
+
fs.accessSync(tunnelsDir());
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function checkSessionsDirExists() {
|
|
225
|
+
try {
|
|
226
|
+
fs.accessSync(sessionsDir());
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
export const doctorCommand = new Command('doctor')
|
|
234
|
+
.description('Scan for and reap stale session / tunnel files')
|
|
235
|
+
.option('--dry-run', 'Report what would be reaped without acting')
|
|
236
|
+
.option('--json', 'Output as JSON')
|
|
237
|
+
.action(async (options) => {
|
|
238
|
+
try {
|
|
239
|
+
// Surface dir-missing state only in JSON mode; in human mode the
|
|
240
|
+
// underlying reapers already handle empty dirs cleanly.
|
|
241
|
+
if (options.json &&
|
|
242
|
+
!checkSessionsDirExists() &&
|
|
243
|
+
!checkTunnelsDirExists() &&
|
|
244
|
+
!options.dryRun) {
|
|
245
|
+
console.log(JSON.stringify({
|
|
246
|
+
dryRun: false,
|
|
247
|
+
staleSessions: [],
|
|
248
|
+
staleTunnels: [],
|
|
249
|
+
actionsTaken: 0,
|
|
250
|
+
}, null, 2));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const result = await runDoctor(options);
|
|
254
|
+
// Silence "did nothing" cases with exit 0; any actual action exits
|
|
255
|
+
// with a non-zero status so scripts notice.
|
|
256
|
+
if (!options.dryRun && result.actionsTaken > 0) {
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
console.log(error(`Doctor failed: ${formatError(err)}`));
|
|
262
|
+
process.exit(2);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
// Silence lint when `warning` isn't used — keep import for future output paths.
|
|
266
|
+
void warning;
|
|
267
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/test/run.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/test/run.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqCpC,eAAO,MAAM,UAAU,SAoNrB,CAAC"}
|
|
@@ -2,33 +2,33 @@
|
|
|
2
2
|
* qa-use test run - Run test definitions
|
|
3
3
|
*/
|
|
4
4
|
import { Command } from 'commander';
|
|
5
|
-
import { getAgentSessionId } from '../../../../lib/env/index.js';
|
|
5
|
+
import { getAgentSessionId, getTunnelModeFromConfig } from '../../../../lib/env/index.js';
|
|
6
6
|
import { getEffectiveWsUrl, startBrowserWithTunnel, stopBrowserWithTunnel, } from '../../lib/browser.js';
|
|
7
7
|
import { createApiClient, loadConfig } from '../../lib/config.js';
|
|
8
8
|
import { downloadAssets } from '../../lib/download.js';
|
|
9
9
|
import { applyVariableOverrides, loadAllTests, loadTestWithDeps, resolveTestPath, } from '../../lib/loader.js';
|
|
10
10
|
import { addDownloadedFile, clearDownloadedFiles, clearStepScreenshots, error, formatError, info, printDownloadedFilesSummary, printPersistenceNote, printScreenshotsSummary, success, } from '../../lib/output.js';
|
|
11
11
|
import { runTest } from '../../lib/runner.js';
|
|
12
|
+
import { addTunnelOption } from '../../lib/tunnel-option.js';
|
|
13
|
+
import { resolveTunnelFlag, resolveTunnelMode } from '../../lib/tunnel-resolve.js';
|
|
12
14
|
function collectVars(value, previous) {
|
|
13
15
|
const [key, val] = value.split('=');
|
|
14
16
|
return { ...previous, [key]: val };
|
|
15
17
|
}
|
|
16
|
-
export const runCommand = new Command('run')
|
|
18
|
+
export const runCommand = addTunnelOption(new Command('run')
|
|
17
19
|
.description('Run a test definition')
|
|
18
20
|
.argument('[test]', 'Test name or path (e.g., auth/login)')
|
|
19
21
|
.option('--id <uuid>', 'Run cloud test by ID instead of local file')
|
|
20
22
|
.option('--all', 'Run all tests in test directory')
|
|
21
23
|
.option('--persist', 'Save test to cloud after run')
|
|
22
|
-
.option('--
|
|
23
|
-
.option('--
|
|
24
|
-
.option('--ws-url <url>', 'Use existing tunneled browser (from `browser create --tunnel`)')
|
|
24
|
+
.option('--headful', 'Show browser window (use with --tunnel on)')
|
|
25
|
+
.option('--ws-url <url>', 'Use existing tunneled browser (from `browser create --tunnel on`)')
|
|
25
26
|
.option('--screenshots', 'Capture screenshots at each step')
|
|
26
27
|
.option('--download', 'Download all assets (screenshots, recordings, HAR) to /tmp/qa-use/downloads/')
|
|
27
28
|
.option('--var <key=value...>', 'Variable overrides', collectVars, {})
|
|
28
29
|
.option('--app-config-id <uuid>', 'App config ID to use')
|
|
29
30
|
.option('--timeout <seconds>', 'Timeout in seconds', '300')
|
|
30
|
-
.option('--verbose', 'Output raw SSE event data for debugging')
|
|
31
|
-
.action(async (test, options) => {
|
|
31
|
+
.option('--verbose', 'Output raw SSE event data for debugging')).action(async (test, options) => {
|
|
32
32
|
try {
|
|
33
33
|
const config = await loadConfig();
|
|
34
34
|
// Check API key
|
|
@@ -74,30 +74,41 @@ export const runCommand = new Command('run')
|
|
|
74
74
|
let browserSession = null;
|
|
75
75
|
let wsUrl = options.wsUrl;
|
|
76
76
|
try {
|
|
77
|
+
// Resolve tri-state tunnel flag: CLI > config > default 'auto'.
|
|
78
|
+
const resolvedTunnelMode = resolveTunnelFlag(options.tunnel, getTunnelModeFromConfig());
|
|
79
|
+
// Figure out the effective base URL for the Phase-2 auto decision:
|
|
80
|
+
// explicit --var base_url wins; otherwise the first test definition's
|
|
81
|
+
// `variables.base_url` (if any). Undefined disables auto.
|
|
82
|
+
const varBaseUrl = options.var?.base_url ??
|
|
83
|
+
testDefinitions?.[0]?.variables?.base_url;
|
|
84
|
+
const tunnelDecision = resolveTunnelMode(resolvedTunnelMode, varBaseUrl, config.api_url);
|
|
85
|
+
const tunnelOn = tunnelDecision === 'on';
|
|
77
86
|
// Validate flag combinations
|
|
78
|
-
if (options.wsUrl && (
|
|
79
|
-
console.log(error('Cannot use --ws-url with --tunnel or --headful'));
|
|
87
|
+
if (options.wsUrl && (tunnelOn || options.headful)) {
|
|
88
|
+
console.log(error('Cannot use --ws-url with --tunnel on or --headful'));
|
|
80
89
|
process.exit(1);
|
|
81
90
|
}
|
|
82
91
|
// Check if running in development mode (localhost API)
|
|
83
92
|
const isDevelopment = process.env.NODE_ENV === 'development' ||
|
|
84
93
|
config.api_url?.includes('localhost') ||
|
|
85
94
|
config.api_url?.includes('127.0.0.1');
|
|
86
|
-
// Handle --headful without --tunnel
|
|
87
|
-
if (options.headful && !
|
|
88
|
-
console.log(error('--headful requires --tunnel
|
|
89
|
-
console.log(' Use: qa-use test run <name> --tunnel --headful');
|
|
90
|
-
console.log(' Or for headless local browser: qa-use test run <name> --tunnel');
|
|
95
|
+
// Handle --headful without --tunnel on
|
|
96
|
+
if (options.headful && !tunnelOn && !isDevelopment) {
|
|
97
|
+
console.log(error('--headful requires --tunnel on'));
|
|
98
|
+
console.log(' Use: qa-use test run <name> --tunnel on --headful');
|
|
99
|
+
console.log(' Or for headless local browser: qa-use test run <name> --tunnel on');
|
|
91
100
|
process.exit(1);
|
|
92
101
|
}
|
|
93
|
-
if (
|
|
102
|
+
if (tunnelOn) {
|
|
94
103
|
const headless = !options.headful;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
browserSession = await startBrowserWithTunnel(
|
|
104
|
+
// Banner + triage-hint come from startBrowserWithTunnel itself;
|
|
105
|
+
// don't double-print an ad-hoc "Starting local browser..." line.
|
|
106
|
+
browserSession = await startBrowserWithTunnel(varBaseUrl, {
|
|
98
107
|
headless,
|
|
99
108
|
apiKey: config.api_key,
|
|
100
109
|
sessionIndex: 0,
|
|
110
|
+
tunnelMode: 'on', // decision already made above
|
|
111
|
+
apiUrl: config.api_url,
|
|
101
112
|
});
|
|
102
113
|
wsUrl = getEffectiveWsUrl(browserSession);
|
|
103
114
|
console.log(success('Browser ready, running test...'));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../../../../src/cli/commands/test/run.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../../../../src/cli/commands/test/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qa-use tunnel close - Force-tear down a tunnel entry in the registry.
|
|
3
|
+
*
|
|
4
|
+
* Phase 3 scope: accept a target URL or hash, resolve the record, and
|
|
5
|
+
* force-release it (tears down in-process handle if we own it, otherwise
|
|
6
|
+
* removes the stale-looking registry file).
|
|
7
|
+
*
|
|
8
|
+
* Phase 4 extends this with session-PID cross-referencing: when the
|
|
9
|
+
* owning PID belongs to a detached browser session (`~/.qa-use/sessions/
|
|
10
|
+
* <id>.json`), SIGTERM that child with a 5 s timeout, SIGKILL fallback,
|
|
11
|
+
* and best-effort backend session-end. That path is scaffolded here but
|
|
12
|
+
* guarded by the sessions-dir tolerance the plan calls for: if the dir
|
|
13
|
+
* doesn't exist or is empty, we skip the session lookup and fall back
|
|
14
|
+
* to a plain force-release + file removal.
|
|
15
|
+
*/
|
|
16
|
+
import { Command } from 'commander';
|
|
17
|
+
export declare const closeCommand: Command;
|
|
18
|
+
//# sourceMappingURL=close.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"close.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/tunnel/close.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyEpC,eAAO,MAAM,YAAY,SAiFrB,CAAC"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qa-use tunnel close - Force-tear down a tunnel entry in the registry.
|
|
3
|
+
*
|
|
4
|
+
* Phase 3 scope: accept a target URL or hash, resolve the record, and
|
|
5
|
+
* force-release it (tears down in-process handle if we own it, otherwise
|
|
6
|
+
* removes the stale-looking registry file).
|
|
7
|
+
*
|
|
8
|
+
* Phase 4 extends this with session-PID cross-referencing: when the
|
|
9
|
+
* owning PID belongs to a detached browser session (`~/.qa-use/sessions/
|
|
10
|
+
* <id>.json`), SIGTERM that child with a 5 s timeout, SIGKILL fallback,
|
|
11
|
+
* and best-effort backend session-end. That path is scaffolded here but
|
|
12
|
+
* guarded by the sessions-dir tolerance the plan calls for: if the dir
|
|
13
|
+
* doesn't exist or is empty, we skip the session lookup and fall back
|
|
14
|
+
* to a plain force-release + file removal.
|
|
15
|
+
*/
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { Command } from 'commander';
|
|
19
|
+
import { sessionsDir } from '../../../../lib/env/paths.js';
|
|
20
|
+
import { tunnelRegistry } from '../../../../lib/tunnel/registry.js';
|
|
21
|
+
import { error, formatError, info, success, warning } from '../../lib/output.js';
|
|
22
|
+
const SIGTERM_GRACE_MS = 5_000;
|
|
23
|
+
function resolveRecord(identifier) {
|
|
24
|
+
if (/^[a-f0-9]{10}$/i.test(identifier)) {
|
|
25
|
+
return tunnelRegistry.getByHash(identifier.toLowerCase());
|
|
26
|
+
}
|
|
27
|
+
return tunnelRegistry.get(identifier);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Read sessions that reference this tunnel target, if any. Tolerates a
|
|
31
|
+
* missing / empty directory (common in Phase 3 since sessions don't
|
|
32
|
+
* exist yet).
|
|
33
|
+
*/
|
|
34
|
+
function findSessionsForTarget(target) {
|
|
35
|
+
const dir = sessionsDir();
|
|
36
|
+
let files;
|
|
37
|
+
try {
|
|
38
|
+
files = fs.readdirSync(dir);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
const out = [];
|
|
44
|
+
for (const name of files) {
|
|
45
|
+
if (!name.endsWith('.json'))
|
|
46
|
+
continue;
|
|
47
|
+
try {
|
|
48
|
+
const raw = fs.readFileSync(path.join(dir, name), 'utf8');
|
|
49
|
+
const parsed = JSON.parse(raw);
|
|
50
|
+
if (parsed.target === target)
|
|
51
|
+
out.push(parsed);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
/* skip unreadable */
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
function isPidAlive(pid) {
|
|
60
|
+
try {
|
|
61
|
+
process.kill(pid, 0);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (err.code === 'EPERM')
|
|
66
|
+
return true;
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function sigtermWithGrace(pid, graceMs) {
|
|
71
|
+
try {
|
|
72
|
+
process.kill(pid, 'SIGTERM');
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Already gone.
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
const deadline = Date.now() + graceMs;
|
|
79
|
+
while (Date.now() < deadline) {
|
|
80
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
81
|
+
if (!isPidAlive(pid))
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
export const closeCommand = new Command('close')
|
|
87
|
+
.description('Force-close a tunnel (by target URL or hash ID)')
|
|
88
|
+
.argument('<target-or-hash>', 'Target URL or tunnel hash ID')
|
|
89
|
+
.option('--json', 'Output as JSON')
|
|
90
|
+
.action(async (identifier, options) => {
|
|
91
|
+
try {
|
|
92
|
+
const record = resolveRecord(identifier);
|
|
93
|
+
if (!record) {
|
|
94
|
+
if (options.json) {
|
|
95
|
+
console.log(JSON.stringify({ closed: false, reason: 'not_found' }));
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.log(warning(`No active tunnel matching ${identifier}`));
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Phase-4 hook: if a detached browser session owns this tunnel,
|
|
103
|
+
// SIGTERM it so the child releases the registry handle cleanly.
|
|
104
|
+
// Phase 3 tolerates an empty sessions dir.
|
|
105
|
+
const sessions = findSessionsForTarget(record.target);
|
|
106
|
+
const reaped = [];
|
|
107
|
+
for (const session of sessions) {
|
|
108
|
+
if (!session.pid || !isPidAlive(session.pid))
|
|
109
|
+
continue;
|
|
110
|
+
const termed = await sigtermWithGrace(session.pid, SIGTERM_GRACE_MS);
|
|
111
|
+
if (!termed) {
|
|
112
|
+
try {
|
|
113
|
+
process.kill(session.pid, 'SIGKILL');
|
|
114
|
+
console.error(warning(`Session PID ${session.pid} did not exit on SIGTERM within ${SIGTERM_GRACE_MS}ms; sent SIGKILL`));
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
/* already gone */
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
reaped.push(session.pid);
|
|
121
|
+
}
|
|
122
|
+
// Force-release regardless of PID-reap outcome. In Phase 3 this is
|
|
123
|
+
// the primary path.
|
|
124
|
+
await tunnelRegistry.forceClose(record.target);
|
|
125
|
+
// Non-session holders (e.g. `tunnel start --hold`, foreground
|
|
126
|
+
// `test run`) are intentionally NOT SIGTERM'd. But after the
|
|
127
|
+
// registry file is gone, the user has no clue the holder is still
|
|
128
|
+
// around. Emit a one-line hint if (a) the registry record pointed
|
|
129
|
+
// at a pid, (b) that pid is still alive, and (c) no session was
|
|
130
|
+
// matched above (otherwise the "Reaped ..." line covers it).
|
|
131
|
+
const lingeringHolderPid = record.pid && reaped.length === 0 && isPidAlive(record.pid) ? record.pid : undefined;
|
|
132
|
+
if (options.json) {
|
|
133
|
+
const payload = { closed: true, target: record.target, reapedPids: reaped };
|
|
134
|
+
if (lingeringHolderPid !== undefined) {
|
|
135
|
+
payload.holderPid = lingeringHolderPid;
|
|
136
|
+
}
|
|
137
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
console.log(success(`Tunnel closed: ${record.target}`));
|
|
141
|
+
if (reaped.length > 0) {
|
|
142
|
+
console.log(info(`Reaped ${reaped.length} session process(es): ${reaped.join(', ')}`));
|
|
143
|
+
}
|
|
144
|
+
if (lingeringHolderPid !== undefined) {
|
|
145
|
+
console.error(info(`Note: holder process PID ${lingeringHolderPid} still running — send SIGTERM (kill ${lingeringHolderPid}) to terminate.`));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
console.log(error(`Failed to close tunnel: ${formatError(err)}`));
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
//# sourceMappingURL=close.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"close.js","sourceRoot":"","sources":["../../../../../src/cli/commands/tunnel/close.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/tunnel/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,wBAAgB,aAAa,IAAI,OAAO,CAWvC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qa-use tunnel - Manage background tunnels
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { closeCommand } from './close.js';
|
|
6
|
+
import { lsCommand } from './ls.js';
|
|
7
|
+
import { startCommand } from './start.js';
|
|
8
|
+
import { statusCommand } from './status.js';
|
|
9
|
+
export function tunnelCommand() {
|
|
10
|
+
const cmd = new Command('tunnel').description('Inspect and manage the qa-use tunnel registry (localhost → public URL)');
|
|
11
|
+
cmd.addCommand(startCommand);
|
|
12
|
+
cmd.addCommand(lsCommand);
|
|
13
|
+
cmd.addCommand(statusCommand);
|
|
14
|
+
cmd.addCommand(closeCommand);
|
|
15
|
+
return cmd;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/cli/commands/tunnel/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,UAAU,aAAa;IAC3B,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,CAC3C,wEAAwE,CACzE,CAAC;IAEF,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IAC7B,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAC1B,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAC9B,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IAE7B,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qa-use tunnel ls - List active tunnels in the registry.
|
|
3
|
+
*
|
|
4
|
+
* Scans `~/.qa-use/tunnels/*.json`, reconciles against owning PID, and
|
|
5
|
+
* renders a table (or JSON) with target, public URL, refcount, TTL
|
|
6
|
+
* remaining.
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
export declare const lsCommand: Command;
|
|
10
|
+
//# sourceMappingURL=ls.d.ts.map
|