@contractkit/plugin-bruno 1.3.0 → 1.4.0

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.
@@ -23,30 +23,30 @@
23
23
  <div class='clearfix'>
24
24
 
25
25
  <div class='fl pad1y space-right2'>
26
- <span class="strong">79.11% </span>
26
+ <span class="strong">80.98% </span>
27
27
  <span class="quiet">Statements</span>
28
- <span class='fraction'>303/383</span>
28
+ <span class='fraction'>345/426</span>
29
29
  </div>
30
30
 
31
31
 
32
32
  <div class='fl pad1y space-right2'>
33
- <span class="strong">71.65% </span>
33
+ <span class="strong">74.19% </span>
34
34
  <span class="quiet">Branches</span>
35
- <span class='fraction'>225/314</span>
35
+ <span class='fraction'>253/341</span>
36
36
  </div>
37
37
 
38
38
 
39
39
  <div class='fl pad1y space-right2'>
40
- <span class="strong">85.96% </span>
40
+ <span class="strong">83.82% </span>
41
41
  <span class="quiet">Functions</span>
42
- <span class='fraction'>49/57</span>
42
+ <span class='fraction'>57/68</span>
43
43
  </div>
44
44
 
45
45
 
46
46
  <div class='fl pad1y space-right2'>
47
- <span class="strong">79.52% </span>
47
+ <span class="strong">81.5% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>268/337</span>
49
+ <span class='fraction'>304/373</span>
50
50
  </div>
51
51
 
52
52
 
@@ -61,7 +61,7 @@
61
61
  </div>
62
62
  </template>
63
63
  </div>
64
- <div class='status-line medium'></div>
64
+ <div class='status-line high'></div>
65
65
  <div class="pad1">
66
66
  <table class="coverage-summary">
67
67
  <thead>
@@ -80,32 +80,32 @@
80
80
  </thead>
81
81
  <tbody><tr>
82
82
  <td class="file high" data-value="codegen-bruno.ts"><a href="codegen-bruno.ts.html">codegen-bruno.ts</a></td>
83
- <td data-value="87.31" class="pic high">
84
- <div class="chart"><div class="cover-fill" style="width: 87%"></div><div class="cover-empty" style="width: 13%"></div></div>
83
+ <td data-value="88.26" class="pic high">
84
+ <div class="chart"><div class="cover-fill" style="width: 88%"></div><div class="cover-empty" style="width: 12%"></div></div>
85
85
  </td>
86
- <td data-value="87.31" class="pct high">87.31%</td>
87
- <td data-value="331" class="abs high">289/331</td>
88
- <td data-value="76.72" class="pct medium">76.72%</td>
89
- <td data-value="275" class="abs medium">211/275</td>
90
- <td data-value="92.15" class="pct high">92.15%</td>
91
- <td data-value="51" class="abs high">47/51</td>
92
- <td data-value="88.58" class="pct high">88.58%</td>
93
- <td data-value="289" class="abs high">256/289</td>
86
+ <td data-value="88.26" class="pct high">88.26%</td>
87
+ <td data-value="375" class="abs high">331/375</td>
88
+ <td data-value="78.1" class="pct medium">78.1%</td>
89
+ <td data-value="306" class="abs medium">239/306</td>
90
+ <td data-value="93.22" class="pct high">93.22%</td>
91
+ <td data-value="59" class="abs high">55/59</td>
92
+ <td data-value="89.29" class="pct high">89.29%</td>
93
+ <td data-value="327" class="abs high">292/327</td>
94
94
  </tr>
95
95
 
96
96
  <tr>
97
97
  <td class="file low" data-value="index.ts"><a href="index.ts.html">index.ts</a></td>
98
- <td data-value="26.92" class="pic low">
99
- <div class="chart"><div class="cover-fill" style="width: 26%"></div><div class="cover-empty" style="width: 74%"></div></div>
98
+ <td data-value="27.45" class="pic low">
99
+ <div class="chart"><div class="cover-fill" style="width: 27%"></div><div class="cover-empty" style="width: 73%"></div></div>
100
100
  </td>
