@kubb/studio 5.12.3 → 5.12.4

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
@@ -230,7 +230,7 @@ function getElapsedMs(hrStart) {
230
230
  }
231
231
  //#endregion
232
232
  //#region package.json
233
- var version = "5.12.3";
233
+ var version = "5.12.4";
234
234
  //#endregion
235
235
  //#region src/resolveConfig.ts
236
236
  /**
@@ -586,6 +586,44 @@ async function rebuildExtension({ disk, studioOptions, kind, extensions }) {
586
586
  });
587
587
  }))(mergeOptions(disk.options ?? {}, studioOptions));
588
588
  }
589
+ /** The root `output` members Studio may set, with the values each takes. The rest, `path` and `postGenerate` among them, stay with the config file. */
590
+ const OUTPUT_VALUES = {
591
+ barrel: (value) => value === false || isPlainObject(value) && Object.keys(value).length === 1 && (value.type === "named" || value.type === "all"),
592
+ format: (value) => value === false || [
593
+ "auto",
594
+ "prettier",
595
+ "biome",
596
+ "oxfmt"
597
+ ].includes(value),
598
+ lint: (value) => value === false || [
599
+ "auto",
600
+ "eslint",
601
+ "biome",
602
+ "oxlint"
603
+ ].includes(value),
604
+ clean: (value) => typeof value === "boolean",
605
+ defaultBanner: (value) => value === false || value === "simple" || value === "full"
606
+ };
607
+ /** Whether Studio may set the root `output` member `key`. */
608
+ function isOutputKey(key) {
609
+ return Object.hasOwn(OUTPUT_VALUES, key);
610
+ }
611
+ /** Whether Studio may set the root `output` member `key` to `value`. */
612
+ function isOutputValue({ key, value }) {
613
+ return isOutputKey(key) && OUTPUT_VALUES[key](value);
614
+ }
615
+ /** The members of `output` Studio may see and set. */
616
+ function pickOutput(output) {
617
+ return Object.fromEntries(Object.entries(output).filter(([key]) => isOutputKey(key)));
618
+ }
619
+ /** Merges Studio's valid `output` overrides, with `null` unsetting a member. */
620
+ function mergeOutput({ output, studio }) {
621
+ if (!isPlainObject(studio)) return output;
622
+ return mergeOptions(output, Object.fromEntries(Object.entries(studio).filter(([key, value]) => value === null ? isOutputKey(key) : isOutputValue({
623
+ key,
624
+ value
625
+ }))));
626
+ }
589
627
  /**
590
628
  * Merges Studio-provided adapter option overrides into the disk config's adapter, see {@link rebuildExtension}.
591
629
  */
