@kubb/studio 5.3.10 → 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.cjs CHANGED
@@ -706,7 +706,7 @@ async function createAgent({ studioUrl, token, name, machineToken }) {
706
706
  }
707
707
  //#endregion
708
708
  //#region package.json
709
- var version = "5.3.10";
709
+ var version = "5.3.12";
710
710
  //#endregion
711
711
  //#region src/hooks.ts
712
712
  /**
@@ -1936,7 +1936,7 @@ function createGenerationStream(hooks, jobId, options = {}) {
1936
1936
  const { peerDependencies, missingDependencies } = await resolvePeerDependencies(config.plugins.map(({ name }) => name));
1937
1937
  const keys = await storage.readKeys();
1938
1938
  const paths = new Set(keys.map((key) => relativeStoragePath(config.root, key)));
1939
- options.onGenerationEnd?.({
1939
+ if ((status ?? "success") === "success") options.onGenerationEnd?.({
1940
1940
  storage,
1941
1941
  root: config.root,
1942
1942
  paths,
@@ -2093,6 +2093,36 @@ const connectWebSocketRpc = async ({ url, token, local }) => {
2093
2093
  * How many files are read from storage at once when serving `readFiles` or packing a snapshot.
2094
2094
  */
2095
2095
  const FILE_READ_CONCURRENCY = 50;
2096
+ /**
2097
+ * Reads every file a run produced back out of its storage.
2098
+ */
2099
+ async function readSnapshot(generation) {
2100
+ const snapshot = /* @__PURE__ */ new Map();
2101
+ await inParallel({
2102
+ items: [...generation.paths],
2103
+ limit: FILE_READ_CONCURRENCY,
2104
+ run: async (path) => {
2105
+ const content = await generation.storage.readItem(absoluteStoragePath(generation.root, path));
2106
+ if (content !== null) snapshot.set(path, content);
2107
+ }
2108
+ });
2109
+ return snapshot;
2110
+ }
2111
+ /**
2112
+ * How each path differs between two runs. Paths with identical content are left out.
2113
+ */
2114
+ function diffSnapshots(previous, current) {
2115
+ const changes = {};
2116
+ for (const [path, content] of current) {
2117
+ if (!previous.has(path)) {
2118
+ changes[path] = "added";
2119
+ continue;
2120
+ }
2121
+ if (previous.get(path) !== content) changes[path] = "changed";
2122
+ }
2123
+ for (const path of previous.keys()) if (!current.has(path)) changes[path] = "removed";
2124
+ return changes;
2125
+ }
2096
2126
  var GenerationRunTarget = class extends capnweb.RpcTarget {
2097
2127
  generationStream;
2098
2128
  generationResult;
@@ -2191,6 +2221,7 @@ var StudioSession = class {
2191
2221
  #isGenerating = false;
2192
2222
  #heartbeatTimer;
2193
2223
  #lastGeneration;
2224
+ #previousGeneration;
2194
2225
  /**
2195
2226
  * Resolves when Studio calls {@link StudioSession.connect}. `studio:ready` waits on this so the
2196
2227
  * host does not queue jobs before the agent session is registered.
@@ -2415,6 +2446,7 @@ var StudioSession = class {
2415
2446
  await this.#warn(`Ignored the spec from Studio; set ${remedy} to generate from it`);
2416
2447
  }
2417
2448
  const resolvedPlugins = plugins ?? config.plugins;
2449
+ if (this.#lastGeneration) this.#previousGeneration = await readSnapshot(this.#lastGeneration);
2418
2450
  this.#lastGeneration = void 0;
2419
2451
  const detach = [setupHookListener(this.#hooks, root, controller.signal)];
2420
2452
  try {
@@ -2443,12 +2475,20 @@ var StudioSession = class {
2443
2475
  command,
2444
2476
  info: `${resolvedPlugins.length} plugin${resolvedPlugins.length === 1 ? "" : "s"}, ${this.#canWrite ? "written to disk" : "in memory"}${inputOverride !== void 0 ? ", from a Studio spec" : ""}`
2445
2477
  });
2446
- const files = [...this.#lastGeneration?.paths ?? []];
2447
- return {
2478
+ const generation = this.#lastGeneration;
2479
+ const files = [...generation?.paths ?? []];
2480
+ const previous = this.#previousGeneration;
2481
+ if (!generation || !previous) return {
2448
2482
  status: "success",
2449
2483
  files,
2450
2484
  fileCount: files.length
2451
2485
  };
2486
+ return {
2487
+ status: "success",
2488
+ files,
2489
+ fileCount: files.length,
2490
+ changes: diffSnapshots(previous, await readSnapshot(generation))
2491
+ };
2452
2492
  } finally {
2453
2493
  this.#isGenerating = false;
2454
2494
  }
@@ -2565,6 +2605,16 @@ var StudioSession = class {
2565
2605
  if (!Array.isArray(data.paths)) return this.#refuse("Ignored files: the message carried no paths", "The request carried no paths");
2566
2606
  const { paths } = data;
2567
2607
  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`);
2608
+ if (data.revision === "previous") {
2609
+ const previous = this.#previousGeneration;
2610
+ if (!previous) return this.#refuse("Ignored files: no previous generation to read from", "No previous generation to compare against");
2611
+ const files = Object.fromEntries(paths.filter((path) => previous.has(path)).map((path) => [path, previous.get(path)]));
2612
+ await this.#hooks.callHook("studio:command:end", {
2613
+ command,
2614
+ info: `read ${Object.keys(files).length}/${paths.length} requested file${paths.length === 1 ? "" : "s"} from the previous run`
2615
+ });
2616
+ return { files };
2617
+ }
2568
2618
  const generation = this.#lastGeneration;
2569
2619
  if (!generation) return this.#refuse("Ignored files: no prior generation to read from", "No prior generation to read from, run a generation first");
2570
2620
  const requested = paths.filter((path) => generation.paths.has(path));