@contractkit/plugin-bruno 0.9.1

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 ADDED
@@ -0,0 +1,631 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/index.ts
5
+ import { resolve, basename as basename2, dirname } from "path";
6
+ import { existsSync, readFileSync, rmSync, readdirSync, rmdirSync } from "fs";
7
+
8
+ // src/codegen-bruno.ts
9
+ import { resolveSecurity, resolveModifiers, SECURITY_NONE } from "@contractkit/core";
10
+ import { basename } from "path";
11
+ var MANIFEST_FILENAME = ".contractkit-bruno-manifest.json";
12
+ function generateOpenCollection(roots, options) {
13
+ const files = [];
14
+ const modelMap = buildModelMap(options.contractRoots ?? []);
15
+ const authOpts = options.auth;
16
+ const defaultScheme = authOpts?.defaultScheme ? authOpts.schemes?.[authOpts.defaultScheme] : void 0;
17
+ const randomExamples = options.randomExamples ?? false;
18
+ const includeInternal = options.includeInternal ?? true;
19
+ files.push({
20
+ relativePath: "opencollection.yml",
21
+ content: generateCollectionRoot(options.collectionName, defaultScheme)
22
+ });
23
+ files.push({
24
+ relativePath: "environments/local.yml",
25
+ content: generateEnvFile(defaultScheme)
26
+ });
27
+ for (let rootIdx = 0; rootIdx < roots.length; rootIdx++) {
28
+ const root = roots[rootIdx];
29
+ const folder = root.meta["area"] ? slugifyName(root.meta["area"]) : deriveFolderName(root.file);
30
+ const displayName = (root.meta["area"] ?? folder).charAt(0).toUpperCase() + (root.meta["area"] ?? folder).slice(1);
31
+ files.push({
32
+ relativePath: `${folder}/folder.yml`,
33
+ content: generateFolderFile(displayName, rootIdx + 1)
34
+ });
35
+ const subarea = root.meta["subarea"];
36
+ const subareaSlug = subarea ? slugifyName(subarea) : void 0;
37
+ const requestDir = subareaSlug ? `${folder}/${subareaSlug}` : folder;
38
+ if (subareaSlug) {
39
+ const subareaDisplayName = subarea.charAt(0).toUpperCase() + subarea.slice(1);
40
+ files.push({
41
+ relativePath: `${requestDir}/folder.yml`,
42
+ content: generateFolderFile(subareaDisplayName, 1)
43
+ });
44
+ }
45
+ let seq = 1;
46
+ for (const route of root.routes) {
47
+ for (const op of route.operations) {
48
+ if (!includeInternal && resolveModifiers(route, op).includes("internal")) continue;
49
+ const requestName = op.name ?? route.path;
50
+ const fileName = op.name ? `${slugifyName(op.name)}.yml` : `${op.method}-${sanitizePath(route.path)}.yml`;
51
+ files.push({
52
+ relativePath: `${requestDir}/${fileName}`,
53
+ content: generateRequestFile(route, op, requestName, seq, modelMap, root, defaultScheme, randomExamples)
54
+ });
55
+ seq++;
56
+ }
57
+ }
58
+ }
59
+ const trackedPaths = [
60
+ ...files.map((f) => f.relativePath),
61
+ MANIFEST_FILENAME
62
+ ].sort();
63
+ files.push({
64
+ relativePath: MANIFEST_FILENAME,
65
+ content: JSON.stringify({
66
+ files: trackedPaths
67
+ }, null, 2) + "\n"
68
+ });
69
+ return files;
70
+ }
71
+ __name(generateOpenCollection, "generateOpenCollection");
72
+ function parseManifest(content) {
73
+ try {
74
+ const parsed = JSON.parse(content);
75
+ if (Array.isArray(parsed?.files) && parsed.files.every((f) => typeof f === "string")) {
76
+ return parsed.files;
77
+ }
78
+ } catch {
79
+ }
80
+ return [];
81
+ }
82
+ __name(parseManifest, "parseManifest");
83
+ function generateCollectionRoot(name, scheme) {
84
+ const lines = [
85
+ `opencollection: "1.0.0"`,
86
+ `info:`,
87
+ ` name: ${yamlString(name)}`
88
+ ];
89
+ if (scheme) {
90
+ lines.push(``);
91
+ lines.push(`request:`);
92
+ lines.push(...renderAuthBlock(scheme, " "));
93
+ }
94
+ lines.push(``);
95
+ return lines.join("\n");
96
+ }
97
+ __name(generateCollectionRoot, "generateCollectionRoot");
98
+ function generateEnvFile(scheme) {
99
+ const lines = [
100
+ `name: Local`,
101
+ `variables:`,
102
+ ` - name: baseUrl`,
103
+ ` value: "http://localhost:3000"`
104
+ ];
105
+ if (scheme) {
106
+ for (const varName of authEnvVarNames(scheme)) {
107
+ lines.push(` - name: ${varName}`);
108
+ lines.push(` value: ""`);
109
+ }
110
+ }
111
+ lines.push(``);
112
+ return lines.join("\n");
113
+ }
114
+ __name(generateEnvFile, "generateEnvFile");
115
+ function generateFolderFile(name, seq) {
116
+ return [
117
+ `info:`,
118
+ ` name: ${yamlString(name)}`,
119
+ ` type: folder`,
120
+ ` seq: ${seq}`,
121
+ ``
122
+ ].join("\n");
123
+ }
124
+ __name(generateFolderFile, "generateFolderFile");
125
+ function generateRequestFile(route, op, name, seq, modelMap, root, defaultScheme, randomExamples = false) {
126
+ const lines = [];
127
+ lines.push(`info:`);
128
+ lines.push(` name: ${yamlString(name)}`);
129
+ lines.push(` type: http`);
130
+ lines.push(` seq: ${seq}`);
131
+ lines.push(``);
132
+ lines.push(`http:`);
133
+ lines.push(` method: ${op.method.toUpperCase()}`);
134
+ lines.push(` url: ${yamlString(`{{baseUrl}}${openCollectionPath(route.path)}`)}`);
135
+ const pathParams = extractPathParamNames(route.path).map((n) => ({
136
+ name: n,
137
+ type: findParamType(route.params, n, modelMap),
138
+ optional: false,
139
+ kind: "path"
140
+ }));
141
+ const queryParams = op.query ? expandParamSource(op.query, modelMap).map((e) => ({
142
+ ...e,
143
+ kind: "query"
144
+ })) : [];
145
+ const allParams = [
146
+ ...pathParams,
147
+ ...queryParams
148
+ ];
149
+ if (allParams.length > 0) {
150
+ lines.push(` params:`);
151
+ for (const p of allParams) {
152
+ lines.push(` - name: ${p.name}`);
153
+ lines.push(` value: ${paramExampleValue(p.type, p.default, randomExamples)}`);
154
+ lines.push(` type: ${p.kind}`);
155
+ if (p.optional && p.kind === "query") lines.push(` disabled: true`);
156
+ }
157
+ }
158
+ if (op.headers) {
159
+ const headerEntries = expandParamSource(op.headers, modelMap);
160
+ if (headerEntries.length > 0) {
161
+ lines.push(` headers:`);
162
+ for (const h of headerEntries) {
163
+ lines.push(` - name: ${h.name}`);
164
+ lines.push(` value: ${paramExampleValue(h.type, h.default, randomExamples)}`);
165
+ if (h.optional) lines.push(` disabled: true`);
166
+ }
167
+ }
168
+ }
169
+ if (defaultScheme) {
170
+ const security = root ? resolveSecurity(route, op, root) : op.security ?? route.security;
171
+ if (security === SECURITY_NONE) {
172
+ lines.push(` auth:`);
173
+ lines.push(` type: none`);
174
+ } else {
175
+ lines.push(` auth: inherit`);
176
+ }
177
+ }
178
+ if (op.request && op.request.bodies.length > 0) {
179
+ const preferredOrder = [
180
+ "application/json",
181
+ "application/x-www-form-urlencoded",
182
+ "multipart/form-data"
183
+ ];
184
+ const primary = preferredOrder.map((ct) => op.request.bodies.find((b) => b.contentType === ct)).find((b) => b !== void 0) ?? op.request.bodies[0];
185
+ lines.push(` body:`);
186
+ if (primary.contentType === "multipart/form-data") {
187
+ lines.push(` type: multipart-form`);
188
+ lines.push(` data: []`);
189
+ } else if (primary.contentType === "application/x-www-form-urlencoded") {
190
+ lines.push(` type: form-urlencoded`);
191
+ lines.push(` data: []`);
192
+ } else {
193
+ const json = JSON.stringify(typeToExampleValue(primary.bodyType, modelMap, randomExamples), null, 2);
194
+ lines.push(` type: json`);
195
+ lines.push(` data: |`);
196
+ for (const jsonLine of json.split("\n")) {
197
+ lines.push(` ${jsonLine}`);
198
+ }
199
+ }
200
+ }
201
+ const expectedStatus = pickAssertionStatus(op.responses);
202
+ const assertedResponse = op.responses.find((r) => r.statusCode === expectedStatus);
203
+ const requiredHeaders = (assertedResponse?.headers ?? []).filter((h) => !h.optional);
204
+ if (expectedStatus !== void 0) {
205
+ lines.push(``);
206
+ lines.push(`runtime:`);
207
+ lines.push(` assertions:`);
208
+ lines.push(` - expression: res.status`);
209
+ lines.push(` operator: eq`);
210
+ lines.push(` value: "${expectedStatus}"`);
211
+ for (const h of requiredHeaders) {
212
+ lines.push(` - expression: res.headers["${h.name.toLowerCase()}"]`);
213
+ lines.push(` operator: isDefined`);
214
+ lines.push(` value: ""`);
215
+ }
216
+ }
217
+ const docs = buildRequestDocs(route, op, assertedResponse);
218
+ if (docs) {
219
+ lines.push(``);
220
+ lines.push(`docs: |-`);
221
+ for (const docLine of docs.split("\n")) {
222
+ lines.push(` ${docLine}`);
223
+ }
224
+ }
225
+ lines.push(``);
226
+ return lines.join("\n");
227
+ }
228
+ __name(generateRequestFile, "generateRequestFile");
229
+ function pickAssertionStatus(responses) {
230
+ const success = responses.find((r) => r.statusCode >= 200 && r.statusCode < 300);
231
+ return success?.statusCode ?? responses[0]?.statusCode;
232
+ }
233
+ __name(pickAssertionStatus, "pickAssertionStatus");
234
+ function buildRequestDocs(route, op, assertedResponse) {
235
+ const parts = [];
236
+ if (route.description) parts.push(route.description.trim());
237
+ if (op.description) parts.push(op.description.trim());
238
+ const headers = assertedResponse?.headers ?? [];
239
+ if (headers.length > 0) {
240
+ const lines = [
241
+ "**Response headers**",
242
+ ""
243
+ ];
244
+ for (const h of headers) {
245
+ const tag = h.optional ? "optional" : "required";
246
+ const desc = h.description ? ` \u2014 ${h.description}` : "";
247
+ lines.push(`- \`${h.name}\` (${tag})${desc}`);
248
+ }
249
+ parts.push(lines.join("\n"));
250
+ }
251
+ return parts.length > 0 ? parts.join("\n\n") : void 0;
252
+ }
253
+ __name(buildRequestDocs, "buildRequestDocs");
254
+ function renderAuthBlock(scheme, indent) {
255
+ const i = indent;
256
+ if (scheme.type === "http" && scheme.scheme === "bearer") {
257
+ return [
258
+ `${i}auth:`,
259
+ `${i} type: bearer`,
260
+ `${i} token: "{{token}}"`
261
+ ];
262
+ }
263
+ if (scheme.type === "http" && scheme.scheme === "basic") {
264
+ return [
265
+ `${i}auth:`,
266
+ `${i} type: basic`,
267
+ `${i} username: "{{username}}"`,
268
+ `${i} password: "{{password}}"`
269
+ ];
270
+ }
271
+ if (scheme.type === "apiKey" && scheme.in === "header") {
272
+ const headerName = scheme.name ?? "X-Api-Key";
273
+ return [
274
+ `${i}auth:`,
275
+ `${i} type: apikey`,
276
+ `${i} key: ${headerName}`,
277
+ `${i} value: "{{apiKey}}"`,
278
+ `${i} placement: header`
279
+ ];
280
+ }
281
+ return [];
282
+ }
283
+ __name(renderAuthBlock, "renderAuthBlock");
284
+ function authEnvVarNames(scheme) {
285
+ if (scheme.type === "http" && scheme.scheme === "bearer") return [
286
+ "token"
287
+ ];
288
+ if (scheme.type === "http" && scheme.scheme === "basic") return [
289
+ "username",
290
+ "password"
291
+ ];
292
+ if (scheme.type === "apiKey") return [
293
+ "apiKey"
294
+ ];
295
+ return [];
296
+ }
297
+ __name(authEnvVarNames, "authEnvVarNames");
298
+ function buildModelMap(contractRoots) {
299
+ const map = /* @__PURE__ */ new Map();
300
+ for (const root of contractRoots) {
301
+ for (const model of root.models) {
302
+ map.set(model.name, model);
303
+ }
304
+ }
305
+ return map;
306
+ }
307
+ __name(buildModelMap, "buildModelMap");
308
+ function resolveModelFields(model, modelMap) {
309
+ const collected = [];
310
+ if (model.bases) {
311
+ for (const base of model.bases) {
312
+ const baseModel = modelMap.get(base);
313
+ if (baseModel) collected.push(...resolveModelFields(baseModel, modelMap));
314
+ }
315
+ }
316
+ return [
317
+ ...collected,
318
+ ...model.fields
319
+ ];
320
+ }
321
+ __name(resolveModelFields, "resolveModelFields");
322
+ function expandParamSource(source, modelMap) {
323
+ if (source.kind === "params") {
324
+ return source.nodes.map((n) => ({
325
+ name: n.name,
326
+ type: n.type,
327
+ default: n.default,
328
+ optional: n.optional
329
+ }));
330
+ }
331
+ if (source.kind === "ref") {
332
+ const model = modelMap.get(source.name);
333
+ if (model) {
334
+ return resolveModelFields(model, modelMap).filter((f) => f.visibility !== "readonly").map((f) => ({
335
+ name: f.name,
336
+ type: f.type,
337
+ default: f.default,
338
+ optional: f.optional
339
+ }));
340
+ }
341
+ const name = source.name.charAt(0).toLowerCase() + source.name.slice(1);
342
+ return [
343
+ {
344
+ name,
345
+ type: void 0,
346
+ optional: false
347
+ }
348
+ ];
349
+ }
350
+ if (source.node.kind === "inlineObject") {
351
+ return source.node.fields.map((f) => ({
352
+ name: f.name,
353
+ type: f.type,
354
+ default: f.default,
355
+ optional: f.optional
356
+ }));
357
+ }
358
+ return [];
359
+ }
360
+ __name(expandParamSource, "expandParamSource");
361
+ function findParamType(source, name, modelMap) {
362
+ if (!source) return void 0;
363
+ if (source.kind === "params") return source.nodes.find((n) => n.name === name)?.type;
364
+ if (source.kind === "ref") {
365
+ const model = modelMap.get(source.name);
366
+ if (model) return resolveModelFields(model, modelMap).find((f) => f.name === name)?.type;
367
+ }
368
+ if (source.kind === "type" && source.node.kind === "inlineObject") {
369
+ return source.node.fields.find((f) => f.name === name)?.type;
370
+ }
371
+ return void 0;
372
+ }
373
+ __name(findParamType, "findParamType");
374
+ function paramExampleValue(type, defaultValue, randomExamples = false) {
375
+ if (defaultValue !== void 0) return `"${defaultValue}"`;
376
+ if (!type) return '""';
377
+ if (type.kind === "enum") return type.values.length > 0 ? `"${type.values[0]}"` : '""';
378
+ if (type.kind === "literal") return `"${type.value}"`;
379
+ if (type.kind !== "scalar") return '""';
380
+ if (randomExamples) {
381
+ const random = randomScalarTemplate(type.name);
382
+ if (random !== void 0) return `"${random}"`;
383
+ }
384
+ switch (type.name) {
385
+ case "uuid":
386
+ return '"00000000-0000-0000-0000-000000000000"';
387
+ case "email":
388
+ return '"user@example.com"';
389
+ case "url":
390
+ return '"https://example.com"';
391
+ case "number":
392
+ case "int":
393
+ case "bigint":
394
+ return '"0"';
395
+ case "boolean":
396
+ return '"true"';
397
+ case "date":
398
+ return '"2024-01-01"';
399
+ case "time":
400
+ return '"00:00:00"';
401
+ case "datetime":
402
+ return '"2024-01-01T00:00:00Z"';
403
+ case "duration":
404
+ return '"PT1H"';
405
+ default:
406
+ return '""';
407
+ }
408
+ }
409
+ __name(paramExampleValue, "paramExampleValue");
410
+ function randomScalarTemplate(name) {
411
+ switch (name) {
412
+ case "uuid":
413
+ return "{{$randomUUID}}";
414
+ case "email":
415
+ return "{{$randomEmail}}";
416
+ case "url":
417
+ return "{{$randomUrl}}";
418
+ case "number":
419
+ case "int":
420
+ case "bigint":
421
+ return "{{$randomInt}}";
422
+ case "boolean":
423
+ return "{{$randomBoolean}}";
424
+ case "datetime":
425
+ return "{{$isoTimestamp}}";
426
+ default:
427
+ return void 0;
428
+ }
429
+ }
430
+ __name(randomScalarTemplate, "randomScalarTemplate");
431
+ function typeToExampleValue(type, modelMap, randomExamples = false) {
432
+ switch (type.kind) {
433
+ case "scalar":
434
+ switch (type.name) {
435
+ case "string":
436
+ return "";
437
+ case "email":
438
+ return randomExamples ? "{{$randomEmail}}" : "user@example.com";
439
+ case "url":
440
+ return randomExamples ? "{{$randomUrl}}" : "https://example.com";
441
+ case "uuid":
442
+ return randomExamples ? "{{$randomUUID}}" : "00000000-0000-0000-0000-000000000000";
443
+ case "number":
444
+ case "int":
445
+ case "bigint":
446
+ return 0;
447
+ case "boolean":
448
+ return true;
449
+ case "date":
450
+ return "2024-01-01";
451
+ case "time":
452
+ return "00:00:00";
453
+ case "datetime":
454
+ return randomExamples ? "{{$isoTimestamp}}" : "2024-01-01T00:00:00Z";
455
+ case "duration":
456
+ return "PT1H";
457
+ case "null":
458
+ return null;
459
+ default:
460
+ return null;
461
+ }
462
+ case "enum":
463
+ return type.values[0] ?? "";
464
+ case "literal":
465
+ return type.value;
466
+ case "array":
467
+ return [
468
+ typeToExampleValue(type.item, modelMap, randomExamples)
469
+ ];
470
+ case "tuple":
471
+ return type.items.map((t) => typeToExampleValue(t, modelMap, randomExamples));
472
+ case "record":
473
+ return {};
474
+ case "union":
475
+ return type.members.length > 0 ? typeToExampleValue(type.members[0], modelMap, randomExamples) : null;
476
+ case "discriminatedUnion":
477
+ return type.members.length > 0 ? typeToExampleValue(type.members[0], modelMap, randomExamples) : null;
478
+ case "intersection":
479
+ return {};
480
+ case "ref": {
481
+ const model = modelMap.get(type.name);
482
+ if (!model) return {};
483
+ if (model.type) return typeToExampleValue(model.type, modelMap, randomExamples);
484
+ return modelToExampleObject(model, modelMap, randomExamples);
485
+ }
486
+ case "lazy":
487
+ return typeToExampleValue(type.inner, modelMap, randomExamples);
488
+ case "inlineObject":
489
+ return fieldsToExampleObject(type.fields, modelMap, randomExamples);
490
+ default:
491
+ return null;
492
+ }
493
+ }
494
+ __name(typeToExampleValue, "typeToExampleValue");
495
+ function modelToExampleObject(model, modelMap, randomExamples = false) {
496
+ return fieldsToExampleObject(resolveModelFields(model, modelMap), modelMap, randomExamples);
497
+ }
498
+ __name(modelToExampleObject, "modelToExampleObject");
499
+ function fieldsToExampleObject(fields, modelMap, randomExamples = false) {
500
+ const obj = {};
501
+ for (const field of fields) {
502
+ if (field.visibility === "readonly") continue;
503
+ if (field.default !== void 0) {
504
+ obj[field.name] = field.default;
505
+ } else if (field.optional) {
506
+ obj[field.name] = null;
507
+ } else {
508
+ obj[field.name] = typeToExampleValue(field.type, modelMap, randomExamples);
509
+ }
510
+ }
511
+ return obj;
512
+ }
513
+ __name(fieldsToExampleObject, "fieldsToExampleObject");
514
+ function openCollectionPath(path) {
515
+ return path.replace(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g, ":$1");
516
+ }
517
+ __name(openCollectionPath, "openCollectionPath");
518
+ function slugifyName(name) {
519
+ const result = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
520
+ return result || "request";
521
+ }
522
+ __name(slugifyName, "slugifyName");
523
+ function sanitizePath(path) {
524
+ const result = path.replace(/^\//, "").replace(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g, "$1").replace(/\//g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
525
+ return result || "root";
526
+ }
527
+ __name(sanitizePath, "sanitizePath");
528
+ function extractPathParamNames(path) {
529
+ return [
530
+ ...path.matchAll(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g)
531
+ ].map((m) => m[1]);
532
+ }
533
+ __name(extractPathParamNames, "extractPathParamNames");
534
+ function deriveFolderName(file) {
535
+ return basename(file).replace(/\.(op|ck)$/, "");
536
+ }
537
+ __name(deriveFolderName, "deriveFolderName");
538
+ function yamlString(value) {
539
+ if (/[:{}[\],&*#?|<>=!%@`"']/.test(value) || /^\s|\s$/.test(value)) {
540
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
541
+ }
542
+ return value;
543
+ }
544
+ __name(yamlString, "yamlString");
545
+
546
+ // src/index.ts
547
+ var plugin = {
548
+ name: "bruno",
549
+ cacheKey: "bruno",
550
+ async generateTargets({ opRoots, contractRoots }, ctx) {
551
+ const { auth, ...config } = ctx.options;
552
+ const base = config.baseDir ? resolve(ctx.rootDir, config.baseDir) : ctx.rootDir;
553
+ const outDir = resolve(base, config.output ?? "bruno-collection");
554
+ const collectionName = config.collectionName ?? basename2(ctx.rootDir);
555
+ cleanupTrackedFiles(outDir);
556
+ const files = generateOpenCollection(opRoots, {
557
+ collectionName,
558
+ contractRoots,
559
+ auth,
560
+ randomExamples: config.randomExamples ?? true,
561
+ includeInternal: config.includeInternal
562
+ });
563
+ for (const { relativePath, content } of files) {
564
+ ctx.emitFile(resolve(outDir, relativePath), content);
565
+ }
566
+ }
567
+ };
568
+ var index_default = plugin;
569
+ function createBrunoPlugin(config, rootDir, auth) {
570
+ return {
571
+ name: "bruno",
572
+ cacheKey: `bruno:${JSON.stringify(config)}`,
573
+ async generateTargets({ opRoots, contractRoots }, ctx) {
574
+ const base = config.baseDir ? resolve(rootDir, config.baseDir) : rootDir;
575
+ const outDir = resolve(base, config.output ?? "bruno-collection");
576
+ const collectionName = config.collectionName ?? basename2(rootDir);
577
+ cleanupTrackedFiles(outDir);
578
+ const files = generateOpenCollection(opRoots, {
579
+ collectionName,
580
+ contractRoots,
581
+ auth,
582
+ randomExamples: config.randomExamples ?? true
583
+ });
584
+ for (const { relativePath, content } of files) {
585
+ ctx.emitFile(resolve(outDir, relativePath), content);
586
+ }
587
+ }
588
+ };
589
+ }
590
+ __name(createBrunoPlugin, "createBrunoPlugin");
591
+ function cleanupTrackedFiles(outDir) {
592
+ const manifestPath = resolve(outDir, MANIFEST_FILENAME);
593
+ if (!existsSync(manifestPath)) return;
594
+ let tracked;
595
+ try {
596
+ tracked = parseManifest(readFileSync(manifestPath, "utf-8"));
597
+ } catch {
598
+ return;
599
+ }
600
+ const removedDirs = /* @__PURE__ */ new Set();
601
+ for (const rel of tracked) {
602
+ const abs = resolve(outDir, rel);
603
+ if (existsSync(abs)) {
604
+ rmSync(abs, {
605
+ force: true
606
+ });
607
+ removedDirs.add(dirname(abs));
608
+ }
609
+ }
610
+ for (const dir of removedDirs) {
611
+ let current = dir;
612
+ while (current.startsWith(outDir) && current !== outDir) {
613
+ try {
614
+ if (readdirSync(current).length === 0) {
615
+ rmdirSync(current);
616
+ current = dirname(current);
617
+ } else {
618
+ break;
619
+ }
620
+ } catch {
621
+ break;
622
+ }
623
+ }
624
+ }
625
+ }
626
+ __name(cleanupTrackedFiles, "cleanupTrackedFiles");
627
+ export {
628
+ createBrunoPlugin,
629
+ index_default as default
630
+ };
631
+ //# sourceMappingURL=index.js.map