@codometer/output 0.0.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.
@@ -0,0 +1,1571 @@
1
+ import { CodeStatisticsResult } from '@codometer/core';
2
+ import { CodometerCompression } from '@codometer/configuration';
3
+ import { CodometerReport as CodometerReport_2 } from '@codometer/core';
4
+ import { ConfigurationService } from '@codometer/configuration';
5
+ import { ConsoleLogger } from '@nestjs/common';
6
+ import { DiscoveryService } from '@codometer/measurement';
7
+ import { EvaluatedLimit } from '@codometer/measurement';
8
+ import { MeasureCommandOptions } from '@codometer/configuration';
9
+ import { MeasureFormat } from '@codometer/configuration';
10
+ import { MeasurementResult } from '@codometer/measurement';
11
+ import { MetricUnit as MetricUnit_2 } from '@codometer/core';
12
+ import pino from 'pino';
13
+ import { ReportFailure as ReportFailure_2 } from '@codometer/core';
14
+ import { ResolvedCodometerConfiguration } from '@codometer/configuration';
15
+ import { ResolvedCodometerCustomStatistic } from '@codometer/configuration';
16
+ import { ResolvedCodometerMarkdownOutput } from '@codometer/configuration';
17
+ import { RunMode } from '@codometer/configuration';
18
+ import { TargetMetricIndex } from '@codometer/measurement';
19
+ import { WriteMarkdownOutput } from '@codometer/configuration';
20
+ import { z } from 'zod';
21
+
22
+ /** Stands in for a column a limit left empty, so the cell is never blank. */
23
+ export declare const ABSENT_LABEL = "\u2014";
24
+
25
+ /** Arguments accepted when building the report from one measurement. */
26
+ declare interface BuildReportArguments {
27
+ failures: readonly ReportFailure_2[];
28
+ /** Every metric each measured target counted, target by target. */
29
+ indexes: ReadonlyMap<string, TargetMetricIndex>;
30
+ limits: readonly EvaluatedLimit[];
31
+ }
32
+
33
+ /**
34
+ * Diffs codometer reports and joins them to a baseline.
35
+ *
36
+ * Imports `LoggerModule` itself rather than relying on a consuming
37
+ * application to import it globally, since this package is a library with no
38
+ * root module of its own — `LoggerModule` is `@Global()`, so importing it
39
+ * here is enough for `LoggerService` to resolve wherever this module is used.
40
+ */
41
+ export declare class ChangesModule {
42
+ }
43
+
44
+ /**
45
+ * Reads what each project's codometer run measured and joins it to a baseline.
46
+ *
47
+ * Codometer is stateless: it measures the tree in front of it and never looks
48
+ * at another branch. Comparing two runs is this package's job, because a
49
+ * baseline needs branches, workflow artifacts, and pull request context —
50
+ * exactly the knowledge a measurement tool has to stay free of.
51
+ *
52
+ * The baseline is a snapshot downloaded from the latest successful `main` run
53
+ * rather than a second build of the base branch, which used to cost a full
54
+ * extra checkout, install, and build on every pull request.
55
+ *
56
+ * Because CI runs `nx affected`, a pull request measures only the projects it
57
+ * touched. Metrics the baseline knows about but this run did not rebuild are
58
+ * still collected — flagged as unmeasured, carrying their baseline value — so
59
+ * a report can cover the whole workspace instead of only the change's blast
60
+ * radius.
61
+ *
62
+ * Every metric a target produced is joined, not only the ones denominated in
63
+ * bytes or the ones a limit was written against — a metric with no limit is
64
+ * measured and reported, never gated, and that includes whether it changed.
65
+ */
66
+ export declare class ChangesService {
67
+ private readonly logger;
68
+ constructor(logger: LoggerService);
69
+ /**
70
+ * Builds the row for a metric only the baseline knows about.
71
+ *
72
+ * Its numbers come from the baseline, so its verdicts are dropped: `breach`
73
+ * and `empty` are cleared rather than spread through. A pull request that
74
+ * never rebuilt a project must not be marked by what that project was doing
75
+ * on `main` — otherwise a limit breached there marks every unrelated pull
76
+ * request, and the row explaining it is not even on screen, because an
77
+ * unmeasured row is easy to miss and a removed one needs to say so plainly.
78
+ *
79
+ * The same reasoning drops the baseline's failures in `readBaseline`. Both
80
+ * exist to keep this change's status derived from this change.
81
+ */
82
+ private buildBaselineRow;
83
+ /** Builds the row for a metric this run measured. */
84
+ private buildMeasuredRow;
85
+ /**
86
+ * Joins one project's current report to its baseline.
87
+ *
88
+ * A baseline metric with no current counterpart was removed when this run
89
+ * rebuilt the project, and merely skipped when it did not.
90
+ */
91
+ private collectProjectRows;
92
+ /** Parses the report's JSON body, tolerating an absent or malformed file. */
93
+ private parseReport;
94
+ /** Reads a baseline report into a name-to-metric lookup. */
95
+ private readBaseline;
96
+ /**
97
+ * The severity of the worst limit a metric breached, if it breached one.
98
+ *
99
+ * A failing breach outranks an advisory one, so a metric that went past
100
+ * its `fail` limit while also going past an advisory limit below it reads
101
+ * as failing rather than as merely advised.
102
+ */
103
+ private readBreach;
104
+ /**
105
+ * The limit a metric is actually held to.
106
+ *
107
+ * The lowest `fail` limit, because that is the one that stops a change, and
108
+ * the lowest of them when several are written because that is the one that
109
+ * binds first. A metric limited only by advice falls back to its lowest
110
+ * `warn` limit — the only limit it has — rather than reporting none.
111
+ */
112
+ private readGoverningLimit;
113
+ /** The first label any limit wrote, which names the row in the table. */
114
+ private readLabel;
115
+ /**
116
+ * Pulls every metric a target produced out of the report.
117
+ *
118
+ * A codometer report carries everything a project measures — files, symbols,
119
+ * lines, bytes — and every one of them is joined against its baseline, not
120
+ * only the ones denominated in bytes.
121
+ *
122
+ * The row is labelled with whatever the limit was written under, falling
123
+ * back to the metric's own dotted name rather than the target's — a target
124
+ * groups many metrics under one name, so labelling every one of them with
125
+ * that shared name would make them indistinguishable from each other.
126
+ */
127
+ private readMetrics;
128
+ /** Derives the Nx project name from a report path. */
129
+ private readProjectName;
130
+ /** Parses a codometer report, tolerating an absent or malformed file. */
131
+ private readReport;
132
+ /**
133
+ * Lists every report path either side knows about, so a project the baseline
134
+ * measured is still accounted for when this run skipped it.
135
+ */
136
+ private readReportPaths;
137
+ /** Joins every current report to the baseline snapshot. */
138
+ collect(args: CollectRowsArguments): MetricCollection;
139
+ }
140
+
141
+ /**
142
+ * The block this report claims inside a shared document. HTML comments are
143
+ * invisible once the markdown is rendered, and every other report claims its
144
+ * own pair, so several can sit in one document without collision.
145
+ */
146
+ export declare const CODOMETER_MARKERS: {
147
+ end: string;
148
+ start: string;
149
+ };
150
+
151
+ /** One codometer report, as far as this diff reads it. */
152
+ declare type CodometerReport = z.infer<typeof codometerReportSchema>;
153
+
154
+ /**
155
+ * The shape of a codometer report, as far as this diff reads it.
156
+ *
157
+ * Validated rather than asserted: the file is output this package does not
158
+ * produce, and a malformed one should read as "nothing measured" instead of
159
+ * flowing through as untyped data.
160
+ *
161
+ * Nothing here is inferred from an absent field. A target says outright whether
162
+ * its globs matched anything, and a metric nobody limited carries an empty
163
+ * `limits` array rather than no limits key at all — so a target that matched
164
+ * no files reads differently from one that genuinely measured zero, and an
165
+ * unlimited metric differently from an unread report.
166
+ *
167
+ * `limits` is a list because a metric may carry more than one: the
168
+ * configuration accepts a `warn` short of a `fail` on purpose, and the gate
169
+ * enforces every one of them.
170
+ *
171
+ * `failures` is read for the same reason the rest of this is. A target the run
172
+ * could not measure contributes no row, and a table that silently holds fewer
173
+ * rows than the workspace has targets is a number that looks right only
174
+ * because whatever would have contradicted it is missing.
175
+ *
176
+ * `unit` is carried through on every row rather than filtered here, so a
177
+ * renderer can format a byte count differently from a plain count without this
178
+ * package needing to know what a renderer does with either.
179
+ */
180
+ export declare const codometerReportSchema: z.ZodObject<{
181
+ failures: z.ZodOptional<z.ZodArray<z.ZodObject<{
182
+ kind: z.ZodEnum<{
183
+ limit: "limit";
184
+ target: "target";
185
+ }>;
186
+ reason: z.ZodString;
187
+ subject: z.ZodString;
188
+ }, z.core.$strip>>>;
189
+ targets: z.ZodArray<z.ZodObject<{
190
+ empty: z.ZodBoolean;
191
+ metrics: z.ZodArray<z.ZodObject<{
192
+ limits: z.ZodArray<z.ZodObject<{
193
+ breached: z.ZodBoolean;
194
+ label: z.ZodNullable<z.ZodString>;
195
+ severity: z.ZodEnum<{
196
+ fail: "fail";
197
+ warn: "warn";
198
+ }>;
199
+ value: z.ZodNumber;
200
+ }, z.core.$strip>>;
201
+ name: z.ZodString;
202
+ unit: z.ZodNullable<z.ZodEnum<{
203
+ bytes: "bytes";
204
+ }>>;
205
+ value: z.ZodNumber;
206
+ }, z.core.$strip>>;
207
+ name: z.ZodString;
208
+ }, z.core.$strip>>;
209
+ }, z.core.$strip>;
210
+
211
+ /** Arguments for reading one project's report and its baseline. */
212
+ export declare interface CollectProjectRowsArguments extends CollectRowsArguments {
213
+ reportPath: string;
214
+ }
215
+
216
+ /** Arguments for joining measured reports to a baseline. */
217
+ export declare interface CollectRowsArguments {
218
+ baselineDirectory: string | undefined;
219
+ workingDirectory: string;
220
+ }
221
+
222
+ /** Heading the whole listing is written under. */
223
+ export declare const CONFIGURATION_HEADING = "# \uD83D\uDD27 Codometer Configuration";
224
+
225
+ /**
226
+ * Renders what a repository configures, as a document rather than a command.
227
+ *
228
+ * Reading the configuration a tree declares is an analysis of configuration,
229
+ * and turning it into a markdown table or a JSON document is a render target
230
+ * like any other — so both live here, above the configuration layer they read
231
+ * and below the command that asks for them. Codometer's own configuration and
232
+ * discovery modules are imported rather than reimplemented: the listing must
233
+ * resolve a file exactly as a measurement would, and must skip the same
234
+ * ignored directories, or it would describe a repository nobody runs.
235
+ */
236
+ export declare class ConfigurationListingModule {
237
+ }
238
+
239
+ /**
240
+ * Finds every codometer configuration in a tree and says what each one holds.
241
+ *
242
+ * Answers the question a repository gains once its limits stop living in one
243
+ * table: where is everything configured, and what does it add up to. It reads
244
+ * configuration and never measures anything, so it needs no build and runs in
245
+ * milliseconds — and no single unreadable file takes the listing down, because
246
+ * every failure is carried back on the result rather than thrown.
247
+ */
248
+ export declare class ConfigurationListingService {
249
+ private readonly configurationService;
250
+ private readonly discoveryService;
251
+ constructor(configurationService: ConfigurationService, discoveryService: DiscoveryService);
252
+ /** Every name a configuration file may be written under, as a set. */
253
+ private readonly configurationFileNames;
254
+ /**
255
+ * Resolves one configuration file, reporting rather than throwing on failure.
256
+ *
257
+ * A file named like a configuration is not always one that loads: a
258
+ * generator template carries placeholders in its own path, and a
259
+ * work-in-progress file may not parse. One such file must not take the
260
+ * listing down with it, because the listing is most wanted precisely when
261
+ * something is wrong. The failure is carried on the entry so the reader sees
262
+ * which file could not be read instead of a shorter list than the tree holds.
263
+ */
264
+ private describeConfiguration;
265
+ /**
266
+ * Renders a limit's value with the unit its metric implies.
267
+ *
268
+ * A resolved limit carries a bare number, so `256000` alone cannot say
269
+ * whether it gates bytes or files. Only a size analysis produces a `.size`
270
+ * metric, which is what makes the suffix a sound test.
271
+ */
272
+ private formatLimitValue;
273
+ /**
274
+ * Resolves the exclusions the walk uses, reporting rather than throwing.
275
+ *
276
+ * The walk root is not guaranteed to have a configuration answering for it:
277
+ * a workspace states its `format` once in a shared object that each project
278
+ * spreads, so the root itself may carry no configuration file at all and the
279
+ * upward search then finds nothing to resolve. A workspace in that shape
280
+ * names its own file with `--config`; one that does neither must not take
281
+ * the listing down — the listing is most wanted precisely when the
282
+ * configuration is in a state somebody is trying to understand — so the
283
+ * built-in exclusions stand in and the failure is carried back to be
284
+ * reported and to fail the run's exit code.
285
+ */
286
+ private resolveWalkExclusions;
287
+ /**
288
+ * Resolves the configuration each file in a tree answers with.
289
+ *
290
+ * Every file is resolved for **its own directory** rather than for the walk
291
+ * root, which is what makes the result match what a per-project run of
292
+ * codometer actually sees: every configuration file is resolved against the
293
+ * directory it sits in, so resolving one anywhere else would report
294
+ * something no run would ever use.
295
+ */
296
+ describeConfigurations(args: DescribeConfigurationsArguments): Promise<ConfiguredTree>;
297
+ /**
298
+ * Finds every configuration file beneath a directory.
299
+ *
300
+ * Walks with the same gitignore-aware discovery a measurement uses, so a
301
+ * configuration inside `node_modules` or a build directory is never picked
302
+ * up, and one inside a folder the repository ignores is never reported as
303
+ * something the repository configures.
304
+ *
305
+ * The exclusions come from whatever configuration answers for the walk root,
306
+ * because that is what says which files this repository considers its own —
307
+ * walking without them would list every configuration in a vendored
308
+ * dependency or a generator template. A root nothing answers for falls back
309
+ * to the built-in exclusions and reports itself instead of failing.
310
+ */
311
+ findConfigurationFiles(args: DescribeConfigurationsArguments): Promise<DiscoveredConfigurationFiles>;
312
+ /** Flattens every configured limit into one row per limit, in walk order. */
313
+ toLimitRows(described: readonly ConfiguredDirectory[]): ConfiguredLimitRow[];
314
+ }
315
+
316
+ /** One configuration file, and everything it resolved to for its own folder. */
317
+ export declare interface ConfiguredDirectory {
318
+ /** The configuration with every default applied, absent when it failed to load. */
319
+ configuration: ResolvedCodometerConfiguration | undefined;
320
+ /** Directory the configuration was resolved for, relative to the walk root. */
321
+ directory: string;
322
+ /** Why the file could not be read, and `undefined` when it was read. */
323
+ error: string | undefined;
324
+ /** Configuration file that answered, relative to the walk root. */
325
+ path: string;
326
+ }
327
+
328
+ /** One row of the limits listing. */
329
+ export declare interface ConfiguredLimitRow {
330
+ /** Directory the limit gates, relative to the walk root. */
331
+ directory: string;
332
+ /** Written label, or a dash when none was written. */
333
+ label: string;
334
+ /** Metric path the limit is written against. */
335
+ metric: string;
336
+ /** Configuration file the limit is declared in, relative to the walk root. */
337
+ path: string;
338
+ severity: string;
339
+ /** Rendered with its unit, so a size reads as a size and a count as a count. */
340
+ value: string;
341
+ }
342
+
343
+ /** Everything a tree configures, plus whatever answered for its root. */
344
+ export declare interface ConfiguredTree {
345
+ described: ConfiguredDirectory[];
346
+ /**
347
+ * Why nothing could be resolved for the walk root, and `undefined` when
348
+ * something was.
349
+ *
350
+ * Carried rather than thrown. The listing is most wanted precisely when the
351
+ * configuration is in a state somebody is trying to understand, so an
352
+ * unreadable root is reported and the walk goes on with the built-in
353
+ * exclusions — but it still fails the run's exit code.
354
+ */
355
+ rootError: string | undefined;
356
+ }
357
+
358
+ /** Arguments accepted when producing every one of a run's outputs. */
359
+ export declare interface DeliverArguments {
360
+ /** Where the printed badge block's own configured counters come from,
361
+ * resolved independently of `destinations.markdown`. */
362
+ consoleMarkdown: ResolvedMarkdownDestination | undefined;
363
+ destinations: RunDestinations;
364
+ /** What goes to standard output, or nothing when the run prints nothing. */
365
+ format: MeasureFormat | undefined;
366
+ measurement: MeasurementResult;
367
+ mode: RunMode;
368
+ report: CodometerReport_2;
369
+ scope: MeasurementScope;
370
+ }
371
+
372
+ /**
373
+ * NestJS module that writes every resolved output a run produces.
374
+ */
375
+ export declare class DeliveryModule {
376
+ }
377
+
378
+ /**
379
+ * Produces every resolved output — the report, the badge block, and whatever
380
+ * the run prints — and says which of the written ones are stale.
381
+ *
382
+ * Split out of `MeasureCommand` so delivering a report is a concern of its
383
+ * own, separate from measuring and from gating on what was measured. Every
384
+ * destination is produced before anything is reported, so a run that writes
385
+ * and gates writes all of its reports even when the gate then trips.
386
+ *
387
+ * Standard output has exactly one writer here, `deliverConsole`. A file sink
388
+ * never prints: two sinks that could each decide to print is how one run put
389
+ * two documents on the stream a pipeline was parsing.
390
+ *
391
+ * `renderBadges` and where a custom statistic's per-instance breaches are
392
+ * rendered are `@codometer/output`'s business, not this service's — it hands
393
+ * over the measured statistics and a destination and lets that package
394
+ * decide what the markdown says.
395
+ */
396
+ export declare class DeliveryService {
397
+ private readonly jsonService;
398
+ private readonly markdownService;
399
+ constructor(jsonService: JsonService, markdownService: MarkdownService);
400
+ /** Print whatever the run asked for, and nothing when it asked for nothing. */
401
+ private deliverConsole;
402
+ /** Write the report to its file, if this run writes or compares one. */
403
+ private deliverJson;
404
+ /**
405
+ * Put the badge block into its markdown file, if this run writes or
406
+ * compares one.
407
+ *
408
+ * The one markdown sink. `MarkdownService.sync` splices the block between
409
+ * its markers when the file carries them, appends it when it does not, and
410
+ * creates the file when it is not there — so a README somebody else wrote
411
+ * and a file holding nothing but badges are the same case, and neither
412
+ * needs a flag of its own.
413
+ */
414
+ private deliverMarkdown;
415
+ /**
416
+ * The size of every input this run measured, in declaration order.
417
+ *
418
+ * Left out rather than reported as zero bytes: an input that ran no size
419
+ * analysis, and an input whose globs matched no file. Both would otherwise
420
+ * publish `0.00 kB` — a figure that is not merely missing but wrong, and
421
+ * wrong in a README a release commits. An input measured before its build
422
+ * lands is the ordinary way to reach the second case, and it is caught by a
423
+ * failing limit only for the inputs that happen to declare one.
424
+ *
425
+ * A run that declared no input beyond `codebase` produces an empty list and
426
+ * no size badges.
427
+ */
428
+ private readTargetSizes;
429
+ /**
430
+ * Whether the run does anything with a file at all.
431
+ *
432
+ * A run that neither writes nor compares leaves every file alone. What it
433
+ * shows instead is `--format`'s business, not a destination's.
434
+ */
435
+ private touchesFiles;
436
+ /** Produce every output, and name the ones found stale. */
437
+ deliver(args: DeliverArguments): string[];
438
+ }
439
+
440
+ /** Which tree to describe, and which configuration answers for its root. */
441
+ export declare interface DescribeConfigurationsArguments {
442
+ /**
443
+ * Configuration file answering for the walk root, when one is named.
444
+ *
445
+ * `undefined` searches upward from the walk root instead. A workspace whose
446
+ * root carries no configuration file — because its shared object lives
447
+ * somewhere every project spreads it from — names that file here, exactly as
448
+ * the workspace-root measurement target already does.
449
+ */
450
+ configurationPath: string | undefined;
451
+ /** Directory the walk starts from, absolute. */
452
+ workingDirectory: string;
453
+ }
454
+
455
+ /**
456
+ * NestJS module that resolves where each of a run's outputs goes.
457
+ */
458
+ export declare class DestinationsModule {
459
+ }
460
+
461
+ /**
462
+ * Resolves which file each of a run's outputs lands in.
463
+ *
464
+ * Kept away from the command itself so that a destination can be resolved and
465
+ * tested without a measurement: which file each output lands in, and whether
466
+ * a bare `--output-*` flag has anything to resolve from at all, are questions
467
+ * that have been got wrong by inferring them from whichever other flag
468
+ * happened to be on the command line.
469
+ *
470
+ * It sits in the output layer rather than beside the flag reading it starts
471
+ * from, because a destination is a render target: what it produces is the
472
+ * path `JsonService` and `MarkdownService` write to, and the exclusion list
473
+ * the measurement is told not to measure.
474
+ */
475
+ export declare class DestinationsService {
476
+ constructor();
477
+ /** Finds the first output of the given type, if the configuration named one. */
478
+ private findConfiguredOutput;
479
+ /** Where the report goes, if this run resolves that destination at all. */
480
+ private resolveJson;
481
+ /**
482
+ * Which markdown file the badge block goes into, if any.
483
+ *
484
+ * One sink rather than two. The block is spliced between its markers when
485
+ * the file already carries them, and appended with them when it does not,
486
+ * so the same flag serves a README somebody else wrote the rest of and a
487
+ * file that holds nothing but badges.
488
+ *
489
+ * The path is never defaulted from nothing: it comes from the command line,
490
+ * or from a configured `markdown` output's own path or `write`. A
491
+ * configured `write` function is a destination in its own right — it picks
492
+ * the file itself — so it counts as "resolvable" even without a path.
493
+ */
494
+ private resolveMarkdown;
495
+ /** Turns a written destination path into an absolute one. */
496
+ private resolvePath;
497
+ /**
498
+ * Lists the files this run writes, relative to the measured directory.
499
+ *
500
+ * What codometer writes is what codometer must not measure, so this is also
501
+ * the exclusion list handed to the measurement.
502
+ */
503
+ listOutputPaths(args: ListOutputPathsArguments): string[];
504
+ /**
505
+ * Which markdown destination the console renders.
506
+ *
507
+ * Resolved from the configuration alone, never gated by whether
508
+ * `--output-markdown` — or any other `--output-*` flag — was passed: the
509
+ * printed badge block must carry every configured custom counter whether or
510
+ * not this run also writes a markdown file, so it is resolved as though no
511
+ * other output flag were on the command line. An explicit `--output-markdown
512
+ * <path>` is still honored, exactly as it would be for the write
513
+ * destination. Nothing here is written to disk; `resolveDestinations`
514
+ * decides that separately.
515
+ */
516
+ resolveConsoleMarkdown(args: ResolveDestinationsArguments): ResolvedMarkdownDestination | undefined;
517
+ /**
518
+ * Resolves which files the run writes, and refuses a destination this run
519
+ * has no way to have produced.
520
+ *
521
+ * `--output-json`/`--output-markdown` passed bare ask this run to write
522
+ * wherever the configuration says to; refused before anything is measured
523
+ * when the configuration names no such output at all, since there is then
524
+ * nowhere to write it.
525
+ */
526
+ resolveDestinations(args: ResolveDestinationsArguments): ResolveDestinationsResult;
527
+ /**
528
+ * Whether a run covers a whole repository or one project inside one.
529
+ *
530
+ * Decided from the measured directory alone, not by walking upward: a
531
+ * directory carrying a repository marker is a repository, and anything
532
+ * beneath one is a project. The first badge group is headed by this, so a
533
+ * project README saying `Repository` over figures that only ever covered
534
+ * that project is the thing it exists to prevent.
535
+ */
536
+ selectScope(workingDirectory: string): MeasurementScope;
537
+ }
538
+
539
+ /** Every configuration file a walk found, plus whatever the walk root said. */
540
+ export declare interface DiscoveredConfigurationFiles {
541
+ /** Configuration file paths, relative to the walk root, in walk order. */
542
+ files: string[];
543
+ /** Why nothing answered for the walk root, and `undefined` when it did. */
544
+ rootError: string | undefined;
545
+ }
546
+
547
+ /** Where a rendered section should land. */
548
+ export declare interface DocumentDestination {
549
+ /** Markdown file to splice the section into, between its markers. */
550
+ markdown: string | undefined;
551
+ /** File to write the section to on its own. */
552
+ output: string | undefined;
553
+ }
554
+
555
+ /** The HTML comments delimiting one report's section inside a document. */
556
+ export declare interface DocumentMarkers {
557
+ end: string;
558
+ start: string;
559
+ }
560
+
561
+ /**
562
+ * Wraps a report body in markers and writes it wherever it was asked for.
563
+ *
564
+ * Imports `LoggerModule` itself rather than relying on a consuming
565
+ * application to import it globally, since this package is a library with no
566
+ * root module of its own — `LoggerModule` is `@Global()`, so importing it
567
+ * here is enough for `LoggerService` to resolve wherever this module is used.
568
+ */
569
+ export declare class DocumentsModule {
570
+ }
571
+
572
+ /**
573
+ * Puts a rendered report body where it was asked for.
574
+ *
575
+ * Every report shares this: wrap the body in its markers, then write it to a
576
+ * file, splice it into a document, or print it. Nothing here knows what any
577
+ * report measures, and nothing here talks to a forge — handing the markdown to
578
+ * a pull request, an issue, or a wiki is the caller's job.
579
+ */
580
+ export declare class DocumentsService {
581
+ private readonly logger;
582
+ constructor(logger: LoggerService);
583
+ /** Reads a document, treating an absent one as empty. */
584
+ private readDocument;
585
+ /**
586
+ * Wraps a rendered body in its markers, then writes it wherever the
587
+ * destination says.
588
+ *
589
+ * With neither a file nor a document the section goes to standard output,
590
+ * which is what makes any report inspectable before it is wired into
591
+ * anything.
592
+ */
593
+ emit(args: EmitArguments): Promise<void>;
594
+ /**
595
+ * Splices a section into a document, or appends it when the markers are
596
+ * absent, and leaves the author's prose either side of it alone.
597
+ */
598
+ splice(document: string, section: string, markers: DocumentMarkers): string;
599
+ /** Wraps a rendered body in its markers. */
600
+ wrap(body: string, markers: DocumentMarkers): string;
601
+ }
602
+
603
+ /** Arguments for wrapping a rendered body and putting it where it was asked for. */
604
+ export declare interface EmitArguments {
605
+ /** The report's rendered body, without its markers. */
606
+ body: string;
607
+ destination: DocumentDestination;
608
+ /** Names the report in log messages, so several reports stay distinguishable. */
609
+ label: string;
610
+ markers: DocumentMarkers;
611
+ }
612
+
613
+ /**
614
+ * Formats a byte count, switching to megabytes once kilobytes get unwieldy.
615
+ *
616
+ * Kilobytes are decimal, matching what codometer parses out of a limit written
617
+ * as `"8 KB"`. Dividing by 1024 here would print a limit as a number the
618
+ * configuration never mentions.
619
+ */
620
+ export declare function formatBytes(bytes: number): string;
621
+
622
+ /** Formats a plain count, grouped with the reader's locale separators. */
623
+ export declare function formatCount(value: number): string;
624
+
625
+ /** Formats a signed delta, or an em dash when there is nothing to compare. */
626
+ export declare function formatDelta(delta: number | undefined, unit: MetricUnit_2): string;
627
+
628
+ /** Formats a value the way its unit calls for. */
629
+ export declare function formatValue(value: number, unit: MetricUnit_2): string;
630
+
631
+ /**
632
+ * Whether a metric changed since the baseline, or is breaching a limit right
633
+ * now regardless of whether it moved.
634
+ *
635
+ * A row that neither changed nor breaches anything has nothing to tell a
636
+ * reviewer, so it never reaches the table. A brand-new metric — one with no
637
+ * baseline at all — counts as changed only when it measured something or its
638
+ * target's globs matched nothing, because a zero-valued metric appearing for
639
+ * the first time (every project carries a metric for every language
640
+ * codometer knows, most of them zero) tells a reviewer nothing "appeared" —
641
+ * least of all on the first run this report ever makes against a project,
642
+ * where every metric is technically without a baseline at once.
643
+ */
644
+ export declare function hasChanged(row: MetricRow): boolean;
645
+
646
+ /** Heading the rendered section is filed under. */
647
+ export declare const HEADING = "## \u23F2\uFE0F Codometer";
648
+
649
+ /**
650
+ * NestJS module that provides JSON statistics report writing.
651
+ */
652
+ export declare class JsonModule {
653
+ }
654
+
655
+ /**
656
+ * Writes the report to a JSON file.
657
+ *
658
+ * The file holds the report and nothing else — no timestamp, no tool version.
659
+ * Anything that changes between two runs over the same tree would report a
660
+ * repository nobody touched as stale.
661
+ */
662
+ export declare class JsonService {
663
+ private readonly logger;
664
+ constructor(logger: LoggerService);
665
+ /**
666
+ * Read an existing report, returning an empty string if absent.
667
+ */
668
+ private readExisting;
669
+ /**
670
+ * Render the report as the JSON document that gets written.
671
+ *
672
+ * Ends with a newline so the file matches what a formatter would leave
673
+ * behind, and so a staleness comparison does not fail over the one byte
674
+ * every other tool in the repository adds.
675
+ */
676
+ render<Report>(args: RenderReportArguments<Report>): string;
677
+ /**
678
+ * Sync the JSON file with the current report.
679
+ *
680
+ * - **Writing**: writes the file, creating parent directories as needed.
681
+ * - **Checking**: returns `true` when the file already holds the current
682
+ * report, `false` when it is missing or stale, and writes nothing.
683
+ */
684
+ sync<Report>(args: SyncJsonArguments<Report>): boolean;
685
+ }
686
+
687
+ /** Column headers of the limits table, in the order they are rendered. */
688
+ export declare const LIMIT_TABLE_COLUMNS: readonly ["Directory", "Metric", "Label", "Severity", "Value", "Declared in"];
689
+
690
+ /** Arguments accepted when listing the files a run writes. */
691
+ export declare interface ListOutputPathsArguments {
692
+ destinations: RunDestinations;
693
+ workingDirectory: string;
694
+ }
695
+
696
+ /**
697
+ * Structured values that belong beside a log line rather than inside it.
698
+ *
699
+ * Counts, percentages, and durations are the values that change on every
700
+ * occurrence, so they are carried as fields: the message stays constant and
701
+ * groupable in telemetry, and the numbers stay queryable instead of having to
702
+ * be parsed back out of prose.
703
+ *
704
+ * The named members are the recurring ones; the index signature keeps the
705
+ * argument open for whatever a given call site needs to attach.
706
+ */
707
+ declare interface LogData {
708
+ [key: string]: unknown;
709
+ /** How many things the operation handled. */
710
+ count?: number;
711
+ /** Wall-clock milliseconds the operation took. */
712
+ durationMs?: number;
713
+ /** Completion between 0 and 100. */
714
+ percent?: number;
715
+ /** How many things the operation set out to handle. */
716
+ total?: number;
717
+ }
718
+
719
+ /**
720
+ * Transient-scoped logger so each injecting class gets its own instance.
721
+ * Each consumer calls `setContext(ClassName.name)` to tag every log line
722
+ * with the originating class. Backed by pino for structured JSON output in
723
+ * production and human-readable pretty-print in development.
724
+ *
725
+ * Messages follow one grammar: an emoji naming the subject, a verb in present
726
+ * progressive or past tense, then the object. Values that vary per call —
727
+ * counts, percentages, durations — go in the `data` argument rather than the
728
+ * message, so the message stays constant enough for telemetry to group on.
729
+ *
730
+ * ```ts
731
+ * this.logger.info("📥 Downloading CSEL sources", undefined, { total: 428 });
732
+ * this.logger.info("📥 Downloaded CSEL sources", undefined, { count: 412 });
733
+ * ```
734
+ */
735
+ declare @Injectable({ scope: Scope.TRANSIENT })
736
+ class LoggerService extends ConsoleLogger {
737
+ // 🏗 Dependency Injection
738
+
739
+ constructor() {
740
+ super();
741
+ }
742
+
743
+ // 🔐 Private Fields
744
+
745
+ private static readonly isProduction =
746
+ process.env["NODE_ENV"] === "production";
747
+
748
+ /**
749
+ * Built on first use, not when this file is evaluated.
750
+ *
751
+ * A destination fixed at import time could only ever be chosen by this
752
+ * package, since every consumer's own code runs after its imports.
753
+ */
754
+ private static rootLogger: pino.Logger | undefined;
755
+
756
+ /** Whether lines go to standard error instead of standard output. */
757
+ private static writesToStandardError = false;
758
+
759
+ private child: pino.Logger = LoggerService.root;
760
+
761
+ // 🔑 Public Fields
762
+
763
+ // 🔏 Private Methods
764
+
765
+ /** Build the pino instance for production or local development output. */
766
+ private static createRootLogger(): pino.Logger {
767
+ const level = process.env["LOG_LEVEL"] ?? "info";
768
+
769
+ if (LoggerService.isProduction) {
770
+ return LoggerService.writesToStandardError
771
+ ? pino({ level }, pino.destination(STANDARD_ERROR_DESCRIPTOR))
772
+ : pino({ level });
773
+ }
774
+
775
+ return pino({
776
+ level,
777
+ transport: {
778
+ options: {
779
+ colorize: true,
780
+ destination: LoggerService.writesToStandardError
781
+ ? STANDARD_ERROR_DESCRIPTOR
782
+ : STANDARD_OUTPUT_DESCRIPTOR,
783
+ // The emoji is a field, not part of the message, so the console can
784
+ // show it while telemetry stores unadorned prose. `ignore` then keeps
785
+ // it from being printed a second time in the trailing object.
786
+ ignore: "pid,hostname,emoji",
787
+ messageFormat: "{emoji} {msg}",
788
+ singleLine: true,
789
+ },
790
+ target: "pino-pretty",
791
+ },
792
+ });
793
+ }
794
+
795
+ /**
796
+ * Sends every subsequent line to standard error instead of standard output.
797
+ *
798
+ * For a command-line application whose standard output *is* its result. A log
799
+ * line sharing that stream is not a diagnostic beside the data, it is a
800
+ * corruption of it. Call it before anything logs — the first statement of the
801
+ * application's bootstrap.
802
+ *
803
+ * A call after the first line warns and changes nothing: the destination is
804
+ * fixed when the pino instance is built, and tearing down a transport
805
+ * somebody is writing through would be worse than refusing. The warning is
806
+ * the point — silently leaving the lines on standard output is how a caller
807
+ * would ship a corrupted pipe without ever being told.
808
+ */
809
+ static logToStandardError(): void {
810
+ if (LoggerService.rootLogger !== undefined) {
811
+ process.emitWarning(
812
+ "LoggerService.logToStandardError() was called after the first log line, so log lines still go to standard output and anything piping that stream will read them as data. Call it as the first statement of the application's bootstrap.",
813
+ );
814
+ return;
815
+ }
816
+
817
+ LoggerService.writesToStandardError = true;
818
+ }
819
+
820
+ /**
821
+ * Fails a malformed message in development, and never in production.
822
+ *
823
+ * A logger that throws in production turns an observability call into an
824
+ * outage, so the check runs only where a developer is present to fix it.
825
+ */
826
+ private assertConventionalMessage(args: {
827
+ context: string | undefined;
828
+ parsed: ParsedLogMessage;
829
+ }): void {
830
+ if (
831
+ LoggerService.isProduction ||
832
+ this.shouldSkipConventionalMessageValidation(args.context)
833
+ ) {
834
+ return;
835
+ }
836
+
837
+ const violation = this.getConventionalMessageViolation(args.parsed);
838
+
839
+ if (violation !== undefined) {
840
+ throw new Error(violation);
841
+ }
842
+ }
843
+
844
+ /** Assembles the object pino merges into the line. */
845
+ private buildBindings(args: {
846
+ context: string | undefined;
847
+ data: LogData | undefined;
848
+ parsed: ParsedLogMessage;
849
+ }): Record<string, unknown> {
850
+ this.assertConventionalMessage({
851
+ context: args.context,
852
+ parsed: args.parsed,
853
+ });
854
+
855
+ return {
856
+ ...args.data,
857
+ context: args.context,
858
+ // Telemetry gets prose; only the console-bound transport reads this.
859
+ ...(LoggerService.isProduction ? {} : { emoji: args.parsed.emoji }),
860
+ };
861
+ }
862
+
863
+ /** Returns a human-readable explanation when the message format is invalid. */
864
+ private getConventionalMessageViolation(
865
+ parsed: ParsedLogMessage,
866
+ ): string | undefined {
867
+ const emoji = parsed.emoji;
868
+ const text = parsed.text;
869
+
870
+ if (emoji === undefined) {
871
+ return `Log message must start with an emoji naming its subject, then a verb: "${text}"`;
872
+ }
873
+
874
+ const firstWord = FIRST_WORD_PATTERN.exec(text)?.[1];
875
+
876
+ if (firstWord === undefined || !this.isConventionalVerb(firstWord)) {
877
+ return `Log message must begin with a verb in present progressive or past tense, got "${firstWord ?? ""}": "${emoji} ${text}"`;
878
+ }
879
+
880
+ return undefined;
881
+ }
882
+
883
+ /**
884
+ * Whether a word is a verb in one of the two tenses the convention allows.
885
+ *
886
+ * Present progressive means the operation is under way; past means it
887
+ * finished. Regular morphology covers both, so a new verb needs no
888
+ * registration anywhere — only irregular pasts are enumerated.
889
+ */
890
+ private isConventionalVerb(word: string): boolean {
891
+ const lowercased = word.toLowerCase();
892
+
893
+ return (
894
+ lowercased.endsWith("ing") ||
895
+ lowercased.endsWith("ed") ||
896
+ IRREGULAR_PAST_VERBS.has(lowercased)
897
+ );
898
+ }
899
+
900
+ /** Splits a leading emoji off a message, leaving prose behind. */
901
+ private parseMessage(message: unknown): ParsedLogMessage {
902
+ const text = String(message);
903
+ const match = LEADING_EMOJI_PATTERN.exec(text);
904
+ const emoji = match?.[1];
905
+
906
+ return emoji === undefined
907
+ ? { emoji: undefined, text }
908
+ : { emoji, text: text.slice(match?.[0].length) };
909
+ }
910
+
911
+ /** Whether a context is intentionally exempt from the validation rule. */
912
+ private shouldSkipConventionalMessageValidation(
913
+ context: string | undefined,
914
+ ): boolean {
915
+ return context !== undefined && UNVALIDATED_LOG_CONTEXTS.has(context);
916
+ }
917
+
918
+ // 🌎 Public Methods
919
+
920
+ /** The pino instance every logger's child is taken from. */
921
+ private static get root(): pino.Logger {
922
+ LoggerService.rootLogger ??= LoggerService.createRootLogger();
923
+
924
+ return LoggerService.rootLogger;
925
+ }
926
+
927
+ /** Normalizes unknown errors into a stable message and timestamped log line. */
928
+ buildErrorLogEntry(
929
+ context: string,
930
+ error: unknown,
931
+ ): { errorMessage: string; logLine: string } {
932
+ const errorMessage =
933
+ error instanceof Error ? error.stack || error.message : String(error);
934
+
935
+ return {
936
+ errorMessage,
937
+ logLine: `[${new Date().toISOString()}] ${context}: ${errorMessage}\n`,
938
+ };
939
+ }
940
+
941
+ /** Builds a timestamped output log file path and ensures the output directory exists. */
942
+ createTimestampedOutputLogFilePath(filePrefix: string): string {
943
+ const outputDirectory = path.join(process.cwd(), "output");
944
+ if (!existsSync(outputDirectory)) {
945
+ mkdirSync(outputDirectory, { recursive: true });
946
+ }
947
+
948
+ return path.join(
949
+ outputDirectory,
950
+ `${filePrefix}-${new Date().toISOString().replaceAll(/[:.]/g, "-")}.log`,
951
+ );
952
+ }
953
+
954
+ /** Logs a debug message at the `debug` level. */
955
+ override debug(message: unknown, context?: string, data?: LogData): void {
956
+ const parsed = this.parseMessage(message);
957
+ this.child.debug(
958
+ this.buildBindings({ context: context ?? this.context, data, parsed }),
959
+ parsed.text,
960
+ );
961
+ }
962
+
963
+ /**
964
+ * Logs an error message at the `error` level, optionally including a stack trace.
965
+ *
966
+ * `ConsoleLogger.error` spends a third slot on a context string that the
967
+ * other levels do not have, so this one accepts either: a string keeps
968
+ * NestJS's meaning, an object is structured data like everywhere else.
969
+ */
970
+ override error(
971
+ message: unknown,
972
+ stackOrContext?: string,
973
+ contextOrData?: LogData | string,
974
+ ): void {
975
+ const parsed = this.parseMessage(message);
976
+ const data = typeof contextOrData === "object" ? contextOrData : undefined;
977
+ const context =
978
+ typeof contextOrData === "string" ? contextOrData : this.context;
979
+
980
+ this.child.error(
981
+ {
982
+ ...this.buildBindings({ context, data, parsed }),
983
+ stack: stackOrContext,
984
+ },
985
+ parsed.text,
986
+ );
987
+ }
988
+
989
+ /** Logs an informational message at the `info` level. */
990
+ info(message: unknown, context?: string, data?: LogData): void {
991
+ const parsed = this.parseMessage(message);
992
+ this.child.info(
993
+ this.buildBindings({ context: context ?? this.context, data, parsed }),
994
+ parsed.text,
995
+ );
996
+ }
997
+
998
+ /**
999
+ * Logs an informational message at the `info` level.
1000
+ *
1001
+ * NestJS and `nest-commander` call this method directly as part of the
1002
+ * framework's own `LoggerService` contract, so it must keep working
1003
+ * exactly as before. Application code should call `info` instead — the
1004
+ * same behavior under a name that says what level it logs at.
1005
+ */
1006
+ override log(message: unknown, context?: string, data?: LogData): void {
1007
+ this.info(message, context, data);
1008
+ }
1009
+
1010
+ /** Sets the context label included in every subsequent log line. */
1011
+ override setContext(context: string): void {
1012
+ super.setContext(context);
1013
+ this.child = LoggerService.root.child({ context });
1014
+ }
1015
+
1016
+ /** Logs a verbose message at the `trace` level. */
1017
+ override verbose(message: unknown, context?: string, data?: LogData): void {
1018
+ const parsed = this.parseMessage(message);
1019
+ this.child.trace(
1020
+ this.buildBindings({ context: context ?? this.context, data, parsed }),
1021
+ parsed.text,
1022
+ );
1023
+ }
1024
+
1025
+ /** Logs a warning message at the `warn` level. */
1026
+ override warn(message: unknown, context?: string, data?: LogData): void {
1027
+ const parsed = this.parseMessage(message);
1028
+ this.child.warn(
1029
+ this.buildBindings({ context: context ?? this.context, data, parsed }),
1030
+ parsed.text,
1031
+ );
1032
+ }
1033
+ }
1034
+
1035
+ /**
1036
+ * NestJS module that provides markdown badge block writing.
1037
+ */
1038
+ export declare class MarkdownModule {
1039
+ }
1040
+
1041
+ /**
1042
+ * Writes generated code statistics badges into a markdown file.
1043
+ */
1044
+ export declare class MarkdownService {
1045
+ private readonly logger;
1046
+ constructor(logger: LoggerService);
1047
+ /**
1048
+ * Build the anchor helpers handed to a configured `write` function.
1049
+ *
1050
+ * Every helper is bound to this run's content, path, and mode, so a writer
1051
+ * that only wants the ordinary splice calls one method with no arguments.
1052
+ *
1053
+ * Each helper keeps the name of the method it wraps, because those names are
1054
+ * the `MarkdownAnchorHelpers` contract a configured writer is written
1055
+ * against.
1056
+ */
1057
+ private buildAnchorHelpers;
1058
+ /**
1059
+ * Assemble the badge groups, in the order they are rendered.
1060
+ *
1061
+ * Every counter the measurement pipeline produces gets a badge, grouped
1062
+ * under a heading naming the language it was measured from. Only the first
1063
+ * group spans languages; the rest report one language each, so a number
1064
+ * that moves can be traced to the analyzer that produced it rather than to
1065
+ * a sum that silently mixes several. That first group is named for the
1066
+ * run's scope — `Repository` for the whole tree, `Project` for one project.
1067
+ *
1068
+ * `Measured Targets` is the one group not counting anything the language analyzers
1069
+ * produced: it reports the size of each declared target this run measured,
1070
+ * which is how a project's README carries the compressed size of what it
1071
+ * ships. It renders nothing when the run declared no target, so the
1072
+ * whole-repository report is byte for byte what it was.
1073
+ */
1074
+ private buildBadgeGroups;
1075
+ /**
1076
+ * Build the matcher for a block delimited by the configured markers.
1077
+ */
1078
+ private buildBlockRegex;
1079
+ /**
1080
+ * Escape a configured marker so it can be searched for literally.
1081
+ */
1082
+ private escapeRegex;
1083
+ /**
1084
+ * Read an existing markdown file, returning an empty string if absent.
1085
+ */
1086
+ private readExisting;
1087
+ /**
1088
+ * Narrow the measured statistics to one destination's own selected
1089
+ * counters.
1090
+ *
1091
+ * `statistics.custom` is every counter the configuration's top-level
1092
+ * `custom` array declares, measured once for the whole run regardless of
1093
+ * which output — if any — renders each one. Two destinations may select
1094
+ * entirely different labels back out of it, so each has to pick its own
1095
+ * subset before rendering, or one destination's badges would carry
1096
+ * another's counters too.
1097
+ */
1098
+ private scopeCustomStatistics;
1099
+ /**
1100
+ * Splice the anchored block into a file, or report whether it is current.
1101
+ *
1102
+ * Replaces the block when the markers are found, appends it when they are
1103
+ * absent, and creates the file when it does not exist. Check mode compares
1104
+ * and writes nothing.
1105
+ *
1106
+ * Deliberately its own splice, not the `documents` module's
1107
+ * `DocumentsService`: that service has no check mode, and its `wrap` omits
1108
+ * the blank line after the opening marker that `wrapInAnchors` below adds
1109
+ * for Prettier — sharing it as-is would either drop check-mode reports or
1110
+ * reformat every README the badge block sits in. Only the byte formatting
1111
+ * was shared; unifying the splice itself needs check mode added to
1112
+ * `DocumentsService` first, which is unscoped work of its own.
1113
+ */
1114
+ private syncAnchoredBlock;
1115
+ /**
1116
+ * Wrap rendered markdown in the configured anchor markers.
1117
+ */
1118
+ private wrapInAnchors;
1119
+ /**
1120
+ * Write markdown to a file, and record that it happened.
1121
+ *
1122
+ * Creates the directory on the way. The block is appended to a file that
1123
+ * does not exist yet as readily as it is spliced into one that does, so a
1124
+ * destination naming a directory nothing has created is an ordinary command
1125
+ * line rather than a mistake.
1126
+ */
1127
+ private writeMarkdownFile;
1128
+ /**
1129
+ * Render the badge block for a destination, description and all.
1130
+ *
1131
+ * The block a splice destination places between its markers, and the body of
1132
+ * the document a whole-file destination writes. Both are the same markdown,
1133
+ * which is why the two sinks never disagree about a number.
1134
+ *
1135
+ * `statistics.custom` carries every counter the top-level `custom` array
1136
+ * declares; this destination's own `custom` list says which of them it
1137
+ * selected, so it is scoped down before rendering.
1138
+ */
1139
+ renderBadges(args: RenderBadgesArguments): string;
1140
+ /**
1141
+ * Render the badge block wrapped in its destination's anchor markers.
1142
+ *
1143
+ * What a splice would place, without placing it — the form a run that writes
1144
+ * nothing shows on the console.
1145
+ */
1146
+ renderBlock(args: RenderBadgesArguments): string;
1147
+ /**
1148
+ * Render the badges as a document of their own.
1149
+ *
1150
+ * No anchor markers: nothing else is in the file, so there is nothing to
1151
+ * anchor the block against.
1152
+ *
1153
+ * Every run, whatever its scope, heads its figures with the same section
1154
+ * heading, because the heading lives inside the markers now rather than
1155
+ * being written by hand above them — one owner instead of one per
1156
+ * document. The groups underneath still distinguish `Repository` from
1157
+ * `Project` by scope, so only the top-level heading unifies.
1158
+ *
1159
+ * A per-instance custom statistic that breached this run gets one more
1160
+ * section after the badges, naming the file and line of each breach —
1161
+ * spec #749 user story 16. Nothing is appended when nothing breached, the
1162
+ * same instinct the badge groups themselves follow.
1163
+ */
1164
+ renderDocument(args: RenderDocumentArguments): string;
1165
+ /**
1166
+ * Sync a splice destination with the current statistics.
1167
+ *
1168
+ * `write` is the whole of the customizable behavior now: it decides what
1169
+ * the markdown says and which file it lands in, replacing what used to be
1170
+ * two separate `render` and `write` callbacks. The built-in behavior — no
1171
+ * `write` configured — renders badges and splices them between the
1172
+ * configured anchor markers.
1173
+ *
1174
+ * Returns whatever a configured `write` returns; the built-in path returns
1175
+ * `false` only when checking, and only when the destination is missing or
1176
+ * stale.
1177
+ */
1178
+ sync(args: SyncMarkdownArguments): boolean;
1179
+ }
1180
+
1181
+ /**
1182
+ * What a run measured: one project, or the repository holding it.
1183
+ *
1184
+ * Carried into the rendering because the first badge group is named after it.
1185
+ * A run scoped to `packages/logger` reporting a `Repository` heading names the
1186
+ * whole workspace for figures that only ever covered one package.
1187
+ */
1188
+ export declare type MeasurementScope = "project" | "repository";
1189
+
1190
+ /**
1191
+ * Everything one collection read: the rows, and what could not be measured.
1192
+ *
1193
+ * Both travel together because a table of rows alone cannot be read honestly.
1194
+ * A target that failed contributes no row, and the reader would have to count
1195
+ * rows against a list of projects to notice it was gone.
1196
+ */
1197
+ export declare interface MetricCollection {
1198
+ failures: ProjectFailure[];
1199
+ rows: MetricRow[];
1200
+ }
1201
+
1202
+ /** One metric joined to the project that measured it and to its baseline. */
1203
+ export declare interface MetricRow extends ReportMetric {
1204
+ baseValue: number | undefined;
1205
+ /** False when this run did not rebuild the project, so the baseline stands in. */
1206
+ measured: boolean;
1207
+ project: string;
1208
+ /** True when the baseline had this metric and the current run does not. */
1209
+ removed: boolean;
1210
+ }
1211
+
1212
+ /** How hard a breached limit lands: advisory, or fatal. */
1213
+ export declare type MetricSeverity = "fail" | "warn";
1214
+
1215
+ /** The denomination a metric's value is counted in. */
1216
+ declare type MetricUnit = "bytes" | null;
1217
+
1218
+ /** Raised when the anchor helper is asked to write a file nothing named. */
1219
+ export declare class MissingMarkdownPathError extends Error {
1220
+ constructor();
1221
+ }
1222
+
1223
+ /** A message split into the emoji the console shows and the prose telemetry stores. */
1224
+ declare interface ParsedLogMessage {
1225
+ emoji: string | undefined;
1226
+ text: string;
1227
+ }
1228
+
1229
+ /**
1230
+ * Something one project's run could not do, and what it was trying to do it to.
1231
+ *
1232
+ * Neither a breach nor staleness: it is the run not having finished. A `target`
1233
+ * failure means a set of files nobody measured, so the rows it would have
1234
+ * produced are absent from the table entirely.
1235
+ */
1236
+ export declare interface ProjectFailure extends ReportFailure {
1237
+ project: string;
1238
+ }
1239
+
1240
+ /** What one project's report yielded: its metrics, and what failed. */
1241
+ export declare interface ProjectReport {
1242
+ failures: ReportFailure[];
1243
+ metrics: ReportMetric[];
1244
+ }
1245
+
1246
+ /** Arguments accepted when rendering the built-in badge report. */
1247
+ declare interface RenderBadgesArguments {
1248
+ destination: ResolvedCodometerMarkdownOutput;
1249
+ scope: MeasurementScope;
1250
+ statistics: CodeStatisticsResult;
1251
+ targets: readonly TargetSize[];
1252
+ }
1253
+
1254
+ /** Arguments accepted when rendering the configuration listing. */
1255
+ export declare interface RenderConfigurationArguments {
1256
+ described: readonly ConfiguredDirectory[];
1257
+ format: string;
1258
+ limitRows: readonly ConfiguredLimitRow[];
1259
+ limitsOnly: boolean;
1260
+ rootError: string | undefined;
1261
+ }
1262
+
1263
+ /**
1264
+ * Turns a resolved configuration listing into the document a reader gets.
1265
+ *
1266
+ * Markdown by default because the listing is a table and a repository already
1267
+ * reads codometer's output as markdown; JSON when something downstream parses
1268
+ * it. Kept apart from the command so the shape of the document is testable
1269
+ * without a command line, and apart from the service so gathering the
1270
+ * configuration never depends on how it will be shown.
1271
+ */
1272
+ export declare class RenderConfigurationService {
1273
+ constructor();
1274
+ /** Renders everything one configuration file resolved to. */
1275
+ private renderDirectory;
1276
+ /** Renders the limits as a markdown table, or a line saying there are none. */
1277
+ private renderLimitsTable;
1278
+ /** Renders a list of names, or an em dash when the list is empty. */
1279
+ private renderNames;
1280
+ /**
1281
+ * Renders why nothing answered for the walk root, when nothing did.
1282
+ *
1283
+ * Nothing at all otherwise, so the caller's line list is unchanged. Said at
1284
+ * the top of the document rather than left out: every limit below is still
1285
+ * real, but the walk that found them ran on the built-in exclusions rather
1286
+ * than the ones this repository declares.
1287
+ */
1288
+ private renderRootError;
1289
+ /** Renders one markdown table row, escaping nothing a path may not hold. */
1290
+ private renderRow;
1291
+ /** Renders the listing in the requested format. */
1292
+ render(args: RenderConfigurationArguments): string;
1293
+ }
1294
+
1295
+ /** Arguments accepted when rendering a whole document of badges. */
1296
+ declare interface RenderDocumentArguments {
1297
+ /** Placed above the badges, exactly as a spliced block places it. */
1298
+ description: string | undefined;
1299
+ scope: MeasurementScope;
1300
+ statistics: CodeStatisticsResult;
1301
+ targets: readonly TargetSize[];
1302
+ }
1303
+
1304
+ /** Renders a codometer change collection as the body of a report. */
1305
+ export declare class RenderModule {
1306
+ }
1307
+
1308
+ /** Arguments accepted when rendering a report as JSON. */
1309
+ export declare interface RenderReportArguments<Report> {
1310
+ indentation: number;
1311
+ report: Report;
1312
+ }
1313
+
1314
+ /** Arguments for rendering the whole section. */
1315
+ export declare interface RenderSectionArguments {
1316
+ /** Run the baseline came from, linked from the comparison line. */
1317
+ baselineUrl: string | undefined;
1318
+ /** What this run could not measure, so an absent row is never silent. */
1319
+ failures: readonly ProjectFailure[];
1320
+ rows: readonly MetricRow[];
1321
+ }
1322
+
1323
+ /** Renders codometer's measured changes as the body of the report. */
1324
+ export declare class RenderService {
1325
+ constructor();
1326
+ /** Groups items by whatever project name a case picks out of each one. */
1327
+ private groupByProject;
1328
+ /**
1329
+ * Whether a project's block should open expanded.
1330
+ *
1331
+ * A breach or a measurement failure must never sit behind a click, so those
1332
+ * two are the only reasons a block starts open. Everything else — a metric
1333
+ * that merely changed — is exactly the kind of thing a collapsed block
1334
+ * exists to keep off the screen by default.
1335
+ */
1336
+ private readIsOpen;
1337
+ /** Every project name either a row or a failure mentions, in sorted order. */
1338
+ private readProjects;
1339
+ /**
1340
+ * Picks the icon for one row, in priority order: a row that disappeared or
1341
+ * matched nothing outranks a breach, and a breach outranks plain growth.
1342
+ */
1343
+ private readStatus;
1344
+ /** Names the run the baseline came from, when there was one to compare against. */
1345
+ private renderComparison;
1346
+ /**
1347
+ * Names everything the run could not do, above the table rather than below.
1348
+ *
1349
+ * A failed target produces no row at all, so without this a project's block
1350
+ * can be empty of rows and still have something to say. A failure is
1351
+ * neither a breach nor staleness — it is the run not having finished — so it
1352
+ * is reported as its own thing and never folded into a metric's value.
1353
+ */
1354
+ private renderFailures;
1355
+ /** Renders one project's block, or nothing if it has nothing to show. */
1356
+ private renderProject;
1357
+ /** Renders one table row. */
1358
+ private renderRow;
1359
+ /**
1360
+ * Renders the report body: its heading, and one block per project with
1361
+ * something to show.
1362
+ */
1363
+ renderSection(args: RenderSectionArguments): string;
1364
+ }
1365
+
1366
+ /**
1367
+ * Where a project's codometer run leaves its report.
1368
+ *
1369
+ * One glob per workspace directory rather than a single recursive sweep, so a
1370
+ * stray report inside `node_modules` or a build output directory cannot be
1371
+ * mistaken for a project's own measurement.
1372
+ */
1373
+ export declare const REPORT_GLOBS: string[];
1374
+
1375
+ /** One report's failure entry, as codometer writes it. */
1376
+ declare type ReportFailure = NonNullable<CodometerReport["failures"]>[number];
1377
+
1378
+ /**
1379
+ * One metric codometer measured, and whatever limits it.
1380
+ *
1381
+ * Everything a row needs from the report itself, before the project it belongs
1382
+ * to and the baseline it is compared against are joined onto it. Carries every
1383
+ * metric a target produced — a byte count, a symbol count, anything — never
1384
+ * filtered by unit or by whether a limit was configured.
1385
+ */
1386
+ declare interface ReportMetric {
1387
+ /**
1388
+ * The severity of the worst limit this metric breached, if it breached one.
1389
+ *
1390
+ * One field for three states rather than a flag beside a severity, because
1391
+ * "breached, severity unknown" is not a state a metric can be in. `undefined`
1392
+ * is no breach, `"warn"` is advisory, `"fail"` stops the change.
1393
+ */
1394
+ breach: MetricSeverity | undefined;
1395
+ /**
1396
+ * True when the target's globs matched no files.
1397
+ *
1398
+ * Read straight off the report rather than inferred from a zero, because a
1399
+ * target that matched nothing clears every limit written against it while
1400
+ * measuring nothing at all — and a target that genuinely measures zero is a
1401
+ * different thing entirely.
1402
+ */
1403
+ empty: boolean;
1404
+ /** What the row is called in the table: the written label, else its target. */
1405
+ label: string;
1406
+ /**
1407
+ * The enforced limit, absent where nothing limits the metric.
1408
+ *
1409
+ * The lowest `fail` limit when there is one, else the lowest `warn` limit.
1410
+ * An advisory limit sitting below it is carried by the row's breach severity
1411
+ * instead.
1412
+ */
1413
+ limit: number | undefined;
1414
+ /**
1415
+ * The metric's name, which is the key it joins to its baseline on.
1416
+ *
1417
+ * Stable by construction — codometer builds it from the target's name and the
1418
+ * metric's path within that target, both of which are written in the
1419
+ * configuration — so a metric present on both sides never reads as one
1420
+ * removal plus one addition.
1421
+ */
1422
+ name: string;
1423
+ /** What `value` is denominated in, so a renderer can format it correctly. */
1424
+ unit: MetricUnit;
1425
+ value: number;
1426
+ }
1427
+
1428
+ /**
1429
+ * NestJS module that renders a measurement as codometer's own report.
1430
+ */
1431
+ export declare class ReportModule {
1432
+ }
1433
+
1434
+ /**
1435
+ * Writes out what a run measured, in codometer's own shape.
1436
+ *
1437
+ * Every metric is listed whether or not anything limits it, and a limit is
1438
+ * listed whether or not it was breached, so one document answers both "what is
1439
+ * this repository" and "what is it held to". Nothing is signalled by a missing
1440
+ * field: an empty match says so, and a metric nobody limited carries an
1441
+ * explicit `null` rather than no limit key at all.
1442
+ */
1443
+ export declare class ReportService {
1444
+ constructor();
1445
+ /** Writes out one limit as the report carries it. */
1446
+ private buildLimit;
1447
+ /** The name a metric answers to across runs. */
1448
+ private buildMetricName;
1449
+ /** Writes out every metric one target measured, in the order counted. */
1450
+ private buildMetrics;
1451
+ /**
1452
+ * Indexes the evaluated limits by the metric each one landed on.
1453
+ *
1454
+ * Keyed by the joined name rather than by the written path, because two
1455
+ * limits may be written differently — one qualified, one leaning on the
1456
+ * default target — and still address the same metric.
1457
+ *
1458
+ * Collected rather than overwritten. Several limits may land on one metric,
1459
+ * and keeping only the last would report a limit the gate is not the only
1460
+ * one enforcing.
1461
+ */
1462
+ private indexLimits;
1463
+ /** What a metric's number counts, where that is anything but things. */
1464
+ private readUnit;
1465
+ /**
1466
+ * Builds the report for one measurement.
1467
+ *
1468
+ * Targets keep the order they were measured in and metrics the order they
1469
+ * were counted in, so two runs over one tree produce byte-identical
1470
+ * documents — which is the whole of what makes staleness meaningful.
1471
+ */
1472
+ build(args: BuildReportArguments): CodometerReport_2;
1473
+ }
1474
+
1475
+ /** Arguments accepted when resolving where each output goes. */
1476
+ export declare interface ResolveDestinationsArguments {
1477
+ configuration: ResolvedCodometerConfiguration;
1478
+ options: MeasureCommandOptions;
1479
+ workingDirectory: string;
1480
+ }
1481
+
1482
+ /** Every file one run writes, and the destinations found along the way. */
1483
+ export declare interface ResolveDestinationsResult {
1484
+ destinations: RunDestinations;
1485
+ errors: string[];
1486
+ }
1487
+
1488
+ /** A JSON output destination, resolved for this run. */
1489
+ export declare interface ResolvedJsonDestination {
1490
+ custom: ResolvedCodometerCustomStatistic[];
1491
+ indentation: number;
1492
+ path: string;
1493
+ }
1494
+
1495
+ /** A markdown output destination, resolved for this run. */
1496
+ export declare interface ResolvedMarkdownDestination {
1497
+ custom: ResolvedCodometerCustomStatistic[];
1498
+ description: string | undefined;
1499
+ endMarker: string;
1500
+ path: string | undefined;
1501
+ startMarker: string;
1502
+ type: "markdown";
1503
+ write: undefined | WriteMarkdownOutput;
1504
+ }
1505
+
1506
+ /**
1507
+ * Every file one run writes.
1508
+ *
1509
+ * Two independent sinks: `json` is the report, and `markdown` is the badge
1510
+ * block spliced between two markers in a file somebody else wrote the rest
1511
+ * of. Neither says anything about standard output — that is `format`'s job
1512
+ * alone, so no destination can print a second document over the one a
1513
+ * pipeline was reading.
1514
+ */
1515
+ export declare interface RunDestinations {
1516
+ json: ResolvedJsonDestination | undefined;
1517
+ markdown: ResolvedMarkdownDestination | undefined;
1518
+ }
1519
+
1520
+ /**
1521
+ * Metric suffix marking a limit whose value is a number of bytes.
1522
+ *
1523
+ * A limit's value is a bare number by the time it is resolved, so nothing left
1524
+ * in the configuration says whether `256000` is a size or a count of files.
1525
+ * The metric path is what still does: only a size analysis produces `.size`.
1526
+ */
1527
+ export declare const SIZE_METRIC_SUFFIX = ".size";
1528
+
1529
+ /** Arguments accepted when syncing a JSON file with a report. */
1530
+ export declare interface SyncJsonArguments<Report> {
1531
+ check: boolean;
1532
+ indentation: number;
1533
+ path: string;
1534
+ report: Report;
1535
+ }
1536
+
1537
+ /** Arguments accepted when syncing a markdown destination with the statistics. */
1538
+ declare interface SyncMarkdownArguments {
1539
+ check: boolean;
1540
+ destination: ResolvedCodometerMarkdownOutput;
1541
+ scope: MeasurementScope;
1542
+ statistics: CodeStatisticsResult;
1543
+ targets: readonly TargetSize[];
1544
+ }
1545
+
1546
+ /** Column headers for a project's metrics table. */
1547
+ export declare const TABLE_HEADER: string[];
1548
+
1549
+ /**
1550
+ * One declared target's measured size, as the badge block reports it.
1551
+ *
1552
+ * Only the targets a run actually measured the size of reach this. A run that
1553
+ * declared none — the whole-repository run, which measures source and has no
1554
+ * build output of its own — renders no size badges at all, so the aggregate
1555
+ * README keeps the single `Repository Size` figure it already carries.
1556
+ */
1557
+ export declare interface TargetSize {
1558
+ bytes: number;
1559
+ compression: CodometerCompression;
1560
+ name: string;
1561
+ }
1562
+
1563
+ /** The exclusions a configuration walk uses, and why they may be the defaults. */
1564
+ export declare interface WalkExclusions {
1565
+ /** Why the walk root's configuration could not be read, if it could not. */
1566
+ error: string | undefined;
1567
+ exclude: string[];
1568
+ excludeFrom: string[];
1569
+ }
1570
+
1571
+ export { }