stackkit-cli 0.4.4 → 0.4.5
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 +26 -2
- package/bin/stackkit.js +1 -1
- package/dist/commands/add.js +20 -39
- package/dist/commands/doctor.d.ts +11 -0
- package/dist/commands/doctor.js +483 -0
- package/dist/commands/list.d.ts +1 -1
- package/dist/commands/list.js +59 -38
- package/dist/index.js +11 -13
- package/dist/types/index.d.ts +15 -0
- package/dist/utils/config-utils.d.ts +2 -0
- package/dist/utils/config-utils.js +88 -0
- package/dist/utils/detect.js +12 -2
- package/dist/utils/env-editor.d.ts +0 -1
- package/dist/utils/env-editor.js +50 -25
- package/dist/utils/files.d.ts +8 -0
- package/dist/utils/files.js +51 -0
- package/dist/utils/js-conversion.d.ts +1 -0
- package/dist/utils/js-conversion.js +244 -0
- package/dist/utils/module-utils.d.ts +2 -0
- package/dist/utils/module-utils.js +461 -0
- package/dist/utils/package-manager.js +15 -31
- package/modules/auth/authjs/files/api/auth/[...nextauth]/route.ts +6 -0
- package/modules/auth/authjs/files/lib/auth-client.ts +11 -0
- package/modules/auth/authjs/files/lib/auth.ts +41 -0
- package/modules/auth/authjs/files/schemas/prisma-schema.prisma +45 -0
- package/modules/auth/authjs/module.json +95 -0
- package/modules/auth/better-auth/files/lib/auth-client.ts +7 -0
- package/modules/auth/better-auth/files/lib/auth.ts +62 -0
- package/modules/auth/better-auth/files/lib/email-service.ts +34 -0
- package/modules/auth/better-auth/files/lib/email-templates.ts +89 -0
- package/modules/auth/better-auth/files/schemas/prisma-schema.prisma +1 -1
- package/modules/auth/better-auth/module.json +164 -27
- package/modules/database/mongoose/files/lib/db.ts +68 -0
- package/modules/database/{mongoose-mongodb → mongoose}/files/models/User.ts +0 -5
- package/modules/database/{mongoose-mongodb → mongoose}/module.json +9 -24
- package/modules/database/prisma/files/lib/prisma.ts +1 -3
- package/modules/database/prisma/files/prisma/schema.prisma +1 -1
- package/modules/database/prisma/files/prisma.config.ts +2 -2
- package/modules/database/prisma/module.json +5 -23
- package/package.json +1 -1
- package/templates/express/.env.example +0 -1
- package/templates/express/package.json +4 -4
- package/templates/express/src/app.ts +2 -2
- package/templates/express/src/features/health/health.controller.ts +18 -0
- package/templates/express/src/features/health/health.route.ts +9 -0
- package/templates/express/src/features/health/health.service.ts +6 -0
- package/templates/nextjs/lib/env.ts +8 -0
- package/templates/nextjs/package.json +7 -7
- package/templates/react-vite/.env.example +1 -2
- package/templates/react-vite/.prettierignore +4 -0
- package/templates/react-vite/.prettierrc +9 -0
- package/templates/react-vite/README.md +22 -0
- package/templates/react-vite/package.json +16 -16
- package/templates/react-vite/src/router.tsx +0 -12
- package/templates/react-vite/vite.config.ts +0 -6
- package/dist/commands/init.d.ts +0 -10
- package/dist/commands/init.js +0 -157
- package/dist/utils/code-inject.d.ts +0 -14
- package/dist/utils/code-inject.js +0 -70
- package/dist/utils/json-editor.d.ts +0 -8
- package/dist/utils/json-editor.js +0 -45
- package/modules/auth/clerk/files/express/auth.ts +0 -7
- package/modules/auth/clerk/files/nextjs/auth-provider.tsx +0 -5
- package/modules/auth/clerk/files/nextjs/middleware.ts +0 -9
- package/modules/auth/clerk/files/react/auth-provider.tsx +0 -15
- package/modules/auth/clerk/module.json +0 -115
- package/modules/database/mongoose-mongodb/files/lib/db.ts +0 -78
- package/templates/express/src/features/auth/auth.controller.ts +0 -48
- package/templates/express/src/features/auth/auth.route.ts +0 -10
- package/templates/express/src/features/auth/auth.service.ts +0 -21
- package/templates/react-vite/src/api/services/user.service.ts +0 -18
- package/templates/react-vite/src/pages/UserProfile.tsx +0 -40
- package/templates/react-vite/src/types/user.d.ts +0 -6
package/README.md
CHANGED
|
@@ -5,6 +5,9 @@ Add authentication and database modules to existing projects.
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
+
# Check project health and compatibility
|
|
9
|
+
npx stackkit-cli doctor
|
|
10
|
+
|
|
8
11
|
# Add authentication
|
|
9
12
|
npx stackkit-cli add auth
|
|
10
13
|
|
|
@@ -21,19 +24,40 @@ npx stackkit-cli list
|
|
|
21
24
|
- **Shows compatible modules** for your project
|
|
22
25
|
- **Installs dependencies** automatically
|
|
23
26
|
- **Configures everything** - files, env vars, and setup
|
|
27
|
+
- **Health checks** with `doctor` command
|
|
24
28
|
|
|
25
29
|
## Available Modules
|
|
26
30
|
|
|
27
31
|
### Authentication
|
|
28
32
|
|
|
29
33
|
- Better Auth (Next.js, Express, React)
|
|
30
|
-
- Clerk (Next.js, Express, React)
|
|
31
34
|
|
|
32
35
|
### Database
|
|
33
36
|
|
|
34
37
|
- Prisma with PostgreSQL or MongoDB (Next.js, Express)
|
|
35
38
|
- Mongoose with MongoDB (Next.js, Express)
|
|
36
39
|
|
|
40
|
+
## Commands
|
|
41
|
+
|
|
42
|
+
### `doctor`
|
|
43
|
+
|
|
44
|
+
Check your project's health and compatibility with StackKit modules.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
stackkit doctor # Basic health check
|
|
48
|
+
stackkit doctor --verbose # Detailed output
|
|
49
|
+
stackkit doctor --json # Machine-readable JSON output
|
|
50
|
+
stackkit doctor --strict # Treat warnings as errors
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The doctor command:
|
|
54
|
+
- Detects your project type and framework
|
|
55
|
+
- Checks Node.js version compatibility
|
|
56
|
+
- Identifies installed StackKit modules
|
|
57
|
+
- Validates environment variables
|
|
58
|
+
- Checks for configuration conflicts
|
|
59
|
+
- Provides actionable next steps
|
|
60
|
+
|
|
37
61
|
## Documentation
|
|
38
62
|
|
|
39
|
-
Full documentation: [stackkit.dev](https://stackkit.dev) | [GitHub](https://github.com/tariqul420/stackkit)
|
|
63
|
+
Full documentation: [stackkit.dev](https://stackkit.dev) | [GitHub](https://github.com/tariqul420/stackkit/issues)
|
package/bin/stackkit.js
CHANGED
package/dist/commands/add.js
CHANGED
|
@@ -50,38 +50,19 @@ async function addCommand(module, options) {
|
|
|
50
50
|
// Merge dependencies based on provider
|
|
51
51
|
const mergedDeps = {};
|
|
52
52
|
const mergedDevDeps = {};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
Object.assign(mergedDeps, moduleMetadata.dependencies);
|
|
53
|
+
// Add shared dependencies
|
|
54
|
+
if (moduleMetadata.frameworkConfigs?.shared?.dependencies) {
|
|
55
|
+
Object.assign(mergedDeps, moduleMetadata.frameworkConfigs.shared.dependencies);
|
|
57
56
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
Object.assign(mergedDeps, moduleMetadata.dependencies.common);
|
|
61
|
-
if (selectedProvider &&
|
|
62
|
-
typeof moduleMetadata.dependencies === "object" &&
|
|
63
|
-
"providers" in moduleMetadata.dependencies &&
|
|
64
|
-
typeof moduleMetadata.dependencies.providers === "object" &&
|
|
65
|
-
selectedProvider in moduleMetadata.dependencies.providers) {
|
|
66
|
-
Object.assign(mergedDeps, moduleMetadata.dependencies.providers[selectedProvider]);
|
|
67
|
-
}
|
|
57
|
+
if (moduleMetadata.frameworkConfigs?.shared?.devDependencies) {
|
|
58
|
+
Object.assign(mergedDevDeps, moduleMetadata.frameworkConfigs.shared.devDependencies);
|
|
68
59
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"common" in moduleMetadata.devDependencies) {
|
|
76
|
-
Object.assign(mergedDevDeps, moduleMetadata.devDependencies.common);
|
|
77
|
-
if (selectedProvider &&
|
|
78
|
-
typeof moduleMetadata.devDependencies === "object" &&
|
|
79
|
-
"providers" in moduleMetadata.devDependencies &&
|
|
80
|
-
typeof moduleMetadata.devDependencies.providers === "object" &&
|
|
81
|
-
selectedProvider in moduleMetadata.devDependencies.providers) {
|
|
82
|
-
Object.assign(mergedDevDeps, moduleMetadata.devDependencies.providers[selectedProvider]);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
60
|
+
// Add provider specific dependencies
|
|
61
|
+
if (selectedProvider && moduleMetadata.databaseAdapters?.providers?.[selectedProvider]?.dependencies) {
|
|
62
|
+
Object.assign(mergedDeps, moduleMetadata.databaseAdapters.providers[selectedProvider].dependencies);
|
|
63
|
+
}
|
|
64
|
+
if (selectedProvider && moduleMetadata.databaseAdapters?.providers?.[selectedProvider]?.devDependencies) {
|
|
65
|
+
Object.assign(mergedDevDeps, moduleMetadata.databaseAdapters.providers[selectedProvider].devDependencies);
|
|
85
66
|
}
|
|
86
67
|
// Update metadata with merged deps
|
|
87
68
|
moduleMetadata.dependencies = mergedDeps;
|
|
@@ -149,8 +130,8 @@ async function addCommand(module, options) {
|
|
|
149
130
|
}
|
|
150
131
|
}
|
|
151
132
|
// Add dependencies
|
|
152
|
-
if (Object.keys(
|
|
153
|
-
const deps = Object.entries(
|
|
133
|
+
if (Object.keys(mergedDeps).length > 0 && options.install !== false) {
|
|
134
|
+
const deps = Object.entries(mergedDeps).map(([name, version]) => `${name}@${version}`);
|
|
154
135
|
if (!options.dryRun) {
|
|
155
136
|
await (0, package_manager_1.addDependencies)(projectRoot, projectInfo.packageManager, deps, false);
|
|
156
137
|
}
|
|
@@ -159,10 +140,8 @@ async function addCommand(module, options) {
|
|
|
159
140
|
}
|
|
160
141
|
}
|
|
161
142
|
// Add dev dependencies
|
|
162
|
-
if (
|
|
163
|
-
Object.
|
|
164
|
-
options.install !== false) {
|
|
165
|
-
const devDeps = Object.entries(moduleMetadata.devDependencies).map(([name, version]) => `${name}@${version}`);
|
|
143
|
+
if (Object.keys(mergedDevDeps).length > 0 && options.install !== false) {
|
|
144
|
+
const devDeps = Object.entries(mergedDevDeps).map(([name, version]) => `${name}@${version}`);
|
|
166
145
|
if (!options.dryRun) {
|
|
167
146
|
await (0, package_manager_1.addDependencies)(projectRoot, projectInfo.packageManager, devDeps, true);
|
|
168
147
|
}
|
|
@@ -171,7 +150,7 @@ async function addCommand(module, options) {
|
|
|
171
150
|
}
|
|
172
151
|
}
|
|
173
152
|
// Add environment variables
|
|
174
|
-
if (moduleMetadata.envVars.length > 0) {
|
|
153
|
+
if (moduleMetadata.envVars && moduleMetadata.envVars.length > 0) {
|
|
175
154
|
// Replace variables in envVars
|
|
176
155
|
const processedEnvVars = moduleMetadata.envVars.map((envVar) => ({
|
|
177
156
|
...envVar,
|
|
@@ -188,7 +167,7 @@ async function addCommand(module, options) {
|
|
|
188
167
|
logger_1.logger.success(`Added ${chalk_1.default.bold(moduleMetadata.displayName)}`);
|
|
189
168
|
logger_1.logger.newLine();
|
|
190
169
|
// Print next steps
|
|
191
|
-
if (moduleMetadata.envVars.some((v) => v.required)) {
|
|
170
|
+
if (moduleMetadata.envVars && moduleMetadata.envVars.some((v) => v.required)) {
|
|
192
171
|
logger_1.logger.log("Next: Fill in environment variables in .env");
|
|
193
172
|
}
|
|
194
173
|
logger_1.logger.newLine();
|
|
@@ -235,8 +214,10 @@ async function loadModuleMetadata(modulesDir, moduleName, provider) {
|
|
|
235
214
|
}
|
|
236
215
|
return null;
|
|
237
216
|
}
|
|
238
|
-
// (removed duplicate import)
|
|
239
217
|
async function applyModulePatches(projectRoot, projectInfo, moduleMetadata, modulesDir, moduleName, options) {
|
|
218
|
+
if (!moduleMetadata.patches || !Array.isArray(moduleMetadata.patches)) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
240
221
|
// Find the module path
|
|
241
222
|
const moduleBasePath = await findModulePath(modulesDir, moduleName, options.provider);
|
|
242
223
|
if (!moduleBasePath) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface DoctorOptions {
|
|
2
|
+
json?: boolean;
|
|
3
|
+
verbose?: boolean;
|
|
4
|
+
strict?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Main doctor command function that performs health checks on a StackKit project
|
|
8
|
+
* @param options Command line options
|
|
9
|
+
*/
|
|
10
|
+
export declare function doctorCommand(options: DoctorOptions): Promise<void>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,483 @@
|
|
|
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.doctorCommand = doctorCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const logger_1 = require("../utils/logger");
|
|
11
|
+
// Constants for consistent messaging
|
|
12
|
+
const MESSAGES = {
|
|
13
|
+
NO_PACKAGE_JSON: "No package.json found in current directory or any parent directory.",
|
|
14
|
+
UNSUPPORTED_PROJECT: "Unsupported project type. Only Next.js, Express, and React projects are supported.",
|
|
15
|
+
NODE_TOO_OLD: (version) => `Node.js version ${version} is not supported. Minimum required: Node 18.`,
|
|
16
|
+
NODE_WARNING: (version) => `Node.js version ${version} is supported but Node 20+ is recommended.`,
|
|
17
|
+
NODE_SUCCESS: (version) => `Node.js version ${version} is supported.`,
|
|
18
|
+
ENV_EXAMPLE_MISSING: ".env.example file missing (recommended for documentation)",
|
|
19
|
+
ENV_EXAMPLE_FOUND: ".env.example file found",
|
|
20
|
+
ENV_MISSING: "No .env or .env.local file found",
|
|
21
|
+
ENV_FOUND: ".env/.env.local file found",
|
|
22
|
+
PRISMA_SCHEMA_MISSING: "Prisma schema missing (required for Prisma)",
|
|
23
|
+
PRISMA_SCHEMA_FOUND: "Prisma schema found",
|
|
24
|
+
AUTH_ROUTES_MISSING: "Auth routes not found (may need configuration)",
|
|
25
|
+
AUTH_ROUTES_FOUND: "Auth routes configured",
|
|
26
|
+
ENV_VARS_MISSING: (vars) => `Missing: ${vars.join(", ")}`,
|
|
27
|
+
ENV_VARS_PRESENT: (vars) => `Present: ${vars.join(", ")}`,
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Main doctor command function that performs health checks on a StackKit project
|
|
31
|
+
* @param options Command line options
|
|
32
|
+
*/
|
|
33
|
+
async function doctorCommand(options) {
|
|
34
|
+
try {
|
|
35
|
+
const report = await runDoctorChecks();
|
|
36
|
+
if (options.json) {
|
|
37
|
+
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
printDoctorReport(report, options.verbose || false);
|
|
41
|
+
// Exit with proper codes
|
|
42
|
+
const hasErrors = report.summary.errors > 0;
|
|
43
|
+
const hasWarnings = report.summary.warnings > 0;
|
|
44
|
+
const strictMode = options.strict || false;
|
|
45
|
+
if (hasErrors || (strictMode && hasWarnings)) {
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
logger_1.logger.error(`Doctor check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Runs all doctor checks and generates a comprehensive health report
|
|
59
|
+
* @returns Promise resolving to a DoctorReport
|
|
60
|
+
*/
|
|
61
|
+
async function runDoctorChecks() {
|
|
62
|
+
const checks = [];
|
|
63
|
+
// Find project root
|
|
64
|
+
const projectRoot = await findProjectRoot();
|
|
65
|
+
checks.push({
|
|
66
|
+
status: "success",
|
|
67
|
+
message: `Found project root: ${projectRoot}`,
|
|
68
|
+
});
|
|
69
|
+
// Read package.json
|
|
70
|
+
const packageJson = await readPackageJson(projectRoot);
|
|
71
|
+
// Detect package manager
|
|
72
|
+
const packageManager = await detectPackageManager(projectRoot);
|
|
73
|
+
// Detect project type
|
|
74
|
+
const projectType = detectProjectType(packageJson);
|
|
75
|
+
if (projectType === "unknown") {
|
|
76
|
+
checks.push({
|
|
77
|
+
status: "error",
|
|
78
|
+
message: MESSAGES.UNSUPPORTED_PROJECT,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
checks.push({
|
|
83
|
+
status: "success",
|
|
84
|
+
message: `Detected project type: ${projectType}`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// Check Node version
|
|
88
|
+
const nodeVersionCheck = checkNodeVersion();
|
|
89
|
+
checks.push(nodeVersionCheck);
|
|
90
|
+
// Detect installed modules
|
|
91
|
+
const authModules = detectAuthModules(packageJson);
|
|
92
|
+
const databaseModules = detectDatabaseModules(packageJson);
|
|
93
|
+
// Check key files
|
|
94
|
+
const filesCheck = await checkKeyFiles(projectRoot, projectType, authModules, databaseModules);
|
|
95
|
+
checks.push(...filesCheck);
|
|
96
|
+
// Check env files
|
|
97
|
+
const envCheck = await checkEnvFiles(projectRoot, authModules, databaseModules);
|
|
98
|
+
checks.push(...envCheck.checks);
|
|
99
|
+
// Check conflicts
|
|
100
|
+
const conflicts = checkConflicts(authModules, databaseModules);
|
|
101
|
+
conflicts.forEach(conflict => {
|
|
102
|
+
checks.push({
|
|
103
|
+
status: "warning",
|
|
104
|
+
message: conflict,
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
// Build report
|
|
108
|
+
const report = {
|
|
109
|
+
project: {
|
|
110
|
+
type: projectType,
|
|
111
|
+
root: projectRoot,
|
|
112
|
+
packageManager,
|
|
113
|
+
},
|
|
114
|
+
runtime: {
|
|
115
|
+
nodeVersion: process.version,
|
|
116
|
+
nodeVersionStatus: nodeVersionCheck.status,
|
|
117
|
+
},
|
|
118
|
+
modules: {
|
|
119
|
+
auth: authModules,
|
|
120
|
+
database: databaseModules,
|
|
121
|
+
},
|
|
122
|
+
files: {
|
|
123
|
+
envExample: await fs_extra_1.default.pathExists(path_1.default.join(projectRoot, ".env.example")),
|
|
124
|
+
env: await fs_extra_1.default.pathExists(path_1.default.join(projectRoot, ".env")) || await fs_extra_1.default.pathExists(path_1.default.join(projectRoot, ".env.local")),
|
|
125
|
+
prismaSchema: databaseModules.includes("prisma") ? await fs_extra_1.default.pathExists(path_1.default.join(projectRoot, "prisma", "schema.prisma")) : undefined,
|
|
126
|
+
authRoutes: authModules.length > 0 ? await checkAuthRoutesExist(projectRoot, projectType) : undefined,
|
|
127
|
+
},
|
|
128
|
+
env: envCheck.envStatus,
|
|
129
|
+
conflicts,
|
|
130
|
+
checks,
|
|
131
|
+
summary: {
|
|
132
|
+
errors: checks.filter(c => c.status === "error").length,
|
|
133
|
+
warnings: checks.filter(c => c.status === "warning").length,
|
|
134
|
+
suggestions: generateSuggestions(),
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
return report;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Finds the project root by walking up directories until package.json is found
|
|
141
|
+
* @returns Promise resolving to the absolute path of the project root
|
|
142
|
+
* @throws Error if no package.json is found
|
|
143
|
+
*/
|
|
144
|
+
async function findProjectRoot() {
|
|
145
|
+
let currentDir = process.cwd();
|
|
146
|
+
while (currentDir !== path_1.default.dirname(currentDir)) {
|
|
147
|
+
const packageJsonPath = path_1.default.join(currentDir, "package.json");
|
|
148
|
+
if (await fs_extra_1.default.pathExists(packageJsonPath)) {
|
|
149
|
+
return currentDir;
|
|
150
|
+
}
|
|
151
|
+
currentDir = path_1.default.dirname(currentDir);
|
|
152
|
+
}
|
|
153
|
+
throw new Error(MESSAGES.NO_PACKAGE_JSON);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Reads and parses package.json from the project root
|
|
157
|
+
* @param projectRoot Absolute path to the project root
|
|
158
|
+
* @returns Promise resolving to the parsed package.json object
|
|
159
|
+
*/
|
|
160
|
+
async function readPackageJson(projectRoot) {
|
|
161
|
+
const packageJsonPath = path_1.default.join(projectRoot, "package.json");
|
|
162
|
+
return await fs_extra_1.default.readJSON(packageJsonPath);
|
|
163
|
+
}
|
|
164
|
+
async function detectPackageManager(projectRoot) {
|
|
165
|
+
const checks = [
|
|
166
|
+
{ file: "pnpm-lock.yaml", manager: "pnpm" },
|
|
167
|
+
{ file: "yarn.lock", manager: "yarn" },
|
|
168
|
+
{ file: "package-lock.json", manager: "npm" },
|
|
169
|
+
];
|
|
170
|
+
for (const check of checks) {
|
|
171
|
+
if (await fs_extra_1.default.pathExists(path_1.default.join(projectRoot, check.file))) {
|
|
172
|
+
return check.manager;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return "npm";
|
|
176
|
+
}
|
|
177
|
+
function detectProjectType(packageJson) {
|
|
178
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
179
|
+
if (deps.next) {
|
|
180
|
+
return "nextjs";
|
|
181
|
+
}
|
|
182
|
+
else if (deps.express) {
|
|
183
|
+
return "express";
|
|
184
|
+
}
|
|
185
|
+
else if (deps.vite && deps.react) {
|
|
186
|
+
return "react-vite";
|
|
187
|
+
}
|
|
188
|
+
else if (deps.react) {
|
|
189
|
+
return "react";
|
|
190
|
+
}
|
|
191
|
+
return "unknown";
|
|
192
|
+
}
|
|
193
|
+
function checkNodeVersion() {
|
|
194
|
+
const version = process.version;
|
|
195
|
+
const majorVersion = parseInt(version.slice(1).split(".")[0]);
|
|
196
|
+
if (majorVersion < 18) {
|
|
197
|
+
return {
|
|
198
|
+
status: "error",
|
|
199
|
+
message: MESSAGES.NODE_TOO_OLD(version),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
else if (majorVersion < 20) {
|
|
203
|
+
return {
|
|
204
|
+
status: "warning",
|
|
205
|
+
message: MESSAGES.NODE_WARNING(version),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
return {
|
|
210
|
+
status: "success",
|
|
211
|
+
message: MESSAGES.NODE_SUCCESS(version),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function detectAuthModules(packageJson) {
|
|
216
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
217
|
+
const modules = [];
|
|
218
|
+
if (deps["better-auth"]) {
|
|
219
|
+
modules.push("better-auth");
|
|
220
|
+
}
|
|
221
|
+
if (deps["next-auth"]) {
|
|
222
|
+
modules.push("authjs");
|
|
223
|
+
}
|
|
224
|
+
return modules;
|
|
225
|
+
}
|
|
226
|
+
function detectDatabaseModules(packageJson) {
|
|
227
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
228
|
+
const modules = [];
|
|
229
|
+
if (deps["@prisma/client"] || deps["prisma"]) {
|
|
230
|
+
modules.push("prisma");
|
|
231
|
+
}
|
|
232
|
+
if (deps["mongoose"]) {
|
|
233
|
+
modules.push("mongoose");
|
|
234
|
+
}
|
|
235
|
+
return modules;
|
|
236
|
+
}
|
|
237
|
+
async function checkKeyFiles(projectRoot, projectType, authModules, databaseModules) {
|
|
238
|
+
const checks = [];
|
|
239
|
+
// Check .env.example
|
|
240
|
+
const envExampleExists = await fs_extra_1.default.pathExists(path_1.default.join(projectRoot, ".env.example"));
|
|
241
|
+
checks.push({
|
|
242
|
+
status: envExampleExists ? "success" : "warning",
|
|
243
|
+
message: envExampleExists ? ".env.example file found" : ".env.example file missing (recommended for documentation)",
|
|
244
|
+
});
|
|
245
|
+
// Check Prisma schema
|
|
246
|
+
if (databaseModules.includes("prisma")) {
|
|
247
|
+
const schemaExists = await fs_extra_1.default.pathExists(path_1.default.join(projectRoot, "prisma", "schema.prisma"));
|
|
248
|
+
checks.push({
|
|
249
|
+
status: schemaExists ? "success" : "error",
|
|
250
|
+
message: schemaExists ? "Prisma schema found" : "Prisma schema missing (required for Prisma)",
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
// Check auth routes
|
|
254
|
+
if (authModules.length > 0 && projectType === "nextjs") {
|
|
255
|
+
const authRoutesExist = await checkAuthRoutesExist(projectRoot, projectType);
|
|
256
|
+
checks.push({
|
|
257
|
+
status: authRoutesExist ? "success" : "warning",
|
|
258
|
+
message: authRoutesExist ? "Auth routes configured" : "Auth routes not found (may need configuration)",
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return checks;
|
|
262
|
+
}
|
|
263
|
+
async function checkAuthRoutesExist(projectRoot, projectType) {
|
|
264
|
+
if (projectType !== "nextjs")
|
|
265
|
+
return true; // Skip for non-Next.js
|
|
266
|
+
const possiblePaths = [
|
|
267
|
+
"app/api/auth/[...nextauth]/route.ts",
|
|
268
|
+
"app/api/auth/[...nextauth]/route.js",
|
|
269
|
+
"src/app/api/auth/[...nextauth]/route.ts",
|
|
270
|
+
"src/app/api/auth/[...nextauth]/route.js",
|
|
271
|
+
"pages/api/auth/[...nextauth].ts",
|
|
272
|
+
"pages/api/auth/[...nextauth].js",
|
|
273
|
+
"src/pages/api/auth/[...nextauth].ts",
|
|
274
|
+
"src/pages/api/auth/[...nextauth].js",
|
|
275
|
+
];
|
|
276
|
+
for (const routePath of possiblePaths) {
|
|
277
|
+
if (await fs_extra_1.default.pathExists(path_1.default.join(projectRoot, routePath))) {
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
async function checkEnvFiles(projectRoot, authModules, databaseModules) {
|
|
284
|
+
const checks = [];
|
|
285
|
+
const requiredKeys = [];
|
|
286
|
+
const missing = [];
|
|
287
|
+
const present = [];
|
|
288
|
+
// Determine required env keys
|
|
289
|
+
if (databaseModules.includes("prisma")) {
|
|
290
|
+
requiredKeys.push("DATABASE_URL");
|
|
291
|
+
}
|
|
292
|
+
if (authModules.includes("authjs")) {
|
|
293
|
+
requiredKeys.push("NEXTAUTH_SECRET", "NEXTAUTH_URL");
|
|
294
|
+
}
|
|
295
|
+
if (authModules.includes("better-auth")) {
|
|
296
|
+
requiredKeys.push("BETTER_AUTH_SECRET", "BETTER_AUTH_URL");
|
|
297
|
+
}
|
|
298
|
+
// Check env files
|
|
299
|
+
const envPaths = [".env", ".env.local"];
|
|
300
|
+
let envContent = "";
|
|
301
|
+
for (const envPath of envPaths) {
|
|
302
|
+
const fullPath = path_1.default.join(projectRoot, envPath);
|
|
303
|
+
if (await fs_extra_1.default.pathExists(fullPath)) {
|
|
304
|
+
envContent = await fs_extra_1.default.readFile(fullPath, "utf-8");
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (!envContent) {
|
|
309
|
+
checks.push({
|
|
310
|
+
status: "warning",
|
|
311
|
+
message: "No .env or .env.local file found",
|
|
312
|
+
});
|
|
313
|
+
return { checks, envStatus: { missing: requiredKeys, present: [] } };
|
|
314
|
+
}
|
|
315
|
+
// Parse env content
|
|
316
|
+
const envLines = envContent.split("\n").map(line => line.trim()).filter(line => line && !line.startsWith("#"));
|
|
317
|
+
const envVars = new Set(envLines.map(line => line.split("=")[0]));
|
|
318
|
+
for (const key of requiredKeys) {
|
|
319
|
+
if (envVars.has(key)) {
|
|
320
|
+
present.push(key);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
missing.push(key);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (missing.length > 0) {
|
|
327
|
+
checks.push({
|
|
328
|
+
status: "error",
|
|
329
|
+
message: `Missing required environment variables: ${missing.join(", ")}`,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
else if (requiredKeys.length > 0) {
|
|
333
|
+
checks.push({
|
|
334
|
+
status: "success",
|
|
335
|
+
message: "All required environment variables are present",
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
return { checks, envStatus: { missing, present } };
|
|
339
|
+
}
|
|
340
|
+
function checkConflicts(authModules, databaseModules) {
|
|
341
|
+
const conflicts = [];
|
|
342
|
+
if (authModules.length > 1) {
|
|
343
|
+
conflicts.push(`Multiple auth providers detected: ${authModules.join(", ")}. Consider using only one.`);
|
|
344
|
+
}
|
|
345
|
+
if (databaseModules.length > 1) {
|
|
346
|
+
conflicts.push(`Multiple database providers detected: ${databaseModules.join(", ")}. Consider using only one.`);
|
|
347
|
+
}
|
|
348
|
+
return conflicts;
|
|
349
|
+
}
|
|
350
|
+
function generateSuggestions() {
|
|
351
|
+
const suggestions = [];
|
|
352
|
+
// Always show available commands
|
|
353
|
+
suggestions.push("stackkit add auth - Add authentication module");
|
|
354
|
+
suggestions.push("stackkit add db - Add database module");
|
|
355
|
+
suggestions.push("stackkit list - View available modules");
|
|
356
|
+
return suggestions;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Prints the doctor report to the console with appropriate formatting
|
|
360
|
+
* @param report The doctor report to print
|
|
361
|
+
* @param verbose Whether to show detailed check information
|
|
362
|
+
*/
|
|
363
|
+
function printDoctorReport(report, verbose) {
|
|
364
|
+
logger_1.logger.header("🔍 StackKit Doctor Report");
|
|
365
|
+
logger_1.logger.newLine();
|
|
366
|
+
// Project info
|
|
367
|
+
logger_1.logger.log(chalk_1.default.bold("Project"));
|
|
368
|
+
logger_1.logger.log(` Type: ${report.project.type}`);
|
|
369
|
+
logger_1.logger.log(` Root: ${report.project.root}`);
|
|
370
|
+
logger_1.logger.log(` Package Manager: ${report.project.packageManager}`);
|
|
371
|
+
logger_1.logger.newLine();
|
|
372
|
+
// Runtime
|
|
373
|
+
logger_1.logger.log(chalk_1.default.bold("Runtime"));
|
|
374
|
+
if (report.runtime.nodeVersionStatus === "success") {
|
|
375
|
+
logger_1.logger.success(`Node.js: ${report.runtime.nodeVersion}`);
|
|
376
|
+
}
|
|
377
|
+
else if (report.runtime.nodeVersionStatus === "warning") {
|
|
378
|
+
logger_1.logger.warn(`Node.js: ${report.runtime.nodeVersion}`);
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
logger_1.logger.error(`Node.js: ${report.runtime.nodeVersion}`);
|
|
382
|
+
}
|
|
383
|
+
logger_1.logger.newLine();
|
|
384
|
+
// Modules
|
|
385
|
+
logger_1.logger.log(chalk_1.default.bold("Modules"));
|
|
386
|
+
if (report.modules.auth.length > 0) {
|
|
387
|
+
logger_1.logger.log(` Auth: ${report.modules.auth.join(", ")}`);
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
logger_1.logger.info("Auth: None");
|
|
391
|
+
}
|
|
392
|
+
if (report.modules.database.length > 0) {
|
|
393
|
+
logger_1.logger.log(` Database: ${report.modules.database.join(", ")}`);
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
logger_1.logger.info("Database: None");
|
|
397
|
+
}
|
|
398
|
+
logger_1.logger.newLine();
|
|
399
|
+
// Files
|
|
400
|
+
logger_1.logger.log(chalk_1.default.bold("Files"));
|
|
401
|
+
if (report.files.envExample) {
|
|
402
|
+
logger_1.logger.success(MESSAGES.ENV_EXAMPLE_FOUND);
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
logger_1.logger.warn(MESSAGES.ENV_EXAMPLE_MISSING);
|
|
406
|
+
logger_1.logger.log(" Hint: Helps other developers understand required environment variables");
|
|
407
|
+
}
|
|
408
|
+
if (report.files.env) {
|
|
409
|
+
logger_1.logger.success(MESSAGES.ENV_FOUND);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
logger_1.logger.warn(MESSAGES.ENV_MISSING);
|
|
413
|
+
logger_1.logger.log(" Hint: Required for local development and production deployment");
|
|
414
|
+
}
|
|
415
|
+
if (report.files.prismaSchema !== undefined) {
|
|
416
|
+
if (report.files.prismaSchema) {
|
|
417
|
+
logger_1.logger.success(MESSAGES.PRISMA_SCHEMA_FOUND);
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
logger_1.logger.error(MESSAGES.PRISMA_SCHEMA_MISSING);
|
|
421
|
+
logger_1.logger.log(" Hint: Defines your database schema and is required for Prisma to work");
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (report.files.authRoutes !== undefined) {
|
|
425
|
+
if (report.files.authRoutes) {
|
|
426
|
+
logger_1.logger.success(MESSAGES.AUTH_ROUTES_FOUND);
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
logger_1.logger.warn(MESSAGES.AUTH_ROUTES_MISSING);
|
|
430
|
+
logger_1.logger.log(" Hint: Authentication routes handle login/logout flows");
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
logger_1.logger.newLine();
|
|
434
|
+
// Environment
|
|
435
|
+
if (report.env.missing.length > 0 || report.env.present.length > 0) {
|
|
436
|
+
logger_1.logger.log(chalk_1.default.bold("Environment Variables"));
|
|
437
|
+
if (report.env.present.length > 0) {
|
|
438
|
+
logger_1.logger.success(MESSAGES.ENV_VARS_PRESENT(report.env.present));
|
|
439
|
+
}
|
|
440
|
+
if (report.env.missing.length > 0) {
|
|
441
|
+
logger_1.logger.error(MESSAGES.ENV_VARS_MISSING(report.env.missing));
|
|
442
|
+
}
|
|
443
|
+
logger_1.logger.newLine();
|
|
444
|
+
}
|
|
445
|
+
// Conflicts
|
|
446
|
+
if (report.conflicts.length > 0) {
|
|
447
|
+
logger_1.logger.log(chalk_1.default.bold("Conflicts"));
|
|
448
|
+
report.conflicts.forEach(conflict => {
|
|
449
|
+
logger_1.logger.warn(conflict);
|
|
450
|
+
});
|
|
451
|
+
logger_1.logger.newLine();
|
|
452
|
+
}
|
|
453
|
+
// Detailed checks if verbose
|
|
454
|
+
if (verbose) {
|
|
455
|
+
logger_1.logger.log(chalk_1.default.bold("Detailed Checks"));
|
|
456
|
+
report.checks.forEach(check => {
|
|
457
|
+
if (check.status === "success") {
|
|
458
|
+
logger_1.logger.success(check.message);
|
|
459
|
+
}
|
|
460
|
+
else if (check.status === "warning") {
|
|
461
|
+
logger_1.logger.warn(check.message);
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
logger_1.logger.error(check.message);
|
|
465
|
+
}
|
|
466
|
+
if (check.details) {
|
|
467
|
+
logger_1.logger.log(` ${chalk_1.default.gray(check.details)}`);
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
logger_1.logger.newLine();
|
|
471
|
+
}
|
|
472
|
+
// Summary
|
|
473
|
+
logger_1.logger.log(chalk_1.default.bold("Summary"));
|
|
474
|
+
logger_1.logger.log(` Errors: ${report.summary.errors}`);
|
|
475
|
+
logger_1.logger.log(` Warnings: ${report.summary.warnings}`);
|
|
476
|
+
logger_1.logger.newLine();
|
|
477
|
+
if (report.summary.suggestions.length > 0) {
|
|
478
|
+
logger_1.logger.log(chalk_1.default.bold("Suggestions"));
|
|
479
|
+
report.summary.suggestions.forEach(suggestion => {
|
|
480
|
+
logger_1.logger.log(` • ${suggestion}`);
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
}
|
package/dist/commands/list.d.ts
CHANGED