101
- <td data-value="26.92" class="pct low">26.92%</td>
102
- <td data-value="52" class="abs low">14/52</td>
103
- <td data-value="35.89" class="pct low">35.89%</td>
104
- <td data-value="39" class="abs low">14/39</td>
105
- <td data-value="33.33" class="pct low">33.33%</td>
106
- <td data-value="6" class="abs low">2/6</td>
107
- <td data-value="25" class="pct low">25%</td>
108
- <td data-value="48" class="abs low">12/48</td>
101
+ <td data-value="27.45" class="pct low">27.45%</td>
102
+ <td data-value="51" class="abs low">14/51</td>
103
+ <td data-value="40" class="pct low">40%</td>
104
+ <td data-value="35" class="abs low">14/35</td>
105
+ <td data-value="22.22" class="pct low">22.22%</td>
106
+ <td data-value="9" class="abs low">2/9</td>
107
+ <td data-value="26.08" class="pct low">26.08%</td>
108
+ <td data-value="46" class="abs low">12/46</td>
109
109
  </tr>
110
110
 
111
111
  </tbody>
@@ -116,7 +116,7 @@
116
116
  <div class='footer quiet pad2 space-top1 center small'>
117
117
  Code coverage generated by
118
118
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
119
- at 2026-05-06T12:37:12.770Z
119
+ at 2026-05-07T11:09:13.542Z
120
120
  </div>
121
121
  <script src="../prettify.js"></script>
122
122
  <script>
@@ -23,30 +23,30 @@
23
23
  <div class='clearfix'>
24
24
 
25
25
  <div class='fl pad1y space-right2'>
26
- <span class="strong">26.92% </span>
26
+ <span class="strong">27.45% </span>
27
27
  <span class="quiet">Statements</span>
28
- <span class='fraction'>14/52</span>
28
+ <span class='fraction'>14/51</span>
29
29
  </div>
30
30
 
31
31
 
32
32
  <div class='fl pad1y space-right2'>
33
- <span class="strong">35.89% </span>
33
+ <span class="strong">40% </span>
34
34
  <span class="quiet">Branches</span>
35
- <span class='fraction'>14/39</span>
35
+ <span class='fraction'>14/35</span>
36
36
  </div>
37
37
 
38
38
 
39
39
  <div class='fl pad1y space-right2'>
40
- <span class="strong">33.33% </span>
40
+ <span class="strong">22.22% </span>
41
41
  <span class="quiet">Functions</span>
42
- <span class='fraction'>2/6</span>
42
+ <span class='fraction'>2/9</span>
43
43
  </div>
44
44
 
45
45
 
46
46
  <div class='fl pad1y space-right2'>
47
- <span class="strong">25% </span>
47
+ <span class="strong">26.08% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>12/48</span>
49
+ <span class='fraction'>12/46</span>
50
50
  </div>
51
51
 
52
52
 
@@ -245,7 +245,16 @@
245
245
  <a name='L180'></a><a href='#L180'>180</a>
246
246
  <a name='L181'></a><a href='#L181'>181</a>
247
247
  <a name='L182'></a><a href='#L182'>182</a>
248
- <a name='L183'></a><a href='#L183'>183</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
248
+ <a name='L183'></a><a href='#L183'>183</a>
249
+ <a name='L184'></a><a href='#L184'>184</a>
250
+ <a name='L185'></a><a href='#L185'>185</a>
251
+ <a name='L186'></a><a href='#L186'>186</a>
252
+ <a name='L187'></a><a href='#L187'>187</a>
253
+ <a name='L188'></a><a href='#L188'>188</a>
254
+ <a name='L189'></a><a href='#L189'>189</a>
255
+ <a name='L190'></a><a href='#L190'>190</a>
256
+ <a name='L191'></a><a href='#L191'>191</a>
257
+ <a name='L192'></a><a href='#L192'>192</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
249
258
  <span class="cline-any cline-neutral">&nbsp;</span>
250
259
  <span class="cline-any cline-neutral">&nbsp;</span>
251
260
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -316,15 +325,10 @@
316
325
  <span class="cline-any cline-neutral">&nbsp;</span>
317
326
  <span class="cline-any cline-neutral">&nbsp;</span>
318
327
  <span class="cline-any cline-neutral">&nbsp;</span>
319
- <span class="cline-any cline-neutral">&nbsp;</span>
320
- <span class="cline-any cline-no">&nbsp;</span>
321
- <span class="cline-any cline-no">&nbsp;</span>
322
328
  <span class="cline-any cline-no">&nbsp;</span>
