@kubb/studio 5.5.0 → 5.5.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.js CHANGED
@@ -16,7 +16,7 @@ import { Diagnostics, Hookable, cacheStorage, createKubb, fsStorage, memoryStora
16
16
  import { builders, detectCodeFormat, generateCode, parseModule } from "magicast";
17
17
  import { existsSync } from "node:fs";
18
18
  import { pathToFileURL } from "node:url";
19
- import { mergeDeep } from "remeda";
19
+ import { isPlainObject } from "remeda";
20
20
  import { gzip } from "node:zlib";
21
21
  import { build } from "tsdown";
22
22
  import { RpcTarget, newWebSocketRpcSession } from "capnweb";
@@ -695,7 +695,7 @@ function resolveAgentCapacity(env = process.env) {
695
695
  }
696
696
  //#endregion
697
697
  //#region package.json
698
- var version = "5.5.0";
698
+ var version = "5.5.2";
699
699
  //#endregion
700
700
  //#region src/operations/resolveConfig.ts
701
701
  /**
@@ -808,8 +808,46 @@ async function resolvePlugins(plugins) {
808
808
  }));
809
809
  }
810
810
  /**
811
+ * Whether `value` comes back unchanged from a JSON round trip. A function, a `RegExp` or another
812
+ * class instance does not: the agent reports options to Studio as JSON, so what Studio sends back
813
+ * for such a value is a lossy copy (`macros: [{ name }]` without its hooks, `pattern: {}`).
814
+ */
815
+ function survivesJson(value) {
816
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
817
+ if (typeof value === "number") return Number.isFinite(value);
818
+ if (Array.isArray(value)) return value.every(survivesJson);
819
+ if (isPlainObject(value)) return Object.values(value).every(survivesJson);
820
+ return false;
821
+ }
822
+ /**
823
+ * Deep-merges Studio's options over the disk options, with Studio taking priority, except where the
824
+ * disk value cannot survive JSON. Studio only ever saw a lossy copy of such a value, so the disk
825
+ * value is kept rather than replaced by what Studio echoes back. Plain objects merge key by key, so
826
+ * Studio can still change a literal that sits next to a function (e.g. `group.type` beside `group.name`).
827
+ *
828
+ * @example
829
+ * ```ts
830
+ * mergeOptions({ macros: [camelCaseMacro], syntaxType: 'type' }, { macros: [{ name: 'camelCase' }], syntaxType: 'interface' })
831
+ * // { macros: [camelCaseMacro], syntaxType: 'interface' }
832
+ * ```
833
+ */
834
+ function mergeOptions(disk, studio) {
835
+ const merged = { ...disk };
836
+ for (const [key, value] of Object.entries(studio)) {
837
+ const current = disk[key];
838
+ if (isPlainObject(current) && isPlainObject(value)) {
839
+ merged[key] = mergeOptions(current, value);
840
+ continue;
841
+ }
842
+ if (current !== void 0 && !survivesJson(current)) continue;
843
+ merged[key] = value;
844
+ }
845
+ return merged;
846
+ }
847
+ /**
811
848
  * Merges studio plugin options with disk config plugins.
812
- * Studio takes priority: options from studio win over disk, and a plugin Studio explicitly
849
+ * Studio takes priority: options from studio win over disk (except values only the disk config
850
+ * can express, see {@link mergeOptions}), and a plugin Studio explicitly
813
851
  * disabled is dropped even when the disk config still lists it. Disk plugins without a studio
814
852
  * counterpart are kept as-is. Studio plugins not present on disk are appended.
815
853
  *
@@ -828,7 +866,7 @@ async function mergePlugins(diskPlugins, studioPlugins) {
828
866
  const merged = await Promise.all(activeDiskPlugins.map(async (diskPlugin) => {
829
867
  const studioEntry = studioEntryByName.get(diskPlugin.name);
830
868
  if (!studioEntry) return diskPlugin;
831
- const options = mergeDeep(diskPlugin.options ?? {}, studioEntry.options ?? {});
869
+ const options = mergeOptions(diskPlugin.options ?? {}, studioEntry.options ?? {});
832
870
  const [resolved] = await resolvePlugins([{
833
871
  name: studioEntry.name,
834
872
  options
@@ -852,7 +890,7 @@ async function mergeAdapter(diskAdapter, studioOptions) {
852
890
  const packageName = `@kubb/adapter-${diskAdapter.name}`;
853
891
  const factory = (await importFromProject(packageName))[toExportName(packageName)];
854
892
  if (typeof factory !== "function") return diskAdapter;
855
- return factory(mergeDeep(diskAdapter.options ?? {}, studioOptions));
893
+ return factory(mergeOptions(diskAdapter.options ?? {}, studioOptions));
856
894
  }
857
895
  //#endregion
858
896
  //#region src/operations/configFile.ts