@codometer/cli 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 +1477 -0
- package/dist/main.module-_z85wgY7.js +658 -0
- package/dist/src/index.d.ts +649 -0
- package/dist/src/index.js +2 -0
- package/dist/src/main.d.ts +1 -0
- package/dist/src/main.js +26 -0
- package/package.json +77 -0
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
import { ChangesService } from '@codometer/output';
|
|
2
|
+
import { CommandRunner } from 'nest-commander';
|
|
3
|
+
import { ConfigurationListingService } from '@codometer/output';
|
|
4
|
+
import { ConfigurationService } from '@codometer/configuration';
|
|
5
|
+
import { ConsoleLogger } from '@nestjs/common';
|
|
6
|
+
import { DeliveryService } from '@codometer/output';
|
|
7
|
+
import { DestinationsService } from '@codometer/output';
|
|
8
|
+
import { DocumentsService } from '@codometer/output';
|
|
9
|
+
import { MeasureCommandOptions } from '@codometer/configuration';
|
|
10
|
+
import { MeasureFormat } from '@codometer/configuration';
|
|
11
|
+
import { MeasurementResult } from '@codometer/measurement';
|
|
12
|
+
import { MeasureService } from '@codometer/measurement';
|
|
13
|
+
import pino from 'pino';
|
|
14
|
+
import { RenderConfigurationService } from '@codometer/output';
|
|
15
|
+
import { RenderService } from '@codometer/output';
|
|
16
|
+
import { ReportService } from '@codometer/output';
|
|
17
|
+
import { ResolvedCodometerConfiguration } from '@codometer/configuration';
|
|
18
|
+
import { ResolvedMarkdownDestination } from '@codometer/output';
|
|
19
|
+
import { RunDestinations } from '@codometer/output';
|
|
20
|
+
import { RunMode } from '@codometer/configuration';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* CLI entry point for the pull request change report.
|
|
24
|
+
*
|
|
25
|
+
* Diffs every project's codometer report against a baseline snapshot and
|
|
26
|
+
* puts the result wherever it was asked for. Only ever reads and writes a
|
|
27
|
+
* local markdown file — handing that file to a pull request, an issue, or a
|
|
28
|
+
* wiki is the caller's job, which keeps this command independent of any one
|
|
29
|
+
* forge's API.
|
|
30
|
+
*/
|
|
31
|
+
export declare class ChangesCommand extends CommandRunner {
|
|
32
|
+
private readonly configurationService;
|
|
33
|
+
private readonly changesService;
|
|
34
|
+
private readonly documentsService;
|
|
35
|
+
private readonly renderService;
|
|
36
|
+
private readonly logger;
|
|
37
|
+
constructor(configurationService: ConfigurationService, changesService: ChangesService, documentsService: DocumentsService, renderService: RenderService, logger: LoggerService);
|
|
38
|
+
/** Parse the baseline directory holding a snapshot of the reports. */
|
|
39
|
+
parseBaseline(value: unknown): string | undefined;
|
|
40
|
+
/** Parse the run URL the baseline came from, linked from the summary. */
|
|
41
|
+
parseBaselineUrl(value: unknown): string | undefined;
|
|
42
|
+
/** Parse the directory to look for codometer reports in. */
|
|
43
|
+
parseDirectory(value: unknown): string;
|
|
44
|
+
/** Parse the markdown document the report is spliced into. */
|
|
45
|
+
parseMarkdown(value: unknown): string | undefined;
|
|
46
|
+
/** Parse the file the report is written to on its own. */
|
|
47
|
+
parseOutput(value: unknown): string | undefined;
|
|
48
|
+
/** Diffs every project's report against the baseline, and emits the result. */
|
|
49
|
+
run(_passedParameters: string[], options: ChangesCommandOptions): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Options accepted by the `changes` command.
|
|
54
|
+
*
|
|
55
|
+
* Typed loosely because commander skips an option's parser when the flag
|
|
56
|
+
* arrives without a value, handing the command `true` instead of text.
|
|
57
|
+
*/
|
|
58
|
+
declare interface ChangesCommandOptions {
|
|
59
|
+
baseline?: unknown;
|
|
60
|
+
baselineUrl?: unknown;
|
|
61
|
+
directory?: unknown;
|
|
62
|
+
markdown?: unknown;
|
|
63
|
+
output?: unknown;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Wires the `changes` command that reports codometer's diff against main. */
|
|
67
|
+
export declare class ChangesModule {
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* CLI entry point for listing what a repository configures.
|
|
72
|
+
*
|
|
73
|
+
* Reports configuration and never measurement: no build is required, nothing
|
|
74
|
+
* is compressed, and no limit is evaluated. That separation is the point — a
|
|
75
|
+
* repository whose limits live one per project has no single place left to
|
|
76
|
+
* read them as a set, and this is that place, without waiting on the builds
|
|
77
|
+
* a measurement would need.
|
|
78
|
+
*/
|
|
79
|
+
export declare class ConfigurationCommand extends CommandRunner {
|
|
80
|
+
private readonly codometerConfigurationService;
|
|
81
|
+
private readonly configurationService;
|
|
82
|
+
private readonly renderConfigurationService;
|
|
83
|
+
private readonly logger;
|
|
84
|
+
constructor(codometerConfigurationService: ConfigurationService, configurationService: ConfigurationListingService, renderConfigurationService: RenderConfigurationService, logger: LoggerService);
|
|
85
|
+
/**
|
|
86
|
+
* Parse the configuration file answering for the walk root.
|
|
87
|
+
*
|
|
88
|
+
* The exclusions the walk uses come from whatever configuration answers for
|
|
89
|
+
* the directory being listed, and a workspace stating its shared object
|
|
90
|
+
* somewhere every project spreads it from has nothing at its own root for
|
|
91
|
+
* the upward search to find — so it names that file here, the same way its
|
|
92
|
+
* measurement target already does.
|
|
93
|
+
*/
|
|
94
|
+
parseConfig(value: string | undefined): string | undefined;
|
|
95
|
+
/** Parse the directory to look for configuration files beneath. */
|
|
96
|
+
parseDirectory(value: unknown): string;
|
|
97
|
+
/** Parse the output format the listing is rendered in. */
|
|
98
|
+
parseFormat(value: unknown): string;
|
|
99
|
+
/** Parse whether to list only the limits. */
|
|
100
|
+
parseLimits(): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Lists what the tree beneath the given directory configures.
|
|
103
|
+
*
|
|
104
|
+
* Writes to standard output rather than a file: the listing is something a
|
|
105
|
+
* reader looks at or pipes onward, and unlike a measurement it has no report
|
|
106
|
+
* anything else consumes.
|
|
107
|
+
*/
|
|
108
|
+
run(_passedParameters: string[], options?: ConfigurationCommandOptions): Promise<void>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Options accepted by the configuration command. */
|
|
112
|
+
declare interface ConfigurationCommandOptions {
|
|
113
|
+
config?: string | undefined;
|
|
114
|
+
directory?: string | undefined;
|
|
115
|
+
format?: string | undefined;
|
|
116
|
+
limits?: boolean | undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Wires the command that lists what a repository configures.
|
|
121
|
+
*
|
|
122
|
+
* Nothing but wiring. Reading the tree and rendering what it found belong to
|
|
123
|
+
* `@codometer/output`, which is where every other render target already
|
|
124
|
+
* lives; this module hands the command the two it needs.
|
|
125
|
+
*/
|
|
126
|
+
export declare class ConfigurationModule {
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Structured values that belong beside a log line rather than inside it.
|
|
131
|
+
*
|
|
132
|
+
* Counts, percentages, and durations are the values that change on every
|
|
133
|
+
* occurrence, so they are carried as fields: the message stays constant and
|
|
134
|
+
* groupable in telemetry, and the numbers stay queryable instead of having to
|
|
135
|
+
* be parsed back out of prose.
|
|
136
|
+
*
|
|
137
|
+
* The named members are the recurring ones; the index signature keeps the
|
|
138
|
+
* argument open for whatever a given call site needs to attach.
|
|
139
|
+
*/
|
|
140
|
+
declare interface LogData {
|
|
141
|
+
[key: string]: unknown;
|
|
142
|
+
/** How many things the operation handled. */
|
|
143
|
+
count?: number;
|
|
144
|
+
/** Wall-clock milliseconds the operation took. */
|
|
145
|
+
durationMs?: number;
|
|
146
|
+
/** Completion between 0 and 100. */
|
|
147
|
+
percent?: number;
|
|
148
|
+
/** How many things the operation set out to handle. */
|
|
149
|
+
total?: number;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Transient-scoped logger so each injecting class gets its own instance.
|
|
154
|
+
* Each consumer calls `setContext(ClassName.name)` to tag every log line
|
|
155
|
+
* with the originating class. Backed by pino for structured JSON output in
|
|
156
|
+
* production and human-readable pretty-print in development.
|
|
157
|
+
*
|
|
158
|
+
* Messages follow one grammar: an emoji naming the subject, a verb in present
|
|
159
|
+
* progressive or past tense, then the object. Values that vary per call —
|
|
160
|
+
* counts, percentages, durations — go in the `data` argument rather than the
|
|
161
|
+
* message, so the message stays constant enough for telemetry to group on.
|
|
162
|
+
*
|
|
163
|
+
* ```ts
|
|
164
|
+
* this.logger.info("📥 Downloading CSEL sources", undefined, { total: 428 });
|
|
165
|
+
* this.logger.info("📥 Downloaded CSEL sources", undefined, { count: 412 });
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
declare @Injectable({ scope: Scope.TRANSIENT })
|
|
169
|
+
class LoggerService extends ConsoleLogger {
|
|
170
|
+
// 🏗 Dependency Injection
|
|
171
|
+
|
|
172
|
+
constructor() {
|
|
173
|
+
super();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 🔐 Private Fields
|
|
177
|
+
|
|
178
|
+
private static readonly isProduction =
|
|
179
|
+
process.env["NODE_ENV"] === "production";
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Built on first use, not when this file is evaluated.
|
|
183
|
+
*
|
|
184
|
+
* A destination fixed at import time could only ever be chosen by this
|
|
185
|
+
* package, since every consumer's own code runs after its imports.
|
|
186
|
+
*/
|
|
187
|
+
private static rootLogger: pino.Logger | undefined;
|
|
188
|
+
|
|
189
|
+
/** Whether lines go to standard error instead of standard output. */
|
|
190
|
+
private static writesToStandardError = false;
|
|
191
|
+
|
|
192
|
+
private child: pino.Logger = LoggerService.root;
|
|
193
|
+
|
|
194
|
+
// 🔑 Public Fields
|
|
195
|
+
|
|
196
|
+
// 🔏 Private Methods
|
|
197
|
+
|
|
198
|
+
/** Build the pino instance for production or local development output. */
|
|
199
|
+
private static createRootLogger(): pino.Logger {
|
|
200
|
+
const level = process.env["LOG_LEVEL"] ?? "info";
|
|
201
|
+
|
|
202
|
+
if (LoggerService.isProduction) {
|
|
203
|
+
return LoggerService.writesToStandardError
|
|
204
|
+
? pino({ level }, pino.destination(STANDARD_ERROR_DESCRIPTOR))
|
|
205
|
+
: pino({ level });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return pino({
|
|
209
|
+
level,
|
|
210
|
+
transport: {
|
|
211
|
+
options: {
|
|
212
|
+
colorize: true,
|
|
213
|
+
destination: LoggerService.writesToStandardError
|
|
214
|
+
? STANDARD_ERROR_DESCRIPTOR
|
|
215
|
+
: STANDARD_OUTPUT_DESCRIPTOR,
|
|
216
|
+
// The emoji is a field, not part of the message, so the console can
|
|
217
|
+
// show it while telemetry stores unadorned prose. `ignore` then keeps
|
|
218
|
+
// it from being printed a second time in the trailing object.
|
|
219
|
+
ignore: "pid,hostname,emoji",
|
|
220
|
+
messageFormat: "{emoji} {msg}",
|
|
221
|
+
singleLine: true,
|
|
222
|
+
},
|
|
223
|
+
target: "pino-pretty",
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Sends every subsequent line to standard error instead of standard output.
|
|
230
|
+
*
|
|
231
|
+
* For a command-line application whose standard output *is* its result. A log
|
|
232
|
+
* line sharing that stream is not a diagnostic beside the data, it is a
|
|
233
|
+
* corruption of it. Call it before anything logs — the first statement of the
|
|
234
|
+
* application's bootstrap.
|
|
235
|
+
*
|
|
236
|
+
* A call after the first line warns and changes nothing: the destination is
|
|
237
|
+
* fixed when the pino instance is built, and tearing down a transport
|
|
238
|
+
* somebody is writing through would be worse than refusing. The warning is
|
|
239
|
+
* the point — silently leaving the lines on standard output is how a caller
|
|
240
|
+
* would ship a corrupted pipe without ever being told.
|
|
241
|
+
*/
|
|
242
|
+
static logToStandardError(): void {
|
|
243
|
+
if (LoggerService.rootLogger !== undefined) {
|
|
244
|
+
process.emitWarning(
|
|
245
|
+
"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.",
|
|
246
|
+
);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
LoggerService.writesToStandardError = true;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Fails a malformed message in development, and never in production.
|
|
255
|
+
*
|
|
256
|
+
* A logger that throws in production turns an observability call into an
|
|
257
|
+
* outage, so the check runs only where a developer is present to fix it.
|
|
258
|
+
*/
|
|
259
|
+
private assertConventionalMessage(args: {
|
|
260
|
+
context: string | undefined;
|
|
261
|
+
parsed: ParsedLogMessage;
|
|
262
|
+
}): void {
|
|
263
|
+
if (
|
|
264
|
+
LoggerService.isProduction ||
|
|
265
|
+
this.shouldSkipConventionalMessageValidation(args.context)
|
|
266
|
+
) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const violation = this.getConventionalMessageViolation(args.parsed);
|
|
271
|
+
|
|
272
|
+
if (violation !== undefined) {
|
|
273
|
+
throw new Error(violation);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Assembles the object pino merges into the line. */
|
|
278
|
+
private buildBindings(args: {
|
|
279
|
+
context: string | undefined;
|
|
280
|
+
data: LogData | undefined;
|
|
281
|
+
parsed: ParsedLogMessage;
|
|
282
|
+
}): Record<string, unknown> {
|
|
283
|
+
this.assertConventionalMessage({
|
|
284
|
+
context: args.context,
|
|
285
|
+
parsed: args.parsed,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
...args.data,
|
|
290
|
+
context: args.context,
|
|
291
|
+
// Telemetry gets prose; only the console-bound transport reads this.
|
|
292
|
+
...(LoggerService.isProduction ? {} : { emoji: args.parsed.emoji }),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Returns a human-readable explanation when the message format is invalid. */
|
|
297
|
+
private getConventionalMessageViolation(
|
|
298
|
+
parsed: ParsedLogMessage,
|
|
299
|
+
): string | undefined {
|
|
300
|
+
const emoji = parsed.emoji;
|
|
301
|
+
const text = parsed.text;
|
|
302
|
+
|
|
303
|
+
if (emoji === undefined) {
|
|
304
|
+
return `Log message must start with an emoji naming its subject, then a verb: "${text}"`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const firstWord = FIRST_WORD_PATTERN.exec(text)?.[1];
|
|
308
|
+
|
|
309
|
+
if (firstWord === undefined || !this.isConventionalVerb(firstWord)) {
|
|
310
|
+
return `Log message must begin with a verb in present progressive or past tense, got "${firstWord ?? ""}": "${emoji} ${text}"`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Whether a word is a verb in one of the two tenses the convention allows.
|
|
318
|
+
*
|
|
319
|
+
* Present progressive means the operation is under way; past means it
|
|
320
|
+
* finished. Regular morphology covers both, so a new verb needs no
|
|
321
|
+
* registration anywhere — only irregular pasts are enumerated.
|
|
322
|
+
*/
|
|
323
|
+
private isConventionalVerb(word: string): boolean {
|
|
324
|
+
const lowercased = word.toLowerCase();
|
|
325
|
+
|
|
326
|
+
return (
|
|
327
|
+
lowercased.endsWith("ing") ||
|
|
328
|
+
lowercased.endsWith("ed") ||
|
|
329
|
+
IRREGULAR_PAST_VERBS.has(lowercased)
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Splits a leading emoji off a message, leaving prose behind. */
|
|
334
|
+
private parseMessage(message: unknown): ParsedLogMessage {
|
|
335
|
+
const text = String(message);
|
|
336
|
+
const match = LEADING_EMOJI_PATTERN.exec(text);
|
|
337
|
+
const emoji = match?.[1];
|
|
338
|
+
|
|
339
|
+
return emoji === undefined
|
|
340
|
+
? { emoji: undefined, text }
|
|
341
|
+
: { emoji, text: text.slice(match?.[0].length) };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** Whether a context is intentionally exempt from the validation rule. */
|
|
345
|
+
private shouldSkipConventionalMessageValidation(
|
|
346
|
+
context: string | undefined,
|
|
347
|
+
): boolean {
|
|
348
|
+
return context !== undefined && UNVALIDATED_LOG_CONTEXTS.has(context);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// 🌎 Public Methods
|
|
352
|
+
|
|
353
|
+
/** The pino instance every logger's child is taken from. */
|
|
354
|
+
private static get root(): pino.Logger {
|
|
355
|
+
LoggerService.rootLogger ??= LoggerService.createRootLogger();
|
|
356
|
+
|
|
357
|
+
return LoggerService.rootLogger;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Normalizes unknown errors into a stable message and timestamped log line. */
|
|
361
|
+
buildErrorLogEntry(
|
|
362
|
+
context: string,
|
|
363
|
+
error: unknown,
|
|
364
|
+
): { errorMessage: string; logLine: string } {
|
|
365
|
+
const errorMessage =
|
|
366
|
+
error instanceof Error ? error.stack || error.message : String(error);
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
errorMessage,
|
|
370
|
+
logLine: `[${new Date().toISOString()}] ${context}: ${errorMessage}\n`,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** Builds a timestamped output log file path and ensures the output directory exists. */
|
|
375
|
+
createTimestampedOutputLogFilePath(filePrefix: string): string {
|
|
376
|
+
const outputDirectory = path.join(process.cwd(), "output");
|
|
377
|
+
if (!existsSync(outputDirectory)) {
|
|
378
|
+
mkdirSync(outputDirectory, { recursive: true });
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return path.join(
|
|
382
|
+
outputDirectory,
|
|
383
|
+
`${filePrefix}-${new Date().toISOString().replaceAll(/[:.]/g, "-")}.log`,
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** Logs a debug message at the `debug` level. */
|
|
388
|
+
override debug(message: unknown, context?: string, data?: LogData): void {
|
|
389
|
+
const parsed = this.parseMessage(message);
|
|
390
|
+
this.child.debug(
|
|
391
|
+
this.buildBindings({ context: context ?? this.context, data, parsed }),
|
|
392
|
+
parsed.text,
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Logs an error message at the `error` level, optionally including a stack trace.
|
|
398
|
+
*
|
|
399
|
+
* `ConsoleLogger.error` spends a third slot on a context string that the
|
|
400
|
+
* other levels do not have, so this one accepts either: a string keeps
|
|
401
|
+
* NestJS's meaning, an object is structured data like everywhere else.
|
|
402
|
+
*/
|
|
403
|
+
override error(
|
|
404
|
+
message: unknown,
|
|
405
|
+
stackOrContext?: string,
|
|
406
|
+
contextOrData?: LogData | string,
|
|
407
|
+
): void {
|
|
408
|
+
const parsed = this.parseMessage(message);
|
|
409
|
+
const data = typeof contextOrData === "object" ? contextOrData : undefined;
|
|
410
|
+
const context =
|
|
411
|
+
typeof contextOrData === "string" ? contextOrData : this.context;
|
|
412
|
+
|
|
413
|
+
this.child.error(
|
|
414
|
+
{
|
|
415
|
+
...this.buildBindings({ context, data, parsed }),
|
|
416
|
+
stack: stackOrContext,
|
|
417
|
+
},
|
|
418
|
+
parsed.text,
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** Logs an informational message at the `info` level. */
|
|
423
|
+
info(message: unknown, context?: string, data?: LogData): void {
|
|
424
|
+
const parsed = this.parseMessage(message);
|
|
425
|
+
this.child.info(
|
|
426
|
+
this.buildBindings({ context: context ?? this.context, data, parsed }),
|
|
427
|
+
parsed.text,
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Logs an informational message at the `info` level.
|
|
433
|
+
*
|
|
434
|
+
* NestJS and `nest-commander` call this method directly as part of the
|
|
435
|
+
* framework's own `LoggerService` contract, so it must keep working
|
|
436
|
+
* exactly as before. Application code should call `info` instead — the
|
|
437
|
+
* same behavior under a name that says what level it logs at.
|
|
438
|
+
*/
|
|
439
|
+
override log(message: unknown, context?: string, data?: LogData): void {
|
|
440
|
+
this.info(message, context, data);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** Sets the context label included in every subsequent log line. */
|
|
444
|
+
override setContext(context: string): void {
|
|
445
|
+
super.setContext(context);
|
|
446
|
+
this.child = LoggerService.root.child({ context });
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** Logs a verbose message at the `trace` level. */
|
|
450
|
+
override verbose(message: unknown, context?: string, data?: LogData): void {
|
|
451
|
+
const parsed = this.parseMessage(message);
|
|
452
|
+
this.child.trace(
|
|
453
|
+
this.buildBindings({ context: context ?? this.context, data, parsed }),
|
|
454
|
+
parsed.text,
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** Logs a warning message at the `warn` level. */
|
|
459
|
+
override warn(message: unknown, context?: string, data?: LogData): void {
|
|
460
|
+
const parsed = this.parseMessage(message);
|
|
461
|
+
this.child.warn(
|
|
462
|
+
this.buildBindings({ context: context ?? this.context, data, parsed }),
|
|
463
|
+
parsed.text,
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Root NestJS application module.
|
|
470
|
+
*/
|
|
471
|
+
export declare class MainModule {
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* CLI entry point for the repository measurement workflow.
|
|
476
|
+
*/
|
|
477
|
+
export declare class MeasureCommand extends CommandRunner {
|
|
478
|
+
private readonly configurationService;
|
|
479
|
+
private readonly measureService;
|
|
480
|
+
private readonly deliveryService;
|
|
481
|
+
private readonly reportService;
|
|
482
|
+
private readonly destinationsService;
|
|
483
|
+
private readonly logger;
|
|
484
|
+
constructor(configurationService: ConfigurationService, measureService: MeasureService, deliveryService: DeliveryService, reportService: ReportService, destinationsService: DestinationsService, logger: LoggerService);
|
|
485
|
+
/**
|
|
486
|
+
* Say which files were left out because codometer writes them.
|
|
487
|
+
*
|
|
488
|
+
* Announced rather than left to be noticed. A file missing from the counts
|
|
489
|
+
* with no explanation reads as a measurement bug, and the last repository to
|
|
490
|
+
* hit this wrote the exclusion into its ignore file by hand.
|
|
491
|
+
*/
|
|
492
|
+
private announceOutputPaths;
|
|
493
|
+
/**
|
|
494
|
+
* Replaces every configured input with the globs `--inputs` named, when it
|
|
495
|
+
* was passed.
|
|
496
|
+
*
|
|
497
|
+
* Not a filter over configured inputs: the run measures exactly the globs
|
|
498
|
+
* given, running the `language` analysis over them, with no other input —
|
|
499
|
+
* not even the built-in `codebase` one — active.
|
|
500
|
+
*/
|
|
501
|
+
private applyInputsOverride;
|
|
502
|
+
/**
|
|
503
|
+
* Read the configuration, or say why it could not be read.
|
|
504
|
+
*
|
|
505
|
+
* A configuration nothing can parse fails the run rather than being taken as
|
|
506
|
+
* an empty one, and so does a directory with no configuration file anywhere
|
|
507
|
+
* above it: `format` is required with no code-level fallback, so an absent
|
|
508
|
+
* file fails on the missing `format` exactly as a file that forgot to write
|
|
509
|
+
* one does. A shared default object, spread by each project's own
|
|
510
|
+
* `codometer.config.ts`, is what states it once for a workspace.
|
|
511
|
+
*/
|
|
512
|
+
private readConfiguration;
|
|
513
|
+
/** Says the command line could not be made sense of, and fails the run. */
|
|
514
|
+
private rejectCommandLine;
|
|
515
|
+
/** Report every breached limit, and say whether one of them fails the run. */
|
|
516
|
+
private reportBreaches;
|
|
517
|
+
/**
|
|
518
|
+
* Report whatever the run could not do, and say whether it fails the run.
|
|
519
|
+
*
|
|
520
|
+
* A failure is neither staleness nor a breach: it is the run not having
|
|
521
|
+
* finished. It stops any run that produces or gates an output, because a
|
|
522
|
+
* report built from a partial measurement is not one to publish and a gate
|
|
523
|
+
* that could not be evaluated has not been passed. A run that does neither
|
|
524
|
+
* says so and exits clean, exactly as its flags promised.
|
|
525
|
+
*/
|
|
526
|
+
private reportFailures;
|
|
527
|
+
/** Weigh every finding, set the exit code once, and say the run is done. */
|
|
528
|
+
private reportFindings;
|
|
529
|
+
/** Report every stale destination, and say whether that fails the run. */
|
|
530
|
+
private reportStaleness;
|
|
531
|
+
/**
|
|
532
|
+
* Reads the flags into everything the rest of the run needs: the mode, the
|
|
533
|
+
* resolved configuration, what to print, and where each output goes.
|
|
534
|
+
*
|
|
535
|
+
* `undefined` when the run cannot proceed at all — a rejected command line,
|
|
536
|
+
* or a configuration that failed to load — with the refusal and exit code
|
|
537
|
+
* already reported, so nothing after this has to check twice.
|
|
538
|
+
*/
|
|
539
|
+
private resolveRunPlan;
|
|
540
|
+
/**
|
|
541
|
+
* Resolve the directory the run measures, and announce that it started.
|
|
542
|
+
*
|
|
543
|
+
* Always the process's working directory: unlike every other flag, there is
|
|
544
|
+
* no per-run override for it — a run measures where it was invoked.
|
|
545
|
+
*/
|
|
546
|
+
private resolveWorkingDirectory;
|
|
547
|
+
/**
|
|
548
|
+
* Parse the set of things the run fails on from command-line input.
|
|
549
|
+
*
|
|
550
|
+
* The parser runs only when `--check` carries a value, so anything reaching
|
|
551
|
+
* it is a written set. A `--check` with no value never arrives here at all
|
|
552
|
+
* and is refused later, because a set with nothing in it is indistinguishable
|
|
553
|
+
* from the flag having been left off — which is how check mode once silently
|
|
554
|
+
* became write mode.
|
|
555
|
+
*/
|
|
556
|
+
parseCheck(value: string): string;
|
|
557
|
+
/**
|
|
558
|
+
* Parse the optional configuration path from command-line input.
|
|
559
|
+
*/
|
|
560
|
+
parseConfig(value: string | undefined): string | undefined;
|
|
561
|
+
/**
|
|
562
|
+
* Parse what the run prints from command-line input.
|
|
563
|
+
*
|
|
564
|
+
* Says nothing about which files are written — that is each `--output-*`
|
|
565
|
+
* flag's job. Omitted, it reads the resolved configuration's own required
|
|
566
|
+
* `format` field rather than inferring one from which other flags are
|
|
567
|
+
* present.
|
|
568
|
+
*/
|
|
569
|
+
parseFormat(value: string): string;
|
|
570
|
+
/**
|
|
571
|
+
* Parse the glob array that replaces every configured input, from
|
|
572
|
+
* command-line input.
|
|
573
|
+
*
|
|
574
|
+
* Each value commander hands the parser is one glob; they accumulate into
|
|
575
|
+
* one array across the whole `--inputs` invocation.
|
|
576
|
+
*/
|
|
577
|
+
parseInputs(value: string, previous?: string[]): string[];
|
|
578
|
+
/**
|
|
579
|
+
* Parse the report's destination from command-line input.
|
|
580
|
+
*
|
|
581
|
+
* `true` when the flag was passed with no value, asking this run to write
|
|
582
|
+
* wherever the resolved configuration's own JSON output says to; a string
|
|
583
|
+
* when a path was given, which is used for this destination alone.
|
|
584
|
+
*/
|
|
585
|
+
parseOutputJson(value: string | true): string | true;
|
|
586
|
+
/**
|
|
587
|
+
* Parse the markdown badge block's destination from command-line input.
|
|
588
|
+
*
|
|
589
|
+
* `true` when the flag was passed with no value, asking this run to write
|
|
590
|
+
* wherever the resolved configuration's own markdown output says to; a
|
|
591
|
+
* string when a path was given, which is used for this destination alone.
|
|
592
|
+
*/
|
|
593
|
+
parseOutputMarkdown(value: string | true): string | true;
|
|
594
|
+
/**
|
|
595
|
+
* Measure the repository and produce every resolved output.
|
|
596
|
+
*
|
|
597
|
+
* Flags are independent: `--output-json`/`--output-markdown` each write
|
|
598
|
+
* their own destination, `--check reports` fails on a stale report,
|
|
599
|
+
* `--check limits` fails on a breached limit, `--format` prints, and none of
|
|
600
|
+
* them turns another on. Every output is produced before any finding is
|
|
601
|
+
* weighed, so a run that writes and gates leaves the report behind even
|
|
602
|
+
* when the gate trips.
|
|
603
|
+
*/
|
|
604
|
+
run(_passedParameters: string[], options: MeasureCommandOptions): Promise<void>;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* NestJS module that wires the measure command to the layers it composes.
|
|
609
|
+
*
|
|
610
|
+
* Nothing but wiring: the configuration layer reads the command line and the
|
|
611
|
+
* configuration file, the measurement layer counts, and the output layer
|
|
612
|
+
* resolves destinations, builds the report, and delivers it. The command
|
|
613
|
+
* calls them in order and sets an exit code.
|
|
614
|
+
*/
|
|
615
|
+
export declare class MeasureModule {
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/** A message split into the emoji the console shows and the prose telemetry stores. */
|
|
619
|
+
declare interface ParsedLogMessage {
|
|
620
|
+
emoji: string | undefined;
|
|
621
|
+
text: string;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/** Arguments accepted when weighing what a run found. */
|
|
625
|
+
export declare interface ReportFindingsArguments {
|
|
626
|
+
measurement: MeasurementResult;
|
|
627
|
+
mode: RunMode;
|
|
628
|
+
/** Destinations found not to hold the current output. Only ever non-empty
|
|
629
|
+
* when the run was comparing, since nothing else reads a destination. */
|
|
630
|
+
stalePaths: string[];
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Everything a run needs once its command line has been made sense of: the
|
|
635
|
+
* resolved configuration, what to print, where each output goes, and what
|
|
636
|
+
* the run does with what it measures.
|
|
637
|
+
*/
|
|
638
|
+
export declare interface RunPlan {
|
|
639
|
+
configuration: ResolvedCodometerConfiguration;
|
|
640
|
+
/** Where the console's own badge block comes from, resolved independently
|
|
641
|
+
* of `destinations.markdown` so it never depends on which `--output-*` flag
|
|
642
|
+
* was passed. */
|
|
643
|
+
consoleMarkdown: ResolvedMarkdownDestination | undefined;
|
|
644
|
+
destinations: RunDestinations;
|
|
645
|
+
format: MeasureFormat | undefined;
|
|
646
|
+
mode: RunMode;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
export { }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/src/main.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { c as e, t } from "../main.module-_z85wgY7.js";
|
|
3
|
+
import { CommandFactory as n } from "nest-commander";
|
|
4
|
+
import "reflect-metadata";
|
|
5
|
+
//#region packages/ic-suite/codometer/codometer-cli/src/main.utilities.ts
|
|
6
|
+
function r(e) {
|
|
7
|
+
let t = /* @__PURE__ */ new Set([
|
|
8
|
+
"changes",
|
|
9
|
+
"configuration",
|
|
10
|
+
"help",
|
|
11
|
+
"measure"
|
|
12
|
+
]), n = /* @__PURE__ */ new Set(["--help", "-h"]), [r] = e;
|
|
13
|
+
return r !== void 0 && (t.has(r) || n.has(r)) ? [...e] : ["measure", ...e];
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region packages/ic-suite/codometer/codometer-cli/src/main.ts
|
|
17
|
+
async function i() {
|
|
18
|
+
e.logToStandardError();
|
|
19
|
+
let i = new e();
|
|
20
|
+
i.setContext("CommandFactory"), process.argv = [...process.argv.slice(0, 2), ...r(process.argv.slice(2))], await n.run(t, {
|
|
21
|
+
bufferLogs: !0,
|
|
22
|
+
logger: i
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
i();
|
|
26
|
+
//#endregion
|