archforge-x 1.0.2 → 1.0.4

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.
@@ -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;
@@ -4,339 +4,42 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.generateProject = generateProject;
7
- const fs_extra_1 = __importDefault(require("fs-extra"));
8
- const path_1 = __importDefault(require("path"));
9
7
  const chalk_1 = __importDefault(require("chalk"));
10
- // --- HELPER FUNCTIONS ---
11
- const writeFile = (root, filePath, content) => {
12
- const fullPath = path_1.default.join(root, filePath);
13
- fs_extra_1.default.mkdirpSync(path_1.default.dirname(fullPath));
14
- fs_extra_1.default.writeFileSync(fullPath, content.trim());
15
- };
16
- const getPackageJson = (name, fw, arch, modules) => {
17
- const deps = {
18
- "dotenv": "^16.0.3",
19
- };
20
- const devDeps = {
21
- "typescript": "^5.0.0",
22
- "@types/node": "^20.0.0",
23
- "jest": "^29.5.0",
24
- "ts-jest": "^29.1.0",
25
- "@types/jest": "^29.5.0",
26
- "eslint": "^8.0.0",
27
- "prettier": "^3.0.0"
28
- };
29
- if (fw === "nestjs") {
30
- Object.assign(deps, {
31
- "@nestjs/common": "^10.0.0",
32
- "@nestjs/core": "^10.0.0",
33
- "@nestjs/platform-express": "^10.0.0",
34
- "reflect-metadata": "^0.1.13",
35
- "rxjs": "^7.8.0"
36
- });
37
- Object.assign(devDeps, {
38
- "@nestjs/cli": "^10.0.0",
39
- "@nestjs/schematics": "^10.0.0",
40
- "@nestjs/testing": "^10.0.0"
41
- });
42
- }
43
- else if (fw === "express") {
44
- Object.assign(deps, { "express": "^4.18.2", "cors": "^2.8.5" });
45
- Object.assign(devDeps, { "@types/express": "^4.17.17", "ts-node-dev": "^2.0.0" });
46
- }
47
- if (modules.includes("auth"))
48
- deps["jsonwebtoken"] = "^9.0.0";
49
- if (modules.includes("auth") && fw === "nestjs")
50
- deps["@nestjs/jwt"] = "^10.0.0";
51
- if (modules.includes("db"))
52
- deps["typeorm"] = "^0.3.17";
53
- if (modules.includes("cache"))
54
- deps["ioredis"] = "^5.3.0";
55
- return JSON.stringify({
56
- name,
57
- version: "0.0.1",
58
- scripts: {
59
- "start:dev": fw === "nestjs" ? "nest start --watch" : "ts-node-dev src/main.ts",
60
- "build": "tsc",
61
- "test": "jest"
62
- },
63
- dependencies: deps,
64
- devDependencies: devDeps
65
- }, null, 2);
66
- };
67
- const getGoMod = (name, fw) => {
68
- return `module ${name}
69
-
70
- go 1.21
71
-
72
- require (
73
- ${fw === "gin" ? 'github.com/gin-gonic/gin v1.9.1' : ''}
74
- github.com/joho/godotenv v1.5.1
75
- )`;
76
- };
77
- const getRequirementsTxt = (fw, modules) => {
78
- const reqs = ["python-dotenv"];
79
- if (fw === "django")
80
- reqs.push("Django>=4.2");
81
- if (fw === "flask")
82
- reqs.push("Flask>=2.3");
83
- if (modules.includes("db"))
84
- reqs.push("psycopg2-binary", "sqlalchemy");
85
- if (modules.includes("auth"))
86
- reqs.push("PyJWT");
87
- return reqs.join("\n");
88
- };
89
- const getArchForgeYaml = (name, arch, lang, fw, modules) => {
90
- let layers = "";
91
- if (arch === "clean") {
92
- layers = `
93
- layers:
94
- - name: "domain"
95
- path: "src/domain"
96
- description: "Enterprise business rules and entities"
97
- forbiddenImports: ["application", "infrastructure", "interface-adapters"]
98
-
99
- - name: "application"
100
- path: "src/application"
101
- description: "Application business rules and use cases"
102
- allowedImports: ["domain"]
103
-
104
- - name: "interface-adapters"
105
- path: "src/interface-adapters"
106
- description: "Controllers, presenters, and gateways"
107
- allowedImports: ["domain", "application"]
108
-
109
- - name: "infrastructure"
110
- path: "src/infrastructure"
111
- description: "Frameworks, drivers, and external tools"
112
- allowedImports: ["domain", "application", "interface-adapters"]
113
- `;
114
- }
115
- else if (arch === "ddd") {
116
- layers = `
117
- layers:
118
- - name: "domain"
119
- path: "src/domain"
120
- description: "Core domain logic and aggregates"
121
- forbiddenImports: ["application", "infrastructure", "interfaces"]
122
-
123
- - name: "application"
124
- path: "src/application"
125
- description: "Application services and orchestration"
126
- allowedImports: ["domain"]
127
-
128
- - name: "infrastructure"
129
- path: "src/infrastructure"
130
- description: "Persistence and external service implementations"
131
- allowedImports: ["domain", "application"]
132
-
133
- - name: "interfaces"
134
- path: "src/interfaces"
135
- description: "Web APIs and user interfaces"
136
- allowedImports: ["domain", "application"]
137
- `;
138
- }
139
- else {
140
- // Default layered
141
- layers = `
142
- layers:
143
- - name: "core"
144
- path: "src/core"
145
- description: "Core business logic"
146
-
147
- - name: "services"
148
- path: "src/services"
149
- allowedImports: ["core"]
150
-
151
- - name: "api"
152
- path: "src/api"
153
- allowedImports: ["core", "services"]
154
- `;
155
- }
156
- return `
157
- version: "1.0"
158
- name: "${name} Architecture"
159
- project:
160
- name: "${name}"
161
- root: "."
162
-
163
- metadata:
164
- type: "${arch}"
165
- language: "${lang}"
166
- framework: "${fw}"
167
- modules: ${JSON.stringify(modules)}
168
-
169
- ${layers.trim()}
170
- `.trim();
171
- };
172
- // --- CONTENT GENERATORS ---
173
- const generateTsCleanArch = (root, options) => {
174
- const fw = options.framework;
175
- writeFile(root, "src/domain/entities/user.entity.ts", `
176
- export class User {
177
- constructor(
178
- public readonly id: string,
179
- public readonly name: string,
180
- public readonly email: string,
181
- public readonly passwordHash: string
182
- ) {}
183
- }
184
- `);
185
- writeFile(root, "src/domain/repositories/user.repository.interface.ts", `
186
- import { User } from "../entities/user.entity";
187
-
188
- export interface IUserRepository {
189
- save(user: User): Promise<User>;
190
- findByEmail(email: string): Promise<User | null>;
191
- findById(id: string): Promise<User | null>;
192
- }
193
- `);
194
- writeFile(root, "src/application/use-cases/create-user.use-case.ts", `
195
- import { User } from "../../domain/entities/user.entity";
196
- import { IUserRepository } from "../../domain/repositories/user.repository.interface";
197
-
198
- export class CreateUserUseCase {
199
- constructor(private userRepository: IUserRepository) {}
200
-
201
- async execute(name: string, email: string, passwordHash: string): Promise<User> {
202
- const existing = await this.userRepository.findByEmail(email);
203
- if (existing) throw new Error("User already exists");
204
-
205
- const user = new User(Date.now().toString(), name, email, passwordHash);
206
- return await this.userRepository.save(user);
207
- }
208
- }
209
- `);
210
- if (fw === "express") {
211
- writeFile(root, "src/interface-adapters/controllers/user.controller.ts", `
212
- import { Request, Response } from "express";
213
- import { CreateUserUseCase } from "../../application/use-cases/create-user.use-case";
214
-
215
- export class UserController {
216
- constructor(private createUserUseCase: CreateUserUseCase) {}
217
-
218
- async create(req: Request, res: Response) {
219
- try {
220
- const { name, email, password } = req.body;
221
- const user = await this.createUserUseCase.execute(name, email, password);
222
- res.status(201).json(user);
223
- } catch (e: any) {
224
- res.status(400).json({ error: e.message });
225
- }
226
- }
227
- }
228
- `);
229
- writeFile(root, "src/main.ts", `
230
- import express from "express";
231
- import { UserController } from "./interface-adapters/controllers/user.controller";
232
- import { CreateUserUseCase } from "./application/use-cases/create-user.use-case";
233
- import { InMemoryUserRepository } from "./infrastructure/repositories/in-memory-user.repository";
234
-
235
- const app = express();
236
- app.use(express.json());
237
-
238
- const userRepo = new InMemoryUserRepository();
239
- const createUserUseCase = new CreateUserUseCase(userRepo);
240
- const userController = new UserController(createUserUseCase);
241
-
242
- app.post("/users", (req, res) => userController.create(req, res));
243
-
244
- app.listen(3000, () => console.log("šŸš€ Server running on PORT 3000 (Clean Architecture)"));
245
- `);
246
- }
247
- else if (fw === "nestjs") {
248
- writeFile(root, "src/app.module.ts", `
249
- import { Module } from '@nestjs/common';
250
- import { UserController } from './interface-adapters/controllers/user.controller';
251
- import { CreateUserUseCase } from './application/use-cases/create-user.use-case';
252
- import { InMemoryUserRepository } from './infrastructure/repositories/in-memory-user.repository';
253
-
254
- @Module({
255
- controllers: [UserController],
256
- providers: [
257
- { provide: 'IUserRepository', useClass: InMemoryUserRepository },
258
- CreateUserUseCase
259
- ],
260
- })
261
- export class AppModule {}
262
- `);
263
- writeFile(root, "src/main.ts", `
264
- import { NestFactory } from '@nestjs/core';
265
- import { AppModule } from './app.module';
266
-
267
- async function bootstrap() {
268
- const app = await NestFactory.create(AppModule);
269
- await app.listen(3000);
270
- console.log("šŸš€ NestJS Server with Clean Architecture running on 3000");
271
- }
272
- bootstrap();
273
- `);
274
- }
275
- writeFile(root, "src/infrastructure/repositories/in-memory-user.repository.ts", `
276
- import { User } from "../../domain/entities/user.entity";
277
- import { IUserRepository } from "../../domain/repositories/user.repository.interface";
278
-
279
- export class InMemoryUserRepository implements IUserRepository {
280
- private users: User[] = [];
281
- async save(user: User): Promise<User> {
282
- this.users.push(user);
283
- return user;
284
- }
285
- async findByEmail(email: string): Promise<User | null> {
286
- return this.users.find(u => u.email === email) || null;
287
- }
288
- async findById(id: string): Promise<User | null> {
289
- return this.users.find(u => u.id === id) || null;
290
- }
291
- }
292
- `);
293
- };
294
- // --- MAIN GENERATOR ---
8
+ const registry_1 = require("./registry");
9
+ // Import Generators
10
+ const express_1 = require("./node/express");
11
+ const nestjs_1 = require("./node/nestjs");
12
+ const nextjs_1 = require("./node/nextjs");
13
+ const fastapi_1 = require("./python/fastapi");
14
+ const django_1 = require("./python/django");
15
+ const gin_1 = require("./go/gin");
16
+ // Register Generators
17
+ registry_1.GeneratorRegistry.register("ts:express", new express_1.ExpressGenerator());
18
+ registry_1.GeneratorRegistry.register("js:express", new express_1.ExpressGenerator());
19
+ registry_1.GeneratorRegistry.register("ts:nestjs", new nestjs_1.NestJSGenerator());
20
+ registry_1.GeneratorRegistry.register("js:nestjs", new nestjs_1.NestJSGenerator()); // NestJS usually TS, but just in case
21
+ registry_1.GeneratorRegistry.register("ts:nextjs", new nextjs_1.NextJSGenerator()); // Next.js
22
+ registry_1.GeneratorRegistry.register("js:nextjs", new nextjs_1.NextJSGenerator());
23
+ registry_1.GeneratorRegistry.register("py:fastapi", new fastapi_1.FastAPIGenerator());
24
+ registry_1.GeneratorRegistry.register("py:django", new django_1.DjangoGenerator());
25
+ registry_1.GeneratorRegistry.register("py", new fastapi_1.FastAPIGenerator()); // Default Python
26
+ registry_1.GeneratorRegistry.register("go:gin", new gin_1.GoGinGenerator());
27
+ registry_1.GeneratorRegistry.register("go", new gin_1.GoGinGenerator()); // Default Go
295
28
  async function generateProject(arch, options = {}) {
296
- const mode = options.mode || 'standard';
297
- const projectName = options.projectName || arch.project.name || "generated-project";
298
- const root = path_1.default.resolve(projectName);
299
- console.log(chalk_1.default.blueBright(`šŸš€ Starting project generation in ${mode} mode...`));
300
- if (mode === 'professional') {
301
- if (!fs_extra_1.default.existsSync(root))
302
- fs_extra_1.default.mkdirpSync(root);
303
- const lang = options.language || arch.metadata.language;
304
- const fw = options.framework || arch.metadata.framework || "";
305
- const modules = options.modules || arch.metadata.modules || [];
306
- const archStyle = options.architecture || arch.metadata.type;
307
- // Config Files
308
- if (lang === "ts" || lang === "js") {
309
- writeFile(root, "package.json", getPackageJson(projectName, fw, archStyle, modules));
310
- writeFile(root, "tsconfig.json", JSON.stringify({
311
- compilerOptions: { target: "ES2020", module: "commonjs", outDir: "./dist", rootDir: "./src", strict: true, experimentalDecorators: true, emitDecoratorMetadata: true },
312
- exclude: ["node_modules", "dist"]
313
- }, null, 2));
314
- }
315
- else if (lang === "go") {
316
- writeFile(root, "go.mod", getGoMod(projectName, fw));
317
- }
318
- else if (lang === "py") {
319
- writeFile(root, "requirements.txt", getRequirementsTxt(fw, modules));
320
- }
321
- writeFile(root, ".env.example", "PORT=3000\nDB_HOST=localhost\nJWT_SECRET=supersecret");
322
- writeFile(root, ".gitignore", "node_modules/\ndist/\n.env\n__pycache__/\n*.exe\nvendor/");
323
- writeFile(root, "README.md", `# ${projectName}\nGenerated by ArchForge X\nArchitecture: ${archStyle}`);
324
- // Generate archforge.yaml Rules File
325
- writeFile(root, "archforge.yaml", getArchForgeYaml(projectName, archStyle, lang, fw, modules));
326
- if (lang === "ts" || lang === "js") {
327
- generateTsCleanArch(root, options);
328
- }
329
- }
330
- else {
331
- // Standard Mode: Just layers and READMEs
332
- for (const layer of arch.layers) {
333
- const layerPath = path_1.default.resolve(root, layer.path);
334
- fs_extra_1.default.mkdirpSync(layerPath);
335
- const readmeContent = `# ${layer.name} Layer\n\nPath: ${layer.path}\n\nAllowed Imports: ${layer.allowedImports?.join(", ") || "Any"}\nForbidden Imports: ${layer.forbiddenImports?.join(", ") || "None"}\n`;
336
- fs_extra_1.default.writeFileSync(path_1.default.join(layerPath, "README.md"), readmeContent);
337
- const baseFileContent = `/**\n * Base file for ${layer.name} layer\n * Generated by ArchForge X\n */\n`;
338
- fs_extra_1.default.writeFileSync(path_1.default.join(layerPath, `${layer.name}.ts`), baseFileContent);
339
- }
29
+ const lang = options.language || arch.metadata.language || "ts";
30
+ const fw = options.framework || arch.metadata.framework || "";
31
+ console.log(chalk_1.default.blueBright(`šŸ” Looking for generator for ${lang} + ${fw}...`));
32
+ const generator = registry_1.GeneratorRegistry.getGenerator(lang, fw);
33
+ if (!generator) {
34
+ console.error(chalk_1.default.red(`āŒ No generator found for language: ${lang} and framework: ${fw}`));
35
+ console.log(chalk_1.default.yellow(`Available generators: ${registry_1.GeneratorRegistry.listGenerators().join(", ")}`));
36
+ return;
37
+ }
38
+ try {
39
+ await generator.generate(arch, options);
40
+ }
41
+ catch (error) {
42
+ console.error(chalk_1.default.red("āŒ Error during generation:"), error);
43
+ throw error;
340
44
  }
341
- console.log(chalk_1.default.green.bold(`\nāœ… Project ${projectName} successfully generated!`));
342
45
  }