@kubb/studio 5.9.4 → 5.9.6
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/README.md +2 -2
- package/dist/index.cjs +69 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +70 -7
- package/dist/index.js.map +1 -1
- package/dist/{protocol-gXoAXvT3.d.ts → protocol-ixLkhvRJ.d.ts} +21 -2
- package/dist/protocol.cjs +4 -0
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.ts +2 -2
- package/dist/protocol.js +3 -1
- package/dist/protocol.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ running in a developer's project and a container running a fixed plugin set.
|
|
|
74
74
|
| `connect.ts` | `connectAgent`: the socket loop and the `Agent` that serves Studio's calls. |
|
|
75
75
|
| `pair.ts` | `pairAgent`: RFC 8628 device pairing. |
|
|
76
76
|
| `snapshot.ts` | `createSnapshot` for CI, and packing a generation into an npm tarball. |
|
|
77
|
-
| `generations.ts` | Running a generation and keeping its files for `readFiles`.
|
|
77
|
+
| `generations.ts` | Running a generation and keeping its files and AST for `readFiles`/`readInput`. |
|
|
78
78
|
| `configFile.ts` | Reading and patching plugin options in `kubb.config.ts`. |
|
|
79
79
|
|
|
80
80
|
## Connection
|
|
@@ -107,7 +107,7 @@ Every permission is off by default, and each covers one trust boundary:
|
|
|
107
107
|
| `allowWrite` | Generated files are written to disk. Off means they exist only in memory and stream to Studio. |
|
|
108
108
|
| `allowExec` | The formatter, the linter, and `output.postGenerate` run as child processes. |
|
|
109
109
|
| `allowConfigEdit` | Studio may change plugin options in `kubb.config.ts`. |
|
|
110
|
-
| `allowRead` | Studio may read back the files a generation produced.
|
|
110
|
+
| `allowRead` | Studio may read back the files a generation produced and its parsed spec. |
|
|
111
111
|
|
|
112
112
|
A sandbox never writes or edits config, and always allows reads.
|
|
113
113
|
|
package/dist/index.cjs
CHANGED
|
@@ -37,6 +37,7 @@ let capnweb = require("capnweb");
|
|
|
37
37
|
let magicast = require("magicast");
|
|
38
38
|
let node_module = require("node:module");
|
|
39
39
|
let node_util = require("node:util");
|
|
40
|
+
let _kubb_ast = require("@kubb/ast");
|
|
40
41
|
let ofetch = require("ofetch");
|
|
41
42
|
let node_zlib = require("node:zlib");
|
|
42
43
|
//#region ../../internals/utils/src/casing.ts
|
|
@@ -176,7 +177,7 @@ function getElapsedMs(hrStart) {
|
|
|
176
177
|
}
|
|
177
178
|
//#endregion
|
|
178
179
|
//#region package.json
|
|
179
|
-
var version = "5.9.
|
|
180
|
+
var version = "5.9.6";
|
|
180
181
|
//#endregion
|
|
181
182
|
//#region src/resolveConfig.ts
|
|
182
183
|
/**
|
|
@@ -1107,7 +1108,10 @@ function relativeStoragePath({ root, filePath }) {
|
|
|
1107
1108
|
* The content fingerprint Studio compares snapshots by. Agent runs and CI uploads must agree on it.
|
|
1108
1109
|
*/
|
|
1109
1110
|
const hashOf = (content) => (0, node_crypto.createHash)("sha1").update(content).digest("hex").slice(0, 16);
|
|
1110
|
-
|
|
1111
|
+
/**
|
|
1112
|
+
* The bytes `entries` keep on disk: output, written files, and kept input.
|
|
1113
|
+
*/
|
|
1114
|
+
const totalBytes = (entries) => entries.reduce((sum, { output, disk, inputBytes }) => sum + output.bytes + (disk?.bytes ?? 0) + (inputBytes ?? 0), 0);
|
|
1111
1115
|
function positiveNumber(value) {
|
|
1112
1116
|
const parsed = Number(value);
|
|
1113
1117
|
return value && Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
|
|
@@ -1150,7 +1154,11 @@ async function generate({ config, hooks, signal, allowExec }) {
|
|
|
1150
1154
|
hooks,
|
|
1151
1155
|
signal
|
|
1152
1156
|
});
|
|
1153
|
-
|
|
1157
|
+
let input = null;
|
|
1158
|
+
const unhook = kubb.hooks.hook("kubb:build:end", (ctx) => {
|
|
1159
|
+
if (ctx.config === kubb.config) input = kubb.driver.inputNode;
|
|
1160
|
+
});
|
|
1161
|
+
const result = await kubb.generate().finally(unhook);
|
|
1154
1162
|
if (!result.success) throw generationFailure(result.diagnostics);
|
|
1155
1163
|
const { storage, root } = kubb.config;
|
|
1156
1164
|
return {
|
|
@@ -1159,7 +1167,8 @@ async function generate({ config, hooks, signal, allowExec }) {
|
|
|
1159
1167
|
paths: new Set(result.files.map((file) => relativeStoragePath({
|
|
1160
1168
|
root,
|
|
1161
1169
|
filePath: file.path
|
|
1162
|
-
})))
|
|
1170
|
+
}))),
|
|
1171
|
+
input
|
|
1163
1172
|
};
|
|
1164
1173
|
}
|
|
1165
1174
|
/**
|
|
@@ -1240,6 +1249,33 @@ function createGenerationStore({ storage, maxCount, maxMb, ttlMs, now = Date.now
|
|
|
1240
1249
|
return {
|
|
1241
1250
|
keep,
|
|
1242
1251
|
drop,
|
|
1252
|
+
/**
|
|
1253
|
+
* Keeps the `InputNode` of `jobId` and returns its size in bytes, or 0 when it keeps none. A ref schema loses
|
|
1254
|
+
* the copy of its target, which `input.schemas` holds: with it, a large spec outgrows what `JSON.stringify` can return.
|
|
1255
|
+
*/
|
|
1256
|
+
async keepInput({ jobId, input, maxSetMb }) {
|
|
1257
|
+
if (!input) return 0;
|
|
1258
|
+
let json;
|
|
1259
|
+
try {
|
|
1260
|
+
json = JSON.stringify(_kubb_ast.ast.transform(input, { schema: (node) => node.type === "ref" && node.schema ? {
|
|
1261
|
+
...node,
|
|
1262
|
+
schema: void 0
|
|
1263
|
+
} : void 0 }));
|
|
1264
|
+
} catch {
|
|
1265
|
+
return 0;
|
|
1266
|
+
}
|
|
1267
|
+
const bytes = Buffer.byteLength(json);
|
|
1268
|
+
if (bytes > maxSetMb * MB) return 0;
|
|
1269
|
+
await storage.writeItem(`${dirOf(jobId)}input.json`, json);
|
|
1270
|
+
return bytes;
|
|
1271
|
+
},
|
|
1272
|
+
/**
|
|
1273
|
+
* The input `keepInput` kept for `jobId`, or `undefined` when there is none.
|
|
1274
|
+
*/
|
|
1275
|
+
async readInput(jobId) {
|
|
1276
|
+
const stored = await storage.readItem(`${dirOf(jobId)}input.json`);
|
|
1277
|
+
return stored === null ? void 0 : JSON.parse(stored);
|
|
1278
|
+
},
|
|
1243
1279
|
get: async (jobId) => (await load()).find((generation) => generation.jobId === jobId && isLive(generation)),
|
|
1244
1280
|
async add(generation) {
|
|
1245
1281
|
const current = await load();
|
|
@@ -1690,6 +1726,7 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1690
1726
|
}
|
|
1691
1727
|
});
|
|
1692
1728
|
}
|
|
1729
|
+
/** Runs one job and keeps its output, and its input with `allowRead`, for later reads. */
|
|
1693
1730
|
async #runGeneration(input, controller) {
|
|
1694
1731
|
this.#assertConnected();
|
|
1695
1732
|
if (this.#session.closed) throw new Error("Connection is closed");
|
|
@@ -1777,11 +1814,17 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1777
1814
|
files,
|
|
1778
1815
|
maxSetMb: this.#limits.maxMb
|
|
1779
1816
|
});
|
|
1817
|
+
const inputBytes = await this.#generations.keepInput({
|
|
1818
|
+
jobId,
|
|
1819
|
+
input: this.#permissions.allowRead ? files.input : null,
|
|
1820
|
+
maxSetMb: this.#limits.maxMb
|
|
1821
|
+
});
|
|
1780
1822
|
if (!output.paths.length && Object.keys(output.hashes).length) await this.#warn({ message: "Kept only the hashes of this generation: its output is too large to keep" });
|
|
1781
1823
|
await this.#generations.add({
|
|
1782
1824
|
jobId,
|
|
1783
1825
|
output,
|
|
1784
|
-
disk
|
|
1826
|
+
disk,
|
|
1827
|
+
inputBytes
|
|
1785
1828
|
});
|
|
1786
1829
|
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" : ""}`);
|
|
1787
1830
|
return {
|
|
@@ -1833,10 +1876,30 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1833
1876
|
return refuse(getErrorMessage(error));
|
|
1834
1877
|
}
|
|
1835
1878
|
}
|
|
1879
|
+
/**
|
|
1880
|
+
* Returns the Kubb AST a kept job parsed its spec into. Refused without `allowRead`.
|
|
1881
|
+
*/
|
|
1882
|
+
async readInput(input) {
|
|
1883
|
+
this.#assertConnected();
|
|
1884
|
+
if (!this.#permissions.allowRead) return this.#refuse({
|
|
1885
|
+
message: require_protocol.READ_NOT_ALLOWED_MESSAGE,
|
|
1886
|
+
permission: "allowRead"
|
|
1887
|
+
});
|
|
1888
|
+
if (typeof input.jobId !== "string" || !input.jobId) return this.#refuse({ message: "The request must name a job" });
|
|
1889
|
+
const generation = await this.#generations.get(input.jobId);
|
|
1890
|
+
if (!generation) return this.#refuse({ message: require_protocol.GENERATION_GONE_MESSAGE });
|
|
1891
|
+
const kept = generation.inputBytes ? await this.#generations.readInput(input.jobId) : void 0;
|
|
1892
|
+
if (!kept) return this.#refuse({ message: require_protocol.NO_INPUT_MESSAGE });
|
|
1893
|
+
await this.#logCommand("readInput", "read the API input");
|
|
1894
|
+
return { input: kept };
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1897
|
+
* Returns the requested files of a kept job, one page of lines at a time. Refused without `allowRead`.
|
|
1898
|
+
*/
|
|
1836
1899
|
async readFiles(input) {
|
|
1837
1900
|
this.#assertConnected();
|
|
1838
1901
|
if (!this.#permissions.allowRead) return this.#refuse({
|
|
1839
|
-
message:
|
|
1902
|
+
message: require_protocol.READ_NOT_ALLOWED_MESSAGE,
|
|
1840
1903
|
permission: "allowRead"
|
|
1841
1904
|
});
|
|
1842
1905
|
if (!Array.isArray(input.paths) || typeof input.jobId !== "string" || !input.jobId) return this.#refuse({ message: "The request must name a job and carry paths" });
|