@kubb/studio 5.5.3 → 5.5.5

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 CHANGED
@@ -1,84 +1,27 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- //#region \0rolldown/runtime.js
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __name = (target, value) => __defProp(target, "name", {
6
- value,
7
- configurable: true
8
- });
9
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
10
- var __getOwnPropNames = Object.getOwnPropertyNames;
11
- var __getProtoOf = Object.getPrototypeOf;
12
- var __hasOwnProp = Object.prototype.hasOwnProperty;
13
- var __copyProps = (to, from, except, desc) => {
14
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
15
- key = keys[i];
16
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
17
- get: ((k) => from[k]).bind(null, key),
18
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
- });
20
- }
21
- return to;
22
- };
23
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
24
- value: mod,
25
- enumerable: true
26
- }) : target, mod));
27
- //#endregion
2
+ const require_rolldown_runtime = require("./rolldown-runtime-qbf5tadS.cjs");
3
+ const require_resolveConfig = require("./resolveConfig-D7I3Nj60.cjs");
28
4
  const require_protocol = require("./protocol.cjs");
29
5
  let node_crypto = require("node:crypto");
30
6
  let node_timers_promises = require("node:timers/promises");
31
7
  let node_process = require("node:process");
32
- node_process = __toESM(node_process, 1);
8
+ node_process = require_rolldown_runtime.__toESM(node_process, 1);
33
9
  let node_child_process = require("node:child_process");
34
10
  let node_fs_promises = require("node:fs/promises");
35
11
  let node_path = require("node:path");
36
- node_path = __toESM(node_path, 1);
12
+ node_path = require_rolldown_runtime.__toESM(node_path, 1);
37
13
  let ofetch = require("ofetch");
38
14
  let node_util = require("node:util");
39
15
  let unstorage = require("unstorage");
40
16
  let unstorage_drivers_fs = require("unstorage/drivers/fs");
41
- unstorage_drivers_fs = __toESM(unstorage_drivers_fs, 1);
17
+ unstorage_drivers_fs = require_rolldown_runtime.__toESM(unstorage_drivers_fs, 1);
42
18
  let node_os = require("node:os");
43
19
  let _kubb_core = require("@kubb/core");
44
- let magicast = require("magicast");
45
- let node_fs = require("node:fs");
46
20
  let node_module = require("node:module");
47
- let node_url = require("node:url");
48
- let remeda = require("remeda");
49
- let node_zlib = require("node:zlib");
50
- let tsdown = require("tsdown");
51
21
  let capnweb = require("capnweb");
52
22
  let ws = require("ws");
53
- ws = __toESM(ws, 1);
23
+ ws = require_rolldown_runtime.__toESM(ws, 1);
54
24
  let tinyexec = require("tinyexec");
55
- //#region ../../internals/utils/src/casing.ts
56
- /**
57
- * Shared implementation for camelCase and PascalCase conversion.
58
- * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
59
- * and capitalizes each word according to `pascal`.
60
- *
61
- * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
62
- */
63
- function toCamelOrPascal(text, pascal) {
64
- return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
65
- if (word.length > 1 && word === word.toUpperCase()) return word;
66
- return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
67
- }).join("").replace(/[^a-zA-Z0-9]/g, "");
68
- }
69
- /**
70
- * Converts `text` to camelCase.
71
- *
72
- * @example Word boundaries
73
- * `camelCase('hello-world') // 'helloWorld'`
74
- *
75
- * @example With a prefix
76
- * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
77
- */
78
- function camelCase(text, { prefix = "", suffix = "" } = {}) {
79
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
80
- }
81
- //#endregion
82
25
  //#region ../../internals/utils/src/errors.ts
83
26
  /**
84
27
  * Coerces an unknown thrown value to an `Error` instance.
@@ -107,91 +50,6 @@ function getErrorMessage(value) {
107
50
  return value instanceof Error ? value.message : String(value);
108
51
  }
109
52
  //#endregion
110
- //#region ../../internals/utils/src/runtime.ts
111
- /**
112
- * Detects the JavaScript runtime executing the current process and exposes its name and version.
113
- *
114
- * Prefer the shared {@link runtime} instance over constructing your own.
115
- */
116
- var Runtime = class {
117
- /**
118
- * `true` when the current process is running under Bun.
119
- *
120
- * Detection keys off the global `Bun` object rather than `process.versions`,
121
- * because Bun polyfills `process.versions.node` for Node compatibility and would
122
- * otherwise look like Node.
123
- *
124
- * @example
125
- * ```ts
126
- * if (runtime.isBun) {
127
- * await Bun.write(path, data)
128
- * }
129
- * ```
130
- */
131
- get isBun() {
132
- return typeof Bun !== "undefined";
133
- }
134
- /**
135
- * `true` when the current process is running under Deno.
136
- */
137
- get isDeno() {
138
- return typeof globalThis.Deno !== "undefined";
139
- }
140
- /**
141
- * `true` when the current process is running under Node.
142
- *
143
- * Bun and Deno are excluded first so a polyfilled `process` does not register as Node.
144
- */
145
- get isNode() {
146
- return !this.isBun && !this.isDeno && typeof process !== "undefined" && process.versions?.node != null;
147
- }
148
- /**
149
- * Name of the runtime executing the current process.
150
- *
151
- * @example
152
- * ```ts
153
- * runtime.name // 'bun' when run with `bun kubb`, 'node' otherwise
154
- * ```
155
- */
156
- get name() {
157
- if (this.isBun) return "bun";
158
- if (this.isDeno) return "deno";
159
- return "node";
160
- }
161
- /**
162
- * Version of the active runtime, or an empty string when it cannot be read.
163
- *
164
- * @example
165
- * ```ts
166
- * runtime.version // '1.3.11' under Bun, '22.22.2' under Node
167
- * ```
168
- */
169
- get version() {
170
- if (this.isBun) return process.versions.bun ?? "";
171
- if (this.isDeno) return globalThis.Deno?.version?.deno ?? "";
172
- return process.versions?.node ?? "";
173
- }
174
- };
175
- /**
176
- * Shared {@link Runtime} instance describing the JavaScript runtime executing the current process.
177
- */
178
- const runtime = new Runtime();
179
- //#endregion
180
- //#region ../../internals/utils/src/fs.ts
181
- /**
182
- * Reads the file at `path` as a UTF-8 string.
183
- * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
184
- *
185
- * @example
186
- * ```ts
187
- * const source = await read('./src/Pet.ts')
188
- * ```
189
- */
190
- async function read(path) {
191
- if (runtime.isBun) return Bun.file(path).text();
192
- return (0, node_fs_promises.readFile)(path, { encoding: "utf8" });
193
- }
194
- //#endregion
195
53
  //#region ../../internals/utils/src/promise.ts
