@vercel/board 0.25.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.
Files changed (72) hide show
  1. package/dist/{a2-CQ3WcqMv.mjs → a2-A830f5NS.mjs} +6 -6
  2. package/dist/access.d.mts +1 -2
  3. package/dist/access.mjs +3 -3
  4. package/dist/{actors-CDDhhFNY.mjs → actors-BLXVp4oZ.mjs} +2 -2
  5. package/dist/{auth-BwWH3Twu.mjs → auth-lTkOgvcE.mjs} +1 -1
  6. package/dist/{backend-DmXjVnil.d.mts → backend-ZKYTe3xa.d.mts} +5 -15
  7. package/dist/backend-a2/blob-vercel.d.mts +1 -1
  8. package/dist/backend-a2/contract.d.mts +1 -2
  9. package/dist/backend-a2/store-memory.d.mts +1 -1
  10. package/dist/backend-a2/store-postgres.d.mts +1 -1
  11. package/dist/backend-a2/store-redis-http.d.mts +1 -1
  12. package/dist/backend-a2/store-redis.d.mts +1 -1
  13. package/dist/backend-a2/store-sqlite.d.mts +1 -1
  14. package/dist/backend-a2.d.mts +5 -6
  15. package/dist/backend-a2.mjs +1 -1
  16. package/dist/{backend-http-Cs-UKpYp.mjs → backend-http-CJd4Fu0U.mjs} +2 -2
  17. package/dist/{backend-http-BJ7AdK0c.mjs → backend-http-CYtfwqAW.mjs} +8 -5
  18. package/dist/{backend-http-BsP0Xq9e.d.mts → backend-http-E7YU0qhn.d.mts} +3 -4
  19. package/dist/backend-http.d.mts +1 -1
  20. package/dist/backend-http.mjs +1 -1
  21. package/dist/backend-http.node.d.mts +1 -1
  22. package/dist/backend-http.node.mjs +1 -1
  23. package/dist/{backend-vercel-CG2SYa03.mjs → backend-vercel-BhwPExmE.mjs} +3 -3
  24. package/dist/{backend-vercel-B0Q0K0O5.mjs → backend-vercel-Bqg2QfTd.mjs} +7 -4
  25. package/dist/{backend-vercel-CED673bx.d.mts → backend-vercel-CIRObA0t.d.mts} +3 -4
  26. package/dist/backend-vercel.d.mts +1 -1
  27. package/dist/backend-vercel.mjs +1 -1
  28. package/dist/backend-vercel.node.d.mts +1 -1
  29. package/dist/backend-vercel.node.mjs +1 -1
  30. package/dist/{blob-C0J3wbGv.d.mts → blob-B-9AvYis.d.mts} +2 -1
  31. package/dist/{chat-provider-6eYkL2ef.d.mts → chat-provider-DsNEBQiA.d.mts} +3 -2
  32. package/dist/cli-embedded.d.mts +2 -2
  33. package/dist/cli-embedded.mjs +1 -1
  34. package/dist/cli.mjs +3 -3
  35. package/dist/{client-BY-BCggB.mjs → client-D0Px7KzF.mjs} +16 -4
  36. package/dist/{provider-C_sj4-GL.d.mts → client-xLxeDz0f.d.mts} +13 -689
  37. package/dist/{conformance-BZ53bxrG.mjs → conformance-DlnfuzEB.mjs} +1 -1
  38. package/dist/conformance.d.mts +1 -1
  39. package/dist/conformance.mjs +1 -1
  40. package/dist/{engine-SeaJ_CIY.mjs → engine-ROa7-IQY.mjs} +2 -2
  41. package/dist/errors-CB4tnZGz.d.mts +16 -0
  42. package/dist/{errors-Bven0k29.mjs → errors-CusN4x2l.mjs} +14 -6
  43. package/dist/exports-D22M68rk.d.mts +5 -0
  44. package/dist/{exports-ByvYtxFT.mjs → exports-DCqgO_mr.mjs} +6 -6
  45. package/dist/{folds-CRYY14kc.d.mts → folds-jS9wlh9_.d.mts} +49 -2
  46. package/dist/{index-k2LfZxl-.d.mts → index-C05it0mC.d.mts} +2 -2
  47. package/dist/index.d.mts +5 -4
  48. package/dist/{index.default-DmfhBZZi.mjs → index.default-BejExznB.mjs} +1 -1
  49. package/dist/index.mjs +6 -6
  50. package/dist/index.node.d.mts +5 -4
  51. package/dist/index.node.mjs +4 -4
  52. package/dist/inspect/app.js +108 -108
  53. package/dist/{main-BNkywL2P.mjs → main-DOhNCWMd.mjs} +70 -65
  54. package/dist/{middleware-B0J7QBq7.mjs → middleware-ChKM1zuu.mjs} +1 -1
  55. package/dist/{node-C51_xzCb.mjs → node-Bkd1x0QI.mjs} +37 -9
  56. package/dist/provider-Cdg1qadh.d.mts +688 -0
  57. package/dist/{serve-DYQ64Q5R.mjs → serve-D2O0C6Kk.mjs} +5 -5
  58. package/dist/{server-BgK1sgj7.mjs → server-BF4WJ7qi.mjs} +4 -4
  59. package/dist/{serving-rXQcEgTO.mjs → serving-CvOWEym2.mjs} +1 -1
  60. package/dist/sync-github.d.mts +2 -2
  61. package/dist/sync-kapso.d.mts +2 -2
  62. package/dist/sync-linear.d.mts +2 -2
  63. package/dist/sync-slack.d.mts +3 -3
  64. package/dist/sync.chat.d.mts +1 -1
  65. package/dist/sync.d.mts +2 -2
  66. package/dist/{tokens-U-XRlEfq.mjs → tokens-BuCU-61R.mjs} +2 -2
  67. package/dist/{types-8yq2VnKI.d.mts → types-aCAf6lGU.d.mts} +1 -1
  68. package/dist/{watchers-hash-C4CJg7K1.mjs → watchers-hash-BHNXy_N7.mjs} +2 -2
  69. package/docs/04-reference/02-board.md +6 -2
  70. package/package.json +1 -1
  71. package/dist/exports-CPrIkpz3.d.mts +0 -4
  72. package/dist/schemas-CWxs3ECJ.d.mts +0 -50
