@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.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { F as PublishedSnapshot, G as SnapshotUploadMetadata, O as InstalledExtensions, a as AgentConnectInfo, c as AgentPermissions, d as ConfigEditOutcome, s as AgentPermission, u as ConfigEdit } from "./protocol-dCzUP4dH.cjs";
1
+ import { F as PublishedSnapshot, G as SnapshotUploadMetadata, O as InstalledExtensions, a as AgentConnectInfo, c as AgentPermissions, d as ConfigEditOutcome, s as AgentPermission, u as ConfigEdit } from "./protocol-CG1bPO_H.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 { F as PublishedSnapshot, G as SnapshotUploadMetadata, O as InstalledExtensions, a as AgentConnectInfo, c as AgentPermissions, d as ConfigEditOutcome, s as AgentPermission, u as ConfigEdit } from "./protocol-dCzUP4dH.js";
1
+ import { F as PublishedSnapshot, G as SnapshotUploadMetadata, O as InstalledExtensions, a as AgentConnectInfo, c as AgentPermissions, d as ConfigEditOutcome, s as AgentPermission, u as ConfigEdit } from "./protocol-CG1bPO_H.js";
2
2
  import { Config, Hookable, KubbHooks } from "@kubb/core";
3
3
  //#region src/connect.d.ts
