@kubb/studio 5.2.4 → 5.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -564,12 +564,14 @@ async function disconnect({ sessionId, token, studioUrl, slug }) {
564
564
  });
565
565
  console.log((0, node_util.styleText)("green", `[${tag}] Disconnected from Studio`));
566
566
  } catch (error) {
567
+ const statusCode = error?.statusCode;
568
+ if (statusCode !== void 0 && statusCode >= 400 && statusCode < 500) return;
567
569
  console.warn((0, node_util.styleText)("yellow", `[${tag}] Failed to notify Studio of disconnection: ${getErrorMessage(error)}`));
568
570
  }
569
571
  }
570
572
  //#endregion
571
573
  //#region package.json
572
- var version = "5.2.4";
574
+ var version = "5.2.6";
573
575
  //#endregion
574
576
  //#region src/hooks.ts
575
577
  /**
@@ -1522,6 +1524,9 @@ const CONNECT_TIMEOUT_MS = 5e3;
1522
1524
  */
1523
1525
  const eventSeqCounters = /* @__PURE__ */ new WeakMap();
1524
1526
  const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
1527
+ function relativeStoragePath(root, filePath) {
1528
+ return ((0, node_path.isAbsolute)(filePath) ? (0, node_path.relative)((0, node_path.resolve)(root), filePath) : filePath).replaceAll("\\", "/");
1529
+ }
1525
1530
  async function resolvePeerDependencies(names) {
1526
1531
  const uniqueNames = [...new Set(names.map(toPackageName))];
1527
1532
  const peerDependencies = {};
@@ -1576,9 +1581,10 @@ function sendAgentMessage(ws$3, message) {
1576
1581
  * Sends a single `kubb:error` payload to Studio, stamped from the same per-socket counter the event stream
1577
1582
  * uses so Studio can still order it against the generation events around it.
1578
1583
  */
1579
- function sendErrorMessage(ws$4, error) {
1584
+ function sendErrorMessage(ws$4, error, jobId) {
1580
1585
  sendAgentMessage(ws$4, {
1581
1586
  type: "agent:data",
1587
+ jobId,
1582
1588
  payload: {
1583
1589
  type: "kubb:error",
1584
1590
  data: [{
@@ -1593,7 +1599,7 @@ function sendErrorMessage(ws$4, error) {
1593
1599
  /**
1594
1600
  * Forwards selected Kubb lifecycle events to Studio as data messages for the active session.
1595
1601
  */
1596
- function setupEventsStream(ws$5, hooks) {
1602
+ function setupEventsStream(ws$5, hooks, jobId) {
1597
1603
  const unhooks = [];
1598
1604
  /**
1599
1605
  * Registers a listener and keeps its remover, so one generation's listeners come off the session
@@ -1605,6 +1611,7 @@ function setupEventsStream(ws$5, hooks) {
1605
1611
  function sendDataMessage(payload) {
1606
1612
  sendAgentMessage(ws$5, {
1607
1613
  type: "agent:data",
1614
+ jobId,
1608
1615
  payload: {
1609
1616
  ...payload,
1610
1617
  timestamp: Date.now(),
@@ -1703,7 +1710,7 @@ function setupEventsStream(ws$5, hooks) {
1703
1710
  limit: FILE_READ_CONCURRENCY,
1704
1711
  run: async (path) => {
1705
1712
  const content = await storage.readItem(path);
1706
- if (content !== null) files[path] = content;
1713
+ if (content !== null) files[relativeStoragePath(config.root, path)] = content;
1707
1714
  }
1708
1715
  });
1709
1716
  sendDataMessage({
@@ -1791,6 +1798,12 @@ function setupEventsStream(ws$5, hooks) {
1791
1798
  //#endregion
1792
1799
  //#region src/StudioSession.ts
1793
1800
  /**
1801
+ * How long the `agent:connect` handshake may go unacknowledged before `studio:ready` is given up
1802
+ * on for this open. A Studio that predates the ack never sends one, so this only ever produces a
1803
+ * warning, not a reconnect.
1804
+ */
1805
+ const READY_TIMEOUT_MS = 1e4;
1806
+ /**
1794
1807
  * Fills in a host's options: the hosted Studio URL, the current working directory, and every
1795
1808
  * permission off unless granted. Idempotent, so a reconnect can pass an already-resolved bag
1796
1809
  * back in.
@@ -1858,9 +1871,11 @@ var StudioSession = class {
1858
1871
  #session;
1859
1872
  #ws;
1860
1873
  #studioVersion;
1874
+ #activeJobId = null;
1861
1875
  #disposed = false;
1862
1876
  #isGenerating = false;
1863
1877
  #heartbeatTimer;
1878
+ #readyTimer;
1864
1879
  #lastPongAt = Date.now();
1865
1880
  constructor(options) {
1866
1881
  this.#options = applyStudioDefaults(options);
@@ -1904,7 +1919,7 @@ var StudioSession = class {
1904
1919
  signal?.addEventListener("abort", this.#onAbort, { once: true });
1905
1920
  this.#unhooks.push(() => signal?.removeEventListener("abort", this.#onAbort));
1906
1921
  this.#heartbeatTimer = setInterval(() => this.#sendHeartbeat(), heartbeatInterval);
1907
- this.#unhooks.push(this.#hooks.hook("kubb:error", ({ error }) => sendErrorMessage(ws, error)));
1922
+ this.#unhooks.push(this.#hooks.hook("kubb:error", ({ error }) => sendErrorMessage(ws, error, this.#activeJobId ?? "connection")));
1908
1923
  } catch (error) {
1909
1924
  await this.#hooks.callHook("studio:error", { error: toError(error) });
1910
1925
  if (error instanceof InvalidAgentTokenError) throw error;
@@ -1994,10 +2009,24 @@ var StudioSession = class {
1994
2009
  });
1995
2010
  try {
1996
2011
  await this.#sendConnectedPayload();
2012
+ this.#waitForReady();
1997
2013
  } catch (error) {
1998
2014
  await this.#warn(`Failed to send the connect payload: ${getErrorMessage(error)}`);
1999
2015
  }
2000
2016
  }
2017
+ /**
2018
+ * Arms the timeout for Studio's `studio:ready` acknowledgement. Re-armed on every open, since a
2019
+ * command sent while the agent is not attached is lost either way (see `#sendConnectedPayload`),
2020
+ * so each reconnect needs its own fresh wait.
2021
+ */
2022
+ #waitForReady() {
2023
+ if (this.#disposed) return;
2024
+ clearTimeout(this.#readyTimer);
2025
+ this.#readyTimer = setTimeout(() => {
2026
+ this.#readyTimer = void 0;
2027
+ Promise.resolve(this.#warn(`Kubb Studio did not confirm the connection was ready within ${READY_TIMEOUT_MS}ms`)).catch(() => {});
2028
+ }, READY_TIMEOUT_MS);
2029
+ }
2001
2030
  #onOpen = () => void this.#handleOpen().catch(() => {});
2002
2031
  #onAbort = () => void this.#end({
2003
2032
  reason: "shutdown",
@@ -2017,6 +2046,8 @@ var StudioSession = class {
2017
2046
  dispose(reason = "cleanup") {
2018
2047
  clearInterval(this.#heartbeatTimer);
2019
2048
  this.#heartbeatTimer = void 0;
2049
+ clearTimeout(this.#readyTimer);
2050
+ this.#readyTimer = void 0;
2020
2051
  try {
2021
2052
  this.#ws?.close(1e3, reason);
2022
2053
  } catch {}
@@ -2051,6 +2082,12 @@ var StudioSession = class {
2051
2082
  this.#lastPongAt = Date.now();
2052
2083
  return;
2053
2084
  }
2085
+ if (require_protocol.isStudioReadyMessage(data)) {
2086
+ clearTimeout(this.#readyTimer);
2087
+ this.#readyTimer = void 0;
2088
+ await this.#hooks.callHook("studio:ready", {});
2089
+ return;
2090
+ }
2054
2091
  if (require_protocol.isDisconnectMessage(data)) {
2055
2092
  await this.#handleDisconnect(data.reason);
2056
2093
  return;
@@ -2103,6 +2140,7 @@ var StudioSession = class {
2103
2140
  return;
2104
2141
  }
2105
2142
  this.#isGenerating = true;
2143
+ this.#activeJobId = data.jobId;
2106
2144
  try {
2107
2145
  const config = await loadConfig();
2108
2146
  const patch = data.payload;
@@ -2115,7 +2153,7 @@ var StudioSession = class {
2115
2153
  await this.#warn(`Ignored the spec from Studio; set ${remedy} to generate from it`);
2116
2154
  }
2117
2155
  const resolvedPlugins = plugins ?? config.plugins;
2118
- const detach = [setupHookListener(this.#hooks, root), setupEventsStream(ws, this.#hooks)];
2156
+ const detach = [setupHookListener(this.#hooks, root), setupEventsStream(ws, this.#hooks, data.jobId)];
2119
2157
  try {
2120
2158
  await generate({
2121
2159
  config: {
@@ -2143,6 +2181,7 @@ var StudioSession = class {
2143
2181
  });
2144
2182
  } finally {
2145
2183
  this.#isGenerating = false;
2184
+ this.#activeJobId = null;
2146
2185
  }
2147
2186
  }
2148
2187
  async #handleSave(ws, data, command) {
@@ -2151,6 +2190,7 @@ var StudioSession = class {
2151
2190
  await this.#warn("Ignored save: the message carried no edits");
2152
2191
  sendAgentMessage(ws, {
2153
2192
  type: "agent:save",
2193
+ jobId: data.jobId,
2154
2194
  payload: {
2155
2195
  outcomes: [],
2156
2196
  changed: false
@@ -2161,6 +2201,7 @@ var StudioSession = class {
2161
2201
  const edits = data.edits;
2162
2202
  const refuse = (reason) => sendAgentMessage(ws, {
2163
2203
  type: "agent:save",
2204
+ jobId: data.jobId,
2164
2205
  payload: {
2165
2206
  outcomes: edits.map((edit) => ({
2166
2207
  edit,
@@ -2184,6 +2225,7 @@ var StudioSession = class {
2184
2225
  if (changed) await (0, node_fs_promises.writeFile)(configFile, patched, "utf-8");
2185
2226
  sendAgentMessage(ws, {
2186
2227
  type: "agent:save",
2228
+ jobId: data.jobId,
2187
2229
  payload: {
2188
2230
  outcomes,
2189
2231
  changed,
@@ -2409,6 +2451,7 @@ exports.InvalidAgentTokenError = InvalidAgentTokenError;
2409
2451
  exports.PairingCanceledError = PairingCanceledError;
2410
2452
  exports.createClient = createClient;
2411
2453
  exports.createFileStorage = createFileStorage;
2454
+ exports.createJobId = require_protocol.createJobId;
2412
2455
  exports.defaultStudioUrl = defaultStudioUrl;
2413
2456
  exports.pollForPairingToken = pollForPairingToken;
2414
2457
  exports.runConnection = runConnection;