@kubb/studio 5.3.4 → 5.3.6
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 -11
- package/dist/index.cjs +508 -490
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +19 -5
- package/dist/index.js +472 -480
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs +33 -41
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.ts +144 -220
- package/dist/protocol.js +32 -35
- package/dist/protocol.js.map +1 -1
- package/package.json +6 -4
- package/dist/rolldown-runtime-qbf5tadS.cjs +0 -38
package/dist/index.cjs
CHANGED
|
@@ -1,19 +1,44 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __name = (target, value) => __defProp(target, "name", {
|
|
6
|
+
value,
|
|
7
|
+
configurable: true
|
|
8
|
+
});
|
|
9
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
10
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
12
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
15
|
+
key = keys[i];
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
17
|
+
get: ((k) => from[k]).bind(null, key),
|
|
18
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
//#endregion
|
|
3
28
|
const require_protocol = require("./protocol.cjs");
|
|
4
29
|
let node_util = require("node:util");
|
|
5
30
|
let node_process = require("node:process");
|
|
6
|
-
node_process =
|
|
31
|
+
node_process = __toESM(node_process, 1);
|
|
7
32
|
let node_child_process = require("node:child_process");
|
|
8
33
|
let node_fs_promises = require("node:fs/promises");
|
|
9
34
|
let node_path = require("node:path");
|
|
10
|
-
node_path =
|
|
35
|
+
node_path = __toESM(node_path, 1);
|
|
11
36
|
let _kubb_core = require("@kubb/core");
|
|
12
37
|
let ofetch = require("ofetch");
|
|
13
38
|
let node_crypto = require("node:crypto");
|
|
14
39
|
let unstorage = require("unstorage");
|
|
15
40
|
let unstorage_drivers_fs = require("unstorage/drivers/fs");
|
|
16
|
-
unstorage_drivers_fs =
|
|
41
|
+
unstorage_drivers_fs = __toESM(unstorage_drivers_fs, 1);
|
|
17
42
|
let tinyexec = require("tinyexec");
|
|
18
43
|
let magicast = require("magicast");
|
|
19
44
|
let node_fs = require("node:fs");
|
|
@@ -23,8 +48,9 @@ let remeda = require("remeda");
|
|
|
23
48
|
let node_os = require("node:os");
|
|
24
49
|
let node_zlib = require("node:zlib");
|
|
25
50
|
let tsdown = require("tsdown");
|
|
51
|
+
let capnweb = require("capnweb");
|
|
26
52
|
let ws = require("ws");
|
|
27
|
-
ws =
|
|
53
|
+
ws = __toESM(ws, 1);
|
|
28
54
|
let node_timers_promises = require("node:timers/promises");
|
|
29
55
|
//#region src/constants.ts
|
|
30
56
|
/**
|
|
@@ -46,6 +72,8 @@ const agentDefaults = {
|
|
|
46
72
|
* a slower cadence would make a healthy agent look dead after a single missed ping.
|
|
47
73
|
*/
|
|
48
74
|
maxHeartbeatIntervalMs: 6e4,
|
|
75
|
+
/** How long a heartbeat ping may take before the session is treated as dead. */
|
|
76
|
+
heartbeatTimeoutMs: 1e4,
|
|
49
77
|
poolSize: 1
|
|
50
78
|
};
|
|
51
79
|
//#endregion
|
|
@@ -339,7 +367,7 @@ async function detectTool$1(candidates) {
|
|
|
339
367
|
for (const candidate of candidates) if (await isToolAvailable(candidate)) return candidate;
|
|
340
368
|
return null;
|
|
341
369
|
}
|
|
342
|
-
|
|
370
|
+
__name(detectTool$1, "detectTool");
|
|
343
371
|
/**
|
|
344
372
|
* Tokenizes a shell command string, respecting single and double quotes.
|
|
345
373
|
*
|
|
@@ -623,7 +651,7 @@ const INITIAL_POLL_DELAY_MS = 2e3;
|
|
|
623
651
|
*/
|
|
624
652
|
const MAX_POLL_INTERVAL_MS = 3e4;
|
|
625
653
|
/**
|
|
626
|
-
* Polls `GET /api/jobs/{id}` until the job reaches
|
|
654
|
+
* Polls `GET /api/jobs/{id}` until the job reaches a terminal status, waiting
|
|
627
655
|
* {@link INITIAL_POLL_DELAY_MS} first and doubling up to {@link MAX_POLL_INTERVAL_MS} so a long
|
|
628
656
|
* job stays inside the API key's rate limit.
|
|
629
657
|
*
|
|
@@ -642,7 +670,7 @@ async function waitForJob({ studioUrl, token, id, timeoutMs = 6e4 }) {
|
|
|
642
670
|
headers: { "x-api-key": token },
|
|
643
671
|
retry: false
|
|
644
672
|
});
|
|
645
|
-
if (job.status === "success" || job.status === "failed") return job;
|
|
673
|
+
if (job.status === "success" || job.status === "failed" || job.status === "canceled") return job;
|
|
646
674
|
} catch (error) {
|
|
647
675
|
const response = error.response;
|
|
648
676
|
if (response?.status !== 429) throw error;
|
|
@@ -678,7 +706,7 @@ async function createAgent({ studioUrl, token, name, machineToken }) {
|
|
|
678
706
|
}
|
|
679
707
|
//#endregion
|
|
680
708
|
//#region package.json
|
|
681
|
-
var version = "5.3.
|
|
709
|
+
var version = "5.3.6";
|
|
682
710
|
//#endregion
|
|
683
711
|
//#region src/hooks.ts
|
|
684
712
|
/**
|
|
@@ -689,16 +717,19 @@ var version = "5.3.4";
|
|
|
689
717
|
* Returns a remover, so a session that runs one generation after another on the same emitter does
|
|
690
718
|
* not stack a listener per run.
|
|
691
719
|
*/
|
|
692
|
-
function setupHookListener(hooks, root) {
|
|
720
|
+
function setupHookListener(hooks, root, signal) {
|
|
693
721
|
return hooks.hook("kubb:hook:start", async (ctx) => {
|
|
694
722
|
const { id, command, args } = ctx;
|
|
695
723
|
if (!id) return;
|
|
696
724
|
const commandWithArgs = args?.length ? `${command} ${args.join(" ")}` : command;
|
|
697
725
|
try {
|
|
698
|
-
const proc = (0, tinyexec.x)(command, [...args ?? []], {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
726
|
+
const proc = (0, tinyexec.x)(command, [...args ?? []], {
|
|
727
|
+
signal,
|
|
728
|
+
nodeOptions: {
|
|
729
|
+
cwd: root,
|
|
730
|
+
detached: true
|
|
731
|
+
}
|
|
732
|
+
});
|
|
702
733
|
for await (const line of proc) await hooks.callHook("kubb:hook:line", {
|
|
703
734
|
id,
|
|
704
735
|
line
|
|
@@ -747,8 +778,11 @@ function waitForHookEnd(hooks, hookId) {
|
|
|
747
778
|
const handleHookEnd = (ctx) => {
|
|
748
779
|
if (ctx.id !== hookId) return;
|
|
749
780
|
hooks.removeHook("kubb:hook:end", handleHookEnd);
|
|
750
|
-
if (ctx.success)
|
|
751
|
-
|
|
781
|
+
if (ctx.success) {
|
|
782
|
+
resolve();
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
reject(ctx.error);
|
|
752
786
|
};
|
|
753
787
|
hooks.hook("kubb:hook:end", handleHookEnd);
|
|
754
788
|
});
|
|
@@ -1287,14 +1321,14 @@ function applyRemove(call, path) {
|
|
|
1287
1321
|
});
|
|
1288
1322
|
}
|
|
1289
1323
|
/**
|
|
1290
|
-
* Adds a `pluginX(...)` call to a config's plugins array.
|
|
1291
|
-
*
|
|
1324
|
+
* Adds a `pluginX(...)` call to a config's plugins array. Replaying the same add is a no-op, while
|
|
1325
|
+
* an import name collision with an unrelated package remains an error.
|
|
1292
1326
|
*/
|
|
1293
1327
|
function applyAddPlugin(mod, config, edit) {
|
|
1294
1328
|
if (!isKubbPluginSpecifier(edit.plugin)) return { reason: `"${edit.plugin}" is not a @kubb/plugin-* package` };
|
|
1295
1329
|
const importName = edit.importName ?? toExportName(edit.plugin);
|
|
1296
1330
|
if (!IDENTIFIER.test(importName)) return { reason: `"${importName}" is not a valid import name` };
|
|
1297
|
-
if (pluginCalls(mod, config).some((plugin) => plugin.packageName === edit.plugin)) return {
|
|
1331
|
+
if (pluginCalls(mod, config).some((plugin) => plugin.packageName === edit.plugin)) return { noop: true };
|
|
1298
1332
|
const taken = importedFrom(mod).get(importName);
|
|
1299
1333
|
if (taken && taken !== edit.plugin) return { reason: `${importName} is already imported from ${taken}` };
|
|
1300
1334
|
const options = edit.options ?? {};
|
|
@@ -1415,6 +1449,10 @@ function applyConfigEdits(source, edits) {
|
|
|
1415
1449
|
applied: false,
|
|
1416
1450
|
reason: result.reason
|
|
1417
1451
|
};
|
|
1452
|
+
if ("noop" in result) return {
|
|
1453
|
+
edit,
|
|
1454
|
+
applied: true
|
|
1455
|
+
};
|
|
1418
1456
|
const afterLine = lastImportEndLine(mod);
|
|
1419
1457
|
let next = (0, magicast.generateCode)(mod, { format }).code;
|
|
1420
1458
|
if (result.addImport) next = insertImportLine({
|
|
@@ -1548,14 +1586,19 @@ function formatGenerationFailure(diagnostics) {
|
|
|
1548
1586
|
* can forward progress to connected clients. After a successful build, auto-formatting and
|
|
1549
1587
|
* linting are applied when configured, followed by any user-defined `hooks.done` commands.
|
|
1550
1588
|
*/
|
|
1551
|
-
async function generate({ config, hooks }) {
|
|
1589
|
+
async function generate({ config, hooks, signal }) {
|
|
1590
|
+
signal?.throwIfAborted();
|
|
1552
1591
|
const hrStart = node_process.default.hrtime();
|
|
1553
1592
|
await hooks.callHook("kubb:generation:start", { config });
|
|
1554
1593
|
await hooks.callHook("kubb:info", { message: config.name ? `Setup generation ${config.name}` : "Setup generation" });
|
|
1555
|
-
const kubb = (0, _kubb_core.createKubb)(config, {
|
|
1594
|
+
const kubb = (0, _kubb_core.createKubb)(config, {
|
|
1595
|
+
hooks,
|
|
1596
|
+
signal
|
|
1597
|
+
});
|
|
1556
1598
|
await kubb.setup();
|
|
1557
1599
|
await hooks.callHook("kubb:info", { message: config.name ? `Build generation ${config.name}` : "Build generation" });
|
|
1558
1600
|
const { files, diagnostics, storage } = await kubb.safeBuild();
|
|
1601
|
+
signal?.throwIfAborted();
|
|
1559
1602
|
await hooks.callHook("kubb:info", { message: "Load summary" });
|
|
1560
1603
|
for (const diagnostic of diagnostics.filter(isProblemErrorDiagnostic)) await hooks.callHook("kubb:error", { error: new Error(diagnostic.plugin ? `${diagnostic.plugin}: ${diagnostic.message}` : diagnostic.message) });
|
|
1561
1604
|
const status = _kubb_core.Diagnostics.hasError(diagnostics) ? "failed" : "success";
|
|
@@ -1590,6 +1633,7 @@ async function generate({ config, hooks }) {
|
|
|
1590
1633
|
await hooks.callHook("kubb:success", { message: `${step.verbing} with ${tool} successfully` });
|
|
1591
1634
|
} catch (caughtError) {
|
|
1592
1635
|
await hooks.callHook("kubb:error", { error: new Error(command.errorMessage, { cause: caughtError }) });
|
|
1636
|
+
signal?.throwIfAborted();
|
|
1593
1637
|
}
|
|
1594
1638
|
await hooks.callHook(`kubb:${step.kind}:end`);
|
|
1595
1639
|
}
|
|
@@ -1741,27 +1785,20 @@ async function createSnapshotPackage(files, packageInfo) {
|
|
|
1741
1785
|
//#endregion
|
|
1742
1786
|
//#region src/ws.ts
|
|
1743
1787
|
/**
|
|
1744
|
-
* How many generated files are read from storage at once when building the
|
|
1745
|
-
* `kubb:generation:end` payload. A spec producing thousands of files would otherwise fire one
|
|
1746
|
-
* `storage.readItem` per file simultaneously.
|
|
1747
|
-
*/
|
|
1748
|
-
const FILE_READ_CONCURRENCY = 50;
|
|
1749
|
-
/**
|
|
1750
1788
|
* How long the initial handshake may take before the socket is closed and the reconnect loop
|
|
1751
1789
|
* takes over.
|
|
1752
1790
|
*/
|
|
1753
1791
|
const CONNECT_TIMEOUT_MS = 5e3;
|
|
1754
|
-
/**
|
|
1755
|
-
* Per-socket event counter. Every data message carries the next value so Studio can restore the
|
|
1756
|
-
* agent's emission order even when the relay delivers frames out of order. Keyed by the socket so
|
|
1757
|
-
* the count stays monotonic across every generation run on one connection, and is dropped
|
|
1758
|
-
* automatically once the socket is collected.
|
|
1759
|
-
*/
|
|
1760
|
-
const eventSeqCounters = /* @__PURE__ */ new WeakMap();
|
|
1761
1792
|
const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
1762
1793
|
function relativeStoragePath(root, filePath) {
|
|
1763
1794
|
return ((0, node_path.isAbsolute)(filePath) ? (0, node_path.relative)((0, node_path.resolve)(root), filePath) : filePath).replaceAll("\\", "/");
|
|
1764
1795
|
}
|
|
1796
|
+
/**
|
|
1797
|
+
* Inverse of {@link relativeStoragePath}: rebuilds the storage key a relative path came from.
|
|
1798
|
+
*/
|
|
1799
|
+
function absoluteStoragePath(root, relativePath) {
|
|
1800
|
+
return (0, node_path.resolve)(root, relativePath);
|
|
1801
|
+
}
|
|
1765
1802
|
async function resolvePeerDependencies(names) {
|
|
1766
1803
|
const uniqueNames = [...new Set(names.map(toPackageName))];
|
|
1767
1804
|
const peerDependencies = {};
|
|
@@ -1776,66 +1813,38 @@ async function resolvePeerDependencies(names) {
|
|
|
1776
1813
|
}));
|
|
1777
1814
|
for (const [index, name] of uniqueNames.entries()) {
|
|
1778
1815
|
const version = versions[index];
|
|
1779
|
-
if (version)
|
|
1780
|
-
|
|
1816
|
+
if (version) {
|
|
1817
|
+
peerDependencies[name] = version;
|
|
1818
|
+
continue;
|
|
1819
|
+
}
|
|
1820
|
+
missingDependencies.push(name);
|
|
1781
1821
|
}
|
|
1782
1822
|
return {
|
|
1783
1823
|
peerDependencies,
|
|
1784
1824
|
missingDependencies
|
|
1785
1825
|
};
|
|
1786
1826
|
}
|
|
1787
|
-
function nextEventSeq(ws$1) {
|
|
1788
|
-
const seq = eventSeqCounters.get(ws$1) ?? 0;
|
|
1789
|
-
eventSeqCounters.set(ws$1, seq + 1);
|
|
1790
|
-
return seq;
|
|
1791
|
-
}
|
|
1792
1827
|
/**
|
|
1793
1828
|
* Opens a Studio WebSocket connection and closes it when the initial handshake exceeds the configured timeout.
|
|
1794
1829
|
*/
|
|
1795
1830
|
function createWebsocket(url, options) {
|
|
1796
|
-
const ws$
|
|
1831
|
+
const ws$1 = new ws.default(url, options);
|
|
1797
1832
|
const timer = setTimeout(() => {
|
|
1798
|
-
if (ws$
|
|
1833
|
+
if (ws$1.readyState === ws.default.CONNECTING) ws$1.close(3008, "Connection timeout");
|
|
1799
1834
|
}, CONNECT_TIMEOUT_MS);
|
|
1800
|
-
ws$
|
|
1801
|
-
ws$
|
|
1802
|
-
return ws$
|
|
1803
|
-
}
|
|
1804
|
-
/**
|
|
1805
|
-
* Sends a serialized agent message when the Studio socket is ready to accept frames.
|
|
1806
|
-
*/
|
|
1807
|
-
function sendAgentMessage(ws$3, message) {
|
|
1808
|
-
try {
|
|
1809
|
-
if (ws$3.readyState !== ws.default.OPEN) return;
|
|
1810
|
-
ws$3.send(JSON.stringify(message));
|
|
1811
|
-
} catch (error) {
|
|
1812
|
-
throw new Error("Failed to send message to Kubb Studio", { cause: error });
|
|
1813
|
-
}
|
|
1814
|
-
}
|
|
1815
|
-
/**
|
|
1816
|
-
* Sends a single `kubb:error` payload to Studio, stamped from the same per-socket counter the event stream
|
|
1817
|
-
* uses so Studio can still order it against the generation events around it.
|
|
1818
|
-
*/
|
|
1819
|
-
function sendErrorMessage(ws$4, error, jobId) {
|
|
1820
|
-
sendAgentMessage(ws$4, {
|
|
1821
|
-
type: "agent:data",
|
|
1822
|
-
jobId,
|
|
1823
|
-
payload: {
|
|
1824
|
-
type: "kubb:error",
|
|
1825
|
-
data: [{
|
|
1826
|
-
message: error.message,
|
|
1827
|
-
stack: error.stack
|
|
1828
|
-
}],
|
|
1829
|
-
timestamp: Date.now(),
|
|
1830
|
-
seq: nextEventSeq(ws$4)
|
|
1831
|
-
}
|
|
1832
|
-
});
|
|
1835
|
+
ws$1.once("open", () => clearTimeout(timer));
|
|
1836
|
+
ws$1.once("close", () => clearTimeout(timer));
|
|
1837
|
+
return ws$1;
|
|
1833
1838
|
}
|
|
1834
|
-
/**
|
|
1835
|
-
|
|
1836
|
-
*/
|
|
1837
|
-
function setupEventsStream(ws$5, hooks, jobId, options = {}) {
|
|
1839
|
+
/** Forwards selected Kubb lifecycle events to a native Cap'n Web stream. */
|
|
1840
|
+
function createGenerationStream(hooks, jobId, options = {}) {
|
|
1838
1841
|
const unhooks = [];
|
|
1842
|
+
let root = "";
|
|
1843
|
+
const transform = new TransformStream(void 0, void 0, { highWaterMark: Infinity });
|
|
1844
|
+
const writer = transform.writable.getWriter();
|
|
1845
|
+
let writes = Promise.resolve();
|
|
1846
|
+
let closed = false;
|
|
1847
|
+
let streamError;
|
|
1839
1848
|
/**
|
|
1840
1849
|
* Registers a listener and keeps its remover, so one generation's listeners come off the session
|
|
1841
1850
|
* emitter again when that generation ends.
|
|
@@ -1843,140 +1852,111 @@ function setupEventsStream(ws$5, hooks, jobId, options = {}) {
|
|
|
1843
1852
|
function on(name, handler) {
|
|
1844
1853
|
unhooks.push(hooks.hook(name, handler));
|
|
1845
1854
|
}
|
|
1846
|
-
function
|
|
1847
|
-
|
|
1848
|
-
type: "agent:data",
|
|
1855
|
+
function emitEvent(type, data) {
|
|
1856
|
+
const event = {
|
|
1849
1857
|
jobId,
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1858
|
+
type,
|
|
1859
|
+
data,
|
|
1860
|
+
version: 1,
|
|
1861
|
+
timestamp: Date.now()
|
|
1862
|
+
};
|
|
1863
|
+
writes = writes.then(() => writer.write(event)).catch((error) => {
|
|
1864
|
+
streamError = error;
|
|
1855
1865
|
});
|
|
1856
1866
|
}
|
|
1857
1867
|
on("kubb:plugin:start", (ctx) => {
|
|
1858
|
-
|
|
1859
|
-
type: "kubb:plugin:start",
|
|
1860
|
-
data: [{ plugin: ctx.plugin }]
|
|
1861
|
-
});
|
|
1868
|
+
emitEvent("kubb:plugin:start", [{ plugin: { name: ctx.plugin.name } }]);
|
|
1862
1869
|
});
|
|
1863
1870
|
on("kubb:plugin:end", (ctx) => {
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
success: ctx.success
|
|
1870
|
-
}]
|
|
1871
|
-
});
|
|
1871
|
+
emitEvent("kubb:plugin:end", [{
|
|
1872
|
+
plugin: { name: ctx.plugin.name },
|
|
1873
|
+
duration: ctx.duration,
|
|
1874
|
+
success: ctx.success
|
|
1875
|
+
}]);
|
|
1872
1876
|
});
|
|
1873
1877
|
on("kubb:build:start", ({ config, adapter }) => {
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
}]
|
|
1880
|
-
});
|
|
1878
|
+
root = config.root;
|
|
1879
|
+
emitEvent("kubb:build:start", [{
|
|
1880
|
+
config: { name: config.name },
|
|
1881
|
+
adapter: { name: adapter.name }
|
|
1882
|
+
}]);
|
|
1881
1883
|
});
|
|
1882
|
-
on("kubb:build:end", ({ files, outputDir }) => {
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
outputDir
|
|
1891
|
-
}]
|
|
1892
|
-
});
|
|
1884
|
+
on("kubb:build:end", ({ files, config, outputDir }) => {
|
|
1885
|
+
emitEvent("kubb:build:end", [{
|
|
1886
|
+
files: files.map((file) => ({
|
|
1887
|
+
path: relativeStoragePath(config.root, file.path),
|
|
1888
|
+
name: file.name
|
|
1889
|
+
})),
|
|
1890
|
+
outputDir
|
|
1891
|
+
}]);
|
|
1893
1892
|
});
|
|
1894
1893
|
on("kubb:files:processing:start", ({ files }) => {
|
|
1895
|
-
|
|
1896
|
-
type: "kubb:files:processing:start",
|
|
1897
|
-
data: [{ total: files.length }]
|
|
1898
|
-
});
|
|
1894
|
+
emitEvent("kubb:files:processing:start", [{ total: files.length }]);
|
|
1899
1895
|
});
|
|
1900
1896
|
on("kubb:files:processing:update", ({ files }) => {
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
percentage
|
|
1908
|
-
})) }]
|
|
1909
|
-
});
|
|
1897
|
+
emitEvent("kubb:files:processing:update", [{ files: files.map(({ file, processed, total, percentage }) => ({
|
|
1898
|
+
file: relativeStoragePath(root, file.path),
|
|
1899
|
+
processed,
|
|
1900
|
+
total,
|
|
1901
|
+
percentage
|
|
1902
|
+
})) }]);
|
|
1910
1903
|
});
|
|
1911
1904
|
on("kubb:files:processing:end", ({ files }) => {
|
|
1912
|
-
|
|
1913
|
-
type: "kubb:files:processing:end",
|
|
1914
|
-
data: [{ total: files.length }]
|
|
1915
|
-
});
|
|
1905
|
+
emitEvent("kubb:files:processing:end", [{ total: files.length }]);
|
|
1916
1906
|
});
|
|
1917
1907
|
for (const type of [
|
|
1918
1908
|
"kubb:info",
|
|
1919
1909
|
"kubb:success",
|
|
1920
1910
|
"kubb:warn"
|
|
1921
1911
|
]) on(type, ({ message, info }) => {
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
info
|
|
1927
|
-
}]
|
|
1928
|
-
});
|
|
1912
|
+
emitEvent(type, [{
|
|
1913
|
+
message,
|
|
1914
|
+
info
|
|
1915
|
+
}]);
|
|
1929
1916
|
});
|
|
1930
1917
|
on("kubb:generation:start", ({ config }) => {
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
plugins: config.plugins.length
|
|
1936
|
-
}]
|
|
1937
|
-
});
|
|
1918
|
+
emitEvent("kubb:generation:start", [{
|
|
1919
|
+
name: config.name,
|
|
1920
|
+
plugins: config.plugins.length
|
|
1921
|
+
}]);
|
|
1938
1922
|
});
|
|
1939
1923
|
on("kubb:generation:end", async ({ config, storage, diagnostics = [], status, hrStart, filesCreated }) => {
|
|
1940
1924
|
const { peerDependencies, missingDependencies } = await resolvePeerDependencies(config.plugins.map(({ name }) => name));
|
|
1941
|
-
const
|
|
1942
|
-
const
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
}
|
|
1950
|
-
});
|
|
1951
|
-
options.onGenerationEnd?.(files);
|
|
1952
|
-
sendDataMessage({
|
|
1953
|
-
type: "kubb:generation:end",
|
|
1954
|
-
data: [{
|
|
1955
|
-
config,
|
|
1956
|
-
storage: options.skipStorage ? {} : files,
|
|
1957
|
-
peerDependencies,
|
|
1958
|
-
missingDependencies
|
|
1959
|
-
}]
|
|
1925
|
+
const keys = await storage.readKeys();
|
|
1926
|
+
const paths = new Set(keys.map((key) => relativeStoragePath(config.root, key)));
|
|
1927
|
+
options.onGenerationEnd?.({
|
|
1928
|
+
storage,
|
|
1929
|
+
root: config.root,
|
|
1930
|
+
paths,
|
|
1931
|
+
peerDependencies,
|
|
1932
|
+
missingDependencies
|
|
1960
1933
|
});
|
|
1934
|
+
emitEvent("kubb:generation:end", []);
|
|
1961
1935
|
if (!hrStart) return;
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
status: status ?? "success"
|
|
1969
|
-
}]
|
|
1970
|
-
});
|
|
1936
|
+
emitEvent("kubb:generation:summary", [{
|
|
1937
|
+
duration: Math.round(getElapsedMs(hrStart)),
|
|
1938
|
+
fileCount: filesCreated ?? 0,
|
|
1939
|
+
failedPlugins: _kubb_core.Diagnostics.failedPlugins(diagnostics).length,
|
|
1940
|
+
status: status ?? "success"
|
|
1941
|
+
}]);
|
|
1971
1942
|
});
|
|
1972
1943
|
on("kubb:error", ({ error }) => {
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1944
|
+
emitEvent("kubb:error", [{
|
|
1945
|
+
message: error.message,
|
|
1946
|
+
stack: error.stack
|
|
1947
|
+
}]);
|
|
1948
|
+
});
|
|
1949
|
+
on("kubb:diagnostic", ({ diagnostic }) => {
|
|
1950
|
+
const cause = "cause" in diagnostic ? diagnostic.cause : void 0;
|
|
1951
|
+
emitEvent("kubb:diagnostic", [{
|
|
1952
|
+
code: diagnostic.code,
|
|
1953
|
+
message: diagnostic.message,
|
|
1954
|
+
severity: diagnostic.severity,
|
|
1955
|
+
location: "location" in diagnostic ? diagnostic.location : void 0,
|
|
1956
|
+
help: "help" in diagnostic ? diagnostic.help : void 0,
|
|
1957
|
+
plugin: "plugin" in diagnostic ? diagnostic.plugin : void 0,
|
|
1958
|
+
stack: cause?.stack
|
|
1959
|
+
}]);
|
|
1980
1960
|
});
|
|
1981
1961
|
for (const type of [
|
|
1982
1962
|
"kubb:lifecycle:start",
|
|
@@ -1988,57 +1968,144 @@ function setupEventsStream(ws$5, hooks, jobId, options = {}) {
|
|
|
1988
1968
|
"kubb:hooks:start",
|
|
1989
1969
|
"kubb:hooks:end"
|
|
1990
1970
|
]) on(type, () => {
|
|
1991
|
-
|
|
1992
|
-
type,
|
|
1993
|
-
data: []
|
|
1994
|
-
});
|
|
1971
|
+
emitEvent(type, []);
|
|
1995
1972
|
});
|
|
1996
1973
|
on("kubb:hook:start", ({ id, command, args }) => {
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
args: args ? [...args] : void 0
|
|
2003
|
-
}]
|
|
2004
|
-
});
|
|
1974
|
+
emitEvent("kubb:hook:start", [{
|
|
1975
|
+
id,
|
|
1976
|
+
command,
|
|
1977
|
+
args: args ? [...args] : void 0
|
|
1978
|
+
}]);
|
|
2005
1979
|
});
|
|
2006
1980
|
on("kubb:hook:line", ({ id, line }) => {
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
line
|
|
2012
|
-
}]
|
|
2013
|
-
});
|
|
1981
|
+
emitEvent("kubb:hook:line", [{
|
|
1982
|
+
id,
|
|
1983
|
+
line
|
|
1984
|
+
}]);
|
|
2014
1985
|
});
|
|
2015
1986
|
on("kubb:hook:end", ({ id, command, args, success, error }) => {
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
} : void 0
|
|
2027
|
-
}]
|
|
2028
|
-
});
|
|
1987
|
+
emitEvent("kubb:hook:end", [{
|
|
1988
|
+
id,
|
|
1989
|
+
command,
|
|
1990
|
+
args: args ? [...args] : void 0,
|
|
1991
|
+
success,
|
|
1992
|
+
error: error ? {
|
|
1993
|
+
message: error.message,
|
|
1994
|
+
stack: error.stack
|
|
1995
|
+
} : void 0
|
|
1996
|
+
}]);
|
|
2029
1997
|
});
|
|
2030
|
-
|
|
1998
|
+
/**
|
|
1999
|
+
* Takes this generation's listeners off the session emitter. Safe to call twice.
|
|
2000
|
+
*/
|
|
2001
|
+
function detach() {
|
|
2031
2002
|
for (const unhook of unhooks) unhook();
|
|
2003
|
+
unhooks.length = 0;
|
|
2004
|
+
}
|
|
2005
|
+
async function close() {
|
|
2006
|
+
if (closed) return;
|
|
2007
|
+
closed = true;
|
|
2008
|
+
detach();
|
|
2009
|
+
await writes;
|
|
2010
|
+
if (streamError) return;
|
|
2011
|
+
await writer.close().catch(() => void 0);
|
|
2012
|
+
}
|
|
2013
|
+
function fail(error) {
|
|
2014
|
+
detach();
|
|
2015
|
+
if (closed) return;
|
|
2016
|
+
closed = true;
|
|
2017
|
+
writer.abort(error).catch(() => void 0);
|
|
2018
|
+
}
|
|
2019
|
+
return {
|
|
2020
|
+
stream: transform.readable,
|
|
2021
|
+
close,
|
|
2022
|
+
dispose: () => fail(),
|
|
2023
|
+
fail
|
|
2032
2024
|
};
|
|
2033
2025
|
}
|
|
2034
2026
|
//#endregion
|
|
2027
|
+
//#region src/rpc.ts
|
|
2028
|
+
/**
|
|
2029
|
+
* The only methods Studio may call on an agent. A `StudioSession` carries far more than
|
|
2030
|
+
* {@link AgentApi}, so it is wrapped rather than exposed: what Cap'n Web can reach is exactly what
|
|
2031
|
+
* this class re-declares.
|
|
2032
|
+
*/
|
|
2033
|
+
var AgentRpcTarget = class extends capnweb.RpcTarget {
|
|
2034
|
+
api;
|
|
2035
|
+
constructor(api) {
|
|
2036
|
+
super();
|
|
2037
|
+
this.api = api;
|
|
2038
|
+
}
|
|
2039
|
+
connect() {
|
|
2040
|
+
return this.api.connect();
|
|
2041
|
+
}
|
|
2042
|
+
startGeneration(input) {
|
|
2043
|
+
return this.api.startGeneration(input);
|
|
2044
|
+
}
|
|
2045
|
+
saveConfig(input) {
|
|
2046
|
+
return this.api.saveConfig(input);
|
|
2047
|
+
}
|
|
2048
|
+
publishSnapshot(input) {
|
|
2049
|
+
return this.api.publishSnapshot(input);
|
|
2050
|
+
}
|
|
2051
|
+
readFiles(input) {
|
|
2052
|
+
return this.api.readFiles(input);
|
|
2053
|
+
}
|
|
2054
|
+
};
|
|
2055
|
+
/**
|
|
2056
|
+
* Opens an authenticated Cap'n Web session to Studio over a WebSocket. Rejects an unencrypted URL
|
|
2057
|
+
* before opening the socket, so a bearer token never reaches a plaintext host.
|
|
2058
|
+
*
|
|
2059
|
+
* @example
|
|
2060
|
+
* ```ts
|
|
2061
|
+
* const rpc = await connectWebSocketRpc({ url: 'wss://studio.kubb.dev/s/1', token, local: session })
|
|
2062
|
+
* await rpc.studio.ping()
|
|
2063
|
+
* ```
|
|
2064
|
+
*/
|
|
2065
|
+
const connectWebSocketRpc = async ({ url, token, local }) => {
|
|
2066
|
+
const { protocol, hostname, host } = new URL(url);
|
|
2067
|
+
if (protocol !== "wss:" && !(protocol === "ws:" && (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]"))) throw new Error(`Refusing unencrypted WebSocket to ${host}`);
|
|
2068
|
+
const socket = createWebsocket(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
2069
|
+
const closed = new Promise((resolve) => socket.once("close", resolve));
|
|
2070
|
+
const studio = (0, capnweb.newWebSocketRpcSession)(socket, new AgentRpcTarget(local));
|
|
2071
|
+
studio.onRpcBroken(() => socket.close());
|
|
2072
|
+
return {
|
|
2073
|
+
studio,
|
|
2074
|
+
closed,
|
|
2075
|
+
close: () => studio[Symbol.dispose]()
|
|
2076
|
+
};
|
|
2077
|
+
};
|
|
2078
|
+
//#endregion
|
|
2035
2079
|
//#region src/StudioSession.ts
|
|
2036
2080
|
/**
|
|
2037
|
-
* How
|
|
2038
|
-
* on for this open. A Studio that predates the ack never sends one, so this only ever produces a
|
|
2039
|
-
* warning, not a reconnect.
|
|
2081
|
+
* How many files are read from storage at once when serving `readFiles` or packing a snapshot.
|
|
2040
2082
|
*/
|
|
2041
|
-
const
|
|
2083
|
+
const FILE_READ_CONCURRENCY = 50;
|
|
2084
|
+
var GenerationRunTarget = class extends capnweb.RpcTarget {
|
|
2085
|
+
generationStream;
|
|
2086
|
+
generationResult;
|
|
2087
|
+
cancelGeneration;
|
|
2088
|
+
stopGeneration;
|
|
2089
|
+
constructor(generationStream, generationResult, cancelGeneration, stopGeneration) {
|
|
2090
|
+
super();
|
|
2091
|
+
this.generationStream = generationStream;
|
|
2092
|
+
this.generationResult = generationResult;
|
|
2093
|
+
this.cancelGeneration = cancelGeneration;
|
|
2094
|
+
this.stopGeneration = stopGeneration;
|
|
2095
|
+
}
|
|
2096
|
+
async events() {
|
|
2097
|
+
return this.generationStream;
|
|
2098
|
+
}
|
|
2099
|
+
result() {
|
|
2100
|
+
return this.generationResult;
|
|
2101
|
+
}
|
|
2102
|
+
cancel() {
|
|
2103
|
+
return this.cancelGeneration();
|
|
2104
|
+
}
|
|
2105
|
+
[Symbol.dispose]() {
|
|
2106
|
+
this.stopGeneration();
|
|
2107
|
+
}
|
|
2108
|
+
};
|
|
2042
2109
|
/**
|
|
2043
2110
|
* Fills in a host's options: the hosted Studio URL, the current working directory, and every
|
|
2044
2111
|
* permission off unless granted. Idempotent, so a reconnect can pass an already-resolved bag
|
|
@@ -2056,6 +2123,7 @@ function applyStudioDefaults(options) {
|
|
|
2056
2123
|
allowConfigEdit: false,
|
|
2057
2124
|
allowInput: false,
|
|
2058
2125
|
allowExec: false,
|
|
2126
|
+
allowRead: false,
|
|
2059
2127
|
...options.permissions
|
|
2060
2128
|
},
|
|
2061
2129
|
retryInterval: options.retryInterval ?? agentDefaults.retryIntervalMs,
|
|
@@ -2077,7 +2145,7 @@ function reconnect(options) {
|
|
|
2077
2145
|
const timer = setTimeout(() => {
|
|
2078
2146
|
signal?.removeEventListener("abort", cancel);
|
|
2079
2147
|
if (signal?.aborted) return;
|
|
2080
|
-
new StudioSession(options).
|
|
2148
|
+
new StudioSession(options).start().catch((error) => {
|
|
2081
2149
|
if (logLevel !== void 0 && logLevel > _kubb_core.logLevel.silent) console.error((0, node_util.styleText)("red", `Reconnect attempt to Kubb Studio failed: ${getErrorMessage(error)}`));
|
|
2082
2150
|
if (error instanceof InvalidAgentTokenError) {
|
|
2083
2151
|
onTokenRejected?.(error);
|
|
@@ -2089,8 +2157,8 @@ function reconnect(options) {
|
|
|
2089
2157
|
signal?.addEventListener("abort", cancel, { once: true });
|
|
2090
2158
|
}
|
|
2091
2159
|
/**
|
|
2092
|
-
* One
|
|
2093
|
-
*
|
|
2160
|
+
* One agent-to-Studio RPC transport: opening it, keeping it alive, and serving remote methods.
|
|
2161
|
+
* `createClient` opens one per pool slot and is the only caller.
|
|
2094
2162
|
*/
|
|
2095
2163
|
var StudioSession = class {
|
|
2096
2164
|
#options;
|
|
@@ -2105,17 +2173,20 @@ var StudioSession = class {
|
|
|
2105
2173
|
* Before it resolves there is nothing to disconnect and no sandbox flag to read.
|
|
2106
2174
|
*/
|
|
2107
2175
|
#session;
|
|
2108
|
-
#
|
|
2176
|
+
#rpc;
|
|
2109
2177
|
#studioVersion;
|
|
2110
|
-
#activeJobId = null;
|
|
2111
2178
|
#disposed = false;
|
|
2112
2179
|
#isGenerating = false;
|
|
2113
2180
|
#heartbeatTimer;
|
|
2114
|
-
#readyTimer;
|
|
2115
|
-
#lastPongAt = Date.now();
|
|
2116
2181
|
#lastGeneration;
|
|
2182
|
+
/**
|
|
2183
|
+
* Resolves when Studio calls {@link StudioSession.connect}. `studio:ready` waits on this so the
|
|
2184
|
+
* host does not queue jobs before the agent session is registered.
|
|
2185
|
+
*/
|
|
2186
|
+
#connectAck = Promise.withResolvers();
|
|
2117
2187
|
constructor(options) {
|
|
2118
2188
|
this.#options = applyStudioDefaults(options);
|
|
2189
|
+
this.#connectAck.promise.catch(() => {});
|
|
2119
2190
|
}
|
|
2120
2191
|
/**
|
|
2121
2192
|
* A sandbox agent runs on Studio's own infrastructure, so it has no user project to touch.
|
|
@@ -2136,7 +2207,13 @@ var StudioSession = class {
|
|
|
2136
2207
|
get #canUseInput() {
|
|
2137
2208
|
return this.#isSandbox || this.#options.permissions.allowInput;
|
|
2138
2209
|
}
|
|
2139
|
-
|
|
2210
|
+
/**
|
|
2211
|
+
* A sandbox agent always allows reading its output back; a local agent only when opted in.
|
|
2212
|
+
*/
|
|
2213
|
+
get #canRead() {
|
|
2214
|
+
return this.#isSandbox || this.#options.permissions.allowRead;
|
|
2215
|
+
}
|
|
2216
|
+
async start() {
|
|
2140
2217
|
const { token, studioUrl, signal, heartbeatInterval, installLogger } = this.#options;
|
|
2141
2218
|
await installLogger?.(this.#hooks);
|
|
2142
2219
|
try {
|
|
@@ -2147,48 +2224,65 @@ var StudioSession = class {
|
|
|
2147
2224
|
});
|
|
2148
2225
|
this.#session = session;
|
|
2149
2226
|
this.#studioVersion = session.version;
|
|
2150
|
-
const
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
this.#
|
|
2227
|
+
const rpc = await (this.#options.connector ?? connectWebSocketRpc)({
|
|
2228
|
+
url: session.url,
|
|
2229
|
+
token,
|
|
2230
|
+
local: this
|
|
2231
|
+
});
|
|
2232
|
+
this.#rpc = rpc;
|
|
2233
|
+
rpc.closed.then(this.#onClose);
|
|
2156
2234
|
signal?.addEventListener("abort", this.#onAbort, { once: true });
|
|
2157
2235
|
this.#unhooks.push(() => signal?.removeEventListener("abort", this.#onAbort));
|
|
2158
|
-
this.#
|
|
2159
|
-
this.#
|
|
2236
|
+
this.#scheduleHeartbeat(heartbeatInterval);
|
|
2237
|
+
await this.#hooks.callHook("studio:connected", {
|
|
2238
|
+
url: studioUrl,
|
|
2239
|
+
versions: {
|
|
2240
|
+
studio: this.#studioVersion,
|
|
2241
|
+
kubb: version,
|
|
2242
|
+
agent: this.#options.version
|
|
2243
|
+
}
|
|
2244
|
+
});
|
|
2245
|
+
await this.#connectAck.promise;
|
|
2246
|
+
await this.#hooks.callHook("studio:ready", {});
|
|
2160
2247
|
} catch (error) {
|
|
2248
|
+
this.#disposed = true;
|
|
2249
|
+
this.dispose();
|
|
2161
2250
|
await this.#hooks.callHook("studio:error", { error: toError(error) });
|
|
2162
2251
|
if (error instanceof InvalidAgentTokenError) throw error;
|
|
2163
2252
|
reconnect(this.#options);
|
|
2164
2253
|
}
|
|
2165
2254
|
}
|
|
2166
|
-
/**
|
|
2167
|
-
* Adds a socket listener and tracks its remover, so `dispose` detaches every listener at once.
|
|
2168
|
-
*/
|
|
2169
|
-
#listen(ws, event, listener) {
|
|
2170
|
-
ws.addEventListener(event, listener);
|
|
2171
|
-
this.#unhooks.push(() => ws.removeEventListener(event, listener));
|
|
2172
|
-
}
|
|
2173
2255
|
#warn(message) {
|
|
2174
2256
|
return this.#hooks.callHook("studio:warn", { message });
|
|
2175
2257
|
}
|
|
2176
2258
|
/**
|
|
2177
|
-
*
|
|
2178
|
-
*
|
|
2259
|
+
* Declines a request: logs why locally, then tells Studio. The two wordings differ on purpose,
|
|
2260
|
+
* since the log names the request that was ignored and the error names what the caller can do.
|
|
2179
2261
|
*/
|
|
2180
|
-
#
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2262
|
+
async #refuse(reason, message) {
|
|
2263
|
+
await this.#warn(reason);
|
|
2264
|
+
throw new Error(message);
|
|
2265
|
+
}
|
|
2266
|
+
#scheduleHeartbeat(interval) {
|
|
2267
|
+
const rpc = this.#rpc;
|
|
2268
|
+
if (!rpc) return;
|
|
2269
|
+
this.#heartbeatTimer = setTimeout(async () => {
|
|
2270
|
+
try {
|
|
2271
|
+
await this.#ping(rpc);
|
|
2272
|
+
} catch {
|
|
2273
|
+
if (this.#rpc === rpc) rpc.close();
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
if (this.#rpc === rpc) this.#scheduleHeartbeat(interval);
|
|
2277
|
+
}, interval);
|
|
2278
|
+
}
|
|
2279
|
+
/**
|
|
2280
|
+
* Races `studio.ping()` against a deadline, so a half-open socket can't hang it forever.
|
|
2281
|
+
* */
|
|
2282
|
+
#ping(rpc) {
|
|
2283
|
+
const { promise: timedOut, reject: onTimeout } = Promise.withResolvers();
|
|
2284
|
+
const timer = setTimeout(() => onTimeout(/* @__PURE__ */ new Error("Heartbeat ping timed out")), agentDefaults.heartbeatTimeoutMs);
|
|
2285
|
+
return Promise.race([rpc.studio.ping(), timedOut]).finally(() => clearTimeout(timer));
|
|
2192
2286
|
}
|
|
2193
2287
|
/**
|
|
2194
2288
|
* Reads `kubb.config.ts` and reports which plugin options Studio may edit.
|
|
@@ -2205,89 +2299,48 @@ var StudioSession = class {
|
|
|
2205
2299
|
return;
|
|
2206
2300
|
}
|
|
2207
2301
|
}
|
|
2208
|
-
async
|
|
2302
|
+
async connect() {
|
|
2209
2303
|
const { configPath, root, version: version$1, loadConfig, permissions } = this.#options;
|
|
2210
|
-
|
|
2211
|
-
const
|
|
2212
|
-
sendAgentMessage(this.#ws, {
|
|
2213
|
-
type: "agent:connect",
|
|
2214
|
-
payload: {
|
|
2215
|
-
versions: {
|
|
2216
|
-
kubb: version,
|
|
2217
|
-
agent: version$1
|
|
2218
|
-
},
|
|
2219
|
-
root,
|
|
2220
|
-
config: {
|
|
2221
|
-
path: configPath,
|
|
2222
|
-
file: await this.#readConfigFileView(),
|
|
2223
|
-
plugins: config.plugins.map((plugin) => ({
|
|
2224
|
-
name: toPackageName(plugin.name),
|
|
2225
|
-
options: plugin.options ?? {}
|
|
2226
|
-
}))
|
|
2227
|
-
},
|
|
2228
|
-
permissions: {
|
|
2229
|
-
...permissions,
|
|
2230
|
-
allowWrite: this.#canWrite,
|
|
2231
|
-
allowInput: this.#canUseInput,
|
|
2232
|
-
allowConfigEdit: this.#canEditConfig
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
});
|
|
2236
|
-
}
|
|
2237
|
-
async #handleOpen() {
|
|
2238
|
-
this.#lastPongAt = Date.now();
|
|
2239
|
-
await this.#hooks.callHook("studio:connected", {
|
|
2240
|
-
url: this.#options.studioUrl,
|
|
2304
|
+
const [config, file] = await Promise.all([loadConfig(), this.#readConfigFileView()]);
|
|
2305
|
+
const payload = {
|
|
2241
2306
|
versions: {
|
|
2242
|
-
studio: this.#studioVersion,
|
|
2243
2307
|
kubb: version,
|
|
2244
|
-
agent:
|
|
2308
|
+
agent: version$1
|
|
2309
|
+
},
|
|
2310
|
+
root,
|
|
2311
|
+
config: {
|
|
2312
|
+
path: configPath,
|
|
2313
|
+
file,
|
|
2314
|
+
plugins: config.plugins.map((plugin) => ({
|
|
2315
|
+
name: toPackageName(plugin.name),
|
|
2316
|
+
options: plugin.options ?? {}
|
|
2317
|
+
}))
|
|
2318
|
+
},
|
|
2319
|
+
permissions: {
|
|
2320
|
+
...permissions,
|
|
2321
|
+
allowWrite: this.#canWrite,
|
|
2322
|
+
allowInput: this.#canUseInput,
|
|
2323
|
+
allowConfigEdit: this.#canEditConfig,
|
|
2324
|
+
allowRead: this.#canRead
|
|
2245
2325
|
}
|
|
2246
|
-
}
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
this.#waitForReady();
|
|
2250
|
-
} catch (error) {
|
|
2251
|
-
await this.#warn(`Failed to send the connect payload: ${getErrorMessage(error)}`);
|
|
2252
|
-
}
|
|
2326
|
+
};
|
|
2327
|
+
this.#connectAck.resolve();
|
|
2328
|
+
return payload;
|
|
2253
2329
|
}
|
|
2254
|
-
|
|
2255
|
-
* Arms the timeout for Studio's `studio:ready` acknowledgement. Re-armed on every open, since a
|
|
2256
|
-
* command sent while the agent is not attached is lost either way (see `#sendConnectedPayload`),
|
|
2257
|
-
* so each reconnect needs its own fresh wait.
|
|
2258
|
-
*/
|
|
2259
|
-
#waitForReady() {
|
|
2260
|
-
if (this.#disposed) return;
|
|
2261
|
-
clearTimeout(this.#readyTimer);
|
|
2262
|
-
this.#readyTimer = setTimeout(() => {
|
|
2263
|
-
this.#readyTimer = void 0;
|
|
2264
|
-
Promise.resolve(this.#warn(`Kubb Studio did not confirm the connection was ready within ${READY_TIMEOUT_MS}ms`)).catch(() => {});
|
|
2265
|
-
}, READY_TIMEOUT_MS);
|
|
2266
|
-
}
|
|
2267
|
-
#onOpen = () => void this.#handleOpen().catch(() => {});
|
|
2268
|
-
#onAbort = () => void this.#end({
|
|
2269
|
-
reason: "shutdown",
|
|
2270
|
-
retry: false
|
|
2271
|
-
});
|
|
2330
|
+
#onAbort = () => void this.#end({ retry: false });
|
|
2272
2331
|
#onClose = () => void this.#end({ retry: true });
|
|
2273
|
-
#onError = () => {
|
|
2274
|
-
this.#hooks.callHook("studio:error", { error: /* @__PURE__ */ new Error("Failed to connect to Kubb Studio") });
|
|
2275
|
-
this.#onClose();
|
|
2276
|
-
};
|
|
2277
2332
|
/**
|
|
2278
2333
|
* Drops the socket and detaches every listener and timer this session added. Idempotent, and
|
|
2279
2334
|
* safe before `connect` opened anything.
|
|
2280
2335
|
*
|
|
2281
2336
|
* @internal
|
|
2282
2337
|
*/
|
|
2283
|
-
dispose(
|
|
2284
|
-
|
|
2338
|
+
dispose() {
|
|
2339
|
+
clearTimeout(this.#heartbeatTimer);
|
|
2285
2340
|
this.#heartbeatTimer = void 0;
|
|
2286
|
-
|
|
2287
|
-
this.#
|
|
2288
|
-
|
|
2289
|
-
this.#ws?.close(1e3, reason);
|
|
2290
|
-
} catch {}
|
|
2341
|
+
this.#rpc?.close();
|
|
2342
|
+
this.#rpc = void 0;
|
|
2343
|
+
this.#connectAck.reject(/* @__PURE__ */ new Error("Session ended before Studio called connect()"));
|
|
2291
2344
|
for (const unhook of this.#unhooks) unhook();
|
|
2292
2345
|
this.#unhooks.length = 0;
|
|
2293
2346
|
}
|
|
@@ -2295,15 +2348,12 @@ var StudioSession = class {
|
|
|
2295
2348
|
* Ends the session: tells Studio it is over, drops the socket, and optionally reconnects.
|
|
2296
2349
|
* `#disposed` keeps the close event from running this twice, and a shutdown from reconnecting.
|
|
2297
2350
|
*/
|
|
2298
|
-
async #end({
|
|
2351
|
+
async #end({ retry }) {
|
|
2299
2352
|
const { studioUrl, token, logLevel } = this.#options;
|
|
2300
2353
|
if (this.#disposed) return;
|
|
2301
2354
|
this.#disposed = true;
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
reason: "shutdown"
|
|
2305
|
-
});
|
|
2306
|
-
this.dispose(reason);
|
|
2355
|
+
this.dispose();
|
|
2356
|
+
await this.#hooks.callHook("studio:disconnected", { reason: retry ? "connection closed" : "shutdown" });
|
|
2307
2357
|
if (this.#session) await disconnect({
|
|
2308
2358
|
sessionId: this.#session.sessionId,
|
|
2309
2359
|
studioUrl,
|
|
@@ -2313,78 +2363,35 @@ var StudioSession = class {
|
|
|
2313
2363
|
}).catch(() => {});
|
|
2314
2364
|
if (retry) reconnect(this.#options);
|
|
2315
2365
|
}
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
return;
|
|
2336
|
-
}
|
|
2337
|
-
await this.#warn(`Ignored an unknown message from Kubb Studio: ${data.type}`);
|
|
2338
|
-
} catch (error) {
|
|
2339
|
-
await this.#hooks.callHook("studio:error", { error: toError(error) });
|
|
2340
|
-
await this.#emitError(toError(error));
|
|
2341
|
-
}
|
|
2342
|
-
};
|
|
2343
|
-
/**
|
|
2344
|
-
* Studio ended the session itself. A revoked one stays ended, an expired one gets a fresh
|
|
2345
|
-
* session, and anything else is left to the socket's own close event.
|
|
2346
|
-
*/
|
|
2347
|
-
async #handleDisconnect(reason) {
|
|
2348
|
-
await this.#hooks.callHook("studio:disconnected", { reason });
|
|
2349
|
-
if (reason !== "revoked" && reason !== "expired") return;
|
|
2350
|
-
this.#disposed = true;
|
|
2351
|
-
this.dispose(`session_${reason}`);
|
|
2352
|
-
if (reason === "expired") reconnect(this.#options);
|
|
2353
|
-
}
|
|
2354
|
-
async #handleCommand(data) {
|
|
2355
|
-
const ws = this.#ws;
|
|
2356
|
-
if (!ws) return;
|
|
2357
|
-
const command = data.type.slice(7);
|
|
2358
|
-
await this.#hooks.callHook("studio:command:start", { command });
|
|
2359
|
-
switch (data.type) {
|
|
2360
|
-
case "studio:generate":
|
|
2361
|
-
await this.#handleGenerate(ws, data, command);
|
|
2362
|
-
return;
|
|
2363
|
-
case "studio:connect":
|
|
2364
|
-
this.#studioVersion = data.version ?? this.#studioVersion;
|
|
2365
|
-
await this.#sendConnectedPayload();
|
|
2366
|
-
await this.#hooks.callHook("studio:command:end", { command });
|
|
2367
|
-
return;
|
|
2368
|
-
case "studio:save":
|
|
2369
|
-
await this.#handleSave(ws, data, command);
|
|
2370
|
-
return;
|
|
2371
|
-
case "studio:snapshot":
|
|
2372
|
-
await this.#handleSnapshot(ws, data, command);
|
|
2373
|
-
return;
|
|
2374
|
-
}
|
|
2366
|
+
startGeneration(data) {
|
|
2367
|
+
const generationStream = createGenerationStream(this.#hooks, data.jobId, { onGenerationEnd: (result) => {
|
|
2368
|
+
this.#lastGeneration = result;
|
|
2369
|
+
} });
|
|
2370
|
+
const controller = new AbortController();
|
|
2371
|
+
const result = this.#runGeneration(data, controller).then(async (value) => {
|
|
2372
|
+
await generationStream.close();
|
|
2373
|
+
return value;
|
|
2374
|
+
}).catch((error) => {
|
|
2375
|
+
generationStream.fail(error);
|
|
2376
|
+
throw error;
|
|
2377
|
+
});
|
|
2378
|
+
result.catch(() => {});
|
|
2379
|
+
return new GenerationRunTarget(generationStream.stream, result, async () => {
|
|
2380
|
+
controller.abort(/* @__PURE__ */ new Error("Generation canceled"));
|
|
2381
|
+
}, () => {
|
|
2382
|
+
controller.abort(/* @__PURE__ */ new Error("Generation canceled"));
|
|
2383
|
+
generationStream.dispose();
|
|
2384
|
+
});
|
|
2375
2385
|
}
|
|
2376
|
-
async #
|
|
2377
|
-
|
|
2378
|
-
if (this.#isGenerating) {
|
|
2379
|
-
await this.#warn("Ignored generate: a generation is already in progress");
|
|
2380
|
-
await this.#emitError(/* @__PURE__ */ new Error("A generation is already in progress, please wait for it to finish"));
|
|
2381
|
-
return;
|
|
2382
|
-
}
|
|
2386
|
+
async #runGeneration(data, controller) {
|
|
2387
|
+
if (this.#isGenerating) return this.#refuse("Ignored generate: a generation is already in progress", "A generation is already in progress, please wait for it to finish");
|
|
2383
2388
|
this.#isGenerating = true;
|
|
2384
|
-
|
|
2389
|
+
const command = "generate";
|
|
2390
|
+
const { root, loadConfig, permissions, client } = this.#options;
|
|
2385
2391
|
try {
|
|
2392
|
+
await this.#hooks.callHook("studio:command:start", { command });
|
|
2386
2393
|
const config = await loadConfig();
|
|
2387
|
-
const patch = data.
|
|
2394
|
+
const patch = data.config;
|
|
2388
2395
|
const plugins = await mergePlugins(config.plugins, patch?.plugins);
|
|
2389
2396
|
const adapter = await mergeAdapter(config.adapter, patch?.adapter);
|
|
2390
2397
|
const inputOverride = this.#isSandbox ? patch?.input ?? "" : permissions.allowInput && patch?.input || void 0;
|
|
@@ -2394,13 +2401,8 @@ var StudioSession = class {
|
|
|
2394
2401
|
await this.#warn(`Ignored the spec from Studio; set ${remedy} to generate from it`);
|
|
2395
2402
|
}
|
|
2396
2403
|
const resolvedPlugins = plugins ?? config.plugins;
|
|
2397
|
-
|
|
2398
|
-
const detach = [setupHookListener(this.#hooks, root
|
|
2399
|
-
skipStorage: client?.kind === "ci",
|
|
2400
|
-
onGenerationEnd: (files) => {
|
|
2401
|
-
generatedFiles = files;
|
|
2402
|
-
}
|
|
2403
|
-
})];
|
|
2404
|
+
this.#lastGeneration = void 0;
|
|
2405
|
+
const detach = [setupHookListener(this.#hooks, root, controller.signal)];
|
|
2404
2406
|
try {
|
|
2405
2407
|
await generate({
|
|
2406
2408
|
config: {
|
|
@@ -2417,140 +2419,155 @@ var StudioSession = class {
|
|
|
2417
2419
|
plugins: resolvedPlugins,
|
|
2418
2420
|
adapter
|
|
2419
2421
|
},
|
|
2420
|
-
hooks: this.#hooks
|
|
2422
|
+
hooks: this.#hooks,
|
|
2423
|
+
signal: controller.signal
|
|
2421
2424
|
});
|
|
2422
2425
|
} finally {
|
|
2423
2426
|
for (const remove of detach) remove();
|
|
2424
|
-
this.#lastGeneration = generatedFiles;
|
|
2425
2427
|
}
|
|
2426
2428
|
await this.#hooks.callHook("studio:command:end", {
|
|
2427
2429
|
command,
|
|
2428
2430
|
info: `${resolvedPlugins.length} plugin${resolvedPlugins.length === 1 ? "" : "s"}, ${this.#canWrite ? "written to disk" : "in memory"}${inputOverride !== void 0 ? ", from a Studio spec" : ""}`
|
|
2429
2431
|
});
|
|
2432
|
+
const files = [...this.#lastGeneration?.paths ?? []];
|
|
2433
|
+
return {
|
|
2434
|
+
status: "success",
|
|
2435
|
+
files,
|
|
2436
|
+
fileCount: files.length
|
|
2437
|
+
};
|
|
2430
2438
|
} finally {
|
|
2431
2439
|
this.#isGenerating = false;
|
|
2432
|
-
this.#activeJobId = null;
|
|
2433
2440
|
}
|
|
2434
2441
|
}
|
|
2435
|
-
async
|
|
2442
|
+
async saveConfig(data) {
|
|
2443
|
+
const command = "saveConfig";
|
|
2444
|
+
await this.#hooks.callHook("studio:command:start", { command });
|
|
2436
2445
|
const { configPath, configFile } = this.#options;
|
|
2437
2446
|
if (!Array.isArray(data.edits)) {
|
|
2438
2447
|
await this.#warn("Ignored save: the message carried no edits");
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
outcomes: [],
|
|
2444
|
-
changed: false
|
|
2445
|
-
}
|
|
2446
|
-
});
|
|
2447
|
-
return;
|
|
2448
|
+
return {
|
|
2449
|
+
outcomes: [],
|
|
2450
|
+
changed: false
|
|
2451
|
+
};
|
|
2448
2452
|
}
|
|
2449
2453
|
const edits = data.edits;
|
|
2450
|
-
const refuse = (reason) =>
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
reason
|
|
2458
|
-
})),
|
|
2459
|
-
changed: false
|
|
2460
|
-
}
|
|
2454
|
+
const refuse = (reason) => ({
|
|
2455
|
+
outcomes: edits.map((edit) => ({
|
|
2456
|
+
edit,
|
|
2457
|
+
applied: false,
|
|
2458
|
+
reason
|
|
2459
|
+
})),
|
|
2460
|
+
changed: false
|
|
2461
2461
|
});
|
|
2462
2462
|
if (!this.#canEditConfig) {
|
|
2463
2463
|
await this.#warn("Ignored save: editing kubb.config.ts was not granted");
|
|
2464
|
-
refuse("the agent was not granted permission to edit kubb.config.ts");
|
|
2465
|
-
return;
|
|
2466
|
-
}
|
|
2467
|
-
if (this.#isGenerating) {
|
|
2468
|
-
refuse("a generation is in progress");
|
|
2469
|
-
return;
|
|
2464
|
+
return refuse("the agent was not granted permission to edit kubb.config.ts");
|
|
2470
2465
|
}
|
|
2466
|
+
if (this.#isGenerating) return refuse("a generation is in progress");
|
|
2471
2467
|
try {
|
|
2472
2468
|
const { source: patched, outcomes, changed } = applyConfigEdits(await read(configFile), edits);
|
|
2473
2469
|
if (changed) await (0, node_fs_promises.writeFile)(configFile, patched, "utf-8");
|
|
2474
|
-
sendAgentMessage(ws, {
|
|
2475
|
-
type: "agent:save",
|
|
2476
|
-
jobId: data.jobId,
|
|
2477
|
-
payload: {
|
|
2478
|
-
outcomes,
|
|
2479
|
-
changed,
|
|
2480
|
-
file: changed ? await this.#readConfigFileView(patched) : void 0
|
|
2481
|
-
}
|
|
2482
|
-
});
|
|
2483
2470
|
const applied = outcomes.filter((outcome) => outcome.applied).length;
|
|
2484
2471
|
await this.#hooks.callHook("studio:command:end", {
|
|
2485
2472
|
command,
|
|
2486
2473
|
info: `applied ${applied}/${outcomes.length} edits to ${configPath}`
|
|
2487
2474
|
});
|
|
2475
|
+
return {
|
|
2476
|
+
outcomes,
|
|
2477
|
+
changed,
|
|
2478
|
+
file: changed ? await this.#readConfigFileView(patched) : void 0
|
|
2479
|
+
};
|
|
2488
2480
|
} catch (error) {
|
|
2489
2481
|
await this.#hooks.callHook("studio:error", { error: toError(error) });
|
|
2490
|
-
refuse(getErrorMessage(error));
|
|
2482
|
+
return refuse(getErrorMessage(error));
|
|
2491
2483
|
}
|
|
2492
2484
|
}
|
|
2493
|
-
async
|
|
2494
|
-
const
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
refuse("a sandbox agent has no project to build a package from");
|
|
2505
|
-
return;
|
|
2506
|
-
}
|
|
2507
|
-
const { name, version, peerDependencies, uploadPath } = data.payload;
|
|
2508
|
-
if (!name || !version || !uploadPath) {
|
|
2509
|
-
await this.#warn("Ignored snapshot: the message was missing required fields");
|
|
2510
|
-
refuse("the message was missing required fields");
|
|
2511
|
-
return;
|
|
2512
|
-
}
|
|
2513
|
-
const files = this.#lastGeneration;
|
|
2514
|
-
if (!files) {
|
|
2515
|
-
await this.#warn("Ignored snapshot: no prior generation to pack");
|
|
2516
|
-
refuse("no prior generation exists to pack, run a generation first");
|
|
2517
|
-
return;
|
|
2518
|
-
}
|
|
2485
|
+
async publishSnapshot(data) {
|
|
2486
|
+
const command = "snapshot";
|
|
2487
|
+
await this.#hooks.callHook("studio:command:start", { command });
|
|
2488
|
+
if (this.#isSandbox) return this.#refuse("Ignored snapshot: a sandbox agent has no project to build a package from", "A sandbox agent has no project to build a package from");
|
|
2489
|
+
const { name, version, bundledDependencies, uploadPath } = data;
|
|
2490
|
+
if (!name || !version || !uploadPath) return this.#refuse("Ignored snapshot: the message was missing required fields", "The request was missing required fields");
|
|
2491
|
+
const generation = this.#lastGeneration;
|
|
2492
|
+
if (!generation) return this.#refuse("Ignored snapshot: no prior generation to pack", "No prior generation exists to pack, run a generation first");
|
|
2493
|
+
const bundled = new Set(bundledDependencies ?? []);
|
|
2494
|
+
const missing = generation.missingDependencies.filter((dependency) => !bundled.has(dependency));
|
|
2495
|
+
if (missing.length) return this.#refuse(`Ignored snapshot: missing dependencies: ${missing.join(", ")}`, `Missing dependencies: ${missing.join(", ")}`);
|
|
2519
2496
|
try {
|
|
2497
|
+
const files = {};
|
|
2498
|
+
await inParallel({
|
|
2499
|
+
items: [...generation.paths],
|
|
2500
|
+
limit: FILE_READ_CONCURRENCY,
|
|
2501
|
+
run: async (relativePath) => {
|
|
2502
|
+
const content = await generation.storage.readItem(absoluteStoragePath(generation.root, relativePath));
|
|
2503
|
+
if (content !== null) files[relativePath] = content;
|
|
2504
|
+
}
|
|
2505
|
+
});
|
|
2520
2506
|
const { bytes, integrity } = await createSnapshotPackage(files, {
|
|
2521
2507
|
name,
|
|
2522
2508
|
version,
|
|
2523
|
-
peerDependencies: peerDependencies
|
|
2509
|
+
peerDependencies: generation.peerDependencies
|
|
2524
2510
|
});
|
|
2525
2511
|
const { token, studioUrl } = this.#options;
|
|
2526
|
-
const
|
|
2512
|
+
const uploadUrl = new URL(uploadPath, studioUrl);
|
|
2513
|
+
if (uploadUrl.origin !== new URL(studioUrl).origin) throw new Error("Snapshot upload path must stay on the Studio origin");
|
|
2514
|
+
const redirect = await fetch(uploadUrl, {
|
|
2527
2515
|
method: "PUT",
|
|
2528
2516
|
headers: { Authorization: `Bearer ${token}` },
|
|
2529
2517
|
redirect: "manual"
|
|
2530
2518
|
});
|
|
2531
2519
|
const storageUrl = redirect.headers.get("location");
|
|
2532
2520
|
if (redirect.status !== 307 || !storageUrl) throw new Error(`Studio did not provide a storage URL (status ${redirect.status})`);
|
|
2533
|
-
const
|
|
2521
|
+
const storage = new URL(storageUrl);
|
|
2522
|
+
if (storage.protocol !== "https:" && storage.hostname !== "localhost" && storage.hostname !== "127.0.0.1") throw new Error(`Refusing snapshot upload to ${storage.origin}`);
|
|
2523
|
+
const response = await fetch(storage, {
|
|
2534
2524
|
method: "PUT",
|
|
2535
|
-
body: new Uint8Array(bytes)
|
|
2525
|
+
body: new Uint8Array(bytes),
|
|
2526
|
+
redirect: "error"
|
|
2536
2527
|
});
|
|
2537
2528
|
if (!response.ok) throw new Error(`Snapshot upload failed with status ${response.status}`);
|
|
2538
|
-
sendAgentMessage(ws, {
|
|
2539
|
-
type: "agent:snapshot",
|
|
2540
|
-
jobId: data.jobId,
|
|
2541
|
-
payload: {
|
|
2542
|
-
status: "ok",
|
|
2543
|
-
integrity
|
|
2544
|
-
}
|
|
2545
|
-
});
|
|
2546
2529
|
await this.#hooks.callHook("studio:command:end", {
|
|
2547
2530
|
command,
|
|
2548
2531
|
info: `packed ${Object.keys(files).length} file${Object.keys(files).length === 1 ? "" : "s"}`
|
|
2549
2532
|
});
|
|
2533
|
+
return {
|
|
2534
|
+
integrity,
|
|
2535
|
+
peerDependencies: generation.peerDependencies
|
|
2536
|
+
};
|
|
2550
2537
|
} catch (error) {
|
|
2551
2538
|
await this.#hooks.callHook("studio:error", { error: toError(error) });
|
|
2552
|
-
|
|
2539
|
+
throw error;
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
async readFiles(data) {
|
|
2543
|
+
const command = "readFiles";
|
|
2544
|
+
await this.#hooks.callHook("studio:command:start", { command });
|
|
2545
|
+
const { client } = this.#options;
|
|
2546
|
+
if (!this.#canRead) {
|
|
2547
|
+
await this.#warn("Ignored files: reading generated files was not granted");
|
|
2548
|
+
const remedy = client?.kind === "cli" ? "--allow-read, or answer yes when kubb studio asks," : "KUBB_AGENT_ALLOW_READ=true";
|
|
2549
|
+
throw new Error(`The agent was not granted permission to read generated files; set ${remedy} to allow it`);
|
|
2553
2550
|
}
|
|
2551
|
+
if (!Array.isArray(data.paths)) return this.#refuse("Ignored files: the message carried no paths", "The request carried no paths");
|
|
2552
|
+
const { paths } = data;
|
|
2553
|
+
if (paths.length > 50) return this.#refuse(`Ignored files: requested ${paths.length} paths, more than the 50 allowed per request`, `At most 50 paths may be requested at once`);
|
|
2554
|
+
const generation = this.#lastGeneration;
|
|
2555
|
+
if (!generation) return this.#refuse("Ignored files: no prior generation to read from", "No prior generation to read from, run a generation first");
|
|
2556
|
+
const requested = paths.filter((path) => generation.paths.has(path));
|
|
2557
|
+
const files = {};
|
|
2558
|
+
await inParallel({
|
|
2559
|
+
items: requested,
|
|
2560
|
+
limit: FILE_READ_CONCURRENCY,
|
|
2561
|
+
run: async (path) => {
|
|
2562
|
+
const content = await generation.storage.readItem(absoluteStoragePath(generation.root, path));
|
|
2563
|
+
if (content !== null) files[path] = content;
|
|
2564
|
+
}
|
|
2565
|
+
});
|
|
2566
|
+
await this.#hooks.callHook("studio:command:end", {
|
|
2567
|
+
command,
|
|
2568
|
+
info: `read ${Object.keys(files).length}/${paths.length} requested file${paths.length === 1 ? "" : "s"}`
|
|
2569
|
+
});
|
|
2570
|
+
return { files };
|
|
2554
2571
|
}
|
|
2555
2572
|
};
|
|
2556
2573
|
//#endregion
|
|
@@ -2587,7 +2604,7 @@ function createClient({ storage, onAuthRequired, ...options }) {
|
|
|
2587
2604
|
...options,
|
|
2588
2605
|
signal: controller.signal,
|
|
2589
2606
|
onTokenRejected: notifyAuthRequired
|
|
2590
|
-
}).
|
|
2607
|
+
}).start()));
|
|
2591
2608
|
},
|
|
2592
2609
|
disconnect() {
|
|
2593
2610
|
controller.abort();
|
|
@@ -2759,12 +2776,13 @@ async function pollForPairingToken({ studioUrl = agentDefaults.studioUrl, sessio
|
|
|
2759
2776
|
//#endregion
|
|
2760
2777
|
exports.InvalidAgentTokenError = InvalidAgentTokenError;
|
|
2761
2778
|
exports.PairingCanceledError = PairingCanceledError;
|
|
2779
|
+
exports.connectWebSocketRpc = connectWebSocketRpc;
|
|
2762
2780
|
exports.createAgent = createAgent;
|
|
2763
2781
|
exports.createClient = createClient;
|
|
2764
2782
|
exports.createFileStorage = createFileStorage;
|
|
2765
2783
|
exports.createJob = createJob;
|
|
2766
|
-
exports.createJobId = require_protocol.createJobId;
|
|
2767
2784
|
exports.defaultStudioUrl = defaultStudioUrl;
|
|
2785
|
+
exports.generationEventTypes = require_protocol.generationEventTypes;
|
|
2768
2786
|
exports.machineTokenFrom = machineTokenFrom;
|
|
2769
2787
|
exports.pollForPairingToken = pollForPairingToken;
|
|
2770
2788
|
exports.runConnection = runConnection;
|