@impeccable/reviews 0.1.25 → 0.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/leftovers.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { execFileSync } from 'node:child_process';
2
2
  import { createHash } from 'node:crypto';
3
- import { existsSync, lstatSync, readFileSync, realpathSync, rmdirSync, unlinkSync, writeFileSync } from 'node:fs';
4
- import { dirname, isAbsolute, relative, sep } from 'node:path';
3
+ import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, rmdirSync, unlinkSync, writeFileSync } from 'node:fs';
4
+ import { dirname, isAbsolute, join, relative, sep } from 'node:path';
5
5
  import { gitEnvironment } from './sandbox.js';
6
6
  const digest = (bytes) => createHash('sha256').update(bytes).digest('hex');
7
7
  /** Up to three paths, then how many more. */
@@ -9,6 +9,14 @@ export const listPaths = (paths) => `${paths.slice(0, 3).join(', ')}${paths.leng
9
9
  /** A pathspec git reads as the path itself, never a pattern. */
10
10
  const literal = (path) => `:(literal)${path}`;
11
11
  const them = (paths, one, more) => (paths.length === 1 ? one : more);
12
+ /** Ignored folders that hold installs, caches or a host's link, which the run's commands change on purpose: never compared. */
13
+ const UNWATCHED = new Set(['node_modules', 'bower_components', '.venv', 'venv', 'vendor', '__pycache__', '.pnpm-store', '.yarn', '.gradle', '.cache', '.turbo', '.parcel-cache', '.vercel', '.netlify', '.wrangler']);
14
+ const unwatched = (rel) => rel.split('/').some((part, i, parts) => UNWATCHED.has(part) || (part === 'cache' && parts[i - 1] === '.next'));
15
+ /** How much of the ignored files is read, folders counted as entries: past either, a folder is left uncompared rather than half seen. */
16
+ const IGNORED_ENTRIES = 5000;
17
+ const IGNORED_BYTES = 64 * 1024 * 1024;
18
+ /** Files up to this size are compared by their bytes, larger ones by their size and time. */
19
+ const HASHED = 1024 * 1024;
12
20
  export function checkoutJournal(checkoutRoot) {
13
21
  const root = realpathSync(checkoutRoot);
14
22
  // As the person, outside the sandbox, as backToStartBranch runs it: no hooks, no fsmonitor.
@@ -60,7 +68,68 @@ export function checkoutJournal(checkoutRoot) {
60
68
  const rel = relative(root, path);
61
69
  return !rel || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel) ? null : rel.split(sep).join('/');
62
70
  };
71
+ /**
72
+ * The files git ignores, outside UNWATCHED, each with what it holds (`seen`), and the ignored folders too large to read
73
+ * (`unseen`); null when git could not list them.
74
+ */
75
+ const ignored = () => {
76
+ let listed;
77
+ try {
78
+ listed = git(['ls-files', '-z', '--others', '--ignored', '--exclude-standard', '--directory']).split('\0').filter((rel) => rel && !unwatched(rel.replace(/\/$/, '')));
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ const seen = new Map();
84
+ const unseen = [];
85
+ let count = 0, bytes = 0;
86
+ for (const top of listed) {
87
+ const found = new Map();
88
+ const look = (rel) => {
89
+ const path = join(root, rel);
90
+ let stat;
91
+ try {
92
+ stat = lstatSync(path);
93
+ }
94
+ catch {
95
+ return true;
96
+ }
97
+ if (++count > IGNORED_ENTRIES)
98
+ return false;
99
+ if (stat.isDirectory()) {
100
+ let names;
101
+ try {
102
+ names = readdirSync(path);
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ return names.every((name) => unwatched(`${rel}/${name}`) || look(`${rel}/${name}`));
108
+ }
109
+ if (!stat.isFile() || stat.size > HASHED)
110
+ found.set(rel, `${stat.mode}:${stat.size}:${stat.mtimeMs}`);
111
+ else {
112
+ if ((bytes += stat.size) > IGNORED_BYTES)
113
+ return false;
114
+ try {
115
+ found.set(rel, digest(readFileSync(path)));
116
+ }
117
+ catch {
118
+ return false;
119
+ }
120
+ }
121
+ return true;
122
+ };
123
+ if (look(top.replace(/\/$/, '')))
124
+ for (const [rel, held] of found)
125
+ seen.set(rel, held);
126
+ else
127
+ unseen.push(top);
128
+ }
129
+ return { seen, unseen };
130
+ };
63
131
  const atStart = untracked();
132
+ const ignoredAtStart = ignored();
64
133
  const files = new Map();
65
134
  const dirs = new Set();
66
135
  const current = (path) => {
@@ -259,13 +328,23 @@ export function checkoutJournal(checkoutRoot) {
259
328
  removeEmptyDirs();
260
329
  return lines(kept, failed);
261
330
  },
262
- appeared() {
331
+ appeared(branch) {
263
332
  const now = untracked();
264
- if (!atStart || !now)
265
- return [];
266
333
  const mine = new Set([...files.keys()].map(named));
267
- const fresh = [...now].filter((path) => !atStart.has(path) && !mine.has(path));
268
- return fresh.length ? [`${listPaths(fresh)} appeared in your checkout during the setup, from a command it ran or from you, so ${them(fresh, 'it stays', 'they stay')}.`] : [];
334
+ const fresh = atStart && now ? [...now].filter((path) => !atStart.has(path) && !mine.has(path)) : [];
335
+ const ignoredNow = ignoredAtStart && ignored();
336
+ // git status shows none of these: a build's output is the usual one, which the person may serve as it is.
337
+ let changed = [];
338
+ if (ignoredAtStart && ignoredNow) {
339
+ const unseen = [...ignoredAtStart.unseen, ...ignoredNow.unseen];
340
+ changed = [...new Set([...ignoredAtStart.seen.keys(), ...ignoredNow.seen.keys()])]
341
+ .filter((rel) => ignoredAtStart.seen.get(rel) !== ignoredNow.seen.get(rel) && !mine.has(rel) && !unseen.some((top) => rel === top || rel.startsWith(top.endsWith('/') ? top : `${top}/`)))
342
+ .sort();
343
+ }
344
+ return [
345
+ ...(fresh.length ? [`${listPaths(fresh)} appeared in your checkout during the setup, from a command it ran or from you, so ${them(fresh, 'it stays', 'they stay')}.`] : []),
346
+ ...(changed.length ? [`${listPaths(changed)}, which git ignores, changed during the setup: if ${them(changed, "it is", "they are")} a build's output, rebuild${branch ? ` on ${branch}` : ''} to put ${them(changed, 'it', 'them')} back.`] : []),
347
+ ];
269
348
  },