4
4
  /**
package/dist/index.js CHANGED
@@ -6,14 +6,19 @@ import path, { dirname, isAbsolute, join, relative, resolve, sep } from "node:pa
6
6
  import process$1 from "node:process";
7
7
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
8
8
  import { pathToFileURL } from "node:url";
9
+ import { Readable, Transform } from "node:stream";
9
10
  import { existsSync } from "node:fs";
10
11
  import { mkdir, mkdir as mkdir$1, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
11
- import { Diagnostics, Hookable, cacheStorage, createKubb, fsStorage, getInputKind, isPackageName, readInstalledPackages, resolveCacheDir, resolveMeta } from "@kubb/core";
12
+ import { Diagnostics, Hookable, cacheStorage, checkoutGitInput, createKubb, fsStorage, getInputKind, isPackageName, isPublicAddress, readInstalledPackages, resolveCacheDir, resolveMeta } from "@kubb/core";
12
13
  import { RpcTarget, newWebSocketRpcSession } from "capnweb";
13
14
  import { detectCodeFormat, parseExpression, parseModule } from "magicast";
14
15
  import { structuredPatch } from "diff";
15
16
  import { stripVTControlCharacters } from "node:util";
16
17
  import { ast } from "@kubb/ast";
18
+ import dns from "node:dns";
19
+ import http from "node:http";
20
+ import https from "node:https";
21
+ import net from "node:net";
17
22
  import { ofetch } from "ofetch";
18
23
  import { gzipSync } from "node:zlib";
19
24
  //#endregion
@@ -196,7 +201,7 @@ function getElapsedMs(hrStart) {
196
201
  }
197
202
  //#endregion
198
203
  //#region package.json
199
- var version = "5.11.6";
204
+ var version = "5.12.1";
200
205
  //#endregion
201
206
  //#region src/resolveConfig.ts
202
207
  /**
@@ -1842,6 +1847,106 @@ function createGenerationStream(hooks, jobId) {
1842
1847
  };
1843
1848
  }
1844
1849
  //#endregion
1850
+ //#region src/publicFetch.ts
1851
+ const MAX_REDIRECTS = 5;
1852
+ const MAX_BYTES = 52428800;
1853
+ const REDIRECTS = /* @__PURE__ */ new Set([
1854
+ 301,
1855
+ 302,
1856
+ 303,
1857
+ 307,
1858
+ 308
1859
+ ]);
1860
+ const NO_BODY = /* @__PURE__ */ new Set([
1861
+ 204,
1862
+ 205,
1863
+ 304
1864
+ ]);
1865
+ function notPublic(host) {
1866
+ return /* @__PURE__ */ new Error(`${host} is not a public address, so a managed agent does not fetch from it`);
1867
+ }
1868
+ function createLookup(blocked) {
1869
+ return (hostname, options, callback) => {
1870
+ dns.lookup(hostname, {
1871
+ ...options,
1872
+ all: true
1873
+ }, (error, addresses) => {
1874
+ if (error) return callback(error, "", 0);
1875
+ const found = addresses;
1876
+ if (!found.length || found.some(({ address }) => !isPublicAddress(address, blocked))) return callback(notPublic(hostname), "", 0);
1877
+ return options.all ? callback(null, found) : callback(null, found[0].address, found[0].family);
1878
+ });
1879
+ };
1880
+ }
1881
+ function request({ url, blocked, signal }) {
1882
+ if (url.protocol !== "http:" && url.protocol !== "https:") return Promise.reject(/* @__PURE__ */ new Error(`Only http and https URLs are fetched, not ${url.protocol}`));
1883
+ const host = url.hostname.replace(/^\[|\]$/g, "");
1884
+ if (net.isIP(host) && !isPublicAddress(host, blocked)) return Promise.reject(notPublic(host));
1885
+ return new Promise((resolve, reject) => {
1886
+ const req = (url.protocol === "https:" ? https.request : http.request)(url, {
1887
+ method: "GET",
1888
+ lookup: createLookup(blocked),
1889
+ signal: signal ?? void 0,
1890
+ headers: { "accept-encoding": "identity" }
1891
+ }, (res) => {
1892
+ const headers = new Headers();
1893
+ for (const [key, value] of Object.entries(res.headers)) for (const item of [value].flat()) if (item !== void 0) headers.append(key, item);
1894
+ let total = 0;
1895
+ const limit = new Transform({ transform(chunk, _encoding, done) {
1896
+ total += chunk.length;
1897
+ done(total > MAX_BYTES ? /* @__PURE__ */ new Error("The response is larger than 50 MB") : null, chunk);
1898
+ } });
1899
+ res.on("error", (error) => limit.destroy(error));
1900
+ res.pipe(limit);
1901
+ const body = NO_BODY.has(res.statusCode ?? 0) ? null : Readable.toWeb(limit);
1902
+ resolve(new Response(body, {
1903
+ status: res.statusCode,
1904
+ statusText: res.statusMessage,
1905
+ headers
1906
+ }));
1907
+ });
1908
+ req.on("error", reject);
1909
+ req.end();
1910
+ });
1911
+ }
1912
+ /**
1913
+ * Builds a `fetch` that only reaches addresses `blocked` does not list. It follows redirects itself
1914
+ * and checks every hop, and takes GET requests only, since that is all a spec load needs.
1915
+ */
1916
+ function createPublicFetch(blocked) {
1917
+ const publicFetch = async (input, init) => {
1918
+ if (typeof input !== "string" && !(input instanceof URL)) throw new TypeError("A managed agent fetches by URL only");
1919
+ if (init?.method && init.method.toUpperCase() !== "GET") throw new TypeError("A managed agent only sends GET requests");
1920
+ let url = new URL(input);
1921
+ for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
1922
+ const response = await request({
1923
+ url,
1924
+ blocked,
1925
+ signal: init?.signal
1926
+ });
1927
+ const location = response.headers.get("location");
1928
+ if (!REDIRECTS.has(response.status) || !location) return response;
1929
+ await response.body?.cancel();
1930
+ url = new URL(location, url);
1931
+ }
1932
+ throw new Error(`Stopped after ${MAX_REDIRECTS} redirects from ${String(input)}`);
1933
+ };
1934
+ return publicFetch;
1935
+ }
1936
+ /**
1937
+ * Runs `run` with the global `fetch` limited to public addresses, then puts the original back.
1938
+ * A managed agent runs one job at a time, so nothing else shares the window.
1939
+ */
1940
+ async function withPublicFetch(run) {
1941
+ const original = globalThis.fetch;
1942
+ globalThis.fetch = createPublicFetch();
1943
+ try {
1944
+ return await run();
1945
+ } finally {
1946
+ globalThis.fetch = original;
1947
+ }
1948
+ }
1949
+ //#endregion
1845
1950
  //#region src/url.ts
1846
1951
  /**
1847
1952
  * Hosted Kubb Studio URL, used when a host passes none.
@@ -1878,6 +1983,11 @@ const OPEN_TIMEOUT_MS = 5e3;
1878
1983
  const MAX_RETRY_DELAY_MS = 3e4;
1879
1984
  const DISK_SNAPSHOT_MAX_FILES = 1e4;
1880
1985
  const SANDBOX_GENERATION_TTL_MS = 9e5;
1986
+ const MANAGED_INPUT_KINDS = /* @__PURE__ */ new Set([
1987
+ "inline",
1988
+ "url",
1989
+ "git"
1990
+ ]);
1881
1991
  /** Extensions `kubb` ships with, so they count as installed without being a direct dependency. */
