popeye-cli 1.0.0 → 1.1.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/README.md +521 -125
- package/dist/adapters/claude.d.ts +16 -4
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +679 -33
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/gemini.d.ts +55 -0
- package/dist/adapters/gemini.d.ts.map +1 -0
- package/dist/adapters/gemini.js +318 -0
- package/dist/adapters/gemini.js.map +1 -0
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +41 -7
- package/dist/adapters/openai.js.map +1 -1
- package/dist/auth/claude.d.ts +11 -9
- package/dist/auth/claude.d.ts.map +1 -1
- package/dist/auth/claude.js +107 -71
- package/dist/auth/claude.js.map +1 -1
- package/dist/auth/gemini.d.ts +58 -0
- package/dist/auth/gemini.d.ts.map +1 -0
- package/dist/auth/gemini.js +172 -0
- package/dist/auth/gemini.js.map +1 -0
- package/dist/auth/index.d.ts +11 -7
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +23 -5
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/keychain.d.ts +20 -7
- package/dist/auth/keychain.d.ts.map +1 -1
- package/dist/auth/keychain.js +85 -29
- package/dist/auth/keychain.js.map +1 -1
- package/dist/auth/openai.d.ts +2 -2
- package/dist/auth/openai.d.ts.map +1 -1
- package/dist/auth/openai.js +30 -32
- package/dist/auth/openai.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +4 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/interactive.d.ts +2 -2
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +1380 -183
- package/dist/cli/interactive.js.map +1 -1
- package/dist/config/defaults.d.ts +6 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +10 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +19 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +20 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +7 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/generators/python.d.ts.map +1 -1
- package/dist/generators/python.js +1 -0
- package/dist/generators/python.js.map +1 -1
- package/dist/generators/typescript.d.ts.map +1 -1
- package/dist/generators/typescript.js +1 -0
- package/dist/generators/typescript.js.map +1 -1
- package/dist/state/index.d.ts +108 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +551 -4
- package/dist/state/index.js.map +1 -1
- package/dist/state/registry.d.ts +52 -0
- package/dist/state/registry.d.ts.map +1 -0
- package/dist/state/registry.js +215 -0
- package/dist/state/registry.js.map +1 -0
- package/dist/types/cli.d.ts +4 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/cli.js.map +1 -1
- package/dist/types/consensus.d.ts +69 -4
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +24 -3
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/workflow.d.ts +55 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +16 -0
- package/dist/types/workflow.js.map +1 -1
- package/dist/workflow/auto-fix.d.ts +45 -0
- package/dist/workflow/auto-fix.d.ts.map +1 -0
- package/dist/workflow/auto-fix.js +274 -0
- package/dist/workflow/auto-fix.js.map +1 -0
- package/dist/workflow/consensus.d.ts +44 -2
- package/dist/workflow/consensus.d.ts.map +1 -1
- package/dist/workflow/consensus.js +565 -17
- package/dist/workflow/consensus.js.map +1 -1
- package/dist/workflow/execution-mode.d.ts +10 -4
- package/dist/workflow/execution-mode.d.ts.map +1 -1
- package/dist/workflow/execution-mode.js +547 -58
- package/dist/workflow/execution-mode.js.map +1 -1
- package/dist/workflow/index.d.ts +14 -2
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +69 -6
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/milestone-workflow.d.ts +34 -0
- package/dist/workflow/milestone-workflow.d.ts.map +1 -0
- package/dist/workflow/milestone-workflow.js +414 -0
- package/dist/workflow/milestone-workflow.js.map +1 -0
- package/dist/workflow/plan-mode.d.ts +14 -1
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +589 -47
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/plan-storage.d.ts +142 -0
- package/dist/workflow/plan-storage.d.ts.map +1 -0
- package/dist/workflow/plan-storage.js +331 -0
- package/dist/workflow/plan-storage.js.map +1 -0
- package/dist/workflow/project-verification.d.ts +37 -0
- package/dist/workflow/project-verification.d.ts.map +1 -0
- package/dist/workflow/project-verification.js +381 -0
- package/dist/workflow/project-verification.js.map +1 -0
- package/dist/workflow/task-workflow.d.ts +37 -0
- package/dist/workflow/task-workflow.d.ts.map +1 -0
- package/dist/workflow/task-workflow.js +383 -0
- package/dist/workflow/task-workflow.js.map +1 -0
- package/dist/workflow/test-runner.d.ts +1 -0
- package/dist/workflow/test-runner.d.ts.map +1 -1
- package/dist/workflow/test-runner.js +9 -5
- package/dist/workflow/test-runner.js.map +1 -1
- package/dist/workflow/ui-designer.d.ts +82 -0
- package/dist/workflow/ui-designer.d.ts.map +1 -0
- package/dist/workflow/ui-designer.js +234 -0
- package/dist/workflow/ui-designer.js.map +1 -0
- package/dist/workflow/ui-setup.d.ts +58 -0
- package/dist/workflow/ui-setup.d.ts.map +1 -0
- package/dist/workflow/ui-setup.js +685 -0
- package/dist/workflow/ui-setup.js.map +1 -0
- package/dist/workflow/ui-verification.d.ts +114 -0
- package/dist/workflow/ui-verification.d.ts.map +1 -0
- package/dist/workflow/ui-verification.js +258 -0
- package/dist/workflow/ui-verification.js.map +1 -0
- package/dist/workflow/workflow-logger.d.ts +110 -0
- package/dist/workflow/workflow-logger.d.ts.map +1 -0
- package/dist/workflow/workflow-logger.js +267 -0
- package/dist/workflow/workflow-logger.js.map +1 -0
- package/package.json +2 -2
- package/src/adapters/claude.ts +815 -34
- package/src/adapters/gemini.ts +373 -0
- package/src/adapters/openai.ts +40 -7
- package/src/auth/claude.ts +120 -78
- package/src/auth/gemini.ts +207 -0
- package/src/auth/index.ts +28 -8
- package/src/auth/keychain.ts +95 -28
- package/src/auth/openai.ts +29 -36
- package/src/cli/index.ts +4 -7
- package/src/cli/interactive.ts +1641 -216
- package/src/config/defaults.ts +10 -2
- package/src/config/index.ts +21 -0
- package/src/config/schema.ts +7 -0
- package/src/generators/python.ts +1 -0
- package/src/generators/typescript.ts +1 -0
- package/src/state/index.ts +713 -4
- package/src/state/registry.ts +278 -0
- package/src/types/cli.ts +4 -0
- package/src/types/consensus.ts +65 -6
- package/src/types/workflow.ts +35 -0
- package/src/workflow/auto-fix.ts +340 -0
- package/src/workflow/consensus.ts +750 -16
- package/src/workflow/execution-mode.ts +673 -74
- package/src/workflow/index.ts +95 -6
- package/src/workflow/milestone-workflow.ts +576 -0
- package/src/workflow/plan-mode.ts +696 -50
- package/src/workflow/plan-storage.ts +482 -0
- package/src/workflow/project-verification.ts +471 -0
- package/src/workflow/task-workflow.ts +525 -0
- package/src/workflow/test-runner.ts +10 -5
- package/src/workflow/ui-designer.ts +337 -0
- package/src/workflow/ui-setup.ts +797 -0
- package/src/workflow/ui-verification.ts +357 -0
- package/src/workflow/workflow-logger.ts +353 -0
- package/tests/config/config.test.ts +1 -1
- package/tests/types/consensus.test.ts +3 -3
- package/tests/workflow/plan-mode.test.ts +213 -0
- package/tests/workflow/test-runner.test.ts +5 -3
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive Project Verification
|
|
3
|
+
* Ensures generated projects are actually complete and runnable
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { promises as fs } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { exec } from 'node:child_process';
|
|
9
|
+
import { promisify } from 'node:util';
|
|
10
|
+
|
|
11
|
+
const execAsync = promisify(exec);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Verification result
|
|
15
|
+
*/
|
|
16
|
+
export interface VerificationResult {
|
|
17
|
+
passed: boolean;
|
|
18
|
+
category: string;
|
|
19
|
+
check: string;
|
|
20
|
+
message: string;
|
|
21
|
+
severity: 'error' | 'warning' | 'info';
|
|
22
|
+
autoFixable: boolean;
|
|
23
|
+
fix?: () => Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Project verification report
|
|
28
|
+
*/
|
|
29
|
+
export interface VerificationReport {
|
|
30
|
+
passed: boolean;
|
|
31
|
+
totalChecks: number;
|
|
32
|
+
passedChecks: number;
|
|
33
|
+
failedChecks: number;
|
|
34
|
+
warnings: number;
|
|
35
|
+
results: VerificationResult[];
|
|
36
|
+
criticalIssues: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check if a file exists
|
|
41
|
+
*/
|
|
42
|
+
async function fileExists(filePath: string): Promise<boolean> {
|
|
43
|
+
try {
|
|
44
|
+
await fs.access(filePath);
|
|
45
|
+
return true;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Read file content safely
|
|
53
|
+
*/
|
|
54
|
+
async function readFile(filePath: string): Promise<string | null> {
|
|
55
|
+
try {
|
|
56
|
+
return await fs.readFile(filePath, 'utf-8');
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Find files matching a pattern recursively
|
|
64
|
+
*/
|
|
65
|
+
async function findFiles(dir: string, pattern: RegExp, maxDepth = 5): Promise<string[]> {
|
|
66
|
+
const results: string[] = [];
|
|
67
|
+
|
|
68
|
+
async function search(currentDir: string, depth: number): Promise<void> {
|
|
69
|
+
if (depth > maxDepth) return;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
73
|
+
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
76
|
+
|
|
77
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules' && entry.name !== 'dist') {
|
|
78
|
+
await search(fullPath, depth + 1);
|
|
79
|
+
} else if (entry.isFile() && pattern.test(entry.name)) {
|
|
80
|
+
results.push(fullPath);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
// Directory not accessible
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await search(dir, 0);
|
|
89
|
+
return results;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Check if content contains Tailwind classes
|
|
94
|
+
*/
|
|
95
|
+
function hasTailwindClasses(content: string): boolean {
|
|
96
|
+
const tailwindPatterns = [
|
|
97
|
+
/className=["'][^"']*(?:flex|grid|block|inline|hidden)/,
|
|
98
|
+
/className=["'][^"']*(?:bg-|text-|border-|rounded-|shadow-|p-|m-|w-|h-)/,
|
|
99
|
+
/className=["'][^"']*(?:hover:|focus:|active:|disabled:)/,
|
|
100
|
+
/className=["'][^"']*(?:sm:|md:|lg:|xl:|2xl:)/,
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
return tailwindPatterns.some(pattern => pattern.test(content));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if content contains TODO placeholders
|
|
108
|
+
*/
|
|
109
|
+
function findTodoPlaceholders(content: string): string[] {
|
|
110
|
+
const patterns = [
|
|
111
|
+
/TODO:?\s*(.+)/gi,
|
|
112
|
+
/<div>.*TODO.*<\/div>/gi,
|
|
113
|
+
/\{\/\*\s*TODO.*\*\/\}/gi,
|
|
114
|
+
/placeholder.*TODO/gi,
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
const todos: string[] = [];
|
|
118
|
+
for (const pattern of patterns) {
|
|
119
|
+
const matches = content.matchAll(pattern);
|
|
120
|
+
for (const match of matches) {
|
|
121
|
+
todos.push(match[0].trim());
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return todos;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Verify CSS/Styling setup
|
|
130
|
+
*/
|
|
131
|
+
async function verifyStylingSetup(projectDir: string): Promise<VerificationResult[]> {
|
|
132
|
+
const results: VerificationResult[] = [];
|
|
133
|
+
const frontendDir = path.join(projectDir, 'packages', 'frontend');
|
|
134
|
+
|
|
135
|
+
// Check if frontend uses Tailwind classes
|
|
136
|
+
const tsxFiles = await findFiles(path.join(frontendDir, 'src'), /\.tsx$/);
|
|
137
|
+
let usesTailwind = false;
|
|
138
|
+
|
|
139
|
+
for (const file of tsxFiles.slice(0, 20)) { // Check first 20 files
|
|
140
|
+
const content = await readFile(file);
|
|
141
|
+
if (content && hasTailwindClasses(content)) {
|
|
142
|
+
usesTailwind = true;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (usesTailwind) {
|
|
148
|
+
// Check Tailwind is in package.json
|
|
149
|
+
const pkgJson = await readFile(path.join(frontendDir, 'package.json'));
|
|
150
|
+
const hasTailwindDep = pkgJson?.includes('tailwindcss');
|
|
151
|
+
|
|
152
|
+
results.push({
|
|
153
|
+
passed: !!hasTailwindDep,
|
|
154
|
+
category: 'Styling',
|
|
155
|
+
check: 'Tailwind CSS dependency',
|
|
156
|
+
message: hasTailwindDep
|
|
157
|
+
? 'Tailwind CSS is installed'
|
|
158
|
+
: 'Components use Tailwind classes but tailwindcss is not in package.json',
|
|
159
|
+
severity: hasTailwindDep ? 'info' : 'error',
|
|
160
|
+
autoFixable: true,
|
|
161
|
+
fix: hasTailwindDep ? undefined : async () => {
|
|
162
|
+
await execAsync('npm install -D tailwindcss @tailwindcss/postcss', { cwd: frontendDir });
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Check PostCSS config
|
|
167
|
+
const hasPostcssConfig = await fileExists(path.join(frontendDir, 'postcss.config.js')) ||
|
|
168
|
+
await fileExists(path.join(frontendDir, 'postcss.config.cjs'));
|
|
169
|
+
|
|
170
|
+
results.push({
|
|
171
|
+
passed: hasPostcssConfig,
|
|
172
|
+
category: 'Styling',
|
|
173
|
+
check: 'PostCSS configuration',
|
|
174
|
+
message: hasPostcssConfig
|
|
175
|
+
? 'PostCSS is configured'
|
|
176
|
+
: 'Missing postcss.config.js for Tailwind CSS',
|
|
177
|
+
severity: hasPostcssConfig ? 'info' : 'error',
|
|
178
|
+
autoFixable: true,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Check CSS file exists and is imported
|
|
182
|
+
const mainTsx = await readFile(path.join(frontendDir, 'src', 'main.tsx'));
|
|
183
|
+
const hasCssImport = mainTsx?.includes("import './index.css'") ||
|
|
184
|
+
mainTsx?.includes('import "./index.css"') ||
|
|
185
|
+
mainTsx?.includes("import '../index.css'");
|
|
186
|
+
|
|
187
|
+
results.push({
|
|
188
|
+
passed: !!hasCssImport,
|
|
189
|
+
category: 'Styling',
|
|
190
|
+
check: 'CSS import in main.tsx',
|
|
191
|
+
message: hasCssImport
|
|
192
|
+
? 'CSS is imported in main entry point'
|
|
193
|
+
: 'No CSS import found in main.tsx - styles will not load',
|
|
194
|
+
severity: hasCssImport ? 'info' : 'error',
|
|
195
|
+
autoFixable: true,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Check index.css exists
|
|
199
|
+
const hasIndexCss = await fileExists(path.join(frontendDir, 'src', 'index.css'));
|
|
200
|
+
|
|
201
|
+
results.push({
|
|
202
|
+
passed: hasIndexCss,
|
|
203
|
+
category: 'Styling',
|
|
204
|
+
check: 'Global CSS file',
|
|
205
|
+
message: hasIndexCss
|
|
206
|
+
? 'Global CSS file exists'
|
|
207
|
+
: 'Missing src/index.css',
|
|
208
|
+
severity: hasIndexCss ? 'info' : 'error',
|
|
209
|
+
autoFixable: true,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return results;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Verify authentication setup
|
|
218
|
+
*/
|
|
219
|
+
async function verifyAuthSetup(projectDir: string): Promise<VerificationResult[]> {
|
|
220
|
+
const results: VerificationResult[] = [];
|
|
221
|
+
const frontendDir = path.join(projectDir, 'packages', 'frontend');
|
|
222
|
+
|
|
223
|
+
// Check if project uses Auth0
|
|
224
|
+
const pkgJson = await readFile(path.join(frontendDir, 'package.json'));
|
|
225
|
+
const usesAuth0 = pkgJson?.includes('@auth0/auth0-react');
|
|
226
|
+
|
|
227
|
+
if (usesAuth0) {
|
|
228
|
+
// Check for dev mode bypass
|
|
229
|
+
const useAuthFile = await readFile(path.join(frontendDir, 'src', 'hooks', 'useAuth.ts'));
|
|
230
|
+
const hasDevBypass = useAuthFile?.includes('isAuth0Configured') ||
|
|
231
|
+
useAuthFile?.includes('DEV_MOCK_USER') ||
|
|
232
|
+
useAuthFile?.includes('development mode');
|
|
233
|
+
|
|
234
|
+
results.push({
|
|
235
|
+
passed: !!hasDevBypass,
|
|
236
|
+
category: 'Authentication',
|
|
237
|
+
check: 'Development mode bypass',
|
|
238
|
+
message: hasDevBypass
|
|
239
|
+
? 'Auth has development mode bypass'
|
|
240
|
+
: 'Auth requires Auth0 configuration - app will not work without it',
|
|
241
|
+
severity: hasDevBypass ? 'info' : 'error',
|
|
242
|
+
autoFixable: false,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Check Auth0 provider has fallback
|
|
246
|
+
const authProviderFile = await readFile(path.join(frontendDir, 'src', 'providers', 'Auth0ProviderWithNavigate.tsx'));
|
|
247
|
+
const hasFallback = authProviderFile?.includes('isAuth0Configured') &&
|
|
248
|
+
authProviderFile?.includes('return <>{children}</>');
|
|
249
|
+
|
|
250
|
+
results.push({
|
|
251
|
+
passed: !!hasFallback,
|
|
252
|
+
category: 'Authentication',
|
|
253
|
+
check: 'Auth0 provider fallback',
|
|
254
|
+
message: hasFallback
|
|
255
|
+
? 'Auth0 provider has fallback for unconfigured state'
|
|
256
|
+
: 'Auth0 provider may hang if not configured',
|
|
257
|
+
severity: hasFallback ? 'info' : 'warning',
|
|
258
|
+
autoFixable: false,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return results;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Verify routes are complete (no TODO placeholders)
|
|
267
|
+
*/
|
|
268
|
+
async function verifyRouteCompleteness(projectDir: string): Promise<VerificationResult[]> {
|
|
269
|
+
const results: VerificationResult[] = [];
|
|
270
|
+
const frontendDir = path.join(projectDir, 'packages', 'frontend');
|
|
271
|
+
|
|
272
|
+
// Check routes file
|
|
273
|
+
const routesFile = await readFile(path.join(frontendDir, 'src', 'routes', 'index.tsx'));
|
|
274
|
+
|
|
275
|
+
if (routesFile) {
|
|
276
|
+
const todos = findTodoPlaceholders(routesFile);
|
|
277
|
+
|
|
278
|
+
results.push({
|
|
279
|
+
passed: todos.length === 0,
|
|
280
|
+
category: 'Routes',
|
|
281
|
+
check: 'Route completeness',
|
|
282
|
+
message: todos.length === 0
|
|
283
|
+
? 'All routes are implemented'
|
|
284
|
+
: `Found ${todos.length} TODO placeholder(s) in routes: ${todos.slice(0, 3).join(', ')}`,
|
|
285
|
+
severity: todos.length === 0 ? 'info' : 'warning',
|
|
286
|
+
autoFixable: false,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Check all page components
|
|
291
|
+
const pageFiles = await findFiles(path.join(frontendDir, 'src', 'pages'), /\.tsx$/);
|
|
292
|
+
let incompletePages: string[] = [];
|
|
293
|
+
|
|
294
|
+
for (const file of pageFiles) {
|
|
295
|
+
const content = await readFile(file);
|
|
296
|
+
if (content) {
|
|
297
|
+
// Check if page is just a placeholder
|
|
298
|
+
const lineCount = content.split('\n').length;
|
|
299
|
+
if (lineCount < 15) {
|
|
300
|
+
incompletePages.push(path.basename(file));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Check for TODO in page content
|
|
304
|
+
const todos = findTodoPlaceholders(content);
|
|
305
|
+
if (todos.length > 0) {
|
|
306
|
+
incompletePages.push(`${path.basename(file)} (TODOs)`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
results.push({
|
|
312
|
+
passed: incompletePages.length === 0,
|
|
313
|
+
category: 'Pages',
|
|
314
|
+
check: 'Page completeness',
|
|
315
|
+
message: incompletePages.length === 0
|
|
316
|
+
? 'All pages are implemented'
|
|
317
|
+
: `Incomplete pages: ${incompletePages.slice(0, 5).join(', ')}`,
|
|
318
|
+
severity: incompletePages.length === 0 ? 'info' : 'warning',
|
|
319
|
+
autoFixable: false,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
return results;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Verify database setup
|
|
327
|
+
*/
|
|
328
|
+
async function verifyDatabaseSetup(projectDir: string): Promise<VerificationResult[]> {
|
|
329
|
+
const results: VerificationResult[] = [];
|
|
330
|
+
const backendDir = path.join(projectDir, 'packages', 'backend');
|
|
331
|
+
|
|
332
|
+
// Check .env.example has database config
|
|
333
|
+
const envExample = await readFile(path.join(projectDir, '.env.example')) ||
|
|
334
|
+
await readFile(path.join(backendDir, '.env.example'));
|
|
335
|
+
|
|
336
|
+
const hasDbConfig = envExample?.includes('DATABASE_URL') ||
|
|
337
|
+
envExample?.includes('DB_HOST');
|
|
338
|
+
|
|
339
|
+
results.push({
|
|
340
|
+
passed: !!hasDbConfig,
|
|
341
|
+
category: 'Database',
|
|
342
|
+
check: 'Database configuration documented',
|
|
343
|
+
message: hasDbConfig
|
|
344
|
+
? 'Database configuration is documented in .env.example'
|
|
345
|
+
: 'Missing database configuration in .env.example',
|
|
346
|
+
severity: hasDbConfig ? 'info' : 'warning',
|
|
347
|
+
autoFixable: true,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Check for docker-compose
|
|
351
|
+
const hasDocker = await fileExists(path.join(projectDir, 'docker-compose.yml')) ||
|
|
352
|
+
await fileExists(path.join(projectDir, 'docker-compose.yaml'));
|
|
353
|
+
|
|
354
|
+
results.push({
|
|
355
|
+
passed: hasDocker,
|
|
356
|
+
category: 'Database',
|
|
357
|
+
check: 'Docker Compose for local development',
|
|
358
|
+
message: hasDocker
|
|
359
|
+
? 'Docker Compose file exists for local development'
|
|
360
|
+
: 'No docker-compose.yml - users need to set up database manually',
|
|
361
|
+
severity: hasDocker ? 'info' : 'warning',
|
|
362
|
+
autoFixable: true,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
return results;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Verify the app actually starts
|
|
370
|
+
*/
|
|
371
|
+
async function verifyAppStarts(projectDir: string): Promise<VerificationResult[]> {
|
|
372
|
+
const results: VerificationResult[] = [];
|
|
373
|
+
const frontendDir = path.join(projectDir, 'packages', 'frontend');
|
|
374
|
+
|
|
375
|
+
// Try to start frontend briefly
|
|
376
|
+
try {
|
|
377
|
+
await execAsync('npm run build', {
|
|
378
|
+
cwd: frontendDir,
|
|
379
|
+
timeout: 120000,
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
results.push({
|
|
383
|
+
passed: true,
|
|
384
|
+
category: 'Build',
|
|
385
|
+
check: 'Frontend builds successfully',
|
|
386
|
+
message: 'Frontend production build completed',
|
|
387
|
+
severity: 'info',
|
|
388
|
+
autoFixable: false,
|
|
389
|
+
});
|
|
390
|
+
} catch (err: unknown) {
|
|
391
|
+
const error = err as { stderr?: string; message?: string };
|
|
392
|
+
results.push({
|
|
393
|
+
passed: false,
|
|
394
|
+
category: 'Build',
|
|
395
|
+
check: 'Frontend builds successfully',
|
|
396
|
+
message: `Frontend build failed: ${error.stderr?.slice(0, 200) || error.message}`,
|
|
397
|
+
severity: 'error',
|
|
398
|
+
autoFixable: false,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return results;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Run comprehensive project verification
|
|
407
|
+
*/
|
|
408
|
+
export async function runComprehensiveVerification(
|
|
409
|
+
projectDir: string,
|
|
410
|
+
onProgress?: (message: string) => void
|
|
411
|
+
): Promise<VerificationReport> {
|
|
412
|
+
const allResults: VerificationResult[] = [];
|
|
413
|
+
|
|
414
|
+
onProgress?.('Checking styling setup...');
|
|
415
|
+
allResults.push(...await verifyStylingSetup(projectDir));
|
|
416
|
+
|
|
417
|
+
onProgress?.('Checking authentication setup...');
|
|
418
|
+
allResults.push(...await verifyAuthSetup(projectDir));
|
|
419
|
+
|
|
420
|
+
onProgress?.('Checking route completeness...');
|
|
421
|
+
allResults.push(...await verifyRouteCompleteness(projectDir));
|
|
422
|
+
|
|
423
|
+
onProgress?.('Checking database setup...');
|
|
424
|
+
allResults.push(...await verifyDatabaseSetup(projectDir));
|
|
425
|
+
|
|
426
|
+
onProgress?.('Verifying app builds...');
|
|
427
|
+
allResults.push(...await verifyAppStarts(projectDir));
|
|
428
|
+
|
|
429
|
+
// Calculate summary
|
|
430
|
+
const passedChecks = allResults.filter(r => r.passed).length;
|
|
431
|
+
const failedChecks = allResults.filter(r => !r.passed && r.severity === 'error').length;
|
|
432
|
+
const warnings = allResults.filter(r => !r.passed && r.severity === 'warning').length;
|
|
433
|
+
|
|
434
|
+
const criticalIssues = allResults
|
|
435
|
+
.filter(r => !r.passed && r.severity === 'error')
|
|
436
|
+
.map(r => `[${r.category}] ${r.message}`);
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
passed: failedChecks === 0,
|
|
440
|
+
totalChecks: allResults.length,
|
|
441
|
+
passedChecks,
|
|
442
|
+
failedChecks,
|
|
443
|
+
warnings,
|
|
444
|
+
results: allResults,
|
|
445
|
+
criticalIssues,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Auto-fix fixable issues
|
|
451
|
+
*/
|
|
452
|
+
export async function autoFixIssues(
|
|
453
|
+
report: VerificationReport,
|
|
454
|
+
onProgress?: (message: string) => void
|
|
455
|
+
): Promise<number> {
|
|
456
|
+
let fixed = 0;
|
|
457
|
+
|
|
458
|
+
for (const result of report.results) {
|
|
459
|
+
if (!result.passed && result.autoFixable && result.fix) {
|
|
460
|
+
try {
|
|
461
|
+
onProgress?.(`Fixing: ${result.check}...`);
|
|
462
|
+
await result.fix();
|
|
463
|
+
fixed++;
|
|
464
|
+
} catch (err) {
|
|
465
|
+
onProgress?.(`Failed to fix ${result.check}: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return fixed;
|
|
471
|
+
}
|