@impeccable/reviews 0.1.5 → 0.1.6

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/main.js CHANGED
@@ -26,12 +26,12 @@ const execFileAsync = promisify(execFile);
26
26
  export const NO_FRONT_END = 'Impeccable reviews web front ends, and this repository does not have one. Nothing was set up.';
27
27
  /** How a run on a repository Impeccable skipped ends when this checkout has a web front end: the setup state lists it with nothing to set up until the person adds it. */
28
28
  const SKIPPED = 'Impeccable skipped this repository because its scan found no web front end, but this checkout has one. Choose Add anyway on the setup page, then run this again.';
29
- /** A pull request of owner/repo as gh sees it: its branch, its state and whether it is still a draft, or why gh could not say. */
29
+ /** A pull request of owner/repo as gh sees it: its branch, the branch it goes into, its state and whether it is still a draft, or why gh could not say. */
30
30
  async function viewPullRequest(slug, number) {
31
31
  try {
32
- const { stdout } = await execFileAsync('gh', ['pr', 'view', String(number), '--repo', slug, '--json', 'headRefName,state,isDraft'], { timeout: 30_000 });
32
+ const { stdout } = await execFileAsync('gh', ['pr', 'view', String(number), '--repo', slug, '--json', 'headRefName,baseRefName,state,isDraft'], { timeout: 30_000 });
33
33
  const pr = JSON.parse(stdout);
34
- return typeof pr.headRefName === 'string' && typeof pr.state === 'string' ? { headRefName: pr.headRefName, state: pr.state, isDraft: pr.isDraft === true } : { error: 'gh answered something else' };
34
+ return typeof pr.headRefName === 'string' && typeof pr.state === 'string' ? { headRefName: pr.headRefName, ...(typeof pr.baseRefName === 'string' ? { baseRefName: pr.baseRefName } : {}), state: pr.state, isDraft: pr.isDraft === true } : { error: 'gh answered something else' };
35
35
  }
36
36
  catch (err) {
37
37
  const { stderr, message } = err;
@@ -10,7 +10,7 @@ The checkout is set up for Netlify: a `netlify.toml`, a `.netlify/` folder, or a
10
10
  - **Skip**: set the preview up as server-previews or static-site says, and say under "What was skipped, and why" that Netlify was found and the sign-in was skipped. The steps below do not apply.
11
11
  2. **Link or create the site**: `netlify link --git-remote-url <the GitHub origin>` links a site Netlify already builds from this repository; when it finds none, `netlify sites:create --name <name>` creates one in the person's team and links this folder. Name it after the repository: lowercase letters, digits and hyphens, at most 40 characters; a name Netlify says is taken gets `-2`. Done when `netlify status` names the linked project.
12
12
  3. **Connect the repository** with impeccable_netlify_connect: it connects the linked site to this repository through Netlify's GitHub app, so pull requests build deploy previews, and asks the person for the app's access when Netlify cannot read the repository. Give `command` and `publish` only where no `netlify.toml` sets the build. Done when it answered `connected: true`; a site `netlify link` found is connected already, and it says so.
13
- 4. **Build and push the test drive** (the Test drive task) as pull-requests' steps 2 to 4 say, with the changes netlify-previews' reference gives: from the default branch, no setup pull request, built once with the build Netlify runs. Stage its files by name: `netlify sites:create` writes `.netlify/` and may add it to `.gitignore`, and that change stays out of the pull request. Done when `git push -u origin impeccable/test-drive` succeeded.
13
+ 4. **Build and push the test drive** (the Test drive task) as pull-requests' steps 2 to 4 say, with the changes netlify-previews' reference gives: from the default branch, no setup pull request, built once with the build Netlify runs. Stage the new page and the page that links to it by name: `netlify sites:create` writes `.netlify/` and may add it to `.gitignore`, and that change stays out of the pull request. Done when `git push -u origin impeccable/test-drive` succeeded.
14
14
  5. **Its draft, its preview proven and saved, then marked ready** as pull-requests' step 4 (Netlify) and step 5 say: the draft, whose deploy preview is the site's first, the wait for it by `pull`, the test drive's page proven on it, impeccable_write to `preview` as netlify-previews' step 2 says, `gh pr ready`, then the recording. The playbook's step 6 then has no pull request left to open: go on with the playbook's step 4. Done when impeccable_test_pr answered "Recorded".
15
15
 
16
16
  ### Reference
@@ -7,13 +7,13 @@ Every run, once the settings are saved. The person merges the setup pull request
7
7
  1. **The setup commit**, when the repository needs a change to keep previews working: the workflow and any fix the build needed, nothing else. With the workflow current on the default branch and no fix, there is none. Check access as workflow-as-person says, branch `impeccable/setup` from `origin/<defaultBranch>`, and bring in the workflow and commit as that case says. Push it in step 7, after the test drive. Done when that case's steps are done.
8
8
  2. **The test drive page**, always. The test drive shows the person a whole review: pages to capture, a finding for each measure, and a new page, which is what starts the review's visitor sessions. Branch `impeccable/test-drive` from the setup commit, so it carries the same workflow and its preview runs from its own head (from the default branch when there is no setup commit). Add one small, self-contained page in the repository's own framework, such as `/impeccable-test-drive`: a file where the routing finds pages, plus its styles, with a heading, a paragraph and a few elements in the app's own words (so the review judges it as one of the app's pages), and the flaws below. Link it from a page **a visitor opens without signing in**, the landing page when it is one, and keep the new page open without signing in too: no guard, on the server or in the browser, sends it to the sign-in. Done when the new page carries every flaw below with its comment, and the page you picked links to it.
9
9
  3. **Prove both pages** with impeccable_verify_preview, for a preview Impeccable builds itself (static-site): the input that proved the preview, with both pages in `entries`. A host's preview (vercel-previews, vercel-setup, netlify-previews, netlify-setup) is proven on the host's own build in step 4 instead; its case says how the branch is built here first. Done when it answers `verified: true`, or the preview is a host's.
10
- 4. **Push the test drive and prove its preview**: commit, `git push -u origin impeccable/test-drive`, then the way its preview comes:
10
+ 4. **Push the test drive and prove its preview**: commit the new page **and** the page that links to it, each staged by name, `git push -u origin impeccable/test-drive`, then the way its preview comes:
11
11
  - **Vercel** (vercel-previews, vercel-setup) builds every push: impeccable_hosted_previews with `branch: "impeccable/test-drive"` waits up to five minutes for its deployment of the pushed head, under a status line with its timer. Prove the test drive's page on that address with impeccable_prove_preview (past Vercel's login as preview-protection says), and save the method as the case says. Then step 5.
