@kubb/studio 5.3.5 → 5.3.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 -11
- package/dist/index.cjs +469 -516
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +19 -5
- package/dist/index.js +433 -506
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs +31 -45
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.ts +126 -275
- package/dist/protocol.js +31 -39
- 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.7";
|
|
682
710
|
//#endregion
|
|
683
711
|
//#region src/hooks.ts
|
|
684
712
|
/**
|
|
@@ -689,16 +717,19 @@ var version = "5.3.5";
|
|
|
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
|
}
|
|
@@ -1707,16 +1751,28 @@ async function createSnapshotPackage(files, packageInfo) {
|
|
|
1707
1751
|
const filePath = (0, node_path.join)(entry.parentPath, entry.name);
|
|
1708
1752
|
return [`package/dist/${(0, node_path.relative)(dist, filePath).split(node_path.sep).join("/")}`, await (0, node_fs_promises.readFile)(filePath, "utf8")];
|
|
1709
1753
|
}));
|
|
1754
|
+
const builtPaths = new Set(builtEntries.map(([path]) => path));
|
|
1755
|
+
const hasBarrel = builtPaths.has("package/dist/index.mjs") && builtPaths.has("package/dist/index.cjs");
|
|
1756
|
+
const barrelFields = hasBarrel ? {
|
|
1757
|
+
main: "./dist/index.cjs",
|
|
1758
|
+
module: "./dist/index.mjs"
|
|
1759
|
+
} : {};
|
|
1760
|
+
const barrelExport = hasBarrel ? { ".": {
|
|
1761
|
+
import: "./dist/index.mjs",
|
|
1762
|
+
require: "./dist/index.cjs"
|
|
1763
|
+
} } : {};
|
|
1710
1764
|
const entries = {
|
|
1711
1765
|
"package/package.json": JSON.stringify({
|
|
1712
1766
|
...packageInfo,
|
|
1713
1767
|
type: "module",
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1768
|
+
...barrelFields,
|
|
1769
|
+
exports: {
|
|
1770
|
+
...barrelExport,
|
|
1771
|
+
"./*": {
|
|
1772
|
+
import: "./dist/*.mjs",
|
|
1773
|
+
require: "./dist/*.cjs"
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1720
1776
|
}, null, 2),
|
|
1721
1777
|
...Object.fromEntries(resolvedPaths.map(({ content, target }) => [target, content])),
|
|
1722
1778
|
...Object.fromEntries(builtEntries)
|
|
@@ -1745,13 +1801,6 @@ async function createSnapshotPackage(files, packageInfo) {
|
|
|
1745
1801
|
* takes over.
|
|
1746
1802
|
*/
|
|
1747
1803
|
const CONNECT_TIMEOUT_MS = 5e3;
|
|
1748
|
-
/**
|
|
1749
|
-
* Per-socket event counter. Every data message carries the next value so Studio can restore the
|
|
1750
|
-
* agent's emission order even when the relay delivers frames out of order. Keyed by the socket so
|
|
1751
|
-
* the count stays monotonic across every generation run on one connection, and is dropped
|
|
1752
|
-
* automatically once the socket is collected.
|
|
1753
|
-
*/
|
|
1754
|
-
const eventSeqCounters = /* @__PURE__ */ new WeakMap();
|
|
1755
1804
|
const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
1756
1805
|
function relativeStoragePath(root, filePath) {
|
|
1757
1806
|
return ((0, node_path.isAbsolute)(filePath) ? (0, node_path.relative)((0, node_path.resolve)(root), filePath) : filePath).replaceAll("\\", "/");
|
|
@@ -1776,66 +1825,38 @@ async function resolvePeerDependencies(names) {
|
|
|
1776
1825
|
}));
|
|
1777
1826
|
for (const [index, name] of uniqueNames.entries()) {
|
|
1778
1827
|
const version = versions[index];
|
|
1779
|
-
if (version)
|
|
1780
|
-
|
|
1828
|
+
if (version) {
|
|
1829
|
+
peerDependencies[name] = version;
|
|
1830
|
+
continue;
|
|
1831
|
+
}
|
|
1832
|
+
missingDependencies.push(name);
|
|
1781
1833
|
}
|
|
1782
1834
|
return {
|
|
1783
1835
|
peerDependencies,
|
|
1784
1836
|
missingDependencies
|
|
1785
1837
|
};
|
|
1786
1838
|
}
|
|
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
1839
|
/**
|
|
1793
1840
|
* Opens a Studio WebSocket connection and closes it when the initial handshake exceeds the configured timeout.
|
|
1794
1841
|
*/
|
|
1795
1842
|
function createWebsocket(url, options) {
|
|
1796
|
-
const ws$
|
|
1843
|
+
const ws$1 = new ws.default(url, options);
|
|
1797
1844
|
const timer = setTimeout(() => {
|
|
1798
|
-
if (ws$
|
|
1845
|
+
if (ws$1.readyState === ws.default.CONNECTING) ws$1.close(3008, "Connection timeout");
|
|
1799
1846
|
}, 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
|
-
});
|
|
1847
|
+
ws$1.once("open", () => clearTimeout(timer));
|
|
1848
|
+
ws$1.once("close", () => clearTimeout(timer));
|
|
1849
|
+
return ws$1;
|
|
1833
1850
|
}
|
|
1834
|
-
/**
|
|
1835
|
-
|
|
1836
|
-
*/
|
|
1837
|
-
function setupEventsStream(ws$5, hooks, jobId, options = {}) {
|
|
1851
|
+
/** Forwards selected Kubb lifecycle events to a native Cap'n Web stream. */
|
|
1852
|
+
function createGenerationStream(hooks, jobId, options = {}) {
|
|
1838
1853
|
const unhooks = [];
|
|
1854
|
+
let root = "";
|
|
1855
|
+
const transform = new TransformStream(void 0, void 0, { highWaterMark: Infinity });
|
|
1856
|
+
const writer = transform.writable.getWriter();
|
|
1857
|
+
let writes = Promise.resolve();
|
|
1858
|
+
let closed = false;
|
|
1859
|
+
let streamError;
|
|
1839
1860
|
/**
|
|
1840
1861
|
* Registers a listener and keeps its remover, so one generation's listeners come off the session
|
|
1841
1862
|
* emitter again when that generation ends.
|
|
@@ -1843,98 +1864,73 @@ function setupEventsStream(ws$5, hooks, jobId, options = {}) {
|
|
|
1843
1864
|
function on(name, handler) {
|
|
1844
1865
|
unhooks.push(hooks.hook(name, handler));
|
|
1845
1866
|
}
|
|
1846
|
-
function
|
|
1847
|
-
|
|
1848
|
-
type: "agent:data",
|
|
1867
|
+
function emitEvent(type, data) {
|
|
1868
|
+
const event = {
|
|
1849
1869
|
jobId,
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1870
|
+
type,
|
|
1871
|
+
data,
|
|
1872
|
+
version: 1,
|
|
1873
|
+
timestamp: Date.now()
|
|
1874
|
+
};
|
|
1875
|
+
writes = writes.then(() => writer.write(event)).catch((error) => {
|
|
1876
|
+
streamError = error;
|
|
1855
1877
|
});
|
|
1856
1878
|
}
|
|
1857
1879
|
on("kubb:plugin:start", (ctx) => {
|
|
1858
|
-
|
|
1859
|
-
type: "kubb:plugin:start",
|
|
1860
|
-
data: [{ plugin: ctx.plugin }]
|
|
1861
|
-
});
|
|
1880
|
+
emitEvent("kubb:plugin:start", [{ plugin: { name: ctx.plugin.name } }]);
|
|
1862
1881
|
});
|
|
1863
1882
|
on("kubb:plugin:end", (ctx) => {
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
success: ctx.success
|
|
1870
|
-
}]
|
|
1871
|
-
});
|
|
1883
|
+
emitEvent("kubb:plugin:end", [{
|
|
1884
|
+
plugin: { name: ctx.plugin.name },
|
|
1885
|
+
duration: ctx.duration,
|
|
1886
|
+
success: ctx.success
|
|
1887
|
+
}]);
|
|
1872
1888
|
});
|
|
1873
1889
|
on("kubb:build:start", ({ config, adapter }) => {
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
}]
|
|
1880
|
-
});
|
|
1890
|
+
root = config.root;
|
|
1891
|
+
emitEvent("kubb:build:start", [{
|
|
1892
|
+
config: { name: config.name },
|
|
1893
|
+
adapter: { name: adapter.name }
|
|
1894
|
+
}]);
|
|
1881
1895
|
});
|
|
1882
1896
|
on("kubb:build:end", ({ files, config, outputDir }) => {
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
outputDir
|
|
1891
|
-
}]
|
|
1892
|
-
});
|
|
1897
|
+
emitEvent("kubb:build:end", [{
|
|
1898
|
+
files: files.map((file) => ({
|
|
1899
|
+
path: relativeStoragePath(config.root, file.path),
|
|
1900
|
+
name: file.name
|
|
1901
|
+
})),
|
|
1902
|
+
outputDir
|
|
1903
|
+
}]);
|
|
1893
1904
|
});
|
|
1894
1905
|
on("kubb:files:processing:start", ({ files }) => {
|
|
1895
|
-
|
|
1896
|
-
type: "kubb:files:processing:start",
|
|
1897
|
-
data: [{ total: files.length }]
|
|
1898
|
-
});
|
|
1906
|
+
emitEvent("kubb:files:processing:start", [{ total: files.length }]);
|
|
1899
1907
|
});
|
|
1900
1908
|
on("kubb:files:processing:update", ({ files }) => {
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
percentage
|
|
1908
|
-
})) }]
|
|
1909
|
-
});
|
|
1909
|
+
emitEvent("kubb:files:processing:update", [{ files: files.map(({ file, processed, total, percentage }) => ({
|
|
1910
|
+
file: relativeStoragePath(root, file.path),
|
|
1911
|
+
processed,
|
|
1912
|
+
total,
|
|
1913
|
+
percentage
|
|
1914
|
+
})) }]);
|
|
1910
1915
|
});
|
|
1911
1916
|
on("kubb:files:processing:end", ({ files }) => {
|
|
1912
|
-
|
|
1913
|
-
type: "kubb:files:processing:end",
|
|
1914
|
-
data: [{ total: files.length }]
|
|
1915
|
-
});
|
|
1917
|
+
emitEvent("kubb:files:processing:end", [{ total: files.length }]);
|
|
1916
1918
|
});
|
|
1917
1919
|
for (const type of [
|
|
1918
1920
|
"kubb:info",
|
|
1919
1921
|
"kubb:success",
|
|
1920
1922
|
"kubb:warn"
|
|
1921
1923
|
]) on(type, ({ message, info }) => {
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
info
|
|
1927
|
-
}]
|
|
1928
|
-
});
|
|
1924
|
+
emitEvent(type, [{
|
|
1925
|
+
message,
|
|
1926
|
+
info
|
|
1927
|
+
}]);
|
|
1929
1928
|
});
|
|
1930
1929
|
on("kubb:generation:start", ({ config }) => {
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
plugins: config.plugins.length
|
|
1936
|
-
}]
|
|
1937
|
-
});
|
|
1930
|
+
emitEvent("kubb:generation:start", [{
|
|
1931
|
+
name: config.name,
|
|
1932
|
+
plugins: config.plugins.length
|
|
1933
|
+
}]);
|
|
1938
1934
|
});
|
|
1939
1935
|
on("kubb:generation:end", async ({ config, storage, diagnostics = [], status, hrStart, filesCreated }) => {
|
|
1940
1936
|
const { peerDependencies, missingDependencies } = await resolvePeerDependencies(config.plugins.map(({ name }) => name));
|
|
@@ -1947,29 +1943,32 @@ function setupEventsStream(ws$5, hooks, jobId, options = {}) {
|
|
|
1947
1943
|
peerDependencies,
|
|
1948
1944
|
missingDependencies
|
|
1949
1945
|
});
|
|
1950
|
-
|
|
1951
|
-
type: "kubb:generation:end",
|
|
1952
|
-
data: []
|
|
1953
|
-
});
|
|
1946
|
+
emitEvent("kubb:generation:end", []);
|
|
1954
1947
|
if (!hrStart) return;
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
status: status ?? "success"
|
|
1962
|
-
}]
|
|
1963
|
-
});
|
|
1948
|
+
emitEvent("kubb:generation:summary", [{
|
|
1949
|
+
duration: Math.round(getElapsedMs(hrStart)),
|
|
1950
|
+
fileCount: filesCreated ?? 0,
|
|
1951
|
+
failedPlugins: _kubb_core.Diagnostics.failedPlugins(diagnostics).length,
|
|
1952
|
+
status: status ?? "success"
|
|
1953
|
+
}]);
|
|
1964
1954
|
});
|
|
1965
1955
|
on("kubb:error", ({ error }) => {
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1956
|
+
emitEvent("kubb:error", [{
|
|
1957
|
+
message: error.message,
|
|
1958
|
+
stack: error.stack
|
|
1959
|
+
}]);
|
|
1960
|
+
});
|
|
1961
|
+
on("kubb:diagnostic", ({ diagnostic }) => {
|
|
1962
|
+
const cause = "cause" in diagnostic ? diagnostic.cause : void 0;
|
|
1963
|
+
emitEvent("kubb:diagnostic", [{
|
|
1964
|
+
code: diagnostic.code,
|
|
1965
|
+
message: diagnostic.message,
|
|
1966
|
+
severity: diagnostic.severity,
|
|
1967
|
+
location: "location" in diagnostic ? diagnostic.location : void 0,
|
|
1968
|
+
help: "help" in diagnostic ? diagnostic.help : void 0,
|
|
1969
|
+
plugin: "plugin" in diagnostic ? diagnostic.plugin : void 0,
|
|
1970
|
+
stack: cause?.stack
|
|
1971
|
+
}]);
|
|
1973
1972
|
});
|
|
1974
1973
|
for (const type of [
|
|
1975
1974
|
"kubb:lifecycle:start",
|
|
@@ -1981,62 +1980,144 @@ function setupEventsStream(ws$5, hooks, jobId, options = {}) {
|
|
|
1981
1980
|
"kubb:hooks:start",
|
|
1982
1981
|
"kubb:hooks:end"
|
|
1983
1982
|
]) on(type, () => {
|
|
1984
|
-
|
|
1985
|
-
type,
|
|
1986
|
-
data: []
|
|
1987
|
-
});
|
|
1983
|
+
emitEvent(type, []);
|
|
1988
1984
|
});
|
|
1989
1985
|
on("kubb:hook:start", ({ id, command, args }) => {
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
args: args ? [...args] : void 0
|
|
1996
|
-
}]
|
|
1997
|
-
});
|
|
1986
|
+
emitEvent("kubb:hook:start", [{
|
|
1987
|
+
id,
|
|
1988
|
+
command,
|
|
1989
|
+
args: args ? [...args] : void 0
|
|
1990
|
+
}]);
|
|
1998
1991
|
});
|
|
1999
1992
|
on("kubb:hook:line", ({ id, line }) => {
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
line
|
|
2005
|
-
}]
|
|
2006
|
-
});
|
|
1993
|
+
emitEvent("kubb:hook:line", [{
|
|
1994
|
+
id,
|
|
1995
|
+
line
|
|
1996
|
+
}]);
|
|
2007
1997
|
});
|
|
2008
1998
|
on("kubb:hook:end", ({ id, command, args, success, error }) => {
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
} : void 0
|
|
2020
|
-
}]
|
|
2021
|
-
});
|
|
1999
|
+
emitEvent("kubb:hook:end", [{
|
|
2000
|
+
id,
|
|
2001
|
+
command,
|
|
2002
|
+
args: args ? [...args] : void 0,
|
|
2003
|
+
success,
|
|
2004
|
+
error: error ? {
|
|
2005
|
+
message: error.message,
|
|
2006
|
+
stack: error.stack
|
|
2007
|
+
} : void 0
|
|
2008
|
+
}]);
|
|
2022
2009
|
});
|
|
2023
|
-
|
|
2010
|
+
/**
|
|
2011
|
+
* Takes this generation's listeners off the session emitter. Safe to call twice.
|
|
2012
|
+
*/
|
|
2013
|
+
function detach() {
|
|
2024
2014
|
for (const unhook of unhooks) unhook();
|
|
2015
|
+
unhooks.length = 0;
|
|
2016
|
+
}
|
|
2017
|
+
async function close() {
|
|
2018
|
+
if (closed) return;
|
|
2019
|
+
closed = true;
|
|
2020
|
+
detach();
|
|
2021
|
+
await writes;
|
|
2022
|
+
if (streamError) return;
|
|
2023
|
+
await writer.close().catch(() => void 0);
|
|
2024
|
+
}
|
|
2025
|
+
function fail(error) {
|
|
2026
|
+
detach();
|
|
2027
|
+
if (closed) return;
|
|
2028
|
+
closed = true;
|
|
2029
|
+
writer.abort(error).catch(() => void 0);
|
|
2030
|
+
}
|
|
2031
|
+
return {
|
|
2032
|
+
stream: transform.readable,
|
|
2033
|
+
close,
|
|
2034
|
+
dispose: () => fail(),
|
|
2035
|
+
fail
|
|
2025
2036
|
};
|
|
2026
2037
|
}
|
|
2027
2038
|
//#endregion
|
|
2028
|
-
//#region src/
|
|
2039
|
+
//#region src/rpc.ts
|
|
2029
2040
|
/**
|
|
2030
|
-
*
|
|
2031
|
-
*
|
|
2041
|
+
* The only methods Studio may call on an agent. A `StudioSession` carries far more than
|
|
2042
|
+
* {@link AgentApi}, so it is wrapped rather than exposed: what Cap'n Web can reach is exactly what
|
|
2043
|
+
* this class re-declares.
|
|
2032
2044
|
*/
|
|
2033
|
-
|
|
2045
|
+
var AgentRpcTarget = class extends capnweb.RpcTarget {
|
|
2046
|
+
api;
|
|
2047
|
+
constructor(api) {
|
|
2048
|
+
super();
|
|
2049
|
+
this.api = api;
|
|
2050
|
+
}
|
|
2051
|
+
connect() {
|
|
2052
|
+
return this.api.connect();
|
|
2053
|
+
}
|
|
2054
|
+
startGeneration(input) {
|
|
2055
|
+
return this.api.startGeneration(input);
|
|
2056
|
+
}
|
|
2057
|
+
saveConfig(input) {
|
|
2058
|
+
return this.api.saveConfig(input);
|
|
2059
|
+
}
|
|
2060
|
+
publishSnapshot(input) {
|
|
2061
|
+
return this.api.publishSnapshot(input);
|
|
2062
|
+
}
|
|
2063
|
+
readFiles(input) {
|
|
2064
|
+
return this.api.readFiles(input);
|
|
2065
|
+
}
|
|
2066
|
+
};
|
|
2067
|
+
/**
|
|
2068
|
+
* Opens an authenticated Cap'n Web session to Studio over a WebSocket. Rejects an unencrypted URL
|
|
2069
|
+
* before opening the socket, so a bearer token never reaches a plaintext host.
|
|
2070
|
+
*
|
|
2071
|
+
* @example
|
|
2072
|
+
* ```ts
|
|
2073
|
+
* const rpc = await connectWebSocketRpc({ url: 'wss://studio.kubb.dev/s/1', token, local: session })
|
|
2074
|
+
* await rpc.studio.ping()
|
|
2075
|
+
* ```
|
|
2076
|
+
*/
|
|
2077
|
+
const connectWebSocketRpc = async ({ url, token, local }) => {
|
|
2078
|
+
const { protocol, hostname, host } = new URL(url);
|
|
2079
|
+
if (protocol !== "wss:" && !(protocol === "ws:" && (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]"))) throw new Error(`Refusing unencrypted WebSocket to ${host}`);
|
|
2080
|
+
const socket = createWebsocket(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
2081
|
+
const closed = new Promise((resolve) => socket.once("close", resolve));
|
|
2082
|
+
const studio = (0, capnweb.newWebSocketRpcSession)(socket, new AgentRpcTarget(local));
|
|
2083
|
+
studio.onRpcBroken(() => socket.close());
|
|
2084
|
+
return {
|
|
2085
|
+
studio,
|
|
2086
|
+
closed,
|
|
2087
|
+
close: () => studio[Symbol.dispose]()
|
|
2088
|
+
};
|
|
2089
|
+
};
|
|
2090
|
+
//#endregion
|
|
2091
|
+
//#region src/StudioSession.ts
|
|
2034
2092
|
/**
|
|
2035
|
-
* How
|
|
2036
|
-
* on for this open. A Studio that predates the ack never sends one, so this only ever produces a
|
|
2037
|
-
* warning, not a reconnect.
|
|
2093
|
+
* How many files are read from storage at once when serving `readFiles` or packing a snapshot.
|
|
2038
2094
|
*/
|
|
2039
|
-
const
|
|
2095
|
+
const FILE_READ_CONCURRENCY = 50;
|
|
2096
|
+
var GenerationRunTarget = class extends capnweb.RpcTarget {
|
|
2097
|
+
generationStream;
|
|
2098
|
+
generationResult;
|
|
2099
|
+
cancelGeneration;
|
|
2100
|
+
stopGeneration;
|
|
2101
|
+
constructor(generationStream, generationResult, cancelGeneration, stopGeneration) {
|
|
2102
|
+
super();
|
|
2103
|
+
this.generationStream = generationStream;
|
|
2104
|
+
this.generationResult = generationResult;
|
|
2105
|
+
this.cancelGeneration = cancelGeneration;
|
|
2106
|
+
this.stopGeneration = stopGeneration;
|
|
2107
|
+
}
|
|
2108
|
+
async events() {
|
|
2109
|
+
return this.generationStream;
|
|
2110
|
+
}
|
|
2111
|
+
result() {
|
|
2112
|
+
return this.generationResult;
|
|
2113
|
+
}
|
|
2114
|
+
cancel() {
|
|
2115
|
+
return this.cancelGeneration();
|
|
2116
|
+
}
|
|
2117
|
+
[Symbol.dispose]() {
|
|
2118
|
+
this.stopGeneration();
|
|
2119
|
+
}
|
|
2120
|
+
};
|
|
2040
2121
|
/**
|
|
2041
2122
|
* Fills in a host's options: the hosted Studio URL, the current working directory, and every
|
|
2042
2123
|
* permission off unless granted. Idempotent, so a reconnect can pass an already-resolved bag
|
|
@@ -2076,7 +2157,7 @@ function reconnect(options) {
|
|
|
2076
2157
|
const timer = setTimeout(() => {
|
|
2077
2158
|
signal?.removeEventListener("abort", cancel);
|
|
2078
2159
|
if (signal?.aborted) return;
|
|
2079
|
-
new StudioSession(options).
|
|
2160
|
+
new StudioSession(options).start().catch((error) => {
|
|
2080
2161
|
if (logLevel !== void 0 && logLevel > _kubb_core.logLevel.silent) console.error((0, node_util.styleText)("red", `Reconnect attempt to Kubb Studio failed: ${getErrorMessage(error)}`));
|
|
2081
2162
|
if (error instanceof InvalidAgentTokenError) {
|
|
2082
2163
|
onTokenRejected?.(error);
|
|
@@ -2088,8 +2169,8 @@ function reconnect(options) {
|
|
|
2088
2169
|
signal?.addEventListener("abort", cancel, { once: true });
|
|
2089
2170
|
}
|
|
2090
2171
|
/**
|
|
2091
|
-
* One
|
|
2092
|
-
*
|
|
2172
|
+
* One agent-to-Studio RPC transport: opening it, keeping it alive, and serving remote methods.
|
|
2173
|
+
* `createClient` opens one per pool slot and is the only caller.
|
|
2093
2174
|
*/
|
|
2094
2175
|
var StudioSession = class {
|
|
2095
2176
|
#options;
|
|
@@ -2104,17 +2185,20 @@ var StudioSession = class {
|
|
|
2104
2185
|
* Before it resolves there is nothing to disconnect and no sandbox flag to read.
|
|
2105
2186
|
*/
|
|
2106
2187
|
#session;
|
|
2107
|
-
#
|
|
2188
|
+
#rpc;
|
|
2108
2189
|
#studioVersion;
|
|
2109
|
-
#activeJobId = null;
|
|
2110
2190
|
#disposed = false;
|
|
2111
2191
|
#isGenerating = false;
|
|
2112
2192
|
#heartbeatTimer;
|
|
2113
|
-
#readyTimer;
|
|
2114
|
-
#lastPongAt = Date.now();
|
|
2115
2193
|
#lastGeneration;
|
|
2194
|
+
/**
|
|
2195
|
+
* Resolves when Studio calls {@link StudioSession.connect}. `studio:ready` waits on this so the
|
|
2196
|
+
* host does not queue jobs before the agent session is registered.
|
|
2197
|
+
*/
|
|
2198
|
+
#connectAck = Promise.withResolvers();
|
|
2116
2199
|
constructor(options) {
|
|
2117
2200
|
this.#options = applyStudioDefaults(options);
|
|
2201
|
+
this.#connectAck.promise.catch(() => {});
|
|
2118
2202
|
}
|
|
2119
2203
|
/**
|
|
2120
2204
|
* A sandbox agent runs on Studio's own infrastructure, so it has no user project to touch.
|
|
@@ -2141,7 +2225,7 @@ var StudioSession = class {
|
|
|
2141
2225
|
get #canRead() {
|
|
2142
2226
|
return this.#isSandbox || this.#options.permissions.allowRead;
|
|
2143
2227
|
}
|
|
2144
|
-
async
|
|
2228
|
+
async start() {
|
|
2145
2229
|
const { token, studioUrl, signal, heartbeatInterval, installLogger } = this.#options;
|
|
2146
2230
|
await installLogger?.(this.#hooks);
|
|
2147
2231
|
try {
|
|
@@ -2152,48 +2236,65 @@ var StudioSession = class {
|
|
|
2152
2236
|
});
|
|
2153
2237
|
this.#session = session;
|
|
2154
2238
|
this.#studioVersion = session.version;
|
|
2155
|
-
const
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
this.#
|
|
2239
|
+
const rpc = await (this.#options.connector ?? connectWebSocketRpc)({
|
|
2240
|
+
url: session.url,
|
|
2241
|
+
token,
|
|
2242
|
+
local: this
|
|
2243
|
+
});
|
|
2244
|
+
this.#rpc = rpc;
|
|
2245
|
+
rpc.closed.then(this.#onClose);
|
|
2161
2246
|
signal?.addEventListener("abort", this.#onAbort, { once: true });
|
|
2162
2247
|
this.#unhooks.push(() => signal?.removeEventListener("abort", this.#onAbort));
|
|
2163
|
-
this.#
|
|
2164
|
-
this.#
|
|
2248
|
+
this.#scheduleHeartbeat(heartbeatInterval);
|
|
2249
|
+
await this.#hooks.callHook("studio:connected", {
|
|
2250
|
+
url: studioUrl,
|
|
2251
|
+
versions: {
|
|
2252
|
+
studio: this.#studioVersion,
|
|
2253
|
+
kubb: version,
|
|
2254
|
+
agent: this.#options.version
|
|
2255
|
+
}
|
|
2256
|
+
});
|
|
2257
|
+
await this.#connectAck.promise;
|
|
2258
|
+
await this.#hooks.callHook("studio:ready", {});
|
|
2165
2259
|
} catch (error) {
|
|
2260
|
+
this.#disposed = true;
|
|
2261
|
+
this.dispose();
|
|
2166
2262
|
await this.#hooks.callHook("studio:error", { error: toError(error) });
|
|
2167
2263
|
if (error instanceof InvalidAgentTokenError) throw error;
|
|
2168
2264
|
reconnect(this.#options);
|
|
2169
2265
|
}
|
|
2170
2266
|
}
|
|
2171
|
-
/**
|
|
2172
|
-
* Adds a socket listener and tracks its remover, so `dispose` detaches every listener at once.
|
|
2173
|
-
*/
|
|
2174
|
-
#listen(ws, event, listener) {
|
|
2175
|
-
ws.addEventListener(event, listener);
|
|
2176
|
-
this.#unhooks.push(() => ws.removeEventListener(event, listener));
|
|
2177
|
-
}
|
|
2178
2267
|
#warn(message) {
|
|
2179
2268
|
return this.#hooks.callHook("studio:warn", { message });
|
|
2180
2269
|
}
|
|
2181
2270
|
/**
|
|
2182
|
-
*
|
|
2183
|
-
*
|
|
2271
|
+
* Declines a request: logs why locally, then tells Studio. The two wordings differ on purpose,
|
|
2272
|
+
* since the log names the request that was ignored and the error names what the caller can do.
|
|
2184
2273
|
*/
|
|
2185
|
-
#
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2274
|
+
async #refuse(reason, message) {
|
|
2275
|
+
await this.#warn(reason);
|
|
2276
|
+
throw new Error(message);
|
|
2277
|
+
}
|
|
2278
|
+
#scheduleHeartbeat(interval) {
|
|
2279
|
+
const rpc = this.#rpc;
|
|
2280
|
+
if (!rpc) return;
|
|
2281
|
+
this.#heartbeatTimer = setTimeout(async () => {
|
|
2282
|
+
try {
|
|
2283
|
+
await this.#ping(rpc);
|
|
2284
|
+
} catch {
|
|
2285
|
+
if (this.#rpc === rpc) rpc.close();
|
|
2286
|
+
return;
|
|
2287
|
+
}
|
|
2288
|
+
if (this.#rpc === rpc) this.#scheduleHeartbeat(interval);
|
|
2289
|
+
}, interval);
|
|
2290
|
+
}
|
|
2291
|
+
/**
|
|
2292
|
+
* Races `studio.ping()` against a deadline, so a half-open socket can't hang it forever.
|
|
2293
|
+
* */
|
|
2294
|
+
#ping(rpc) {
|
|
2295
|
+
const { promise: timedOut, reject: onTimeout } = Promise.withResolvers();
|
|
2296
|
+
const timer = setTimeout(() => onTimeout(/* @__PURE__ */ new Error("Heartbeat ping timed out")), agentDefaults.heartbeatTimeoutMs);
|
|
2297
|
+
return Promise.race([rpc.studio.ping(), timedOut]).finally(() => clearTimeout(timer));
|
|
2197
2298
|
}
|
|
2198
2299
|
/**
|
|
2199
2300
|
* Reads `kubb.config.ts` and reports which plugin options Studio may edit.
|
|
@@ -2210,90 +2311,48 @@ var StudioSession = class {
|
|
|
2210
2311
|
return;
|
|
2211
2312
|
}
|
|
2212
2313
|
}
|
|
2213
|
-
async
|
|
2314
|
+
async connect() {
|
|
2214
2315
|
const { configPath, root, version: version$1, loadConfig, permissions } = this.#options;
|
|
2215
|
-
|
|
2216
|
-
const
|
|
2217
|
-
sendAgentMessage(this.#ws, {
|
|
2218
|
-
type: "agent:connect",
|
|
2219
|
-
payload: {
|
|
2220
|
-
versions: {
|
|
2221
|
-
kubb: version,
|
|
2222
|
-
agent: version$1
|
|
2223
|
-
},
|
|
2224
|
-
root,
|
|
2225
|
-
config: {
|
|
2226
|
-
path: configPath,
|
|
2227
|
-
file: await this.#readConfigFileView(),
|
|
2228
|
-
plugins: config.plugins.map((plugin) => ({
|
|
2229
|
-
name: toPackageName(plugin.name),
|
|
2230
|
-
options: plugin.options ?? {}
|
|
2231
|
-
}))
|
|
2232
|
-
},
|
|
2233
|
-
permissions: {
|
|
2234
|
-
...permissions,
|
|
2235
|
-
allowWrite: this.#canWrite,
|
|
2236
|
-
allowInput: this.#canUseInput,
|
|
2237
|
-
allowConfigEdit: this.#canEditConfig,
|
|
2238
|
-
allowRead: this.#canRead
|
|
2239
|
-
}
|
|
2240
|
-
}
|
|
2241
|
-
});
|
|
2242
|
-
}
|
|
2243
|
-
async #handleOpen() {
|
|
2244
|
-
this.#lastPongAt = Date.now();
|
|
2245
|
-
await this.#hooks.callHook("studio:connected", {
|
|
2246
|
-
url: this.#options.studioUrl,
|
|
2316
|
+
const [config, file] = await Promise.all([loadConfig(), this.#readConfigFileView()]);
|
|
2317
|
+
const payload = {
|
|
2247
2318
|
versions: {
|
|
2248
|
-
studio: this.#studioVersion,
|
|
2249
2319
|
kubb: version,
|
|
2250
|
-
agent:
|
|
2320
|
+
agent: version$1
|
|
2321
|
+
},
|
|
2322
|
+
root,
|
|
2323
|
+
config: {
|
|
2324
|
+
path: configPath,
|
|
2325
|
+
file,
|
|
2326
|
+
plugins: config.plugins.map((plugin) => ({
|
|
2327
|
+
name: toPackageName(plugin.name),
|
|
2328
|
+
options: plugin.options ?? {}
|
|
2329
|
+
}))
|
|
2330
|
+
},
|
|
2331
|
+
permissions: {
|
|
2332
|
+
...permissions,
|
|
2333
|
+
allowWrite: this.#canWrite,
|
|
2334
|
+
allowInput: this.#canUseInput,
|
|
2335
|
+
allowConfigEdit: this.#canEditConfig,
|
|
2336
|
+
allowRead: this.#canRead
|
|
2251
2337
|
}
|
|
2252
|
-
}
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
this.#waitForReady();
|
|
2256
|
-
} catch (error) {
|
|
2257
|
-
await this.#warn(`Failed to send the connect payload: ${getErrorMessage(error)}`);
|
|
2258
|
-
}
|
|
2338
|
+
};
|
|
2339
|
+
this.#connectAck.resolve();
|
|
2340
|
+
return payload;
|
|
2259
2341
|
}
|
|
2260
|
-
|
|
2261
|
-
* Arms the timeout for Studio's `studio:ready` acknowledgement. Re-armed on every open, since a
|
|
2262
|
-
* command sent while the agent is not attached is lost either way (see `#sendConnectedPayload`),
|
|
2263
|
-
* so each reconnect needs its own fresh wait.
|
|
2264
|
-
*/
|
|
2265
|
-
#waitForReady() {
|
|
2266
|
-
if (this.#disposed) return;
|
|
2267
|
-
clearTimeout(this.#readyTimer);
|
|
2268
|
-
this.#readyTimer = setTimeout(() => {
|
|
2269
|
-
this.#readyTimer = void 0;
|
|
2270
|
-
Promise.resolve(this.#warn(`Kubb Studio did not confirm the connection was ready within ${READY_TIMEOUT_MS}ms`)).catch(() => {});
|
|
2271
|
-
}, READY_TIMEOUT_MS);
|
|
2272
|
-
}
|
|
2273
|
-
#onOpen = () => void this.#handleOpen().catch(() => {});
|
|
2274
|
-
#onAbort = () => void this.#end({
|
|
2275
|
-
reason: "shutdown",
|
|
2276
|
-
retry: false
|
|
2277
|
-
});
|
|
2342
|
+
#onAbort = () => void this.#end({ retry: false });
|
|
2278
2343
|
#onClose = () => void this.#end({ retry: true });
|
|
2279
|
-
#onError = () => {
|
|
2280
|
-
this.#hooks.callHook("studio:error", { error: /* @__PURE__ */ new Error("Failed to connect to Kubb Studio") });
|
|
2281
|
-
this.#onClose();
|
|
2282
|
-
};
|
|
2283
2344
|
/**
|
|
2284
2345
|
* Drops the socket and detaches every listener and timer this session added. Idempotent, and
|
|
2285
2346
|
* safe before `connect` opened anything.
|
|
2286
2347
|
*
|
|
2287
2348
|
* @internal
|
|
2288
2349
|
*/
|
|
2289
|
-
dispose(
|
|
2290
|
-
|
|
2350
|
+
dispose() {
|
|
2351
|
+
clearTimeout(this.#heartbeatTimer);
|
|
2291
2352
|
this.#heartbeatTimer = void 0;
|
|
2292
|
-
|
|
2293
|
-
this.#
|
|
2294
|
-
|
|
2295
|
-
this.#ws?.close(1e3, reason);
|
|
2296
|
-
} catch {}
|
|
2353
|
+
this.#rpc?.close();
|
|
2354
|
+
this.#rpc = void 0;
|
|
2355
|
+
this.#connectAck.reject(/* @__PURE__ */ new Error("Session ended before Studio called connect()"));
|
|
2297
2356
|
for (const unhook of this.#unhooks) unhook();
|
|
2298
2357
|
this.#unhooks.length = 0;
|
|
2299
2358
|
}
|
|
@@ -2301,15 +2360,12 @@ var StudioSession = class {
|
|
|
2301
2360
|
* Ends the session: tells Studio it is over, drops the socket, and optionally reconnects.
|
|
2302
2361
|
* `#disposed` keeps the close event from running this twice, and a shutdown from reconnecting.
|
|
2303
2362
|
*/
|
|
2304
|
-
async #end({
|
|
2363
|
+
async #end({ retry }) {
|
|
2305
2364
|
const { studioUrl, token, logLevel } = this.#options;
|
|
2306
2365
|
if (this.#disposed) return;
|
|
2307
2366
|
this.#disposed = true;
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
reason: "shutdown"
|
|
2311
|
-
});
|
|
2312
|
-
this.dispose(reason);
|
|
2367
|
+
this.dispose();
|
|
2368
|
+
await this.#hooks.callHook("studio:disconnected", { reason: retry ? "connection closed" : "shutdown" });
|
|
2313
2369
|
if (this.#session) await disconnect({
|
|
2314
2370
|
sessionId: this.#session.sessionId,
|
|
2315
2371
|
studioUrl,
|
|
@@ -2319,81 +2375,35 @@ var StudioSession = class {
|
|
|
2319
2375
|
}).catch(() => {});
|
|
2320
2376
|
if (retry) reconnect(this.#options);
|
|
2321
2377
|
}
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
return;
|
|
2342
|
-
}
|
|
2343
|
-
await this.#warn(`Ignored an unknown message from Kubb Studio: ${data.type}`);
|
|
2344
|
-
} catch (error) {
|
|
2345
|
-
await this.#hooks.callHook("studio:error", { error: toError(error) });
|
|
2346
|
-
await this.#emitError(toError(error));
|
|
2347
|
-
}
|
|
2348
|
-
};
|
|
2349
|
-
/**
|
|
2350
|
-
* Studio ended the session itself. A revoked one stays ended, an expired one gets a fresh
|
|
2351
|
-
* session, and anything else is left to the socket's own close event.
|
|
2352
|
-
*/
|
|
2353
|
-
async #handleDisconnect(reason) {
|
|
2354
|
-
await this.#hooks.callHook("studio:disconnected", { reason });
|
|
2355
|
-
if (reason !== "revoked" && reason !== "expired") return;
|
|
2356
|
-
this.#disposed = true;
|
|
2357
|
-
this.dispose(`session_${reason}`);
|
|
2358
|
-
if (reason === "expired") reconnect(this.#options);
|
|
2359
|
-
}
|
|
2360
|
-
async #handleCommand(data) {
|
|
2361
|
-
const ws = this.#ws;
|
|
2362
|
-
if (!ws) return;
|
|
2363
|
-
const command = data.type.slice(7);
|
|
2364
|
-
await this.#hooks.callHook("studio:command:start", { command });
|
|
2365
|
-
switch (data.type) {
|
|
2366
|
-
case "studio:generate":
|
|
2367
|
-
await this.#handleGenerate(ws, data, command);
|
|
2368
|
-
return;
|
|
2369
|
-
case "studio:connect":
|
|
2370
|
-
this.#studioVersion = data.version ?? this.#studioVersion;
|
|
2371
|
-
await this.#sendConnectedPayload();
|
|
2372
|
-
await this.#hooks.callHook("studio:command:end", { command });
|
|
2373
|
-
return;
|
|
2374
|
-
case "studio:save":
|
|
2375
|
-
await this.#handleSave(ws, data, command);
|
|
2376
|
-
return;
|
|
2377
|
-
case "studio:snapshot":
|
|
2378
|
-
await this.#handleSnapshot(ws, data, command);
|
|
2379
|
-
return;
|
|
2380
|
-
case "studio:files":
|
|
2381
|
-
await this.#handleFiles(ws, data, command);
|
|
2382
|
-
return;
|
|
2383
|
-
}
|
|
2378
|
+
startGeneration(data) {
|
|
2379
|
+
const generationStream = createGenerationStream(this.#hooks, data.jobId, { onGenerationEnd: (result) => {
|
|
2380
|
+
this.#lastGeneration = result;
|
|
2381
|
+
} });
|
|
2382
|
+
const controller = new AbortController();
|
|
2383
|
+
const result = this.#runGeneration(data, controller).then(async (value) => {
|
|
2384
|
+
await generationStream.close();
|
|
2385
|
+
return value;
|
|
2386
|
+
}).catch((error) => {
|
|
2387
|
+
generationStream.fail(error);
|
|
2388
|
+
throw error;
|
|
2389
|
+
});
|
|
2390
|
+
result.catch(() => {});
|
|
2391
|
+
return new GenerationRunTarget(generationStream.stream, result, async () => {
|
|
2392
|
+
controller.abort(/* @__PURE__ */ new Error("Generation canceled"));
|
|
2393
|
+
}, () => {
|
|
2394
|
+
controller.abort(/* @__PURE__ */ new Error("Generation canceled"));
|
|
2395
|
+
generationStream.dispose();
|
|
2396
|
+
});
|
|
2384
2397
|
}
|
|
2385
|
-
async #
|
|
2386
|
-
|
|
2387
|
-
if (this.#isGenerating) {
|
|
2388
|
-
await this.#warn("Ignored generate: a generation is already in progress");
|
|
2389
|
-
await this.#emitError(/* @__PURE__ */ new Error("A generation is already in progress, please wait for it to finish"));
|
|
2390
|
-
return;
|
|
2391
|
-
}
|
|
2398
|
+
async #runGeneration(data, controller) {
|
|
2399
|
+
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");
|
|
2392
2400
|
this.#isGenerating = true;
|
|
2393
|
-
|
|
2401
|
+
const command = "generate";
|
|
2402
|
+
const { root, loadConfig, permissions, client } = this.#options;
|
|
2394
2403
|
try {
|
|
2404
|
+
await this.#hooks.callHook("studio:command:start", { command });
|
|
2395
2405
|
const config = await loadConfig();
|
|
2396
|
-
const patch = data.
|
|
2406
|
+
const patch = data.config;
|
|
2397
2407
|
const plugins = await mergePlugins(config.plugins, patch?.plugins);
|
|
2398
2408
|
const adapter = await mergeAdapter(config.adapter, patch?.adapter);
|
|
2399
2409
|
const inputOverride = this.#isSandbox ? patch?.input ?? "" : permissions.allowInput && patch?.input || void 0;
|
|
@@ -2404,9 +2414,7 @@ var StudioSession = class {
|
|
|
2404
2414
|
}
|
|
2405
2415
|
const resolvedPlugins = plugins ?? config.plugins;
|
|
2406
2416
|
this.#lastGeneration = void 0;
|
|
2407
|
-
const detach = [setupHookListener(this.#hooks, root
|
|
2408
|
-
this.#lastGeneration = result;
|
|
2409
|
-
} })];
|
|
2417
|
+
const detach = [setupHookListener(this.#hooks, root, controller.signal)];
|
|
2410
2418
|
try {
|
|
2411
2419
|
await generate({
|
|
2412
2420
|
config: {
|
|
@@ -2423,7 +2431,8 @@ var StudioSession = class {
|
|
|
2423
2431
|
plugins: resolvedPlugins,
|
|
2424
2432
|
adapter
|
|
2425
2433
|
},
|
|
2426
|
-
hooks: this.#hooks
|
|
2434
|
+
hooks: this.#hooks,
|
|
2435
|
+
signal: controller.signal
|
|
2427
2436
|
});
|
|
2428
2437
|
} finally {
|
|
2429
2438
|
for (const remove of detach) remove();
|
|
@@ -2432,102 +2441,70 @@ var StudioSession = class {
|
|
|
2432
2441
|
command,
|
|
2433
2442
|
info: `${resolvedPlugins.length} plugin${resolvedPlugins.length === 1 ? "" : "s"}, ${this.#canWrite ? "written to disk" : "in memory"}${inputOverride !== void 0 ? ", from a Studio spec" : ""}`
|
|
2434
2443
|
});
|
|
2444
|
+
const files = [...this.#lastGeneration?.paths ?? []];
|
|
2445
|
+
return {
|
|
2446
|
+
status: "success",
|
|
2447
|
+
files,
|
|
2448
|
+
fileCount: files.length
|
|
2449
|
+
};
|
|
2435
2450
|
} finally {
|
|
2436
2451
|
this.#isGenerating = false;
|
|
2437
|
-
this.#activeJobId = null;
|
|
2438
2452
|
}
|
|
2439
2453
|
}
|
|
2440
|
-
async
|
|
2454
|
+
async saveConfig(data) {
|
|
2455
|
+
const command = "saveConfig";
|
|
2456
|
+
await this.#hooks.callHook("studio:command:start", { command });
|
|
2441
2457
|
const { configPath, configFile } = this.#options;
|
|
2442
2458
|
if (!Array.isArray(data.edits)) {
|
|
2443
2459
|
await this.#warn("Ignored save: the message carried no edits");
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
outcomes: [],
|
|
2449
|
-
changed: false
|
|
2450
|
-
}
|
|
2451
|
-
});
|
|
2452
|
-
return;
|
|
2460
|
+
return {
|
|
2461
|
+
outcomes: [],
|
|
2462
|
+
changed: false
|
|
2463
|
+
};
|
|
2453
2464
|
}
|
|
2454
2465
|
const edits = data.edits;
|
|
2455
|
-
const refuse = (reason) =>
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
reason
|
|
2463
|
-
})),
|
|
2464
|
-
changed: false
|
|
2465
|
-
}
|
|
2466
|
+
const refuse = (reason) => ({
|
|
2467
|
+
outcomes: edits.map((edit) => ({
|
|
2468
|
+
edit,
|
|
2469
|
+
applied: false,
|
|
2470
|
+
reason
|
|
2471
|
+
})),
|
|
2472
|
+
changed: false
|
|
2466
2473
|
});
|
|
2467
2474
|
if (!this.#canEditConfig) {
|
|
2468
2475
|
await this.#warn("Ignored save: editing kubb.config.ts was not granted");
|
|
2469
|
-
refuse("the agent was not granted permission to edit kubb.config.ts");
|
|
2470
|
-
return;
|
|
2471
|
-
}
|
|
2472
|
-
if (this.#isGenerating) {
|
|
2473
|
-
refuse("a generation is in progress");
|
|
2474
|
-
return;
|
|
2476
|
+
return refuse("the agent was not granted permission to edit kubb.config.ts");
|
|
2475
2477
|
}
|
|
2478
|
+
if (this.#isGenerating) return refuse("a generation is in progress");
|
|
2476
2479
|
try {
|
|
2477
2480
|
const { source: patched, outcomes, changed } = applyConfigEdits(await read(configFile), edits);
|
|
2478
2481
|
if (changed) await (0, node_fs_promises.writeFile)(configFile, patched, "utf-8");
|
|
2479
|
-
sendAgentMessage(ws, {
|
|
2480
|
-
type: "agent:save",
|
|
2481
|
-
jobId: data.jobId,
|
|
2482
|
-
payload: {
|
|
2483
|
-
outcomes,
|
|
2484
|
-
changed,
|
|
2485
|
-
file: changed ? await this.#readConfigFileView(patched) : void 0
|
|
2486
|
-
}
|
|
2487
|
-
});
|
|
2488
2482
|
const applied = outcomes.filter((outcome) => outcome.applied).length;
|
|
2489
2483
|
await this.#hooks.callHook("studio:command:end", {
|
|
2490
2484
|
command,
|
|
2491
2485
|
info: `applied ${applied}/${outcomes.length} edits to ${configPath}`
|
|
2492
2486
|
});
|
|
2487
|
+
return {
|
|
2488
|
+
outcomes,
|
|
2489
|
+
changed,
|
|
2490
|
+
file: changed ? await this.#readConfigFileView(patched) : void 0
|
|
2491
|
+
};
|
|
2493
2492
|
} catch (error) {
|
|
2494
2493
|
await this.#hooks.callHook("studio:error", { error: toError(error) });
|
|
2495
|
-
refuse(getErrorMessage(error));
|
|
2494
|
+
return refuse(getErrorMessage(error));
|
|
2496
2495
|
}
|
|
2497
2496
|
}
|
|
2498
|
-
async
|
|
2499
|
-
const
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
message
|
|
2505
|
-
}
|
|
2506
|
-
});
|
|
2507
|
-
if (this.#isSandbox) {
|
|
2508
|
-
await this.#warn("Ignored snapshot: a sandbox agent has no project to build a package from");
|
|
2509
|
-
refuse("a sandbox agent has no project to build a package from");
|
|
2510
|
-
return;
|
|
2511
|
-
}
|
|
2512
|
-
const { name, version, bundledDependencies, uploadPath } = data.payload;
|
|
2513
|
-
if (!name || !version || !uploadPath) {
|
|
2514
|
-
await this.#warn("Ignored snapshot: the message was missing required fields");
|
|
2515
|
-
refuse("the message was missing required fields");
|
|
2516
|
-
return;
|
|
2517
|
-
}
|
|
2497
|
+
async publishSnapshot(data) {
|
|
2498
|
+
const command = "snapshot";
|
|
2499
|
+
await this.#hooks.callHook("studio:command:start", { command });
|
|
2500
|
+
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");
|
|
2501
|
+
const { name, version, bundledDependencies, uploadPath } = data;
|
|
2502
|
+
if (!name || !version || !uploadPath) return this.#refuse("Ignored snapshot: the message was missing required fields", "The request was missing required fields");
|
|
2518
2503
|
const generation = this.#lastGeneration;
|
|
2519
|
-
if (!generation)
|
|
2520
|
-
await this.#warn("Ignored snapshot: no prior generation to pack");
|
|
2521
|
-
refuse("no prior generation exists to pack, run a generation first");
|
|
2522
|
-
return;
|
|
2523
|
-
}
|
|
2504
|
+
if (!generation) return this.#refuse("Ignored snapshot: no prior generation to pack", "No prior generation exists to pack, run a generation first");
|
|
2524
2505
|
const bundled = new Set(bundledDependencies ?? []);
|
|
2525
2506
|
const missing = generation.missingDependencies.filter((dependency) => !bundled.has(dependency));
|
|
2526
|
-
if (missing.length) {
|
|
2527
|
-
await this.#warn(`Ignored snapshot: missing dependencies: ${missing.join(", ")}`);
|
|
2528
|
-
refuse(`missing dependencies: ${missing.join(", ")}`);
|
|
2529
|
-
return;
|
|
2530
|
-
}
|
|
2507
|
+
if (missing.length) return this.#refuse(`Ignored snapshot: missing dependencies: ${missing.join(", ")}`, `Missing dependencies: ${missing.join(", ")}`);
|
|
2531
2508
|
try {
|
|
2532
2509
|
const files = {};
|
|
2533
2510
|
await inParallel({
|
|
@@ -2544,68 +2521,50 @@ var StudioSession = class {
|
|
|
2544
2521
|
peerDependencies: generation.peerDependencies
|
|
2545
2522
|
});
|
|
2546
2523
|
const { token, studioUrl } = this.#options;
|
|
2547
|
-
const
|
|
2524
|
+
const uploadUrl = new URL(uploadPath, studioUrl);
|
|
2525
|
+
if (uploadUrl.origin !== new URL(studioUrl).origin) throw new Error("Snapshot upload path must stay on the Studio origin");
|
|
2526
|
+
const redirect = await fetch(uploadUrl, {
|
|
2548
2527
|
method: "PUT",
|
|
2549
2528
|
headers: { Authorization: `Bearer ${token}` },
|
|
2550
2529
|
redirect: "manual"
|
|
2551
2530
|
});
|
|
2552
2531
|
const storageUrl = redirect.headers.get("location");
|
|
2553
2532
|
if (redirect.status !== 307 || !storageUrl) throw new Error(`Studio did not provide a storage URL (status ${redirect.status})`);
|
|
2554
|
-
const
|
|
2533
|
+
const storage = new URL(storageUrl);
|
|
2534
|
+
if (storage.protocol !== "https:" && storage.hostname !== "localhost" && storage.hostname !== "127.0.0.1") throw new Error(`Refusing snapshot upload to ${storage.origin}`);
|
|
2535
|
+
const response = await fetch(storage, {
|
|
2555
2536
|
method: "PUT",
|
|
2556
|
-
body: new Uint8Array(bytes)
|
|
2537
|
+
body: new Uint8Array(bytes),
|
|
2538
|
+
redirect: "error"
|
|
2557
2539
|
});
|
|
2558
2540
|
if (!response.ok) throw new Error(`Snapshot upload failed with status ${response.status}`);
|
|
2559
|
-
sendAgentMessage(ws, {
|
|
2560
|
-
type: "agent:snapshot",
|
|
2561
|
-
jobId: data.jobId,
|
|
2562
|
-
payload: {
|
|
2563
|
-
status: "ok",
|
|
2564
|
-
integrity,
|
|
2565
|
-
peerDependencies: generation.peerDependencies
|
|
2566
|
-
}
|
|
2567
|
-
});
|
|
2568
2541
|
await this.#hooks.callHook("studio:command:end", {
|
|
2569
2542
|
command,
|
|
2570
2543
|
info: `packed ${Object.keys(files).length} file${Object.keys(files).length === 1 ? "" : "s"}`
|
|
2571
2544
|
});
|
|
2545
|
+
return {
|
|
2546
|
+
integrity,
|
|
2547
|
+
peerDependencies: generation.peerDependencies
|
|
2548
|
+
};
|
|
2572
2549
|
} catch (error) {
|
|
2573
2550
|
await this.#hooks.callHook("studio:error", { error: toError(error) });
|
|
2574
|
-
|
|
2551
|
+
throw error;
|
|
2575
2552
|
}
|
|
2576
2553
|
}
|
|
2577
|
-
async
|
|
2554
|
+
async readFiles(data) {
|
|
2555
|
+
const command = "readFiles";
|
|
2556
|
+
await this.#hooks.callHook("studio:command:start", { command });
|
|
2578
2557
|
const { client } = this.#options;
|
|
2579
|
-
const refuse = (message) => sendAgentMessage(ws, {
|
|
2580
|
-
type: "agent:files",
|
|
2581
|
-
jobId: data.jobId,
|
|
2582
|
-
payload: {
|
|
2583
|
-
status: "error",
|
|
2584
|
-
message
|
|
2585
|
-
}
|
|
2586
|
-
});
|
|
2587
2558
|
if (!this.#canRead) {
|
|
2588
2559
|
await this.#warn("Ignored files: reading generated files was not granted");
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
}
|
|
2592
|
-
if (!Array.isArray(data.payload?.paths)) {
|
|
2593
|
-
await this.#warn("Ignored files: the message carried no paths");
|
|
2594
|
-
refuse("the message carried no paths");
|
|
2595
|
-
return;
|
|
2596
|
-
}
|
|
2597
|
-
const { paths } = data.payload;
|
|
2598
|
-
if (paths.length > 50) {
|
|
2599
|
-
await this.#warn(`Ignored files: requested ${paths.length} paths, more than the 50 allowed per request`);
|
|
2600
|
-
refuse(`at most 50 paths may be requested at once`);
|
|
2601
|
-
return;
|
|
2560
|
+
const remedy = client?.kind === "cli" ? "--allow-read, or answer yes when kubb studio asks," : "KUBB_AGENT_ALLOW_READ=true";
|
|
2561
|
+
throw new Error(`The agent was not granted permission to read generated files; set ${remedy} to allow it`);
|
|
2602
2562
|
}
|
|
2563
|
+
if (!Array.isArray(data.paths)) return this.#refuse("Ignored files: the message carried no paths", "The request carried no paths");
|
|
2564
|
+
const { paths } = data;
|
|
2565
|
+
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`);
|
|
2603
2566
|
const generation = this.#lastGeneration;
|
|
2604
|
-
if (!generation)
|
|
2605
|
-
await this.#warn("Ignored files: no prior generation to read from");
|
|
2606
|
-
refuse("no prior generation to read from, run a generation first");
|
|
2607
|
-
return;
|
|
2608
|
-
}
|
|
2567
|
+
if (!generation) return this.#refuse("Ignored files: no prior generation to read from", "No prior generation to read from, run a generation first");
|
|
2609
2568
|
const requested = paths.filter((path) => generation.paths.has(path));
|
|
2610
2569
|
const files = {};
|
|
2611
2570
|
await inParallel({
|
|
@@ -2616,18 +2575,11 @@ var StudioSession = class {
|
|
|
2616
2575
|
if (content !== null) files[path] = content;
|
|
2617
2576
|
}
|
|
2618
2577
|
});
|
|
2619
|
-
sendAgentMessage(ws, {
|
|
2620
|
-
type: "agent:files",
|
|
2621
|
-
jobId: data.jobId,
|
|
2622
|
-
payload: {
|
|
2623
|
-
status: "ok",
|
|
2624
|
-
files
|
|
2625
|
-
}
|
|
2626
|
-
});
|
|
2627
2578
|
await this.#hooks.callHook("studio:command:end", {
|
|
2628
2579
|
command,
|
|
2629
2580
|
info: `read ${Object.keys(files).length}/${paths.length} requested file${paths.length === 1 ? "" : "s"}`
|
|
2630
2581
|
});
|
|
2582
|
+
return { files };
|
|
2631
2583
|
}
|
|
2632
2584
|
};
|
|
2633
2585
|
//#endregion
|
|
@@ -2664,7 +2616,7 @@ function createClient({ storage, onAuthRequired, ...options }) {
|
|
|
2664
2616
|
...options,
|
|
2665
2617
|
signal: controller.signal,
|
|
2666
2618
|
onTokenRejected: notifyAuthRequired
|
|
2667
|
-
}).
|
|
2619
|
+
}).start()));
|
|
2668
2620
|
},
|
|
2669
2621
|
disconnect() {
|
|
2670
2622
|
controller.abort();
|
|
@@ -2836,12 +2788,13 @@ async function pollForPairingToken({ studioUrl = agentDefaults.studioUrl, sessio
|
|
|
2836
2788
|
//#endregion
|
|
2837
2789
|
exports.InvalidAgentTokenError = InvalidAgentTokenError;
|
|
2838
2790
|
exports.PairingCanceledError = PairingCanceledError;
|
|
2791
|
+
exports.connectWebSocketRpc = connectWebSocketRpc;
|
|
2839
2792
|
exports.createAgent = createAgent;
|
|
2840
2793
|
exports.createClient = createClient;
|
|
2841
2794
|
exports.createFileStorage = createFileStorage;
|
|
2842
2795
|
exports.createJob = createJob;
|
|
2843
|
-
exports.createJobId = require_protocol.createJobId;
|
|
2844
2796
|
exports.defaultStudioUrl = defaultStudioUrl;
|
|
2797
|
+
exports.generationEventTypes = require_protocol.generationEventTypes;
|
|
2845
2798
|
exports.machineTokenFrom = machineTokenFrom;
|
|
2846
2799
|
exports.pollForPairingToken = pollForPairingToken;
|
|
2847
2800
|
exports.runConnection = runConnection;
|