@kubb/studio 5.12.2 → 5.12.4
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 +195 -26
- 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 +196 -27
- package/dist/index.js.map +1 -1
- package/dist/{protocol-DhNrwhqp.d.ts → protocol-C4loKcxw.d.cts} +66 -12
- package/dist/{protocol-DhNrwhqp.d.cts → protocol-C4loKcxw.d.ts} +66 -12
- package/dist/protocol.cjs +15 -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 +15 -1
- package/dist/protocol.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Y as SnapshotUploadMetadata, a as AgentConnectInfo, c as AgentPermissions, f as ConfigEditOutcome, j as InstalledExtensions, s as AgentPermission, u as ConfigEdit, z as PublishedSnapshot } from "./protocol-C4loKcxw.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 {
|
|
1
|
+
import { Y as SnapshotUploadMetadata, a as AgentConnectInfo, c as AgentPermissions, f as ConfigEditOutcome, j as InstalledExtensions, s as AgentPermission, u as ConfigEdit, z as PublishedSnapshot } from "./protocol-C4loKcxw.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.
|
|
205
|
+
var version = "5.12.4";
|
|
206
206
|
//#endregion
|
|
207
207
|
//#region src/resolveConfig.ts
|
|
208
208
|
/**
|
|
@@ -558,6 +558,44 @@ async function rebuildExtension({ disk, studioOptions, kind, extensions }) {
|
|
|
558
558
|
});
|
|
559
559
|
}))(mergeOptions(disk.options ?? {}, studioOptions));
|
|
560
560
|
}
|
|
561
|
+
/** The root `output` members Studio may set, with the values each takes. The rest, `path` and `postGenerate` among them, stay with the config file. */
|
|
562
|
+
const OUTPUT_VALUES = {
|
|
563
|
+
barrel: (value) => value === false || isPlainObject(value) && Object.keys(value).length === 1 && (value.type === "named" || value.type === "all"),
|
|
564
|
+
format: (value) => value === false || [
|
|
565
|
+
"auto",
|
|
566
|
+
"prettier",
|
|
567
|
+
"biome",
|
|
568
|
+
"oxfmt"
|
|
569
|
+
].includes(value),
|
|
570
|
+
lint: (value) => value === false || [
|
|
571
|
+
"auto",
|
|
572
|
+
"eslint",
|
|
573
|
+
"biome",
|
|
574
|
+
"oxlint"
|
|
575
|
+
].includes(value),
|
|
576
|
+
clean: (value) => typeof value === "boolean",
|
|
577
|
+
defaultBanner: (value) => value === false || value === "simple" || value === "full"
|
|
578
|
+
};
|
|
579
|
+
/** Whether Studio may set the root `output` member `key`. */
|
|
580
|
+
function isOutputKey(key) {
|
|
581
|
+
return Object.hasOwn(OUTPUT_VALUES, key);
|
|
582
|
+
}
|
|
583
|
+
/** Whether Studio may set the root `output` member `key` to `value`. */
|
|
584
|
+
function isOutputValue({ key, value }) {
|
|
585
|
+
return isOutputKey(key) && OUTPUT_VALUES[key](value);
|
|
586
|
+
}
|
|
587
|
+
/** The members of `output` Studio may see and set. */
|
|
588
|
+
function pickOutput(output) {
|
|
589
|
+
return Object.fromEntries(Object.entries(output).filter(([key]) => isOutputKey(key)));
|
|
590
|
+
}
|
|
591
|
+
/** Merges Studio's valid `output` overrides, with `null` unsetting a member. */
|
|
592
|
+
function mergeOutput({ output, studio }) {
|
|
593
|
+
if (!isPlainObject(studio)) return output;
|
|
594
|
+
return mergeOptions(output, Object.fromEntries(Object.entries(studio).filter(([key, value]) => value === null ? isOutputKey(key) : isOutputValue({
|
|
595
|
+
key,
|
|
596
|
+
value
|
|
597
|
+
}))));
|
|
598
|
+
}
|
|
561
599
|
/**
|
|
562
600
|
* Merges Studio-provided adapter option overrides into the disk config's adapter, see {@link rebuildExtension}.
|
|
563
601
|
*/
|
|
@@ -600,13 +638,7 @@ const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
|
600
638
|
* line count, so `enable-plugin` restores exactly those lines and not whatever comments follow.
|
|
601
639
|
*/
|
|
602
640
|
const DISABLED_MARKER = /^\/\/ kubb:disabled (.+)\s+(\d+)$/;
|
|
603
|
-
const OPERATIONS =
|
|
604
|
-
"set",
|
|
605
|
-
"remove",
|
|
606
|
-
"add-plugin",
|
|
607
|
-
"disable-plugin",
|
|
608
|
-
"enable-plugin"
|
|
609
|
-
]);
|
|
641
|
+
const OPERATIONS = new Set(Object.values(ConfigEditOperation));
|
|
610
642
|
const KINDS = /* @__PURE__ */ new Set([
|
|
611
643
|
"plugin",
|
|
612
644
|
"adapter",
|
|
@@ -617,17 +649,20 @@ const KINDS = /* @__PURE__ */ new Set([
|
|
|
617
649
|
* The edit crossed the wire, so its declared type is a promise the sender may not have kept.
|
|
618
650
|
*/
|
|
619
651
|
function invalidEdit(edit) {
|
|
620
|
-
const { operation, plugin, config, path, kind } = typeof edit === "object" && edit !== null ? edit : {};
|
|
652
|
+
const { operation, plugin, config, path, kind, input } = typeof edit === "object" && edit !== null ? edit : {};
|
|
621
653
|
if (typeof operation !== "string" || !OPERATIONS.has(operation)) return `unknown operation ${JSON.stringify(operation)}`;
|
|
622
|
-
if (typeof plugin !== "string" || !plugin) return "the edit names no plugin";
|
|
623
|
-
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
654
|
if (config !== void 0 && typeof config !== "string" && !Number.isSafeInteger(config)) return "config must name an entry or give its index";
|
|
626
|
-
if (operation ===
|
|
655
|
+
if (operation === ConfigEditOperation.SET_INPUT) return typeof input === "string" && input.trim() ? void 0 : "the edit names no input";
|
|
656
|
+
const isOutput = operation === ConfigEditOperation.SET_OUTPUT || operation === ConfigEditOperation.REMOVE_OUTPUT;
|
|
657
|
+
if (!isOutput && (typeof plugin !== "string" || !plugin)) return "the edit names no plugin";
|
|
658
|
+
if (kind !== void 0 && (typeof kind !== "string" || !KINDS.has(kind))) return `unknown kind ${JSON.stringify(kind)}`;
|
|
659
|
+
if (kind !== void 0 && operation !== ConfigEditOperation.SET && operation !== ConfigEditOperation.REMOVE) return `${operation} edits the plugins array, so it takes no kind`;
|
|
660
|
+
if (isOutput || operation === ConfigEditOperation.SET || operation === ConfigEditOperation.REMOVE) {
|
|
627
661
|
if (!Array.isArray(path) || !path.every((key) => typeof key === "string" && key)) return "path must be a list of option names";
|
|
628
662
|
if (!path.length) return "no option path given";
|
|
629
663
|
const unsafe = path.find((key) => !isSafeKey(key));
|
|
630
664
|
if (unsafe !== void 0) return `${unsafe} cannot be used as an option name`;
|
|
665
|
+
if (isOutput && !isOutputKey(path[0])) return `output.${path[0]} can only be changed in the config file`;
|
|
631
666
|
}
|
|
632
667
|
}
|
|
633
668
|
/** Where a node sits in the source text. */
|
|
@@ -928,9 +963,12 @@ function readPartial(node, prefix = []) {
|
|
|
928
963
|
}
|
|
929
964
|
/** The top-level options of a plugin call, each flagged with whether Studio may write it. */
|
|
930
965
|
function readOptions(call) {
|
|
931
|
-
const entries = {};
|
|
932
966
|
const options = call.arguments[0];
|
|
933
|
-
|
|
967
|
+
return options?.type === "ObjectExpression" ? readObject(options) : {};
|
|
968
|
+
}
|
|
969
|
+
/** The members of an object literal, each flagged with whether Studio may write it. */
|
|
970
|
+
function readObject(options) {
|
|
971
|
+
const entries = {};
|
|
934
972
|
for (const [index, entry] of options.properties.entries()) {
|
|
935
973
|
const key = memberKey(entry);
|
|
936
974
|
if (key === void 0 || !isSafeKey(key)) continue;
|
|
@@ -1003,11 +1041,13 @@ function readConfig(source) {
|
|
|
1003
1041
|
});
|
|
1004
1042
|
const [adapter] = factoryCalls(mod, config, "adapter").map(toView);
|
|
1005
1043
|
const parsers = factoryCalls(mod, config, "parser").map(toView);
|
|
1044
|
+
const output = outputObject(config);
|
|
1006
1045
|
return {
|
|
1007
1046
|
name: configName(config),
|
|
1008
1047
|
plugins,
|
|
1009
1048
|
...adapter && { adapter },
|
|
1010
|
-
...parsers.length ? { parsers } : {}
|
|
1049
|
+
...parsers.length ? { parsers } : {},
|
|
1050
|
+
...!("reason" in output) && { output: pickOutput(readObject(output.object)) }
|
|
1011
1051
|
};
|
|
1012
1052
|
})
|
|
1013
1053
|
};
|
|
@@ -1056,6 +1096,16 @@ function applySet(source, call, path, value, quote) {
|
|
|
1056
1096
|
return { source: splice(source, closing, closing, printValue(nest(path, value), quote)) };
|
|
1057
1097
|
}
|
|
1058
1098
|
if (options.type !== "ObjectExpression") return { reason: "the factory was not called with an object literal" };
|
|
1099
|
+
return setIn({
|
|
1100
|
+
source,
|
|
1101
|
+
options,
|
|
1102
|
+
path,
|
|
1103
|
+
value,
|
|
1104
|
+
quote
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
/** Writes the literal `value` at `path` inside `options`, adding any intermediate object it needs. */
|
|
1108
|
+
function setIn({ source, options, path, value, quote }) {
|
|
1059
1109
|
const target = walk(options, path);
|
|
1060
1110
|
if ("reason" in target) return target;
|
|
1061
1111
|
if ("missing" in target) {
|
|
@@ -1064,6 +1114,43 @@ function applySet(source, call, path, value, quote) {
|
|
|
1064
1114
|
}
|
|
1065
1115
|
return { source: splice(source, rangeOf(target.value).start, rangeOf(target.value).end, printValue(value, quote)) };
|
|
1066
1116
|
}
|
|
1117
|
+
/** Writes the config's top-level `input` as a string literal, replacing what it held. */
|
|
1118
|
+
function applySetInput(source, config, input, quote) {
|
|
1119
|
+
const value = printValue(input, quote);
|
|
1120
|
+
const found = findMember(config, "input");
|
|
1121
|
+
if (!found) return { source: insertItem(source, config, `input: ${value}`) };
|
|
1122
|
+
if (found.member.type !== "ObjectProperty") return { reason: "input is not a plain property" };
|
|
1123
|
+
if (isShadowed(config, found.index)) return { reason: "a spread after input could override it" };
|
|
1124
|
+
return { source: splice(source, rangeOf(found.member.value).start, rangeOf(found.member.value).end, value) };
|
|
1125
|
+
}
|
|
1126
|
+
/** The config's root `output` object literal, or why Studio cannot edit inside it. */
|
|
1127
|
+
function outputObject(config) {
|
|
1128
|
+
const found = findMember(config, "output");
|
|
1129
|
+
if (found?.member.type !== "ObjectProperty" || found.member.value.type !== "ObjectExpression") return { reason: "output is not an object literal" };
|
|
1130
|
+
if (isShadowed(config, found.index)) return { reason: "a spread after output could override it" };
|
|
1131
|
+
return { object: found.member.value };
|
|
1132
|
+
}
|
|
1133
|
+
/** Writes or drops a member of the config's root `output` object. */
|
|
1134
|
+
function applyOutputEdit({ source, config, edit, quote }) {
|
|
1135
|
+
const output = outputObject(config);
|
|
1136
|
+
if ("reason" in output) return output;
|
|
1137
|
+
if (edit.operation === ConfigEditOperation.REMOVE_OUTPUT) return removeIn({
|
|
1138
|
+
source,
|
|
1139
|
+
options: output.object,
|
|
1140
|
+
path: edit.path
|
|
1141
|
+
});
|
|
1142
|
+
if (!isOptionValue(edit.value) || !isOutputValue({
|
|
1143
|
+
key: edit.path[0],
|
|
1144
|
+
value: nest(edit.path.slice(1), edit.value)
|
|
1145
|
+
})) return { reason: `the value is not one output.${edit.path[0]} takes` };
|
|
1146
|
+
return setIn({
|
|
1147
|
+
source,
|
|
1148
|
+
options: output.object,
|
|
1149
|
+
path: edit.path,
|
|
1150
|
+
value: edit.value,
|
|
1151
|
+
quote
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1067
1154
|
/**
|
|
1068
1155
|
* Deletes the option at `path`, falling the plugin back to its default for it. An option the file
|
|
1069
1156
|
* does not set already has its default, so removing it changes nothing.
|
|
@@ -1072,6 +1159,14 @@ function applyRemove(source, call, path) {
|
|
|
1072
1159
|
const options = call.arguments[0];
|
|
1073
1160
|
if (!options) return { source };
|
|
1074
1161
|
if (options.type !== "ObjectExpression") return { reason: "the factory was not called with an object literal" };
|
|
1162
|
+
return removeIn({
|
|
1163
|
+
source,
|
|
1164
|
+
options,
|
|
1165
|
+
path
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
/** Deletes the member at `path` inside `options`. A path that is not there already holds its default. */
|
|
1169
|
+
function removeIn({ source, options, path }) {
|
|
1075
1170
|
const target = walk(options, path);
|
|
1076
1171
|
if ("reason" in target) return target;
|
|
1077
1172
|
if ("missing" in target) return { source };
|
|
@@ -1160,7 +1255,14 @@ function applyEdit({ source, edit, quote, extensions }) {
|
|
|
1160
1255
|
const findConfig = (entries) => typeof ref === "string" ? entries.configs.find((entry) => configName(entry) === ref) : entries.configs[ref ?? 0];
|
|
1161
1256
|
const config = findConfig(parsed);
|
|
1162
1257
|
if (!config) return { reason: `no config entry found for ${JSON.stringify(ref)}` };
|
|
1163
|
-
if (edit.operation ===
|
|
1258
|
+
if (edit.operation === ConfigEditOperation.SET_INPUT) return applySetInput(source, config, edit.input, quote);
|
|
1259
|
+
if (edit.operation === ConfigEditOperation.SET_OUTPUT || edit.operation === ConfigEditOperation.REMOVE_OUTPUT) return applyOutputEdit({
|
|
1260
|
+
source,
|
|
1261
|
+
config,
|
|
1262
|
+
edit,
|
|
1263
|
+
quote
|
|
1264
|
+
});
|
|
1265
|
+
if (edit.operation === ConfigEditOperation.DISABLE_PLUGIN) {
|
|
1164
1266
|
const result = disablePlugin(source, parsed.mod, config, edit.plugin);
|
|
1165
1267
|
if (!("reason" in result) || !result.reason.includes("shares a line")) return result;
|
|
1166
1268
|
const expanded = expandPluginsArray(source, config);
|
|
@@ -1168,8 +1270,8 @@ function applyEdit({ source, edit, quote, extensions }) {
|
|
|
1168
1270
|
const reparsedConfig = reparsed && !("reason" in reparsed) ? findConfig(reparsed) : void 0;
|
|
1169
1271
|
return expanded !== void 0 && reparsed && !("reason" in reparsed) && reparsedConfig ? disablePlugin(expanded, reparsed.mod, reparsedConfig, edit.plugin) : result;
|
|
1170
1272
|
}
|
|
1171
|
-
if (edit.operation ===
|
|
1172
|
-
if (edit.operation ===
|
|
1273
|
+
if (edit.operation === ConfigEditOperation.ENABLE_PLUGIN) return enablePlugin(source, config, edit.plugin);
|
|
1274
|
+
if (edit.operation === ConfigEditOperation.ADD_PLUGIN) return applyAddPlugin({
|
|
1173
1275
|
source,
|
|
1174
1276
|
parsed,
|
|
1175
1277
|
config,
|
|
@@ -1185,7 +1287,7 @@ function applyEdit({ source, edit, quote, extensions }) {
|
|
|
1185
1287
|
if (kind === "adapter") return { reason: `${edit.plugin} is not the config's adapter` };
|
|
1186
1288
|
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
1289
|
}
|
|
1188
|
-
return edit.operation ===
|
|
1290
|
+
return edit.operation === ConfigEditOperation.SET ? applySet(source, call, edit.path, edit.value, quote) : applyRemove(source, call, edit.path);
|
|
1189
1291
|
}
|
|
1190
1292
|
/**
|
|
1191
1293
|
* Applies edits to a `kubb.config.ts` in place. Each edit rewrites only the text it targets, so
|
|
@@ -2123,6 +2225,7 @@ var Agent = class extends RpcTarget {
|
|
|
2123
2225
|
allowConfigEdit: false,
|
|
2124
2226
|
allowInstall: false,
|
|
2125
2227
|
allowUpdate: false,
|
|
2228
|
+
allowSaveInput: false,
|
|
2126
2229
|
allowRead: true,
|
|
2127
2230
|
allowReadAst: true
|
|
2128
2231
|
} : permissions;
|
|
@@ -2148,13 +2251,13 @@ var Agent = class extends RpcTarget {
|
|
|
2148
2251
|
permission
|
|
2149
2252
|
});
|
|
2150
2253
|
}
|
|
2151
|
-
async #refuse({ message, permission }) {
|
|
2254
|
+
async #refuse({ message, permission, code = Diagnostics.code.unknown }) {
|
|
2152
2255
|
await this.#warn({
|
|
2153
2256
|
message,
|
|
2154
2257
|
permission
|
|
2155
2258
|
});
|
|
2156
2259
|
throw new Diagnostics.Error({
|
|
2157
|
-
code
|
|
2260
|
+
code,
|
|
2158
2261
|
severity: "error",
|
|
2159
2262
|
message
|
|
2160
2263
|
});
|
|
@@ -2346,12 +2449,22 @@ var Agent = class extends RpcTarget {
|
|
|
2346
2449
|
});
|
|
2347
2450
|
const activePlugins = plugins ?? config.plugins;
|
|
2348
2451
|
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, {
|
|
2452
|
+
const checkout = this.#managed && studioInput !== void 0 && getInputKind(studioInput) === "git" ? await checkoutGitInput(studioInput, signal, {
|
|
2453
|
+
publicOnly: true,
|
|
2454
|
+
authorization: input.gitAuth?.authorization
|
|
2455
|
+
}).catch((error) => this.#refuse({
|
|
2456
|
+
message: getErrorMessage(error),
|
|
2457
|
+
code: Diagnostics.code.gitInputFailed
|
|
2458
|
+
})) : void 0;
|
|
2350
2459
|
const run = () => generate({
|
|
2351
2460
|
config: {
|
|
2352
2461
|
...config,
|
|
2353
2462
|
root,
|
|
2354
2463
|
input: checkout?.path ?? studioInput ?? config.input,
|
|
2464
|
+
output: mergeOutput({
|
|
2465
|
+
output: config.output,
|
|
2466
|
+
studio: patch?.output
|
|
2467
|
+
}),
|
|
2355
2468
|
storage: outputRoot ? stagingStorage({
|
|
2356
2469
|
root,
|
|
2357
2470
|
directory: outputRoot
|
|
@@ -2420,11 +2533,29 @@ var Agent = class extends RpcTarget {
|
|
|
2420
2533
|
if (busy) return refuse(busy);
|
|
2421
2534
|
this.#session.saving = true;
|
|
2422
2535
|
try {
|
|
2423
|
-
const
|
|
2536
|
+
const kept = [];
|
|
2537
|
+
for (const edit of edits) kept.push(await this.#keepInput(edit));
|
|
2538
|
+
const written = await writeConfigEdits({
|
|
2424
2539
|
filePath: configFile,
|
|
2425
|
-
edits,
|
|
2540
|
+
edits: kept.flatMap((edit) => "reason" in edit ? [] : [edit]),
|
|
2426
2541
|
extensions: await this.#installedExtensions()
|
|
2427
2542
|
});
|
|
2543
|
+
let next = 0;
|
|
2544
|
+
const outcomes = kept.map((edit, index) => "reason" in edit ? {
|
|
2545
|
+
edit: edits[index],
|
|
2546
|
+
applied: false,
|
|
2547
|
+
reason: edit.reason
|
|
2548
|
+
} : {
|
|
2549
|
+
...written.outcomes[next++],
|
|
2550
|
+
edit: edits[index]
|
|
2551
|
+
});
|
|
2552
|
+
let wroteDocument = false;
|
|
2553
|
+
for (const [index, edit] of kept.entries()) {
|
|
2554
|
+
if (!("document" in edit) || !edit.document || !outcomes[index].applied) continue;
|
|
2555
|
+
await writeFile(path.join(this.#options.root, edit.document.fileName), edit.document.text, "utf-8");
|
|
2556
|
+
wroteDocument = true;
|
|
2557
|
+
}
|
|
2558
|
+
const changed = written.changed || wroteDocument;
|
|
2428
2559
|
const applied = outcomes.filter((outcome) => outcome.applied).length;
|
|
2429
2560
|
await this.#logCommand("saveConfig", `applied ${applied}/${outcomes.length} edits to ${configPath}`, input.requestedBy);
|
|
2430
2561
|
return {
|
|
@@ -2439,6 +2570,33 @@ var Agent = class extends RpcTarget {
|
|
|
2439
2570
|
}
|
|
2440
2571
|
}
|
|
2441
2572
|
/**
|
|
2573
|
+
* Readies a `set-input` edit for the config file: a document is pointed at `openapi.yaml` (`openapi.json`
|
|
2574
|
+
* for JSON) in the project, one file per config entry, for `saveConfig` to write once the edit applies.
|
|
2575
|
+
* A managed agent refuses a file path, as in a generation.
|
|
2576
|
+
*/
|
|
2577
|
+
async #keepInput(edit) {
|
|
2578
|
+
if (edit?.operation !== ConfigEditOperation.SET_INPUT || typeof edit.input !== "string" || !edit.input.trim()) return edit;
|
|
2579
|
+
if (!this.#permissions.allowSaveInput) {
|
|
2580
|
+
await this.#warn({
|
|
2581
|
+
message: "Ignored the spec to save: saving the input was not granted",
|
|
2582
|
+
permission: AgentPermission.SAVE_INPUT
|
|
2583
|
+
});
|
|
2584
|
+
return { reason: "the agent was not granted permission to save the input" };
|
|
2585
|
+
}
|
|
2586
|
+
const kind = getInputKind(edit.input);
|
|
2587
|
+
if (this.#managed && !MANAGED_INPUT_KINDS.has(kind)) return { reason: AgentMessage.MANAGED_INPUT_KIND };
|
|
2588
|
+
if (kind !== "inline") return edit;
|
|
2589
|
+
const fileName = `openapi${edit.config === void 0 || edit.config === 0 ? "" : `-${String(edit.config).replace(/[^\w.-]/g, "-")}`}.${isJson(edit.input) ? "json" : "yaml"}`;
|
|
2590
|
+
return {
|
|
2591
|
+
...edit,
|
|
2592
|
+
input: `./${fileName}`,
|
|
2593
|
+
document: {
|
|
2594
|
+
fileName,
|
|
2595
|
+
text: edit.input
|
|
2596
|
+
}
|
|
2597
|
+
};
|
|
2598
|
+
}
|
|
2599
|
+
/**
|
|
2442
2600
|
* Installs a registry extension into the project with `kubb add <id> --yes`, which also adds a
|
|
2443
2601
|
* plugin to `kubb.config.ts`. Studio calls `connect()` again afterwards to see the result.
|
|
2444
2602
|
*/
|
|
@@ -2739,6 +2897,15 @@ function pickConfig(configs, ref) {
|
|
|
2739
2897
|
if (ref !== void 0 && !Number.isSafeInteger(ref)) return void 0;
|
|
2740
2898
|
return configs[ref ?? 0];
|
|
2741
2899
|
}
|
|
2900
|
+
/** Whether `text` parses as JSON, so a pasted document is saved under the matching extension. */
|
|
2901
|
+
function isJson(text) {
|
|
2902
|
+
try {
|
|
2903
|
+
JSON.parse(text);
|
|
2904
|
+
return true;
|
|
2905
|
+
} catch {
|
|
2906
|
+
return false;
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2742
2909
|
/** The input path or URL Studio may show: no inline document, and no credentials from a URL. */
|
|
2743
2910
|
function reportedInput(input) {
|
|
2744
2911
|
if (typeof input !== "string") return void 0;
|
|
@@ -2757,6 +2924,7 @@ function toEntryView(config, extensions) {
|
|
|
2757
2924
|
...config.name !== void 0 && { name: config.name },
|
|
2758
2925
|
...input !== void 0 && { input },
|
|
2759
2926
|
output: config.output.path,
|
|
2927
|
+
outputOptions: toJSONOptions(pickOutput(config.output)),
|
|
2760
2928
|
plugins: config.plugins.map((plugin) => ({
|
|
2761
2929
|
name: toStudioName({
|
|
2762
2930
|
name: resolveMeta(plugin).name,
|
|
@@ -2794,6 +2962,7 @@ async function runAgent({ options, openRpc }) {
|
|
|
2794
2962
|
allowConfigEdit: false,
|
|
2795
2963
|
allowInstall: false,
|
|
2796
2964
|
allowUpdate: false,
|
|
2965
|
+
allowSaveInput: false,
|
|
2797
2966
|
allowExec: false,
|
|
2798
2967
|
allowRead: false,
|
|
2799
2968
|
allowReadAst: false,
|