323
329
  <span class="cline-any cline-no">&nbsp;</span>
324
330
  <span class="cline-any cline-neutral">&nbsp;</span>
325
- <span class="cline-any cline-no">&nbsp;</span>
326
331
  <span class="cline-any cline-neutral">&nbsp;</span>
327
- <span class="cline-any cline-no">&nbsp;</span>
328
332
  <span class="cline-any cline-neutral">&nbsp;</span>
329
333
  <span class="cline-any cline-neutral">&nbsp;</span>
330
334
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -332,8 +336,6 @@
332
336
  <span class="cline-any cline-neutral">&nbsp;</span>
333
337
  <span class="cline-any cline-neutral">&nbsp;</span>
334
338
  <span class="cline-any cline-neutral">&nbsp;</span>
335
- <span class="cline-any cline-no">&nbsp;</span>
336
- <span class="cline-any cline-no">&nbsp;</span>
337
339
  <span class="cline-any cline-neutral">&nbsp;</span>
338
340
  <span class="cline-any cline-neutral">&nbsp;</span>
339
341
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -347,9 +349,13 @@
347
349
  <span class="cline-any cline-neutral">&nbsp;</span>
348
350
  <span class="cline-any cline-neutral">&nbsp;</span>
349
351
  <span class="cline-any cline-neutral">&nbsp;</span>
352
+ <span class="cline-any cline-no">&nbsp;</span>
353
+ <span class="cline-any cline-neutral">&nbsp;</span>
354
+ <span class="cline-any cline-neutral">&nbsp;</span>
350
355
  <span class="cline-any cline-neutral">&nbsp;</span>
351
356
  <span class="cline-any cline-neutral">&nbsp;</span>
352
357
  <span class="cline-any cline-neutral">&nbsp;</span>
358
+ <span class="cline-any cline-no">&nbsp;</span>
353
359
  <span class="cline-any cline-neutral">&nbsp;</span>
354
360
  <span class="cline-any cline-neutral">&nbsp;</span>
355
361
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -357,26 +363,24 @@
357
363
  <span class="cline-any cline-neutral">&nbsp;</span>
358
364
  <span class="cline-any cline-neutral">&nbsp;</span>
359
365
  <span class="cline-any cline-neutral">&nbsp;</span>
360
- <span class="cline-any cline-no">&nbsp;</span>
361
366
  <span class="cline-any cline-neutral">&nbsp;</span>
362
367
  <span class="cline-any cline-neutral">&nbsp;</span>
363
368
  <span class="cline-any cline-neutral">&nbsp;</span>
364
369
  <span class="cline-any cline-neutral">&nbsp;</span>
365
- <span class="cline-any cline-no">&nbsp;</span>
366
- <span class="cline-any cline-no">&nbsp;</span>
367
- <span class="cline-any cline-no">&nbsp;</span>
368
370
  <span class="cline-any cline-neutral">&nbsp;</span>
369
- <span class="cline-any cline-no">&nbsp;</span>
370
371
  <span class="cline-any cline-neutral">&nbsp;</span>
371
- <span class="cline-any cline-no">&nbsp;</span>
372
372
  <span class="cline-any cline-neutral">&nbsp;</span>
373
373
  <span class="cline-any cline-neutral">&nbsp;</span>
374
374
  <span class="cline-any cline-neutral">&nbsp;</span>
375
375
  <span class="cline-any cline-neutral">&nbsp;</span>
376
376
  <span class="cline-any cline-neutral">&nbsp;</span>
377
377
  <span class="cline-any cline-neutral">&nbsp;</span>
378
+ <span class="cline-any cline-no">&nbsp;</span>
379
+ <span class="cline-any cline-no">&nbsp;</span>
380
+ <span class="cline-any cline-no">&nbsp;</span>
378
381
  <span class="cline-any cline-neutral">&nbsp;</span>
379
382
  <span class="cline-any cline-no">&nbsp;</span>
383
+ <span class="cline-any cline-neutral">&nbsp;</span>
380
384
  <span class="cline-any cline-no">&nbsp;</span>
381
385
  <span class="cline-any cline-neutral">&nbsp;</span>
382
386
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -388,27 +392,40 @@
388
392
  <span class="cline-any cline-neutral">&nbsp;</span>
389
393
  <span class="cline-any cline-neutral">&nbsp;</span>
