@domain-first/project-structure 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 +9 -0
- package/dist/config-file.d.ts +21 -0
- package/dist/file-system.d.ts +22 -0
- package/dist/index.cjs +950 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +916 -0
- package/dist/project-root.d.ts +4 -0
- package/dist/project-root.spec.d.ts +1 -0
- package/dist/unknown-format-naming.d.ts +9 -0
- package/dist/use-cases/index.d.ts +10 -0
- package/dist/use-cases/initialize.d.ts +1 -0
- package/dist/use-cases/scaffold-new-aggregate.d.ts +2 -0
- package/dist/use-cases/scaffold-new-application-port.d.ts +2 -0
- package/dist/use-cases/scaffold-new-bounded-context.d.ts +1 -0
- package/dist/use-cases/scaffold-new-command.d.ts +2 -0
- package/dist/use-cases/scaffold-new-domain-service.d.ts +2 -0
- package/dist/use-cases/scaffold-new-error.d.ts +2 -0
- package/dist/use-cases/scaffold-new-handlers-rest-endpoint.d.ts +2 -0
- package/dist/use-cases/scaffold-new-query.d.ts +2 -0
- package/dist/use-cases/scaffold-new-use-case.d.ts +2 -0
- package/package.json +49 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,916 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { checkbox as prompts_checkbox, confirm as prompts_confirm, input, select as prompts_select } from "@inquirer/prompts";
|
|
3
|
+
import node_path from "node:path";
|
|
4
|
+
import zod from "zod";
|
|
5
|
+
import node_fs from "node:fs";
|
|
6
|
+
class FileSystemItem {
|
|
7
|
+
path;
|
|
8
|
+
constructor(path){
|
|
9
|
+
this.path = path;
|
|
10
|
+
}
|
|
11
|
+
subitem = (relativePath)=>new this.constructor(node_path.resolve(this.path, ...relativePath));
|
|
12
|
+
get exists() {
|
|
13
|
+
return node_fs.existsSync(this.path);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
class Folder extends FileSystemItem {
|
|
17
|
+
createIfNotExisted = ()=>{
|
|
18
|
+
node_fs.mkdirSync(this.path, {
|
|
19
|
+
recursive: true
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
get content() {
|
|
23
|
+
if (!this.exists) return {
|
|
24
|
+
subfolderNames: [],
|
|
25
|
+
fileNames: []
|
|
26
|
+
};
|
|
27
|
+
const content = node_fs.readdirSync(this.path, {
|
|
28
|
+
withFileTypes: true
|
|
29
|
+
});
|
|
30
|
+
return {
|
|
31
|
+
subfolderNames: content.filter((x)=>x.isDirectory()).map((x)=>x.name),
|
|
32
|
+
fileNames: content.filter((x)=>!x.isDirectory()).map((x)=>node_path.parse(x.name).name)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
get name() {
|
|
36
|
+
return node_path.basename(this.path);
|
|
37
|
+
}
|
|
38
|
+
createFile = (name, content)=>{
|
|
39
|
+
this.createIfNotExisted();
|
|
40
|
+
const file = new File(this.path).subitem([
|
|
41
|
+
name
|
|
42
|
+
]);
|
|
43
|
+
file.data = content;
|
|
44
|
+
return file;
|
|
45
|
+
};
|
|
46
|
+
file = (name)=>{
|
|
47
|
+
this.createIfNotExisted();
|
|
48
|
+
const file = new File(this.path).subitem([
|
|
49
|
+
name
|
|
50
|
+
]);
|
|
51
|
+
return file;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
class File extends FileSystemItem {
|
|
55
|
+
get data() {
|
|
56
|
+
if (!this.exists) throw new Error('File does not exist');
|
|
57
|
+
return node_fs.readFileSync(this.path, 'utf-8');
|
|
58
|
+
}
|
|
59
|
+
set data(content) {
|
|
60
|
+
node_fs.writeFileSync(this.path, content);
|
|
61
|
+
}
|
|
62
|
+
addLine = (newLine, separator = '\n')=>{
|
|
63
|
+
let content = '';
|
|
64
|
+
try {
|
|
65
|
+
content = node_fs.readFileSync(this.path, 'utf8');
|
|
66
|
+
} catch {}
|
|
67
|
+
node_fs.writeFileSync(this.path, [
|
|
68
|
+
content.trim(),
|
|
69
|
+
newLine.trim()
|
|
70
|
+
].filter((x)=>!!x).join(separator), 'utf8');
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
class ProjectRoot {
|
|
74
|
+
path;
|
|
75
|
+
constructor(){
|
|
76
|
+
let dir = process.cwd();
|
|
77
|
+
while(dir !== node_path.parse(dir).root){
|
|
78
|
+
if (node_fs.existsSync(node_path.join(dir, 'package.json'))) {
|
|
79
|
+
this.path = dir;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
dir = node_path.dirname(dir);
|
|
83
|
+
}
|
|
84
|
+
throw new Error('No package.json was found');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const configFileName = 'domain-first.project-structure.config.json';
|
|
88
|
+
const configSchema = zod.object({
|
|
89
|
+
rootFolder: zod.string(),
|
|
90
|
+
domainFirstPackages: zod.array(zod["enum"]([
|
|
91
|
+
'@domain-first/types',
|
|
92
|
+
'@domain-first/errors',
|
|
93
|
+
'@domain-first/handlers',
|
|
94
|
+
'@domain-first/wire',
|
|
95
|
+
'@domain-first/handlers-rest'
|
|
96
|
+
])),
|
|
97
|
+
defaultPersistenceLayerImplementation: zod.string()
|
|
98
|
+
});
|
|
99
|
+
class ConfigFile {
|
|
100
|
+
data;
|
|
101
|
+
constructor(){
|
|
102
|
+
const filePath = ConfigFile.path;
|
|
103
|
+
const file = new File(filePath);
|
|
104
|
+
const configObject = JSON.parse(file.data);
|
|
105
|
+
this.data = configSchema.parse(configObject);
|
|
106
|
+
}
|
|
107
|
+
static get Instance() {
|
|
108
|
+
return new ConfigFile();
|
|
109
|
+
}
|
|
110
|
+
static get path() {
|
|
111
|
+
const { path: root } = new ProjectRoot();
|
|
112
|
+
return node_path.resolve(root, configFileName);
|
|
113
|
+
}
|
|
114
|
+
static get exists() {
|
|
115
|
+
return new File(ConfigFile.path).exists;
|
|
116
|
+
}
|
|
117
|
+
static write = (config)=>{
|
|
118
|
+
new File(ConfigFile.path).data = JSON.stringify(config, null, '\t');
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const initializeUseCase = async ()=>{
|
|
122
|
+
const rootFolderName = await input({
|
|
123
|
+
message: 'Root folder (use "/" for inner folders):',
|
|
124
|
+
default: 'src'
|
|
125
|
+
});
|
|
126
|
+
const domainFirstPackages = await prompts_checkbox({
|
|
127
|
+
message: '@domain-first packages',
|
|
128
|
+
choices: [
|
|
129
|
+
'@domain-first/types',
|
|
130
|
+
'@domain-first/errors',
|
|
131
|
+
'@domain-first/handlers',
|
|
132
|
+
'@domain-first/wire',
|
|
133
|
+
'@domain-first/handlers-rest'
|
|
134
|
+
]
|
|
135
|
+
});
|
|
136
|
+
const defaultPersistenceLayerImplementation = await input({
|
|
137
|
+
message: 'Default persistence layer implementation:'
|
|
138
|
+
});
|
|
139
|
+
ConfigFile.write({
|
|
140
|
+
rootFolder: rootFolderName,
|
|
141
|
+
domainFirstPackages,
|
|
142
|
+
defaultPersistenceLayerImplementation
|
|
143
|
+
});
|
|
144
|
+
const { path } = new ProjectRoot();
|
|
145
|
+
const rootFolder = new Folder(path);
|
|
146
|
+
if (domainFirstPackages.includes('@domain-first/wire')) rootFolder.subitem([
|
|
147
|
+
rootFolderName,
|
|
148
|
+
'shared',
|
|
149
|
+
'wiring'
|
|
150
|
+
]).createFile('env-branched-wire.ts', `
|
|
151
|
+
import { branchedWire } from '@domain-first/wire'
|
|
152
|
+
|
|
153
|
+
export const envBranchedWire = branchedWire(() => {
|
|
154
|
+
return process.env.NODE_ENV as 'test' | 'development' | 'production'
|
|
155
|
+
});
|
|
156
|
+
`);
|
|
157
|
+
if (domainFirstPackages.includes('@domain-first/errors')) rootFolder.subitem([
|
|
158
|
+
rootFolderName,
|
|
159
|
+
'shared'
|
|
160
|
+
]).subitem([
|
|
161
|
+
'domain',
|
|
162
|
+
'errors'
|
|
163
|
+
]).createFile('index.ts', `
|
|
164
|
+
import { errorNamespace } from '@domain-first/errors'
|
|
165
|
+
|
|
166
|
+
export const SharedErrors = errorNamespace('shared')
|
|
167
|
+
`.trim());
|
|
168
|
+
};
|
|
169
|
+
class UnknownFormatNaming {
|
|
170
|
+
rawSource;
|
|
171
|
+
camelCase;
|
|
172
|
+
PascalCase;
|
|
173
|
+
constructor(rawSource){
|
|
174
|
+
this.rawSource = rawSource;
|
|
175
|
+
const source = rawSource.trim();
|
|
176
|
+
if (!source.includes('-')) {
|
|
177
|
+
this.camelCase = source.substring(0, 1).toLowerCase() + source.substring(1);
|
|
178
|
+
this.PascalCase = source.substring(0, 1).toUpperCase() + source.substring(1);
|
|
179
|
+
}
|
|
180
|
+
const words = source.split('-');
|
|
181
|
+
this.camelCase = words.map((word, index)=>0 === index ? word.toLowerCase() : word[0].toUpperCase() + word.slice(1).toLowerCase()).join('');
|
|
182
|
+
this.PascalCase = words.map((word)=>word[0].toUpperCase() + word.slice(1).toLowerCase()).join('');
|
|
183
|
+
}
|
|
184
|
+
get fileName() {
|
|
185
|
+
return this.rawSource;
|
|
186
|
+
}
|
|
187
|
+
get variableName() {
|
|
188
|
+
return this.camelCase;
|
|
189
|
+
}
|
|
190
|
+
get ClassName() {
|
|
191
|
+
return this.PascalCase;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const scaffoldNewAggregateUseCase = async (boundedContextFolder)=>{
|
|
195
|
+
const aggregateName = await input({
|
|
196
|
+
message: 'Aggregate name:'
|
|
197
|
+
});
|
|
198
|
+
const aggregateNaming = new UnknownFormatNaming(aggregateName);
|
|
199
|
+
const withRepository = await prompts_confirm({
|
|
200
|
+
message: 'With Repository'
|
|
201
|
+
});
|
|
202
|
+
const aggregatesFolder = boundedContextFolder.subitem([
|
|
203
|
+
'domain',
|
|
204
|
+
'aggregates',
|
|
205
|
+
aggregateName
|
|
206
|
+
]);
|
|
207
|
+
aggregatesFolder.createFile(`${aggregateNaming.fileName}.aggregate-root.ts`, ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/types') ? `
|
|
208
|
+
import { domainType } from '@domain-first/types'
|
|
209
|
+
|
|
210
|
+
export class ${aggregateNaming.ClassName} extends domainType() {
|
|
211
|
+
|
|
212
|
+
}`.trim() : `export class ${aggregateNaming.ClassName} {}`);
|
|
213
|
+
const barrelFile = aggregatesFolder.createFile("index.ts", `export * from './${aggregateNaming.fileName}.aggregate-root'`);
|
|
214
|
+
if (withRepository) {
|
|
215
|
+
aggregatesFolder.createFile(`${aggregateNaming.fileName}-repository.ts`, `export abstract class ${aggregateNaming.ClassName}Repository { }`);
|
|
216
|
+
barrelFile.addLine(`export * from './${aggregateNaming.fileName}-repository'`);
|
|
217
|
+
const infrastructureImplementationType = await input({
|
|
218
|
+
message: 'Repository infrastructure implementation type:',
|
|
219
|
+
default: 'prisma'
|
|
220
|
+
});
|
|
221
|
+
let addTestImplementation = false;
|
|
222
|
+
let testImplementationType = '';
|
|
223
|
+
addTestImplementation = await prompts_confirm({
|
|
224
|
+
message: 'Add a test implementation for Repository'
|
|
225
|
+
});
|
|
226
|
+
if (addTestImplementation) testImplementationType = await input({
|
|
227
|
+
message: 'Test Repository infrastructure implementation type:',
|
|
228
|
+
default: 'in-memory'
|
|
229
|
+
});
|
|
230
|
+
const infrastructureRepositoriesFolder = boundedContextFolder.subitem([
|
|
231
|
+
'infrastructure',
|
|
232
|
+
'repositories'
|
|
233
|
+
]);
|
|
234
|
+
const implementationTypes = [
|
|
235
|
+
infrastructureImplementationType,
|
|
236
|
+
testImplementationType
|
|
237
|
+
].filter((x)=>!!x);
|
|
238
|
+
for (const implementation of implementationTypes){
|
|
239
|
+
const naming = new UnknownFormatNaming(implementation);
|
|
240
|
+
infrastructureRepositoriesFolder.subitem([
|
|
241
|
+
naming.fileName
|
|
242
|
+
]).createFile(`${naming.fileName}-${aggregateName}-repository.ts`, `
|
|
243
|
+
import { ${aggregateNaming.ClassName}Repository } from '../../../domain/aggregates/${aggregateName}'
|
|
244
|
+
|
|
245
|
+
export class ${naming.ClassName}${aggregateNaming.ClassName}Repository implements ${aggregateNaming.ClassName}Repository {
|
|
246
|
+
constructor() {}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
`.trim());
|
|
250
|
+
if (ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire')) {
|
|
251
|
+
const wiringFolder = boundedContextFolder.subitem([
|
|
252
|
+
'wiring',
|
|
253
|
+
'infrastructure',
|
|
254
|
+
'repositories',
|
|
255
|
+
naming.fileName
|
|
256
|
+
]);
|
|
257
|
+
wiringFolder.createFile(`wire-${naming.fileName}-${aggregateNaming.fileName}-repository.ts`, `
|
|
258
|
+
import { wireClass } from '@domain-first/wire'
|
|
259
|
+
import { ${naming.ClassName}${aggregateNaming.ClassName}Repository } from '../../../../infrastructure/repositories/${naming.fileName}/${naming.fileName}-${aggregateNaming.fileName}-repository'
|
|
260
|
+
|
|
261
|
+
export const wire${naming.ClassName}${aggregateNaming.ClassName}Repository = wireClass(
|
|
262
|
+
${naming.ClassName}${aggregateNaming.ClassName}Repository,
|
|
263
|
+
[]
|
|
264
|
+
)
|
|
265
|
+
`.trim());
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire')) {
|
|
269
|
+
const implementationNaming = new UnknownFormatNaming(infrastructureImplementationType);
|
|
270
|
+
const testImplementationNaming = new UnknownFormatNaming(testImplementationType);
|
|
271
|
+
boundedContextFolder.subitem([
|
|
272
|
+
'wiring',
|
|
273
|
+
'domain',
|
|
274
|
+
'repositories'
|
|
275
|
+
]).createFile(`wire-${aggregateNaming.fileName}-repository.ts`, `
|
|
276
|
+
import { envBranchedWire } from '../../../../../shared/wiring/env-branched-wire'
|
|
277
|
+
${implementationTypes.map((x)=>new UnknownFormatNaming(x)).map(({ ClassName, fileName })=>`import { wire${ClassName}${aggregateNaming.ClassName}Repository } from '../../infrastructure/repositories/${fileName}/wire-${fileName}-${aggregateNaming.fileName}-repository'`).join('\n')}
|
|
278
|
+
|
|
279
|
+
export const wire${aggregateNaming.ClassName}Repository = envBranchedWire({
|
|
280
|
+
test: wire${addTestImplementation ? testImplementationNaming.ClassName : implementationNaming.ClassName}${aggregateNaming.ClassName}Repository,
|
|
281
|
+
development: wire${implementationNaming.ClassName}${aggregateNaming.ClassName}Repository,
|
|
282
|
+
production: wire${implementationNaming.ClassName}${aggregateNaming.ClassName}Repository
|
|
283
|
+
})
|
|
284
|
+
`.trim());
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
const scaffoldNewApplicationPort = async (boundedContextFolder)=>{
|
|
289
|
+
const name = await input({
|
|
290
|
+
message: 'Name: '
|
|
291
|
+
});
|
|
292
|
+
const naming = new UnknownFormatNaming(name);
|
|
293
|
+
const wiringFolder = boundedContextFolder.subitem([
|
|
294
|
+
'wiring',
|
|
295
|
+
'application',
|
|
296
|
+
'ports',
|
|
297
|
+
naming.fileName
|
|
298
|
+
]);
|
|
299
|
+
const infraFolder = boundedContextFolder.subitem([
|
|
300
|
+
'wiring',
|
|
301
|
+
'infrastructure',
|
|
302
|
+
'application-ports'
|
|
303
|
+
]);
|
|
304
|
+
const applicationPortsFolder = boundedContextFolder.subitem([
|
|
305
|
+
'application',
|
|
306
|
+
'ports'
|
|
307
|
+
]);
|
|
308
|
+
applicationPortsFolder.createFile(`${naming.fileName}.ts`, `
|
|
309
|
+
export abstract class ${naming.ClassName} {
|
|
310
|
+
|
|
311
|
+
}
|
|
312
|
+
`.trim());
|
|
313
|
+
const implementationType = await input({
|
|
314
|
+
message: 'Adapter implementation type:'
|
|
315
|
+
});
|
|
316
|
+
const addTestImplementation = await prompts_confirm({
|
|
317
|
+
message: 'With test implementation'
|
|
318
|
+
});
|
|
319
|
+
let testImplementationType = '';
|
|
320
|
+
if (addTestImplementation) testImplementationType = await input({
|
|
321
|
+
message: 'Test adapter implementation type:',
|
|
322
|
+
default: 'mock'
|
|
323
|
+
});
|
|
324
|
+
const infrastructureFolder = boundedContextFolder.subitem([
|
|
325
|
+
'infrastructure',
|
|
326
|
+
'application-adapters'
|
|
327
|
+
]);
|
|
328
|
+
const implementationTypes = [
|
|
329
|
+
implementationType,
|
|
330
|
+
testImplementationType
|
|
331
|
+
].filter((x)=>!!x);
|
|
332
|
+
for (const implementation of implementationTypes){
|
|
333
|
+
const currentNaming = new UnknownFormatNaming(`${implementation}-${naming.fileName}`);
|
|
334
|
+
infrastructureFolder.subitem([
|
|
335
|
+
implementation
|
|
336
|
+
]).createFile(`${currentNaming.fileName}.ts`, `
|
|
337
|
+
import { ${naming.ClassName} } from '../../../application/ports/${naming.fileName}'
|
|
338
|
+
|
|
339
|
+
export class ${currentNaming.ClassName} extends ${naming.ClassName} {
|
|
340
|
+
|
|
341
|
+
}
|
|
342
|
+
`);
|
|
343
|
+
if (ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire')) infraFolder.subitem([
|
|
344
|
+
implementation
|
|
345
|
+
]).createFile(`wire-${currentNaming.fileName}.ts`, `
|
|
346
|
+
import { wireClass } from '@domain-first/wire'
|
|
347
|
+
import { ${currentNaming.ClassName} } from '../../../../infrastructure/application-adapters/${implementation}/${currentNaming.fileName}'
|
|
348
|
+
|
|
349
|
+
export const wire${currentNaming.ClassName} = wireClass(
|
|
350
|
+
${currentNaming.ClassName},
|
|
351
|
+
[]
|
|
352
|
+
)
|
|
353
|
+
`.trim());
|
|
354
|
+
}
|
|
355
|
+
if (ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire')) wiringFolder.createFile(`wire-${naming.fileName}.ts`, `
|
|
356
|
+
import { envBranchedWire } from '../../../../../${'shared' === boundedContextFolder.name ? '' : '../'}shared/wiring/env-branched-wire'
|
|
357
|
+
${implementationTypes.map((x)=>({
|
|
358
|
+
naming: new UnknownFormatNaming(`${x}-${naming.fileName}`),
|
|
359
|
+
implementation: x
|
|
360
|
+
})).map(({ naming: { ClassName, fileName }, implementation })=>`import { wire${ClassName} } from '../../../infrastructure/application-ports/${implementation}/wire-${fileName}'`).join('\n')}
|
|
361
|
+
|
|
362
|
+
export const wire${naming.ClassName} = envBranchedWire({
|
|
363
|
+
test: wire${new UnknownFormatNaming(`${implementationTypes.at(-1)}-${naming.fileName}`).ClassName},
|
|
364
|
+
development: wire${new UnknownFormatNaming(`${implementationTypes.at(0)}-${naming.fileName}`).ClassName},
|
|
365
|
+
production: wire${new UnknownFormatNaming(`${implementationTypes.at(0)}-${naming.fileName}`).ClassName}
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
`.trim());
|
|
369
|
+
};
|
|
370
|
+
const scaffoldNewBoundedContextUseCase = async ()=>{
|
|
371
|
+
const name = await input({
|
|
372
|
+
message: 'Name:',
|
|
373
|
+
validate: (x)=>!!x.length
|
|
374
|
+
});
|
|
375
|
+
const { path } = new ProjectRoot();
|
|
376
|
+
const rootFolder = new Folder(path);
|
|
377
|
+
const folder = rootFolder.subitem([
|
|
378
|
+
ConfigFile.Instance.data.rootFolder,
|
|
379
|
+
'bounded-contexts',
|
|
380
|
+
name
|
|
381
|
+
]);
|
|
382
|
+
if (ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/errors')) folder.subitem([
|
|
383
|
+
'domain',
|
|
384
|
+
'errors'
|
|
385
|
+
]).createFile('index.ts', `
|
|
386
|
+
import { errorNamespace } from '@domain-first/errors'
|
|
387
|
+
|
|
388
|
+
export const ${new UnknownFormatNaming(name).ClassName}Errors = errorNamespace('${name}')
|
|
389
|
+
`.trim());
|
|
390
|
+
folder.createIfNotExisted();
|
|
391
|
+
};
|
|
392
|
+
const scaffoldNewCommand = async (boundedContextFolder)=>{
|
|
393
|
+
const commandsFolder = boundedContextFolder.subitem([
|
|
394
|
+
'application',
|
|
395
|
+
'commands'
|
|
396
|
+
]);
|
|
397
|
+
const infrastructureFolder = boundedContextFolder.subitem([
|
|
398
|
+
'infrastructure',
|
|
399
|
+
'commands'
|
|
400
|
+
]);
|
|
401
|
+
const commandName = await input({
|
|
402
|
+
message: 'Name: '
|
|
403
|
+
});
|
|
404
|
+
const naming = new UnknownFormatNaming(commandName);
|
|
405
|
+
const implementationType = await input({
|
|
406
|
+
message: 'Command infrastructure implementation type:',
|
|
407
|
+
default: ConfigFile.Instance.data.defaultPersistenceLayerImplementation
|
|
408
|
+
});
|
|
409
|
+
let addTestImplementation = false;
|
|
410
|
+
let testImplementationType = '';
|
|
411
|
+
addTestImplementation = await prompts_confirm({
|
|
412
|
+
message: 'Add a test implementation for Command'
|
|
413
|
+
});
|
|
414
|
+
if (addTestImplementation) testImplementationType = await input({
|
|
415
|
+
message: 'Test Command infrastructure implementation type:',
|
|
416
|
+
default: 'in-memory'
|
|
417
|
+
});
|
|
418
|
+
const implementationTypes = [
|
|
419
|
+
implementationType,
|
|
420
|
+
testImplementationType
|
|
421
|
+
].filter((x)=>!!x);
|
|
422
|
+
for (const implementation of implementationTypes){
|
|
423
|
+
const formatNaming = new UnknownFormatNaming(implementation);
|
|
424
|
+
infrastructureFolder.subitem([
|
|
425
|
+
formatNaming.fileName
|
|
426
|
+
]).createFile(`${formatNaming.fileName}-${naming.fileName}-command.ts`, ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/handlers') ? `
|
|
427
|
+
import { defineHandler } from '@domain-first/handlers'
|
|
428
|
+
import { ${naming.ClassName}Command } from '../../../application/commands/${naming.fileName}-command'
|
|
429
|
+
|
|
430
|
+
export class ${formatNaming.ClassName}${naming.ClassName}Command implements ${naming.ClassName}Command {
|
|
431
|
+
constructor() {}
|
|
432
|
+
|
|
433
|
+
handle = defineHandler({
|
|
434
|
+
inputSchema: ${naming.ClassName}Command.inputSchema,
|
|
435
|
+
outputSchema: ${naming.ClassName}Command.outputSchema,
|
|
436
|
+
handler: async (input) => {
|
|
437
|
+
return {}
|
|
438
|
+
}
|
|
439
|
+
})
|
|
440
|
+
}
|
|
441
|
+
`.trim() : `
|
|
442
|
+
import { ${naming.ClassName}Command } from '../../../domain/commands/${naming.fileName}-command'
|
|
443
|
+
|
|
444
|
+
export class ${formatNaming.ClassName}${naming.ClassName}Command implements ${naming.ClassName}Command {
|
|
445
|
+
|
|
446
|
+
}
|
|
447
|
+
`.trim());
|
|
448
|
+
if (ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire')) {
|
|
449
|
+
const wiringFolder = boundedContextFolder.subitem([
|
|
450
|
+
'wiring',
|
|
451
|
+
'infrastructure',
|
|
452
|
+
'commands',
|
|
453
|
+
formatNaming.fileName
|
|
454
|
+
]);
|
|
455
|
+
wiringFolder.createFile(`wire-${formatNaming.fileName}-${naming.fileName}-command.ts`, `
|
|
456
|
+
import { wireClass } from '@domain-first/wire'
|
|
457
|
+
import { ${formatNaming.ClassName}${naming.ClassName}Command } from '../../../../infrastructure/commands/${formatNaming.fileName}/${formatNaming.fileName}-${naming.fileName}-command'
|
|
458
|
+
|
|
459
|
+
export const wire${formatNaming.ClassName}${naming.ClassName}Command = wireClass(
|
|
460
|
+
${formatNaming.ClassName}${naming.ClassName}Command,
|
|
461
|
+
[]
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
`.trim());
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/handlers')) commandsFolder.createFile(`${naming.fileName}-command.ts`, `
|
|
468
|
+
import type { Handler } from '@domain-first/handlers'
|
|
469
|
+
|
|
470
|
+
export abstract class ${naming.ClassName}Command {
|
|
471
|
+
static inputSchema = {}
|
|
472
|
+
static outputSchema = {}
|
|
473
|
+
|
|
474
|
+
abstract handle: Handler<typeof ${naming.ClassName}Command.inputSchema, typeof ${naming.ClassName}Command.outputSchema>
|
|
475
|
+
}
|
|
476
|
+
`.trim());
|
|
477
|
+
else commandsFolder.createFile(`${naming.fileName}-command.ts`, `
|
|
478
|
+
export abstract class ${naming.ClassName}Command {
|
|
479
|
+
|
|
480
|
+
}`.trim());
|
|
481
|
+
if (ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire')) {
|
|
482
|
+
const implementationNaming = new UnknownFormatNaming(implementationType);
|
|
483
|
+
const testImplementationNaming = new UnknownFormatNaming(testImplementationType);
|
|
484
|
+
boundedContextFolder.subitem([
|
|
485
|
+
'wiring',
|
|
486
|
+
'application',
|
|
487
|
+
'commands'
|
|
488
|
+
]).createFile(`wire-${naming.fileName}-command.ts`, `
|
|
489
|
+
import { envBranchedWire } from '../../../../../shared/wiring/env-branched-wire'
|
|
490
|
+
${implementationTypes.map((x)=>new UnknownFormatNaming(x)).map(({ ClassName, fileName })=>`import { wire${ClassName}${naming.ClassName}Command } from '../../infrastructure/commands/${fileName}/wire-${fileName}-${naming.fileName}-command'`).join('\n')}
|
|
491
|
+
|
|
492
|
+
export const wire${naming.ClassName}Command = envBranchedWire({
|
|
493
|
+
test: wire${addTestImplementation ? testImplementationNaming.ClassName : implementationNaming.ClassName}${naming.ClassName}Command,
|
|
494
|
+
development: wire${implementationNaming.ClassName}${naming.ClassName}Command,
|
|
495
|
+
production: wire${implementationNaming.ClassName}${naming.ClassName}Command
|
|
496
|
+
})
|
|
497
|
+
`.trim());
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
const scaffoldNewDomainService = async (boundedContextFolder)=>{
|
|
501
|
+
const portName = await input({
|
|
502
|
+
message: 'Name:'
|
|
503
|
+
});
|
|
504
|
+
const naming = new UnknownFormatNaming(portName);
|
|
505
|
+
boundedContextFolder.subitem([
|
|
506
|
+
'domain',
|
|
507
|
+
'services'
|
|
508
|
+
]).createFile(`${naming.fileName}-service.ts`, `
|
|
509
|
+
export class ${naming.ClassName}Service {
|
|
510
|
+
|
|
511
|
+
}
|
|
512
|
+
`.trim());
|
|
513
|
+
if (ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire')) boundedContextFolder.subitem([
|
|
514
|
+
'wiring',
|
|
515
|
+
'domain',
|
|
516
|
+
'services'
|
|
517
|
+
]).createFile(`wire-${naming.fileName}-service.ts`, `
|
|
518
|
+
import { wireClass } from '@domain-first/wire'
|
|
519
|
+
import { ${naming.ClassName}Service } from '../../../domain/services/${naming.fileName}-service'
|
|
520
|
+
|
|
521
|
+
export const wire${naming.ClassName}Service = wireClass(
|
|
522
|
+
${naming.ClassName}Service,
|
|
523
|
+
[]
|
|
524
|
+
)
|
|
525
|
+
`.trim());
|
|
526
|
+
};
|
|
527
|
+
const scaffoldNewErrorUseCase = async (boundedContextFolder)=>{
|
|
528
|
+
const currentErrorFolder = boundedContextFolder.subitem([
|
|
529
|
+
'domain',
|
|
530
|
+
'errors'
|
|
531
|
+
]);
|
|
532
|
+
const currentErrorNamespaces = currentErrorFolder.content.fileNames.filter((x)=>'index' !== x);
|
|
533
|
+
const variants = [
|
|
534
|
+
'New error without namespace',
|
|
535
|
+
...currentErrorNamespaces.map((x)=>`New error in namespace ${x}`),
|
|
536
|
+
'New namespace'
|
|
537
|
+
];
|
|
538
|
+
const selectedVariant = await prompts_select({
|
|
539
|
+
choices: variants,
|
|
540
|
+
message: 'Action:'
|
|
541
|
+
});
|
|
542
|
+
if ('New error without namespace' === selectedVariant) {
|
|
543
|
+
const errorName = await input({
|
|
544
|
+
message: 'Error name:'
|
|
545
|
+
});
|
|
546
|
+
const naming = new UnknownFormatNaming(errorName);
|
|
547
|
+
currentErrorFolder.file('index.ts').addLine(`export const ${naming.ClassName}Error = ${new UnknownFormatNaming(boundedContextFolder.name).ClassName}Errors.define('${naming.fileName}')`, '\n\n');
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if ('New namespace' === selectedVariant) {
|
|
551
|
+
const namespaceName = await input({
|
|
552
|
+
message: 'Namespace name:'
|
|
553
|
+
});
|
|
554
|
+
const { ClassName, fileName } = new UnknownFormatNaming(namespaceName);
|
|
555
|
+
currentErrorFolder.createFile(`${fileName}.ts`, `
|
|
556
|
+
import { errorNamespace } from '@domain-first/errors'
|
|
557
|
+
|
|
558
|
+
const ${ClassName}Errors = errorNamespace('${boundedContextFolder.name}').subnamespace('${fileName}')
|
|
559
|
+
`);
|
|
560
|
+
currentErrorFolder.file('index.ts').addLine(`export * from './${fileName}'`);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const namespace = selectedVariant.substring(23);
|
|
564
|
+
const errorName = await input({
|
|
565
|
+
message: 'Error name:'
|
|
566
|
+
});
|
|
567
|
+
const { ClassName, fileName } = new UnknownFormatNaming(errorName);
|
|
568
|
+
currentErrorFolder.file(`${namespace}.ts`).addLine(`export const ${ClassName}Error = ${new UnknownFormatNaming(namespace).ClassName}Errors.define('${fileName}')`, '\n\n');
|
|
569
|
+
};
|
|
570
|
+
const scaffoldNewHandlersRestEndpoint = async (boundedContextFolder)=>{
|
|
571
|
+
const restPresentationFolder = boundedContextFolder.subitem([
|
|
572
|
+
'presentation',
|
|
573
|
+
'rest'
|
|
574
|
+
]);
|
|
575
|
+
const controllers = restPresentationFolder.content.subfolderNames;
|
|
576
|
+
let controller = await prompts_select({
|
|
577
|
+
message: 'Controller: ',
|
|
578
|
+
choices: [
|
|
579
|
+
...controllers,
|
|
580
|
+
'New controller'
|
|
581
|
+
]
|
|
582
|
+
});
|
|
583
|
+
if ('New controller' === controller) {
|
|
584
|
+
controller = await input({
|
|
585
|
+
message: 'Name: '
|
|
586
|
+
});
|
|
587
|
+
if (ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire')) boundedContextFolder.subitem([
|
|
588
|
+
'wiring',
|
|
589
|
+
'presentation',
|
|
590
|
+
'rest'
|
|
591
|
+
]).file('index.ts').addLine(`export * from './${controller}'`);
|
|
592
|
+
}
|
|
593
|
+
const method = await prompts_select({
|
|
594
|
+
message: 'Method:',
|
|
595
|
+
choices: [
|
|
596
|
+
'get',
|
|
597
|
+
'post',
|
|
598
|
+
'patch',
|
|
599
|
+
'delete',
|
|
600
|
+
'put'
|
|
601
|
+
]
|
|
602
|
+
});
|
|
603
|
+
const path = (await input({
|
|
604
|
+
message: 'Relative path (split by "/"):'
|
|
605
|
+
})).split('/').filter((x)=>!!x);
|
|
606
|
+
const controllerFolder = restPresentationFolder.subitem([
|
|
607
|
+
controller
|
|
608
|
+
]);
|
|
609
|
+
const nameBase = `${controller}-${path.length ? `${path.join('-')}-` : ''}${method}-endpoint`;
|
|
610
|
+
const endpointNaming = new UnknownFormatNaming(nameBase);
|
|
611
|
+
controllerFolder.createFile(`${endpointNaming.fileName}.ts`, `
|
|
612
|
+
import { EndpointGenerator } from '@domain-first/handlers-rest'
|
|
613
|
+
|
|
614
|
+
export class ${endpointNaming.ClassName} {
|
|
615
|
+
constructor(private readonly endpointGenerator: EndpointGenerator) {}
|
|
616
|
+
|
|
617
|
+
get logic() {
|
|
618
|
+
return () => {}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
get handle() {
|
|
622
|
+
return this.endpointGenerator(this.logic, {
|
|
623
|
+
route: { method: '${method}', path: ['${controller}'${path.length ? `, ${path.map((x)=>`'${x}'`).join(', ')}` : ''}] },
|
|
624
|
+
tags: ['${boundedContextFolder.name}: ${controller}'],
|
|
625
|
+
})
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
}
|
|
629
|
+
`.trim());
|
|
630
|
+
if (ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire')) {
|
|
631
|
+
const wiringFolder = boundedContextFolder.subitem([
|
|
632
|
+
'wiring',
|
|
633
|
+
'presentation',
|
|
634
|
+
'rest',
|
|
635
|
+
controller
|
|
636
|
+
]);
|
|
637
|
+
const wireNameBase = `wire-${nameBase}`;
|
|
638
|
+
const wireNaming = new UnknownFormatNaming(wireNameBase);
|
|
639
|
+
wiringFolder.createFile(`${wireNaming.fileName}.ts`, `
|
|
640
|
+
import { wireClass } from '@domain-first/wire'
|
|
641
|
+
import { ${endpointNaming.ClassName} } from '../../../../presentation/rest/${controller}/${endpointNaming.fileName}'
|
|
642
|
+
|
|
643
|
+
export const wire${endpointNaming.ClassName} = wireClass(
|
|
644
|
+
${endpointNaming.ClassName},
|
|
645
|
+
[]
|
|
646
|
+
)
|
|
647
|
+
`);
|
|
648
|
+
wiringFolder.file('index.ts').addLine(`export * from './${wireNaming.fileName}'`);
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
const scaffoldNewQuery = async (boundedContextFolder)=>{
|
|
652
|
+
const queriesFolder = boundedContextFolder.subitem([
|
|
653
|
+
'application',
|
|
654
|
+
'queries'
|
|
655
|
+
]);
|
|
656
|
+
const infrastructureFolder = boundedContextFolder.subitem([
|
|
657
|
+
'infrastructure',
|
|
658
|
+
'queries'
|
|
659
|
+
]);
|
|
660
|
+
const queryName = await input({
|
|
661
|
+
message: 'Name: '
|
|
662
|
+
});
|
|
663
|
+
const naming = new UnknownFormatNaming(queryName);
|
|
664
|
+
const implementationType = await input({
|
|
665
|
+
message: 'Query infrastructure implementation type:',
|
|
666
|
+
default: ConfigFile.Instance.data.defaultPersistenceLayerImplementation
|
|
667
|
+
});
|
|
668
|
+
let addTestImplementation = false;
|
|
669
|
+
let testImplementationType = '';
|
|
670
|
+
addTestImplementation = await prompts_confirm({
|
|
671
|
+
message: 'Add a test implementation for Query'
|
|
672
|
+
});
|
|
673
|
+
if (addTestImplementation) testImplementationType = await input({
|
|
674
|
+
message: 'Test Query infrastructure implementation type:',
|
|
675
|
+
default: 'in-memory'
|
|
676
|
+
});
|
|
677
|
+
const implementationTypes = [
|
|
678
|
+
implementationType,
|
|
679
|
+
testImplementationType
|
|
680
|
+
].filter((x)=>!!x);
|
|
681
|
+
for (const implementation of implementationTypes){
|
|
682
|
+
const formatNaming = new UnknownFormatNaming(implementation);
|
|
683
|
+
infrastructureFolder.subitem([
|
|
684
|
+
formatNaming.fileName
|
|
685
|
+
]).createFile(`${formatNaming.fileName}-${naming.fileName}-query.ts`, ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/handlers') ? `
|
|
686
|
+
import { defineHandler } from '@domain-first/handlers'
|
|
687
|
+
import { ${naming.ClassName}Query } from '../../../application/queries/${naming.fileName}-query'
|
|
688
|
+
|
|
689
|
+
export class ${formatNaming.ClassName}${naming.ClassName}Query implements ${naming.ClassName}Query {
|
|
690
|
+
constructor() {}
|
|
691
|
+
|
|
692
|
+
handle = defineHandler({
|
|
693
|
+
inputSchema: ${naming.ClassName}Query.inputSchema,
|
|
694
|
+
outputSchema: ${naming.ClassName}Query.outputSchema,
|
|
695
|
+
handler: async (input) => {
|
|
696
|
+
return {}
|
|
697
|
+
}
|
|
698
|
+
})
|
|
699
|
+
}
|
|
700
|
+
`.trim() : `
|
|
701
|
+
import { ${naming.ClassName}Query } from '../../../domain/queries/${naming.fileName}-query'
|
|
702
|
+
|
|
703
|
+
export class ${formatNaming.ClassName}${naming.ClassName}Query implements ${naming.ClassName}Query {
|
|
704
|
+
|
|
705
|
+
}
|
|
706
|
+
`.trim());
|
|
707
|
+
if (ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire')) {
|
|
708
|
+
const wiringFolder = boundedContextFolder.subitem([
|
|
709
|
+
'wiring',
|
|
710
|
+
'infrastructure',
|
|
711
|
+
'queries',
|
|
712
|
+
formatNaming.fileName
|
|
713
|
+
]);
|
|
714
|
+
wiringFolder.createFile(`wire-${formatNaming.fileName}-${naming.fileName}-query.ts`, `
|
|
715
|
+
import { wireClass } from '@domain-first/wire'
|
|
716
|
+
import { ${formatNaming.ClassName}${naming.ClassName}Query } from '../../../../infrastructure/queries/${formatNaming.fileName}/${formatNaming.fileName}-${naming.fileName}-query'
|
|
717
|
+
|
|
718
|
+
export const wire${formatNaming.ClassName}${naming.ClassName}Query = wireClass(
|
|
719
|
+
${formatNaming.ClassName}${naming.ClassName}Query,
|
|
720
|
+
[]
|
|
721
|
+
)
|
|
722
|
+
`.trim());
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
if (ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/handlers')) queriesFolder.createFile(`${naming.fileName}-query.ts`, `
|
|
726
|
+
import type { Handler } from '@domain-first/handlers'
|
|
727
|
+
|
|
728
|
+
export abstract class ${naming.ClassName}Query {
|
|
729
|
+
static inputSchema = {}
|
|
730
|
+
static outputSchema = {}
|
|
731
|
+
|
|
732
|
+
abstract handle: Handler<typeof ${naming.ClassName}Query.inputSchema, typeof ${naming.ClassName}Query.outputSchema>
|
|
733
|
+
}
|
|
734
|
+
`.trim());
|
|
735
|
+
else queriesFolder.createFile(`${naming.fileName}-query.ts`, `
|
|
736
|
+
export abstract class ${naming.ClassName}Query {
|
|
737
|
+
|
|
738
|
+
}`.trim());
|
|
739
|
+
if (ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire')) {
|
|
740
|
+
const implementationNaming = new UnknownFormatNaming(implementationType);
|
|
741
|
+
const testImplementationNaming = new UnknownFormatNaming(testImplementationType);
|
|
742
|
+
boundedContextFolder.subitem([
|
|
743
|
+
'wiring',
|
|
744
|
+
'application',
|
|
745
|
+
'queries'
|
|
746
|
+
]).createFile(`wire-${naming.fileName}-query.ts`, `
|
|
747
|
+
import { envBranchedWire } from '../../../../../shared/wiring/env-branched-wire'
|
|
748
|
+
${implementationTypes.map((x)=>new UnknownFormatNaming(x)).map(({ ClassName, fileName })=>`import { wire${ClassName}${naming.ClassName}Query } from '../../infrastructure/queries/${fileName}/wire-${fileName}-${naming.fileName}-query'`).join('\n')}
|
|
749
|
+
|
|
750
|
+
export const wire${naming.ClassName}Query = envBranchedWire({
|
|
751
|
+
test: wire${addTestImplementation ? testImplementationNaming.ClassName : implementationNaming.ClassName}${naming.ClassName}Query,
|
|
752
|
+
development: wire${implementationNaming.ClassName}${naming.ClassName}Query,
|
|
753
|
+
production: wire${implementationNaming.ClassName}${naming.ClassName}Query
|
|
754
|
+
})
|
|
755
|
+
`.trim());
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
const scaffoldNewUseCase = async (boundedContextFolder)=>{
|
|
759
|
+
const useCasesFolder = boundedContextFolder.subitem([
|
|
760
|
+
'application',
|
|
761
|
+
'use-cases'
|
|
762
|
+
]);
|
|
763
|
+
const useCaseName = await input({
|
|
764
|
+
message: 'Name: '
|
|
765
|
+
});
|
|
766
|
+
const naming = new UnknownFormatNaming(useCaseName);
|
|
767
|
+
useCasesFolder.createFile(`${naming.fileName}-use-case.ts`, ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/handlers') ? `
|
|
768
|
+
import { defineHandler } from '@domain-first/handlers'
|
|
769
|
+
|
|
770
|
+
export class ${naming.ClassName}UseCase {
|
|
771
|
+
constructor() {}
|
|
772
|
+
|
|
773
|
+
static inputSchema = {}
|
|
774
|
+
static outputSchema = {}
|
|
775
|
+
|
|
776
|
+
handle = defineHandler({
|
|
777
|
+
inputSchema: ${naming.ClassName}UseCase.inputSchema,
|
|
778
|
+
outputSchema: ${naming.ClassName}UseCase.outputSchema,
|
|
779
|
+
handler: async (input) => {
|
|
780
|
+
return {}
|
|
781
|
+
}
|
|
782
|
+
})
|
|
783
|
+
}
|
|
784
|
+
`.trim() : `
|
|
785
|
+
export class ${naming.ClassName}UseCase {
|
|
786
|
+
|
|
787
|
+
}
|
|
788
|
+
`.trim());
|
|
789
|
+
if (ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire')) boundedContextFolder.subitem([
|
|
790
|
+
'wiring',
|
|
791
|
+
'application',
|
|
792
|
+
'use-cases'
|
|
793
|
+
]).createFile(`wire-${naming.fileName}-use-case.ts`, `
|
|
794
|
+
import { wireClass } from '@domain-first/wire'
|
|
795
|
+
import { ${naming.ClassName}UseCase } from '../../../application/use-cases/${naming.fileName}-use-case'
|
|
796
|
+
|
|
797
|
+
export const wire${naming.ClassName}UseCase = wireClass(
|
|
798
|
+
${naming.ClassName}UseCase,
|
|
799
|
+
[]
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
`.trim());
|
|
803
|
+
};
|
|
804
|
+
const main = async ()=>{
|
|
805
|
+
if (!ConfigFile.exists) return void await initializeUseCase();
|
|
806
|
+
const rootPath = new ProjectRoot().path;
|
|
807
|
+
const currentBoundedContextOptions = new Folder(rootPath).subitem([
|
|
808
|
+
ConfigFile.Instance.data.rootFolder,
|
|
809
|
+
'bounded-contexts'
|
|
810
|
+
]).content.subfolderNames.map((name)=>({
|
|
811
|
+
name: `Bounded Context: ${name}`,
|
|
812
|
+
action: 'Scaffold in bounded context'
|
|
813
|
+
}));
|
|
814
|
+
const scaffoldNewBoundedContext = {
|
|
815
|
+
name: 'Scaffold new bounded context',
|
|
816
|
+
action: 'Scaffold new bounded context'
|
|
817
|
+
};
|
|
818
|
+
const options = [
|
|
819
|
+
...currentBoundedContextOptions,
|
|
820
|
+
{
|
|
821
|
+
name: 'Shared Layer',
|
|
822
|
+
action: 'Scaffold in bounded context'
|
|
823
|
+
},
|
|
824
|
+
scaffoldNewBoundedContext
|
|
825
|
+
];
|
|
826
|
+
const result = await prompts_select({
|
|
827
|
+
message: '@domain-first/project-structure',
|
|
828
|
+
choices: options.map((x)=>x.name)
|
|
829
|
+
});
|
|
830
|
+
const { action, name } = options.find((x)=>x.name === result);
|
|
831
|
+
switch(action){
|
|
832
|
+
case 'Scaffold new bounded context':
|
|
833
|
+
return void await scaffoldNewBoundedContextUseCase();
|
|
834
|
+
case 'Scaffold in bounded context':
|
|
835
|
+
{
|
|
836
|
+
const specificContext = name.startsWith('Bounded');
|
|
837
|
+
const boundedContextFolder = new Folder(new ProjectRoot().path).subitem(specificContext ? [
|
|
838
|
+
ConfigFile.Instance.data.rootFolder,
|
|
839
|
+
'bounded-contexts',
|
|
840
|
+
name.substring(17)
|
|
841
|
+
] : [
|
|
842
|
+
ConfigFile.Instance.data.rootFolder,
|
|
843
|
+
'shared'
|
|
844
|
+
]);
|
|
845
|
+
const layer = await prompts_select({
|
|
846
|
+
message: 'Layer:',
|
|
847
|
+
choices: [
|
|
848
|
+
'Domain',
|
|
849
|
+
'Application',
|
|
850
|
+
'Presentation'
|
|
851
|
+
]
|
|
852
|
+
});
|
|
853
|
+
if ('Domain' === layer) {
|
|
854
|
+
const operation = await prompts_select({
|
|
855
|
+
message: 'Domain layer operation:',
|
|
856
|
+
choices: specificContext ? [
|
|
857
|
+
'New Aggregate',
|
|
858
|
+
'Errors',
|
|
859
|
+
'New Service'
|
|
860
|
+
] : [
|
|
861
|
+
'Errors'
|
|
862
|
+
]
|
|
863
|
+
});
|
|
864
|
+
switch(operation){
|
|
865
|
+
case 'New Aggregate':
|
|
866
|
+
return void await scaffoldNewAggregateUseCase(boundedContextFolder);
|
|
867
|
+
case 'Errors':
|
|
868
|
+
return void await scaffoldNewErrorUseCase(boundedContextFolder);
|
|
869
|
+
case 'New Service':
|
|
870
|
+
return void await scaffoldNewDomainService(boundedContextFolder);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if ('Application' === layer) {
|
|
874
|
+
const operation = await prompts_select({
|
|
875
|
+
message: 'Application layer operation:',
|
|
876
|
+
choices: specificContext ? [
|
|
877
|
+
'New Query',
|
|
878
|
+
'New Command',
|
|
879
|
+
'New Use Case',
|
|
880
|
+
'New Port'
|
|
881
|
+
] : [
|
|
882
|
+
'New Port'
|
|
883
|
+
]
|
|
884
|
+
});
|
|
885
|
+
switch(operation){
|
|
886
|
+
case 'New Query':
|
|
887
|
+
return void await scaffoldNewQuery(boundedContextFolder);
|
|
888
|
+
case 'New Command':
|
|
889
|
+
return void await scaffoldNewCommand(boundedContextFolder);
|
|
890
|
+
case 'New Use Case':
|
|
891
|
+
return void await scaffoldNewUseCase(boundedContextFolder);
|
|
892
|
+
case 'New Port':
|
|
893
|
+
return void await scaffoldNewApplicationPort(boundedContextFolder);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
if ('Presentation' === layer) {
|
|
897
|
+
const action = await prompts_select({
|
|
898
|
+
message: 'Presentation layer operation:',
|
|
899
|
+
choices: specificContext ? [
|
|
900
|
+
ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/handlers-rest') ? 'New Handlers-REST Endpoint' : void 0
|
|
901
|
+
].filter((x)=>x) : [
|
|
902
|
+
''
|
|
903
|
+
]
|
|
904
|
+
});
|
|
905
|
+
switch(action){
|
|
906
|
+
case 'New Handlers-REST Endpoint':
|
|
907
|
+
return void await scaffoldNewHandlersRestEndpoint(boundedContextFolder);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
default:
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
main();
|