@impeccable/reviews 0.1.16 → 0.1.18
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 +1 -1
- package/dist/agent.js +7 -1
- package/dist/cloudflare-access.js +36 -17
- package/dist/cloudflare.js +9 -4
- package/dist/fence.js +47 -15
- package/dist/host-questions.js +17 -2
- package/dist/hosted.js +67 -9
- package/dist/main.js +46 -4
- package/dist/netlify.js +8 -2
- package/dist/playbook/cases/cloudflare-previews.md +3 -3
- package/dist/playbook/cases/first-preview-failure.md +0 -1
- package/dist/playbook/cases/live-app-address.md +1 -1
- package/dist/playbook/cases/monorepo.md +5 -6
- package/dist/playbook/cases/netlify-previews.md +2 -2
- package/dist/playbook/cases/netlify-setup.md +2 -2
- package/dist/playbook/cases/preview-protection.md +3 -2
- package/dist/playbook/cases/pull-requests.md +4 -5
- package/dist/playbook/cases/server-previews.md +3 -3
- package/dist/playbook/cases/vercel-previews.md +3 -3
- package/dist/playbook/cases/vercel-setup.md +2 -2
- package/dist/playbook.js +6 -5
- package/dist/preflight.js +56 -0
- package/dist/sandbox.js +3 -3
- package/dist/tools.js +162 -34
- package/dist/ui/look.js +24 -12
- package/dist/ui/run-state.js +0 -5
- package/dist/ui/screens.js +4 -4
- package/dist/vercel-bypass.js +1 -0
- package/dist/vercel.js +4 -2
- package/package.json +1 -1
- package/dist/playbook/cases/static-site.md +0 -16
package/dist/ui/look.js
CHANGED
|
@@ -80,18 +80,29 @@ function balanced(text, max) {
|
|
|
80
80
|
const said = (text, inner, st) => text.split('\n\n').flatMap((part, i) => [...(i ? [''] : []), ...balanced(part, Math.min(TEXT_WIDTH, inner)).map((line) => center(st.soft(line), inner))]);
|
|
81
81
|
/** Wrapped text whose first line starts with `first` and the rest line up under it. */
|
|
82
82
|
const hang = (text, width, first) => wrapTextWithAnsi(text, Math.max(10, width - first.length)).map((line, i) => (i ? ' '.repeat(first.length) : first) + line);
|
|
83
|
-
/** An address cut into rows no wider than `width
|
|
84
|
-
function
|
|
83
|
+
/** An address or a folder cut into rows no wider than `width` columns, after a slash where one falls in the row's last two thirds, so a name in it breaks only where it must. */
|
|
84
|
+
function slashRows(text, width) {
|
|
85
85
|
const size = Math.max(10, width);
|
|
86
86
|
const rows = [];
|
|
87
|
-
for (let rest =
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
for (let rest = text; rest;) {
|
|
88
|
+
// The longest start of it that fits, by the columns its characters take, a wide one counting two.
|
|
89
|
+
let fit = 0;
|
|
90
|
+
for (let columns = 0; fit < rest.length;) {
|
|
91
|
+
const char = String.fromCodePoint(rest.codePointAt(fit));
|
|
92
|
+
if ((columns += visibleWidth(char)) > size)
|
|
93
|
+
break;
|
|
94
|
+
fit += char.length;
|
|
95
|
+
}
|
|
96
|
+
const head = rest.slice(0, Math.max(fit, 1));
|
|
97
|
+
const slash = fit < rest.length ? Math.max(head.lastIndexOf('/'), head.lastIndexOf('\\')) + 1 : 0;
|
|
98
|
+
const cut = fit >= rest.length ? rest.length : slash && visibleWidth(head.slice(0, slash)) > size / 3 ? slash : head.length;
|
|
90
99
|
rows.push(rest.slice(0, cut));
|
|
91
100
|
rest = rest.slice(cut);
|
|
92
101
|
}
|
|
93
|
-
return rows
|
|
102
|
+
return rows;
|
|
94
103
|
}
|
|
104
|
+
/** An address cut into rows as slashRows cuts it, each linking to the whole of it, so any row opens it. */
|
|
105
|
+
const urlRows = (url, width, ink, st) => slashRows(url, width).map((row) => st.link(url, ink(row)));
|
|
95
106
|
/** A label, softer, over its address. */
|
|
96
107
|
const linkLines = (label, url, inner, st) => [center(st.soft(label), inner), ...block(urlRows(url, ruleWidth(inner), st.full, st), inner)];
|
|
97
108
|
/** The way to put a link on the clipboard, while it is not there yet. */
|
|
@@ -102,6 +113,8 @@ const pullAction = (url, copied, inner, st) => [
|
|
|
102
113
|
copied ? center(st.soft('Copied to clipboard!'), inner) : copyWay(inner, st),
|
|
103
114
|
];
|
|
104
115
|
/** What waits on the person, under the supporting text: two lines at most, the rest counted. */
|
|
116
|
+
/** The hosts the run used, a line each under the result. */
|
|
117
|
+
const usedLines = (outro, inner, st) => (outro.used ?? []).flatMap((line) => said(line, inner, st));
|
|
105
118
|
function leftLines(todo, inner, st) {
|
|
106
119
|
const shown = todo.length > 2 ? [todo[0], `${todo.length - 1} more in the report`] : todo;
|
|
107
120
|
return shown.map((line) => center(st.soft(`→ ${line}`), inner));
|
|
@@ -244,12 +257,10 @@ function runPage(scene, f, inner, st) {
|
|
|
244
257
|
const waiting = row(`waiting for ${scene.wait.host ? `${scene.wait.host}'s preview` : 'the preview'} · ${scene.wait.timer}`);
|
|
245
258
|
return ink(visibleWidth(waiting) <= inner ? waiting : row(scene.wait.timer));
|
|
246
259
|
});
|
|
247
|
-
const counted = scene.tasks.filter((t) => t.status !== 'skipped');
|
|
248
|
-
const skipped = scene.tasks.length - counted.length;
|
|
249
260
|
const widest = Math.max(...lines.map((line) => visibleWidth(line)));
|
|
261
|
+
// No count over the list: the list shows each task's state itself.
|
|
250
262
|
return {
|
|
251
263
|
heading: 'Setting up',
|
|
252
|
-
text: said(`${counted.filter((t) => t.status === 'completed').length} of ${counted.length} done${skipped ? ` · ${skipped} skipped` : ''}`, inner, st),
|
|
253
264
|
rule: divider(inner, st),
|
|
254
265
|
action: block(lines, inner, widest),
|
|
255
266
|
at: running >= 0 ? running : Math.max(0, scene.tasks.findIndex((t) => t.status === 'pending')),
|
|
@@ -261,7 +272,8 @@ function outroPage(scene, inner, st) {
|
|
|
261
272
|
if (outro.link) {
|
|
262
273
|
return {
|
|
263
274
|
heading: outro.title,
|
|
264
|
-
|
|
275
|
+
// A warning is read whole: wrapped, where centered on one line it would be cut.
|
|
276
|
+
text: [...said(outro.result, inner, st), ...usedLines(outro, inner, st), ...leftLines(outro.todo, inner, st), ...outro.warnings.flatMap((line) => said(`! ${line}`, inner, st))],
|
|
265
277
|
rule: divider(inner, st),
|
|
266
278
|
action: pullAction(outro.link, scene.copied, inner, st),
|
|
267
279
|
ways: scene.leaving ? undefined : [['Close', 'Enter']],
|
|
@@ -273,7 +285,7 @@ function outroPage(scene, inner, st) {
|
|
|
273
285
|
const links = outro.links.flatMap((l) => linkLines(l.label, l.url, inner, st));
|
|
274
286
|
return {
|
|
275
287
|
heading: outro.title,
|
|
276
|
-
text: said(outro.result, inner, st),
|
|
288
|
+
text: [...said(outro.result, inner, st), ...usedLines(outro, inner, st)],
|
|
277
289
|
rule: divider(inner, st),
|
|
278
290
|
action: [...block(notes, inner), ...(notes.length && links.length ? [''] : []), ...links, ...(outro.links.length === 1 ? [scene.copied ? center(st.soft('Copied to clipboard!'), inner) : copyWay(inner, st)] : [])],
|
|
279
291
|
ways: scene.leaving ? undefined : [['Close', 'Enter']],
|
|
@@ -299,7 +311,7 @@ function pageOf(f, inner) {
|
|
|
299
311
|
const under = scene.note ?? (scene.copied ? 'Copied to clipboard!' : '');
|
|
300
312
|
const link = block(urlRows(scene.link, ruleWidth(inner), st.soft, st), inner);
|
|
301
313
|
// In the action, which scrolls and keeps every line, so a short terminal still shows which checkout this approves.
|
|
302
|
-
const checkout = scene.checkout && !scene.again ? [...balanced(scene.checkout.repository, Math.min(TEXT_WIDTH, inner)).map((line) => center(st.full(line), inner)), ...
|
|
314
|
+
const checkout = scene.checkout && !scene.again ? [...balanced(scene.checkout.repository, Math.min(TEXT_WIDTH, inner)).map((line) => center(st.full(line), inner)), ...slashRows(`in ${scene.checkout.folder}`, Math.min(TEXT_WIDTH, inner)).map((line) => center(st.soft(line), inner)), ''] : [];
|
|
303
315
|
return {
|
|
304
316
|
heading: scene.again ? 'Sign in again' : START.heading,
|
|
305
317
|
text: said(scene.again ? AGAIN[scene.again] : 'Sign in with GitHub in your browser, then approve this repository.', inner, st),
|
package/dist/ui/run-state.js
CHANGED
|
@@ -83,9 +83,4 @@ export class RunState {
|
|
|
83
83
|
return { previous: this.current, current: `Still working… ${elapsed(silence)}` };
|
|
84
84
|
return { previous: this.previous, current: this.current };
|
|
85
85
|
}
|
|
86
|
-
/** Completed tasks out of all but the skipped ones. */
|
|
87
|
-
progress() {
|
|
88
|
-
const counted = this.tasks.filter((t) => t.status !== 'skipped');
|
|
89
|
-
return { completed: counted.filter((t) => t.status === 'completed').length, total: counted.length };
|
|
90
|
-
}
|
|
91
86
|
}
|
package/dist/ui/screens.js
CHANGED
|
@@ -303,7 +303,7 @@ class Wizard {
|
|
|
303
303
|
if (choices && question.picked.size)
|
|
304
304
|
return done({ skipped: false, values: choices.filter((_, i) => question.picked.has(i)).map((o) => o.value) });
|
|
305
305
|
if (choices)
|
|
306
|
-
question.note = 'Pick at least one, or skip.';
|
|
306
|
+
question.note = q.skippable ? 'Pick at least one, or skip.' : 'Pick at least one.';
|
|
307
307
|
else {
|
|
308
308
|
const value = question.field.getValue().trim();
|
|
309
309
|
if (value)
|
|
@@ -478,7 +478,7 @@ export function showRun(opts) {
|
|
|
478
478
|
}
|
|
479
479
|
/** The outro in plain lines, for the scrollback: what stays once the screen has gone. */
|
|
480
480
|
function summary(o) {
|
|
481
|
-
return ['', `${OUTCOME[o.outcome]} ${o.title}. ${o.result}`, ...o.links.map((l) => `${l.label}: ${l.url}`), ...o.todo.map((line) => `→ ${line}`), ...o.warnings.map((warning) => `! ${warning}`)];
|
|
481
|
+
return ['', `${OUTCOME[o.outcome]} ${o.title}. ${o.result}`, ...(o.used ?? []), ...o.links.map((l) => `${l.label}: ${l.url}`), ...o.todo.map((line) => `→ ${line}`), ...o.warnings.map((warning) => `! ${warning}`)];
|
|
482
482
|
}
|
|
483
483
|
/** Opens `url` in the browser and puts it on the clipboard, true once it is there. Neither can fail the screen, which shows the link either way. */
|
|
484
484
|
async function handOver(url, hand) {
|
|
@@ -493,9 +493,9 @@ export async function showPullRequest(url, hand) {
|
|
|
493
493
|
return;
|
|
494
494
|
const copied = await handOver(url, hand);
|
|
495
495
|
await new Promise((resolve) => {
|
|
496
|
+
// The screen stays until the end screen takes its place, so no empty screen shows between the two.
|
|
496
497
|
const done = () => {
|
|
497
498
|
clearTimeout(timer);
|
|
498
|
-
screen.remove(view);
|
|
499
499
|
resolve();
|
|
500
500
|
};
|
|
501
501
|
const timer = setTimeout(done, CLOSE_AFTER_MS).unref();
|
|
@@ -507,7 +507,7 @@ export async function showPullRequest(url, hand) {
|
|
|
507
507
|
/** The last screen: what happened, what is left, any warnings and its pull request (handed over as it appears) or the links, until a key other than ↑↓ PgUp PgDn is pressed, or for 20 minutes. Its summary then stays in the scrollback. */
|
|
508
508
|
export async function showOutro(outro, hand) {
|
|
509
509
|
// What is left and why it stopped can be the model's words, which reach the scrollback after the clipboard holds the pull request.
|
|
510
|
-
const o = { ...outro, result: plainText(outro.result), todo: outro.todo.map(plainText), warnings: outro.warnings.map(plainText) };
|
|
510
|
+
const o = { ...outro, result: plainText(outro.result), todo: outro.todo.map(plainText), warnings: outro.warnings.map(plainText), ...(outro.used ? { used: outro.used.map(plainText) } : {}) };
|
|
511
511
|
const screen = host();
|
|
512
512
|
if (screen) {
|
|
513
513
|
const copied = o.link && hand ? await handOver(o.link, hand) : false;
|
package/dist/vercel-bypass.js
CHANGED
|
@@ -182,6 +182,7 @@ export async function passVercelLogin(deps) {
|
|
|
182
182
|
else {
|
|
183
183
|
// The project holds the secret from here on, whatever the reads below say: the screen says to copy it, never to make another.
|
|
184
184
|
held = made.made;
|
|
185
|
+
deps.made?.(made.made, project.name);
|
|
185
186
|
// Read back by its label, again for a few seconds while the settings do not list it yet.
|
|
186
187
|
let ours;
|
|
187
188
|
for (const end = Date.now() + wait.totalMs;;) {
|
package/dist/vercel.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
4
4
|
import { folderOf, matchesBranch, TEST_DRIVE_BRANCH } from './host-previews.js';
|
|
5
|
-
import { asLink, readLink } from './vercel-bypass.js';
|
|
5
|
+
import { asLink, isBypassSecret, readLink, SECRET_NOTE } from './vercel-bypass.js';
|
|
6
6
|
/** `vercel whoami`: the CLI prints the username, or says it has no credentials and exits 1. */
|
|
7
7
|
export async function vercelStatus(vercel) {
|
|
8
8
|
try {
|
|
@@ -147,7 +147,9 @@ export async function readLinkedProject(vercel, link) {
|
|
|
147
147
|
// The live app: the production deployment's first alias, the project's own domain, as Vercel lists them (its automatic aliases follow). Its own hashed address is one build's, which the next production deployment leaves behind: never the before.
|
|
148
148
|
const live = target && !failed(target) ? (Array.isArray(target.alias) ? target.alias : []).map(hostname).find(Boolean) : undefined;
|
|
149
149
|
const root = folderOf(project.rootDirectory);
|
|
150
|
-
|
|
150
|
+
// As impeccable_vercel_bypass picks its secret: one for automation, labelled for Impeccable.
|
|
151
|
+
const labelled = Object.entries(record(project.protectionBypass) ?? {}).some(([value, meta]) => isBypassSecret(value) && record(meta)?.scope === 'automation-bypass' && record(meta)?.note === SECRET_NOTE);
|
|
152
|
+
return { ...(labelled ? { bypassLabelled: true } : {}), ...(root !== undefined ? { rootDirectory: root } : {}), ...(typeof project.name === 'string' && project.name ? { name: project.name } : {}), production: exists ? 'exists' : 'none', ...(typeof branch === 'string' && branch ? { productionBranch: branch } : {}), ...(live ? { productionUrl: `https://${live}` } : {}), protection: typeof protection === 'string' ? protection : null, previewsProtected, deploymentHosts: new Set(hosts), ...(repository ? { repository } : {}), ignoredBuildStep: typeof ignore === 'string' && ignore.trim().length > 0, githubDeployments: record(project.gitProviderOptions)?.createDeployments !== 'disabled' };
|
|
151
153
|
}
|
|
152
154
|
/**
|
|
153
155
|
* The linked project as this repository's: read now, and connected to the repository on Vercel, which its Git link
|
package/package.json
CHANGED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
### When this applies
|
|
2
|
-
|
|
3
|
-
The pages need no server from this repository: plain HTML, or a build that writes a complete static site. Pages that need SSR, API routes or a backend here follow server-previews; an external API they call does not change that. A build that writes only JavaScript, CSS or types is a library, not a site.
|
|
4
|
-
|
|
5
|
-
### Steps
|
|
6
|
-
|
|
7
|
-
1. **Find the output**: the folder the build writes the pages to (framework config, scripts), or, with no build, the folder that holds the real `index.html`. Done when you can name that folder and the build command, or that there is none.
|
|
8
|
-
2. **Prove it** with impeccable_verify_preview in static mode. Done when it answers `verified: true` with the folder's `index.html` in `files`; a failure names its `stage`, which the report explains, and pull-requests' "No preview, no test drive" says how the run goes on without a proof. When it answers that this machine has no sandbox for the check (Linux, or `--no-sandbox`), the check is skipped: save the build the scripts name in step 3, and the preview job on the pull request proves it; put that under "What is not verified".
|
|
9
|
-
3. **Save it** with impeccable_write to `preview`: `intendedMethod: "static-upload"`, `outputDir` and the commands you proved, clearing a saved command you did not prove; when the check was skipped on this machine, the build the scripts name is what you save, and the preview job proves it. Done when the answer's `keys` show each key stored or unchanged.
|
|
10
|
-
|
|
11
|
-
### Reference
|
|
12
|
-
|
|
13
|
-
- **No build**: `steps` is empty and the output is the page's folder, `.` for the checkout's root. Invent no manifest, build, runtime or server for plain HTML.
|
|
14
|
-
- **A base path**: `<base>/index.html` must exist under the output folder; pass `basePath` to the check, and save it.
|
|
15
|
-
- **A single-page app**: the write takes no `spa` key, and the entry check proves no deep link; report a route the preview cannot serve.
|
|
16
|
-
- A verified result proves the entry files exist, nothing more: rendering, assets, deep links and external APIs go under "What is not verified".
|