@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 +65 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +67 -78
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -12,9 +12,9 @@ import { existsSync } from "node:fs";
|
|
|
12
12
|
import { Diagnostics, Hookable, cacheStorage, createKubb, fsStorage, getInputKind, isPackageName, memoryStorage, readInstalledPackages, resolveCacheDir, resolveMeta } from "@kubb/core";
|
|
13
13
|
import { RpcTarget, newWebSocketRpcSession } from "capnweb";
|
|
14
14
|
import { detectCodeFormat, parseExpression, parseModule } from "magicast";
|
|
15
|
-
import {
|
|
15
|
+
import { stripVTControlCharacters } from "node:util";
|
|
16
16
|
import { ofetch } from "ofetch";
|
|
17
|
-
import {
|
|
17
|
+
import { gzipSync } from "node:zlib";
|
|
18
18
|
//#region ../../internals/utils/src/casing.ts
|
|
19
19
|
/**
|
|
20
20
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
@@ -152,10 +152,18 @@ function getElapsedMs(hrStart) {
|
|
|
152
152
|
}
|
|
153
153
|
//#endregion
|
|
154
154
|
//#region package.json
|
|
155
|
-
var version = "5.9.
|
|
155
|
+
var version = "5.9.4";
|
|
156
156
|
//#endregion
|
|
157
157
|
//#region src/resolveConfig.ts
|
|
158
158
|
/**
|
|
159
|
+
* Turns the JSON config Studio sends back into live Kubb objects.
|
|
160
|
+
*
|
|
161
|
+
* A plugin or adapter instance carries closures (`parse`, `getImports`, ...) that cannot survive
|
|
162
|
+
* JSON, so both sides pass options over the wire and the factory is re-invoked here with the merged
|
|
163
|
+
* result. Only packages {@link isAllowedPlugin} accepts are resolved this way, never an arbitrary
|
|
164
|
+
* module the payload names.
|
|
165
|
+
*/
|
|
166
|
+
/**
|
|
159
167
|
* Imports a package, falling back to how the user's project would resolve it.
|
|
160
168
|
*
|
|
161
169
|
* `import()` resolves from this file, so a linked or globally installed Studio (`pnpm link`,
|
|
@@ -234,8 +242,9 @@ function isAllowedPlugin({ packageName, extensions }) {
|
|
|
234
242
|
return KUBB_PLUGIN_SPECIFIER.test(packageName) || extensions.get(packageName)?.manifest.kind === "plugin";
|
|
235
243
|
}
|
|
236
244
|
/**
|
|
237
|
-
* Dynamically imports
|
|
238
|
-
* {@link isAllowedPlugin}
|
|
245
|
+
* Dynamically imports an extension package and returns its factory. Callers check
|
|
246
|
+
* {@link isAllowedPlugin} or derive the package from a disk instance first, so an arbitrary module
|
|
247
|
+
* never reaches `import()`.
|
|
239
248
|
*
|
|
240
249
|
* Packages must be pre-installed in the Docker image at build time via the `KUBB_PACKAGES`
|
|
241
250
|
* build ARG, no runtime installation is possible in the distroless container.
|
|
@@ -243,13 +252,9 @@ function isAllowedPlugin({ packageName, extensions }) {
|
|
|
243
252
|
* Resolution order: the manifest's `export`, else the camelCase named export the package name
|
|
244
253
|
* implies (e.g. `pluginTs`), then the default export.
|
|
245
254
|
*
|
|
246
|
-
* @throws if the package
|
|
255
|
+
* @throws if the package cannot be imported or exports no callable factory.
|
|
247
256
|
*/
|
|
248
|
-
async function
|
|
249
|
-
if (!isAllowedPlugin({
|
|
250
|
-
packageName,
|
|
251
|
-
extensions
|
|
252
|
-
})) 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".`);
|
|
257
|
+
async function loadFactory({ packageName, extensions }) {
|
|
253
258
|
let mod;
|
|
254
259
|
try {
|
|
255
260
|
mod = await importFromProject(packageName);
|
|
@@ -268,7 +273,7 @@ async function loadPluginFactory({ packageName, extensions }) {
|
|
|
268
273
|
}
|
|
269
274
|
/**
|
|
270
275
|
* Resolves each plugin entry by dynamically importing its package and calling its factory with the
|
|
271
|
-
* provided options.
|
|
276
|
+
* provided options. Only packages {@link isAllowedPlugin} accepts are loaded.
|
|
272
277
|
*
|
|
273
278
|
* @example
|
|
274
279
|
* ```ts
|
|
@@ -282,7 +287,11 @@ async function loadPluginFactory({ packageName, extensions }) {
|
|
|
282
287
|
*/
|
|
283
288
|
async function resolvePlugins({ plugins, extensions = /* @__PURE__ */ new Map() }) {
|
|
284
289
|
return Promise.all(plugins.map(async ({ name, options }) => {
|
|
285
|
-
|
|
290
|
+
if (!isAllowedPlugin({
|
|
291
|
+
packageName: name,
|
|
292
|
+
extensions
|
|
293
|
+
})) 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".`);
|
|
294
|
+
return (await loadFactory({
|
|
286
295
|
packageName: name,
|
|
287
296
|
extensions
|
|
288
297
|
}))(options ?? {});
|
|
@@ -430,13 +439,12 @@ async function mergePlugins({ diskPlugins, studioPlugins, extensions = /* @__PUR
|
|
|
430
439
|
async function rebuildExtension({ disk, studioOptions, kind, extensions }) {
|
|
431
440
|
if (!studioOptions || !Object.keys(studioOptions).length) return disk;
|
|
432
441
|
const { name } = resolveMeta(disk);
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
442
|
+
return (await loadFactory({
|
|
443
|
+
packageName: [...extensions.values()].find(({ manifest }) => manifest.kind === kind && manifest.name === name)?.package ?? `@kubb/${kind}-${name}`,
|
|
444
|
+
extensions
|
|
445
|
+
}).catch((cause) => {
|
|
436
446
|
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 });
|
|
437
|
-
});
|
|
438
|
-
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.`);
|
|
439
|
-
return factory(mergeOptions(disk.options ?? {}, studioOptions));
|
|
447
|
+
}))(mergeOptions(disk.options ?? {}, studioOptions));
|
|
440
448
|
}
|
|
441
449
|
/**
|
|
442
450
|
* Merges Studio-provided adapter option overrides into the disk config's adapter, see {@link rebuildExtension}.
|
|
@@ -1431,7 +1439,6 @@ function createGenerationStream(hooks, jobId) {
|
|
|
1431
1439
|
return {
|
|
1432
1440
|
stream,
|
|
1433
1441
|
close,
|
|
1434
|
-
dispose: () => fail(),
|
|
1435
1442
|
fail
|
|
1436
1443
|
};
|
|
1437
1444
|
}
|
|
@@ -1564,29 +1571,29 @@ var Agent = class extends RpcTarget {
|
|
|
1564
1571
|
});
|
|
1565
1572
|
throw new Error(message);
|
|
1566
1573
|
}
|
|
1567
|
-
|
|
1574
|
+
#assertConnected() {
|
|
1568
1575
|
if (!this.#session.info) throw new Error("Studio must call connect() before any other command");
|
|
1569
|
-
|
|
1570
|
-
|
|
1576
|
+
}
|
|
1577
|
+
#logCommand(command, info) {
|
|
1578
|
+
return emit(this.#options.hooks, "studio:command", {
|
|
1571
1579
|
command,
|
|
1572
1580
|
info
|
|
1573
1581
|
});
|
|
1574
|
-
return value;
|
|
1575
1582
|
}
|
|
1576
|
-
async #readConfigFileView() {
|
|
1583
|
+
async #readConfigFileView(source) {
|
|
1577
1584
|
if (!this.#permissions.allowConfigEdit) return void 0;
|
|
1578
1585
|
try {
|
|
1579
|
-
return readConfig(await
|
|
1586
|
+
return readConfig(await source);
|
|
1580
1587
|
} catch (error) {
|
|
1581
1588
|
await this.#warn({ message: `Could not read ${this.#options.configFile}: ${getErrorMessage(error)}` });
|
|
1582
1589
|
return;
|
|
1583
1590
|
}
|
|
1584
1591
|
}
|
|
1585
|
-
async #installedExtensions() {
|
|
1586
|
-
const
|
|
1592
|
+
async #installedExtensions(source = read(this.#options.configFile)) {
|
|
1593
|
+
const imports = await source.then(readImports, () => []);
|
|
1587
1594
|
const installed = await readInstalledPackages({
|
|
1588
1595
|
root: this.#options.root,
|
|
1589
|
-
packages: [...
|
|
1596
|
+
packages: [...imports, ...BUILT_IN_PACKAGES],
|
|
1590
1597
|
resolveFrom: ["kubb"]
|
|
1591
1598
|
});
|
|
1592
1599
|
return new Map(installed.flatMap(({ package: name, version, manifest }) => version !== void 0 && manifest ? [[name, {
|
|
@@ -1597,11 +1604,12 @@ var Agent = class extends RpcTarget {
|
|
|
1597
1604
|
}
|
|
1598
1605
|
async connect(info) {
|
|
1599
1606
|
this.#session.info = info;
|
|
1600
|
-
const { configPath, root, version: version$1, loadConfig } = this.#options;
|
|
1607
|
+
const { configPath, root, version: version$1, loadConfig, configFile } = this.#options;
|
|
1608
|
+
const source = read(configFile);
|
|
1601
1609
|
const [config, file, extensions] = await Promise.all([
|
|
1602
1610
|
loadConfig(),
|
|
1603
|
-
this.#readConfigFileView(),
|
|
1604
|
-
this.#installedExtensions()
|
|
1611
|
+
this.#readConfigFileView(source),
|
|
1612
|
+
this.#installedExtensions(source)
|
|
1605
1613
|
]);
|
|
1606
1614
|
this.#session.connected.resolve(info);
|
|
1607
1615
|
return {
|
|
@@ -1654,11 +1662,12 @@ var Agent = class extends RpcTarget {
|
|
|
1654
1662
|
result,
|
|
1655
1663
|
stop: () => {
|
|
1656
1664
|
controller.abort(/* @__PURE__ */ new Error("Generation canceled"));
|
|
1657
|
-
stream.
|
|
1665
|
+
stream.fail();
|
|
1658
1666
|
}
|
|
1659
1667
|
});
|
|
1660
1668
|
}
|
|
1661
1669
|
async #runGeneration(input, controller) {
|
|
1670
|
+
this.#assertConnected();
|
|
1662
1671
|
if (this.#session.closed) throw new Error("Connection is closed");
|
|
1663
1672
|
if (this.#session.active) return this.#refuse({ message: "A generation is already in progress, please wait for it to finish" });
|
|
1664
1673
|
const settled = Promise.withResolvers();
|
|
@@ -1670,13 +1679,10 @@ var Agent = class extends RpcTarget {
|
|
|
1670
1679
|
let root = this.#options.root;
|
|
1671
1680
|
try {
|
|
1672
1681
|
if (this.#sandbox) root = await mkdtemp(path.join(tmpdir(), "kubb-job-"));
|
|
1673
|
-
return await this.#
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
root,
|
|
1678
|
-
signal: controller.signal
|
|
1679
|
-
})
|
|
1682
|
+
return await this.#generate({
|
|
1683
|
+
input,
|
|
1684
|
+
root,
|
|
1685
|
+
signal: controller.signal
|
|
1680
1686
|
});
|
|
1681
1687
|
} finally {
|
|
1682
1688
|
if (root !== this.#options.root) await Promise.all([rm(root, {
|
|
@@ -1753,31 +1759,24 @@ var Agent = class extends RpcTarget {
|
|
|
1753
1759
|
output,
|
|
1754
1760
|
disk
|
|
1755
1761
|
});
|
|
1762
|
+
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" : ""}`);
|
|
1756
1763
|
return {
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
disk: disk ? { hashes: disk.hashes } : void 0
|
|
1763
|
-
},
|
|
1764
|
-
info: `${activePlugins.length} plugin${activePlugins.length === 1 ? "" : "s"}, ${this.#permissions.allowWrite ? "written to disk" : "in memory"}${studioInput !== void 0 ? ", from a Studio spec" : ""}`
|
|
1764
|
+
status: "success",
|
|
1765
|
+
files: [...files.paths],
|
|
1766
|
+
fileCount: files.paths.size,
|
|
1767
|
+
hashes: output.hashes,
|
|
1768
|
+
disk: disk ? { hashes: disk.hashes } : void 0
|
|
1765
1769
|
};
|
|
1766
1770
|
}
|
|
1767
|
-
saveConfig(input) {
|
|
1768
|
-
|
|
1769
|
-
command: "saveConfig",
|
|
1770
|
-
run: () => this.#saveConfig(input)
|
|
1771
|
-
});
|
|
1772
|
-
}
|
|
1773
|
-
async #saveConfig(input) {
|
|
1771
|
+
async saveConfig(input) {
|
|
1772
|
+
this.#assertConnected();
|
|
1774
1773
|
const { configPath, configFile } = this.#options;
|
|
1775
1774
|
if (!Array.isArray(input.edits)) {
|
|
1776
1775
|
await this.#warn({ message: "Ignored save: the message carried no edits" });
|
|
1777
|
-
return {
|
|
1776
|
+
return {
|
|
1778
1777
|
outcomes: [],
|
|
1779
1778
|
changed: false
|
|
1780
|
-
}
|
|
1779
|
+
};
|
|
1781
1780
|
}
|
|
1782
1781
|
const edits = input.edits;
|
|
1783
1782
|
const refuse = (reason) => ({
|
|
@@ -1790,9 +1789,9 @@ var Agent = class extends RpcTarget {
|
|
|
1790
1789
|
});
|
|
1791
1790
|
if (!this.#permissions.allowConfigEdit) {
|
|
1792
1791
|
await this.#warn({ message: "Ignored save: editing kubb.config.ts was not granted" });
|
|
1793
|
-
return
|
|
1792
|
+
return refuse("the agent was not granted permission to edit kubb.config.ts");
|
|
1794
1793
|
}
|
|
1795
|
-
if (this.#session.active) return
|
|
1794
|
+
if (this.#session.active) return refuse("a generation is in progress");
|
|
1796
1795
|
try {
|
|
1797
1796
|
const { outcomes, changed } = await writeConfigEdits({
|
|
1798
1797
|
filePath: configFile,
|
|
@@ -1800,25 +1799,18 @@ var Agent = class extends RpcTarget {
|
|
|
1800
1799
|
extensions: await this.#installedExtensions()
|
|
1801
1800
|
});
|
|
1802
1801
|
const applied = outcomes.filter((outcome) => outcome.applied).length;
|
|
1802
|
+
await this.#logCommand("saveConfig", `applied ${applied}/${outcomes.length} edits to ${configPath}`);
|
|
1803
1803
|
return {
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
changed
|
|
1807
|
-
},
|
|
1808
|
-
info: `applied ${applied}/${outcomes.length} edits to ${configPath}`
|
|
1804
|
+
outcomes,
|
|
1805
|
+
changed
|
|
1809
1806
|
};
|
|
1810
1807
|
} catch (error) {
|
|
1811
1808
|
await emit(this.#options.hooks, "studio:error", { error: toError(error) });
|
|
1812
|
-
return
|
|
1809
|
+
return refuse(getErrorMessage(error));
|
|
1813
1810
|
}
|
|
1814
1811
|
}
|
|
1815
|
-
readFiles(input) {
|
|
1816
|
-
|
|
1817
|
-
command: "readFiles",
|
|
1818
|
-
run: () => this.#readFiles(input)
|
|
1819
|
-
});
|
|
1820
|
-
}
|
|
1821
|
-
async #readFiles(input) {
|
|
1812
|
+
async readFiles(input) {
|
|
1813
|
+
this.#assertConnected();
|
|
1822
1814
|
if (!this.#permissions.allowRead) return this.#refuse({
|
|
1823
1815
|
message: "The agent was not granted permission to read generated files",
|
|
1824
1816
|
permission: "allowRead"
|
|
@@ -1838,10 +1830,8 @@ var Agent = class extends RpcTarget {
|
|
|
1838
1830
|
cursor: input.cursor,
|
|
1839
1831
|
limit: input.limit
|
|
1840
1832
|
});
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
info: `read ${Object.keys(result.files).length}/${paths.length} requested file${paths.length === 1 ? "" : "s"}`
|
|
1844
|
-
};
|
|
1833
|
+
await this.#logCommand("readFiles", `read ${Object.keys(result.files).length}/${paths.length} requested file${paths.length === 1 ? "" : "s"}`);
|
|
1834
|
+
return result;
|
|
1845
1835
|
}
|
|
1846
1836
|
};
|
|
1847
1837
|
const openWebSocketRpc = ({ url, token, instanceId, agent }) => {
|
|
@@ -2132,7 +2122,6 @@ async function pairAgent({ onCode, onRetry, maxAttempts = 1, ...options }) {
|
|
|
2132
2122
|
}
|
|
2133
2123
|
//#endregion
|
|
2134
2124
|
//#region src/snapshot.ts
|
|
2135
|
-
const gzipAsync = promisify(gzip);
|
|
2136
2125
|
const UPLOAD_TIMEOUT_MS = 12e4;
|
|
2137
2126
|
const BUNDLED_DEPENDENCIES = ["@kubb/plugin-barrel"];
|
|
2138
2127
|
/**
|
|
@@ -2252,7 +2241,7 @@ async function createSnapshotPackage({ files, packageInfo }) {
|
|
|
2252
2241
|
Buffer.alloc((512 - bytes.length % 512) % 512)
|
|
2253
2242
|
];
|
|
2254
2243
|
});
|
|
2255
|
-
return
|
|
2244
|
+
return gzipSync(Buffer.concat([...chunks, Buffer.alloc(1024)]));
|
|
2256
2245
|
} finally {
|
|
2257
2246
|
await rm(root, {
|
|
2258
2247
|
recursive: true,
|