@neohm/nh-cli 1.4.1 → 2.0.0
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/README.md +21 -21
- package/package.json +1 -1
- package/src/commands/check.js +2 -2
- package/src/commands/gen.flag.js +16 -16
- package/src/commands/gen.js +14 -14
- package/src/commands/sync.js +1 -1
- package/src/index.js +7 -7
- package/src/lib/flavour.js +1 -1
- package/src/lib/sync.engine.js +20 -20
- package/src/lib/templates.js +47 -47
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ sibling [`@neohm/nestendx`](../nestendx).
|
|
|
6
6
|
|
|
7
7
|
`nh` generates fully wired modules, entities, controllers, services, DTOs, data
|
|
8
8
|
processors, migrations and seeds — and keeps your import barrels
|
|
9
|
-
(`
|
|
9
|
+
(`module.registry.ts`) and `app.module.ts` registration in sync, so the project
|
|
10
10
|
builds without any manual import wiring.
|
|
11
11
|
|
|
12
12
|
## Two flavours, one CLI
|
|
@@ -64,7 +64,7 @@ The project root is auto-detected by walking up from the current directory for a
|
|
|
64
64
|
| `nh gen <flag>` | Generate one artifact instead of a whole module — see [Flag generation](#flag-generation). |
|
|
65
65
|
| `nh migration` | Generate a standalone migration `<timestamp>M-<Name>.ts`. |
|
|
66
66
|
| `nh seed` | Generate a seed file `<timestamp>S-<Name>.ts`. |
|
|
67
|
-
| `nh sync` | Rebuild every `
|
|
67
|
+
| `nh sync` | Rebuild every `module.registry.ts` barrel and register modules in `app.module.ts`. |
|
|
68
68
|
| `nh check` | Dry-run of `sync` — show what would change without writing. |
|
|
69
69
|
| `nh list` | List scaffolded modules with their entities and providers. |
|
|
70
70
|
| `nh remove <name>` | Safely remove a scaffolded module and de-register it (migrations are preserved). |
|
|
@@ -83,7 +83,7 @@ $ nh gen
|
|
|
83
83
|
? Create controller (ShapeController)? Yes
|
|
84
84
|
? Create DTO (AddShapeDataDto)? Yes
|
|
85
85
|
? Create data processor (ShapeDataProcessor)? Yes
|
|
86
|
-
? Create
|
|
86
|
+
? Create filter processor (ShapeFilterProcessor)? Yes
|
|
87
87
|
? Create migration (M-AddShapeTable)? Yes
|
|
88
88
|
```
|
|
89
89
|
|
|
@@ -92,13 +92,13 @@ Produces, under `src/shape/`:
|
|
|
92
92
|
```text
|
|
93
93
|
shape/
|
|
94
94
|
shape.module.ts # ShapeModule (imports CommonModule, QueueModule, ...)
|
|
95
|
-
|
|
95
|
+
module.registry.ts # barrel of controllers/services/jobs/subscribers
|
|
96
96
|
controllers/shape.controller.ts
|
|
97
97
|
services/shape.service.ts
|
|
98
98
|
dtos/add.shape.data.dto.ts
|
|
99
99
|
entities/shape.entity.ts
|
|
100
100
|
libraries/shape.data.processor.ts
|
|
101
|
-
libraries/shape.
|
|
101
|
+
libraries/shape.filter.processor.ts
|
|
102
102
|
enums/ jobs/ subscribers/
|
|
103
103
|
external/ # optional API-key surface (nh gen -x ...)
|
|
104
104
|
controllers/ libraries/ services/
|
|
@@ -125,11 +125,11 @@ Generated artifacts extend the `@neohm/nestend` base classes:
|
|
|
125
125
|
`TenantEntity` relation, and `TenantScopeSubscriber` stamps and guards it),
|
|
126
126
|
`CommonEntity` under `nhx`; both with an empty `attributes` jsonb column
|
|
127
127
|
- DTO → `CommonDataDto`
|
|
128
|
-
- Data processor → `
|
|
128
|
+
- Data processor → `BaseDataProcessor` (`process()` → `validate()` → `set()`),
|
|
129
129
|
reached through the service's `upsert()`. It takes the caller and, on edit,
|
|
130
130
|
loads the row scoped to them — another owner's `id` is a field error, never an
|
|
131
131
|
update.
|
|
132
|
-
-
|
|
132
|
+
- Filter processor → `BaseFilterProcessor` (column allowlist config, `id` default sort). It takes `CommonListFilterDto` directly — no per-list DTO is generated, since paging, sorting, filters and free-text search all live on the base DTO and the filterable columns are declared in the processor's `columns` config.
|
|
133
133
|
|
|
134
134
|
### Controller → service → processor
|
|
135
135
|
|
|
@@ -150,7 +150,7 @@ answers you already gave:
|
|
|
150
150
|
| `get(user, id)` | always | `GET /api/v1/shape/:id` |
|
|
151
151
|
| `remove(user, id)` | always | `DELETE /api/v1/shape/:id` |
|
|
152
152
|
| `upsert(user, data)` | data processor: Yes | `POST /api/v1/shape` |
|
|
153
|
-
| `list(user, data)` |
|
|
153
|
+
| `list(user, data)` | filter processor: Yes | `POST /api/v1/shape/list` |
|
|
154
154
|
|
|
155
155
|
`get()` and `remove()` keep their logic in the service — no processor: `remove()`
|
|
156
156
|
soft-deletes through the same owner-scoped lookup as `get()`. A module that
|
|
@@ -210,14 +210,14 @@ identifiers only: create the `sys_scopes` row and the three `sys_roles` rows
|
|
|
210
210
|
Bare `nh gen` scaffolds a whole module. Once that module exists, most work is
|
|
211
211
|
adding *one* more file to it — which is what the target flags do. Each asks
|
|
212
212
|
only what it cannot infer, refuses to shadow a name already in the project,
|
|
213
|
-
registers the result in `
|
|
213
|
+
registers the result in `module.registry.ts`, and formats what it wrote.
|
|
214
214
|
|
|
215
215
|
| Command | Generates | Asks |
|
|
216
216
|
| --- | --- | --- |
|
|
217
217
|
| `nh gen -c` | `controllers/<name>.controller.ts` | module, name, route path |
|
|
218
218
|
| `nh gen -s` | `services/<name>.service.ts` | module, name |
|
|
219
219
|
| `nh gen -dp` | `libraries/<name>.data.processor.ts` + `dtos/add.<name>.data.dto.ts` | module, name |
|
|
220
|
-
| `nh gen -
|
|
220
|
+
| `nh gen -fp` | `libraries/<name>.filter.processor.ts` | module, name, table name |
|
|
221
221
|
| `nh gen -e` | `entities/<name>.entity.ts` + `dtos/<name>.attributes.dto.ts` + `jobs/<name>.job.ts` + `subscribers/<name>.subscriber.ts` + a migration | module, name, table name |
|
|
222
222
|
| `nh gen -j` | `jobs/<name>.job.ts` | module, name |
|
|
223
223
|
|
|
@@ -230,10 +230,10 @@ entity, `job.constants.ts` / `job.maps.ts` for the job it brought along.
|
|
|
230
230
|
|
|
231
231
|
The two jobs are deliberately different shapes:
|
|
232
232
|
|
|
233
|
-
| | Driven by | `handle()` takes |
|
|
234
|
-
| --- | --- | --- |
|
|
235
|
-
| the job `-e` brings along | the entity's subscriber | `event: DatabaseEventDto<<Name>Entity>` |
|
|
236
|
-
| `nh gen -j` | an explicit `dispatch()` | `data: unknown` |
|
|
233
|
+
| | Extends | Driven by | `handle()` takes |
|
|
234
|
+
| --- | --- | --- | --- |
|
|
235
|
+
| the job `-e` brings along | `BaseEntityJob` | the entity's subscriber | `event: DatabaseEventDto<<Name>Entity>` |
|
|
236
|
+
| `nh gen -j` | `BaseJob` | an explicit `dispatch()` | `data: unknown` |
|
|
237
237
|
|
|
238
238
|
So reach for `-j` when you want a queue worker that is not tied to a table's
|
|
239
239
|
lifecycle.
|
|
@@ -250,7 +250,7 @@ Only one target flag per run; the module is created if it does not exist yet.
|
|
|
250
250
|
| `nh gen -x -c` | `external/controllers/external.<name>.controller.ts` |
|
|
251
251
|
| `nh gen -x -s` | `external/services/external.<name>.service.ts` |
|
|
252
252
|
| `nh gen -x -dp` | `external/libraries/external.<name>.data.processor.ts` |
|
|
253
|
-
| `nh gen -x -
|
|
253
|
+
| `nh gen -x -fp` | `external/libraries/external.<name>.filter.processor.ts` |
|
|
254
254
|
|
|
255
255
|
External files take an `external.` filename prefix and an `External` class
|
|
256
256
|
prefix. An external controller differs from an internal one in three ways, all
|
|
@@ -262,7 +262,7 @@ than `AuthService`.
|
|
|
262
262
|
inward/outward face, so there is nothing to mirror. Both refuse the flag rather
|
|
263
263
|
than quietly ignoring it.
|
|
264
264
|
|
|
265
|
-
External providers register into the *same* `
|
|
265
|
+
External providers register into the *same* `module.registry.ts` categories as
|
|
266
266
|
their internal siblings, listed after them — so `nh sync` and `nh check` see
|
|
267
267
|
`<module>/external/<folder>` as well as `<module>/<folder>`.
|
|
268
268
|
|
|
@@ -297,7 +297,7 @@ this keeps that true for entity names long enough to push a line past 80
|
|
|
297
297
|
columns. A project without prettier installed still gets its files, just
|
|
298
298
|
unformatted.
|
|
299
299
|
|
|
300
|
-
- Migration → `
|
|
300
|
+
- Migration → `MigrationHelper`
|
|
301
301
|
- Seed → `SeederUtility`
|
|
302
302
|
|
|
303
303
|
Re-running `gen` for an existing module reuses it and only adds new files —
|
|
@@ -306,7 +306,7 @@ existing files are never overwritten.
|
|
|
306
306
|
## `nh sync` and `nh check`
|
|
307
307
|
|
|
308
308
|
`sync` scans every module under `src/` — including its `external/` mirror —
|
|
309
|
-
and regenerates its `
|
|
309
|
+
and regenerates its `module.registry.ts` barrel from the files actually present in
|
|
310
310
|
`controllers/`, `services/`, `libraries/`, `dtos/`, `jobs/` and
|
|
311
311
|
`subscribers/`, then ensures each module is imported and listed in
|
|
312
312
|
`app.module.ts`. Files are classified by their **folder** and the presence of an
|
|
@@ -317,9 +317,9 @@ never silently dropped. The operation is idempotent.
|
|
|
317
317
|
|
|
318
318
|
```text
|
|
319
319
|
$ nh check
|
|
320
|
-
|
|
321
|
-
ok src/shape/
|
|
322
|
-
update src/test/
|
|
320
|
+
module registries:
|
|
321
|
+
ok src/shape/module.registry.ts
|
|
322
|
+
update src/test/module.registry.ts
|
|
323
323
|
+ services: TestService
|
|
324
324
|
|
|
325
325
|
app.module.ts:
|
package/package.json
CHANGED
package/src/commands/check.js
CHANGED
|
@@ -21,8 +21,8 @@ async function check() {
|
|
|
21
21
|
const plan = engine.buildPlan(paths, modules);
|
|
22
22
|
let changes = 0;
|
|
23
23
|
|
|
24
|
-
console.log(chalk.bold('\
|
|
25
|
-
for (const p of plan.
|
|
24
|
+
console.log(chalk.bold('\nmodule registries:'));
|
|
25
|
+
for (const p of plan.registries) {
|
|
26
26
|
const rel = path.relative(paths.root, p.path);
|
|
27
27
|
if (p.changed) {
|
|
28
28
|
changes++;
|
package/src/commands/gen.flag.js
CHANGED
|
@@ -21,7 +21,7 @@ const { registerJob, registerEntity } = require('../lib/registry');
|
|
|
21
21
|
const { findConflict, describeConflict } = require('../lib/collision');
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* Single-artifact generation: `nh gen -c`, `-s`, `-dp`, `-
|
|
24
|
+
* Single-artifact generation: `nh gen -c`, `-s`, `-dp`, `-fp`, `-e`, `-j`, and
|
|
25
25
|
* the `-x` (external, API-key surface) variants of the first four.
|
|
26
26
|
*
|
|
27
27
|
* Bare `nh gen` still runs the full wizard — this path exists for adding one
|
|
@@ -34,7 +34,7 @@ const LABELS = {
|
|
|
34
34
|
controller: 'controller',
|
|
35
35
|
service: 'service',
|
|
36
36
|
dataProcessor: 'data processor',
|
|
37
|
-
|
|
37
|
+
filterProcessor: 'filter processor',
|
|
38
38
|
entity: 'entity',
|
|
39
39
|
job: 'job',
|
|
40
40
|
};
|
|
@@ -44,7 +44,7 @@ const DESCRIBES = {
|
|
|
44
44
|
controller: 'controller',
|
|
45
45
|
service: 'service',
|
|
46
46
|
dataProcessor: 'data processor (+ its payload DTO)',
|
|
47
|
-
|
|
47
|
+
filterProcessor: 'filter processor',
|
|
48
48
|
entity: 'entity, its job and subscriber, and a migration',
|
|
49
49
|
job: 'job',
|
|
50
50
|
};
|
|
@@ -55,14 +55,14 @@ const EXTERNAL_CAPABLE = new Set([
|
|
|
55
55
|
'controller',
|
|
56
56
|
'service',
|
|
57
57
|
'dataProcessor',
|
|
58
|
-
'
|
|
58
|
+
'filterProcessor',
|
|
59
59
|
]);
|
|
60
60
|
|
|
61
61
|
const FLAG = {
|
|
62
62
|
controller: '-c',
|
|
63
63
|
service: '-s',
|
|
64
64
|
dataProcessor: '-dp',
|
|
65
|
-
|
|
65
|
+
filterProcessor: '-fp',
|
|
66
66
|
entity: '-e',
|
|
67
67
|
job: '-j',
|
|
68
68
|
};
|
|
@@ -103,14 +103,14 @@ function primaryArtifacts(target, moduleDir, n, external) {
|
|
|
103
103
|
return external
|
|
104
104
|
? one(n.externalProcessorClass, 'libraries', n.externalProcessorFile)
|
|
105
105
|
: one(n.processorClass, 'libraries', n.processorFile);
|
|
106
|
-
case '
|
|
106
|
+
case 'filterProcessor':
|
|
107
107
|
return external
|
|
108
108
|
? one(
|
|
109
|
-
n.
|
|
109
|
+
n.externalFilterProcessorClass,
|
|
110
110
|
'libraries',
|
|
111
|
-
n.
|
|
111
|
+
n.externalFilterProcessorFile,
|
|
112
112
|
)
|
|
113
|
-
: one(n.
|
|
113
|
+
: one(n.filterProcessorClass, 'libraries', n.filterProcessorFile);
|
|
114
114
|
case 'entity':
|
|
115
115
|
return [
|
|
116
116
|
{
|
|
@@ -151,11 +151,11 @@ function ensureModule(paths, moduleDot, external, report) {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
const modulePath = path.join(moduleDir, `${moduleDot}.module.ts`);
|
|
154
|
-
const
|
|
154
|
+
const registryPath = path.join(moduleDir, 'module.registry.ts');
|
|
155
155
|
const a = writeFile(modulePath, tpl.moduleFile(moduleDot));
|
|
156
|
-
const b = writeFile(
|
|
156
|
+
const b = writeFile(registryPath, tpl.emptyModuleRegistry());
|
|
157
157
|
if (a !== 'skipped') report(a, modulePath, paths.root);
|
|
158
|
-
if (b !== 'skipped') report(b,
|
|
158
|
+
if (b !== 'skipped') report(b, registryPath, paths.root);
|
|
159
159
|
|
|
160
160
|
if (!existed) log.info(`Created module "${moduleDot}".`);
|
|
161
161
|
return moduleDir;
|
|
@@ -166,7 +166,7 @@ async function genFlag(target, { external = false } = {}) {
|
|
|
166
166
|
if (external && !EXTERNAL_CAPABLE.has(target)) {
|
|
167
167
|
throw new Error(
|
|
168
168
|
`-x is not supported for ${LABELS[target]} — external applies to ` +
|
|
169
|
-
`-c, -s, -dp and -
|
|
169
|
+
`-c, -s, -dp and -fp only. Run \`nh gen ${FLAG[target]}\` without -x.`,
|
|
170
170
|
);
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -225,7 +225,7 @@ async function genFlag(target, { external = false } = {}) {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
let tableName;
|
|
228
|
-
if (target === '
|
|
228
|
+
if (target === 'filterProcessor' || target === 'entity') {
|
|
229
229
|
({ tableName } = await inquirer.prompt([
|
|
230
230
|
{
|
|
231
231
|
type: 'input',
|
|
@@ -295,8 +295,8 @@ async function genFlag(target, { external = false } = {}) {
|
|
|
295
295
|
write(path.join(moduleDir, 'dtos', n.dtoFile), tpl.dto(rawName));
|
|
296
296
|
break;
|
|
297
297
|
|
|
298
|
-
case '
|
|
299
|
-
write(dest, tpl.
|
|
298
|
+
case 'filterProcessor':
|
|
299
|
+
write(dest, tpl.filterProcessor(rawName, tableName, { external }));
|
|
300
300
|
break;
|
|
301
301
|
|
|
302
302
|
case 'entity': {
|
package/src/commands/gen.js
CHANGED
|
@@ -25,7 +25,7 @@ async function gen() {
|
|
|
25
25
|
|
|
26
26
|
// Every file this run writes or rewrites, formatted in one pass at the end.
|
|
27
27
|
// Each write is announced through report(), so recording paths here catches
|
|
28
|
-
// templates, the constant registries, the
|
|
28
|
+
// templates, the constant registries, the module registry and app.module alike.
|
|
29
29
|
const touched = [];
|
|
30
30
|
const report = (result, filePath, root) => {
|
|
31
31
|
if (result !== "skipped") touched.push(filePath);
|
|
@@ -51,7 +51,7 @@ async function gen() {
|
|
|
51
51
|
log.info(`Creating module "${moduleDot}".`);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
// Always ensure the module skeleton (folders + module file +
|
|
54
|
+
// Always ensure the module skeleton (folders + module file + module registry).
|
|
55
55
|
for (const sub of MODULE_SUBFOLDERS) ensureDir(path.join(moduleDir, sub));
|
|
56
56
|
report(
|
|
57
57
|
writeFile(
|
|
@@ -61,8 +61,8 @@ async function gen() {
|
|
|
61
61
|
path.join(moduleDir, `${moduleDot}.module.ts`),
|
|
62
62
|
paths.root,
|
|
63
63
|
);
|
|
64
|
-
const
|
|
65
|
-
report(writeFile(
|
|
64
|
+
const registryPath = path.join(moduleDir, "module.registry.ts");
|
|
65
|
+
report(writeFile(registryPath, tpl.emptyModuleRegistry()), registryPath, paths.root);
|
|
66
66
|
|
|
67
67
|
// 2. Entity ---------------------------------------------------------------
|
|
68
68
|
const { entityName } = await inquirer.prompt([
|
|
@@ -125,7 +125,7 @@ async function gen() {
|
|
|
125
125
|
// job, subscriber or sibling service goes through, never an opt-in. It is
|
|
126
126
|
// written after the processor prompts, because those answers decide which
|
|
127
127
|
// methods it (and the controller) ships with: `get()` always, `upsert()`
|
|
128
|
-
// with a data processor, `list()` with a
|
|
128
|
+
// with a data processor, `list()` with a filter processor.
|
|
129
129
|
|
|
130
130
|
// 5. Optional: data processor (includes Add*DataDto) ----------------------
|
|
131
131
|
const { processor } = await inquirer.prompt([
|
|
@@ -145,20 +145,20 @@ async function gen() {
|
|
|
145
145
|
report(writeFile(procPath, tpl.processor(entityName, { withEntity: true })), procPath, paths.root);
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
// 6. Optional:
|
|
149
|
-
const {
|
|
148
|
+
// 6. Optional: filter processor (takes CommonListFilterDto directly) ------
|
|
149
|
+
const { filterProcessor } = await inquirer.prompt([
|
|
150
150
|
{
|
|
151
151
|
type: "confirm",
|
|
152
|
-
name: "
|
|
153
|
-
message: `Create
|
|
152
|
+
name: "filterProcessor",
|
|
153
|
+
message: `Create filter processor (${n.filterProcessorClass})?`,
|
|
154
154
|
default: true,
|
|
155
155
|
},
|
|
156
156
|
]);
|
|
157
157
|
|
|
158
|
-
if (
|
|
159
|
-
const listProcPath = path.join(moduleDir, "libraries", n.
|
|
158
|
+
if (filterProcessor) {
|
|
159
|
+
const listProcPath = path.join(moduleDir, "libraries", n.filterProcessorFile);
|
|
160
160
|
report(
|
|
161
|
-
writeFile(listProcPath, tpl.
|
|
161
|
+
writeFile(listProcPath, tpl.filterProcessor(entityName, tableName)),
|
|
162
162
|
listProcPath,
|
|
163
163
|
paths.root,
|
|
164
164
|
);
|
|
@@ -166,7 +166,7 @@ async function gen() {
|
|
|
166
166
|
|
|
167
167
|
const servicePath = path.join(moduleDir, "services", n.serviceFile);
|
|
168
168
|
report(
|
|
169
|
-
writeFile(servicePath, tpl.service(entityName, { processor,
|
|
169
|
+
writeFile(servicePath, tpl.service(entityName, { processor, filterProcessor })),
|
|
170
170
|
servicePath,
|
|
171
171
|
paths.root,
|
|
172
172
|
);
|
|
@@ -176,7 +176,7 @@ async function gen() {
|
|
|
176
176
|
report(
|
|
177
177
|
writeFile(
|
|
178
178
|
p,
|
|
179
|
-
tpl.controller(entityName, { processor,
|
|
179
|
+
tpl.controller(entityName, { processor, filterProcessor, enums }),
|
|
180
180
|
),
|
|
181
181
|
p,
|
|
182
182
|
paths.root,
|
package/src/commands/sync.js
CHANGED
|
@@ -19,7 +19,7 @@ async function sync() {
|
|
|
19
19
|
const plan = engine.buildPlan(paths, modules);
|
|
20
20
|
const written = engine.applyPlan(plan);
|
|
21
21
|
|
|
22
|
-
for (const p of plan.
|
|
22
|
+
for (const p of plan.registries) {
|
|
23
23
|
const rel = path.relative(paths.root, p.path);
|
|
24
24
|
if (p.changed) log.overwritten(rel);
|
|
25
25
|
else console.log(` ${chalk.dim('ok')} ${rel}`);
|
package/src/index.js
CHANGED
|
@@ -32,12 +32,12 @@ program
|
|
|
32
32
|
|
|
33
33
|
// `gen` with no target flag runs the full wizard; a target flag generates just
|
|
34
34
|
// that one artifact. -x mirrors a target onto the module's external (API-key)
|
|
35
|
-
// surface and applies to -c, -s, -dp and -
|
|
35
|
+
// surface and applies to -c, -s, -dp and -fp only.
|
|
36
36
|
const GEN_TARGETS = [
|
|
37
37
|
['controller', 'controller'],
|
|
38
38
|
['service', 'service'],
|
|
39
39
|
['dataProcessor', 'dataProcessor'],
|
|
40
|
-
['
|
|
40
|
+
['filterProcessor', 'filterProcessor'],
|
|
41
41
|
['entity', 'entity'],
|
|
42
42
|
['job', 'job'],
|
|
43
43
|
];
|
|
@@ -50,7 +50,7 @@ program
|
|
|
50
50
|
.option('-c, --controller', 'generate only a controller')
|
|
51
51
|
.option('-s, --service', 'generate only a service')
|
|
52
52
|
.option('-dp, --data-processor', 'generate only a data processor')
|
|
53
|
-
.option('-
|
|
53
|
+
.option('-fp, --filter-processor', 'generate only a filter processor')
|
|
54
54
|
.option('-e, --entity', 'generate an entity and its migration')
|
|
55
55
|
.option('-j, --job', 'generate only a job')
|
|
56
56
|
.option('-x, --external', 'put it on the external (api/e/v1) surface')
|
|
@@ -62,7 +62,7 @@ Examples:
|
|
|
62
62
|
${cli} gen -c controller only (asks module, name, route path)
|
|
63
63
|
${cli} gen -s bare injectable service
|
|
64
64
|
${cli} gen -dp data processor (+ its Add<Name>DataDto if missing)
|
|
65
|
-
${cli} gen -
|
|
65
|
+
${cli} gen -fp filter processor
|
|
66
66
|
${cli} gen -e entity + migration, registered in the constants/maps
|
|
67
67
|
${cli} gen -j job, registered in job.constants/job.maps
|
|
68
68
|
${cli} gen -x -c external controller under <module>/external/controllers
|
|
@@ -75,12 +75,12 @@ Examples:
|
|
|
75
75
|
);
|
|
76
76
|
if (chosen.length > 1) {
|
|
77
77
|
throw new Error(
|
|
78
|
-
'Pick one target flag at a time (-c, -s, -dp, -
|
|
78
|
+
'Pick one target flag at a time (-c, -s, -dp, -fp, -e or -j).',
|
|
79
79
|
);
|
|
80
80
|
}
|
|
81
81
|
if (!chosen.length) {
|
|
82
82
|
if (opts.external) {
|
|
83
|
-
throw new Error('-x needs a target: -c, -s, -dp or -
|
|
83
|
+
throw new Error('-x needs a target: -c, -s, -dp or -fp.');
|
|
84
84
|
}
|
|
85
85
|
return require('./commands/gen')();
|
|
86
86
|
}
|
|
@@ -102,7 +102,7 @@ program
|
|
|
102
102
|
|
|
103
103
|
program
|
|
104
104
|
.command('sync')
|
|
105
|
-
.description('rebuild
|
|
105
|
+
.description('rebuild module registries and register modules in app.module.ts')
|
|
106
106
|
.action(run(() => require('./commands/sync')()));
|
|
107
107
|
|
|
108
108
|
program
|
package/src/lib/flavour.js
CHANGED
|
@@ -7,7 +7,7 @@ const path = require('path');
|
|
|
7
7
|
* Which package the generated code is written against.
|
|
8
8
|
*
|
|
9
9
|
* The two share every convention this CLI encodes — dot.separated filenames,
|
|
10
|
-
* the processor lifecycle,
|
|
10
|
+
* the processor lifecycle, module registries, the migration utility — and
|
|
11
11
|
* differ in what a record belongs to. `nestend` is the tenanted model: rows
|
|
12
12
|
* carry a `tenant_id`, access is a scope *and* a role. `nestendx` is the B2C
|
|
13
13
|
* one: users register themselves and hold scopes alone, so a row belongs to the
|
package/src/lib/sync.engine.js
CHANGED
|
@@ -8,12 +8,12 @@ const scan = require('./source.scan');
|
|
|
8
8
|
/**
|
|
9
9
|
* The sync engine is the single brain behind `nh sync`, `nh check` and the
|
|
10
10
|
* auto-wiring step of `nh gen`. It scans scaffolded modules, regenerates each
|
|
11
|
-
* module's
|
|
11
|
+
* module's module.registry.ts from the files actually present on disk, and
|
|
12
12
|
* registers every module in app.module.ts — so the project builds without any
|
|
13
13
|
* manual import wiring.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
// Provider categories registered through
|
|
16
|
+
// Provider categories registered through module.registry.ts. Files are classified
|
|
17
17
|
// by the folder they live in (not by a fragile filename suffix) so that
|
|
18
18
|
// non-standard names — e.g. the boilerplate's `test.service..ts` — are still
|
|
19
19
|
// picked up and never silently dropped from the barrel.
|
|
@@ -27,12 +27,12 @@ const CATEGORIES = [
|
|
|
27
27
|
];
|
|
28
28
|
|
|
29
29
|
// Files in a category folder that are not providers themselves.
|
|
30
|
-
const NON_PROVIDER_FILES = new Set(['
|
|
30
|
+
const NON_PROVIDER_FILES = new Set(['module.registry.ts', 'index.ts']);
|
|
31
31
|
|
|
32
32
|
// A module may expose an API-key surface from `<module>/external/<folder>`
|
|
33
33
|
// (e.g. external/controllers/external.test.controller.ts). Those classes are
|
|
34
34
|
// providers of the same categories as their internal siblings and belong in
|
|
35
|
-
// the same
|
|
35
|
+
// the same module.registry arrays — listed after the internal ones — so the
|
|
36
36
|
// scanner has to walk both roots or `nh sync` would strip them from the
|
|
37
37
|
// barrel.
|
|
38
38
|
const EXTERNAL_DIR = 'external';
|
|
@@ -109,7 +109,7 @@ function scanModule(dir, modFile) {
|
|
|
109
109
|
moduleFile: modFile,
|
|
110
110
|
moduleClass: exportedClassName(modFile),
|
|
111
111
|
moduleImportPath: `./${moduleDot}/${path.basename(modFile, '.ts')}`,
|
|
112
|
-
|
|
112
|
+
registryPath: path.join(dir, 'module.registry.ts'),
|
|
113
113
|
providers,
|
|
114
114
|
entities: listTsFiles(path.join(dir, 'entities'), '.entity.ts'),
|
|
115
115
|
dtos: listTsFiles(path.join(dir, 'dtos'), '.dto.ts'),
|
|
@@ -117,7 +117,7 @@ function scanModule(dir, modFile) {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
/**
|
|
120
|
-
* Render the
|
|
120
|
+
* Render the module.registry.ts content for a scanned module.
|
|
121
121
|
*
|
|
122
122
|
* The array literals are wrapped exactly the way prettier's 80-column printer
|
|
123
123
|
* would: inline while they fit, one class per line once they don't. `nh gen`
|
|
@@ -125,7 +125,7 @@ function scanModule(dir, modFile) {
|
|
|
125
125
|
* every later `nh check` would report the barrel as drifted purely because
|
|
126
126
|
* prettier had reflowed it.
|
|
127
127
|
*/
|
|
128
|
-
function
|
|
128
|
+
function buildRegistryContent(mod) {
|
|
129
129
|
const importLines = [];
|
|
130
130
|
const arrayLines = [];
|
|
131
131
|
for (const cat of CATEGORIES) {
|
|
@@ -142,19 +142,19 @@ function buildEs6Content(mod) {
|
|
|
142
142
|
);
|
|
143
143
|
}
|
|
144
144
|
const header = importLines.length ? importLines.join('\n') + '\n\n' : '';
|
|
145
|
-
return `${header}export const
|
|
145
|
+
return `${header}export const moduleRegistry = {\n${arrayLines.join('\n')}\n};\n`;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
/** Plan the
|
|
149
|
-
function
|
|
150
|
-
const next =
|
|
151
|
-
const current = fs.existsSync(mod.
|
|
152
|
-
? fs.readFileSync(mod.
|
|
148
|
+
/** Plan the module.registry.ts rewrite for one module. */
|
|
149
|
+
function planRegistry(mod) {
|
|
150
|
+
const next = buildRegistryContent(mod);
|
|
151
|
+
const current = fs.existsSync(mod.registryPath)
|
|
152
|
+
? fs.readFileSync(mod.registryPath, 'utf8')
|
|
153
153
|
: null;
|
|
154
154
|
return {
|
|
155
|
-
type: '
|
|
155
|
+
type: 'registry',
|
|
156
156
|
moduleDot: mod.moduleDot,
|
|
157
|
-
path: mod.
|
|
157
|
+
path: mod.registryPath,
|
|
158
158
|
current,
|
|
159
159
|
next,
|
|
160
160
|
changed: current !== next,
|
|
@@ -251,18 +251,18 @@ function planAppModule(paths, modules) {
|
|
|
251
251
|
};
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
-
/** Full plan: every module's
|
|
254
|
+
/** Full plan: every module's module registry + app.module registration. */
|
|
255
255
|
function buildPlan(paths, modules = scanModules(paths)) {
|
|
256
256
|
return {
|
|
257
257
|
modules,
|
|
258
|
-
|
|
258
|
+
registries: modules.map(planRegistry),
|
|
259
259
|
appModule: planAppModule(paths, modules),
|
|
260
260
|
};
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
function applyPlan(plan) {
|
|
264
264
|
const written = [];
|
|
265
|
-
for (const p of plan.
|
|
265
|
+
for (const p of plan.registries) {
|
|
266
266
|
if (p.changed) {
|
|
267
267
|
fs.writeFileSync(p.path, p.next);
|
|
268
268
|
written.push(p.path);
|
|
@@ -280,8 +280,8 @@ module.exports = {
|
|
|
280
280
|
EXTERNAL_DIR,
|
|
281
281
|
scanModules,
|
|
282
282
|
scanModule,
|
|
283
|
-
|
|
284
|
-
|
|
283
|
+
buildRegistryContent,
|
|
284
|
+
planRegistry,
|
|
285
285
|
planAppModule,
|
|
286
286
|
buildPlan,
|
|
287
287
|
applyPlan,
|
package/src/lib/templates.js
CHANGED
|
@@ -15,7 +15,7 @@ const { accessNames } = require('./access');
|
|
|
15
15
|
* Code templates. All generated code imports shared base classes from the
|
|
16
16
|
* package the active flavour names — `@neohm/nestend` under `nh`,
|
|
17
17
|
* `@neohm/nestendx` under `nhx` (verified exports: CommonEntity, CommonDataDto,
|
|
18
|
-
*
|
|
18
|
+
* BaseDataProcessor, MigrationHelper, SeederUtility, etc.) — and matches the
|
|
19
19
|
* dot.separated filename / PascalCase class conventions used across the
|
|
20
20
|
* boilerplates.
|
|
21
21
|
*
|
|
@@ -59,8 +59,8 @@ function names(base) {
|
|
|
59
59
|
dtoFile: `add.${dot}.data.dto.ts`,
|
|
60
60
|
processorClass: `${pascal}DataProcessor`,
|
|
61
61
|
processorFile: `${dot}.data.processor.ts`,
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
filterProcessorClass: `${pascal}FilterProcessor`,
|
|
63
|
+
filterProcessorFile: `${dot}.filter.processor.ts`,
|
|
64
64
|
subscriberClass: `${pascal}Subscriber`,
|
|
65
65
|
subscriberFile: `${dot}.subscriber.ts`,
|
|
66
66
|
jobClass: `${pascal}Job`,
|
|
@@ -74,8 +74,8 @@ function names(base) {
|
|
|
74
74
|
externalServiceFile: `external.${dot}.service.ts`,
|
|
75
75
|
externalProcessorClass: `External${pascal}DataProcessor`,
|
|
76
76
|
externalProcessorFile: `external.${dot}.data.processor.ts`,
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
externalFilterProcessorClass: `External${pascal}FilterProcessor`,
|
|
78
|
+
externalFilterProcessorFile: `external.${dot}.filter.processor.ts`,
|
|
79
79
|
// Fed through naming.migrationFileName/migrationClassName so the migration
|
|
80
80
|
// `nh gen` writes is named exactly like one from `nh migration`.
|
|
81
81
|
migrationName: `Add${pascal}Table`,
|
|
@@ -137,14 +137,14 @@ function attributesDto(base) {
|
|
|
137
137
|
*/
|
|
138
138
|
function controller(
|
|
139
139
|
base,
|
|
140
|
-
{ processor = false,
|
|
140
|
+
{ processor = false, filterProcessor = false, enums = {} } = {},
|
|
141
141
|
) {
|
|
142
142
|
const n = names(base);
|
|
143
143
|
const a = accessNames(base);
|
|
144
144
|
const scopeEnum = enums.scope || 'ScopeEnum';
|
|
145
145
|
const roleEnum = enums.role || 'RoleEnum';
|
|
146
146
|
const svcProp = `${n.camel}Service`;
|
|
147
|
-
const hasPost = processor ||
|
|
147
|
+
const hasPost = processor || filterProcessor;
|
|
148
148
|
|
|
149
149
|
// @nestjs/common members, alphabetised the way prettier leaves them.
|
|
150
150
|
const nestCommon = [
|
|
@@ -159,7 +159,7 @@ function controller(
|
|
|
159
159
|
nestCommon.sort();
|
|
160
160
|
|
|
161
161
|
const nestendImports = ['AuthService'];
|
|
162
|
-
if (
|
|
162
|
+
if (filterProcessor) nestendImports.push('CommonListFilterDto');
|
|
163
163
|
nestendImports.push('StrictValidationPipe');
|
|
164
164
|
|
|
165
165
|
const imports = [
|
|
@@ -218,7 +218,7 @@ function controller(
|
|
|
218
218
|
}`);
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
if (
|
|
221
|
+
if (filterProcessor) {
|
|
222
222
|
methods.push(` @Post('list')
|
|
223
223
|
@ApiOperation({ summary: 'List, search and filter ${n.kebab}.' })
|
|
224
224
|
async list(@Body() body: CommonListFilterDto) {
|
|
@@ -259,14 +259,14 @@ ${methods.join('\n\n')}
|
|
|
259
259
|
* controller, so nothing downstream has to know how a record is fetched.
|
|
260
260
|
*
|
|
261
261
|
* `get()` is unconditional — it is the one operation that needs no processor.
|
|
262
|
-
* `upsert()` appears with a data processor, `list()` with a
|
|
262
|
+
* `upsert()` appears with a data processor, `list()` with a filter processor.
|
|
263
263
|
*/
|
|
264
|
-
function service(base, { processor = false,
|
|
264
|
+
function service(base, { processor = false, filterProcessor = false } = {}) {
|
|
265
265
|
const n = names(base);
|
|
266
266
|
const label = toWords(base).join(' ');
|
|
267
267
|
|
|
268
268
|
const nestendImports = [];
|
|
269
|
-
if (
|
|
269
|
+
if (filterProcessor) {
|
|
270
270
|
nestendImports.push('CommonListFilterDto', 'ListFilterService');
|
|
271
271
|
}
|
|
272
272
|
nestendImports.push('NotFoundException', 'UserEntity');
|
|
@@ -275,9 +275,9 @@ function service(base, { processor = false, listProcessor = false } = {}) {
|
|
|
275
275
|
namedImport(nestendImports, pkg()),
|
|
276
276
|
`import { Injectable } from '@nestjs/common';`,
|
|
277
277
|
];
|
|
278
|
-
if (
|
|
278
|
+
if (filterProcessor) {
|
|
279
279
|
imports.push(
|
|
280
|
-
`import { ${n.
|
|
280
|
+
`import { ${n.filterProcessorClass} } from '../libraries/${n.dot}.filter.processor';`,
|
|
281
281
|
);
|
|
282
282
|
}
|
|
283
283
|
if (processor) {
|
|
@@ -291,7 +291,7 @@ function service(base, { processor = false, listProcessor = false } = {}) {
|
|
|
291
291
|
);
|
|
292
292
|
|
|
293
293
|
const members = [];
|
|
294
|
-
if (
|
|
294
|
+
if (filterProcessor) {
|
|
295
295
|
members.push(
|
|
296
296
|
` constructor(protected readonly listFilterService: ListFilterService) {}`,
|
|
297
297
|
);
|
|
@@ -328,12 +328,12 @@ function service(base, { processor = false, listProcessor = false } = {}) {
|
|
|
328
328
|
}`);
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
-
if (
|
|
331
|
+
if (filterProcessor) {
|
|
332
332
|
// Emit the call already wrapped the way prettier's 80-column printer
|
|
333
333
|
// would, so a long entity name doesn't leave the generated file dirty.
|
|
334
334
|
// Prettier's three tiers: keep the fluent one-liner; else break the
|
|
335
335
|
// .process() argument; else break the constructor arguments too.
|
|
336
|
-
const call = `new ${n.
|
|
336
|
+
const call = `new ${n.filterProcessorClass}(this.listFilterService, user)`;
|
|
337
337
|
const oneLine = ` return ${call}.process(data);`;
|
|
338
338
|
const brokenArg = ` return ${call}.process(`;
|
|
339
339
|
let body;
|
|
@@ -342,7 +342,7 @@ function service(base, { processor = false, listProcessor = false } = {}) {
|
|
|
342
342
|
} else if (brokenArg.length <= 80) {
|
|
343
343
|
body = `${brokenArg}\n data,\n );`;
|
|
344
344
|
} else {
|
|
345
|
-
body = ` return new ${n.
|
|
345
|
+
body = ` return new ${n.filterProcessorClass}(\n this.listFilterService,\n user,\n ).process(data);`;
|
|
346
346
|
}
|
|
347
347
|
members.push(` async list(user: UserEntity, data: CommonListFilterDto) {
|
|
348
348
|
${body}
|
|
@@ -424,12 +424,12 @@ export class ${cls} {}
|
|
|
424
424
|
*/
|
|
425
425
|
function additionalJob(base) {
|
|
426
426
|
const n = names(base);
|
|
427
|
-
return `import {
|
|
427
|
+
return `import { BaseJob, QueueService } from '${pkg()}';
|
|
428
428
|
import { Injectable } from '@nestjs/common';
|
|
429
429
|
import { JobConstants } from '../../constants/job.constants';
|
|
430
430
|
|
|
431
431
|
@Injectable()
|
|
432
|
-
export class ${n.jobClass} extends
|
|
432
|
+
export class ${n.jobClass} extends BaseJob {
|
|
433
433
|
constructor(protected readonly queueService: QueueService) {
|
|
434
434
|
super(JobConstants.${n.camel}Job);
|
|
435
435
|
}
|
|
@@ -458,10 +458,10 @@ function processor(base, { external = false, withEntity = false } = {}) {
|
|
|
458
458
|
const up = external ? '../..' : '..';
|
|
459
459
|
const cls = external ? n.externalProcessorClass : n.processorClass;
|
|
460
460
|
if (external) {
|
|
461
|
-
return `import {
|
|
461
|
+
return `import { BaseDataProcessor } from '${pkg()}';
|
|
462
462
|
import { ${n.dtoClass} } from '${up}/dtos/add.${n.dot}.data.dto';
|
|
463
463
|
|
|
464
|
-
export class ${cls} extends
|
|
464
|
+
export class ${cls} extends BaseDataProcessor {
|
|
465
465
|
protected payload: ${n.dtoClass};
|
|
466
466
|
|
|
467
467
|
async process(data: ${n.dtoClass}) {
|
|
@@ -518,10 +518,10 @@ export class ${cls} extends CommonDataProcessor {
|
|
|
518
518
|
: ` private set(): void {
|
|
519
519
|
// TODO: persist ${n.pascal} from this.payload, assigning fields explicitly.
|
|
520
520
|
}`;
|
|
521
|
-
return `import {
|
|
521
|
+
return `import { BaseDataProcessor, UserEntity } from '${pkg()}';
|
|
522
522
|
import { ${n.dtoClass} } from '${up}/dtos/add.${n.dot}.data.dto';${entityImport}
|
|
523
523
|
|
|
524
|
-
export class ${cls} extends
|
|
524
|
+
export class ${cls} extends BaseDataProcessor {
|
|
525
525
|
protected payload: ${n.dtoClass};${existingField}
|
|
526
526
|
|
|
527
527
|
constructor(private readonly user: UserEntity) {
|
|
@@ -558,13 +558,13 @@ ${set}
|
|
|
558
558
|
`;
|
|
559
559
|
}
|
|
560
560
|
|
|
561
|
-
function
|
|
561
|
+
function filterProcessor(base, tableName, { external = false } = {}) {
|
|
562
562
|
const n = names(base);
|
|
563
|
-
const cls = external ? n.
|
|
563
|
+
const cls = external ? n.externalFilterProcessorClass : n.filterProcessorClass;
|
|
564
564
|
return `import {
|
|
565
565
|
CommonListFilterConfig,
|
|
566
566
|
CommonListFilterDto,
|
|
567
|
-
|
|
567
|
+
BaseFilterProcessor,
|
|
568
568
|
FilterValueTypeEnum,
|
|
569
569
|
ListFilterService,
|
|
570
570
|
SortDirection,
|
|
@@ -589,7 +589,7 @@ function listProcessor(base, tableName, { external = false } = {}) {
|
|
|
589
589
|
* \`columns\`. Standing literal clauses go in \`predicates\`; anything
|
|
590
590
|
* carrying a value goes in the method, where add() binds it.
|
|
591
591
|
*/
|
|
592
|
-
export class ${cls} extends
|
|
592
|
+
export class ${cls} extends BaseFilterProcessor {
|
|
593
593
|
protected config: CommonListFilterConfig = {
|
|
594
594
|
query: '${tableName} a',
|
|
595
595
|
columns: [
|
|
@@ -639,14 +639,14 @@ ${
|
|
|
639
639
|
|
|
640
640
|
function subscriber(base) {
|
|
641
641
|
const n = names(base);
|
|
642
|
-
return `import {
|
|
642
|
+
return `import { BaseEntitySubscriber } from '${pkg()}';
|
|
643
643
|
import { EventSubscriber } from 'typeorm';
|
|
644
644
|
import { ${n.entityClass} } from '../entities/${n.dot}.entity';
|
|
645
645
|
import { DataSource } from 'typeorm';
|
|
646
646
|
import { ${n.jobClass} } from '../jobs/${n.dot}.job';
|
|
647
647
|
|
|
648
648
|
@EventSubscriber()
|
|
649
|
-
export class ${n.subscriberClass} extends
|
|
649
|
+
export class ${n.subscriberClass} extends BaseEntitySubscriber<${n.entityClass}> {
|
|
650
650
|
constructor(
|
|
651
651
|
protected readonly datasource: DataSource,
|
|
652
652
|
protected readonly entityJob: ${n.jobClass},
|
|
@@ -667,13 +667,13 @@ export class ${n.subscriberClass} extends CommonSubscriber<${n.entityClass}> {
|
|
|
667
667
|
|
|
668
668
|
function job(base) {
|
|
669
669
|
const n = names(base);
|
|
670
|
-
return `import {
|
|
670
|
+
return `import { BaseEntityJob, QueueService, DatabaseEventDto } from '${pkg()}';
|
|
671
671
|
import { Injectable } from '@nestjs/common';
|
|
672
672
|
import { JobConstants } from '../../constants/job.constants';
|
|
673
673
|
import { ${n.entityClass} } from '../entities/${n.dot}.entity';
|
|
674
674
|
|
|
675
675
|
@Injectable()
|
|
676
|
-
export class ${n.jobClass} extends
|
|
676
|
+
export class ${n.jobClass} extends BaseEntityJob {
|
|
677
677
|
constructor(protected readonly queueService: QueueService) {
|
|
678
678
|
super(JobConstants.${n.camel}Job);
|
|
679
679
|
}
|
|
@@ -693,9 +693,9 @@ export class ${n.jobClass} extends CommonJob {
|
|
|
693
693
|
function genMigration(base, tableName, timestamp) {
|
|
694
694
|
const n = names(base);
|
|
695
695
|
const className = migrationClassName(n.migrationName, timestamp);
|
|
696
|
-
return `import {
|
|
696
|
+
return `import { MigrationHelper } from '${pkg()}';
|
|
697
697
|
|
|
698
|
-
export class ${className} extends
|
|
698
|
+
export class ${className} extends MigrationHelper {
|
|
699
699
|
constructor() {
|
|
700
700
|
super('${tableName}');
|
|
701
701
|
this.process();
|
|
@@ -726,9 +726,9 @@ ${current().tenantScoped ? ` this.index('tenant_id');\n` : ''} this.index(
|
|
|
726
726
|
/** Standalone migration (`nh migration`). */
|
|
727
727
|
function migration(migrationName, tableName, timestamp) {
|
|
728
728
|
const className = migrationClassName(migrationName, timestamp);
|
|
729
|
-
return `import {
|
|
729
|
+
return `import { MigrationHelper } from '${pkg()}';
|
|
730
730
|
|
|
731
|
-
export class ${className} extends
|
|
731
|
+
export class ${className} extends MigrationHelper {
|
|
732
732
|
constructor() {
|
|
733
733
|
super('${tableName}');
|
|
734
734
|
this.process();
|
|
@@ -841,9 +841,9 @@ ${members}
|
|
|
841
841
|
`;
|
|
842
842
|
}
|
|
843
843
|
|
|
844
|
-
/** A freshly scaffolded module before any
|
|
845
|
-
function
|
|
846
|
-
return `export const
|
|
844
|
+
/** A freshly scaffolded module before any module.registry content exists. */
|
|
845
|
+
function emptyModuleRegistry() {
|
|
846
|
+
return `export const moduleRegistry = {
|
|
847
847
|
controllers: [],
|
|
848
848
|
services: [],
|
|
849
849
|
libraries: [],
|
|
@@ -863,16 +863,16 @@ import {
|
|
|
863
863
|
AuthModule,
|
|
864
864
|
SystemPropertyModule,
|
|
865
865
|
} from '${pkg()}';
|
|
866
|
-
import {
|
|
866
|
+
import { moduleRegistry } from './module.registry';
|
|
867
867
|
|
|
868
868
|
@Module({
|
|
869
869
|
imports: [CommonModule, QueueModule, AuthModule, SystemPropertyModule],
|
|
870
|
-
controllers: [...
|
|
870
|
+
controllers: [...moduleRegistry.controllers],
|
|
871
871
|
providers: [
|
|
872
|
-
...
|
|
873
|
-
...
|
|
874
|
-
...
|
|
875
|
-
...
|
|
872
|
+
...moduleRegistry.services,
|
|
873
|
+
...moduleRegistry.libraries,
|
|
874
|
+
...moduleRegistry.jobs,
|
|
875
|
+
...moduleRegistry.subscribers,
|
|
876
876
|
],
|
|
877
877
|
})
|
|
878
878
|
export class ${pascal}Module {}
|
|
@@ -893,7 +893,7 @@ module.exports = {
|
|
|
893
893
|
additionalJob,
|
|
894
894
|
dto,
|
|
895
895
|
processor,
|
|
896
|
-
|
|
896
|
+
filterProcessor,
|
|
897
897
|
subscriber,
|
|
898
898
|
job,
|
|
899
899
|
genMigration,
|
|
@@ -903,6 +903,6 @@ module.exports = {
|
|
|
903
903
|
entityConstantsFile,
|
|
904
904
|
jobMapsFile,
|
|
905
905
|
entityMapsFile,
|
|
906
|
-
|
|
906
|
+
emptyModuleRegistry,
|
|
907
907
|
moduleFile,
|
|
908
908
|
};
|