@kubb/studio 5.9.3 → 5.9.4

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
@@ -176,10 +176,18 @@ function getElapsedMs(hrStart) {
176
176
  }
177
177
  //#endregion
178
178
  //#region package.json
179
- var version = "5.9.3";
179
+ var version = "5.9.4";
180
180
  //#endregion
181
181
  //#region src/resolveConfig.ts
182
182
  /**
183
+ * Turns the JSON config Studio sends back into live Kubb objects.
184
+ *
185
+ * A plugin or adapter instance carries closures (`parse`, `getImports`, ...) that cannot survive
186
+ * JSON, so both sides pass options over the wire and the factory is re-invoked here with the merged
187
+ * result. Only packages {@link isAllowedPlugin} accepts are resolved this way, never an arbitrary
188
+ * module the payload names.
189
+ */
190
+ /**
183
191
  * Imports a package, falling back to how the user's project would resolve it.
184
192
  *
185
193
  * `import()` resolves from this file, so a linked or globally installed Studio (`pnpm link`,
@@ -258,8 +266,9 @@ function isAllowedPlugin({ packageName, extensions }) {
258
266
  return KUBB_PLUGIN_SPECIFIER.test(packageName) || extensions.get(packageName)?.manifest.kind === "plugin";
259
267
  }
260
268
  /**
261
- * Dynamically imports a plugin package and returns its factory. Only packages
262
- * {@link isAllowedPlugin} accepts reach `import()`.
269
+ * Dynamically imports an extension package and returns its factory. Callers check
270
+ * {@link isAllowedPlugin} or derive the package from a disk instance first, so an arbitrary module
271
+ * never reaches `import()`.
263
272
  *
264
273
  * Packages must be pre-installed in the Docker image at build time via the `KUBB_PACKAGES`
265
274
  * build ARG, no runtime installation is possible in the distroless container.
@@ -267,13 +276,9 @@ function isAllowedPlugin({ packageName, extensions }) {
267
276
  * Resolution order: the manifest's `export`, else the camelCase named export the package name
268
277
  * implies (e.g. `pluginTs`), then the default export.
269
278
  *
270
- * @throws if the package is not allowed, cannot be imported, or exports no callable factory.
279
+ * @throws if the package cannot be imported or exports no callable factory.
271
280
  */
