archforge-x 1.0.0 ā 1.0.3
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/dist/cli/commands/sync.js +22 -0
- package/dist/cli/interactive.js +41 -3
- package/dist/core/architecture/schema.js +23 -2
- package/dist/core/architecture/validator.js +112 -0
- package/dist/generators/base.js +166 -0
- package/dist/generators/generator.js +35 -332
- package/dist/generators/go/gin.js +327 -0
- package/dist/generators/node/express.js +920 -0
- package/dist/generators/node/nestjs.js +770 -0
- package/dist/generators/node/nextjs.js +252 -0
- package/dist/generators/python/django.js +327 -0
- package/dist/generators/python/fastapi.js +309 -0
- package/dist/generators/registry.js +25 -0
- package/dist/index.js +10 -1
- package/package.json +6 -4
- package/dist/generators/professional.js +0 -509
- package/dist/generators/templates/base.js +0 -10
- package/dist/sample-app/application/application.js +0 -8
- package/dist/sample-app/domain/domain.js +0 -2
- package/dist/sample-app/domain/domain.spec.js +0 -2
- package/dist/sample-app/infrastructure/infrastructure.js +0 -8
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.syncCommand = syncCommand;
|
|
7
|
+
const validator_1 = require("../../core/architecture/validator");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
async function syncCommand() {
|
|
10
|
+
const projectRoot = process.cwd();
|
|
11
|
+
const validator = new validator_1.ArchitectureValidator();
|
|
12
|
+
console.log(chalk_1.default.blue("š Syncing architecture with archforge.yaml..."));
|
|
13
|
+
const result = await validator.validate(projectRoot);
|
|
14
|
+
if (result.success) {
|
|
15
|
+
console.log(chalk_1.default.green.bold("ā
Architecture is valid and in sync!"));
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
console.log(chalk_1.default.red.bold("ā Architecture violations found:"));
|
|
19
|
+
result.errors.forEach(err => console.log(chalk_1.default.red(` - ${err}`)));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
package/dist/cli/interactive.js
CHANGED
|
@@ -52,13 +52,47 @@ async function interactiveCLI() {
|
|
|
52
52
|
{ title: "Hexagonal Architecture", value: "hexagonal" },
|
|
53
53
|
],
|
|
54
54
|
});
|
|
55
|
+
const ormResponse = await (0, prompts_1.default)({
|
|
56
|
+
type: "select",
|
|
57
|
+
name: "orm",
|
|
58
|
+
message: "Select ORM:",
|
|
59
|
+
choices: langResponse.language === "ts" || langResponse.language === "js"
|
|
60
|
+
? [
|
|
61
|
+
{ title: "Prisma", value: "prisma" },
|
|
62
|
+
{ title: "TypeORM", value: "typeorm" },
|
|
63
|
+
{ title: "Sequelize", value: "sequelize" },
|
|
64
|
+
{ title: "Drizzle", value: "drizzle" },
|
|
65
|
+
{ title: "None", value: "none" },
|
|
66
|
+
]
|
|
67
|
+
: langResponse.language === "py"
|
|
68
|
+
? [
|
|
69
|
+
{ title: "SQLAlchemy", value: "sqlalchemy" },
|
|
70
|
+
{ title: "Django ORM", value: "django" },
|
|
71
|
+
{ title: "None", value: "none" },
|
|
72
|
+
]
|
|
73
|
+
: [
|
|
74
|
+
{ title: "GORM", value: "gorm" },
|
|
75
|
+
{ title: "SQLC", value: "sqlc" },
|
|
76
|
+
{ title: "None", value: "none" },
|
|
77
|
+
],
|
|
78
|
+
});
|
|
79
|
+
const dbResponse = await (0, prompts_1.default)({
|
|
80
|
+
type: ormResponse.orm === "none" ? null : "select",
|
|
81
|
+
name: "database",
|
|
82
|
+
message: "Select Database:",
|
|
83
|
+
choices: [
|
|
84
|
+
{ title: "PostgreSQL", value: "postgresql" },
|
|
85
|
+
{ title: "MySQL", value: "mysql" },
|
|
86
|
+
{ title: "SQLite", value: "sqlite" },
|
|
87
|
+
{ title: "MongoDB", value: "mongodb" },
|
|
88
|
+
],
|
|
89
|
+
});
|
|
55
90
|
const modulesResponse = await (0, prompts_1.default)({
|
|
56
91
|
type: "multiselect",
|
|
57
92
|
name: "modules",
|
|
58
93
|
message: "Select optional modules:",
|
|
59
94
|
choices: [
|
|
60
95
|
{ title: "Authentication (JWT)", value: "auth" },
|
|
61
|
-
{ title: "Database (ORM setup)", value: "db" },
|
|
62
96
|
{ title: "Caching (Redis)", value: "cache" },
|
|
63
97
|
{ title: "Docker Support", value: "docker" },
|
|
64
98
|
{ title: "CI/CD (GitHub Actions)", value: "ci" },
|
|
@@ -73,12 +107,14 @@ async function interactiveCLI() {
|
|
|
73
107
|
});
|
|
74
108
|
console.log(chalk_1.default.yellow("\nš§ Generating project..."));
|
|
75
109
|
const options = {
|
|
76
|
-
mode: 'professional',
|
|
110
|
+
// mode: 'professional',
|
|
77
111
|
language: langResponse.language,
|
|
78
112
|
framework: frameworkResponse.framework,
|
|
79
113
|
architecture: archResponse.architecture,
|
|
80
114
|
modules: modulesResponse.modules,
|
|
81
115
|
projectName: projectNameResp.projectName,
|
|
116
|
+
orm: ormResponse.orm,
|
|
117
|
+
database: dbResponse.database,
|
|
82
118
|
};
|
|
83
119
|
// Create a dummy arch definition for the generator
|
|
84
120
|
const dummyArch = {
|
|
@@ -92,7 +128,9 @@ async function interactiveCLI() {
|
|
|
92
128
|
type: archResponse.architecture,
|
|
93
129
|
language: langResponse.language,
|
|
94
130
|
framework: frameworkResponse.framework,
|
|
95
|
-
modules: modulesResponse.modules
|
|
131
|
+
modules: modulesResponse.modules,
|
|
132
|
+
orm: ormResponse.orm,
|
|
133
|
+
database: dbResponse.database
|
|
96
134
|
},
|
|
97
135
|
layers: []
|
|
98
136
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.architectureSchema = exports.metadataSchema = exports.layerSchema = void 0;
|
|
3
|
+
exports.architectureSchema = exports.customStructureSchema = exports.rulesSchema = exports.namingSchema = exports.metadataSchema = exports.layerSchema = void 0;
|
|
4
4
|
// src/core/architecture/schema.ts
|
|
5
5
|
const zod_1 = require("zod");
|
|
6
6
|
exports.layerSchema = zod_1.z.object({
|
|
@@ -9,15 +9,33 @@ exports.layerSchema = zod_1.z.object({
|
|
|
9
9
|
description: zod_1.z.string().optional(),
|
|
10
10
|
allowedImports: zod_1.z.array(zod_1.z.string()).optional(),
|
|
11
11
|
forbiddenImports: zod_1.z.array(zod_1.z.string()).optional(),
|
|
12
|
+
canImport: zod_1.z.array(zod_1.z.string()).optional(),
|
|
12
13
|
strict: zod_1.z.boolean().optional(),
|
|
13
14
|
});
|
|
14
15
|
exports.metadataSchema = zod_1.z.object({
|
|
15
|
-
type: zod_1.z.enum(['clean', 'ddd', 'layered', 'hexagonal', 'microservices']),
|
|
16
|
+
type: zod_1.z.enum(['clean', 'ddd', 'layered', 'hexagonal', 'microservices', 'mvc']),
|
|
16
17
|
language: zod_1.z.enum(['ts', 'js', 'py', 'go']),
|
|
17
18
|
framework: zod_1.z.string().optional(),
|
|
19
|
+
orm: zod_1.z.string().optional(),
|
|
20
|
+
database: zod_1.z.string().optional(),
|
|
18
21
|
modules: zod_1.z.array(zod_1.z.string()).optional(),
|
|
19
22
|
version: zod_1.z.string().optional(),
|
|
20
23
|
});
|
|
24
|
+
exports.namingSchema = zod_1.z.object({
|
|
25
|
+
services: zod_1.z.string().optional(),
|
|
26
|
+
repositories: zod_1.z.string().optional(),
|
|
27
|
+
controllers: zod_1.z.string().optional(),
|
|
28
|
+
entities: zod_1.z.string().optional(),
|
|
29
|
+
});
|
|
30
|
+
exports.rulesSchema = zod_1.z.object({
|
|
31
|
+
forbidDirectDbAccess: zod_1.z.boolean().optional(),
|
|
32
|
+
forbidFrameworkInDomain: zod_1.z.boolean().optional(),
|
|
33
|
+
enforceLayerBoundaries: zod_1.z.boolean().optional(),
|
|
34
|
+
});
|
|
35
|
+
exports.customStructureSchema = zod_1.z.object({
|
|
36
|
+
enabled: zod_1.z.boolean(),
|
|
37
|
+
folders: zod_1.z.array(zod_1.z.object({ path: zod_1.z.string() })),
|
|
38
|
+
});
|
|
21
39
|
exports.architectureSchema = zod_1.z.object({
|
|
22
40
|
version: zod_1.z.string().default("1.0"),
|
|
23
41
|
name: zod_1.z.string(),
|
|
@@ -28,5 +46,8 @@ exports.architectureSchema = zod_1.z.object({
|
|
|
28
46
|
}),
|
|
29
47
|
metadata: exports.metadataSchema,
|
|
30
48
|
layers: zod_1.z.array(exports.layerSchema),
|
|
49
|
+
naming: exports.namingSchema.optional(),
|
|
50
|
+
rules: exports.rulesSchema.optional(),
|
|
51
|
+
customStructure: exports.customStructureSchema.optional(),
|
|
31
52
|
strict: zod_1.z.boolean().optional(),
|
|
32
53
|
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ArchitectureValidator = void 0;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
10
|
+
class ArchitectureValidator {
|
|
11
|
+
async validate(projectRoot) {
|
|
12
|
+
const configPath = path_1.default.join(projectRoot, "archforge.yaml");
|
|
13
|
+
if (!fs_extra_1.default.existsSync(configPath)) {
|
|
14
|
+
return { success: false, errors: ["archforge.yaml not found"], warnings: [] };
|
|
15
|
+
}
|
|
16
|
+
const config = js_yaml_1.default.load(fs_extra_1.default.readFileSync(configPath, "utf8"));
|
|
17
|
+
const errors = [];
|
|
18
|
+
const warnings = [];
|
|
19
|
+
// 1. Validate Folder Structure
|
|
20
|
+
if (config.layers) {
|
|
21
|
+
for (const [name, layer] of Object.entries(config.layers)) {
|
|
22
|
+
const layerPath = path_1.default.join(projectRoot, layer.path);
|
|
23
|
+
if (!fs_extra_1.default.existsSync(layerPath)) {
|
|
24
|
+
errors.push(`Layer "${name}" path not found: ${layer.path}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// 2. Validate Custom Folders
|
|
29
|
+
if (config.customStructure?.enabled && config.customStructure.folders) {
|
|
30
|
+
for (const folder of config.customStructure.folders) {
|
|
31
|
+
const folderPath = path_1.default.join(projectRoot, folder.path);
|
|
32
|
+
if (!fs_extra_1.default.existsSync(folderPath)) {
|
|
33
|
+
errors.push(`Custom folder not found: ${folder.path}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// 3. Validate Naming Conventions
|
|
38
|
+
if (config.naming) {
|
|
39
|
+
this.validateNamingConventions(projectRoot, config.naming, errors);
|
|
40
|
+
}
|
|
41
|
+
// 4. Validate Layer Imports (Basic check for forbidden imports in files)
|
|
42
|
+
if (config.layers && config.rules?.enforceLayerBoundaries) {
|
|
43
|
+
this.validateLayerImports(projectRoot, config.layers, errors);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
success: errors.length === 0,
|
|
47
|
+
errors,
|
|
48
|
+
warnings
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
validateNamingConventions(root, naming, errors) {
|
|
52
|
+
// Simple check: scan src and verify file suffixes
|
|
53
|
+
const scanDir = (dir) => {
|
|
54
|
+
if (!fs_extra_1.default.existsSync(dir))
|
|
55
|
+
return;
|
|
56
|
+
const files = fs_extra_1.default.readdirSync(dir);
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
const fullPath = path_1.default.join(dir, file);
|
|
59
|
+
if (fs_extra_1.default.statSync(fullPath).isDirectory()) {
|
|
60
|
+
scanDir(fullPath);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const lowerFile = file.toLowerCase();
|
|
64
|
+
if (dir.includes("services") && naming.services) {
|
|
65
|
+
const pattern = naming.services.replace("*", "").toLowerCase();
|
|
66
|
+
if (!lowerFile.includes(pattern)) {
|
|
67
|
+
errors.push(`Naming violation: Service file "${file}" does not match pattern "${naming.services}"`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (dir.includes("repositories") && naming.repositories) {
|
|
71
|
+
const pattern = naming.repositories.replace("*", "").toLowerCase();
|
|
72
|
+
if (!lowerFile.includes(pattern)) {
|
|
73
|
+
errors.push(`Naming violation: Repository file "${file}" does not match pattern "${naming.repositories}"`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
scanDir(path_1.default.join(root, "src"));
|
|
80
|
+
}
|
|
81
|
+
validateLayerImports(root, layers, errors) {
|
|
82
|
+
// This is a complex task for a simple validator, but we can do a basic grep-like check
|
|
83
|
+
// for forbidden layer paths in files of other layers.
|
|
84
|
+
for (const [name, layer] of Object.entries(layers)) {
|
|
85
|
+
const layerPath = path_1.default.join(root, layer.path);
|
|
86
|
+
if (!fs_extra_1.default.existsSync(layerPath))
|
|
87
|
+
continue;
|
|
88
|
+
const canImport = layer.canImport || [];
|
|
89
|
+
const forbiddenLayers = Object.keys(layers).filter(l => l !== name && !canImport.includes(l));
|
|
90
|
+
const scanFiles = (dir) => {
|
|
91
|
+
const files = fs_extra_1.default.readdirSync(dir);
|
|
92
|
+
for (const file of files) {
|
|
93
|
+
const fullPath = path_1.default.join(dir, file);
|
|
94
|
+
if (fs_extra_1.default.statSync(fullPath).isDirectory()) {
|
|
95
|
+
scanFiles(fullPath);
|
|
96
|
+
}
|
|
97
|
+
else if (file.endsWith(".ts") || file.endsWith(".py") || file.endsWith(".go")) {
|
|
98
|
+
const content = fs_extra_1.default.readFileSync(fullPath, "utf8");
|
|
99
|
+
for (const forbidden of forbiddenLayers) {
|
|
100
|
+
const forbiddenPath = layers[forbidden].path.replace("src/", "");
|
|
101
|
+
if (content.includes(forbiddenPath) && !content.includes(`from .`)) {
|
|
102
|
+
errors.push(`Architecture violation: Layer "${name}" (${file}) imports from forbidden layer "${forbidden}"`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
scanFiles(layerPath);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
exports.ArchitectureValidator = ArchitectureValidator;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BaseGenerator = void 0;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
11
|
+
class BaseGenerator {
|
|
12
|
+
async generate(arch, options) {
|
|
13
|
+
const projectName = options.projectName || arch.project.name || "generated-project";
|
|
14
|
+
const root = path_1.default.resolve(projectName);
|
|
15
|
+
console.log(chalk_1.default.blueBright(`š Starting ${this.getGeneratorName()} generation for ${projectName}...`));
|
|
16
|
+
if (!fs_extra_1.default.existsSync(root)) {
|
|
17
|
+
fs_extra_1.default.mkdirpSync(root);
|
|
18
|
+
}
|
|
19
|
+
await this.generateProjectStructure(root, arch, options);
|
|
20
|
+
this.generateArchForgeConfig(root, arch, options);
|
|
21
|
+
// Ensure all layer and custom folders exist
|
|
22
|
+
const config = js_yaml_1.default.load(fs_extra_1.default.readFileSync(path_1.default.join(root, "archforge.yaml"), "utf8"));
|
|
23
|
+
if (config.layers) {
|
|
24
|
+
for (const layer of Object.values(config.layers)) {
|
|
25
|
+
this.createDir(root, layer.path);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (config.customStructure?.enabled && config.customStructure.folders) {
|
|
29
|
+
for (const folder of config.customStructure.folders) {
|
|
30
|
+
this.createDir(root, folder.path);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (options.modules?.includes("docker")) {
|
|
34
|
+
console.log(chalk_1.default.blue(" š³ Generating Docker configuration..."));
|
|
35
|
+
this.generateDocker(root, options);
|
|
36
|
+
}
|
|
37
|
+
if (options.modules?.includes("ci")) {
|
|
38
|
+
console.log(chalk_1.default.blue(" š· Generating CI/CD workflows..."));
|
|
39
|
+
this.generateCI(root, options);
|
|
40
|
+
}
|
|
41
|
+
this.generateProductionDocs(root, arch, options);
|
|
42
|
+
this.ensureNoEmptyFolders(root);
|
|
43
|
+
console.log(chalk_1.default.green.bold(`\nā
${this.getGeneratorName()} project ${projectName} successfully generated!`));
|
|
44
|
+
}
|
|
45
|
+
generateProductionDocs(root, arch, options) {
|
|
46
|
+
this.writeFile(root, "docs/architecture.md", `
|
|
47
|
+
# Architecture Guide
|
|
48
|
+
|
|
49
|
+
This project follows the **${options.architecture || arch.metadata.type}** architecture style.
|
|
50
|
+
|
|
51
|
+
## Layers
|
|
52
|
+
${arch.layers.map(l => `- **${l.name}**: ${l.description || 'Located at ' + l.path}`).join('\n')}
|
|
53
|
+
|
|
54
|
+
## Rules
|
|
55
|
+
- **Layer Boundaries**: Enforced via \`archforge.yaml\`.
|
|
56
|
+
- **Naming**: Services must end with \`Service\`, Repositories with \`Repository\`.
|
|
57
|
+
`);
|
|
58
|
+
this.writeFile(root, "docs/database.md", `
|
|
59
|
+
# Database Guide
|
|
60
|
+
|
|
61
|
+
- **ORM**: ${options.orm || arch.metadata.orm}
|
|
62
|
+
- **Database**: ${options.database || arch.metadata.database}
|
|
63
|
+
|
|
64
|
+
## Setup
|
|
65
|
+
1. Ensure Docker is running.
|
|
66
|
+
2. Run \`docker-compose up -d\`.
|
|
67
|
+
3. Migrations are handled automatically on startup.
|
|
68
|
+
`);
|
|
69
|
+
this.writeFile(root, "docs/caching.md", `
|
|
70
|
+
# Caching Guide
|
|
71
|
+
|
|
72
|
+
This project implements a first-class caching layer.
|
|
73
|
+
|
|
74
|
+
## Strategies
|
|
75
|
+
- **Redis**: Used in production for distributed caching.
|
|
76
|
+
- **In-Memory**: Used for local development or as a fallback.
|
|
77
|
+
|
|
78
|
+
## Usage
|
|
79
|
+
Inject the \`ICache\` interface into your services.
|
|
80
|
+
`);
|
|
81
|
+
this.writeFile(root, "docs/rules.md", `
|
|
82
|
+
# ArchForge Rules
|
|
83
|
+
|
|
84
|
+
This project uses \`archforge.yaml\` to enforce architectural standards.
|
|
85
|
+
|
|
86
|
+
## How to Sync
|
|
87
|
+
Run \`npx archforge sync\` to validate the structure.
|
|
88
|
+
`);
|
|
89
|
+
}
|
|
90
|
+
ensureNoEmptyFolders(root) {
|
|
91
|
+
const scan = (dir) => {
|
|
92
|
+
const files = fs_extra_1.default.readdirSync(dir);
|
|
93
|
+
if (files.length === 0) {
|
|
94
|
+
this.writeFile(dir, "README.md", `# ${path_1.default.basename(dir)}\n\nThis folder is part of the architectural structure.`);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
for (const file of files) {
|
|
98
|
+
const fullPath = path_1.default.join(dir, file);
|
|
99
|
+
if (fs_extra_1.default.statSync(fullPath).isDirectory()) {
|
|
100
|
+
scan(fullPath);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
scan(path_1.default.join(root, "src"));
|
|
106
|
+
}
|
|
107
|
+
generateArchForgeConfig(root, arch, options) {
|
|
108
|
+
const config = {
|
|
109
|
+
architecture: options.architecture || arch.metadata.type,
|
|
110
|
+
stack: options.framework || arch.metadata.framework,
|
|
111
|
+
orm: options.orm || arch.metadata.orm || "none",
|
|
112
|
+
database: options.database || arch.metadata.database || "none",
|
|
113
|
+
layers: arch.layers.length > 0 ? arch.layers.reduce((acc, layer) => {
|
|
114
|
+
acc[layer.name] = {
|
|
115
|
+
path: layer.path,
|
|
116
|
+
canImport: layer.canImport || layer.allowedImports || []
|
|
117
|
+
};
|
|
118
|
+
return acc;
|
|
119
|
+
}, {}) : this.getDefaultLayers(options.architecture || "clean"),
|
|
120
|
+
naming: arch.naming || {
|
|
121
|
+
services: "*Service",
|
|
122
|
+
repositories: "*Repository",
|
|
123
|
+
controllers: "*Controller"
|
|
124
|
+
},
|
|
125
|
+
rules: arch.rules || {
|
|
126
|
+
forbidDirectDbAccess: true,
|
|
127
|
+
forbidFrameworkInDomain: true,
|
|
128
|
+
enforceLayerBoundaries: true
|
|
129
|
+
},
|
|
130
|
+
customStructure: arch.customStructure || {
|
|
131
|
+
enabled: true,
|
|
132
|
+
folders: [
|
|
133
|
+
{ path: "src/modules" },
|
|
134
|
+
{ path: "src/shared" }
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
this.writeFile(root, "archforge.yaml", `
|
|
139
|
+
# ArchForge X Configuration
|
|
140
|
+
# This file is the single source of truth for your project architecture.
|
|
141
|
+
${require('js-yaml').dump(config)}
|
|
142
|
+
`.trim());
|
|
143
|
+
}
|
|
144
|
+
getDefaultLayers(arch) {
|
|
145
|
+
if (arch === "clean") {
|
|
146
|
+
return {
|
|
147
|
+
domain: { path: "src/domain", canImport: [] },
|
|
148
|
+
application: { path: "src/application", canImport: ["domain"] },
|
|
149
|
+
infrastructure: { path: "src/infrastructure", canImport: ["application", "domain"] },
|
|
150
|
+
presentation: { path: "src/presentation", canImport: ["application"] }
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
// Add other defaults as needed
|
|
154
|
+
return {};
|
|
155
|
+
}
|
|
156
|
+
writeFile(root, filePath, content) {
|
|
157
|
+
const fullPath = path_1.default.join(root, filePath);
|
|
158
|
+
fs_extra_1.default.mkdirpSync(path_1.default.dirname(fullPath));
|
|
159
|
+
fs_extra_1.default.writeFileSync(fullPath, content.trim());
|
|
160
|
+
}
|
|
161
|
+
createDir(root, dirPath) {
|
|
162
|
+
const fullPath = path_1.default.join(root, dirPath);
|
|
163
|
+
fs_extra_1.default.mkdirpSync(fullPath);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
exports.BaseGenerator = BaseGenerator;
|