@kubb/studio 5.12.2 → 5.12.3

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.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as InstalledExtensions, L as PublishedSnapshot, a as AgentConnectInfo, c as AgentPermissions, d as ConfigEditOutcome, q as SnapshotUploadMetadata, s as AgentPermission, u as ConfigEdit } from "./protocol-DhNrwhqp.cjs";
1
+ import { J as SnapshotUploadMetadata, R as PublishedSnapshot, a as AgentConnectInfo, c as AgentPermissions, f as ConfigEditOutcome, j as InstalledExtensions, s as AgentPermission, u as ConfigEdit } from "./protocol-WYICLbSg.cjs";
2
2
  import { Config, Hookable, KubbHooks } from "@kubb/core";
3
3
  //#region src/connect.d.ts
4
4
  /**
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as InstalledExtensions, L as PublishedSnapshot, a as AgentConnectInfo, c as AgentPermissions, d as ConfigEditOutcome, q as SnapshotUploadMetadata, s as AgentPermission, u as ConfigEdit } from "./protocol-DhNrwhqp.js";
1
+ import { J as SnapshotUploadMetadata, R as PublishedSnapshot, a as AgentConnectInfo, c as AgentPermissions, f as ConfigEditOutcome, j as InstalledExtensions, s as AgentPermission, u as ConfigEdit } from "./protocol-WYICLbSg.js";
2
2
  import { Config, Hookable, KubbHooks } from "@kubb/core";
3
3
  //#region src/connect.d.ts
4
4
  /**
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
- import { AGENT_INSTANCE_HEADER, AGENT_SOCKET_PATH, AgentCloseCode, AgentMessage, AgentPermission, MAX_SNAPSHOT_BYTES, generationEventTypes } from "./protocol.js";
1
+ import { AGENT_INSTANCE_HEADER, AGENT_SOCKET_PATH, AgentCloseCode, AgentMessage, AgentPermission, ConfigEditOperation, MAX_SNAPSHOT_BYTES, generationEventTypes } from "./protocol.js";
2
2
  import { createRequire } from "node:module";
3
3
  import { createHash, hash, randomUUID } from "node:crypto";
4
4
  import { existsSync } from "node:fs";
5
+ import { mkdir, mkdir as mkdir$1, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
5
6
  import { tmpdir } from "node:os";
6
7
  import path, { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
8
  import process$1 from "node:process";
8
9
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
9
10
  import { x } from "tinyexec";
10
- import { mkdir, mkdir as mkdir$1, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
11
11
  import { Diagnostics, Hookable, cacheStorage, checkoutGitInput, createKubb, fsStorage, getInputKind, isPackageName, isPublicAddress, readInstalledPackages, resolveCacheDir, resolveMeta } from "@kubb/core";
12
12
  import { RpcTarget, newWebSocketRpcSession } from "capnweb";
13
13
  import { detectCodeFormat, parseExpression, parseModule } from "magicast";
@@ -202,7 +202,7 @@ function getElapsedMs(hrStart) {
202
202
  }
203
203
  //#endregion
204
204
  //#region package.json
205
- var version = "5.12.2";
205
+ var version = "5.12.3";
206
206
  //#endregion
207
207
  //#region src/resolveConfig.ts
208
208
  /**
@@ -600,13 +600,7 @@ const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
600
600
  * line count, so `enable-plugin` restores exactly those lines and not whatever comments follow.
601
601
  */
602
602
  const DISABLED_MARKER = /^\/\/ kubb:disabled (.+)\s+(\d+)$/;
