@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.cjs +89 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +90 -23
- package/dist/index.js.map +1 -1
- package/dist/{protocol-DhNrwhqp.d.ts → protocol-WYICLbSg.d.cts} +36 -7
- package/dist/{protocol-DhNrwhqp.d.cts → protocol-WYICLbSg.d.ts} +36 -7
- package/dist/protocol.cjs +13 -0
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +2 -2
- package/dist/protocol.d.ts +2 -2
- package/dist/protocol.js +13 -1
- package/dist/protocol.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
const require_protocol = require("./protocol.cjs");
|
|
25
25
|
let node_crypto = require("node:crypto");
|
|
26
26
|
let node_fs = require("node:fs");
|
|
27
|
+
let node_fs_promises = require("node:fs/promises");
|
|
27
28
|
let node_module = require("node:module");
|
|
28
29
|
let node_os = require("node:os");
|
|
29
30
|
let node_path = require("node:path");
|
|
@@ -32,7 +33,6 @@ let node_process = require("node:process");
|
|
|
32
33
|
node_process = __toESM(node_process, 1);
|
|
33
34
|
let node_timers_promises = require("node:timers/promises");
|
|
34
35
|
let tinyexec = require("tinyexec");
|
|
35
|
-
let node_fs_promises = require("node:fs/promises");
|
|
36
36
|
let _kubb_core = require("@kubb/core");
|
|
37
37
|
let capnweb = require("capnweb");
|
|
38
38
|
let magicast = require("magicast");
|
|
@@ -230,7 +230,7 @@ function getElapsedMs(hrStart) {
|
|
|
230
230
|
}
|
|
231
231
|
//#endregion
|
|
232
232
|
//#region package.json
|
|
233
|
-
var version = "5.12.
|
|
233
|
+
var version = "5.12.3";
|
|
234
234
|
//#endregion
|
|
235
235
|
//#region src/resolveConfig.ts
|
|
236
236
|
/**
|
|
@@ -628,13 +628,7 @@ const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
|
628
628
|
* line count, so `enable-plugin` restores exactly those lines and not whatever comments follow.
|
|
629
629
|
*/
|
|
630
630
|
const DISABLED_MARKER = /^\/\/ kubb:disabled (.+)\s+(\d+)$/;
|
|
631
|
-
const OPERATIONS =
|
|
632
|
-
"set",
|
|
633
|
-
"remove",
|
|
634
|
-
"add-plugin",
|
|
635
|
-
"disable-plugin",
|
|
636
|
-
"enable-plugin"
|
|
637
|
-
]);
|
|
631
|
+
const OPERATIONS = new Set(Object.values(require_protocol.ConfigEditOperation));
|
|
638
632
|
const KINDS = /* @__PURE__ */ new Set([
|
|
639
633
|
"plugin",
|
|
640
634
|
"adapter",
|
|
@@ -645,13 +639,14 @@ const KINDS = /* @__PURE__ */ new Set([
|
|
|
645
639
|
* The edit crossed the wire, so its declared type is a promise the sender may not have kept.
|
|
646
640
|
*/
|
|
647
641
|
function invalidEdit(edit) {
|
|
648
|
-
const { operation, plugin, config, path, kind } = typeof edit === "object" && edit !== null ? edit : {};
|
|
642
|
+
const { operation, plugin, config, path, kind, input } = typeof edit === "object" && edit !== null ? edit : {};
|
|
649
643
|
if (typeof operation !== "string" || !OPERATIONS.has(operation)) return `unknown operation ${JSON.stringify(operation)}`;
|
|
644
|
+
if (config !== void 0 && typeof config !== "string" && !Number.isSafeInteger(config)) return "config must name an entry or give its index";
|
|
645
|
+
if (operation === require_protocol.ConfigEditOperation.SET_INPUT) return typeof input === "string" && input.trim() ? void 0 : "the edit names no input";
|
|
650
646
|
if (typeof plugin !== "string" || !plugin) return "the edit names no plugin";
|
|
651
647
|
if (kind !== void 0 && (typeof kind !== "string" || !KINDS.has(kind))) return `unknown kind ${JSON.stringify(kind)}`;
|
|
652
|
-
if (kind !== void 0 && operation !==
|
|
653
|
-
if (
|
|
654
|
-
if (operation === "set" || operation === "remove") {
|
|
648
|
+
if (kind !== void 0 && operation !== require_protocol.ConfigEditOperation.SET && operation !== require_protocol.ConfigEditOperation.REMOVE) return `${operation} edits the plugins array, so it takes no kind`;
|
|
649
|
+
if (operation === require_protocol.ConfigEditOperation.SET || operation === require_protocol.ConfigEditOperation.REMOVE) {
|
|
655
650
|
if (!Array.isArray(path) || !path.every((key) => typeof key === "string" && key)) return "path must be a list of option names";
|
|
656
651
|
if (!path.length) return "no option path given";
|
|
657
652
|
const unsafe = path.find((key) => !isSafeKey(key));
|
|
@@ -1092,6 +1087,15 @@ function applySet(source, call, path, value, quote) {
|
|
|
1092
1087
|
}
|
|
1093
1088
|
return { source: splice(source, rangeOf(target.value).start, rangeOf(target.value).end, printValue(value, quote)) };
|
|
1094
1089
|
}
|
|
1090
|
+
/** Writes the config's top-level `input` as a string literal, replacing what it held. */
|
|
1091
|
+
function applySetInput(source, config, input, quote) {
|
|
1092
|
+
const value = printValue(input, quote);
|
|
1093
|
+
const found = findMember(config, "input");
|
|
1094
|
+
if (!found) return { source: insertItem(source, config, `input: ${value}`) };
|
|
1095
|
+
if (found.member.type !== "ObjectProperty") return { reason: "input is not a plain property" };
|
|
1096
|
+
if (isShadowed(config, found.index)) return { reason: "a spread after input could override it" };
|
|
1097
|
+
return { source: splice(source, rangeOf(found.member.value).start, rangeOf(found.member.value).end, value) };
|
|
1098
|
+
}
|
|
1095
1099
|
/**
|
|
1096
1100
|
* Deletes the option at `path`, falling the plugin back to its default for it. An option the file
|
|
1097
1101
|
* does not set already has its default, so removing it changes nothing.
|
|
@@ -1188,7 +1192,8 @@ function applyEdit({ source, edit, quote, extensions }) {
|
|
|
1188
1192
|
const findConfig = (entries) => typeof ref === "string" ? entries.configs.find((entry) => configName(entry) === ref) : entries.configs[ref ?? 0];
|
|
1189
1193
|
const config = findConfig(parsed);
|
|
1190
1194
|
if (!config) return { reason: `no config entry found for ${JSON.stringify(ref)}` };
|
|
1191
|
-
if (edit.operation ===
|
|
1195
|
+
if (edit.operation === require_protocol.ConfigEditOperation.SET_INPUT) return applySetInput(source, config, edit.input, quote);
|
|
1196
|
+
if (edit.operation === require_protocol.ConfigEditOperation.DISABLE_PLUGIN) {
|
|
1192
1197
|
const result = disablePlugin(source, parsed.mod, config, edit.plugin);
|
|
1193
1198
|
if (!("reason" in result) || !result.reason.includes("shares a line")) return result;
|
|
1194
1199
|
const expanded = expandPluginsArray(source, config);
|
|
@@ -1196,8 +1201,8 @@ function applyEdit({ source, edit, quote, extensions }) {
|
|
|
1196
1201
|
const reparsedConfig = reparsed && !("reason" in reparsed) ? findConfig(reparsed) : void 0;
|
|
1197
1202
|
return expanded !== void 0 && reparsed && !("reason" in reparsed) && reparsedConfig ? disablePlugin(expanded, reparsed.mod, reparsedConfig, edit.plugin) : result;
|
|
1198
1203
|
}
|
|
1199
|
-
if (edit.operation ===
|
|
1200
|
-
if (edit.operation ===
|
|
1204
|
+
if (edit.operation === require_protocol.ConfigEditOperation.ENABLE_PLUGIN) return enablePlugin(source, config, edit.plugin);
|
|
1205
|
+
if (edit.operation === require_protocol.ConfigEditOperation.ADD_PLUGIN) return applyAddPlugin({
|
|
1201
1206
|
source,
|
|
1202
1207
|
parsed,
|
|
1203
1208
|
config,
|
|
@@ -1213,7 +1218,7 @@ function applyEdit({ source, edit, quote, extensions }) {
|
|
|
1213
1218
|
if (kind === "adapter") return { reason: `${edit.plugin} is not the config's adapter` };
|
|
1214
1219
|
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` };
|
|
1215
1220
|
}
|
|
1216
|
-
return edit.operation ===
|
|
1221
|
+
return edit.operation === require_protocol.ConfigEditOperation.SET ? applySet(source, call, edit.path, edit.value, quote) : applyRemove(source, call, edit.path);
|
|
1217
1222
|
}
|
|
1218
1223
|
/**
|
|
1219
1224
|
* Applies edits to a `kubb.config.ts` in place. Each edit rewrites only the text it targets, so
|
|
@@ -2151,6 +2156,7 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
2151
2156
|
allowConfigEdit: false,
|
|
2152
2157
|
allowInstall: false,
|
|
2153
2158
|
allowUpdate: false,
|
|
2159
|
+
allowSaveInput: false,
|
|
2154
2160
|
allowRead: true,
|
|
2155
2161
|
allowReadAst: true
|
|
2156
2162
|
} : permissions;
|
|
@@ -2176,13 +2182,13 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
2176
2182
|
permission
|
|
2177
2183
|
});
|
|
2178
2184
|
}
|
|
2179
|
-
async #refuse({ message, permission }) {
|
|
2185
|
+
async #refuse({ message, permission, code = _kubb_core.Diagnostics.code.unknown }) {
|
|
2180
2186
|
await this.#warn({
|
|
2181
2187
|
message,
|
|
2182
2188
|
permission
|
|
2183
2189
|
});
|
|
2184
2190
|
throw new _kubb_core.Diagnostics.Error({
|
|
2185
|
-
code
|
|
2191
|
+
code,
|
|
2186
2192
|
severity: "error",
|
|
2187
2193
|
message
|
|
2188
2194
|
});
|
|
@@ -2374,7 +2380,13 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
2374
2380
|
});
|
|
2375
2381
|
const activePlugins = plugins ?? config.plugins;
|
|
2376
2382
|
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" });
|
|
2377
|
-
const checkout = this.#managed && studioInput !== void 0 && (0, _kubb_core.getInputKind)(studioInput) === "git" ? await (0, _kubb_core.checkoutGitInput)(studioInput, signal, {
|
|
2383
|
+
const checkout = this.#managed && studioInput !== void 0 && (0, _kubb_core.getInputKind)(studioInput) === "git" ? await (0, _kubb_core.checkoutGitInput)(studioInput, signal, {
|
|
2384
|
+
publicOnly: true,
|
|
2385
|
+
authorization: input.gitAuth?.authorization
|
|
2386
|
+
}).catch((error) => this.#refuse({
|
|
2387
|
+
message: getErrorMessage(error),
|
|
2388
|
+
code: _kubb_core.Diagnostics.code.gitInputFailed
|
|
2389
|
+
})) : void 0;
|
|
2378
2390
|
const run = () => generate({
|
|
2379
2391
|
config: {
|
|
2380
2392
|
...config,
|
|
@@ -2448,11 +2460,29 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
2448
2460
|
if (busy) return refuse(busy);
|
|
2449
2461
|
this.#session.saving = true;
|
|
2450
2462
|
try {
|
|
2451
|
-
const
|
|
2463
|
+
const kept = [];
|
|
2464
|
+
for (const edit of edits) kept.push(await this.#keepInput(edit));
|
|
2465
|
+
const written = await writeConfigEdits({
|
|
2452
2466
|
filePath: configFile,
|
|
2453
|
-
edits,
|
|
2467
|
+
edits: kept.flatMap((edit) => "reason" in edit ? [] : [edit]),
|
|
2454
2468
|
extensions: await this.#installedExtensions()
|
|
2455
2469
|
});
|
|
2470
|
+
let next = 0;
|
|
2471
|
+
const outcomes = kept.map((edit, index) => "reason" in edit ? {
|
|
2472
|
+
edit: edits[index],
|
|
2473
|
+
applied: false,
|
|
2474
|
+
reason: edit.reason
|
|
2475
|
+
} : {
|
|
2476
|
+
...written.outcomes[next++],
|
|
2477
|
+
edit: edits[index]
|
|
2478
|
+
});
|
|
2479
|
+
let wroteDocument = false;
|
|
2480
|
+
for (const [index, edit] of kept.entries()) {
|
|
2481
|
+
if (!("document" in edit) || !edit.document || !outcomes[index].applied) continue;
|
|
2482
|
+
await (0, node_fs_promises.writeFile)(node_path.default.join(this.#options.root, edit.document.fileName), edit.document.text, "utf-8");
|
|
2483
|
+
wroteDocument = true;
|
|
2484
|
+
}
|
|
2485
|
+
const changed = written.changed || wroteDocument;
|
|
2456
2486
|
const applied = outcomes.filter((outcome) => outcome.applied).length;
|
|
2457
2487
|
await this.#logCommand("saveConfig", `applied ${applied}/${outcomes.length} edits to ${configPath}`, input.requestedBy);
|
|
2458
2488
|
return {
|
|
@@ -2467,6 +2497,33 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
2467
2497
|
}
|
|
2468
2498
|
}
|
|
2469
2499
|
/**
|
|
2500
|
+
* Readies a `set-input` edit for the config file: a document is pointed at `openapi.yaml` (`openapi.json`
|
|
2501
|
+
* for JSON) in the project, one file per config entry, for `saveConfig` to write once the edit applies.
|
|
2502
|
+
* A managed agent refuses a file path, as in a generation.
|
|
2503
|
+
*/
|
|
2504
|
+
async #keepInput(edit) {
|
|
2505
|
+
if (edit?.operation !== require_protocol.ConfigEditOperation.SET_INPUT || typeof edit.input !== "string" || !edit.input.trim()) return edit;
|
|
2506
|
+
if (!this.#permissions.allowSaveInput) {
|
|
2507
|
+
await this.#warn({
|
|
2508
|
+
message: "Ignored the spec to save: saving the input was not granted",
|
|
2509
|
+
permission: require_protocol.AgentPermission.SAVE_INPUT
|
|
2510
|
+
});
|
|
2511
|
+
return { reason: "the agent was not granted permission to save the input" };
|
|
2512
|
+
}
|
|
2513
|
+
const kind = (0, _kubb_core.getInputKind)(edit.input);
|
|
2514
|
+
if (this.#managed && !MANAGED_INPUT_KINDS.has(kind)) return { reason: require_protocol.AgentMessage.MANAGED_INPUT_KIND };
|
|
2515
|
+
if (kind !== "inline") return edit;
|
|
2516
|
+
const fileName = `openapi${edit.config === void 0 || edit.config === 0 ? "" : `-${String(edit.config).replace(/[^\w.-]/g, "-")}`}.${isJson(edit.input) ? "json" : "yaml"}`;
|
|
2517
|
+
return {
|
|
2518
|
+
...edit,
|
|
2519
|
+
input: `./${fileName}`,
|
|
2520
|
+
document: {
|
|
2521
|
+
fileName,
|
|
2522
|
+
text: edit.input
|
|
2523
|
+
}
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
/**
|
|
2470
2527
|
* Installs a registry extension into the project with `kubb add <id> --yes`, which also adds a
|
|
2471
2528
|
* plugin to `kubb.config.ts`. Studio calls `connect()` again afterwards to see the result.
|
|
2472
2529
|
*/
|
|
@@ -2767,6 +2824,15 @@ function pickConfig(configs, ref) {
|
|
|
2767
2824
|
if (ref !== void 0 && !Number.isSafeInteger(ref)) return void 0;
|
|
2768
2825
|
return configs[ref ?? 0];
|
|
2769
2826
|
}
|
|
2827
|
+
/** Whether `text` parses as JSON, so a pasted document is saved under the matching extension. */
|
|
2828
|
+
function isJson(text) {
|
|
2829
|
+
try {
|
|
2830
|
+
JSON.parse(text);
|
|
2831
|
+
return true;
|
|
2832
|
+
} catch {
|
|
2833
|
+
return false;
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2770
2836
|
/** The input path or URL Studio may show: no inline document, and no credentials from a URL. */
|
|
2771
2837
|
function reportedInput(input) {
|
|
2772
2838
|
if (typeof input !== "string") return void 0;
|
|
@@ -2822,6 +2888,7 @@ async function runAgent({ options, openRpc }) {
|
|
|
2822
2888
|
allowConfigEdit: false,
|
|
2823
2889
|
allowInstall: false,
|
|
2824
2890
|
allowUpdate: false,
|
|
2891
|
+
allowSaveInput: false,
|
|
2825
2892
|
allowExec: false,
|
|
2826
2893
|
allowRead: false,
|
|
2827
2894
|
allowReadAst: false,
|