@kubb/studio 5.9.4 → 5.9.5

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 { L as SnapshotUploadMetadata, a as AgentConnectInfo, c as ConfigEdit, j as PublishedSnapshot, l as ConfigEditOutcome, o as AgentPermissions, w as InstalledExtensions } from "./protocol-gXoAXvT3.js";
2
+ import { M as PublishedSnapshot, V as SnapshotUploadMetadata, a as AgentConnectInfo, c as ConfigEdit, l as ConfigEditOutcome, o as AgentPermissions, w as InstalledExtensions } from "./protocol-ixLkhvRJ.js";
3
3
  import { Config, Hookable, KubbHooks } from "@kubb/core";
4
4
  //#region src/connect.d.ts
5
5
  /**
@@ -46,7 +46,7 @@ declare global {
46
46
  * A finished command and what it did, such as `applied 2/3 edits to kubb.config.ts`.
47
47
  */
48
48
  'studio:command': [ctx: {
49
- command: 'generate' | 'saveConfig' | 'readFiles';
49
+ command: 'generate' | 'saveConfig' | 'readFiles' | 'readInput';
50
50
  info: string;
51
51
  }];
52
52
  /**
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "./rolldown-runtime-CRm0XQPb.js";
2
- import { AGENT_INSTANCE_HEADER, AGENT_SOCKET_PATH, AgentCloseCode, GENERATION_GONE_MESSAGE, MAX_PAGE_LINES, MAX_SNAPSHOT_BYTES, generationEventTypes } from "./protocol.js";
2
+ import { AGENT_INSTANCE_HEADER, AGENT_SOCKET_PATH, AgentCloseCode, GENERATION_GONE_MESSAGE, MAX_PAGE_LINES, MAX_SNAPSHOT_BYTES, NO_INPUT_MESSAGE, READ_NOT_ALLOWED_MESSAGE, generationEventTypes } from "./protocol.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { createHash, hash, randomUUID } from "node:crypto";
5
5
  import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
@@ -13,6 +13,7 @@ import { Diagnostics, Hookable, cacheStorage, createKubb, fsStorage, getInputKin
13
13
  import { RpcTarget, newWebSocketRpcSession } from "capnweb";
14
14
  import { detectCodeFormat, parseExpression, parseModule } from "magicast";
15
15
  import { stripVTControlCharacters } from "node:util";
16
+ import { ast } from "@kubb/ast";
16
17
  import { ofetch } from "ofetch";
17
18
  import { gzipSync } from "node:zlib";
18
19
  //#region ../../internals/utils/src/casing.ts
@@ -152,7 +153,7 @@ function getElapsedMs(hrStart) {
152
153
  }
153
154
  //#endregion
154
155
  //#region package.json
155
- var version = "5.9.4";
156
+ var version = "5.9.5";
156
157
  //#endregion
157
158
  //#region src/resolveConfig.ts
158
159
  /**
@@ -1083,7 +1084,10 @@ function relativeStoragePath({ root, filePath }) {
1083
1084
  * The content fingerprint Studio compares snapshots by. Agent runs and CI uploads must agree on it.
1084
1085
  */
1085
1086
  const hashOf = (content) => createHash("sha1").update(content).digest("hex").slice(0, 16);
1086
- const totalBytes = (entries) => entries.reduce((sum, { output, disk }) => sum + output.bytes + (disk?.bytes ?? 0), 0);
1087
+ /**
1088
+ * The bytes `entries` keep on disk: output, written files, and kept input.
1089
+ */
1090
+ const totalBytes = (entries) => entries.reduce((sum, { output, disk, inputBytes }) => sum + output.bytes + (disk?.bytes ?? 0) + (inputBytes ?? 0), 0);
1087
1091
  function positiveNumber(value) {
1088
1092
  const parsed = Number(value);
1089
1093
  return value && Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
@@ -1126,7 +1130,11 @@ async function generate({ config, hooks, signal, allowExec }) {
1126
1130
  hooks,
1127
1131
  signal
1128
1132
  });
1129
- const result = await kubb.generate();
1133
+ let input = null;
1134
+ const unhook = kubb.hooks.hook("kubb:build:end", (ctx) => {
1135
+ if (ctx.config === kubb.config) input = kubb.driver.inputNode;
1136
+ });
1137
+ const result = await kubb.generate().finally(unhook);
1130
1138
  if (!result.success) throw generationFailure(result.diagnostics);
1131
1139
  const { storage, root } = kubb.config;
1132
1140
  return {
@@ -1135,7 +1143,8 @@ async function generate({ config, hooks, signal, allowExec }) {
1135
1143
  paths: new Set(result.files.map((file) => relativeStoragePath({
1136
1144
  root,
1137
1145
  filePath: file.path
1138
- })))
1146
+ }))),
1147
+ input
1139
1148
  };
1140
1149
  }
