@kubb/studio 5.6.0 → 5.6.2
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 +80 -169
- package/dist/index.cjs +1452 -964
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +102 -406
- package/dist/index.js +1427 -947
- package/dist/index.js.map +1 -1
- package/dist/protocol-P0znFp9w.d.ts +626 -0
- package/dist/protocol.cjs +13 -13
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.ts +2 -628
- package/dist/protocol.js +12 -14
- package/dist/protocol.js.map +1 -1
- package/package.json +7 -12
- package/dist/configFile-dJtYeqX1.cjs +0 -567
- package/dist/configFile-dJtYeqX1.cjs.map +0 -1
- package/dist/configFile-zhp5DQIe.js +0 -566
- package/dist/configFile-zhp5DQIe.js.map +0 -1
- package/dist/resolveConfig-CjF6j-3y.cjs +0 -399
- package/dist/resolveConfig-CjF6j-3y.cjs.map +0 -1
- package/dist/resolveConfig-DOUv_Tid.js +0 -352
- package/dist/resolveConfig-DOUv_Tid.js.map +0 -1
- package/dist/rolldown-runtime-qbf5tadS.cjs +0 -38
- package/dist/snapshotPackage-BFjiRhGd.cjs +0 -177
- package/dist/snapshotPackage-BFjiRhGd.cjs.map +0 -1
- package/dist/snapshotPackage-BwZ8IJX-.js +0 -176
- package/dist/snapshotPackage-BwZ8IJX-.js.map +0 -1
- package/dist/url-DC87bYiU.cjs +0 -19
- package/dist/url-DC87bYiU.cjs.map +0 -1
- package/dist/url-DhFfJpxp.js +0 -14
- package/dist/url-DhFfJpxp.js.map +0 -1
package/README.md
CHANGED
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
|
|
25
25
|
Kubb Studio client runtime.
|
|
26
26
|
|
|
27
|
-
Connects a Kubb project to [Kubb Studio](https://kubb.studio) through typed Cap'n Web RPC over
|
|
28
|
-
authenticated WebSocket. It backs
|
|
29
|
-
`kubblabs/kubb-agent` Docker image.
|
|
27
|
+
Connects a Kubb project to [Kubb Studio](https://kubb.studio) through typed Cap'n Web RPC over one
|
|
28
|
+
authenticated WebSocket, and publishes snapshots from CI in one HTTP request. It backs the `kubb studio`
|
|
29
|
+
CLI command and the `kubblabs/kubb-agent` Docker image.
|
|
30
30
|
|
|
31
31
|
Most people never install this directly. Reach for `kubb studio` instead, which pairs your machine
|
|
32
32
|
and runs this for you.
|
|
@@ -40,63 +40,63 @@ npm install @kubb/studio
|
|
|
40
40
|
## Usage
|
|
41
41
|
|
|
42
42
|
```typescript
|
|
43
|
-
import {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
onTokenRejected: async () => null,
|
|
64
|
-
})
|
|
43
|
+
import { Hookable, type KubbHooks } from '@kubb/core'
|
|
44
|
+
import { connectAgent, InvalidAgentTokenError, pairAgent } from '@kubb/studio'
|
|
45
|
+
|
|
46
|
+
const hooks = new Hookable<KubbHooks>()
|
|
47
|
+
hooks.hook('studio:connected', ({ agentSlug }) => console.log(`Connected as ${agentSlug}`))
|
|
48
|
+
hooks.hook('studio:warn', ({ message }) => console.warn(message))
|
|
49
|
+
|
|
50
|
+
let token = await readStoredToken()
|
|
51
|
+
|
|
52
|
+
for (;;) {
|
|
53
|
+
token ??= (await pairAgent({ type: 'cli', name: 'my-project', hostname, machineId, onCode })).token
|
|
54
|
+
try {
|
|
55
|
+
// Resolves on shutdown, rejects when Studio refuses the token.
|
|
56
|
+
await connectAgent({ token, configPath: 'kubb.config.ts', loadConfig, version: '1.0.0', hooks, signal })
|
|
57
|
+
break
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (!(error instanceof InvalidAgentTokenError)) throw error
|
|
60
|
+
token = undefined
|
|
61
|
+
}
|
|
62
|
+
}
|
|
65
63
|
```
|
|
66
64
|
|
|
67
|
-
The runtime discovers nothing on its own
|
|
68
|
-
own
|
|
69
|
-
container running a fixed plugin set.
|
|
65
|
+
The runtime discovers nothing on its own. The host passes the token, the config loader, its own
|
|
66
|
+
version, and a hook emitter, and keeps its own credentials. That is what lets one runtime serve a CLI
|
|
67
|
+
running in a developer's project and a container running a fixed plugin set.
|
|
68
|
+
|
|
69
|
+
## Source layout
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
| File | What it owns |
|
|
72
|
+
| ---------------- | -------------------------------------------------------------------------------- |
|
|
73
|
+
| `protocol.ts` | The wire contract Studio compiles against, published as `@kubb/studio/protocol`. |
|
|
74
|
+
| `connect.ts` | `connectAgent`: the socket loop and the `Agent` that serves Studio's calls. |
|
|
75
|
+
| `pair.ts` | `pairAgent`: RFC 8628 device pairing. |
|
|
76
|
+
| `snapshot.ts` | `createSnapshot` for CI, and packing a generation into an npm tarball. |
|
|
77
|
+
| `generations.ts` | Running a generation and keeping its files for `readFiles`. |
|
|
78
|
+
| `configFile.ts` | Reading and patching plugin options in `kubb.config.ts`. |
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
contains focused work such as pairing, generation, config edits, snapshots, and transport; it may
|
|
75
|
-
import the protocol. `src/runtime` owns connection state and coordinates operations; it may import
|
|
76
|
-
both lower layers. Keep new code in the lowest layer that can own it, with direct internal imports.
|
|
77
|
-
Only `@kubb/studio` and `@kubb/studio/protocol` are public entry points.
|
|
80
|
+
## Connection
|
|
78
81
|
|
|
79
|
-
|
|
82
|
+
`connectAgent` opens `wss://<studio>/api/agent/socket` with the agent's bearer token and an
|
|
83
|
+
`x-kubb-instance-id` header. Studio calls `connect(info)` as the first RPC call. `info` says whether
|
|
84
|
+
the agent is a sandbox and carries its slug and Studio's version. From then on the agent pings every
|
|
85
|
+
30 seconds, and Studio schedules one job at a time on the socket.
|
|
80
86
|
|
|
81
|
-
|
|
82
|
-
`kubb studio snapshot` in CI. All three follow the steps below. The runtime owns only the
|
|
83
|
-
connection: it doesn't read CI environment variables or print anything, and behaves the same in
|
|
84
|
-
every host.
|
|
87
|
+
Studio ends a socket on purpose with one of these close codes:
|
|
85
88
|
|
|
86
|
-
|
|
|
87
|
-
|
|
|
88
|
-
|
|
|
89
|
-
|
|
|
90
|
-
|
|
|
91
|
-
|
|
|
92
|
-
| Token rejected | pair again once | pair again once | fail the run |
|
|
93
|
-
| Output | renders the `studio:*` hooks | renders the `studio:*` hooks | renders the `studio:*` hooks |
|
|
89
|
+
| Code | Meaning | `connectAgent` |
|
|
90
|
+
| ------ | --------------------------------------------- | ------------------------------------- |
|
|
91
|
+
| `4001` | The token is invalid, expired, or revoked | Rejects with `InvalidAgentTokenError` |
|
|
92
|
+
| `4002` | Another process of the same agent took over | Resolves |
|
|
93
|
+
| `4003` | The agent is older than Studio accepts | Rejects with `IncompatibleAgentError` |
|
|
94
|
+
| other | An ordinary drop, `1011` for a Studio failure | Reconnects with jittered backoff |
|
|
94
95
|
|
|
95
|
-
Everything the runtime has to say goes through
|
|
96
|
-
`studio:
|
|
97
|
-
|
|
98
|
-
`studio:
|
|
99
|
-
`permission`, and the host adds its own remedy, a CLI flag or an environment variable.
|
|
96
|
+
Everything the runtime has to say goes through the `hooks` emitter: `studio:connected`,
|
|
97
|
+
`studio:disconnected`, `studio:reconnecting`, `studio:command`, `studio:warn`, and `studio:error`,
|
|
98
|
+
next to the `kubb:*` hooks of each generation. When a request is refused for a missing permission,
|
|
99
|
+
`studio:warn` names it as `permission`, so the host can add its own remedy.
|
|
100
100
|
|
|
101
101
|
## Permissions
|
|
102
102
|
|
|
@@ -109,135 +109,46 @@ Every permission is off by default, and each covers one trust boundary:
|
|
|
109
109
|
| `allowConfigEdit` | Studio may change plugin options in `kubb.config.ts`. |
|
|
110
110
|
| `allowRead` | Studio may read back the files a generation produced. |
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
Both hosts follow the same steps and send the agent's bearer token with every call. `/api/agent` is
|
|
115
|
-
the machine-facing surface, singular because the caller is describing itself. The plural
|
|
116
|
-
`/api/agents` is the collection a signed-in user manages in the browser, and the runtime never
|
|
117
|
-
touches it.
|
|
118
|
-
|
|
119
|
-
| Step | Call | What it does |
|
|
120
|
-
| -------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
121
|
-
| Register | `POST /api/agent/connect` | Binds the token to this machine and process (`machineToken`, `instanceId`), reports `capacity`, and returns `socketUrl` |
|
|
122
|
-
| Connect | Configured RPC connector on `socketUrl` | Opens the process's one socket with the bearer token and `x-kubb-instance-id`, then attaches the typed `AgentApi`/`StudioApi` RPC session |
|
|
123
|
-
|
|
124
|
-
Every connection attempt registers first, so a reconnect is also how the agent registers again.
|
|
125
|
-
Each heartbeat carries the agent's load. A process keeps one socket, not one per job: Studio
|
|
126
|
-
schedules jobs onto it up to the advertised `maxConcurrent`. Studio's close codes say whether to
|
|
127
|
-
come back: `4001` registers and reconnects, `4002` (another instance took over) and `4003` (the
|
|
128
|
-
agent is too old or was deleted) stay down. A `426` at registration means the agent is too old for
|
|
129
|
-
that Studio.
|
|
130
|
-
|
|
131
|
-
`runConnection` owns registration retries, capped reconnect backoff, credential replacement, and
|
|
132
|
-
shutdown. `StudioSession` serves one attempt and waits for canceled generation work to settle before
|
|
133
|
-
the loop reconnects. `createClient` remains a thin compatibility facade over that loop.
|
|
134
|
-
|
|
135
|
-
The runtime reconnects when the socket drops and keeps retrying while Studio is unreachable. Generation progress is a native Cap'n Web `ReadableStream` on the generation
|
|
136
|
-
capability; durable job status is read through the HTTP job API.
|
|
112
|
+
A sandbox never writes or edits config, and always allows reads.
|
|
137
113
|
|
|
138
|
-
##
|
|
139
|
-
|
|
140
|
-
`@kubb/studio` exposes a deliberately small public event API. Every live event uses the envelope
|
|
141
|
-
`{ version: 1, jobId, type, data, timestamp }`. The native stream preserves order and applies
|
|
142
|
-
backpressure; it is not a replay cursor. Job status and the terminal result remain authoritative
|
|
143
|
-
after a reconnect.
|
|
144
|
-
|
|
145
|
-
The stable catalog is `generationEventTypes`: generation and build progress, file processing,
|
|
146
|
-
plugin progress, log levels, diagnostics, command-hook output, and the terminal generation summary.
|
|
147
|
-
Each `type` is a lifecycle name registered by `@kubb/core`, while `data` is the JSON-safe projection
|
|
148
|
-
defined by `GenerationEventPayloads`. AST traversal, live config and adapter objects, storage, and
|
|
149
|
-
plugin implementations never leave the agent. New core hooks stay private until Studio explicitly
|
|
150
|
-
adds their name and serializer projection.
|
|
151
|
-
|
|
152
|
-
### Pairing
|
|
114
|
+
## Pairing
|
|
153
115
|
|
|
154
116
|
No host starts with a token, so each one pairs over
|
|
155
|
-
[RFC 8628](https://www.rfc-editor.org/rfc/rfc8628.html) device authorization
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
`
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
`maxAttempts` above 1 it asks for a fresh code when one expires unapproved. A denial throws
|
|
164
|
-
`PairingDeniedError`, an expired code `PairingExpiredError`, and an aborted `signal`
|
|
117
|
+
[RFC 8628](https://www.rfc-editor.org/rfc/rfc8628.html) device authorization. `pairAgent` asks Studio
|
|
118
|
+
for a code, hands it to `onCode` to show, and polls until someone approves it in the browser.
|
|
119
|
+
|
|
120
|
+
`machineId` is a secret the host keeps for the machine. Studio stores its hash, so the same machine
|
|
121
|
+
pairing again rotates one agent's token instead of creating a second agent. Studio records the
|
|
122
|
+
`type` with the agent. `cli` is `kubb studio`, which any member can approve. `user` and `sandbox`
|
|
123
|
+
are the Docker image, run normally or as a sandbox. Only an admin can approve them. A denial
|
|
124
|
+
throws `PairingDeniedError`, an expired code `PairingExpiredError`, and an aborted `signal`
|
|
165
125
|
`PairingCanceledError`.
|
|
166
126
|
|
|
167
|
-
|
|
168
|
-
import { pairAgent } from '@kubb/studio'
|
|
169
|
-
|
|
170
|
-
const { token, agent } = await pairAgent({
|
|
171
|
-
type: 'cli',
|
|
172
|
-
name: 'my-project',
|
|
173
|
-
hostname: os.hostname(),
|
|
174
|
-
onCode: (session) => console.log(`Approve ${session.user_code} at ${session.verification_uri}`),
|
|
175
|
-
})
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
## Asynchronous jobs
|
|
179
|
-
|
|
180
|
-
CI and automation queue work with `createJob` and poll with `waitForJob`. Both send the
|
|
181
|
-
organization CI API key as `x-api-key`. They do not open a WebSocket.
|
|
127
|
+
## CI snapshots
|
|
182
128
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
| Status | `GET /api/jobs/{id}` | Returns the job until `success`, `failed`, or `canceled` |
|
|
129
|
+
`createSnapshot` generates the project like `kubb generate`, with the formatter, the linter, and
|
|
130
|
+
`output.postGenerate`. It then packs the output as an npm tarball and sends one
|
|
131
|
+
`POST /api/snapshots` with the organization CI API key as `x-api-key`.
|
|
187
132
|
|
|
188
133
|
```typescript
|
|
189
|
-
import {
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
134
|
+
import { createSnapshot } from '@kubb/studio'
|
|
135
|
+
|
|
136
|
+
const snapshot = await createSnapshot({
|
|
137
|
+
apiKey: process.env.KUBB_TOKEN!,
|
|
138
|
+
config,
|
|
139
|
+
id: 'gh:123:pr-4',
|
|
140
|
+
name: 'acme/api#4',
|
|
141
|
+
packageName: '@acme/api',
|
|
197
142
|
version: '1.0.0',
|
|
143
|
+
commit: process.env.GITHUB_SHA,
|
|
144
|
+
baseId: 'gh:123:refs/heads/main',
|
|
198
145
|
})
|
|
199
|
-
|
|
200
|
-
const finished = await waitForJob({
|
|
201
|
-
studioUrl: 'https://kubb.studio',
|
|
202
|
-
token: process.env.KUBB_TOKEN!,
|
|
203
|
-
id: job.id,
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
if (finished.status === 'failed') throw new Error(finished.error)
|
|
207
|
-
if (finished.status === 'canceled') throw new Error('Studio job was canceled')
|
|
208
|
-
const snapshot = finished.snapshot
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
Pass `commit` with a snapshot job, and the finished snapshot carries `changes`: the files added,
|
|
212
|
-
changed, and removed since the previous snapshot of the same package on the same agent, and which
|
|
213
|
-
snapshot (and commit) that was. `base` is `null` on the first one.
|
|
214
|
-
|
|
215
|
-
Pass `baseId` (the `id` another CI agent's runs register under) and the snapshot also carries
|
|
216
|
-
`branchChanges`: the same comparison against that agent's latest snapshot. On a GitHub pull
|
|
217
|
-
request or a GitLab merge request, `kubb studio snapshot` passes the agent its base branch's runs
|
|
218
|
-
use. Elsewhere, pass `--base-id` with the `--id` those runs use.
|
|
219
|
-
|
|
220
|
-
Runs that share an agent (one pull request, or one branch) must not overlap, since registering it
|
|
221
|
-
again ends the other run's session. Serialize them per ref, such as a GitHub Actions `concurrency`
|
|
222
|
-
group on `github.ref` or a GitLab `resource_group` on `$CI_COMMIT_REF_SLUG`.
|
|
223
|
-
|
|
224
|
-
A snapshot job packs the tarball on the agent, not on Studio. The agent `PUT`s an empty request to
|
|
225
|
-
a path Studio provides, gets back a redirect to a short-lived storage URL, and uploads the tarball
|
|
226
|
-
there. The storage URL never crosses the RPC socket.
|
|
227
|
-
|
|
228
|
-
## Protocol
|
|
229
|
-
|
|
230
|
-
`@kubb/studio/protocol` holds the RPC contracts both ends share, so the agent and Studio compile
|
|
231
|
-
against one definition rather than two hand-maintained copies. Its only import is `KubbHooks` from
|
|
232
|
-
`@kubb/core`, which the published event names are checked against.
|
|
233
|
-
|
|
234
|
-
```typescript
|
|
235
|
-
import type { AgentApi, GenerationEvent, GenerationRun, RpcConnection, RpcConnector, StudioApi } from '@kubb/studio'
|
|
236
146
|
```
|
|
237
147
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
148
|
+
Snapshots that share an `id` belong to one CI agent in Studio. `changes` lists the files added,
|
|
149
|
+
changed, and removed since that agent's previous snapshot of the package. With `baseId`,
|
|
150
|
+
`branchChanges` compares against the latest snapshot of the CI agent with that id, such as the base
|
|
151
|
+
branch's.
|
|
241
152
|
|
|
242
153
|
## Supporting Kubb
|
|
243
154
|
|