@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/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-D7I3Nj60.cjs");
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 stores a ping at most once a minute, so
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.5.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$1 = 1048576;
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$1;
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()).reduce((sum, { output, disk }) => sum + output.bytes + (disk?.bytes ?? 0), 0),
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
- const weight = () => entries.reduce((sum, { output, disk }) => sum + output.bytes + (disk?.bytes ?? 0), 0);
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, options = {}) {
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
- on("kubb:plugin:start", (ctx) => {
793
- emitEvent("kubb:plugin:start", [{ plugin: { name: ctx.plugin.name } }]);
794
- });
795
- on("kubb:plugin:end", (ctx) => {
796
- emitEvent("kubb:plugin:end", [{
797
- plugin: { name: ctx.plugin.name },
798
- duration: ctx.duration,
799
- success: ctx.success
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(config.root, file.path),
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
- on("kubb:files:processing:start", ({ files }) => {
819
- emitEvent("kubb:files:processing:start", [{ total: files.length }]);
820
- });
821
- on("kubb:files:processing:update", ({ files }) => {
822
- emitEvent("kubb:files:processing:update", [{ files: files.map(({ file, processed, total, percentage }) => ({
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
- on("kubb:files:processing:end", ({ files }) => {
830
- emitEvent("kubb:files:processing:end", [{ total: files.length }]);
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
- on("kubb:generation:start", ({ config }) => {
843
- emitEvent("kubb:generation:start", [{
844
- name: config.name,
845
- plugins: config.plugins.length
846
- }]);
847
- });
848
- on("kubb:generation:end", async ({ config, storage, diagnostics = [], status, hrStart, filesCreated }) => {
849
- const { peerDependencies, missingDependencies } = await resolvePeerDependencies(config.plugins.map(({ name }) => name));
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
- for (const type of [
889
- "kubb:lifecycle:start",
890
- "kubb:lifecycle:end",
891
- "kubb:format:start",
892
- "kubb:format:end",
893
- "kubb:lint:start",
894
- "kubb:lint:end",
895
- "kubb:hooks:start",
896
- "kubb:hooks:end"
897
- ]) on(type, () => {
898
- emitEvent(type, []);
899
- });
900
- on("kubb:hook:start", ({ id, command, args }) => {
901
- emitEvent("kubb:hook:start", [{
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 === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]"))) throw new Error(`Refusing unencrypted WebSocket to ${host}`);
1011
- const socket = createWebsocket(url, { headers: {
1012
- Authorization: `Bearer ${token}`,
1013
- [require_protocol.AGENT_INSTANCE_HEADER]: instanceId
1014
- } });
1015
- const closed = new Promise((resolve) => socket.once("close", (code, reason) => resolve({
1016
- code,
1017
- reason: reason.toString()
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/operations/hooks.ts
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
- * `isToolAvailable` spawns a process, and a long-lived connection generates repeatedly, so each
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
- async function runHook({ hooks, id, command, args }) {
1149
- const hookId = (0, node_crypto.hash)("sha256", id);
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: this.#studioVersion,
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(rssMb()),
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-BTVeov27.cjs"));
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, { onGenerationEnd: (result) => {
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.#hooks.callHook("studio:command:start", { command });
1684
- const config = await loadConfig();
1685
- const patch = data.config;
1686
- const plugins = await require_resolveConfig.mergePlugins(config.plugins, patch?.plugins);
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
- input: inputOverride ?? config.input,
1698
- storage: this.#canWrite ? (0, _kubb_core.fsStorage)() : (0, _kubb_core.memoryStorage)(),
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 saveConfig(data) {
1752
- const command = "saveConfig";
1753
- await this.#hooks.callHook("studio:command:start", { command });
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-BTVeov27.cjs"));
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
- outcomes,
1789
- changed,
1790
- file: changed ? await this.#readConfigFileView(patched) : void 0
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
- async publishSnapshot(data) {
1798
- const command = "snapshot";
1799
- await this.#hooks.callHook("studio:command:start", { command });
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-BwM3LFmZ.cjs"));
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
- integrity,
1834
- peerDependencies: generation.peerDependencies
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
- async readFiles(data) {
1846
- const command = "readFiles";
1847
- await this.#hooks.callHook("studio:command:start", { command });
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
- await this.#hooks.callHook("studio:command:end", {
1866
- command,
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