@@ -643,14 +681,16 @@ function invalidEdit(edit) {
643
681
  if (typeof operation !== "string" || !OPERATIONS.has(operation)) return `unknown operation ${JSON.stringify(operation)}`;
644
682
  if (config !== void 0 && typeof config !== "string" && !Number.isSafeInteger(config)) return "config must name an entry or give its index";
645
683
  if (operation === require_protocol.ConfigEditOperation.SET_INPUT) return typeof input === "string" && input.trim() ? void 0 : "the edit names no input";
646
- if (typeof plugin !== "string" || !plugin) return "the edit names no plugin";
684
+ const isOutput = operation === require_protocol.ConfigEditOperation.SET_OUTPUT || operation === require_protocol.ConfigEditOperation.REMOVE_OUTPUT;
685
+ if (!isOutput && (typeof plugin !== "string" || !plugin)) return "the edit names no plugin";
647
686
  if (kind !== void 0 && (typeof kind !== "string" || !KINDS.has(kind))) return `unknown kind ${JSON.stringify(kind)}`;
648
687
  if (kind !== void 0 && operation !== require_protocol.ConfigEditOperation.SET && operation !== require_protocol.ConfigEditOperation.REMOVE) return `${operation} edits the plugins array, so it takes no kind`;
649
- if (operation === require_protocol.ConfigEditOperation.SET || operation === require_protocol.ConfigEditOperation.REMOVE) {
688
+ if (isOutput || operation === require_protocol.ConfigEditOperation.SET || operation === require_protocol.ConfigEditOperation.REMOVE) {
650
689
  if (!Array.isArray(path) || !path.every((key) => typeof key === "string" && key)) return "path must be a list of option names";
651
690
  if (!path.length) return "no option path given";
652
691
  const unsafe = path.find((key) => !isSafeKey(key));
653
692
  if (unsafe !== void 0) return `${unsafe} cannot be used as an option name`;
693
+ if (isOutput && !isOutputKey(path[0])) return `output.${path[0]} can only be changed in the config file`;
654
694
  }
655
695
  }
656
696
  /** Where a node sits in the source text. */
@@ -951,9 +991,12 @@ function readPartial(node, prefix = []) {
951
991
  }
952
992
  /** The top-level options of a plugin call, each flagged with whether Studio may write it. */
953
993
  function readOptions(call) {
954
- const entries = {};
955
994
  const options = call.arguments[0];
956
- if (options?.type !== "ObjectExpression") return entries;
995
+ return options?.type === "ObjectExpression" ? readObject(options) : {};
996
+ }
997
+ /** The members of an object literal, each flagged with whether Studio may write it. */
998
+ function readObject(options) {
999
+ const entries = {};
957
1000
  for (const [index, entry] of options.properties.entries()) {
958
1001
  const key = memberKey(entry);
959
1002
  if (key === void 0 || !isSafeKey(key)) continue;
@@ -1026,11 +1069,13 @@ function readConfig(source) {
1026
1069
  });
1027
1070
  const [adapter] = factoryCalls(mod, config, "adapter").map(toView);
1028
1071
  const parsers = factoryCalls(mod, config, "parser").map(toView);
1072
+ const output = outputObject(config);
1029
1073
  return {
1030
1074
  name: configName(config),
1031
1075
  plugins,
1032
1076
  ...adapter && { adapter },
1033
- ...parsers.length ? { parsers } : {}
1077
+ ...parsers.length ? { parsers } : {},
1078
+ ...!("reason" in output) && { output: pickOutput(readObject(output.object)) }
1034
1079
  };
1035
1080
  })
1036
1081
  };
@@ -1079,6 +1124,16 @@ function applySet(source, call, path, value, quote) {
1079
1124
  return { source: splice(source, closing, closing, printValue(nest(path, value), quote)) };
1080
1125
  }
1081
1126
  if (options.type !== "ObjectExpression") return { reason: "the factory was not called with an object literal" };
