@kubb/studio 5.6.1 → 5.6.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.d.ts CHANGED
@@ -220,28 +220,18 @@ type CreateSnapshotOptions = Omit<SnapshotUploadMetadata, 'hashes' | 'peerDepend
220
220
  */
221
221
  apiKey: string;
222
222
  /**
223
- * The project's Kubb config. Like `kubb generate`, it writes to `output.path`, then runs the
224
- * formatter, the linter, and `output.postGenerate`.
223
+ * The project's Kubb config, generated like `kubb generate`: formatter, linter, and `output.postGenerate` included.
225
224
  */
226
225
  config: Config;
227
226
  hooks?: Hookable<KubbHooks>;
228
227
  signal?: AbortSignal;
229
228
  };
230
229
  /**
231
- * Generates the project, packs it as an npm tarball, and publishes it to Studio in one
232
- * `POST /api/snapshots` request.
230
+ * Generates the project, packs it as an npm tarball, and publishes it to Studio in one `POST /api/snapshots` request.
233
231
  *
234
232
  * @example
235
233
  * ```ts
236
- * const snapshot = await createSnapshot({
237
- * apiKey: process.env.KUBB_TOKEN!,
238
- * config,
239
- * id: 'gh:123:pr-4',
240
- * name: 'acme/api#4',
241
- * packageName: '@acme/api',
242
- * version: '1.0.0',
243
- * })
244
- * console.log(snapshot.url, snapshot.changes)
234
+ * const { url } = await createSnapshot({ apiKey: process.env.KUBB_TOKEN!, config, id: 'gh:123:pr-4', name: 'acme/api#4', packageName: '@acme/api', version: '1.0.0' })
245
235
  * ```
246
236
  */
247
237
  export declare function createSnapshot({ studioUrl, apiKey, config, hooks, signal, ...metadata }: CreateSnapshotOptions): Promise<PublishedSnapshot>;
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { createRequire } from "node:module";
4
4
  import { createHash, hash, randomUUID } from "node:crypto";
5
5
  import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
6
6
  import { tmpdir } from "node:os";
