nestcraftx 0.2.4 → 0.2.6
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/.gitattributes +6 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
- package/.github/ISSUE_TEMPLATE/pull_request_template.md +24 -0
- package/CHANGELOG.fr.md +97 -97
- package/CHANGELOG.md +98 -98
- package/CLI_USAGE.fr.md +331 -331
- package/CLI_USAGE.md +364 -364
- package/DEMO.fr.md +292 -292
- package/DEMO.md +294 -294
- package/LICENSE +21 -21
- package/MIGRATION_GUIDE.fr.md +127 -127
- package/MIGRATION_GUIDE.md +124 -124
- package/QUICK_START.fr.md +152 -152
- package/QUICK_START.md +169 -169
- package/README.fr.md +653 -659
- package/SECURITY.md +10 -0
- package/bin/nestcraft.js +84 -64
- package/commands/demo.js +333 -330
- package/commands/generate.js +93 -0
- package/commands/generateConf.js +91 -0
- package/commands/help.js +78 -78
- package/commands/info.js +48 -48
- package/commands/new.js +338 -335
- package/commands/start.js +19 -19
- package/commands/test.js +7 -7
- package/package.json +41 -41
- package/readme.md +638 -643
- package/utils/cliParser.js +133 -76
- package/utils/colors.js +62 -62
- package/utils/configs/configureDocker.js +120 -120
- package/utils/configs/setupCleanArchitecture.js +563 -557
- package/utils/configs/setupLightArchitecture.js +701 -660
- package/utils/envGenerator.js +122 -122
- package/utils/file-utils/packageJsonUtils.js +49 -55
- package/utils/file-utils/saveProjectConfig.js +36 -0
- package/utils/fullModeInput.js +607 -607
- package/utils/generators/application/dtoUpdater.js +54 -0
- package/utils/generators/cleanModuleGenerator.js +475 -0
- package/utils/generators/database/setupDatabase.js +31 -0
- package/utils/generators/domain/entityUpdater.js +78 -0
- package/utils/generators/infrastructure/mapperUpdater.js +65 -0
- package/utils/generators/lightModuleGenerator.js +131 -0
- package/utils/generators/relation/relation.engine.js +64 -0
- package/utils/interactive/askEntityInputs.js +165 -0
- package/utils/lightModeInput.js +460 -460
- package/utils/loggers/logError.js +7 -7
- package/utils/loggers/logInfo.js +7 -7
- package/utils/loggers/logSuccess.js +7 -7
- package/utils/loggers/logWarning.js +7 -7
- package/utils/setups/orms/typeOrmSetup.js +630 -630
- package/utils/setups/projectSetup.js +46 -46
- package/utils/setups/setupAuth.js +973 -926
- package/utils/setups/setupDatabase.js +75 -75
- package/utils/setups/setupLogger.js +69 -59
- package/utils/setups/setupMongoose.js +377 -432
- package/utils/setups/setupPrisma.js +802 -630
- package/utils/setups/setupSwagger.js +97 -88
- package/utils/shell.js +32 -32
- package/utils/spinner.js +57 -57
- package/utils/systemCheck.js +124 -124
- package/utils/userInput.js +421 -421
- package/utils/utils.js +2197 -1762
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const { updateFile } = require("../../userInput");
|
|
2
|
+
|
|
3
|
+
// src/utils/generators/infrastructure/mapperUpdater.js
|
|
4
|
+
async function patchMapperWithRelation(source, targetName, mode = "full") {
|
|
5
|
+
const targetLow = targetName.toLowerCase();
|
|
6
|
+
|
|
7
|
+
if (mode === "full") {
|
|
8
|
+
return patchCleanMapper(source, targetLow);
|
|
9
|
+
} else {
|
|
10
|
+
return patchLightRepositoryMapper(source, targetLow);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function patchCleanMapper(source, targetName) {
|
|
15
|
+
const mapperPath = `src/${source.toLowerCase()}/infrastructure/mappers/${source.toLowerCase()}.mapper.ts`;
|
|
16
|
+
const targetLow = targetName.toLowerCase();
|
|
17
|
+
|
|
18
|
+
// 1. ToDomain : Ajouter l'argument au constructeur de l'entité
|
|
19
|
+
await updateFile({
|
|
20
|
+
path: mapperPath,
|
|
21
|
+
pattern:
|
|
22
|
+
/(toDomain\(data: any\): .* \{[\s\S]*?return new .*?\()([\s\S]*?)(\);)/m,
|
|
23
|
+
replacement: `$1$2 data.${targetLow}Id$3`,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// 2. ToPersistence : Ajouter le champ pour Prisma
|
|
27
|
+
await updateFile({
|
|
28
|
+
path: mapperPath,
|
|
29
|
+
pattern: /(toPersistence\(dto: .* \{[\s\S]*?return \{)/m,
|
|
30
|
+
replacement: `$1\n ${targetLow}Id: dto.${targetLow}Id,`,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// 3. PATCH toUpdatePersistence → add FK update condition
|
|
34
|
+
await updateFile({
|
|
35
|
+
path: mapperPath,
|
|
36
|
+
pattern: /(toUpdatePersistence\(dto: .*?\{\s*const data: any = \{\};)/m,
|
|
37
|
+
replacement: `$1\n\n if (dto.${targetLow}Id !== undefined) data.${targetLow}Id = dto.${targetLow}Id;`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* LIGHT MODE → patch repository.ts (toEntity + create/update)
|
|
43
|
+
*/
|
|
44
|
+
async function patchLightRepositoryMapper(entityName, targetLow) {
|
|
45
|
+
const repoPath = `src/${entityName.toLowerCase()}/repositories/${entityName.toLowerCase()}.repository.ts`;
|
|
46
|
+
// const targetRepoPath = `src/${targetLow.toLowerCase()}/repositories/${targetLow.toLowerCase()}.repository.ts`;
|
|
47
|
+
|
|
48
|
+
// 1. Patch toEntity constructor
|
|
49
|
+
/* await updateFile({
|
|
50
|
+
path: repoPath,
|
|
51
|
+
pattern:
|
|
52
|
+
/(private toEntity\(raw: any\): .*?\{\s*return new .*?\([\s\S]*?)(\);\s*\})/m,
|
|
53
|
+
replacement: `$1,\n raw.${targetLow}Id$2`,
|
|
54
|
+
}); */
|
|
55
|
+
|
|
56
|
+
// 2. Patch toEntity constructor in target
|
|
57
|
+
await updateFile({
|
|
58
|
+
path: repoPath,
|
|
59
|
+
pattern:
|
|
60
|
+
/(private toEntity\(raw: any\): .*?\{\s*return new .*?\([\s\S]*?)(\);\s*\})/m,
|
|
61
|
+
replacement: `$1\n raw.${targetLow}Id$2`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { patchMapperWithRelation };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
const {
|
|
2
|
+
createDirectory,
|
|
3
|
+
createFile,
|
|
4
|
+
safeUpdateAppModule,
|
|
5
|
+
capitalize,
|
|
6
|
+
decapitalize,
|
|
7
|
+
} = require("../userInput");
|
|
8
|
+
const { generateEntityFileContent, generateDto } = require("../utils");
|
|
9
|
+
const {
|
|
10
|
+
generateLightRepository,
|
|
11
|
+
generateLightService,
|
|
12
|
+
generateLightController,
|
|
13
|
+
generateLightModule,
|
|
14
|
+
} = require("../configs/setupLightArchitecture");
|
|
15
|
+
const { logSuccess } = require("../loggers/logSuccess");
|
|
16
|
+
const { logError } = require("../loggers/logError");
|
|
17
|
+
const setupDatabase = require("./database/setupDatabase");
|
|
18
|
+
const { logInfo } = require("../loggers/logInfo");
|
|
19
|
+
const { updateExistingEntityRelation } = require("./domain/entityUpdater");
|
|
20
|
+
const { applyRelationPatches } = require("./relation/relation.engine");
|
|
21
|
+
|
|
22
|
+
async function lightModuleGenerator(entity, config) {
|
|
23
|
+
try {
|
|
24
|
+
const entityNameCap = capitalize(entity.name);
|
|
25
|
+
const entityNameLow = decapitalize(entity.name);
|
|
26
|
+
const entityPath = `src/${entityNameLow}`;
|
|
27
|
+
const mode = "light";
|
|
28
|
+
|
|
29
|
+
logInfo(`🚀 Generating light module for ${entityNameCap}`);
|
|
30
|
+
|
|
31
|
+
// 1️⃣ Créer les dossiers
|
|
32
|
+
await createDirectory(`${entityPath}/entities`);
|
|
33
|
+
await createDirectory(`${entityPath}/dtos`);
|
|
34
|
+
await createDirectory(`${entityPath}/services`);
|
|
35
|
+
await createDirectory(`${entityPath}/repositories`);
|
|
36
|
+
await createDirectory(`${entityPath}/controllers`);
|
|
37
|
+
|
|
38
|
+
// 2️⃣ Générer l'entité
|
|
39
|
+
const entityContent = await generateEntityFileContent(entity, "light");
|
|
40
|
+
await createFile({
|
|
41
|
+
path: `${entityPath}/entities/${entityNameLow}.entity.ts`,
|
|
42
|
+
contente: entityContent,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// 3️⃣ Générer les DTOs
|
|
46
|
+
const dtoContent = await generateDto(
|
|
47
|
+
entity,
|
|
48
|
+
config.swagger,
|
|
49
|
+
false,
|
|
50
|
+
"light",
|
|
51
|
+
);
|
|
52
|
+
await createFile({
|
|
53
|
+
path: `${entityPath}/dtos/${entityNameLow}.dto.ts`,
|
|
54
|
+
contente: dtoContent,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// 4️⃣ Générer le repository
|
|
58
|
+
const repositoryContent = generateLightRepository(
|
|
59
|
+
entityNameCap,
|
|
60
|
+
entityNameLow,
|
|
61
|
+
config.orm,
|
|
62
|
+
entity,
|
|
63
|
+
);
|
|
64
|
+
await createFile({
|
|
65
|
+
path: `${entityPath}/repositories/${entityNameLow}.repository.ts`,
|
|
66
|
+
contente: repositoryContent,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// 5️⃣ Générer le service
|
|
70
|
+
const serviceContent = generateLightService(entityNameCap, entityNameLow);
|
|
71
|
+
await createFile({
|
|
72
|
+
path: `${entityPath}/services/${entityNameLow}.service.ts`,
|
|
73
|
+
contente: serviceContent,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// 6️⃣ Générer le controller
|
|
77
|
+
const controllerContent = generateLightController(
|
|
78
|
+
entityNameCap,
|
|
79
|
+
entityNameLow,
|
|
80
|
+
config.swagger,
|
|
81
|
+
);
|
|
82
|
+
await createFile({
|
|
83
|
+
path: `${entityPath}/controllers/${entityNameLow}.controller.ts`,
|
|
84
|
+
contente: controllerContent,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// 7️⃣ Générer le module
|
|
88
|
+
const moduleContent = generateLightModule(
|
|
89
|
+
entityNameCap,
|
|
90
|
+
entityNameLow,
|
|
91
|
+
entityPath,
|
|
92
|
+
config.orm,
|
|
93
|
+
config.auth,
|
|
94
|
+
);
|
|
95
|
+
await createFile({
|
|
96
|
+
path: `${entityPath}/${entityNameLow}.module.ts`,
|
|
97
|
+
contente: moduleContent,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ÉTAPE RELATIONS : Patching des fichiers si une relation existe
|
|
101
|
+
if (entity.relation) {
|
|
102
|
+
const { target, type } = entity.relation;
|
|
103
|
+
logInfo(
|
|
104
|
+
`🔗 Linking ${entityNameCap} with ${capitalize(target)} (${type})...`,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
await updateExistingEntityRelation(target, entity.name, type);
|
|
108
|
+
|
|
109
|
+
await applyRelationPatches(
|
|
110
|
+
entity.name,
|
|
111
|
+
target,
|
|
112
|
+
type,
|
|
113
|
+
config.swagger,
|
|
114
|
+
mode,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 8️ Auto-enregistrement dans AppModule
|
|
119
|
+
await safeUpdateAppModule(entityNameLow);
|
|
120
|
+
|
|
121
|
+
// 9
|
|
122
|
+
await setupDatabase(config, entity);
|
|
123
|
+
|
|
124
|
+
logSuccess(`✨ Light module ${entityNameCap} generated successfully!`);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
logError(`Error generating light module: ${error}`);
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = lightModuleGenerator;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const { patchDtoWithRelation } = require("../application/dtoUpdater");
|
|
2
|
+
const { patchMapperWithRelation } = require("../infrastructure/mapperUpdater");
|
|
3
|
+
|
|
4
|
+
function resolveForeignKeyOwner(newEntityName, target, relationType) {
|
|
5
|
+
switch (relationType) {
|
|
6
|
+
case "1-n":
|
|
7
|
+
// FK on the N side → target
|
|
8
|
+
return null /* {
|
|
9
|
+
ownerEntity: target,
|
|
10
|
+
relatedEntity: newEntityName,
|
|
11
|
+
} */;
|
|
12
|
+
|
|
13
|
+
case "n-1":
|
|
14
|
+
// FK on the N side → source
|
|
15
|
+
return {
|
|
16
|
+
ownerEntity: target,
|
|
17
|
+
relatedEntity: newEntityName,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
case "1-1":
|
|
21
|
+
// Convention: FK on source side
|
|
22
|
+
return {
|
|
23
|
+
ownerEntity: target,
|
|
24
|
+
relatedEntity: newEntityName,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
case "n-n":
|
|
28
|
+
// Pivot table → no FK field
|
|
29
|
+
return null;
|
|
30
|
+
|
|
31
|
+
default:
|
|
32
|
+
throw new Error(`❌ Unknown relation type: ${relationType}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function applyRelationPatches(
|
|
37
|
+
newEntityName,
|
|
38
|
+
target,
|
|
39
|
+
relationType,
|
|
40
|
+
useSwagger,
|
|
41
|
+
mode = "full",
|
|
42
|
+
) {
|
|
43
|
+
const owner = resolveForeignKeyOwner(newEntityName, target, relationType);
|
|
44
|
+
|
|
45
|
+
if (!owner) {
|
|
46
|
+
// console.log("ℹ️ n-n relation detected → pivot table, skipping DTO patch");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Patch DTO
|
|
51
|
+
await patchDtoWithRelation(
|
|
52
|
+
owner.ownerEntity,
|
|
53
|
+
owner.relatedEntity,
|
|
54
|
+
useSwagger,
|
|
55
|
+
mode,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
await patchMapperWithRelation(owner.ownerEntity, owner.relatedEntity, mode);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
resolveForeignKeyOwner,
|
|
63
|
+
applyRelationPatches,
|
|
64
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// src/utils/interactive/askEntityInputs.js
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const readline = require("readline-sync");
|
|
5
|
+
const inquirer = require("inquirer");
|
|
6
|
+
const { info, success } = require("../colors");
|
|
7
|
+
const { logWarning } = require("../loggers/logWarning");
|
|
8
|
+
const { capitalize } = require("../userInput");
|
|
9
|
+
const actualInquirer = inquirer.default || inquirer;
|
|
10
|
+
|
|
11
|
+
async function askEntityInputs(targetName) {
|
|
12
|
+
const entity = { name: targetName, fields: [], relation: null };
|
|
13
|
+
|
|
14
|
+
console.log(
|
|
15
|
+
`\n${info("[ENTITY DESIGN]")} Define fields for "${targetName}" :`,
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
while (true) {
|
|
19
|
+
let fname = readline.question(" Field name (leave empty to finish) : ");
|
|
20
|
+
if (!fname) break;
|
|
21
|
+
|
|
22
|
+
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(fname)) {
|
|
23
|
+
logWarning("Invalid field name.");
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const baseTypeChoices = [
|
|
28
|
+
"string",
|
|
29
|
+
"text",
|
|
30
|
+
"number",
|
|
31
|
+
"boolean",
|
|
32
|
+
"Date",
|
|
33
|
+
"uuid",
|
|
34
|
+
"json",
|
|
35
|
+
"enum",
|
|
36
|
+
"array",
|
|
37
|
+
"object",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const typeAnswer = await actualInquirer.prompt([
|
|
41
|
+
{
|
|
42
|
+
type: "list",
|
|
43
|
+
name: "ftype",
|
|
44
|
+
message: `Type for "${fname}"`,
|
|
45
|
+
choices: baseTypeChoices,
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
let ftype = typeAnswer.ftype;
|
|
50
|
+
|
|
51
|
+
// Logique de raffinement des types (identique à ton script original)
|
|
52
|
+
if (ftype === "array") {
|
|
53
|
+
const inner = await actualInquirer.prompt([
|
|
54
|
+
{
|
|
55
|
+
type: "list",
|
|
56
|
+
name: "innerType",
|
|
57
|
+
message: `Type of elements for "${fname}[]"`,
|
|
58
|
+
choices: baseTypeChoices.filter(
|
|
59
|
+
(c) => c !== "array" && c !== "object",
|
|
60
|
+
),
|
|
61
|
+
},
|
|
62
|
+
]);
|
|
63
|
+
ftype = `${inner.innerType}[]`;
|
|
64
|
+
} else if (ftype === "enum") {
|
|
65
|
+
ftype = capitalize(fname) + "Enum";
|
|
66
|
+
} else if (ftype === "object") {
|
|
67
|
+
const obj = await actualInquirer.prompt([
|
|
68
|
+
{
|
|
69
|
+
type: "input",
|
|
70
|
+
name: "val",
|
|
71
|
+
message: "Complex type name :",
|
|
72
|
+
default: "json",
|
|
73
|
+
},
|
|
74
|
+
]);
|
|
75
|
+
ftype = capitalize(obj.val);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
entity.fields.push({ name: fname, type: ftype });
|
|
79
|
+
console.log(` Type for "${fname}" : ${ftype} ${success("[✓]")}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const relationData = await askRelationInputs(entity.name);
|
|
83
|
+
entity.relation = relationData;
|
|
84
|
+
|
|
85
|
+
return entity;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function askRelationInputs(newEntityName) {
|
|
89
|
+
const srcPath = path.join(process.cwd(), "src");
|
|
90
|
+
|
|
91
|
+
// Sécurité : Vérifie si le dossier src existe
|
|
92
|
+
if (!fs.existsSync(srcPath)) return null;
|
|
93
|
+
|
|
94
|
+
const blacklist = [
|
|
95
|
+
"common",
|
|
96
|
+
"prisma",
|
|
97
|
+
"auth",
|
|
98
|
+
"mail",
|
|
99
|
+
"infrastructure",
|
|
100
|
+
"shared",
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
const modules = fs.readdirSync(srcPath).filter((folder) => {
|
|
104
|
+
const fullPath = path.join(srcPath, folder);
|
|
105
|
+
|
|
106
|
+
// 1. D'abord vérifier si c'est un dossier (important pour éviter ENOTDIR)
|
|
107
|
+
const isDirectory = fs.lstatSync(fullPath).isDirectory();
|
|
108
|
+
if (!isDirectory) return false;
|
|
109
|
+
|
|
110
|
+
// 2. Ensuite les autres filtres
|
|
111
|
+
const isNotBlacklisted = !blacklist.includes(folder.toLowerCase());
|
|
112
|
+
const isNotCurrent = folder.toLowerCase() !== newEntityName.toLowerCase();
|
|
113
|
+
|
|
114
|
+
// 3. Enfin vérifier s'il y a un fichier .module.ts à l'intérieur
|
|
115
|
+
const hasModuleFile = fs
|
|
116
|
+
.readdirSync(fullPath)
|
|
117
|
+
.some((file) => file.endsWith(".module.ts"));
|
|
118
|
+
|
|
119
|
+
return isNotBlacklisted && isNotCurrent && hasModuleFile;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (modules.length === 0) {
|
|
123
|
+
console.log(info("\n[INFO] No existing modules found for relations."));
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const { wantRelation } = await actualInquirer.prompt([
|
|
128
|
+
{
|
|
129
|
+
type: "confirm",
|
|
130
|
+
name: "wantRelation",
|
|
131
|
+
message: "Add relation with an existing module?",
|
|
132
|
+
default: false,
|
|
133
|
+
},
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
if (!wantRelation) return null;
|
|
137
|
+
|
|
138
|
+
// On demande les détails de la relation
|
|
139
|
+
const relation = await actualInquirer.prompt([
|
|
140
|
+
{
|
|
141
|
+
type: "list",
|
|
142
|
+
name: "target",
|
|
143
|
+
message: "Select the target module:",
|
|
144
|
+
choices: modules,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
type: "list",
|
|
148
|
+
name: "type",
|
|
149
|
+
message: "Relation type (NewEntity -> Target):",
|
|
150
|
+
choices: [
|
|
151
|
+
{ name: "1-n (One-to-Many: This has many of those)", value: "1-n" },
|
|
152
|
+
{
|
|
153
|
+
name: "n-1 (Many-to-One: This belongs to one of those)",
|
|
154
|
+
value: "n-1",
|
|
155
|
+
},
|
|
156
|
+
{ name: "1-1 (One-to-One)", value: "1-1" },
|
|
157
|
+
{ name: "n-n (Many-to-Many)", value: "n-n" },
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
return relation;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = { askEntityInputs };
|