@kubb/studio 0.0.0-canary-20260903193839

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.
@@ -0,0 +1,575 @@
1
+ require("./rolldown-runtime-qbf5tadS.cjs");
2
+ const require_resolveConfig = require("./resolveConfig-Ci-BVhN_.cjs");
3
+ let magicast = require("magicast");
4
+ //#region src/configFile.ts
5
+ /**
6
+ * A valid JavaScript identifier, so an import name can only ever print as `import { name } from`,
7
+ * never as source that breaks out of the import statement.
8
+ */
9
+ const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
10
+ /**
11
+ * `key: value` as an object literal property, in the file's quote and key style.
12
+ *
13
+ * Uses magicast's literal builder for the key/value nodes, then wraps them as a Babel
14
+ * `ObjectProperty`, the type the rest of this file reads.
15
+ */
16
+ function literalProperty({ key, value }) {
17
+ const built = magicast.builders.literal({ [key]: value }).properties[0];
18
+ return {
19
+ type: "ObjectProperty",
20
+ key: built.key,
21
+ value: built.value,
22
+ computed: false,
23
+ shorthand: false
24
+ };
25
+ }
26
+ /**
27
+ * Marks the comment block a `disable-plugin` leaves behind, so `enable-plugin` can find its way
28
+ * back to the exact lines it commented out. Carries the block's line count, so `enable-plugin`
29
+ * restores exactly those lines instead of scanning forward through whatever comments follow.
30
+ */
31
+ const DISABLED_MARKER = "kubb:disabled";
32
+ /**
33
+ * The one line `disable-plugin` writes above the comment block it produces for `plugin`.
34
+ */
35
+ function formatMarker(plugin, lineCount, indent = "") {
36
+ return `${indent}// ${DISABLED_MARKER} ${plugin} ${lineCount}`;
37
+ }
38
+ /**
39
+ * The plugin and comment-block length a marker line names, when `line` is one.
40
+ */
41
+ function parseMarker(line) {
42
+ const trimmed = line.trim();
43
+ if (!trimmed.startsWith(`// ${DISABLED_MARKER} `)) return;
44
+ const match = trimmed.slice(`// ${DISABLED_MARKER} `.length).match(/^(.+)\s+(\d+)$/);
45
+ return match ? {
46
+ plugin: match[1],
47
+ lineCount: Number(match[2])
48
+ } : void 0;
49
+ }
50
+ /**
51
+ * Steps through a config's wrappers to the object literal underneath: a `satisfies`/`as`
52
+ * assertion, a `() => ...` factory, or a factory whose block body returns the config.
53
+ */
54
+ function unwrap(node) {
55
+ if (!node) return;
56
+ if (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression") return unwrap(node.expression);
57
+ if (node.type !== "ArrowFunctionExpression" && node.type !== "FunctionExpression") return node;
58
+ if (node.body.type !== "BlockStatement") return unwrap(node.body);
59
+ return unwrap(node.body.body.find((statement) => statement.type === "ReturnStatement")?.argument);
60
+ }
61
+ /**
62
+ * Every config object in `export default defineConfig(...)`, or why the file is unmanaged.
63
+ *
64
+ * An array export gets one entry per element, matching {@link ConfigRef}'s numeric index.
65
+ *
66
+ * Walks the parsed AST rather than magicast's proxies, which throw on node types they cannot
67
+ * cast, most of what an unmanaged config file is made of.
68
+ */
69
+ function findConfigs(mod) {
70
+ const exported = unwrap((mod.$ast.type === "Program" ? mod.$ast.body : []).find((node) => node.type === "ExportDefaultDeclaration")?.declaration);
71
+ if (!exported) return { reason: "no default export found" };
72
+ if (exported.type !== "CallExpression" || exported.callee.type !== "Identifier" || exported.callee.name !== "defineConfig") return { reason: "default export is not a defineConfig(...) call" };
73
+ const argument = unwrap(exported.arguments[0]);
74
+ if (!argument) return { reason: "defineConfig(...) was called without a config" };
75
+ if (argument.type === "ArrayExpression") {
76
+ const configs = [];
77
+ for (const element of argument.elements) {
78
+ const entry = unwrap(element);
79
+ if (entry?.type !== "ObjectExpression") return { reason: "config is not an object literal" };
80
+ configs.push(entry);
81
+ }
82
+ return { configs };
83
+ }
84
+ if (argument.type !== "ObjectExpression") return { reason: "config is not an object literal" };
85
+ return { configs: [argument] };
86
+ }
87
+ /**
88
+ * The config entry an edit names, defaulting to the first when it names none.
89
+ */
90
+ function selectConfig(configs, ref) {
91
+ if (ref === void 0) return configs[0];
92
+ if (typeof ref === "number") return configs[ref];
93
+ return configs.find((config) => configName(config) === ref);
94
+ }
95
+ function configName(config) {
96
+ const name = property(config, "name");
97
+ return name?.type === "StringLiteral" ? name.value : void 0;
98
+ }
99
+ /**
100
+ * The name of an object literal property, for the two key shapes a config uses: `key: value` and
101
+ * `'key': value`. `undefined` for a computed key, which the patcher never touches.
102
+ */
103
+ function propertyKey(entry) {
104
+ if (entry.key.type === "Identifier") return entry.key.name;
105
+ if (entry.key.type === "StringLiteral") return entry.key.value;
106
+ }
107
+ /**
108
+ * The index of an object literal's own property named `key`, `-1` when it has none.
109
+ */
110
+ function entryIndex({ node, key }) {
111
+ return node.properties.findIndex((entry) => entry.type === "ObjectProperty" && propertyKey(entry) === key);
112
+ }
113
+ /**
114
+ * The value node of an object literal's own property.
115
+ */
116
+ function property(node, key) {
117
+ const index = entryIndex({
118
+ node,
119
+ key
120
+ });
121
+ return index === -1 ? void 0 : node.properties[index].value;
122
+ }
123
+ /**
124
+ * Writes `key: value` on an object literal, replacing the value when the property is already there.
125
+ *
126
+ * An existing property has its value swapped in place rather than being replaced whole, so recast
127
+ * reprints only that value and leaves the object's own layout alone.
128
+ */
129
+ function setProperty({ node, key, value }) {
130
+ const entry = literalProperty({
131
+ key,
132
+ value
133
+ });
134
+ const index = entryIndex({
135
+ node,
136
+ key
137
+ });
138
+ if (index === -1) {
139
+ node.properties.push(entry);
140
+ return;
141
+ }
142
+ node.properties[index].value = entry.value;
143
+ }
144
+ /**
145
+ * Drops `key` from an object literal.
146
+ */
147
+ function removeProperty({ node, key }) {
148
+ const index = entryIndex({
149
+ node,
150
+ key
151
+ });
152
+ if (index !== -1) node.properties.splice(index, 1);
153
+ }
154
+ /**
155
+ * Reads a literal node's value: a primitive, or an object/array built only from primitives.
156
+ * `undefined` for anything else, so a caller can use this both to read a value and to check
157
+ * whether a node is a literal at all.
158
+ */
159
+ function readLiteral(node) {
160
+ if (!node) return;
161
+ if (node.type === "StringLiteral" || node.type === "NumericLiteral" || node.type === "BooleanLiteral") return node.value;
162
+ if (node.type === "NullLiteral") return null;
163
+ if (node.type === "TemplateLiteral") return node.expressions.length === 0 ? node.quasis[0]?.value.cooked ?? "" : void 0;
164
+ if (node.type === "UnaryExpression") {
165
+ const value = readLiteral(node.argument);
166
+ if (typeof value !== "number") return;
167
+ if (node.operator === "-") return -value;
168
+ if (node.operator === "+") return value;
169
+ return;
170
+ }
171
+ if (node.type === "ArrayExpression") {
172
+ const values = node.elements.map((element) => element ? readLiteral(element) : void 0);
173
+ return values.every((value) => value !== void 0) ? values : void 0;
174
+ }
175
+ if (node.type === "ObjectExpression") {
176
+ const entries = {};
177
+ for (const entry of node.properties) {
178
+ if (entry.type !== "ObjectProperty") return;
179
+ const key = propertyKey(entry);
180
+ const value = readLiteral(entry.value);
181
+ if (key === void 0 || value === void 0) return;
182
+ entries[key] = value;
183
+ }
184
+ return entries;
185
+ }
186
+ }
187
+ /**
188
+ * Maps a factory identifier in the file back to the module it was imported from.
189
+ */
190
+ function importedFrom(mod) {
191
+ return new Map(mod.imports.$items.map((item) => [item.local, item.from]));
192
+ }
193
+ /**
194
+ * Every `pluginX(...)` element of a config's plugins array that resolves to an import.
195
+ */
196
+ function pluginCalls(mod, config) {
197
+ const plugins = property(config, "plugins");
198
+ if (plugins?.type !== "ArrayExpression") return [];
199
+ const imports = importedFrom(mod);
200
+ return plugins.elements.flatMap((element) => {
201
+ if (element?.type !== "CallExpression" || element.callee.type !== "Identifier") return [];
202
+ const packageName = imports.get(element.callee.name);
203
+ return packageName ? [{
204
+ importName: element.callee.name,
205
+ packageName,
206
+ call: element
207
+ }] : [];
208
+ });
209
+ }
210
+ /**
211
+ * Plugins a previous `disable-plugin` commented out of this config, keyed by package name.
212
+ *
213
+ * Read from the marker lines rather than the AST, since a commented-out call is no longer a node.
214
+ */
215
+ function disabledMarkers(source) {
216
+ return source.split("\n").flatMap((line, index) => {
217
+ const marker = parseMarker(line);
218
+ return marker ? [{
219
+ packageName: marker.plugin,
220
+ line: index + 1
221
+ }] : [];
222
+ });
223
+ }
224
+ /**
225
+ * Reads which plugins the file declares and which of their options Studio may write.
226
+ *
227
+ * @example
228
+ * ```ts
229
+ * const view = readConfig(await readFile('kubb.config.ts', 'utf8'))
230
+ * if (view.managed) {
231
+ * view.configs.forEach((config) => console.log(config.name, config.plugins.length))
232
+ * }
233
+ * ```
234
+ */
235
+ function readConfig(source) {
236
+ let mod;
237
+ try {
238
+ mod = (0, magicast.parseModule)(source);
239
+ } catch {
240
+ return {
241
+ managed: false,
242
+ reason: "the config file could not be parsed"
243
+ };
244
+ }
245
+ const found = findConfigs(mod);
246
+ if ("reason" in found) return {
247
+ managed: false,
248
+ reason: found.reason
249
+ };
250
+ const importNames = new Map([...importedFrom(mod)].map(([local, from]) => [from, local]));
251
+ const disabled = disabledMarkers(source);
252
+ return {
253
+ managed: true,
254
+ configs: found.configs.map((config) => {
255
+ const plugins = pluginCalls(mod, config).map(({ importName, packageName, call }) => {
256
+ const entries = {};
257
+ const options = call.arguments[0];
258
+ if (options?.type === "ObjectExpression") for (const entry of options.properties) {
259
+ if (entry.type !== "ObjectProperty") continue;
260
+ const key = propertyKey(entry);
261
+ if (key === void 0) continue;
262
+ const value = readLiteral(entry.value);
263
+ entries[key] = value === void 0 ? { literal: false } : {
264
+ literal: true,
265
+ value
266
+ };
267
+ }
268
+ return {
269
+ importName,
270
+ packageName,
271
+ options: entries
272
+ };
273
+ });
274
+ const start = config.loc?.start.line ?? 0;
275
+ const end = config.loc?.end.line ?? Number.POSITIVE_INFINITY;
276
+ for (const { packageName } of disabled.filter((entry) => entry.line >= start && entry.line <= end)) plugins.push({
277
+ importName: importNames.get(packageName) ?? require_resolveConfig.toExportName(packageName),
278
+ packageName,
279
+ options: {},
280
+ disabled: true
281
+ });
282
+ return {
283
+ name: configName(config),
284
+ plugins
285
+ };
286
+ })
287
+ };
288
+ }
289
+ /**
290
+ * Whether a value can be written into a config file as a literal.
291
+ *
292
+ * This is the trust boundary for edits that arrive over the agent WebSocket: a function, `undefined`,
293
+ * or a non-finite number is refused rather than printed into the user's source.
294
+ */
295
+ function isOptionValue(value) {
296
+ if (value === null) return true;
297
+ if (typeof value === "string" || typeof value === "boolean") return true;
298
+ if (typeof value === "number") return Number.isFinite(value);
299
+ if (Array.isArray(value)) return value.every(isOptionValue);
300
+ if (typeof value === "object") return Object.values(value).every(isOptionValue);
301
+ return false;
302
+ }
303
+ /**
304
+ * The options object of a plugin call, when it was called with one.
305
+ */
306
+ function getOptions(call) {
307
+ const options = call.arguments[0];
308
+ return options?.type === "ObjectExpression" ? options : void 0;
309
+ }
310
+ /**
311
+ * The options object of a plugin call, creating an empty one when the plugin was called bare.
312
+ */
313
+ function ensureOptions(call) {
314
+ if (call.arguments.length === 0) call.arguments.push({
315
+ type: "ObjectExpression",
316
+ properties: []
317
+ });
318
+ return getOptions(call);
319
+ }
320
+ /**
321
+ * Walks `path` down to the object holding its last key, descending only through object literals.
322
+ */
323
+ function optionParent(options, path) {
324
+ let object = options;
325
+ for (const [index, key] of path.entries()) {
326
+ if (index === path.length - 1) return {
327
+ object,
328
+ key
329
+ };
330
+ if (property(object, key) === void 0) setProperty({
331
+ node: object,
332
+ key,
333
+ value: {}
334
+ });
335
+ const next = property(object, key);
336
+ if (next?.type !== "ObjectExpression") return { reason: `${key} is not an object, so ${path.join(".")} cannot be reached` };
337
+ object = next;
338
+ }
339
+ return { reason: "no option path given" };
340
+ }
341
+ /**
342
+ * Writes `value` at `path` inside a plugin call's options, creating the options object and any
343
+ * intermediate object along the path as needed. Refuses when the current value at `path` is
344
+ * something other than a literal, so an option customized in code is never overwritten.
345
+ */
346
+ function applySet(call, path, value) {
347
+ if (!isOptionValue(value)) return "the value is not a literal that can be written to a config file";
348
+ const options = ensureOptions(call);
349
+ if (!options) return "the plugin was not called with an object literal";
350
+ const target = optionParent(options, path);
351
+ if ("reason" in target) return target.reason;
352
+ const current = property(target.object, target.key);
353
+ if (current !== void 0 && readLiteral(current) === void 0) return `${path.join(".")} is customized in code`;
354
+ setProperty({
355
+ node: target.object,
356
+ key: target.key,
357
+ value
358
+ });
359
+ }
360
+ /**
361
+ * Deletes the property at `path` inside a plugin call's options, falling the plugin back to its
362
+ * default for that option. Refuses when the value at `path` is not a literal, for the same reason
363
+ * `applySet` does.
364
+ */
365
+ function applyRemove(call, path) {
366
+ const options = getOptions(call);
367
+ if (!options) return "the plugin has no options to remove";
368
+ const target = optionParent(options, path);
369
+ if ("reason" in target) return target.reason;
370
+ const current = property(target.object, target.key);
371
+ if (current === void 0) return `${path.join(".")} is not set`;
372
+ if (readLiteral(current) === void 0) return `${path.join(".")} is customized in code`;
373
+ removeProperty({
374
+ node: target.object,
375
+ key: target.key
376
+ });
377
+ }
378
+ /**
379
+ * Adds a `pluginX(...)` call to a config's plugins array. Refuses when the plugin is already
380
+ * present, or when its import name collides with an unrelated existing import.
381
+ */
382
+ function applyAddPlugin(mod, config, edit) {
383
+ if (!require_resolveConfig.isKubbPluginSpecifier(edit.plugin)) return { reason: `"${edit.plugin}" is not a @kubb/plugin-* package` };
384
+ const importName = edit.importName ?? require_resolveConfig.toExportName(edit.plugin);
385
+ if (!IDENTIFIER.test(importName)) return { reason: `"${importName}" is not a valid import name` };
386
+ if (pluginCalls(mod, config).some((plugin) => plugin.packageName === edit.plugin)) return { reason: `${edit.plugin} is already in the plugins array` };
387
+ const taken = importedFrom(mod).get(importName);
388
+ if (taken && taken !== edit.plugin) return { reason: `${importName} is already imported from ${taken}` };
389
+ const options = edit.options ?? {};
390
+ if (!isOptionValue(options)) return { reason: "the options are not literals that can be written to a config file" };
391
+ const plugins = property(config, "plugins");
392
+ if (plugins?.type !== "ArrayExpression") return { reason: "plugins is not an array literal" };
393
+ const call = Object.keys(options).length ? magicast.builders.functionCall(importName, options) : magicast.builders.functionCall(importName);
394
+ plugins.elements.push(call.$ast);
395
+ return taken ? {} : { addImport: {
396
+ importName,
397
+ moduleSpecifier: edit.plugin
398
+ } };
399
+ }
400
+ /**
401
+ * Comments out a plugin call in place, keeping its options on disk so `enable-plugin` can restore
402
+ * them exactly. Operates on `source` text rather than the AST: a commented-out call is no longer a
403
+ * node magicast can address, and the surrounding array must not reflow when its element count
404
+ * never actually changes.
405
+ */
406
+ function disablePlugin(source, mod, config, plugin) {
407
+ const target = pluginCalls(mod, config).find((entry) => entry.packageName === plugin);
408
+ if (!target) return { reason: `${plugin} is not in the plugins array` };
409
+ const loc = target.call.loc;
410
+ if (!loc?.start || !loc.end) return { reason: `${plugin} has no source location to comment out` };
411
+ const lines = source.split("\n");
412
+ const from = loc.start.line - 1;
413
+ const to = loc.end.line - 1;
414
+ const firstLine = lines[from] ?? "";
415
+ const lastLine = lines[to] ?? "";
416
+ 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` };
417
+ const indent = firstLine.match(/^\s*/)?.[0] ?? "";
418
+ const commented = lines.slice(from, to + 1).map((line) => line.trim() ? `${indent}// ${line.slice(indent.length)}` : indent ? `${indent}//` : "//");
419
+ lines.splice(from, to - from + 1, formatMarker(plugin, commented.length, indent), ...commented);
420
+ return { source: lines.join("\n") };
421
+ }
422
+ /**
423
+ * Uncomments the block a previous `disable-plugin` left behind for `plugin`.
424
+ */
425
+ function enablePlugin(source, plugin) {
426
+ const lines = source.split("\n");
427
+ for (const [index, line] of lines.entries()) {
428
+ const marker = parseMarker(line);
429
+ if (marker?.plugin !== plugin) continue;
430
+ const end = index + 1 + marker.lineCount;
431
+ const restored = lines.slice(index + 1, end).map((commented) => commented.replace(/^(\s*)\/\/ ?/, "$1"));
432
+ lines.splice(index, end - index, ...restored);
433
+ return { source: lines.join("\n") };
434
+ }
435
+ return { reason: `${plugin} is not disabled` };
436
+ }
437
+ /**
438
+ * Re-parses `source` and resolves the config entry an edit targets. Every edit re-parses rather
439
+ * than sharing one module across the batch, since the disable/enable edits rewrite `source` as
440
+ * text and would otherwise leave the others working from a stale tree.
441
+ */
442
+ function parseTarget(source, ref) {
443
+ let mod;
444
+ try {
445
+ mod = (0, magicast.parseModule)(source);
446
+ } catch {
447
+ return { reason: "the config file could not be parsed" };
448
+ }
449
+ const found = findConfigs(mod);
450
+ if ("reason" in found) return { reason: found.reason };
451
+ const config = selectConfig(found.configs, ref);
452
+ if (!config) return { reason: `no config entry found for ${JSON.stringify(ref)}` };
453
+ return {
454
+ mod,
455
+ config
456
+ };
457
+ }
458
+ /**
459
+ * Applies edits to a `kubb.config.ts` in place. Every node the edits do not touch keeps its
460
+ * original text, so comments, formatting, and hand-written code around the config survive.
461
+ *
462
+ * Edits are independent: one that cannot be applied is reported in `outcomes` and the rest still run.
463
+ *
464
+ * @example
465
+ * ```ts
466
+ * const { source, outcomes } = applyConfigEdits(current, [
467
+ * { operation: 'set', plugin: '@kubb/plugin-ts', path: ['enum', 'type'], value: 'enum' },
468
+ * ])
469
+ * ```
470
+ *
471
+ * @note recast always reprints a semicolon on a reprinted statement, so editing a block-body
472
+ * `defineConfig` in a semicolon-free file adds one to the `return` line. This is a known gap.
473
+ * Strip it when `detectCodeFormat` reports `useSemi: false`, if it turns out to matter in practice.
474
+ */
475
+ function applyConfigEdits(source, edits) {
476
+ let current = source;
477
+ const format = (0, magicast.detectCodeFormat)(source);
478
+ const endsWithNewline = source.endsWith("\n");
479
+ const outcomes = edits.map((edit) => {
480
+ const target = parseTarget(current, edit.config);
481
+ if ("reason" in target) return {
482
+ edit,
483
+ applied: false,
484
+ reason: target.reason
485
+ };
486
+ const { mod, config } = target;
487
+ if (edit.operation === "disable-plugin" || edit.operation === "enable-plugin") {
488
+ const result = edit.operation === "disable-plugin" ? disablePlugin(current, mod, config, edit.plugin) : enablePlugin(current, edit.plugin);
489
+ if ("reason" in result) return {
490
+ edit,
491
+ applied: false,
492
+ reason: result.reason
493
+ };
494
+ current = result.source;
495
+ return {
496
+ edit,
497
+ applied: true
498
+ };
499
+ }
500
+ if (edit.operation === "add-plugin") {
501
+ const result = applyAddPlugin(mod, config, edit);
502
+ if ("reason" in result) return {
503
+ edit,
504
+ applied: false,
505
+ reason: result.reason
506
+ };
507
+ const afterLine = lastImportEndLine(mod);
508
+ let next = (0, magicast.generateCode)(mod, { format }).code;
509
+ if (result.addImport) next = insertImportLine({
510
+ source: next,
511
+ afterLine,
512
+ ...result.addImport
513
+ });
514
+ current = withTrailingNewline(next, endsWithNewline);
515
+ return {
516
+ edit,
517
+ applied: true
518
+ };
519
+ }
520
+ const pluginCall = pluginCalls(mod, config).find((plugin) => plugin.packageName === edit.plugin);
521
+ if (!pluginCall) return {
522
+ edit,
523
+ applied: false,
524
+ reason: `${edit.plugin} is not in the plugins array`
525
+ };
526
+ const reason = edit.operation === "set" ? applySet(pluginCall.call, edit.path, edit.value) : applyRemove(pluginCall.call, edit.path);
527
+ if (!reason) current = withTrailingNewline((0, magicast.generateCode)(mod, { format }).code, endsWithNewline);
528
+ return {
529
+ edit,
530
+ applied: !reason,
531
+ reason
532
+ };
533
+ });
534
+ return {
535
+ source: current,
536
+ outcomes,
537
+ changed: current !== source
538
+ };
539
+ }
540
+ /**
541
+ * The 1-based line where the file's last import declaration ends, or `0` when it has none. Read
542
+ * off the parsed module, so a multi-line `import {\n x,\n} from '...'` reports its closing line
543
+ * rather than the `import` keyword.
544
+ */
545
+ function lastImportEndLine(mod) {
546
+ return (mod.$ast.type === "Program" ? mod.$ast.body : []).filter((node) => node.type === "ImportDeclaration").at(-1)?.loc?.end.line ?? 0;
547
+ }
548
+ /**
549
+ * Writes an import after the last one already in the file, matching its quote style and whether it
550
+ * ends in a semicolon. `afterLine` is where that last import ends, `0` for a file with none.
551
+ *
552
+ * Written as plain text rather than through magicast's import builder, which prints a brand-new
553
+ * import declaration with its own default spacing and a semicolon regardless of `format`, since
554
+ * that formatting only governs nodes recast can diff against the original source.
555
+ */
556
+ function insertImportLine({ source, importName, moduleSpecifier, afterLine }) {
557
+ const lines = source.split("\n");
558
+ const lastImportLine = afterLine > 0 ? lines[afterLine - 1] : void 0;
559
+ const quote = lastImportLine?.includes(`"`) ? `"` : `'`;
560
+ const line = `import { ${importName} } from ${quote}${moduleSpecifier}${quote}${lastImportLine?.trimEnd().endsWith(";") ? ";" : ""}`;
561
+ lines.splice(afterLine, 0, ...afterLine > 0 ? [line] : [line, ""]);
562
+ return lines.join("\n");
563
+ }
564
+ /**
565
+ * `generateCode` always drops the file's trailing newline. Restore it when the input had one.
566
+ */
567
+ function withTrailingNewline(code, hadTrailingNewline) {
568
+ if (!hadTrailingNewline || code.endsWith("\n")) return code;
569
+ return `${code}\n`;
570
+ }
571
+ //#endregion
572
+ exports.applyConfigEdits = applyConfigEdits;
573
+ exports.readConfig = readConfig;
574
+
575
+ //# sourceMappingURL=configFile-DjzP1_Ln.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configFile-DjzP1_Ln.cjs","names":["builders","parseModule","toExportName","isKubbPluginSpecifier","detectCodeFormat","generateCode"],"sources":["../src/configFile.ts"],"sourcesContent":["import { builders, detectCodeFormat, generateCode, parseModule } from 'magicast'\nimport type { ASTNode, ProxifiedModule } from 'magicast'\nimport type { ConfigEdit, ConfigEditOutcome, ConfigFileView, ConfigRef, ConfigView, OptionValue, PluginView } from './protocol/index.ts'\nimport { isKubbPluginSpecifier, toExportName } from './resolveConfig.ts'\n\n/**\n * A valid JavaScript identifier, so an import name can only ever print as `import { name } from`,\n * never as source that breaks out of the import statement.\n */\nconst IDENTIFIER = /^[A-Za-z_$][\\w$]*$/\n\n/**\n * A config or plugin options object literal in the file.\n */\ntype ObjectNode = Extract<ASTNode, { type: 'ObjectExpression' }>\n\n/**\n * A `pluginX(...)` call in a config's `plugins` array.\n */\ntype CallNode = Extract<ASTNode, { type: 'CallExpression' }>\n\n/**\n * A `key: value` entry of an object literal.\n */\ntype ObjectPropertyNode = Extract<ASTNode, { type: 'ObjectProperty' }>\n\n/**\n * `key: value` as an object literal property, in the file's quote and key style.\n *\n * Uses magicast's literal builder for the key/value nodes, then wraps them as a Babel\n * `ObjectProperty`, the type the rest of this file reads.\n */\nfunction literalProperty({ key, value }: { key: string; value: OptionValue }): ObjectPropertyNode {\n const built = (builders.literal({ [key]: value }) as unknown as ObjectNode).properties[0] as unknown as ObjectPropertyNode\n return { type: 'ObjectProperty', key: built.key, value: built.value, computed: false, shorthand: false }\n}\n\n/**\n * What `applyConfigEdits` did to a config file.\n */\ntype ApplyResult = {\n /**\n * The file's text after every applicable edit, unchanged from the input when none applied.\n */\n source: string\n /**\n * One entry per edit, in the order they were given.\n */\n outcomes: Array<ConfigEditOutcome>\n /**\n * Whether `source` differs from the input.\n */\n changed: boolean\n}\n\n/**\n * Marks the comment block a `disable-plugin` leaves behind, so `enable-plugin` can find its way\n * back to the exact lines it commented out. Carries the block's line count, so `enable-plugin`\n * restores exactly those lines instead of scanning forward through whatever comments follow.\n */\nconst DISABLED_MARKER = 'kubb:disabled'\n\n/**\n * The one line `disable-plugin` writes above the comment block it produces for `plugin`.\n */\nfunction formatMarker(plugin: string, lineCount: number, indent = ''): string {\n return `${indent}// ${DISABLED_MARKER} ${plugin} ${lineCount}`\n}\n\n/**\n * The plugin and comment-block length a marker line names, when `line` is one.\n */\nfunction parseMarker(line: string): { plugin: string; lineCount: number } | undefined {\n const trimmed = line.trim()\n if (!trimmed.startsWith(`// ${DISABLED_MARKER} `)) {\n return undefined\n }\n\n const match = trimmed.slice(`// ${DISABLED_MARKER} `.length).match(/^(.+)\\s+(\\d+)$/)\n return match ? { plugin: match[1]!, lineCount: Number(match[2]) } : undefined\n}\n\n/**\n * Steps through a config's wrappers to the object literal underneath: a `satisfies`/`as`\n * assertion, a `() => ...` factory, or a factory whose block body returns the config.\n */\nfunction unwrap(node: ASTNode | null | undefined): ASTNode | undefined {\n if (!node) {\n return undefined\n }\n if (node.type === 'TSAsExpression' || node.type === 'TSSatisfiesExpression') {\n return unwrap(node.expression)\n }\n if (node.type !== 'ArrowFunctionExpression' && node.type !== 'FunctionExpression') {\n return node\n }\n if (node.body.type !== 'BlockStatement') {\n return unwrap(node.body)\n }\n\n const returned = node.body.body.find((statement): statement is Extract<ASTNode, { type: 'ReturnStatement' }> => statement.type === 'ReturnStatement')\n return unwrap(returned?.argument)\n}\n\n/**\n * Every config object in `export default defineConfig(...)`, or why the file is unmanaged.\n *\n * An array export gets one entry per element, matching {@link ConfigRef}'s numeric index.\n *\n * Walks the parsed AST rather than magicast's proxies, which throw on node types they cannot\n * cast, most of what an unmanaged config file is made of.\n */\nfunction findConfigs(mod: ProxifiedModule): { configs: Array<ObjectNode> } | { reason: string } {\n const body = mod.$ast.type === 'Program' ? mod.$ast.body : []\n const declaration = body.find((node): node is Extract<ASTNode, { type: 'ExportDefaultDeclaration' }> => node.type === 'ExportDefaultDeclaration')\n\n const exported = unwrap(declaration?.declaration)\n if (!exported) {\n return { reason: 'no default export found' }\n }\n if (exported.type !== 'CallExpression' || exported.callee.type !== 'Identifier' || exported.callee.name !== 'defineConfig') {\n return { reason: 'default export is not a defineConfig(...) call' }\n }\n\n const argument = unwrap(exported.arguments[0])\n if (!argument) {\n return { reason: 'defineConfig(...) was called without a config' }\n }\n if (argument.type === 'ArrayExpression') {\n const configs: Array<ObjectNode> = []\n\n for (const element of argument.elements) {\n const entry = unwrap(element)\n if (entry?.type !== 'ObjectExpression') {\n return { reason: 'config is not an object literal' }\n }\n configs.push(entry)\n }\n return { configs }\n }\n if (argument.type !== 'ObjectExpression') {\n return { reason: 'config is not an object literal' }\n }\n return { configs: [argument] }\n}\n\n/**\n * The config entry an edit names, defaulting to the first when it names none.\n */\nfunction selectConfig(configs: Array<ObjectNode>, ref: ConfigRef | undefined): ObjectNode | undefined {\n if (ref === undefined) {\n return configs[0]\n }\n if (typeof ref === 'number') {\n return configs[ref]\n }\n return configs.find((config) => configName(config) === ref)\n}\n\nfunction configName(config: ObjectNode): string | undefined {\n const name = property(config, 'name')\n return name?.type === 'StringLiteral' ? name.value : undefined\n}\n\n/**\n * The name of an object literal property, for the two key shapes a config uses: `key: value` and\n * `'key': value`. `undefined` for a computed key, which the patcher never touches.\n */\nfunction propertyKey(entry: Extract<ASTNode, { type: 'ObjectProperty' }>): string | undefined {\n if (entry.key.type === 'Identifier') {\n return entry.key.name\n }\n if (entry.key.type === 'StringLiteral') {\n return entry.key.value\n }\n return undefined\n}\n\n/**\n * The index of an object literal's own property named `key`, `-1` when it has none.\n */\nfunction entryIndex({ node, key }: { node: ObjectNode; key: string }): number {\n return node.properties.findIndex((entry) => entry.type === 'ObjectProperty' && propertyKey(entry) === key)\n}\n\n/**\n * The value node of an object literal's own property.\n */\nfunction property(node: ObjectNode, key: string): ASTNode | undefined {\n const index = entryIndex({ node, key })\n return index === -1 ? undefined : (node.properties[index] as ObjectPropertyNode).value\n}\n\n/**\n * Writes `key: value` on an object literal, replacing the value when the property is already there.\n *\n * An existing property has its value swapped in place rather than being replaced whole, so recast\n * reprints only that value and leaves the object's own layout alone.\n */\nfunction setProperty({ node, key, value }: { node: ObjectNode; key: string; value: OptionValue }): void {\n const entry = literalProperty({ key, value })\n const index = entryIndex({ node, key })\n\n if (index === -1) {\n node.properties.push(entry)\n return\n }\n ;(node.properties[index] as ObjectPropertyNode).value = entry.value\n}\n\n/**\n * Drops `key` from an object literal.\n */\nfunction removeProperty({ node, key }: { node: ObjectNode; key: string }): void {\n const index = entryIndex({ node, key })\n if (index !== -1) {\n node.properties.splice(index, 1)\n }\n}\n\n/**\n * Reads a literal node's value: a primitive, or an object/array built only from primitives.\n * `undefined` for anything else, so a caller can use this both to read a value and to check\n * whether a node is a literal at all.\n */\nfunction readLiteral(node: ASTNode | undefined): OptionValue | undefined {\n if (!node) {\n return undefined\n }\n if (node.type === 'StringLiteral' || node.type === 'NumericLiteral' || node.type === 'BooleanLiteral') {\n return node.value\n }\n if (node.type === 'NullLiteral') {\n return null\n }\n if (node.type === 'TemplateLiteral') {\n return node.expressions.length === 0 ? (node.quasis[0]?.value.cooked ?? '') : undefined\n }\n if (node.type === 'UnaryExpression') {\n const value = readLiteral(node.argument)\n if (typeof value !== 'number') {\n return undefined\n }\n if (node.operator === '-') {\n return -value\n }\n if (node.operator === '+') {\n return value\n }\n return undefined\n }\n if (node.type === 'ArrayExpression') {\n const values = node.elements.map((element) => (element ? readLiteral(element) : undefined))\n return values.every((value) => value !== undefined) ? values : undefined\n }\n if (node.type === 'ObjectExpression') {\n const entries: Record<string, OptionValue> = {}\n for (const entry of node.properties) {\n if (entry.type !== 'ObjectProperty') {\n return undefined\n }\n const key = propertyKey(entry)\n const value = readLiteral(entry.value)\n if (key === undefined || value === undefined) {\n return undefined\n }\n entries[key] = value\n }\n return entries\n }\n return undefined\n}\n\n/**\n * Maps a factory identifier in the file back to the module it was imported from.\n */\nfunction importedFrom(mod: ProxifiedModule): Map<string, string> {\n return new Map(mod.imports.$items.map((item) => [item.local, item.from]))\n}\n\n/**\n * Every `pluginX(...)` element of a config's plugins array that resolves to an import.\n */\nfunction pluginCalls(mod: ProxifiedModule, config: ObjectNode): Array<{ importName: string; packageName: string; call: CallNode }> {\n const plugins = property(config, 'plugins')\n if (plugins?.type !== 'ArrayExpression') {\n return []\n }\n\n const imports = importedFrom(mod)\n\n return plugins.elements.flatMap((element) => {\n if (element?.type !== 'CallExpression' || element.callee.type !== 'Identifier') {\n return []\n }\n const packageName = imports.get(element.callee.name)\n return packageName ? [{ importName: element.callee.name, packageName, call: element }] : []\n })\n}\n\n/**\n * Plugins a previous `disable-plugin` commented out of this config, keyed by package name.\n *\n * Read from the marker lines rather than the AST, since a commented-out call is no longer a node.\n */\nfunction disabledMarkers(source: string): Array<{ packageName: string; line: number }> {\n return source.split('\\n').flatMap((line, index) => {\n const marker = parseMarker(line)\n return marker ? [{ packageName: marker.plugin, line: index + 1 }] : []\n })\n}\n\n/**\n * Reads which plugins the file declares and which of their options Studio may write.\n *\n * @example\n * ```ts\n * const view = readConfig(await readFile('kubb.config.ts', 'utf8'))\n * if (view.managed) {\n * view.configs.forEach((config) => console.log(config.name, config.plugins.length))\n * }\n * ```\n */\nexport function readConfig(source: string): ConfigFileView {\n let mod: ProxifiedModule\n try {\n mod = parseModule(source)\n } catch {\n return { managed: false, reason: 'the config file could not be parsed' }\n }\n\n const found = findConfigs(mod)\n if ('reason' in found) {\n return { managed: false, reason: found.reason }\n }\n\n const importNames = new Map([...importedFrom(mod)].map(([local, from]) => [from, local]))\n const disabled = disabledMarkers(source)\n\n return {\n managed: true,\n configs: found.configs.map((config): ConfigView => {\n const plugins = pluginCalls(mod, config).map(({ importName, packageName, call }): PluginView => {\n const entries: PluginView['options'] = {}\n const options = call.arguments[0]\n\n if (options?.type === 'ObjectExpression') {\n for (const entry of options.properties) {\n if (entry.type !== 'ObjectProperty') {\n continue\n }\n const key = propertyKey(entry)\n if (key === undefined) {\n continue\n }\n const value = readLiteral(entry.value)\n entries[key] = value === undefined ? { literal: false } : { literal: true, value }\n }\n }\n return { importName, packageName, options: entries }\n })\n\n const start = config.loc?.start.line ?? 0\n const end = config.loc?.end.line ?? Number.POSITIVE_INFINITY\n\n for (const { packageName } of disabled.filter((entry) => entry.line >= start && entry.line <= end)) {\n plugins.push({\n importName: importNames.get(packageName) ?? toExportName(packageName),\n packageName,\n options: {},\n disabled: true,\n })\n }\n\n return { name: configName(config), plugins }\n }),\n }\n}\n\n/**\n * Whether a value can be written into a config file as a literal.\n *\n * This is the trust boundary for edits that arrive over the agent WebSocket: a function, `undefined`,\n * or a non-finite number is refused rather than printed into the user's source.\n */\nexport function isOptionValue(value: unknown): value is OptionValue {\n if (value === null) {\n return true\n }\n if (typeof value === 'string' || typeof value === 'boolean') {\n return true\n }\n if (typeof value === 'number') {\n return Number.isFinite(value)\n }\n if (Array.isArray(value)) {\n return value.every(isOptionValue)\n }\n if (typeof value === 'object') {\n return Object.values(value).every(isOptionValue)\n }\n return false\n}\n\n/**\n * The options object of a plugin call, when it was called with one.\n */\nfunction getOptions(call: CallNode): ObjectNode | undefined {\n const options = call.arguments[0]\n return options?.type === 'ObjectExpression' ? options : undefined\n}\n\n/**\n * The options object of a plugin call, creating an empty one when the plugin was called bare.\n */\nfunction ensureOptions(call: CallNode): ObjectNode | undefined {\n if (call.arguments.length === 0) {\n call.arguments.push({ type: 'ObjectExpression', properties: [] })\n }\n return getOptions(call)\n}\n\n/**\n * Walks `path` down to the object holding its last key, descending only through object literals.\n */\nfunction optionParent(options: ObjectNode, path: Array<string>): { object: ObjectNode; key: string } | { reason: string } {\n let object = options\n\n for (const [index, key] of path.entries()) {\n if (index === path.length - 1) {\n return { object, key }\n }\n\n if (property(object, key) === undefined) {\n setProperty({ node: object, key, value: {} })\n }\n\n const next = property(object, key)\n if (next?.type !== 'ObjectExpression') {\n return { reason: `${key} is not an object, so ${path.join('.')} cannot be reached` }\n }\n object = next\n }\n return { reason: 'no option path given' }\n}\n\n/**\n * Writes `value` at `path` inside a plugin call's options, creating the options object and any\n * intermediate object along the path as needed. Refuses when the current value at `path` is\n * something other than a literal, so an option customized in code is never overwritten.\n */\nfunction applySet(call: CallNode, path: Array<string>, value: unknown): string | undefined {\n if (!isOptionValue(value)) {\n return 'the value is not a literal that can be written to a config file'\n }\n\n const options = ensureOptions(call)\n if (!options) {\n return 'the plugin was not called with an object literal'\n }\n\n const target = optionParent(options, path)\n if ('reason' in target) {\n return target.reason\n }\n\n const current = property(target.object, target.key)\n if (current !== undefined && readLiteral(current) === undefined) {\n return `${path.join('.')} is customized in code`\n }\n\n setProperty({ node: target.object, key: target.key, value })\n return undefined\n}\n\n/**\n * Deletes the property at `path` inside a plugin call's options, falling the plugin back to its\n * default for that option. Refuses when the value at `path` is not a literal, for the same reason\n * `applySet` does.\n */\nfunction applyRemove(call: CallNode, path: Array<string>): string | undefined {\n const options = getOptions(call)\n if (!options) {\n return 'the plugin has no options to remove'\n }\n\n const target = optionParent(options, path)\n if ('reason' in target) {\n return target.reason\n }\n\n const current = property(target.object, target.key)\n if (current === undefined) {\n return `${path.join('.')} is not set`\n }\n if (readLiteral(current) === undefined) {\n return `${path.join('.')} is customized in code`\n }\n\n removeProperty({ node: target.object, key: target.key })\n return undefined\n}\n\n/**\n * Outcome of `applyAddPlugin`. `addImport` is set when the new plugin call needs an import line\n * the caller must still insert; absent when the import was already there.\n */\ntype AddPluginResult = { reason: string } | { addImport?: { importName: string; moduleSpecifier: string } }\n\n/**\n * Adds a `pluginX(...)` call to a config's plugins array. Refuses when the plugin is already\n * present, or when its import name collides with an unrelated existing import.\n */\nfunction applyAddPlugin(mod: ProxifiedModule, config: ObjectNode, edit: Extract<ConfigEdit, { operation: 'add-plugin' }>): AddPluginResult {\n if (!isKubbPluginSpecifier(edit.plugin)) {\n return { reason: `\"${edit.plugin}\" is not a @kubb/plugin-* package` }\n }\n\n const importName = edit.importName ?? toExportName(edit.plugin)\n if (!IDENTIFIER.test(importName)) {\n return { reason: `\"${importName}\" is not a valid import name` }\n }\n\n if (pluginCalls(mod, config).some((plugin) => plugin.packageName === edit.plugin)) {\n return { reason: `${edit.plugin} is already in the plugins array` }\n }\n\n const taken = importedFrom(mod).get(importName)\n if (taken && taken !== edit.plugin) {\n return { reason: `${importName} is already imported from ${taken}` }\n }\n\n const options = edit.options ?? {}\n if (!isOptionValue(options)) {\n return { reason: 'the options are not literals that can be written to a config file' }\n }\n\n const plugins = property(config, 'plugins')\n if (plugins?.type !== 'ArrayExpression') {\n return { reason: 'plugins is not an array literal' }\n }\n\n const call = Object.keys(options).length ? builders.functionCall(importName, options) : builders.functionCall(importName)\n plugins.elements.push(call.$ast as CallNode)\n\n return taken ? {} : { addImport: { importName, moduleSpecifier: edit.plugin } }\n}\n\n/**\n * Comments out a plugin call in place, keeping its options on disk so `enable-plugin` can restore\n * them exactly. Operates on `source` text rather than the AST: a commented-out call is no longer a\n * node magicast can address, and the surrounding array must not reflow when its element count\n * never actually changes.\n */\nfunction disablePlugin(source: string, mod: ProxifiedModule, config: ObjectNode, plugin: string): { source: string } | { reason: string } {\n const target = pluginCalls(mod, config).find((entry) => entry.packageName === plugin)\n if (!target) {\n return { reason: `${plugin} is not in the plugins array` }\n }\n\n const loc = target.call.loc\n if (!loc?.start || !loc.end) {\n return { reason: `${plugin} has no source location to comment out` }\n }\n\n const lines = source.split('\\n')\n const from = loc.start.line - 1\n const to = loc.end.line - 1\n const firstLine = lines[from] ?? ''\n const lastLine = lines[to] ?? ''\n\n // Only safe to comment out when the call sits alone on its lines: anything else sharing the\n // first line before it, or the last line after it besides a trailing comma, would be swallowed\n // into the comment along with the call, corrupting the file.\n if (firstLine.slice(0, loc.start.column).trim() !== '' || !/^,?\\s*$/.test(lastLine.slice(loc.end.column))) {\n return { reason: `${plugin} shares a line with other code, so it cannot be commented out safely` }\n }\n\n const indent = firstLine.match(/^\\s*/)?.[0] ?? ''\n const commented = lines.slice(from, to + 1).map((line) => (line.trim() ? `${indent}// ${line.slice(indent.length)}` : indent ? `${indent}//` : '//'))\n lines.splice(from, to - from + 1, formatMarker(plugin, commented.length, indent), ...commented)\n\n return { source: lines.join('\\n') }\n}\n\n/**\n * Uncomments the block a previous `disable-plugin` left behind for `plugin`.\n */\nfunction enablePlugin(source: string, plugin: string): { source: string } | { reason: string } {\n const lines = source.split('\\n')\n\n for (const [index, line] of lines.entries()) {\n const marker = parseMarker(line)\n if (marker?.plugin !== plugin) {\n continue\n }\n\n // Bounded by the marker's own line count rather than scanning for trailing `//` lines, so a\n // comment or another disabled block right after this one is left untouched.\n const end = index + 1 + marker.lineCount\n const restored = lines.slice(index + 1, end).map((commented) => commented.replace(/^(\\s*)\\/\\/ ?/, '$1'))\n lines.splice(index, end - index, ...restored)\n\n return { source: lines.join('\\n') }\n }\n\n return { reason: `${plugin} is not disabled` }\n}\n\n/**\n * Re-parses `source` and resolves the config entry an edit targets. Every edit re-parses rather\n * than sharing one module across the batch, since the disable/enable edits rewrite `source` as\n * text and would otherwise leave the others working from a stale tree.\n */\nfunction parseTarget(source: string, ref: ConfigRef | undefined): { mod: ProxifiedModule; config: ObjectNode } | { reason: string } {\n let mod: ProxifiedModule\n try {\n mod = parseModule(source)\n } catch {\n return { reason: 'the config file could not be parsed' }\n }\n\n const found = findConfigs(mod)\n if ('reason' in found) {\n return { reason: found.reason }\n }\n\n const config = selectConfig(found.configs, ref)\n if (!config) {\n return { reason: `no config entry found for ${JSON.stringify(ref)}` }\n }\n\n return { mod, config }\n}\n\n/**\n * Applies edits to a `kubb.config.ts` in place. Every node the edits do not touch keeps its\n * original text, so comments, formatting, and hand-written code around the config survive.\n *\n * Edits are independent: one that cannot be applied is reported in `outcomes` and the rest still run.\n *\n * @example\n * ```ts\n * const { source, outcomes } = applyConfigEdits(current, [\n * { operation: 'set', plugin: '@kubb/plugin-ts', path: ['enum', 'type'], value: 'enum' },\n * ])\n * ```\n *\n * @note recast always reprints a semicolon on a reprinted statement, so editing a block-body\n * `defineConfig` in a semicolon-free file adds one to the `return` line. This is a known gap.\n * Strip it when `detectCodeFormat` reports `useSemi: false`, if it turns out to matter in practice.\n */\nexport function applyConfigEdits(source: string, edits: Array<ConfigEdit>): ApplyResult {\n let current = source\n const format = detectCodeFormat(source)\n const endsWithNewline = source.endsWith('\\n')\n\n const outcomes = edits.map((edit): ConfigEditOutcome => {\n const target = parseTarget(current, edit.config)\n if ('reason' in target) {\n return { edit, applied: false, reason: target.reason }\n }\n const { mod, config } = target\n\n if (edit.operation === 'disable-plugin' || edit.operation === 'enable-plugin') {\n const result = edit.operation === 'disable-plugin' ? disablePlugin(current, mod, config, edit.plugin) : enablePlugin(current, edit.plugin)\n if ('reason' in result) {\n return { edit, applied: false, reason: result.reason }\n }\n current = result.source\n return { edit, applied: true }\n }\n\n if (edit.operation === 'add-plugin') {\n const result = applyAddPlugin(mod, config, edit)\n if ('reason' in result) {\n return { edit, applied: false, reason: result.reason }\n }\n const afterLine = lastImportEndLine(mod)\n let next = generateCode(mod, { format }).code\n if (result.addImport) {\n next = insertImportLine({ source: next, afterLine, ...result.addImport })\n }\n current = withTrailingNewline(next, endsWithNewline)\n return { edit, applied: true }\n }\n\n const pluginCall = pluginCalls(mod, config).find((plugin) => plugin.packageName === edit.plugin)\n if (!pluginCall) {\n return { edit, applied: false, reason: `${edit.plugin} is not in the plugins array` }\n }\n\n const reason = edit.operation === 'set' ? applySet(pluginCall.call, edit.path, edit.value) : applyRemove(pluginCall.call, edit.path)\n if (!reason) {\n current = withTrailingNewline(generateCode(mod, { format }).code, endsWithNewline)\n }\n return { edit, applied: !reason, reason }\n })\n\n return { source: current, outcomes, changed: current !== source }\n}\n\n/**\n * The 1-based line where the file's last import declaration ends, or `0` when it has none. Read\n * off the parsed module, so a multi-line `import {\\n x,\\n} from '...'` reports its closing line\n * rather than the `import` keyword.\n */\nfunction lastImportEndLine(mod: ProxifiedModule): number {\n const body = mod.$ast.type === 'Program' ? mod.$ast.body : []\n\n return body.filter((node) => node.type === 'ImportDeclaration').at(-1)?.loc?.end.line ?? 0\n}\n\n/**\n * Writes an import after the last one already in the file, matching its quote style and whether it\n * ends in a semicolon. `afterLine` is where that last import ends, `0` for a file with none.\n *\n * Written as plain text rather than through magicast's import builder, which prints a brand-new\n * import declaration with its own default spacing and a semicolon regardless of `format`, since\n * that formatting only governs nodes recast can diff against the original source.\n */\nfunction insertImportLine({\n source,\n importName,\n moduleSpecifier,\n afterLine,\n}: {\n source: string\n importName: string\n moduleSpecifier: string\n afterLine: number\n}): string {\n const lines = source.split('\\n')\n\n const lastImportLine = afterLine > 0 ? lines[afterLine - 1] : undefined\n const quote = lastImportLine?.includes(`\"`) ? `\"` : `'`\n const semicolon = lastImportLine?.trimEnd().endsWith(';') ? ';' : ''\n const line = `import { ${importName} } from ${quote}${moduleSpecifier}${quote}${semicolon}`\n\n lines.splice(afterLine, 0, ...(afterLine > 0 ? [line] : [line, '']))\n\n return lines.join('\\n')\n}\n\n/**\n * `generateCode` always drops the file's trailing newline. Restore it when the input had one.\n */\nfunction withTrailingNewline(code: string, hadTrailingNewline: boolean): string {\n if (!hadTrailingNewline || code.endsWith('\\n')) {\n return code\n }\n return `${code}\\n`\n}\n"],"mappings":"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"}