1141
1150
  /**
@@ -1216,6 +1225,33 @@ function createGenerationStore({ storage, maxCount, maxMb, ttlMs, now = Date.now
1216
1225
  return {
1217
1226
  keep,
1218
1227
  drop,
1228
+ /**
1229
+ * Keeps the `InputNode` of `jobId` and returns its size in bytes, or 0 when it keeps none. A ref schema loses
1230
+ * the copy of its target, which `input.schemas` holds: with it, a large spec outgrows what `JSON.stringify` can return.
1231
+ */
1232
+ async keepInput({ jobId, input, maxSetMb }) {
1233
+ if (!input) return 0;
1234
+ let json;
1235
+ try {
1236
+ json = JSON.stringify(ast.transform(input, { schema: (node) => node.type === "ref" && node.schema ? {
1237
+ ...node,
1238
+ schema: void 0
1239
+ } : void 0 }));
1240
+ } catch {
1241
+ return 0;
1242
+ }
1243
+ const bytes = Buffer.byteLength(json);
1244
+ if (bytes > maxSetMb * MB) return 0;
1245
+ await storage.writeItem(`${dirOf(jobId)}input.json`, json);
1246
+ return bytes;
1247
+ },
1248
+ /**
1249
+ * The input `keepInput` kept for `jobId`, or `undefined` when there is none.
1250
+ */
1251
+ async readInput(jobId) {
1252
+ const stored = await storage.readItem(`${dirOf(jobId)}input.json`);
1253
+ return stored === null ? void 0 : JSON.parse(stored);
1254
+ },
1219
1255
  get: async (jobId) => (await load()).find((generation) => generation.jobId === jobId && isLive(generation)),
1220
1256
  async add(generation) {
1221
1257
  const current = await load();
@@ -1666,6 +1702,7 @@ var Agent = class extends RpcTarget {
1666
1702
  }
1667
1703
  });
1668
1704
  }
1705
+ /** Runs one job and keeps its output, and its input with `allowRead`, for later reads. */
1669
1706
  async #runGeneration(input, controller) {
1670
1707
  this.#assertConnected();
1671
1708
  if (this.#session.closed) throw new Error("Connection is closed");
@@ -1753,11 +1790,17 @@ var Agent = class extends RpcTarget {
1753
1790
  files,
1754
1791
  maxSetMb: this.#limits.maxMb
1755
1792
  });
1793
+ const inputBytes = await this.#generations.keepInput({
1794
+ jobId,
1795
+ input: this.#permissions.allowRead ? files.input : null,
1796
+ maxSetMb: this.#limits.maxMb
1797
+ });
1756
1798
  if (!output.paths.length && Object.keys(output.hashes).length) await this.#warn({ message: "Kept only the hashes of this generation: its output is too large to keep" });
1757
1799
  await this.#generations.add({
1758
1800
  jobId,
1759
1801
  output,
1760
- disk
1802
+ disk,
1803
+ inputBytes
1761
1804
  });
1762
1805
  await this.#logCommand("generate", `${activePlugins.length} plugin${activePlugins.length === 1 ? "" : "s"}, ${this.#permissions.allowWrite ? "written to disk" : "in memory"}${studioInput !== void 0 ? ", from a Studio spec" : ""}`);
1763
1806
  return {
@@ -1809,10 +1852,30 @@ var Agent = class extends RpcTarget {
1809
1852
  return refuse(getErrorMessage(error));
1810
1853
  }
1811
1854
  }
1855
+ /**
1856
+ * Returns the Kubb AST a kept job parsed its spec into. Refused without `allowRead`.
1857
+ */
1858
+ async readInput(input) {
1859
+ this.#assertConnected();
1860
+ if (!this.#permissions.allowRead) return this.#refuse({
1861
+ message: READ_NOT_ALLOWED_MESSAGE,
1862
+ permission: "allowRead"
1863
+ });
1864
+ if (typeof input.jobId !== "string" || !input.jobId) return this.#refuse({ message: "The request must name a job" });
1865
+ const generation = await this.#generations.get(input.jobId);
1866
+ if (!generation) return this.#refuse({ message: GENERATION_GONE_MESSAGE });
1867
+ const kept = generation.inputBytes ? await this.#generations.readInput(input.jobId) : void 0;
1868
+ if (!kept) return this.#refuse({ message: NO_INPUT_MESSAGE });
1869
+ await this.#logCommand("readInput", "read the API input");
1870
+ return { input: kept };
1871
+ }
1872
+ /**
1873
+ * Returns the requested files of a kept job, one page of lines at a time. Refused without `allowRead`.
1874
+ */
1812
1875
  async readFiles(input) {
1813
1876
  this.#assertConnected();
1814
1877
  if (!this.#permissions.allowRead) return this.#refuse({
1815
- message: "The agent was not granted permission to read generated files",
1878
+ message: READ_NOT_ALLOWED_MESSAGE,
1816
1879
  permission: "allowRead"
1817
1880
  });
1818
1881
  if (!Array.isArray(input.paths) || typeof input.jobId !== "string" || !input.jobId) return this.#refuse({ message: "The request must name a job and carry paths" });