@impeccable/reviews 0.1.5 → 0.1.7

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.
Files changed (41) hide show
  1. package/README.md +21 -20
  2. package/dist/agent.js +9 -4
  3. package/dist/cloudflare-access.js +77 -47
  4. package/dist/cloudflare.js +325 -23
  5. package/dist/fence.js +939 -143
  6. package/dist/focus.js +5 -4
  7. package/dist/host-previews.js +107 -0
  8. package/dist/host-questions.js +90 -20
  9. package/dist/hosted.js +72 -21
  10. package/dist/http.js +2 -0
  11. package/dist/log.js +7 -1
  12. package/dist/login.js +45 -13
  13. package/dist/main.js +32 -17
  14. package/dist/netlify-password.js +8 -6
  15. package/dist/netlify.js +43 -14
  16. package/dist/pi-paths.js +69 -0
  17. package/dist/playbook/cases/cloudflare-previews.md +12 -4
  18. package/dist/playbook/cases/first-preview-failure.md +1 -1
  19. package/dist/playbook/cases/live-app-address.md +15 -0
  20. package/dist/playbook/cases/monorepo.md +1 -1
  21. package/dist/playbook/cases/netlify-previews.md +3 -1
  22. package/dist/playbook/cases/netlify-setup.md +8 -7
  23. package/dist/playbook/cases/preview-protection.md +2 -1
  24. package/dist/playbook/cases/pull-requests.md +4 -4
  25. package/dist/playbook/cases/static-site.md +2 -2
  26. package/dist/playbook/cases/vercel-previews.md +2 -0
  27. package/dist/playbook/cases/vercel-setup.md +9 -6
  28. package/dist/playbook.js +2 -2
  29. package/dist/preflight.js +36 -7
  30. package/dist/report.js +1 -0
  31. package/dist/sandbox.js +364 -50
  32. package/dist/scan.js +134 -0
  33. package/dist/sync.js +44 -7
  34. package/dist/tools.js +397 -88
  35. package/dist/ui/bridge.js +18 -4
  36. package/dist/ui/look.js +1 -1
  37. package/dist/ui/screens.js +5 -3
  38. package/dist/ui/sensitive.js +10 -0
  39. package/dist/vercel-bypass.js +12 -7
  40. package/dist/vercel.js +110 -17
  41. package/package.json +1 -1
package/dist/ui/bridge.js CHANGED
@@ -1,10 +1,24 @@
1
1
  import { createInterface } from 'node:readline';
2
2
  import { Writable } from 'node:stream';
3
- import { noteSensitiveAnswer } from './sensitive.js';
3
+ import { noteSensitiveAnswer, plainText } from './sensitive.js';
4
4
  let mounted = null;
5
+ /** A question as the person sees it: every line of it plain text. */
6
+ function shown(q) {
7
+ const out = { ...q, prompt: plainText(q.prompt) };
8
+ for (const key of ['title', 'hint', 'prefill', 'caution', 'confirmLabel', 'skipLabel']) {
9
+ const text = q[key];
10
+ if (text !== undefined)
11
+ out[key] = plainText(text);
12
+ }
13
+ if (q.fields)
14
+ out.fields = [plainText(q.fields[0]), plainText(q.fields[1])];
15
+ if (q.options)
16
+ out.options = q.options.map((o) => ({ ...o, label: plainText(o.label) }));
17
+ return out;
18
+ }
5
19
  /** Asks the person: the question on the screen, or printed with one line read from stdin. */