12
12
  - **Netlify** (netlify-previews, netlify-setup) builds a deploy preview only for a pull request: open the test drive now as a **draft**, step 5's `gh pr create` with `--draft`, which Impeccable leaves unreviewed. impeccable_netlify_previews with `pull` the number it printed waits up to five minutes for the deploy preview, under a status line with its timer. Prove the test drive's page on it, save the method as the case says, then `gh pr ready impeccable/test-drive --repo <owner>/<repo>` marks it ready, which starts the review. Then step 5's recording.
13
13
  - **Impeccable's own preview** (static-site): the workflow in the branch builds it on the pull request, and Impeccable's review holds up to eight minutes for that job before it renders, so step 3's proof and the workflow in the branch (from the setup commit) are all this step takes. Then step 5.
14
14
 
15
15
  A host's wait that ran out shows its own screen, which opens the host's page where its builds are turned on and waits again on the person's yes. Done when the test drive's page is proven on its preview, and a host's method saved; when nothing proved it, the reference below says how the run goes on.
16
- 5. **Open the test drive and record it**: `gh pr create --repo <owner>/<repo> --head impeccable/test-drive --base <defaultBranch>` with the title "Impeccable test drive (safe to close)" and a body that starts "This pull request only shows Impeccable reviewing a change. Close it when you are done; nothing here needs to be merged." and then names the two pages and lists the flaws planted for the review to find, one line each (a Netlify draft opened this way in step 4, and was marked ready there). Record it right away with impeccable_test_pr, so the web page follows its review; it refuses a draft. Done when impeccable_test_pr answered "Recorded".
16
+ 5. **Open the test drive and record it**: `gh pr create --repo <owner>/<repo> --head impeccable/test-drive --base <defaultBranch>` with the title "Impeccable test drive (safe to close)" and a body that starts "This pull request only shows Impeccable reviewing a change. Close it when you are done; nothing here needs to be merged." and then names the pages its diff holds, no more, and lists the flaws planted for the review to find, one line each (a Netlify draft opened this way in step 4, and was marked ready there). Record it right away with impeccable_test_pr, so the web page follows its review; it refuses a draft, and a branch that changes no existing page. Done when impeccable_test_pr answered "Recorded".
17
17
  6. **Check the test drive's preview once**, while it is the newest open pull request: impeccable_write to `latest-pull-check` with the app's `appKey`. Done when it answered; this early a 409 `no-preview-running` or a job still starting is expected, and the preview goes under "What is not verified".