@@ -1,14 +1,14 @@
1
1
  import { i as fileSize, n as DIRECT_UPLOAD_BYTES_MAX } from "./files-SniSmeEr.mjs";
2
2
  import { a as AttachmentId } from "./schemas-Be08nyfW.mjs";
3
- import { d as VERSION, i as withMentions } from "./watchers-hash-C4CJg7K1.mjs";
3
+ import { d as VERSION, i as withMentions } from "./watchers-hash-BHNXy_N7.mjs";
4
4
  import { n as displayEmoji, r as normalizeEmoji } from "./emoji-CTLpvH-A.mjs";
5
- import { t as BoardError } from "./client-BY-BCggB.mjs";
5
+ import { t as BoardError } from "./client-D0Px7KzF.mjs";
6
6
  import { i as groups, n as filters$1, o as options, r as globalOptions, s as usage$1, t as commands } from "./commands-BtL4jFq8.mjs";
7
- import { n as connect, s as vercelProjectFile, t as boardUrl } from "./node-C51_xzCb.mjs";
8
- import { t as http } from "./backend-http-Cs-UKpYp.mjs";
9
- import { t as board } from "./server-BgK1sgj7.mjs";
10
- import { t as conformance } from "./conformance-BZ53bxrG.mjs";
11
- import { n as vercel, t as credentialSource } from "./backend-vercel-B0Q0K0O5.mjs";
7
+ import { n as connect, s as vercelProjectFile, t as boardUrl } from "./node-Bkd1x0QI.mjs";
8
+ import { t as http } from "./backend-http-CJd4Fu0U.mjs";
9
+ import { t as board } from "./server-BF4WJ7qi.mjs";
10
+ import { t as conformance } from "./conformance-DlnfuzEB.mjs";
11
+ import { n as vercel, t as credentialSource } from "./backend-vercel-Bqg2QfTd.mjs";
12
12
  import { t as displayText } from "./bots-CGkO76qH.mjs";
13
13
  import { spawn } from "node:child_process";
