stackkit-cli 0.4.3 → 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 +10 -1
- package/dist/commands/add.js +128 -10
- 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 +29 -2
- 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 +75 -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 +63 -0
- package/modules/auth/better-auth/module.json +191 -0
- package/modules/database/mongoose/files/lib/db.ts +68 -0
- package/modules/database/mongoose/files/models/User.ts +34 -0
- package/modules/database/mongoose/module.json +55 -0
- package/modules/database/prisma/files/lib/prisma.ts +4 -0
- package/modules/database/prisma/files/prisma/schema.prisma +8 -0
- package/modules/database/prisma/files/prisma.config.ts +12 -0
- package/modules/database/prisma/module.json +122 -0
- package/package.json +1 -1
- package/templates/express/.env.example +1 -10
- package/templates/express/package.json +15 -21
- package/templates/express/src/app.ts +9 -29
- package/templates/express/src/config/env.ts +3 -14
- 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/express/src/middlewares/error.middleware.ts +2 -2
- package/templates/express/src/server.ts +1 -1
- package/templates/express/template.json +1 -5
- package/templates/express/tsconfig.json +0 -1
- 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/better-auth-express/adapters/mongoose-mongodb.ts +0 -13
- package/modules/auth/better-auth-express/adapters/prisma-mongodb.ts +0 -15
- package/modules/auth/better-auth-express/adapters/prisma-postgresql.ts +0 -15
- package/modules/auth/better-auth-express/files/lib/auth.ts +0 -16
- package/modules/auth/better-auth-express/files/routes/auth.ts +0 -12
- package/modules/auth/better-auth-express/files/schemas/prisma-mongodb-schema.prisma +0 -72
- package/modules/auth/better-auth-express/files/schemas/prisma-postgresql-schema.prisma +0 -72
- package/modules/auth/better-auth-express/module.json +0 -61
- package/modules/auth/better-auth-nextjs/adapters/mongoose-mongodb.ts +0 -24
- package/modules/auth/better-auth-nextjs/adapters/prisma-mongodb.ts +0 -26
- package/modules/auth/better-auth-nextjs/adapters/prisma-postgresql.ts +0 -26
- package/modules/auth/better-auth-nextjs/files/lib/auth.ts +0 -26
- package/modules/auth/better-auth-nextjs/files/schemas/prisma-mongodb-schema.prisma +0 -72
- package/modules/auth/better-auth-nextjs/files/schemas/prisma-postgresql-schema.prisma +0 -72
- package/modules/auth/better-auth-nextjs/module.json +0 -62
- package/modules/auth/better-auth-react/files/lib/auth-client.ts +0 -9
- package/modules/auth/better-auth-react/module.json +0 -28
- package/modules/auth/clerk-express/files/lib/auth.ts +0 -7
- package/modules/auth/clerk-express/module.json +0 -34
- package/modules/auth/clerk-nextjs/files/lib/auth-provider.tsx +0 -5
- package/modules/auth/clerk-nextjs/files/middleware.ts +0 -9
- package/modules/auth/clerk-nextjs/module.json +0 -64
- package/modules/auth/clerk-react/files/lib/auth-provider.tsx +0 -15
- package/modules/auth/clerk-react/module.json +0 -28
- package/modules/database/mongoose-mongodb/files/lib/db.ts +0 -40
- package/modules/database/mongoose-mongodb/module.json +0 -55
- package/modules/database/prisma-mongodb/files/lib/db.ts +0 -9
- package/modules/database/prisma-mongodb/files/prisma/schema.prisma +0 -17
- package/modules/database/prisma-mongodb/module.json +0 -60
- package/modules/database/prisma-postgresql/files/lib/db.ts +0 -9
- package/modules/database/prisma-postgresql/files/prisma/schema.prisma +0 -17
- package/modules/database/prisma-postgresql/module.json +0 -60
- 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/modules/auth/{better-auth-nextjs → better-auth}/files/api/auth/[...all]/route.ts +0 -0
|
@@ -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
package/dist/commands/list.js
CHANGED
|
@@ -9,37 +9,35 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const logger_1 = require("../utils/logger");
|
|
11
11
|
async function listCommand(options) {
|
|
12
|
-
const
|
|
13
|
-
const showModules = !options.
|
|
12
|
+
const showFrameworks = !options.modules || options.frameworks;
|
|
13
|
+
const showModules = !options.frameworks || options.modules;
|
|
14
14
|
try {
|
|
15
|
+
logger_1.logger.header("StackKit Resources");
|
|
15
16
|
logger_1.logger.newLine();
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
let hasFrameworks = false;
|
|
18
|
+
let hasModules = false;
|
|
19
|
+
// List frameworks
|
|
20
|
+
if (showFrameworks) {
|
|
18
21
|
const templatesDir = path_1.default.join(__dirname, "..", "..", "templates");
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
logger_1.logger.log(` ${chalk_1.default.cyan("•")} ${template.displayName}`);
|
|
22
|
+
const frameworks = await getAvailableFrameworks(templatesDir);
|
|
23
|
+
if (frameworks.length > 0) {
|
|
24
|
+
hasFrameworks = true;
|
|
25
|
+
logger_1.logger.log(chalk_1.default.bold.blue("FRAMEWORKS"));
|
|
26
|
+
frameworks.forEach((framework, index) => {
|
|
27
|
+
const isLast = index === frameworks.length - 1;
|
|
28
|
+
const prefix = isLast ? "└──" : "├──";
|
|
29
|
+
logger_1.logger.log(` ${chalk_1.default.gray(prefix)} ${chalk_1.default.cyan(framework.displayName)}`);
|
|
28
30
|
});
|
|
31
|
+
logger_1.logger.newLine();
|
|
29
32
|
}
|
|
30
|
-
logger_1.logger.newLine();
|
|
31
33
|
}
|
|
32
34
|
// List modules
|
|
33
35
|
if (showModules) {
|
|
34
36
|
const modulesDir = path_1.default.join(__dirname, "..", "..", "modules");
|
|
35
37
|
const modules = await getAvailableModules(modulesDir);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
logger_1.logger.
|
|
39
|
-
logger_1.logger.log(chalk_1.default.dim(" No modules available"));
|
|
40
|
-
logger_1.logger.newLine();
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
38
|
+
if (modules.length > 0) {
|
|
39
|
+
hasModules = true;
|
|
40
|
+
logger_1.logger.log(chalk_1.default.bold.magenta("MODULES"));
|
|
43
41
|
// Group by category
|
|
44
42
|
const grouped = modules.reduce((acc, mod) => {
|
|
45
43
|
if (!acc[mod.category]) {
|
|
@@ -48,36 +46,59 @@ async function listCommand(options) {
|
|
|
48
46
|
acc[mod.category].push(mod);
|
|
49
47
|
return acc;
|
|
50
48
|
}, {});
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
const categories = Object.keys(grouped);
|
|
50
|
+
categories.forEach((category, categoryIndex) => {
|
|
51
|
+
const mods = grouped[category];
|
|
52
|
+
const isLastCategory = categoryIndex === categories.length - 1;
|
|
53
|
+
const categoryPrefix = isLastCategory ? "└──" : "├──";
|
|
54
|
+
logger_1.logger.log(` ${chalk_1.default.gray(categoryPrefix)} ${chalk_1.default.yellow(formatCategoryName(category))} ${chalk_1.default.dim(`(${mods.length})`)}`);
|
|
55
|
+
mods.forEach((mod, modIndex) => {
|
|
56
|
+
const isLastMod = modIndex === mods.length - 1;
|
|
57
|
+
const modPrefix = isLastCategory ? (isLastMod ? " └──" : " ├──") : (isLastMod ? "│ └──" : "│ ├──");
|
|
58
|
+
logger_1.logger.log(` ${chalk_1.default.gray(modPrefix)} ${chalk_1.default.green(mod.displayName)}`);
|
|
59
|
+
// Show additional details for database modules
|
|
60
|
+
if (mod.category === "database" && mod.name === "prisma") {
|
|
61
|
+
const providerPrefix = isLastCategory ? (isLastMod ? " └──" : " ├──") : (isLastMod ? "│ └──" : "│ ├──");
|
|
62
|
+
logger_1.logger.log(` ${chalk_1.default.gray(providerPrefix)} ${chalk_1.default.dim("Providers: PostgreSQL, MongoDB, MySQL, SQLite")}`);
|
|
63
|
+
}
|
|
56
64
|
});
|
|
57
|
-
}
|
|
65
|
+
});
|
|
58
66
|
logger_1.logger.newLine();
|
|
59
67
|
}
|
|
60
68
|
}
|
|
69
|
+
if (!hasFrameworks && !hasModules) {
|
|
70
|
+
logger_1.logger.log(chalk_1.default.dim("No resources available"));
|
|
71
|
+
logger_1.logger.newLine();
|
|
72
|
+
}
|
|
73
|
+
logger_1.logger.log(chalk_1.default.dim("Use 'stackkit add <module>' to add modules to your project"));
|
|
74
|
+
logger_1.logger.newLine();
|
|
61
75
|
}
|
|
62
76
|
catch (error) {
|
|
63
77
|
logger_1.logger.error(`Failed to list resources: ${error.message}`);
|
|
64
78
|
process.exit(1);
|
|
65
79
|
}
|
|
66
80
|
}
|
|
67
|
-
async function
|
|
81
|
+
async function getAvailableFrameworks(templatesDir) {
|
|
68
82
|
if (!(await fs_extra_1.default.pathExists(templatesDir))) {
|
|
69
83
|
return [];
|
|
70
84
|
}
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
const frameworkDirs = await fs_extra_1.default.readdir(templatesDir);
|
|
86
|
+
const frameworks = frameworkDirs
|
|
87
|
+
.filter((dir) => dir !== "node_modules" && dir !== ".git")
|
|
88
|
+
.map((dir) => ({
|
|
89
|
+
name: dir,
|
|
90
|
+
displayName: formatFrameworkName(dir),
|
|
91
|
+
}));
|
|
92
|
+
return frameworks;
|
|
93
|
+
}
|
|
94
|
+
function formatFrameworkName(name) {
|
|
95
|
+
return name
|
|
96
|
+
.split("-")
|
|
97
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
98
|
+
.join(" ");
|
|
99
|
+
}
|
|
100
|
+
function formatCategoryName(name) {
|
|
101
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
81
102
|
}
|
|
82
103
|
async function getAvailableModules(modulesDir) {
|
|
83
104
|
if (!(await fs_extra_1.default.pathExists(modulesDir))) {
|
package/dist/index.js
CHANGED
|
@@ -7,28 +7,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const commander_1 = require("commander");
|
|
9
9
|
const add_1 = require("./commands/add");
|
|
10
|
-
const
|
|
10
|
+
const doctor_1 = require("./commands/doctor");
|
|
11
11
|
const list_1 = require("./commands/list");
|
|
12
12
|
const program = new commander_1.Command();
|
|
13
13
|
program
|
|
14
14
|
.name("stackkit")
|
|
15
15
|
.description("Production-ready project generator and module CLI")
|
|
16
16
|
.version("0.3.2");
|
|
17
|
-
// Init command
|
|
18
|
-
program
|
|
19
|
-
.command("init [project-name]")
|
|
20
|
-
.description("Create a new project from a template")
|
|
21
|
-
.option("-t, --template <template>", "Template to use")
|
|
22
|
-
.option("--pm <pm>", "Package manager to use (npm, yarn, pnpm, bun)")
|
|
23
|
-
.option("--no-install", "Skip installing dependencies")
|
|
24
|
-
.option("--no-git", "Skip git initialization")
|
|
25
|
-
.option("-y, --yes", "Skip prompts and use defaults")
|
|
26
|
-
.action(init_1.initCommand);
|
|
27
17
|
// List command
|
|
28
18
|
program
|
|
29
19
|
.command("list")
|
|
30
|
-
.description("List available
|
|
31
|
-
.option("-
|
|
20
|
+
.description("List available frameworks and modules")
|
|
21
|
+
.option("-f, --frameworks", "List only frameworks")
|
|
32
22
|
.option("-m, --modules", "List only modules")
|
|
33
23
|
.action(list_1.listCommand);
|
|
34
24
|
// Add command
|
|
@@ -40,6 +30,14 @@ program
|
|
|
40
30
|
.option("--dry-run", "Show what would be changed without making changes")
|
|
41
31
|
.option("--no-install", "Skip installing dependencies")
|
|
42
32
|
.action(add_1.addCommand);
|
|
33
|
+
// Doctor command
|
|
34
|
+
program
|
|
35
|
+
.command("doctor")
|
|
36
|
+
.description("Check project health and compatibility with StackKit modules")
|
|
37
|
+
.option("--json", "Output results in JSON format")
|
|
38
|
+
.option("--verbose", "Show detailed information")
|
|
39
|
+
.option("--strict", "Treat warnings as errors")
|
|
40
|
+
.action(doctor_1.doctorCommand);
|
|
43
41
|
// Error handling
|
|
44
42
|
program.on("command:*", () => {
|
|
45
43
|
// Use logger for error and info
|