@conformetry/generation 0.0.0-stage → 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 +433 -2
- package/dist/src/index.d.ts +141 -0
- package/dist/src/index.js +124 -0
- package/package.json +58 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jimmy Paolini
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,434 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 👔 Conformetry Generation
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The generation runtime for [Conformetry](../conformetry-cli/README.md): it
|
|
4
|
+
walks a template tree, renders every path and every file, and writes the
|
|
5
|
+
result.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install --save-dev @conformetry/generation
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { GenerationService } from "@conformetry/generation";
|
|
15
|
+
|
|
16
|
+
const result = await generationService.runGenerator({
|
|
17
|
+
definition: {
|
|
18
|
+
name: "nestjs-service-module",
|
|
19
|
+
templateDirectoryPath: "templates/nestjs-service-module",
|
|
20
|
+
},
|
|
21
|
+
inputs: { name: "billing" },
|
|
22
|
+
instancePath: "packages/billing/src/modules",
|
|
23
|
+
});
|
|
24
|
+
// → { generatedFilePaths: [...], outputDirectoryPath: "..." }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The lifecycle is `preGenerate` → render → `postGenerate` → format, and the
|
|
28
|
+
returned file paths are sorted.
|
|
29
|
+
|
|
30
|
+
## Rendering
|
|
31
|
+
|
|
32
|
+
`RenderingService` is the single owner of template substitution across the
|
|
33
|
+
whole toolchain. Generation renders templates to create files; validation
|
|
34
|
+
renders the _same_ templates to compare against files that already exist. Both
|
|
35
|
+
must substitute identically or validation would flag the files the generator
|
|
36
|
+
itself produced — which is why neither reimplements this.
|
|
37
|
+
|
|
38
|
+
Contents and paths are both rendered with
|
|
39
|
+
[mustache](https://mustache.github.io), HTML escaping disabled so substituted
|
|
40
|
+
values cannot corrupt source code. `buildNameSubstitutions` derives
|
|
41
|
+
`nameCamelCase`, `nameKebabCase`, `namePascalCase`, and `nameSnakeCase` from a
|
|
42
|
+
single name; callers merge their own inputs over the result, so an explicit
|
|
43
|
+
input of the same key always wins.
|
|
44
|
+
|
|
45
|
+
> **An interpolated placeholder nobody supplied raises
|
|
46
|
+
> `MissingSubstitutionError`.** Mustache would otherwise render it as an empty
|
|
47
|
+
> string rather than leaving the token visible, and because generation and
|
|
48
|
+
> validation render identically, both halves of the loop lost the same value and
|
|
49
|
+
> agreed that nothing was wrong. A hole rendered into both sides of a comparison
|
|
50
|
+
> is not something that comparison can report, so the renderer refuses instead.
|
|
51
|
+
>
|
|
52
|
+
> Section tags are exempt, deliberately: `{{#field}}` and `{{^field}}` are
|
|
53
|
+
> conditionals, so an absent name is how a template asks for a block to be
|
|
54
|
+
> skipped or taken. A supplied value that is the empty string is an answer too —
|
|
55
|
+
> only an absent key is a hole. Together those are how a template says
|
|
56
|
+
> "optional": ask with a section, interpolate inside it.
|
|
57
|
+
|
|
58
|
+
Paths once used a `__field__` syntax of their own, on the assumption that
|
|
59
|
+
braces were not portable across filesystems. They are — and the separate syntax
|
|
60
|
+
could not tell a placeholder from a Python dunder, so a template shipping
|
|
61
|
+
`__init__.py` depended on `init` never being a substitution.
|
|
62
|
+
|
|
63
|
+
## Adapters
|
|
64
|
+
|
|
65
|
+
Filesystem and formatter access go through `FileSystemAdapter` and
|
|
66
|
+
`FormatterAdapter`, defaulting to disk and a no-op. That is how
|
|
67
|
+
[`@conformetry/nx`](../conformetry-nx/README.md) reuses this runtime unchanged
|
|
68
|
+
against an Nx generator `Tree`. Rendering deliberately is _not_ an adapter, for
|
|
69
|
+
the reason above.
|
|
70
|
+
|
|
71
|
+
## Exports
|
|
72
|
+
|
|
73
|
+
`GenerationService`, `RenderingService`, their modules, and the
|
|
74
|
+
`FileSystemAdapter`, `FormatterAdapter`, `GeneratorDefinition`, and
|
|
75
|
+
`Substitutions` types.
|
|
76
|
+
|
|
77
|
+
## Test
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
nx run conformetry-generation:vitest
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
MIT — see [LICENSE](../../../../LICENSE).
|
|
86
|
+
|
|
87
|
+
## 👔 Conformetry
|
|
88
|
+
|
|
89
|
+
This project was generated from the [nestjs-service-project](../../../../configuration/conformetry-templates/nestjs-service-project) conformetry template.
|
|
90
|
+
|
|
91
|
+
<!-- callidescope:start -->
|
|
92
|
+
|
|
93
|
+
## 🔭 Callidescope
|
|
94
|
+
|
|
95
|
+
Call stacks traced through `packages/ic-suite/conformetry/conformetry-generation`, deepest first. Each frame shows what it takes, what it returns, and what its documentation says.
|
|
96
|
+
|
|
97
|
+
| Measure | Value |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| Callables | 13 |
|
|
100
|
+
| Files | 11 |
|
|
101
|
+
| Calls traced | 10 |
|
|
102
|
+
| Call stacks | 1 |
|
|
103
|
+
| Deepest stack | 2 |
|
|
104
|
+
| Stacks through recursion | 0 |
|
|
105
|
+
| Unfollowable calls | 2 |
|
|
106
|
+
|
|
107
|
+
### Limits
|
|
108
|
+
|
|
109
|
+
What this project is judged against, as declared in its own `callidescope.config.ts`.
|
|
110
|
+
|
|
111
|
+
| Limit | Value |
|
|
112
|
+
| --- | --- |
|
|
113
|
+
| `maximumDepth` | 8 |
|
|
114
|
+
| `maximumBreadth` | 4 |
|
|
115
|
+
|
|
116
|
+
### Call stacks (depth)
|
|
117
|
+
|
|
118
|
+
**1. `GenerationService.listDirectory`** — depth 2 · orphan-root
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
🚀 GenerationService.listDirectory(directoryPath: string): Promise<DirectoryEntry[]> [packages/ic-suite/conformetry/conformetry-generation/src/modules/generation/generation.service.ts:38]
|
|
122
|
+
└─> GenerationService.map(…)(entry: Dirent<string>): { isDirectory: boolean; name: string; } [packages/ic-suite/conformetry/conformetry-generation/src/modules/generation/generation.service.ts:41]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Breadth
|
|
126
|
+
|
|
127
|
+
| Callable | Breadth | Calls directly | Location |
|
|
128
|
+
| --- | --- | --- | --- |
|
|
129
|
+
| `GenerationService.runGenerator` | 4 | `GenerationService.resolveAdapters`, `GenerationService.normalizeInputs`, `GenerationService.buildSubstitutions`, `GenerationService.renderDirectory` | `packages/ic-suite/conformetry/conformetry-generation/src/modules/generation/generation.service.ts:190` |
|
|
130
|
+
| `GenerationService.renderDirectory` | 2 | `ConfigurationService.renderPath`, `GenerationService.renderFile` | `packages/ic-suite/conformetry/conformetry-generation/src/modules/generation/generation.service.ts:104` |
|
|
131
|
+
| `GenerationService.listDirectory` | 1 | `GenerationService.map(…)` | `packages/ic-suite/conformetry/conformetry-generation/src/modules/generation/generation.service.ts:38` |
|
|
132
|
+
|
|
133
|
+
<details>
|
|
134
|
+
<summary>2 more callables</summary>
|
|
135
|
+
|
|
136
|
+
| Callable | Breadth | Calls directly | Location |
|
|
137
|
+
| --- | --- | --- | --- |
|
|
138
|
+
| `GenerationService.buildSubstitutions` | 1 | `ConfigurationService.buildNameSubstitutions` | `packages/ic-suite/conformetry/conformetry-generation/src/modules/generation/generation.service.ts:74` |
|
|
139
|
+
| `GenerationService.renderFile` | 1 | `ConfigurationService.renderContent` | `packages/ic-suite/conformetry/conformetry-generation/src/modules/generation/generation.service.ts:153` |
|
|
140
|
+
|
|
141
|
+
</details>
|
|
142
|
+
<!-- callidescope:end -->
|
|
143
|
+
|
|
144
|
+
## 🕸️ Codependix
|
|
145
|
+
|
|
146
|
+
Dependency graphs exported by [codependix](https://github.com/Organizzolini/codebase/tree/main/packages/ic-suite/codependix/codependix-cli), regenerated by `nx run codebase:codependix:write`.
|
|
147
|
+
|
|
148
|
+
### Nx Neighborhood
|
|
149
|
+
|
|
150
|
+
<!-- codependix:start name="codependix-nx-projects" -->
|
|
151
|
+
```mermaid
|
|
152
|
+
graph LR
|
|
153
|
+
conformetry_cli["conformetry-cli"]
|
|
154
|
+
conformetry_configuration["conformetry-configuration"]
|
|
155
|
+
conformetry_examples["conformetry-examples"]
|
|
156
|
+
conformetry_generation["conformetry-generation"]
|
|
157
|
+
conformetry_nx["conformetry-nx"]
|
|
158
|
+
conformetry_cli --> conformetry_generation
|
|
159
|
+
conformetry_examples --> conformetry_generation
|
|
160
|
+
conformetry_generation --> conformetry_configuration
|
|
161
|
+
conformetry_nx --> conformetry_generation
|
|
162
|
+
classDef subject fill:#7c3aed,color:#fff,stroke:#4c1d95,stroke-width:2px
|
|
163
|
+
class conformetry_generation subject
|
|
164
|
+
```
|
|
165
|
+
<!-- codependix:end name="codependix-nx-projects" -->
|
|
166
|
+
|
|
167
|
+
### NestJS Module Graph
|
|
168
|
+
|
|
169
|
+
<!-- codependix:start name="codependix-nestjs-modules" -->
|
|
170
|
+
```mermaid
|
|
171
|
+
flowchart LR
|
|
172
|
+
ConfigurationModule
|
|
173
|
+
GenerationModule
|
|
174
|
+
InputModule
|
|
175
|
+
InstanceDiscoveryModule
|
|
176
|
+
InstanceGroupModule
|
|
177
|
+
RenderingModule
|
|
178
|
+
TemplateDiscoveryModule
|
|
179
|
+
ConfigurationModule --> InputModule
|
|
180
|
+
ConfigurationModule --> InstanceDiscoveryModule
|
|
181
|
+
ConfigurationModule --> InstanceGroupModule
|
|
182
|
+
ConfigurationModule --> RenderingModule
|
|
183
|
+
ConfigurationModule --> TemplateDiscoveryModule
|
|
184
|
+
GenerationModule --> ConfigurationModule
|
|
185
|
+
InstanceDiscoveryModule --> InstanceGroupModule
|
|
186
|
+
InstanceDiscoveryModule --> RenderingModule
|
|
187
|
+
InstanceDiscoveryModule --> TemplateDiscoveryModule
|
|
188
|
+
TemplateDiscoveryModule --> RenderingModule
|
|
189
|
+
```
|
|
190
|
+
<!-- codependix:end name="codependix-nestjs-modules" -->
|
|
191
|
+
|
|
192
|
+
### File Imports
|
|
193
|
+
|
|
194
|
+
<!-- codependix:start name="codependix-file-imports" -->
|
|
195
|
+
```mermaid
|
|
196
|
+
graph LR
|
|
197
|
+
file_callidescope_config_ts["callidescope.config.ts"]
|
|
198
|
+
file_codependix_config_ts["codependix.config.ts"]
|
|
199
|
+
file_codometer_config_ts["codometer.config.ts"]
|
|
200
|
+
file_eslint_config_ts["eslint.config.ts"]
|
|
201
|
+
file_src_index_ts["src/index.ts"]
|
|
202
|
+
file_src_modules_generation_generation_constants_ts["src/modules/generation/generation.constants.ts"]
|
|
203
|
+
file_src_modules_generation_generation_module_ts["src/modules/generation/generation.module.ts"]
|
|
204
|
+
file_src_modules_generation_generation_module_unit_test_ts["src/modules/generation/generation.module.unit.test.ts"]
|
|
205
|
+
file_src_modules_generation_generation_service_ts["src/modules/generation/generation.service.ts"]
|
|
206
|
+
file_src_modules_generation_generation_service_unit_test_ts["src/modules/generation/generation.service.unit.test.ts"]
|
|
207
|
+
file_src_modules_generation_generation_types_ts["src/modules/generation/generation.types.ts"]
|
|
208
|
+
file_testing_mocks_ts["testing/mocks.ts"]
|
|
209
|
+
file_testing_setup_ts["testing/setup.ts"]
|
|
210
|
+
file_vite_config_ts["vite.config.ts"]
|
|
211
|
+
file_vitest_config_ts["vitest.config.ts"]
|
|
212
|
+
file_src_modules_generation_generation_module_ts --> file_src_modules_generation_generation_service_ts
|
|
213
|
+
file_src_modules_generation_generation_module_unit_test_ts --> file_src_modules_generation_generation_module_ts
|
|
214
|
+
file_src_modules_generation_generation_service_ts --> file_src_modules_generation_generation_types_ts
|
|
215
|
+
file_src_modules_generation_generation_service_unit_test_ts --> file_src_modules_generation_generation_service_ts
|
|
216
|
+
file_src_modules_generation_generation_service_unit_test_ts --> file_src_modules_generation_generation_types_ts
|
|
217
|
+
```
|
|
218
|
+
<!-- codependix:end name="codependix-file-imports" -->
|
|
219
|
+
|
|
220
|
+
<!-- codometer:start -->
|
|
221
|
+
|
|
222
|
+
## ⏲️ Codometer
|
|
223
|
+
|
|
224
|
+
### Project
|
|
225
|
+
|
|
226
|
+

|
|
227
|
+

|
|
228
|
+

|
|
229
|
+

|
|
230
|
+
|
|
231
|
+
### Measured Targets
|
|
232
|
+
|
|
233
|
+

|
|
234
|
+
|
|
235
|
+
### TypeScript
|
|
236
|
+
|
|
237
|
+

|
|
238
|
+

|
|
239
|
+

|
|
240
|
+

|
|
241
|
+

|
|
242
|
+

|
|
243
|
+

|
|
244
|
+
|
|
245
|
+
### JavaScript
|
|
246
|
+
|
|
247
|
+

|
|
248
|
+

|
|
249
|
+

|
|
250
|
+

|
|
251
|
+

|
|
252
|
+

|
|
253
|
+

|
|
254
|
+

|
|
255
|
+

|
|
256
|
+

|
|
257
|
+

|
|
258
|
+

|
|
259
|
+

|
|
260
|
+

|
|
261
|
+
|
|
262
|
+
### Python
|
|
263
|
+
|
|
264
|
+

|
|
265
|
+

|
|
266
|
+

|
|
267
|
+

|
|
268
|
+

|
|
269
|
+

|
|
270
|
+

|
|
271
|
+

|
|
272
|
+

|
|
273
|
+

|
|
274
|
+

|
|
275
|
+

|
|
276
|
+
|
|
277
|
+
### JSON
|
|
278
|
+
|
|
279
|
+

|
|
280
|
+

|
|
281
|
+

|
|
282
|
+

|
|
283
|
+

|
|
284
|
+

|
|
285
|
+

|
|
286
|
+

|
|
287
|
+

|
|
288
|
+

|
|
289
|
+

|
|
290
|
+

|
|
291
|
+
|
|
292
|
+
### YAML
|
|
293
|
+
|
|
294
|
+

|
|
295
|
+

|
|
296
|
+

|
|
297
|
+

|
|
298
|
+

|
|
299
|
+

|
|
300
|
+

|
|
301
|
+

|
|
302
|
+

|
|
303
|
+

|
|
304
|
+

|
|
305
|
+
|
|
306
|
+
### TOML
|
|
307
|
+
|
|
308
|
+

|
|
309
|
+

|
|
310
|
+

|
|
311
|
+

|
|
312
|
+

|
|
313
|
+

|
|
314
|
+

|
|
315
|
+
|
|
316
|
+
### Shell
|
|
317
|
+
|
|
318
|
+

|
|
319
|
+

|
|
320
|
+

|
|
321
|
+

|
|
322
|
+

|
|
323
|
+

|
|
324
|
+

|
|
325
|
+

|
|
326
|
+

|
|
327
|
+

|
|
328
|
+

|
|
329
|
+
|
|
330
|
+
### SQL
|
|
331
|
+
|
|
332
|
+

|
|
333
|
+

|
|
334
|
+

|
|
335
|
+

|
|
336
|
+

|
|
337
|
+

|
|
338
|
+

|
|
339
|
+

|
|
340
|
+

|
|
341
|
+

|
|
342
|
+

|
|
343
|
+
|
|
344
|
+
### HCL
|
|
345
|
+
|
|
346
|
+

|
|
347
|
+

|
|
348
|
+

|
|
349
|
+

|
|
350
|
+

|
|
351
|
+

|
|
352
|
+

|
|
353
|
+

|
|
354
|
+

|
|
355
|
+
|
|
356
|
+
### CSS
|
|
357
|
+
|
|
358
|
+

|
|
359
|
+

|
|
360
|
+

|
|
361
|
+

|
|
362
|
+

|
|
363
|
+

|
|
364
|
+

|
|
365
|
+

|
|
366
|
+

|
|
367
|
+
|
|
368
|
+
### Conventions
|
|
369
|
+
|
|
370
|
+

|
|
371
|
+

|
|
372
|
+

|
|
373
|
+

|
|
374
|
+

|
|
375
|
+

|
|
376
|
+

|
|
377
|
+

|
|
378
|
+

|
|
379
|
+

|
|
380
|
+

|
|
381
|
+

|
|
382
|
+

|
|
383
|
+

|
|
384
|
+

|
|
385
|
+

|
|
386
|
+

|
|
387
|
+

|
|
388
|
+
|
|
389
|
+
### Jupyter
|
|
390
|
+
|
|
391
|
+

|
|
392
|
+

|
|
393
|
+

|
|
394
|
+

|
|
395
|
+

|
|
396
|
+

|
|
397
|
+

|
|
398
|
+

|
|
399
|
+

|
|
400
|
+

|
|
401
|
+

|
|
402
|
+

|
|
403
|
+

|
|
404
|
+

|
|
405
|
+

|
|
406
|
+

|
|
407
|
+

|
|
408
|
+

|
|
409
|
+

|
|
410
|
+

|
|
411
|
+
|
|
412
|
+
### Markdown
|
|
413
|
+
|
|
414
|
+

|
|
415
|
+

|
|
416
|
+

|
|
417
|
+

|
|
418
|
+

|
|
419
|
+

|
|
420
|
+

|
|
421
|
+

|
|
422
|
+

|
|
423
|
+

|
|
424
|
+

|
|
425
|
+

|
|
426
|
+

|
|
427
|
+

|
|
428
|
+

|
|
429
|
+

|
|
430
|
+

|
|
431
|
+

|
|
432
|
+

|
|
433
|
+

|
|
434
|
+
<!-- codometer:end -->
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { ConfigurationService } from '@conformetry/configuration';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A directory entry read from the source template tree.
|
|
5
|
+
*/
|
|
6
|
+
export declare interface DirectoryEntry {
|
|
7
|
+
isDirectory: boolean;
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Filesystem operations used by the runtime.
|
|
13
|
+
*
|
|
14
|
+
* Exists so a host with a virtual filesystem — an Nx generator `Tree`, for
|
|
15
|
+
* instance — can run generation without touching disk. The default
|
|
16
|
+
* implementation reads and writes directly.
|
|
17
|
+
*/
|
|
18
|
+
export declare interface FileSystemAdapter {
|
|
19
|
+
listDirectory(directoryPath: string): Promise<DirectoryEntry[]>;
|
|
20
|
+
makeDirectory(directoryPath: string): Promise<void>;
|
|
21
|
+
readFile(filePath: string): Promise<string>;
|
|
22
|
+
writeFile(filePath: string, content: string): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Formatting applied once generation completes.
|
|
27
|
+
*
|
|
28
|
+
* The default is a no-op: formatting is the host's concern, and a plain CLI
|
|
29
|
+
* run leaves files to the workspace formatter.
|
|
30
|
+
*/
|
|
31
|
+
export declare interface FormatterAdapter {
|
|
32
|
+
formatFiles(filePaths: string[]): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Provides the generator runtime that renders a template tree onto disk.
|
|
37
|
+
*/
|
|
38
|
+
export declare class GenerationModule {
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Runs conformetry generators: walks a template tree, renders every path and
|
|
43
|
+
* file through the configuration layer, and writes the result.
|
|
44
|
+
*
|
|
45
|
+
* Filesystem and formatter access go through adapters so a host with a virtual
|
|
46
|
+
* filesystem (an Nx generator `Tree`) can reuse this runtime unchanged.
|
|
47
|
+
* Rendering deliberately is *not* an adapter — validation must substitute
|
|
48
|
+
* exactly as generation does, so both render through one service.
|
|
49
|
+
*/
|
|
50
|
+
export declare class GenerationService {
|
|
51
|
+
private readonly configurationService;
|
|
52
|
+
constructor(configurationService: ConfigurationService);
|
|
53
|
+
/** Reads and writes directly to disk. Used when no adapter is supplied. */
|
|
54
|
+
private readonly defaultFileSystem;
|
|
55
|
+
/** Leaves formatting to the workspace formatter unless a host overrides it. */
|
|
56
|
+
private readonly defaultFormatter;
|
|
57
|
+
/**
|
|
58
|
+
* Merges the derived name variants with the caller's inputs.
|
|
59
|
+
*
|
|
60
|
+
* Inputs are spread last so an explicit value always wins over the variant
|
|
61
|
+
* derived from the same name.
|
|
62
|
+
*/
|
|
63
|
+
private buildSubstitutions;
|
|
64
|
+
/** Drops unset inputs so they never shadow a derived name substitution. */
|
|
65
|
+
private normalizeInputs;
|
|
66
|
+
/**
|
|
67
|
+
* Recursively renders a template tree, returning every generated file path.
|
|
68
|
+
*/
|
|
69
|
+
private renderDirectory;
|
|
70
|
+
/** Renders one template file and writes it, returning the written path. */
|
|
71
|
+
private renderFile;
|
|
72
|
+
/** Falls back to the disk filesystem and no-op formatter when unset. */
|
|
73
|
+
private resolveAdapters;
|
|
74
|
+
/**
|
|
75
|
+
* Runs the generator lifecycle — `preGenerate`, render, `postGenerate`,
|
|
76
|
+
* format — and returns the sorted list of generated file paths.
|
|
77
|
+
*/
|
|
78
|
+
runGenerator(args: RunGeneratorArguments): Promise<RunGeneratorResult>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Generator metadata and runtime configuration.
|
|
83
|
+
*/
|
|
84
|
+
export declare interface GeneratorDefinition {
|
|
85
|
+
description?: string;
|
|
86
|
+
hooks?: GeneratorHooks;
|
|
87
|
+
name: string;
|
|
88
|
+
templateDirectoryPath: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A generator hook function.
|
|
93
|
+
*/
|
|
94
|
+
declare type GeneratorHook = (context: GeneratorHookContext) => Promise<void> | void;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Data exposed to generator hooks.
|
|
98
|
+
*/
|
|
99
|
+
declare interface GeneratorHookContext {
|
|
100
|
+
definition: GeneratorDefinition;
|
|
101
|
+
generatedFilePaths: string[];
|
|
102
|
+
input: Record<string, string>;
|
|
103
|
+
outputDirectoryPath: string;
|
|
104
|
+
substitutions: Record<string, string>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Generator lifecycle hooks.
|
|
109
|
+
*/
|
|
110
|
+
declare interface GeneratorHooks {
|
|
111
|
+
postGenerate?: GeneratorHook;
|
|
112
|
+
preGenerate?: GeneratorHook;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Arguments accepted by the runtime runner.
|
|
117
|
+
*/
|
|
118
|
+
declare interface RunGeneratorArguments {
|
|
119
|
+
definition: GeneratorDefinition;
|
|
120
|
+
filesystem?: FileSystemAdapter;
|
|
121
|
+
formatter?: FormatterAdapter;
|
|
122
|
+
inputs?: Record<string, string | undefined>;
|
|
123
|
+
/**
|
|
124
|
+
* The directory the template's tree is rendered into.
|
|
125
|
+
*
|
|
126
|
+
* A template that should produce a folder contains that folder, so this is
|
|
127
|
+
* the folder's parent. Nothing here derives a path — the caller decides
|
|
128
|
+
* where an instance goes, because that is workspace knowledge.
|
|
129
|
+
*/
|
|
130
|
+
instancePath: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Result returned by the runtime runner.
|
|
135
|
+
*/
|
|
136
|
+
declare interface RunGeneratorResult {
|
|
137
|
+
generatedFilePaths: string[];
|
|
138
|
+
outputDirectoryPath: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { }
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { ConfigurationModule as e, ConfigurationService as t } from "@conformetry/configuration";
|
|
2
|
+
import { Injectable as n, Module as r } from "@nestjs/common";
|
|
3
|
+
import { mkdir as i, readFile as a, readdir as o, writeFile as s } from "node:fs/promises";
|
|
4
|
+
import c from "node:path";
|
|
5
|
+
//#region \0@oxc-project+runtime@0.151.0/helpers/esm/decorateMetadata.js
|
|
6
|
+
function l(e, t) {
|
|
7
|
+
if (typeof Reflect == "object" && typeof Reflect.metadata == "function") return Reflect.metadata(e, t);
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region \0@oxc-project+runtime@0.151.0/helpers/esm/decorate.js
|
|
11
|
+
function u(e, t, n, r) {
|
|
12
|
+
var i = arguments.length, a = i < 3 ? t : r === null ? r = Object.getOwnPropertyDescriptor(t, n) : r, o;
|
|
13
|
+
if (typeof Reflect == "object" && typeof Reflect.decorate == "function") a = Reflect.decorate(e, t, n, r);
|
|
14
|
+
else for (var s = e.length - 1; s >= 0; s--) (o = e[s]) && (a = (i < 3 ? o(a) : i > 3 ? o(t, n, a) : o(t, n)) || a);
|
|
15
|
+
return i > 3 && a && Object.defineProperty(t, n, a), a;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region packages/ic-suite/conformetry/conformetry-generation/src/modules/generation/generation.service.ts
|
|
19
|
+
var d = class {
|
|
20
|
+
configurationService;
|
|
21
|
+
constructor(e) {
|
|
22
|
+
this.configurationService = e;
|
|
23
|
+
}
|
|
24
|
+
defaultFileSystem = {
|
|
25
|
+
listDirectory: async (e) => (await o(e, { withFileTypes: !0 })).map((e) => ({
|
|
26
|
+
isDirectory: e.isDirectory(),
|
|
27
|
+
name: e.name
|
|
28
|
+
})),
|
|
29
|
+
makeDirectory: async (e) => {
|
|
30
|
+
await i(e, { recursive: !0 });
|
|
31
|
+
},
|
|
32
|
+
readFile: async (e) => a(e, "utf8"),
|
|
33
|
+
writeFile: async (e, t) => {
|
|
34
|
+
await i(c.dirname(e), { recursive: !0 }), await s(e, t, "utf8");
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
defaultFormatter = { formatFiles: async (e) => {
|
|
38
|
+
await Promise.resolve();
|
|
39
|
+
} };
|
|
40
|
+
buildSubstitutions(e) {
|
|
41
|
+
return {
|
|
42
|
+
...this.configurationService.buildNameSubstitutions(e.inputs.name ?? e.definitionName),
|
|
43
|
+
...e.inputs
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
normalizeInputs(e) {
|
|
47
|
+
let t = {};
|
|
48
|
+
for (let [n, r] of Object.entries(e)) r !== void 0 && (t[n] = r);
|
|
49
|
+
return t;
|
|
50
|
+
}
|
|
51
|
+
async renderDirectory(e) {
|
|
52
|
+
let t = await e.filesystem.listDirectory(e.templateDirectoryPath), n = [];
|
|
53
|
+
for (let r of t) {
|
|
54
|
+
let t = c.join(e.templateDirectoryPath, r.name), i = c.join(e.instanceDirectoryPath, this.configurationService.renderPath({
|
|
55
|
+
subject: t,
|
|
56
|
+
substitutions: e.substitutions,
|
|
57
|
+
templatePath: r.name
|
|
58
|
+
}));
|
|
59
|
+
if (r.isDirectory) {
|
|
60
|
+
await e.filesystem.makeDirectory(i), n.push(...await this.renderDirectory({
|
|
61
|
+
filesystem: e.filesystem,
|
|
62
|
+
instanceDirectoryPath: i,
|
|
63
|
+
substitutions: e.substitutions,
|
|
64
|
+
templateDirectoryPath: t
|
|
65
|
+
}));
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
n.push(await this.renderFile({
|
|
69
|
+
filesystem: e.filesystem,
|
|
70
|
+
instancePath: i,
|
|
71
|
+
substitutions: e.substitutions,
|
|
72
|
+
templatePath: t
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
return n;
|
|
76
|
+
}
|
|
77
|
+
async renderFile(e) {
|
|
78
|
+
let t = await e.filesystem.readFile(e.templatePath);
|
|
79
|
+
return await e.filesystem.writeFile(e.instancePath, this.configurationService.renderContent({
|
|
80
|
+
subject: e.templatePath,
|
|
81
|
+
substitutions: e.substitutions,
|
|
82
|
+
templateContent: t
|
|
83
|
+
})), e.instancePath;
|
|
84
|
+
}
|
|
85
|
+
resolveAdapters(e) {
|
|
86
|
+
return {
|
|
87
|
+
filesystem: e.filesystem ?? this.defaultFileSystem,
|
|
88
|
+
formatter: e.formatter ?? this.defaultFormatter
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async runGenerator(e) {
|
|
92
|
+
let { filesystem: t, formatter: n } = this.resolveAdapters(e), r = this.normalizeInputs(e.inputs ?? {}), i = this.buildSubstitutions({
|
|
93
|
+
definitionName: e.definition.name,
|
|
94
|
+
inputs: r
|
|
95
|
+
}), a = {
|
|
96
|
+
definition: e.definition,
|
|
97
|
+
generatedFilePaths: [],
|
|
98
|
+
input: r,
|
|
99
|
+
outputDirectoryPath: e.instancePath,
|
|
100
|
+
substitutions: i
|
|
101
|
+
};
|
|
102
|
+
return await e.definition.hooks?.preGenerate?.(a), a.generatedFilePaths = (await this.renderDirectory({
|
|
103
|
+
filesystem: t,
|
|
104
|
+
instanceDirectoryPath: e.instancePath,
|
|
105
|
+
substitutions: i,
|
|
106
|
+
templateDirectoryPath: e.definition.templateDirectoryPath
|
|
107
|
+
})).toSorted(), await e.definition.hooks?.postGenerate?.(a), await n.formatFiles(a.generatedFilePaths), {
|
|
108
|
+
generatedFilePaths: a.generatedFilePaths,
|
|
109
|
+
outputDirectoryPath: e.instancePath
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
d = u([n(), l("design:paramtypes", [t === void 0 ? Object : t])], d);
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region packages/ic-suite/conformetry/conformetry-generation/src/modules/generation/generation.module.ts
|
|
116
|
+
var f = class {};
|
|
117
|
+
f = u([r({
|
|
118
|
+
controllers: [],
|
|
119
|
+
exports: [d],
|
|
120
|
+
imports: [e],
|
|
121
|
+
providers: [d]
|
|
122
|
+
})], f);
|
|
123
|
+
//#endregion
|
|
124
|
+
export { f as GenerationModule, d as GenerationService };
|
package/package.json
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
{
|
|
2
|
+
"author": "Jimmy Paolini",
|
|
3
|
+
"bugs": {
|
|
4
|
+
"url": "https://github.com/Organizzolini/codebase/issues"
|
|
5
|
+
},
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@conformetry/configuration": "0.0.1",
|
|
8
|
+
"@nestjs/common": "^11.1.21 || ^12.0.1",
|
|
9
|
+
"reflect-metadata": "^0.2.2"
|
|
10
|
+
},
|
|
11
|
+
"description": "Template rendering and file generation runtime for Conformetry",
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@golevelup/ts-vitest": "^4.0.0",
|
|
14
|
+
"@nestjs/testing": "^12.0.1",
|
|
15
|
+
"@nx/eslint-plugin": "^23.0.0",
|
|
16
|
+
"@swc/helpers": "^0.5.21",
|
|
17
|
+
"@types/node": "^26.5.1",
|
|
18
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
19
|
+
"typescript": "^6.0.3",
|
|
20
|
+
"vitest": "^5.0.0"
|
|
21
|
+
},
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/src/index.d.ts",
|
|
25
|
+
"default": "./dist/src/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./package.json": "./package.json"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"homepage": "https://github.com/Organizzolini/codebase/tree/main/packages/ic-suite/conformetry/conformetry-generation#readme",
|
|
35
|
+
"keywords": [
|
|
36
|
+
"conformetry",
|
|
37
|
+
"generation",
|
|
38
|
+
"rendering",
|
|
39
|
+
"scaffolding",
|
|
40
|
+
"templates"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"main": "./dist/src/index.js",
|
|
2
44
|
"name": "@conformetry/generation",
|
|
3
|
-
"
|
|
4
|
-
|
|
5
|
-
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public",
|
|
47
|
+
"registry": "https://registry.npmjs.org"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"directory": "packages/ic-suite/conformetry/conformetry-generation",
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/Organizzolini/codebase.git"
|
|
53
|
+
},
|
|
54
|
+
"type": "module",
|
|
55
|
+
"typeCoverage": {
|
|
56
|
+
"atLeast": 100,
|
|
57
|
+
"strict": true
|
|
58
|
+
},
|
|
59
|
+
"types": "./dist/src/index.d.ts",
|
|
60
|
+
"version": "0.0.1"
|
|
6
61
|
}
|