@kubb/studio 5.12.0 → 5.12.2
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 +283 -11
- 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 +279 -11
- package/dist/index.js.map +1 -1
- package/dist/{protocol-Dn1jdnFB.d.ts → protocol-DhNrwhqp.d.cts} +43 -4
- package/dist/{protocol-Dn1jdnFB.d.cts → protocol-DhNrwhqp.d.ts} +43 -4
- package/dist/protocol.cjs +5 -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 +5 -0
- package/dist/protocol.js.map +1 -1
- package/package.json +5 -4
package/dist/index.cjs
CHANGED
|
@@ -23,22 +23,32 @@ 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_module = require("node:module");
|
|
26
28
|
let node_os = require("node:os");
|
|
27
29
|
let node_path = require("node:path");
|
|
28
30
|
node_path = __toESM(node_path, 1);
|
|
29
31
|
let node_process = require("node:process");
|
|
30
32
|
node_process = __toESM(node_process, 1);
|
|
31
33
|
let node_timers_promises = require("node:timers/promises");
|
|
32
|
-
let
|
|
33
|
-
let node_fs = require("node:fs");
|
|
34
|
+
let tinyexec = require("tinyexec");
|
|
34
35
|
let node_fs_promises = require("node:fs/promises");
|
|
35
36
|
let _kubb_core = require("@kubb/core");
|
|
36
37
|
let capnweb = require("capnweb");
|
|
37
38
|
let magicast = require("magicast");
|
|
38
|
-
let
|
|
39
|
+
let node_url = require("node:url");
|
|
39
40
|
let diff = require("diff");
|
|
40
41
|
let node_util = require("node:util");
|
|
41
42
|
let _kubb_ast = require("@kubb/ast");
|
|
43
|
+
let node_dns = require("node:dns");
|
|
44
|
+
node_dns = __toESM(node_dns, 1);
|
|
45
|
+
let node_http = require("node:http");
|
|
46
|
+
node_http = __toESM(node_http, 1);
|
|
47
|
+
let node_https = require("node:https");
|
|
48
|
+
node_https = __toESM(node_https, 1);
|
|
49
|
+
let node_net = require("node:net");
|
|
50
|
+
node_net = __toESM(node_net, 1);
|
|
51
|
+
let node_stream = require("node:stream");
|
|
42
52
|
let ofetch = require("ofetch");
|
|
43
53
|
let node_zlib = require("node:zlib");
|
|
44
54
|
//#region ../../internals/utils/src/casing.ts
|
|
@@ -220,7 +230,7 @@ function getElapsedMs(hrStart) {
|
|
|
220
230
|
}
|
|
221
231
|
//#endregion
|
|
222
232
|
//#region package.json
|
|
223
|
-
var version = "5.12.
|
|
233
|
+
var version = "5.12.2";
|
|
224
234
|
//#endregion
|
|
225
235
|
//#region src/resolveConfig.ts
|
|
226
236
|
/**
|
|
@@ -1866,6 +1876,106 @@ function createGenerationStream(hooks, jobId) {
|
|
|
1866
1876
|
};
|
|
1867
1877
|
}
|
|
1868
1878
|
//#endregion
|
|
1879
|
+
//#region src/publicFetch.ts
|
|
1880
|
+
const MAX_REDIRECTS = 5;
|
|
1881
|
+
const MAX_BYTES = 52428800;
|
|
1882
|
+
const REDIRECTS = /* @__PURE__ */ new Set([
|
|
1883
|
+
301,
|
|
1884
|
+
302,
|
|
1885
|
+
303,
|
|
1886
|
+
307,
|
|
1887
|
+
308
|
|
1888
|
+
]);
|
|
1889
|
+
const NO_BODY = /* @__PURE__ */ new Set([
|
|
1890
|
+
204,
|
|
1891
|
+
205,
|
|
1892
|
+
304
|
|
1893
|
+
]);
|
|
1894
|
+
function notPublic(host) {
|
|
1895
|
+
return /* @__PURE__ */ new Error(`${host} is not a public address, so a managed agent does not fetch from it`);
|
|
1896
|
+
}
|
|
1897
|
+
function createLookup(blocked) {
|
|
1898
|
+
return (hostname, options, callback) => {
|
|
1899
|
+
node_dns.default.lookup(hostname, {
|
|
1900
|
+
...options,
|
|
1901
|
+
all: true
|
|
1902
|
+
}, (error, addresses) => {
|
|
1903
|
+
if (error) return callback(error, "", 0);
|
|
1904
|
+
const found = addresses;
|
|
1905
|
+
if (!found.length || found.some(({ address }) => !(0, _kubb_core.isPublicAddress)(address, blocked))) return callback(notPublic(hostname), "", 0);
|
|
1906
|
+
return options.all ? callback(null, found) : callback(null, found[0].address, found[0].family);
|
|
1907
|
+
});
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
function request({ url, blocked, signal }) {
|
|
1911
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return Promise.reject(/* @__PURE__ */ new Error(`Only http and https URLs are fetched, not ${url.protocol}`));
|
|
1912
|
+
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
1913
|
+
if (node_net.default.isIP(host) && !(0, _kubb_core.isPublicAddress)(host, blocked)) return Promise.reject(notPublic(host));
|
|
1914
|
+
return new Promise((resolve, reject) => {
|
|
1915
|
+
const req = (url.protocol === "https:" ? node_https.default.request : node_http.default.request)(url, {
|
|
1916
|
+
method: "GET",
|
|
1917
|
+
lookup: createLookup(blocked),
|
|
1918
|
+
signal: signal ?? void 0,
|
|
1919
|
+
headers: { "accept-encoding": "identity" }
|
|
1920
|
+
}, (res) => {
|
|
1921
|
+
const headers = new Headers();
|
|
1922
|
+
for (const [key, value] of Object.entries(res.headers)) for (const item of [value].flat()) if (item !== void 0) headers.append(key, item);
|
|
1923
|
+
let total = 0;
|
|
1924
|
+
const limit = new node_stream.Transform({ transform(chunk, _encoding, done) {
|
|
1925
|
+
total += chunk.length;
|
|
1926
|
+
done(total > MAX_BYTES ? /* @__PURE__ */ new Error("The response is larger than 50 MB") : null, chunk);
|
|
1927
|
+
} });
|
|
1928
|
+
res.on("error", (error) => limit.destroy(error));
|
|
1929
|
+
res.pipe(limit);
|
|
1930
|
+
const body = NO_BODY.has(res.statusCode ?? 0) ? null : node_stream.Readable.toWeb(limit);
|
|
1931
|
+
resolve(new Response(body, {
|
|
1932
|
+
status: res.statusCode,
|
|
1933
|
+
statusText: res.statusMessage,
|
|
1934
|
+
headers
|
|
1935
|
+
}));
|
|
1936
|
+
});
|
|
1937
|
+
req.on("error", reject);
|
|
1938
|
+
req.end();
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
/**
|
|
1942
|
+
* Builds a `fetch` that only reaches addresses `blocked` does not list. It follows redirects itself
|
|
1943
|
+
* and checks every hop, and takes GET requests only, since that is all a spec load needs.
|
|
1944
|
+
*/
|
|
1945
|
+
function createPublicFetch(blocked) {
|
|
1946
|
+
const publicFetch = async (input, init) => {
|
|
1947
|
+
if (typeof input !== "string" && !(input instanceof URL)) throw new TypeError("A managed agent fetches by URL only");
|
|
1948
|
+
if (init?.method && init.method.toUpperCase() !== "GET") throw new TypeError("A managed agent only sends GET requests");
|
|
1949
|
+
let url = new URL(input);
|
|
1950
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
1951
|
+
const response = await request({
|
|
1952
|
+
url,
|
|
1953
|
+
blocked,
|
|
1954
|
+
signal: init?.signal
|
|
1955
|
+
});
|
|
1956
|
+
const location = response.headers.get("location");
|
|
1957
|
+
if (!REDIRECTS.has(response.status) || !location) return response;
|
|
1958
|
+
await response.body?.cancel();
|
|
1959
|
+
url = new URL(location, url);
|
|
1960
|
+
}
|
|
1961
|
+
throw new Error(`Stopped after ${MAX_REDIRECTS} redirects from ${String(input)}`);
|
|
1962
|
+
};
|
|
1963
|
+
return publicFetch;
|
|
1964
|
+
}
|
|
1965
|
+
/**
|
|
1966
|
+
* Runs `run` with the global `fetch` limited to public addresses, then puts the original back.
|
|
1967
|
+
* A managed agent runs one job at a time, so nothing else shares the window.
|
|
1968
|
+
*/
|
|
1969
|
+
async function withPublicFetch(run) {
|
|
1970
|
+
const original = globalThis.fetch;
|
|
1971
|
+
globalThis.fetch = createPublicFetch();
|
|
1972
|
+
try {
|
|
1973
|
+
return await run();
|
|
1974
|
+
} finally {
|
|
1975
|
+
globalThis.fetch = original;
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
//#endregion
|
|
1869
1979
|
//#region src/url.ts
|
|
1870
1980
|
/**
|
|
1871
1981
|
* Hosted Kubb Studio URL, used when a host passes none.
|
|
@@ -1902,6 +2012,11 @@ const OPEN_TIMEOUT_MS = 5e3;
|
|
|
1902
2012
|
const MAX_RETRY_DELAY_MS = 3e4;
|
|
1903
2013
|
const DISK_SNAPSHOT_MAX_FILES = 1e4;
|
|
1904
2014
|
const SANDBOX_GENERATION_TTL_MS = 9e5;
|
|
2015
|
+
const MANAGED_INPUT_KINDS = /* @__PURE__ */ new Set([
|
|
2016
|
+
"inline",
|
|
2017
|
+
"url",
|
|
2018
|
+
"git"
|
|
2019
|
+
]);
|
|
1905
2020
|
/** Extensions `kubb` ships with, so they count as installed without being a direct dependency. */
|
|
1906
2021
|
const BUILT_IN_PACKAGES = [
|
|
1907
2022
|
"@kubb/adapter-oas",
|
|
@@ -1927,6 +2042,43 @@ var IncompatibleAgentError = class extends Error {
|
|
|
1927
2042
|
this.name = "IncompatibleAgentError";
|
|
1928
2043
|
}
|
|
1929
2044
|
};
|
|
2045
|
+
const INSTALL_TIMEOUT_MS = 3e5;
|
|
2046
|
+
/** `owner/slug` or `slug`, as `kubb add` takes it. Checked before it becomes an argument. */
|
|
2047
|
+
const REGISTRY_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)?$/;
|
|
2048
|
+
/** The CLI the project depends on, so `kubb add` installs next to what the config imports. */
|
|
2049
|
+
function findKubbBin(root) {
|
|
2050
|
+
const require$1 = (0, node_module.createRequire)(node_path.default.resolve(root, "package.json"));
|
|
2051
|
+
const names = ["kubb", "@kubb/cli"];
|
|
2052
|
+
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));
|
|
2053
|
+
}
|
|
2054
|
+
/** Runs a `kubb` command without a shell, and keeps the end of its output for the failure reason. */
|
|
2055
|
+
async function runKubb({ bin, args, cwd, signal }) {
|
|
2056
|
+
try {
|
|
2057
|
+
const { stdout, stderr, exitCode } = await (0, tinyexec.x)(node_process.default.execPath, [bin, ...args], {
|
|
2058
|
+
signal,
|
|
2059
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
2060
|
+
throwOnError: false,
|
|
2061
|
+
nodeOptions: {
|
|
2062
|
+
cwd,
|
|
2063
|
+
env: {
|
|
2064
|
+
...node_process.default.env,
|
|
2065
|
+
NO_COLOR: "1",
|
|
2066
|
+
FORCE_COLOR: "0"
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
});
|
|
2070
|
+
const output = `${stdout}\n${stderr}`.trim().split("\n").slice(-20).join("\n");
|
|
2071
|
+
return {
|
|
2072
|
+
failed: exitCode !== 0,
|
|
2073
|
+
output: output || `kubb ${args[0]} exited with ${exitCode ?? "a signal"}`
|
|
2074
|
+
};
|
|
2075
|
+
} catch (error) {
|
|
2076
|
+
return {
|
|
2077
|
+
failed: true,
|
|
2078
|
+
output: `kubb ${args[0]} did not run: ${getErrorMessage(error)}`
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
1930
2082
|
function emit(hooks, name, ...args) {
|
|
1931
2083
|
return Promise.resolve(hooks.callHook(name, ...args)).catch(() => {});
|
|
1932
2084
|
}
|
|
@@ -1988,12 +2140,17 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1988
2140
|
get #sandbox() {
|
|
1989
2141
|
return this.#session.info?.sandbox === true;
|
|
1990
2142
|
}
|
|
2143
|
+
get #managed() {
|
|
2144
|
+
return this.#session.info?.managed === true;
|
|
2145
|
+
}
|
|
1991
2146
|
get #permissions() {
|
|
1992
2147
|
const { permissions } = this.#options;
|
|
1993
2148
|
return this.#sandbox ? {
|
|
1994
2149
|
...permissions,
|
|
1995
2150
|
allowWrite: false,
|
|
1996
2151
|
allowConfigEdit: false,
|
|
2152
|
+
allowInstall: false,
|
|
2153
|
+
allowUpdate: false,
|
|
1997
2154
|
allowRead: true,
|
|
1998
2155
|
allowReadAst: true
|
|
1999
2156
|
} : permissions;
|
|
@@ -2145,6 +2302,8 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
2145
2302
|
message: "Connection is closed"
|
|
2146
2303
|
});
|
|
2147
2304
|
if (this.#session.active) return this.#refuse({ message: require_protocol.AgentMessage.GENERATION_IN_PROGRESS });
|
|
2305
|
+
if (this.#session.install) return this.#refuse({ message: require_protocol.AgentMessage.INSTALL_IN_PROGRESS });
|
|
2306
|
+
if (this.#session.saving) return this.#refuse({ message: require_protocol.AgentMessage.SAVE_IN_PROGRESS });
|
|
2148
2307
|
const settled = Promise.withResolvers();
|
|
2149
2308
|
this.#session.active = {
|
|
2150
2309
|
jobId: input.jobId,
|
|
@@ -2183,8 +2342,9 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
2183
2342
|
*/
|
|
2184
2343
|
async #generate({ input, root, outputRoot, signal }) {
|
|
2185
2344
|
const { jobId, config: patch } = input;
|
|
2186
|
-
const studioInput = this.#sandbox ? patch?.input : void 0;
|
|
2345
|
+
const studioInput = this.#sandbox || this.#managed ? patch?.input : void 0;
|
|
2187
2346
|
if (this.#sandbox && (typeof studioInput !== "string" || !studioInput.trim() || (0, _kubb_core.getInputKind)(studioInput) !== "inline")) return this.#refuse({ message: require_protocol.AgentMessage.SANDBOX_INPUT_ONLY });
|
|
2347
|
+
if (this.#managed && studioInput !== void 0 && !MANAGED_INPUT_KINDS.has((0, _kubb_core.getInputKind)(studioInput))) return this.#refuse({ message: require_protocol.AgentMessage.MANAGED_INPUT_KIND });
|
|
2188
2348
|
const [configs, extensions] = await Promise.all([this.#options.loadConfigs(), this.#installedExtensions()]);
|
|
2189
2349
|
const config = pickConfig(configs, patch?.config);
|
|
2190
2350
|
if (!config) return this.#refuse({ message: require_protocol.AgentMessage.CONFIG_ENTRY_NOT_FOUND });
|
|
@@ -2213,12 +2373,13 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
2213
2373
|
throw error;
|
|
2214
2374
|
});
|
|
2215
2375
|
const activePlugins = plugins ?? config.plugins;
|
|
2216
|
-
if (patch?.input && !this.#sandbox) await emit(this.#options.hooks, "kubb:warn", { message: "Ignored the spec from Studio: generating from a Studio spec is only available to a sandbox agent" });
|
|
2217
|
-
const
|
|
2376
|
+
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, { publicOnly: true }).catch((error) => this.#refuse({ message: getErrorMessage(error) })) : void 0;
|
|
2378
|
+
const run = () => generate({
|
|
2218
2379
|
config: {
|
|
2219
2380
|
...config,
|
|
2220
2381
|
root,
|
|
2221
|
-
input: studioInput ?? config.input,
|
|
2382
|
+
input: checkout?.path ?? studioInput ?? config.input,
|
|
2222
2383
|
storage: outputRoot ? stagingStorage({
|
|
2223
2384
|
root,
|
|
2224
2385
|
directory: outputRoot
|
|
@@ -2230,11 +2391,12 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
2230
2391
|
hooks: this.#options.hooks,
|
|
2231
2392
|
signal,
|
|
2232
2393
|
allowExec: this.#permissions.allowWrite && this.#permissions.allowExec
|
|
2233
|
-
})
|
|
2394
|
+
});
|
|
2395
|
+
const files = await (this.#managed ? withPublicFetch(run) : run()).catch(async (error) => {
|
|
2234
2396
|
await this.#generations.drop(jobId);
|
|
2235
2397
|
await this.#logCommand("generate", getErrorMessage(error), input.requestedBy, true);
|
|
2236
2398
|
throw error;
|
|
2237
|
-
});
|
|
2399
|
+
}).finally(() => checkout?.dispose());
|
|
2238
2400
|
const output = await this.#generations.keep({
|
|
2239
2401
|
jobId,
|
|
2240
2402
|
files,
|
|
@@ -2282,7 +2444,9 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
2282
2444
|
await this.#warn({ message: "Ignored save: editing kubb.config.ts was not granted" });
|
|
2283
2445
|
return refuse("the agent was not granted permission to edit kubb.config.ts");
|
|
2284
2446
|
}
|
|
2285
|
-
|
|
2447
|
+
const busy = this.#busy();
|
|
2448
|
+
if (busy) return refuse(busy);
|
|
2449
|
+
this.#session.saving = true;
|
|
2286
2450
|
try {
|
|
2287
2451
|
const { outcomes, changed } = await writeConfigEdits({
|
|
2288
2452
|
filePath: configFile,
|
|
@@ -2298,6 +2462,110 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
2298
2462
|
} catch (error) {
|
|
2299
2463
|
await emit(this.#options.hooks, "studio:error", { error: toError(error) });
|
|
2300
2464
|
return refuse(getErrorMessage(error));
|
|
2465
|
+
} finally {
|
|
2466
|
+
this.#session.saving = false;
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
/**
|
|
2470
|
+
* Installs a registry extension into the project with `kubb add <id> --yes`, which also adds a
|
|
2471
|
+
* plugin to `kubb.config.ts`. Studio calls `connect()` again afterwards to see the result.
|
|
2472
|
+
*/
|
|
2473
|
+
async installExtension(input) {
|
|
2474
|
+
this.#assertConnected();
|
|
2475
|
+
const { root, configFile, kubbBin = findKubbBin(root) } = this.#options;
|
|
2476
|
+
const refuse = async (reason, permission) => {
|
|
2477
|
+
await this.#warn({
|
|
2478
|
+
message: `Ignored install: ${reason}`,
|
|
2479
|
+
permission
|
|
2480
|
+
});
|
|
2481
|
+
return {
|
|
2482
|
+
installed: false,
|
|
2483
|
+
reason
|
|
2484
|
+
};
|
|
2485
|
+
};
|
|
2486
|
+
if (typeof input?.id !== "string" || !REGISTRY_ID.test(input.id)) return refuse("the message carried no registry id");
|
|
2487
|
+
if (!this.#permissions.allowInstall) return refuse("installing extensions was not granted", require_protocol.AgentPermission.INSTALL);
|
|
2488
|
+
if (!this.#permissions.allowConfigEdit) return refuse("editing kubb.config.ts was not granted", require_protocol.AgentPermission.CONFIG_EDIT);
|
|
2489
|
+
const busy = this.#busy();
|
|
2490
|
+
if (busy) return refuse(busy);
|
|
2491
|
+
if (!kubbBin) return refuse("kubb is not installed in the project");
|
|
2492
|
+
const { failed, output } = await this.#runKubb({
|
|
2493
|
+
bin: kubbBin,
|
|
2494
|
+
args: [
|
|
2495
|
+
"add",
|
|
2496
|
+
input.id,
|
|
2497
|
+
"--yes",
|
|
2498
|
+
"--config",
|
|
2499
|
+
configFile
|
|
2500
|
+
]
|
|
2501
|
+
});
|
|
2502
|
+
await this.#logCommand("installExtension", `${failed ? "failed to install" : "installed"} ${input.id}`, input.requestedBy, failed);
|
|
2503
|
+
return failed ? {
|
|
2504
|
+
installed: false,
|
|
2505
|
+
reason: output
|
|
2506
|
+
} : { installed: true };
|
|
2507
|
+
}
|
|
2508
|
+
/**
|
|
2509
|
+
* Moves installed extensions to the registry's version with `kubb update [ids] --yes`, which
|
|
2510
|
+
* installs packages and leaves `kubb.config.ts` alone. Studio calls `connect()` again afterwards.
|
|
2511
|
+
*/
|
|
2512
|
+
async updateExtensions(input) {
|
|
2513
|
+
this.#assertConnected();
|
|
2514
|
+
const { root, kubbBin = findKubbBin(root) } = this.#options;
|
|
2515
|
+
const refuse = async (reason, permission) => {
|
|
2516
|
+
await this.#warn({
|
|
2517
|
+
message: `Ignored update: ${reason}`,
|
|
2518
|
+
permission
|
|
2519
|
+
});
|
|
2520
|
+
return {
|
|
2521
|
+
updated: false,
|
|
2522
|
+
reason
|
|
2523
|
+
};
|
|
2524
|
+
};
|
|
2525
|
+
const ids = input?.ids ?? [];
|
|
2526
|
+
if (!Array.isArray(ids) || !ids.every((id) => typeof id === "string" && REGISTRY_ID.test(id))) return refuse("the message carried an invalid registry id");
|
|
2527
|
+
if (!this.#permissions.allowUpdate) return refuse("updating extensions was not granted", require_protocol.AgentPermission.UPDATE);
|
|
2528
|
+
const busy = this.#busy();
|
|
2529
|
+
if (busy) return refuse(busy);
|
|
2530
|
+
if (!kubbBin) return refuse("kubb is not installed in the project");
|
|
2531
|
+
const { failed, output } = await this.#runKubb({
|
|
2532
|
+
bin: kubbBin,
|
|
2533
|
+
args: [
|
|
2534
|
+
"update",
|
|
2535
|
+
...ids,
|
|
2536
|
+
"--yes"
|
|
2537
|
+
]
|
|
2538
|
+
});
|
|
2539
|
+
await this.#logCommand("updateExtensions", `${failed ? "failed to update" : "updated"} ${ids.join(", ") || "all extensions"}`, input?.requestedBy, failed);
|
|
2540
|
+
return failed ? {
|
|
2541
|
+
updated: false,
|
|
2542
|
+
reason: output
|
|
2543
|
+
} : { updated: true };
|
|
2544
|
+
}
|
|
2545
|
+
/** Why a command that touches the project cannot start right now. */
|
|
2546
|
+
#busy() {
|
|
2547
|
+
if (this.#session.active) return "a generation is in progress";
|
|
2548
|
+
if (this.#session.install) return "an install or update is in progress";
|
|
2549
|
+
if (this.#session.saving) return "a save is in progress";
|
|
2550
|
+
}
|
|
2551
|
+
/** Runs `kubb` in the project while holding off saves and generations, and ends it when the socket closes. */
|
|
2552
|
+
async #runKubb({ bin, args }) {
|
|
2553
|
+
const controller = new AbortController();
|
|
2554
|
+
const settled = Promise.withResolvers();
|
|
2555
|
+
this.#session.install = {
|
|
2556
|
+
abort: () => controller.abort(),
|
|
2557
|
+
settled: settled.promise
|
|
2558
|
+
};
|
|
2559
|
+
try {
|
|
2560
|
+
return await runKubb({
|
|
2561
|
+
bin,
|
|
2562
|
+
args,
|
|
2563
|
+
cwd: this.#options.root,
|
|
2564
|
+
signal: controller.signal
|
|
2565
|
+
});
|
|
2566
|
+
} finally {
|
|
2567
|
+
this.#session.install = void 0;
|
|
2568
|
+
settled.resolve();
|
|
2301
2569
|
}
|
|
2302
2570
|
}
|
|
2303
2571
|
/**
|
|
@@ -2482,6 +2750,8 @@ async function runSession({ options, openRpc, url, instanceId }) {
|
|
|
2482
2750
|
await emit(hooks, "studio:warn", { message: "Canceled the running generation: the connection to Kubb Studio closed" });
|
|
2483
2751
|
}
|
|
2484
2752
|
await active?.settled;
|
|
2753
|
+
session.install?.abort();
|
|
2754
|
+
await session.install?.settled;
|
|
2485
2755
|
await agent.clear();
|
|
2486
2756
|
return {
|
|
2487
2757
|
...close,
|
|
@@ -2550,6 +2820,8 @@ async function runAgent({ options, openRpc }) {
|
|
|
2550
2820
|
permissions: {
|
|
2551
2821
|
allowWrite: false,
|
|
2552
2822
|
allowConfigEdit: false,
|
|
2823
|
+
allowInstall: false,
|
|
2824
|
+
allowUpdate: false,
|
|
2553
2825
|
allowExec: false,
|
|
2554
2826
|
allowRead: false,
|
|
2555
2827
|
allowReadAst: false,
|