@kubb/studio 5.3.11 → 5.3.12

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.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as __name } from "./rolldown-runtime-CRm0XQPb.js";
2
- import { AgentApi, AgentPermissions, ClientInfo, ConfigEdit, ConnectMessagePayload, GenerateInput, GenerateResult, GenerationEvent, GenerationEventPayloads, GenerationEventType, GenerationRun, PublishSnapshotInput, PublishSnapshotResult, RpcConnection, RpcConnector, StudioApi, generationEventTypes } from "./protocol.js";
2
+ import { AgentApi, AgentPermissions, ClientInfo, ConfigEdit, ConnectMessagePayload, FileChange, GenerateInput, GenerateResult, GenerationEvent, GenerationEventPayloads, GenerationEventType, GenerationRun, PublishSnapshotInput, PublishSnapshotResult, RpcConnection, RpcConnector, StudioApi, generationEventTypes } from "./protocol.js";
3
3
  import { Config, Hookable, KubbHooks } from "@kubb/core";
4
4
  import { Storage } from "unstorage";
5
5
  //#region src/api.d.ts
@@ -554,5 +554,5 @@ export declare function pollForPairingToken({ studioUrl, session, signal }: Poll
554
554
  */
555
555
  export declare const connectWebSocketRpc: RpcConnector;
556
556
  //#endregion
557
- export { type AgentApi, type Client, type ClientOptions, type ConfigEdit, type ConnectMessagePayload, type ConnectionOptions, type GenerateInput, type GenerateResult, type GenerationEvent, type GenerationEventPayloads, type GenerationEventType, type GenerationRun, type PublishSnapshotInput, type PublishSnapshotResult, type RpcConnection, type RpcConnector, type StudioAgent, type StudioApi, type StudioConnectedContext, type StudioJob, type StudioJobStatus, type StudioSnapshot, generationEventTypes };
557
+ export { type AgentApi, type Client, type ClientOptions, type ConfigEdit, type ConnectMessagePayload, type ConnectionOptions, type FileChange, type GenerateInput, type GenerateResult, type GenerationEvent, type GenerationEventPayloads, type GenerationEventType, type GenerationRun, type PublishSnapshotInput, type PublishSnapshotResult, type RpcConnection, type RpcConnector, type StudioAgent, type StudioApi, type StudioConnectedContext, type StudioJob, type StudioJobStatus, type StudioSnapshot, generationEventTypes };
558
558
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -676,7 +676,7 @@ async function createAgent({ studioUrl, token, name, machineToken }) {
676
676
  }
677
677
  //#endregion
678
678
  //#region package.json
679
- var version = "5.3.11";
679
+ var version = "5.3.12";
680
680
  //#endregion
681
681
  //#region src/hooks.ts
682
682
  /**
@@ -1906,7 +1906,7 @@ function createGenerationStream(hooks, jobId, options = {}) {
1906
1906
  const { peerDependencies, missingDependencies } = await resolvePeerDependencies(config.plugins.map(({ name }) => name));
1907
1907
  const keys = await storage.readKeys();
1908
1908
  const paths = new Set(keys.map((key) => relativeStoragePath(config.root, key)));
1909
- options.onGenerationEnd?.({
1909
+ if ((status ?? "success") === "success") options.onGenerationEnd?.({
1910
1910
  storage,
1911
1911
  root: config.root,
1912
1912
  paths,
@@ -2063,6 +2063,36 @@ const connectWebSocketRpc = async ({ url, token, local }) => {
2063
2063
  * How many files are read from storage at once when serving `readFiles` or packing a snapshot.
2064
2064
  */
2065
2065
  const FILE_READ_CONCURRENCY = 50;
2066
+ /**
2067
+ * Reads every file a run produced back out of its storage.
2068
+ */
2069
+ async function readSnapshot(generation) {
2070
+ const snapshot = /* @__PURE__ */ new Map();
2071
+ await inParallel({
2072
+ items: [...generation.paths],
2073
+ limit: FILE_READ_CONCURRENCY,
2074
+ run: async (path) => {
2075
+ const content = await generation.storage.readItem(absoluteStoragePath(generation.root, path));
2076
+ if (content !== null) snapshot.set(path, content);
2077
+ }
2078
+ });
2079
+ return snapshot;
2080
+ }
2081
+ /**
2082
+ * How each path differs between two runs. Paths with identical content are left out.
2083
+ */
2084
+ function diffSnapshots(previous, current) {
2085
+ const changes = {};
2086
+ for (const [path, content] of current) {
2087
+ if (!previous.has(path)) {
2088
+ changes[path] = "added";
2089
+ continue;
2090
+ }
2091
+ if (previous.get(path) !== content) changes[path] = "changed";
2092
+ }
2093
+ for (const path of previous.keys()) if (!current.has(path)) changes[path] = "removed";
2094
+ return changes;
2095
+ }
2066
2096
  var GenerationRunTarget = class extends RpcTarget {
2067
2097
  generationStream;
2068
2098
  generationResult;
@@ -2161,6 +2191,7 @@ var StudioSession = class {
2161
2191
  #isGenerating = false;
2162
2192
  #heartbeatTimer;
2163
2193
  #lastGeneration;
2194
+ #previousGeneration;
2164
2195
  /**
2165
2196
  * Resolves when Studio calls {@link StudioSession.connect}. `studio:ready` waits on this so the
2166
2197
  * host does not queue jobs before the agent session is registered.
@@ -2385,6 +2416,7 @@ var StudioSession = class {
2385
2416
  await this.#warn(`Ignored the spec from Studio; set ${remedy} to generate from it`);
2386
2417
  }
2387
2418
  const resolvedPlugins = plugins ?? config.plugins;
2419
+ if (this.#lastGeneration) this.#previousGeneration = await readSnapshot(this.#lastGeneration);
2388
2420
  this.#lastGeneration = void 0;
2389
2421
  const detach = [setupHookListener(this.#hooks, root, controller.signal)];
2390
2422
  try {
@@ -2413,12 +2445,20 @@ var StudioSession = class {
2413
2445
  command,
2414
2446
  info: `${resolvedPlugins.length} plugin${resolvedPlugins.length === 1 ? "" : "s"}, ${this.#canWrite ? "written to disk" : "in memory"}${inputOverride !== void 0 ? ", from a Studio spec" : ""}`
2415
2447
  });
2416
- const files = [...this.#lastGeneration?.paths ?? []];
2417
- return {
2448
+ const generation = this.#lastGeneration;
2449
+ const files = [...generation?.paths ?? []];
2450
+ const previous = this.#previousGeneration;
2451
+ if (!generation || !previous) return {
2418
2452
  status: "success",
2419
2453
  files,
2420
2454
  fileCount: files.length
2421
2455
  };
2456
+ return {
2457
+ status: "success",
2458
+ files,
2459
+ fileCount: files.length,
2460
+ changes: diffSnapshots(previous, await readSnapshot(generation))
2461
+ };
2422
2462
  } finally {
2423
2463
  this.#isGenerating = false;
2424
2464
  }
@@ -2535,6 +2575,16 @@ var StudioSession = class {
2535
2575
  if (!Array.isArray(data.paths)) return this.#refuse("Ignored files: the message carried no paths", "The request carried no paths");
2536
2576
  const { paths } = data;
2537
2577
  if (paths.length > 50) return this.#refuse(`Ignored files: requested ${paths.length} paths, more than the 50 allowed per request`, `At most 50 paths may be requested at once`);
2578
+ if (data.revision === "previous") {
2579
+ const previous = this.#previousGeneration;
2580
+ if (!previous) return this.#refuse("Ignored files: no previous generation to read from", "No previous generation to compare against");
2581
+ const files = Object.fromEntries(paths.filter((path) => previous.has(path)).map((path) => [path, previous.get(path)]));
2582
+ await this.#hooks.callHook("studio:command:end", {
2583
+ command,
2584
+ info: `read ${Object.keys(files).length}/${paths.length} requested file${paths.length === 1 ? "" : "s"} from the previous run`
2585
+ });
2586
+ return { files };
2587
+ }
2538
2588
  const generation = this.#lastGeneration;
2539
2589
  if (!generation) return this.#refuse("Ignored files: no prior generation to read from", "No prior generation to read from, run a generation first");
2540
2590
  const requested = paths.filter((path) => generation.paths.has(path));