603
- const OPERATIONS = /* @__PURE__ */ new Set([
604
- "set",
605
- "remove",
606
- "add-plugin",
607
- "disable-plugin",
608
- "enable-plugin"
609
- ]);
603
+ const OPERATIONS = new Set(Object.values(ConfigEditOperation));
610
604
  const KINDS = /* @__PURE__ */ new Set([
611
605
  "plugin",
612
606
  "adapter",
@@ -617,13 +611,14 @@ const KINDS = /* @__PURE__ */ new Set([
617
611
  * The edit crossed the wire, so its declared type is a promise the sender may not have kept.
618
612
  */
619
613
  function invalidEdit(edit) {
620
- const { operation, plugin, config, path, kind } = typeof edit === "object" && edit !== null ? edit : {};
614
+ const { operation, plugin, config, path, kind, input } = typeof edit === "object" && edit !== null ? edit : {};
621
615
  if (typeof operation !== "string" || !OPERATIONS.has(operation)) return `unknown operation ${JSON.stringify(operation)}`;
616
+ if (config !== void 0 && typeof config !== "string" && !Number.isSafeInteger(config)) return "config must name an entry or give its index";
617
+ if (operation === ConfigEditOperation.SET_INPUT) return typeof input === "string" && input.trim() ? void 0 : "the edit names no input";
622
618
  if (typeof plugin !== "string" || !plugin) return "the edit names no plugin";
623
619
  if (kind !== void 0 && (typeof kind !== "string" || !KINDS.has(kind))) return `unknown kind ${JSON.stringify(kind)}`;
624
- if (kind !== void 0 && operation !== "set" && operation !== "remove") return `${operation} edits the plugins array, so it takes no kind`;
625
- if (config !== void 0 && typeof config !== "string" && !Number.isSafeInteger(config)) return "config must name an entry or give its index";
626
- if (operation === "set" || operation === "remove") {
620
+ if (kind !== void 0 && operation !== ConfigEditOperation.SET && operation !== ConfigEditOperation.REMOVE) return `${operation} edits the plugins array, so it takes no kind`;
621
+ if (operation === ConfigEditOperation.SET || operation === ConfigEditOperation.REMOVE) {
627
622
  if (!Array.isArray(path) || !path.every((key) => typeof key === "string" && key)) return "path must be a list of option names";
628
623
  if (!path.length) return "no option path given";
629
624
  const unsafe = path.find((key) => !isSafeKey(key));
@@ -1064,6 +1059,15 @@ function applySet(source, call, path, value, quote) {
1064
1059
  }
1065
1060
  return { source: splice(source, rangeOf(target.value).start, rangeOf(target.value).end, printValue(value, quote)) };
1066
1061
  }
1062
+ /** Writes the config's top-level `input` as a string literal, replacing what it held. */
1063
+ function applySetInput(source, config, input, quote) {
1064
+ const value = printValue(input, quote);
1065
+ const found = findMember(config, "input");
1066
+ if (!found) return { source: insertItem(source, config, `input: ${value}`) };
1067
+ if (found.member.type !== "ObjectProperty") return { reason: "input is not a plain property" };
1068
+ if (isShadowed(config, found.index)) return { reason: "a spread after input could override it" };
1069
+ return { source: splice(source, rangeOf(found.member.value).start, rangeOf(found.member.value).end, value) };
1070
+ }
1067
1071
  /**
1068
1072
  * Deletes the option at `path`, falling the plugin back to its default for it. An option the file
1069
1073
  * does not set already has its default, so removing it changes nothing.
@@ -1160,7 +1164,8 @@ function applyEdit({ source, edit, quote, extensions }) {
1160
1164
  const findConfig = (entries) => typeof ref === "string" ? entries.configs.find((entry) => configName(entry) === ref) : entries.configs[ref ?? 0];
1161
1165
  const config = findConfig(parsed);
1162
1166
  if (!config) return { reason: `no config entry found for ${JSON.stringify(ref)}` };
1163
- if (edit.operation === "disable-plugin") {
1167
+ if (edit.operation === ConfigEditOperation.SET_INPUT) return applySetInput(source, config, edit.input, quote);
1168
+ if (edit.operation === ConfigEditOperation.DISABLE_PLUGIN) {
1164
1169
  const result = disablePlugin(source, parsed.mod, config, edit.plugin);
1165
1170
  if (!("reason" in result) || !result.reason.includes("shares a line")) return result;
1166
1171
  const expanded = expandPluginsArray(source, config);
@@ -1168,8 +1173,8 @@ function applyEdit({ source, edit, quote, extensions }) {
1168
1173
  const reparsedConfig = reparsed && !("reason" in reparsed) ? findConfig(reparsed) : void 0;
1169
1174
  return expanded !== void 0 && reparsed && !("reason" in reparsed) && reparsedConfig ? disablePlugin(expanded, reparsed.mod, reparsedConfig, edit.plugin) : result;
1170
1175
  }
1171
- if (edit.operation === "enable-plugin") return enablePlugin(source, config, edit.plugin);
1172
- if (edit.operation === "add-plugin") return applyAddPlugin({
1176
+ if (edit.operation === ConfigEditOperation.ENABLE_PLUGIN) return enablePlugin(source, config, edit.plugin);
1177
+ if (edit.operation === ConfigEditOperation.ADD_PLUGIN) return applyAddPlugin({
1173
1178
  source,
1174
1179
  parsed,
1175
1180
  config,
@@ -1185,7 +1190,7 @@ function applyEdit({ source, edit, quote, extensions }) {
1185
1190
  if (kind === "adapter") return { reason: `${edit.plugin} is not the config's adapter` };
1186
1191
  return { reason: kind === "plugin" && disabledBlocks(source, config).some((block) => block.packageName === edit.plugin) ? `${edit.plugin} is disabled, enable it first` : `${edit.plugin} is not in the ${kind}s array` };
1187
1192
  }
1188
- return edit.operation === "set" ? applySet(source, call, edit.path, edit.value, quote) : applyRemove(source, call, edit.path);
1193
+ return edit.operation === ConfigEditOperation.SET ? applySet(source, call, edit.path, edit.value, quote) : applyRemove(source, call, edit.path);
1189
1194
  }
1190
1195
  /**
1191
1196
  * Applies edits to a `kubb.config.ts` in place. Each edit rewrites only the text it targets, so
@@ -2123,6 +2128,7 @@ var Agent = class extends RpcTarget {
2123
2128
  allowConfigEdit: false,
2124
2129
  allowInstall: false,
2125
2130
  allowUpdate: false,
2131
+ allowSaveInput: false,
2126
2132
  allowRead: true,
2127
2133
  allowReadAst: true
2128
2134
  } : permissions;
@@ -2148,13 +2154,13 @@ var Agent = class extends RpcTarget {
2148
2154
  permission
2149
2155
  });
2150
2156
  }
2151
- async #refuse({ message, permission }) {
2157
+ async #refuse({ message, permission, code = Diagnostics.code.unknown }) {
2152
2158
  await this.#warn({
2153
2159
  message,
2154
2160
  permission
2155
2161
  });
2156
2162
  throw new Diagnostics.Error({
2157
- code: Diagnostics.code.unknown,
2163
+ code,
2158
2164
  severity: "error",
2159
2165
  message
2160
2166
  });
@@ -2346,7 +2352,13 @@ var Agent = class extends RpcTarget {
2346
2352
  });
2347
2353
  const activePlugins = plugins ?? config.plugins;
2348
2354
  if (patch?.input && !this.#sandbox && !this.#managed) await emit(this.#options.hooks, "kubb:warn", { message: "Ignored the spec from Studio: generating from a Studio spec is only available to a sandbox or managed agent" });
2349
- const checkout = this.#managed && studioInput !== void 0 && getInputKind(studioInput) === "git" ? await checkoutGitInput(studioInput, signal, { publicOnly: true }).catch((error) => this.#refuse({ message: getErrorMessage(error) })) : void 0;
2355
+ const checkout = this.#managed && studioInput !== void 0 && getInputKind(studioInput) === "git" ? await checkoutGitInput(studioInput, signal, {
2356
+ publicOnly: true,
2357
+ authorization: input.gitAuth?.authorization
2358
+ }).catch((error) => this.#refuse({
2359
+ message: getErrorMessage(error),
2360
+ code: Diagnostics.code.gitInputFailed
2361
+ })) : void 0;
2350
2362
  const run = () => generate({
2351
2363
  config: {
2352
2364
  ...config,
@@ -2420,11 +2432,29 @@ var Agent = class extends RpcTarget {
2420
2432
  if (busy) return refuse(busy);
2421
2433
  this.#session.saving = true;
2422
2434
  try {
2423
- const { outcomes, changed } = await writeConfigEdits({
2435
+ const kept = [];
2436
+ for (const edit of edits) kept.push(await this.#keepInput(edit));
2437
+ const written = await writeConfigEdits({
2424
2438
  filePath: configFile,
2425
- edits,
2439
+ edits: kept.flatMap((edit) => "reason" in edit ? [] : [edit]),
2426
2440
  extensions: await this.#installedExtensions()
2427
2441
  });
2442
+ let next = 0;
2443
+ const outcomes = kept.map((edit, index) => "reason" in edit ? {
2444
+ edit: edits[index],
2445
+ applied: false,
2446
+ reason: edit.reason
2447
+ } : {
2448
+ ...written.outcomes[next++],
2449
+ edit: edits[index]
2450
+ });
2451
+ let wroteDocument = false;
2452
+ for (const [index, edit] of kept.entries()) {
2453
+ if (!("document" in edit) || !edit.document || !outcomes[index].applied) continue;
2454
+ await writeFile(path.join(this.#options.root, edit.document.fileName), edit.document.text, "utf-8");
2455
+ wroteDocument = true;
2456
+ }
2457
+ const changed = written.changed || wroteDocument;
2428
2458
  const applied = outcomes.filter((outcome) => outcome.applied).length;
2429
2459
  await this.#logCommand("saveConfig", `applied ${applied}/${outcomes.length} edits to ${configPath}`, input.requestedBy);
2430
2460
  return {
@@ -2439,6 +2469,33 @@ var Agent = class extends RpcTarget {
2439
2469
  }
2440
2470
  }
2441
2471
  /**
2472
+ * Readies a `set-input` edit for the config file: a document is pointed at `openapi.yaml` (`openapi.json`
2473
+ * for JSON) in the project, one file per config entry, for `saveConfig` to write once the edit applies.
2474
+ * A managed agent refuses a file path, as in a generation.
2475
+ */
2476
+ async #keepInput(edit) {
2477
+ if (edit?.operation !== ConfigEditOperation.SET_INPUT || typeof edit.input !== "string" || !edit.input.trim()) return edit;
2478
+ if (!this.#permissions.allowSaveInput) {
2479
+ await this.#warn({
2480
+ message: "Ignored the spec to save: saving the input was not granted",
2481
+ permission: AgentPermission.SAVE_INPUT
2482
+ });
2483
+ return { reason: "the agent was not granted permission to save the input" };
2484
+ }
2485
+ const kind = getInputKind(edit.input);
2486
+ if (this.#managed && !MANAGED_INPUT_KINDS.has(kind)) return { reason: AgentMessage.MANAGED_INPUT_KIND };
2487
+ if (kind !== "inline") return edit;
2488
+ const fileName = `openapi${edit.config === void 0 || edit.config === 0 ? "" : `-${String(edit.config).replace(/[^\w.-]/g, "-")}`}.${isJson(edit.input) ? "json" : "yaml"}`;
2489
+ return {
2490
+ ...edit,
2491
+ input: `./${fileName}`,
2492
+ document: {
2493
+ fileName,
2494
+ text: edit.input
2495
+ }
2496
+ };
2497
+ }
2498
+ /**
2442
2499
  * Installs a registry extension into the project with `kubb add <id> --yes`, which also adds a
2443
2500
  * plugin to `kubb.config.ts`. Studio calls `connect()` again afterwards to see the result.
2444
2501
  */
@@ -2739,6 +2796,15 @@ function pickConfig(configs, ref) {
2739
2796
  if (ref !== void 0 && !Number.isSafeInteger(ref)) return void 0;
2740
2797
  return configs[ref ?? 0];
2741
2798
  }
2799
+ /** Whether `text` parses as JSON, so a pasted document is saved under the matching extension. */
2800
+ function isJson(text) {
2801
+ try {
2802
+ JSON.parse(text);
2803
+ return true;
2804
+ } catch {
2805
+ return false;
2806
+ }
2807
+ }
2742
2808
  /** The input path or URL Studio may show: no inline document, and no credentials from a URL. */
2743
2809
  function reportedInput(input) {
2744
2810
  if (typeof input !== "string") return void 0;
@@ -2794,6 +2860,7 @@ async function runAgent({ options, openRpc }) {
2794
2860
  allowConfigEdit: false,
2795
2861
  allowInstall: false,
2796
2862
  allowUpdate: false,
2863
+ allowSaveInput: false,
2797
2864
  allowExec: false,
2798
2865
  allowRead: false,
2799
2866
  allowReadAst: false,