6
20
  export async function askPerson(q) {
7
- const answer = await (mounted ? mounted.ask(q) : askOnStdin(q));
21
+ const answer = await (mounted ? mounted.ask(shown(q)) : askOnStdin(shown(q)));
8
22
  if (q.sensitive)
9
23
  for (const value of [answer.value, ...(answer.values ?? [])])
10
24
  if (value)
@@ -15,11 +29,11 @@ export async function askPerson(q) {
15
29
  }
16
30
  /** A status line for the status band. */
17
31
  export function reportStatus(line) {
18
- mounted?.status(line);
32
+ mounted?.status(plainText(line));
19
33
  }
20
34
  /** Replaces the task list. */
21
35
  export function reportTasks(tasks) {
22
- mounted?.tasks(tasks);
36
+ mounted?.tasks(tasks.map((t) => ({ ...t, title: plainText(t.title) })));
23
37
  }
24
38
  /** The run waits on something outside it (a host's build): the screen counts the time up beside the status line until `null` ends it. */
25
39
  export function reportWait(wait) {
package/dist/ui/look.js CHANGED
@@ -304,7 +304,7 @@ function pageOf(f, inner) {
304
304
  heading: scene.again ? 'Sign in again' : START.heading,
305
305
  text: said(scene.again ? AGAIN[scene.again] : 'Sign in with GitHub in your browser, then approve this repository.', inner, st),
306
306
  rule: divider(inner, st),
307
- action: [...checkout, ...link, ...(under ? ['', center(st.full(under), inner)] : []), ...(scene.copied === false ? ['', copyWay(inner, st)] : [])],
307
+ action: [...checkout, ...link, ...(under ? ['', center(st.full(under), inner)] : []), ...(scene.copied ? [] : ['', copyWay(inner, st)])],
308
308
  };
309
309
  }
310
310
  case 'run':
@@ -7,6 +7,7 @@ import { mountUi } from './bridge.js';
7
7
  import { createFrameWriter, frameState } from './frames.js';
8
8
  import { CLOSES_AFTER_MINUTES, drawFrame, OUTCOME, PRODUCT, palette } from './look.js';
9
9
  import { RunState } from './run-state.js';
10
+ import { plainText } from './sensitive.js';
10
11
  /** What the command does before a screen ends it at once (Ctrl+C before a run, or a signal): report the stop (main.ts), briefly. */
11
12
  let beforeExit = async () => { };
12
13
  export function onScreenExit(fn) {
@@ -439,7 +440,7 @@ export function showIntro(repository, note) {
439
440
  }
440
441
  /**
441
442
  * The sign-in screen, in place of whatever is showing, until `close`: the
442
- * link, put on the clipboard as it opens (`onCopy`, and again on C), and a way
443
+ * link, put on the clipboard only when the person presses C (`onCopy`), and a way
443
444
  * to paste the code from a remote shell (P calls `onPaste`, and a null from it
444
445
  * says the code was wrong); Esc calls `onCancel`. Over a run it asks to sign
445
446
  * in again.
@@ -453,7 +454,6 @@ export function showSignIn(link, opts) {
453
454
  }
454
455
  const view = { kind: 'signin', link, opts, pasting: false, again: screen.views.some((v) => v.kind === 'run') && (opts.why ?? 'ran-out') };
455
456
  screen.push(view);
456
- screen.copy(view);
457
457
  return { close: () => screen.remove(view) };
458
458
  }
459
459
  /**
@@ -505,7 +505,9 @@ export async function showPullRequest(url, hand) {
505
505
  });
506
506
  }
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
- export async function showOutro(o, hand) {
508
+ export async function showOutro(outro, hand) {
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) };
509
511
  const screen = host();
510
512
  if (screen) {
511
513
  const copied = o.link && hand ? await handOver(o.link, hand) : false;
@@ -12,3 +12,13 @@ export function noteSensitiveAnswer(value) {
12
12
  for (const listener of listeners)
13
13
  listener(value);
14
14
  }
15
+ /**
16
+ * What makes a terminal act rather than show text: an escape sequence (CSI, OSC, a string such as DCS, or two characters) or a control
17
+ * character other than a tab or a newline. OSC 52 writes the clipboard, OSC 0 the window's title, OSC 8 links words to another address.
18
+ * An OSC or a string that is never ended runs to the next escape, as the terminal reads it.
19
+ */
20
+ const CONTROL = /\x1b(?:\[[0-?]*[ -/]*[@-~]|[\]PX^_][^\x07\x1b]*(?:\x07|\x1b\\)?|[ -/]*[0-~])?|[\x00-\x08\x0b-\x1f\x7f-\x9f]/g;
21
+ /** `text` without what CONTROL matches: the model's words, and the repository's through them, as the screen and the scrollback show them. */
22
+ export function plainText(text) {
23
+ return text.replace(CONTROL, '');
24
+ }
@@ -27,17 +27,21 @@ export const makeApi = (link) => [
27
27
  '--raw', '--non-interactive',
28
28
  ];
29
29
  const record = (value) => (value !== null && typeof value === 'object' && !Array.isArray(value) ? value : undefined);
30
+ /** The ids as Vercel makes them, and nothing else goes into a request's path: the file is in the checkout, which the repository and the sandboxed agent write. */
31
+ const PROJECT_ID = /^prj_[A-Za-z0-9]+$/;
32
+ const ORG_ID = /^(?:team_)?[A-Za-z0-9]+$/;
33
+ /** The link as the file has it; null with no file, or ids that are not Vercel's. What project the ids name is the CLI's to confirm (readOwnProject in vercel.ts). */
30
34
  export async function readLink(cwd) {
31
35
  try {
32
36
  const link = record(JSON.parse(await readFile(join(cwd, '.vercel', 'project.json'), 'utf8')));
33
- return typeof link?.projectId === 'string' && typeof link.orgId === 'string' ? { projectId: link.projectId, orgId: link.orgId } : null;
37
+ return typeof link?.projectId === 'string' && PROJECT_ID.test(link.projectId) && typeof link.orgId === 'string' && ORG_ID.test(link.orgId) ? { projectId: link.projectId, orgId: link.orgId } : null;
34
38
  }
35
39
  catch {
36
40
  return null;
37
41
  }
38
42
  }
39
- /** The linked project's name and automation secrets; null when the CLI could not say: signed out, not linked, or the role does not allow it. */
40
- async function readProject(run) {
43
+ /** The linked project's name and automation secrets; null when the CLI could not say: signed out, not linked, or the role does not allow it; or when what it read is not the project `link` names. */
44
+ async function readProject(run, link) {
41
45
  let settings;
42
46
  try {
43
47
  const printed = await run(READ);
@@ -46,7 +50,7 @@ async function readProject(run) {
46
50
  catch {
47
51
  return null;
48
52
  }
49
- if (typeof settings?.projectId !== 'string')
53
+ if (typeof settings?.projectId !== 'string' || (link && settings.projectId !== link.projectId))
50
54
  return null;
51
55
  const secrets = Object.entries(record(settings.protectionBypass) ?? {}).flatMap(([value, meta]) => {
52
56
  const m = record(meta);
@@ -85,7 +89,8 @@ async function make(run, link) {
85
89
  * what it gave opens nothing, the person may paste one.
86
90
  */
87
91
  export async function passVercelLogin(deps) {
88
- const project = await readProject(deps.run);
92
+ const link = await deps.link();
93
+ const project = await readProject(deps.run, link);
89
94
  const from = project?.name ? { project: project.name } : {};
90
95
  const finish = async (secret, how) => {
91
96
  deps.remember(secret);
@@ -111,10 +116,10 @@ export async function passVercelLogin(deps) {
111
116
  if (!(await deps.consent()))
112
117
  return { status: 'skipped', reason: 'The person skipped creating a bypass secret.', ...from };
113
118
  const before = new Set(project.secrets.map((s) => s.value));
114
- if (!(await make(deps.run, await deps.link())))
119
+ if (!(await make(deps.run, link)))
115
120
  stopped = 'The Vercel CLI could not make a bypass secret on the project: that takes the Member role or Project Administrator.';
116
121
  else {
117
- const ours = pick((await readProject(deps.run))?.secrets ?? [], before);
122
+ const ours = pick((await readProject(deps.run, link))?.secrets ?? [], before);
118
123
  if (!ours)
119
124
  stopped = 'The secret the Vercel CLI made could not be read back.';
120
125
  else {
package/dist/vercel.js CHANGED
@@ -1,4 +1,8 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
1
3
  import { setTimeout as sleep } from 'node:timers/promises';
4
+ import { matchesBranch, TEST_DRIVE_BRANCH } from './host-previews.js';
5
+ import { readLink } from './vercel-bypass.js';
2
6
  /** `vercel whoami`: the CLI prints the username, or says it has no credentials and exits 1. */
3
7
  export async function vercelStatus(vercel) {
4
8
  try {
@@ -80,6 +84,8 @@ const projectApi = (link, change) => [
80
84
  '--raw', '--non-interactive',
81
85
  ];
82
86
  export const readProjectApi = (link) => projectApi(link);
87
+ /** `vercel api` reading one deployment by its host, GET /v13/deployments/{idOrUrl} (https://vercel.com/docs/rest-api/deployments/get-a-deployment), which names the project it belongs to; a team's deployment needs its team on the request. */
88
+ export const readDeploymentApi = (link, host) => ['api', `/v13/deployments/${encodeURIComponent(host)}${link.orgId.startsWith('team_') ? `?teamId=${encodeURIComponent(link.orgId)}` : ''}`, '--raw', '--non-interactive'];
83
89
  /**
84
90
  * Vercel Authentication on All Deployments: Vercel's login in front of every address of the project, its production
85
91
  * domains included (https://vercel.com/docs/deployment-protection#all-deployments), as the endpoint takes it
@@ -92,39 +98,126 @@ const printedProject = async (vercel, args) => {
92
98
  return record(JSON.parse(printed.slice(printed.indexOf('{'), printed.lastIndexOf('}') + 1)));
93
99
  };
94
100
  const scope = (project) => record(project?.ssoProtection)?.deploymentType;
95
- /** The linked project as `vercel api` prints it: its production deployment (the production target, or one among the latest) unless every one failed or was cancelled, its Git link's production branch, its protection and its deployments' hosts. Null when the CLI could not read it. */
101
+ /** The linked project as `vercel api` prints it: its production deployment (the production target, or one among the latest) unless every one failed or was cancelled, its Git link's repository and production branch, its protection and its deployments' hosts. Null when the CLI could not read it. */
96
102
  export async function readVercelProject(vercel, link) {
103
+ const project = await readLinkedProject(vercel, link);
104
+ return project === 'missing' ? null : project;
105
+ }
106
+ /** Vercel's answer for a project it has no record of: the API's error, or the CLI's word for it. */
107
+ const NOT_FOUND = /"code"\s*:\s*"not_found"|project not found/i;
108
+ /** The linked project as readVercelProject reads it, or `missing` when Vercel answers that no such project exists (the link file names one that was deleted, or another team's), as told from a read that failed, which says nothing either way. */
109
+ export async function readLinkedProject(vercel, link) {
97
110
  let project;
98
111
  try {
99
112
  project = await printedProject(vercel, readProjectApi(link));
100
113
  }
101
- catch {
102
- return null;
114
+ catch (err) {
115
+ const said = String(err.stdout ?? '') + String(err.stderr ?? '');
116
+ return NOT_FOUND.test(said) ? 'missing' : null;
103
117
  }
118
+ if (record(project?.error)?.code === 'not_found')
119
+ return 'missing';
104
120
  if (project?.id !== link.projectId)
105
121
  return null;
106
122
  const target = record(record(project.targets)?.production);
107
123
  const latest = (Array.isArray(project.latestDeployments) ? project.latestDeployments : []).map(record).filter((d) => d !== undefined);
108
124
  const failed = (d) => d.readyState === 'ERROR' || d.readyState === 'CANCELED';
109
125
  const exists = (target !== undefined && !failed(target)) || latest.some((d) => d.target === 'production' && !failed(d));
110
- const branch = record(project.link)?.productionBranch;
126
+ const git = record(project.link);
127
+ const branch = git?.productionBranch;
128
+ const repository = git?.type === 'github' && typeof git.org === 'string' && typeof git.repo === 'string' && git.org && git.repo ? `${git.org}/${git.repo}`.toLowerCase() : undefined;
111
129
  const protection = scope(project);
130
+ // Each method is an object while on, null or absent while off (https://vercel.com/docs/rest-api/projects/update-an-existing-project); every scope but Trusted IPs' "production" covers the preview addresses.
131
+ const ips = record(project.trustedIps);
132
+ const previewsProtected = record(project.ssoProtection) !== undefined || record(project.passwordProtection) !== undefined || (ips !== undefined && ips.deploymentType !== 'production');
133
+ const hostname = (value) => (typeof value === 'string' && /^(https?:\/\/)?[a-z0-9.-]{1,253}$/i.test(value) ? value.replace(/^https?:\/\//, '').toLowerCase() : undefined);
112
134
  const hosts = [target, ...latest].flatMap((d) => (typeof d?.url === 'string' && d.url ? [d.url.replace(/^https?:\/\//, '').toLowerCase()] : []));
113
- return { ...(typeof project.name === 'string' && project.name ? { name: project.name } : {}), production: exists ? 'exists' : 'none', ...(typeof branch === 'string' && branch ? { productionBranch: branch } : {}), protection: typeof protection === 'string' ? protection : null, deploymentHosts: new Set(hosts) };
135
+ const ignore = project.commandForIgnoringBuildStep;
136
+ // 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.
137
+ const live = target && !failed(target) ? (Array.isArray(target.alias) ? target.alias : []).map(hostname).find(Boolean) : undefined;
138
+ return { ...(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' };
139
+ }
140
+ /**
141
+ * The linked project as this repository's: read now, and connected to the repository on Vercel, which its Git link
142
+ * says. The link file names whatever project the person can administer (the repository, or the sandboxed agent, writes
143
+ * it), so the run locks, and sends the secrets of, only a project this read vouches for; `reason` says what stopped it.
144
+ */
145
+ export async function readOwnProject(vercel, link, repository) {
146
+ const project = await readVercelProject(vercel, link);
147
+ if (!project)
148
+ return { reason: 'The Vercel CLI could not read the project (vercel api): it is signed out, or this CLI has no api command.' };
149
+ if (project.repository !== repository.toLowerCase())
150
+ return { reason: `The Vercel project this folder is linked to${project.name ? ` (${project.name})` : ''} is not connected to this repository on Vercel.` };
151
+ return project;
152
+ }
153
+ /**
154
+ * Whether the project made the deployment at `host`: one of its latest, as the project read lists them; else, since a
155
+ * project lists its newest deployments alone while an older pull request's preview stays served, the deployment read
156
+ * on its own by its host, as the person, which names its project. False when that read fails: another team's, or none.
157
+ */
158
+ export async function projectMadeDeployment(vercel, link, project, host) {
159
+ if (project.deploymentHosts.has(host))
160
+ return true;
161
+ try {
162
+ return (await printedProject(vercel, readDeploymentApi(link, host)))?.projectId === link.projectId;
163
+ }
164
+ catch {
165
+ return false;
166
+ }
167
+ }
168
+ /**
169
+ * Whether Vercel builds the test drive's branch at all, as the checkout's vercel.json
170
+ * (https://vercel.com/docs/project-configuration/git-configuration#git.deploymentenabled: `git.deploymentEnabled` false
171
+ * turns every automatic deployment off, and a branch rule, in minimatch syntax, turns its branches off unless a rule
172
+ * says true) and the linked project say: an Ignored Build Step decides commit by commit, so the answer is unknown; with
173
+ * Vercel's GitHub deployments off, Impeccable finds no preview. The linked project counts only as this repository's
174
+ * (readOwnProject): another repository's settings are never the fact. Nothing here changes Vercel: the fact is reported.
175
+ * Undefined where the checkout names no Vercel setting and is linked to no project.
176
+ */
177
+ export async function vercelPreviewsFact(cwd, repository, vercel) {
178
+ let setting;
179
+ try {
180
+ setting = record(record(JSON.parse(await readFile(join(cwd, 'vercel.json'), 'utf8')))?.git)?.deploymentEnabled;
181
+ }
182
+ catch {
183
+ // No vercel.json, or none that reads as JSON: Vercel's default, every branch.
184
+ }
185
+ const link = await readLink(cwd);
186
+ if (setting === undefined && !link)
187
+ return undefined;
188
+ // The CLI runs only for a linked project, and only this repository's own counts.
189
+ const own = link ? await readOwnProject(vercel(), link, repository) : null;
190
+ const project = own && !('reason' in own) ? own : null;
191
+ const fact = { project: project?.name ?? '', enabled: true, ...(project?.productionBranch ? { productionBranch: project.productionBranch } : {}) };
192
+ if (setting === false)
193
+ return { ...fact, enabled: false, reason: `vercel.json turns automatic deployments off for every branch (git.deploymentEnabled: false), so a push of ${TEST_DRIVE_BRANCH} builds nothing.` };
194
+ const branches = record(setting);
195
+ if (branches) {
196
+ const matched = Object.entries(branches).filter(([rule]) => matchesBranch(rule, TEST_DRIVE_BRANCH, true));
197
+ if (matched.length && !matched.some(([, on]) => on === true))
198
+ return { ...fact, enabled: false, reason: `vercel.json turns automatic deployments off for ${TEST_DRIVE_BRANCH} (git.deploymentEnabled: "${matched[0][0]}": false).` };
199
+ }
200
+ if (own && 'reason' in own)
201
+ return { ...fact, enabled: 'unknown', reason: own.reason };
202
+ if (project && !project.githubDeployments)
203
+ return { ...fact, enabled: false, reason: 'The project records no deployments on GitHub (Vercel GitHub deployments are off), and GitHub\'s record is where Impeccable finds Vercel\'s previews.' };
204
+ if (project?.ignoredBuildStep)
205
+ return { ...fact, enabled: 'unknown', reason: 'An Ignored Build Step is set on the project (Settings → Git), which decides commit by commit whether a push builds.' };
206
+ return fact;
114
207
  }
115
208
  /**
116
209
  * The linked project before its first push: read through `vercel api`, and, with no production deployment, locked
117
210
  * behind Vercel's login at every address, since Vercel makes a new project's first deployment Production whatever
118
211
  * branch it builds and Standard Protection serves production domains to anyone. A project that deploys production
119
- * already (live or building), or is locked already, is left as it is.
212
+ * already (live or building), or is locked already, is left as it is, and so is one not connected to `repository`.
120
213
  */
121
- export async function prepareFirstDeployment(vercel, link, lock = true) {
122
- const project = await readVercelProject(vercel, link);
123
- if (!project)
124
- return { production: 'unknown', locked: false, reason: 'The Vercel CLI could not read the project (vercel api): it is signed out, or this CLI has no api command.' };
214
+ export async function prepareFirstDeployment(vercel, link, repository, lock = true) {
215
+ const project = await readOwnProject(vercel, link, repository);
216
+ if ('reason' in project)
217
+ return { production: 'unknown', locked: false, reason: project.reason };
125
218
  const branch = { ...(project.productionBranch ? { productionBranch: project.productionBranch } : {}), ...(project.name ? { project: project.name } : {}) };
126
219
  if (project.production === 'exists')
127
- return { production: 'exists', ...branch };
220
+ return { production: 'exists', ...branch, ...(project.productionUrl ? { productionUrl: project.productionUrl } : {}) };
128
221
  if (project.protection === 'all')
129
222
  return { production: 'none', locked: true, ...branch };
130
223
  if (!lock)
@@ -146,9 +239,9 @@ export function vercelProjectPage(page, team, project) {
146
239
  return team && project ? `https://vercel.com/${encodeURIComponent(team)}/${encodeURIComponent(project)}/${page}` : `https://vercel.com/d?to=${encodeURIComponent(`/[team]/[project]/${page}`)}`;
147
240
  }
148
241
  /** Reads the project again every `wait.everyMs` until Vercel's login stands in front of every address of it, or it deploys production, or `wait.totalMs` has passed (a read that failed counts as neither): the person is locking it meanwhile. */
149
- export async function waitForVercelLock(vercel, link, deps) {
242
+ export async function waitForVercelLock(vercel, link, repository, deps) {
150
243
  for (const end = Date.now() + deps.wait.totalMs;;) {
151
- const first = await prepareFirstDeployment(vercel, link, false);
244
+ const first = await prepareFirstDeployment(vercel, link, repository, false);
152
245
  // A read that failed says nothing yet, so the wait goes on.
153
246
  if (first.production === 'exists' || first.locked || Date.now() >= end || deps.signal?.aborted)
154
247
  return first;
@@ -163,9 +256,9 @@ export async function waitForVercelLock(vercel, link, deps) {
163
256
  /**
164
257
  * The hosts of the linked project's deployments while Vercel's login stands in front of every address of it, read
165
258
  * now: a Production deployment GitHub lists for a pushed branch is its preview only when it is one of these. Empty
166
- * when the project is not locked at every address, or could not be read.
259
+ * when the project is not locked at every address, is not this repository's, or could not be read.
167
260
  */
168
- export async function lockedDeploymentHosts(vercel, link) {
169
- const project = await readVercelProject(vercel, link);
170
- return project?.protection === 'all' ? project.deploymentHosts : new Set();
261
+ export async function lockedDeploymentHosts(vercel, link, repository) {
262
+ const project = await readOwnProject(vercel, link, repository);
263
+ return !('reason' in project) && project.protection === 'all' ? project.deploymentHosts : new Set();
171
264
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@impeccable/reviews",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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,