18
18
  7. **Open the setup pull request**, when there is a setup commit: `git push -u origin impeccable/setup`, then `gh pr create --repo <owner>/<repo> --head impeccable/setup --base <defaultBranch>` with the title "Set up Impeccable previews" and a body that starts "Merge this to keep Impeccable's previews working." and then says what it carries and why, one line each. Record it with impeccable_test_pr: the test drive again, and this one in `setup_pr`. Done when impeccable_test_pr answered "Recorded".
19
19
 
@@ -11,7 +11,7 @@ The checkout is set up for Vercel (`vercel.json`, `.vercel/project.json`, a `ver
11
11
  - **Skip**: set the preview up as server-previews or static-site says, and say under "What was skipped, and why" that Vercel was found and the sign-in was skipped. The steps below do not apply.
12
12
  2. **Create the project** (run each Vercel command plain: its answer is short, and a pipe to `tail` is refused): `vercel link --yes --team <the team>` creates the project in that team, named after the folder (or links the one that has that name), and connects the GitHub `origin`; the CLI names no team on its own without a terminal, so the option is given whenever there is a team. Done when it prints the linked project.
13
13
  3. **Connect the repository, and lock the project before its first deployment**, with impeccable_vercel_connect: it runs the connection on the linked project, asks the person for Vercel's GitHub app's access when the app cannot reach the repository, and then, on a project with no production deployment, puts Vercel's login in front of every address of the project, its own included. Vercel makes a new project's first deployment Production whatever branch it builds and serves it at the project's own address, and the test drive's push is that first deployment. When Vercel refuses the lock, the tool asks the person on one screen to turn it on in the project's settings and waits for it. Done when it answered `connected: true` with `production: "exists"` or `locked: true`; a project connected by the link already is, and it says so.
14
- 4. **Build and push the test drive** (the Test drive task) as pull-requests' steps 2 to 4 say, with the changes vercel-previews' reference gives: from the default branch, no setup pull request, built once with plain commands. Stage its files by name: `vercel link` writes `.vercel/` and a `.env.local` that holds a Vercel token, and may add `.vercel` to `.gitignore`, and none of that goes into the pull request. Its push starts the project's first deployment. Done when `git push -u origin impeccable/test-drive` succeeded.
14
+ 4. **Build and push the test drive** (the Test drive task) as pull-requests' steps 2 to 4 say, with the changes vercel-previews' reference gives: from the default branch, no setup pull request, built once with plain commands. Stage the new page and the page that links to it by name: `vercel link` writes `.vercel/` and a `.env.local` that holds a Vercel token, and may add `.vercel` to `.gitignore`, and none of that goes into the pull request. Its push starts the project's first deployment. Done when `git push -u origin impeccable/test-drive` succeeded.
15
15
  5. **Its preview, proven and saved, then its pull request** as pull-requests' step 4 (Vercel) and step 5 say: the wait for the pushed head's deployment, the test drive's page proven on the address it lists (past Vercel's login as preview-protection says), impeccable_write to `preview` as vercel-previews' step 2 says, then the pull request and its recording. The playbook's step 6 then has no pull request left to open: go on with the playbook's step 4. Done when impeccable_test_pr answered "Recorded".
16
16
 
17
17
  ### Reference
package/dist/tools.js CHANGED
@@ -303,7 +303,7 @@ async function runPullRequest(ctx, pr, branch, signal) {
303
303
  if (!await (ctx.setupPullHeadReader ?? readSetupPullHead)(ctx.owner, ctx.repo, pr.number, found.headRefName, { signal })) {
304
304
  return { problem: `the branch of #${pr.number} could not be verified in this repository; a fork's branch cannot be recorded` };
305
305
  }
