@kubb/studio 5.9.0 → 5.9.2
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 +203 -207
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -9
- package/dist/index.js +203 -207
- package/dist/index.js.map +1 -1
- package/dist/{protocol-BfjNY8-8.d.ts → protocol-gXoAXvT3.d.ts} +41 -10
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.ts +2 -2
- package/dist/protocol.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -95,74 +95,34 @@ function getErrorMessage(value) {
|
|
|
95
95
|
}
|
|
96
96
|
//#endregion
|
|
97
97
|
//#region ../../internals/utils/src/runtime.ts
|
|
98
|
+
const isBun = typeof Bun !== "undefined";
|
|
99
|
+
const isDeno = "Deno" in globalThis;
|
|
98
100
|
/**
|
|
99
|
-
*
|
|
101
|
+
* The JavaScript runtime executing the current process: its name, version and whether it is Bun.
|
|
100
102
|
*
|
|
101
|
-
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* runtime.name // 'bun' when run with `bun kubb`, 'node' otherwise
|
|
106
|
+
* ```
|
|
102
107
|
*/
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
*
|
|
107
|
-
* Detection keys off the global `Bun` object rather than `process.versions`,
|
|
108
|
-
* because Bun polyfills `process.versions.node` for Node compatibility and would
|
|
109
|
-
* otherwise look like Node.
|
|
110
|
-
*
|
|
111
|
-
* @example
|
|
112
|
-
* ```ts
|
|
113
|
-
* if (runtime.isBun) {
|
|
114
|
-
* await Bun.write(path, data)
|
|
115
|
-
* }
|
|
116
|
-
* ```
|
|
117
|
-
*/
|
|
118
|
-
get isBun() {
|
|
119
|
-
return typeof Bun !== "undefined";
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* `true` when the current process is running under Deno.
|
|
123
|
-
*/
|
|
124
|
-
get isDeno() {
|
|
125
|
-
return typeof globalThis.Deno !== "undefined";
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* `true` when the current process is running under Node.
|
|
129
|
-
*
|
|
130
|
-
* Bun and Deno are excluded first so a polyfilled `process` does not register as Node.
|
|
131
|
-
*/
|
|
132
|
-
get isNode() {
|
|
133
|
-
return !this.isBun && !this.isDeno && typeof process !== "undefined" && process.versions?.node != null;
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Name of the runtime executing the current process.
|
|
137
|
-
*
|
|
138
|
-
* @example
|
|
139
|
-
* ```ts
|
|
140
|
-
* runtime.name // 'bun' when run with `bun kubb`, 'node' otherwise
|
|
141
|
-
* ```
|
|
142
|
-
*/
|
|
143
|
-
get name() {
|
|
144
|
-
if (this.isBun) return "bun";
|
|
145
|
-
if (this.isDeno) return "deno";
|
|
146
|
-
return "node";
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Version of the active runtime, or an empty string when it cannot be read.
|
|
150
|
-
*
|
|
151
|
-
* @example
|
|
152
|
-
* ```ts
|
|
153
|
-
* runtime.version // '1.3.11' under Bun, '22.22.2' under Node
|
|
154
|
-
* ```
|
|
155
|
-
*/
|
|
156
|
-
get version() {
|
|
157
|
-
if (this.isBun) return process.versions.bun ?? "";
|
|
158
|
-
if (this.isDeno) return globalThis.Deno?.version?.deno ?? "";
|
|
159
|
-
return process.versions?.node ?? "";
|
|
160
|
-
}
|
|
108
|
+
const runtime = {
|
|
109
|
+
isBun,
|
|
110
|
+
...detect()
|
|
161
111
|
};
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
112
|
+
function detect() {
|
|
113
|
+
if (isBun) return {
|
|
114
|
+
name: "bun",
|
|
115
|
+
version: process.versions.bun ?? ""
|
|
116
|
+
};
|
|
117
|
+
if (isDeno) return {
|
|
118
|
+
name: "deno",
|
|
119
|
+
version: globalThis.Deno?.version?.deno ?? ""
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
name: "node",
|
|
123
|
+
version: process.versions?.node ?? ""
|
|
124
|
+
};
|
|
125
|
+
}
|
|
166
126
|
//#endregion
|
|
167
127
|
//#region ../../internals/utils/src/fs.ts
|
|
168
128
|
/**
|
|
@@ -216,7 +176,7 @@ function getElapsedMs(hrStart) {
|
|
|
216
176
|
}
|
|
217
177
|
//#endregion
|
|
218
178
|
//#region package.json
|
|
219
|
-
var version = "5.9.
|
|
179
|
+
var version = "5.9.2";
|
|
220
180
|
//#endregion
|
|
221
181
|
//#region src/resolveConfig.ts
|
|
222
182
|
/**
|
|
@@ -261,47 +221,19 @@ function toPluginName(packageName) {
|
|
|
261
221
|
function toPackageName(name) {
|
|
262
222
|
return name.startsWith("plugin-") ? `@kubb/${name}` : name;
|
|
263
223
|
}
|
|
264
|
-
function resolvesFrom(base, packageName) {
|
|
265
|
-
try {
|
|
266
|
-
(0, node_module.createRequire)(base).resolve(`${packageName}/package.json`);
|
|
267
|
-
return true;
|
|
268
|
-
} catch {
|
|
269
|
-
return false;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
224
|
/**
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*/
|
|
277
|
-
function isInstalled({ packageName, root }) {
|
|
278
|
-
const project = (0, node_url.pathToFileURL)(`${root}/`);
|
|
279
|
-
if (resolvesFrom(project, packageName) || resolvesFrom(require("url").pathToFileURL(__filename).href, packageName)) return true;
|
|
280
|
-
try {
|
|
281
|
-
return resolvesFrom((0, node_module.createRequire)(project).resolve("kubb/package.json"), packageName);
|
|
282
|
-
} catch {
|
|
283
|
-
return false;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* The name Studio knows a plugin by: its `@kubb/plugin-*` package when one is installed, otherwise
|
|
288
|
-
* the plugin's own `name`, so a plugin defined in the user's project is never reported as a Kubb
|
|
289
|
-
* package that does not exist. An installed extension whose manifest `name` matches wins.
|
|
225
|
+
* The name Studio knows an extension by: the package of the installed extension whose manifest
|
|
226
|
+
* `name` matches, otherwise its own `name`, so a plugin defined in the user's project is never
|
|
227
|
+
* reported as a Kubb package that does not exist.
|
|
290
228
|
*
|
|
291
229
|
* @example
|
|
292
230
|
* ```ts
|
|
293
|
-
* toStudioName({ name: 'plugin-ts',
|
|
294
|
-
* toStudioName({ name: 'plugin-zod-operations',
|
|
231
|
+
* toStudioName({ name: 'plugin-ts', extensions }) // '@kubb/plugin-ts'
|
|
232
|
+
* toStudioName({ name: 'plugin-zod-operations', extensions }) // 'plugin-zod-operations', a local plugin
|
|
295
233
|
* ```
|
|
296
234
|
*/
|
|
297
|
-
function toStudioName({ name,
|
|
298
|
-
|
|
299
|
-
if (installed) return installed.package;
|
|
300
|
-
const packageName = toPackageName(name);
|
|
301
|
-
return packageName !== name && isInstalled({
|
|
302
|
-
packageName,
|
|
303
|
-
root
|
|
304
|
-
}) ? packageName : name;
|
|
235
|
+
function toStudioName({ name, extensions = /* @__PURE__ */ new Map(), kind = "plugin" }) {
|
|
236
|
+
return [...extensions.values()].find(({ manifest }) => manifest.kind === kind && manifest.name === name)?.package ?? name;
|
|
305
237
|
}
|
|
306
238
|
/**
|
|
307
239
|
* Derives the conventional named export for a `@kubb/*` plugin package from its package name.
|
|
@@ -380,33 +312,6 @@ async function resolvePlugins({ plugins, extensions = /* @__PURE__ */ new Map()
|
|
|
380
312
|
}))(options ?? {});
|
|
381
313
|
}));
|
|
382
314
|
}
|
|
383
|
-
/** The installed version of each plugin's package, plus the names that could not be resolved. */
|
|
384
|
-
async function resolvePeerDependencies(names) {
|
|
385
|
-
const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
386
|
-
const uniqueNames = [...new Set(names.map(toPackageName))];
|
|
387
|
-
const peerDependencies = {};
|
|
388
|
-
const missingDependencies = [];
|
|
389
|
-
const versions = await Promise.all(uniqueNames.map(async (name) => {
|
|
390
|
-
try {
|
|
391
|
-
const path = require$1.resolve(`${name}/package.json`);
|
|
392
|
-
return JSON.parse(await (0, node_fs_promises.readFile)(path, "utf8")).version;
|
|
393
|
-
} catch {
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
}));
|
|
397
|
-
for (const [index, name] of uniqueNames.entries()) {
|
|
398
|
-
const version = versions[index];
|
|
399
|
-
if (version) {
|
|
400
|
-
peerDependencies[name] = version;
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
missingDependencies.push(name);
|
|
404
|
-
}
|
|
405
|
-
return {
|
|
406
|
-
peerDependencies,
|
|
407
|
-
missingDependencies
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
315
|
/**
|
|
411
316
|
* Keys that reach an object's prototype when assigned. Never read from or written into options.
|
|
412
317
|
*/
|
|
@@ -469,6 +374,7 @@ function isOptionValue(value) {
|
|
|
469
374
|
* ```ts
|
|
470
375
|
* mergeOptions({ macros: [camelCaseMacro], syntaxType: 'type' }, { macros: [{ name: 'camelCase' }], syntaxType: 'interface' })
|
|
471
376
|
* // { macros: [camelCaseMacro], syntaxType: 'interface' }
|
|
377
|
+
* mergeOptions({ emptySchemaType: 'any' }, { emptySchemaType: null }) // {}
|
|
472
378
|
* ```
|
|
473
379
|
*/
|
|
474
380
|
function mergeOptions(disk, studio) {
|
|
@@ -476,6 +382,10 @@ function mergeOptions(disk, studio) {
|
|
|
476
382
|
for (const [key, value] of Object.entries(studio)) {
|
|
477
383
|
if (!isSafeKey(key)) continue;
|
|
478
384
|
const current = disk[key];
|
|
385
|
+
if (value === null) {
|
|
386
|
+
if (current === void 0 || isOptionValue(current)) delete merged[key];
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
479
389
|
if (isPlainObject(current) && isPlainObject(value)) {
|
|
480
390
|
merged[key] = mergeOptions(current, value);
|
|
481
391
|
continue;
|
|
@@ -533,20 +443,54 @@ async function mergePlugins({ diskPlugins, studioPlugins, extensions = /* @__PUR
|
|
|
533
443
|
})];
|
|
534
444
|
}
|
|
535
445
|
/**
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
446
|
+
* Re-invokes the factory behind a disk adapter or parser with Studio's options merged over its own.
|
|
447
|
+
* An instance carries live functions (`parse`, `print`, ...) that cannot survive JSON, so Studio's
|
|
448
|
+
* options are a patch, not a replacement, and the factory is found from the disk instance itself: the
|
|
449
|
+
* installed extension whose manifest names it, or the `@kubb/<kind>-<name>` package for a built-in
|
|
450
|
+
* one. Throws when no factory resolves, as `mergePlugins` does, so an override Studio sent is never
|
|
451
|
+
* dropped without a word. An extension without a manifest, such as `parserTsx`, has no options in
|
|
452
|
+
* Studio, so Studio never sends one for it.
|
|
453
|
+
*/
|
|
454
|
+
async function rebuildExtension({ disk, studioOptions, kind, extensions }) {
|
|
455
|
+
if (!studioOptions || !Object.keys(studioOptions).length) return disk;
|
|
456
|
+
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) => {
|
|
460
|
+
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));
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Merges Studio-provided adapter option overrides into the disk config's adapter, see {@link rebuildExtension}.
|
|
543
467
|
*/
|
|
544
|
-
async function mergeAdapter(diskAdapter, studioOptions) {
|
|
545
|
-
if (!
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
468
|
+
async function mergeAdapter(diskAdapter, studioOptions, extensions = /* @__PURE__ */ new Map()) {
|
|
469
|
+
if (!diskAdapter) return diskAdapter;
|
|
470
|
+
return rebuildExtension({
|
|
471
|
+
disk: diskAdapter,
|
|
472
|
+
studioOptions,
|
|
473
|
+
kind: "adapter",
|
|
474
|
+
extensions
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Merges Studio-provided parser option overrides into the disk config's parsers, matched by the name
|
|
479
|
+
* Studio knows each parser by, see {@link rebuildExtension}. Parsers Studio does not name are kept as is.
|
|
480
|
+
*/
|
|
481
|
+
async function mergeParsers({ diskParsers, studioParsers, extensions = /* @__PURE__ */ new Map() }) {
|
|
482
|
+
if (!studioParsers?.length) return diskParsers;
|
|
483
|
+
const studioOptions = new Map(studioParsers.map((entry) => [entry.name, entry.options]));
|
|
484
|
+
return Promise.all(diskParsers.map((parser) => rebuildExtension({
|
|
485
|
+
disk: parser,
|
|
486
|
+
studioOptions: studioOptions.get(toStudioName({
|
|
487
|
+
name: (0, _kubb_core.resolveMeta)(parser).name,
|
|
488
|
+
extensions,
|
|
489
|
+
kind: "parser"
|
|
490
|
+
})),
|
|
491
|
+
kind: "parser",
|
|
492
|
+
extensions
|
|
493
|
+
})));
|
|
550
494
|
}
|
|
551
495
|
//#endregion
|
|
552
496
|
//#region src/configFile.ts
|
|
@@ -567,14 +511,21 @@ const OPERATIONS = /* @__PURE__ */ new Set([
|
|
|
567
511
|
"disable-plugin",
|
|
568
512
|
"enable-plugin"
|
|
569
513
|
]);
|
|
514
|
+
const KINDS = /* @__PURE__ */ new Set([
|
|
515
|
+
"plugin",
|
|
516
|
+
"adapter",
|
|
517
|
+
"parser"
|
|
518
|
+
]);
|
|
570
519
|
/**
|
|
571
520
|
* Why an edit from Studio cannot be applied as sent, checked before anything reads its fields.
|
|
572
521
|
* The edit crossed the wire, so its declared type is a promise the sender may not have kept.
|
|
573
522
|
*/
|
|
574
523
|
function invalidEdit(edit) {
|
|
575
|
-
const { operation, plugin, config, path,
|
|
524
|
+
const { operation, plugin, config, path, kind } = typeof edit === "object" && edit !== null ? edit : {};
|
|
576
525
|
if (typeof operation !== "string" || !OPERATIONS.has(operation)) return `unknown operation ${JSON.stringify(operation)}`;
|
|
577
526
|
if (typeof plugin !== "string" || !plugin) return "the edit names no plugin";
|
|
527
|
+
if (kind !== void 0 && (typeof kind !== "string" || !KINDS.has(kind))) return `unknown kind ${JSON.stringify(kind)}`;
|
|
528
|
+
if (kind !== void 0 && operation !== "set" && operation !== "remove") return `${operation} edits the plugins array, so it takes no kind`;
|
|
578
529
|
if (config !== void 0 && typeof config !== "string" && !Number.isSafeInteger(config)) return "config must name an entry or give its index";
|
|
579
530
|
if (operation === "set" || operation === "remove") {
|
|
580
531
|
if (!Array.isArray(path) || !path.every((key) => typeof key === "string" && key)) return "path must be a list of option names";
|
|
@@ -582,7 +533,6 @@ function invalidEdit(edit) {
|
|
|
582
533
|
const unsafe = path.find((key) => !isSafeKey(key));
|
|
583
534
|
if (unsafe !== void 0) return `${unsafe} cannot be used as an option name`;
|
|
584
535
|
}
|
|
585
|
-
if (operation === "add-plugin" && importName !== void 0 && typeof importName !== "string") return "importName must be a string";
|
|
586
536
|
}
|
|
587
537
|
/** Where a node sits in the source text. */
|
|
588
538
|
function rangeOf(node) {
|
|
@@ -777,29 +727,42 @@ function importedFrom(mod) {
|
|
|
777
727
|
const imports = /* @__PURE__ */ new Map();
|
|
778
728
|
for (const statement of bodyOf(mod)) {
|
|
779
729
|
if (statement.type === "ImportDeclaration") {
|
|
780
|
-
for (const specifier of statement.specifiers)
|
|
730
|
+
for (const specifier of statement.specifiers) {
|
|
731
|
+
const imported = specifier.type === "ImportSpecifier" ? specifier.imported : specifier.local;
|
|
732
|
+
imports.set(specifier.local.name, {
|
|
733
|
+
from: statement.source.value,
|
|
734
|
+
exported: imported.type === "Identifier" ? imported.name : imported.value
|
|
735
|
+
});
|
|
736
|
+
}
|
|
781
737
|
continue;
|
|
782
738
|
}
|
|
783
739
|
if (statement.type !== "VariableDeclaration") continue;
|
|
784
740
|
for (const declarator of statement.declarations) {
|
|
785
741
|
const from = requiredModule(declarator.init);
|
|
786
742
|
if (from === void 0 || declarator.id.type !== "ObjectPattern") continue;
|
|
787
|
-
for (const entry of declarator.id.properties) if (entry.type === "ObjectProperty" && entry.value.type === "Identifier") imports.set(entry.value.name,
|
|
743
|
+
for (const entry of declarator.id.properties) if (entry.type === "ObjectProperty" && entry.value.type === "Identifier") imports.set(entry.value.name, {
|
|
744
|
+
from,
|
|
745
|
+
exported: memberKey(entry) ?? entry.value.name
|
|
746
|
+
});
|
|
788
747
|
}
|
|
789
748
|
}
|
|
790
749
|
return imports;
|
|
791
750
|
}
|
|
792
|
-
/**
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
751
|
+
/**
|
|
752
|
+
* Every factory call of one kind that resolves to an import: the `pluginX(...)` elements of the
|
|
753
|
+
* `plugins` array, the `adapter` value, or the elements of the `parsers` array.
|
|
754
|
+
*/
|
|
755
|
+
function factoryCalls(mod, config, kind = "plugin") {
|
|
756
|
+
const node = property(config, kind === "adapter" ? "adapter" : `${kind}s`);
|
|
757
|
+
const elements = kind === "adapter" ? [node] : node?.type === "ArrayExpression" ? node.elements : [];
|
|
796
758
|
const imports = importedFrom(mod);
|
|
797
|
-
return
|
|
759
|
+
return elements.flatMap((element) => {
|
|
798
760
|
if (element?.type !== "CallExpression" || element.callee.type !== "Identifier") return [];
|
|
799
|
-
const
|
|
800
|
-
return
|
|
761
|
+
const imported = imports.get(element.callee.name);
|
|
762
|
+
return imported ? [{
|
|
801
763
|
importName: element.callee.name,
|
|
802
|
-
|
|
764
|
+
exportName: imported.exported,
|
|
765
|
+
packageName: imported.from,
|
|
803
766
|
call: element
|
|
804
767
|
}] : [];
|
|
805
768
|
});
|
|
@@ -868,11 +831,11 @@ function readConfig(source) {
|
|
|
868
831
|
reason: parsed.reason
|
|
869
832
|
};
|
|
870
833
|
const { mod, configs } = parsed;
|
|
871
|
-
const importNames = new Map([...importedFrom(mod)].map(([local, from]) => [from, local]));
|
|
834
|
+
const importNames = new Map([...importedFrom(mod)].map(([local, { from }]) => [from, local]));
|
|
872
835
|
return {
|
|
873
836
|
managed: true,
|
|
874
837
|
configs: configs.map((config) => {
|
|
875
|
-
const active =
|
|
838
|
+
const active = factoryCalls(mod, config).map(({ importName, packageName, call }) => ({
|
|
876
839
|
line: call.loc?.start.line ?? 0,
|
|
877
840
|
plugin: {
|
|
878
841
|
importName,
|
|
@@ -890,9 +853,18 @@ function readConfig(source) {
|
|
|
890
853
|
}
|
|
891
854
|
}));
|
|
892
855
|
const plugins = [...active, ...disabled].sort((a, b) => a.line - b.line).map(({ plugin }) => plugin);
|
|
856
|
+
const toView = ({ importName, packageName, call }) => ({
|
|
857
|
+
importName,
|
|
858
|
+
packageName,
|
|
859
|
+
options: readOptions(call)
|
|
860
|
+
});
|
|
861
|
+
const [adapter] = factoryCalls(mod, config, "adapter").map(toView);
|
|
862
|
+
const parsers = factoryCalls(mod, config, "parser").map(toView);
|
|
893
863
|
return {
|
|
894
864
|
name: configName(config),
|
|
895
|
-
plugins
|
|
865
|
+
plugins,
|
|
866
|
+
...adapter && { adapter },
|
|
867
|
+
...parsers.length ? { parsers } : {}
|
|
896
868
|
};
|
|
897
869
|
})
|
|
898
870
|
};
|
|
@@ -900,7 +872,7 @@ function readConfig(source) {
|
|
|
900
872
|
/** The modules the config file imports, in source order. Empty when the file cannot be parsed. */
|
|
901
873
|
function readImports(source) {
|
|
902
874
|
try {
|
|
903
|
-
return [...new Set(importedFrom((0, magicast.parseModule)(source)).values())];
|
|
875
|
+
return [...new Set([...importedFrom((0, magicast.parseModule)(source)).values()].map(({ from }) => from))];
|
|
904
876
|
} catch {
|
|
905
877
|
return [];
|
|
906
878
|
}
|
|
@@ -937,10 +909,10 @@ function applySet(source, call, path, value, quote) {
|
|
|
937
909
|
const options = call.arguments[0];
|
|
938
910
|
if (!options) {
|
|
939
911
|
const closing = rangeOf(call).end - 1;
|
|
940
|
-
if (source[closing] !== ")") return { reason: "the
|
|
912
|
+
if (source[closing] !== ")") return { reason: "the factory call could not be located in the file" };
|
|
941
913
|
return { source: splice(source, closing, closing, printValue(nest(path, value), quote)) };
|
|
942
914
|
}
|
|
943
|
-
if (options.type !== "ObjectExpression") return { reason: "the
|
|
915
|
+
if (options.type !== "ObjectExpression") return { reason: "the factory was not called with an object literal" };
|
|
944
916
|
const target = walk(options, path);
|
|
945
917
|
if ("reason" in target) return target;
|
|
946
918
|
if ("missing" in target) {
|
|
@@ -952,7 +924,7 @@ function applySet(source, call, path, value, quote) {
|
|
|
952
924
|
/** Deletes the option at `path`, falling the plugin back to its default for it. */
|
|
953
925
|
function applyRemove(source, call, path) {
|
|
954
926
|
const options = call.arguments[0];
|
|
955
|
-
if (options?.type !== "ObjectExpression") return { reason: "the
|
|
927
|
+
if (options?.type !== "ObjectExpression") return { reason: "the factory call has no options to remove" };
|
|
956
928
|
const target = walk(options, path);
|
|
957
929
|
if ("reason" in target) return target;
|
|
958
930
|
if ("missing" in target) return { reason: `${path.join(".")} is not set` };
|
|
@@ -983,10 +955,10 @@ function applyAddPlugin({ source, parsed, config, edit, quote, extensions }) {
|
|
|
983
955
|
packageName: edit.plugin,
|
|
984
956
|
extensions
|
|
985
957
|
})) return { reason: `"${edit.plugin}" is not a @kubb/plugin-* package or an installed Kubb plugin` };
|
|
986
|
-
const importName = extensions.get(edit.plugin)?.manifest.export ??
|
|
958
|
+
const importName = extensions.get(edit.plugin)?.manifest.export ?? toExportName(edit.plugin);
|
|
987
959
|
if (!IDENTIFIER.test(importName)) return { reason: `"${importName}" is not a valid import name` };
|
|
988
|
-
if (
|
|
989
|
-
const taken = importedFrom(parsed.mod).get(importName);
|
|
960
|
+
if (factoryCalls(parsed.mod, config).some((plugin) => plugin.packageName === edit.plugin)) return { source };
|
|
961
|
+
const taken = importedFrom(parsed.mod).get(importName)?.from;
|
|
990
962
|
if (taken && taken !== edit.plugin) return { reason: `${importName} is already imported from ${taken}` };
|
|
991
963
|
const options = edit.options ?? {};
|
|
992
964
|
if (!isOptionValue(options)) return { reason: "the options are not literals that can be written to a config file" };
|
|
@@ -997,7 +969,7 @@ function applyAddPlugin({ source, parsed, config, edit, quote, extensions }) {
|
|
|
997
969
|
}
|
|
998
970
|
/** Comments out a plugin call in place, keeping its options on disk so `enable-plugin` can restore them exactly. */
|
|
999
971
|
function disablePlugin(source, mod, config, plugin) {
|
|
1000
|
-
const loc =
|
|
972
|
+
const loc = factoryCalls(mod, config).find((entry) => entry.packageName === plugin)?.call.loc;
|
|
1001
973
|
if (!loc) return { reason: `${plugin} is not in the plugins array` };
|
|
1002
974
|
const lines = source.split("\n");
|
|
1003
975
|
const from = loc.start.line - 1;
|
|
@@ -1034,8 +1006,14 @@ function applyEdit({ source, edit, quote, extensions }) {
|
|
|
1034
1006
|
quote,
|
|
1035
1007
|
extensions
|
|
1036
1008
|
});
|
|
1037
|
-
const
|
|
1038
|
-
|
|
1009
|
+
const kind = edit.kind ?? "plugin";
|
|
1010
|
+
const calls = factoryCalls(parsed.mod, config, kind).filter((entry) => entry.packageName === edit.plugin);
|
|
1011
|
+
const exportName = extensions.get(edit.plugin)?.manifest.export ?? toExportName(edit.plugin);
|
|
1012
|
+
const call = (calls.find((entry) => entry.exportName === exportName) ?? calls[0])?.call;
|
|
1013
|
+
if (!call) {
|
|
1014
|
+
if (kind === "adapter") return { reason: `${edit.plugin} is not the config's adapter` };
|
|
1015
|
+
return { reason: kind === "plugin" && disabledBlocks(source, config).some((block) => block.packageName === edit.plugin) ? `${edit.plugin} is disabled, enable it first` : `${edit.plugin} is not in the ${kind}s array` };
|
|
1016
|
+
}
|
|
1039
1017
|
return edit.operation === "set" ? applySet(source, call, edit.path, edit.value, quote) : applyRemove(source, call, edit.path);
|
|
1040
1018
|
}
|
|
1041
1019
|
/**
|
|
@@ -1107,32 +1085,6 @@ async function writeConfigEdits({ filePath, edits, extensions }) {
|
|
|
1107
1085
|
return result;
|
|
1108
1086
|
}
|
|
1109
1087
|
//#endregion
|
|
1110
|
-
//#region src/extensions.ts
|
|
1111
|
-
/** Extensions `kubb` ships with, so they count as installed without being a direct dependency. */
|
|
1112
|
-
const BUILT_IN_PACKAGES = [
|
|
1113
|
-
"@kubb/adapter-oas",
|
|
1114
|
-
"@kubb/parser-ts",
|
|
1115
|
-
"@kubb/parser-md",
|
|
1116
|
-
"@kubb/plugin-barrel"
|
|
1117
|
-
];
|
|
1118
|
-
/**
|
|
1119
|
-
* Finds the extensions installed in the project at `root`: its `dependencies` and `devDependencies`,
|
|
1120
|
-
* the `packages` its config file imports, and the extensions `kubb` ships with. Keeps each package
|
|
1121
|
-
* that resolves and ships a valid `kubb-extension.json`. Never throws.
|
|
1122
|
-
*/
|
|
1123
|
-
async function discoverExtensions({ root, packages = [] }) {
|
|
1124
|
-
const installed = await (0, _kubb_core.readInstalledPackages)({
|
|
1125
|
-
root,
|
|
1126
|
-
packages: [...packages, ...BUILT_IN_PACKAGES],
|
|
1127
|
-
resolveFrom: ["kubb"]
|
|
1128
|
-
});
|
|
1129
|
-
return new Map(installed.flatMap(({ package: name, version, manifest }) => version !== void 0 && manifest ? [[name, {
|
|
1130
|
-
package: name,
|
|
1131
|
-
version,
|
|
1132
|
-
manifest
|
|
1133
|
-
}]] : []));
|
|
1134
|
-
}
|
|
1135
|
-
//#endregion
|
|
1136
1088
|
//#region src/generations.ts
|
|
1137
1089
|
const READ_CONCURRENCY = 50;
|
|
1138
1090
|
const MB = 1048576;
|
|
@@ -1540,6 +1492,13 @@ const OPEN_TIMEOUT_MS = 5e3;
|
|
|
1540
1492
|
const MAX_RETRY_DELAY_MS = 3e4;
|
|
1541
1493
|
const DISK_SNAPSHOT_MAX_FILES = 1e4;
|
|
1542
1494
|
const SANDBOX_GENERATION_TTL_MS = 9e5;
|
|
1495
|
+
/** Extensions `kubb` ships with, so they count as installed without being a direct dependency. */
|
|
1496
|
+
const BUILT_IN_PACKAGES = [
|
|
1497
|
+
"@kubb/adapter-oas",
|
|
1498
|
+
"@kubb/parser-ts",
|
|
1499
|
+
"@kubb/parser-md",
|
|
1500
|
+
"@kubb/plugin-barrel"
|
|
1501
|
+
];
|
|
1543
1502
|
/**
|
|
1544
1503
|
* Studio closed with {@link AgentCloseCode.UNAUTHORIZED}. Hosts forget the token and pair again.
|
|
1545
1504
|
*/
|
|
@@ -1638,10 +1597,10 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1638
1597
|
});
|
|
1639
1598
|
return value;
|
|
1640
1599
|
}
|
|
1641
|
-
async #readConfigFileView(
|
|
1600
|
+
async #readConfigFileView() {
|
|
1642
1601
|
if (!this.#permissions.allowConfigEdit) return void 0;
|
|
1643
1602
|
try {
|
|
1644
|
-
return readConfig(
|
|
1603
|
+
return readConfig(await read(this.#options.configFile));
|
|
1645
1604
|
} catch (error) {
|
|
1646
1605
|
await this.#warn({ message: `Could not read ${this.#options.configFile}: ${getErrorMessage(error)}` });
|
|
1647
1606
|
return;
|
|
@@ -1649,10 +1608,16 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1649
1608
|
}
|
|
1650
1609
|
async #installedExtensions() {
|
|
1651
1610
|
const source = await read(this.#options.configFile).catch(() => "");
|
|
1652
|
-
|
|
1611
|
+
const installed = await (0, _kubb_core.readInstalledPackages)({
|
|
1653
1612
|
root: this.#options.root,
|
|
1654
|
-
packages: readImports(source)
|
|
1613
|
+
packages: [...readImports(source), ...BUILT_IN_PACKAGES],
|
|
1614
|
+
resolveFrom: ["kubb"]
|
|
1655
1615
|
});
|
|
1616
|
+
return new Map(installed.flatMap(({ package: name, version, manifest }) => version !== void 0 && manifest ? [[name, {
|
|
1617
|
+
package: name,
|
|
1618
|
+
version,
|
|
1619
|
+
manifest
|
|
1620
|
+
}]] : []));
|
|
1656
1621
|
}
|
|
1657
1622
|
async connect(info) {
|
|
1658
1623
|
this.#session.info = info;
|
|
@@ -1675,11 +1640,26 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1675
1640
|
plugins: config.plugins.map((plugin) => ({
|
|
1676
1641
|
name: toStudioName({
|
|
1677
1642
|
name: (0, _kubb_core.resolveMeta)(plugin).name,
|
|
1678
|
-
root,
|
|
1679
1643
|
extensions
|
|
1680
1644
|
}),
|
|
1681
1645
|
options: toJSONOptions(plugin.options)
|
|
1682
1646
|
})),
|
|
1647
|
+
adapter: config.adapter && {
|
|
1648
|
+
name: toStudioName({
|
|
1649
|
+
name: (0, _kubb_core.resolveMeta)(config.adapter).name,
|
|
1650
|
+
extensions,
|
|
1651
|
+
kind: "adapter"
|
|
1652
|
+
}),
|
|
1653
|
+
options: toJSONOptions(config.adapter.options)
|
|
1654
|
+
},
|
|
1655
|
+
parsers: config.parsers.map((parser) => ({
|
|
1656
|
+
name: toStudioName({
|
|
1657
|
+
name: (0, _kubb_core.resolveMeta)(parser).name,
|
|
1658
|
+
extensions,
|
|
1659
|
+
kind: "parser"
|
|
1660
|
+
}),
|
|
1661
|
+
options: toJSONOptions(parser.options)
|
|
1662
|
+
})),
|
|
1683
1663
|
extensions: [...extensions.values()]
|
|
1684
1664
|
},
|
|
1685
1665
|
permissions: this.#permissions
|
|
@@ -1739,11 +1719,19 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1739
1719
|
const studioInput = this.#sandbox ? patch?.input : void 0;
|
|
1740
1720
|
if (this.#sandbox && (typeof studioInput !== "string" || !studioInput.trim() || (0, _kubb_core.getInputKind)(studioInput) !== "inline")) return this.#refuse({ message: "A sandbox agent generates only from the OpenAPI document content Studio sends, not from a file path or URL" });
|
|
1741
1721
|
const [config, extensions] = await Promise.all([this.#options.loadConfig(), this.#installedExtensions()]);
|
|
1742
|
-
const [plugins, adapter] = await Promise.all([
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1722
|
+
const [plugins, adapter, parsers] = await Promise.all([
|
|
1723
|
+
mergePlugins({
|
|
1724
|
+
diskPlugins: config.plugins,
|
|
1725
|
+
studioPlugins: patch?.plugins,
|
|
1726
|
+
extensions
|
|
1727
|
+
}),
|
|
1728
|
+
mergeAdapter(config.adapter, patch?.adapter, extensions),
|
|
1729
|
+
mergeParsers({
|
|
1730
|
+
diskParsers: config.parsers,
|
|
1731
|
+
studioParsers: patch?.parsers,
|
|
1732
|
+
extensions
|
|
1733
|
+
})
|
|
1734
|
+
]).catch(async (error) => {
|
|
1747
1735
|
await emit(this.#options.hooks, "kubb:error", { error: toError(error) });
|
|
1748
1736
|
throw error;
|
|
1749
1737
|
});
|
|
@@ -1767,7 +1755,8 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1767
1755
|
input: studioInput ?? config.input,
|
|
1768
1756
|
storage: this.#permissions.allowWrite ? (0, _kubb_core.fsStorage)() : (0, _kubb_core.memoryStorage)(),
|
|
1769
1757
|
plugins: activePlugins,
|
|
1770
|
-
adapter
|
|
1758
|
+
adapter,
|
|
1759
|
+
parsers
|
|
1771
1760
|
},
|
|
1772
1761
|
hooks: this.#options.hooks,
|
|
1773
1762
|
signal,
|
|
@@ -1829,7 +1818,7 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1829
1818
|
}
|
|
1830
1819
|
if (this.#session.active) return { value: refuse("a generation is in progress") };
|
|
1831
1820
|
try {
|
|
1832
|
-
const {
|
|
1821
|
+
const { outcomes, changed } = await writeConfigEdits({
|
|
1833
1822
|
filePath: configFile,
|
|
1834
1823
|
edits,
|
|
1835
1824
|
extensions: await this.#installedExtensions()
|
|
@@ -1838,8 +1827,7 @@ var Agent = class extends capnweb.RpcTarget {
|
|
|
1838
1827
|
return {
|
|
1839
1828
|
value: {
|
|
1840
1829
|
outcomes,
|
|
1841
|
-
changed
|
|
1842
|
-
file: changed ? await this.#readConfigFileView(source) : void 0
|
|
1830
|
+
changed
|
|
1843
1831
|
},
|
|
1844
1832
|
info: `applied ${applied}/${outcomes.length} edits to ${configPath}`
|
|
1845
1833
|
};
|
|
@@ -2320,8 +2308,16 @@ async function createSnapshot({ studioUrl = defaultStudioUrl, apiKey, config, ho
|
|
|
2320
2308
|
if (content === null) throw new Error(`The generation listed ${path} but did not keep its content`);
|
|
2321
2309
|
return [path, content];
|
|
2322
2310
|
})));
|
|
2323
|
-
const
|
|
2324
|
-
const
|
|
2311
|
+
const pluginNames = config.plugins.map((plugin) => (0, _kubb_core.resolveMeta)(plugin).name);
|
|
2312
|
+
const installed = await (0, _kubb_core.readInstalledPackages)({
|
|
2313
|
+
root: config.root,
|
|
2314
|
+
packages: pluginNames.map(toPackageName),
|
|
2315
|
+
resolveFrom: ["kubb"]
|
|
2316
|
+
});
|
|
2317
|
+
const packageOf = (name) => installed.find(({ manifest }) => manifest?.kind === "plugin" && manifest.name === name)?.package ?? toPackageName(name);
|
|
2318
|
+
const names = [...new Set(pluginNames.map(packageOf))];
|
|
2319
|
+
const peerDependencies = Object.fromEntries(installed.flatMap(({ package: name, version }) => names.includes(name) && version ? [[name, version]] : []));
|
|
2320
|
+
const missing = names.filter((name) => !(name in peerDependencies) && !BUNDLED_DEPENDENCIES.includes(name));
|
|
2325
2321
|
if (missing.length) throw new Error(`Missing dependencies: ${missing.join(", ")}`);
|
|
2326
2322
|
const bytes = await createSnapshotPackage({
|
|
2327
2323
|
files,
|