@kubb/studio 5.3.15 → 5.3.17
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 +92 -25
- package/dist/index.cjs +521 -347
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +116 -48
- package/dist/index.js +517 -349
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs +20 -0
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.ts +80 -50
- package/dist/protocol.js +19 -1
- package/dist/protocol.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -40,32 +40,67 @@ npm install @kubb/studio
|
|
|
40
40
|
## Usage
|
|
41
41
|
|
|
42
42
|
```typescript
|
|
43
|
-
import {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
import { createFileStorage, runConnection, setStorage } from '@kubb/studio'
|
|
44
|
+
|
|
45
|
+
// The machine identity lives in this storage, so install it before anything pairs or connects.
|
|
46
|
+
setStorage(createFileStorage('./.kubb-cache'))
|
|
47
|
+
|
|
48
|
+
const outcome = await runConnection({
|
|
49
|
+
credentials: { token: process.env.KUBB_AGENT_TOKEN! },
|
|
50
|
+
clientOptions: () => ({
|
|
51
|
+
configPath: 'kubb.config.ts',
|
|
52
|
+
version: '1.0.0',
|
|
53
|
+
loadConfig: () => loadMyKubbConfig(),
|
|
54
|
+
installLogger: (hooks) => {
|
|
55
|
+
hooks.hook('studio:connected', ({ url }) => console.log(`Connected to ${url}`))
|
|
56
|
+
hooks.hook('studio:reconnecting', ({ delayMs }) => console.log(`Retrying in ${delayMs}ms`))
|
|
57
|
+
hooks.hook('studio:warn', ({ message }) => console.warn(message))
|
|
58
|
+
hooks.hook('studio:error', ({ error }) => console.error(error.message))
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
signal: shutdown.signal,
|
|
62
|
+
// Return a new credential to reconnect with, `null` to stop, or throw to fail the run.
|
|
63
|
+
onTokenRejected: async () => null,
|
|
51
64
|
})
|
|
52
|
-
|
|
53
|
-
await client.connect()
|
|
54
65
|
```
|
|
55
66
|
|
|
56
67
|
The runtime discovers nothing on its own: the host injects the config loader, the storage, and its
|
|
57
68
|
own version. That is what lets one runtime serve a CLI running in a developer's project and a
|
|
58
69
|
container running a fixed plugin set.
|
|
59
70
|
|
|
71
|
+
## Hosts and the runtime
|
|
72
|
+
|
|
73
|
+
Three hosts run this package: `kubb studio`, the `kubblabs/kubb-agent` Docker image, and
|
|
74
|
+
`kubb studio snapshot` in CI. All three follow the steps below. The runtime owns only the
|
|
75
|
+
connection: it doesn't read CI environment variables or print anything, and behaves the same in
|
|
76
|
+
every host.
|
|
77
|
+
|
|
78
|
+
| Step | `kubb studio` | Docker agent | `kubb studio snapshot` |
|
|
79
|
+
| ---------------- | --------------------------------------- | ---------------------------------------- | ---------------------------------------- |
|
|
80
|
+
| Machine identity | `setStorage` under `~/.kubb` | `setStorage` on the Nitro `kubb` mount | `KUBB_AGENT_SECRET` from the CI identity |
|
|
81
|
+
| Credentials | stored, or `pairAgent({ type: 'cli' })` | stored, or `pairAgent({ type: 'user' })` | `createAgent` with the CI API key |
|
|
82
|
+
| Permissions | flags and a per-project prompt | `KUBB_AGENT_ALLOW_*` | flags only |
|
|
83
|
+
| Connection | `runConnection` | `runConnection` | `runConnection` |
|
|
84
|
+
| Token rejected | pair again once | pair again once | fail the run |
|
|
85
|
+
| Output | renders the `studio:*` hooks | renders the `studio:*` hooks | renders the `studio:*` hooks |
|
|
86
|
+
|
|
87
|
+
Everything the runtime has to say goes through hooks on the emitter `installLogger` receives:
|
|
88
|
+
`studio:connecting`, `studio:connected`, `studio:ready`, `studio:reconnecting`,
|
|
89
|
+
`studio:disconnected`, `studio:command:start`, `studio:command:end`, `studio:warn`, and
|
|
90
|
+
`studio:error`. When a request is refused for a missing permission, `studio:warn` carries it as
|
|
91
|
+
`permission`, and the host adds its own remedy, a CLI flag or an environment variable.
|
|
92
|
+
|
|
60
93
|
## Permissions
|
|
61
94
|
|
|
62
95
|
Every permission is off by default, and each covers one trust boundary:
|
|
63
96
|
|
|
64
|
-
| Option
|
|
65
|
-
|
|
|
66
|
-
| `allowWrite`
|
|
67
|
-
| `allowInput`
|
|
68
|
-
| `allowExec`
|
|
97
|
+
| Option | What it grants |
|
|
98
|
+
| ----------------- | ---------------------------------------------------------------------------------------------- |
|
|
99
|
+
| `allowWrite` | Generated files are written to disk. Off means they exist only in memory and stream to Studio. |
|
|
100
|
+
| `allowInput` | An OpenAPI spec sent by Studio replaces the one on disk. |
|
|
101
|
+
| `allowExec` | The formatter, the linter, and `output.postGenerate` run as child processes. |
|
|
102
|
+
| `allowConfigEdit` | Studio may change plugin options in `kubb.config.ts`. |
|
|
103
|
+
| `allowRead` | Studio may read back the files a generation produced. |
|
|
69
104
|
|
|
70
105
|
## Connection flow
|
|
71
106
|
|
|
@@ -74,14 +109,19 @@ the machine-facing surface, singular because the caller is describing itself. Th
|
|
|
74
109
|
`/api/agents` is the collection a signed-in user manages in the browser, and the runtime never
|
|
75
110
|
touches it.
|
|
76
111
|
|
|
77
|
-
| Step
|
|
78
|
-
|
|
|
79
|
-
| Register
|
|
80
|
-
|
|
|
81
|
-
|
|
82
|
-
|
|
112
|
+
| Step | Call | What it does |
|
|
113
|
+
| -------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
114
|
+
| Register | `POST /api/agent/connect` | Binds the token to this machine and process (`machineToken`, `instanceId`), reports `capacity`, and returns `socketUrl` |
|
|
115
|
+
| 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 |
|
|
116
|
+
|
|
117
|
+
Every connection attempt registers first, so a reconnect is also how the agent registers again.
|
|
118
|
+
Each heartbeat carries the agent's load. A process keeps one socket, not one per job: Studio
|
|
119
|
+
schedules jobs onto it up to the advertised `maxConcurrent`. Studio's close codes say whether to
|
|
120
|
+
come back: `4001` registers and reconnects, `4002` (another instance took over) and `4003` (the
|
|
121
|
+
agent is too old or was deleted) stay down. A `426` at registration means the agent is too old for
|
|
122
|
+
that Studio.
|
|
83
123
|
|
|
84
|
-
The runtime reconnects on its own when
|
|
124
|
+
The runtime reconnects on its own when the socket drops, and keeps retrying while Studio is
|
|
85
125
|
unreachable. Generation progress is a native Cap'n Web `ReadableStream` on the generation
|
|
86
126
|
capability; durable job status is read through the HTTP job API.
|
|
87
127
|
|
|
@@ -107,9 +147,23 @@ No host starts with a token, so each one pairs over
|
|
|
107
147
|
`POST /api/agent/token` until someone approves in the browser. Studio mints the token once and stores only its hash, so
|
|
108
148
|
nothing can read it back.
|
|
109
149
|
|
|
110
|
-
`
|
|
111
|
-
|
|
112
|
-
and
|
|
150
|
+
`pairAgent` runs that whole flow and hands each code to the host's `onCode` to show. The `type` is
|
|
151
|
+
what Studio registers the machine as: `cli` is a `kubb studio` machine any signed-in member can
|
|
152
|
+
approve, and `user` or `sandbox` is the Docker image, whose codes only an admin can approve. With
|
|
153
|
+
`maxAttempts` above 1 it asks for a fresh code when one expires unapproved. A denial throws
|
|
154
|
+
`PairingDeniedError`, an expired code `PairingExpiredError`, and an aborted `signal`
|
|
155
|
+
`PairingCanceledError`.
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { pairAgent } from '@kubb/studio'
|
|
159
|
+
|
|
160
|
+
const { token, agent } = await pairAgent({
|
|
161
|
+
type: 'cli',
|
|
162
|
+
name: 'my-project',
|
|
163
|
+
hostname: os.hostname(),
|
|
164
|
+
onCode: (session) => console.log(`Approve ${session.user_code} at ${session.verification_uri}`),
|
|
165
|
+
})
|
|
166
|
+
```
|
|
113
167
|
|
|
114
168
|
## Asynchronous jobs
|
|
115
169
|
|
|
@@ -144,6 +198,19 @@ if (finished.status === 'canceled') throw new Error('Studio job was canceled')
|
|
|
144
198
|
const snapshot = finished.snapshot
|
|
145
199
|
```
|
|
146
200
|
|
|
201
|
+
Pass `commit` with a snapshot job, and the finished snapshot carries `changes`: the files added,
|
|
202
|
+
changed, and removed since the previous snapshot of the same package on the same agent, and which
|
|
203
|
+
snapshot (and commit) that was. `base` is `null` on the first one.
|
|
204
|
+
|
|
205
|
+
Pass `baseId` (the `id` another CI agent's runs register under) and the snapshot also carries
|
|
206
|
+
`branchChanges`: the same comparison against that agent's latest snapshot. On a GitHub pull
|
|
207
|
+
request or a GitLab merge request, `kubb studio snapshot` passes the agent its base branch's runs
|
|
208
|
+
use. Elsewhere, pass `--base-id` with the `--id` those runs use.
|
|
209
|
+
|
|
210
|
+
Runs that share an agent (one pull request, or one branch) must not overlap, since registering it
|
|
211
|
+
again ends the other run's session. Serialize them per ref, such as a GitHub Actions `concurrency`
|
|
212
|
+
group on `github.ref` or a GitLab `resource_group` on `$CI_COMMIT_REF_SLUG`.
|
|
213
|
+
|
|
147
214
|
A snapshot job packs the tarball on the agent, not on Studio. The agent `PUT`s an empty request to
|
|
148
215
|
a path Studio provides, gets back a redirect to a short-lived storage URL, and uploads the tarball
|
|
149
216
|
there. The storage URL never crosses the RPC socket.
|