@conformetry/nx 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1094 @@
1
+ import { ConfigurationService } from '@conformetry/configuration';
2
+ import { ConformetryGeneratorDefinition } from '@conformetry/configuration';
3
+ import { ConformetryInstanceGroup } from '@conformetry/configuration';
4
+ import { ConsoleLogger } from '@nestjs/common';
5
+ import { CreateNodes } from '@nx/devkit';
6
+ import { FileSystemAdapter } from '@conformetry/generation';
7
+ import { FormatterAdapter } from '@conformetry/generation';
8
+ import { GenerationService } from '@conformetry/generation';
9
+ import { Instance } from '@conformetry/configuration';
10
+ import pino from 'pino';
11
+ import { ReportingService } from '@conformetry/output';
12
+ import { Tree } from '@nx/devkit';
13
+ import { ValidationService } from '@conformetry/validation';
14
+
15
+ /**
16
+ * Provides the `Tree`-backed adapters generation writes through.
17
+ *
18
+ * Separate from the plugin module so that a host with a different virtual
19
+ * filesystem can supply its own adapters without rewriting the runner.
20
+ */
21
+ export declare class AdapterModule {
22
+ }
23
+
24
+ /**
25
+ * Backs generation with an Nx `Tree` instead of the filesystem.
26
+ *
27
+ * This is what makes `nx g --dry-run` and Nx's change preview work: the
28
+ * generic generation service writes through whatever adapter it is handed, and
29
+ * a `Tree` records writes without touching disk until Nx flushes them. Running
30
+ * the CLI as a subprocess, which is what this package used to do, wrote
31
+ * straight to disk and made both features lie.
32
+ */
33
+ export declare class AdapterService {
34
+ constructor();
35
+ /**
36
+ * Lists a directory through the tree when it is inside the workspace, and
37
+ * through the filesystem otherwise.
38
+ *
39
+ * Templates usually live inside the workspace, so reading them through the
40
+ * tree means a generator run sees edits an earlier generator in the same run
41
+ * made — which is what a caller expects from a composed generator.
42
+ */
43
+ private listDirectory;
44
+ /** Reads a file through the tree when possible, the filesystem otherwise. */
45
+ private readFile;
46
+ /**
47
+ * Converts an absolute path to the workspace-relative form a `Tree` uses,
48
+ * or returns `undefined` when the path lies outside the workspace.
49
+ */
50
+ private resolveTreePath;
51
+ /**
52
+ * Builds the adapters one generator run writes through.
53
+ *
54
+ * `makeDirectory` is a no-op because a `Tree` has no directories of its own —
55
+ * writing `a/b/c.ts` implies them. The formatter defers to Nx so generated
56
+ * files are formatted by the workspace's own configuration.
57
+ */
58
+ createAdapters(args: CreateAdaptersArguments): TreeAdapters;
59
+ }
60
+
61
+ /**
62
+ * Emits the generator plugin and puts it where Nx will find it.
63
+ *
64
+ * Run from a `postinstall`, which is what makes the emitted plugin a build
65
+ * artifact rather than a committed one. `GeneratorService.emitPlugin` is called
66
+ * directly rather than through `nx sync`, which builds the whole project graph
67
+ * before it emits anything — too slow to pay for on every install, and it would
68
+ * fail the install itself whenever any project in the workspace momentarily
69
+ * fails to load.
70
+ */
71
+ export declare function bootstrapPlugin(workspaceRoot: string): Promise<EmittedFile[]>;
72
+
73
+ /**
74
+ * The conformetry configuration as this plugin reads it.
75
+ *
76
+ * Authors type their config as this rather than as `ConformetryConfiguration`
77
+ * to have their instance groups checked against what Nx can actually resolve.
78
+ */
79
+ export declare type ConformetryNxConfiguration = ConformetryNxGeneratorDefinition[];
80
+
81
+ /** One generator, whose instance groups this plugin resolves against Nx. */
82
+ export declare interface ConformetryNxGeneratorDefinition extends Omit<ConformetryGeneratorDefinition, "instances"> {
83
+ instances?: ConformetryNxInstanceGroup[] | undefined;
84
+ }
85
+
86
+ /**
87
+ * One instance group, read as either a workspace glob or a project selector.
88
+ *
89
+ * The two forms are told apart by `tags`, so a generator says where it belongs
90
+ * in exactly one place. There is no second field that could disagree with this
91
+ * one, which is what the single key buys: a separate scope that excluded a
92
+ * project the globs reached narrowed validation silently, and validation
93
+ * cannot notice instances it was never offered.
94
+ */
95
+ export declare type ConformetryNxInstanceGroup = ConformetryNxProjectInstanceGroup | ConformetryNxWorkspaceInstanceGroup;
96
+
97
+ /** Instances located by project tag, with globs read inside each project. */
98
+ export declare interface ConformetryNxProjectInstanceGroup extends ConformetryInstanceGroup {
99
+ /**
100
+ * Globs relative to each matching project's root, or `.` for the project
101
+ * itself.
102
+ *
103
+ * Omitted selects the projects without locating anything in them, which is
104
+ * what a template with no instances yet wants: `nx g` is still confined to
105
+ * the projects the template suits.
106
+ */
107
+ patterns?: string[] | undefined;
108
+ /** Nx project tags a project must carry. A project must carry all of them. */
109
+ tags: string[];
110
+ }
111
+
112
+ /** Instances located by workspace-relative globs, as any host resolves them. */
113
+ export declare interface ConformetryNxWorkspaceInstanceGroup extends ConformetryInstanceGroup {
114
+ patterns: string[];
115
+ /** Absent: this is the form a host with no project graph also uses. */
116
+ tags?: undefined;
117
+ }
118
+
119
+ declare const conformetryPlugin: {
120
+ createNodes: CreateNodes;
121
+ name: string;
122
+ };
123
+ export default conformetryPlugin;
124
+
125
+ /** Options accepted from this plugin's `nx.json` registration. */
126
+ export declare interface ConformetryPluginOptions {
127
+ /** Where the conformetry configuration lives, workspace-root relative. */
128
+ readonly configurationPath: string;
129
+ /** Name of the inferred per-project validation target. */
130
+ readonly validateTargetName: string;
131
+ }
132
+
133
+ /** Arguments for building the adapters that back one generator run. */
134
+ declare interface CreateAdaptersArguments {
135
+ readonly tree: Tree;
136
+ readonly workspaceRoot: string;
137
+ }
138
+
139
+ /** Arguments for emitting the consumer's generator plugin. */
140
+ declare interface EmitPluginArguments {
141
+ readonly configurationPath: string;
142
+ /** Directory the plugin is written to, relative to the workspace root. */
143
+ readonly outputPath: string;
144
+ /** Package name the emitted plugin is addressed by, as in `nx g <name>:x`. */
145
+ readonly packageName: string;
146
+ /**
147
+ * The workspace's projects, used to enumerate a scoped generator's choices.
148
+ *
149
+ * Passed in rather than read here so emitting stays pure with respect to the
150
+ * filesystem: the graph, the sync generator, and the install-time bootstrap
151
+ * each know how to list projects, and they do not agree on how.
152
+ */
153
+ readonly projects?: readonly ProjectScope[] | undefined;
154
+ }
155
+
156
+ /** One file the generator emits, with its workspace-relative path. */
157
+ declare interface EmittedFile {
158
+ readonly content: string;
159
+ readonly filePath: string;
160
+ }
161
+
162
+ /** Arguments for collecting the instances that belong to one project. */
163
+ declare interface FindProjectInstancesArguments {
164
+ readonly configurationPath: string;
165
+ readonly project: ProjectScope;
166
+ readonly workspaceRoot: string;
167
+ }
168
+
169
+ /**
170
+ * Derives an Nx generator plugin from the conformetry configuration.
171
+ *
172
+ * Nx needs a `generators.json`, a factory function per generator, and a JSON
173
+ * schema per generator — none of which it will accept as runtime data. The
174
+ * generators a workspace has are a property of its configuration, so rather
175
+ * than hand-maintaining three files per generator, they are emitted from the
176
+ * configuration and kept honest by `nx sync:check`.
177
+ */
178
+ declare class GeneratorService {
179
+ private readonly configurationService;
180
+ private readonly scopeService;
181
+ constructor(configurationService: ConfigurationService, scopeService: ScopeService);
182
+ /**
183
+ * Builds the module Nx calls into for one generator.
184
+ *
185
+ * One file per generator, each exporting a single `generate`, because Nx
186
+ * does not pass a generator its own name — the name has to be bound at the
187
+ * call site. Binding it per file rather than per export in a shared module
188
+ * means a generator's factory sits next to the schema of the same name, and
189
+ * removing a generator removes a file rather than editing one.
190
+ */
191
+ private buildGeneratorModule;
192
+ /**
193
+ * Builds the `generators.json` Nx reads.
194
+ *
195
+ * Schemas are referenced by a path inside the emitted plugin, never one that
196
+ * escapes it — a schema path pointing outside the package resolves to
197
+ * nothing once the package is installed somewhere else.
198
+ */
199
+ private buildGeneratorsManifest;
200
+ /**
201
+ * Builds one generator's JSON schema from its configured inputs.
202
+ *
203
+ * Every input is required: a conformetry generator substitutes each of its
204
+ * placeholders, and mustache renders a missing one as empty rather than
205
+ * failing, so an optional input would silently produce a hole.
206
+ */
207
+ private buildSchema;
208
+ /**
209
+ * Builds the schema's properties, narrowing the project input to the scope.
210
+ *
211
+ * An `enum` is what makes `nx g` offer only the projects a generator suits,
212
+ * and what makes it reject one it does not — Nx builds its prompt from the
213
+ * schema, so constraining the schema constrains the prompt. Left untouched
214
+ * when the generator names no scope, or when the scope matches nothing: an
215
+ * empty `enum` would leave the prompt with nothing to pick and read as a
216
+ * broken generator rather than an unscoped one.
217
+ */
218
+ private buildSchemaProperties;
219
+ /**
220
+ * The projects a generator's tagged groups admit, or nothing when it has
221
+ * none.
222
+ */
223
+ private resolveScopedProjectNames;
224
+ /** Serializes emitted JSON the way the workspace formatter would. */
225
+ private stringify;
226
+ /**
227
+ * Returns every file the consumer's generator plugin consists of.
228
+ *
229
+ * Pure with respect to the filesystem: the caller writes these through an Nx
230
+ * `Tree`, which is what lets `nx sync:check` compare them against what is on
231
+ * disk without touching it.
232
+ */
233
+ emitPlugin(args: EmitPluginArguments): Promise<EmittedFile[]>;
234
+ }
235
+
236
+ /** A target this plugin infers onto a project. */
237
+ declare interface InferredTarget {
238
+ readonly cache: boolean;
239
+ readonly executor: string;
240
+ /** Files whose change must invalidate the cached result. */
241
+ readonly inputs?: string[];
242
+ readonly options: Record<string, unknown>;
243
+ }
244
+
245
+ /** One project's inferred targets, keyed by target name. */
246
+ declare type InferredTargets = Record<string, InferredTarget>;
247
+
248
+ /** Arguments for inferring targets across every project in a workspace. */
249
+ declare interface InferTargetsArguments {
250
+ readonly options: unknown;
251
+ /** Every `project.json` Nx matched, workspace-root relative. */
252
+ readonly projectConfigurationFiles: readonly string[];
253
+ readonly workspaceRoot: string;
254
+ }
255
+
256
+ /**
257
+ * Provides Nx-aware expansion of the configured instance globs.
258
+ *
259
+ * Imports the generic discovery module rather than globbing here, so the
260
+ * plugin and the CLI resolve instances by exactly the same rules.
261
+ */
262
+ export declare class InstancesModule {
263
+ }
264
+
265
+ /**
266
+ * Turns Nx project knowledge into the instances conformetry validates.
267
+ *
268
+ * This is the whole reason the plugin exists: the generic packages take a list
269
+ * of paths, and deciding which paths belong to which project — and which
270
+ * projects a configured instance group applies to — is Nx-shaped knowledge
271
+ * that would otherwise have to live inside them.
272
+ */
273
+ export declare class InstancesService {
274
+ private readonly configurationService;
275
+ private readonly scopeService;
276
+ constructor(configurationService: ConfigurationService, scopeService: ScopeService);
277
+ /**
278
+ * Returns whether an instance belongs to a project.
279
+ *
280
+ * Tested against the instance itself — the instance path joined with the
281
+ * name — not the instance path alone. A project-level instance's path is
282
+ * the directory *holding* projects, so testing that would place every
283
+ * project's own instance outside it.
284
+ */
285
+ private isInsideProject;
286
+ /**
287
+ * Expands every instance group that applies to a project, keeping only the
288
+ * instances that live inside it.
289
+ *
290
+ * The globs stay workspace-relative rather than being rewritten per project:
291
+ * a pattern such as `packages/*` is the author describing the workspace, and
292
+ * rewriting it into a project-relative form would change what it means.
293
+ */
294
+ findProjectInstances(args: FindProjectInstancesArguments): Promise<Instance[]>;
295
+ }
296
+
297
+ /**
298
+ * Structured values that belong beside a log line rather than inside it.
299
+ *
300
+ * Counts, percentages, and durations are the values that change on every
301
+ * occurrence, so they are carried as fields: the message stays constant and
302
+ * groupable in telemetry, and the numbers stay queryable instead of having to
303
+ * be parsed back out of prose.
304
+ *
305
+ * The named members are the recurring ones; the index signature keeps the
306
+ * argument open for whatever a given call site needs to attach.
307
+ */
308
+ declare interface LogData {
309
+ [key: string]: unknown;
310
+ /** How many things the operation handled. */
311
+ count?: number;
312
+ /** Wall-clock milliseconds the operation took. */
313
+ durationMs?: number;
314
+ /** Completion between 0 and 100. */
315
+ percent?: number;
316
+ /** How many things the operation set out to handle. */
317
+ total?: number;
318
+ }
319
+
320
+ /**
321
+ * Transient-scoped logger so each injecting class gets its own instance.
322
+ * Each consumer calls `setContext(ClassName.name)` to tag every log line
323
+ * with the originating class. Backed by pino for structured JSON output in
324
+ * production and human-readable pretty-print in development.
325
+ *
326
+ * Messages follow one grammar: an emoji naming the subject, a verb in present
327
+ * progressive or past tense, then the object. Values that vary per call —
328
+ * counts, percentages, durations — go in the `data` argument rather than the
329
+ * message, so the message stays constant enough for telemetry to group on.
330
+ *
331
+ * ```ts
332
+ * this.logger.info("📥 Downloading CSEL sources", undefined, { total: 428 });
333
+ * this.logger.info("📥 Downloaded CSEL sources", undefined, { count: 412 });
334
+ * ```
335
+ */
336
+ declare @Injectable({ scope: Scope.TRANSIENT })
337
+ class LoggerService extends ConsoleLogger {
338
+ // 🏗 Dependency Injection
339
+
340
+ constructor() {
341
+ super();
342
+ }
343
+
344
+ // 🔐 Private Fields
345
+
346
+ private static readonly isProduction =
347
+ process.env["NODE_ENV"] === "production";
348
+
349
+ /**
350
+ * Built on first use, not when this file is evaluated.
351
+ *
352
+ * A destination fixed at import time could only ever be chosen by this
353
+ * package, since every consumer's own code runs after its imports.
354
+ */
355
+ private static rootLogger: pino.Logger | undefined;
356
+
357
+ /** Whether lines go to standard error instead of standard output. */
358
+ private static writesToStandardError = false;
359
+
360
+ private child: pino.Logger = LoggerService.root;
361
+
362
+ // 🔑 Public Fields
363
+
364
+ // 🔏 Private Methods
365
+
366
+ /** Build the pino instance for production or local development output. */
367
+ private static createRootLogger(): pino.Logger {
368
+ const level = process.env["LOG_LEVEL"] ?? "info";
369
+
370
+ if (LoggerService.isProduction) {
371
+ return LoggerService.writesToStandardError
372
+ ? pino({ level }, pino.destination(STANDARD_ERROR_DESCRIPTOR))
373
+ : pino({ level });
374
+ }
375
+
376
+ return pino({
377
+ level,
378
+ transport: {
379
+ options: {
380
+ colorize: true,
381
+ destination: LoggerService.writesToStandardError
382
+ ? STANDARD_ERROR_DESCRIPTOR
383
+ : STANDARD_OUTPUT_DESCRIPTOR,
384
+ // The emoji is a field, not part of the message, so the console can
385
+ // show it while telemetry stores unadorned prose. `ignore` then keeps
386
+ // it from being printed a second time in the trailing object.
387
+ ignore: "pid,hostname,emoji",
388
+ messageFormat: "{emoji} {msg}",
389
+ singleLine: true,
390
+ },
391
+ target: "pino-pretty",
392
+ },
393
+ });
394
+ }
395
+
396
+ /**
397
+ * Sends every subsequent line to standard error instead of standard output.
398
+ *
399
+ * For a command-line application whose standard output *is* its result. A log
400
+ * line sharing that stream is not a diagnostic beside the data, it is a
401
+ * corruption of it. Call it before anything logs — the first statement of the
402
+ * application's bootstrap.
403
+ *
404
+ * A call after the first line warns and changes nothing: the destination is
405
+ * fixed when the pino instance is built, and tearing down a transport
406
+ * somebody is writing through would be worse than refusing. The warning is
407
+ * the point — silently leaving the lines on standard output is how a caller
408
+ * would ship a corrupted pipe without ever being told.
409
+ */
410
+ static logToStandardError(): void {
411
+ if (LoggerService.rootLogger !== undefined) {
412
+ process.emitWarning(
413
+ "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.",
414
+ );
415
+ return;
416
+ }
417
+
418
+ LoggerService.writesToStandardError = true;
419
+ }
420
+
421
+ /**
422
+ * Fails a malformed message in development, and never in production.
423
+ *
424
+ * A logger that throws in production turns an observability call into an
425
+ * outage, so the check runs only where a developer is present to fix it.
426
+ */
427
+ private assertConventionalMessage(args: {
428
+ context: string | undefined;
429
+ parsed: ParsedLogMessage;
430
+ }): void {
431
+ if (
432
+ LoggerService.isProduction ||
433
+ this.shouldSkipConventionalMessageValidation(args.context)
434
+ ) {
435
+ return;
436
+ }
437
+
438
+ const violation = this.getConventionalMessageViolation(args.parsed);
439
+
440
+ if (violation !== undefined) {
441
+ throw new Error(violation);
442
+ }
443
+ }
444
+
445
+ /** Assembles the object pino merges into the line. */
446
+ private buildBindings(args: {
447
+ context: string | undefined;
448
+ data: LogData | undefined;
449
+ parsed: ParsedLogMessage;
450
+ }): Record<string, unknown> {
451
+ this.assertConventionalMessage({
452
+ context: args.context,
453
+ parsed: args.parsed,
454
+ });
455
+
456
+ return {
457
+ ...args.data,
458
+ context: args.context,
459
+ // Telemetry gets prose; only the console-bound transport reads this.
460
+ ...(LoggerService.isProduction ? {} : { emoji: args.parsed.emoji }),
461
+ };
462
+ }
463
+
464
+ /** Returns a human-readable explanation when the message format is invalid. */
465
+ private getConventionalMessageViolation(
466
+ parsed: ParsedLogMessage,
467
+ ): string | undefined {
468
+ const emoji = parsed.emoji;
469
+ const text = parsed.text;
470
+
471
+ if (emoji === undefined) {
472
+ return `Log message must start with an emoji naming its subject, then a verb: "${text}"`;
473
+ }
474
+
475
+ const firstWord = FIRST_WORD_PATTERN.exec(text)?.[1];
476
+
477
+ if (firstWord === undefined || !this.isConventionalVerb(firstWord)) {
478
+ return `Log message must begin with a verb in present progressive or past tense, got "${firstWord ?? ""}": "${emoji} ${text}"`;
479
+ }
480
+
481
+ return undefined;
482
+ }
483
+
484
+ /**
485
+ * Whether a word is a verb in one of the two tenses the convention allows.
486
+ *
487
+ * Present progressive means the operation is under way; past means it
488
+ * finished. Regular morphology covers both, so a new verb needs no
489
+ * registration anywhere — only irregular pasts are enumerated.
490
+ */
491
+ private isConventionalVerb(word: string): boolean {
492
+ const lowercased = word.toLowerCase();
493
+
494
+ return (
495
+ lowercased.endsWith("ing") ||
496
+ lowercased.endsWith("ed") ||
497
+ IRREGULAR_PAST_VERBS.has(lowercased)
498
+ );
499
+ }
500
+
501
+ /** Splits a leading emoji off a message, leaving prose behind. */
502
+ private parseMessage(message: unknown): ParsedLogMessage {
503
+ const text = String(message);
504
+ const match = LEADING_EMOJI_PATTERN.exec(text);
505
+ const emoji = match?.[1];
506
+
507
+ return emoji === undefined
508
+ ? { emoji: undefined, text }
509
+ : { emoji, text: text.slice(match?.[0].length) };
510
+ }
511
+
512
+ /** Whether a context is intentionally exempt from the validation rule. */
513
+ private shouldSkipConventionalMessageValidation(
514
+ context: string | undefined,
515
+ ): boolean {
516
+ return context !== undefined && UNVALIDATED_LOG_CONTEXTS.has(context);
517
+ }
518
+
519
+ // 🌎 Public Methods
520
+
521
+ /** The pino instance every logger's child is taken from. */
522
+ private static get root(): pino.Logger {
523
+ LoggerService.rootLogger ??= LoggerService.createRootLogger();
524
+
525
+ return LoggerService.rootLogger;
526
+ }
527
+
528
+ /** Normalizes unknown errors into a stable message and timestamped log line. */
529
+ buildErrorLogEntry(
530
+ context: string,
531
+ error: unknown,
532
+ ): { errorMessage: string; logLine: string } {
533
+ const errorMessage =
534
+ error instanceof Error ? error.stack || error.message : String(error);
535
+
536
+ return {
537
+ errorMessage,
538
+ logLine: `[${new Date().toISOString()}] ${context}: ${errorMessage}\n`,
539
+ };
540
+ }
541
+
542
+ /** Builds a timestamped output log file path and ensures the output directory exists. */
543
+ createTimestampedOutputLogFilePath(filePrefix: string): string {
544
+ const outputDirectory = path.join(process.cwd(), "output");
545
+ if (!existsSync(outputDirectory)) {
546
+ mkdirSync(outputDirectory, { recursive: true });
547
+ }
548
+
549
+ return path.join(
550
+ outputDirectory,
551
+ `${filePrefix}-${new Date().toISOString().replaceAll(/[:.]/g, "-")}.log`,
552
+ );
553
+ }
554
+
555
+ /** Logs a debug message at the `debug` level. */
556
+ override debug(message: unknown, context?: string, data?: LogData): void {
557
+ const parsed = this.parseMessage(message);
558
+ this.child.debug(
559
+ this.buildBindings({ context: context ?? this.context, data, parsed }),
560
+ parsed.text,
561
+ );
562
+ }
563
+
564
+ /**
565
+ * Logs an error message at the `error` level, optionally including a stack trace.
566
+ *
567
+ * `ConsoleLogger.error` spends a third slot on a context string that the
568
+ * other levels do not have, so this one accepts either: a string keeps
569
+ * NestJS's meaning, an object is structured data like everywhere else.
570
+ */
571
+ override error(
572
+ message: unknown,
573
+ stackOrContext?: string,
574
+ contextOrData?: LogData | string,
575
+ ): void {
576
+ const parsed = this.parseMessage(message);
577
+ const data = typeof contextOrData === "object" ? contextOrData : undefined;
578
+ const context =
579
+ typeof contextOrData === "string" ? contextOrData : this.context;
580
+
581
+ this.child.error(
582
+ {
583
+ ...this.buildBindings({ context, data, parsed }),
584
+ stack: stackOrContext,
585
+ },
586
+ parsed.text,
587
+ );
588
+ }
589
+
590
+ /** Logs an informational message at the `info` level. */
591
+ info(message: unknown, context?: string, data?: LogData): void {
592
+ const parsed = this.parseMessage(message);
593
+ this.child.info(
594
+ this.buildBindings({ context: context ?? this.context, data, parsed }),
595
+ parsed.text,
596
+ );
597
+ }
598
+
599
+ /**
600
+ * Logs an informational message at the `info` level.
601
+ *
602
+ * NestJS and `nest-commander` call this method directly as part of the
603
+ * framework's own `LoggerService` contract, so it must keep working
604
+ * exactly as before. Application code should call `info` instead — the
605
+ * same behavior under a name that says what level it logs at.
606
+ */
607
+ override log(message: unknown, context?: string, data?: LogData): void {
608
+ this.info(message, context, data);
609
+ }
610
+
611
+ /** Sets the context label included in every subsequent log line. */
612
+ override setContext(context: string): void {
613
+ super.setContext(context);
614
+ this.child = LoggerService.root.child({ context });
615
+ }
616
+
617
+ /** Logs a verbose message at the `trace` level. */
618
+ override verbose(message: unknown, context?: string, data?: LogData): void {
619
+ const parsed = this.parseMessage(message);
620
+ this.child.trace(
621
+ this.buildBindings({ context: context ?? this.context, data, parsed }),
622
+ parsed.text,
623
+ );
624
+ }
625
+
626
+ /** Logs a warning message at the `warn` level. */
627
+ override warn(message: unknown, context?: string, data?: LogData): void {
628
+ const parsed = this.parseMessage(message);
629
+ this.child.warn(
630
+ this.buildBindings({ context: context ?? this.context, data, parsed }),
631
+ parsed.text,
632
+ );
633
+ }
634
+ }
635
+
636
+ /**
637
+ * Root module of the plugin's application context.
638
+ *
639
+ * Built once per process and cached — the Nx daemon is long-lived, so paying
640
+ * for a NestJS context on every generator invocation would make the plugin the
641
+ * slowest thing in the graph.
642
+ */
643
+ export declare class MainModule {
644
+ }
645
+
646
+ /**
647
+ * Provides resolution of the options Nx passes this plugin.
648
+ *
649
+ * Kept apart from the modules that consume them so that adding an option
650
+ * touches one service rather than every call site that reads `nx.json`.
651
+ */
652
+ export declare class OptionsModule {
653
+ }
654
+
655
+ /**
656
+ * Narrows the untyped options object Nx hands a plugin.
657
+ *
658
+ * Nx passes whatever the consumer wrote in `nx.json` with no validation, so
659
+ * every field is checked before use rather than cast. A bad value falls back
660
+ * to its default: a typo in a target name should not stop the project graph
661
+ * from being built.
662
+ */
663
+ export declare class OptionsService {
664
+ constructor();
665
+ /** Narrows an untrusted value to an array without widening it to `any`. */
666
+ private isUnknownArray;
667
+ /** Reads this plugin's `configurationPath` out of an `nx.json`, if it names one. */
668
+ private readRegisteredConfigurationPath;
669
+ /** Reads a string field from an untrusted record, or `undefined`. */
670
+ private readString;
671
+ /**
672
+ * Resolves the configuration path a workspace means, without assuming one.
673
+ *
674
+ * Nx passes plugin options to `createNodes` and to executors, but not to a
675
+ * global sync generator or to anything run outside Nx entirely, such as the
676
+ * install-time bootstrap. Those read the registration themselves rather than
677
+ * assuming a path, so a workspace that keeps its configuration somewhere
678
+ * other than the root is not silently read from nothing.
679
+ *
680
+ * With no registration, the conventional root filenames are tried in order.
681
+ * `exists` is supplied by the caller rather than reached for here, because
682
+ * the callers do not agree on what a filesystem is: two of them read disk
683
+ * and the sync generator reads an Nx `Tree`.
684
+ */
685
+ resolveConfigurationPath(args: {
686
+ exists: (candidatePath: string) => boolean;
687
+ nxConfiguration: unknown;
688
+ }): string;
689
+ /**
690
+ * Extracts the generator inputs from an Nx options object.
691
+ *
692
+ * Nx hands a generator every option the consumer passed, including the ones
693
+ * that configure this plugin rather than the generator. Those are dropped so
694
+ * a template placeholder is never accidentally filled with a config path,
695
+ * and non-string values are dropped because substitutions are text.
696
+ */
697
+ resolveGeneratorInputs(options: unknown): Record<string, string | undefined>;
698
+ /**
699
+ * Resolves the effective plugin options from an untrusted value.
700
+ *
701
+ * Falls back to the most conventional configuration filename rather than
702
+ * discovering which one is present, which needs a filesystem; callers that
703
+ * have one resolve the path with `resolveConfigurationPath` first and pass
704
+ * the result in.
705
+ */
706
+ resolvePluginOptions(options: unknown): ConformetryPluginOptions;
707
+ }
708
+
709
+ /** A message split into the emoji the console shows and the prose telemetry stores. */
710
+ declare interface ParsedLogMessage {
711
+ emoji: string | undefined;
712
+ text: string;
713
+ }
714
+
715
+ /**
716
+ * Decides where a generator writes, by reading the workspace it writes into.
717
+ *
718
+ * `conformetry-generation` takes a destination and renders into it; it has no
719
+ * opinion about layout, exactly as `conformetry-validation` takes instances
720
+ * and has no opinion about how they were found. Layout is Nx-shaped knowledge,
721
+ * so it is answered here — and answered by looking at the projects and module
722
+ * folders that already exist, rather than by a configured convention that
723
+ * would go stale the moment one project deviated.
724
+ */
725
+ declare class PathsService {
726
+ private readonly instancesService;
727
+ private readonly configurationService;
728
+ private readonly scopeService;
729
+ constructor(instancesService: InstancesService, configurationService: ConfigurationService, scopeService: ScopeService);
730
+ /**
731
+ * Locates a named module, refusing one the project does not have.
732
+ *
733
+ * Naming a module means writing into one that exists. Placing the files at a
734
+ * made-up path instead scattered a stray directory across the project root
735
+ * and reported success, which reads as the generator having worked.
736
+ */
737
+ private requireModulePath;
738
+ /**
739
+ * Infers where a project keeps its modules, from where its modules already
740
+ * are.
741
+ *
742
+ * The directory holding the most of them wins, which is what makes this
743
+ * robust: one stray directory alongside `src/modules` does not move new
744
+ * modules next to it. Returns `undefined` for a project with no modules yet,
745
+ * because there is then nothing to infer from and guessing a convention is
746
+ * how a generic package acquires one repository's layout.
747
+ */
748
+ private resolveModuleParentPath;
749
+ /**
750
+ * Finds the directory holding an existing module of the given name.
751
+ *
752
+ * Used when a generator adds files to a module rather than creating one —
753
+ * `nestjs-service-file` writes a service into a module that already exists,
754
+ * so the module has to be located rather than placed.
755
+ */
756
+ private resolveModulePath;
757
+ /**
758
+ * Places a project that does not exist yet, which is why no project lookup
759
+ * can answer this. An unrecognized type is used verbatim, so the first
760
+ * project of a new type still lands somewhere sensible.
761
+ */
762
+ private resolveNewProjectPath;
763
+ /**
764
+ * The folder a generator's scope places new instances in, if it names one.
765
+ *
766
+ * Preferred over inferring from existing instances, because a scope is the
767
+ * author stating where instances belong — inference only guesses it, and
768
+ * guesses nothing at all in a project that has none yet.
769
+ */
770
+ private resolveScopedDirectory;
771
+ /**
772
+ * Infers the workspace directory a new project of a given type belongs in,
773
+ * from where projects of that type already live.
774
+ *
775
+ * `type: "packages"` resolves to whichever directory actually holds the
776
+ * workspace's packages, so the answer stays right in a workspace that calls
777
+ * it something else.
778
+ */
779
+ private resolveTypeDirectoryPath;
780
+ /**
781
+ * Resolves the absolute directory a generator's template tree is laid over.
782
+ *
783
+ * This is the *parent* of anything the template creates, because a template
784
+ * that produces a folder contains that folder. Falls back to the workspace
785
+ * root when the inputs name nothing to locate.
786
+ */
787
+ resolveGenerationPath(args: ResolveGenerationPathArguments): Promise<string>;
788
+ }
789
+
790
+ /**
791
+ * Wires the generic conformetry packages into one Nx-facing service.
792
+ *
793
+ * The plugin owns no generation or validation logic of its own; it supplies
794
+ * the Nx-shaped inputs — project roots, tags, a `Tree` — that the generic
795
+ * packages cannot know about.
796
+ */
797
+ export declare class PluginModule {
798
+ }
799
+
800
+ /**
801
+ * The plugin's whole surface: infer targets, generate, validate.
802
+ *
803
+ * Every entry point resolves the plugin's options and the conformetry
804
+ * configuration itself rather than taking them apart, so the Nx-facing
805
+ * functions in `index.ts` stay thin wrappers with no logic of their own.
806
+ */
807
+ export declare class PluginService {
808
+ private readonly adapterService;
809
+ private readonly instancesService;
810
+ private readonly configurationService;
811
+ private readonly generatorService;
812
+ private readonly generationService;
813
+ private readonly optionsService;
814
+ private readonly pathsService;
815
+ private readonly projectsService;
816
+ private readonly reportingService;
817
+ private readonly validationService;
818
+ private readonly logger;
819
+ constructor(adapterService: AdapterService, instancesService: InstancesService, configurationService: ConfigurationService, generatorService: GeneratorService, generationService: GenerationService, optionsService: OptionsService, pathsService: PathsService, projectsService: ProjectsService, reportingService: ReportingService, validationService: ValidationService, logger: LoggerService);
820
+ /**
821
+ * Fails when the emitted Nx plugin no longer matches the configuration.
822
+ *
823
+ * `generators.json` and its schemas are derived from `conformetry.config.ts`,
824
+ * so an edit to the configuration that is not followed by `nx sync` leaves Nx
825
+ * offering generators that no longer exist, or hiding ones that do. Comparing
826
+ * here rather than trusting `nx sync:check` means the plugin's own commands
827
+ * cannot run against a stale plugin.
828
+ */
829
+ private assertEmittedPluginCurrent;
830
+ /** Fails fast when the plugin would run against a stale or broken setup. */
831
+ private assertPluginInSync;
832
+ /**
833
+ * Fails when a configured generator points at a template that is not there.
834
+ *
835
+ * Template contents never reach `generators.json`, so a missing template
836
+ * directory is invisible to the drift check above: the emitted plugin still
837
+ * matches the configuration, and the failure only surfaces later as an empty
838
+ * generation or an instance matching nothing.
839
+ */
840
+ private assertTemplatesExist;
841
+ /** Reads the workspace's `nx.json`, or nothing when there is none. */
842
+ private readNxConfiguration;
843
+ /**
844
+ * Resolves this plugin's options against the workspace's registration.
845
+ *
846
+ * Nx passes plugin options to `createNodes` but not to a generator or to an
847
+ * inferred target's executor, which see only what the caller typed. Reading
848
+ * `nx.json` here rather than trusting a default is what lets a workspace keep
849
+ * its configuration somewhere other than the conventional path: the
850
+ * registration is the base, and anything Nx did pass wins over it.
851
+ */
852
+ private resolveOptions;
853
+ /**
854
+ * Builds one input glob per configured generator's template folder.
855
+ *
856
+ * A template's own files are not part of `configurationPath` or the emitted
857
+ * plugin, so without this an instance's validate target would keep a stale
858
+ * cache hit across an edit to the very template it is measured against —
859
+ * which is exactly how a broken template placeholder once reached every
860
+ * matched instance's `package.json` unnoticed.
861
+ */
862
+ private resolveTemplateInputs;
863
+ /** Reads every configured generator's template folder. */
864
+ private resolveTemplates;
865
+ /**
866
+ * Infers a validation target onto every project that holds at least one
867
+ * instance.
868
+ *
869
+ * Projects with nothing to validate get no target at all, rather than a
870
+ * target that trivially passes — an empty target still costs a task in every
871
+ * `run-many`, and it makes `nx show project` claim a capability the project
872
+ * does not have.
873
+ */
874
+ inferTargets(args: InferTargetsArguments): Promise<Map<string, InferredTargets>>;
875
+ /**
876
+ * Runs one configured generator against an Nx tree.
877
+ *
878
+ * Nothing is written to disk here — the tree records the writes and Nx
879
+ * decides whether to flush them, which is what makes `--dry-run` honest.
880
+ */
881
+ runGenerator(args: RunGeneratorArguments): Promise<string[]>;
882
+ /** Validates one project's instances and renders the report. */
883
+ runValidation(args: RunValidationArguments): Promise<RunValidationResult>;
884
+ }
885
+
886
+ /** One Nx project, reduced to what instance scoping needs. */
887
+ export declare interface ProjectScope {
888
+ readonly name: string;
889
+ /** Project root, relative to the workspace root. */
890
+ readonly root: string;
891
+ readonly tags: string[];
892
+ }
893
+
894
+ /**
895
+ * Reads the workspace's projects straight from their `project.json` files.
896
+ *
897
+ * Not from the project graph, because two of the callers have none: inferring
898
+ * targets is part of *building* that graph, and the install-time bootstrap
899
+ * runs with no Nx at all. One implementation shared by every caller is what
900
+ * keeps the emitted plugin byte-identical however it was produced — the drift
901
+ * check compares those bytes, and would fire on any disagreement.
902
+ */
903
+ declare class ProjectsService {
904
+ constructor();
905
+ /** Narrows an untrusted value to an array without widening it to `any`. */
906
+ private isUnknownArray;
907
+ /**
908
+ * Walks a directory for `project.json` files.
909
+ *
910
+ * Hidden directories and dependencies are skipped: the emitted plugin lives
911
+ * in one of the former, and walking the latter would take longer than every
912
+ * other part of an emit put together.
913
+ */
914
+ private listProjectConfigurationFiles;
915
+ /**
916
+ * Reads the paths `.nxignore` excludes from project discovery.
917
+ *
918
+ * Honored because a `project.json` inside a generator template is not a
919
+ * project — it is a file the template will one day render — and `.nxignore`
920
+ * is where a workspace already says so. Reading it keeps this walk agreeing
921
+ * with the graph Nx itself builds.
922
+ */
923
+ private readIgnoredPaths;
924
+ /**
925
+ * Every project in the workspace, as a scope a generator can be matched to.
926
+ *
927
+ * Sorted by name so that anything derived from this list — the choices an
928
+ * emitted schema offers, above all — is stable between runs.
929
+ */
930
+ listWorkspaceProjects(workspaceRoot: string): ProjectScope[];
931
+ /** Reads one project's name, root, and tags from its `project.json`. */
932
+ readProjectScope(args: ReadProjectScopeArguments): ProjectScope;
933
+ }
934
+
935
+ /** Arguments for reading one project's configuration. */
936
+ declare interface ReadProjectScopeArguments {
937
+ readonly projectConfigurationFile: string;
938
+ readonly workspaceRoot: string;
939
+ }
940
+
941
+ /** Arguments for deciding where a generator writes. */
942
+ declare interface ResolveGenerationPathArguments {
943
+ readonly configurationPath: string;
944
+ /**
945
+ * The generator being run, used to find the scope it is confined to.
946
+ *
947
+ * Optional so a caller with no generator in hand — anything resolving a path
948
+ * outside a generator run — still gets the inferred layout.
949
+ */
950
+ readonly generatorName?: string | undefined;
951
+ /** The generator's own inputs, such as `project`, `module`, and `name`. */
952
+ readonly inputs: Record<string, string | undefined>;
953
+ readonly tree: Tree;
954
+ readonly workspaceRoot: string;
955
+ }
956
+
957
+ /** Resolves the service backing target inference, generation, and validation. */
958
+ export declare function resolvePluginService(): Promise<PluginService>;
959
+
960
+ /**
961
+ * Bootstraps the plugin, warning rather than failing the install.
962
+ *
963
+ * A `postinstall` that exits non-zero fails `pnpm install` itself, which would
964
+ * leave an unrelated dependency change uninstallable for as long as the
965
+ * conformetry configuration is mid-edit. Nothing is lost by warning: every
966
+ * conformetry command re-checks the emitted plugin against the configuration
967
+ * and refuses to run against a stale one.
968
+ */
969
+ export declare function runBootstrapCli(workspaceRoot: string): Promise<void>;
970
+
971
+ /**
972
+ * Runs one configured generator against an Nx tree.
973
+ *
974
+ * This is the machinery a consumer's generated generator wrappers call. The
975
+ * published package deliberately declares no generators of its own: which
976
+ * generators exist is a property of the consumer's configuration, not of this
977
+ * package, so `nx g @conformetry/nx:anything` resolves nothing by
978
+ * design.
979
+ */
980
+ export declare function runConformetryGenerator(args: {
981
+ generatorName: string;
982
+ options?: Record<string, unknown>;
983
+ tree: Tree;
984
+ }): Promise<string[]>;
985
+
986
+ /** Arguments for running one generator against an Nx tree. */
987
+ declare interface RunGeneratorArguments {
988
+ readonly generatorName: string;
989
+ readonly options: Record<string, unknown>;
990
+ readonly tree: Tree;
991
+ readonly workspaceRoot: string;
992
+ }
993
+
994
+ /** Arguments for validating one project. */
995
+ declare interface RunValidationArguments {
996
+ readonly languageNames?: string[];
997
+ readonly options: unknown;
998
+ readonly project: ProjectScope;
999
+ /** Run-level conformance floor; the weakest of the three threshold levels. */
1000
+ readonly threshold?: number;
1001
+ readonly workspaceRoot: string;
1002
+ }
1003
+
1004
+ /** The outcome of validating one project. */
1005
+ declare interface RunValidationResult {
1006
+ readonly ok: boolean;
1007
+ readonly report: string;
1008
+ }
1009
+
1010
+ /**
1011
+ * Provides the reading and matching of a generator's project scope.
1012
+ *
1013
+ * Depends only on the dependency-free `ConfigurationModule`, for the one rule
1014
+ * that says whether a group is project-scoped at all: a scope is otherwise
1015
+ * answered from the configuration and a list of projects the caller already
1016
+ * has, so this stays usable from the graph, from a generator, and from the
1017
+ * install-time bootstrap alike.
1018
+ */
1019
+ export declare class ScopeModule {
1020
+ }
1021
+
1022
+ /**
1023
+ * Reads an instance group as Nx resolves it.
1024
+ *
1025
+ * A group carrying `tags` selects projects and reads its globs inside each
1026
+ * one; a group without them is a workspace glob, which is what a host with no
1027
+ * project graph writes. Telling the two apart by a field the group already has
1028
+ * is what keeps a generator's location stated once — nothing else can
1029
+ * contradict it, and so nothing can silently narrow it.
1030
+ *
1031
+ * Which of the two a group is, is the configuration layer's to say. The groups
1032
+ * this plugin claims and the ones `@conformetry/configuration` reads on its own
1033
+ * must be exact complements, and nothing fails if they are not — a group both
1034
+ * hosts skipped is simply never validated. One rule, read from one place, is
1035
+ * what rules that out.
1036
+ */
1037
+ export declare class ScopeService {
1038
+ private readonly configurationService;
1039
+ constructor(configurationService: ConfigurationService);
1040
+ /** Whether a group locates its instances by project tag. */
1041
+ private isProjectGroup;
1042
+ /**
1043
+ * Returns whether a group applies to a project.
1044
+ *
1045
+ * A group with no tags applies everywhere — tags narrow a group, they do not
1046
+ * opt it in, so a configuration that never mentions them still reaches every
1047
+ * project. A project must carry every tag the group names, so a second tag
1048
+ * narrows the group further rather than widening it.
1049
+ */
1050
+ matchesProject(args: {
1051
+ group: ConformetryInstanceGroup;
1052
+ project: ProjectScope;
1053
+ }): boolean;
1054
+ /**
1055
+ * Resolves one group against a project, into workspace-relative globs.
1056
+ *
1057
+ * A tagged group's globs are read inside the project, so `src/modules/*`
1058
+ * means the same thing in every project it selects. An untagged group is
1059
+ * returned as written, which is how a host with no projects resolves it.
1060
+ * Either way the result is indistinguishable downstream from a hand-written
1061
+ * glob — discovery, validation, and layout inference need know nothing.
1062
+ */
1063
+ resolveGroup(args: {
1064
+ group: ConformetryInstanceGroup;
1065
+ project: ProjectScope;
1066
+ }): ConformetryInstanceGroup[];
1067
+ /**
1068
+ * The folder a group's first glob points at, with any wildcard trimmed off.
1069
+ *
1070
+ * `src/modules/*` places a new module in `src/modules`; a glob that starts
1071
+ * with a wildcard places nothing, and layout falls back to being inferred.
1072
+ */
1073
+ resolveScopedDirectory(groups: ConformetryInstanceGroup[]): string | undefined;
1074
+ /**
1075
+ * The projects a generator's groups admit, by name and sorted.
1076
+ *
1077
+ * Sorted because the emitted schema is compared byte for byte by the drift
1078
+ * check, and an unstable order would report drift on every re-emit. A
1079
+ * generator with no tagged group admits nothing here, which the caller reads
1080
+ * as "do not constrain the prompt at all".
1081
+ */
1082
+ resolveScopedProjectNames(args: {
1083
+ groups: ConformetryInstanceGroup[];
1084
+ projects: ProjectScope[];
1085
+ }): string[];
1086
+ }
1087
+
1088
+ /** The filesystem and formatter adapters one generator run writes through. */
1089
+ declare interface TreeAdapters {
1090
+ readonly filesystem: FileSystemAdapter;
1091
+ readonly formatter: FormatterAdapter;
1092
+ }
1093
+
1094
+ export { }