@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.cjs
CHANGED
|
@@ -176,10 +176,18 @@ function getElapsedMs(hrStart) {
|
|
|
176
176
|
}
|
|
177
177
|
//#endregion
|
|
178
178
|
//#region package.json
|
|
179
|
-
var version = "5.9.
|
|
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
|
|
262
|
-
* {@link isAllowedPlugin}
|
|
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
|
|
279
|
+
* @throws if the package cannot be imported or exports no callable factory.
|
|
271
280
|
*/
|
|
272
|
-
async function
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
1598
|
+
#assertConnected() {
|
|
1592
1599
|
if (!this.#session.info) throw new Error("Studio must call connect() before any other command");
|
|
1593
|
-
|
|
1594
|
-
|
|
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
|
|
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
|
|
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: [...
|
|
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.
|
|
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.#
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
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
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
1816
|
+
return refuse("the agent was not granted permission to edit kubb.config.ts");
|
|
1818
1817
|
}
|
|
1819
|
-
if (this.#session.active) return
|
|
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
|
-
|
|
1829
|
-
|
|
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
|
|
1833
|
+
return refuse(getErrorMessage(error));
|
|
1837
1834
|
}
|
|
1838
1835
|
}
|
|
1839
|
-
readFiles(input) {
|
|
1840
|
-
|
|
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
|
-
|
|
1866
|
-
|
|
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
|
|
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,
|