@kubb/studio 5.12.1 → 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 +246 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +246 -27
- package/dist/index.js.map +1 -1
- package/dist/{protocol-CG1bPO_H.d.ts → protocol-WYICLbSg.d.cts} +68 -7
- package/dist/{protocol-CG1bPO_H.d.cts → protocol-WYICLbSg.d.ts} +68 -7
- package/dist/protocol.cjs +17 -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 +17 -1
- package/dist/protocol.js.map +1 -1
- package/package.json +5 -4
package/dist/index.cjs
CHANGED
|
@@ -23,20 +23,20 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
//#endregion
|
|
24
24
|
const require_protocol = require("./protocol.cjs");
|
|
25
25
|
let node_crypto = require("node:crypto");
|
|
26
|
+
let node_fs = require("node:fs");
|
|
27
|
+
let node_fs_promises = require("node:fs/promises");
|
|
28
|
+
let node_module = require("node:module");
|
|
26
29
|
let node_os = require("node:os");
|
|
27
30
|
let node_path = require("node:path");
|
|
28
31
|
node_path = __toESM(node_path, 1);
|
|
29
32
|
let node_process = require("node:process");
|
|
30
33
|
node_process = __toESM(node_process, 1);
|
|
31
34
|
let node_timers_promises = require("node:timers/promises");
|
|
32
|
-
let
|
|
33
|
-
let node_stream = require("node:stream");
|
|
34
|
-
let node_fs = require("node:fs");
|
|
35
|
-
let node_fs_promises = require("node:fs/promises");
|
|
35
|
+
let tinyexec = require("tinyexec");
|
|
36
36
|
let _kubb_core = require("@kubb/core");
|
|
37
37
|
let capnweb = require("capnweb");
|
|
38
38
|
let magicast = require("magicast");
|
|
39
|
-
let
|
|
39
|
+
let node_url = require("node:url");
|
|
40
40
|
let diff = require("diff");
|
|
41
41
|
let node_util = require("node:util");
|
|
42
42
|
let _kubb_ast = require("@kubb/ast");
|
|
@@ -48,6 +48,7 @@ let node_https = require("node:https");
|
|
|
48
48
|
node_https = __toESM(node_https, 1);
|
|
49
49
|
let node_net = require("node:net");
|
|
50
50
|
node_net = __toESM(node_net, 1);
|
|
51
|
+
let node_stream = require("node:stream");
|
|
51
52
|
let ofetch = require("ofetch");
|
|
52
53
|
let node_zlib = require("node:zlib");
|
|
53
54
|
//#region ../../internals/utils/src/casing.ts
|
|
@@ -229,7 +230,7 @@ function getElapsedMs(hrStart) {
|
|
|
229
230
|
}
|
|
230
231
|
//#endregion
|
|
231
232
|
//#region package.json
|
|
232
|
-
var version = "5.12.
|
|
233
|
+
var version = "5.12.3";
|
|
233
234
|
//#endregion
|
|
234
235
|
//#region src/resolveConfig.ts
|
|
235
236
|
/**
|
|
@@ -627,13 +628,7 @@ const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
|
627
628
|
* line count, so `enable-plugin` restores exactly those lines and not whatever comments follow.
|
|
628
629
|
*/
|
|
629
630
|
const DISABLED_MARKER = /^\/\/ kubb:disabled (.+)\s+(\d+)$/;
|
|
630
|
-
const OPERATIONS =
|
|
631
|
-
"set",
|
|
632
|
-
"remove",
|
|
633
|
-
"add-plugin",
|
|
634
|
-
"disable-plugin",
|
|
635
|
-
"enable-plugin"
|
|
636
|
-
]);
|
|
631
|
+
const OPERATIONS = new Set(Object.values(require_protocol.ConfigEditOperation));
|
|
637
632
|
const KINDS = /* @__PURE__ */ new Set([
|
|
638
633
|
"plugin",
|
|
639
634
|
"adapter",
|
|
@@ -644,13 +639,14 @@ const KINDS = /* @__PURE__ */ new Set([
|
|
|
644
639
|
* The edit crossed the wire, so its declared type is a promise the sender may not have kept.
|
|
645
640
|
*/
|
|
646
641
|
function invalidEdit(edit) {
|
|
647
|
-
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 : {};
|
|
648
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";
|
|
649
646
|
if (typeof plugin !== "string" || !plugin) return "the edit names no plugin";
|
|
650
647
|
if (kind !== void 0 && (typeof kind !== "string" || !KINDS.has(kind))) return `unknown kind ${JSON.stringify(kind)}`;
|
|
651
|
-
if (kind !== void 0 && operation !==
|
|
652
|
-
if (
|
|
653
|
-
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) {
|
|
654
650
|
if (!Array.isArray(path) || !path.every((key) => typeof key === "string" && key)) return "path must be a list of option names";
|
|
655
651
|
if (!path.length) return "no option path given";
|
|
656
652
|
const unsafe = path.find((key) => !isSafeKey(key));
|
|
@@ -1091,6 +1087,15 @@ function applySet(source, call, path, value, quote) {
|
|
|
1091
1087
|
}
|
|
1092
1088
|
return { source: splice(source, rangeOf(target.value).start, rangeOf(target.value).end, printValue(value, quote)) };
|
|
1093
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
|
+
}
|
|
1094
1099
|
/**
|
|
1095
1100
|
* Deletes the option at `path`, falling the plugin back to its default for it. An option the file
|
|
1096
1101
|
* does not set already has its default, so removing it changes nothing.
|
|
@@ -1187,7 +1192,8 @@ function applyEdit({ source, edit, quote, extensions }) {
|
|
|
1187
1192
|
const findConfig = (entries) => typeof ref === "string" ? entries.configs.find((entry) => configName(entry) === ref) : entries.configs[ref ?? 0];
|
|
1188
1193
|
const config = findConfig(parsed);
|
|
1189
1194
|
if (!config) return { reason: `no config entry found for ${JSON.stringify(ref)}` };
|
|
1190
|
-
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) {
|
|
1191
1197
|
const result = disablePlugin(source, parsed.mod, config, edit.plugin);
|
|
1192
1198
|
if (!("reason" in result) || !result.reason.includes("shares a line")) return result;
|
|
1193
1199
|
const expanded = expandPluginsArray(source, config);
|
|
@@ -1195,8 +1201,8 @@ function applyEdit({ source, edit, quote, extensions }) {
|
|
|
1195
1201
|
const reparsedConfig = reparsed && !("reason" in reparsed) ? findConfig(reparsed) : void 0;
|
|
1196
1202
|
return expanded !== void 0 && reparsed && !("reason" in reparsed) && reparsedConfig ? disablePlugin(expanded, reparsed.mod, reparsedConfig, edit.plugin) : result;
|
|
1197
1203
|
}
|
|
1198
|
-
if (edit.operation ===
|
|
1199
|
-
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({
|
|
1200
1206
|
source,
|
|
1201
1207
|
parsed,
|
|
1202
1208
|
config,
|
|
@@ -1212,7 +1218,7 @@ function applyEdit({ source, edit, quote, extensions }) {
|
|
|
1212
1218
|
if (kind === "adapter") return { reason: `${edit.plugin} is not the config's adapter` };
|
|
1213
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` };
|
|
1214
1220
|
}
|
|
1215
|
-
return edit.operation ===
|
|
1221
|
+
return edit.operation === require_protocol.ConfigEditOperation.SET ? applySet(source, call, edit.path, edit.value, quote) : applyRemove(source, call, edit.path);
|
|
1216
1222
|
}
|
|
1217
1223
|
/**
|
|
1218
1224
|
* Applies edits to a `kubb.config.ts` in place. Each edit rewrites only the text it targets, so
|
|
@@ -2041,6 +2047,43 @@ var IncompatibleAgentError = class extends Error {
|
|
|
2041
2047
|
this.name = "IncompatibleAgentError";
|
|
2042
2048
|
}
|
|
2043
2049
|
};
|
|
2050
|
+
const INSTALL_TIMEOUT_MS = 3e5;
|
|
2051
|
+
/** `owner/slug` or `slug`, as `kubb add` takes it. Checked before it becomes an argument. */
|
|
2052
|
+
const REGISTRY_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)?$/;
|
|
2053
|
+
/** The CLI the project depends on, so `kubb add` installs next to what the config imports. */
|
|
2054
|
+
function findKubbBin(root) {
|
|
2055
|
+
const require$1 = (0, node_module.createRequire)(node_path.default.resolve(root, "package.json"));
|
|
2056
|
+
const names = ["kubb", "@kubb/cli"];
|
|
2057
|
+
return (require$1.resolve.paths("kubb") ?? []).flatMap((dir) => names.map((name) => node_path.default.join(dir, name, "bin", "kubb.js"))).find((bin) => (0, node_fs.existsSync)(bin));
|
|
2058
|
+
}
|
|
2059
|
+
/** Runs a `kubb` command without a shell, and keeps the end of its output for the failure reason. */
|
|
2060
|
+
async function runKubb({ bin, args, cwd, signal }) {
|
|
2061
|
+
try {
|
|
2062
|
+
const { stdout, stderr, exitCode } = await (0, tinyexec.x)(node_process.default.execPath, [bin, ...args], {
|
|
2063
|
+
signal,
|
|
2064
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
2065
|
+
throwOnError: false,
|
|
2066
|
+
nodeOptions: {
|
|
2067
|
+
cwd,
|
|
2068
|
+
env: {
|
|
2069
|
+
...node_process.default.env,
|
|
2070
|
+
NO_COLOR: "1",
|
|
2071
|
+
FORCE_COLOR: "0"
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
});
|
|
2075
|
+
const output = `${stdout}\n${stderr}`.trim().split("\n").slice(-20).join("\n");
|
|
2076
|
+
return {
|
|
2077
|
+
failed: exitCode !== 0,
|
|
2078
|
+
output: output || `kubb ${args[0]} exited with ${exitCode ?? "a signal"}`
|
|
2079
|
+
};
|
|
2080
|
+
} catch (error) {
|
|
2081
|
+
return {
|
|
2082
|
+
failed: true,
|
|
2083
|
+
output: `kubb ${args[0]} did not run: ${getErrorMessage(error)}`
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2044
2087
|
function emit(hooks, name, ...args) {
|
|
2045
2088
|
return Promise.resolve(hooks.callHook(name, ...args)).catch(() => {});
|
|
2046
2089
|
}
|
|
@@ -2111,6 +2154,9 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
2111
2154
|
...permissions,
|
|
2112
2155
|
allowWrite: false,
|
|
2113
2156
|
allowConfigEdit: false,
|
|
2157
|
+
allowInstall: false,
|
|
2158
|
+
allowUpdate: false,
|
|
2159
|
+
allowSaveInput: false,
|
|
2114
2160
|
allowRead: true,
|
|
2115
2161
|
allowReadAst: true
|
|
2116
2162
|
} : permissions;
|
|
@@ -2136,13 +2182,13 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
2136
2182
|
permission
|
|
2137
2183
|
});
|
|
2138
2184
|
}
|
|
2139
|
-
async #refuse({ message, permission }) {
|
|
2185
|
+
async #refuse({ message, permission, code = _kubb_core.Diagnostics.code.unknown }) {
|
|
2140
2186
|
await this.#warn({
|
|
2141
2187
|
message,
|
|
2142
2188
|
permission
|
|
2143
2189
|
});
|
|
2144
2190
|
throw new _kubb_core.Diagnostics.Error({
|
|
2145
|
-
code
|
|
2191
|
+
code,
|
|
2146
2192
|
severity: "error",
|
|
2147
2193
|
message
|
|
2148
2194
|
});
|
|
@@ -2262,6 +2308,8 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
2262
2308
|
message: "Connection is closed"
|
|
2263
2309
|
});
|
|
2264
2310
|
if (this.#session.active) return this.#refuse({ message: require_protocol.AgentMessage.GENERATION_IN_PROGRESS });
|
|
2311
|
+
if (this.#session.install) return this.#refuse({ message: require_protocol.AgentMessage.INSTALL_IN_PROGRESS });
|
|
2312
|
+
if (this.#session.saving) return this.#refuse({ message: require_protocol.AgentMessage.SAVE_IN_PROGRESS });
|
|
2265
2313
|
const settled = Promise.withResolvers();
|
|
2266
2314
|
this.#session.active = {
|
|
2267
2315
|
jobId: input.jobId,
|
|
@@ -2332,7 +2380,13 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
2332
2380
|
});
|
|
2333
2381
|
const activePlugins = plugins ?? config.plugins;
|
|
2334
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" });
|
|
2335
|
-
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;
|
|
2336
2390
|
const run = () => generate({
|
|
2337
2391
|
config: {
|
|
2338
2392
|
...config,
|
|
@@ -2402,13 +2456,33 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
2402
2456
|
await this.#warn({ message: "Ignored save: editing kubb.config.ts was not granted" });
|
|
2403
2457
|
return refuse("the agent was not granted permission to edit kubb.config.ts");
|
|
2404
2458
|
}
|
|
2405
|
-
|
|
2459
|
+
const busy = this.#busy();
|
|
2460
|
+
if (busy) return refuse(busy);
|
|
2461
|
+
this.#session.saving = true;
|
|
2406
2462
|
try {
|
|
2407
|
-
const
|
|
2463
|
+
const kept = [];
|
|
2464
|
+
for (const edit of edits) kept.push(await this.#keepInput(edit));
|
|
2465
|
+
const written = await writeConfigEdits({
|
|
2408
2466
|
filePath: configFile,
|
|
2409
|
-
edits,
|
|
2467
|
+
edits: kept.flatMap((edit) => "reason" in edit ? [] : [edit]),
|
|
2410
2468
|
extensions: await this.#installedExtensions()
|
|
2411
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;
|
|
2412
2486
|
const applied = outcomes.filter((outcome) => outcome.applied).length;
|
|
2413
2487
|
await this.#logCommand("saveConfig", `applied ${applied}/${outcomes.length} edits to ${configPath}`, input.requestedBy);
|
|
2414
2488
|
return {
|
|
@@ -2418,6 +2492,137 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
2418
2492
|
} catch (error) {
|
|
2419
2493
|
await emit(this.#options.hooks, "studio:error", { error: toError(error) });
|
|
2420
2494
|
return refuse(getErrorMessage(error));
|
|
2495
|
+
} finally {
|
|
2496
|
+
this.#session.saving = false;
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
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
|
+
/**
|
|
2527
|
+
* Installs a registry extension into the project with `kubb add <id> --yes`, which also adds a
|
|
2528
|
+
* plugin to `kubb.config.ts`. Studio calls `connect()` again afterwards to see the result.
|
|
2529
|
+
*/
|
|
2530
|
+
async installExtension(input) {
|
|
2531
|
+
this.#assertConnected();
|
|
2532
|
+
const { root, configFile, kubbBin = findKubbBin(root) } = this.#options;
|
|
2533
|
+
const refuse = async (reason, permission) => {
|
|
2534
|
+
await this.#warn({
|
|
2535
|
+
message: `Ignored install: ${reason}`,
|
|
2536
|
+
permission
|
|
2537
|
+
});
|
|
2538
|
+
return {
|
|
2539
|
+
installed: false,
|
|
2540
|
+
reason
|
|
2541
|
+
};
|
|
2542
|
+
};
|
|
2543
|
+
if (typeof input?.id !== "string" || !REGISTRY_ID.test(input.id)) return refuse("the message carried no registry id");
|
|
2544
|
+
if (!this.#permissions.allowInstall) return refuse("installing extensions was not granted", require_protocol.AgentPermission.INSTALL);
|
|
2545
|
+
if (!this.#permissions.allowConfigEdit) return refuse("editing kubb.config.ts was not granted", require_protocol.AgentPermission.CONFIG_EDIT);
|
|
2546
|
+
const busy = this.#busy();
|
|
2547
|
+
if (busy) return refuse(busy);
|
|
2548
|
+
if (!kubbBin) return refuse("kubb is not installed in the project");
|
|
2549
|
+
const { failed, output } = await this.#runKubb({
|
|
2550
|
+
bin: kubbBin,
|
|
2551
|
+
args: [
|
|
2552
|
+
"add",
|
|
2553
|
+
input.id,
|
|
2554
|
+
"--yes",
|
|
2555
|
+
"--config",
|
|
2556
|
+
configFile
|
|
2557
|
+
]
|
|
2558
|
+
});
|
|
2559
|
+
await this.#logCommand("installExtension", `${failed ? "failed to install" : "installed"} ${input.id}`, input.requestedBy, failed);
|
|
2560
|
+
return failed ? {
|
|
2561
|
+
installed: false,
|
|
2562
|
+
reason: output
|
|
2563
|
+
} : { installed: true };
|
|
2564
|
+
}
|
|
2565
|
+
/**
|
|
2566
|
+
* Moves installed extensions to the registry's version with `kubb update [ids] --yes`, which
|
|
2567
|
+
* installs packages and leaves `kubb.config.ts` alone. Studio calls `connect()` again afterwards.
|
|
2568
|
+
*/
|
|
2569
|
+
async updateExtensions(input) {
|
|
2570
|
+
this.#assertConnected();
|
|
2571
|
+
const { root, kubbBin = findKubbBin(root) } = this.#options;
|
|
2572
|
+
const refuse = async (reason, permission) => {
|
|
2573
|
+
await this.#warn({
|
|
2574
|
+
message: `Ignored update: ${reason}`,
|
|
2575
|
+
permission
|
|
2576
|
+
});
|
|
2577
|
+
return {
|
|
2578
|
+
updated: false,
|
|
2579
|
+
reason
|
|
2580
|
+
};
|
|
2581
|
+
};
|
|
2582
|
+
const ids = input?.ids ?? [];
|
|
2583
|
+
if (!Array.isArray(ids) || !ids.every((id) => typeof id === "string" && REGISTRY_ID.test(id))) return refuse("the message carried an invalid registry id");
|
|
2584
|
+
if (!this.#permissions.allowUpdate) return refuse("updating extensions was not granted", require_protocol.AgentPermission.UPDATE);
|
|
2585
|
+
const busy = this.#busy();
|
|
2586
|
+
if (busy) return refuse(busy);
|
|
2587
|
+
if (!kubbBin) return refuse("kubb is not installed in the project");
|
|
2588
|
+
const { failed, output } = await this.#runKubb({
|
|
2589
|
+
bin: kubbBin,
|
|
2590
|
+
args: [
|
|
2591
|
+
"update",
|
|
2592
|
+
...ids,
|
|
2593
|
+
"--yes"
|
|
2594
|
+
]
|
|
2595
|
+
});
|
|
2596
|
+
await this.#logCommand("updateExtensions", `${failed ? "failed to update" : "updated"} ${ids.join(", ") || "all extensions"}`, input?.requestedBy, failed);
|
|
2597
|
+
return failed ? {
|
|
2598
|
+
updated: false,
|
|
2599
|
+
reason: output
|
|
2600
|
+
} : { updated: true };
|
|
2601
|
+
}
|
|
2602
|
+
/** Why a command that touches the project cannot start right now. */
|
|
2603
|
+
#busy() {
|
|
2604
|
+
if (this.#session.active) return "a generation is in progress";
|
|
2605
|
+
if (this.#session.install) return "an install or update is in progress";
|
|
2606
|
+
if (this.#session.saving) return "a save is in progress";
|
|
2607
|
+
}
|
|
2608
|
+
/** Runs `kubb` in the project while holding off saves and generations, and ends it when the socket closes. */
|
|
2609
|
+
async #runKubb({ bin, args }) {
|
|
2610
|
+
const controller = new AbortController();
|
|
2611
|
+
const settled = Promise.withResolvers();
|
|
2612
|
+
this.#session.install = {
|
|
2613
|
+
abort: () => controller.abort(),
|
|
2614
|
+
settled: settled.promise
|
|
2615
|
+
};
|
|
2616
|
+
try {
|
|
2617
|
+
return await runKubb({
|
|
2618
|
+
bin,
|
|
2619
|
+
args,
|
|
2620
|
+
cwd: this.#options.root,
|
|
2621
|
+
signal: controller.signal
|
|
2622
|
+
});
|
|
2623
|
+
} finally {
|
|
2624
|
+
this.#session.install = void 0;
|
|
2625
|
+
settled.resolve();
|
|
2421
2626
|
}
|
|
2422
2627
|
}
|
|
2423
2628
|
/**
|
|
@@ -2602,6 +2807,8 @@ async function runSession({ options, openRpc, url, instanceId }) {
|
|
|
2602
2807
|
await emit(hooks, "studio:warn", { message: "Canceled the running generation: the connection to Kubb Studio closed" });
|
|
2603
2808
|
}
|
|
2604
2809
|
await active?.settled;
|
|
2810
|
+
session.install?.abort();
|
|
2811
|
+
await session.install?.settled;
|
|
2605
2812
|
await agent.clear();
|
|
2606
2813
|
return {
|
|
2607
2814
|
...close,
|
|
@@ -2617,6 +2824,15 @@ function pickConfig(configs, ref) {
|
|
|
2617
2824
|
if (ref !== void 0 && !Number.isSafeInteger(ref)) return void 0;
|
|
2618
2825
|
return configs[ref ?? 0];
|
|
2619
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
|
+
}
|
|
2620
2836
|
/** The input path or URL Studio may show: no inline document, and no credentials from a URL. */
|
|
2621
2837
|
function reportedInput(input) {
|
|
2622
2838
|
if (typeof input !== "string") return void 0;
|
|
@@ -2670,6 +2886,9 @@ async function runAgent({ options, openRpc }) {
|
|
|
2670
2886
|
permissions: {
|
|
2671
2887
|
allowWrite: false,
|
|
2672
2888
|
allowConfigEdit: false,
|
|
2889
|
+
allowInstall: false,
|
|
2890
|
+
allowUpdate: false,
|
|
2891
|
+
allowSaveInput: false,
|
|
2673
2892
|
allowExec: false,
|
|
2674
2893
|
allowRead: false,
|
|
2675
2894
|
allowReadAst: false,
|