390
394
  <span class="cline-any cline-neutral">&nbsp;</span>
395
+ <span class="cline-any cline-no">&nbsp;</span>
391
396
  <span class="cline-any cline-neutral">&nbsp;</span>
392
397
  <span class="cline-any cline-neutral">&nbsp;</span>
398
+ <span class="cline-any cline-no">&nbsp;</span>
393
399
  <span class="cline-any cline-neutral">&nbsp;</span>
394
400
  <span class="cline-any cline-no">&nbsp;</span>
395
401
  <span class="cline-any cline-no">&nbsp;</span>
396
402
  <span class="cline-any cline-neutral">&nbsp;</span>
397
403
  <span class="cline-any cline-neutral">&nbsp;</span>
404
+ <span class="cline-any cline-neutral">&nbsp;</span>
405
+ <span class="cline-any cline-neutral">&nbsp;</span>
406
+ <span class="cline-any cline-neutral">&nbsp;</span>
407
+ <span class="cline-any cline-no">&nbsp;</span>
408
+ <span class="cline-any cline-no">&nbsp;</span>
398
409
  <span class="cline-any cline-no">&nbsp;</span>
399
410
  <span class="cline-any cline-no">&nbsp;</span>
400
411
  <span class="cline-any cline-neutral">&nbsp;</span>
401
412
  <span class="cline-any cline-no">&nbsp;</span>
402
413
  <span class="cline-any cline-neutral">&nbsp;</span>
403
414
  <span class="cline-any cline-neutral">&nbsp;</span>
415
+ <span class="cline-any cline-neutral">&nbsp;</span>
416
+ <span class="cline-any cline-neutral">&nbsp;</span>
417
+ <span class="cline-any cline-neutral">&nbsp;</span>
418
+ <span class="cline-any cline-neutral">&nbsp;</span>
419
+ <span class="cline-any cline-neutral">&nbsp;</span>
420
+ <span class="cline-any cline-neutral">&nbsp;</span>
421
+ <span class="cline-any cline-neutral">&nbsp;</span>
422
+ <span class="cline-any cline-no">&nbsp;</span>
404
423
  <span class="cline-any cline-no">&nbsp;</span>
405
424
  <span class="cline-any cline-no">&nbsp;</span>
406
425
  <span class="cline-any cline-no">&nbsp;</span>
407
426
  <span class="cline-any cline-no">&nbsp;</span>
408
427
  <span class="cline-any cline-no">&nbsp;</span>
409
428
  <span class="cline-any cline-no">&nbsp;</span>
410
- <span class="cline-any cline-neutral">&nbsp;</span>
411
- <span class="cline-any cline-neutral">&nbsp;</span>
412
429
  <span class="cline-any cline-neutral">&nbsp;</span>
413
430
  <span class="cline-any cline-neutral">&nbsp;</span>
414
431
  <span class="cline-any cline-no">&nbsp;</span>
@@ -427,11 +444,12 @@
427
444
  <span class="cline-any cline-neutral">&nbsp;</span>
428
445
  <span class="cline-any cline-neutral">&nbsp;</span>
429
446
  <span class="cline-any cline-neutral">&nbsp;</span>
447
+ <span class="cline-any cline-neutral">&nbsp;</span>
430
448
  <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { resolve, basename, dirname } from 'node:path';
431
449
  import { existsSync, readFileSync, rmSync, readdirSync, rmdirSync } from 'node:fs';
432
- import { generateOpenCollection, MANIFEST_FILENAME, parseManifest } from './codegen-bruno.js';
450
+ import { generateOpenCollectionIncremental, MANIFEST_FILENAME, parseManifest, emptyManifest } from './codegen-bruno.js';
433
451
  import type { BrunoSecurityScheme } from './codegen-bruno.js';
434
- import type { ContractKitPlugin, PluginValue } from '@contractkit/core';
452
+ import type { ContractKitPlugin, PluginContext, PluginValue, OpRootNode, ContractRootNode, IncrementalManifest } from '@contractkit/core';
435
453
  &nbsp;
436
454
  /** Configuration accepted by the Bruno plugin, both via `contractkit.config.json` and `createBrunoPlugin`. */
