@kubb/studio 5.2.5 → 5.2.7
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 +32 -0
- package/dist/index.cjs +70 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +105 -2
- package/dist/index.js +70 -9
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs +4 -0
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.ts +584 -2
- package/dist/protocol.js +4 -1
- package/dist/protocol.js.map +1 -1
- package/package.json +4 -4
- package/dist/index-Dq2uq38g.d.ts +0 -579
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __name } from "./rolldown-runtime-CRm0XQPb.js";
|
|
2
|
-
import {
|
|
2
|
+
import { AgentPermissions, ClientInfo, createJobId } from "./protocol.js";
|
|
3
3
|
import { Storage } from "unstorage";
|
|
4
4
|
import { Config, Hookable, KubbHooks } from "@kubb/core";
|
|
5
5
|
//#region src/api.d.ts
|
|
@@ -11,6 +11,109 @@ import { Config, Hookable, KubbHooks } from "@kubb/core";
|
|
|
11
11
|
export declare class InvalidAgentTokenError extends Error {
|
|
12
12
|
constructor(studioUrl: string, options?: ErrorOptions);
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Status values returned by Studio's jobs API.
|
|
16
|
+
*/
|
|
17
|
+
type StudioJobStatus = 'queued' | 'running' | 'success' | 'failed';
|
|
18
|
+
/**
|
|
19
|
+
* Package view returned on a successful snapshot job from Studio.
|
|
20
|
+
*/
|
|
21
|
+
type StudioSnapshot = {
|
|
22
|
+
/**
|
|
23
|
+
* Immutable snapshot id.
|
|
24
|
+
*/
|
|
25
|
+
id: string;
|
|
26
|
+
/**
|
|
27
|
+
* npm package name, or `null` when Studio stored none.
|
|
28
|
+
*/
|
|
29
|
+
name: string | null;
|
|
30
|
+
/**
|
|
31
|
+
* npm package version, or `null` when Studio stored none.
|
|
32
|
+
*/
|
|
33
|
+
version: string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Subresource integrity hash for the tarball, or `null` when unavailable.
|
|
36
|
+
*/
|
|
37
|
+
integrity: string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Preferable download path, often the readable `/packages/{agentSlug}/{name}.tgz` form.
|
|
40
|
+
*/
|
|
41
|
+
url: string;
|
|
42
|
+
/**
|
|
43
|
+
* Stable download path keyed by snapshot id.
|
|
44
|
+
*/
|
|
45
|
+
snapshotIdUrl: string;
|
|
46
|
+
/**
|
|
47
|
+
* ISO timestamp after which Studio may delete the tarball.
|
|
48
|
+
*/
|
|
49
|
+
expiresAt: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Job record from `POST /api/jobs` and `GET /api/jobs/{id}`.
|
|
53
|
+
*/
|
|
54
|
+
type StudioJob = {
|
|
55
|
+
/**
|
|
56
|
+
* Job id returned by Studio when the job was queued.
|
|
57
|
+
*/
|
|
58
|
+
id: string;
|
|
59
|
+
/**
|
|
60
|
+
* Current status. Poll until `success` or `failed`.
|
|
61
|
+
*/
|
|
62
|
+
status: StudioJobStatus;
|
|
63
|
+
/**
|
|
64
|
+
* Failure message when `status` is `failed`.
|
|
65
|
+
*/
|
|
66
|
+
error?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Package view when a snapshot job finished successfully.
|
|
69
|
+
*/
|
|
70
|
+
snapshot?: StudioSnapshot;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Queues a generation or snapshot job on Studio (`POST /api/jobs`).
|
|
74
|
+
*
|
|
75
|
+
* Returns as soon as Studio accepts the job (`202`). Poll with {@link waitForJob} until it finishes.
|
|
76
|
+
* Authenticates with the organization CI API key via `x-api-key`.
|
|
77
|
+
*
|
|
78
|
+
* @example Snapshot job
|
|
79
|
+
* ```ts
|
|
80
|
+
* const job = await createJob({
|
|
81
|
+
* studioUrl: 'https://kubb.studio',
|
|
82
|
+
* token: process.env.KUBB_TOKEN!,
|
|
83
|
+
* type: 'snapshot',
|
|
84
|
+
* agentId: agent.id,
|
|
85
|
+
* name: '@kubb/demo',
|
|
86
|
+
* version: '1.0.0',
|
|
87
|
+
* })
|
|
88
|
+
* const finished = await waitForJob({ studioUrl, token, id: job.id })
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export declare function createJob({ studioUrl, token, type, agentId, name, version, config }: {
|
|
92
|
+
studioUrl: string;
|
|
93
|
+
token: string;
|
|
94
|
+
type: 'generation' | 'snapshot';
|
|
95
|
+
agentId: string;
|
|
96
|
+
name?: string;
|
|
97
|
+
version?: string;
|
|
98
|
+
config?: Record<string, unknown>;
|
|
99
|
+
}): Promise<StudioJob>;
|
|
100
|
+
/**
|
|
101
|
+
* Polls `GET /api/jobs/{id}` until the job reaches `success` or `failed`.
|
|
102
|
+
*
|
|
103
|
+
* A `failed` job resolves normally. Check `job.status` and `job.error`. Throws only when the
|
|
104
|
+
* deadline passes before Studio finishes.
|
|
105
|
+
*/
|
|
106
|
+
export declare function waitForJob({ studioUrl, token, id, timeoutMs }: {
|
|
107
|
+
studioUrl: string;
|
|
108
|
+
token: string;
|
|
109
|
+
id: string;
|
|
110
|
+
/**
|
|
111
|
+
* How long to keep polling before throwing, in milliseconds.
|
|
112
|
+
*
|
|
113
|
+
* @default 60000
|
|
114
|
+
*/
|
|
115
|
+
timeoutMs?: number;
|
|
116
|
+
}): Promise<StudioJob>;
|
|
14
117
|
//#endregion
|
|
15
118
|
//#region src/StudioSession.d.ts
|
|
16
119
|
type StudioSessionOptions = {
|
|
@@ -377,5 +480,5 @@ type PollOptions = {
|
|
|
377
480
|
*/
|
|
378
481
|
export declare function pollForPairingToken({ studioUrl, session, signal }: PollOptions): Promise<PairingResult>;
|
|
379
482
|
//#endregion
|
|
380
|
-
export type
|
|
483
|
+
export { type Client, type ClientOptions, type ConnectionOptions, type StudioConnectedContext, type StudioJob, type StudioJobStatus, type StudioSnapshot, createJobId };
|
|
381
484
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { t as __name } from "./rolldown-runtime-CRm0XQPb.js";
|
|
2
|
-
import { isCommandMessage, isDisconnectMessage, isStudioPingMessage, isStudioReadyMessage } from "./protocol.js";
|
|
2
|
+
import { createJobId, isCommandMessage, isDisconnectMessage, isStudioPingMessage, isStudioReadyMessage } from "./protocol.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { styleText } from "node:util";
|
|
5
5
|
import { readFile, writeFile } from "node:fs/promises";
|
|
6
|
-
import path from "node:path";
|
|
6
|
+
import path, { isAbsolute, relative, resolve } from "node:path";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
8
|
import { FetchError, ofetch } from "ofetch";
|
|
9
9
|
import { hash, randomBytes } from "node:crypto";
|
|
@@ -559,12 +559,62 @@ async function disconnect({ sessionId, token, studioUrl, slug }) {
|
|
|
559
559
|
});
|
|
560
560
|
console.log(styleText("green", `[${tag}] Disconnected from Studio`));
|
|
561
561
|
} catch (error) {
|
|
562
|
+
const statusCode = error?.statusCode;
|
|
563
|
+
if (statusCode !== void 0 && statusCode >= 400 && statusCode < 500) return;
|
|
562
564
|
console.warn(styleText("yellow", `[${tag}] Failed to notify Studio of disconnection: ${getErrorMessage(error)}`));
|
|
563
565
|
}
|
|
564
566
|
}
|
|
567
|
+
/**
|
|
568
|
+
* Queues a generation or snapshot job on Studio (`POST /api/jobs`).
|
|
569
|
+
*
|
|
570
|
+
* Returns as soon as Studio accepts the job (`202`). Poll with {@link waitForJob} until it finishes.
|
|
571
|
+
* Authenticates with the organization CI API key via `x-api-key`.
|
|
572
|
+
*
|
|
573
|
+
* @example Snapshot job
|
|
574
|
+
* ```ts
|
|
575
|
+
* const job = await createJob({
|
|
576
|
+
* studioUrl: 'https://kubb.studio',
|
|
577
|
+
* token: process.env.KUBB_TOKEN!,
|
|
578
|
+
* type: 'snapshot',
|
|
579
|
+
* agentId: agent.id,
|
|
580
|
+
* name: '@kubb/demo',
|
|
581
|
+
* version: '1.0.0',
|
|
582
|
+
* })
|
|
583
|
+
* const finished = await waitForJob({ studioUrl, token, id: job.id })
|
|
584
|
+
* ```
|
|
585
|
+
*/
|
|
586
|
+
async function createJob({ studioUrl, token, type, agentId, name, version, config }) {
|
|
587
|
+
const { job } = await ofetch(`${studioUrl}/api/jobs`, {
|
|
588
|
+
method: "POST",
|
|
589
|
+
headers: { "x-api-key": token },
|
|
590
|
+
body: {
|
|
591
|
+
type,
|
|
592
|
+
agentId,
|
|
593
|
+
name,
|
|
594
|
+
version,
|
|
595
|
+
config
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
return job;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Polls `GET /api/jobs/{id}` until the job reaches `success` or `failed`.
|
|
602
|
+
*
|
|
603
|
+
* A `failed` job resolves normally. Check `job.status` and `job.error`. Throws only when the
|
|
604
|
+
* deadline passes before Studio finishes.
|
|
605
|
+
*/
|
|
606
|
+
async function waitForJob({ studioUrl, token, id, timeoutMs = 6e4 }) {
|
|
607
|
+
const deadline = Date.now() + timeoutMs;
|
|
608
|
+
for (;;) {
|
|
609
|
+
const { job } = await ofetch(`${studioUrl}/api/jobs/${id}`, { headers: { "x-api-key": token } });
|
|
610
|
+
if (job.status === "success" || job.status === "failed") return job;
|
|
611
|
+
if (Date.now() >= deadline) throw new Error("Timed out waiting for the Studio job");
|
|
612
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
613
|
+
}
|
|
614
|
+
}
|
|
565
615
|
//#endregion
|
|
566
616
|
//#region package.json
|
|
567
|
-
var version = "5.2.
|
|
617
|
+
var version = "5.2.7";
|
|
568
618
|
//#endregion
|
|
569
619
|
//#region src/hooks.ts
|
|
570
620
|
/**
|
|
@@ -1517,6 +1567,9 @@ const CONNECT_TIMEOUT_MS = 5e3;
|
|
|
1517
1567
|
*/
|
|
1518
1568
|
const eventSeqCounters = /* @__PURE__ */ new WeakMap();
|
|
1519
1569
|
const require = createRequire(import.meta.url);
|
|
1570
|
+
function relativeStoragePath(root, filePath) {
|
|
1571
|
+
return (isAbsolute(filePath) ? relative(resolve(root), filePath) : filePath).replaceAll("\\", "/");
|
|
1572
|
+
}
|
|
1520
1573
|
async function resolvePeerDependencies(names) {
|
|
1521
1574
|
const uniqueNames = [...new Set(names.map(toPackageName))];
|
|
1522
1575
|
const peerDependencies = {};
|
|
@@ -1571,9 +1624,10 @@ function sendAgentMessage(ws, message) {
|
|
|
1571
1624
|
* Sends a single `kubb:error` payload to Studio, stamped from the same per-socket counter the event stream
|
|
1572
1625
|
* uses so Studio can still order it against the generation events around it.
|
|
1573
1626
|
*/
|
|
1574
|
-
function sendErrorMessage(ws, error) {
|
|
1627
|
+
function sendErrorMessage(ws, error, jobId) {
|
|
1575
1628
|
sendAgentMessage(ws, {
|
|
1576
1629
|
type: "agent:data",
|
|
1630
|
+
jobId,
|
|
1577
1631
|
payload: {
|
|
1578
1632
|
type: "kubb:error",
|
|
1579
1633
|
data: [{
|
|
@@ -1588,7 +1642,7 @@ function sendErrorMessage(ws, error) {
|
|
|
1588
1642
|
/**
|
|
1589
1643
|
* Forwards selected Kubb lifecycle events to Studio as data messages for the active session.
|
|
1590
1644
|
*/
|
|
1591
|
-
function setupEventsStream(ws, hooks) {
|
|
1645
|
+
function setupEventsStream(ws, hooks, jobId) {
|
|
1592
1646
|
const unhooks = [];
|
|
1593
1647
|
/**
|
|
1594
1648
|
* Registers a listener and keeps its remover, so one generation's listeners come off the session
|
|
@@ -1600,6 +1654,7 @@ function setupEventsStream(ws, hooks) {
|
|
|
1600
1654
|
function sendDataMessage(payload) {
|
|
1601
1655
|
sendAgentMessage(ws, {
|
|
1602
1656
|
type: "agent:data",
|
|
1657
|
+
jobId,
|
|
1603
1658
|
payload: {
|
|
1604
1659
|
...payload,
|
|
1605
1660
|
timestamp: Date.now(),
|
|
@@ -1698,7 +1753,7 @@ function setupEventsStream(ws, hooks) {
|
|
|
1698
1753
|
limit: FILE_READ_CONCURRENCY,
|
|
1699
1754
|
run: async (path) => {
|
|
1700
1755
|
const content = await storage.readItem(path);
|
|
1701
|
-
if (content !== null) files[path] = content;
|
|
1756
|
+
if (content !== null) files[relativeStoragePath(config.root, path)] = content;
|
|
1702
1757
|
}
|
|
1703
1758
|
});
|
|
1704
1759
|
sendDataMessage({
|
|
@@ -1859,6 +1914,7 @@ var StudioSession = class {
|
|
|
1859
1914
|
#session;
|
|
1860
1915
|
#ws;
|
|
1861
1916
|
#studioVersion;
|
|
1917
|
+
#activeJobId = null;
|
|
1862
1918
|
#disposed = false;
|
|
1863
1919
|
#isGenerating = false;
|
|
1864
1920
|
#heartbeatTimer;
|
|
@@ -1906,7 +1962,7 @@ var StudioSession = class {
|
|
|
1906
1962
|
signal?.addEventListener("abort", this.#onAbort, { once: true });
|
|
1907
1963
|
this.#unhooks.push(() => signal?.removeEventListener("abort", this.#onAbort));
|
|
1908
1964
|
this.#heartbeatTimer = setInterval(() => this.#sendHeartbeat(), heartbeatInterval);
|
|
1909
|
-
this.#unhooks.push(this.#hooks.hook("kubb:error", ({ error }) => sendErrorMessage(ws, error)));
|
|
1965
|
+
this.#unhooks.push(this.#hooks.hook("kubb:error", ({ error }) => sendErrorMessage(ws, error, this.#activeJobId ?? "connection")));
|
|
1910
1966
|
} catch (error) {
|
|
1911
1967
|
await this.#hooks.callHook("studio:error", { error: toError(error) });
|
|
1912
1968
|
if (error instanceof InvalidAgentTokenError) throw error;
|
|
@@ -2127,6 +2183,7 @@ var StudioSession = class {
|
|
|
2127
2183
|
return;
|
|
2128
2184
|
}
|
|
2129
2185
|
this.#isGenerating = true;
|
|
2186
|
+
this.#activeJobId = data.jobId;
|
|
2130
2187
|
try {
|
|
2131
2188
|
const config = await loadConfig();
|
|
2132
2189
|
const patch = data.payload;
|
|
@@ -2139,7 +2196,7 @@ var StudioSession = class {
|
|
|
2139
2196
|
await this.#warn(`Ignored the spec from Studio; set ${remedy} to generate from it`);
|
|
2140
2197
|
}
|
|
2141
2198
|
const resolvedPlugins = plugins ?? config.plugins;
|
|
2142
|
-
const detach = [setupHookListener(this.#hooks, root), setupEventsStream(ws, this.#hooks)];
|
|
2199
|
+
const detach = [setupHookListener(this.#hooks, root), setupEventsStream(ws, this.#hooks, data.jobId)];
|
|
2143
2200
|
try {
|
|
2144
2201
|
await generate({
|
|
2145
2202
|
config: {
|
|
@@ -2167,6 +2224,7 @@ var StudioSession = class {
|
|
|
2167
2224
|
});
|
|
2168
2225
|
} finally {
|
|
2169
2226
|
this.#isGenerating = false;
|
|
2227
|
+
this.#activeJobId = null;
|
|
2170
2228
|
}
|
|
2171
2229
|
}
|
|
2172
2230
|
async #handleSave(ws, data, command) {
|
|
@@ -2175,6 +2233,7 @@ var StudioSession = class {
|
|
|
2175
2233
|
await this.#warn("Ignored save: the message carried no edits");
|
|
2176
2234
|
sendAgentMessage(ws, {
|
|
2177
2235
|
type: "agent:save",
|
|
2236
|
+
jobId: data.jobId,
|
|
2178
2237
|
payload: {
|
|
2179
2238
|
outcomes: [],
|
|
2180
2239
|
changed: false
|
|
@@ -2185,6 +2244,7 @@ var StudioSession = class {
|
|
|
2185
2244
|
const edits = data.edits;
|
|
2186
2245
|
const refuse = (reason) => sendAgentMessage(ws, {
|
|
2187
2246
|
type: "agent:save",
|
|
2247
|
+
jobId: data.jobId,
|
|
2188
2248
|
payload: {
|
|
2189
2249
|
outcomes: edits.map((edit) => ({
|
|
2190
2250
|
edit,
|
|
@@ -2208,6 +2268,7 @@ var StudioSession = class {
|
|
|
2208
2268
|
if (changed) await writeFile(configFile, patched, "utf-8");
|
|
2209
2269
|
sendAgentMessage(ws, {
|
|
2210
2270
|
type: "agent:save",
|
|
2271
|
+
jobId: data.jobId,
|
|
2211
2272
|
payload: {
|
|
2212
2273
|
outcomes,
|
|
2213
2274
|
changed,
|
|
@@ -2429,6 +2490,6 @@ async function pollForPairingToken({ studioUrl = agentDefaults.studioUrl, sessio
|
|
|
2429
2490
|
throw new Error("The pairing code expired, pair again");
|
|
2430
2491
|
}
|
|
2431
2492
|
//#endregion
|
|
2432
|
-
export { InvalidAgentTokenError, PairingCanceledError, createClient, createFileStorage, defaultStudioUrl, pollForPairingToken, runConnection, setStorage, startPairing };
|
|
2493
|
+
export { InvalidAgentTokenError, PairingCanceledError, createClient, createFileStorage, createJob, createJobId, defaultStudioUrl, pollForPairingToken, runConnection, setStorage, startPairing, waitForJob };
|
|
2433
2494
|
|
|
2434
2495
|
//# sourceMappingURL=index.js.map
|