@kubb/studio 5.5.5 → 5.6.0
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/dist/{configFile-BTVeov27.cjs → configFile-dJtYeqX1.cjs} +27 -45
- package/dist/configFile-dJtYeqX1.cjs.map +1 -0
- package/dist/{configFile-BymXUi9P.js → configFile-zhp5DQIe.js} +25 -43
- package/dist/configFile-zhp5DQIe.js.map +1 -0
- package/dist/index.cjs +290 -669
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +54 -62
- package/dist/index.js +292 -670
- package/dist/index.js.map +1 -1
- package/dist/{resolveConfig-D7I3Nj60.cjs → resolveConfig-CjF6j-3y.cjs} +49 -10
- package/dist/resolveConfig-CjF6j-3y.cjs.map +1 -0
- package/dist/{resolveConfig-DMIG_MH-.js → resolveConfig-DOUv_Tid.js} +38 -11
- package/dist/resolveConfig-DOUv_Tid.js.map +1 -0
- package/dist/{snapshotPackage-BwM3LFmZ.cjs → snapshotPackage-BFjiRhGd.cjs} +4 -3
- package/dist/snapshotPackage-BFjiRhGd.cjs.map +1 -0
- package/dist/{snapshotPackage-I8bt8KTR.js → snapshotPackage-BwZ8IJX-.js} +4 -3
- package/dist/snapshotPackage-BwZ8IJX-.js.map +1 -0
- package/dist/url-DC87bYiU.cjs +19 -0
- package/dist/url-DC87bYiU.cjs.map +1 -0
- package/dist/url-DhFfJpxp.js +14 -0
- package/dist/url-DhFfJpxp.js.map +1 -0
- package/package.json +5 -8
- package/dist/configFile-BTVeov27.cjs.map +0 -1
- package/dist/configFile-BymXUi9P.js.map +0 -1
- package/dist/resolveConfig-D7I3Nj60.cjs.map +0 -1
- package/dist/resolveConfig-DMIG_MH-.js.map +0 -1
- package/dist/snapshotPackage-BwM3LFmZ.cjs.map +0 -1
- package/dist/snapshotPackage-I8bt8KTR.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_rolldown_runtime = require("./rolldown-runtime-qbf5tadS.cjs");
|
|
3
|
-
const require_resolveConfig = require("./resolveConfig-
|
|
3
|
+
const require_resolveConfig = require("./resolveConfig-CjF6j-3y.cjs");
|
|
4
4
|
const require_protocol = require("./protocol.cjs");
|
|
5
|
+
const require_url = require("./url-DC87bYiU.cjs");
|
|
5
6
|
let node_crypto = require("node:crypto");
|
|
6
7
|
let node_timers_promises = require("node:timers/promises");
|
|
7
8
|
let node_process = require("node:process");
|
|
8
9
|
node_process = require_rolldown_runtime.__toESM(node_process, 1);
|
|
9
|
-
let node_child_process = require("node:child_process");
|
|
10
|
-
let node_fs_promises = require("node:fs/promises");
|
|
11
10
|
let node_path = require("node:path");
|
|
12
11
|
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
12
|
+
let node_fs_promises = require("node:fs/promises");
|
|
13
13
|
let ofetch = require("ofetch");
|
|
14
14
|
let node_util = require("node:util");
|
|
15
15
|
let unstorage = require("unstorage");
|
|
@@ -17,11 +17,7 @@ let unstorage_drivers_fs = require("unstorage/drivers/fs");
|
|
|
17
17
|
unstorage_drivers_fs = require_rolldown_runtime.__toESM(unstorage_drivers_fs, 1);
|
|
18
18
|
let node_os = require("node:os");
|
|
19
19
|
let _kubb_core = require("@kubb/core");
|
|
20
|
-
let node_module = require("node:module");
|
|
21
20
|
let capnweb = require("capnweb");
|
|
22
|
-
let ws = require("ws");
|
|
23
|
-
ws = require_rolldown_runtime.__toESM(ws, 1);
|
|
24
|
-
let tinyexec = require("tinyexec");
|
|
25
21
|
//#region ../../internals/utils/src/errors.ts
|
|
26
22
|
/**
|
|
27
23
|
* Coerces an unknown thrown value to an `Error` instance.
|
|
@@ -52,44 +48,6 @@ function getErrorMessage(value) {
|
|
|
52
48
|
//#endregion
|
|
53
49
|
//#region ../../internals/utils/src/promise.ts
|
|
54
50
|
/**
|
|
55
|
-
* Wraps `factory` with a keyed cache backed by the provided store.
|
|
56
|
-
*
|
|
57
|
-
* Pass a `WeakMap` for object keys (results are GC-eligible when the key is
|
|
58
|
-
* collected) or a `Map` for primitive keys. For multi-argument functions,
|
|
59
|
-
* nest two `memoize` calls — the outer keyed by the first argument, the
|
|
60
|
-
* inner (created once per outer miss) keyed by the second.
|
|
61
|
-
*
|
|
62
|
-
* Because the cache is owned by the caller, it can be shared, inspected, or
|
|
63
|
-
* cleared independently of the memoized function.
|
|
64
|
-
*
|
|
65
|
-
* @example Single WeakMap key
|
|
66
|
-
* ```ts
|
|
67
|
-
* const cache = new WeakMap<SchemaNode, Set<string>>()
|
|
68
|
-
* const getRefs = memoize(cache, (node) => collectRefs(node))
|
|
69
|
-
* ```
|
|
70
|
-
*
|
|
71
|
-
* @example Single Map key (primitive)
|
|
72
|
-
* ```ts
|
|
73
|
-
* const cache = new Map<string, Resolver>()
|
|
74
|
-
* const getResolver = memoize(cache, (name) => buildResolver(name))
|
|
75
|
-
* ```
|
|
76
|
-
*
|
|
77
|
-
* @example Two-level (object + primitive)
|
|
78
|
-
* ```ts
|
|
79
|
-
* const outer = new WeakMap<Params[], Map<string, Params[]>>()
|
|
80
|
-
* const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
|
|
81
|
-
* fn(params)('camelcase')
|
|
82
|
-
* ```
|
|
83
|
-
*/
|
|
84
|
-
function memoize(store, factory) {
|
|
85
|
-
return (key) => {
|
|
86
|
-
if (store.has(key)) return store.get(key);
|
|
87
|
-
const value = factory(key);
|
|
88
|
-
store.set(key, value);
|
|
89
|
-
return value;
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
51
|
* Runs `run` over every item with at most `limit` in flight. Workers share one iterator, so each
|
|
94
52
|
* takes the next item the moment it frees up instead of waiting for a batch to drain.
|
|
95
53
|
*
|
|
@@ -106,114 +64,6 @@ async function inParallel({ items, limit, run }) {
|
|
|
106
64
|
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()));
|
|
107
65
|
}
|
|
108
66
|
//#endregion
|
|
109
|
-
//#region ../../internals/utils/src/tools.ts
|
|
110
|
-
/**
|
|
111
|
-
* CLI command descriptors for each supported code formatter.
|
|
112
|
-
*/
|
|
113
|
-
const formatters = {
|
|
114
|
-
prettier: {
|
|
115
|
-
command: "prettier",
|
|
116
|
-
args: (outputPath) => [
|
|
117
|
-
"--ignore-unknown",
|
|
118
|
-
"--write",
|
|
119
|
-
outputPath
|
|
120
|
-
],
|
|
121
|
-
errorMessage: "Prettier not found"
|
|
122
|
-
},
|
|
123
|
-
biome: {
|
|
124
|
-
command: "biome",
|
|
125
|
-
args: (outputPath) => [
|
|
126
|
-
"format",
|
|
127
|
-
"--write",
|
|
128
|
-
outputPath
|
|
129
|
-
],
|
|
130
|
-
errorMessage: "Biome not found"
|
|
131
|
-
},
|
|
132
|
-
oxfmt: {
|
|
133
|
-
command: "oxfmt",
|
|
134
|
-
args: (outputPath) => [outputPath],
|
|
135
|
-
errorMessage: "Oxfmt not found"
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
/**
|
|
139
|
-
* CLI command descriptors for each supported linter.
|
|
140
|
-
*/
|
|
141
|
-
const linters = {
|
|
142
|
-
eslint: {
|
|
143
|
-
command: "eslint",
|
|
144
|
-
args: (outputPath) => [outputPath, "--fix"],
|
|
145
|
-
errorMessage: "Eslint not found"
|
|
146
|
-
},
|
|
147
|
-
biome: {
|
|
148
|
-
command: "biome",
|
|
149
|
-
args: (outputPath) => [
|
|
150
|
-
"lint",
|
|
151
|
-
"--fix",
|
|
152
|
-
outputPath
|
|
153
|
-
],
|
|
154
|
-
errorMessage: "Biome not found"
|
|
155
|
-
},
|
|
156
|
-
oxlint: {
|
|
157
|
-
command: "oxlint",
|
|
158
|
-
args: (outputPath) => [
|
|
159
|
-
"--fix",
|
|
160
|
-
"--no-ignore",
|
|
161
|
-
outputPath
|
|
162
|
-
],
|
|
163
|
-
errorMessage: "Oxlint not found"
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
/**
|
|
167
|
-
* Preference order for `format: 'auto'`, most-preferred first. Spelled out rather than taken from
|
|
168
|
-
* the table's key order, which is arbitrary and would silently change what `auto` picks.
|
|
169
|
-
*/
|
|
170
|
-
const FORMATTER_PREFERENCE = [
|
|
171
|
-
"oxfmt",
|
|
172
|
-
"biome",
|
|
173
|
-
"prettier"
|
|
174
|
-
];
|
|
175
|
-
/**
|
|
176
|
-
* Preference order for `lint: 'auto'`, most-preferred first.
|
|
177
|
-
*/
|
|
178
|
-
const LINTER_PREFERENCE = [
|
|
179
|
-
"oxlint",
|
|
180
|
-
"biome",
|
|
181
|
-
"eslint"
|
|
182
|
-
];
|
|
183
|
-
/**
|
|
184
|
-
* Whether `name` is on PATH and answers `--version` with a zero exit.
|
|
185
|
-
*/
|
|
186
|
-
function isToolAvailable(name) {
|
|
187
|
-
return new Promise((resolve) => {
|
|
188
|
-
const child = (0, node_child_process.spawn)(name, ["--version"], { stdio: "ignore" });
|
|
189
|
-
child.on("close", (code) => resolve(code === 0));
|
|
190
|
-
child.on("error", () => resolve(false));
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Returns the first installed executable from `candidates`, or `null` when none are found.
|
|
195
|
-
*
|
|
196
|
-
* Not memoized: a long-running host that probes repeatedly should cache the result itself, and a
|
|
197
|
-
* `--watch` build should keep noticing a tool installed mid-session.
|
|
198
|
-
*/
|
|
199
|
-
async function detectTool$1(candidates) {
|
|
200
|
-
for (const candidate of candidates) if (await isToolAvailable(candidate)) return candidate;
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
require_rolldown_runtime.__name(detectTool$1, "detectTool");
|
|
204
|
-
/**
|
|
205
|
-
* Tokenizes a shell command string, respecting single and double quotes.
|
|
206
|
-
*
|
|
207
|
-
* @example
|
|
208
|
-
* ```ts
|
|
209
|
-
* tokenize('git commit -m "initial commit"')
|
|
210
|
-
* // → ['git', 'commit', '-m', 'initial commit']
|
|
211
|
-
* ```
|
|
212
|
-
*/
|
|
213
|
-
function tokenize(command) {
|
|
214
|
-
return (command.match(/[^\s"']+|"([^"]*)"|'([^']*)'/g) ?? []).map((token) => token.replace(/^["']|["']$/g, ""));
|
|
215
|
-
}
|
|
216
|
-
//#endregion
|
|
217
67
|
//#region ../../internals/utils/src/time.ts
|
|
218
68
|
/**
|
|
219
69
|
* Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.
|
|
@@ -547,8 +397,8 @@ const agentDefaults = {
|
|
|
547
397
|
heartbeatIntervalMs: 3e4,
|
|
548
398
|
/**
|
|
549
399
|
* Slowest heartbeat a host may ask for. Studio drops an agent from the active list once its
|
|
550
|
-
* stored ping is older than its liveness window, and it
|
|
551
|
-
* a slower cadence would make a healthy agent look dead after a single missed ping.
|
|
400
|
+
* stored ping is older than its liveness window, and it persists a ping at most once an hour,
|
|
401
|
+
* so a slower cadence would make a healthy agent look dead after a single missed ping.
|
|
552
402
|
*/
|
|
553
403
|
maxHeartbeatIntervalMs: 6e4,
|
|
554
404
|
/** How long a heartbeat ping may take before the session is treated as dead. */
|
|
@@ -583,13 +433,20 @@ function resolveAgentCapacity(env = process.env) {
|
|
|
583
433
|
}
|
|
584
434
|
//#endregion
|
|
585
435
|
//#region package.json
|
|
586
|
-
var version = "5.
|
|
436
|
+
var version = "5.6.0";
|
|
587
437
|
//#endregion
|
|
588
438
|
//#region src/operations/generations.ts
|
|
589
439
|
const READ_CONCURRENCY = 50;
|
|
590
|
-
const MB
|
|
440
|
+
const MB = 1048576;
|
|
591
441
|
const INDEX_KEY = "studio/generations.json";
|
|
442
|
+
/**
|
|
443
|
+
* The path form {@link SourceFiles} keys on: relative to `root`, with forward slashes.
|
|
444
|
+
*/
|
|
445
|
+
function relativeStoragePath({ root, filePath }) {
|
|
446
|
+
return ((0, node_path.isAbsolute)(filePath) ? (0, node_path.relative)((0, node_path.resolve)(root), filePath) : filePath).replaceAll("\\", "/");
|
|
447
|
+
}
|
|
592
448
|
const hashOf = (content) => (0, node_crypto.createHash)("sha1").update(content).digest("hex").slice(0, 16);
|
|
449
|
+
const totalBytes = (entries) => entries.reduce((sum, { output, disk }) => sum + output.bytes + (disk?.bytes ?? 0), 0);
|
|
593
450
|
/**
|
|
594
451
|
* What the output directory holds on disk before a run. `undefined` when `outputPath` is not a real
|
|
595
452
|
* subdirectory of `root` (listing the root would take in every source file) or holds more than `maxFiles`.
|
|
@@ -632,7 +489,7 @@ function createGenerationStore({ storage, maxCount, maxMb, ttlMs, now = Date.now
|
|
|
632
489
|
* Copies `files` into the store as one set of `jobId`. Above `maxSetMb` only the hashes are kept.
|
|
633
490
|
*/
|
|
634
491
|
async function keep({ jobId, source, files, maxSetMb }) {
|
|
635
|
-
const maxSetBytes = maxSetMb * MB
|
|
492
|
+
const maxSetBytes = maxSetMb * MB;
|
|
636
493
|
const hashes = {};
|
|
637
494
|
let bytes = 0;
|
|
638
495
|
await inParallel({
|
|
@@ -670,7 +527,7 @@ function createGenerationStore({ storage, maxCount, maxMb, ttlMs, now = Date.now
|
|
|
670
527
|
get: async (jobId) => (await load()).find((generation) => generation.jobId === jobId && isLive(generation)),
|
|
671
528
|
latest: async () => (await load()).filter(isLive).at(-1),
|
|
672
529
|
/** Total bytes of every set the store holds, expired ones too until the next add drops them. */
|
|
673
|
-
bytes: async () => (await load())
|
|
530
|
+
bytes: async () => totalBytes(await load()),
|
|
674
531
|
async add(generation) {
|
|
675
532
|
const current = await load();
|
|
676
533
|
for (const expired of current.filter((entry) => !isLive(entry))) await drop(expired.jobId);
|
|
@@ -679,8 +536,7 @@ function createGenerationStore({ storage, maxCount, maxMb, ttlMs, now = Date.now
|
|
|
679
536
|
...generation,
|
|
680
537
|
keptAt: now()
|
|
681
538
|
});
|
|
682
|
-
|
|
683
|
-
while (entries.length > 1 && (entries.length > maxCount || weight() > maxMb * MB$1)) await drop(entries.shift().jobId);
|
|
539
|
+
while (entries.length > 1 && (entries.length > maxCount || totalBytes(entries) > maxMb * MB)) await drop(entries.shift().jobId);
|
|
684
540
|
index = entries;
|
|
685
541
|
await storage.writeItem(INDEX_KEY, JSON.stringify(entries));
|
|
686
542
|
},
|
|
@@ -719,40 +575,11 @@ function createGenerationStore({ storage, maxCount, maxMb, ttlMs, now = Date.now
|
|
|
719
575
|
}
|
|
720
576
|
//#endregion
|
|
721
577
|
//#region src/operations/generationEvents.ts
|
|
722
|
-
const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
723
|
-
function relativeStoragePath(root, filePath) {
|
|
724
|
-
return ((0, node_path.isAbsolute)(filePath) ? (0, node_path.relative)((0, node_path.resolve)(root), filePath) : filePath).replaceAll("\\", "/");
|
|
725
|
-
}
|
|
726
|
-
async function resolvePeerDependencies(names) {
|
|
727
|
-
const uniqueNames = [...new Set(names.map(require_resolveConfig.toPackageName))];
|
|
728
|
-
const peerDependencies = {};
|
|
729
|
-
const missingDependencies = [];
|
|
730
|
-
const versions = await Promise.all(uniqueNames.map(async (name) => {
|
|
731
|
-
try {
|
|
732
|
-
const path = require$1.resolve(`${name}/package.json`);
|
|
733
|
-
return JSON.parse(await (0, node_fs_promises.readFile)(path, "utf8")).version;
|
|
734
|
-
} catch {
|
|
735
|
-
return;
|
|
736
|
-
}
|
|
737
|
-
}));
|
|
738
|
-
for (const [index, name] of uniqueNames.entries()) {
|
|
739
|
-
const version = versions[index];
|
|
740
|
-
if (version) {
|
|
741
|
-
peerDependencies[name] = version;
|
|
742
|
-
continue;
|
|
743
|
-
}
|
|
744
|
-
missingDependencies.push(name);
|
|
745
|
-
}
|
|
746
|
-
return {
|
|
747
|
-
peerDependencies,
|
|
748
|
-
missingDependencies
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
578
|
const MAX_QUEUED_EVENTS = 1024;
|
|
752
579
|
const RESERVED_EVENTS = 64;
|
|
753
580
|
const isDiscardable = (event) => event.type === "kubb:files:processing:update" || event.type === "kubb:info" || event.type === "kubb:success";
|
|
754
581
|
/** Forwards selected Kubb lifecycle events to a native Cap'n Web stream. */
|
|
755
|
-
function createGenerationStream(hooks, jobId
|
|
582
|
+
function createGenerationStream(hooks, jobId) {
|
|
756
583
|
const unhooks = [];
|
|
757
584
|
let root = "";
|
|
758
585
|
let controller;
|
|
@@ -789,129 +616,92 @@ function createGenerationStream(hooks, jobId, options = {}) {
|
|
|
789
616
|
}
|
|
790
617
|
controller.enqueue(event);
|
|
791
618
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
}]);
|
|
801
|
-
});
|
|
802
|
-
on("kubb:build:start", ({ config, adapter }) => {
|
|
803
|
-
root = config.root;
|
|
804
|
-
emitEvent("kubb:build:start", [{
|
|
619
|
+
const projections = {
|
|
620
|
+
"kubb:plugin:start": ({ plugin }) => [{ plugin: { name: plugin.name } }],
|
|
621
|
+
"kubb:plugin:end": ({ plugin, duration, success }) => [{
|
|
622
|
+
plugin: { name: plugin.name },
|
|
623
|
+
duration,
|
|
624
|
+
success
|
|
625
|
+
}],
|
|
626
|
+
"kubb:build:start": ({ config, adapter }) => [{
|
|
805
627
|
config: { name: config.name },
|
|
806
628
|
adapter: { name: adapter.name }
|
|
807
|
-
}]
|
|
808
|
-
|
|
809
|
-
on("kubb:build:end", ({ files, config, outputDir }) => {
|
|
810
|
-
emitEvent("kubb:build:end", [{
|
|
629
|
+
}],
|
|
630
|
+
"kubb:build:end": ({ files, config, outputDir }) => [{
|
|
811
631
|
files: files.map((file) => ({
|
|
812
|
-
path: relativeStoragePath(
|
|
632
|
+
path: relativeStoragePath({
|
|
633
|
+
root: config.root,
|
|
634
|
+
filePath: file.path
|
|
635
|
+
}),
|
|
813
636
|
name: file.name
|
|
814
637
|
})),
|
|
815
638
|
outputDir
|
|
816
|
-
}]
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
file: relativeStoragePath(root, file.path),
|
|
639
|
+
}],
|
|
640
|
+
"kubb:files:processing:start": ({ files }) => [{ total: files.length }],
|
|
641
|
+
"kubb:files:processing:update": ({ files }) => [{ files: files.map(({ file, processed, total, percentage }) => ({
|
|
642
|
+
file: relativeStoragePath({
|
|
643
|
+
root,
|
|
644
|
+
filePath: file.path
|
|
645
|
+
}),
|
|
824
646
|
processed,
|
|
825
647
|
total,
|
|
826
648
|
percentage
|
|
827
|
-
})) }]
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
});
|
|
832
|
-
for (const type of [
|
|
833
|
-
"kubb:info",
|
|
834
|
-
"kubb:success",
|
|
835
|
-
"kubb:warn"
|
|
836
|
-
]) on(type, ({ message, info }) => {
|
|
837
|
-
emitEvent(type, [{
|
|
838
|
-
message,
|
|
649
|
+
})) }],
|
|
650
|
+
"kubb:files:processing:end": ({ files }) => [{ total: files.length }],
|
|
651
|
+
"kubb:info": ({ message, info }) => [{
|
|
652
|
+
message: (0, node_util.stripVTControlCharacters)(message),
|
|
839
653
|
info
|
|
840
|
-
}]
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
const keys = await storage.readKeys();
|
|
851
|
-
const paths = new Set(keys.map((key) => relativeStoragePath(config.root, key)));
|
|
852
|
-
options.onGenerationEnd?.({
|
|
853
|
-
output: {
|
|
854
|
-
storage,
|
|
855
|
-
root: config.root,
|
|
856
|
-
paths
|
|
857
|
-
},
|
|
858
|
-
peerDependencies,
|
|
859
|
-
missingDependencies
|
|
860
|
-
});
|
|
861
|
-
emitEvent("kubb:generation:end", []);
|
|
862
|
-
if (!hrStart) return;
|
|
863
|
-
emitEvent("kubb:generation:summary", [{
|
|
864
|
-
duration: Math.round(getElapsedMs(hrStart)),
|
|
865
|
-
fileCount: filesCreated ?? 0,
|
|
866
|
-
failedPlugins: _kubb_core.Diagnostics.failedPlugins(diagnostics).length,
|
|
867
|
-
status: status ?? "success"
|
|
868
|
-
}]);
|
|
869
|
-
});
|
|
870
|
-
on("kubb:error", ({ error }) => {
|
|
871
|
-
emitEvent("kubb:error", [{
|
|
654
|
+
}],
|
|
655
|
+
"kubb:success": ({ message, info }) => [{
|
|
656
|
+
message: (0, node_util.stripVTControlCharacters)(message),
|
|
657
|
+
info
|
|
658
|
+
}],
|
|
659
|
+
"kubb:warn": ({ message, info }) => [{
|
|
660
|
+
message: (0, node_util.stripVTControlCharacters)(message),
|
|
661
|
+
info
|
|
662
|
+
}],
|
|
663
|
+
"kubb:error": ({ error }) => [{
|
|
872
664
|
message: error.message,
|
|
873
665
|
stack: error.stack
|
|
874
|
-
}]
|
|
875
|
-
|
|
876
|
-
on("kubb:diagnostic", ({ diagnostic }) => {
|
|
877
|
-
const cause = "cause" in diagnostic ? diagnostic.cause : void 0;
|
|
878
|
-
emitEvent("kubb:diagnostic", [{
|
|
666
|
+
}],
|
|
667
|
+
"kubb:diagnostic": ({ diagnostic }) => [{
|
|
879
668
|
code: diagnostic.code,
|
|
880
669
|
message: diagnostic.message,
|
|
881
670
|
severity: diagnostic.severity,
|
|
882
671
|
location: "location" in diagnostic ? diagnostic.location : void 0,
|
|
883
672
|
help: "help" in diagnostic ? diagnostic.help : void 0,
|
|
884
673
|
plugin: "plugin" in diagnostic ? diagnostic.plugin : void 0,
|
|
885
|
-
stack: cause?.stack
|
|
886
|
-
}]
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
"kubb:
|
|
892
|
-
"kubb:
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
674
|
+
stack: "cause" in diagnostic ? diagnostic.cause?.stack : void 0
|
|
675
|
+
}],
|
|
676
|
+
"kubb:generation:start": ({ config }) => [{
|
|
677
|
+
name: config.name,
|
|
678
|
+
plugins: config.plugins.length
|
|
679
|
+
}],
|
|
680
|
+
"kubb:generation:end": () => [],
|
|
681
|
+
"kubb:generation:summary": ({ duration, fileCount, failedPlugins, status }) => [{
|
|
682
|
+
duration,
|
|
683
|
+
fileCount,
|
|
684
|
+
failedPlugins,
|
|
685
|
+
status
|
|
686
|
+
}],
|
|
687
|
+
"kubb:lifecycle:start": () => [],
|
|
688
|
+
"kubb:lifecycle:end": () => [],
|
|
689
|
+
"kubb:format:start": () => [],
|
|
690
|
+
"kubb:format:end": () => [],
|
|
691
|
+
"kubb:lint:start": () => [],
|
|
692
|
+
"kubb:lint:end": () => [],
|
|
693
|
+
"kubb:hooks:start": () => [],
|
|
694
|
+
"kubb:hooks:end": () => [],
|
|
695
|
+
"kubb:hook:start": ({ id, command, args }) => [{
|
|
902
696
|
id,
|
|
903
697
|
command,
|
|
904
698
|
args: args ? [...args] : void 0
|
|
905
|
-
}]
|
|
906
|
-
|
|
907
|
-
on("kubb:hook:line", ({ id, line }) => {
|
|
908
|
-
emitEvent("kubb:hook:line", [{
|
|
699
|
+
}],
|
|
700
|
+
"kubb:hook:line": ({ id, line }) => [{
|
|
909
701
|
id,
|
|
910
702
|
line
|
|
911
|
-
}]
|
|
912
|
-
|
|
913
|
-
on("kubb:hook:end", ({ id, command, args, success, error }) => {
|
|
914
|
-
emitEvent("kubb:hook:end", [{
|
|
703
|
+
}],
|
|
704
|
+
"kubb:hook:end": ({ id, command, args, success, error }) => [{
|
|
915
705
|
id,
|
|
916
706
|
command,
|
|
917
707
|
args: args ? [...args] : void 0,
|
|
@@ -920,6 +710,22 @@ function createGenerationStream(hooks, jobId, options = {}) {
|
|
|
920
710
|
message: error.message,
|
|
921
711
|
stack: error.stack
|
|
922
712
|
} : void 0
|
|
713
|
+
}]
|
|
714
|
+
};
|
|
715
|
+
function forward(type) {
|
|
716
|
+
on(type, (...args) => emitEvent(type, projections[type](...args)));
|
|
717
|
+
}
|
|
718
|
+
on("kubb:build:start", ({ config }) => {
|
|
719
|
+
root = config.root;
|
|
720
|
+
});
|
|
721
|
+
for (const type of require_protocol.generationEventTypes) forward(type);
|
|
722
|
+
on("kubb:generation:end", ({ diagnostics = [], status, hrStart, filesCreated }) => {
|
|
723
|
+
if (!hrStart) return;
|
|
724
|
+
emitEvent("kubb:generation:summary", [{
|
|
725
|
+
duration: Math.round(getElapsedMs(hrStart)),
|
|
726
|
+
fileCount: filesCreated ?? 0,
|
|
727
|
+
failedPlugins: _kubb_core.Diagnostics.failedPlugins(diagnostics).length,
|
|
728
|
+
status: status ?? "success"
|
|
923
729
|
}]);
|
|
924
730
|
});
|
|
925
731
|
/**
|
|
@@ -949,22 +755,8 @@ function createGenerationStream(hooks, jobId, options = {}) {
|
|
|
949
755
|
};
|
|
950
756
|
}
|
|
951
757
|
//#endregion
|
|
952
|
-
//#region src/operations/websocket.ts
|
|
953
|
-
const CONNECT_TIMEOUT_MS = 5e3;
|
|
954
|
-
/**
|
|
955
|
-
* Opens a Studio WebSocket connection and closes it when the initial handshake exceeds the configured timeout.
|
|
956
|
-
*/
|
|
957
|
-
function createWebsocket(url, options) {
|
|
958
|
-
const ws$1 = new ws.default(url, options);
|
|
959
|
-
const timer = setTimeout(() => {
|
|
960
|
-
if (ws$1.readyState === ws.default.CONNECTING) ws$1.close(3008, "Connection timeout");
|
|
961
|
-
}, CONNECT_TIMEOUT_MS);
|
|
962
|
-
ws$1.once("open", () => clearTimeout(timer));
|
|
963
|
-
ws$1.once("close", () => clearTimeout(timer));
|
|
964
|
-
return ws$1;
|
|
965
|
-
}
|
|
966
|
-
//#endregion
|
|
967
758
|
//#region src/operations/rpc.ts
|
|
759
|
+
const CONNECT_TIMEOUT_MS = 5e3;
|
|
968
760
|
/**
|
|
969
761
|
* The only methods Studio may call on an agent. A `StudioSession` carries far more than
|
|
970
762
|
* {@link AgentApi}, so it is wrapped rather than exposed: what Cap'n Web can reach is exactly what
|
|
@@ -996,6 +788,41 @@ var AgentRpcTarget = class extends capnweb.RpcTarget {
|
|
|
996
788
|
}
|
|
997
789
|
};
|
|
998
790
|
/**
|
|
791
|
+
* Opens a Studio WebSocket, closing it when the handshake exceeds {@link CONNECT_TIMEOUT_MS}. Node
|
|
792
|
+
* fires no `close` event when the handshake itself fails, only `error`, so `closed` settles as 1006 there.
|
|
793
|
+
*/
|
|
794
|
+
function openSocket({ url, headers }) {
|
|
795
|
+
const socket = new WebSocket(url, { headers });
|
|
796
|
+
return {
|
|
797
|
+
socket,
|
|
798
|
+
closed: new Promise((resolve) => {
|
|
799
|
+
let opened = false;
|
|
800
|
+
const timer = setTimeout(() => {
|
|
801
|
+
if (socket.readyState === WebSocket.CONNECTING) socket.close(3008, "Connection timeout");
|
|
802
|
+
}, CONNECT_TIMEOUT_MS);
|
|
803
|
+
socket.addEventListener("open", () => {
|
|
804
|
+
opened = true;
|
|
805
|
+
clearTimeout(timer);
|
|
806
|
+
}, { once: true });
|
|
807
|
+
socket.addEventListener("close", (event) => {
|
|
808
|
+
clearTimeout(timer);
|
|
809
|
+
resolve({
|
|
810
|
+
code: event.code,
|
|
811
|
+
reason: event.reason
|
|
812
|
+
});
|
|
813
|
+
}, { once: true });
|
|
814
|
+
socket.addEventListener("error", () => {
|
|
815
|
+
if (opened) return;
|
|
816
|
+
clearTimeout(timer);
|
|
817
|
+
resolve({
|
|
818
|
+
code: 1006,
|
|
819
|
+
reason: ""
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
})
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
999
826
|
* Opens an authenticated Cap'n Web session to Studio over a WebSocket. Rejects an unencrypted URL
|
|
1000
827
|
* before opening the socket, so a bearer token never reaches a plaintext host.
|
|
1001
828
|
*
|
|
@@ -1007,15 +834,14 @@ var AgentRpcTarget = class extends capnweb.RpcTarget {
|
|
|
1007
834
|
*/
|
|
1008
835
|
const connectWebSocketRpc = async ({ url, token, instanceId, local }) => {
|
|
1009
836
|
const { protocol, hostname, host } = new URL(url);
|
|
1010
|
-
if (protocol !== "wss:" && !(protocol === "ws:" && (hostname
|
|
1011
|
-
const socket =
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
})));
|
|
837
|
+
if (protocol !== "wss:" && !(protocol === "ws:" && require_url.isLoopbackHost(hostname))) throw new Error(`Refusing unencrypted WebSocket to ${host}`);
|
|
838
|
+
const { socket, closed } = openSocket({
|
|
839
|
+
url,
|
|
840
|
+
headers: {
|
|
841
|
+
Authorization: `Bearer ${token}`,
|
|
842
|
+
[require_protocol.AGENT_INSTANCE_HEADER]: instanceId
|
|
843
|
+
}
|
|
844
|
+
});
|
|
1019
845
|
const studio = (0, capnweb.newWebSocketRpcSession)(socket, new AgentRpcTarget(local));
|
|
1020
846
|
studio.onRpcBroken(() => socket.close());
|
|
1021
847
|
return {
|
|
@@ -1025,256 +851,16 @@ const connectWebSocketRpc = async ({ url, token, instanceId, local }) => {
|
|
|
1025
851
|
};
|
|
1026
852
|
};
|
|
1027
853
|
//#endregion
|
|
1028
|
-
//#region src/
|
|
1029
|
-
/**
|
|
1030
|
-
* Register a `kubb:hook:start` listener that spawns the requested command via tinyexec,
|
|
1031
|
-
* streams each stdout line as a `kubb:hook:line` event, and calls `kubb:hook:end` with the result.
|
|
1032
|
-
* A failure travels on `kubb:hook:end` only: the caller waiting on it reports `kubb:error` once.
|
|
1033
|
-
* Streaming the output lets Kubb Studio render live hook progress over the WebSocket connection.
|
|
1034
|
-
*
|
|
1035
|
-
* Returns a remover, so a session that runs one generation after another on the same emitter does
|
|
1036
|
-
* not stack a listener per run.
|
|
1037
|
-
*/
|
|
1038
|
-
function setupHookListener(hooks, root, signal) {
|
|
1039
|
-
return hooks.hook("kubb:hook:start", async (ctx) => {
|
|
1040
|
-
const { id, command, args } = ctx;
|
|
1041
|
-
if (!id) return;
|
|
1042
|
-
const commandWithArgs = args?.length ? `${command} ${args.join(" ")}` : command;
|
|
1043
|
-
try {
|
|
1044
|
-
const proc = (0, tinyexec.x)(command, [...args ?? []], {
|
|
1045
|
-
signal,
|
|
1046
|
-
nodeOptions: {
|
|
1047
|
-
cwd: root,
|
|
1048
|
-
detached: true
|
|
1049
|
-
}
|
|
1050
|
-
});
|
|
1051
|
-
for await (const line of proc) await hooks.callHook("kubb:hook:line", {
|
|
1052
|
-
id,
|
|
1053
|
-
line
|
|
1054
|
-
});
|
|
1055
|
-
const { exitCode } = await proc;
|
|
1056
|
-
if (exitCode !== 0) {
|
|
1057
|
-
const error = /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`);
|
|
1058
|
-
await hooks.callHook("kubb:hook:end", {
|
|
1059
|
-
id,
|
|
1060
|
-
command,
|
|
1061
|
-
args,
|
|
1062
|
-
success: false,
|
|
1063
|
-
error
|
|
1064
|
-
});
|
|
1065
|
-
return;
|
|
1066
|
-
}
|
|
1067
|
-
await hooks.callHook("kubb:hook:end", {
|
|
1068
|
-
id,
|
|
1069
|
-
command,
|
|
1070
|
-
args,
|
|
1071
|
-
success: true,
|
|
1072
|
-
error: null
|
|
1073
|
-
});
|
|
1074
|
-
} catch (caughtError) {
|
|
1075
|
-
const error = /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`);
|
|
1076
|
-
error.cause = caughtError;
|
|
1077
|
-
await hooks.callHook("kubb:hook:end", {
|
|
1078
|
-
id,
|
|
1079
|
-
command,
|
|
1080
|
-
args,
|
|
1081
|
-
success: false,
|
|
1082
|
-
error
|
|
1083
|
-
});
|
|
1084
|
-
}
|
|
1085
|
-
});
|
|
1086
|
-
}
|
|
1087
|
-
/**
|
|
1088
|
-
* Waits for the `kubb:hook:end` matching `hookId`. Register this before calling `kubb:hook:start`:
|
|
1089
|
-
* `callHook` awaits its listeners, and {@link setupHookListener} calls `kubb:hook:end` from inside
|
|
1090
|
-
* that same listener, so a handler added afterward would already have missed it.
|
|
1091
|
-
*/
|
|
1092
|
-
function waitForHookEnd(hooks, hookId) {
|
|
1093
|
-
return new Promise((resolve, reject) => {
|
|
1094
|
-
const handleHookEnd = (ctx) => {
|
|
1095
|
-
if (ctx.id !== hookId) return;
|
|
1096
|
-
hooks.removeHook("kubb:hook:end", handleHookEnd);
|
|
1097
|
-
if (ctx.success) {
|
|
1098
|
-
resolve();
|
|
1099
|
-
return;
|
|
1100
|
-
}
|
|
1101
|
-
reject(ctx.error);
|
|
1102
|
-
};
|
|
1103
|
-
hooks.hook("kubb:hook:end", handleHookEnd);
|
|
1104
|
-
});
|
|
1105
|
-
}
|
|
1106
|
-
//#endregion
|
|
1107
|
-
//#region src/operations/generate.ts
|
|
854
|
+
//#region src/runtime/StudioSession.ts
|
|
1108
855
|
const DISK_SNAPSHOT_MAX_FILES = 1e4;
|
|
1109
856
|
/**
|
|
1110
|
-
*
|
|
1111
|
-
* executable is probed once per process. The CLI deliberately does not memoize: a `--watch` build
|
|
1112
|
-
* should keep noticing a tool installed mid-session.
|
|
1113
|
-
*/
|
|
1114
|
-
const detectTool = memoize(/* @__PURE__ */ new Map(), detectTool$1);
|
|
1115
|
-
/**
|
|
1116
|
-
* The two post-build tool steps. Formatting and linting differ only in which tools they look for,
|
|
1117
|
-
* so they run through one loop rather than two near-identical blocks.
|
|
1118
|
-
*
|
|
1119
|
-
* `noun` and `verbing` are spelled out instead of built from `kind`. Concatenating `` `${kind}ter` ``
|
|
1120
|
-
* and `` `${kind}ting` `` works for `format`, but doubles the `t` in `lint`, giving "lintter" and
|
|
1121
|
-
* "lintting" instead of "linter" and "linting".
|
|
1122
|
-
*/
|
|
1123
|
-
const TOOL_STEPS = [{
|
|
1124
|
-
kind: "format",
|
|
1125
|
-
noun: "formatter",
|
|
1126
|
-
verbing: "Formatting",
|
|
1127
|
-
tools: formatters,
|
|
1128
|
-
detect: FORMATTER_PREFERENCE
|
|
1129
|
-
}, {
|
|
1130
|
-
kind: "lint",
|
|
1131
|
-
noun: "linter",
|
|
1132
|
-
verbing: "Linting",
|
|
1133
|
-
tools: linters,
|
|
1134
|
-
detect: LINTER_PREFERENCE
|
|
1135
|
-
}];
|
|
1136
|
-
/**
|
|
1137
|
-
* Absolute path of the directory the formatter and linter are pointed at.
|
|
1138
|
-
*/
|
|
1139
|
-
function outputPath(config) {
|
|
1140
|
-
return node_path.default.isAbsolute(config.output.path) ? config.output.path : node_path.default.resolve(node_process.default.cwd(), config.root, config.output.path);
|
|
1141
|
-
}
|
|
1142
|
-
/**
|
|
1143
|
-
* Emits `kubb:hook:start` and waits for the matching `kubb:hook:end`. The host spawns the process:
|
|
1144
|
-
* this only describes what to run and when it finished.
|
|
1145
|
-
*
|
|
1146
|
-
* @throws whatever the command failed with, so callers can report it their own way.
|
|
857
|
+
* Folds error-severity diagnostics into one thrown error so logs name the failing plugin or pass.
|
|
1147
858
|
*/
|
|
1148
|
-
|
|
1149
|
-
const
|
|
1150
|
-
const hookEnd = waitForHookEnd(hooks, hookId);
|
|
1151
|
-
await hooks.callHook("kubb:hook:start", {
|
|
1152
|
-
id: hookId,
|
|
1153
|
-
command,
|
|
1154
|
-
args: [...args]
|
|
1155
|
-
});
|
|
1156
|
-
await hookEnd;
|
|
1157
|
-
}
|
|
1158
|
-
function isProblemErrorDiagnostic(diagnostic) {
|
|
1159
|
-
return (diagnostic.kind ?? "problem") === "problem" && diagnostic.severity === "error";
|
|
1160
|
-
}
|
|
1161
|
-
/**
|
|
1162
|
-
* Folds error-severity diagnostics into one thrown error so logs name the failing plugin.
|
|
1163
|
-
*/
|
|
1164
|
-
function formatGenerationFailure(diagnostics) {
|
|
1165
|
-
const reasons = diagnostics.filter(isProblemErrorDiagnostic).map((diagnostic) => diagnostic.plugin ? `${diagnostic.plugin}: ${diagnostic.message}` : diagnostic.message);
|
|
859
|
+
function generationFailure(diagnostics) {
|
|
860
|
+
const reasons = diagnostics.filter(_kubb_core.Diagnostics.isProblem).filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.plugin ? `${diagnostic.plugin}: ${diagnostic.message}` : diagnostic.message);
|
|
1166
861
|
if (!reasons.length) return /* @__PURE__ */ new Error("Generation failed");
|
|
1167
862
|
return /* @__PURE__ */ new Error(`Generation failed: ${reasons.length} error${reasons.length === 1 ? "" : "s"}: ${reasons.join("; ")}`);
|
|
1168
863
|
}
|
|
1169
|
-
/**
|
|
1170
|
-
* Runs a full Kubb code-generation cycle for the given config.
|
|
1171
|
-
*
|
|
1172
|
-
* Emits lifecycle events on the provided `hooks` emitter so callers (e.g. the WebSocket stream)
|
|
1173
|
-
* can forward progress to connected clients. After a successful build, auto-formatting and
|
|
1174
|
-
* linting are applied when configured, followed by any user-defined `hooks.done` commands.
|
|
1175
|
-
*/
|
|
1176
|
-
async function generate({ config, hooks, signal }) {
|
|
1177
|
-
signal?.throwIfAborted();
|
|
1178
|
-
const hrStart = node_process.default.hrtime();
|
|
1179
|
-
await hooks.callHook("kubb:generation:start", { config });
|
|
1180
|
-
await hooks.callHook("kubb:info", { message: config.name ? `Setup generation ${config.name}` : "Setup generation" });
|
|
1181
|
-
const kubb = (0, _kubb_core.createKubb)(config, {
|
|
1182
|
-
hooks,
|
|
1183
|
-
signal
|
|
1184
|
-
});
|
|
1185
|
-
await kubb.setup();
|
|
1186
|
-
await hooks.callHook("kubb:info", { message: config.name ? `Build generation ${config.name}` : "Build generation" });
|
|
1187
|
-
const { files, diagnostics, storage } = await kubb.safeBuild();
|
|
1188
|
-
signal?.throwIfAborted();
|
|
1189
|
-
await hooks.callHook("kubb:info", { message: "Load summary" });
|
|
1190
|
-
for (const diagnostic of diagnostics.filter(isProblemErrorDiagnostic)) await hooks.callHook("kubb:error", { error: new Error(diagnostic.plugin ? `${diagnostic.plugin}: ${diagnostic.message}` : diagnostic.message) });
|
|
1191
|
-
const status = _kubb_core.Diagnostics.hasError(diagnostics) ? "failed" : "success";
|
|
1192
|
-
await hooks.callHook("kubb:generation:end", {
|
|
1193
|
-
config,
|
|
1194
|
-
storage: {
|
|
1195
|
-
...storage,
|
|
1196
|
-
readKeys: async () => [...new Set(files.map((file) => file.path))]
|
|
1197
|
-
},
|
|
1198
|
-
diagnostics,
|
|
1199
|
-
status,
|
|
1200
|
-
hrStart,
|
|
1201
|
-
filesCreated: files.length
|
|
1202
|
-
});
|
|
1203
|
-
if (status === "failed") throw formatGenerationFailure(diagnostics);
|
|
1204
|
-
await hooks.callHook("kubb:success", { message: "Generation successfully" });
|
|
1205
|
-
for (const step of TOOL_STEPS) {
|
|
1206
|
-
const setting = config.output[step.kind];
|
|
1207
|
-
if (!setting) continue;
|
|
1208
|
-
await hooks.callHook(`kubb:${step.kind}:start`);
|
|
1209
|
-
const tool = setting === "auto" ? await detectTool(step.detect) : setting;
|
|
1210
|
-
if (!tool) await hooks.callHook("kubb:warn", { message: `No ${step.noun} found (${step.detect.join(", ")}). Skipping ${step.verbing.toLowerCase()}.` });
|
|
1211
|
-
if (tool && setting === "auto") await hooks.callHook("kubb:info", { message: `Auto-detected ${step.noun}: ${(0, node_util.styleText)("dim", tool)}` });
|
|
1212
|
-
const command = tool ? step.tools[tool] : void 0;
|
|
1213
|
-
if (command) try {
|
|
1214
|
-
await runHook({
|
|
1215
|
-
hooks,
|
|
1216
|
-
id: [config.name, tool].filter(Boolean).join("-"),
|
|
1217
|
-
command: command.command,
|
|
1218
|
-
args: command.args(outputPath(config))
|
|
1219
|
-
});
|
|
1220
|
-
await hooks.callHook("kubb:success", { message: `${step.verbing} with ${tool} successfully` });
|
|
1221
|
-
} catch (caughtError) {
|
|
1222
|
-
await hooks.callHook("kubb:error", { error: new Error(command.errorMessage, { cause: caughtError }) });
|
|
1223
|
-
signal?.throwIfAborted();
|
|
1224
|
-
}
|
|
1225
|
-
await hooks.callHook(`kubb:${step.kind}:end`);
|
|
1226
|
-
}
|
|
1227
|
-
if (config.output.postGenerate?.length) {
|
|
1228
|
-
await hooks.callHook("kubb:hooks:start");
|
|
1229
|
-
for (const entry of config.output.postGenerate) {
|
|
1230
|
-
const line = typeof entry === "string" ? entry : entry.command;
|
|
1231
|
-
const [cmd, ...args] = tokenize(line);
|
|
1232
|
-
if (!cmd) continue;
|
|
1233
|
-
try {
|
|
1234
|
-
await runHook({
|
|
1235
|
-
hooks,
|
|
1236
|
-
id: line,
|
|
1237
|
-
command: cmd,
|
|
1238
|
-
args
|
|
1239
|
-
});
|
|
1240
|
-
} catch (error) {
|
|
1241
|
-
await hooks.callHook("kubb:error", { error: toError(error) });
|
|
1242
|
-
throw error;
|
|
1243
|
-
}
|
|
1244
|
-
await hooks.callHook("kubb:success", { message: `${line} successfully executed` });
|
|
1245
|
-
}
|
|
1246
|
-
await hooks.callHook("kubb:hooks:end");
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
async function runGenerationOperation({ config, hooks, signal, jobId, store, snapshotRoot, maxSnapshotMb }) {
|
|
1250
|
-
const diskFiles = snapshotRoot ? await listDisk({
|
|
1251
|
-
root: snapshotRoot,
|
|
1252
|
-
outputPath: config.output.path,
|
|
1253
|
-
maxFiles: DISK_SNAPSHOT_MAX_FILES
|
|
1254
|
-
}) : void 0;
|
|
1255
|
-
const disk = diskFiles ? await store.keep({
|
|
1256
|
-
jobId,
|
|
1257
|
-
source: "disk",
|
|
1258
|
-
files: diskFiles,
|
|
1259
|
-
maxSetMb: maxSnapshotMb
|
|
1260
|
-
}) : void 0;
|
|
1261
|
-
const removeHookListener = setupHookListener(hooks, config.root, signal);
|
|
1262
|
-
try {
|
|
1263
|
-
await generate({
|
|
1264
|
-
config,
|
|
1265
|
-
hooks,
|
|
1266
|
-
signal
|
|
1267
|
-
});
|
|
1268
|
-
} catch (error) {
|
|
1269
|
-
await store.drop(jobId);
|
|
1270
|
-
throw error;
|
|
1271
|
-
} finally {
|
|
1272
|
-
removeHookListener();
|
|
1273
|
-
}
|
|
1274
|
-
return disk;
|
|
1275
|
-
}
|
|
1276
|
-
//#endregion
|
|
1277
|
-
//#region src/runtime/StudioSession.ts
|
|
1278
864
|
/** A sandbox shares one in-memory generation store across tenants, so old generations expire. */
|
|
1279
865
|
const SANDBOX_GENERATION_TTL_MS = 9e5;
|
|
1280
866
|
/**
|
|
@@ -1341,7 +927,6 @@ function applyStudioDefaults(options) {
|
|
|
1341
927
|
allowRead: false,
|
|
1342
928
|
...options.permissions
|
|
1343
929
|
},
|
|
1344
|
-
retryInterval: options.retryInterval ?? agentDefaults.retryIntervalMs,
|
|
1345
930
|
heartbeatInterval: Math.min(options.heartbeatInterval ?? agentDefaults.heartbeatIntervalMs, agentDefaults.maxHeartbeatIntervalMs),
|
|
1346
931
|
capacity: {
|
|
1347
932
|
...resolveAgentCapacity(),
|
|
@@ -1355,10 +940,6 @@ function applyStudioDefaults(options) {
|
|
|
1355
940
|
* and with it each other's events, so the connection keeps this limit.
|
|
1356
941
|
*/
|
|
1357
942
|
const RUNTIME_MAX_CONCURRENT = 1;
|
|
1358
|
-
const MB = 1048576;
|
|
1359
|
-
function rssMb() {
|
|
1360
|
-
return node_process.default.memoryUsage().rss / MB;
|
|
1361
|
-
}
|
|
1362
943
|
/**
|
|
1363
944
|
* Reads what Studio meant by closing the connection. A code Studio did not send on purpose is an
|
|
1364
945
|
* ordinary drop, and the agent reconnects as it always has.
|
|
@@ -1406,12 +987,10 @@ var StudioSession = class {
|
|
|
1406
987
|
*/
|
|
1407
988
|
#registration;
|
|
1408
989
|
#rpc;
|
|
1409
|
-
#studioVersion;
|
|
1410
990
|
#disposed = false;
|
|
1411
991
|
#isGenerating = false;
|
|
1412
992
|
#activeJob;
|
|
1413
993
|
#heartbeatTimer;
|
|
1414
|
-
#lastGeneration;
|
|
1415
994
|
#store;
|
|
1416
995
|
#limits = resolveGenerationLimits();
|
|
1417
996
|
/**
|
|
@@ -1478,7 +1057,6 @@ var StudioSession = class {
|
|
|
1478
1057
|
});
|
|
1479
1058
|
signal?.throwIfAborted();
|
|
1480
1059
|
this.#registration = registration;
|
|
1481
|
-
this.#studioVersion = registration.version;
|
|
1482
1060
|
const rpc = await (this.#options.connector ?? connectWebSocketRpc)({
|
|
1483
1061
|
url: registration.socketUrl,
|
|
1484
1062
|
token,
|
|
@@ -1495,7 +1073,7 @@ var StudioSession = class {
|
|
|
1495
1073
|
await this.#hooks.callHook("studio:connected", {
|
|
1496
1074
|
url: studioUrl,
|
|
1497
1075
|
versions: {
|
|
1498
|
-
studio:
|
|
1076
|
+
studio: registration.version,
|
|
1499
1077
|
kubb: version,
|
|
1500
1078
|
agent: this.#options.version
|
|
1501
1079
|
},
|
|
@@ -1527,6 +1105,19 @@ var StudioSession = class {
|
|
|
1527
1105
|
await this.#warn(reason, permission);
|
|
1528
1106
|
throw new Error(message);
|
|
1529
1107
|
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Brackets one Studio command with `studio:command:start` and `studio:command:end`. A command
|
|
1110
|
+
* that declined its request reports no `info`, and only the start is heard.
|
|
1111
|
+
*/
|
|
1112
|
+
async #command({ command, run }) {
|
|
1113
|
+
await this.#hooks.callHook("studio:command:start", { command });
|
|
1114
|
+
const { value, info } = await run();
|
|
1115
|
+
if (info !== void 0) await this.#hooks.callHook("studio:command:end", {
|
|
1116
|
+
command,
|
|
1117
|
+
info
|
|
1118
|
+
});
|
|
1119
|
+
return value;
|
|
1120
|
+
}
|
|
1530
1121
|
#scheduleHeartbeat(interval) {
|
|
1531
1122
|
const rpc = this.#rpc;
|
|
1532
1123
|
if (!rpc) return;
|
|
@@ -1556,7 +1147,7 @@ var StudioSession = class {
|
|
|
1556
1147
|
async #load() {
|
|
1557
1148
|
return {
|
|
1558
1149
|
running: this.#isGenerating ? 1 : 0,
|
|
1559
|
-
rssMb: Math.round(
|
|
1150
|
+
rssMb: Math.round(node_process.default.memoryUsage().rss / 1048576),
|
|
1560
1151
|
storeBytes: await this.#generations.bytes(),
|
|
1561
1152
|
accepting: true
|
|
1562
1153
|
};
|
|
@@ -1570,7 +1161,7 @@ var StudioSession = class {
|
|
|
1570
1161
|
async #readConfigFileView(source) {
|
|
1571
1162
|
if (!this.#canEditConfig) return;
|
|
1572
1163
|
try {
|
|
1573
|
-
const { readConfig } = await Promise.resolve().then(() => require("./configFile-
|
|
1164
|
+
const { readConfig } = await Promise.resolve().then(() => require("./configFile-dJtYeqX1.cjs"));
|
|
1574
1165
|
return readConfig(source ?? await require_resolveConfig.read(this.#options.configFile));
|
|
1575
1166
|
} catch (error) {
|
|
1576
1167
|
await this.#warn(`Could not read ${this.#options.configFile}: ${getErrorMessage(error)}`);
|
|
@@ -1638,9 +1229,7 @@ var StudioSession = class {
|
|
|
1638
1229
|
}
|
|
1639
1230
|
}
|
|
1640
1231
|
startGeneration(data) {
|
|
1641
|
-
const generationStream = createGenerationStream(this.#hooks, data.jobId
|
|
1642
|
-
this.#lastGeneration = result;
|
|
1643
|
-
} });
|
|
1232
|
+
const generationStream = createGenerationStream(this.#hooks, data.jobId);
|
|
1644
1233
|
const controller = new AbortController();
|
|
1645
1234
|
const cancelRun = async () => {
|
|
1646
1235
|
controller.abort(/* @__PURE__ */ new Error("Generation canceled"));
|
|
@@ -1675,89 +1264,116 @@ var StudioSession = class {
|
|
|
1675
1264
|
jobId: data.jobId,
|
|
1676
1265
|
cancel: cancelRun
|
|
1677
1266
|
};
|
|
1678
|
-
const command = "generate";
|
|
1679
|
-
const { loadConfig, permissions } = this.#options;
|
|
1680
1267
|
let root = this.#options.root;
|
|
1681
1268
|
try {
|
|
1682
1269
|
if (this.#isSandbox) root = await createJobRoot();
|
|
1683
|
-
await this.#
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
const adapter = await require_resolveConfig.mergeAdapter(config.adapter, patch?.adapter);
|
|
1688
|
-
const inputOverride = this.#isSandbox ? patch?.input ?? "" : void 0;
|
|
1689
|
-
if (permissions.allowWrite && this.#isSandbox) await this.#warn("Running in a sandbox, so writing files is disabled");
|
|
1690
|
-
if (patch?.input && !this.#isSandbox) await this.#warn("Ignored the spec from Studio: generating from a Studio spec is only available to a sandbox agent");
|
|
1691
|
-
const resolvedPlugins = plugins ?? config.plugins;
|
|
1692
|
-
this.#lastGeneration = void 0;
|
|
1693
|
-
const disk = await runGenerationOperation({
|
|
1694
|
-
config: {
|
|
1695
|
-
...config,
|
|
1270
|
+
return await this.#command({
|
|
1271
|
+
command: "generate",
|
|
1272
|
+
run: () => this.#generate({
|
|
1273
|
+
data,
|
|
1696
1274
|
root,
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
output: permissions.allowExec ? { ...config.output } : {
|
|
1700
|
-
...config.output,
|
|
1701
|
-
format: false,
|
|
1702
|
-
lint: false,
|
|
1703
|
-
postGenerate: []
|
|
1704
|
-
},
|
|
1705
|
-
plugins: resolvedPlugins,
|
|
1706
|
-
adapter
|
|
1707
|
-
},
|
|
1708
|
-
hooks: this.#hooks,
|
|
1709
|
-
signal: controller.signal,
|
|
1710
|
-
jobId: data.jobId,
|
|
1711
|
-
store: this.#generations,
|
|
1712
|
-
snapshotRoot: this.#hasProjectOnDisk ? root : void 0,
|
|
1713
|
-
maxSnapshotMb: this.#limits.maxSnapshotMb
|
|
1275
|
+
signal: controller.signal
|
|
1276
|
+
})
|
|
1714
1277
|
});
|
|
1715
|
-
await this.#hooks.callHook("studio:command:end", {
|
|
1716
|
-
command,
|
|
1717
|
-
info: `${resolvedPlugins.length} plugin${resolvedPlugins.length === 1 ? "" : "s"}, ${this.#canWrite ? "written to disk" : "in memory"}${inputOverride !== void 0 ? ", from a Studio spec" : ""}`
|
|
1718
|
-
});
|
|
1719
|
-
const generation = this.#lastGeneration;
|
|
1720
|
-
const output = generation ? await this.#generations.keep({
|
|
1721
|
-
jobId: data.jobId,
|
|
1722
|
-
source: "output",
|
|
1723
|
-
files: generation.output,
|
|
1724
|
-
maxSetMb: this.#limits.maxMb
|
|
1725
|
-
}) : void 0;
|
|
1726
|
-
if (generation && output) {
|
|
1727
|
-
if (!output.paths.length && Object.keys(output.hashes).length) await this.#warn("Kept only the hashes of this generation: its output is too large to keep");
|
|
1728
|
-
const { peerDependencies, missingDependencies } = generation;
|
|
1729
|
-
await this.#generations.add({
|
|
1730
|
-
jobId: data.jobId,
|
|
1731
|
-
output,
|
|
1732
|
-
disk,
|
|
1733
|
-
peerDependencies,
|
|
1734
|
-
missingDependencies
|
|
1735
|
-
});
|
|
1736
|
-
}
|
|
1737
|
-
const files = [...generation?.output.paths ?? []];
|
|
1738
|
-
return {
|
|
1739
|
-
status: "success",
|
|
1740
|
-
files,
|
|
1741
|
-
fileCount: files.length,
|
|
1742
|
-
hashes: output?.hashes ?? {},
|
|
1743
|
-
disk: disk ? { hashes: disk.hashes } : void 0
|
|
1744
|
-
};
|
|
1745
1278
|
} finally {
|
|
1746
|
-
this.#lastGeneration = void 0;
|
|
1747
1279
|
if (root !== this.#options.root) await removeJobRoot(root);
|
|
1748
1280
|
this.#isGenerating = false;
|
|
1749
1281
|
}
|
|
1750
1282
|
}
|
|
1751
|
-
async
|
|
1752
|
-
const
|
|
1753
|
-
|
|
1283
|
+
async #generate({ data, root, signal }) {
|
|
1284
|
+
const { loadConfig, permissions } = this.#options;
|
|
1285
|
+
const config = await loadConfig();
|
|
1286
|
+
const patch = data.config;
|
|
1287
|
+
const plugins = await require_resolveConfig.mergePlugins(config.plugins, patch?.plugins);
|
|
1288
|
+
const adapter = await require_resolveConfig.mergeAdapter(config.adapter, patch?.adapter);
|
|
1289
|
+
const inputOverride = this.#isSandbox ? patch?.input ?? "" : void 0;
|
|
1290
|
+
if (permissions.allowWrite && this.#isSandbox) await this.#warn("Running in a sandbox, so writing files is disabled");
|
|
1291
|
+
if (patch?.input && !this.#isSandbox) await this.#warn("Ignored the spec from Studio: generating from a Studio spec is only available to a sandbox agent");
|
|
1292
|
+
const resolvedPlugins = plugins ?? config.plugins;
|
|
1293
|
+
const storage = this.#canWrite ? (0, _kubb_core.fsStorage)() : (0, _kubb_core.memoryStorage)();
|
|
1294
|
+
const diskFiles = this.#hasProjectOnDisk ? await listDisk({
|
|
1295
|
+
root,
|
|
1296
|
+
outputPath: config.output.path,
|
|
1297
|
+
maxFiles: DISK_SNAPSHOT_MAX_FILES
|
|
1298
|
+
}) : void 0;
|
|
1299
|
+
const disk = diskFiles ? await this.#generations.keep({
|
|
1300
|
+
jobId: data.jobId,
|
|
1301
|
+
source: "disk",
|
|
1302
|
+
files: diskFiles,
|
|
1303
|
+
maxSetMb: this.#limits.maxSnapshotMb
|
|
1304
|
+
}) : void 0;
|
|
1305
|
+
const result = await (0, _kubb_core.createKubb)({
|
|
1306
|
+
...config,
|
|
1307
|
+
root,
|
|
1308
|
+
input: inputOverride ?? config.input,
|
|
1309
|
+
storage,
|
|
1310
|
+
output: permissions.allowExec ? { ...config.output } : {
|
|
1311
|
+
...config.output,
|
|
1312
|
+
format: false,
|
|
1313
|
+
lint: false,
|
|
1314
|
+
postGenerate: []
|
|
1315
|
+
},
|
|
1316
|
+
plugins: resolvedPlugins,
|
|
1317
|
+
adapter
|
|
1318
|
+
}, {
|
|
1319
|
+
hooks: this.#hooks,
|
|
1320
|
+
signal
|
|
1321
|
+
}).generate().catch(async (error) => {
|
|
1322
|
+
await this.#generations.drop(data.jobId);
|
|
1323
|
+
throw error;
|
|
1324
|
+
});
|
|
1325
|
+
if (!result.success) {
|
|
1326
|
+
await this.#generations.drop(data.jobId);
|
|
1327
|
+
throw generationFailure(result.diagnostics);
|
|
1328
|
+
}
|
|
1329
|
+
const paths = new Set(result.files.map((file) => relativeStoragePath({
|
|
1330
|
+
root,
|
|
1331
|
+
filePath: file.path
|
|
1332
|
+
})));
|
|
1333
|
+
const output = await this.#generations.keep({
|
|
1334
|
+
jobId: data.jobId,
|
|
1335
|
+
source: "output",
|
|
1336
|
+
files: {
|
|
1337
|
+
storage,
|
|
1338
|
+
root,
|
|
1339
|
+
paths
|
|
1340
|
+
},
|
|
1341
|
+
maxSetMb: this.#limits.maxMb
|
|
1342
|
+
});
|
|
1343
|
+
if (!output.paths.length && Object.keys(output.hashes).length) await this.#warn("Kept only the hashes of this generation: its output is too large to keep");
|
|
1344
|
+
const { peerDependencies, missingDependencies } = await require_resolveConfig.resolvePeerDependencies(resolvedPlugins.map(({ name }) => name));
|
|
1345
|
+
await this.#generations.add({
|
|
1346
|
+
jobId: data.jobId,
|
|
1347
|
+
output,
|
|
1348
|
+
disk,
|
|
1349
|
+
peerDependencies,
|
|
1350
|
+
missingDependencies
|
|
1351
|
+
});
|
|
1352
|
+
return {
|
|
1353
|
+
value: {
|
|
1354
|
+
status: "success",
|
|
1355
|
+
files: [...paths],
|
|
1356
|
+
fileCount: paths.size,
|
|
1357
|
+
hashes: output.hashes,
|
|
1358
|
+
disk: disk ? { hashes: disk.hashes } : void 0
|
|
1359
|
+
},
|
|
1360
|
+
info: `${resolvedPlugins.length} plugin${resolvedPlugins.length === 1 ? "" : "s"}, ${this.#canWrite ? "written to disk" : "in memory"}${inputOverride !== void 0 ? ", from a Studio spec" : ""}`
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
saveConfig(data) {
|
|
1364
|
+
return this.#command({
|
|
1365
|
+
command: "saveConfig",
|
|
1366
|
+
run: () => this.#saveConfig(data)
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
async #saveConfig(data) {
|
|
1754
1370
|
const { configPath, configFile } = this.#options;
|
|
1755
1371
|
if (!Array.isArray(data.edits)) {
|
|
1756
1372
|
await this.#warn("Ignored save: the message carried no edits");
|
|
1757
|
-
return {
|
|
1373
|
+
return { value: {
|
|
1758
1374
|
outcomes: [],
|
|
1759
1375
|
changed: false
|
|
1760
|
-
};
|
|
1376
|
+
} };
|
|
1761
1377
|
}
|
|
1762
1378
|
const edits = data.edits;
|
|
1763
1379
|
const refuse = (reason) => ({
|
|
@@ -1770,33 +1386,36 @@ var StudioSession = class {
|
|
|
1770
1386
|
});
|
|
1771
1387
|
if (!this.#canEditConfig) {
|
|
1772
1388
|
await this.#warn("Ignored save: editing kubb.config.ts was not granted");
|
|
1773
|
-
return refuse("the agent was not granted permission to edit kubb.config.ts");
|
|
1389
|
+
return { value: refuse("the agent was not granted permission to edit kubb.config.ts") };
|
|
1774
1390
|
}
|
|
1775
|
-
if (this.#isGenerating) return refuse("a generation is in progress");
|
|
1391
|
+
if (this.#isGenerating) return { value: refuse("a generation is in progress") };
|
|
1776
1392
|
try {
|
|
1777
|
-
const { writeConfigEdits } = await Promise.resolve().then(() => require("./configFile-
|
|
1393
|
+
const { writeConfigEdits } = await Promise.resolve().then(() => require("./configFile-dJtYeqX1.cjs"));
|
|
1778
1394
|
const { source: patched, outcomes, changed } = await writeConfigEdits({
|
|
1779
1395
|
filePath: configFile,
|
|
1780
1396
|
edits
|
|
1781
1397
|
});
|
|
1782
1398
|
const applied = outcomes.filter((outcome) => outcome.applied).length;
|
|
1783
|
-
await this.#hooks.callHook("studio:command:end", {
|
|
1784
|
-
command,
|
|
1785
|
-
info: `applied ${applied}/${outcomes.length} edits to ${configPath}`
|
|
1786
|
-
});
|
|
1787
1399
|
return {
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1400
|
+
value: {
|
|
1401
|
+
outcomes,
|
|
1402
|
+
changed,
|
|
1403
|
+
file: changed ? await this.#readConfigFileView(patched) : void 0
|
|
1404
|
+
},
|
|
1405
|
+
info: `applied ${applied}/${outcomes.length} edits to ${configPath}`
|
|
1791
1406
|
};
|
|
1792
1407
|
} catch (error) {
|
|
1793
1408
|
await this.#hooks.callHook("studio:error", { error: toError(error) });
|
|
1794
|
-
return refuse(getErrorMessage(error));
|
|
1409
|
+
return { value: refuse(getErrorMessage(error)) };
|
|
1795
1410
|
}
|
|
1796
1411
|
}
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1412
|
+
publishSnapshot(data) {
|
|
1413
|
+
return this.#command({
|
|
1414
|
+
command: "snapshot",
|
|
1415
|
+
run: () => this.#publishSnapshot(data)
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
async #publishSnapshot(data) {
|
|
1800
1419
|
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");
|
|
1801
1420
|
const { name, version, bundledDependencies, uploadPath } = data;
|
|
1802
1421
|
if (!name || !version || !uploadPath) return this.#refuse("Ignored snapshot: the message was missing required fields", "The request was missing required fields");
|
|
@@ -1806,7 +1425,7 @@ var StudioSession = class {
|
|
|
1806
1425
|
const missing = generation.missingDependencies.filter((dependency) => !bundled.has(dependency));
|
|
1807
1426
|
if (missing.length) return this.#refuse(`Ignored snapshot: missing dependencies: ${missing.join(", ")}`, `Missing dependencies: ${missing.join(", ")}`);
|
|
1808
1427
|
try {
|
|
1809
|
-
const { createSnapshotPackage, uploadSnapshot } = await Promise.resolve().then(() => require("./snapshotPackage-
|
|
1428
|
+
const { createSnapshotPackage, uploadSnapshot } = await Promise.resolve().then(() => require("./snapshotPackage-BFjiRhGd.cjs"));
|
|
1810
1429
|
const { files } = await this.#generations.read({
|
|
1811
1430
|
generation,
|
|
1812
1431
|
source: "output",
|
|
@@ -1825,13 +1444,12 @@ var StudioSession = class {
|
|
|
1825
1444
|
token,
|
|
1826
1445
|
shutdown: signal
|
|
1827
1446
|
});
|
|
1828
|
-
await this.#hooks.callHook("studio:command:end", {
|
|
1829
|
-
command,
|
|
1830
|
-
info: `packed ${Object.keys(files).length} file${Object.keys(files).length === 1 ? "" : "s"}`
|
|
1831
|
-
});
|
|
1832
1447
|
return {
|
|
1833
|
-
|
|
1834
|
-
|
|
1448
|
+
value: {
|
|
1449
|
+
integrity,
|
|
1450
|
+
peerDependencies: generation.peerDependencies
|
|
1451
|
+
},
|
|
1452
|
+
info: `packed ${Object.keys(files).length} file${Object.keys(files).length === 1 ? "" : "s"}`
|
|
1835
1453
|
};
|
|
1836
1454
|
} catch (error) {
|
|
1837
1455
|
await this.#hooks.callHook("studio:error", { error: toError(error) });
|
|
@@ -1842,9 +1460,13 @@ var StudioSession = class {
|
|
|
1842
1460
|
get #hasProjectOnDisk() {
|
|
1843
1461
|
return !this.#isSandbox && this.#canRead;
|
|
1844
1462
|
}
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1463
|
+
readFiles(data) {
|
|
1464
|
+
return this.#command({
|
|
1465
|
+
command: "readFiles",
|
|
1466
|
+
run: () => this.#readFiles(data)
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
async #readFiles(data) {
|
|
1848
1470
|
if (!this.#canRead) return this.#refuse("Ignored files: reading generated files was not granted", "The agent was not granted permission to read generated files", "allowRead");
|
|
1849
1471
|
if (!Array.isArray(data.paths)) return this.#refuse("Ignored files: the message carried no paths", "The request carried no paths");
|
|
1850
1472
|
const { paths } = data;
|
|
@@ -1862,11 +1484,10 @@ var StudioSession = class {
|
|
|
1862
1484
|
cursor: data.cursor,
|
|
1863
1485
|
limit: data.limit
|
|
1864
1486
|
});
|
|
1865
|
-
|
|
1866
|
-
|
|
1487
|
+
return {
|
|
1488
|
+
value: result,
|
|
1867
1489
|
info: `read ${Object.keys(result.files).length}/${paths.length} requested file${paths.length === 1 ? "" : "s"}`
|
|
1868
|
-
}
|
|
1869
|
-
return result;
|
|
1490
|
+
};
|
|
1870
1491
|
}
|
|
1871
1492
|
};
|
|
1872
1493
|
//#endregion
|