@kubb/studio 5.10.1 → 5.10.3
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 +1 -0
- package/dist/index.cjs +417 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +260 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.js +419 -44
- package/dist/index.js.map +1 -1
- package/dist/protocol-CrMJymjT.d.cts +778 -0
- package/dist/{protocol-C1PwN9fa.d.ts → protocol-CrMJymjT.d.ts} +96 -12
- package/dist/protocol.cjs +34 -10
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +2 -0
- package/dist/protocol.d.ts +2 -2
- package/dist/protocol.js +32 -7
- package/dist/protocol.js.map +1 -1
- package/package.json +28 -10
- package/dist/rolldown-runtime-CRm0XQPb.js +0 -9
package/dist/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ let _kubb_core = require("@kubb/core");
|
|
|
36
36
|
let capnweb = require("capnweb");
|
|
37
37
|
let magicast = require("magicast");
|
|
38
38
|
let node_module = require("node:module");
|
|
39
|
+
let diff = require("diff");
|
|
39
40
|
let node_util = require("node:util");
|
|
40
41
|
let _kubb_ast = require("@kubb/ast");
|
|
41
42
|
let ofetch = require("ofetch");
|
|
@@ -177,7 +178,7 @@ function getElapsedMs(hrStart) {
|
|
|
177
178
|
}
|
|
178
179
|
//#endregion
|
|
179
180
|
//#region package.json
|
|
180
|
-
var version = "5.10.
|
|
181
|
+
var version = "5.10.3";
|
|
181
182
|
//#endregion
|
|
182
183
|
//#region src/resolveConfig.ts
|
|
183
184
|
/**
|
|
@@ -279,22 +280,42 @@ function isAllowedPlugin({ packageName, extensions }) {
|
|
|
279
280
|
*
|
|
280
281
|
* @throws if the package cannot be imported or exports no callable factory.
|
|
281
282
|
*/
|
|
282
|
-
async function loadFactory({ packageName, extensions }) {
|
|
283
|
+
async function loadFactory({ packageName, extensions, kind = "plugin" }) {
|
|
284
|
+
const label = {
|
|
285
|
+
plugin: "Plugin",
|
|
286
|
+
adapter: "Adapter",
|
|
287
|
+
parser: "Parser"
|
|
288
|
+
}[kind];
|
|
283
289
|
let mod;
|
|
284
290
|
try {
|
|
285
291
|
mod = await importFromProject(packageName);
|
|
286
292
|
} catch (cause) {
|
|
287
|
-
throw new Error(
|
|
293
|
+
throw new _kubb_core.Diagnostics.Error({
|
|
294
|
+
code: kind === "plugin" ? _kubb_core.Diagnostics.code.pluginNotFound : _kubb_core.Diagnostics.code.unknown,
|
|
295
|
+
severity: "error",
|
|
296
|
+
message: `${label} "${packageName}" could not be loaded. Make sure it is installed: \`npm install ${packageName}\``,
|
|
297
|
+
cause: toError(cause)
|
|
298
|
+
});
|
|
288
299
|
}
|
|
289
300
|
const manifestExport = extensions.get(packageName)?.manifest.export;
|
|
290
301
|
if (manifestExport) {
|
|
291
302
|
if (typeof mod[manifestExport] === "function") return mod[manifestExport];
|
|
292
|
-
throw new Error(
|
|
303
|
+
throw new _kubb_core.Diagnostics.Error({
|
|
304
|
+
code: _kubb_core.Diagnostics.code.incompatibleExtension,
|
|
305
|
+
severity: "error",
|
|
306
|
+
message: `${label} "${packageName}" does not export "${manifestExport}", the factory its kubb-extension.json names.`,
|
|
307
|
+
help: "Install a version of the plugin whose exports match its kubb-extension.json."
|
|
308
|
+
});
|
|
293
309
|
}
|
|
294
310
|
const exportName = toExportName(packageName);
|
|
295
311
|
if (typeof mod[exportName] === "function") return mod[exportName];
|
|
296
312
|
if (typeof mod["default"] === "function") return mod["default"];
|
|
297
|
-
throw new Error(
|
|
313
|
+
throw new _kubb_core.Diagnostics.Error({
|
|
314
|
+
code: _kubb_core.Diagnostics.code.incompatibleExtension,
|
|
315
|
+
severity: "error",
|
|
316
|
+
message: `${label} "${packageName}" does not export a callable factory. Tried the named export "${exportName}" and "default".`,
|
|
317
|
+
help: "Install a Kubb plugin package that exports its factory."
|
|
318
|
+
});
|
|
298
319
|
}
|
|
299
320
|
/**
|
|
300
321
|
* Resolves each plugin entry by dynamically importing its package and calling its factory with the
|
|
@@ -315,7 +336,11 @@ async function resolvePlugins({ plugins, extensions = /* @__PURE__ */ new Map()
|
|
|
315
336
|
if (!isAllowedPlugin({
|
|
316
337
|
packageName: name,
|
|
317
338
|
extensions
|
|
318
|
-
})) throw new Error(
|
|
339
|
+
})) throw new _kubb_core.Diagnostics.Error({
|
|
340
|
+
code: _kubb_core.Diagnostics.code.pluginNotFound,
|
|
341
|
+
severity: "error",
|
|
342
|
+
message: `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".`
|
|
343
|
+
});
|
|
319
344
|
return (await loadFactory({
|
|
320
345
|
packageName: name,
|
|
321
346
|
extensions
|
|
@@ -442,7 +467,12 @@ async function mergePlugins({ diskPlugins, studioPlugins, extensions = /* @__PUR
|
|
|
442
467
|
extensions
|
|
443
468
|
}).catch((cause) => {
|
|
444
469
|
if (unchanged) return [diskPlugin];
|
|
445
|
-
throw new Error(
|
|
470
|
+
throw new _kubb_core.Diagnostics.Error({
|
|
471
|
+
code: _kubb_core.Diagnostics.code.pluginNotFound,
|
|
472
|
+
severity: "error",
|
|
473
|
+
message: `Plugin "${diskPlugin.name}" is not an installed @kubb/plugin-* package or Kubb extension, so Studio cannot change its options. Edit it in the config file, or only enable or disable it from Studio.`,
|
|
474
|
+
cause: toError(cause)
|
|
475
|
+
});
|
|
446
476
|
});
|
|
447
477
|
return resolved ?? diskPlugin;
|
|
448
478
|
}));
|
|
@@ -466,9 +496,15 @@ async function rebuildExtension({ disk, studioOptions, kind, extensions }) {
|
|
|
466
496
|
const { name } = (0, _kubb_core.resolveMeta)(disk);
|
|
467
497
|
return (await loadFactory({
|
|
468
498
|
packageName: [...extensions.values()].find(({ manifest }) => manifest.kind === kind && manifest.name === name)?.package ?? `@kubb/${kind}-${name}`,
|
|
469
|
-
extensions
|
|
499
|
+
extensions,
|
|
500
|
+
kind
|
|
470
501
|
}).catch((cause) => {
|
|
471
|
-
throw new Error(
|
|
502
|
+
throw new _kubb_core.Diagnostics.Error({
|
|
503
|
+
code: kind === "plugin" ? _kubb_core.Diagnostics.code.pluginNotFound : _kubb_core.Diagnostics.code.unknown,
|
|
504
|
+
severity: "error",
|
|
505
|
+
message: `The ${kind} "${name}" is not an installed Kubb extension, so Studio cannot change its options. Edit it in the config file.`,
|
|
506
|
+
cause: toError(cause)
|
|
507
|
+
});
|
|
472
508
|
}))(mergeOptions(disk.options ?? {}, studioOptions));
|
|
473
509
|
}
|
|
474
510
|
/**
|
|
@@ -545,7 +581,11 @@ function invalidEdit(edit) {
|
|
|
545
581
|
}
|
|
546
582
|
/** Where a node sits in the source text. */
|
|
547
583
|
function rangeOf(node) {
|
|
548
|
-
if (typeof node.start !== "number" || typeof node.end !== "number") throw new Error(
|
|
584
|
+
if (typeof node.start !== "number" || typeof node.end !== "number") throw new _kubb_core.Diagnostics.Error({
|
|
585
|
+
code: _kubb_core.Diagnostics.code.internalError,
|
|
586
|
+
severity: "error",
|
|
587
|
+
message: "the config file could not be mapped back to its source text"
|
|
588
|
+
});
|
|
549
589
|
return {
|
|
550
590
|
start: node.start,
|
|
551
591
|
end: node.end
|
|
@@ -797,6 +837,44 @@ function disabledBlocks(source, config) {
|
|
|
797
837
|
}];
|
|
798
838
|
});
|
|
799
839
|
}
|
|
840
|
+
/**
|
|
841
|
+
* Reads an object option that holds some code: its literal members as `value`, and the paths of
|
|
842
|
+
* the members that are code as `locked`. `set` can write any path outside `locked`, so Studio
|
|
843
|
+
* edits those fields. `undefined` for a computed key, since it could set any path.
|
|
844
|
+
*/
|
|
845
|
+
function readPartial(node, prefix = []) {
|
|
846
|
+
const value = {};
|
|
847
|
+
const locked = [];
|
|
848
|
+
for (const [index, entry] of node.properties.entries()) {
|
|
849
|
+
if (entry.type === "SpreadElement") continue;
|
|
850
|
+
const key = memberKey(entry);
|
|
851
|
+
if (key === void 0) return void 0;
|
|
852
|
+
if (findMember(node, key)?.index !== index) continue;
|
|
853
|
+
const path = [...prefix, key];
|
|
854
|
+
if (!isSafeKey(key)) {
|
|
855
|
+
locked.push(path);
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
const member = entry.type === "ObjectProperty" && !isShadowed(node, index) ? entry.value : void 0;
|
|
859
|
+
const literal = readLiteral(member);
|
|
860
|
+
if (literal !== void 0) {
|
|
861
|
+
value[key] = literal;
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
if (member?.type !== "ObjectExpression") {
|
|
865
|
+
locked.push(path);
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
const nested = readPartial(member, path);
|
|
869
|
+
if (!nested) return void 0;
|
|
870
|
+
value[key] = nested.value;
|
|
871
|
+
locked.push(...nested.locked);
|
|
872
|
+
}
|
|
873
|
+
return {
|
|
874
|
+
value,
|
|
875
|
+
locked
|
|
876
|
+
};
|
|
877
|
+
}
|
|
800
878
|
/** The top-level options of a plugin call, each flagged with whether Studio may write it. */
|
|
801
879
|
function readOptions(call) {
|
|
802
880
|
const entries = {};
|
|
@@ -805,10 +883,15 @@ function readOptions(call) {
|
|
|
805
883
|
for (const [index, entry] of options.properties.entries()) {
|
|
806
884
|
const key = memberKey(entry);
|
|
807
885
|
if (key === void 0 || !isSafeKey(key)) continue;
|
|
808
|
-
const
|
|
809
|
-
|
|
886
|
+
const member = entry.type === "ObjectProperty" && !isShadowed(options, index) ? entry.value : void 0;
|
|
887
|
+
const value = readLiteral(member);
|
|
888
|
+
const partial = value === void 0 && member?.type === "ObjectExpression" ? readPartial(member) : void 0;
|
|
889
|
+
entries[key] = value !== void 0 ? {
|
|
810
890
|
literal: true,
|
|
811
891
|
value
|
|
892
|
+
} : {
|
|
893
|
+
literal: false,
|
|
894
|
+
...partial
|
|
812
895
|
};
|
|
813
896
|
}
|
|
814
897
|
return entries;
|
|
@@ -1094,6 +1177,159 @@ async function writeConfigEdits({ filePath, edits, extensions }) {
|
|
|
1094
1177
|
return result;
|
|
1095
1178
|
}
|
|
1096
1179
|
//#endregion
|
|
1180
|
+
//#region src/diff.ts
|
|
1181
|
+
const CONTEXT_LINES = 3;
|
|
1182
|
+
const DIFF_TIMEOUT_MS = 1e3;
|
|
1183
|
+
const splitLines = (text) => text === "" ? [] : text.split(/(?<=\n)/);
|
|
1184
|
+
function longestIncreasing(values) {
|
|
1185
|
+
const tails = [];
|
|
1186
|
+
const prev = [];
|
|
1187
|
+
for (const [index, value] of values.entries()) {
|
|
1188
|
+
let low = 0;
|
|
1189
|
+
let high = tails.length;
|
|
1190
|
+
while (low < high) {
|
|
1191
|
+
const mid = low + high >> 1;
|
|
1192
|
+
if (values[tails[mid]] < value) low = mid + 1;
|
|
1193
|
+
else high = mid;
|
|
1194
|
+
}
|
|
1195
|
+
prev[index] = low > 0 ? tails[low - 1] : -1;
|
|
1196
|
+
tails[low] = index;
|
|
1197
|
+
}
|
|
1198
|
+
const result = [];
|
|
1199
|
+
for (let index = tails.at(-1) ?? -1; index !== -1; index = prev[index]) result.push(index);
|
|
1200
|
+
return result.reverse();
|
|
1201
|
+
}
|
|
1202
|
+
function cutPoints(before, after) {
|
|
1203
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1204
|
+
for (const [index, line] of before.entries()) {
|
|
1205
|
+
const count = counts.get(line);
|
|
1206
|
+
if (count) count.before++;
|
|
1207
|
+
else counts.set(line, {
|
|
1208
|
+
before: 1,
|
|
1209
|
+
after: 0,
|
|
1210
|
+
index
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
const afterIndex = /* @__PURE__ */ new Map();
|
|
1214
|
+
for (const [index, line] of after.entries()) {
|
|
1215
|
+
const count = counts.get(line);
|
|
1216
|
+
if (!count) continue;
|
|
1217
|
+
count.after++;
|
|
1218
|
+
afterIndex.set(line, index);
|
|
1219
|
+
}
|
|
1220
|
+
const unique = [...counts].filter(([, count]) => count.before === 1 && count.after === 1).map(([line, count]) => [count.index, afterIndex.get(line)]);
|
|
1221
|
+
const anchors = longestIncreasing(unique.map(([, index]) => index)).map((index) => unique[index]);
|
|
1222
|
+
const cuts = [[0, 0]];
|
|
1223
|
+
let run = {
|
|
1224
|
+
before: 0,
|
|
1225
|
+
after: 0,
|
|
1226
|
+
length: 0
|
|
1227
|
+
};
|
|
1228
|
+
const closeRun = () => {
|
|
1229
|
+
if (run.length > 6) cuts.push([run.before + CONTEXT_LINES, run.after + CONTEXT_LINES]);
|
|
1230
|
+
};
|
|
1231
|
+
let i = 0;
|
|
1232
|
+
let j = 0;
|
|
1233
|
+
for (const [anchorBefore, anchorAfter] of anchors) {
|
|
1234
|
+
const unchanged = anchorBefore - i === anchorAfter - j && before.slice(i, anchorBefore).every((line, offset) => line === after[j + offset]);
|
|
1235
|
+
if (unchanged && run.length && run.before + run.length === i) run.length += anchorBefore - i + 1;
|
|
1236
|
+
else {
|
|
1237
|
+
closeRun();
|
|
1238
|
+
run = unchanged ? {
|
|
1239
|
+
before: i,
|
|
1240
|
+
after: j,
|
|
1241
|
+
length: anchorBefore - i + 1
|
|
1242
|
+
} : {
|
|
1243
|
+
before: anchorBefore,
|
|
1244
|
+
after: anchorAfter,
|
|
1245
|
+
length: 1
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
i = anchorBefore + 1;
|
|
1249
|
+
j = anchorAfter + 1;
|
|
1250
|
+
}
|
|
1251
|
+
closeRun();
|
|
1252
|
+
cuts.push([before.length, after.length]);
|
|
1253
|
+
return cuts;
|
|
1254
|
+
}
|
|
1255
|
+
function replaceHunk(before, after) {
|
|
1256
|
+
let start = 0;
|
|
1257
|
+
while (start < before.length && start < after.length && before[start] === after[start]) start++;
|
|
1258
|
+
let end = 0;
|
|
1259
|
+
while (end < before.length - start && end < after.length - start && before[before.length - 1 - end] === after[after.length - 1 - end]) end++;
|
|
1260
|
+
const removed = before.slice(start, before.length - end);
|
|
1261
|
+
const added = after.slice(start, after.length - end);
|
|
1262
|
+
if (!removed.length && !added.length) return [];
|
|
1263
|
+
const lines = (prefix, rows) => rows.flatMap((row) => row.endsWith("\n") ? [`${prefix}${row.slice(0, -1)}`] : [`${prefix}${row}`, "\"]);
|
|
1264
|
+
return [{
|
|
1265
|
+
oldStart: start + 1,
|
|
1266
|
+
oldLines: removed.length,
|
|
1267
|
+
newStart: start + 1,
|
|
1268
|
+
newLines: added.length,
|
|
1269
|
+
lines: [...lines("-", removed), ...lines("+", added)]
|
|
1270
|
+
}];
|
|
1271
|
+
}
|
|
1272
|
+
function diffHunks(before, after, timeout = DIFF_TIMEOUT_MS) {
|
|
1273
|
+
const beforeLines = splitLines(before);
|
|
1274
|
+
const afterLines = splitLines(after);
|
|
1275
|
+
const cuts = cutPoints(beforeLines, afterLines);
|
|
1276
|
+
const deadline = Date.now() + timeout;
|
|
1277
|
+
const hunks = [];
|
|
1278
|
+
let coarse = false;
|
|
1279
|
+
for (const [index, [beforeStart, afterStart]] of cuts.slice(0, -1).entries()) {
|
|
1280
|
+
const [beforeEnd, afterEnd] = cuts[index + 1];
|
|
1281
|
+
const segmentBefore = beforeLines.slice(beforeStart, beforeEnd);
|
|
1282
|
+
const segmentAfter = afterLines.slice(afterStart, afterEnd);
|
|
1283
|
+
const patch = (0, diff.structuredPatch)("", "", segmentBefore.join(""), segmentAfter.join(""), void 0, void 0, {
|
|
1284
|
+
context: CONTEXT_LINES,
|
|
1285
|
+
timeout: Math.max(0, deadline - Date.now())
|
|
1286
|
+
});
|
|
1287
|
+
if (!patch) coarse = true;
|
|
1288
|
+
for (const hunk of patch?.hunks ?? replaceHunk(segmentBefore, segmentAfter)) hunks.push({
|
|
1289
|
+
...hunk,
|
|
1290
|
+
oldStart: hunk.oldStart + beforeStart,
|
|
1291
|
+
newStart: hunk.newStart + afterStart
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
const lines = hunks.flatMap((hunk) => hunk.lines);
|
|
1295
|
+
return {
|
|
1296
|
+
hunks,
|
|
1297
|
+
stats: {
|
|
1298
|
+
additions: lines.filter((line) => line[0] === "+").length,
|
|
1299
|
+
deletions: lines.filter((line) => line[0] === "-").length,
|
|
1300
|
+
hunks: hunks.length,
|
|
1301
|
+
lines: lines.length,
|
|
1302
|
+
coarse
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
function pageHunks({ hunks, cursor, limit }) {
|
|
1307
|
+
const page = [];
|
|
1308
|
+
let used = 0;
|
|
1309
|
+
let start = 0;
|
|
1310
|
+
for (const [index, hunk] of hunks.entries()) {
|
|
1311
|
+
const offset = Math.max(0, cursor - start);
|
|
1312
|
+
start += hunk.lines.length;
|
|
1313
|
+
const remaining = hunk.lines.length - offset;
|
|
1314
|
+
if (remaining <= 0) continue;
|
|
1315
|
+
if (used > 0 && remaining > limit - used) break;
|
|
1316
|
+
let take = Math.min(remaining, limit - used);
|
|
1317
|
+
if (hunk.lines[offset + take]?.startsWith("\\")) take++;
|
|
1318
|
+
page.push({
|
|
1319
|
+
index,
|
|
1320
|
+
...hunk,
|
|
1321
|
+
lines: hunk.lines.slice(offset, offset + take)
|
|
1322
|
+
});
|
|
1323
|
+
used += take;
|
|
1324
|
+
if (used >= limit) break;
|
|
1325
|
+
}
|
|
1326
|
+
const total = hunks.reduce((sum, hunk) => sum + hunk.lines.length, 0);
|
|
1327
|
+
return {
|
|
1328
|
+
hunks: page,
|
|
1329
|
+
nextCursor: cursor + used < total ? cursor + used : null
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
//#endregion
|
|
1097
1333
|
//#region src/generations.ts
|
|
1098
1334
|
const READ_CONCURRENCY = 50;
|
|
1099
1335
|
const MB = 1048576;
|
|
@@ -1223,8 +1459,8 @@ function createGenerationStore({ storage, maxCount, maxMb, ttlMs, now = Date.now
|
|
|
1223
1459
|
if (path.split("/").includes("..")) return;
|
|
1224
1460
|
const content = await files.storage.readItem((0, node_path.resolve)(files.root, path));
|
|
1225
1461
|
if (content === null) return;
|
|
1226
|
-
if (source ===
|
|
1227
|
-
hashes[path] = hashOf(source ===
|
|
1462
|
+
if (source === require_protocol.FileSource.OUTPUT && !content.trim()) return;
|
|
1463
|
+
hashes[path] = hashOf(source === require_protocol.FileSource.OUTPUT ? `${content.trim()}\n` : content);
|
|
1228
1464
|
bytes += Buffer.byteLength(content);
|
|
1229
1465
|
if (bytes <= maxSetBytes) await storage.writeItem(`${dirOf(jobId)}${source}/${path}`, content);
|
|
1230
1466
|
}
|
|
@@ -1326,7 +1562,7 @@ function createGenerationStore({ storage, maxCount, maxMb, ttlMs, now = Date.now
|
|
|
1326
1562
|
//#region src/generationEvents.ts
|
|
1327
1563
|
const MAX_QUEUED_EVENTS = 1024;
|
|
1328
1564
|
const RESERVED_EVENTS = 64;
|
|
1329
|
-
const isDiscardable = (
|
|
1565
|
+
const isDiscardable = (type) => type === "kubb:files:processing:update" || type === "kubb:info" || type === "kubb:success";
|
|
1330
1566
|
/** Forwards selected Kubb lifecycle events to a native Cap'n Web stream. */
|
|
1331
1567
|
function createGenerationStream(hooks, jobId) {
|
|
1332
1568
|
const unhooks = [];
|
|
@@ -1358,7 +1594,7 @@ function createGenerationStream(hooks, jobId) {
|
|
|
1358
1594
|
version: 1,
|
|
1359
1595
|
timestamp: Date.now()
|
|
1360
1596
|
};
|
|
1361
|
-
if (controller.desiredSize <= RESERVED_EVENTS && isDiscardable(
|
|
1597
|
+
if (controller.desiredSize <= RESERVED_EVENTS && isDiscardable(type)) return;
|
|
1362
1598
|
if (controller.desiredSize <= 0) {
|
|
1363
1599
|
fail(/* @__PURE__ */ new Error(`Generation event stream exceeded ${MAX_QUEUED_EVENTS} queued events`));
|
|
1364
1600
|
return;
|
|
@@ -1462,7 +1698,10 @@ function createGenerationStream(hooks, jobId) {
|
|
|
1462
1698
|
}]
|
|
1463
1699
|
};
|
|
1464
1700
|
function forward(type) {
|
|
1465
|
-
on(type, (...args) =>
|
|
1701
|
+
on(type, (...args) => {
|
|
1702
|
+
emitEvent(type, projections[type](...args));
|
|
1703
|
+
if (!isDiscardable(type)) return new Promise((resolve) => setImmediate(resolve));
|
|
1704
|
+
});
|
|
1466
1705
|
}
|
|
1467
1706
|
on("kubb:build:start", ({ config }) => {
|
|
1468
1707
|
root = config.root;
|
|
@@ -1522,7 +1761,11 @@ function isLoopbackHost(hostname) {
|
|
|
1522
1761
|
*/
|
|
1523
1762
|
function secureUrl({ studioUrl, path, socket = false }) {
|
|
1524
1763
|
const url = new URL(`${studioUrl.replace(/\/+$/, "")}${path}`);
|
|
1525
|
-
if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopbackHost(url.hostname))) throw new Error(
|
|
1764
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopbackHost(url.hostname))) throw new _kubb_core.Diagnostics.Error({
|
|
1765
|
+
code: _kubb_core.Diagnostics.code.unknown,
|
|
1766
|
+
severity: "error",
|
|
1767
|
+
message: `Refusing to reach Kubb Studio at ${url.origin}: use an https:// URL, or a loopback host for local development`
|
|
1768
|
+
});
|
|
1526
1769
|
if (socket) url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
1527
1770
|
return url;
|
|
1528
1771
|
}
|
|
@@ -1592,6 +1835,7 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1592
1835
|
#session;
|
|
1593
1836
|
#limits = resolveGenerationLimits();
|
|
1594
1837
|
#store;
|
|
1838
|
+
#diffs = /* @__PURE__ */ new Map();
|
|
1595
1839
|
constructor({ options, session }) {
|
|
1596
1840
|
super();
|
|
1597
1841
|
this.#options = options;
|
|
@@ -1630,10 +1874,18 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1630
1874
|
message,
|
|
1631
1875
|
permission
|
|
1632
1876
|
});
|
|
1633
|
-
throw new Error(
|
|
1877
|
+
throw new _kubb_core.Diagnostics.Error({
|
|
1878
|
+
code: _kubb_core.Diagnostics.code.unknown,
|
|
1879
|
+
severity: "error",
|
|
1880
|
+
message
|
|
1881
|
+
});
|
|
1634
1882
|
}
|
|
1635
1883
|
#assertConnected() {
|
|
1636
|
-
if (!this.#session.info) throw new Error(
|
|
1884
|
+
if (!this.#session.info) throw new _kubb_core.Diagnostics.Error({
|
|
1885
|
+
code: _kubb_core.Diagnostics.code.internalError,
|
|
1886
|
+
severity: "error",
|
|
1887
|
+
message: "Studio must call connect() before any other command"
|
|
1888
|
+
});
|
|
1637
1889
|
}
|
|
1638
1890
|
#logCommand(command, info) {
|
|
1639
1891
|
return emit(this.#options.hooks, "studio:command", {
|
|
@@ -1730,7 +1982,11 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1730
1982
|
/** Runs one job and keeps its output, and its input with `allowRead`, for later reads. */
|
|
1731
1983
|
async #runGeneration(input, controller) {
|
|
1732
1984
|
this.#assertConnected();
|
|
1733
|
-
if (this.#session.closed) throw new Error(
|
|
1985
|
+
if (this.#session.closed) throw new _kubb_core.Diagnostics.Error({
|
|
1986
|
+
code: _kubb_core.Diagnostics.code.internalError,
|
|
1987
|
+
severity: "error",
|
|
1988
|
+
message: "Connection is closed"
|
|
1989
|
+
});
|
|
1734
1990
|
if (this.#session.active) return this.#refuse({ message: "A generation is already in progress, please wait for it to finish" });
|
|
1735
1991
|
const settled = Promise.withResolvers();
|
|
1736
1992
|
this.#session.active = {
|
|
@@ -1788,7 +2044,7 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1788
2044
|
}) : void 0;
|
|
1789
2045
|
const disk = diskFiles ? await this.#generations.keep({
|
|
1790
2046
|
jobId,
|
|
1791
|
-
source:
|
|
2047
|
+
source: require_protocol.FileSource.DISK,
|
|
1792
2048
|
files: diskFiles,
|
|
1793
2049
|
maxSetMb: this.#limits.maxSnapshotMb
|
|
1794
2050
|
}) : void 0;
|
|
@@ -1811,7 +2067,7 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1811
2067
|
});
|
|
1812
2068
|
const output = await this.#generations.keep({
|
|
1813
2069
|
jobId,
|
|
1814
|
-
source:
|
|
2070
|
+
source: require_protocol.FileSource.OUTPUT,
|
|
1815
2071
|
files,
|
|
1816
2072
|
maxSetMb: this.#limits.maxMb
|
|
1817
2073
|
});
|
|
@@ -1883,14 +2139,14 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1883
2139
|
async readInput(input) {
|
|
1884
2140
|
this.#assertConnected();
|
|
1885
2141
|
if (!this.#permissions.allowReadAst) return this.#refuse({
|
|
1886
|
-
message: require_protocol.
|
|
1887
|
-
permission:
|
|
2142
|
+
message: require_protocol.AgentMessage.READ_AST_NOT_ALLOWED,
|
|
2143
|
+
permission: require_protocol.AgentPermission.READ_AST
|
|
1888
2144
|
});
|
|
1889
2145
|
if (typeof input.jobId !== "string" || !input.jobId) return this.#refuse({ message: "The request must name a job" });
|
|
1890
2146
|
const generation = await this.#generations.get(input.jobId);
|
|
1891
|
-
if (!generation) return this.#refuse({ message: require_protocol.
|
|
2147
|
+
if (!generation) return this.#refuse({ message: require_protocol.AgentMessage.GENERATION_GONE });
|
|
1892
2148
|
const kept = generation.inputBytes ? await this.#generations.readInput(input.jobId) : void 0;
|
|
1893
|
-
if (!kept) return this.#refuse({ message: require_protocol.
|
|
2149
|
+
if (!kept) return this.#refuse({ message: require_protocol.AgentMessage.NO_INPUT });
|
|
1894
2150
|
await this.#logCommand("readInput", "read the API input");
|
|
1895
2151
|
return { input: kept };
|
|
1896
2152
|
}
|
|
@@ -1900,16 +2156,16 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1900
2156
|
async readFiles(input) {
|
|
1901
2157
|
this.#assertConnected();
|
|
1902
2158
|
if (!this.#permissions.allowRead) return this.#refuse({
|
|
1903
|
-
message: require_protocol.
|
|
1904
|
-
permission:
|
|
2159
|
+
message: require_protocol.AgentMessage.READ_NOT_ALLOWED,
|
|
2160
|
+
permission: require_protocol.AgentPermission.READ
|
|
1905
2161
|
});
|
|
1906
2162
|
if (!Array.isArray(input.paths) || typeof input.jobId !== "string" || !input.jobId) return this.#refuse({ message: "The request must name a job and carry paths" });
|
|
1907
2163
|
const { paths } = input;
|
|
1908
2164
|
if (paths.length > 50) return this.#refuse({ message: `At most 50 paths may be requested at once` });
|
|
1909
2165
|
const generation = await this.#generations.get(input.jobId);
|
|
1910
|
-
if (!generation) return this.#refuse({ message: require_protocol.
|
|
2166
|
+
if (!generation) return this.#refuse({ message: require_protocol.AgentMessage.GENERATION_GONE });
|
|
1911
2167
|
if (!isValidPage(input)) return this.#refuse({ message: `The cursor must be a whole line from 0 and come with a limit of 1 to ${require_protocol.MAX_PAGE_LINES} lines` });
|
|
1912
|
-
const source = input.source ===
|
|
2168
|
+
const source = input.source === require_protocol.FileSource.DISK ? require_protocol.FileSource.DISK : require_protocol.FileSource.OUTPUT;
|
|
1913
2169
|
if (!generation[source]) return this.#refuse({ message: "This agent kept no snapshot of the files on disk for that job" });
|
|
1914
2170
|
const result = await this.#generations.read({
|
|
1915
2171
|
generation,
|
|
@@ -1921,6 +2177,72 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1921
2177
|
await this.#logCommand("readFiles", `read ${Object.keys(result.files).length}/${paths.length} requested file${paths.length === 1 ? "" : "s"}`);
|
|
1922
2178
|
return result;
|
|
1923
2179
|
}
|
|
2180
|
+
/**
|
|
2181
|
+
* Returns one page of the line diff of a file between two kept sets. Refused without `allowRead`.
|
|
2182
|
+
*/
|
|
2183
|
+
async readDiff(input) {
|
|
2184
|
+
this.#assertConnected();
|
|
2185
|
+
if (!this.#permissions.allowRead) return this.#refuse({
|
|
2186
|
+
message: require_protocol.AgentMessage.READ_NOT_ALLOWED,
|
|
2187
|
+
permission: require_protocol.AgentPermission.READ
|
|
2188
|
+
});
|
|
2189
|
+
const { jobId, path, base, cursor = 0, limit } = input;
|
|
2190
|
+
if (typeof jobId !== "string" || !jobId || typeof path !== "string" || !path || typeof base?.jobId !== "string" || !base.jobId) return this.#refuse({ message: "The request must name a job, a base, and a path" });
|
|
2191
|
+
if (!isValidPage({
|
|
2192
|
+
cursor,
|
|
2193
|
+
limit
|
|
2194
|
+
})) return this.#refuse({ message: `The cursor must be a whole line from 0 and come with a limit of 1 to ${require_protocol.MAX_PAGE_LINES} lines` });
|
|
2195
|
+
const [generation, baseGeneration] = await Promise.all([this.#generations.get(jobId), this.#generations.get(base.jobId)]);
|
|
2196
|
+
if (!generation || !baseGeneration) return this.#refuse({ message: require_protocol.AgentMessage.GENERATION_GONE });
|
|
2197
|
+
const baseSource = base.source === require_protocol.FileSource.DISK ? require_protocol.FileSource.DISK : require_protocol.FileSource.OUTPUT;
|
|
2198
|
+
if (!baseGeneration[baseSource]) return this.#refuse({ message: "This agent kept no snapshot of the files on disk for that job" });
|
|
2199
|
+
const [after, before] = await Promise.all([this.#generations.read({
|
|
2200
|
+
generation,
|
|
2201
|
+
source: require_protocol.FileSource.OUTPUT,
|
|
2202
|
+
paths: [path]
|
|
2203
|
+
}), this.#generations.read({
|
|
2204
|
+
generation: baseGeneration,
|
|
2205
|
+
source: baseSource,
|
|
2206
|
+
paths: [path]
|
|
2207
|
+
})]);
|
|
2208
|
+
const afterContent = after.files[path];
|
|
2209
|
+
if (afterContent === void 0 && path in generation.output.hashes) return this.#refuse({ message: "This agent kept only the hashes of that job" });
|
|
2210
|
+
const beforeContent = before.files[path];
|
|
2211
|
+
if (beforeContent === void 0) return {
|
|
2212
|
+
hunks: [],
|
|
2213
|
+
nextCursor: null,
|
|
2214
|
+
hasBase: false,
|
|
2215
|
+
stats: {
|
|
2216
|
+
additions: 0,
|
|
2217
|
+
deletions: 0,
|
|
2218
|
+
hunks: 0,
|
|
2219
|
+
lines: 0,
|
|
2220
|
+
coarse: false
|
|
2221
|
+
}
|
|
2222
|
+
};
|
|
2223
|
+
const key = JSON.stringify([
|
|
2224
|
+
jobId,
|
|
2225
|
+
base.jobId,
|
|
2226
|
+
baseSource,
|
|
2227
|
+
path
|
|
2228
|
+
]);
|
|
2229
|
+
let diff = this.#diffs.get(key);
|
|
2230
|
+
if (!diff) {
|
|
2231
|
+
diff = diffHunks(beforeContent, afterContent ?? "");
|
|
2232
|
+
this.#diffs.set(key, diff);
|
|
2233
|
+
if (this.#diffs.size > 4) this.#diffs.delete(this.#diffs.keys().next().value);
|
|
2234
|
+
}
|
|
2235
|
+
await this.#logCommand("readDiff", `read the diff of ${path}`);
|
|
2236
|
+
return {
|
|
2237
|
+
...pageHunks({
|
|
2238
|
+
hunks: diff.hunks,
|
|
2239
|
+
cursor,
|
|
2240
|
+
limit
|
|
2241
|
+
}),
|
|
2242
|
+
hasBase: true,
|
|
2243
|
+
stats: diff.stats
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
1924
2246
|
};
|
|
1925
2247
|
const openWebSocketRpc = ({ url, token, instanceId, agent }) => {
|
|
1926
2248
|
const socket = new WebSocket(url, { headers: {
|
|
@@ -2129,7 +2451,12 @@ async function startPairing({ studioUrl = defaultStudioUrl, type, name, hostname
|
|
|
2129
2451
|
},
|
|
2130
2452
|
signal
|
|
2131
2453
|
});
|
|
2132
|
-
if (typeof pairing?.device_code !== "string") throw new Error(
|
|
2454
|
+
if (typeof pairing?.device_code !== "string") throw new _kubb_core.Diagnostics.Error({
|
|
2455
|
+
code: _kubb_core.Diagnostics.code.unknown,
|
|
2456
|
+
severity: "error",
|
|
2457
|
+
message: "Kubb Studio returned no pairing code",
|
|
2458
|
+
help: "Try again later. If it keeps failing, check the Kubb Studio status."
|
|
2459
|
+
});
|
|
2133
2460
|
return pairing;
|
|
2134
2461
|
} catch (error) {
|
|
2135
2462
|
if (signal?.aborted) throw new PairingCanceledError();
|
|
@@ -2170,7 +2497,12 @@ async function pollForPairingToken({ studioUrl = defaultStudioUrl, session, sign
|
|
|
2170
2497
|
signal,
|
|
2171
2498
|
ignoreResponseError: true
|
|
2172
2499
|
});
|
|
2173
|
-
if (status >= 500 || !_data || typeof _data !== "object") throw new Error(
|
|
2500
|
+
if (status >= 500 || !_data || typeof _data !== "object") throw new _kubb_core.Diagnostics.Error({
|
|
2501
|
+
code: _kubb_core.Diagnostics.code.inputRequestFailed,
|
|
2502
|
+
severity: "error",
|
|
2503
|
+
message: `Kubb Studio answered with status ${status}`,
|
|
2504
|
+
help: "Try again later."
|
|
2505
|
+
});
|
|
2174
2506
|
response = _data;
|
|
2175
2507
|
} catch (error) {
|
|
2176
2508
|
if (signal?.aborted) throw new PairingCanceledError();
|
|
@@ -2178,7 +2510,11 @@ async function pollForPairingToken({ studioUrl = defaultStudioUrl, session, sign
|
|
|
2178
2510
|
continue;
|
|
2179
2511
|
}
|
|
2180
2512
|
if (isPairingResult(response)) return response;
|
|
2181
|
-
if (!("error" in response) || typeof response.error !== "string") throw new Error(
|
|
2513
|
+
if (!("error" in response) || typeof response.error !== "string") throw new _kubb_core.Diagnostics.Error({
|
|
2514
|
+
code: _kubb_core.Diagnostics.code.unknown,
|
|
2515
|
+
severity: "error",
|
|
2516
|
+
message: "Kubb Studio returned an empty pairing response, pair again"
|
|
2517
|
+
});
|
|
2182
2518
|
if (response.error === "authorization_pending") continue;
|
|
2183
2519
|
if (response.error === "slow_down") {
|
|
2184
2520
|
intervalMs += 5e3;
|
|
@@ -2186,7 +2522,12 @@ async function pollForPairingToken({ studioUrl = defaultStudioUrl, session, sign
|
|
|
2186
2522
|
}
|
|
2187
2523
|
if (response.error === "access_denied") throw new PairingDeniedError(response.error_description);
|
|
2188
2524
|
if (response.error === "expired_token" || response.error === "invalid_grant") throw new PairingExpiredError(response.error_description);
|
|
2189
|
-
throw new Error(
|
|
2525
|
+
throw new _kubb_core.Diagnostics.Error({
|
|
2526
|
+
code: _kubb_core.Diagnostics.code.unknown,
|
|
2527
|
+
severity: "error",
|
|
2528
|
+
message: response.error_description ?? `Pairing failed (${response.error})`,
|
|
2529
|
+
help: "Pair again with `kubb studio login`."
|
|
2530
|
+
});
|
|
2190
2531
|
}
|
|
2191
2532
|
throw new PairingExpiredError();
|
|
2192
2533
|
}
|
|
@@ -2236,7 +2577,12 @@ function splitEntryPath(path) {
|
|
|
2236
2577
|
prefix
|
|
2237
2578
|
};
|
|
2238
2579
|
}
|
|
2239
|
-
throw new Error(
|
|
2580
|
+
throw new _kubb_core.Diagnostics.Error({
|
|
2581
|
+
code: _kubb_core.Diagnostics.code.unknown,
|
|
2582
|
+
severity: "error",
|
|
2583
|
+
message: `Snapshot path is too long for a tar entry: ${path}`,
|
|
2584
|
+
help: "Shorten the output path or the generated file names."
|
|
2585
|
+
});
|
|
2240
2586
|
}
|
|
2241
2587
|
function header(path, size) {
|
|
2242
2588
|
const { name, prefix } = splitEntryPath(path);
|
|
@@ -2269,7 +2615,12 @@ async function createSnapshotPackage({ files, packageInfo }) {
|
|
|
2269
2615
|
for (const [name, content] of Object.entries(files)) {
|
|
2270
2616
|
const path = packagePath(name);
|
|
2271
2617
|
const owner = sources.get(path)?.name;
|
|
2272
|
-
if (owner) throw new Error(
|
|
2618
|
+
if (owner) throw new _kubb_core.Diagnostics.Error({
|
|
2619
|
+
code: _kubb_core.Diagnostics.code.unknown,
|
|
2620
|
+
severity: "error",
|
|
2621
|
+
message: `Snapshot has two generated files that sanitize to the same path "package/${path}": "${owner}" and "${name}"`,
|
|
2622
|
+
help: "Rename one of the generated files, for example with a plugin resolver."
|
|
2623
|
+
});
|
|
2273
2624
|
sources.set(path, {
|
|
2274
2625
|
name,
|
|
2275
2626
|
content
|
|
@@ -2359,7 +2710,11 @@ async function createSnapshot({ studioUrl = defaultStudioUrl, apiKey, config, ho
|
|
|
2359
2710
|
});
|
|
2360
2711
|
const files = Object.fromEntries(await Promise.all([...generated.paths].map(async (path) => {
|
|
2361
2712
|
const content = await generated.storage.readItem((0, node_path.resolve)(generated.root, path));
|
|
2362
|
-
if (content === null) throw new Error(
|
|
2713
|
+
if (content === null) throw new _kubb_core.Diagnostics.Error({
|
|
2714
|
+
code: _kubb_core.Diagnostics.code.internalError,
|
|
2715
|
+
severity: "error",
|
|
2716
|
+
message: `The generation listed ${path} but did not keep its content`
|
|
2717
|
+
});
|
|
2363
2718
|
return [path, content];
|
|
2364
2719
|
})));
|
|
2365
2720
|
const pluginNames = config.plugins.map((plugin) => (0, _kubb_core.resolveMeta)(plugin).name);
|
|
@@ -2372,7 +2727,12 @@ async function createSnapshot({ studioUrl = defaultStudioUrl, apiKey, config, ho
|
|
|
2372
2727
|
const names = [...new Set(pluginNames.map(packageOf))];
|
|
2373
2728
|
const peerDependencies = Object.fromEntries(installed.flatMap(({ package: name, version }) => names.includes(name) && version ? [[name, version]] : []));
|
|
2374
2729
|
const missing = names.filter((name) => !(name in peerDependencies) && !BUNDLED_DEPENDENCIES.includes(name));
|
|
2375
|
-
if (missing.length) throw new Error(
|
|
2730
|
+
if (missing.length) throw new _kubb_core.Diagnostics.Error({
|
|
2731
|
+
code: _kubb_core.Diagnostics.code.missingDependency,
|
|
2732
|
+
severity: "error",
|
|
2733
|
+
message: `Missing dependencies: ${missing.join(", ")}`,
|
|
2734
|
+
help: "Install the listed plugin packages in the project."
|
|
2735
|
+
});
|
|
2376
2736
|
const bytes = await createSnapshotPackage({
|
|
2377
2737
|
files,
|
|
2378
2738
|
packageInfo: {
|
|
@@ -2381,7 +2741,12 @@ async function createSnapshot({ studioUrl = defaultStudioUrl, apiKey, config, ho
|
|
|
2381
2741
|
peerDependencies
|
|
2382
2742
|
}
|
|
2383
2743
|
});
|
|
2384
|
-
if (bytes.length > 52428800) throw new Error(
|
|
2744
|
+
if (bytes.length > 52428800) throw new _kubb_core.Diagnostics.Error({
|
|
2745
|
+
code: _kubb_core.Diagnostics.code.unknown,
|
|
2746
|
+
severity: "error",
|
|
2747
|
+
message: `The snapshot is ${Math.ceil(bytes.length / 1024 / 1024)} MB, over the ${require_protocol.MAX_SNAPSHOT_BYTES / 1024 / 1024} MB limit`,
|
|
2748
|
+
help: "Generate fewer files, for example with `include` or `exclude` on the plugins."
|
|
2749
|
+
});
|
|
2385
2750
|
const hashes = Object.fromEntries(Object.entries(files).map(([path, content]) => [path, hashOf(content)]));
|
|
2386
2751
|
const body = new FormData();
|
|
2387
2752
|
body.set("metadata", JSON.stringify({
|
|
@@ -2401,9 +2766,19 @@ async function createSnapshot({ studioUrl = defaultStudioUrl, apiKey, config, ho
|
|
|
2401
2766
|
});
|
|
2402
2767
|
if (!ok) {
|
|
2403
2768
|
const upgrade = _data?.data?.upgradeUrl ? ` Upgrade at ${_data.data.upgradeUrl}.` : "";
|
|
2404
|
-
throw new Error(
|
|
2769
|
+
throw new _kubb_core.Diagnostics.Error({
|
|
2770
|
+
code: _kubb_core.Diagnostics.code.inputRequestFailed,
|
|
2771
|
+
severity: "error",
|
|
2772
|
+
message: `Kubb Studio refused the snapshot (status ${status})${_data?.message ? `: ${_data.message}` : ""}.${upgrade}`,
|
|
2773
|
+
help: "Check the API key and your plan, then try again."
|
|
2774
|
+
});
|
|
2405
2775
|
}
|
|
2406
|
-
if (!_data?.snapshot) throw new Error(
|
|
2776
|
+
if (!_data?.snapshot) throw new _kubb_core.Diagnostics.Error({
|
|
2777
|
+
code: _kubb_core.Diagnostics.code.inputRequestFailed,
|
|
2778
|
+
severity: "error",
|
|
2779
|
+
message: `Kubb Studio returned no snapshot (status ${status})`,
|
|
2780
|
+
help: "Try again later."
|
|
2781
|
+
});
|
|
2407
2782
|
return _data.snapshot;
|
|
2408
2783
|
}
|
|
2409
2784
|
//#endregion
|