@vercel/board 0.20.0 → 0.21.0
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/README.md +0 -25
- package/dist/{a2-D__qQpe7.mjs → a2-BgNAyr_f.mjs} +1 -1
- package/dist/backend-a2.mjs +1 -1
- package/dist/{backend-http-CG5y03UP.mjs → backend-http-CLcXkcAU.mjs} +190 -140
- package/dist/backend-http-Dzb4lpMG.mjs +18 -0
- package/dist/backend-http.mjs +1 -1
- package/dist/backend-http.node.mjs +1 -1
- package/dist/backend-vercel-BjUpoLR9.mjs +60 -0
- package/dist/backend-vercel-CYOqtLPc.d.mts +656 -0
- package/dist/backend-vercel-DjL8tYIF.mjs +1314 -0
- package/dist/backend-vercel.d.mts +9 -0
- package/dist/backend-vercel.mjs +11 -0
- package/dist/backend-vercel.node.d.mts +12 -0
- package/dist/backend-vercel.node.mjs +2 -0
- package/dist/cli-embedded.d.mts +5 -5
- package/dist/cli-embedded.mjs +6 -5
- package/dist/cli.mjs +3 -3
- package/dist/{exports-DqWJ7nzC.mjs → exports-DIivm0je.mjs} +1 -1
- package/dist/{index.default-C3luVnd-.mjs → index.default-DNKzR4md.mjs} +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.node.mjs +1 -1
- package/dist/inspect/app.js +29 -29
- package/dist/{main-wB-VfTCL.mjs → main-BZ7nOIhV.mjs} +75 -508
- package/dist/{backend-http-c-VHpUOs.mjs → node-BeKPcokJ.mjs} +15 -17
- package/dist/{serve-AnApUWOR.mjs → serve-8zkBGMmb.mjs} +3 -3
- package/dist/{server-BqVickb5.mjs → server-Du0rSpcW.mjs} +2 -2
- package/dist/{watchers-hash-DYwvkhdX.mjs → watchers-hash-9sxfL1Gd.mjs} +1 -1
- package/docs/03-guides/01-coding-agents.md +0 -94
- package/docs/04-reference/01-cli.md +26 -23
- package/docs/index.md +1 -2
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -89,31 +89,6 @@ or WhatsApp. Automatic wakeups and subscriptions aren't part of this skill.
|
|
|
89
89
|
The skill installation is global per user with `-g`; omit it to install in
|
|
90
90
|
the project.
|
|
91
91
|
|
|
92
|
-
### Keep the board updated after each turn
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
npm i -g @vercel/board # the hook runs `vb` on every turn end: a global install starts fastest
|
|
96
|
-
vb hooks install # Claude Code, Codex, Cursor, and Pi; or name them: vb hooks install pi
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
This adds a turn-end hook to each agent. When a turn ends without the agent
|
|
100
|
-
writing to the board, the hook reminds it once to share a 1–3 sentence
|
|
101
|
-
summary: `vb reply` in a thread it's working in, or `vb post` in the channel
|
|
102
|
-
that fits. With nothing to share, it just finishes. The hook never posts: the
|
|
103
|
-
agent uses the CLI, so it can read the board too. Install allows the agent to
|
|
104
|
-
run those commands without a prompt (Claude Code, Cursor). Codex's default
|
|
105
|
-
sandbox has no network, so install prints a permission profile that allows
|
|
106
|
-
just the board.
|
|
107
|
-
|
|
108
|
-
Updates go to one thread per person, repo, and branch in the `work` channel
|
|
109
|
-
(`--channel` picks another), so a task started in Codex and continued in
|
|
110
|
-
Claude Code reads as one thread. They stay on the board: they don't sync to
|
|
111
|
-
Slack or GitHub. The hook acts only in linked projects (or with `BOARD_URL`
|
|
112
|
-
set) and never fails a turn. Codex asks you to approve the hook once in
|
|
113
|
-
`/hooks`; Cursor runs hooks in interactive sessions only, not with
|
|
114
|
-
`cursor-agent -p`. Pi gets an extension (`~/.pi/agent/extensions/board.ts`,
|
|
115
|
-
or under `PI_CODING_AGENT_DIR`) that runs in every mode, `pi -p` included. `vb hooks status` and `vb hooks uninstall` manage it.
|
|
116
|
-
|
|
117
92
|
The design lives in [SPEC.md](../../SPEC.md). Status: the board, its handle, the CLI,
|
|
118
93
|
inspector, Slack, GitHub, and WhatsApp (Kapso) sync, and watchers work;
|
|
119
94
|
cursors are next.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as DIRECT_UPLOAD_BYTES_MAX, r as IMPORT_BYTES_MAX, t as ATTACHMENT_BYTES_MAX } from "./files-SniSmeEr.mjs";
|
|
2
2
|
import { _ as RemoteWatcherId, b as actorKind, i as Attachment, x as platformAvatar } from "./schemas-Be08nyfW.mjs";
|
|
3
3
|
import "./external-BV_cUncs.mjs";
|
|
4
|
-
import { f as VERSION, n as compileWatcher, o as METHODS, p as SYNC, r as hasCriteria, t as canonicalWatchers } from "./watchers-hash-
|
|
4
|
+
import { f as VERSION, n as compileWatcher, o as METHODS, p as SYNC, r as hasCriteria, t as canonicalWatchers } from "./watchers-hash-9sxfL1Gd.mjs";
|
|
5
5
|
import { r as duration, t as BoardError } from "./client-ClznIBMg.mjs";
|
|
6
6
|
import { n as derivedId, r as isKeyIdOf } from "./ids-C6AYo1bu.mjs";
|
|
7
7
|
import { a as forbidden, c as toErrorResponse, i as duplicateId, r as badRequest, s as notFound, t as HttpError } from "./errors-CQ-fwxM3.mjs";
|
package/dist/backend-a2.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as fsBlob, r as memoryBlob, t as a2 } from "./a2-
|
|
1
|
+
import { n as fsBlob, r as memoryBlob, t as a2 } from "./a2-BgNAyr_f.mjs";
|
|
2
2
|
export { a2, fsBlob, memoryBlob };
|
|
@@ -1,9 +1,172 @@
|
|
|
1
1
|
import "./files-SniSmeEr.mjs";
|
|
2
2
|
import "./schemas-Be08nyfW.mjs";
|
|
3
|
-
import { k as RegisterBody, n as compileWatcher, o as METHODS, q as routes, t as canonicalWatchers } from "./watchers-hash-
|
|
3
|
+
import { k as RegisterBody, n as compileWatcher, o as METHODS, q as routes, t as canonicalWatchers } from "./watchers-hash-9sxfL1Gd.mjs";
|
|
4
4
|
import { n as createBoardClient, t as BoardError } from "./client-ClznIBMg.mjs";
|
|
5
5
|
import { a as sha256, i as keyId, n as derivedId, t as DERIVED_ID } from "./ids-C6AYo1bu.mjs";
|
|
6
6
|
import { a as forbidden, o as json, t as HttpError } from "./errors-CQ-fwxM3.mjs";
|
|
7
|
+
//#region src/server/registrant.ts
|
|
8
|
+
/** Registering again this soon after the same registration only repeats it. */
|
|
9
|
+
const REGISTER_EVERY_MS = 1e4;
|
|
10
|
+
/** How long one wake-up runs matches before it leaves the rest to the next. */
|
|
11
|
+
const DRAIN_MS = 24e4;
|
|
12
|
+
/** A pull with nothing to hand out holds this long, when the board pulls on its own. */
|
|
13
|
+
const POLL_WAIT_S = 25;
|
|
14
|
+
const describe$1 = (error) => ((error instanceof Error ? error.message : String(error)) || "unknown error").slice(0, 500);
|
|
15
|
+
/** Only an https URL can be woken; anything else pulls on its own. */
|
|
16
|
+
const wakeable = (here) => here?.startsWith("https://") ? here : null;
|
|
17
|
+
/**
|
|
18
|
+
* A path on this Vercel project for a board that wakes by path, from `here`, this board's URL:
|
|
19
|
+
* from production, or from a preview of a branch, which it names. Null anywhere else (off Vercel,
|
|
20
|
+
* development, a custom environment), where only a URL wakes it.
|
|
21
|
+
*/
|
|
22
|
+
function wakePath(here) {
|
|
23
|
+
const env = typeof process !== "undefined" && process.env.VERCEL === "1" ? process.env : null;
|
|
24
|
+
const environment = env?.VERCEL_TARGET_ENV || env?.VERCEL_ENV;
|
|
25
|
+
const branch = environment === "preview" ? env?.VERCEL_GIT_COMMIT_REF || null : null;
|
|
26
|
+
if (!here || environment !== "production" && !branch) return null;
|
|
27
|
+
return {
|
|
28
|
+
path: new URL(here).pathname,
|
|
29
|
+
...branch ? { branch } : {}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Runs a board's watchers on another board that matches them: it registers them there, is woken
|
|
34
|
+
* when one matches, with nothing in the wake-up but a hash, and pulls what matched with its own
|
|
35
|
+
* credentials, runs it, and acks it. When it can't be woken (no https URL of its own: a dev
|
|
36
|
+
* server, say), it pulls on its own. http() and vercel() run their watchers this way.
|
|
37
|
+
*/
|
|
38
|
+
function registrant(options) {
|
|
39
|
+
const { registry, rethrow } = options;
|
|
40
|
+
const bound = options.binding;
|
|
41
|
+
let last = null;
|
|
42
|
+
let registering = null;
|
|
43
|
+
/** Registers this board's watchers with the other board, as itself, replacing its last registration. */
|
|
44
|
+
async function register(here) {
|
|
45
|
+
const current = await options.hash();
|
|
46
|
+
if (last && last.url === here && last.result.hash === current && Date.now() - last.at < REGISTER_EVERY_MS) return last.result;
|
|
47
|
+
registering ??= options.wakeAt(here).then((where) => registry.register(where)).then((result) => {
|
|
48
|
+
last = {
|
|
49
|
+
url: here,
|
|
50
|
+
at: Date.now(),
|
|
51
|
+
result
|
|
52
|
+
};
|
|
53
|
+
return result;
|
|
54
|
+
}).finally(() => {
|
|
55
|
+
registering = null;
|
|
56
|
+
});
|
|
57
|
+
return registering;
|
|
58
|
+
}
|
|
59
|
+
/** Runs one match and acks it: done, its error, or a skip for a watcher this board no longer has. */
|
|
60
|
+
async function runOne(match) {
|
|
61
|
+
let outcome = {};
|
|
62
|
+
if (!bound().subscriptions[match.watcher]) outcome = { skip: true };
|
|
63
|
+
else try {
|
|
64
|
+
await bound().run(match.watcher, match.event, {
|
|
65
|
+
trigger: `watch:${match.watcher}:${match.id}`,
|
|
66
|
+
attempt: match.attempt + 1
|
|
67
|
+
});
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error(`[board] watcher ${match.watcher} failed:`, error);
|
|
70
|
+
outcome = { error: describe$1(error) };
|
|
71
|
+
}
|
|
72
|
+
await registry.ack(match.id, {
|
|
73
|
+
lease: match.lease,
|
|
74
|
+
...outcome
|
|
75
|
+
}).catch((error) => console.error(`[board] couldn't ack a match:`, describe$1(error)));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Pulls and runs what's matched until nothing's left, or this run's time is up, and what sync
|
|
79
|
+
* queued in its inbox. A thread's matches come one at a time.
|
|
80
|
+
*/
|
|
81
|
+
async function drain() {
|
|
82
|
+
let ran = 0;
|
|
83
|
+
const until = Date.now() + DRAIN_MS;
|
|
84
|
+
while (Date.now() < until) {
|
|
85
|
+
const matches = await registry.matches({ limit: 10 });
|
|
86
|
+
if (matches.length === 0) break;
|
|
87
|
+
await Promise.all(matches.map(runOne));
|
|
88
|
+
ran += matches.length;
|
|
89
|
+
}
|
|
90
|
+
return ran + await bound().pull();
|
|
91
|
+
}
|
|
92
|
+
let polling = null;
|
|
93
|
+
/** For a board that can't be woken: pulls on its own, each pull held open until something matches. */
|
|
94
|
+
function poll() {
|
|
95
|
+
if (polling) return;
|
|
96
|
+
const controller = polling = new AbortController();
|
|
97
|
+
(async () => {
|
|
98
|
+
for (let pause = 1e3; !controller.signal.aborted;) try {
|
|
99
|
+
await register(null);
|
|
100
|
+
const matches = await registry.matches({
|
|
101
|
+
limit: 10,
|
|
102
|
+
wait: POLL_WAIT_S,
|
|
103
|
+
signal: controller.signal
|
|
104
|
+
});
|
|
105
|
+
await Promise.all(matches.map(runOne));
|
|
106
|
+
await bound().pull();
|
|
107
|
+
pause = 1e3;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (controller.signal.aborted) break;
|
|
110
|
+
console.error(`[board] couldn't pull matches from ${options.remote}:`, describe$1(error));
|
|
111
|
+
await new Promise((resolve) => setTimeout(resolve, pause));
|
|
112
|
+
pause = Math.min(pause * 2, 6e4);
|
|
113
|
+
}
|
|
114
|
+
})();
|
|
115
|
+
}
|
|
116
|
+
let started = null;
|
|
117
|
+
let failedAt = 0;
|
|
118
|
+
/**
|
|
119
|
+
* On first use, a board with watchers or sync registers them if it knows its URL. One on Vercel
|
|
120
|
+
* learns its mount path from a request (a `POST /watchers`, a webhook, or a wake-up); one that
|
|
121
|
+
* can't be woken registers without one and pulls instead. Resolves once the first try is done.
|
|
122
|
+
* One that fails tries again on a later use, at most every `REGISTER_EVERY_MS`: nothing would
|
|
123
|
+
* wake a board the other doesn't know.
|
|
124
|
+
*/
|
|
125
|
+
function start() {
|
|
126
|
+
if (started) return started;
|
|
127
|
+
if (!options.hasWork() || Date.now() - failedAt < REGISTER_EVERY_MS) return Promise.resolve();
|
|
128
|
+
const here = wakeable(bound().url());
|
|
129
|
+
if (!here && typeof process !== "undefined" && process.env.VERCEL === "1") return Promise.resolve();
|
|
130
|
+
started = register(here).then(() => {}, (error) => {
|
|
131
|
+
started = null;
|
|
132
|
+
failedAt = Date.now();
|
|
133
|
+
console.error(`[board] couldn't register with ${options.remote}:`, describe$1(error));
|
|
134
|
+
});
|
|
135
|
+
if (!here) poll();
|
|
136
|
+
return started;
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
start,
|
|
140
|
+
routes: [{
|
|
141
|
+
method: "POST",
|
|
142
|
+
path: "/wake",
|
|
143
|
+
async handle(request) {
|
|
144
|
+
started ??= Promise.resolve();
|
|
145
|
+
if (request.headers.get("x-board-watchers") !== await options.hash()) await register(wakeable(bound().url())).catch(rethrow);
|
|
146
|
+
return json({ ran: await drain().catch(rethrow) });
|
|
147
|
+
}
|
|
148
|
+
}, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
path: "/watchers",
|
|
151
|
+
async handle(request, { caller }) {
|
|
152
|
+
if (caller) return options.registerFor(caller, request);
|
|
153
|
+
started ??= Promise.resolve();
|
|
154
|
+
const here = wakeable(bound().url());
|
|
155
|
+
const registration = await register(here).catch(rethrow);
|
|
156
|
+
if (!here) poll();
|
|
157
|
+
return json(registration);
|
|
158
|
+
}
|
|
159
|
+
}],
|
|
160
|
+
/** Registers this board's watchers with the other board now, as `POST /watchers` does. */
|
|
161
|
+
registerNow: () => register(wakeable(bound().url())),
|
|
162
|
+
/** Stops pulling, for a board that pulls on its own. */
|
|
163
|
+
close: () => {
|
|
164
|
+
polling?.abort();
|
|
165
|
+
polling = null;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
7
170
|
//#region src/server/backend-http.ts
|
|
8
171
|
/** A watcher's or client's name is the profile its writes are by. */
|
|
9
172
|
const PROFILE = /^[A-Za-z][A-Za-z0-9-]{0,31}$/;
|
|
@@ -73,12 +236,6 @@ function scopeOf(identity) {
|
|
|
73
236
|
const entries = Object.entries(access.summary()).map(([pattern, permissions]) => `${pattern}=${permissions.join(",")}`);
|
|
74
237
|
return entries.length > 0 ? entries.join(";") : "*=";
|
|
75
238
|
}
|
|
76
|
-
/** Registering again this soon after the same registration only repeats it. */
|
|
77
|
-
const REGISTER_EVERY_MS = 1e4;
|
|
78
|
-
/** How long one wake-up runs matches before it leaves the rest to the next. */
|
|
79
|
-
const DRAIN_MS = 24e4;
|
|
80
|
-
/** A pull with nothing to hand out holds this long, when the board pulls on its own. */
|
|
81
|
-
const POLL_WAIT_S = 25;
|
|
82
239
|
const describe = (error) => ((error instanceof Error ? error.message : String(error)) || "unknown error").slice(0, 500);
|
|
83
240
|
/** The SDK's error, as the board answers it: the remote's own status and code, or 502 when it never answered. */
|
|
84
241
|
const rethrow = (error) => {
|
|
@@ -352,140 +509,36 @@ function createHttp(url, options, environment) {
|
|
|
352
509
|
return { targets: [...targets, ...here] };
|
|
353
510
|
}
|
|
354
511
|
};
|
|
355
|
-
/** Only an https URL can be woken; anything else pulls on its own. */
|
|
356
|
-
const wakeable = (here) => here?.startsWith("https://") ? here : null;
|
|
357
512
|
/**
|
|
358
513
|
* Where the remote wakes this board: on a board that wakes by path, the path on this project,
|
|
359
|
-
* which gets through its Deployment Protection
|
|
360
|
-
* which it names; anywhere else (off Vercel, development, a custom environment), its URL.
|
|
514
|
+
* which gets through its Deployment Protection (`wakePath`); anywhere else, its URL.
|
|
361
515
|
*/
|
|
362
516
|
async function wakeAt(here) {
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
const branch = environment === "preview" ? env?.VERCEL_GIT_COMMIT_REF || null : null;
|
|
366
|
-
if (!here || environment !== "production" && !branch || !(await discovery()).wake_by_path) return { url: here };
|
|
367
|
-
return {
|
|
368
|
-
path: new URL(here).pathname,
|
|
369
|
-
...branch ? { branch } : {}
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
let last = null;
|
|
373
|
-
let registering = null;
|
|
374
|
-
/** Registers this board's watchers with the remote, as itself, replacing its last registration. */
|
|
375
|
-
async function register(here) {
|
|
376
|
-
const current = await watchersHash();
|
|
377
|
-
if (last && last.url === here && last.result.hash === current && Date.now() - last.at < REGISTER_EVERY_MS) return last.result;
|
|
378
|
-
registering ??= wakeAt(here).then((where) => own.register({
|
|
379
|
-
...where,
|
|
380
|
-
watch: specs,
|
|
381
|
-
...sync.length > 0 ? { sync } : {}
|
|
382
|
-
})).then((result) => {
|
|
383
|
-
last = {
|
|
384
|
-
url: here,
|
|
385
|
-
at: Date.now(),
|
|
386
|
-
result
|
|
387
|
-
};
|
|
388
|
-
return result;
|
|
389
|
-
}).finally(() => {
|
|
390
|
-
registering = null;
|
|
391
|
-
});
|
|
392
|
-
return registering;
|
|
393
|
-
}
|
|
394
|
-
/** Runs one match and acks it: done, its error, or a skip for a watcher this board no longer has. */
|
|
395
|
-
async function runOne(match) {
|
|
396
|
-
let outcome = {};
|
|
397
|
-
if (!bound().subscriptions[match.watcher]) outcome = { skip: true };
|
|
398
|
-
else try {
|
|
399
|
-
await bound().run(match.watcher, match.event, {
|
|
400
|
-
trigger: `watch:${match.watcher}:${match.id}`,
|
|
401
|
-
attempt: match.attempt + 1
|
|
402
|
-
});
|
|
403
|
-
} catch (error) {
|
|
404
|
-
console.error(`[board] watcher ${match.watcher} failed:`, error);
|
|
405
|
-
outcome = { error: describe(error) };
|
|
406
|
-
}
|
|
407
|
-
await own.ack(match.id, {
|
|
408
|
-
lease: match.lease,
|
|
409
|
-
...outcome
|
|
410
|
-
}).catch((error) => console.error(`[board] couldn't ack a match:`, describe(error)));
|
|
411
|
-
}
|
|
412
|
-
/**
|
|
413
|
-
* Pulls and runs what's matched until nothing's left, or this run's time is up, and what sync
|
|
414
|
-
* queued in its inbox. A thread's matches come one at a time.
|
|
415
|
-
*/
|
|
416
|
-
async function drain() {
|
|
417
|
-
let ran = 0;
|
|
418
|
-
const until = Date.now() + DRAIN_MS;
|
|
419
|
-
while (Date.now() < until) {
|
|
420
|
-
const matches = await own.matches({ limit: 10 });
|
|
421
|
-
if (matches.length === 0) break;
|
|
422
|
-
await Promise.all(matches.map(runOne));
|
|
423
|
-
ran += matches.length;
|
|
424
|
-
}
|
|
425
|
-
return ran + await bound().pull();
|
|
426
|
-
}
|
|
427
|
-
let polling = null;
|
|
428
|
-
/** For a board that can't be woken: pulls on its own, each pull held open until something matches. */
|
|
429
|
-
function poll() {
|
|
430
|
-
if (polling) return;
|
|
431
|
-
const controller = polling = new AbortController();
|
|
432
|
-
(async () => {
|
|
433
|
-
for (let pause = 1e3; !controller.signal.aborted;) try {
|
|
434
|
-
await register(null);
|
|
435
|
-
const matches = await own.matches({
|
|
436
|
-
limit: 10,
|
|
437
|
-
wait: POLL_WAIT_S,
|
|
438
|
-
signal: controller.signal
|
|
439
|
-
});
|
|
440
|
-
await Promise.all(matches.map(runOne));
|
|
441
|
-
await bound().pull();
|
|
442
|
-
pause = 1e3;
|
|
443
|
-
} catch (error) {
|
|
444
|
-
if (controller.signal.aborted) break;
|
|
445
|
-
console.error(`[board] couldn't pull matches from ${remote}:`, describe(error));
|
|
446
|
-
await new Promise((resolve) => setTimeout(resolve, pause));
|
|
447
|
-
pause = Math.min(pause * 2, 6e4);
|
|
448
|
-
}
|
|
449
|
-
})();
|
|
517
|
+
const path = wakePath(here);
|
|
518
|
+
return path && (await discovery()).wake_by_path ? path : { url: here };
|
|
450
519
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
started ??= Promise.resolve();
|
|
471
|
-
if (request.headers.get("x-board-watchers") !== await watchersHash()) await register(wakeable(bound().url())).catch(rethrow);
|
|
472
|
-
return json({ ran: await drain().catch(rethrow) });
|
|
473
|
-
}
|
|
474
|
-
}, {
|
|
475
|
-
method: "POST",
|
|
476
|
-
path: "/watchers",
|
|
477
|
-
async handle(request, { caller }) {
|
|
478
|
-
if (caller) return json(await methods.register(caller, RegisterBody.parse(await request.json()), request.signal));
|
|
479
|
-
started ??= Promise.resolve();
|
|
480
|
-
const here = wakeable(bound().url());
|
|
481
|
-
const registration = await register(here).catch(rethrow);
|
|
482
|
-
if (!here) poll();
|
|
483
|
-
return json(registration);
|
|
484
|
-
}
|
|
485
|
-
}];
|
|
520
|
+
const runner = registrant({
|
|
521
|
+
remote,
|
|
522
|
+
registry: {
|
|
523
|
+
register: (where) => own.register({
|
|
524
|
+
...where,
|
|
525
|
+
watch: specs,
|
|
526
|
+
...sync.length > 0 ? { sync } : {}
|
|
527
|
+
}),
|
|
528
|
+
matches: (query) => own.matches(query),
|
|
529
|
+
ack: (id, body) => own.ack(id, body)
|
|
530
|
+
},
|
|
531
|
+
wakeAt,
|
|
532
|
+
binding: bound,
|
|
533
|
+
hash: watchersHash,
|
|
534
|
+
hasWork: () => Object.keys(specs).length > 0 || sync.length > 0,
|
|
535
|
+
rethrow,
|
|
536
|
+
registerFor: async (caller, request) => json(await methods.register(caller, RegisterBody.parse(await request.json()), request.signal))
|
|
537
|
+
});
|
|
538
|
+
const start = runner.start;
|
|
486
539
|
return {
|
|
487
540
|
...methods,
|
|
488
|
-
routes:
|
|
541
|
+
routes: runner.routes,
|
|
489
542
|
keeps: false,
|
|
490
543
|
bind(next) {
|
|
491
544
|
if (binding) throw new Error("http: this backend already serves a board: create one http() per board()");
|
|
@@ -505,13 +558,10 @@ function createHttp(url, options, environment) {
|
|
|
505
558
|
binding = next;
|
|
506
559
|
},
|
|
507
560
|
/** Registers this board's watchers with the board it uses now, as `POST /watchers` does. */
|
|
508
|
-
registerWatchers:
|
|
561
|
+
registerWatchers: runner.registerNow,
|
|
509
562
|
/** Stops pulling, for a board that pulls on its own. */
|
|
510
|
-
close:
|
|
511
|
-
polling?.abort();
|
|
512
|
-
polling = null;
|
|
513
|
-
}
|
|
563
|
+
close: runner.close
|
|
514
564
|
};
|
|
515
565
|
}
|
|
516
566
|
//#endregion
|
|
517
|
-
export { createHttp as t };
|
|
567
|
+
export { wakePath as a, registrant as i, callerProfile as n, createHttp as r, PROJECTS_OWN as t };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { r as createHttp } from "./backend-http-CLcXkcAU.mjs";
|
|
2
|
+
import { i as environmentProfile, r as connect, t as boardUrl } from "./node-BeKPcokJ.mjs";
|
|
3
|
+
//#region src/backend-http/index.ts
|
|
4
|
+
const environment = {
|
|
5
|
+
url: boardUrl,
|
|
6
|
+
fetch: (url) => connect(url).fetch,
|
|
7
|
+
profile: environmentProfile
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The board at `url`, which keeps the log: this board uses it as this deployment. Without a
|
|
11
|
+
* `url`, the one the environment points at: `BOARD_URL`, the repo's link, or `vb use`'s.
|
|
12
|
+
* Without a `fetch`, it sends the credentials `vb` would.
|
|
13
|
+
*/
|
|
14
|
+
function http(url, options = {}) {
|
|
15
|
+
return createHttp(url, options, environment);
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { http as t };
|
package/dist/backend-http.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as http } from "./index.default-
|
|
1
|
+
import { t as http } from "./index.default-DNKzR4md.mjs";
|
|
2
2
|
export { http };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as http } from "./backend-http-
|
|
1
|
+
import { t as http } from "./backend-http-Dzb4lpMG.mjs";
|
|
2
2
|
export { http };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { c as vercelLogin, i as environmentProfile, l as vercelProjectFile } from "./node-BeKPcokJ.mjs";
|
|
2
|
+
import { t as createVercel } from "./backend-vercel-DjL8tYIF.mjs";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { decodeJwt } from "jose";
|
|
5
|
+
import { getVercelOidcToken } from "@vercel/oidc";
|
|
6
|
+
//#region src/backend-vercel/index.ts
|
|
7
|
+
/** On Vercel, or with its OIDC token pulled (`vercel env pull`): a deployment acts as its project. */
|
|
8
|
+
const deployed = () => process.env.VERCEL === "1" || !!process.env.VERCEL_OIDC_TOKEN;
|
|
9
|
+
/**
|
|
10
|
+
* A token Vercel's API takes: `VERCEL_TOKEN`, this deployment's OIDC token, or your Vercel login,
|
|
11
|
+
* the one the Vercel CLI hands a CLI it runs (`VERCEL_AUTH_TOKEN`) or your `vercel login`.
|
|
12
|
+
*/
|
|
13
|
+
async function token() {
|
|
14
|
+
if (process.env.VERCEL_TOKEN) return process.env.VERCEL_TOKEN;
|
|
15
|
+
if (deployed()) return getVercelOidcToken();
|
|
16
|
+
return vercelLogin();
|
|
17
|
+
}
|
|
18
|
+
/** Where `token` comes from, in words: what `vb whoami` says. */
|
|
19
|
+
function credentialSource() {
|
|
20
|
+
if (process.env.VERCEL_TOKEN) return "VERCEL_TOKEN";
|
|
21
|
+
if (deployed()) return "Vercel OIDC";
|
|
22
|
+
return process.env.VERCEL_AUTH_TOKEN ? "the Vercel CLI" : "vercel login";
|
|
23
|
+
}
|
|
24
|
+
/** The team: `VERCEL_ORG_ID`, the one the deployment's OIDC token names, or the linked project's (the nearest `.vercel/project.json`). */
|
|
25
|
+
async function team() {
|
|
26
|
+
if (process.env.VERCEL_ORG_ID) return process.env.VERCEL_ORG_ID;
|
|
27
|
+
if (!process.env.VERCEL_TOKEN && deployed()) try {
|
|
28
|
+
const owner = decodeJwt(await getVercelOidcToken()).owner_id;
|
|
29
|
+
if (typeof owner === "string") return owner;
|
|
30
|
+
} catch {}
|
|
31
|
+
const file = vercelProjectFile();
|
|
32
|
+
if (!file) return null;
|
|
33
|
+
try {
|
|
34
|
+
const linked = JSON.parse(await readFile(file, "utf8"));
|
|
35
|
+
if (typeof linked.orgId === "string") return linked.orgId;
|
|
36
|
+
} catch {}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const environment = {
|
|
40
|
+
fetch: () => async (input, init = {}) => {
|
|
41
|
+
const headers = new Headers(init.headers);
|
|
42
|
+
headers.set("authorization", `Bearer ${await token()}`);
|
|
43
|
+
return globalThis.fetch(input, {
|
|
44
|
+
...init,
|
|
45
|
+
headers
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
team,
|
|
49
|
+
profile: environmentProfile
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Your team's board, in Vercel's API: this board keeps nothing, and reads and writes that one as
|
|
53
|
+
* this deployment's project, or as you. Without `board`, the team's board; without `fetch`, it
|
|
54
|
+
* sends the credentials the Vercel CLI or the deployment has.
|
|
55
|
+
*/
|
|
56
|
+
function vercel(options = {}) {
|
|
57
|
+
return createVercel(options, environment);
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { vercel as n, credentialSource as t };
|