@datadisco/qa 0.2.0 → 0.3.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 +51 -4
- package/dist/cli.js +160 -59
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,17 +38,60 @@ to the hosted app and is overridable with `--api-url` / `DATADISCO_API_URL`.
|
|
|
38
38
|
export DATADISCO_API_TOKEN=ddqa_xxx
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
## Works in any CI
|
|
42
|
+
|
|
43
|
+
`report-preview` and `wait` auto-detect the repository, PR number, and head
|
|
44
|
+
SHA from whatever CI they're running in — GitHub Actions gets full support out
|
|
45
|
+
of the box, and every other CI (or a bare git checkout) still works with zero
|
|
46
|
+
flags in the common case:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
DATADISCO_API_TOKEN=ddqa_xxx npx @datadisco/qa report-preview --url https://pr-482.preview.acme.dev
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Each value is resolved through its own chain of fallbacks, in order, stopping
|
|
53
|
+
at the first one that resolves:
|
|
54
|
+
|
|
55
|
+
| Value | Resolution order |
|
|
56
|
+
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
57
|
+
| Repository | `--repo` → `$GITHUB_REPOSITORY` → the git checkout's github.com `origin` remote |
|
|
58
|
+
| Head SHA | `--sha` → the GitHub Actions `pull_request` event payload → `$GITHUB_SHA` → the git checkout's `HEAD` |
|
|
59
|
+
| PR number | `--pr` → GitHub Actions (`GITHUB_REF` / event payload) → CircleCI, Jenkins, Buildkite, Travis CI, or Drone's PR env var → _(unresolved)_ |
|
|
60
|
+
|
|
61
|
+
`--repo`, `--pr`, and `--sha` always override auto-detection when passed.
|
|
62
|
+
|
|
63
|
+
Unlike the repository and head SHA, the PR number is allowed to stay
|
|
64
|
+
unresolved: `report-preview` will still report the preview URL with just the
|
|
65
|
+
repo + head SHA, and the DataDisco server resolves the PR from the commit
|
|
66
|
+
itself. `wait` needs _either_ a PR number or a head SHA to poll by — pass
|
|
67
|
+
`--sha` explicitly if neither auto-detects (e.g. a CI DataDisco doesn't
|
|
68
|
+
recognize yet).
|
|
69
|
+
|
|
70
|
+
| CI | Repo | Head SHA | PR number |
|
|
71
|
+
| ----------------- | ------------------- | ------------------------------------- | ----------------------------------------------------------------- |
|
|
72
|
+
| GitHub Actions | `GITHUB_REPOSITORY` | `pull_request` payload / `GITHUB_SHA` | `GITHUB_REF` / `pull_request` payload |
|
|
73
|
+
| CircleCI | git origin remote | git `HEAD` | `CIRCLE_PULL_REQUEST` / `CIRCLE_PR_NUMBER` |
|
|
74
|
+
| Jenkins | git origin remote | git `HEAD` | `CHANGE_ID` |
|
|
75
|
+
| Buildkite | git origin remote | git `HEAD` | `BUILDKITE_PULL_REQUEST` |
|
|
76
|
+
| Travis CI | git origin remote | git `HEAD` | `TRAVIS_PULL_REQUEST` |
|
|
77
|
+
| Drone | git origin remote | git `HEAD` | `DRONE_PULL_REQUEST` |
|
|
78
|
+
| Bare git checkout | git origin remote | git `HEAD` | not auto-detected — pass `--pr` or rely on server-side resolution |
|
|
79
|
+
|
|
80
|
+
The git `origin` remote fallback only recognizes github.com remotes (SSH or
|
|
81
|
+
HTTPS); for anything else, pass `--repo` explicitly.
|
|
82
|
+
|
|
41
83
|
## Commands
|
|
42
84
|
|
|
43
85
|
### `report-preview`
|
|
44
86
|
|
|
45
87
|
Report the deployed preview URL for a PR head SHA so its pending QA run leaves
|
|
46
|
-
`PENDING_PREVIEW` and starts.
|
|
47
|
-
|
|
88
|
+
`PENDING_PREVIEW` and starts. `--repo`, `--pr`, and `--sha` are auto-detected —
|
|
89
|
+
see [Works in any CI](#works-in-any-ci) — and the PR number is optional: if it
|
|
90
|
+
can't be auto-detected, the server resolves it from the head SHA itself.
|
|
48
91
|
|
|
49
92
|
```sh
|
|
50
93
|
datadisco-qa report-preview --url https://pr-482.preview.acme.dev
|
|
51
|
-
# explicit outside
|
|
94
|
+
# explicit, e.g. outside any auto-detected CI:
|
|
52
95
|
datadisco-qa report-preview \
|
|
53
96
|
--url https://pr-482.preview.acme.dev \
|
|
54
97
|
--repo acme/site --pr 482 --sha "$GIT_SHA"
|
|
@@ -73,10 +116,14 @@ message, instead of burning the retry window.
|
|
|
73
116
|
### `wait`
|
|
74
117
|
|
|
75
118
|
Block until the PR's QA run reaches a verdict, printing each phase transition.
|
|
76
|
-
Polls every 15s; `--timeout` (minutes, default 30) caps the wait.
|
|
119
|
+
Polls every 15s; `--timeout` (minutes, default 30) caps the wait. `--repo` and
|
|
120
|
+
`--pr` are auto-detected the same way as `report-preview`; if the PR number
|
|
121
|
+
isn't detectable, pass `--sha` to poll by head SHA instead.
|
|
77
122
|
|
|
78
123
|
```sh
|
|
79
124
|
datadisco-qa wait --pr 482
|
|
125
|
+
# or, when the PR number isn't auto-detected:
|
|
126
|
+
datadisco-qa wait --sha "$GIT_SHA"
|
|
80
127
|
```
|
|
81
128
|
|
|
82
129
|
**Exit codes** (so pipelines can branch on the outcome):
|
package/dist/cli.js
CHANGED
|
@@ -35,60 +35,137 @@ function assertValidPreviewUrl(url) {
|
|
|
35
35
|
|
|
36
36
|
// src/context.ts
|
|
37
37
|
import { readFileSync } from "fs";
|
|
38
|
+
|
|
39
|
+
// src/git.ts
|
|
40
|
+
import { execFileSync } from "child_process";
|
|
41
|
+
var defaultRunner = (args) => execFileSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
42
|
+
function detectRepoFullName(run2 = defaultRunner) {
|
|
43
|
+
const remote = tryGit(run2, ["remote", "get-url", "origin"]);
|
|
44
|
+
return remote ? parseRepoFromRemote(remote) : void 0;
|
|
45
|
+
}
|
|
46
|
+
function detectHeadSha(run2 = defaultRunner) {
|
|
47
|
+
return tryGit(run2, ["rev-parse", "HEAD"]);
|
|
48
|
+
}
|
|
49
|
+
function parseRepoFromRemote(remote) {
|
|
50
|
+
const match = remote.match(/github\.com[:/]([^/\s]+\/[^/\s]+?)(?:\.git)?$/);
|
|
51
|
+
return match ? match[1] : void 0;
|
|
52
|
+
}
|
|
53
|
+
function tryGit(run2, args) {
|
|
54
|
+
try {
|
|
55
|
+
return run2(args) || void 0;
|
|
56
|
+
} catch {
|
|
57
|
+
return void 0;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/context.ts
|
|
38
62
|
var REPO_PATTERN = /^[^/\s]+\/[^/\s]+$/;
|
|
39
63
|
var PR_REF_PATTERN = /^refs\/pull\/(\d+)\//;
|
|
40
|
-
|
|
64
|
+
var REPO_MISSING_MESSAGE = "Repository not set: pass --repo owner/name (auto-detected from the GitHub Actions env or a github.com origin remote on the git checkout).";
|
|
65
|
+
var HEAD_SHA_MISSING_MESSAGE = "Head SHA not set: pass --sha <sha> (auto-detected from the GitHub Actions env or the git checkout's HEAD).";
|
|
66
|
+
var PR_NUMBER_MISSING_MESSAGE = "Pull request number not set: pass --pr <number> (auto-detected in GitHub Actions, CircleCI, Jenkins, Buildkite, Travis CI, and Drone).";
|
|
67
|
+
var PR_OR_SHA_MISSING_MESSAGE = "Neither a pull request number nor a head SHA could be resolved: pass --pr <number> or --sha <sha> (or run inside a supported CI, or a git checkout).";
|
|
68
|
+
function resolveRunContext(input = {}) {
|
|
69
|
+
const context = resolveRunContextAllowingShaOnly(input);
|
|
70
|
+
if (context.prNumber === void 0) {
|
|
71
|
+
throw new ConfigError(PR_NUMBER_MISSING_MESSAGE);
|
|
72
|
+
}
|
|
73
|
+
return { ...context, prNumber: context.prNumber };
|
|
74
|
+
}
|
|
75
|
+
function resolveRunContextAllowingShaOnly(input = {}) {
|
|
41
76
|
const env = input.env ?? process.env;
|
|
42
77
|
const payload = loadEventPayload(input, env);
|
|
78
|
+
const headSha = resolveHeadSha(input, env, payload) ?? detectHeadSha(input.gitRunner);
|
|
79
|
+
if (!headSha) {
|
|
80
|
+
throw new ConfigError(HEAD_SHA_MISSING_MESSAGE);
|
|
81
|
+
}
|
|
43
82
|
return {
|
|
44
83
|
repo: resolveRepo(input, env),
|
|
45
|
-
prNumber: resolvePrNumber(input, env, payload)
|
|
84
|
+
prNumber: resolvePrNumber(input, env, payload),
|
|
85
|
+
headSha
|
|
46
86
|
};
|
|
47
87
|
}
|
|
48
|
-
function
|
|
88
|
+
function resolvePrContext(input = {}) {
|
|
49
89
|
const env = input.env ?? process.env;
|
|
50
90
|
const payload = loadEventPayload(input, env);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
91
|
+
const prNumber = resolvePrNumber(input, env, payload);
|
|
92
|
+
const knownHeadSha = resolveHeadSha(input, env, payload);
|
|
93
|
+
const headSha = prNumber === void 0 ? knownHeadSha ?? detectHeadSha(input.gitRunner) : knownHeadSha;
|
|
94
|
+
if (prNumber === void 0 && !headSha) {
|
|
95
|
+
throw new ConfigError(PR_OR_SHA_MISSING_MESSAGE);
|
|
96
|
+
}
|
|
97
|
+
return { repo: resolveRepo(input, env), prNumber, headSha };
|
|
56
98
|
}
|
|
57
99
|
function resolveRepo(input, env = {}) {
|
|
58
|
-
const repo = input.repo ?? env.GITHUB_REPOSITORY;
|
|
100
|
+
const repo = input.repo ?? env.GITHUB_REPOSITORY ?? detectRepoFullName(input.gitRunner);
|
|
59
101
|
if (!repo) {
|
|
60
|
-
throw new ConfigError(
|
|
61
|
-
"Repository not set: pass --repo owner/name (auto-detected from $GITHUB_REPOSITORY in GitHub Actions)."
|
|
62
|
-
);
|
|
102
|
+
throw new ConfigError(REPO_MISSING_MESSAGE);
|
|
63
103
|
}
|
|
64
104
|
if (!REPO_PATTERN.test(repo)) {
|
|
65
105
|
throw new ConfigError(`--repo must look like "owner/name", got "${repo}".`);
|
|
66
106
|
}
|
|
67
107
|
return repo;
|
|
68
108
|
}
|
|
109
|
+
function resolveHeadSha(input, env = {}, payload) {
|
|
110
|
+
return input.sha ?? payload.headSha ?? env.GITHUB_SHA;
|
|
111
|
+
}
|
|
69
112
|
function resolvePrNumber(input, env = {}, payload) {
|
|
70
|
-
const candidate = input.pr ?? parsePrFromRef(env.GITHUB_REF) ?? payload.prNumber;
|
|
71
|
-
|
|
72
|
-
if (candidate === void 0 || !Number.isInteger(prNumber) || prNumber <= 0) {
|
|
73
|
-
throw new ConfigError(
|
|
74
|
-
"Pull request number not set: pass --pr <number> (auto-detected from the pull_request event in GitHub Actions)."
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
return prNumber;
|
|
113
|
+
const candidate = input.pr ?? parsePrFromRef(env.GITHUB_REF) ?? payload.prNumber ?? resolvePrNumberFromCiEnv(env);
|
|
114
|
+
return toPositiveInteger(candidate);
|
|
78
115
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
"Head SHA not set: pass --sha <sha> (the commit the preview was built from)."
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
return headSha;
|
|
116
|
+
function toPositiveInteger(candidate) {
|
|
117
|
+
if (candidate === void 0) return void 0;
|
|
118
|
+
const parsed = Number(candidate);
|
|
119
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
87
120
|
}
|
|
88
121
|
function parsePrFromRef(ref) {
|
|
89
122
|
const match = ref?.match(PR_REF_PATTERN);
|
|
90
123
|
return match ? match[1] : void 0;
|
|
91
124
|
}
|
|
125
|
+
var CI_PR_ENV_SOURCES = [
|
|
126
|
+
{
|
|
127
|
+
// CircleCI: CIRCLE_PULL_REQUEST is a URL (https://github.com/o/r/pull/123);
|
|
128
|
+
// CIRCLE_PR_NUMBER is a plain number and is checked as a fallback.
|
|
129
|
+
ci: "CircleCI",
|
|
130
|
+
read: (env) => numericPrCandidate(trailingNumber(env.CIRCLE_PULL_REQUEST)) ?? numericPrCandidate(env.CIRCLE_PR_NUMBER)
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
// Jenkins (Multibranch/PR builders): CHANGE_ID is the PR number.
|
|
134
|
+
ci: "Jenkins",
|
|
135
|
+
read: (env) => numericPrCandidate(env.CHANGE_ID)
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
// Buildkite: BUILDKITE_PULL_REQUEST is "false" on non-PR builds.
|
|
139
|
+
ci: "Buildkite",
|
|
140
|
+
read: (env) => numericPrCandidate(env.BUILDKITE_PULL_REQUEST)
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
// Travis CI: TRAVIS_PULL_REQUEST is "false" on non-PR builds.
|
|
144
|
+
ci: "Travis CI",
|
|
145
|
+
read: (env) => numericPrCandidate(env.TRAVIS_PULL_REQUEST)
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
// Drone: DRONE_PULL_REQUEST is the PR number, unset outside PR builds.
|
|
149
|
+
ci: "Drone",
|
|
150
|
+
read: (env) => numericPrCandidate(env.DRONE_PULL_REQUEST)
|
|
151
|
+
}
|
|
152
|
+
];
|
|
153
|
+
function resolvePrNumberFromCiEnv(env) {
|
|
154
|
+
for (const source of CI_PR_ENV_SOURCES) {
|
|
155
|
+
const candidate = source.read(env);
|
|
156
|
+
if (candidate !== void 0) return candidate;
|
|
157
|
+
}
|
|
158
|
+
return void 0;
|
|
159
|
+
}
|
|
160
|
+
function trailingNumber(value) {
|
|
161
|
+
const match = value?.match(/(\d+)\/?$/);
|
|
162
|
+
return match?.[1];
|
|
163
|
+
}
|
|
164
|
+
function numericPrCandidate(value) {
|
|
165
|
+
if (!value || value === "false") return void 0;
|
|
166
|
+
const parsed = Number(value);
|
|
167
|
+
return Number.isInteger(parsed) && parsed > 0 ? value : void 0;
|
|
168
|
+
}
|
|
92
169
|
function loadEventPayload(input, env = {}) {
|
|
93
170
|
const read = input.readEventPayload ?? (() => readEventFile(env.GITHUB_EVENT_PATH));
|
|
94
171
|
const raw = safeRead(read);
|
|
@@ -120,6 +197,21 @@ function numberOrUndefined(value) {
|
|
|
120
197
|
function stringOrUndefined(value) {
|
|
121
198
|
return typeof value === "string" ? value : void 0;
|
|
122
199
|
}
|
|
200
|
+
function describeContextTarget(context) {
|
|
201
|
+
if (context.prNumber !== void 0) return `${context.repo}#${context.prNumber}`;
|
|
202
|
+
if (context.headSha) return `${context.repo}@${context.headSha}`;
|
|
203
|
+
return context.repo;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/legacy-server.ts
|
|
207
|
+
var LEGACY_SERVER_BAD_REQUEST_STATUS = 400;
|
|
208
|
+
function isUnresolvedPrOnLegacyServer(options) {
|
|
209
|
+
return !options.prNumberKnown && options.status === LEGACY_SERVER_BAD_REQUEST_STATUS && !options.code;
|
|
210
|
+
}
|
|
211
|
+
var LEGACY_SERVER_PR_HINT = "if this is an older DataDisco server, pass --pr <number> explicitly";
|
|
212
|
+
function withLegacyServerPrHint(error) {
|
|
213
|
+
return `${error} (${LEGACY_SERVER_PR_HINT})`;
|
|
214
|
+
}
|
|
123
215
|
|
|
124
216
|
// src/api.ts
|
|
125
217
|
var REPORT_PREVIEW_PATH = "/api/qa/report-preview";
|
|
@@ -159,7 +251,7 @@ async function reportPreview(config, params, fetchImpl) {
|
|
|
159
251
|
async function getRunStatus(config, params, fetchImpl) {
|
|
160
252
|
const url = new URL(joinUrl(config.apiUrl, RUN_STATUS_PATH));
|
|
161
253
|
url.searchParams.set("repo", params.repo);
|
|
162
|
-
url
|
|
254
|
+
setRunLookupParam(url, params);
|
|
163
255
|
const response = await fetchImpl(url.toString(), {
|
|
164
256
|
headers: { authorization: `Bearer ${config.apiToken}` }
|
|
165
257
|
});
|
|
@@ -167,7 +259,19 @@ async function getRunStatus(config, params, fetchImpl) {
|
|
|
167
259
|
if (response.ok && isRunStatus(payload.run)) {
|
|
168
260
|
return { ok: true, run: payload.run };
|
|
169
261
|
}
|
|
170
|
-
return {
|
|
262
|
+
return {
|
|
263
|
+
ok: false,
|
|
264
|
+
status: response.status,
|
|
265
|
+
error: errorMessage(payload),
|
|
266
|
+
code: stringOrUndefined2(payload.code)
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function setRunLookupParam(url, params) {
|
|
270
|
+
if (params.prNumber !== void 0) {
|
|
271
|
+
url.searchParams.set("prNumber", String(params.prNumber));
|
|
272
|
+
} else if (params.headSha) {
|
|
273
|
+
url.searchParams.set("headSha", params.headSha);
|
|
274
|
+
}
|
|
171
275
|
}
|
|
172
276
|
function joinUrl(base, path) {
|
|
173
277
|
return `${base.replace(/\/+$/, "")}${path}`;
|
|
@@ -301,7 +405,7 @@ var POLL_INTERVAL_MS = 15e3;
|
|
|
301
405
|
var MINUTE_MS = 6e4;
|
|
302
406
|
async function waitForVerdict(api, context, deps, timeoutMs) {
|
|
303
407
|
const deadline = deps.now() + timeoutMs;
|
|
304
|
-
deps.logger.info(`Waiting for QA verdict on ${context
|
|
408
|
+
deps.logger.info(`Waiting for QA verdict on ${describeContextTarget(context)}\u2026`);
|
|
305
409
|
let lastStatus;
|
|
306
410
|
while (true) {
|
|
307
411
|
const outcome = await api.getRunStatus(context);
|
|
@@ -316,7 +420,9 @@ async function waitForVerdict(api, context, deps, timeoutMs) {
|
|
|
316
420
|
return verdict.exitCode;
|
|
317
421
|
}
|
|
318
422
|
} else if (outcome.status !== 404) {
|
|
319
|
-
deps.logger.error(
|
|
423
|
+
deps.logger.error(
|
|
424
|
+
`Could not read QA run status: ${describeRunStatusFailure(outcome, context)}`
|
|
425
|
+
);
|
|
320
426
|
return 1;
|
|
321
427
|
}
|
|
322
428
|
if (deps.now() >= deadline) {
|
|
@@ -328,6 +434,16 @@ async function waitForVerdict(api, context, deps, timeoutMs) {
|
|
|
328
434
|
await deps.sleep(POLL_INTERVAL_MS);
|
|
329
435
|
}
|
|
330
436
|
}
|
|
437
|
+
function describeRunStatusFailure(outcome, context) {
|
|
438
|
+
if (isUnresolvedPrOnLegacyServer({
|
|
439
|
+
prNumberKnown: context.prNumber !== void 0,
|
|
440
|
+
status: outcome.status,
|
|
441
|
+
code: outcome.code
|
|
442
|
+
})) {
|
|
443
|
+
return withLegacyServerPrHint(outcome.error);
|
|
444
|
+
}
|
|
445
|
+
return outcome.error;
|
|
446
|
+
}
|
|
331
447
|
var DEFAULT_TIMEOUT_FLAG = {
|
|
332
448
|
flag: "--timeout",
|
|
333
449
|
defaultMinutes: DEFAULT_TIMEOUT_MINUTES
|
|
@@ -359,7 +475,7 @@ async function runReportPreview(options, deps = defaultDeps) {
|
|
|
359
475
|
}
|
|
360
476
|
assertValidPreviewUrl(options.url);
|
|
361
477
|
const config = resolveConfig(options);
|
|
362
|
-
const context =
|
|
478
|
+
const context = resolveRunContextAllowingShaOnly(options);
|
|
363
479
|
const waitRunTimeoutMs = resolveTimeoutMs(options.waitRunTimeout, WAIT_RUN_TIMEOUT_SPEC);
|
|
364
480
|
const api = deps.createApi(config);
|
|
365
481
|
const outcome = await reportPreviewUntilRunExists(api, context, options.url, deps, {
|
|
@@ -367,11 +483,11 @@ async function runReportPreview(options, deps = defaultDeps) {
|
|
|
367
483
|
waitRunTimeoutMs
|
|
368
484
|
});
|
|
369
485
|
if (!outcome.ok) {
|
|
370
|
-
deps.logger.error(`Could not report preview URL: ${describeFailure(outcome)}`);
|
|
486
|
+
deps.logger.error(`Could not report preview URL: ${describeFailure(outcome, context)}`);
|
|
371
487
|
return 1;
|
|
372
488
|
}
|
|
373
489
|
deps.logger.success(
|
|
374
|
-
`Preview reported for ${context
|
|
490
|
+
`Preview reported for ${describeContextTarget(context)} \u2014 QA run ${outcome.runId} queued.`
|
|
375
491
|
);
|
|
376
492
|
return 0;
|
|
377
493
|
}
|
|
@@ -385,7 +501,14 @@ async function reportPreviewUntilRunExists(api, context, url, deps, policy) {
|
|
|
385
501
|
function isRunNotCreatedYet(outcome) {
|
|
386
502
|
return !outcome.ok && outcome.status === RUN_NOT_CREATED_YET_STATUS && outcome.retryable !== false;
|
|
387
503
|
}
|
|
388
|
-
function describeFailure(outcome) {
|
|
504
|
+
function describeFailure(outcome, context) {
|
|
505
|
+
if (isUnresolvedPrOnLegacyServer({
|
|
506
|
+
prNumberKnown: context.prNumber !== void 0,
|
|
507
|
+
status: outcome.status,
|
|
508
|
+
code: outcome.code
|
|
509
|
+
})) {
|
|
510
|
+
return withLegacyServerPrHint(outcome.error);
|
|
511
|
+
}
|
|
389
512
|
return outcome.code ? `${outcome.error} [${outcome.code}]` : outcome.error;
|
|
390
513
|
}
|
|
391
514
|
async function retryReportPreviewUntilRunExists(api, context, url, deps, waitRunTimeoutMs) {
|
|
@@ -410,28 +533,6 @@ function withRunNeverAppearedHint(error) {
|
|
|
410
533
|
return `${error} (gave up waiting \u2014 the QA run never appeared)`;
|
|
411
534
|
}
|
|
412
535
|
|
|
413
|
-
// src/git.ts
|
|
414
|
-
import { execFileSync } from "child_process";
|
|
415
|
-
var defaultRunner = (args) => execFileSync("git", args, { encoding: "utf8" }).trim();
|
|
416
|
-
function detectRepoFullName(run2 = defaultRunner) {
|
|
417
|
-
const remote = tryGit(run2, ["remote", "get-url", "origin"]);
|
|
418
|
-
return remote ? parseRepoFromRemote(remote) : void 0;
|
|
419
|
-
}
|
|
420
|
-
function detectHeadSha(run2 = defaultRunner) {
|
|
421
|
-
return tryGit(run2, ["rev-parse", "HEAD"]);
|
|
422
|
-
}
|
|
423
|
-
function parseRepoFromRemote(remote) {
|
|
424
|
-
const match = remote.match(/github\.com[:/]([^/\s]+\/[^/\s]+?)(?:\.git)?$/);
|
|
425
|
-
return match ? match[1] : void 0;
|
|
426
|
-
}
|
|
427
|
-
function tryGit(run2, args) {
|
|
428
|
-
try {
|
|
429
|
-
return run2(args) || void 0;
|
|
430
|
-
} catch {
|
|
431
|
-
return void 0;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
536
|
// src/tunnel-provider.ts
|
|
436
537
|
import localtunnel from "localtunnel";
|
|
437
538
|
async function openTunnel(port) {
|
|
@@ -505,7 +606,7 @@ cli.command("report-preview", "Report a PR's preview-deploy URL so its pending Q
|
|
|
505
606
|
"--wait-run-timeout <minutes>",
|
|
506
607
|
"How long to retry while the QA run hasn't been created yet (default 12)"
|
|
507
608
|
).action((options) => run(() => runReportPreview(options)));
|
|
508
|
-
cli.command("wait", "Block until the PR's QA run reaches a verdict").option("--repo <owner/name>", "Repository the PR belongs to").option("--pr <number>", "Pull request number").option("--timeout <minutes>", "Give up after this many minutes (default 30)").option("--api-url <url>", "DataDisco API base URL").option("--api-token <token>", "Workspace API token (or DATADISCO_API_TOKEN)").action((options) => run(() => runWait(options)));
|
|
609
|
+
cli.command("wait", "Block until the PR's QA run reaches a verdict").option("--repo <owner/name>", "Repository the PR belongs to").option("--pr <number>", "Pull request number").option("--sha <sha>", "Head SHA to look up the run by, if the PR number isn't known").option("--timeout <minutes>", "Give up after this many minutes (default 30)").option("--api-url <url>", "DataDisco API base URL").option("--api-token <token>", "Workspace API token (or DATADISCO_API_TOKEN)").action((options) => run(() => runWait(options)));
|
|
509
610
|
cli.command("tunnel", "Expose a local build and start a QA run against it").option("--port <port>", "Local port serving the build").option("--url <url>", "Use an externally managed tunnel URL instead of --port").option("--repo <owner/name>", "Repository (defaults to the git origin remote)").option("--pr <number>", "Pull request number").option("--sha <sha>", "Head SHA (defaults to the local git HEAD)").option("--no-wait", "Queue the run and exit without waiting for a verdict").option("--timeout <minutes>", "Give up waiting after this many minutes").option("--api-url <url>", "DataDisco API base URL").option("--api-token <token>", "Workspace API token (or DATADISCO_API_TOKEN)").action((options) => run(() => runTunnel(options)));
|
|
510
611
|
async function run(command) {
|
|
511
612
|
try {
|
package/package.json
CHANGED