437
455
  export interface BrunoPluginConfig {
@@ -496,27 +514,10 @@ function describe(value: PluginValue): string {
496
514
  &nbsp;
497
515
  const plugin: ContractKitPlugin = {
498
516
  name: 'bruno',
499
- cacheKey: 'bruno',
500
517
  validateExtension: validateBrunoExtension,
501
518
  async <span class="fstat-no" title="function not covered" >generateTargets({</span> opRoots, contractRoots }, ctx) {
502
519
  const { auth, ...config } = <span class="cstat-no" title="statement not covered" >ctx.options </span>as BrunoPluginOptions;
503
- const base = <span class="cstat-no" title="statement not covered" >config.baseDir ? resolve(ctx.rootDir, config.baseDir) : ctx.rootDir;</span>
504
- const <span class="cstat-no" title="statement not covered" >outDir = resolve(base, config.output ?? 'bruno-collection');</span>
505
- const collectionName = <span class="cstat-no" title="statement not covered" >config.collectionName ?? basename(ctx.rootDir);</span>
506
- &nbsp;
507
- <span class="cstat-no" title="statement not covered" > cleanupTrackedFiles(outDir);</span>
508
- &nbsp;
509
- const <span class="cstat-no" title="statement not covered" >files = generateOpenCollection(opRoots, {</span>
510
- collectionName,
511
- contractRoots,
512
- auth,
513
- randomExamples: config.randomExamples ?? true,
514
- includeInternal: config.includeInternal,
515
- environments: config.environments,
516
- });
517
- <span class="cstat-no" title="statement not covered" > for (const { relativePath, content } of files) {</span>
518
- <span class="cstat-no" title="statement not covered" > ctx.emitFile(resolve(outDir, relativePath), content);</span>
519
- }
520
+ <span class="cstat-no" title="statement not covered" > await runBrunoCodegen(opRoots, contractRoots, ctx, config, auth);</span>
520
521
  },
521
522
  };
522
523
  &nbsp;
@@ -541,58 +542,83 @@ export function <span class="fstat-no" title="function not covered" >createBruno
541
542
  ): ContractKitPlugin {
542
543
  <span class="cstat-no" title="statement not covered" > return {</span>
543
544
  name: 'bruno',
544
- cacheKey: `bruno:${JSON.stringify(config)}`,
545
545
  validateExtension: validateBrunoExtension,
546
546
  async <span class="fstat-no" title="function not covered" >generateTargets({</span> opRoots, contractRoots }, ctx) {
547
- const base = <span class="cstat-no" title="statement not covered" >config.baseDir ? resolve(rootDir, config.baseDir) : rootDir;</span>
548
- const <span class="cstat-no" title="statement not covered" >outDir = resolve(base, config.output ?? 'bruno-collection');</span>
549
- const collectionName = <span class="cstat-no" title="statement not covered" >config.collectionName ?? basename(rootDir);</span>
550
- &nbsp;
551
- <span class="cstat-no" title="statement not covered" > cleanupTrackedFiles(outDir);</span>
552
- &nbsp;
553
- const <span class="cstat-no" title="statement not covered" >files = generateOpenCollection(opRoots, {</span>
554
- collectionName,
555
- contractRoots,
556
- auth,
557
- randomExamples: config.randomExamples ?? true,
558
- includeInternal: config.includeInternal,
559
- environments: config.environments,
560
- });
561
- <span class="cstat-no" title="statement not covered" > for (const { relativePath, content } of files) {</span>
562
- <span class="cstat-no" title="statement not covered" > ctx.emitFile(resolve(outDir, relativePath), content);</span>
563
- }
547
+ // The factory captures rootDir at creation time; ctx.rootDir may differ when the
548
+ // plugin is loaded via a config file, so respect the explicit one passed in here.
549
+ <span class="cstat-no" title="statement not covered" > await runBrunoCodegen(opRoots, contractRoots, { ...ctx, rootDir }, config, auth);</span>
564
550
  },
565
551
  };
566
552
  }
567
553
  &nbsp;
568
554
  /**
569
- * Delete files this plugin generated on the previous run, leaving anything
570
- * the user added (custom .bru files, scripts, secrets, etc.) untouched.
555
+ * Shared orchestration used by both the default export and {@link createBrunoPlugin}.
571
556
  *
572
- * On first run or after manual deletion of the manifest — nothing is
573
- * removed; stale files from prior versions linger until manually cleaned.
557
+ * Reads the prior manifest, runs the cache-aware codegen, deletes any files that
558
+ * are no longer produced, and emits the changed files plus the new manifest. When
559
+ * `ctx.cacheEnabled` is `false` (e.g. `--force`) the prior manifest is ignored so
560
+ * every op regenerates.
574
561
  */
575
- function <span class="fstat-no" title="function not covered" >cleanupTrackedFiles(o</span>utDir: string): void {
576
- const <span class="cstat-no" title="statement not covered" >manifestPath = resolve(outDir, MANIFEST_FILENAME);</span>
577
- <span class="cstat-no" title="statement not covered" > if (!existsSync(manifestPath)) <span class="cstat-no" title="statement not covered" >return;</span></span>
562
+ async function <span class="fstat-no" title="function not covered" >runBrunoCodegen(</span>
563
+ opRoots: OpRootNode[],
564
+ contractRoots: ContractRootNode[],
565
+ ctx: PluginContext,
566
+ config: BrunoPluginConfig,
567
+ auth: BrunoPluginOptions['auth'],
568
+ ): Promise&lt;void&gt; {
569
+ const base = <span class="cstat-no" title="statement not covered" >config.baseDir ? resolve(ctx.rootDir, config.baseDir) : ctx.rootDir;</span>
570
+ const <span class="cstat-no" title="statement not covered" >outDir = resolve(base, config.output ?? 'bruno-collection');</span>
571
+ const collectionName = <span class="cstat-no" title="statement not covered" >config.collectionName ?? basename(ctx.rootDir);</span>
572
+ &nbsp;
573
+ const prevManifest: IncrementalManifest = <span class="cstat-no" title="statement not covered" >ctx.cacheEnabled ? readManifest(outDir) : emptyManifest();</span>
578
574
  &nbsp;
579
- let tracked: string[];
575
+ const <span class="cstat-no" title="statement not covered" >result = generateOpenCollectionIncremental(</span>
576
+ opRoots,
577
+ {
578
+ collectionName,
579
+ contractRoots,
580
+ auth,
581
+ randomExamples: config.randomExamples ?? true,
582
+ includeInternal: config.includeInternal,
583
+ environments: config.environments,
584
+ },
585
+ prevManifest,
586
+ <span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" > relPath =&gt; e</span>xistsSync(resolve(outDir, relPath)),</span>
587
+ );
588
+ &nbsp;
589
+ <span class="cstat-no" title="statement not covered" > deleteStalePaths(outDir, result.deletedPaths);</span>
590
+ &nbsp;
591
+ <span class="cstat-no" title="statement not covered" > for (const { relativePath, content } of result.filesToWrite) {</span>
592
+ <span class="cstat-no" title="statement not covered" > ctx.emitFile(resolve(outDir, relativePath), content);</span>
593
+ }
594
+ }
595
+ &nbsp;
596
+ /** Read the previous run's manifest. Returns an empty manifest when the file is missing or unreadable so the next run safely starts from scratch. */
597
+ function <span class="fstat-no" title="function not covered" >readManifest(o</span>utDir: string): IncrementalManifest {
598
+ const <span class="cstat-no" title="statement not covered" >manifestPath = resolve(outDir, MANIFEST_FILENAME);</span>
599
+ <span class="cstat-no" title="statement not covered" > if (!existsSync(manifestPath)) <span class="cstat-no" title="statement not covered" >return emptyManifest();</span></span>
580
600
  <span class="cstat-no" title="statement not covered" > try {</span>
581
- <span class="cstat-no" title="statement not covered" > tracked = parseManifest(readFileSync(manifestPath, 'utf-8'));</span>
601
+ <span class="cstat-no" title="statement not covered" > return parseManifest(readFileSync(manifestPath, 'utf-8'));</span>
582
602
  } catch {
583
- <span class="cstat-no" title="statement not covered" > return;</span>
603
+ <span class="cstat-no" title="statement not covered" > return emptyManifest();</span>
584
604
  }
605
+ }
585
606
  &nbsp;
607
+ /**
608
+ * Delete files that the previous run tracked but the current run doesn't produce, then
609
+ * walk back up each affected directory and remove it if it's now empty (stopping at outDir).
610
+ * Anything user-added survives because the manifest only ever lists plugin-generated paths.
611
+ */
612
+ function <span class="fstat-no" title="function not covered" >deleteStalePaths(o</span>utDir: string, relPaths: string[]): void {
613
+ <span class="cstat-no" title="statement not covered" > if (relPaths.length === 0) <span class="cstat-no" title="statement not covered" >return;</span></span>
586
614
  const removedDirs = <span class="cstat-no" title="statement not covered" >new Set&lt;string&gt;();</span>
587
- <span class="cstat-no" title="statement not covered" > for (const rel of tracked) {</span>
615
+ <span class="cstat-no" title="statement not covered" > for (const rel of relPaths) {</span>
588
616
  const <span class="cstat-no" title="statement not covered" >abs = resolve(outDir, rel);</span>
589
617
  <span class="cstat-no" title="statement not covered" > if (existsSync(abs)) {</span>
590
618
  <span class="cstat-no" title="statement not covered" > rmSync(abs, { force: true });</span>
591
619
  <span class="cstat-no" title="statement not covered" > removedDirs.add(dirname(abs));</span>
592
620
  }
593
621
  }
594
- &nbsp;
595
- // Walk up from each affected directory and remove it if empty, stopping at outDir.
596
622
  <span class="cstat-no" title="statement not covered" > for (const dir of removedDirs) {</span>
597
623
  let current = <span class="cstat-no" title="statement not covered" >dir;</span>
598
624
  <span class="cstat-no" title="statement not covered" > while (current.startsWith(outDir) &amp;&amp; current !== outDir) {</span>
@@ -609,6 +635,7 @@ function <span class="fstat-no" title="function not covered" >cleanupTrackedFile
609
635
  }
610
636
  }
611
637
  }
638
+ &nbsp;
612
639
  &nbsp;</pre></td></tr></table></pre>
613
640
 
614
641
  <div class='push'></div><!-- for sticky footer -->
@@ -616,7 +643,7 @@ function <span class="fstat-no" title="function not covered" >cleanupTrackedFile
616
643
  <div class='footer quiet pad2 space-top1 center small'>
617
644
  Code coverage generated by
618
645
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
619
- at 2026-05-06T12:37:12.770Z
646
+ at 2026-05-07T11:09:13.542Z
620
647
  </div>
621
648
  <script src="../prettify.js"></script>
622
649
  <script>
@@ -266,11 +266,11 @@
266
266
  <span class="cline-any cline-neutral">&nbsp;</span>
267
267
  <span class="cline-any cline-neutral">&nbsp;</span>
268
268
  <span class="cline-any cline-neutral">&nbsp;</span>
269
- <span class="cline-any cline-yes">258x</span>
269
+ <span class="cline-any cline-yes">288x</span>
270
270
  <span class="cline-any cline-neutral">&nbsp;</span>
271
271
  <span class="cline-any cline-neutral">&nbsp;</span>
272
272
  <span class="cline-any cline-neutral">&nbsp;</span>
273
- <span class="cline-any cline-yes">48x</span>
273
+ <span class="cline-any cline-yes">53x</span>
274
274
  <span class="cline-any cline-neutral">&nbsp;</span>
275
275
  <span class="cline-any cline-neutral">&nbsp;</span>
276
276
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -298,7 +298,7 @@
298
298
  <span class="cline-any cline-neutral">&nbsp;</span>
299
299
  <span class="cline-any cline-neutral">&nbsp;</span>
300
300
  <span class="cline-any cline-neutral">&nbsp;</span>
301
- <span class="cline-any cline-yes">11x</span>
301
+ <span class="cline-any cline-yes">12x</span>
302
302
  <span class="cline-any cline-neutral">&nbsp;</span>
303
303
  <span class="cline-any cline-neutral">&nbsp;</span>
304
304
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -310,7 +310,7 @@
310
310
  <span class="cline-any cline-neutral">&nbsp;</span>
311
311
  <span class="cline-any cline-neutral">&nbsp;</span>
312
312
  <span class="cline-any cline-neutral">&nbsp;</span>
313
- <span class="cline-any cline-yes">36x</span>
313
+ <span class="cline-any cline-yes">39x</span>
314
314
  <span class="cline-any cline-neutral">&nbsp;</span>
315
315
  <span class="cline-any cline-neutral">&nbsp;</span>
316
316
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -322,7 +322,7 @@
322
322
  <span class="cline-any cline-neutral">&nbsp;</span>
323
323
  <span class="cline-any cline-neutral">&nbsp;</span>
324
324
  <span class="cline-any cline-neutral">&nbsp;</span>
325
- <span class="cline-any cline-yes">9x</span>
325
+ <span class="cline-any cline-yes">11x</span>
326
326
  <span class="cline-any cline-neutral">&nbsp;</span>
327
327
  <span class="cline-any cline-neutral">&nbsp;</span>
328
328
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -332,7 +332,7 @@
332
332
  <span class="cline-any cline-neutral">&nbsp;</span>
333
333
  <span class="cline-any cline-neutral">&nbsp;</span>
334
334
  <span class="cline-any cline-neutral">&nbsp;</span>
335
- <span class="cline-any cline-yes">8x</span>
335
+ <span class="cline-any cline-yes">10x</span>
336
336
  <span class="cline-any cline-neutral">&nbsp;</span>
337
337
  <span class="cline-any cline-neutral">&nbsp;</span>
338
338
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -355,8 +355,8 @@
355
355
  <span class="cline-any cline-neutral">&nbsp;</span>
356
356
  <span class="cline-any cline-neutral">&nbsp;</span>
357
357
  <span class="cline-any cline-neutral">&nbsp;</span>
358
- <span class="cline-any cline-yes">19x</span>
359
- <span class="cline-any cline-yes">19x</span>
358
+ <span class="cline-any cline-yes">22x</span>
359
+ <span class="cline-any cline-yes">22x</span>
360
360
  <span class="cline-any cline-neutral">&nbsp;</span>
361
361
  <span class="cline-any cline-neutral">&nbsp;</span>
362
362
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -384,10 +384,10 @@
384
384
  <span class="cline-any cline-neutral">&nbsp;</span>
385
385
  <span class="cline-any cline-neutral">&nbsp;</span>
386
386
  <span class="cline-any cline-neutral">&nbsp;</span>
387
- <span class="cline-any cline-yes">99x</span>
388
- <span class="cline-any cline-yes">99x</span>
389
- <span class="cline-any cline-yes">99x</span>
390
- <span class="cline-any cline-yes">99x</span>
387
+ <span class="cline-any cline-yes">115x</span>
388
+ <span class="cline-any cline-yes">115x</span>
389
+ <span class="cline-any cline-yes">115x</span>
390
+ <span class="cline-any cline-yes">115x</span>
391
391
  <span class="cline-any cline-neutral">&nbsp;</span>
392
392
  <span class="cline-any cline-neutral">&nbsp;</span>
393
393
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -402,12 +402,12 @@
402
402
  <span class="cline-any cline-neutral">&nbsp;</span>
403
403
  <span class="cline-any cline-neutral">&nbsp;</span>
404
404
  <span class="cline-any cline-neutral">&nbsp;</span>
405
- <span class="cline-any cline-yes">95x</span>
406
- <span class="cline-any cline-yes">95x</span>
405
+ <span class="cline-any cline-yes">104x</span>
406
+ <span class="cline-any cline-yes">104x</span>
407
407
  <span class="cline-any cline-neutral">&nbsp;</span>
408
408
  <span class="cline-any cline-neutral">&nbsp;</span>
409
409
  <span class="cline-any cline-neutral">&nbsp;</span>
410
- <span class="cline-any cline-yes">92x</span>
410
+ <span class="cline-any cline-yes">101x</span>
411
411
  <span class="cline-any cline-neutral">&nbsp;</span>
412
412
  <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import type {
413
413
  ContractRootNode,
@@ -589,7 +589,7 @@ export function opRoot(routes: OpRouteNode[], file = 'users.op', meta: Record&lt
589
589
  <div class='footer quiet pad2 space-top1 center small'>
590
590
  Code coverage generated by
591
591
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
592
- at 2026-05-06T12:37:12.770Z
592
+ at 2026-05-07T11:09:13.542Z
593
593
  </div>
594
594
  <script src="../prettify.js"></script>
595
595
  <script>
@@ -101,7 +101,7 @@
101
101
  <div class='footer quiet pad2 space-top1 center small'>
102
102
  Code coverage generated by
103
103
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
104
- at 2026-05-06T12:37:12.770Z
104
+ at 2026-05-07T11:09:13.542Z
105
105
  </div>
106
106
  <script src="../prettify.js"></script>
107
107
  <script>