@juangadm/pre-post 1.0.0 → 1.2.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 +99 -20
- package/dist/baseline.d.ts +208 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +413 -0
- package/dist/baseline.js.map +1 -0
- package/dist/bin/cli.js +87 -42
- package/dist/bin/cli.js.map +1 -1
- package/dist/browser.d.ts +31 -3
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +291 -97
- package/dist/browser.js.map +1 -1
- package/dist/commands/compare.d.ts.map +1 -1
- package/dist/commands/compare.js +21 -3
- package/dist/commands/compare.js.map +1 -1
- package/dist/commands/detect.d.ts +5 -0
- package/dist/commands/detect.d.ts.map +1 -1
- package/dist/commands/detect.js +4 -1
- package/dist/commands/detect.js.map +1 -1
- package/dist/commands/doctor.d.ts +32 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +90 -9
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/login.js +1 -1
- package/dist/commands/pr.d.ts +4 -0
- package/dist/commands/pr.d.ts.map +1 -1
- package/dist/commands/pr.js +157 -51
- package/dist/commands/pr.js.map +1 -1
- package/dist/commands/prune.js +1 -1
- package/dist/commands/prune.js.map +1 -1
- package/dist/comparison.d.ts +104 -0
- package/dist/comparison.d.ts.map +1 -0
- package/dist/comparison.js +241 -0
- package/dist/comparison.js.map +1 -0
- package/dist/config.d.ts +12 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +25 -3
- package/dist/config.js.map +1 -1
- package/dist/deployments.d.ts +71 -0
- package/dist/deployments.d.ts.map +1 -0
- package/dist/deployments.js +204 -0
- package/dist/deployments.js.map +1 -0
- package/dist/diff-pool.d.ts.map +1 -1
- package/dist/diff-pool.js +5 -2
- package/dist/diff-pool.js.map +1 -1
- package/dist/diff-worker.d.ts +3 -0
- package/dist/diff-worker.d.ts.map +1 -1
- package/dist/diff-worker.js +1 -0
- package/dist/diff-worker.js.map +1 -1
- package/dist/diff.d.ts +10 -0
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +117 -7
- package/dist/diff.js.map +1 -1
- package/dist/doctor.d.ts +42 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +73 -7
- package/dist/doctor.js.map +1 -1
- package/dist/git.d.ts +41 -3
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +103 -7
- package/dist/git.js.map +1 -1
- package/dist/github.d.ts +92 -2
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +191 -20
- package/dist/github.js.map +1 -1
- package/dist/landing.d.ts +48 -0
- package/dist/landing.d.ts.map +1 -0
- package/dist/landing.js +93 -0
- package/dist/landing.js.map +1 -0
- package/dist/pkg.d.ts +9 -0
- package/dist/pkg.d.ts.map +1 -1
- package/dist/pkg.js +14 -0
- package/dist/pkg.js.map +1 -1
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +59 -28
- package/dist/report.js.map +1 -1
- package/dist/routes.d.ts +27 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +98 -23
- package/dist/routes.js.map +1 -1
- package/dist/run.d.ts +88 -8
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +224 -28
- package/dist/run.js.map +1 -1
- package/dist/sameness.d.ts +91 -0
- package/dist/sameness.d.ts.map +1 -0
- package/dist/sameness.js +157 -0
- package/dist/sameness.js.map +1 -0
- package/dist/sessions.d.ts.map +1 -1
- package/dist/sessions.js +7 -1
- package/dist/sessions.js.map +1 -1
- package/dist/shift.d.ts +67 -0
- package/dist/shift.d.ts.map +1 -0
- package/dist/shift.js +184 -0
- package/dist/shift.js.map +1 -0
- package/dist/types.d.ts +83 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -1
- package/dist/url.d.ts +12 -0
- package/dist/url.d.ts.map +1 -0
- package/dist/url.js +30 -0
- package/dist/url.js.map +1 -0
- package/package.json +1 -1
- package/skill/SKILL.md +40 -7
package/dist/baseline.js
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serve the PR's base commit locally, as the "Pre" side.
|
|
3
|
+
*
|
|
4
|
+
* This is the baseline that always works: it needs no deployment, no
|
|
5
|
+
* production URL and no network, so it is what keeps pre-post usable inside a
|
|
6
|
+
* sandbox — a cloud agent container, a CI job, or any box behind an egress
|
|
7
|
+
* allowlist. It is also the most honest comparison available, because both
|
|
8
|
+
* sides then render in the same environment with the same fonts and the same
|
|
9
|
+
* browser, leaving the diff as the only variable.
|
|
10
|
+
*
|
|
11
|
+
* The cost is time: a checkout, an install and a dev-server boot. So callers
|
|
12
|
+
* reach for it after a reachable deployment, not before one.
|
|
13
|
+
*/
|
|
14
|
+
import { spawn, execFileSync } from 'child_process';
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import net from 'net';
|
|
17
|
+
import os from 'os';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import { NeedsHumanError } from './errors.js';
|
|
20
|
+
import { devScript, readPackage } from './pkg.js';
|
|
21
|
+
import { findAppRoots } from './routes.js';
|
|
22
|
+
const NPM = { bin: 'npm', install: ['install'], retry: { argv: ['install', '--legacy-peer-deps'], when: isPeerConflict }, run: (s, a) => ['run', s, '--', ...a] };
|
|
23
|
+
const PNPM = { bin: 'pnpm', install: ['install', '--prefer-offline'], run: (s, a) => ['run', s, ...a] };
|
|
24
|
+
const YARN = { bin: 'yarn', install: ['install'], run: (s, a) => ['run', s, ...a] };
|
|
25
|
+
const BUN = { bin: 'bun', install: ['install'], run: (s, a) => ['run', s, ...a] };
|
|
26
|
+
const MANAGERS = [
|
|
27
|
+
{ lockfile: 'pnpm-lock.yaml', pm: PNPM },
|
|
28
|
+
{ lockfile: 'yarn.lock', pm: YARN },
|
|
29
|
+
{ lockfile: 'bun.lockb', pm: BUN },
|
|
30
|
+
{ lockfile: 'package-lock.json', pm: NPM },
|
|
31
|
+
];
|
|
32
|
+
const BY_NAME = { npm: NPM, pnpm: PNPM, yarn: YARN, bun: BUN };
|
|
33
|
+
/**
|
|
34
|
+
* Which package manager this tree *declares*, from its `packageManager` field
|
|
35
|
+
* or its lockfile. Defaults to npm.
|
|
36
|
+
*
|
|
37
|
+
* The field wins over the lockfile because it is the deliberate statement: a
|
|
38
|
+
* repository mid-migration can carry two lockfiles, but it names one manager.
|
|
39
|
+
*/
|
|
40
|
+
export function detectPackageManager(dir, repoRoot) {
|
|
41
|
+
for (const candidate of [dir, repoRoot].filter(Boolean)) {
|
|
42
|
+
const field = readPackage(candidate)?.packageManager;
|
|
43
|
+
const named = typeof field === 'string' ? BY_NAME[field.split('@')[0].trim()] : undefined;
|
|
44
|
+
if (named)
|
|
45
|
+
return named;
|
|
46
|
+
for (const { lockfile, pm } of MANAGERS) {
|
|
47
|
+
if (fs.existsSync(path.join(candidate, lockfile)))
|
|
48
|
+
return pm;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return NPM;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Is `bin` runnable here?
|
|
55
|
+
*
|
|
56
|
+
* A PATH scan rather than a `--version` subprocess: this answers before every
|
|
57
|
+
* local baseline, and spawning a process to learn that a process cannot be
|
|
58
|
+
* spawned is the wrong shape.
|
|
59
|
+
*/
|
|
60
|
+
export function onPath(bin, env = process.env) {
|
|
61
|
+
const dirs = (env.PATH ?? '').split(path.delimiter).filter(Boolean);
|
|
62
|
+
const exts = process.platform === 'win32'
|
|
63
|
+
? (env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean)
|
|
64
|
+
: [''];
|
|
65
|
+
return dirs.some(dir => exts.some(ext => {
|
|
66
|
+
// throwIfNoEntry keeps the common case (a miss) from constructing an
|
|
67
|
+
// exception; the catch stays for the rarer EACCES/ELOOP.
|
|
68
|
+
try {
|
|
69
|
+
return fs.statSync(path.join(dir, bin + ext), { throwIfNoEntry: false })?.isFile() ?? false;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The manager to actually run: what the repository declares, if it is installed.
|
|
78
|
+
*
|
|
79
|
+
* Declaring pnpm does not put pnpm on PATH. On a real machine it was not there,
|
|
80
|
+
* and the local baseline — the fallback that exists for when nothing is
|
|
81
|
+
* deployed — died on `pnpm install` with no hint that the missing piece was
|
|
82
|
+
* pnpm itself. The same shell could still build the CLI, so the tool ran while
|
|
83
|
+
* its last resort could not.
|
|
84
|
+
*
|
|
85
|
+
* npm ships with Node, so it is the fallback with the best odds of being
|
|
86
|
+
* present. It will not honour a pnpm or yarn lockfile, so the baseline it
|
|
87
|
+
* installs can drift from the locked versions — worth saying out loud, and
|
|
88
|
+
* still far better than no baseline at all.
|
|
89
|
+
*/
|
|
90
|
+
export function resolvePackageManager(dir, repoRoot, has = onPath) {
|
|
91
|
+
const declared = detectPackageManager(dir, repoRoot);
|
|
92
|
+
if (has(declared.bin))
|
|
93
|
+
return { pm: declared, declared };
|
|
94
|
+
return { pm: has(NPM.bin) ? NPM : null, declared };
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The directory to actually serve.
|
|
98
|
+
*
|
|
99
|
+
* The app root is detected from the files a branch changed, which answers
|
|
100
|
+
* "which routes moved" — not "which package can be served". A PR touching only
|
|
101
|
+
* the CLI resolves to the repo root, which has no dev script even though the
|
|
102
|
+
* site next door does. So prefer the detected directory and otherwise take the
|
|
103
|
+
* nearest package that can start a server.
|
|
104
|
+
*/
|
|
105
|
+
export function servableDir(treeRoot, appPrefix) {
|
|
106
|
+
const preferred = appPrefix ? path.join(treeRoot, appPrefix) : treeRoot;
|
|
107
|
+
const preferredScript = devScript(preferred);
|
|
108
|
+
if (preferredScript)
|
|
109
|
+
return { dir: preferred, script: preferredScript };
|
|
110
|
+
// Deepest first: a workspace app beats the repo root that merely contains it.
|
|
111
|
+
for (const dir of findAppRoots(treeRoot).sort((a, b) => b.length - a.length)) {
|
|
112
|
+
const script = devScript(dir);
|
|
113
|
+
if (script)
|
|
114
|
+
return { dir, script };
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
/** Local env files, in the order a framework would layer them. */
|
|
119
|
+
const ENV_FILES = ['.env', '.env.local', '.env.development', '.env.development.local'];
|
|
120
|
+
/**
|
|
121
|
+
* Copy the working checkout's local env files into a throwaway worktree.
|
|
122
|
+
*
|
|
123
|
+
* .env files are gitignored, so a fresh worktree of the base commit has none.
|
|
124
|
+
* An app that needs one to boot then either never starts or serves an error
|
|
125
|
+
* page — and an error page diffed against the real branch reports a wall of
|
|
126
|
+
* changes the PR never made, which is worse than reporting no baseline at all.
|
|
127
|
+
*
|
|
128
|
+
* Only ever writes inside `to`, and returns names, never values: these files
|
|
129
|
+
* are exactly the ones that hold secrets.
|
|
130
|
+
*/
|
|
131
|
+
export function copyEnvFiles(from, to, appPrefix) {
|
|
132
|
+
const copied = [];
|
|
133
|
+
const root = path.resolve(to);
|
|
134
|
+
for (const dir of appPrefix ? ['', appPrefix] : ['']) {
|
|
135
|
+
for (const name of ENV_FILES) {
|
|
136
|
+
const src = path.join(from, dir, name);
|
|
137
|
+
const dest = path.resolve(root, dir, name);
|
|
138
|
+
// The worktree is the only place this may write.
|
|
139
|
+
if (dest !== root && !dest.startsWith(root + path.sep))
|
|
140
|
+
continue;
|
|
141
|
+
if (!fs.existsSync(src) || fs.existsSync(dest))
|
|
142
|
+
continue;
|
|
143
|
+
try {
|
|
144
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
145
|
+
fs.copyFileSync(src, dest);
|
|
146
|
+
copied.push(path.posix.join(dir, name));
|
|
147
|
+
}
|
|
148
|
+
catch { /* best effort: a missing env file is not fatal on its own */ }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return copied;
|
|
152
|
+
}
|
|
153
|
+
/** An OS-assigned free port. */
|
|
154
|
+
export function freePort() {
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
const srv = net.createServer();
|
|
157
|
+
srv.once('error', reject);
|
|
158
|
+
srv.listen(0, '127.0.0.1', () => {
|
|
159
|
+
const port = srv.address().port;
|
|
160
|
+
srv.close(() => resolve(port));
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/** Keep the end of the output: managers put the diagnosis last. */
|
|
165
|
+
function tail(text, lines = 24) {
|
|
166
|
+
const kept = text.replace(/\s+$/, '').split('\n').slice(-lines);
|
|
167
|
+
return kept.join('\n').trim();
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Room for the noisiest install that still works.
|
|
171
|
+
*
|
|
172
|
+
* Node's default is 1 MiB per stream, and exceeding it kills the child with
|
|
173
|
+
* ENOBUFS — which this code would read as a failed install. A verbose set of
|
|
174
|
+
* postinstall scripts must not be the reason a baseline cannot be built, and
|
|
175
|
+
* the buffer is transient and freed as soon as the tail is taken.
|
|
176
|
+
*/
|
|
177
|
+
const MAX_INSTALL_OUTPUT = 64 * 1024 * 1024;
|
|
178
|
+
const runInstall = (bin, argv, cwd, timeoutMs) => {
|
|
179
|
+
try {
|
|
180
|
+
execFileSync(bin, argv, { cwd, timeout: Math.max(1, timeoutMs), encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: MAX_INSTALL_OUTPUT });
|
|
181
|
+
// Only a failure has anything to explain, so the successful install's log
|
|
182
|
+
// is dropped rather than split into lines nothing will read.
|
|
183
|
+
return { argv, ok: true, output: '' };
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
const e = err;
|
|
187
|
+
const output = [e.stdout, e.stderr].map(part => (part ? String(part) : '')).join('');
|
|
188
|
+
return { argv, ok: false, output: tail(output || e.message || '') };
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* Does this look like npm refusing to resolve a peer range?
|
|
193
|
+
*
|
|
194
|
+
* Matched on npm's own error code rather than on prose, so it does not depend
|
|
195
|
+
* on the wording of a given npm release. The second pattern is the same
|
|
196
|
+
* failure as reported by older npm, which does not always print the code.
|
|
197
|
+
*/
|
|
198
|
+
export function isPeerConflict(output) {
|
|
199
|
+
return /ERESOLVE/i.test(output) || /could not resolve dependency/i.test(output);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Install this app's dependencies, retrying once past a peer-dependency wall.
|
|
203
|
+
*
|
|
204
|
+
* The retry is deliberately narrow: only on the specific failure that a looser
|
|
205
|
+
* resolver would not have had, and only for a manager that has a looser mode.
|
|
206
|
+
* A baseline installed with `--legacy-peer-deps` is not a perfect reproduction
|
|
207
|
+
* of the branch's own install — but the alternative is no baseline at all, and
|
|
208
|
+
* the comparison this tool exists to make is between two renders of the same
|
|
209
|
+
* app, not between two dependency trees.
|
|
210
|
+
*/
|
|
211
|
+
export function installDeps(pm, cwd, timeoutMs, run = runInstall) {
|
|
212
|
+
const deadline = Date.now() + timeoutMs;
|
|
213
|
+
const first = run(pm.bin, pm.install, cwd, timeoutMs);
|
|
214
|
+
if (first.ok || !pm.retry?.when(first.output))
|
|
215
|
+
return { ok: first.ok, attempts: [first] };
|
|
216
|
+
// The remainder of the original budget, not a fresh one: a first attempt
|
|
217
|
+
// that burned the clock must not let the retry double the wall time.
|
|
218
|
+
const second = run(pm.bin, pm.retry.argv, cwd, deadline - Date.now());
|
|
219
|
+
return { ok: second.ok, attempts: [first, second] };
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* The install failed and nothing else can rescue this run.
|
|
223
|
+
*
|
|
224
|
+
* Its own error rather than a quiet null, for the reason docs/portability.md
|
|
225
|
+
* §1 gives about base resolution: a run that compared nothing must not exit
|
|
226
|
+
* clean. A null here fell through to "no baseline", and on a repository with a
|
|
227
|
+
* configured production URL it fell through to comparing against that instead
|
|
228
|
+
* — a different answer to a different question, published as if it were this
|
|
229
|
+
* one.
|
|
230
|
+
*/
|
|
231
|
+
/**
|
|
232
|
+
* What a reader has to do about a failed install, in one sentence.
|
|
233
|
+
*
|
|
234
|
+
* One sentence because that is what `NeedsHumanError` is for (AGENTS.md): the
|
|
235
|
+
* CLI prints the whole message for exit 3, so pasting a screen of package
|
|
236
|
+
* manager output here would bury the instruction inside it. The output is
|
|
237
|
+
* still the thing that answers "why", so it goes to the log instead — above
|
|
238
|
+
* this line, where it reads as diagnostics rather than as the remedy.
|
|
239
|
+
*
|
|
240
|
+
* The command names the caller's own directory, never the worktree: by the
|
|
241
|
+
* time anyone reads this, cleanup has deleted the worktree, so telling them to
|
|
242
|
+
* run it there would be an instruction they cannot follow.
|
|
243
|
+
*/
|
|
244
|
+
export function installFailureHint(where, ranIn) {
|
|
245
|
+
return `Could not install ${where} for the baseline; run \`install\` in ${ranIn} to fix it, or re-run with --no-local-baseline and pass --before <production-url>.`;
|
|
246
|
+
}
|
|
247
|
+
export class BaselineInstallError extends NeedsHumanError {
|
|
248
|
+
result;
|
|
249
|
+
constructor(result, where, ranIn) {
|
|
250
|
+
super(installFailureHint(where, ranIn));
|
|
251
|
+
this.result = result;
|
|
252
|
+
this.name = 'BaselineInstallError';
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async function waitForServer(url, timeoutMs, alive) {
|
|
256
|
+
const deadline = Date.now() + timeoutMs;
|
|
257
|
+
while (Date.now() < deadline) {
|
|
258
|
+
if (!alive())
|
|
259
|
+
return false;
|
|
260
|
+
try {
|
|
261
|
+
await fetch(url, { signal: AbortSignal.timeout(2000) });
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
await new Promise(r => setTimeout(r, 500));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Check out `sha` into a throwaway worktree, install, and start its dev server.
|
|
272
|
+
*
|
|
273
|
+
* Returns null when this repo cannot be served this way (no dev script, no
|
|
274
|
+
* install, server never came up). Callers treat that as "try the next option",
|
|
275
|
+
* so every failure path cleans up after itself and stays quiet.
|
|
276
|
+
*/
|
|
277
|
+
export async function serveBaseCommit(opts) {
|
|
278
|
+
return serveLocally(opts);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Boot this repository's dev server without one already running.
|
|
282
|
+
*
|
|
283
|
+
* The working tree is what the author is looking at, uncommitted edits and all,
|
|
284
|
+
* so it is the honest "Post" when nothing is deployed. Serving it needs no
|
|
285
|
+
* worktree and usually no install, which is why it is cheaper than the base
|
|
286
|
+
* side it gets paired with.
|
|
287
|
+
*/
|
|
288
|
+
export async function serveWorkingTree(opts) {
|
|
289
|
+
return serveLocally({ ...opts, sha: undefined });
|
|
290
|
+
}
|
|
291
|
+
async function serveLocally(opts) {
|
|
292
|
+
const log = opts.log ?? (() => undefined);
|
|
293
|
+
const what = opts.sha ? `base commit ${opts.sha.slice(0, 7)}` : 'the working tree';
|
|
294
|
+
/** Every caller treats null as "try the next option", so say why before returning it. */
|
|
295
|
+
const skip = (detail) => {
|
|
296
|
+
log(`Could not serve ${what}: ${detail}`);
|
|
297
|
+
return null;
|
|
298
|
+
};
|
|
299
|
+
const timeoutMs = opts.timeoutMs ?? 300_000;
|
|
300
|
+
const deadline = Date.now() + timeoutMs;
|
|
301
|
+
const worktree = opts.sha ? fs.mkdtempSync(path.join(os.tmpdir(), 'pre-post-base-')) : opts.repoRoot;
|
|
302
|
+
let child = null;
|
|
303
|
+
let stopped = false;
|
|
304
|
+
// A detached dev server survives Ctrl-C and keeps its port, so tear it down
|
|
305
|
+
// on the signals that end the run as well as on the normal path.
|
|
306
|
+
const onSignal = () => { void cleanup(); };
|
|
307
|
+
const untrap = () => {
|
|
308
|
+
process.off('SIGINT', onSignal);
|
|
309
|
+
process.off('SIGTERM', onSignal);
|
|
310
|
+
process.off('exit', onSignal);
|
|
311
|
+
};
|
|
312
|
+
const cleanup = async () => {
|
|
313
|
+
if (stopped)
|
|
314
|
+
return;
|
|
315
|
+
stopped = true;
|
|
316
|
+
untrap();
|
|
317
|
+
if (child && child.exitCode === null) {
|
|
318
|
+
try {
|
|
319
|
+
process.kill(-child.pid, 'SIGTERM');
|
|
320
|
+
}
|
|
321
|
+
catch { /* already gone */ }
|
|
322
|
+
}
|
|
323
|
+
// Guard on the thing that actually matters: only a throwaway worktree may be
|
|
324
|
+
// removed. Checking `opts.sha` instead would let any future caller that
|
|
325
|
+
// points `worktree` at a real checkout delete uncommitted work.
|
|
326
|
+
if (worktree === opts.repoRoot)
|
|
327
|
+
return;
|
|
328
|
+
try {
|
|
329
|
+
execFileSync('git', ['worktree', 'remove', '--force', worktree], { cwd: opts.repoRoot, stdio: 'ignore' });
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
fs.rmSync(worktree, { recursive: true, force: true });
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
if (opts.sha) {
|
|
336
|
+
try {
|
|
337
|
+
execFileSync('git', ['worktree', 'add', '--detach', '--force', worktree, opts.sha], { cwd: opts.repoRoot, stdio: 'ignore' });
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
await cleanup();
|
|
341
|
+
return skip('the worktree checkout failed.');
|
|
342
|
+
}
|
|
343
|
+
const copied = copyEnvFiles(opts.repoRoot, worktree, opts.appPrefix);
|
|
344
|
+
if (copied.length)
|
|
345
|
+
log(`Using local env file(s) for the baseline: ${copied.join(', ')}`);
|
|
346
|
+
}
|
|
347
|
+
const app = servableDir(worktree, opts.appPrefix);
|
|
348
|
+
if (!app) {
|
|
349
|
+
await cleanup();
|
|
350
|
+
return skip('no package with a dev, serve or start script was found.');
|
|
351
|
+
}
|
|
352
|
+
const { dir: appDir, script } = app;
|
|
353
|
+
const { pm, declared } = resolvePackageManager(appDir, worktree, opts.pathHas);
|
|
354
|
+
const rel = path.relative(worktree, appDir);
|
|
355
|
+
// The worktree is a throwaway that cleanup deletes on the way out, so every
|
|
356
|
+
// "run it by hand" below names the same directory in the caller's own
|
|
357
|
+
// checkout instead — the one that will still be there to run it in.
|
|
358
|
+
const appIn = path.join(opts.repoRoot, rel);
|
|
359
|
+
const where = rel || 'the repository';
|
|
360
|
+
if (!pm) {
|
|
361
|
+
await cleanup();
|
|
362
|
+
return skip(`neither ${declared.bin} nor npm is on PATH, so nothing can install ${where}.`);
|
|
363
|
+
}
|
|
364
|
+
const install = !fs.existsSync(path.join(appDir, 'node_modules'));
|
|
365
|
+
// Substituting npm for the declared manager is safe in a throwaway worktree
|
|
366
|
+
// and not in the caller's own checkout: npm cannot share a node_modules with
|
|
367
|
+
// pnpm, so installing over their tree leaves the working copy broken — the
|
|
368
|
+
// state the README tells people to `rm -rf node_modules` out of. Taking
|
|
369
|
+
// screenshots must not cost someone their install, so this stops instead.
|
|
370
|
+
// Only the install is dangerous; running a script against a tree that is
|
|
371
|
+
// already installed is not, which is why this asks about both.
|
|
372
|
+
if (pm !== declared && install && worktree === opts.repoRoot) {
|
|
373
|
+
await cleanup();
|
|
374
|
+
return skip(`${declared.bin} is not on PATH, and installing ${where} with npm instead would leave a node_modules your ${declared.bin} cannot use. Install ${declared.bin}, or run \`${declared.bin} install\` in ${appIn}.`);
|
|
375
|
+
}
|
|
376
|
+
if (pm !== declared) {
|
|
377
|
+
log(`${declared.bin} is not on PATH; installing the baseline with ${pm.bin} instead (it will not honour the ${declared.bin} lockfile).`);
|
|
378
|
+
}
|
|
379
|
+
log(`Starting a dev server for ${what} (${pm.bin} ${script}) ...`);
|
|
380
|
+
if (install) {
|
|
381
|
+
const result = installDeps(pm, appDir, deadline - Date.now());
|
|
382
|
+
if (!result.ok) {
|
|
383
|
+
const tried = result.attempts.map(a => `\`${pm.bin} ${a.argv.join(' ')}\``).join(', then ');
|
|
384
|
+
log(`${tried} failed${worktree === opts.repoRoot ? '' : ' in a throwaway worktree of the base commit'}:`);
|
|
385
|
+
log(result.attempts[result.attempts.length - 1].output);
|
|
386
|
+
await cleanup();
|
|
387
|
+
throw new BaselineInstallError(result, where, appIn);
|
|
388
|
+
}
|
|
389
|
+
const retried = result.attempts[1];
|
|
390
|
+
if (retried) {
|
|
391
|
+
log(`\`${pm.bin} ${pm.install.join(' ')}\` hit a peer-dependency conflict; installed the baseline with \`${retried.argv.join(' ')}\` instead.`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const port = await freePort();
|
|
395
|
+
const url = `http://localhost:${port}`;
|
|
396
|
+
child = spawn(pm.bin, pm.run(script, ['--port', String(port)]), {
|
|
397
|
+
cwd: appDir,
|
|
398
|
+
stdio: 'ignore',
|
|
399
|
+
detached: true,
|
|
400
|
+
env: { ...process.env, PORT: String(port), BROWSER: 'none' },
|
|
401
|
+
});
|
|
402
|
+
child.unref();
|
|
403
|
+
const ready = await waitForServer(url, Math.max(1, deadline - Date.now()), () => !!child && child.exitCode === null);
|
|
404
|
+
if (!ready) {
|
|
405
|
+
await cleanup();
|
|
406
|
+
return skip(`${pm.bin} ${script} did not start serving within ${Math.round(timeoutMs / 1000)}s (missing env vars are the usual cause).`);
|
|
407
|
+
}
|
|
408
|
+
process.once('SIGINT', onSignal);
|
|
409
|
+
process.once('SIGTERM', onSignal);
|
|
410
|
+
process.once('exit', onSignal);
|
|
411
|
+
return { url, stop: cleanup };
|
|
412
|
+
}
|
|
413
|
+
//# sourceMappingURL=baseline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.js","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,EAAgB,YAAY,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAiC3C,MAAM,GAAG,GAAmB,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,oBAAoB,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;AAClL,MAAM,IAAI,GAAmB,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,SAAS,EAAE,kBAAkB,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;AACxH,MAAM,IAAI,GAAmB,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;AACpG,MAAM,GAAG,GAAmB,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;AAElG,MAAM,QAAQ,GAAoD;IAChE,EAAE,QAAQ,EAAE,gBAAgB,EAAE,EAAE,EAAE,IAAI,EAAE;IACxC,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE;IACnC,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,EAAE,GAAG,EAAE;IAClC,EAAE,QAAQ,EAAE,mBAAmB,EAAE,EAAE,EAAE,GAAG,EAAE;CAC3C,CAAC;AAEF,MAAM,OAAO,GAAmC,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAE/F;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAW,EAAE,QAAiB;IACjE,KAAK,MAAM,SAAS,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAa,EAAE,CAAC;QACpE,MAAM,KAAK,GAAG,WAAW,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC;QACrD,MAAM,KAAK,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC1F,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;QACxB,KAAK,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,QAAQ,EAAE,CAAC;YACxC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;gBAAE,OAAO,EAAE,CAAC;QAC/D,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,MAAM,CAAC,GAAW,EAAE,MAAyB,OAAO,CAAC,GAAG;IACtE,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACpE,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO;QACvC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,IAAI,qBAAqB,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;QACnE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACT,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;QACtC,qEAAqE;QACrE,yDAAyD;QACzD,IAAI,CAAC;YAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,KAAK,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,KAAK,CAAC;QAAC,CAAC;IAC9H,CAAC,CAAC,CAAC,CAAC;AACN,CAAC;AASD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,qBAAqB,CACnC,GAAW,EACX,QAAiB,EACjB,MAAgC,MAAM;IAEtC,MAAM,QAAQ,GAAG,oBAAoB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACrD,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACzD,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC;AACrD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,SAAkB;IAC9D,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACxE,MAAM,eAAe,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7C,IAAI,eAAe;QAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IACxE,8EAA8E;IAC9E,KAAK,MAAM,GAAG,IAAI,YAAY,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7E,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,MAAM;YAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;IACrC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,kEAAkE;AAClE,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,kBAAkB,EAAE,wBAAwB,CAAC,CAAC;AAEvF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,EAAU,EAAE,SAAkB;IACvE,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC9B,KAAK,MAAM,GAAG,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QACrD,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;YACvC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;YAC3C,iDAAiD;YACjD,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC;gBAAE,SAAS;YACjE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,SAAS;YACzD,IAAI,CAAC;gBACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBACtD,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBAC3B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;YAC1C,CAAC;YAAC,MAAM,CAAC,CAAC,6DAA6D,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,QAAQ;IACtB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC;QAC/B,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1B,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE;YAC9B,MAAM,IAAI,GAAI,GAAG,CAAC,OAAO,EAAsB,CAAC,IAAI,CAAC;YACrD,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AA0BD,mEAAmE;AACnE,SAAS,IAAI,CAAC,IAAY,EAAE,KAAK,GAAG,EAAE;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;IAChE,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;AAChC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,kBAAkB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAE5C,MAAM,UAAU,GAAkB,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE;IAC9D,IAAI,CAAC;QACH,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC;QACvJ,0EAA0E;QAC1E,6DAA6D;QAC7D,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACxC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,GAAG,GAA+E,CAAC;QAC1F,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrF,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;IACtE,CAAC;AACH,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,OAAO,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,+BAA+B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAClF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CACzB,EAAkB,EAClB,GAAW,EACX,SAAiB,EACjB,MAAqB,UAAU;IAE/B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,MAAM,KAAK,GAAG,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;IACtD,IAAI,KAAK,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;IAC1F,yEAAyE;IACzE,qEAAqE;IACrE,MAAM,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACtE,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC;AACtD,CAAC;AAED;;;;;;;;;GASG;AACH;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,KAAa;IAC7D,OAAO,qBAAqB,KAAK,yCAAyC,KAAK,oFAAoF,CAAC;AACtK,CAAC;AAED,MAAM,OAAO,oBAAqB,SAAQ,eAAe;IAC3B;IAA5B,YAA4B,MAAqB,EAAE,KAAa,EAAE,KAAa;QAC7E,KAAK,CAAC,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QADd,WAAM,GAAN,MAAM,CAAe;QAE/C,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED,KAAK,UAAU,aAAa,CAAC,GAAW,EAAE,SAAiB,EAAE,KAAoB;IAC/E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,KAAK,EAAE;YAAE,OAAO,KAAK,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACxD,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAeD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAqB;IACzD,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAkC;IACvE,OAAO,YAAY,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,IAAqB;IAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC;IACnF,yFAAyF;IACzF,MAAM,IAAI,GAAG,CAAC,MAAc,EAAQ,EAAE;QACpC,GAAG,CAAC,mBAAmB,IAAI,KAAK,MAAM,EAAE,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IACF,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,OAAO,CAAC;IAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IAExC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC;IACrG,IAAI,KAAK,GAAwB,IAAI,CAAC;IACtC,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,4EAA4E;IAC5E,iEAAiE;IACjE,MAAM,QAAQ,GAAG,GAAG,EAAE,GAAG,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,GAAG,EAAE;QAClB,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QACjC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAChC,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,KAAK,IAAmB,EAAE;QACxC,IAAI,OAAO;YAAE,OAAO;QACpB,OAAO,GAAG,IAAI,CAAC;QACf,MAAM,EAAE,CAAC;QACT,IAAI,KAAK,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YACrC,IAAI,CAAC;gBAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAI,EAAE,SAAS,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;QAC5E,CAAC;QACD,6EAA6E;QAC7E,wEAAwE;QACxE,gEAAgE;QAChE,IAAI,QAAQ,KAAK,IAAI,CAAC,QAAQ;YAAE,OAAO;QACvC,IAAI,CAAC;YACH,YAAY,CAAC,KAAK,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC5G,CAAC;QAAC,MAAM,CAAC;YACP,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACxD,CAAC;IACH,CAAC,CAAC;IAEF,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,IAAI,CAAC;YACH,YAAY,CAAC,KAAK,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC/H,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,OAAO,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC,+BAA+B,CAAC,CAAC;QAC/C,CAAC;QACD,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACrE,IAAI,MAAM,CAAC,MAAM;YAAE,GAAG,CAAC,6CAA6C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED,MAAM,GAAG,GAAG,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAClD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,OAAO,EAAE,CAAC;QAChB,OAAO,IAAI,CAAC,yDAAyD,CAAC,CAAC;IACzE,CAAC;IACD,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IAEpC,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,qBAAqB,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/E,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5C,4EAA4E;IAC5E,sEAAsE;IACtE,oEAAoE;IACpE,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,GAAG,IAAI,gBAAgB,CAAC;IACtC,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,OAAO,EAAE,CAAC;QAChB,OAAO,IAAI,CAAC,WAAW,QAAQ,CAAC,GAAG,+CAA+C,KAAK,GAAG,CAAC,CAAC;IAC9F,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;IAClE,4EAA4E;IAC5E,6EAA6E;IAC7E,2EAA2E;IAC3E,wEAAwE;IACxE,0EAA0E;IAC1E,yEAAyE;IACzE,+DAA+D;IAC/D,IAAI,EAAE,KAAK,QAAQ,IAAI,OAAO,IAAI,QAAQ,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC7D,MAAM,OAAO,EAAE,CAAC;QAChB,OAAO,IAAI,CAAC,GAAG,QAAQ,CAAC,GAAG,mCAAmC,KAAK,qDAAqD,QAAQ,CAAC,GAAG,wBAAwB,QAAQ,CAAC,GAAG,cAAc,QAAQ,CAAC,GAAG,iBAAiB,KAAK,GAAG,CAAC,CAAC;IAC/N,CAAC;IACD,IAAI,EAAE,KAAK,QAAQ,EAAE,CAAC;QACpB,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,iDAAiD,EAAE,CAAC,GAAG,oCAAoC,QAAQ,CAAC,GAAG,aAAa,CAAC,CAAC;IAC3I,CAAC;IACD,GAAG,CAAC,6BAA6B,IAAI,KAAK,EAAE,CAAC,GAAG,IAAI,MAAM,OAAO,CAAC,CAAC;IACnE,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAC9D,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC5F,GAAG,CAAC,GAAG,KAAK,UAAU,QAAQ,KAAK,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,6CAA6C,GAAG,CAAC,CAAC;YAC1G,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YACxD,MAAM,OAAO,EAAE,CAAC;YAChB,MAAM,IAAI,oBAAoB,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QACnC,IAAI,OAAO,EAAE,CAAC;YACZ,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,oEAAoE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QAClJ,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,EAAE,CAAC;IAC9B,MAAM,GAAG,GAAG,oBAAoB,IAAI,EAAE,CAAC;IACvC,KAAK,GAAG,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;QAC9D,GAAG,EAAE,MAAM;QACX,KAAK,EAAE,QAAQ;QACf,QAAQ,EAAE,IAAI;QACd,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE;KAC7D,CAAC,CAAC;IACH,KAAK,CAAC,KAAK,EAAE,CAAC;IAEd,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC;IACrH,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,OAAO,EAAE,CAAC;QAChB,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,MAAM,iCAAiC,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,2CAA2C,CAAC,CAAC;IAC3I,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACjC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAClC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAChC,CAAC"}
|
package/dist/bin/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { runPr } from '../commands/pr.js';
|
|
|
5
5
|
import { runCompare } from '../commands/compare.js';
|
|
6
6
|
import { runLogin } from '../commands/login.js';
|
|
7
7
|
import { runPrune } from '../commands/prune.js';
|
|
8
|
-
import { runDoctor } from '../commands/doctor.js';
|
|
8
|
+
import { doctorExitCode, runDoctor } from '../commands/doctor.js';
|
|
9
9
|
import { runDetect } from '../commands/detect.js';
|
|
10
10
|
import { NeedsHumanError } from '../errors.js';
|
|
11
11
|
import { buildSummary } from '../report.js';
|
|
@@ -21,39 +21,63 @@ const VERSION = (() => {
|
|
|
21
21
|
const SUBCOMMANDS = ['pr', 'detect', 'login', 'prune', 'doctor', 'compare'];
|
|
22
22
|
const argv = process.argv.slice(2);
|
|
23
23
|
const subcommand = SUBCOMMANDS.includes(argv[0]) ? argv.shift() : null;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
'
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
},
|
|
56
|
-
}
|
|
24
|
+
/**
|
|
25
|
+
* parseArgs throws a TypeError for an unknown flag, and an uncaught one prints
|
|
26
|
+
* a Node-internal stack trace: eight lines of `node:internal/util/parse_args`
|
|
27
|
+
* for a typo. The CLI is mostly read by agents, so the first line has to be the
|
|
28
|
+
* thing to fix, not a frame from Node's parser.
|
|
29
|
+
*
|
|
30
|
+
* Only the parse is wrapped, and the message is Node's own — this reformats a
|
|
31
|
+
* failure, it does not decide what counts as one.
|
|
32
|
+
*/
|
|
33
|
+
function parse() {
|
|
34
|
+
try {
|
|
35
|
+
return parseArgs({
|
|
36
|
+
args: argv,
|
|
37
|
+
allowPositionals: true,
|
|
38
|
+
options: OPTIONS,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
const message = err?.message?.split('\n')[0] ?? 'could not parse the arguments';
|
|
43
|
+
console.error(`pre-post: ${message}`);
|
|
44
|
+
console.error('Run: pre-post --help');
|
|
45
|
+
process.exit(2);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const OPTIONS = {
|
|
49
|
+
help: { type: 'boolean', short: 'h' },
|
|
50
|
+
version: { type: 'boolean', short: 'v' },
|
|
51
|
+
before: { type: 'string' },
|
|
52
|
+
after: { type: 'string' },
|
|
53
|
+
routes: { type: 'string' },
|
|
54
|
+
'max-routes': { type: 'string' },
|
|
55
|
+
framework: { type: 'string' },
|
|
56
|
+
base: { type: 'string' },
|
|
57
|
+
'no-local-baseline': { type: 'boolean' },
|
|
58
|
+
viewports: { type: 'string' },
|
|
59
|
+
mobile: { type: 'boolean', short: 'm' },
|
|
60
|
+
tablet: { type: 'boolean', short: 't' },
|
|
61
|
+
responsive: { type: 'boolean', short: 'r' },
|
|
62
|
+
size: { type: 'string' },
|
|
63
|
+
full: { type: 'boolean', short: 'f' },
|
|
64
|
+
'viewport-only': { type: 'boolean' },
|
|
65
|
+
scale: { type: 'string' },
|
|
66
|
+
threshold: { type: 'string' },
|
|
67
|
+
'min-changed-area': { type: 'string' },
|
|
68
|
+
'max-height': { type: 'string' },
|
|
69
|
+
wait: { type: 'string' },
|
|
70
|
+
header: { type: 'string', multiple: true },
|
|
71
|
+
cookie: { type: 'string', multiple: true },
|
|
72
|
+
output: { type: 'string', short: 'o' },
|
|
73
|
+
'dry-run': { type: 'boolean' },
|
|
74
|
+
'no-comment': { type: 'boolean' },
|
|
75
|
+
pr: { type: 'string' },
|
|
76
|
+
json: { type: 'boolean' },
|
|
77
|
+
days: { type: 'string' },
|
|
78
|
+
quiet: { type: 'boolean', short: 'q' },
|
|
79
|
+
};
|
|
80
|
+
const { values, positionals } = parse();
|
|
57
81
|
function printHelp() {
|
|
58
82
|
console.log(`pre-post ${VERSION} — before/after screenshots for pull requests
|
|
59
83
|
|
|
@@ -61,6 +85,7 @@ USAGE
|
|
|
61
85
|
pre-post pr [options] Detect routes, capture, diff, publish, comment on the PR
|
|
62
86
|
pre-post <before> <after> [options] Compare two URLs (or two PNG files)
|
|
63
87
|
pre-post detect Print the routes this branch affects (JSON)
|
|
88
|
+
pre-post ... --base <ref> Compare against <ref> instead of the detected fork point
|
|
64
89
|
pre-post login <url> Sign in once; the session is reused for captures
|
|
65
90
|
pre-post prune [--days 90] Delete screenshots for PRs closed longer ago
|
|
66
91
|
pre-post doctor Check browser, GitHub auth, dev server
|
|
@@ -70,16 +95,22 @@ OPTIONS
|
|
|
70
95
|
--after <url> Local base URL (auto-detected)
|
|
71
96
|
--routes /a,/b Explicit routes instead of detection
|
|
72
97
|
--max-routes <n> Cap on detected routes (default 6)
|
|
73
|
-
--viewports desktop,mobile,1440x900 Viewports (default desktop
|
|
98
|
+
--viewports desktop,mobile,1440x900 Viewports (default desktop)
|
|
99
|
+
-r, --responsive Desktop and mobile
|
|
74
100
|
-m, --mobile -t, --tablet --size WxH Single-viewport shorthands
|
|
75
101
|
-f, --full | --viewport-only Full page (default for pr) or first screen only
|
|
76
102
|
--scale <n> Device scale factor (default 2)
|
|
77
|
-
--threshold <0..1>
|
|
103
|
+
--threshold <0..1> Changed share of the canvas that counts (default 0.001)
|
|
104
|
+
--min-changed-area <px2> Changed area in CSS px² that counts (default 100)
|
|
105
|
+
A capture counts as changed when EITHER holds, so
|
|
106
|
+
a 0.04% diff on a full page is still a change if it
|
|
107
|
+
paints more than 100 CSS px².
|
|
78
108
|
--max-height <px> Full-page height cap in CSS px (default 2400)
|
|
79
109
|
--wait <ms> Extra wait after the page settles
|
|
80
110
|
--header k=v Extra request header (repeatable)
|
|
81
111
|
--cookie name=value Cookie for the production host (repeatable)
|
|
82
112
|
-o, --output <dir> Where to write images (default: temp dir)
|
|
113
|
+
--no-local-baseline Do not rebuild the baseline from the base commit
|
|
83
114
|
--dry-run Capture and diff only; no upload, no comment
|
|
84
115
|
--no-comment Publish images but do not touch the PR
|
|
85
116
|
--pr <number> Target a specific PR
|
|
@@ -166,6 +197,7 @@ async function main() {
|
|
|
166
197
|
fullPage: values['viewport-only'] ? false : values.full ? true : undefined,
|
|
167
198
|
scale: num(values.scale, '--scale'),
|
|
168
199
|
threshold: num(values.threshold, '--threshold'),
|
|
200
|
+
minChangedArea: num(values['min-changed-area'], '--min-changed-area'),
|
|
169
201
|
maxHeight: num(values['max-height'], '--max-height'),
|
|
170
202
|
maxRoutes: num(values['max-routes'], '--max-routes'),
|
|
171
203
|
wait: num(values.wait, '--wait'),
|
|
@@ -176,14 +208,21 @@ async function main() {
|
|
|
176
208
|
const { before, after } = resolveTargets();
|
|
177
209
|
switch (subcommand) {
|
|
178
210
|
case 'pr': {
|
|
211
|
+
// before/after come from flags here; positionals would be silently dropped.
|
|
212
|
+
if (positionals.length) {
|
|
213
|
+
console.error(`pre-post pr takes no positional arguments (got: ${positionals.join(' ')}). Use --before and --after.`);
|
|
214
|
+
process.exit(2);
|
|
215
|
+
}
|
|
179
216
|
const cookies = values.cookie ? Object.entries(kv(values.cookie)).map(([name, value]) => ({ name, value })) : undefined;
|
|
180
217
|
const result = await runPr({
|
|
181
218
|
...common,
|
|
182
219
|
before,
|
|
183
220
|
after,
|
|
184
221
|
framework: values.framework,
|
|
222
|
+
base: values.base,
|
|
185
223
|
cookies,
|
|
186
224
|
dryRun: values['dry-run'],
|
|
225
|
+
localBaseline: !values['no-local-baseline'],
|
|
187
226
|
comment: !values['no-comment'],
|
|
188
227
|
pr: num(values.pr, '--pr'),
|
|
189
228
|
version: VERSION,
|
|
@@ -194,7 +233,7 @@ async function main() {
|
|
|
194
233
|
return;
|
|
195
234
|
}
|
|
196
235
|
case 'detect': {
|
|
197
|
-
const detection = runDetect({ maxRoutes: common.maxRoutes, framework: values.framework });
|
|
236
|
+
const detection = runDetect({ maxRoutes: common.maxRoutes, framework: values.framework, base: values.base });
|
|
198
237
|
console.log(JSON.stringify(detection, null, values.json ? 2 : 0));
|
|
199
238
|
return;
|
|
200
239
|
}
|
|
@@ -214,11 +253,17 @@ async function main() {
|
|
|
214
253
|
}
|
|
215
254
|
case 'doctor': {
|
|
216
255
|
const checks = await runDoctor();
|
|
217
|
-
for (const c of checks)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
256
|
+
for (const c of checks) {
|
|
257
|
+
// Advisory failures are worth reading and are not failures of the
|
|
258
|
+
// command, so they must not look like the ones that are.
|
|
259
|
+
const status = c.ok ? 'ok' : c.required ? 'FAIL' : 'note';
|
|
260
|
+
console.log(`${c.name.padEnd(10)} ${status.padEnd(4)} ${c.detail}`);
|
|
261
|
+
}
|
|
262
|
+
const code = doctorExitCode(checks);
|
|
263
|
+
console.log(code === 0
|
|
264
|
+
? 'ready — pre-post pr can run'
|
|
265
|
+
: `not ready — fix: ${checks.filter(c => c.required && !c.ok).map(c => c.name).join(', ')}`);
|
|
266
|
+
process.exit(code);
|
|
222
267
|
}
|
|
223
268
|
case 'compare':
|
|
224
269
|
default: {
|