1882
1992
  const BUILT_IN_PACKAGES = [
1883
1993
  "@kubb/adapter-oas",
@@ -1964,6 +2074,9 @@ var Agent = class extends RpcTarget {
1964
2074
  get #sandbox() {
1965
2075
  return this.#session.info?.sandbox === true;
1966
2076
  }
2077
+ get #managed() {
2078
+ return this.#session.info?.managed === true;
2079
+ }
1967
2080
  get #permissions() {
1968
2081
  const { permissions } = this.#options;
1969
2082
  return this.#sandbox ? {
@@ -2159,8 +2272,9 @@ var Agent = class extends RpcTarget {
2159
2272
  */
2160
2273
  async #generate({ input, root, outputRoot, signal }) {
2161
2274
  const { jobId, config: patch } = input;
2162
- const studioInput = this.#sandbox ? patch?.input : void 0;
2275
+ const studioInput = this.#sandbox || this.#managed ? patch?.input : void 0;
2163
2276
  if (this.#sandbox && (typeof studioInput !== "string" || !studioInput.trim() || getInputKind(studioInput) !== "inline")) return this.#refuse({ message: AgentMessage.SANDBOX_INPUT_ONLY });
2277
+ if (this.#managed && studioInput !== void 0 && !MANAGED_INPUT_KINDS.has(getInputKind(studioInput))) return this.#refuse({ message: AgentMessage.MANAGED_INPUT_KIND });
2164
2278
  const [configs, extensions] = await Promise.all([this.#options.loadConfigs(), this.#installedExtensions()]);
2165
2279
  const config = pickConfig(configs, patch?.config);
2166
2280
  if (!config) return this.#refuse({ message: AgentMessage.CONFIG_ENTRY_NOT_FOUND });
@@ -2189,12 +2303,13 @@ var Agent = class extends RpcTarget {
2189
2303
  throw error;
2190
2304
  });
2191
2305
  const activePlugins = plugins ?? config.plugins;
2192
- 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" });
2193
- const files = await generate({
2306
+ 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" });
2307
+ const checkout = this.#managed && studioInput !== void 0 && getInputKind(studioInput) === "git" ? await checkoutGitInput(studioInput, signal, { publicOnly: true }).catch((error) => this.#refuse({ message: getErrorMessage(error) })) : void 0;
2308
+ const run = () => generate({
2194
2309
  config: {
2195
2310
  ...config,
2196
2311
  root,
2197
- input: studioInput ?? config.input,
2312
+ input: checkout?.path ?? studioInput ?? config.input,
2198
2313
  storage: outputRoot ? stagingStorage({
2199
2314
  root,
2200
2315
  directory: outputRoot
@@ -2206,11 +2321,12 @@ var Agent = class extends RpcTarget {
2206
2321
  hooks: this.#options.hooks,
2207
2322
  signal,
2208
2323
  allowExec: this.#permissions.allowWrite && this.#permissions.allowExec
2209
- }).catch(async (error) => {
2324
+ });
2325
+ const files = await (this.#managed ? withPublicFetch(run) : run()).catch(async (error) => {
2210
2326
  await this.#generations.drop(jobId);
2211
2327
  await this.#logCommand("generate", getErrorMessage(error), input.requestedBy, true);
2212
2328
  throw error;
2213
- });
2329
+ }).finally(() => checkout?.dispose());
2214
2330
  const output = await this.#generations.keep({
2215
2331
  jobId,
2216
2332
  files,
@@ -2478,7 +2594,7 @@ function reportedInput(input) {
2478
2594
  if (typeof input !== "string") return void 0;
2479
2595
  const kind = getInputKind(input);
2480
2596
  if (kind === "inline") return void 0;
2481
- if (kind !== "url") return input;
2597
+ if (kind !== "url" && kind !== "git" || !URL.canParse(input)) return input;
2482
2598
  const url = new URL(input);
2483
2599
  url.username = "";
2484
2600
  url.password = "";