@kubb/studio 5.11.6 → 5.12.1

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 CHANGED
@@ -30,6 +30,7 @@ let node_process = require("node:process");
30
30
  node_process = __toESM(node_process, 1);
31
31
  let node_timers_promises = require("node:timers/promises");
32
32
  let node_url = require("node:url");
33
+ let node_stream = require("node:stream");
33
34
  let node_fs = require("node:fs");
34
35
  let node_fs_promises = require("node:fs/promises");
35
36
  let _kubb_core = require("@kubb/core");
@@ -39,6 +40,14 @@ let node_module = require("node:module");
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);
42
51
  let ofetch = require("ofetch");
43
52
  let node_zlib = require("node:zlib");
44
53
  //#region ../../internals/utils/src/casing.ts
@@ -220,7 +229,7 @@ function getElapsedMs(hrStart) {
220
229
  }
221
230
  //#endregion
222
231
  //#region package.json
223
- var version = "5.11.6";
232
+ var version = "5.12.1";
224
233
  //#endregion
225
234
  //#region src/resolveConfig.ts
226
235
  /**
@@ -1866,6 +1875,106 @@ function createGenerationStream(hooks, jobId) {
1866
1875
  };
1867
1876
  }
1868
1877
  //#endregion
1878
+ //#region src/publicFetch.ts
1879
+ const MAX_REDIRECTS = 5;
1880
+ const MAX_BYTES = 52428800;
1881
+ const REDIRECTS = /* @__PURE__ */ new Set([
1882
+ 301,
1883
+ 302,
1884
+ 303,
1885
+ 307,
1886
+ 308
1887
+ ]);
1888
+ const NO_BODY = /* @__PURE__ */ new Set([
1889
+ 204,
1890
+ 205,
1891
+ 304
1892
+ ]);
1893
+ function notPublic(host) {
1894
+ return /* @__PURE__ */ new Error(`${host} is not a public address, so a managed agent does not fetch from it`);
1895
+ }
1896
+ function createLookup(blocked) {
1897
+ return (hostname, options, callback) => {
1898
+ node_dns.default.lookup(hostname, {
1899
+ ...options,
1900
+ all: true
1901
+ }, (error, addresses) => {
1902
+ if (error) return callback(error, "", 0);
1903
+ const found = addresses;
1904
+ if (!found.length || found.some(({ address }) => !(0, _kubb_core.isPublicAddress)(address, blocked))) return callback(notPublic(hostname), "", 0);
1905
+ return options.all ? callback(null, found) : callback(null, found[0].address, found[0].family);
1906
+ });
1907
+ };
1908
+ }
1909
+ function request({ url, blocked, signal }) {
1910
+ if (url.protocol !== "http:" && url.protocol !== "https:") return Promise.reject(/* @__PURE__ */ new Error(`Only http and https URLs are fetched, not ${url.protocol}`));
1911
+ const host = url.hostname.replace(/^\[|\]$/g, "");
1912
+ if (node_net.default.isIP(host) && !(0, _kubb_core.isPublicAddress)(host, blocked)) return Promise.reject(notPublic(host));
1913
+ return new Promise((resolve, reject) => {
1914
+ const req = (url.protocol === "https:" ? node_https.default.request : node_http.default.request)(url, {
1915
+ method: "GET",
1916
+ lookup: createLookup(blocked),
1917
+ signal: signal ?? void 0,
1918
+ headers: { "accept-encoding": "identity" }
1919
+ }, (res) => {
1920
+ const headers = new Headers();
1921
+ for (const [key, value] of Object.entries(res.headers)) for (const item of [value].flat()) if (item !== void 0) headers.append(key, item);
1922
+ let total = 0;
1923
+ const limit = new node_stream.Transform({ transform(chunk, _encoding, done) {
1924
+ total += chunk.length;
1925
+ done(total > MAX_BYTES ? /* @__PURE__ */ new Error("The response is larger than 50 MB") : null, chunk);
1926
+ } });
1927
+ res.on("error", (error) => limit.destroy(error));
1928
+ res.pipe(limit);
1929
+ const body = NO_BODY.has(res.statusCode ?? 0) ? null : node_stream.Readable.toWeb(limit);
1930
+ resolve(new Response(body, {
1931
+ status: res.statusCode,
1932
+ statusText: res.statusMessage,
1933
+ headers
1934
+ }));
1935
+ });
1936
+ req.on("error", reject);
1937
+ req.end();
1938
+ });
1939
+ }
1940
+ /**
1941
+ * Builds a `fetch` that only reaches addresses `blocked` does not list. It follows redirects itself
1942
+ * and checks every hop, and takes GET requests only, since that is all a spec load needs.
1943
+ */
1944
+ function createPublicFetch(blocked) {
1945
+ const publicFetch = async (input, init) => {
1946
+ if (typeof input !== "string" && !(input instanceof URL)) throw new TypeError("A managed agent fetches by URL only");
1947
+ if (init?.method && init.method.toUpperCase() !== "GET") throw new TypeError("A managed agent only sends GET requests");
1948
+ let url = new URL(input);
1949
+ for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
1950
+ const response = await request({
1951
+ url,
1952
+ blocked,
1953
+ signal: init?.signal
1954
+ });
1955
+ const location = response.headers.get("location");
1956
+ if (!REDIRECTS.has(response.status) || !location) return response;
1957
+ await response.body?.cancel();
1958
+ url = new URL(location, url);
1959
+ }
1960
+ throw new Error(`Stopped after ${MAX_REDIRECTS} redirects from ${String(input)}`);
1961
+ };
1962
+ return publicFetch;
1963
+ }
1964
+ /**
1965
+ * Runs `run` with the global `fetch` limited to public addresses, then puts the original back.
1966
+ * A managed agent runs one job at a time, so nothing else shares the window.
1967
+ */
1968
+ async function withPublicFetch(run) {
1969
+ const original = globalThis.fetch;
1970
+ globalThis.fetch = createPublicFetch();
1971
+ try {
1972
+ return await run();
1973
+ } finally {
1974
+ globalThis.fetch = original;
1975
+ }
1976
+ }
1977
+ //#endregion
1869
1978
  //#region src/url.ts