270
349
  };
271
350
  }
package/dist/main.js CHANGED
@@ -123,7 +123,7 @@ export async function main(argv) {
123
123
  backToStart = () => {
124
124
  const kept = journal.undo();
125
125
  const branch = backToStartBranch(checkout.root, startBranch);
126
- return [...kept, ...(branch ? [branch] : []), ...journal.appeared()];
126
+ return [...kept, ...(branch ? [branch] : []), ...journal.appeared(startBranch)];
127
127
  };
128
128
  // From the checkout's top, where the sandbox lays its own protections of a checkout.
129
129
  process.chdir(checkout.root);
@@ -6,10 +6,10 @@ Every run that saves a preview method, for the state's `before` item: the addres
6
6
 
7
7
  1. **Find the address**: the `production` address a host tool answered (impeccable_netlify_previews, the Netlify site's own; impeccable_cloudflare_previews, the Pages project's, `https://<project>.pages.dev`; impeccable_hosted_previews, the linked Vercel project's own domain where GitHub records a production deployment of it; impeccable_vercel_connect's `productionUrl` where production exists), else the live-site line the item's `proposal` read in the README, checked against the file, when it is the site's or project's own address on one of the hosts (an address elsewhere is only the host tools' to name). A project or site created in this run has no live app yet. Done when you have one address, or know there is none.