7
- import path, { isAbsolute, join, relative, resolve, sep } from "node:path";
7
+ import path, { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
8
8
  import process$1 from "node:process";
9
9
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
10
10
  import { pathToFileURL } from "node:url";
@@ -192,7 +192,7 @@ function getElapsedMs(hrStart) {
192
192
  }
193
193
  //#endregion
194
194
  //#region package.json
195
- var version = "5.6.1";
195
+ var version = "5.6.2";
196
196
  //#endregion
197
197
  //#region src/resolveConfig.ts
198
198
  /**
@@ -1034,19 +1034,21 @@ async function generate({ config, hooks, signal, allowExec }) {
1034
1034
  lint: false,
1035
1035
  postGenerate: []
1036
1036
  };
1037
- const result = await createKubb({
1037
+ const kubb = createKubb({
1038
1038
  ...config,
1039
1039
  output
1040
1040
  }, {
1041
1041
  hooks,
1042
1042
  signal
1043
- }).generate();
1043
+ });
1044
+ const result = await kubb.generate();
1044
1045
  if (!result.success) throw generationFailure(result.diagnostics);
1046
+ const { storage, root } = kubb.config;
1045
1047
  return {
1046
- storage: config.storage,
1047
- root: config.root,
1048
+ storage,
1049
+ root,
1048
1050
  paths: new Set(result.files.map((file) => relativeStoragePath({
1049
- root: config.root,
1051
+ root,
1050
1052
  filePath: file.path
1051
1053
  })))
1052
1054
  };
@@ -1991,29 +1993,24 @@ const gzipAsync = promisify(gzip);
1991
1993
  const UPLOAD_TIMEOUT_MS = 12e4;
1992
1994
  const BUNDLED_DEPENDENCIES = ["@kubb/plugin-barrel"];
1993
1995
  /**
1994
- * Maps a generated file's path to its place inside the tarball, stripping everything before a
1995
- * `src`/`dist` segment and any `..`/empty path segment so a crafted file name cannot escape the
1996
- * `package/` root.
1996
+ * Maps a generated file to its path in the package, from its `src`/`dist` segment on, without `..` parts that could escape it.
1997
1997
  */
1998
1998
  function packagePath(filePath) {
1999
1999
  const normalized = filePath.replaceAll("\\", "/");
2000
- return `package/${(normalized.match(/\/(?:src|dist)\/.*$/)?.[0].slice(1) ?? normalized.replace(/^\/+/, "")).split("/").filter((part) => part && part !== "." && part !== "..").join("/")}`;
2000
+ return (normalized.match(/\/(?:src|dist)\/.*$/)?.[0] ?? normalized).split("/").filter((part) => part && part !== "." && part !== "..").join("/");
2001
2001
  }
2002
2002
  /**
2003
- * Splits a tarball entry path into the legacy 100-byte `name` field and, when the path does not
2004
- * fit, the 155-byte USTAR `prefix` field that extends it. Throws rather than silently truncating
2005
- * a path the format cannot address (max 256 bytes: 100 name + 1 separator + 155 prefix).
2003
+ * Splits a tar path into the 100-byte USTAR `name` and the 155-byte `prefix`, and throws for a path that fits neither.
2006
2004
  */
2007
2005
  function splitEntryPath(path) {
2008
- if (Buffer.byteLength(path, "utf8") <= 100) return {
2006
+ if (Buffer.byteLength(path) <= 100) return {
2009
2007
  name: path,
2010
2008
  prefix: ""
2011
2009
  };
2012
- for (let i = path.length - 1; i >= 0; i--) {
2013
- if (path[i] !== "/") continue;
2010
+ for (let i = path.lastIndexOf("/"); i > 0; i = path.lastIndexOf("/", i - 1)) {
2014
2011
  const prefix = path.slice(0, i);
2015
2012
  const name = path.slice(i + 1);
2016
- if (Buffer.byteLength(prefix, "utf8") <= 155 && Buffer.byteLength(name, "utf8") <= 100) return {
2013
+ if (Buffer.byteLength(prefix) <= 155 && Buffer.byteLength(name) <= 100) return {
2017
2014
  name,
2018
2015
  prefix
2019
2016
  };
@@ -2023,21 +2020,23 @@ function splitEntryPath(path) {
2023
2020
  function header(path, size) {
2024
2021
  const { name, prefix } = splitEntryPath(path);
2025
2022
  const value = Buffer.alloc(512);
2026
- value.write(name, 0, "utf8");
2027
- value.write("0000644\0", 100, "ascii");
2028
- value.write("0000000\0", 108, "ascii");
2029
- value.write("0000000\0", 116, "ascii");
2030
- value.write(`${size.toString(8).padStart(11, "0")}\0`, 124, "ascii");
2031
- value.write("03560116604\0", 136, "ascii");
2023
+ value.write(name);
2024
+ value.write("0000644\0", 100);
2025
+ for (const offset of [
2026
+ 108,
2027
+ 116,
2028
+ 265,
2029
+ 297
2030
+ ]) value.write("0000000\0", offset);
2031
+ value.write(`${size.toString(8).padStart(11, "0")}\0`, 124);
2032
+ value.write("03560116604\0", 136);
2032
2033
  value.fill(32, 148, 156);
2033
- value.write("0", 156, "ascii");
2034
- value.write("ustar\0", 257, "ascii");
2035
- value.write("00", 263, "ascii");
2036
- value.write("0000000\0", 265, "ascii");
2037
- value.write("0000000\0", 297, "ascii");
2038
- value.write(prefix, 345, "utf8");
2039
- const checksum = [...value].reduce((sum, byte) => sum + byte, 0);
2040
- value.write(`${checksum.toString(8).padStart(6, "0")}\0 `, 148, "ascii");
2034
+ value.write("0", 156);
2035
+ value.write("ustar\0", 257);
2036
+ value.write("00", 263);
2037
+ value.write(prefix, 345);
2038
+ const checksum = value.reduce((sum, byte) => sum + byte, 0);
2039
+ value.write(`${checksum.toString(8).padStart(6, "0")}\0 `, 148);
2041
2040
  return value;
2042
2041
  }
2043
2042
  /**
@@ -2045,31 +2044,29 @@ function header(path, size) {
2045
2044
  * generated sources, a `tsdown`-built `dist/` (esm + cjs), and a `package.json` manifest.
2046
2045
  */
2047
2046
  async function createSnapshotPackage({ files, packageInfo }) {
2047
+ const sources = /* @__PURE__ */ new Map();
2048
+ for (const [name, content] of Object.entries(files)) {
2049
+ const path = packagePath(name);
2050
+ const owner = sources.get(path)?.name;
2051
+ if (owner) throw new Error(`Snapshot has two generated files that sanitize to the same path "package/${path}": "${owner}" and "${name}"`);
2052
+ sources.set(path, {
2053
+ name,
2054
+ content
2055
+ });
2056
+ }
2048
2057
  const root = await mkdtemp(join(tmpdir(), "kubb-snapshot-"));
2049
2058
  const dist = join(root, "dist");
2050
- const resolvedPaths = Object.entries(files).map(([name, content]) => ({
2051
- name,
2052
- content,
2053
- target: packagePath(name)
2054
- }));
2055
- const targetOwners = /* @__PURE__ */ new Map();
2056
- for (const { name, target } of resolvedPaths) {
2057
- const owner = targetOwners.get(target);
2058
- if (owner) throw new Error(`Snapshot has two generated files that sanitize to the same path "${target}": "${owner}" and "${name}"`);
2059
- targetOwners.set(target, name);
2060
- }
2061
2059
  try {
2062
2060
  await mkdir(dist);
2063
- await Promise.all(resolvedPaths.map(async ({ content, target }) => {
2064
- const path = join(root, target.slice(8));
2065
- await mkdir(join(path, ".."), { recursive: true });
2066
- await writeFile(path, content);
2061
+ await Promise.all([...sources].map(async ([path, { content }]) => {
2062
+ await mkdir(dirname(join(root, path)), { recursive: true });
2063
+ await writeFile(join(root, path), content);
2067
2064
  }));
2068
- const sourceEntries = resolvedPaths.filter(({ name }) => /\.(?:[cm]?[jt]sx?)$/.test(name)).map(({ target }) => join(root, target.slice(8)));
2069
- if (sourceEntries.length) {
2065
+ const entry = [...sources.keys()].filter((path) => /\.(?:[cm]?[jt]sx?)$/.test(path)).map((path) => join(root, path));
2066
+ if (entry.length) {
2070
2067
  const { build } = await import("tsdown");
2071
2068
  await build({
2072
- entry: sourceEntries,
2069
+ entry,
2073
2070
  outDir: dist,
2074
2071
  format: ["esm", "cjs"],
2075
2072
  dts: false,
@@ -2080,38 +2077,38 @@ async function createSnapshotPackage({ files, packageInfo }) {
2080
2077
  fixedExtension: true
2081
2078
  });
2082
2079
  }
2083
- const builtEntries = await Promise.all((await fsStorage().readKeys(dist)).map(async (key) => [`package/dist/${key}`, await readFile(join(dist, key), "utf8")]));
2084
- const builtPaths = new Set(builtEntries.map(([path]) => path));
2085
- const hasBarrel = builtPaths.has("package/dist/index.mjs") && builtPaths.has("package/dist/index.cjs");
2086
- const barrelFields = hasBarrel ? {
2087
- main: "./dist/index.cjs",
2088
- module: "./dist/index.mjs"
2089
- } : {};
2090
- const barrelExport = hasBarrel ? { ".": {
2091
- import: "./dist/index.mjs",
2092
- require: "./dist/index.cjs"
2093
- } } : {};
2094
- const entries = {
2095
- "package/package.json": JSON.stringify({
2096
- ...packageInfo,
2097
- type: "module",
2098
- ...barrelFields,
2099
- exports: {
2100
- ...barrelExport,
2101
- "./*": {
2102
- import: "./dist/*.mjs",
2103
- require: "./dist/*.cjs"
2104
- }
2080
+ const built = await fsStorage().readKeys(dist);
2081
+ const hasBarrel = built.includes("index.mjs") && built.includes("index.cjs");
2082
+ const manifest = {
2083
+ ...packageInfo,
2084
+ type: "module",
2085
+ ...hasBarrel ? {
2086
+ main: "./dist/index.cjs",
2087
+ module: "./dist/index.mjs"
2088
+ } : {},
2089
+ exports: {
2090
+ ...hasBarrel ? { ".": {
2091
+ import: "./dist/index.mjs",
2092
+ require: "./dist/index.cjs"
2093
+ } } : {},
2094
+ "./*": {
2095
+ import: "./dist/*.mjs",
2096
+ require: "./dist/*.cjs"
2105
2097
  }
2106
- }, null, 2),
2107
- ...Object.fromEntries(resolvedPaths.map(({ content, target }) => [target, content])),
2108
- ...Object.fromEntries(builtEntries)
2098
+ }
2109
2099
  };
2110
- const chunks = [];
2111
- for (const [name, content] of Object.entries(entries)) {
2100
+ const chunks = [...new Map([
2101
+ ["package.json", JSON.stringify(manifest, null, 2)],
2102
+ ...[...sources].map(([path, { content }]) => [path, content]),
2103
+ ...await Promise.all(built.map(async (key) => [`dist/${key}`, await readFile(join(dist, key), "utf8")]))
2104
+ ])].flatMap(([path, content]) => {
2112
2105
  const bytes = Buffer.from(content);
2113
- chunks.push(header(name, bytes.length), bytes, Buffer.alloc((512 - bytes.length % 512) % 512));
2114
- }
2106
+ return [
2107
+ header(`package/${path}`, bytes.length),
2108
+ bytes,
2109
+ Buffer.alloc((512 - bytes.length % 512) % 512)
2110
+ ];
2111
+ });
2115
2112
  return await gzipAsync(Buffer.concat([...chunks, Buffer.alloc(1024)]));
2116
2113
  } finally {
2117
2114
  await rm(root, {
@@ -2120,25 +2117,12 @@ async function createSnapshotPackage({ files, packageInfo }) {
2120
2117
  });
2121
2118
  }
2122
2119
  }
2123
- function failureMessage({ status, _data }) {
2124
- const upgrade = _data?.data?.upgradeUrl ? ` Upgrade at ${_data.data.upgradeUrl}.` : "";
2125
- return `Kubb Studio refused the snapshot (status ${status})${_data?.message ? `: ${_data.message}` : ""}.${upgrade}`;
2126
- }
2127
2120
  /**
2128
- * Generates the project, packs it as an npm tarball, and publishes it to Studio in one
2129
- * `POST /api/snapshots` request.
2121
+ * Generates the project, packs it as an npm tarball, and publishes it to Studio in one `POST /api/snapshots` request.
2130
2122
  *
2131
2123
  * @example
2132
2124
  * ```ts
2133
- * const snapshot = await createSnapshot({
2134
- * apiKey: process.env.KUBB_TOKEN!,
2135
- * config,
2136
- * id: 'gh:123:pr-4',
2137
- * name: 'acme/api#4',
2138
- * packageName: '@acme/api',
2139
- * version: '1.0.0',
2140
- * })
2141
- * console.log(snapshot.url, snapshot.changes)
2125
+ * const { url } = await createSnapshot({ apiKey: process.env.KUBB_TOKEN!, config, id: 'gh:123:pr-4', name: 'acme/api#4', packageName: '@acme/api', version: '1.0.0' })
2142
2126
  * ```
2143
2127
  */
2144
2128
  async function createSnapshot({ studioUrl = defaultStudioUrl, apiKey, config, hooks, signal, ...metadata }) {
@@ -2178,7 +2162,7 @@ async function createSnapshot({ studioUrl = defaultStudioUrl, apiKey, config, ho
2178
2162
  }));
2179
2163
  body.set("tarball", new Blob([new Uint8Array(bytes)], { type: "application/gzip" }), "snapshot.tgz");
2180
2164
  const timeout = AbortSignal.timeout(UPLOAD_TIMEOUT_MS);
2181
- const response = await ofetch.raw(url.href, {
2165
+ const { ok, status, _data } = await ofetch.raw(url.href, {
2182
2166
  method: "POST",
2183
2167
  headers: { "x-api-key": apiKey },
2184
2168
  body,
@@ -2186,10 +2170,12 @@ async function createSnapshot({ studioUrl = defaultStudioUrl, apiKey, config, ho
2186
2170
  signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
2187
2171
  ignoreResponseError: true
2188
2172
  });
2189
- if (!response.ok) throw new Error(failureMessage(response));
2190
- const snapshot = response._data?.snapshot;
2191
- if (!snapshot) throw new Error(`Kubb Studio returned no snapshot (status ${response.status})`);
2192
- return snapshot;
2173
+ if (!ok) {
2174
+ const upgrade = _data?.data?.upgradeUrl ? ` Upgrade at ${_data.data.upgradeUrl}.` : "";
2175
+ throw new Error(`Kubb Studio refused the snapshot (status ${status})${_data?.message ? `: ${_data.message}` : ""}.${upgrade}`);
2176
+ }
2177
+ if (!_data?.snapshot) throw new Error(`Kubb Studio returned no snapshot (status ${status})`);
2178
+ return _data.snapshot;
2193
2179
  }
2194
2180
  //#endregion
2195
2181
  export { IncompatibleAgentError, InvalidAgentTokenError, PairingCanceledError, PairingDeniedError, PairingExpiredError, connectAgent, createSnapshot, defaultStudioUrl, pairAgent };