1870
1979
  /**
1871
1980
  * Hosted Kubb Studio URL, used when a host passes none.
@@ -1902,6 +2011,11 @@ const OPEN_TIMEOUT_MS = 5e3;
1902
2011
  const MAX_RETRY_DELAY_MS = 3e4;
1903
2012
  const DISK_SNAPSHOT_MAX_FILES = 1e4;
1904
2013
  const SANDBOX_GENERATION_TTL_MS = 9e5;
2014
+ const MANAGED_INPUT_KINDS = /* @__PURE__ */ new Set([
2015
+ "inline",
2016
+ "url",
2017
+ "git"
2018
+ ]);
1905
2019
  /** Extensions `kubb` ships with, so they count as installed without being a direct dependency. */
1906
2020
  const BUILT_IN_PACKAGES = [
1907
2021
  "@kubb/adapter-oas",
@@ -1988,6 +2102,9 @@ var Agent = class extends capnweb.RpcTarget {
1988
2102
  get #sandbox() {
1989
2103
  return this.#session.info?.sandbox === true;
1990
2104
  }
2105
+ get #managed() {
2106
+ return this.#session.info?.managed === true;
2107
+ }
1991
2108
  get #permissions() {
1992
2109
  const { permissions } = this.#options;
1993
2110
  return this.#sandbox ? {
@@ -2183,8 +2300,9 @@ var Agent = class extends capnweb.RpcTarget {
2183
2300
  */
2184
2301
  async #generate({ input, root, outputRoot, signal }) {
2185
2302
  const { jobId, config: patch } = input;
2186
- const studioInput = this.#sandbox ? patch?.input : void 0;
2303
+ const studioInput = this.#sandbox || this.#managed ? patch?.input : void 0;
2187
2304
  if (this.#sandbox && (typeof studioInput !== "string" || !studioInput.trim() || (0, _kubb_core.getInputKind)(studioInput) !== "inline")) return this.#refuse({ message: require_protocol.AgentMessage.SANDBOX_INPUT_ONLY });
2305
+ 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
2306
  const [configs, extensions] = await Promise.all([this.#options.loadConfigs(), this.#installedExtensions()]);
2189
2307
  const config = pickConfig(configs, patch?.config);
2190
2308
  if (!config) return this.#refuse({ message: require_protocol.AgentMessage.CONFIG_ENTRY_NOT_FOUND });
@@ -2213,12 +2331,13 @@ var Agent = class extends capnweb.RpcTarget {
2213
2331
  throw error;
2214
2332
  });
2215
2333
  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 files = await generate({
2334
+ 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, { publicOnly: true }).catch((error) => this.#refuse({ message: getErrorMessage(error) })) : void 0;
2336
+ const run = () => generate({
2218
2337
  config: {
2219
2338
  ...config,
2220
2339
  root,
2221
- input: studioInput ?? config.input,
2340
+ input: checkout?.path ?? studioInput ?? config.input,
2222
2341
  storage: outputRoot ? stagingStorage({
2223
2342
  root,
2224
2343
  directory: outputRoot
@@ -2230,11 +2349,12 @@ var Agent = class extends capnweb.RpcTarget {
2230
2349
  hooks: this.#options.hooks,
2231
2350
  signal,
2232
2351
  allowExec: this.#permissions.allowWrite && this.#permissions.allowExec
2233
- }).catch(async (error) => {
2352
+ });
2353
+ const files = await (this.#managed ? withPublicFetch(run) : run()).catch(async (error) => {
2234
2354
  await this.#generations.drop(jobId);
2235
2355
  await this.#logCommand("generate", getErrorMessage(error), input.requestedBy, true);
2236
2356
  throw error;
2237
- });
2357
+ }).finally(() => checkout?.dispose());
2238
2358
  const output = await this.#generations.keep({
2239
2359
  jobId,
2240
2360
  files,
@@ -2502,7 +2622,7 @@ function reportedInput(input) {
2502
2622
  if (typeof input !== "string") return void 0;
2503
2623
  const kind = (0, _kubb_core.getInputKind)(input);
2504
2624
  if (kind === "inline") return void 0;
2505
- if (kind !== "url") return input;
2625
+ if (kind !== "url" && kind !== "git" || !URL.canParse(input)) return input;
2506
2626
  const url = new URL(input);
2507
2627
  url.username = "";
2508
2628
  url.password = "";