272
- async function loadPluginFactory({ packageName, extensions }) {
273
- if (!isAllowedPlugin({
274
- packageName,
275
- extensions
276
- })) throw new Error(`Plugin "${packageName}" is not an installed Kubb plugin. Studio loads @kubb/plugin-* packages and installed packages that ship a kubb-extension.json with kind "plugin".`);
281
+ async function loadFactory({ packageName, extensions }) {
277
282
  let mod;
278
283
  try {
279
284
  mod = await importFromProject(packageName);
@@ -292,7 +297,7 @@ async function loadPluginFactory({ packageName, extensions }) {
292
297
  }
293
298
  /**
294
299
  * Resolves each plugin entry by dynamically importing its package and calling its factory with the
295
- * provided options. See {@link loadPluginFactory} for which packages are allowed.
300
+ * provided options. Only packages {@link isAllowedPlugin} accepts are loaded.
296
301
  *
297
302
  * @example
298
303
  * ```ts
@@ -306,7 +311,11 @@ async function loadPluginFactory({ packageName, extensions }) {
306
311
  */
307
312
  async function resolvePlugins({ plugins, extensions = /* @__PURE__ */ new Map() }) {
308
313
  return Promise.all(plugins.map(async ({ name, options }) => {
309
- return (await loadPluginFactory({
314
+ if (!isAllowedPlugin({
315
+ packageName: name,
316
+ extensions
317
+ })) throw new Error(`Plugin "${name}" is not an installed Kubb plugin. Studio loads @kubb/plugin-* packages and installed packages that ship a kubb-extension.json with kind "plugin".`);
318
+ return (await loadFactory({
310
319
  packageName: name,
311
320
  extensions
312
321
  }))(options ?? {});
@@ -454,13 +463,12 @@ async function mergePlugins({ diskPlugins, studioPlugins, extensions = /* @__PUR
454
463
  async function rebuildExtension({ disk, studioOptions, kind, extensions }) {
455
464
  if (!studioOptions || !Object.keys(studioOptions).length) return disk;
456
465
  const { name } = (0, _kubb_core.resolveMeta)(disk);
457
- const installed = [...extensions.values()].find(({ manifest }) => manifest.kind === kind && manifest.name === name);
458
- const packageName = installed?.package ?? `@kubb/${kind}-${name}`;
459
- const factory = await importFromProject(packageName).then((mod) => mod[installed?.manifest.export ?? toExportName(packageName)]).catch((cause) => {
466
+ return (await loadFactory({
467
+ packageName: [...extensions.values()].find(({ manifest }) => manifest.kind === kind && manifest.name === name)?.package ?? `@kubb/${kind}-${name}`,
468
+ extensions
469
+ }).catch((cause) => {
460
470
  throw new Error(`The ${kind} "${name}" is not an installed Kubb extension, so Studio cannot change its options. Edit it in the config file.`, { cause });
461
- });
462
- if (typeof factory !== "function") throw new Error(`The ${kind} "${name}" is not an installed Kubb extension, so Studio cannot change its options. Edit it in the config file.`);
463
- return factory(mergeOptions(disk.options ?? {}, studioOptions));
471
+ }))(mergeOptions(disk.options ?? {}, studioOptions));
464
472
  }
465
473
  /**
466
474
  * Merges Studio-provided adapter option overrides into the disk config's adapter, see {@link rebuildExtension}.
@@ -1455,7 +1463,6 @@ function createGenerationStream(hooks, jobId) {
1455
1463
  return {
1456
1464
  stream,
1457
1465
  close,
1458
- dispose: () => fail(),
1459
1466
  fail
1460
1467
  };
1461
1468
  }
@@ -1588,29 +1595,29 @@ var Agent = class extends capnweb.RpcTarget {
1588
1595
  });
1589
1596
  throw new Error(message);
1590
1597
  }
1591
- async #command({ command, run }) {
1598
+ #assertConnected() {
1592
1599
  if (!this.#session.info) throw new Error("Studio must call connect() before any other command");
1593
- const { value, info } = await run();
1594
- if (info !== void 0) await emit(this.#options.hooks, "studio:command", {
1600
+ }
1601
+ #logCommand(command, info) {
1602
+ return emit(this.#options.hooks, "studio:command", {
1595
1603
  command,
1596
1604
  info
1597
1605
  });
1598
- return value;
1599
1606
  }
1600
- async #readConfigFileView() {
1607
+ async #readConfigFileView(source) {
1601
1608
  if (!this.#permissions.allowConfigEdit) return void 0;
1602
1609
  try {
1603
- return readConfig(await read(this.#options.configFile));
1610
+ return readConfig(await source);
1604
1611
  } catch (error) {
1605
1612
  await this.#warn({ message: `Could not read ${this.#options.configFile}: ${getErrorMessage(error)}` });
1606
1613
  return;
1607
1614
  }
1608
1615
  }
1609
- async #installedExtensions() {
1610
- const source = await read(this.#options.configFile).catch(() => "");
1616
+ async #installedExtensions(source = read(this.#options.configFile)) {
1617
+ const imports = await source.then(readImports, () => []);
1611
1618
  const installed = await (0, _kubb_core.readInstalledPackages)({
1612
1619
  root: this.#options.root,
1613
- packages: [...readImports(source), ...BUILT_IN_PACKAGES],
1620
+ packages: [...imports, ...BUILT_IN_PACKAGES],
1614
1621
  resolveFrom: ["kubb"]
1615
1622
  });
1616
1623
  return new Map(installed.flatMap(({ package: name, version, manifest }) => version !== void 0 && manifest ? [[name, {
@@ -1621,11 +1628,12 @@ var Agent = class extends capnweb.RpcTarget {
1621
1628
  }
1622
1629
  async connect(info) {
1623
1630
  this.#session.info = info;
1624
- const { configPath, root, version: version$1, loadConfig } = this.#options;
1631
+ const { configPath, root, version: version$1, loadConfig, configFile } = this.#options;
1632
+ const source = read(configFile);
1625
1633
  const [config, file, extensions] = await Promise.all([
1626
1634
  loadConfig(),
1627
- this.#readConfigFileView(),
1628
- this.#installedExtensions()
1635
+ this.#readConfigFileView(source),
1636
+ this.#installedExtensions(source)
1629
1637
  ]);
1630
1638
  this.#session.connected.resolve(info);
1631
1639
  return {
@@ -1678,11 +1686,12 @@ var Agent = class extends capnweb.RpcTarget {
1678
1686
  result,
1679
1687
  stop: () => {
1680
1688
  controller.abort(/* @__PURE__ */ new Error("Generation canceled"));
1681
- stream.dispose();
1689
+ stream.fail();
1682
1690
  }
1683
1691
  });
1684
1692
  }
1685
1693
  async #runGeneration(input, controller) {
1694
+ this.#assertConnected();
1686
1695
  if (this.#session.closed) throw new Error("Connection is closed");
1687
1696
  if (this.#session.active) return this.#refuse({ message: "A generation is already in progress, please wait for it to finish" });
1688
1697
  const settled = Promise.withResolvers();
@@ -1694,13 +1703,10 @@ var Agent = class extends capnweb.RpcTarget {
1694
1703
  let root = this.#options.root;
1695
1704
  try {
1696
1705
  if (this.#sandbox) root = await (0, node_fs_promises.mkdtemp)(node_path.default.join((0, node_os.tmpdir)(), "kubb-job-"));
1697
- return await this.#command({
1698
- command: "generate",
1699
- run: () => this.#generate({
1700
- input,
1701
- root,
1702
- signal: controller.signal
1703
- })
1706
+ return await this.#generate({
1707
+ input,
1708
+ root,
1709
+ signal: controller.signal
1704
1710
  });
1705
1711
  } finally {
1706
1712
  if (root !== this.#options.root) await Promise.all([(0, node_fs_promises.rm)(root, {
@@ -1777,31 +1783,24 @@ var Agent = class extends capnweb.RpcTarget {
1777
1783
  output,
1778
1784
  disk
1779
1785
  });
1786
+ await this.#logCommand("generate", `${activePlugins.length} plugin${activePlugins.length === 1 ? "" : "s"}, ${this.#permissions.allowWrite ? "written to disk" : "in memory"}${studioInput !== void 0 ? ", from a Studio spec" : ""}`);
1780
1787
  return {
1781
- value: {
1782
- status: "success",
1783
- files: [...files.paths],
1784
- fileCount: files.paths.size,
1785
- hashes: output.hashes,
1786
- disk: disk ? { hashes: disk.hashes } : void 0
1787
- },
1788
- info: `${activePlugins.length} plugin${activePlugins.length === 1 ? "" : "s"}, ${this.#permissions.allowWrite ? "written to disk" : "in memory"}${studioInput !== void 0 ? ", from a Studio spec" : ""}`
1788
+ status: "success",
1789
+ files: [...files.paths],
1790
+ fileCount: files.paths.size,
1791
+ hashes: output.hashes,
1792
+ disk: disk ? { hashes: disk.hashes } : void 0
1789
1793
  };
1790
1794
  }
1791
- saveConfig(input) {
1792
- return this.#command({
1793
- command: "saveConfig",
1794
- run: () => this.#saveConfig(input)
1795
- });
1796
- }
1797
- async #saveConfig(input) {
1795
+ async saveConfig(input) {
1796
+ this.#assertConnected();
1798
1797
  const { configPath, configFile } = this.#options;
1799
1798
  if (!Array.isArray(input.edits)) {
1800
1799
  await this.#warn({ message: "Ignored save: the message carried no edits" });
1801
- return { value: {
1800
+ return {
1802
1801
  outcomes: [],
1803
1802
  changed: false
1804
- } };
1803
+ };
1805
1804
  }
1806
1805
  const edits = input.edits;
1807
1806
  const refuse = (reason) => ({
@@ -1814,9 +1813,9 @@ var Agent = class extends capnweb.RpcTarget {
1814
1813
  });
1815
1814
  if (!this.#permissions.allowConfigEdit) {
1816
1815
  await this.#warn({ message: "Ignored save: editing kubb.config.ts was not granted" });
1817
- return { value: refuse("the agent was not granted permission to edit kubb.config.ts") };
1816
+ return refuse("the agent was not granted permission to edit kubb.config.ts");
1818
1817
  }
1819
- if (this.#session.active) return { value: refuse("a generation is in progress") };
1818
+ if (this.#session.active) return refuse("a generation is in progress");
1820
1819
  try {
1821
1820
  const { outcomes, changed } = await writeConfigEdits({
1822
1821
  filePath: configFile,
@@ -1824,25 +1823,18 @@ var Agent = class extends capnweb.RpcTarget {
1824
1823
  extensions: await this.#installedExtensions()
1825
1824
  });
1826
1825
  const applied = outcomes.filter((outcome) => outcome.applied).length;
1826
+ await this.#logCommand("saveConfig", `applied ${applied}/${outcomes.length} edits to ${configPath}`);
1827
1827
  return {
1828
- value: {
1829
- outcomes,
1830
- changed
1831
- },
1832
- info: `applied ${applied}/${outcomes.length} edits to ${configPath}`
1828
+ outcomes,
1829
+ changed
1833
1830
  };
1834
1831
  } catch (error) {
1835
1832
  await emit(this.#options.hooks, "studio:error", { error: toError(error) });
1836
- return { value: refuse(getErrorMessage(error)) };
1833
+ return refuse(getErrorMessage(error));
1837
1834
  }
1838
1835
  }
1839
- readFiles(input) {
1840
- return this.#command({
1841
- command: "readFiles",
1842
- run: () => this.#readFiles(input)
1843
- });
1844
- }
1845
- async #readFiles(input) {
1836
+ async readFiles(input) {
1837
+ this.#assertConnected();
1846
1838
  if (!this.#permissions.allowRead) return this.#refuse({
1847
1839
  message: "The agent was not granted permission to read generated files",
1848
1840
  permission: "allowRead"
@@ -1862,10 +1854,8 @@ var Agent = class extends capnweb.RpcTarget {
1862
1854
  cursor: input.cursor,
1863
1855
  limit: input.limit
1864
1856
  });
1865
- return {
1866
- value: result,
1867
- info: `read ${Object.keys(result.files).length}/${paths.length} requested file${paths.length === 1 ? "" : "s"}`
1868
- };
1857
+ await this.#logCommand("readFiles", `read ${Object.keys(result.files).length}/${paths.length} requested file${paths.length === 1 ? "" : "s"}`);
1858
+ return result;
1869
1859
  }
1870
1860
  };
1871
1861
  const openWebSocketRpc = ({ url, token, instanceId, agent }) => {
@@ -2156,7 +2146,6 @@ async function pairAgent({ onCode, onRetry, maxAttempts = 1, ...options }) {
2156
2146
  }
2157
2147
  //#endregion
2158
2148
  //#region src/snapshot.ts
2159
- const gzipAsync = (0, node_util.promisify)(node_zlib.gzip);
2160
2149
  const UPLOAD_TIMEOUT_MS = 12e4;
2161
2150
  const BUNDLED_DEPENDENCIES = ["@kubb/plugin-barrel"];
2162
2151
  /**
@@ -2276,7 +2265,7 @@ async function createSnapshotPackage({ files, packageInfo }) {
2276
2265
  Buffer.alloc((512 - bytes.length % 512) % 512)
2277
2266
  ];
2278
2267
  });
2279
- return await gzipAsync(Buffer.concat([...chunks, Buffer.alloc(1024)]));
2268
+ return (0, node_zlib.gzipSync)(Buffer.concat([...chunks, Buffer.alloc(1024)]));
2280
2269
  } finally {
2281
2270
  await (0, node_fs_promises.rm)(root, {
2282
2271
  recursive: true,