@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.
- package/LICENSE +21 -0
- package/README.md +922 -0
- package/dist/bootstrap.utilities-EhWhm5J3.js +61 -0
- package/dist/plugin-context.utilities-CuP36-vK.js +973 -0
- package/dist/src/executors/validate/executor.d.ts +31 -0
- package/dist/src/executors/validate/executor.js +22 -0
- package/dist/src/generators/sync/generator.d.ts +26 -0
- package/dist/src/generators/sync/generator.js +21 -0
- package/dist/src/index.d.ts +1094 -0
- package/dist/src/index.js +28 -0
- package/dist/src/main.d.ts +1 -0
- package/dist/src/main.js +5 -0
- package/executors.json +10 -0
- package/generators.json +10 -0
- package/package.json +85 -0
|
@@ -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 { }
|