196
54
  /**
197
55
  * Wraps `factory` with a keyed cache backed by the provided store.
@@ -342,7 +200,7 @@ async function detectTool$1(candidates) {
342
200
  for (const candidate of candidates) if (await isToolAvailable(candidate)) return candidate;
343
201
  return null;
344
202
  }
345
- __name(detectTool$1, "detectTool");
203
+ require_rolldown_runtime.__name(detectTool$1, "detectTool");
346
204
  /**
347
205
  * Tokenizes a shell command string, respecting single and double quotes.
348
206
  *
@@ -725,944 +583,7 @@ function resolveAgentCapacity(env = process.env) {
725
583
  }
726
584
  //#endregion
727
585
  //#region package.json
728
- var version = "5.5.3";
729
- //#endregion
730
- //#region src/operations/resolveConfig.ts
731
- /**
732
- * Imports a package, falling back to how the user's project would resolve it.
733
- *
734
- * `import()` resolves from this file, so a linked or globally installed Studio (`pnpm link`,
735
- * `npm i -g`) only sees its own `node_modules` and misses the plugins installed next to the user's
736
- * config. The retry resolves from `process.cwd()` instead.
737
- */
738
- async function importFromProject(packageName) {
739
- try {
740
- return await import(packageName);
741
- } catch {
742
- const resolved = (0, node_module.createRequire)((0, node_url.pathToFileURL)(`${process.cwd()}/`)).resolve(packageName);
743
- const esm = resolved.replace(/\.cjs$/, ".js");
744
- return await import((0, node_url.pathToFileURL)(esm !== resolved && (0, node_fs.existsSync)(esm) ? esm : resolved).href);
745
- }
746
- }
747
- /**
748
- * Strips the `@kubb/` scope from a plugin package name, matching the `name` convention Kubb
749
- * plugin factories use internally.
750
- *
751
- * @example
752
- * ```ts
753
- * toPluginName('@kubb/plugin-ts') // 'plugin-ts'
754
- * ```
755
- */
756
- function toPluginName(packageName) {
757
- return packageName.split("/").pop() ?? packageName;
758
- }
759
- /**
760
- * Adds the `@kubb/` scope a plugin's package carries but its `name` does not, the inverse of
761
- * {@link toPluginName}. A name outside the `plugin-` convention is left alone, so a third-party
762
- * plugin is not reported as one of Kubb's.
763
- *
764
- * @example
765
- * ```ts
766
- * toPackageName('plugin-ts') // '@kubb/plugin-ts'
767
- * toPackageName('@acme/my-plugin') // '@acme/my-plugin'
768
- * ```
769
- */
770
- function toPackageName(name) {
771
- return name.startsWith("plugin-") ? `@kubb/${name}` : name;
772
- }
773
- /**
774
- * Derives the conventional named export for a `@kubb/*` plugin package from its package name.
775
- *
776
- * @example
777
- * ```ts
778
- * toExportName('@kubb/plugin-react-query') // 'pluginReactQuery'
779
- * toExportName('@kubb/plugin-ts') // 'pluginTs'
780
- * ```
781
- */
782
- function toExportName(packageName) {
783
- return camelCase(toPluginName(packageName));
784
- }
785
- /**
786
- * A `@kubb/plugin-*` package specifier. Nothing else may reach `import()`: only Kubb's own
787
- * plugins are supported, so a payload naming anything else, a third-party package or a path, is
788
- * refused before it can execute.
789
- */
790
- const KUBB_PLUGIN_SPECIFIER = /^@kubb\/plugin-[\w.-]+$/;
791
- /**
792
- * Whether `name` is a `@kubb/plugin-*` specifier. Exported so `configFile.ts` can refuse the same
793
- * shape before printing a Studio-supplied plugin name into the config file's source text.
794
- */
795
- function isKubbPluginSpecifier(name) {
796
- return KUBB_PLUGIN_SPECIFIER.test(name);
797
- }
798
- /**
799
- * Dynamically imports a `@kubb/plugin-*` package and returns its factory function.
800
- *
801
- * Packages must be pre-installed in the Docker image at build time via the `KUBB_PACKAGES`
802
- * build ARG, no runtime installation is possible in the distroless container.
803
- *
804
- * Resolution order: the camelCase named export the package name implies (e.g. `pluginTs`), then
805
- * the default export.
806
- *
807
- * @throws if the package cannot be imported or exports no callable factory.
808
- */
809
- async function loadPluginFactory(packageName) {
810
- if (!isKubbPluginSpecifier(packageName)) throw new Error(`Plugin "${packageName}" is not a @kubb/plugin-* package. Kubb Studio only supports Kubb's own plugins.`);
811
- let mod;
812
- try {
813
- mod = await importFromProject(packageName);
814
- } catch (cause) {
815
- throw new Error(`Plugin "${packageName}" could not be loaded. Make sure it is installed: \`npm install ${packageName}\``, { cause });
816
- }
817
- const exportName = toExportName(packageName);
818
- if (typeof mod[exportName] === "function") return mod[exportName];
819
- if (typeof mod["default"] === "function") return mod["default"];
820
- throw new Error(`Plugin "${packageName}" does not export a callable factory. Tried the named export "${exportName}" and "default".`);
821
- }
822
- /**
823
- * Resolves each plugin entry by dynamically importing the `@kubb/plugin-*` package and
824
- * calling its factory with the provided options.
825
- *
826
- * Packages must be pre-installed in the Docker image at build time, use the `KUBB_PACKAGES`
827
- * build ARG to control which ones are available at runtime.
828
- *
829
- * @example
830
- * ```ts
831
- * { name: '@kubb/plugin-react-query', options: { output: { path: './hooks' } } }
832
- * { name: '@kubb/plugin-ts', options: { output: { path: './types' } } }
833
- * ```
834
- */
835
- async function resolvePlugins(plugins) {
836
- return Promise.all(plugins.map(async ({ name, options }) => {
837
- return (await loadPluginFactory(name))(options ?? {});
838
- }));
839
- }
840
- /**
841
- * Whether `value` comes back unchanged from a JSON round trip. A function, a `RegExp` or another
842
- * class instance does not: the agent reports options to Studio as JSON, so what Studio sends back
843
- * for such a value is a lossy copy (`macros: [{ name }]` without its hooks, `pattern: {}`).
844
- */
845
- function survivesJson(value) {
846
- if (value === null || typeof value === "string" || typeof value === "boolean") return true;
847
- if (typeof value === "number") return Number.isFinite(value);
848
- if (Array.isArray(value)) return value.every(survivesJson);
849
- if ((0, remeda.isPlainObject)(value)) return Object.values(value).every(survivesJson);
850
- return false;
851
- }
852
- /**
853
- * Deep-merges Studio's options over the disk options, with Studio taking priority, except where the
854
- * disk value cannot survive JSON. Studio only ever saw a lossy copy of such a value, so the disk
855
- * value is kept rather than replaced by what Studio echoes back. Plain objects merge key by key, so
856
- * Studio can still change a literal that sits next to a function (e.g. `group.type` beside `group.name`).
857
- *
858
- * @example
859
- * ```ts
860
- * mergeOptions({ macros: [camelCaseMacro], syntaxType: 'type' }, { macros: [{ name: 'camelCase' }], syntaxType: 'interface' })
861
- * // { macros: [camelCaseMacro], syntaxType: 'interface' }
862
- * ```
863
- */
864
- function mergeOptions(disk, studio) {
865
- const merged = { ...disk };
866
- for (const [key, value] of Object.entries(studio)) {
867
- const current = disk[key];
868
- if ((0, remeda.isPlainObject)(current) && (0, remeda.isPlainObject)(value)) {
869
- merged[key] = mergeOptions(current, value);
870
- continue;
871
- }
872
- if (current !== void 0 && !survivesJson(current)) continue;
873
- merged[key] = value;
874
- }
875
- return merged;
876
- }
877
- /**
878
- * Merges studio plugin options with disk config plugins.
879
- * Studio takes priority: options from studio win over disk (except values only the disk config
880
- * can express, see {@link mergeOptions}), and a plugin Studio explicitly
881
- * disabled is dropped even when the disk config still lists it. Disk plugins without a studio
882
- * counterpart are kept as-is. Studio plugins not present on disk are appended.
883
- *
884
- * For plugins present in both configs, the plugin is re-instantiated with merged options
885
- * so that all internal closures correctly reference the merged values.
886
- */
887
- async function mergePlugins(diskPlugins, studioPlugins) {
888
- const disabledNames = new Set((studioPlugins ?? []).filter((entry) => entry.disabled).map((entry) => toPluginName(entry.name)));
889
- const activeDiskPlugins = disabledNames.size ? diskPlugins?.filter((plugin) => !disabledNames.has(plugin.name)) : diskPlugins;
890
- const activeStudioPlugins = studioPlugins?.filter((entry) => !entry.disabled);
891
- if (!activeDiskPlugins && !activeStudioPlugins?.length) return void 0;
892
- if (!activeStudioPlugins?.length) return activeDiskPlugins;
893
- if (!activeDiskPlugins) return resolvePlugins(activeStudioPlugins);
894
- const studioEntryByName = new Map(activeStudioPlugins.map((entry) => [toPluginName(entry.name), entry]));
895
- const diskNames = new Set(activeDiskPlugins.map((plugin) => plugin.name));
896
- const merged = await Promise.all(activeDiskPlugins.map(async (diskPlugin) => {
897
- const studioEntry = studioEntryByName.get(diskPlugin.name);
898
- if (!studioEntry) return diskPlugin;
899
- const options = mergeOptions(diskPlugin.options ?? {}, studioEntry.options ?? {});
900
- const [resolved] = await resolvePlugins([{
901
- name: studioEntry.name,
902
- options
903
- }]);
904
- return resolved ?? diskPlugin;
905
- }));
906
- const studioOnly = activeStudioPlugins.filter((entry) => !diskNames.has(toPluginName(entry.name)));
907
- return [...merged, ...await resolvePlugins(studioOnly)];
908
- }
909
- /**
910
- * Merges Studio-provided adapter option overrides into the disk config's adapter.
911
- *
912
- * Adapter instances carry live functions (`parse`, `getImports`, ...) that can't survive
913
- * JSON serialization over the WebSocket, so `studioOptions` is treated as an options patch
914
- * rather than a replacement adapter. Re-invokes the same `@kubb/adapter-<name>` factory the
915
- * disk config used, with the merged options, so the resulting instance has fresh closures
916
- * over the merged values instead of a plain object missing `parse`.
917
- */
918
- async function mergeAdapter(diskAdapter, studioOptions) {
919
- if (!studioOptions || !diskAdapter) return diskAdapter;
920
- const packageName = `@kubb/adapter-${diskAdapter.name}`;
921
- const factory = (await importFromProject(packageName))[toExportName(packageName)];
922
- if (typeof factory !== "function") return diskAdapter;
923
- return factory(mergeOptions(diskAdapter.options ?? {}, studioOptions));
924
- }
925
- //#endregion
926
- //#region src/operations/configFile.ts
927
- /**
928
- * A valid JavaScript identifier, so an import name can only ever print as `import { name } from`,
929
- * never as source that breaks out of the import statement.
930
- */
931
- const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
932
- /**
933
- * `key: value` as an object literal property, in the file's quote and key style.
934
- *
935
- * Uses magicast's literal builder for the key/value nodes, then wraps them as a Babel
936
- * `ObjectProperty`, the type the rest of this file reads.
937
- */
938
- function literalProperty({ key, value }) {
939
- const built = magicast.builders.literal({ [key]: value }).properties[0];
940
- return {
941
- type: "ObjectProperty",
942
- key: built.key,
943
- value: built.value,
944
- computed: false,
945
- shorthand: false
946
- };
947
- }
948
- /**
949
- * Marks the comment block a `disable-plugin` leaves behind, so `enable-plugin` can find its way
950
- * back to the exact lines it commented out. Carries the block's line count, so `enable-plugin`
951
- * restores exactly those lines instead of scanning forward through whatever comments follow.
952
- */
953
- const DISABLED_MARKER = "kubb:disabled";
954
- /**
955
- * The one line `disable-plugin` writes above the comment block it produces for `plugin`.
956
- */
957
- function formatMarker(plugin, lineCount, indent = "") {
958
- return `${indent}// ${DISABLED_MARKER} ${plugin} ${lineCount}`;
959
- }
960
- /**
961
- * The plugin and comment-block length a marker line names, when `line` is one.
962
- */
963
- function parseMarker(line) {
964
- const trimmed = line.trim();
965
- if (!trimmed.startsWith(`// ${DISABLED_MARKER} `)) return;
966
- const match = trimmed.slice(`// ${DISABLED_MARKER} `.length).match(/^(.+)\s+(\d+)$/);
967
- return match ? {
968
- plugin: match[1],
969
- lineCount: Number(match[2])
970
- } : void 0;
971
- }
972
- /**
973
- * Steps through a config's wrappers to the object literal underneath: a `satisfies`/`as`
974
- * assertion, a `() => ...` factory, or a factory whose block body returns the config.
975
- */
976
- function unwrap(node) {
977
- if (!node) return;
978
- if (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression") return unwrap(node.expression);
979
- if (node.type !== "ArrowFunctionExpression" && node.type !== "FunctionExpression") return node;
980
- if (node.body.type !== "BlockStatement") return unwrap(node.body);
981
- return unwrap(node.body.body.find((statement) => statement.type === "ReturnStatement")?.argument);
982
- }
983
- /**
984
- * Every config object in `export default defineConfig(...)`, or why the file is unmanaged.
985
- *
986
- * An array export gets one entry per element, matching {@link ConfigRef}'s numeric index.
987
- *
988
- * Walks the parsed AST rather than magicast's proxies, which throw on node types they cannot
989
- * cast, most of what an unmanaged config file is made of.
990
- */
991
- function findConfigs(mod) {
992
- const exported = unwrap((mod.$ast.type === "Program" ? mod.$ast.body : []).find((node) => node.type === "ExportDefaultDeclaration")?.declaration);
993
- if (!exported) return { reason: "no default export found" };
994
- if (exported.type !== "CallExpression" || exported.callee.type !== "Identifier" || exported.callee.name !== "defineConfig") return { reason: "default export is not a defineConfig(...) call" };
995
- const argument = unwrap(exported.arguments[0]);
996
- if (!argument) return { reason: "defineConfig(...) was called without a config" };
997
- if (argument.type === "ArrayExpression") {
998
- const configs = [];
999
- for (const element of argument.elements) {
1000
- const entry = unwrap(element);
1001
- if (entry?.type !== "ObjectExpression") return { reason: "config is not an object literal" };
1002
- configs.push(entry);
1003
- }
1004
- return { configs };
1005
- }
1006
- if (argument.type !== "ObjectExpression") return { reason: "config is not an object literal" };
1007
- return { configs: [argument] };
1008
- }
1009
- /**
1010
- * The config entry an edit names, defaulting to the first when it names none.
1011
- */
1012
- function selectConfig(configs, ref) {
1013
- if (ref === void 0) return configs[0];
1014
- if (typeof ref === "number") return configs[ref];
1015
- return configs.find((config) => configName(config) === ref);
1016
- }
1017
- function configName(config) {
1018
- const name = property(config, "name");
1019
- return name?.type === "StringLiteral" ? name.value : void 0;
1020
- }
1021
- /**
1022
- * The name of an object literal property, for the two key shapes a config uses: `key: value` and
1023
- * `'key': value`. `undefined` for a computed key, which the patcher never touches.
1024
- */
1025
- function propertyKey(entry) {
1026
- if (entry.key.type === "Identifier") return entry.key.name;
1027
- if (entry.key.type === "StringLiteral") return entry.key.value;
1028
- }
1029
- /**
1030
- * The index of an object literal's own property named `key`, `-1` when it has none.
1031
- */
1032
- function entryIndex({ node, key }) {
1033
- return node.properties.findIndex((entry) => entry.type === "ObjectProperty" && propertyKey(entry) === key);
1034
- }
1035
- /**
1036
- * The value node of an object literal's own property.
1037
- */
1038
- function property(node, key) {
1039
- const index = entryIndex({
1040
- node,
1041
- key
1042
- });
1043
- return index === -1 ? void 0 : node.properties[index].value;
1044
- }
1045
- /**
1046
- * Writes `key: value` on an object literal, replacing the value when the property is already there.
1047
- *
1048
- * An existing property has its value swapped in place rather than being replaced whole, so recast
1049
- * reprints only that value and leaves the object's own layout alone.
1050
- */
1051
- function setProperty({ node, key, value }) {
1052
- const entry = literalProperty({
1053
- key,
1054
- value
1055
- });
1056
- const index = entryIndex({
1057
- node,
1058
- key
1059
- });
1060
- if (index === -1) {
1061
- node.properties.push(entry);
1062
- return;
1063
- }
1064
- node.properties[index].value = entry.value;
1065
- }
1066
- /**
1067
- * Drops `key` from an object literal.
1068
- */
1069
- function removeProperty({ node, key }) {
1070
- const index = entryIndex({
1071
- node,
1072
- key
1073
- });
1074
- if (index !== -1) node.properties.splice(index, 1);
1075
- }
1076
- /**
1077
- * Reads a literal node's value: a primitive, or an object/array built only from primitives.
1078
- * `undefined` for anything else, so a caller can use this both to read a value and to check
1079
- * whether a node is a literal at all.
1080
- */
1081
- function readLiteral(node) {
1082
- if (!node) return;
1083
- if (node.type === "StringLiteral" || node.type === "NumericLiteral" || node.type === "BooleanLiteral") return node.value;
1084
- if (node.type === "NullLiteral") return null;
1085
- if (node.type === "TemplateLiteral") return node.expressions.length === 0 ? node.quasis[0]?.value.cooked ?? "" : void 0;
1086
- if (node.type === "UnaryExpression") {
1087
- const value = readLiteral(node.argument);
1088
- if (typeof value !== "number") return;
1089
- if (node.operator === "-") return -value;
1090
- if (node.operator === "+") return value;
1091
- return;
1092
- }
1093
- if (node.type === "ArrayExpression") {
1094
- const values = node.elements.map((element) => element ? readLiteral(element) : void 0);
1095
- return values.every((value) => value !== void 0) ? values : void 0;
1096
- }
1097
- if (node.type === "ObjectExpression") {
1098
- const entries = {};
1099
- for (const entry of node.properties) {
1100
- if (entry.type !== "ObjectProperty") return;
1101
- const key = propertyKey(entry);
1102
- const value = readLiteral(entry.value);
1103
- if (key === void 0 || value === void 0) return;
1104
- entries[key] = value;
1105
- }
1106
- return entries;
1107
- }
1108
- }
1109
- /**
1110
- * Maps a factory identifier in the file back to the module it was imported from.
1111
- */
1112
- function importedFrom(mod) {
1113
- return new Map(mod.imports.$items.map((item) => [item.local, item.from]));
1114
- }
1115
- /**
1116
- * Every `pluginX(...)` element of a config's plugins array that resolves to an import.
1117
- */
1118
- function pluginCalls(mod, config) {
1119
- const plugins = property(config, "plugins");
1120
- if (plugins?.type !== "ArrayExpression") return [];
1121
- const imports = importedFrom(mod);
1122
- return plugins.elements.flatMap((element) => {
1123
- if (element?.type !== "CallExpression" || element.callee.type !== "Identifier") return [];
1124
- const packageName = imports.get(element.callee.name);
1125
- return packageName ? [{
1126
- importName: element.callee.name,
1127
- packageName,
1128
- call: element
1129
- }] : [];
1130
- });
1131
- }
1132
- /**
1133
- * Plugins a previous `disable-plugin` commented out of this config, keyed by package name.
1134
- *
1135
- * Read from the marker lines rather than the AST, since a commented-out call is no longer a node.
1136
- */
1137
- function disabledMarkers(source) {
1138
- return source.split("\n").flatMap((line, index) => {
1139
- const marker = parseMarker(line);
1140
- return marker ? [{
1141
- packageName: marker.plugin,
1142
- line: index + 1
1143
- }] : [];
1144
- });
1145
- }
1146
- /**
1147
- * Reads which plugins the file declares and which of their options Studio may write.
1148
- *
1149
- * @example
1150
- * ```ts
1151
- * const view = readConfig(await readFile('kubb.config.ts', 'utf8'))
1152
- * if (view.managed) {
1153
- * view.configs.forEach((config) => console.log(config.name, config.plugins.length))
1154
- * }
1155
- * ```
1156
- */
1157
- function readConfig(source) {
1158
- let mod;
1159
- try {
1160
- mod = (0, magicast.parseModule)(source);
1161
- } catch {
1162
- return {
1163
- managed: false,
1164
- reason: "the config file could not be parsed"
1165
- };
1166
- }
1167
- const found = findConfigs(mod);
1168
- if ("reason" in found) return {
1169
- managed: false,
1170
- reason: found.reason
1171
- };
1172
- const importNames = new Map([...importedFrom(mod)].map(([local, from]) => [from, local]));
1173
- const disabled = disabledMarkers(source);
1174
- return {
1175
- managed: true,
1176
- configs: found.configs.map((config) => {
1177
- const plugins = pluginCalls(mod, config).map(({ importName, packageName, call }) => {
1178
- const entries = {};
1179
- const options = call.arguments[0];
1180
- if (options?.type === "ObjectExpression") for (const entry of options.properties) {
1181
- if (entry.type !== "ObjectProperty") continue;
1182
- const key = propertyKey(entry);
1183
- if (key === void 0) continue;
1184
- const value = readLiteral(entry.value);
1185
- entries[key] = value === void 0 ? { literal: false } : {
1186
- literal: true,
1187
- value
1188
- };
1189
- }
1190
- return {
1191
- importName,
1192
- packageName,
1193
- options: entries
1194
- };
1195
- });
1196
- const start = config.loc?.start.line ?? 0;
1197
- const end = config.loc?.end.line ?? Number.POSITIVE_INFINITY;
1198
- for (const { packageName } of disabled.filter((entry) => entry.line >= start && entry.line <= end)) plugins.push({
1199
- importName: importNames.get(packageName) ?? toExportName(packageName),
1200
- packageName,
1201
- options: {},
1202
- disabled: true
1203
- });
1204
- return {
1205
- name: configName(config),
1206
- plugins
1207
- };
1208
- })
1209
- };
1210
- }
1211
- /**
1212
- * Whether a value can be written into a config file as a literal.
1213
- *
1214
- * This is the trust boundary for edits that arrive over the agent WebSocket: a function, `undefined`,
1215
- * or a non-finite number is refused rather than printed into the user's source.
1216
- */
1217
- function isOptionValue(value) {
1218
- if (value === null) return true;
1219
- if (typeof value === "string" || typeof value === "boolean") return true;
1220
- if (typeof value === "number") return Number.isFinite(value);
1221
- if (Array.isArray(value)) return value.every(isOptionValue);
1222
- if (typeof value === "object") return Object.values(value).every(isOptionValue);
1223
- return false;
1224
- }
1225
- /**
1226
- * The options object of a plugin call, when it was called with one.
1227
- */
1228
- function getOptions(call) {
1229
- const options = call.arguments[0];
1230
- return options?.type === "ObjectExpression" ? options : void 0;
1231
- }
1232
- /**
1233
- * The options object of a plugin call, creating an empty one when the plugin was called bare.
1234
- */
1235
- function ensureOptions(call) {
1236
- if (call.arguments.length === 0) call.arguments.push({
1237
- type: "ObjectExpression",
1238
- properties: []
1239
- });
1240
- return getOptions(call);
1241
- }
1242
- /**
1243
- * Walks `path` down to the object holding its last key, descending only through object literals.
1244
- */
1245
- function optionParent(options, path) {
1246
- let object = options;
1247
- for (const [index, key] of path.entries()) {
1248
- if (index === path.length - 1) return {
1249
- object,
1250
- key
1251
- };
1252
- if (property(object, key) === void 0) setProperty({
1253
- node: object,
1254
- key,
1255
- value: {}
1256
- });
1257
- const next = property(object, key);
1258
- if (next?.type !== "ObjectExpression") return { reason: `${key} is not an object, so ${path.join(".")} cannot be reached` };
1259
- object = next;
1260
- }
1261
- return { reason: "no option path given" };
1262
- }
1263
- /**
1264
- * Writes `value` at `path` inside a plugin call's options, creating the options object and any
1265
- * intermediate object along the path as needed. Refuses when the current value at `path` is
1266
- * something other than a literal, so an option customized in code is never overwritten.
1267
- */
1268
- function applySet(call, path, value) {
1269
- if (!isOptionValue(value)) return "the value is not a literal that can be written to a config file";
1270
- const options = ensureOptions(call);
1271
- if (!options) return "the plugin was not called with an object literal";
1272
- const target = optionParent(options, path);
1273
- if ("reason" in target) return target.reason;
1274
- const current = property(target.object, target.key);
1275
- if (current !== void 0 && readLiteral(current) === void 0) return `${path.join(".")} is customized in code`;
1276
- setProperty({
1277
- node: target.object,
1278
- key: target.key,
1279
- value
1280
- });
1281
- }
1282
- /**
1283
- * Deletes the property at `path` inside a plugin call's options, falling the plugin back to its
1284
- * default for that option. Refuses when the value at `path` is not a literal, for the same reason
1285
- * `applySet` does.
1286
- */
1287
- function applyRemove(call, path) {
1288
- const options = getOptions(call);
1289
- if (!options) return "the plugin has no options to remove";
1290
- const target = optionParent(options, path);
1291
- if ("reason" in target) return target.reason;
1292
- const current = property(target.object, target.key);
1293
- if (current === void 0) return `${path.join(".")} is not set`;
1294
- if (readLiteral(current) === void 0) return `${path.join(".")} is customized in code`;
1295
- removeProperty({
1296
- node: target.object,
1297
- key: target.key
1298
- });
1299
- }
1300
- /**
1301
- * Adds a `pluginX(...)` call to a config's plugins array. Replaying the same add is a no-op, while
1302
- * an import name collision with an unrelated package remains an error.
1303
- */
1304
- function applyAddPlugin(mod, config, edit) {
1305
- if (!isKubbPluginSpecifier(edit.plugin)) return { reason: `"${edit.plugin}" is not a @kubb/plugin-* package` };
1306
- const importName = edit.importName ?? toExportName(edit.plugin);
1307
- if (!IDENTIFIER.test(importName)) return { reason: `"${importName}" is not a valid import name` };
1308
- if (pluginCalls(mod, config).some((plugin) => plugin.packageName === edit.plugin)) return { noop: true };
1309
- const taken = importedFrom(mod).get(importName);
1310
- if (taken && taken !== edit.plugin) return { reason: `${importName} is already imported from ${taken}` };
1311
- const options = edit.options ?? {};
1312
- if (!isOptionValue(options)) return { reason: "the options are not literals that can be written to a config file" };
1313
- const plugins = property(config, "plugins");
1314
- if (plugins?.type !== "ArrayExpression") return { reason: "plugins is not an array literal" };
1315
- const call = Object.keys(options).length ? magicast.builders.functionCall(importName, options) : magicast.builders.functionCall(importName);
1316
- plugins.elements.push(call.$ast);
1317
- return taken ? {} : { addImport: {
1318
- importName,
1319
- moduleSpecifier: edit.plugin
1320
- } };
1321
- }
1322
- /**
1323
- * Comments out a plugin call in place, keeping its options on disk so `enable-plugin` can restore
1324
- * them exactly. Operates on `source` text rather than the AST: a commented-out call is no longer a
1325
- * node magicast can address, and the surrounding array must not reflow when its element count
1326
- * never actually changes.
1327
- */
1328
- function disablePlugin(source, mod, config, plugin) {
1329
- const target = pluginCalls(mod, config).find((entry) => entry.packageName === plugin);
1330
- if (!target) return { reason: `${plugin} is not in the plugins array` };
1331
- const loc = target.call.loc;
1332
- if (!loc?.start || !loc.end) return { reason: `${plugin} has no source location to comment out` };
1333
- const lines = source.split("\n");
1334
- const from = loc.start.line - 1;
1335
- const to = loc.end.line - 1;
1336
- const firstLine = lines[from] ?? "";
1337
- const lastLine = lines[to] ?? "";
1338
- if (firstLine.slice(0, loc.start.column).trim() !== "" || !/^,?\s*$/.test(lastLine.slice(loc.end.column))) return { reason: `${plugin} shares a line with other code, so it cannot be commented out safely` };
1339
- const indent = firstLine.match(/^\s*/)?.[0] ?? "";
1340
- const commented = lines.slice(from, to + 1).map((line) => line.trim() ? `${indent}// ${line.slice(indent.length)}` : indent ? `${indent}//` : "//");
1341
- lines.splice(from, to - from + 1, formatMarker(plugin, commented.length, indent), ...commented);
1342
- return { source: lines.join("\n") };
1343
- }
1344
- /**
1345
- * Uncomments the block a previous `disable-plugin` left behind for `plugin`.
1346
- */
1347
- function enablePlugin(source, plugin) {
1348
- const lines = source.split("\n");
1349
- for (const [index, line] of lines.entries()) {
1350
- const marker = parseMarker(line);
1351
- if (marker?.plugin !== plugin) continue;
1352
- const end = index + 1 + marker.lineCount;
1353
- const restored = lines.slice(index + 1, end).map((commented) => commented.replace(/^(\s*)\/\/ ?/, "$1"));
1354
- lines.splice(index, end - index, ...restored);
1355
- return { source: lines.join("\n") };
1356
- }
1357
- return { reason: `${plugin} is not disabled` };
1358
- }
1359
- /**
1360
- * Re-parses `source` and resolves the config entry an edit targets. Every edit re-parses rather
1361
- * than sharing one module across the batch, since the disable/enable edits rewrite `source` as
1362
- * text and would otherwise leave the others working from a stale tree.
1363
- */
1364
- function parseTarget(source, ref) {
1365
- let mod;
1366
- try {
1367
- mod = (0, magicast.parseModule)(source);
1368
- } catch {
1369
- return { reason: "the config file could not be parsed" };
1370
- }
1371
- const found = findConfigs(mod);
1372
- if ("reason" in found) return { reason: found.reason };
1373
- const config = selectConfig(found.configs, ref);
1374
- if (!config) return { reason: `no config entry found for ${JSON.stringify(ref)}` };
1375
- return {
1376
- mod,
1377
- config
1378
- };
1379
- }
1380
- /**
1381
- * Applies edits to a `kubb.config.ts` in place. Every node the edits do not touch keeps its
1382
- * original text, so comments, formatting, and hand-written code around the config survive.
1383
- *
1384
- * Edits are independent: one that cannot be applied is reported in `outcomes` and the rest still run.
1385
- *
1386
- * @example
1387
- * ```ts
1388
- * const { source, outcomes } = applyConfigEdits(current, [
1389
- * { operation: 'set', plugin: '@kubb/plugin-ts', path: ['enum', 'type'], value: 'enum' },
1390
- * ])
1391
- * ```
1392
- *
1393
- * @note recast always reprints a semicolon on a reprinted statement, so editing a block-body
1394
- * `defineConfig` in a semicolon-free file adds one to the `return` line. This is a known gap.
1395
- * Strip it when `detectCodeFormat` reports `useSemi: false`, if it turns out to matter in practice.
1396
- */
1397
- function applyConfigEdits(source, edits) {
1398
- let current = source;
1399
- const format = (0, magicast.detectCodeFormat)(source);
1400
- const endsWithNewline = source.endsWith("\n");
1401
- const outcomes = edits.map((edit) => {
1402
- const target = parseTarget(current, edit.config);
1403
- if ("reason" in target) return {
1404
- edit,
1405
- applied: false,
1406
- reason: target.reason
1407
- };
1408
- const { mod, config } = target;
1409
- if (edit.operation === "disable-plugin" || edit.operation === "enable-plugin") {
1410
- const result = edit.operation === "disable-plugin" ? disablePlugin(current, mod, config, edit.plugin) : enablePlugin(current, edit.plugin);
1411
- if ("reason" in result) return {
1412
- edit,
1413
- applied: false,
1414
- reason: result.reason
1415
- };
1416
- current = result.source;
1417
- return {
1418
- edit,
1419
- applied: true
1420
- };
1421
- }
1422
- if (edit.operation === "add-plugin") {
1423
- const result = applyAddPlugin(mod, config, edit);
1424
- if ("reason" in result) return {
1425
- edit,
1426
- applied: false,
1427
- reason: result.reason
1428
- };
1429
- if ("noop" in result) return {
1430
- edit,
1431
- applied: true
1432
- };
1433
- const afterLine = lastImportEndLine(mod);
1434
- let next = (0, magicast.generateCode)(mod, { format }).code;
1435
- if (result.addImport) next = insertImportLine({
1436
- source: next,
1437
- afterLine,
1438
- ...result.addImport
1439
- });
1440
- current = withTrailingNewline(next, endsWithNewline);
1441
- return {
1442
- edit,
1443
- applied: true
1444
- };
1445
- }
1446
- const pluginCall = pluginCalls(mod, config).find((plugin) => plugin.packageName === edit.plugin);
1447
- if (!pluginCall) return {
1448
- edit,
1449
- applied: false,
1450
- reason: `${edit.plugin} is not in the plugins array`
1451
- };
1452
- const reason = edit.operation === "set" ? applySet(pluginCall.call, edit.path, edit.value) : applyRemove(pluginCall.call, edit.path);
1453
- if (!reason) current = withTrailingNewline((0, magicast.generateCode)(mod, { format }).code, endsWithNewline);
1454
- return {
1455
- edit,
1456
- applied: !reason,
1457
- reason
1458
- };
1459
- });
1460
- return {
1461
- source: current,
1462
- outcomes,
1463
- changed: current !== source
1464
- };
1465
- }
1466
- async function writeConfigEdits({ filePath, edits }) {
1467
- const result = applyConfigEdits(await read(filePath), edits);
1468
- if (result.changed) await (0, node_fs_promises.writeFile)(filePath, result.source, "utf-8");
1469
- return result;
1470
- }
1471
- /**
1472
- * The 1-based line where the file's last import declaration ends, or `0` when it has none. Read
1473
- * off the parsed module, so a multi-line `import {\n x,\n} from '...'` reports its closing line
1474
- * rather than the `import` keyword.
1475
- */
1476
- function lastImportEndLine(mod) {
1477
- return (mod.$ast.type === "Program" ? mod.$ast.body : []).filter((node) => node.type === "ImportDeclaration").at(-1)?.loc?.end.line ?? 0;
1478
- }
1479
- /**
1480
- * Writes an import after the last one already in the file, matching its quote style and whether it
1481
- * ends in a semicolon. `afterLine` is where that last import ends, `0` for a file with none.
1482
- *
1483
- * Written as plain text rather than through magicast's import builder, which prints a brand-new
1484
- * import declaration with its own default spacing and a semicolon regardless of `format`, since
1485
- * that formatting only governs nodes recast can diff against the original source.
1486
- */
1487
- function insertImportLine({ source, importName, moduleSpecifier, afterLine }) {
1488
- const lines = source.split("\n");
1489
- const lastImportLine = afterLine > 0 ? lines[afterLine - 1] : void 0;
1490
- const quote = lastImportLine?.includes(`"`) ? `"` : `'`;
1491
- const line = `import { ${importName} } from ${quote}${moduleSpecifier}${quote}${lastImportLine?.trimEnd().endsWith(";") ? ";" : ""}`;
1492
- lines.splice(afterLine, 0, ...afterLine > 0 ? [line] : [line, ""]);
1493
- return lines.join("\n");
1494
- }
1495
- /**
1496
- * `generateCode` always drops the file's trailing newline. Restore it when the input had one.
1497
- */
1498
- function withTrailingNewline(code, hadTrailingNewline) {
1499
- if (!hadTrailingNewline || code.endsWith("\n")) return code;
1500
- return `${code}\n`;
1501
- }
1502
- //#endregion
1503
- //#region src/operations/snapshotPackage.ts
1504
- const gzipAsync = (0, node_util.promisify)(node_zlib.gzip);
1505
- const UPLOAD_TIMEOUT_MS = 12e4;
1506
- /**
1507
- * Maps a generated file's path to its place inside the tarball, stripping everything before a
1508
- * `src`/`dist` segment and any `..`/empty path segment so a crafted file name cannot escape the
1509
- * `package/` root.
1510
- */
1511
- function packagePath(filePath) {
1512
- const normalized = filePath.replaceAll("\\", "/");
1513
- return `package/${(normalized.match(/\/(?:src|dist)\/.*$/)?.[0].slice(1) ?? normalized.replace(/^\/+/, "")).split("/").filter((part) => part && part !== "." && part !== "..").join("/")}`;
1514
- }
1515
- /**
1516
- * Splits a tarball entry path into the legacy 100-byte `name` field and, when the path does not
1517
- * fit, the 155-byte USTAR `prefix` field that extends it. Throws rather than silently truncating
1518
- * a path the format cannot address (max 256 bytes: 100 name + 1 separator + 155 prefix).
1519
- */
1520
- function splitEntryPath(path) {
1521
- if (Buffer.byteLength(path, "utf8") <= 100) return {
1522
- name: path,
1523
- prefix: ""
1524
- };
1525
- for (let i = path.length - 1; i >= 0; i--) {
1526
- if (path[i] !== "/") continue;
1527
- const prefix = path.slice(0, i);
1528
- const name = path.slice(i + 1);
1529
- if (Buffer.byteLength(prefix, "utf8") <= 155 && Buffer.byteLength(name, "utf8") <= 100) return {
1530
- name,
1531
- prefix
1532
- };
1533
- }
1534
- throw new Error(`Snapshot path is too long for a tar entry: ${path}`);
1535
- }
1536
- function header(path, size) {
1537
- const { name, prefix } = splitEntryPath(path);
1538
- const value = Buffer.alloc(512);
1539
- value.write(name, 0, "utf8");
1540
- value.write("0000644\0", 100, "ascii");
1541
- value.write("0000000\0", 108, "ascii");
1542
- value.write("0000000\0", 116, "ascii");
1543
- value.write(`${size.toString(8).padStart(11, "0")}\0`, 124, "ascii");
1544
- value.write(`${Math.floor(Date.now() / 1e3).toString(8).padStart(11, "0")}\0`, 136, "ascii");
1545
- value.fill(32, 148, 156);
1546
- value.write("0", 156, "ascii");
1547
- value.write("ustar\0", 257, "ascii");
1548
- value.write("00", 263, "ascii");
1549
- value.write("0000000\0", 265, "ascii");
1550
- value.write("0000000\0", 297, "ascii");
1551
- value.write(prefix, 345, "utf8");
1552
- const checksum = [...value].reduce((sum, byte) => sum + byte, 0);
1553
- value.write(`${checksum.toString(8).padStart(6, "0")}\0 `, 148, "ascii");
1554
- return value;
1555
- }
1556
- /**
1557
- * Packs a generation's files into a gzipped, npm-installable tarball: a `package/` root with the
1558
- * generated sources, a `tsdown`-built `dist/` (esm + cjs), and a `package.json` manifest.
1559
- */
1560
- async function createSnapshotPackage(files, packageInfo) {
1561
- const root = await (0, node_fs_promises.mkdtemp)((0, node_path.join)((0, node_os.tmpdir)(), "kubb-snapshot-"));
1562
- const dist = (0, node_path.join)(root, "dist");
1563
- const resolvedPaths = Object.entries(files).map(([name, content]) => ({
1564
- name,
1565
- content,
1566
- target: packagePath(name)
1567
- }));
1568
- const targetOwners = /* @__PURE__ */ new Map();
1569
- for (const { name, target } of resolvedPaths) {
1570
- const owner = targetOwners.get(target);
1571
- if (owner) throw new Error(`Snapshot has two generated files that sanitize to the same path "${target}": "${owner}" and "${name}"`);
1572
- targetOwners.set(target, name);
1573
- }
1574
- try {
1575
- await (0, node_fs_promises.mkdir)(dist);
1576
- await Promise.all(resolvedPaths.map(async ({ content, target }) => {
1577
- const path = (0, node_path.join)(root, target.slice(8));
1578
- await (0, node_fs_promises.mkdir)((0, node_path.join)(path, ".."), { recursive: true });
1579
- await (0, node_fs_promises.writeFile)(path, content);
1580
- }));
1581
- const sourceEntries = resolvedPaths.filter(({ name }) => /\.(?:[cm]?[jt]sx?)$/.test(name)).map(({ target }) => (0, node_path.join)(root, target.slice(8)));
1582
- if (sourceEntries.length) await (0, tsdown.build)({
1583
- entry: sourceEntries,
1584
- outDir: dist,
1585
- format: ["esm", "cjs"],
1586
- dts: false,
1587
- sourcemap: false,
1588
- unbundle: true,
1589
- report: false,
1590
- logLevel: "silent",
1591
- fixedExtension: true
1592
- });
1593
- const builtEntries = await Promise.all((await Array.fromAsync((0, node_fs_promises.glob)("**/*", {
1594
- cwd: dist,
1595
- withFileTypes: true
1596
- }))).filter((entry) => entry.isFile()).map(async (entry) => {
1597
- const filePath = (0, node_path.join)(entry.parentPath, entry.name);
1598
- return [`package/dist/${(0, node_path.relative)(dist, filePath).split(node_path.sep).join("/")}`, await (0, node_fs_promises.readFile)(filePath, "utf8")];
1599
- }));
1600
- const builtPaths = new Set(builtEntries.map(([path]) => path));
1601
- const hasBarrel = builtPaths.has("package/dist/index.mjs") && builtPaths.has("package/dist/index.cjs");
1602
- const barrelFields = hasBarrel ? {
1603
- main: "./dist/index.cjs",
1604
- module: "./dist/index.mjs"
1605
- } : {};
1606
- const barrelExport = hasBarrel ? { ".": {
1607
- import: "./dist/index.mjs",
1608
- require: "./dist/index.cjs"
1609
- } } : {};
1610
- const entries = {
1611
- "package/package.json": JSON.stringify({
1612
- ...packageInfo,
1613
- type: "module",
1614
- ...barrelFields,
1615
- exports: {
1616
- ...barrelExport,
1617
- "./*": {
1618
- import: "./dist/*.mjs",
1619
- require: "./dist/*.cjs"
1620
- }
1621
- }
1622
- }, null, 2),
1623
- ...Object.fromEntries(resolvedPaths.map(({ content, target }) => [target, content])),
1624
- ...Object.fromEntries(builtEntries)
1625
- };
1626
- const chunks = [];
1627
- for (const [name, content] of Object.entries(entries)) {
1628
- const bytes = Buffer.from(content);
1629
- chunks.push(header(name, bytes.length), bytes, Buffer.alloc((512 - bytes.length % 512) % 512));
1630
- }
1631
- const bytes = await gzipAsync(Buffer.concat([...chunks, Buffer.alloc(1024)]));
1632
- return {
1633
- bytes,
1634
- integrity: `sha512-${(0, node_crypto.createHash)("sha512").update(bytes).digest("base64")}`
1635
- };
1636
- } finally {
1637
- await (0, node_fs_promises.rm)(root, {
1638
- recursive: true,
1639
- force: true
1640
- });
1641
- }
1642
- }
1643
- async function uploadSnapshot({ bytes, uploadPath, studioUrl, token, shutdown }) {
1644
- const uploadUrl = new URL(uploadPath, studioUrl);
1645
- if (uploadUrl.origin !== new URL(studioUrl).origin) throw new Error("Snapshot upload path must stay on the Studio origin");
1646
- const timeout = AbortSignal.timeout(UPLOAD_TIMEOUT_MS);
1647
- const signal = shutdown ? AbortSignal.any([shutdown, timeout]) : timeout;
1648
- const redirect = await fetch(uploadUrl, {
1649
- method: "PUT",
1650
- headers: { Authorization: `Bearer ${token}` },
1651
- redirect: "manual",
1652
- signal
1653
- });
1654
- const storageUrl = redirect.headers.get("location");
1655
- if (redirect.status !== 307 || !storageUrl) throw new Error(`Studio did not provide a storage URL (status ${redirect.status})`);
1656
- const storage = new URL(storageUrl);
1657
- if (storage.protocol !== "https:" && storage.hostname !== "localhost" && storage.hostname !== "127.0.0.1") throw new Error(`Refusing snapshot upload to ${storage.origin}`);
1658
- const response = await fetch(storage, {
1659
- method: "PUT",
1660
- body: new Uint8Array(bytes),
1661
- redirect: "error",
1662
- signal
1663
- });
1664
- if (!response.ok) throw new Error(`Snapshot upload failed with status ${response.status}`);
1665
- }
586
+ var version = "5.5.5";
1666
587
  //#endregion
1667
588
  //#region src/operations/generations.ts
1668
589
  const READ_CONCURRENCY = 50;
@@ -1803,7 +724,7 @@ function relativeStoragePath(root, filePath) {
1803
724
  return ((0, node_path.isAbsolute)(filePath) ? (0, node_path.relative)((0, node_path.resolve)(root), filePath) : filePath).replaceAll("\\", "/");
1804
725
  }
1805
726
  async function resolvePeerDependencies(names) {
1806
- const uniqueNames = [...new Set(names.map(toPackageName))];
727
+ const uniqueNames = [...new Set(names.map(require_resolveConfig.toPackageName))];
1807
728
  const peerDependencies = {};
1808
729
  const missingDependencies = [];
1809
730
  const versions = await Promise.all(uniqueNames.map(async (name) => {
@@ -2108,6 +1029,7 @@ const connectWebSocketRpc = async ({ url, token, instanceId, local }) => {
2108
1029
  /**
2109
1030
  * Register a `kubb:hook:start` listener that spawns the requested command via tinyexec,
2110
1031
  * streams each stdout line as a `kubb:hook:line` event, and calls `kubb:hook:end` with the result.
1032
+ * A failure travels on `kubb:hook:end` only: the caller waiting on it reports `kubb:error` once.
2111
1033
  * Streaming the output lets Kubb Studio render live hook progress over the WebSocket connection.
2112
1034
  *
2113
1035
  * Returns a remover, so a session that runs one generation after another on the same emitter does
@@ -2140,7 +1062,6 @@ function setupHookListener(hooks, root, signal) {
2140
1062
  success: false,
2141
1063
  error
2142
1064
  });
2143
- await hooks.callHook("kubb:error", { error });
2144
1065
  return;
2145
1066
  }
2146
1067
  await hooks.callHook("kubb:hook:end", {
@@ -2160,7 +1081,6 @@ function setupHookListener(hooks, root, signal) {
2160
1081
  success: false,
2161
1082
  error
2162
1083
  });
2163
- await hooks.callHook("kubb:error", { error });
2164
1084
  }
2165
1085
  });
2166
1086
  }
@@ -2310,12 +1230,17 @@ async function generate({ config, hooks, signal }) {
2310
1230
  const line = typeof entry === "string" ? entry : entry.command;
2311
1231
  const [cmd, ...args] = tokenize(line);
2312
1232
  if (!cmd) continue;
2313
- await runHook({
2314
- hooks,
2315
- id: line,
2316
- command: cmd,
2317
- args
2318
- });
1233
+ try {
1234
+ await runHook({
1235
+ hooks,
1236
+ id: line,
1237
+ command: cmd,
1238
+ args
1239
+ });
1240
+ } catch (error) {
1241
+ await hooks.callHook("kubb:error", { error: toError(error) });
1242
+ throw error;
1243
+ }
2319
1244
  await hooks.callHook("kubb:success", { message: `${line} successfully executed` });
2320
1245
  }
2321
1246
  await hooks.callHook("kubb:hooks:end");
@@ -2645,7 +1570,8 @@ var StudioSession = class {
2645
1570
  async #readConfigFileView(source) {
2646
1571
  if (!this.#canEditConfig) return;
2647
1572
  try {
2648
- return readConfig(source ?? await read(this.#options.configFile));
1573
+ const { readConfig } = await Promise.resolve().then(() => require("./configFile-BTVeov27.cjs"));
1574
+ return readConfig(source ?? await require_resolveConfig.read(this.#options.configFile));
2649
1575
  } catch (error) {
2650
1576
  await this.#warn(`Could not read ${this.#options.configFile}: ${getErrorMessage(error)}`);
2651
1577
  return;
@@ -2664,7 +1590,7 @@ var StudioSession = class {
2664
1590
  path: configPath,
2665
1591
  file,
2666
1592
  plugins: config.plugins.map((plugin) => ({
2667
- name: toPackageName(plugin.name),
1593
+ name: require_resolveConfig.toPackageName(plugin.name),
2668
1594
  options: plugin.options ?? {}
2669
1595
  }))
2670
1596
  },
@@ -2757,8 +1683,8 @@ var StudioSession = class {
2757
1683
  await this.#hooks.callHook("studio:command:start", { command });
2758
1684
  const config = await loadConfig();
2759
1685
  const patch = data.config;
2760
- const plugins = await mergePlugins(config.plugins, patch?.plugins);
2761
- const adapter = await mergeAdapter(config.adapter, patch?.adapter);
1686
+ const plugins = await require_resolveConfig.mergePlugins(config.plugins, patch?.plugins);
1687
+ const adapter = await require_resolveConfig.mergeAdapter(config.adapter, patch?.adapter);
2762
1688
  const inputOverride = this.#isSandbox ? patch?.input ?? "" : void 0;
2763
1689
  if (permissions.allowWrite && this.#isSandbox) await this.#warn("Running in a sandbox, so writing files is disabled");
2764
1690
  if (patch?.input && !this.#isSandbox) await this.#warn("Ignored the spec from Studio: generating from a Studio spec is only available to a sandbox agent");
@@ -2817,6 +1743,7 @@ var StudioSession = class {
2817
1743
  disk: disk ? { hashes: disk.hashes } : void 0
2818
1744
  };
2819
1745
  } finally {
1746
+ this.#lastGeneration = void 0;
2820
1747
  if (root !== this.#options.root) await removeJobRoot(root);
2821
1748
  this.#isGenerating = false;
2822
1749
  }
@@ -2847,6 +1774,7 @@ var StudioSession = class {
2847
1774
  }
2848
1775
  if (this.#isGenerating) return refuse("a generation is in progress");
2849
1776
  try {
1777
+ const { writeConfigEdits } = await Promise.resolve().then(() => require("./configFile-BTVeov27.cjs"));
2850
1778
  const { source: patched, outcomes, changed } = await writeConfigEdits({
2851
1779
  filePath: configFile,
2852
1780
  edits
@@ -2878,6 +1806,7 @@ var StudioSession = class {
2878
1806
  const missing = generation.missingDependencies.filter((dependency) => !bundled.has(dependency));
2879
1807
  if (missing.length) return this.#refuse(`Ignored snapshot: missing dependencies: ${missing.join(", ")}`, `Missing dependencies: ${missing.join(", ")}`);
2880
1808
  try {
1809
+ const { createSnapshotPackage, uploadSnapshot } = await Promise.resolve().then(() => require("./snapshotPackage-BwM3LFmZ.cjs"));
2881
1810
  const { files } = await this.#generations.read({
2882
1811
  generation,
2883
1812
  source: "output",