8
8
  2. **Prove it** with impeccable_prove_preview, under the status line *"Opening the live app"*: that address, or a page on it a visitor opens without signing in, and words the page shows that the checkout holds. A key the run already got in place (Vercel's bypass secret, Netlify's site password, Cloudflare's service token) goes with it where the lock is the same project's or site's. Done when it answers `proven: true`. **An address no key opens is not set**: a `gate`, the app's own sign-in on every page, or no answer leaves the item as it is, and the report says what the address answered.
9
- 3. **Save it** with impeccable_write to `preview`: `beforeUrl` the proven address, in the same write as the method where the proof came before it (`{"intendedMethod": "preview-deploy", "beforeUrl": "https://…"}`), else in a write of its own; `appKey` the app's on a repository of several apps, one address per app. Done when the answer carries `beforeUrl`; its `beforeCheck.access` says how Impeccable's own check opened it: `ok`; `auth-wall`, the app's sign-in, which the saved test accounts get past; anything else goes under "What is not verified".
9
+ 3. **Save it** with impeccable_write to `preview`: `beforeUrl` the proven address's root, `https://<host>` with no path, whatever page the proof opened (a login page is one role's, and the review signs in as another; impeccable_write saves the root either way), in the same write as the method where the proof came before it (`{"intendedMethod": "preview-deploy", "beforeUrl": "https://…"}`), else in a write of its own; `appKey` the app's on a repository of several apps, one address per app. Done when the answer carries `beforeUrl`; its `beforeCheck.access` says how Impeccable's own check opened it: `ok`; `auth-wall`, the app's sign-in, which the saved test accounts get past; anything else goes under "What is not verified".
10
10
 
11
11
  ### Reference
12
12
 
13
- - **The report** names the address under "What was configured, and by whom" (*The live app's address, https://…, as the before of every review, by the setup agent.*), or, with none set, says under "What was skipped, and why" what stopped it: no live app yet, no address known, or what the address answered.
13
+ - **The report** names the address under "What was configured, and by whom" (*The live app's address, https://…, as the before of every review, by the setup agent.*; where the proof opened a page other than the root, what the root does next, such as *A signed-out visit there lands on the login page.*, so that page never reads as the before), or, with none set, says under "What was skipped, and why" what stopped it: no live app yet, no address known, or what the address answered.
14
14
  - Never save a preview's address (one deployment's, with a hash or a branch in its name), a placeholder, or an address the proof did not answer with this app: impeccable_write refuses an unproven one. Clear nothing: an address the person saved stays.
15
15
  - **Status lines** belong to the Preview method task.
package/dist/playbook.js CHANGED
@@ -20,7 +20,7 @@ This run was started with --signed-in-test-drive: the test drive changes a page
20
20
  - **Accounts first**: set up the sign-in as the sign-in case says before the test drive's push, even where a host's case goes to pull-requests' step 4 earlier. The fence pushes and opens a test drive only for an app with a test account saved.
21
21
  - **No account saved** (the app has no sign-in of its own, or every account was skipped): the flag cannot be honoured. Push and open no test drive, on a public page or any other: mark the Test drive task failed and say why under "What was skipped, and why"; impeccable_report opens the report with the flag's line itself.
22
22
  - **The page** (step 2): the new page sits behind the sign-in, guarded as the app guards its other signed-in pages, and is linked from a page every role reaches once signed in, such as the page they land on; a login page or a page a visitor opens signed out holds no link. Where no page reaches every role, link it from one role's page and name that role in the test drive's body.
23
- - **The proof** (step 4): prove the preview on a page it shows a visitor signed out, the login page where the test drive's page sends that visitor to the sign-in.
23
+ - **The proof** (step 4): prove the preview on a page it shows a visitor signed out, the login page where the test drive's page sends that visitor to the sign-in. That proves the preview, not the before: the before stays the live app's root, as live-app-address says.
24
24
  - **The body** (step 5): it says the page is behind the sign-in, and which roles reach it.`;
25
25
  /** The system prompt: the setup, its steps, then the reference and the cases. The instructions JSON carries every write shape and the fence decides what runs; this restates neither. */
26
26
  export function playbook(repo) {
package/dist/tools.js CHANGED
@@ -2676,7 +2676,8 @@ export function createTools(ctx) {
2676
2676
  }
2677
2677
  }
2678
2678
  const testAccounts = target.url.endsWith('/test-accounts');
2679
- let body = p.body;
2679
+ // The before is the live app's root, whatever page the proof opened: a login page there is one role's, and the review, signed in as another, met it as a wall (#429).
2680
+ let body = target.url.endsWith('/preview') && saved && typeof saved.beforeUrl === 'string' ? { ...saved, beforeUrl: new URL(saved.beforeUrl).origin } : p.body;
2680
2681
  if (testAccounts) {
2681
2682
  // Only what the person typed into a hidden question is ever sent as a password.
2682
2683
  const list = object(body)?.accounts;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@impeccable/reviews",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "Set up a repository for Impeccable for PRs from your terminal.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "private": false,