@stackweld/core 0.2.0
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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-lint.log +498 -0
- package/.turbo/turbo-test.log +21 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/dist/__tests__/compatibility-scorer.test.d.ts +2 -0
- package/dist/__tests__/compatibility-scorer.test.d.ts.map +1 -0
- package/dist/__tests__/compatibility-scorer.test.js +226 -0
- package/dist/__tests__/compatibility-scorer.test.js.map +1 -0
- package/dist/__tests__/rules-engine.test.d.ts +2 -0
- package/dist/__tests__/rules-engine.test.d.ts.map +1 -0
- package/dist/__tests__/rules-engine.test.js +161 -0
- package/dist/__tests__/rules-engine.test.js.map +1 -0
- package/dist/__tests__/scaffold-orchestrator.test.d.ts +2 -0
- package/dist/__tests__/scaffold-orchestrator.test.d.ts.map +1 -0
- package/dist/__tests__/scaffold-orchestrator.test.js +149 -0
- package/dist/__tests__/scaffold-orchestrator.test.js.map +1 -0
- package/dist/__tests__/stack-engine.test.d.ts +2 -0
- package/dist/__tests__/stack-engine.test.d.ts.map +1 -0
- package/dist/__tests__/stack-engine.test.js +278 -0
- package/dist/__tests__/stack-engine.test.js.map +1 -0
- package/dist/db/database.d.ts +9 -0
- package/dist/db/database.d.ts.map +1 -0
- package/dist/db/database.js +106 -0
- package/dist/db/database.js.map +1 -0
- package/dist/db/index.d.ts +2 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +2 -0
- package/dist/db/index.js.map +1 -0
- package/dist/engine/compatibility-scorer.d.ts +37 -0
- package/dist/engine/compatibility-scorer.d.ts.map +1 -0
- package/dist/engine/compatibility-scorer.js +178 -0
- package/dist/engine/compatibility-scorer.js.map +1 -0
- package/dist/engine/compose-generator.d.ts +35 -0
- package/dist/engine/compose-generator.d.ts.map +1 -0
- package/dist/engine/compose-generator.js +95 -0
- package/dist/engine/compose-generator.js.map +1 -0
- package/dist/engine/cost-estimator.d.ts +22 -0
- package/dist/engine/cost-estimator.d.ts.map +1 -0
- package/dist/engine/cost-estimator.js +451 -0
- package/dist/engine/cost-estimator.js.map +1 -0
- package/dist/engine/env-analyzer.d.ts +36 -0
- package/dist/engine/env-analyzer.d.ts.map +1 -0
- package/dist/engine/env-analyzer.js +111 -0
- package/dist/engine/env-analyzer.js.map +1 -0
- package/dist/engine/health-checker.d.ts +20 -0
- package/dist/engine/health-checker.d.ts.map +1 -0
- package/dist/engine/health-checker.js +377 -0
- package/dist/engine/health-checker.js.map +1 -0
- package/dist/engine/index.d.ts +11 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +7 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/infra-generator.d.ts +26 -0
- package/dist/engine/infra-generator.d.ts.map +1 -0
- package/dist/engine/infra-generator.js +751 -0
- package/dist/engine/infra-generator.js.map +1 -0
- package/dist/engine/migration-planner.d.ts +34 -0
- package/dist/engine/migration-planner.d.ts.map +1 -0
- package/dist/engine/migration-planner.js +427 -0
- package/dist/engine/migration-planner.js.map +1 -0
- package/dist/engine/performance-profiler.d.ts +22 -0
- package/dist/engine/performance-profiler.d.ts.map +1 -0
- package/dist/engine/performance-profiler.js +292 -0
- package/dist/engine/performance-profiler.js.map +1 -0
- package/dist/engine/plugin-loader.d.ts +36 -0
- package/dist/engine/plugin-loader.d.ts.map +1 -0
- package/dist/engine/plugin-loader.js +157 -0
- package/dist/engine/plugin-loader.js.map +1 -0
- package/dist/engine/preferences.d.ts +24 -0
- package/dist/engine/preferences.d.ts.map +1 -0
- package/dist/engine/preferences.js +62 -0
- package/dist/engine/preferences.js.map +1 -0
- package/dist/engine/rules-engine.d.ts +31 -0
- package/dist/engine/rules-engine.d.ts.map +1 -0
- package/dist/engine/rules-engine.js +179 -0
- package/dist/engine/rules-engine.js.map +1 -0
- package/dist/engine/runtime-manager.d.ts +65 -0
- package/dist/engine/runtime-manager.d.ts.map +1 -0
- package/dist/engine/runtime-manager.js +181 -0
- package/dist/engine/runtime-manager.js.map +1 -0
- package/dist/engine/scaffold-orchestrator.d.ts +103 -0
- package/dist/engine/scaffold-orchestrator.d.ts.map +1 -0
- package/dist/engine/scaffold-orchestrator.js +934 -0
- package/dist/engine/scaffold-orchestrator.js.map +1 -0
- package/dist/engine/stack-detector.d.ts +21 -0
- package/dist/engine/stack-detector.d.ts.map +1 -0
- package/dist/engine/stack-detector.js +313 -0
- package/dist/engine/stack-detector.js.map +1 -0
- package/dist/engine/stack-differ.d.ts +26 -0
- package/dist/engine/stack-differ.d.ts.map +1 -0
- package/dist/engine/stack-differ.js +80 -0
- package/dist/engine/stack-differ.js.map +1 -0
- package/dist/engine/stack-engine.d.ts +54 -0
- package/dist/engine/stack-engine.d.ts.map +1 -0
- package/dist/engine/stack-engine.js +186 -0
- package/dist/engine/stack-engine.js.map +1 -0
- package/dist/engine/stack-serializer.d.ts +32 -0
- package/dist/engine/stack-serializer.d.ts.map +1 -0
- package/dist/engine/stack-serializer.js +75 -0
- package/dist/engine/stack-serializer.js.map +1 -0
- package/dist/engine/standards-linter.d.ts +34 -0
- package/dist/engine/standards-linter.d.ts.map +1 -0
- package/dist/engine/standards-linter.js +162 -0
- package/dist/engine/standards-linter.js.map +1 -0
- package/dist/engine/tech-installer.d.ts +37 -0
- package/dist/engine/tech-installer.d.ts.map +1 -0
- package/dist/engine/tech-installer.js +508 -0
- package/dist/engine/tech-installer.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/project.d.ts +33 -0
- package/dist/types/project.d.ts.map +1 -0
- package/dist/types/project.js +6 -0
- package/dist/types/project.js.map +1 -0
- package/dist/types/stack.d.ts +29 -0
- package/dist/types/stack.d.ts.map +1 -0
- package/dist/types/stack.js +6 -0
- package/dist/types/stack.js.map +1 -0
- package/dist/types/technology.d.ts +47 -0
- package/dist/types/technology.d.ts.map +1 -0
- package/dist/types/technology.js +6 -0
- package/dist/types/technology.js.map +1 -0
- package/dist/types/template.d.ts +34 -0
- package/dist/types/template.d.ts.map +1 -0
- package/dist/types/template.js +6 -0
- package/dist/types/template.js.map +1 -0
- package/dist/types/validation.d.ts +20 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +5 -0
- package/dist/types/validation.js.map +1 -0
- package/package.json +39 -0
- package/src/__tests__/compatibility-scorer.test.ts +264 -0
- package/src/__tests__/rules-engine.test.ts +170 -0
- package/src/__tests__/scaffold-orchestrator.test.ts +161 -0
- package/src/__tests__/stack-engine.test.ts +328 -0
- package/src/db/database.ts +112 -0
- package/src/db/index.ts +1 -0
- package/src/engine/compatibility-scorer.ts +222 -0
- package/src/engine/compose-generator.ts +134 -0
- package/src/engine/cost-estimator.ts +498 -0
- package/src/engine/env-analyzer.ts +156 -0
- package/src/engine/health-checker.ts +421 -0
- package/src/engine/index.ts +17 -0
- package/src/engine/infra-generator.ts +837 -0
- package/src/engine/migration-planner.ts +496 -0
- package/src/engine/performance-profiler.ts +354 -0
- package/src/engine/plugin-loader.ts +216 -0
- package/src/engine/preferences.ts +85 -0
- package/src/engine/rules-engine.ts +204 -0
- package/src/engine/runtime-manager.ts +207 -0
- package/src/engine/scaffold-orchestrator.ts +1052 -0
- package/src/engine/stack-detector.ts +345 -0
- package/src/engine/stack-differ.ts +118 -0
- package/src/engine/stack-engine.ts +258 -0
- package/src/engine/stack-serializer.ts +95 -0
- package/src/engine/standards-linter.ts +210 -0
- package/src/engine/tech-installer.ts +650 -0
- package/src/index.ts +78 -0
- package/src/types/index.ts +10 -0
- package/src/types/project.ts +36 -0
- package/src/types/stack.ts +32 -0
- package/src/types/technology.ts +58 -0
- package/src/types/template.ts +37 -0
- package/src/types/validation.ts +22 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack Health Monitor — Checks project health across multiple dimensions.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
6
|
+
import * as fs from "node:fs";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
|
|
9
|
+
// ─── Types ────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
export interface HealthReport {
|
|
12
|
+
overall: "healthy" | "warning" | "critical";
|
|
13
|
+
checks: HealthCheck[];
|
|
14
|
+
summary: { passed: number; warnings: number; critical: number };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface HealthCheck {
|
|
18
|
+
name: string;
|
|
19
|
+
status: "pass" | "warn" | "fail";
|
|
20
|
+
message: string;
|
|
21
|
+
suggestion?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ─── Helpers ──────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
function fileExists(filePath: string): boolean {
|
|
27
|
+
return fs.existsSync(filePath);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readFileContent(filePath: string): string | null {
|
|
31
|
+
try {
|
|
32
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function execQuiet(command: string, cwd?: string): string | null {
|
|
39
|
+
try {
|
|
40
|
+
return execSync(command, { stdio: "pipe", timeout: 10000, cwd }).toString().trim();
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getDirSizeMB(dirPath: string): number | null {
|
|
47
|
+
const result = execQuiet(`du -sm "${dirPath}" 2>/dev/null`);
|
|
48
|
+
if (!result) return null;
|
|
49
|
+
const match = result.match(/^(\d+)/);
|
|
50
|
+
return match ? parseInt(match[1], 10) : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ─── Individual Checks ───────────────────────────────
|
|
54
|
+
|
|
55
|
+
function checkLockFile(projectPath: string): HealthCheck {
|
|
56
|
+
const lockFiles = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"];
|
|
57
|
+
for (const lockFile of lockFiles) {
|
|
58
|
+
if (fileExists(path.join(projectPath, lockFile))) {
|
|
59
|
+
return {
|
|
60
|
+
name: "Lock file present",
|
|
61
|
+
status: "pass",
|
|
62
|
+
message: `${lockFile} found`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (fileExists(path.join(projectPath, "package.json"))) {
|
|
67
|
+
return {
|
|
68
|
+
name: "Lock file present",
|
|
69
|
+
status: "warn",
|
|
70
|
+
message: "No lock file found (pnpm-lock.yaml, package-lock.json, yarn.lock)",
|
|
71
|
+
suggestion: "Run your package manager install to generate a lock file",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
name: "Lock file present",
|
|
76
|
+
status: "pass",
|
|
77
|
+
message: "No package.json — lock file not applicable",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function checkEnvExample(projectPath: string): HealthCheck {
|
|
82
|
+
const hasEnv = fileExists(path.join(projectPath, ".env"));
|
|
83
|
+
const hasExample = fileExists(path.join(projectPath, ".env.example"));
|
|
84
|
+
|
|
85
|
+
if (hasEnv && !hasExample) {
|
|
86
|
+
return {
|
|
87
|
+
name: ".env.example exists",
|
|
88
|
+
status: "warn",
|
|
89
|
+
message: ".env exists but no .env.example for reference",
|
|
90
|
+
suggestion: "Create .env.example with placeholder values for team onboarding",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (hasExample) {
|
|
94
|
+
return {
|
|
95
|
+
name: ".env.example exists",
|
|
96
|
+
status: "pass",
|
|
97
|
+
message: ".env.example found",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
name: ".env.example exists",
|
|
102
|
+
status: "pass",
|
|
103
|
+
message: "No .env file — .env.example not needed",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function checkGitignore(projectPath: string): HealthCheck {
|
|
108
|
+
const isGitRepo = fileExists(path.join(projectPath, ".git"));
|
|
109
|
+
const hasGitignore = fileExists(path.join(projectPath, ".gitignore"));
|
|
110
|
+
|
|
111
|
+
if (isGitRepo && !hasGitignore) {
|
|
112
|
+
return {
|
|
113
|
+
name: ".gitignore exists",
|
|
114
|
+
status: "warn",
|
|
115
|
+
message: "Git repo without .gitignore",
|
|
116
|
+
suggestion: "Create a .gitignore to prevent committing unwanted files",
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (hasGitignore) {
|
|
120
|
+
return {
|
|
121
|
+
name: ".gitignore exists",
|
|
122
|
+
status: "pass",
|
|
123
|
+
message: ".gitignore found",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
name: ".gitignore exists",
|
|
128
|
+
status: "pass",
|
|
129
|
+
message: "Not a git repo — .gitignore not applicable",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function checkNodeModulesSize(projectPath: string): HealthCheck {
|
|
134
|
+
const nmPath = path.join(projectPath, "node_modules");
|
|
135
|
+
if (!fileExists(nmPath)) {
|
|
136
|
+
return {
|
|
137
|
+
name: "node_modules size",
|
|
138
|
+
status: "pass",
|
|
139
|
+
message: "No node_modules directory",
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const sizeMB = getDirSizeMB(nmPath);
|
|
143
|
+
if (sizeMB === null) {
|
|
144
|
+
return {
|
|
145
|
+
name: "node_modules size",
|
|
146
|
+
status: "pass",
|
|
147
|
+
message: "Could not determine node_modules size",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (sizeMB > 500) {
|
|
151
|
+
return {
|
|
152
|
+
name: "node_modules size",
|
|
153
|
+
status: "warn",
|
|
154
|
+
message: `node_modules: ${sizeMB}MB (consider cleaning)`,
|
|
155
|
+
suggestion: "Run `npx npkill` or reinstall with fewer dependencies",
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
name: "node_modules size",
|
|
160
|
+
status: "pass",
|
|
161
|
+
message: `node_modules: ${sizeMB}MB`,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function checkDockerAvailable(projectPath: string): HealthCheck {
|
|
166
|
+
const hasCompose =
|
|
167
|
+
fileExists(path.join(projectPath, "docker-compose.yml")) ||
|
|
168
|
+
fileExists(path.join(projectPath, "docker-compose.yaml")) ||
|
|
169
|
+
fileExists(path.join(projectPath, "compose.yml")) ||
|
|
170
|
+
fileExists(path.join(projectPath, "compose.yaml"));
|
|
171
|
+
|
|
172
|
+
if (!hasCompose) {
|
|
173
|
+
return {
|
|
174
|
+
name: "Docker available",
|
|
175
|
+
status: "pass",
|
|
176
|
+
message: "No docker-compose file — Docker check not applicable",
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const dockerVersion = execQuiet("docker --version");
|
|
180
|
+
if (!dockerVersion) {
|
|
181
|
+
return {
|
|
182
|
+
name: "Docker available",
|
|
183
|
+
status: "warn",
|
|
184
|
+
message: "docker-compose file found but Docker is not available",
|
|
185
|
+
suggestion: "Install Docker: https://docs.docker.com/get-docker/",
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
name: "Docker available",
|
|
190
|
+
status: "pass",
|
|
191
|
+
message: "Docker available for docker-compose project",
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function checkTypeScriptStrict(projectPath: string): HealthCheck {
|
|
196
|
+
const tsconfigPath = path.join(projectPath, "tsconfig.json");
|
|
197
|
+
if (!fileExists(tsconfigPath)) {
|
|
198
|
+
return {
|
|
199
|
+
name: "TypeScript strict mode",
|
|
200
|
+
status: "pass",
|
|
201
|
+
message: "No tsconfig.json — TypeScript check not applicable",
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const content = readFileContent(tsconfigPath);
|
|
205
|
+
if (!content) {
|
|
206
|
+
return {
|
|
207
|
+
name: "TypeScript strict mode",
|
|
208
|
+
status: "pass",
|
|
209
|
+
message: "Could not read tsconfig.json",
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
// Strip comments for basic JSON parsing
|
|
214
|
+
const stripped = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
215
|
+
const config = JSON.parse(stripped);
|
|
216
|
+
if (config.compilerOptions?.strict === true) {
|
|
217
|
+
return {
|
|
218
|
+
name: "TypeScript strict mode",
|
|
219
|
+
status: "pass",
|
|
220
|
+
message: "strict: true enabled",
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
name: "TypeScript strict mode",
|
|
225
|
+
status: "warn",
|
|
226
|
+
message: "TypeScript strict mode is not enabled",
|
|
227
|
+
suggestion: 'Set "strict": true in tsconfig.json compilerOptions',
|
|
228
|
+
};
|
|
229
|
+
} catch {
|
|
230
|
+
return {
|
|
231
|
+
name: "TypeScript strict mode",
|
|
232
|
+
status: "pass",
|
|
233
|
+
message: "Could not parse tsconfig.json",
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function checkGitClean(projectPath: string): HealthCheck {
|
|
239
|
+
const isGitRepo = fileExists(path.join(projectPath, ".git"));
|
|
240
|
+
if (!isGitRepo) {
|
|
241
|
+
return {
|
|
242
|
+
name: "Git status clean",
|
|
243
|
+
status: "pass",
|
|
244
|
+
message: "Not a git repo",
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const output = execQuiet("git status --porcelain", projectPath);
|
|
248
|
+
if (output === null) {
|
|
249
|
+
return {
|
|
250
|
+
name: "Git status clean",
|
|
251
|
+
status: "pass",
|
|
252
|
+
message: "Could not check git status",
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
if (output.length === 0) {
|
|
256
|
+
return {
|
|
257
|
+
name: "Git status clean",
|
|
258
|
+
status: "pass",
|
|
259
|
+
message: "Working tree is clean",
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const changedFiles = output.split("\n").length;
|
|
263
|
+
return {
|
|
264
|
+
name: "Git status clean",
|
|
265
|
+
status: "warn",
|
|
266
|
+
message: `${changedFiles} uncommitted change(s)`,
|
|
267
|
+
suggestion: "Commit or stash your changes",
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function checkEnvNotCommitted(projectPath: string): HealthCheck {
|
|
272
|
+
const gitignorePath = path.join(projectPath, ".gitignore");
|
|
273
|
+
const hasEnv = fileExists(path.join(projectPath, ".env"));
|
|
274
|
+
const isGitRepo = fileExists(path.join(projectPath, ".git"));
|
|
275
|
+
|
|
276
|
+
if (!hasEnv || !isGitRepo) {
|
|
277
|
+
return {
|
|
278
|
+
name: ".env not committed",
|
|
279
|
+
status: "pass",
|
|
280
|
+
message: "No .env or not a git repo",
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const gitignoreContent = readFileContent(gitignorePath);
|
|
285
|
+
if (!gitignoreContent) {
|
|
286
|
+
return {
|
|
287
|
+
name: ".env not committed",
|
|
288
|
+
status: "fail",
|
|
289
|
+
message: ".env exists but no .gitignore found — secrets may be committed",
|
|
290
|
+
suggestion: 'Create .gitignore with ".env" entry',
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const lines = gitignoreContent.split("\n").map((l) => l.trim());
|
|
295
|
+
const envIgnored = lines.some(
|
|
296
|
+
(line) => line === ".env" || line === ".env*" || line === "*.env" || line === ".env.*",
|
|
297
|
+
);
|
|
298
|
+
if (!envIgnored) {
|
|
299
|
+
return {
|
|
300
|
+
name: ".env not committed",
|
|
301
|
+
status: "fail",
|
|
302
|
+
message: ".env exists but is not listed in .gitignore",
|
|
303
|
+
suggestion: "Add .env to your .gitignore immediately",
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
name: ".env not committed",
|
|
308
|
+
status: "pass",
|
|
309
|
+
message: ".env is listed in .gitignore",
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function checkNoSecretsInCode(projectPath: string): HealthCheck {
|
|
314
|
+
const srcDir = path.join(projectPath, "src");
|
|
315
|
+
if (!fileExists(srcDir)) {
|
|
316
|
+
return {
|
|
317
|
+
name: "No secrets in code",
|
|
318
|
+
status: "pass",
|
|
319
|
+
message: "No src/ directory to scan",
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const secretPatterns = [
|
|
324
|
+
"sk-[a-zA-Z0-9]{20,}",
|
|
325
|
+
"sk_live_[a-zA-Z0-9]+",
|
|
326
|
+
"AKIA[A-Z0-9]{16}",
|
|
327
|
+
"ghp_[a-zA-Z0-9]{36}",
|
|
328
|
+
"gho_[a-zA-Z0-9]{36}",
|
|
329
|
+
"glpat-[a-zA-Z0-9\\-_]{20}",
|
|
330
|
+
"xox[bpors]-[a-zA-Z0-9\\-]+",
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
const pattern = secretPatterns.join("|");
|
|
334
|
+
const result = execQuiet(
|
|
335
|
+
`grep -rEl '${pattern}' "${srcDir}" --include='*.ts' --include='*.js' --include='*.tsx' --include='*.jsx' --include='*.py' --include='*.go' --include='*.rs' 2>/dev/null`,
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
if (result && result.length > 0) {
|
|
339
|
+
const files = result.split("\n").filter(Boolean);
|
|
340
|
+
return {
|
|
341
|
+
name: "No secrets in code",
|
|
342
|
+
status: "fail",
|
|
343
|
+
message: `Potential secrets found in ${files.length} file(s)`,
|
|
344
|
+
suggestion: "Move secrets to environment variables and rotate compromised keys",
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
name: "No secrets in code",
|
|
349
|
+
status: "pass",
|
|
350
|
+
message: "No secret patterns detected in src/",
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function checkPackageJson(projectPath: string): HealthCheck {
|
|
355
|
+
const pkgPath = path.join(projectPath, "package.json");
|
|
356
|
+
if (!fileExists(pkgPath)) {
|
|
357
|
+
return {
|
|
358
|
+
name: "package.json present",
|
|
359
|
+
status: "pass",
|
|
360
|
+
message: "No package.json — not a Node.js project",
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
// Check if lock file exists alongside it (already handled in checkLockFile)
|
|
364
|
+
// Here we just verify package.json is valid
|
|
365
|
+
const content = readFileContent(pkgPath);
|
|
366
|
+
if (!content) {
|
|
367
|
+
return {
|
|
368
|
+
name: "package.json present",
|
|
369
|
+
status: "warn",
|
|
370
|
+
message: "Could not read package.json",
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
try {
|
|
374
|
+
JSON.parse(content);
|
|
375
|
+
return {
|
|
376
|
+
name: "package.json present",
|
|
377
|
+
status: "pass",
|
|
378
|
+
message: "package.json is valid JSON",
|
|
379
|
+
};
|
|
380
|
+
} catch {
|
|
381
|
+
return {
|
|
382
|
+
name: "package.json present",
|
|
383
|
+
status: "warn",
|
|
384
|
+
message: "package.json contains invalid JSON",
|
|
385
|
+
suggestion: "Fix the JSON syntax in package.json",
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ─── Main Function ────────────────────────────────────
|
|
391
|
+
|
|
392
|
+
export function checkProjectHealth(projectPath: string): HealthReport {
|
|
393
|
+
const resolvedPath = path.resolve(projectPath);
|
|
394
|
+
|
|
395
|
+
const checks: HealthCheck[] = [
|
|
396
|
+
checkPackageJson(resolvedPath),
|
|
397
|
+
checkLockFile(resolvedPath),
|
|
398
|
+
checkGitignore(resolvedPath),
|
|
399
|
+
checkEnvExample(resolvedPath),
|
|
400
|
+
checkEnvNotCommitted(resolvedPath),
|
|
401
|
+
checkNoSecretsInCode(resolvedPath),
|
|
402
|
+
checkTypeScriptStrict(resolvedPath),
|
|
403
|
+
checkDockerAvailable(resolvedPath),
|
|
404
|
+
checkNodeModulesSize(resolvedPath),
|
|
405
|
+
checkGitClean(resolvedPath),
|
|
406
|
+
];
|
|
407
|
+
|
|
408
|
+
const passed = checks.filter((c) => c.status === "pass").length;
|
|
409
|
+
const warnings = checks.filter((c) => c.status === "warn").length;
|
|
410
|
+
const critical = checks.filter((c) => c.status === "fail").length;
|
|
411
|
+
|
|
412
|
+
let overall: HealthReport["overall"] = "healthy";
|
|
413
|
+
if (critical > 0) overall = "critical";
|
|
414
|
+
else if (warnings > 0) overall = "warning";
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
overall,
|
|
418
|
+
checks,
|
|
419
|
+
summary: { passed, warnings, critical },
|
|
420
|
+
};
|
|
421
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type { UserPreferences } from "./preferences.js";
|
|
2
|
+
export {
|
|
3
|
+
getDefaultPreferences,
|
|
4
|
+
getPreference,
|
|
5
|
+
getPreferenceKeys,
|
|
6
|
+
getPreferences,
|
|
7
|
+
resetPreferences,
|
|
8
|
+
setPreference,
|
|
9
|
+
} from "./preferences.js";
|
|
10
|
+
export { RulesEngine } from "./rules-engine.js";
|
|
11
|
+
export type { RuntimeOptions } from "./runtime-manager.js";
|
|
12
|
+
export { RuntimeManager } from "./runtime-manager.js";
|
|
13
|
+
export type { ScaffoldOutput } from "./scaffold-orchestrator.js";
|
|
14
|
+
export { ScaffoldOrchestrator } from "./scaffold-orchestrator.js";
|
|
15
|
+
export { StackEngine } from "./stack-engine.js";
|
|
16
|
+
export type { InstallContext, InstallResult } from "./tech-installer.js";
|
|
17
|
+
export { installTechnologies } from "./tech-installer.js";
|