1127
+ return setIn({
1128
+ source,
1129
+ options,
1130
+ path,
1131
+ value,
1132
+ quote
1133
+ });
1134
+ }
1135
+ /** Writes the literal `value` at `path` inside `options`, adding any intermediate object it needs. */
1136
+ function setIn({ source, options, path, value, quote }) {
1082
1137
  const target = walk(options, path);
1083
1138
  if ("reason" in target) return target;
1084
1139
  if ("missing" in target) {
@@ -1096,6 +1151,34 @@ function applySetInput(source, config, input, quote) {
1096
1151
  if (isShadowed(config, found.index)) return { reason: "a spread after input could override it" };
1097
1152
  return { source: splice(source, rangeOf(found.member.value).start, rangeOf(found.member.value).end, value) };
1098
1153
  }
1154
+ /** The config's root `output` object literal, or why Studio cannot edit inside it. */
1155
+ function outputObject(config) {
1156
+ const found = findMember(config, "output");
1157
+ if (found?.member.type !== "ObjectProperty" || found.member.value.type !== "ObjectExpression") return { reason: "output is not an object literal" };
1158
+ if (isShadowed(config, found.index)) return { reason: "a spread after output could override it" };
1159
+ return { object: found.member.value };
1160
+ }
1161
+ /** Writes or drops a member of the config's root `output` object. */
1162
+ function applyOutputEdit({ source, config, edit, quote }) {
1163
+ const output = outputObject(config);
1164
+ if ("reason" in output) return output;
1165
+ if (edit.operation === require_protocol.ConfigEditOperation.REMOVE_OUTPUT) return removeIn({
1166
+ source,
1167
+ options: output.object,
1168
+ path: edit.path
1169
+ });
1170
+ if (!isOptionValue(edit.value) || !isOutputValue({
1171
+ key: edit.path[0],
1172
+ value: nest(edit.path.slice(1), edit.value)
1173
+ })) return { reason: `the value is not one output.${edit.path[0]} takes` };
1174
+ return setIn({
1175
+ source,
1176
+ options: output.object,
1177
+ path: edit.path,
1178
+ value: edit.value,
1179
+ quote
1180
+ });
1181
+ }
1099
1182
  /**
1100
1183
  * Deletes the option at `path`, falling the plugin back to its default for it. An option the file
1101
1184
  * does not set already has its default, so removing it changes nothing.
@@ -1104,6 +1187,14 @@ function applyRemove(source, call, path) {
1104
1187
  const options = call.arguments[0];
1105
1188
  if (!options) return { source };
1106
1189
  if (options.type !== "ObjectExpression") return { reason: "the factory was not called with an object literal" };
1190
+ return removeIn({
1191
+ source,
1192
+ options,
1193
+ path
1194
+ });
1195
+ }
1196
+ /** Deletes the member at `path` inside `options`. A path that is not there already holds its default. */
1197
+ function removeIn({ source, options, path }) {
1107
1198
  const target = walk(options, path);
1108
1199
  if ("reason" in target) return target;
1109
1200
  if ("missing" in target) return { source };
@@ -1193,6 +1284,12 @@ function applyEdit({ source, edit, quote, extensions }) {
1193
1284
  const config = findConfig(parsed);
1194
1285
  if (!config) return { reason: `no config entry found for ${JSON.stringify(ref)}` };
1195
1286
  if (edit.operation === require_protocol.ConfigEditOperation.SET_INPUT) return applySetInput(source, config, edit.input, quote);
1287
+ if (edit.operation === require_protocol.ConfigEditOperation.SET_OUTPUT || edit.operation === require_protocol.ConfigEditOperation.REMOVE_OUTPUT) return applyOutputEdit({
1288
+ source,
1289
+ config,
1290
+ edit,
1291
+ quote
1292
+ });
1196
1293
  if (edit.operation === require_protocol.ConfigEditOperation.DISABLE_PLUGIN) {
1197
1294
  const result = disablePlugin(source, parsed.mod, config, edit.plugin);
1198
1295
  if (!("reason" in result) || !result.reason.includes("shares a line")) return result;
@@ -2392,6 +2489,10 @@ var Agent = class extends capnweb.RpcTarget {
2392
2489
  ...config,
2393
2490
  root,
2394
2491
  input: checkout?.path ?? studioInput ?? config.input,
2492
+ output: mergeOutput({
2493
+ output: config.output,
2494
+ studio: patch?.output
2495
+ }),
2395
2496
  storage: outputRoot ? stagingStorage({
2396
2497
  root,
2397
2498
  directory: outputRoot
@@ -2851,6 +2952,7 @@ function toEntryView(config, extensions) {
2851
2952
  ...config.name !== void 0 && { name: config.name },
2852
2953
  ...input !== void 0 && { input },
2853
2954
  output: config.output.path,
2955
+ outputOptions: toJSONOptions(pickOutput(config.output)),
2854
2956
  plugins: config.plugins.map((plugin) => ({
2855
2957
  name: toStudioName({
2856
2958
  name: (0, _kubb_core.resolveMeta)(plugin).name,