306
- return { branch: found.headRefName };
306
+ return { branch: found.headRefName, ...(found.baseRefName ? { base: found.baseRefName } : {}) };
307
307
  }
308
308
  const PREVIEW_COMMAND = { type: 'object', additionalProperties: false, required: ['command'], properties: {
309
309
  command: { type: 'string', minLength: 1, description: 'A command of the repository\'s own scripts or tools, without cd.' },
@@ -771,6 +771,20 @@ export async function driveCoversSetup(cwd, setupBranch, driveBranch) {
771
771
  return err.code === 1 ? false : null;
772
772
  }
773
773
  }
774
+ /**
775
+ * What the pushed `head` changes against `base`, as their remote-tracking refs in this checkout show GitHub: each file's
776
+ * `git diff --name-status` letter and its path as it is, never quoted; a rename as a removal and an addition. Null when either ref is missing or git fails.
777
+ */
778
+ export async function pushedChanges(cwd, base, head) {
779
+ try {
780
+ const { stdout } = await execFileAsync('git', ['diff', '-z', '--name-status', '--no-renames', `refs/remotes/origin/${base}...refs/remotes/origin/${head}`], { cwd, timeout: 10_000, env: gitEnvironment() });
781
+ const fields = stdout.split('\0');
782
+ return Array.from({ length: Math.floor(fields.length / 2) }, (_, i) => ({ status: fields[2 * i], path: fields[2 * i + 1] }));
783
+ }
784
+ catch {
785
+ return null;
786
+ }
787
+ }
774
788
  const LIMIT = 20;
775
789
  const WORKFLOW = '.github/workflows/impeccable-preview.yml';
776
790
  const STATUSES = ['queued', 'requested', 'waiting', 'pending', 'in_progress', 'completed'];
@@ -1998,7 +2012,7 @@ export function createTools(ctx) {
1998
2012
  reportTasks(tasks);
1999
2013
  return 'Saved.';
2000
2014
  }),
2001
- tool('impeccable_test_pr', 'Record the pull requests', 'Record the test drive as soon as it is open, so the web page follows its review, and again with setup_pr once the setup pull request is open. Each must be an open pull request from this repository\'s own impeccable/ branch; the setup pull request, once recorded, cannot change.', TEST_PR, async (p, _pi, signal) => {
2015
+ tool('impeccable_test_pr', 'Record the pull requests', 'Record the test drive as soon as it is open, so the web page follows its review, and again with setup_pr once the setup pull request is open. Each must be an open pull request from this repository\'s own impeccable/ branch, the test drive\'s also changing the existing page that links to it; the setup pull request, once recorded, cannot change.', TEST_PR, async (p, pi, signal) => {
2002
2016
  const drive = { number: p.number, url: p.url.trim() };
2003
2017
  const given = p.setup_pr;
2004
2018
  const setup = given && { number: given.number, url: given.url.trim() };
@@ -2007,6 +2021,12 @@ export function createTools(ctx) {
2007
2021
  const found = await runPullRequest(ctx, drive, 'impeccable/test-drive', signal);
2008
2022
  if ('problem' in found)
2009
2023
  return `Refused: ${found.problem}.`;
2024
+ // The page that links to the test drive is an existing file the branch changes; files under dot folders (.github, .netlify) and dotfiles never link to it (#241).
2025
+ const changes = !recordedBranches && found.base ? await (ctx.pushedChanges ?? pushedChanges)(pi.cwd, found.base, found.branch) : null;
2026
+ const files = (changes ?? []).filter(({ path }) => !path.split('/').some((part) => part.startsWith('.')));
2027
+ if (files.length && files.every(({ status }) => status === 'A')) {
2028
+ return `Refused: ${found.branch} only adds ${files.map(({ path }) => path).join(', ')} and changes no existing page, so nothing links to the test drive. Commit the page that links to it and push, prove the new push's preview again as pull-requests' step 4 says, then record it again.`;
2029
+ }
2010
2030
  let setupBranch = recordedBranches?.setup;
2011
2031
  if (setup) {
2012
2032
  // The setup pull request carries the preview attempts, so its binding cannot change.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@impeccable/reviews",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
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,