@vercel/board 0.24.0 → 0.25.1
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/{a2-BxfKZFvJ.mjs → a2-A830f5NS.mjs} +6 -6
- package/dist/access.d.mts +1 -2
- package/dist/access.mjs +3 -3
- package/dist/{actors-rPnEQLZu.mjs → actors-BLXVp4oZ.mjs} +2 -2
- package/dist/{auth-5nLsx9qB.mjs → auth-lTkOgvcE.mjs} +1 -1
- package/dist/{backend-D0GDYiFr.d.mts → backend-ZKYTe3xa.d.mts} +5 -11
- package/dist/backend-a2/blob-vercel.d.mts +1 -1
- package/dist/backend-a2/contract.d.mts +1 -2
- package/dist/backend-a2/store-memory.d.mts +1 -1
- package/dist/backend-a2/store-postgres.d.mts +1 -1
- package/dist/backend-a2/store-redis-http.d.mts +1 -1
- package/dist/backend-a2/store-redis.d.mts +1 -1
- package/dist/backend-a2/store-sqlite.d.mts +1 -1
- package/dist/backend-a2.d.mts +5 -6
- package/dist/backend-a2.mjs +1 -1
- package/dist/{backend-http-tGUo0e8j.mjs → backend-http-CJd4Fu0U.mjs} +2 -2
- package/dist/{backend-http-rfiG-AKP.mjs → backend-http-CYtfwqAW.mjs} +9 -6
- package/dist/{backend-http-Dvq2p__1.d.mts → backend-http-E7YU0qhn.d.mts} +3 -4
- package/dist/backend-http.d.mts +1 -1
- package/dist/backend-http.mjs +1 -1
- package/dist/backend-http.node.d.mts +1 -1
- package/dist/backend-http.node.mjs +1 -1
- package/dist/{backend-vercel-DM5PyMc2.mjs → backend-vercel-BhwPExmE.mjs} +3 -3
- package/dist/{backend-vercel-C3zuxmcD.mjs → backend-vercel-Bqg2QfTd.mjs} +7 -4
- package/dist/{backend-vercel-01QKDobk.d.mts → backend-vercel-CIRObA0t.d.mts} +3 -4
- package/dist/backend-vercel.d.mts +1 -1
- package/dist/backend-vercel.mjs +1 -1
- package/dist/backend-vercel.node.d.mts +1 -1
- package/dist/backend-vercel.node.mjs +1 -1
- package/dist/{blob-C0J3wbGv.d.mts → blob-B-9AvYis.d.mts} +2 -1
- package/dist/{chat-provider-D0k3y8zY.d.mts → chat-provider-DsNEBQiA.d.mts} +3 -2
- package/dist/cli-embedded.d.mts +55 -4
- package/dist/cli-embedded.mjs +38 -18
- package/dist/cli.mjs +4 -4
- package/dist/{client-ClznIBMg.mjs → client-D0Px7KzF.mjs} +26 -8
- package/dist/{provider-D11Dfda0.d.mts → client-xLxeDz0f.d.mts} +21 -688
- package/dist/{conformance-DQ9qVzWP.mjs → conformance-DlnfuzEB.mjs} +1 -1
- package/dist/conformance.d.mts +1 -1
- package/dist/conformance.mjs +1 -1
- package/dist/{engine-kHfSOXnQ.mjs → engine-ROa7-IQY.mjs} +2 -2
- package/dist/errors-CB4tnZGz.d.mts +16 -0
- package/dist/{errors-CQ-fwxM3.mjs → errors-CusN4x2l.mjs} +18 -6
- package/dist/exports-D22M68rk.d.mts +5 -0
- package/dist/{exports-Bw81oDDl.mjs → exports-DCqgO_mr.mjs} +6 -6
- package/dist/{folds-CRYY14kc.d.mts → folds-jS9wlh9_.d.mts} +49 -2
- package/dist/{index-B_hWanKF.d.mts → index-C05it0mC.d.mts} +2 -2
- package/dist/index.d.mts +5 -4
- package/dist/{index.default-B6oPlvEa.mjs → index.default-BejExznB.mjs} +1 -1
- package/dist/index.mjs +6 -6
- package/dist/index.node.d.mts +5 -4
- package/dist/index.node.mjs +4 -4
- package/dist/inspect/app.js +108 -108
- package/dist/{main-CzWvRy91.mjs → main-DOhNCWMd.mjs} +712 -142
- package/dist/{middleware-DUjyQP_a.mjs → middleware-ChKM1zuu.mjs} +1 -1
- package/dist/{node-CirFYqw0.mjs → node-Bkd1x0QI.mjs} +56 -28
- package/dist/provider-Cdg1qadh.d.mts +688 -0
- package/dist/{serve-DHyA-6FR.mjs → serve-D2O0C6Kk.mjs} +6 -5
- package/dist/{server-Cp2oIQOj.mjs → server-BF4WJ7qi.mjs} +4 -4
- package/dist/{serving-B307gppu.mjs → serving-CvOWEym2.mjs} +1 -1
- package/dist/sync-github.d.mts +2 -2
- package/dist/sync-kapso.d.mts +2 -2
- package/dist/sync-linear.d.mts +2 -2
- package/dist/sync-slack.d.mts +3 -3
- package/dist/sync.chat.d.mts +1 -1
- package/dist/sync.d.mts +2 -2
- package/dist/{tokens-DUXaXwOj.mjs → tokens-BuCU-61R.mjs} +2 -2
- package/dist/{types-8yq2VnKI.d.mts → types-aCAf6lGU.d.mts} +1 -1
- package/dist/{watchers-hash-C_1jGA53.mjs → watchers-hash-BHNXy_N7.mjs} +2 -2
- package/docs/01-board/03-identity.md +16 -7
- package/docs/01-board/04-messages.md +2 -2
- package/docs/01-board/05-reading.md +16 -8
- package/docs/01-board/07-schedules.md +2 -2
- package/docs/01-board/09-access.md +5 -4
- package/docs/03-guides/01-coding-agents.md +4 -2
- package/docs/03-guides/03-ci.md +2 -2
- package/docs/04-reference/01-cli.md +78 -19
- package/docs/04-reference/02-board.md +6 -1
- package/docs/04-reference/03-http.md +4 -0
- package/package.json +1 -1
- package/dist/exports-87_yESpp.d.mts +0 -4
- package/dist/schemas-CWxs3ECJ.d.mts +0 -50
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { m as MESSAGE_TEXT_MAX, p as Labels } from "./schemas-Be08nyfW.mjs";
|
|
2
|
-
import { t as HttpError } from "./errors-
|
|
2
|
+
import { t as HttpError } from "./errors-CusN4x2l.mjs";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region src/server/middleware.ts
|
|
5
5
|
/** A write the middleware didn't let through: the API answers 422 with what it responded. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as BoardError } from "./client-
|
|
1
|
+
import { t as BoardError } from "./client-D0Px7KzF.mjs";
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
@@ -52,6 +52,13 @@ const LOOPBACK = /* @__PURE__ */ new Set([
|
|
|
52
52
|
"localhost",
|
|
53
53
|
"[::1]"
|
|
54
54
|
]);
|
|
55
|
+
const PROJECT_ID = /^prj_[A-Za-z0-9]{1,64}$/;
|
|
56
|
+
const URL_SHAPE = /^https?:\/\/[A-Za-z0-9.-]{1,253}(:\d{1,5})?(\/[A-Za-z0-9._~%/-]{0,2000})?$/;
|
|
57
|
+
/**
|
|
58
|
+
* A value a board said, as a message can show it: only when it's what it should be (a project
|
|
59
|
+
* ID, a URL), since the handle's messages are trusted where a board's words aren't.
|
|
60
|
+
*/
|
|
61
|
+
const shown = (value, shape) => typeof value === "string" && shape.test(value) ? value : void 0;
|
|
55
62
|
/** Where `vb serve` leaves the token for the board it runs at `url`: only this user can read it. */
|
|
56
63
|
function localTokenFile(url) {
|
|
57
64
|
const normalized = url.replace(/\/+$/, "");
|
|
@@ -99,7 +106,7 @@ function detectAgent(env = process.env) {
|
|
|
99
106
|
profile: named,
|
|
100
107
|
session: null
|
|
101
108
|
});
|
|
102
|
-
if (found.length > 1) throw new BoardError("ambiguous_agent", `
|
|
109
|
+
if (found.length > 1) throw new BoardError("ambiguous_agent", `Several coding agents' environments are set (${found.map((a) => a.profile).join(", ")}), so it can't tell which one is writing.`, 0, { hint: "Pick one with a profile, or none to write as yourself." });
|
|
103
110
|
return found[0] ?? null;
|
|
104
111
|
}
|
|
105
112
|
const expiry = (token) => {
|
|
@@ -157,7 +164,7 @@ async function personToken(audience, discovery) {
|
|
|
157
164
|
return filled;
|
|
158
165
|
}
|
|
159
166
|
if (cached) return cached;
|
|
160
|
-
if (!discovery.project_id) throw new BoardError("no_project", `
|
|
167
|
+
if (!discovery.project_id) throw new BoardError("no_project", `The board at ${audience} doesn't know its Vercel project, so there's no Vercel login token to mint for it.`, 0, { hint: "Pass a token it accepts in BOARD_TOKEN." });
|
|
161
168
|
const authToken = await vercelLogin().catch(() => null);
|
|
162
169
|
const project = await boardProject(audience, discovery, authToken);
|
|
163
170
|
let development;
|
|
@@ -167,7 +174,7 @@ async function personToken(audience, discovery) {
|
|
|
167
174
|
method: "POST",
|
|
168
175
|
headers: { authorization: `Bearer ${authToken}` }
|
|
169
176
|
});
|
|
170
|
-
if (response.status === 403 || response.status === 404) throw new BoardError("no_access", "
|
|
177
|
+
if (response.status === 403 || response.status === 404) throw new BoardError("no_access", "Your Vercel account can't access this board's project.", response.status, { hint: "Ask someone on the project's team for access to it." });
|
|
171
178
|
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
|
|
172
179
|
development = (await response.json()).token;
|
|
173
180
|
} catch (error) {
|
|
@@ -210,8 +217,8 @@ async function boardProject(url, discovery, authToken) {
|
|
|
210
217
|
});
|
|
211
218
|
return response.ok ? await response.json() : null;
|
|
212
219
|
} : vercelCliApi, host);
|
|
213
|
-
if (!found) throw new BoardError("no_access", `
|
|
214
|
-
if (found.projectId !== discovery.project_id) throw new BoardError("wrong_project", `
|
|
220
|
+
if (!found) throw new BoardError("no_access", `Your Vercel login can't see the project ${host} belongs to.`, 0, { hint: "Log in as someone on the project's team (vercel login), or ask for access to it." });
|
|
221
|
+
if (found.projectId !== discovery.project_id) throw new BoardError("wrong_project", `The board at ${url} says its project is ${shown(discovery.project_id, PROJECT_ID) ?? "another one"}, but it's a deployment of ${found.projectId}, so no token is minted for it.`, 0);
|
|
215
222
|
return {
|
|
216
223
|
id: found.projectId,
|
|
217
224
|
team: found.ownerId.startsWith("team_") ? found.ownerId : null
|
|
@@ -230,7 +237,7 @@ async function tokenFromVercelCli(project) {
|
|
|
230
237
|
const { stdout } = await promisify(execFile)("vercel", args, { timeout: 3e4 });
|
|
231
238
|
return JSON.parse(stdout).token;
|
|
232
239
|
} catch {
|
|
233
|
-
throw new BoardError("not_logged_in", "not
|
|
240
|
+
throw new BoardError("not_logged_in", "You're not logged in to Vercel.", 0, { hint: "Run vercel login." });
|
|
234
241
|
}
|
|
235
242
|
}
|
|
236
243
|
/** Your Vercel username, display name, and avatar (Vercel serves avatars publicly). */
|
|
@@ -252,13 +259,13 @@ async function githubActionsToken(audience) {
|
|
|
252
259
|
const url = new URL(process.env.ACTIONS_ID_TOKEN_REQUEST_URL);
|
|
253
260
|
url.searchParams.set("audience", audience);
|
|
254
261
|
const response = await fetch(url, { headers: { authorization: `bearer ${process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN}` } });
|
|
255
|
-
if (!response.ok) throw new BoardError("no_token", `GitHub Actions refused an OIDC token (${response.status}):
|
|
262
|
+
if (!response.ok) throw new BoardError("no_token", `GitHub Actions refused an OIDC token (${response.status}).`, 0, { hint: "Give the job `permissions: id-token: write`." });
|
|
256
263
|
return (await response.json()).value;
|
|
257
264
|
}
|
|
258
265
|
/** The board the environment points at: `BOARD_URL`. */
|
|
259
266
|
function boardUrl() {
|
|
260
267
|
const url = process.env.BOARD_URL?.replace(/\/+$/, "");
|
|
261
|
-
if (!url) throw new BoardError("no_board", "no board URL: set BOARD_URL
|
|
268
|
+
if (!url) throw new BoardError("no_board", "There's no board URL: BOARD_URL isn't set.", 0, { hint: "Set BOARD_URL, or pass the URL to http()." });
|
|
262
269
|
return url;
|
|
263
270
|
}
|
|
264
271
|
/**
|
|
@@ -272,9 +279,9 @@ function connect(url, options = {}) {
|
|
|
272
279
|
const doFetch = guarded(url, options.fetch ?? ((input, init) => globalThis.fetch(input, init)), options.token);
|
|
273
280
|
let discovery;
|
|
274
281
|
const discover = () => discovery ??= doFetch(url).then(async (response) => {
|
|
275
|
-
if (!response.ok) throw new BoardError("no_board", `no board at ${url}
|
|
282
|
+
if (!response.ok) throw new BoardError("no_board", `There's no board at ${url}: it answered ${response.status}.`, response.status);
|
|
276
283
|
const found = await response.json();
|
|
277
|
-
if (found.audience !== url) throw new BoardError("wrong_audience", `
|
|
284
|
+
if (found.audience !== url) throw new BoardError("wrong_audience", `The board at ${url} says its URL is ${shown(found.audience, URL_SHAPE) ?? "another one"}.`, 0, { hint: shown(found.audience, URL_SHAPE) ? `Use ${found.audience}.` : "Use the URL its discovery names." });
|
|
278
285
|
return found;
|
|
279
286
|
});
|
|
280
287
|
let cached = null;
|
|
@@ -381,6 +388,19 @@ var Wall = class extends Error {
|
|
|
381
388
|
}
|
|
382
389
|
};
|
|
383
390
|
/**
|
|
391
|
+
* A wall reached after a redirect to another origin, which none of a request's credentials follow,
|
|
392
|
+
* by design: no bypass or token gets through it from here, so none is looked for.
|
|
393
|
+
*/
|
|
394
|
+
var Moved = class extends Error {
|
|
395
|
+
origin;
|
|
396
|
+
status;
|
|
397
|
+
constructor(origin, status) {
|
|
398
|
+
super("moved");
|
|
399
|
+
this.origin = origin;
|
|
400
|
+
this.status = status;
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
/**
|
|
384
404
|
* This deployment's Vercel OIDC token, with `url` as its audience: what a hosted client sends a
|
|
385
405
|
* board, for Deployment Protection's Trusted Sources. Its `cause` says why it couldn't.
|
|
386
406
|
*/
|
|
@@ -388,7 +408,7 @@ async function hostedToken(url) {
|
|
|
388
408
|
try {
|
|
389
409
|
return await getVercelOidcToken({ audience: url });
|
|
390
410
|
} catch (cause) {
|
|
391
|
-
throw Object.assign(new BoardError("no_token", `
|
|
411
|
+
throw Object.assign(new BoardError("no_token", `This deployment can't get a Vercel OIDC token for ${url}.`, 0, { hint: "Enable OIDC on its project, or pass a token (BOARD_TOKEN), and add its project to the board project's Deployment Protection Trusted Sources." }), { cause });
|
|
392
412
|
}
|
|
393
413
|
}
|
|
394
414
|
/** A host this deployment answers on, as Vercel's system variables name them (bare hosts). */
|
|
@@ -403,7 +423,12 @@ function guarded(url, doFetch, suppliedToken) {
|
|
|
403
423
|
let secret;
|
|
404
424
|
let looked;
|
|
405
425
|
let envRejected = false;
|
|
406
|
-
return
|
|
426
|
+
return (input, init) => attempt(input, init).catch((error) => {
|
|
427
|
+
if (!(error instanceof Moved)) throw error;
|
|
428
|
+
const there = shown(`${error.origin}${new URL(url).pathname}`, URL_SHAPE);
|
|
429
|
+
throw new BoardError("redirected", `${url} redirects to ${there ? new URL(there).origin : "another origin"}, and its credentials don't follow it there, so Deployment Protection turns it away.`, error.status, { hint: there ? `Use ${there}.` : "Use the URL it redirects to." });
|
|
430
|
+
});
|
|
431
|
+
async function attempt(input, init = {}) {
|
|
407
432
|
const headers = new Headers(init.headers ?? (input instanceof Request ? input.headers : void 0));
|
|
408
433
|
init = {
|
|
409
434
|
...init,
|
|
@@ -414,7 +439,7 @@ function guarded(url, doFetch, suppliedToken) {
|
|
|
414
439
|
return await through(doFetch, input, init, fromEnv);
|
|
415
440
|
} catch (error) {
|
|
416
441
|
if (!(error instanceof Wall)) throw error;
|
|
417
|
-
if (process.env.VERCEL !== "1") throw new BoardError("protected", `${url} is behind Vercel Deployment Protection and rejected VERCEL_AUTOMATION_BYPASS_SECRET
|
|
442
|
+
if (process.env.VERCEL !== "1") throw new BoardError("protected", `${url} is behind Vercel Deployment Protection, and it rejected VERCEL_AUTOMATION_BYPASS_SECRET.`, error.status, { hint: `Check VERCEL_AUTOMATION_BYPASS_SECRET against ${WHERE}.` });
|
|
418
443
|
envRejected = true;
|
|
419
444
|
}
|
|
420
445
|
if (process.env.VERCEL === "1") {
|
|
@@ -423,7 +448,7 @@ function guarded(url, doFetch, suppliedToken) {
|
|
|
423
448
|
headers.set("x-vercel-trusted-oidc-idp-token", token);
|
|
424
449
|
}
|
|
425
450
|
return through(doFetch, input, init, null).catch((error) => {
|
|
426
|
-
throw error instanceof Wall ? new BoardError("protected", `${url} rejected Vercel OIDC
|
|
451
|
+
throw error instanceof Wall ? new BoardError("protected", `${url} is behind Vercel Deployment Protection, and it rejected this deployment's Vercel OIDC token.`, error.status, { hint: "Add this project and environment to the board project's Deployment Protection Trusted Sources." }) : error;
|
|
427
452
|
});
|
|
428
453
|
}
|
|
429
454
|
const used = await (secret ??= readBypass(host));
|
|
@@ -445,9 +470,9 @@ function guarded(url, doFetch, suppliedToken) {
|
|
|
445
470
|
return through(doFetch, input, init, found);
|
|
446
471
|
});
|
|
447
472
|
} catch (error) {
|
|
448
|
-
throw error instanceof Wall ? new BoardError("protected", `${url} is behind Vercel Deployment Protection and its project's automation bypass didn't get through
|
|
473
|
+
throw error instanceof Wall ? new BoardError("protected", `${url} is behind Vercel Deployment Protection, and its project's automation bypass didn't get through.`, error.status, { hint: `Set VERCEL_AUTOMATION_BYPASS_SECRET (${WHERE}).` }) : error;
|
|
449
474
|
}
|
|
450
|
-
}
|
|
475
|
+
}
|
|
451
476
|
}
|
|
452
477
|
/**
|
|
453
478
|
* One request that recognizes the wall: a redirect to `/sso-api`, an HTML
|
|
@@ -464,6 +489,8 @@ async function through(doFetch, input, init, bypass) {
|
|
|
464
489
|
const method = (init.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
465
490
|
let headers = new Headers(init.headers ?? (input instanceof Request ? input.headers : void 0));
|
|
466
491
|
if (bypass) headers.set("x-vercel-protection-bypass", bypass);
|
|
492
|
+
let moved = null;
|
|
493
|
+
const wall = (status) => moved ? new Moved(moved, status) : new Wall(status);
|
|
467
494
|
for (let hops = 0;; hops++) {
|
|
468
495
|
const response = await doFetch(hops === 0 ? input : target, {
|
|
469
496
|
...init,
|
|
@@ -473,10 +500,11 @@ async function through(doFetch, input, init, bypass) {
|
|
|
473
500
|
const location = response.status >= 300 && response.status < 400 ? response.headers.get("location") : null;
|
|
474
501
|
if (location) {
|
|
475
502
|
const next = new URL(location, target);
|
|
476
|
-
if (next.pathname.startsWith("/sso-api")) throw
|
|
503
|
+
if (next.pathname.startsWith("/sso-api")) throw wall(response.status);
|
|
477
504
|
if (method === "GET" || method === "HEAD") {
|
|
478
|
-
if (hops >= 20) throw new BoardError("too_many_redirects",
|
|
505
|
+
if (hops >= 20) throw new BoardError("too_many_redirects", `${target} redirected more than 20 times.`, response.status);
|
|
479
506
|
if (next.origin !== new URL(target).origin) {
|
|
507
|
+
moved = next.origin;
|
|
480
508
|
headers = new Headers(headers);
|
|
481
509
|
headers.delete("authorization");
|
|
482
510
|
headers.delete("x-vercel-trusted-oidc-idp-token");
|
|
@@ -487,8 +515,8 @@ async function through(doFetch, input, init, bypass) {
|
|
|
487
515
|
}
|
|
488
516
|
}
|
|
489
517
|
const { status } = response;
|
|
490
|
-
if (/^text\/html\b/i.test(response.headers.get("content-type") ?? "") && !response.headers.has("content-disposition") && (status >= 200 && status < 300 || status === 401 || status === 403)) throw
|
|
491
|
-
if (status === 401 && response.headers.has("x-vercel-error")) throw
|
|
518
|
+
if (/^text\/html\b/i.test(response.headers.get("content-type") ?? "") && !response.headers.has("content-disposition") && (status >= 200 && status < 300 || status === 401 || status === 403)) throw wall(status);
|
|
519
|
+
if (status === 401 && response.headers.has("x-vercel-error")) throw wall(status);
|
|
492
520
|
return response;
|
|
493
521
|
}
|
|
494
522
|
}
|
|
@@ -517,7 +545,7 @@ const automationBypass = (bypasses) => Object.entries(bypasses ?? {}).find(([, v
|
|
|
517
545
|
* `protectionBypass` has it. When there's none, it's created.
|
|
518
546
|
*/
|
|
519
547
|
async function projectBypass(url, host, doFetch) {
|
|
520
|
-
const blocked = (why) => new BoardError("protected", `${url} is behind Vercel Deployment Protection, and your Vercel login ${why}, or set VERCEL_AUTOMATION_BYPASS_SECRET (${WHERE})
|
|
548
|
+
const blocked = (why, fix) => new BoardError("protected", `${url} is behind Vercel Deployment Protection, and your Vercel login ${why}.`, 0, { hint: `${fix}, or set VERCEL_AUTOMATION_BYPASS_SECRET (${WHERE}).` });
|
|
521
549
|
let api;
|
|
522
550
|
try {
|
|
523
551
|
const token = await vercelLogin();
|
|
@@ -536,19 +564,19 @@ async function projectBypass(url, host, doFetch) {
|
|
|
536
564
|
api = vercelCliApi;
|
|
537
565
|
}
|
|
538
566
|
const project = await findProject(api, host);
|
|
539
|
-
if (!project) throw blocked("can't see its project
|
|
567
|
+
if (!project) throw blocked("can't see its project", "Log in as someone on its project's team (vercel login), ask for access");
|
|
540
568
|
const scope = project.ownerId.startsWith("team_") ? `?teamId=${project.ownerId}` : "";
|
|
541
569
|
const details = await api("GET", `/v9/projects/${project.projectId}${scope}`);
|
|
542
|
-
if (!details) throw blocked("can't read its project's settings
|
|
570
|
+
if (!details) throw blocked("can't read its project's settings", "Ask for access to its project");
|
|
543
571
|
const existing = automationBypass(details.protectionBypass);
|
|
544
572
|
if (existing) return {
|
|
545
573
|
secret: existing,
|
|
546
574
|
created: false
|
|
547
575
|
};
|
|
548
|
-
if (!details.ssoProtection && !details.passwordProtection && !details.passport) throw new BoardError("no_board", `no board at ${url}: it answered with a web page, and its Vercel project isn't protected
|
|
576
|
+
if (!details.ssoProtection && !details.passwordProtection && !details.passport) throw new BoardError("no_board", `There's no board at ${url}: it answered with a web page, and its Vercel project isn't protected.`, 0);
|
|
549
577
|
const created = await api("PATCH", `/v1/projects/${project.projectId}/protection-bypass${scope}`);
|
|
550
578
|
const secret = automationBypass(created?.protectionBypass);
|
|
551
|
-
if (!secret) throw blocked("can't read or create its project's automation bypass
|
|
579
|
+
if (!secret) throw blocked("can't read or create its project's automation bypass", "Ask a project admin to create one");
|
|
552
580
|
process.stderr.write(`created a Protection Bypass for Automation on the Vercel project ${details?.name ?? project.projectId}\n`);
|
|
553
581
|
return {
|
|
554
582
|
secret,
|
|
@@ -579,7 +607,7 @@ async function findProject(api, host) {
|
|
|
579
607
|
let vercelCli;
|
|
580
608
|
const vercelCliApi = async (method, path) => {
|
|
581
609
|
vercelCli ??= promisify(execFile)("vercel", ["whoami", "--non-interactive"], { timeout: 1e4 }).then(() => true, () => false);
|
|
582
|
-
if (!await vercelCli) throw new BoardError("not_logged_in", "not
|
|
610
|
+
if (!await vercelCli) throw new BoardError("not_logged_in", "You're not logged in to Vercel.", 0, { hint: "Run vercel login." });
|
|
583
611
|
const args = [
|
|
584
612
|
"api",
|
|
585
613
|
path,
|
|
@@ -595,7 +623,7 @@ const vercelCliApi = async (method, path) => {
|
|
|
595
623
|
run.child.stdin?.end(method === "PATCH" ? "{}" : "");
|
|
596
624
|
return JSON.parse((await run).stdout);
|
|
597
625
|
} catch (error) {
|
|
598
|
-
if (error.code === "ENOENT") throw new BoardError("not_logged_in", "not
|
|
626
|
+
if (error.code === "ENOENT") throw new BoardError("not_logged_in", "You're not logged in to Vercel.", 0, { hint: "Run vercel login." });
|
|
599
627
|
return null;
|
|
600
628
|
}
|
|
601
629
|
};
|