@codependix/nestjs-modules 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,666 @@
1
+ import { ConsoleLogger } from '@nestjs/common';
2
+ import pino from 'pino';
3
+
4
+ /**
5
+ * Structured values that belong beside a log line rather than inside it.
6
+ *
7
+ * Counts, percentages, and durations are the values that change on every
8
+ * occurrence, so they are carried as fields: the message stays constant and
9
+ * groupable in telemetry, and the numbers stay queryable instead of having to
10
+ * be parsed back out of prose.
11
+ *
12
+ * The named members are the recurring ones; the index signature keeps the
13
+ * argument open for whatever a given call site needs to attach.
14
+ */
15
+ declare interface LogData {
16
+ [key: string]: unknown;
17
+ /** How many things the operation handled. */
18
+ count?: number;
19
+ /** Wall-clock milliseconds the operation took. */
20
+ durationMs?: number;
21
+ /** Completion between 0 and 100. */
22
+ percent?: number;
23
+ /** How many things the operation set out to handle. */
24
+ total?: number;
25
+ }
26
+
27
+ /**
28
+ * Transient-scoped logger so each injecting class gets its own instance.
29
+ * Each consumer calls `setContext(ClassName.name)` to tag every log line
30
+ * with the originating class. Backed by pino for structured JSON output in
31
+ * production and human-readable pretty-print in development.
32
+ *
33
+ * Messages follow one grammar: an emoji naming the subject, a verb in present
34
+ * progressive or past tense, then the object. Values that vary per call —
35
+ * counts, percentages, durations — go in the `data` argument rather than the
36
+ * message, so the message stays constant enough for telemetry to group on.
37
+ *
38
+ * ```ts
39
+ * this.logger.info("📥 Downloading CSEL sources", undefined, { total: 428 });
40
+ * this.logger.info("📥 Downloaded CSEL sources", undefined, { count: 412 });
41
+ * ```
42
+ */
43
+ declare @Injectable({ scope: Scope.TRANSIENT })
44
+ class LoggerService extends ConsoleLogger {
45
+ // 🏗 Dependency Injection
46
+
47
+ constructor() {
48
+ super();
49
+ }
50
+
51
+ // 🔐 Private Fields
52
+
53
+ private static readonly isProduction =
54
+ process.env["NODE_ENV"] === "production";
55
+
56
+ /**
57
+ * Built on first use, not when this file is evaluated.
58
+ *
59
+ * A destination fixed at import time could only ever be chosen by this
60
+ * package, since every consumer's own code runs after its imports.
61
+ */
62
+ private static rootLogger: pino.Logger | undefined;
63
+
64
+ /** Whether lines go to standard error instead of standard output. */
65
+ private static writesToStandardError = false;
66
+
67
+ private child: pino.Logger = LoggerService.root;
68
+
69
+ // 🔑 Public Fields
70
+
71
+ // 🔏 Private Methods
72
+
73
+ /** Build the pino instance for production or local development output. */
74
+ private static createRootLogger(): pino.Logger {
75
+ const level = process.env["LOG_LEVEL"] ?? "info";
76
+
77
+ if (LoggerService.isProduction) {
78
+ return LoggerService.writesToStandardError
79
+ ? pino({ level }, pino.destination(STANDARD_ERROR_DESCRIPTOR))
80
+ : pino({ level });
81
+ }
82
+
83
+ return pino({
84
+ level,
85
+ transport: {
86
+ options: {
87
+ colorize: true,
88
+ destination: LoggerService.writesToStandardError
89
+ ? STANDARD_ERROR_DESCRIPTOR
90
+ : STANDARD_OUTPUT_DESCRIPTOR,
91
+ // The emoji is a field, not part of the message, so the console can
92
+ // show it while telemetry stores unadorned prose. `ignore` then keeps
93
+ // it from being printed a second time in the trailing object.
94
+ ignore: "pid,hostname,emoji",
95
+ messageFormat: "{emoji} {msg}",
96
+ singleLine: true,
97
+ },
98
+ target: "pino-pretty",
99
+ },
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Sends every subsequent line to standard error instead of standard output.
105
+ *
106
+ * For a command-line application whose standard output *is* its result. A log
107
+ * line sharing that stream is not a diagnostic beside the data, it is a
108
+ * corruption of it. Call it before anything logs — the first statement of the
109
+ * application's bootstrap.
110
+ *
111
+ * A call after the first line warns and changes nothing: the destination is
112
+ * fixed when the pino instance is built, and tearing down a transport
113
+ * somebody is writing through would be worse than refusing. The warning is
114
+ * the point — silently leaving the lines on standard output is how a caller
115
+ * would ship a corrupted pipe without ever being told.
116
+ */
117
+ static logToStandardError(): void {
118
+ if (LoggerService.rootLogger !== undefined) {
119
+ process.emitWarning(
120
+ "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.",
121
+ );
122
+ return;
123
+ }
124
+
125
+ LoggerService.writesToStandardError = true;
126
+ }
127
+
128
+ /**
129
+ * Fails a malformed message in development, and never in production.
130
+ *
131
+ * A logger that throws in production turns an observability call into an
132
+ * outage, so the check runs only where a developer is present to fix it.
133
+ */
134
+ private assertConventionalMessage(args: {
135
+ context: string | undefined;
136
+ parsed: ParsedLogMessage;
137
+ }): void {
138
+ if (
139
+ LoggerService.isProduction ||
140
+ this.shouldSkipConventionalMessageValidation(args.context)
141
+ ) {
142
+ return;
143
+ }
144
+
145
+ const violation = this.getConventionalMessageViolation(args.parsed);
146
+
147
+ if (violation !== undefined) {
148
+ throw new Error(violation);
149
+ }
150
+ }
151
+
152
+ /** Assembles the object pino merges into the line. */
153
+ private buildBindings(args: {
154
+ context: string | undefined;
155
+ data: LogData | undefined;
156
+ parsed: ParsedLogMessage;
157
+ }): Record<string, unknown> {
158
+ this.assertConventionalMessage({
159
+ context: args.context,
160
+ parsed: args.parsed,
161
+ });
162
+
163
+ return {
164
+ ...args.data,
165
+ context: args.context,
166
+ // Telemetry gets prose; only the console-bound transport reads this.
167
+ ...(LoggerService.isProduction ? {} : { emoji: args.parsed.emoji }),
168
+ };
169
+ }
170
+
171
+ /** Returns a human-readable explanation when the message format is invalid. */
172
+ private getConventionalMessageViolation(
173
+ parsed: ParsedLogMessage,
174
+ ): string | undefined {
175
+ const emoji = parsed.emoji;
176
+ const text = parsed.text;
177
+
178
+ if (emoji === undefined) {
179
+ return `Log message must start with an emoji naming its subject, then a verb: "${text}"`;
180
+ }
181
+
182
+ const firstWord = FIRST_WORD_PATTERN.exec(text)?.[1];
183
+
184
+ if (firstWord === undefined || !this.isConventionalVerb(firstWord)) {
185
+ return `Log message must begin with a verb in present progressive or past tense, got "${firstWord ?? ""}": "${emoji} ${text}"`;
186
+ }
187
+
188
+ return undefined;
189
+ }
190
+
191
+ /**
192
+ * Whether a word is a verb in one of the two tenses the convention allows.
193
+ *
194
+ * Present progressive means the operation is under way; past means it
195
+ * finished. Regular morphology covers both, so a new verb needs no
196
+ * registration anywhere — only irregular pasts are enumerated.
197
+ */
198
+ private isConventionalVerb(word: string): boolean {
199
+ const lowercased = word.toLowerCase();
200
+
201
+ return (
202
+ lowercased.endsWith("ing") ||
203
+ lowercased.endsWith("ed") ||
204
+ IRREGULAR_PAST_VERBS.has(lowercased)
205
+ );
206
+ }
207
+
208
+ /** Splits a leading emoji off a message, leaving prose behind. */
209
+ private parseMessage(message: unknown): ParsedLogMessage {
210
+ const text = String(message);
211
+ const match = LEADING_EMOJI_PATTERN.exec(text);
212
+ const emoji = match?.[1];
213
+
214
+ return emoji === undefined
215
+ ? { emoji: undefined, text }
216
+ : { emoji, text: text.slice(match?.[0].length) };
217
+ }
218
+
219
+ /** Whether a context is intentionally exempt from the validation rule. */
220
+ private shouldSkipConventionalMessageValidation(
221
+ context: string | undefined,
222
+ ): boolean {
223
+ return context !== undefined && UNVALIDATED_LOG_CONTEXTS.has(context);
224
+ }
225
+
226
+ // 🌎 Public Methods
227
+
228
+ /** The pino instance every logger's child is taken from. */
229
+ private static get root(): pino.Logger {
230
+ LoggerService.rootLogger ??= LoggerService.createRootLogger();
231
+
232
+ return LoggerService.rootLogger;
233
+ }
234
+
235
+ /** Normalizes unknown errors into a stable message and timestamped log line. */
236
+ buildErrorLogEntry(
237
+ context: string,
238
+ error: unknown,
239
+ ): { errorMessage: string; logLine: string } {
240
+ const errorMessage =
241
+ error instanceof Error ? error.stack || error.message : String(error);
242
+
243
+ return {
244
+ errorMessage,
245
+ logLine: `[${new Date().toISOString()}] ${context}: ${errorMessage}\n`,
246
+ };
247
+ }
248
+
249
+ /** Builds a timestamped output log file path and ensures the output directory exists. */
250
+ createTimestampedOutputLogFilePath(filePrefix: string): string {
251
+ const outputDirectory = path.join(process.cwd(), "output");
252
+ if (!existsSync(outputDirectory)) {
253
+ mkdirSync(outputDirectory, { recursive: true });
254
+ }
255
+
256
+ return path.join(
257
+ outputDirectory,
258
+ `${filePrefix}-${new Date().toISOString().replaceAll(/[:.]/g, "-")}.log`,
259
+ );
260
+ }
261
+
262
+ /** Logs a debug message at the `debug` level. */
263
+ override debug(message: unknown, context?: string, data?: LogData): void {
264
+ const parsed = this.parseMessage(message);
265
+ this.child.debug(
266
+ this.buildBindings({ context: context ?? this.context, data, parsed }),
267
+ parsed.text,
268
+ );
269
+ }
270
+
271
+ /**
272
+ * Logs an error message at the `error` level, optionally including a stack trace.
273
+ *
274
+ * `ConsoleLogger.error` spends a third slot on a context string that the
275
+ * other levels do not have, so this one accepts either: a string keeps
276
+ * NestJS's meaning, an object is structured data like everywhere else.
277
+ */
278
+ override error(
279
+ message: unknown,
280
+ stackOrContext?: string,
281
+ contextOrData?: LogData | string,
282
+ ): void {
283
+ const parsed = this.parseMessage(message);
284
+ const data = typeof contextOrData === "object" ? contextOrData : undefined;
285
+ const context =
286
+ typeof contextOrData === "string" ? contextOrData : this.context;
287
+
288
+ this.child.error(
289
+ {
290
+ ...this.buildBindings({ context, data, parsed }),
291
+ stack: stackOrContext,
292
+ },
293
+ parsed.text,
294
+ );
295
+ }
296
+
297
+ /** Logs an informational message at the `info` level. */
298
+ info(message: unknown, context?: string, data?: LogData): void {
299
+ const parsed = this.parseMessage(message);
300
+ this.child.info(
301
+ this.buildBindings({ context: context ?? this.context, data, parsed }),
302
+ parsed.text,
303
+ );
304
+ }
305
+
306
+ /**
307
+ * Logs an informational message at the `info` level.
308
+ *
309
+ * NestJS and `nest-commander` call this method directly as part of the
310
+ * framework's own `LoggerService` contract, so it must keep working
311
+ * exactly as before. Application code should call `info` instead — the
312
+ * same behavior under a name that says what level it logs at.
313
+ */
314
+ override log(message: unknown, context?: string, data?: LogData): void {
315
+ this.info(message, context, data);
316
+ }
317
+
318
+ /** Sets the context label included in every subsequent log line. */
319
+ override setContext(context: string): void {
320
+ super.setContext(context);
321
+ this.child = LoggerService.root.child({ context });
322
+ }
323
+
324
+ /** Logs a verbose message at the `trace` level. */
325
+ override verbose(message: unknown, context?: string, data?: LogData): void {
326
+ const parsed = this.parseMessage(message);
327
+ this.child.trace(
328
+ this.buildBindings({ context: context ?? this.context, data, parsed }),
329
+ parsed.text,
330
+ );
331
+ }
332
+
333
+ /** Logs a warning message at the `warn` level. */
334
+ override warn(message: unknown, context?: string, data?: LogData): void {
335
+ const parsed = this.parseMessage(message);
336
+ this.child.warn(
337
+ this.buildBindings({ context: context ?? this.context, data, parsed }),
338
+ parsed.text,
339
+ );
340
+ }
341
+ }
342
+
343
+ /** Legend explaining the rounded-node convention for an ambient module. */
344
+ export declare const MODULE_GRAPH_AMBIENT_LEGEND = "_Rounded modules are global: every module can inject them, so their edges are left out._";
345
+
346
+ /**
347
+ * Smallest graph the ambient-module rule is allowed to fire on.
348
+ *
349
+ * Below this a module imported by everything else is just a small graph, not
350
+ * a global one: in a two-module project the only import there is would
351
+ * qualify.
352
+ */
353
+ export declare const MODULE_GRAPH_AMBIENT_MINIMUM_MODULES = 4;
354
+
355
+ /** Mermaid diagram type and direction the graph is rendered as. */
356
+ export declare const MODULE_GRAPH_MERMAID_HEADER = "flowchart LR";
357
+
358
+ /** Rendered in place of a diagram for a project with no modules to graph. */
359
+ export declare const MODULE_GRAPH_UNCONNECTED = "_This project defines no NestJS modules to graph._";
360
+
361
+ /** Provides the NestJS module import Graph builder and renderer. */
362
+ export declare class ModuleGraphModule {
363
+ }
364
+
365
+ /**
366
+ * Reduces an explored NestJS container into a Graph and renders it.
367
+ *
368
+ * Ported from `tools/synchronization`'s `nestjs-module-graphs` command (see
369
+ * issue #242), which this package replaces: the ambient-module heuristic and
370
+ * mermaid rendering are unchanged, but the cross-project ownership and
371
+ * grouping that command layers on top are not — codependix's Graph describes
372
+ * one project's own container, not the workspace's opinion of who else's
373
+ * modules it touches. `NestjsProjectService.exploreProject` is what supplies
374
+ * the explored tree this service turns into a Graph.
375
+ *
376
+ * `NestjsProjectService.exploreProject` reports the container's view rather than the
377
+ * decorators', which means every `@Global()` module is listed as an import of
378
+ * every other module. Drawn literally, one global module contributes an edge
379
+ * per module in the project and buries the structure worth reading, so those
380
+ * edges are left out and the module is drawn as a rounded node on its own.
381
+ */
382
+ export declare class ModuleGraphService {
383
+ constructor();
384
+ /** Walks the tree into the edges worth drawing and the nodes they touch. */
385
+ private collectEdgesAndNodes;
386
+ /** Sorts edges by source then target so the rendered diagram never churns. */
387
+ private compareEdges;
388
+ /** Sorts nodes by name then declaring file so the graph is stable. */
389
+ private compareNodes;
390
+ /** Counts how many modules import each module. */
391
+ private countInboundEdges;
392
+ /**
393
+ * Names the modules every other module imports.
394
+ *
395
+ * A global module is registered into every module in the container, so it
396
+ * arrives with one inbound edge short of the module count.
397
+ */
398
+ private findAmbientModuleNames;
399
+ /** Renders a module as a plain node, or a rounded one when it is ambient. */
400
+ private renderNode;
401
+ /** Sorts names into a stable order. */
402
+ private sortNames;
403
+ /** Reduces an explored container to a Graph of its module imports. */
404
+ buildGraph(tree: NestjsExploredModule[], projectName: string): NestjsModuleGraph;
405
+ /** Derives the project-relative folder a module belongs to from its declaring file. */
406
+ deriveModuleFolder(node: NestjsModuleGraphNode): string;
407
+ /** Renders a module graph as a fenced mermaid diagram. */
408
+ renderMermaid(graph: NestjsModuleGraph): string;
409
+ }
410
+
411
+ /** Header declaring the mermaid diagram type and its default layout direction. */
412
+ export declare const NESTJS_MODULES_WORKSPACE_GRAPH_MERMAID_HEADER = "graph LR";
413
+
414
+ /** Rendered in place of a diagram for a workspace with no NestJS modules at all. */
415
+ export declare const NESTJS_MODULES_WORKSPACE_GRAPH_UNCONNECTED = "_This workspace has no NestJS modules, in any project._";
416
+
417
+ /**
418
+ * Modules NestJS creates to host a dynamic module's providers.
419
+ *
420
+ * These are implementation details of `forRoot`/`forRootAsync` rather than
421
+ * anything a project declares — `TypeOrmModule` is in a project's design and
422
+ * stays in the graph, while the `TypeOrmCoreModule` it builds underneath is
423
+ * not. The synthetic root belongs here for the same reason: this package
424
+ * created it, so it is not part of the project. So does `InternalCoreModule`,
425
+ * which NestJS adds to every container and imports into every module. It is
426
+ * matched by name because NestJS 11 and 12 export it from different paths.
427
+ */
428
+ export declare const NESTJS_PROJECT_IGNORED_MODULES: RegExp[];
429
+
430
+ /** File suffix that marks a NestJS module definition. */
431
+ export declare const NESTJS_PROJECT_MODULE_FILE_SUFFIX = ".module.ts";
432
+
433
+ /** Export a root module file is expected to provide. */
434
+ export declare const NESTJS_PROJECT_ROOT_MODULE_EXPORT = "MainModule";
435
+
436
+ /**
437
+ * Path, relative to a project root, of the module a project bootstraps.
438
+ *
439
+ * A project without one is a library rather than an application, and gets a
440
+ * synthetic root built from every module it defines instead.
441
+ */
442
+ export declare const NESTJS_PROJECT_ROOT_MODULE_FILE = "src/main.module.ts";
443
+
444
+ /**
445
+ * Additionally ignored when the root is synthetic.
446
+ *
447
+ * The synthetic root supplies a global `ConfigModule` so that a package whose
448
+ * modules read configuration in a `useFactory` can be scanned at all. That
449
+ * scaffolding is this package's, not the project's, so it stays out of the
450
+ * graph.
451
+ */
452
+ export declare const NESTJS_PROJECT_SYNTHETIC_IGNORED_MODULES: RegExp[];
453
+
454
+ /** Nx project tag that marks a project as one this package graphs. */
455
+ export declare const NESTJS_PROJECT_TAG = "framework:nestjs";
456
+
457
+ /** A module in an explored NestJS container. */
458
+ export declare interface NestjsExploredModule {
459
+ /**
460
+ * Path to the file declaring the module class, relative to the project
461
+ * root. Undefined for a module the project imports rather than declares.
462
+ */
463
+ readonly declaringFile?: string | undefined;
464
+ /** Class names of the modules this one imports, in the container's order. */
465
+ readonly imports: string[];
466
+ /** The module class name. */
467
+ readonly name: string;
468
+ }
469
+
470
+ /**
471
+ * A NestJS project's module import graph, reduced to what an export needs.
472
+ *
473
+ * Built from the exploration of a project's container in
474
+ * preview mode — see `NestjsProjectService.exploreProject` — and kept to the
475
+ * plain module-name and edge shape a diagram or a JSON export can render
476
+ * directly, without carrying the container's own provider or controller
477
+ * metadata.
478
+ */
479
+ export declare interface NestjsModuleGraph {
480
+ /**
481
+ * Modules every other module imports, and whose edges are therefore left
482
+ * out. Reported so a caller can say why the diagram looks sparser than the
483
+ * container does.
484
+ */
485
+ readonly ambientModuleNames: string[];
486
+ /** Every drawn import relationship, sorted so the diagram is stable. */
487
+ readonly edges: NestjsModuleGraphEdge[];
488
+ /** Modules left with no drawn edge in either direction. */
489
+ readonly isolatedModuleNames: string[];
490
+ /** Every module node in the graph, sorted. */
491
+ readonly nodes: NestjsModuleGraphNode[];
492
+ /** The project the graph was built from. */
493
+ readonly projectName: string;
494
+ }
495
+
496
+ /** One module importing another. */
497
+ export declare interface NestjsModuleGraphEdge {
498
+ readonly source: string;
499
+ readonly target: string;
500
+ }
501
+
502
+ /** One module in a NestJS project's module import graph. */
503
+ export declare interface NestjsModuleGraphNode {
504
+ /** Path to the file declaring the module class, relative to the project root. */
505
+ readonly declaringFile: string;
506
+ /** The module class name. */
507
+ readonly name: string;
508
+ }
509
+
510
+ /**
511
+ * The whole-workspace NestJS module graph: every NestJS project's own module
512
+ * graph, combined into one.
513
+ *
514
+ * Exported once at the workspace root rather than once per project — see
515
+ * `codependix-nx-projects`'s `WorkspaceGraph`, which this mirrors. A module's
516
+ * class name alone is not unique across the workspace — two different
517
+ * projects can each declare their own `AppModule` — so every node here is
518
+ * qualified with the project it belongs to (see
519
+ * `WorkspaceGraphService.qualifyModuleName`) rather than reused as-is from a
520
+ * single project's `NestjsModuleGraph`.
521
+ */
522
+ export declare interface NestjsModulesWorkspaceGraph {
523
+ /** Every drawn import relationship, sorted so the diagram never churns. */
524
+ readonly edges: NestjsModulesWorkspaceGraphEdge[];
525
+ /** Every module in the graph, qualified by project and sorted. */
526
+ readonly moduleNames: string[];
527
+ }
528
+
529
+ /** One module importing another, both qualified by the project they belong to. */
530
+ export declare interface NestjsModulesWorkspaceGraphEdge {
531
+ readonly source: string;
532
+ readonly target: string;
533
+ }
534
+
535
+ /** Provides the whole-workspace NestJS module graph builder. */
536
+ export declare class NestjsModulesWorkspaceGraphModule {
537
+ }
538
+
539
+ /**
540
+ * Builds the whole-workspace NestJS module graph — every NestJS project's
541
+ * own module graph, combined into one — exported once at the workspace root
542
+ * rather than once per project.
543
+ *
544
+ * A module's class name alone is only unique within its own project — two
545
+ * different projects each declare their own `AppModule`, and even within one
546
+ * project `ModuleGraphService` already collapses two same-named modules into
547
+ * one node — so every node is qualified with the project it belongs to before
548
+ * the projects' graphs are combined, mirroring
549
+ * `codependix-nx-projects`'s `WorkspaceGraphService` without reusing its
550
+ * code, since the two build genuinely different kinds of graph from
551
+ * genuinely different sources. `ModuleGraphService.buildGraph` has already
552
+ * dropped ambient-module edges per project by the time a graph reaches here,
553
+ * so nothing further needs deciding about ambience at workspace scope.
554
+ */
555
+ export declare class NestjsModulesWorkspaceGraphService {
556
+ constructor();
557
+ /** Sorts edges by source then target so a rendered diagram never churns. */
558
+ private compareEdges;
559
+ /** Qualifies a module's class name with the project it belongs to. */
560
+ private qualifyModuleName;
561
+ /** Renders one qualified module name as a mermaid node. */
562
+ private renderNode;
563
+ /** Sorts names into a stable order. */
564
+ private sortNames;
565
+ /** Turns a qualified module name into an identifier mermaid accepts. */
566
+ private toNodeIdentifier;
567
+ /**
568
+ * Builds the whole-workspace NestJS module graph from every discovered
569
+ * NestJS project's own already-built module graph.
570
+ */
571
+ buildWorkspaceGraph(graphs: NestjsModuleGraph[]): NestjsModulesWorkspaceGraph;
572
+ /** Renders the whole-workspace NestJS module graph as a mermaid diagram. */
573
+ renderMermaid(workspaceGraph: NestjsModulesWorkspaceGraph): string;
574
+ }
575
+
576
+ /** A workspace project tagged `framework:nestjs`. */
577
+ export declare interface NestjsProject {
578
+ /** Absolute path of the project directory. */
579
+ readonly absoluteRoot: string;
580
+ /** Project directory name, which is also the Nx project name. */
581
+ readonly name: string;
582
+ /**
583
+ * Absolute path of the project's root module file, when it has one.
584
+ *
585
+ * Undefined for a library package, whose container is rooted in a
586
+ * synthetic module built from every module the package defines.
587
+ */
588
+ readonly rootModuleFile: string | undefined;
589
+ }
590
+
591
+ /** Provides NestJS project discovery and container exploration. */
592
+ export declare class NestjsProjectModule {
593
+ }
594
+
595
+ /**
596
+ * Discovers the workspace's `framework:nestjs` projects and explores each
597
+ * one's container.
598
+ *
599
+ * Ported from `tools/synchronization`'s `nestjs-module-graphs` command (see
600
+ * issue #242), which this package replaces: exploration still runs the
601
+ * container in NestJS preview mode, which registers every module and
602
+ * provider without instantiating any of them. That is what makes a project
603
+ * safe to graph from a workstation or from CI: a project building a
604
+ * `TypeOrmModule.forRootAsync` options factory never has a database
605
+ * contacted. Project discovery reads the tags each project already carries
606
+ * rather than walking the filesystem for a `project.json`, since whoever
607
+ * read the Nx project graph for the Nx exports has already collected them.
608
+ */
609
+ export declare class NestjsProjectService {
610
+ private readonly logger;
611
+ constructor(logger: LoggerService);
612
+ /** Roots a package that bootstraps nothing in every module it defines. */
613
+ private buildSyntheticRootModule;
614
+ /**
615
+ * Discovers declaring files for every module in a project, relative to its
616
+ * root.
617
+ */
618
+ private discoverModuleDeclaringFiles;
619
+ /**
620
+ * Lists every module in a container with the modules it imports, both by
621
+ * class name and in the container's registration order, leaving the ignored
622
+ * modules out of the list and out of every import.
623
+ *
624
+ * Reads the container through its public `ModulesContainer` provider, which
625
+ * a preview-mode container still hands out because NestJS registers it as a
626
+ * value. The container's view is what `import` edges report, so a global
627
+ * module appears as an import of every module rather than only of those
628
+ * that name it.
629
+ */
630
+ private exploreContainer;
631
+ /** Finds every module definition file beneath a directory. */
632
+ private findModuleFiles;
633
+ /** Imports a module file and returns every module class it exports. */
634
+ private loadModuleClasses;
635
+ /** Imports a root module file and returns the module class it exports. */
636
+ private loadRootModule;
637
+ /** Describes a project, noting whether it bootstraps a root module. */
638
+ describeProject(absoluteRoot: string, name: string): NestjsProject;
639
+ /**
640
+ * Filters an already-read list of Nx projects down to the ones tagged
641
+ * `framework:nestjs`, and describes each one.
642
+ *
643
+ * Projects are returned in the order they were given, which callers keep
644
+ * sorted by name — the same order `codependix-nx-projects`'s `NeighborhoodService`
645
+ * reads the Nx project graph's own projects in.
646
+ */
647
+ discoverProjects(projects: {
648
+ absoluteRoot: string;
649
+ name: string;
650
+ tags: string[];
651
+ }[]): NestjsProject[];
652
+ /** Explores a project's container in preview mode and returns its tree. */
653
+ exploreProject(project: NestjsProject): Promise<NestjsExploredModule[]>;
654
+ /** Reports whether a project's Nx tags mark it as a NestJS project. */
655
+ isNestjsProject(project: {
656
+ tags: string[];
657
+ }): boolean;
658
+ }
659
+
660
+ /** A message split into the emoji the console shows and the prose telemetry stores. */
661
+ declare interface ParsedLogMessage {
662
+ emoji: string | undefined;
663
+ text: string;
664
+ }
665
+
666
+ export { }