14
14
  import { readFile, stat, writeFile } from "node:fs/promises";
@@ -1011,14 +1011,15 @@ function missing() {
1011
1011
  if (command === "download") return `attachment ${a}`;
1012
1012
  }
1013
1013
  /**
1014
- * What `error` means for this run, in the Vercel CLI's terms. The handle's own errors (`vercel
1015
- * login` missing, Deployment Protection) say what and why themselves; the board's are said by what
1016
- * this run was doing, with the board's own words under it.
1014
+ * What `error` means for this run, in the Vercel CLI's terms. What the handle raised itself says
1015
+ * what and why, and its hint what fixes it. What a board answered is said by what this run was
1016
+ * doing, with the board's words under it and in `data`, never in what to run: a board can answer
1017
+ * with any code, the handle's own included.
1017
1018
  */
1018
1019
  function failureOf(error) {
1019
1020
  if (error instanceof Failed) return error.failure;
1020
1021
  const message = error instanceof Error ? error.message : String(error);
1021
- const { code, status, hint } = error;
1022
+ const { code, status, hint, origin } = error;
1022
1023
  if (typeof code !== "string" || typeof status !== "number") return {
1023
1024
  class: "unknown",
1024
1025
  reason: "unexpected_error",
@@ -1028,61 +1029,65 @@ function failureOf(error) {
1028
1029
  const said = { error: {
1029
1030
  code,
1030
1031
  message,
1031
- status
1032
+ status,
1033
+ ...typeof hint === "string" ? { hint } : {}
1032
1034
  } };
1033
- const fix = typeof hint === "string" ? [{ instruction: hint }] : [];
1034
- const own = (kind, reason, next = fix) => ({
1035
- class: kind,
1036
- reason,
1037
- message,
1038
- ...next.length ? { next } : {},
1039
- data: said
1040
- });
1041
- switch (code) {
1042
- case "not_logged_in": return {
1043
- class: "auth",
1044
- reason: "login_required",
1045
- message: "You're not logged in to Vercel.",
1046
- next: [{
1047
- instruction: "Log in",
1048
- argv: ["vercel", "login"]
1049
- }],
1035
+ if (origin === "handle") {
1036
+ const fix = typeof hint === "string" ? [{ instruction: hint }] : [];
1037
+ const own = (kind, reason, next = fix) => ({
1038
+ class: kind,
1039
+ reason,
1040
+ message,
1041
+ ...next.length ? { next } : {},
1050
1042
  data: said
1051
- };
1052
- case "no_access": return own("permission", "permission_denied");
1053
- case "wrong_project": return own("permission", "wrong_project");
1054
- case "protected": return own("permission", "deployment_protected");
1055
- case "no_project": return own("platform", "no_project");
1056
- case "no_token": return own("auth", "token_unavailable");
1057
- case "no_board": return own("not_found", "board_not_found");
1058
- case "too_many_redirects": return own("network", "network_error");
1059
- case "invalid_duration": return own("usage", "invalid_arguments", [helpRecovery()]);
1060
- case "wrong_audience": return own("usage", "wrong_url");
1061
- case "ambiguous_agent": return own("usage", "ambiguous_agent", [{
1062
- instruction: "Write as one of them",
1063
- argv: rerun("--as", "<PROFILE>")
1064
- }, {
1065
- instruction: "Or as yourself",
1066
- argv: rerun("--human")
1067
- }]);
1068
- case "too_large": return {
1069
- ...own("limit", "payload_too_large"),
1070
- message: `Can't ${doing()}. ${sentence(message)}`
1071
- };
1072
- case "upload_failed": return {
1073
- ...own("platform", "upload_failed"),
1074
- message: `Failed to ${doing()}.`,
1075
- details: [message]
1076
- };
1077
- case "port_taken": return own("conflict", "port_taken", [{
1078
- instruction: "Pick another port",
1079
- argv: [
1080
- ...cli(),
1081
- current.command ?? "serve",
1082
- "--port",
1083
- "<PORT>"
1084
- ]
1085
- }]);
1043
+ });
1044
+ switch (code) {
1045
+ case "not_logged_in": return {
1046
+ class: "auth",
1047
+ reason: "login_required",
1048
+ message: "You're not logged in to Vercel.",
1049
+ next: [{
1050
+ instruction: "Log in",
1051
+ argv: ["vercel", "login"]
1052
+ }],
1053
+ data: said
1054
+ };
1055
+ case "no_access": return own("permission", "permission_denied");
1056
+ case "wrong_project": return own("permission", "wrong_project");
1057
+ case "protected": return own("permission", "deployment_protected");
1058
+ case "no_project": return own("platform", "no_project");
1059
+ case "no_token": return own("auth", "token_unavailable");
1060
+ case "no_board": return own("not_found", "board_not_found");
1061
+ case "too_many_redirects": return own("network", "network_error");
1062
+ case "invalid_duration": return own("usage", "invalid_arguments", [helpRecovery()]);
1063
+ case "wrong_audience":
1064
+ case "redirected": return own("usage", "wrong_url");
1065
+ case "ambiguous_agent": return own("usage", "ambiguous_agent", [{
1066
+ instruction: "Write as one of them",
1067
+ argv: rerun("--as", "<PROFILE>")
1068
+ }, {
1069
+ instruction: "Or as yourself",
1070
+ argv: rerun("--human")
1071
+ }]);
1072
+ case "too_large": return {
1073
+ ...own("limit", "payload_too_large"),
1074
+ message: `Can't ${doing()}. ${sentence(message)}`
1075
+ };
1076
+ case "upload_failed": return {
1077
+ ...own("platform", "upload_failed"),
1078
+ message: `Failed to ${doing()}.`,
1079
+ details: [message]
1080
+ };
1081
+ case "port_taken": return own("conflict", "port_taken", [{
1082
+ instruction: "Pick another port",
1083
+ argv: [
1084
+ ...cli(),
1085
+ current.command ?? "serve",
1086
+ "--port",
1087
+ "<PORT>"
1088
+ ]
1089
+ }]);
1090
+ }
1086
1091
  }
1087
1092
  const theirs = (kind, reason, headline, next = []) => ({
1088
1093
  class: kind,
@@ -1459,7 +1464,7 @@ async function run(command, args) {
1459
1464
  };
1460
1465
  const old = outdated("This board can't list threads.");
1461
1466
  const page = await board.threads(query).catch((error) => {
1462
- throw error instanceof BoardError && error.status === 404 ? old : error;
1467
+ throw error instanceof BoardError && error.status === 404 && /^no route: /.test(error.message) ? old : error;
1463
1468
  });
1464
1469
  if (!Array.isArray(page.threads)) throw old;
1465
1470
  if (!page.threads.length && target) {
@@ -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-Bven0k29.mjs";
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-BY-BCggB.mjs";
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(/\/+$/, "");
@@ -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", `The board at ${discovery.url} 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." });
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;
@@ -211,7 +218,7 @@ async function boardProject(url, discovery, authToken) {
211
218
  return response.ok ? await response.json() : null;
212
219
  } : vercelCliApi, host);
213
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." });
214
- if (found.projectId !== discovery.project_id) throw new BoardError("wrong_project", `The board at ${url} says its project is ${discovery.project_id}, but it's a deployment of ${found.projectId}, so no token is minted for it.`, 0);
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
@@ -274,7 +281,7 @@ function connect(url, options = {}) {
274
281
  const discover = () => discovery ??= doFetch(url).then(async (response) => {
275
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", `The board at ${url} says its URL is ${found.audience}.`, 0, { hint: `Use ${found.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
  */
@@ -403,7 +423,12 @@ function guarded(url, doFetch, suppliedToken) {
403
423
  let secret;
404
424
  let looked;
405
425
  let envRejected = false;
406
- return async (input, init = {}) => {
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,
@@ -447,7 +472,7 @@ function guarded(url, doFetch, suppliedToken) {
447
472
  } catch (error) {
448
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 new Wall(response.status);
503
+ if (next.pathname.startsWith("/sso-api")) throw wall(response.status);
477
504
  if (method === "GET" || method === "HEAD") {
478
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 new Wall(status);
491
- if (status === 401 && response.headers.has("x-vercel-error")) throw new Wall(status);
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
  }