@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.
- package/LICENSE +21 -0
- package/README.md +573 -0
- package/dist/src/index.d.ts +1571 -0
- package/dist/src/index.js +1338 -0
- package/package.json +66 -0
|
@@ -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 { }
|