@conformetry/configuration 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,1034 @@
1
+ import { InventoriedInstance } from '@conformetry/core';
2
+ import { InventoriedPairing } from '@conformetry/core';
3
+ import { InventoriedTemplate } from '@conformetry/core';
4
+ import { PreparedValidationDocument } from '@conformetry/core';
5
+
6
+ /**
7
+ * How a caller says "every template" to `validate --templates`.
8
+ *
9
+ * A sentinel rather than a second flag, because supplying a value is how this
10
+ * command line opts out of being prompted: omitting `--templates` in a
11
+ * terminal asks, and passing `all` decides without narrowing. It lives beside
12
+ * the other command-line vocabulary because that is what it is — the word a
13
+ * caller types — and `assertNoCollisions` reads it from here so a template can
14
+ * never be named the same thing.
15
+ */
16
+ export declare const ALL_TEMPLATES_SELECTION = "all";
17
+
18
+ /**
19
+ * Provides the configuration layer's one public service.
20
+ *
21
+ * The five modules above supply the collaborators `ConfigurationService` is
22
+ * assembled from, and none of them is re-exported: a consumer outside this
23
+ * package injects `ConfigurationService` and nothing else, which is what makes
24
+ * this layer one entry point rather than a bag of services a caller has to
25
+ * know the names of. The three sibling ic-suite toolchains publish exactly
26
+ * this pair, and conformetry now reads as the same shape.
27
+ */
28
+ export declare class ConfigurationModule {
29
+ }
30
+
31
+ /**
32
+ * The one answer to "what is this run actually configured to do".
33
+ *
34
+ * Every question a caller outside this package can ask about configuration is
35
+ * asked here: what a config file declares, which templates it points at, which
36
+ * instances exist and what explains them, what a placeholder renders to, and
37
+ * what to ask a person for when an input was left off. A consumer therefore
38
+ * injects this and nothing else from this package, which is what makes the
39
+ * configuration layer one layer rather than a bag of collaborators a caller
40
+ * has to know the names of — the shape callidescope, codependix and codometer
41
+ * already publish.
42
+ *
43
+ * Loading a config file is the one job held here rather than delegated;
44
+ * everything below it is a one-line hand-off. Prompting, option parsing,
45
+ * rendering, template discovery, instance discovery and instance-group reading
46
+ * stay six classes in their own files, because they are six different jobs.
47
+ * What they stop being is six public entry points.
48
+ */
49
+ export declare class ConfigurationService {
50
+ private readonly inputPromptingService;
51
+ private readonly inputService;
52
+ private readonly instanceDiscoveryService;
53
+ private readonly instanceGroupService;
54
+ private readonly renderingService;
55
+ private readonly templateDiscoveryService;
56
+ constructor(inputPromptingService: InputPromptingService, inputService: InputService, instanceDiscoveryService: InstanceDiscoveryService, instanceGroupService: InstanceGroupService, renderingService: RenderingService, templateDiscoveryService: TemplateDiscoveryService);
57
+ /**
58
+ * Fills in the optional halves of one parsed generator entry.
59
+ *
60
+ * A generator with no inputs and no instances is legal — it renders a fixed
61
+ * template nobody validates — so both default to empty rather than failing.
62
+ */
63
+ private applyGeneratorDefaults;
64
+ /**
65
+ * Walks upward from the process cwd looking for the workspace manifest.
66
+ *
67
+ * Used to resolve a config path given relative to the workspace root even
68
+ * when the command was invoked from a nested directory.
69
+ */
70
+ private findWorkspaceRoot;
71
+ /** Loads a config module, choosing the reader by extension. */
72
+ private loadConfigurationModule;
73
+ /** Reads a JSON or JSONC config file. */
74
+ private loadJsonConfiguration;
75
+ /**
76
+ * Resolves a config path against the cwd, falling back to the workspace root.
77
+ */
78
+ private resolveConfigurationPath;
79
+ /** Derives the case variants every template can reference from one name. */
80
+ buildNameSubstitutions(name: string): Substitutions;
81
+ /** Reads one template folder. */
82
+ collectTemplate(args: Parameters<TemplateDiscoveryService["collectTemplate"]>[0]): TemplateDefinition;
83
+ /** Reads every configured generator's template folder. */
84
+ collectTemplates(args: {
85
+ configuration: ConformetryConfiguration;
86
+ workingDirectory: string;
87
+ }): TemplateDefinition[];
88
+ /** Expands instance globs into the instances that exist. */
89
+ findInstances(args: FindInstancesArguments): Instance[];
90
+ /** Whether anybody is there to answer a question. */
91
+ isAtTerminal(): boolean;
92
+ /** Whether a group locates its instances inside the hosts its tags select. */
93
+ isProjectScoped(group: ConformetryInstanceGroup): boolean;
94
+ /**
95
+ * Loads, validates, and normalizes a conformetry configuration file.
96
+ *
97
+ * Throws `UnknownConfigurationFileTypeError` for an unreadable extension, and
98
+ * propagates the Zod error for a malformed registry — a bad config should
99
+ * fail loudly rather than silently validate nothing.
100
+ */
101
+ loadConformetryConfiguration(configurationPath: string): Promise<ConformetryConfiguration>;
102
+ /** Resolves every instance to the template, or templates, that explain it. */
103
+ matchInstances(args: {
104
+ instances: Instance[];
105
+ templates: TemplateDefinition[];
106
+ }): ResolvedInstances;
107
+ /** Splits a comma-delimited filter option into its values. */
108
+ parseCommaDelimitedOption(value: string | undefined): string[] | undefined;
109
+ /** Trims an optional string option, treating blank as absent. */
110
+ parseOptionalOption(value: string | undefined): string | undefined;
111
+ /** Parses a threshold option as a ratio from 0 to 1. */
112
+ parseThresholdOption(value: string | undefined): number | undefined;
113
+ /**
114
+ * Prepares the rendered template and instance document pairs for each matched
115
+ * instance, restricted to the extensions the caller's languages claim.
116
+ */
117
+ prepareDocuments(args: PrepareDocumentsArguments): PreparedInstanceDocuments[];
118
+ /** Asks which single template to run, filtering as the caller types. */
119
+ promptForTemplate(templates: Parameters<InputPromptingService["promptForTemplate"]>[0]): Promise<string | undefined>;
120
+ /** Asks which templates to narrow a run to. */
121
+ promptForTemplates(templates: Parameters<InputPromptingService["promptForTemplates"]>[0]): Promise<string[] | undefined>;
122
+ /** Keeps the groups a host with no project graph can actually locate. */
123
+ readWorkspaceGroups(groups: readonly ConformetryInstanceGroup[]): ConformetryInstanceGroup[];
124
+ /** Renders template contents with mustache. */
125
+ renderContent(args: Parameters<RenderingService["renderContent"]>[0]): string;
126
+ /** Renders a template path with mustache. */
127
+ renderPath(args: Parameters<RenderingService["renderPath"]>[0]): string;
128
+ /** Resolves generator inputs from raw command-line arguments. */
129
+ resolveGeneratorInputs(args: ResolveGeneratorInputsArguments): Promise<Record<string, string>>;
130
+ /** Lists every file a matched instance's template requires it to have. */
131
+ resolveInstanceFiles(instances: MatchedInstance[]): InstanceFile[];
132
+ /** Lists every instance found, paired with the templates that explain it. */
133
+ resolveInventoriedInstances(args: ResolveInventoryArguments): ReturnType<InstanceDiscoveryService["resolveInventoriedInstances"]>;
134
+ /** Lists every template declared, paired with the instances it explains. */
135
+ resolveInventoriedTemplates(args: ResolveInventoryArguments): ReturnType<InstanceDiscoveryService["resolveInventoriedTemplates"]>;
136
+ }
137
+
138
+ /**
139
+ * The loaded configuration: one entry per generator.
140
+ *
141
+ * An array rather than a keyed record because a generator's name is already a
142
+ * field, and a record made the name true in two places at once.
143
+ */
144
+ export declare type ConformetryConfiguration = ConformetryGeneratorDefinition[];
145
+
146
+ /** One generator, with the template it renders and the instances it governs. */
147
+ export declare interface ConformetryGeneratorDefinition {
148
+ description?: string;
149
+ /**
150
+ * The values this generator substitutes, as JSON Schema fragments. Named for
151
+ * what the implementation calls them everywhere else — a generator takes
152
+ * inputs and renders a template with them.
153
+ */
154
+ inputs: Record<string, ConformetryGeneratorInputDefinition>;
155
+ /**
156
+ * Where this generator's output already lives in the workspace. Validation
157
+ * expands these to find what to check; generation reads them to learn where
158
+ * a new instance belongs.
159
+ */
160
+ instances: ConformetryInstanceGroup[];
161
+ name: string;
162
+ /** The template folder, relative to the workspace root. */
163
+ templatePath: string;
164
+ /**
165
+ * Lowest conformance score, from 0 to 1, an instance of this template may
166
+ * have and still pass.
167
+ *
168
+ * Left undefined when unset rather than defaulted to 1, so a run-level
169
+ * `--threshold` still has somewhere to apply. The default is reached only
170
+ * when no level supplies a value.
171
+ */
172
+ threshold?: number;
173
+ }
174
+
175
+ /** One configurable input, expressed as a JSON Schema fragment. */
176
+ export declare type ConformetryGeneratorInputDefinition = Record<string, unknown>;
177
+
178
+ /**
179
+ * One set of instance globs and the substitutions their template renders with.
180
+ *
181
+ * Directory and file patterns behave differently on purpose — see
182
+ * `Instance.path`.
183
+ */
184
+ export declare interface ConformetryInstanceGroup {
185
+ /**
186
+ * Globs locating this group's instances.
187
+ *
188
+ * Workspace-relative on their own. A host that resolves `tags` may instead
189
+ * read them relative to each labelled host it selects — see
190
+ * `ConformetryNxInstanceGroup` in `@conformetry/nx` — which is why a group
191
+ * naming only tags is legal: it selects without locating.
192
+ */
193
+ patterns?: string[] | undefined;
194
+ /**
195
+ * Values every placeholder this generator's template uses must be given.
196
+ * Mustache renders an unknown placeholder as empty, so a missing entry shows
197
+ * up as a silent hole rather than an error.
198
+ */
199
+ substitutions?: Record<string, string> | undefined;
200
+ /**
201
+ * Labels selecting the hosts this group applies to.
202
+ *
203
+ * The base configuration carries them uninterpreted, because it has no
204
+ * notion of a host to match them against; `conformetry-nx` reads them as Nx
205
+ * project tags, and another host is free to read them as something else. A
206
+ * group with no tags applies everywhere.
207
+ */
208
+ tags?: string[] | undefined;
209
+ /**
210
+ * Lowest conformance score the instances in this group may have, overriding
211
+ * the generator's own threshold.
212
+ *
213
+ * This is what makes a migration bearable: one directory being brought onto
214
+ * a new template can be held to a lower bar while every other instance of
215
+ * the same template stays strict.
216
+ */
217
+ threshold?: number | undefined;
218
+ }
219
+
220
+ /** Arguments for expanding instance globs into instances. */
221
+ export declare interface FindInstancesArguments {
222
+ /** Glob patterns, resolved against the working directory. */
223
+ readonly patterns: string[];
224
+ /**
225
+ * Applied to every instance these patterns produce. Callers that need
226
+ * per-project values, such as an Nx plugin supplying `type`, call once per
227
+ * project rather than passing a lookup table.
228
+ */
229
+ readonly substitutions?: Substitutions;
230
+ /** Applied to every instance these patterns produce, when the group sets one. */
231
+ readonly threshold?: number;
232
+ readonly workingDirectory: string;
233
+ }
234
+
235
+ /**
236
+ * Thrown when a command line cannot be turned into a run.
237
+ *
238
+ * One class rather than one per cause: every cause is the same event to
239
+ * whoever catches it — nothing was generated, and the fix is to retype the
240
+ * flags. Only the wording varies, which is what the factory below does.
241
+ */
242
+ export declare class InputError extends Error {
243
+ constructor(message: string);
244
+ }
245
+
246
+ /**
247
+ * Parses command-line options into generator inputs.
248
+ *
249
+ * Generator inputs are not declared as CLI flags ahead of time — they come
250
+ * from whichever generator the user selected — so they are scanned out of the
251
+ * raw argument list and matched against the generator's schema.
252
+ */
253
+ declare class InputOptionsService {
254
+ constructor();
255
+ /**
256
+ * Reads one option into the accumulating input map and reports how many
257
+ * argument tokens it consumed, so the caller can advance past its value.
258
+ */
259
+ private collectOneInput;
260
+ /** Reads the option name from `--flag` or `--flag=value`. */
261
+ private readOptionName;
262
+ /**
263
+ * Reads an option's value from either `--flag=value` or `--flag value`.
264
+ *
265
+ * Returns `undefined` when the next token is another flag, so a valueless
266
+ * flag does not swallow the option that follows it.
267
+ */
268
+ private readOptionValue;
269
+ /** Matches an option name to a schema property, in camel or kebab form. */
270
+ private resolvePropertyName;
271
+ /**
272
+ * Scans raw arguments for flags matching the generator's schema.
273
+ *
274
+ * Unknown flags are ignored rather than rejected: the argument list also
275
+ * carries the command's own options, which are handled elsewhere.
276
+ */
277
+ collectGeneratorInputs(args: {
278
+ rawArguments: string[];
279
+ schema: JsonSchemaDefinition;
280
+ }): Record<string, string>;
281
+ /** Coerces mixed-typed runtime options into string inputs. */
282
+ normalizeRuntimeOptions(options: Record<string, unknown>): Record<string, string | undefined>;
283
+ /**
284
+ * Resolves where generated files should land: an explicit output option, a
285
+ * resolved project root, or a directory named after the generator.
286
+ */
287
+ resolveTargetDirectoryPath(args: {
288
+ defaultGeneratedOutputDirectory?: string;
289
+ generatorName: string;
290
+ options: Record<string, unknown>;
291
+ resolveProjectRootPath?: (projectName: string) => string | undefined;
292
+ }): string;
293
+ }
294
+
295
+ /**
296
+ * Asks the user for input values interactively.
297
+ *
298
+ * Whether anybody *can* be asked is decided here rather than by the caller,
299
+ * and `promptForInput` refuses rather than draws itself when nobody can. That
300
+ * used to be the caller's job, behind a `--no-interactive` flag; a flag is a
301
+ * poor place for it, because forgetting to consult it is what hung this CLI
302
+ * in non-interactive environments.
303
+ *
304
+ * `isAtTerminal` stays public because some callers genuinely need the question
305
+ * rather than the refusal: an **optional** input nobody can be asked about is
306
+ * left out where a required one is refused, and the two template pickers below
307
+ * are only ever reached once a command has already asked it.
308
+ *
309
+ * Those pickers widen this service's remit from "values a schema declares" to
310
+ * "also, which template to run". That is a deliberate trade: one prompting
311
+ * seam is easier to keep honest than two, and a second one would have to read
312
+ * the terminal for itself.
313
+ */
314
+ declare class InputPromptingService {
315
+ private readonly inputSchemaService;
316
+ constructor(inputSchemaService: InputSchemaService);
317
+ /** Overridable so tests never touch a real terminal. */
318
+ private readonly promptRunner;
319
+ /**
320
+ * Refuses to draw a prompt nobody can answer.
321
+ *
322
+ * `prompts` does not fail on a non-terminal stdin — it renders the menu,
323
+ * never resolves, and lets the process exit 0, so a run that generated
324
+ * nothing reads as one that succeeded.
325
+ */
326
+ private assertCanPrompt;
327
+ /** Renders one template as a choice, omitting an absent description. */
328
+ private describeChoice;
329
+ /**
330
+ * Whether anybody is there to answer a question.
331
+ *
332
+ * The one place the terminal is read, so the refusal in front of a prompt
333
+ * and the caller's decision to skip an optional input cannot disagree about
334
+ * what counts as interactive. `isTTY` is read as falsy rather than coerced:
335
+ * `@types/node` calls it a `boolean` while it is `undefined` off a terminal,
336
+ * so lint rejects the coercion that would say so.
337
+ */
338
+ isAtTerminal(): boolean;
339
+ /**
340
+ * Prompts for one value, offering a choice list when the schema declares an
341
+ * `enum` and free text otherwise.
342
+ */
343
+ promptForInput(input: SchemaInput): Promise<string | undefined>;
344
+ /**
345
+ * Asks which single template to run, filtering as the caller types.
346
+ *
347
+ * Autocomplete rather than a plain select because a template name is long
348
+ * by design — `nestjs-service-module` — and typing a fragment is what makes
349
+ * that cheap. Whether anybody can be asked at all is the caller's decision,
350
+ * already taken by the time this is reached.
351
+ */
352
+ promptForTemplate(templates: readonly TemplateChoice[]): Promise<string | undefined>;
353
+ /**
354
+ * Asks which templates to narrow a run to, offering the `all` sentinel
355
+ * alongside them so the picker can express everything the flag can.
356
+ *
357
+ * Ticking nothing resolves to no selection rather than an empty one: an
358
+ * empty narrowing would validate nothing at all, where the caller plainly
359
+ * declined to narrow.
360
+ */
361
+ promptForTemplates(templates: readonly TemplateChoice[]): Promise<string[] | undefined>;
362
+ }
363
+
364
+ /**
365
+ * Reads and enforces the JSON Schema fragments that describe generator inputs.
366
+ *
367
+ * Every accessor tolerates a malformed or absent schema: a generator with no
368
+ * declared parameters should still run, and an author's typo should not crash
369
+ * the CLI before it can report anything useful.
370
+ */
371
+ declare class InputSchemaService {
372
+ constructor();
373
+ /** Reads one property off a schema fragment when it is an object. */
374
+ private readSchemaProperty;
375
+ /** Validates a value against a schema `enum`, when one is declared. */
376
+ private validateEnum;
377
+ /** Validates a value against `minLength` and `maxLength`. */
378
+ private validateLength;
379
+ /** Validates a value against a schema `pattern`. */
380
+ private validatePattern;
381
+ /** Describes one input, resolving its schema fragment and required flag. */
382
+ describeInput(args: {
383
+ inputName: string;
384
+ schema: JsonSchemaDefinition;
385
+ }): SchemaInput;
386
+ /** Reads the string members of a schema `enum`. */
387
+ readEnumValues(propertySchema: unknown): string[];
388
+ /** Reads a schema `description`, falling back to a generic prompt. */
389
+ readPromptMessage(input: SchemaInput): string;
390
+ /** Lists the input names a schema declares. */
391
+ readPropertyNames(schema: JsonSchemaDefinition): string[];
392
+ /**
393
+ * Validates a value, returning `true` or the reason it failed.
394
+ *
395
+ * An empty value is only an error when the input is required, so optional
396
+ * inputs can be skipped by pressing enter at a prompt.
397
+ */
398
+ validateValue(args: {
399
+ input: SchemaInput;
400
+ value: unknown;
401
+ }): string | true;
402
+ }
403
+
404
+ /**
405
+ * Resolves generator inputs from the command line, asking for what is missing.
406
+ *
407
+ * Nobody passes a "may I prompt" boolean any more. It used to be threaded
408
+ * through every method here from a `--no-interactive` flag, which put the one
409
+ * decision that can hang the process in the hands of each caller in turn;
410
+ * `InputPromptingService` owns it now and refuses at the prompt itself.
411
+ */
412
+ declare class InputService {
413
+ private readonly inputOptionsService;
414
+ private readonly inputPromptingService;
415
+ private readonly inputSchemaService;
416
+ constructor(inputOptionsService: InputOptionsService, inputPromptingService: InputPromptingService, inputSchemaService: InputSchemaService);
417
+ /** Validates a value the caller already had, throwing if it is invalid. */
418
+ private acceptProvidedValue;
419
+ /** Walks a schema, taking each value from the resolver or a prompt. */
420
+ private resolveInputs;
421
+ /**
422
+ * Obtains one missing value by prompting.
423
+ *
424
+ * With nobody at a terminal the two kinds of input part ways: an optional
425
+ * one is left out, and a required one is refused. Prompting anyway is what
426
+ * used to hang the process, and refusing an optional input would fail runs
427
+ * that never needed the value at all.
428
+ */
429
+ private resolveMissingValue;
430
+ /** Splits a comma-delimited filter option into its values. */
431
+ parseCommaDelimitedOption(value: string | undefined): string[] | undefined;
432
+ /** Trims an optional string option, treating blank as absent. */
433
+ parseOptionalOption(value: string | undefined): string | undefined;
434
+ /** Trims a required string option, rejecting blank values. */
435
+ parseRequiredOption(args: {
436
+ optionName: string;
437
+ value: string;
438
+ }): string;
439
+ /**
440
+ * Parses a threshold option as a ratio from 0 to 1.
441
+ *
442
+ * Rejected loudly rather than clamped: `--threshold 90` is someone meaning
443
+ * 90%, and silently reading it as "always passes" would turn a typo into a
444
+ * validation run that can never fail.
445
+ */
446
+ parseThresholdOption(value: string | undefined): number | undefined;
447
+ /** Resolves generator inputs from raw command-line arguments. */
448
+ resolveGeneratorInputs(args: ResolveGeneratorInputsArguments): Promise<Record<string, string>>;
449
+ /** Resolves inputs from values the caller already parsed. */
450
+ resolveInputsFromValues(args: ResolveInputsFromValuesArguments): Promise<Record<string, string>>;
451
+ }
452
+
453
+ /**
454
+ * A place a template's tree is rendered into, plus the name it renders with.
455
+ *
456
+ * Instances come from the caller's glob expansion. Nothing here knows how they
457
+ * were found — that is the host's job, because globbing a workspace and
458
+ * reading project labels is workspace knowledge, not template knowledge.
459
+ */
460
+ export declare interface Instance {
461
+ /**
462
+ * Absolute paths the match is restricted to, or `undefined` for the whole
463
+ * directory tree.
464
+ *
465
+ * This is what lets a two-file template like `nestjs-service-file` win. A
466
+ * directory glob leaves the scope open and the largest fitting template
467
+ * wins; a file glob narrows the scope to the matched files, so a template
468
+ * describing exactly those files fits better than one describing the whole
469
+ * directory.
470
+ */
471
+ readonly fileScope?: string[];
472
+ /** Drives the name substitutions — a directory basename or a filename stem. */
473
+ readonly nameStem: string;
474
+ /**
475
+ * Absolute path to the directory the template's tree is laid over.
476
+ *
477
+ * A template that produces a folder contains that folder, so this is the
478
+ * folder's *parent*: `nestjs-service-module` holds `{{nameKebabCase}}/…`,
479
+ * and its path is `…/src/modules`. A template that produces loose files
480
+ * holds them at its root, and its path is the directory those files sit in.
481
+ */
482
+ readonly path: string;
483
+ /**
484
+ * Substitutions the caller supplies on top of the derived name variants,
485
+ * such as `type` from the project graph. Mustache renders an unknown
486
+ * placeholder as empty, so anything a template references must arrive here.
487
+ */
488
+ readonly substitutions?: Substitutions;
489
+ /**
490
+ * Lowest conformance score this instance may have, from the instance group
491
+ * that located it. The narrowest of the three levels, so it wins.
492
+ */
493
+ readonly threshold?: number;
494
+ }
495
+
496
+ /**
497
+ * Expands instance glob patterns into instances.
498
+ *
499
+ * The globs are the author's assertion of what was generated from a template;
500
+ * nothing is inferred from directory names or marker files, which is what the
501
+ * previous matcher did and what made it repo-specific.
502
+ */
503
+ declare class InstanceDiscoveryLocatingService {
504
+ constructor();
505
+ /**
506
+ * Derives the substitutions an instance's own location answers.
507
+ *
508
+ * `type` is the top-level directory the instance sits in — `packages` for
509
+ * `packages/widgets` — because that is what a project template's own
510
+ * `project.json` renders into its paths. Derived rather than configured for
511
+ * the same reason the name variants are: it is a fact about where the
512
+ * instance is, and restating it per glob is how the two drift apart. A
513
+ * configured value still wins, which is what a workspace nesting its
514
+ * projects deeper needs.
515
+ */
516
+ private deriveLocationSubstitutions;
517
+ /**
518
+ * Returns the literal filename suffix a pattern ends with, such as
519
+ * `.service.ts` for `**\/*.service.ts`, or `""` when the pattern's last
520
+ * segment holds no wildcard.
521
+ *
522
+ * This is what a file instance's name is derived from: the part of the
523
+ * filename the glob did not spell out is the name.
524
+ */
525
+ private resolveGlobSuffix;
526
+ /**
527
+ * Derives the name an instance's substitutions are built from.
528
+ *
529
+ * A directory is named by itself. A file is named by what remains once the
530
+ * glob's literal suffix is removed, so `differences.service.ts` matched by
531
+ * `*.service.ts` and `differences.service.unit.test.ts` matched by
532
+ * `*.service.unit.test.ts` both yield `differences` — which is what collapses the
533
+ * two into a single two-file instance.
534
+ */
535
+ private resolveNameStem;
536
+ /**
537
+ * Expands every pattern and returns one instance per distinct path, name,
538
+ * and scope kind.
539
+ *
540
+ * A directory match leaves the file scope open, so the largest fitting
541
+ * template wins. File matches for the same name are unioned into one scoped
542
+ * instance, so a template describing exactly those files wins instead. A
543
+ * directory and a file instance for the same name stay separate on purpose:
544
+ * both run, and deduplication reports the finding from the smaller template.
545
+ */
546
+ findInstances(args: FindInstancesArguments): Instance[];
547
+ }
548
+
549
+ /**
550
+ * Decides which template an instance directory was generated from.
551
+ *
552
+ * Nothing declares the answer — an instance is ordinary code with no marker
553
+ * saying where it came from — so the template is inferred from how much of its
554
+ * structure the instance already has.
555
+ */
556
+ declare class InstanceDiscoveryMatchingService {
557
+ private readonly templateDiscoveryService;
558
+ private readonly renderingService;
559
+ constructor(templateDiscoveryService: TemplateDiscoveryService, renderingService: RenderingService);
560
+ /**
561
+ * Orders template matches best-first.
562
+ *
563
+ * Coverage ratio leads, because absolute count alone lets a large template
564
+ * win on a weak partial match — a module directory matching three of a
565
+ * seven-file GraphQL template would beat nothing, and used to. Absolute
566
+ * count breaks ratio ties, so a five-file module template beats a two-file
567
+ * service-file template when both match completely. Name makes it
568
+ * deterministic.
569
+ */
570
+ private compareMatches;
571
+ /**
572
+ * Builds the substitutions an instance's template is rendered with.
573
+ *
574
+ * Name variants come from the instance's stem; caller-supplied values are
575
+ * spread last so an explicit `type` or `description` always wins.
576
+ */
577
+ buildSubstitutions(instance: Instance): Substitutions;
578
+ /**
579
+ * Resolves every instance to the template — or templates — that explain it.
580
+ *
581
+ * An instance matching nothing, or matching two templates equally well but
582
+ * only partially, is returned as unmatched rather than dropped: the caller
583
+ * asserted these are instances, so silence would hide both a drifted
584
+ * instance and a pair of indistinguishable templates.
585
+ *
586
+ * A complete tie is different, and is matched against every tied template. A
587
+ * module holding both a command and a service really is an instance of both
588
+ * `nestjs-command-module` and `nestjs-service-module`; calling that ambiguous
589
+ * would demand the author narrow a glob that is not wrong.
590
+ */
591
+ matchInstances(args: {
592
+ instances: Instance[];
593
+ templates: TemplateDefinition[];
594
+ }): ResolvedInstances;
595
+ /**
596
+ * Weighs every template that shares at least one file with the instance,
597
+ * best-first.
598
+ *
599
+ * Public because a caller may need the ranking itself and not just the
600
+ * verdict: nothing records which template an instance came from, so an
601
+ * ambiguous or unmatched outcome is only explainable by showing what was
602
+ * weighed and how well each template fitted.
603
+ */
604
+ matchTemplates(args: {
605
+ instance: Instance;
606
+ substitutions: Substitutions;
607
+ templates: TemplateDefinition[];
608
+ }): TemplateMatch[];
609
+ }
610
+
611
+ /**
612
+ * Finds the generated code a workspace holds, and decides what explains it.
613
+ *
614
+ * This is the workspace-facing half of discovery: the template-discovery module
615
+ * reads the templates root, and this one reads the tree those templates were
616
+ * rendered into. Matching lives here because every answer it produces is
617
+ * instance-shaped — a matched instance, an unmatched one, or a ranking of
618
+ * templates against one path.
619
+ */
620
+ declare class InstanceDiscoveryService {
621
+ private readonly instanceDiscoveryLocatingService;
622
+ private readonly instanceDiscoveryMatchingService;
623
+ private readonly instanceGroupService;
624
+ private readonly templateDiscoveryService;
625
+ constructor(instanceDiscoveryLocatingService: InstanceDiscoveryLocatingService, instanceDiscoveryMatchingService: InstanceDiscoveryMatchingService, instanceGroupService: InstanceGroupService, templateDiscoveryService: TemplateDiscoveryService);
626
+ /** Weighs one instance against every template, best fit first. */
627
+ private weighInstance;
628
+ /** Builds the substitutions an instance's template is rendered with. */
629
+ buildSubstitutions(instance: Instance): ReturnType<InstanceDiscoveryMatchingService["buildSubstitutions"]>;
630
+ /** Expands instance globs into the instances that exist. */
631
+ findInstances(args: FindInstancesArguments): Instance[];
632
+ /** Resolves every instance to the template, or templates, that explain it. */
633
+ matchInstances(args: {
634
+ instances: Instance[];
635
+ templates: TemplateDefinition[];
636
+ }): ResolvedInstances;
637
+ /** Ranks every template that shares a file with one instance, best first. */
638
+ matchTemplates(args: Parameters<InstanceDiscoveryMatchingService["matchTemplates"]>[0]): ReturnType<InstanceDiscoveryMatchingService["matchTemplates"]>;
639
+ /**
640
+ * Prepares the rendered template and instance document pairs for each matched
641
+ * instance, restricted to the extensions the caller's languages claim.
642
+ */
643
+ prepareDocuments(args: PrepareDocumentsArguments): PreparedInstanceDocuments[];
644
+ /**
645
+ * Keeps the groups a host with no project graph can actually locate.
646
+ *
647
+ * A group carrying `tags` reads its globs *inside each project the tags
648
+ * select*, so `src/modules/*` has no meaning until a project root is joined
649
+ * to it. This package has no notion of a project, so such a group is not
650
+ * addressed to it and is left out rather than expanded from the working
651
+ * directory — which would match whatever happened to sit at the same
652
+ * relative path and measure it against a template scoped to other projects.
653
+ * `@conformetry/nx` resolves those groups first, so what reaches discovery
654
+ * from there is already workspace-relative and untagged groups are all that
655
+ * is left to read.
656
+ *
657
+ * Dropping them is deliberately silent here and reported by the caller: this
658
+ * is a question about one group, and only a command knows whether the run it
659
+ * was asked for has been left with nothing.
660
+ *
661
+ * Which groups those are is `InstanceGroupService`'s to say, so that this and
662
+ * the set `@conformetry/nx` claims stay exact complements.
663
+ */
664
+ readWorkspaceGroups(groups: readonly ConformetryInstanceGroup[]): ConformetryInstanceGroup[];
665
+ /** Lists every file a matched instance's template requires it to have. */
666
+ resolveInstanceFiles(instances: MatchedInstance[]): InstanceFile[];
667
+ /**
668
+ * Lists every instance found, paired with the templates that explain it.
669
+ *
670
+ * `templateNames` narrows the pairing rather than the search, so an instance
671
+ * that no named template explains drops out entirely.
672
+ */
673
+ resolveInventoriedInstances(args: ResolveInventoryArguments): InventoriedInstance[];
674
+ /**
675
+ * Lists every template declared, paired with the instances it explains.
676
+ *
677
+ * `instancePatterns` narrows which instances are considered, which is what
678
+ * turns this into "which templates explain this path". A path can legitimately
679
+ * belong to several templates, so every one that fits is reported.
680
+ */
681
+ resolveInventoriedTemplates(args: ResolveInventoryArguments): InventoriedTemplate[];
682
+ /** Weighs every instance the globs find against every declared template. */
683
+ takeInventory(args: ResolveInventoryArguments): {
684
+ templates: TemplateDefinition[];
685
+ weighed: {
686
+ instance: Instance;
687
+ pairings: InventoriedPairing[];
688
+ }[];
689
+ };
690
+ }
691
+
692
+ /** A file a matched template requires its instance to have. */
693
+ export declare interface InstanceFile {
694
+ readonly instance: MatchedInstance;
695
+ readonly instanceFilePath: string;
696
+ readonly templateFilePath: string;
697
+ }
698
+
699
+ /**
700
+ * Reads an instance group's own fields, for every host that resolves one.
701
+ *
702
+ * `ConformetryInstanceGroup` is declared beside this, and how its `tags` are
703
+ * read decides which host owns a group — so the reading lives here rather than
704
+ * in either host. `@conformetry/nx` resolves project-scoped groups against the
705
+ * project graph; a host without one leaves them alone. The two answers must be
706
+ * complements, and nothing would fail if they stopped being: each host would
707
+ * simply take a different set of groups to be its own, and validation would
708
+ * quietly measure the wrong tree.
709
+ *
710
+ * Dependency-free on purpose. Every host reaches it through
711
+ * `ConfigurationService`, so nothing has to be wired up to ask this question —
712
+ * including the install-time bootstrap, which has no project graph to hand
713
+ * anybody.
714
+ */
715
+ declare class InstanceGroupService {
716
+ constructor();
717
+ /**
718
+ * Whether a group locates its instances inside the hosts its tags select.
719
+ *
720
+ * Non-empty `tags` is the whole rule. An empty array is not a selector — it
721
+ * selects nothing and would silence the group entirely — so it reads as the
722
+ * workspace form, the same as omitting the field.
723
+ */
724
+ isProjectScoped(group: ConformetryInstanceGroup): boolean;
725
+ }
726
+
727
+ /**
728
+ * Minimal JSON Schema fragment used to discover a generator's inputs.
729
+ *
730
+ * Only the parts conformetry actually reads are modelled — `properties` for
731
+ * the input names, and `required` for which ones must be supplied.
732
+ */
733
+ export declare interface JsonSchemaDefinition {
734
+ [key: string]: unknown;
735
+ properties?: Record<string, unknown>;
736
+ }
737
+
738
+ /** An instance paired with the template that best explains it. */
739
+ export declare interface MatchedInstance {
740
+ readonly instance: Instance;
741
+ /** How many of the template's files the instance already has. */
742
+ readonly matchedFileCount: number;
743
+ readonly substitutions: Substitutions;
744
+ readonly template: TemplateDefinition;
745
+ }
746
+
747
+ /**
748
+ * A required input that cannot be asked for, because stdin is not a terminal.
749
+ *
750
+ * `prompts` does not fail there — it draws its menu, never resolves, and the
751
+ * process exits 0 having done nothing, which is exactly how this CLI used to
752
+ * hang in non-interactive environments. Naming the flag to pass is the whole
753
+ * remedy, so the message carries it.
754
+ */
755
+ export declare const missingInputError: (inputName: string) => InputError;
756
+
757
+ /**
758
+ * Raised when a template interpolates a placeholder nobody supplied a value
759
+ * for.
760
+ *
761
+ * Refusing is the only outcome that cannot be mistaken for success: mustache
762
+ * would render the placeholder as an empty string, and because generation and
763
+ * validation render identically, both halves of the loop would lose the same
764
+ * value and agree that nothing was wrong. Section tags are exempt, and why is
765
+ * in this package's README.
766
+ */
767
+ export declare class MissingSubstitutionError extends Error {
768
+ constructor(args: {
769
+ placeholders: readonly string[];
770
+ subject: string;
771
+ });
772
+ }
773
+
774
+ /** Documents prepared for one matched instance. */
775
+ export declare interface PreparedInstanceDocuments {
776
+ readonly documents: PreparedValidationDocument[];
777
+ readonly instance: MatchedInstance;
778
+ }
779
+
780
+ /** Arguments for preparing comparison documents for matched instances. */
781
+ export declare interface PrepareDocumentsArguments {
782
+ readonly fileExtensions: string[];
783
+ readonly instances: MatchedInstance[];
784
+ }
785
+
786
+ /**
787
+ * Renders conformetry template placeholders.
788
+ *
789
+ * This is the single owner of template rendering across the workspace.
790
+ * Generation renders templates to create files; validation renders the same
791
+ * templates to compare them against existing files. Both must substitute
792
+ * identically or validation would flag files the generator itself produced,
793
+ * so neither reimplements this.
794
+ */
795
+ declare class RenderingService {
796
+ constructor();
797
+ /** Refuses to render a template asking for a value nobody supplied. */
798
+ private assertEverySubstitutionSupplied;
799
+ /**
800
+ * Every placeholder a template interpolates, deduplicated.
801
+ *
802
+ * Mustache's own parser reads them, so an interpolation is told from a
803
+ * comment, a partial, and a delimiter change the way mustache tells them.
804
+ * Section names are skipped and their bodies still walked — `{{#field}}` and
805
+ * `{{^field}}` are conditionals, so absence is an answer there — as is the
806
+ * implicit iterator `{{.}}`, which names no field.
807
+ */
808
+ private collectInterpolatedNames;
809
+ /**
810
+ * Derives the case variants every template can reference from one name.
811
+ *
812
+ * Callers merge their own inputs over this result, so an explicit input of
813
+ * the same key always wins over the derived variant.
814
+ */
815
+ buildNameSubstitutions(name: string): Substitutions;
816
+ /**
817
+ * Renders template contents with mustache.
818
+ *
819
+ * Full mustache is available — sections, inverted sections, partials — with
820
+ * HTML escaping disabled so substituted values cannot corrupt source code.
821
+ *
822
+ * An interpolated placeholder nobody supplied raises
823
+ * `MissingSubstitutionError`. `subject` is what that error names, so pass
824
+ * the template's path when there is one.
825
+ */
826
+ renderContent(args: {
827
+ subject?: string;
828
+ substitutions: Substitutions;
829
+ templateContent: string;
830
+ }): string;
831
+ /**
832
+ * Renders a template path with mustache, the same way contents are rendered.
833
+ *
834
+ * Paths once used a `__field__` syntax of their own, on the assumption that
835
+ * braces were not portable across filesystems. They are — and the separate
836
+ * syntax could not tell a placeholder from a Python dunder, so a template
837
+ * shipping `__init__.py` depended on `init` never being a substitution.
838
+ */
839
+ renderPath(args: {
840
+ subject?: string;
841
+ substitutions: Substitutions;
842
+ templatePath: string;
843
+ }): string;
844
+ }
845
+
846
+ /** The outcome of resolving a set of instances against a set of templates. */
847
+ export declare interface ResolvedInstances {
848
+ readonly matched: MatchedInstance[];
849
+ readonly unmatched: UnmatchedInstance[];
850
+ }
851
+
852
+ /** Arguments for resolving generator inputs from raw CLI arguments. */
853
+ export declare interface ResolveGeneratorInputsArguments {
854
+ rawArguments: string[];
855
+ schema: JsonSchemaDefinition;
856
+ }
857
+
858
+ /** Arguments for resolving inputs from values the caller already has. */
859
+ declare interface ResolveInputsFromValuesArguments {
860
+ providedInputs: Record<string, string | undefined>;
861
+ schema: JsonSchemaDefinition;
862
+ }
863
+
864
+ /**
865
+ * Arguments for taking an inventory of templates and instances.
866
+ *
867
+ * Both filters narrow the pairing rather than the search, so an entry that
868
+ * survives neither drops out entirely.
869
+ */
870
+ export declare interface ResolveInventoryArguments {
871
+ readonly configuration: ConformetryConfiguration;
872
+ /** Globs narrowing which instances count; the configured ones when absent. */
873
+ readonly instancePatterns?: string[] | undefined;
874
+ /** Template names narrowing which templates count; all of them when absent. */
875
+ readonly templateNames?: string[] | undefined;
876
+ readonly workingDirectory: string;
877
+ }
878
+
879
+ /** One schema-backed input being resolved. */
880
+ declare interface SchemaInput {
881
+ readonly inputName: string;
882
+ readonly isRequired: boolean;
883
+ readonly propertySchema: unknown;
884
+ }
885
+
886
+ /**
887
+ * Placeholder values applied when rendering a template.
888
+ *
889
+ * Built from the generator name by `RenderingService.buildNameSubstitutions`
890
+ * and then merged with the caller's own inputs, so an explicit input always
891
+ * wins over a derived name variant.
892
+ */
893
+ export declare type Substitutions = Record<string, string>;
894
+
895
+ /**
896
+ * One template a picker can offer.
897
+ *
898
+ * Deliberately not `ConformetryGeneratorDefinition`: the picker needs only the
899
+ * name it returns and the description it shows, and asking for the whole
900
+ * definition would tie prompting to the configuration's shape.
901
+ */
902
+ export declare interface TemplateChoice {
903
+ readonly description?: string | undefined;
904
+ readonly name: string;
905
+ }
906
+
907
+ /** One template: a directory of mustache files under the templates root. */
908
+ export declare interface TemplateDefinition {
909
+ /** Absolute path to the template's own directory. */
910
+ readonly directoryPath: string;
911
+ /** Absolute paths of every file in the template, sorted. */
912
+ readonly filePaths: string[];
913
+ /** The template's directory name, used to identify it. */
914
+ readonly name: string;
915
+ /**
916
+ * Lowest conformance score an instance of this template may have, from the
917
+ * generator that owns it. Undefined when the generator sets none.
918
+ */
919
+ readonly threshold?: number;
920
+ }
921
+
922
+ /**
923
+ * Reads template folders and maps their files onto instance files.
924
+ *
925
+ * A template's tree is laid over the instance path verbatim, so a template
926
+ * that should produce a folder contains that folder — `{{nameKebabCase}}/…` —
927
+ * rather than the runtime inventing one. That keeps the shape of the output
928
+ * visible in the template itself, and lets one template produce a folder while
929
+ * another produces loose files, with nothing but their contents to say so.
930
+ *
931
+ * Rendering goes through `RenderingService` so a template is substituted here
932
+ * exactly as it was when the generator wrote the file — otherwise validation
933
+ * would report differences the generator itself introduced.
934
+ */
935
+ declare class TemplateDiscoveryService {
936
+ private readonly renderingService;
937
+ constructor(renderingService: RenderingService);
938
+ /** Lists every file under a directory, recursively and sorted. */
939
+ private collectFilePaths;
940
+ /**
941
+ * Reads one template folder.
942
+ *
943
+ * The folder is the whole definition — nothing but the files it contains is
944
+ * needed to validate against it.
945
+ */
946
+ collectTemplate(args: {
947
+ name: string;
948
+ templatePath: string;
949
+ threshold?: number | undefined;
950
+ }): TemplateDefinition;
951
+ /**
952
+ * Reads every configured generator's template folder.
953
+ *
954
+ * Every host needs this before it can match anything, and each was resolving
955
+ * the configured paths itself. Resolving them once here keeps one answer to
956
+ * where a generator's template lives.
957
+ */
958
+ collectTemplates(args: {
959
+ configuration: ConformetryConfiguration;
960
+ workingDirectory: string;
961
+ }): TemplateDefinition[];
962
+ /**
963
+ * Counts how many of a template's files the instance path already has.
964
+ *
965
+ * When a file scope is given, only files inside it count — that is what lets
966
+ * a file glob select a template describing exactly those files rather than
967
+ * the larger template describing the whole directory.
968
+ */
969
+ countMatchingFiles(args: {
970
+ fileScope?: string[] | undefined;
971
+ instancePath: string;
972
+ substitutions: Substitutions;
973
+ template: TemplateDefinition;
974
+ }): number;
975
+ /**
976
+ * Pairs one template file with its instance, rendering the template.
977
+ *
978
+ * Returns `undefined` when the instance does not exist — that is a missing
979
+ * file, which the file-existence pass reports; a language validator has nothing
980
+ * to compare and should not see the pair at all.
981
+ */
982
+ prepareDocument(args: {
983
+ instancePath: string;
984
+ substitutions: Substitutions;
985
+ templateDirectoryPath: string;
986
+ templateFilePath: string;
987
+ }): PreparedValidationDocument | undefined;
988
+ /** Maps a template file path to the instance file path it governs. */
989
+ resolveInstanceFilePath(args: {
990
+ instancePath: string;
991
+ substitutions: Substitutions;
992
+ templateDirectoryPath: string;
993
+ templateFilePath: string;
994
+ }): string;
995
+ }
996
+
997
+ /**
998
+ * One template weighed against an instance during matching.
999
+ *
1000
+ * `matchRatio` measures which template *fits*, by file presence alone. It is
1001
+ * deliberately not called a score: an instance's conformance score measures
1002
+ * how well the matched template's contents are honoured, which is a different
1003
+ * number arrived at a different way.
1004
+ */
1005
+ export declare interface TemplateMatch {
1006
+ readonly matchedFileCount: number;
1007
+ /** Share of the template's files the instance already has, 0 to 1. */
1008
+ readonly matchRatio: number;
1009
+ readonly template: TemplateDefinition;
1010
+ }
1011
+
1012
+ /** Raised when the configuration path points to an unsupported file type. */
1013
+ export declare class UnknownConfigurationFileTypeError extends Error {
1014
+ constructor(filePath: string);
1015
+ }
1016
+
1017
+ /** An instance no template explains well enough to validate against. */
1018
+ export declare interface UnmatchedInstance {
1019
+ readonly instance: Instance;
1020
+ readonly reason: UnmatchedReason;
1021
+ /** Templates that tied, when the reason is `ambiguous`. */
1022
+ readonly tiedTemplateNames: string[];
1023
+ }
1024
+
1025
+ /**
1026
+ * Why an instance could not be matched.
1027
+ *
1028
+ * Both are reported rather than skipped: a glob is the caller asserting these
1029
+ * directories are instances, so an instance matching nothing is a real finding,
1030
+ * and a tie means two templates are indistinguishable.
1031
+ */
1032
+ export declare type UnmatchedReason = "ambiguous" | "no-match";
1033
+
1034
+ export { }