stackkit 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -12
- package/dist/lib/fs/files.js +1 -1
- package/dist/lib/generation/code-generator.d.ts +2 -7
- package/dist/lib/generation/code-generator.js +120 -56
- package/dist/lib/utils/fs-helpers.d.ts +1 -1
- package/modules/auth/authjs/generator.json +16 -16
- package/modules/auth/better-auth/files/express/middlewares/authorize.ts +121 -41
- package/modules/auth/better-auth/files/express/modules/auth/auth.controller.ts +257 -0
- package/modules/auth/better-auth/files/express/modules/auth/auth.interface.ts +23 -0
- package/modules/auth/better-auth/files/express/modules/auth/auth.route.ts +22 -0
- package/modules/auth/better-auth/files/express/modules/auth/auth.service.ts +403 -0
- package/modules/auth/better-auth/files/express/templates/google-redirect.ejs +91 -0
- package/modules/auth/better-auth/files/express/templates/otp.ejs +87 -0
- package/modules/auth/better-auth/files/express/types/express.d.ts +6 -8
- package/modules/auth/better-auth/files/express/utils/cookie.ts +19 -0
- package/modules/auth/better-auth/files/express/utils/jwt.ts +34 -0
- package/modules/auth/better-auth/files/express/utils/token.ts +66 -0
- package/modules/auth/better-auth/files/nextjs/api/auth/[...all]/route.ts +1 -1
- package/modules/auth/better-auth/files/nextjs/lib/auth/auth-guards.ts +11 -1
- package/modules/auth/better-auth/files/nextjs/templates/email-otp.tsx +74 -0
- package/modules/auth/better-auth/files/shared/config/env.ts +113 -0
- package/modules/auth/better-auth/files/shared/lib/auth-client.ts +1 -1
- package/modules/auth/better-auth/files/shared/lib/auth.ts +151 -62
- package/modules/auth/better-auth/files/shared/prisma/schema.prisma +22 -11
- package/modules/auth/better-auth/files/shared/utils/email.ts +71 -0
- package/modules/auth/better-auth/generator.json +159 -81
- package/modules/database/mongoose/generator.json +18 -18
- package/modules/database/prisma/generator.json +44 -44
- package/package.json +1 -1
- package/templates/express/env.example +2 -2
- package/templates/express/eslint.config.mjs +7 -0
- package/templates/express/node_modules/.bin/acorn +17 -0
- package/templates/express/node_modules/.bin/eslint +17 -0
- package/templates/express/node_modules/.bin/tsc +17 -0
- package/templates/express/node_modules/.bin/tsserver +17 -0
- package/templates/express/node_modules/.bin/tsx +17 -0
- package/templates/express/package.json +12 -6
- package/templates/express/src/app.ts +15 -7
- package/templates/express/src/config/cors.ts +8 -7
- package/templates/express/src/config/env.ts +26 -5
- package/templates/express/src/config/logger.ts +2 -2
- package/templates/express/src/config/rate-limit.ts +2 -2
- package/templates/express/src/modules/health/health.controller.ts +13 -11
- package/templates/express/src/routes/index.ts +1 -6
- package/templates/express/src/server.ts +12 -12
- package/templates/express/src/shared/errors/app-error.ts +16 -0
- package/templates/express/src/shared/middlewares/error.middleware.ts +154 -12
- package/templates/express/src/shared/middlewares/not-found.middleware.ts +2 -1
- package/templates/express/src/shared/utils/catch-async.ts +11 -0
- package/templates/express/src/shared/utils/pagination.ts +6 -1
- package/templates/express/src/shared/utils/send-response.ts +25 -0
- package/templates/nextjs/lib/env.ts +19 -8
- package/modules/auth/better-auth/files/shared/lib/email/email-service.ts +0 -33
- package/modules/auth/better-auth/files/shared/lib/email/email-templates.ts +0 -89
- package/templates/express/eslint.config.cjs +0 -42
- package/templates/express/src/config/helmet.ts +0 -5
- package/templates/express/src/modules/health/health.service.ts +0 -6
- package/templates/express/src/shared/errors/error-codes.ts +0 -9
- package/templates/express/src/shared/logger/logger.ts +0 -20
- package/templates/express/src/shared/utils/async-handler.ts +0 -9
- package/templates/express/src/shared/utils/response.ts +0 -9
package/README.md
CHANGED
|
@@ -1,34 +1,113 @@
|
|
|
1
1
|
# StackKit CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Production-ready full-stack project generator.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
No installation required. Use npx to get the latest version:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
+
npx stackkit@latest <command>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
### create
|
|
16
|
+
|
|
17
|
+
Generate a new project:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Interactive mode
|
|
10
21
|
npx stackkit@latest create my-app
|
|
22
|
+
|
|
23
|
+
# With options
|
|
24
|
+
npx stackkit@latest create my-app \
|
|
25
|
+
--framework nextjs \
|
|
26
|
+
--database prisma-postgresql \
|
|
27
|
+
--auth better-auth \
|
|
28
|
+
--language typescript
|
|
11
29
|
```
|
|
12
30
|
|
|
13
|
-
|
|
31
|
+
**Options:**
|
|
32
|
+
|
|
33
|
+
- `--framework, -f` - nextjs, express, react
|
|
34
|
+
- `--database, -d` - prisma-postgresql, prisma-mysql, prisma-sqlite, prisma-mongodb, mongoose, none
|
|
35
|
+
- `--auth, -a` - better-auth, authjs, none
|
|
36
|
+
- `--language, -l` - typescript, javascript
|
|
37
|
+
- `--package-manager, -p` - pnpm, npm, yarn, bun
|
|
38
|
+
- `--yes, -y` - Use defaults
|
|
39
|
+
- `--skip-install` - Don't install dependencies
|
|
40
|
+
- `--no-git` - Don't initialize git
|
|
41
|
+
|
|
42
|
+
### add
|
|
43
|
+
|
|
44
|
+
Add features to existing project:
|
|
14
45
|
|
|
15
46
|
```bash
|
|
16
|
-
npx stackkit@latest add
|
|
47
|
+
npx stackkit@latest add
|
|
17
48
|
```
|
|
18
49
|
|
|
19
|
-
|
|
50
|
+
Interactively select and install modules.
|
|
51
|
+
|
|
52
|
+
### doctor
|
|
53
|
+
|
|
54
|
+
Diagnose project health:
|
|
20
55
|
|
|
21
56
|
```bash
|
|
22
57
|
npx stackkit@latest doctor
|
|
58
|
+
|
|
59
|
+
# Verbose output
|
|
60
|
+
npx stackkit@latest doctor --verbose
|
|
61
|
+
|
|
62
|
+
# JSON output (for CI/CD)
|
|
63
|
+
npx stackkit@latest doctor --json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### list
|
|
67
|
+
|
|
68
|
+
Show available options:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx stackkit@latest list
|
|
23
72
|
```
|
|
24
73
|
|
|
25
|
-
## Supported
|
|
74
|
+
## Supported Technologies
|
|
75
|
+
|
|
76
|
+
**Frameworks:**
|
|
77
|
+
|
|
78
|
+
- Next.js (App Router)
|
|
79
|
+
- Express (TypeScript API)
|
|
80
|
+
- React (Vite SPA)
|
|
81
|
+
|
|
82
|
+
**Databases:**
|
|
83
|
+
|
|
84
|
+
- Prisma (PostgreSQL, MySQL, SQLite, MongoDB)
|
|
85
|
+
- Mongoose (MongoDB)
|
|
86
|
+
|
|
87
|
+
**Authentication:**
|
|
26
88
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
|
|
89
|
+
- Better Auth (All frameworks)
|
|
90
|
+
- Auth.js (Next.js only)
|
|
91
|
+
|
|
92
|
+
## Examples
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Full-stack Next.js
|
|
96
|
+
npx stackkit@latest create my-app --framework nextjs --database prisma-postgresql --auth better-auth
|
|
97
|
+
|
|
98
|
+
# Express API
|
|
99
|
+
npx stackkit@latest create my-api --framework express --database mongoose
|
|
100
|
+
|
|
101
|
+
# React frontend
|
|
102
|
+
npx stackkit@latest create my-spa --framework react --auth better-auth
|
|
103
|
+
```
|
|
30
104
|
|
|
31
105
|
## Links
|
|
32
106
|
|
|
33
|
-
-
|
|
34
|
-
-
|
|
107
|
+
- **Documentation**: https://stackkit.tariqul.dev
|
|
108
|
+
- **GitHub**: https://github.com/tariqul420/stackkit
|
|
109
|
+
- **Issues**: https://github.com/tariqul420/stackkit/issues
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT © [Tariqul Islam](https://github.com/tariqul420)
|
package/dist/lib/fs/files.js
CHANGED
|
@@ -19,7 +19,7 @@ async function copyBaseFramework(templatesDir, targetDir, framework) {
|
|
|
19
19
|
throw new Error(`Base template not found for framework: ${framework}\n` + `Expected at: ${baseDir}`);
|
|
20
20
|
}
|
|
21
21
|
await fs_extra_1.default.copy(baseDir, targetDir, {
|
|
22
|
-
filter: (src) => !constants_1.EXCLUDE_FROM_COPY.
|
|
22
|
+
filter: (src) => !constants_1.EXCLUDE_FROM_COPY.some((entry) => entry === path_1.default.basename(src)),
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
async function mergePackageJson(targetDir, config) {
|
|
@@ -74,16 +74,14 @@ export declare class AdvancedCodeGenerator {
|
|
|
74
74
|
private copyTemplate;
|
|
75
75
|
private processOperationTemplates;
|
|
76
76
|
private executeCreateFile;
|
|
77
|
+
private parsePathPattern;
|
|
78
|
+
private collectFilesRecursively;
|
|
77
79
|
private executePatchFile;
|
|
78
80
|
private executeAddDependency;
|
|
79
81
|
private executeAddScript;
|
|
80
82
|
private executeAddEnv;
|
|
81
83
|
private executeRunCommand;
|
|
82
84
|
private generatePackageJson;
|
|
83
|
-
/**
|
|
84
|
-
* Apply generators to an existing project directory instead of creating a new template.
|
|
85
|
-
* This executes applicable operations and updates package.json in-place.
|
|
86
|
-
*/
|
|
87
85
|
applyToProject(selectedModules: {
|
|
88
86
|
framework: string;
|
|
89
87
|
database?: string;
|
|
@@ -95,8 +93,5 @@ export declare class AdvancedCodeGenerator {
|
|
|
95
93
|
databases: string[];
|
|
96
94
|
auths: string[];
|
|
97
95
|
};
|
|
98
|
-
/**
|
|
99
|
-
* Register a generator config dynamically (used for modules that only provide module.json patches).
|
|
100
|
-
*/
|
|
101
96
|
registerGenerator(type: "framework" | "database" | "auth", name: string, config: GeneratorConfig): void;
|
|
102
97
|
}
|
|
@@ -128,8 +128,7 @@ class AdvancedCodeGenerator {
|
|
|
128
128
|
const varNameMatch = result.substring(varStart + 7, equalsIndex).trim();
|
|
129
129
|
if (!varNameMatch)
|
|
130
130
|
break;
|
|
131
|
-
// Find the
|
|
132
|
-
// Start with braceCount = 1 because {{#var is already open
|
|
131
|
+
// Find the closing }} for the variable definition, accounting for nested {{#var}}...{{/var}}
|
|
133
132
|
let braceCount = 1;
|
|
134
133
|
const valueStart = equalsIndex + 1;
|
|
135
134
|
let valueEnd = valueStart;
|
|
@@ -400,20 +399,6 @@ class AdvancedCodeGenerator {
|
|
|
400
399
|
!relativePath.startsWith("node_modules/"));
|
|
401
400
|
},
|
|
402
401
|
});
|
|
403
|
-
// If template provides a .env.example, create a .env from it when
|
|
404
|
-
// the target project does not already have a .env file.
|
|
405
|
-
try {
|
|
406
|
-
const envExampleSrc = path.join(templatePath, ".env.example");
|
|
407
|
-
const envDest = path.join(outputPath, ".env");
|
|
408
|
-
if ((await fs.pathExists(envExampleSrc)) && !(await fs.pathExists(envDest))) {
|
|
409
|
-
await fs.copy(envExampleSrc, envDest);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
catch {
|
|
413
|
-
// ignore (not critical)
|
|
414
|
-
}
|
|
415
|
-
// Ensure gitignore is present in target even if template authors
|
|
416
|
-
// renamed it to avoid npm/package issues (e.g. 'gitignore' or '_gitignore')
|
|
417
402
|
try {
|
|
418
403
|
const gitCandidates = [".gitignore", "gitignore", "_gitignore"];
|
|
419
404
|
for (const g of gitCandidates) {
|
|
@@ -430,7 +415,6 @@ class AdvancedCodeGenerator {
|
|
|
430
415
|
catch {
|
|
431
416
|
// ignore
|
|
432
417
|
}
|
|
433
|
-
// Ensure any top-level dotfiles declared in template.json are created
|
|
434
418
|
try {
|
|
435
419
|
const templateJsonPath = path.join(templatePath, "template.json");
|
|
436
420
|
if (await fs.pathExists(templateJsonPath)) {
|
|
@@ -454,9 +438,7 @@ class AdvancedCodeGenerator {
|
|
|
454
438
|
}
|
|
455
439
|
continue;
|
|
456
440
|
}
|
|
457
|
-
// For other dotfiles
|
|
458
|
-
// actually contains a dotfile source. Don't synthesize from non-dot fallbacks
|
|
459
|
-
// to avoid creating files unintentionally.
|
|
441
|
+
// For other dotfiles, only copy if the exact dotfile exists in the template to avoid unintended fallbacks
|
|
460
442
|
const srcDot = path.join(templatePath, f);
|
|
461
443
|
if (await fs.pathExists(srcDot)) {
|
|
462
444
|
await fs.copy(srcDot, targetDest);
|
|
@@ -505,6 +487,17 @@ class AdvancedCodeGenerator {
|
|
|
505
487
|
catch {
|
|
506
488
|
// ignore
|
|
507
489
|
}
|
|
490
|
+
// Handle .env.example -> .env copying if .env doesn't already exist
|
|
491
|
+
try {
|
|
492
|
+
const envExampleDest = path.join(outputPath, ".env.example");
|
|
493
|
+
const envDest = path.join(outputPath, ".env");
|
|
494
|
+
if ((await fs.pathExists(envExampleDest)) && !(await fs.pathExists(envDest))) {
|
|
495
|
+
await fs.copy(envExampleDest, envDest);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
// ignore (not critical)
|
|
500
|
+
}
|
|
508
501
|
}
|
|
509
502
|
}
|
|
510
503
|
processOperationTemplates(operation, context) {
|
|
@@ -559,28 +552,88 @@ class AdvancedCodeGenerator {
|
|
|
559
552
|
async executeCreateFile(operation, context, outputPath) {
|
|
560
553
|
if (!operation.destination)
|
|
561
554
|
return;
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
await fs.ensureDir(path.dirname(destinationPath));
|
|
565
|
-
let content;
|
|
555
|
+
const processedDestination = this.processTemplate(operation.destination, context);
|
|
556
|
+
const { basePath: destinationBasePath, mode: destinationMode } = this.parsePathPattern(processedDestination);
|
|
566
557
|
if (operation.content) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
558
|
+
const destinationPath = path.join(outputPath, processedDestination);
|
|
559
|
+
await fs.ensureDir(path.dirname(destinationPath));
|
|
560
|
+
const content = this.processTemplate(operation.content, context);
|
|
561
|
+
await fs.writeFile(destinationPath, content, "utf-8");
|
|
562
|
+
try {
|
|
563
|
+
const rel = path.relative(outputPath, destinationPath);
|
|
564
|
+
if (rel && !this.createdFiles.includes(rel))
|
|
565
|
+
this.createdFiles.push(rel);
|
|
575
566
|
}
|
|
576
|
-
|
|
577
|
-
|
|
567
|
+
catch {
|
|
568
|
+
// ignore logging failures
|
|
578
569
|
}
|
|
570
|
+
return;
|
|
579
571
|
}
|
|
580
|
-
|
|
572
|
+
if (!operation.source) {
|
|
581
573
|
throw new Error(`Create file operation must have either 'content' or 'source' field`);
|
|
582
574
|
}
|
|
583
|
-
|
|
575
|
+
const processedSource = this.processTemplate(operation.source, context);
|
|
576
|
+
const { basePath: sourceBasePathRel, mode: sourceMode } = this.parsePathPattern(processedSource);
|
|
577
|
+
const sourcePath = (0, generator_utils_1.locateOperationSource)(operation.generatorType, operation.generator, sourceBasePathRel);
|
|
578
|
+
if (!sourcePath || !(await fs.pathExists(sourcePath))) {
|
|
579
|
+
throw new Error(`Source file not found: ${sourcePath}`);
|
|
580
|
+
}
|
|
581
|
+
const sourceStat = await fs.stat(sourcePath);
|
|
582
|
+
const shouldCreateMultipleFiles = sourceStat.isDirectory() || sourceMode !== "single";
|
|
583
|
+
if (shouldCreateMultipleFiles) {
|
|
584
|
+
if (!sourceStat.isDirectory()) {
|
|
585
|
+
throw new Error(`Source path must be a directory for wildcard copy: ${processedSource}`);
|
|
586
|
+
}
|
|
587
|
+
let sourceFiles = [];
|
|
588
|
+
if (sourceMode === "flat") {
|
|
589
|
+
const entries = await fs.readdir(sourcePath);
|
|
590
|
+
for (const entry of entries) {
|
|
591
|
+
const candidatePath = path.join(sourcePath, entry);
|
|
592
|
+
const candidateStat = await fs.stat(candidatePath);
|
|
593
|
+
if (candidateStat.isFile()) {
|
|
594
|
+
sourceFiles.push(candidatePath);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
sourceFiles = await this.collectFilesRecursively(sourcePath);
|
|
600
|
+
}
|
|
601
|
+
for (const sourceFilePath of sourceFiles) {
|
|
602
|
+
let relativeDestinationPath;
|
|
603
|
+
if (destinationMode === "recursive") {
|
|
604
|
+
relativeDestinationPath = path.relative(sourcePath, sourceFilePath);
|
|
605
|
+
}
|
|
606
|
+
else if (destinationMode === "flat") {
|
|
607
|
+
relativeDestinationPath = path.basename(sourceFilePath);
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
relativeDestinationPath =
|
|
611
|
+
sourceMode === "flat"
|
|
612
|
+
? path.basename(sourceFilePath)
|
|
613
|
+
: path.relative(sourcePath, sourceFilePath);
|
|
614
|
+
}
|
|
615
|
+
const destinationPath = path.join(outputPath, destinationBasePath, relativeDestinationPath);
|
|
616
|
+
await fs.ensureDir(path.dirname(destinationPath));
|
|
617
|
+
let content = await fs.readFile(sourceFilePath, "utf-8");
|
|
618
|
+
content = this.processTemplate(content, context);
|
|
619
|
+
await fs.writeFile(destinationPath, content, "utf-8");
|
|
620
|
+
try {
|
|
621
|
+
const rel = path.relative(outputPath, destinationPath);
|
|
622
|
+
if (rel && !this.createdFiles.includes(rel))
|
|
623
|
+
this.createdFiles.push(rel);
|
|
624
|
+
}
|
|
625
|
+
catch {
|
|
626
|
+
// ignore logging failures
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
const destinationPath = destinationMode === "single"
|
|
632
|
+
? path.join(outputPath, processedDestination)
|
|
633
|
+
: path.join(outputPath, destinationBasePath, path.basename(sourcePath));
|
|
634
|
+
await fs.ensureDir(path.dirname(destinationPath));
|
|
635
|
+
let content = await fs.readFile(sourcePath, "utf-8");
|
|
636
|
+
content = this.processTemplate(content, context);
|
|
584
637
|
await fs.writeFile(destinationPath, content, "utf-8");
|
|
585
638
|
try {
|
|
586
639
|
const rel = path.relative(outputPath, destinationPath);
|
|
@@ -591,6 +644,31 @@ class AdvancedCodeGenerator {
|
|
|
591
644
|
// ignore logging failures
|
|
592
645
|
}
|
|
593
646
|
}
|
|
647
|
+
parsePathPattern(inputPath) {
|
|
648
|
+
const normalizedPath = inputPath.replace(/\\/g, "/");
|
|
649
|
+
if (normalizedPath.endsWith("/**")) {
|
|
650
|
+
return { basePath: normalizedPath.slice(0, -3), mode: "recursive" };
|
|
651
|
+
}
|
|
652
|
+
if (normalizedPath.endsWith("/*")) {
|
|
653
|
+
return { basePath: normalizedPath.slice(0, -2), mode: "flat" };
|
|
654
|
+
}
|
|
655
|
+
return { basePath: normalizedPath, mode: "single" };
|
|
656
|
+
}
|
|
657
|
+
async collectFilesRecursively(rootDirPath) {
|
|
658
|
+
const files = [];
|
|
659
|
+
const entries = await fs.readdir(rootDirPath);
|
|
660
|
+
for (const entry of entries) {
|
|
661
|
+
const fullPath = path.join(rootDirPath, entry);
|
|
662
|
+
const stat = await fs.stat(fullPath);
|
|
663
|
+
if (stat.isDirectory()) {
|
|
664
|
+
files.push(...(await this.collectFilesRecursively(fullPath)));
|
|
665
|
+
}
|
|
666
|
+
else if (stat.isFile()) {
|
|
667
|
+
files.push(fullPath);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return files;
|
|
671
|
+
}
|
|
594
672
|
async executePatchFile(operation, context, outputPath) {
|
|
595
673
|
if (!operation.destination)
|
|
596
674
|
return;
|
|
@@ -608,14 +686,12 @@ class AdvancedCodeGenerator {
|
|
|
608
686
|
switch (patchOp.type) {
|
|
609
687
|
case "add-import":
|
|
610
688
|
if (patchOp.imports) {
|
|
611
|
-
// Process imports and trim any accidental surrounding blank lines
|
|
612
689
|
const imports = patchOp.imports
|
|
613
690
|
.map((imp) => this.processTemplate(imp, context))
|
|
614
691
|
.join("\n")
|
|
615
692
|
.replace(/^\n+/, "")
|
|
616
693
|
.replace(/\n+$/, "");
|
|
617
|
-
//
|
|
618
|
-
// extra blank lines.
|
|
694
|
+
// Split content into lines for easier manipulation
|
|
619
695
|
const lines = content.split("\n");
|
|
620
696
|
// Find the last import line index
|
|
621
697
|
let lastImportIndex = -1;
|
|
@@ -623,9 +699,7 @@ class AdvancedCodeGenerator {
|
|
|
623
699
|
if (lines[i].trim().startsWith("import"))
|
|
624
700
|
lastImportIndex = i;
|
|
625
701
|
}
|
|
626
|
-
//
|
|
627
|
-
// immediately after the imports, overwrite that blank line to
|
|
628
|
-
// avoid introducing an extra empty line above the inserted imports.
|
|
702
|
+
// Determine where to insert new imports: after the last existing import, or at the top if no imports exist
|
|
629
703
|
const insertIndex = lastImportIndex === -1 ? 0 : lastImportIndex + 1;
|
|
630
704
|
// Only add imports that don't already exist in the file
|
|
631
705
|
const importLines = imports
|
|
@@ -641,8 +715,7 @@ class AdvancedCodeGenerator {
|
|
|
641
715
|
else {
|
|
642
716
|
lines.splice(insertIndex, 0, ...newImportLines);
|
|
643
717
|
}
|
|
644
|
-
// After
|
|
645
|
-
// Find last import line index again
|
|
718
|
+
// After adding imports, ensure there is exactly one blank line after the last import statement (if there are any imports)
|
|
646
719
|
let lastIdx = -1;
|
|
647
720
|
for (let i = 0; i < lines.length; i++) {
|
|
648
721
|
if (lines[i].trim().startsWith("import"))
|
|
@@ -650,7 +723,6 @@ class AdvancedCodeGenerator {
|
|
|
650
723
|
}
|
|
651
724
|
const nextIdx = lastIdx + 1;
|
|
652
725
|
if (lastIdx !== -1) {
|
|
653
|
-
// Remove multiple blank lines after imports
|
|
654
726
|
let j = nextIdx;
|
|
655
727
|
while (j < lines.length && lines[j].trim() === "") {
|
|
656
728
|
j++;
|
|
@@ -685,7 +757,6 @@ class AdvancedCodeGenerator {
|
|
|
685
757
|
// Normalize code: trim surrounding newlines and ensure single trailing newline
|
|
686
758
|
let codeNormalized = processedCode.replace(/^\n+|\n+$/g, "") + "\n";
|
|
687
759
|
// If right already starts with a newline, avoid double-blank by
|
|
688
|
-
// removing trailing newline from codeNormalized so only one newline remains
|
|
689
760
|
const rightStartsWithNewline = right.startsWith("\n");
|
|
690
761
|
if (rightStartsWithNewline && codeNormalized.endsWith("\n")) {
|
|
691
762
|
codeNormalized = codeNormalized.replace(/\n+$/, "");
|
|
@@ -704,7 +775,6 @@ class AdvancedCodeGenerator {
|
|
|
704
775
|
// Normalize code: trim surrounding newlines and ensure single trailing newline
|
|
705
776
|
let codeNormalized = processedCode.replace(/^\n+|\n+$/g, "") + "\n";
|
|
706
777
|
// If right already starts with a newline, avoid double-blank by
|
|
707
|
-
// removing trailing newline from codeNormalized so only one newline remains
|
|
708
778
|
const rightStartsWithNewline = right.startsWith("\n");
|
|
709
779
|
if (rightStartsWithNewline && codeNormalized.endsWith("\n")) {
|
|
710
780
|
codeNormalized = codeNormalized.replace(/\n+$/, "");
|
|
@@ -840,8 +910,7 @@ class AdvancedCodeGenerator {
|
|
|
840
910
|
if ((type === "framework" && name === selectedModules.framework) ||
|
|
841
911
|
(type === "database" && name === selectedModules.database) ||
|
|
842
912
|
(type === "auth" && name === selectedModules.auth)) {
|
|
843
|
-
//
|
|
844
|
-
// operations. Keep merging scripts from generator configs for now.
|
|
913
|
+
// Merge dependencies, devDependencies, and scripts from the generator into the cumulative objects
|
|
845
914
|
Object.assign(allScripts, generator.scripts);
|
|
846
915
|
}
|
|
847
916
|
}
|
|
@@ -860,10 +929,7 @@ class AdvancedCodeGenerator {
|
|
|
860
929
|
};
|
|
861
930
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
862
931
|
}
|
|
863
|
-
|
|
864
|
-
* Apply generators to an existing project directory instead of creating a new template.
|
|
865
|
-
* This executes applicable operations and updates package.json in-place.
|
|
866
|
-
*/
|
|
932
|
+
// Public method to apply generators to an existing project (used for "Add to existing project" flow)
|
|
867
933
|
async applyToProject(selectedModules, features, projectPath) {
|
|
868
934
|
const context = {
|
|
869
935
|
...selectedModules,
|
|
@@ -934,9 +1000,7 @@ class AdvancedCodeGenerator {
|
|
|
934
1000
|
}
|
|
935
1001
|
return { frameworks, databases, auths };
|
|
936
1002
|
}
|
|
937
|
-
|
|
938
|
-
* Register a generator config dynamically (used for modules that only provide module.json patches).
|
|
939
|
-
*/
|
|
1003
|
+
// Method to register a generator configuration
|
|
940
1004
|
registerGenerator(type, name, config) {
|
|
941
1005
|
this.generators.set(`${type}:${name}`, config);
|
|
942
1006
|
}
|
|
@@ -7,6 +7,6 @@ export declare function writeFile(filePath: string, content: string): Promise<vo
|
|
|
7
7
|
export declare function copyDir(source: string, destination: string, options?: CopyFilterFunction): Promise<void>;
|
|
8
8
|
export declare function readDir(dirPath: string): Promise<string[]>;
|
|
9
9
|
export declare function isDirectory(dirPath: string): Promise<boolean>;
|
|
10
|
-
export declare function findFilesInDir(dirPath: string, predicate: (
|
|
10
|
+
export declare function findFilesInDir(dirPath: string, predicate: (fileName: string) => boolean): Promise<string[]>;
|
|
11
11
|
export declare function getEnvFilePath(projectRoot: string): Promise<string | null>;
|
|
12
12
|
export {};
|
|
@@ -3,11 +3,6 @@
|
|
|
3
3
|
"type": "auth",
|
|
4
4
|
"priority": 10,
|
|
5
5
|
"operations": [
|
|
6
|
-
{
|
|
7
|
-
"type": "create-file",
|
|
8
|
-
"source": "shared/lib/auth.ts",
|
|
9
|
-
"destination": "server/auth/auth.ts"
|
|
10
|
-
},
|
|
11
6
|
{
|
|
12
7
|
"type": "create-file",
|
|
13
8
|
"source": "nextjs/api/auth/[...nextauth]/route.ts",
|
|
@@ -18,6 +13,11 @@
|
|
|
18
13
|
"source": "nextjs/proxy.ts",
|
|
19
14
|
"destination": "proxy.ts"
|
|
20
15
|
},
|
|
16
|
+
{
|
|
17
|
+
"type": "create-file",
|
|
18
|
+
"source": "shared/lib/auth.ts",
|
|
19
|
+
"destination": "server/auth/auth.ts"
|
|
20
|
+
},
|
|
21
21
|
{
|
|
22
22
|
"type": "patch-file",
|
|
23
23
|
"destination": "prisma/schema.prisma",
|
|
@@ -36,6 +36,17 @@
|
|
|
36
36
|
"@auth/prisma-adapter": "^2.11.1"
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
|
+
{
|
|
40
|
+
"type": "add-dependency",
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"bcryptjs": "^3.0.3",
|
|
43
|
+
"next-auth": "^5.0.0-beta.30",
|
|
44
|
+
"nodemailer": "^7.0.12"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/nodemailer": "^7.0.5"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
39
50
|
{
|
|
40
51
|
"type": "add-env",
|
|
41
52
|
"envVars": {
|
|
@@ -48,17 +59,6 @@
|
|
|
48
59
|
"EMAIL_PASS": "",
|
|
49
60
|
"EMAIL_FROM": "noreply@yourapp.com"
|
|
50
61
|
}
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"type": "add-dependency",
|
|
54
|
-
"dependencies": {
|
|
55
|
-
"next-auth": "^5.0.0-beta.30",
|
|
56
|
-
"bcryptjs": "^3.0.3",
|
|
57
|
-
"nodemailer": "^7.0.12"
|
|
58
|
-
},
|
|
59
|
-
"devDependencies": {
|
|
60
|
-
"@types/nodemailer": "^7.0.5"
|
|
61
|
-
}
|
|
62
62
|
}
|
|
63
63
|
]
|
|
64
64
|
}
|