popeye-cli 1.7.0 → 1.9.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 +148 -7
- package/cheatsheet.md +440 -0
- package/dist/cli/commands/db.d.ts +10 -0
- package/dist/cli/commands/db.d.ts.map +1 -0
- package/dist/cli/commands/db.js +240 -0
- package/dist/cli/commands/db.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +18 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +255 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/index.d.ts +3 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +3 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/review.d.ts +31 -0
- package/dist/cli/commands/review.d.ts.map +1 -0
- package/dist/cli/commands/review.js +156 -0
- package/dist/cli/commands/review.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +4 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +218 -61
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/admin-wizard.d.ts +25 -0
- package/dist/generators/admin-wizard.d.ts.map +1 -0
- package/dist/generators/admin-wizard.js +123 -0
- package/dist/generators/admin-wizard.js.map +1 -0
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +10 -3
- package/dist/generators/all.js.map +1 -1
- package/dist/generators/database.d.ts +58 -0
- package/dist/generators/database.d.ts.map +1 -0
- package/dist/generators/database.js +229 -0
- package/dist/generators/database.js.map +1 -0
- package/dist/generators/fullstack.d.ts.map +1 -1
- package/dist/generators/fullstack.js +23 -7
- package/dist/generators/fullstack.js.map +1 -1
- package/dist/generators/index.d.ts +2 -0
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +2 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/templates/admin-wizard-python.d.ts +32 -0
- package/dist/generators/templates/admin-wizard-python.d.ts.map +1 -0
- package/dist/generators/templates/admin-wizard-python.js +425 -0
- package/dist/generators/templates/admin-wizard-python.js.map +1 -0
- package/dist/generators/templates/admin-wizard-react.d.ts +48 -0
- package/dist/generators/templates/admin-wizard-react.d.ts.map +1 -0
- package/dist/generators/templates/admin-wizard-react.js +554 -0
- package/dist/generators/templates/admin-wizard-react.js.map +1 -0
- package/dist/generators/templates/database-docker.d.ts +23 -0
- package/dist/generators/templates/database-docker.d.ts.map +1 -0
- package/dist/generators/templates/database-docker.js +221 -0
- package/dist/generators/templates/database-docker.js.map +1 -0
- package/dist/generators/templates/database-python.d.ts +54 -0
- package/dist/generators/templates/database-python.d.ts.map +1 -0
- package/dist/generators/templates/database-python.js +723 -0
- package/dist/generators/templates/database-python.js.map +1 -0
- package/dist/generators/templates/database-typescript.d.ts +34 -0
- package/dist/generators/templates/database-typescript.d.ts.map +1 -0
- package/dist/generators/templates/database-typescript.js +232 -0
- package/dist/generators/templates/database-typescript.js.map +1 -0
- package/dist/generators/templates/fullstack.d.ts.map +1 -1
- package/dist/generators/templates/fullstack.js +29 -0
- package/dist/generators/templates/fullstack.js.map +1 -1
- package/dist/generators/templates/index.d.ts +5 -0
- package/dist/generators/templates/index.d.ts.map +1 -1
- package/dist/generators/templates/index.js +5 -0
- package/dist/generators/templates/index.js.map +1 -1
- package/dist/state/index.d.ts +10 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +21 -0
- package/dist/state/index.js.map +1 -1
- package/dist/types/audit.d.ts +623 -0
- package/dist/types/audit.d.ts.map +1 -0
- package/dist/types/audit.js +240 -0
- package/dist/types/audit.js.map +1 -0
- package/dist/types/database-runtime.d.ts +86 -0
- package/dist/types/database-runtime.d.ts.map +1 -0
- package/dist/types/database-runtime.js +61 -0
- package/dist/types/database-runtime.js.map +1 -0
- package/dist/types/database.d.ts +85 -0
- package/dist/types/database.d.ts.map +1 -0
- package/dist/types/database.js +71 -0
- package/dist/types/database.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/workflow.d.ts +36 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +7 -0
- package/dist/types/workflow.js.map +1 -1
- package/dist/workflow/audit-analyzer.d.ts +58 -0
- package/dist/workflow/audit-analyzer.d.ts.map +1 -0
- package/dist/workflow/audit-analyzer.js +420 -0
- package/dist/workflow/audit-analyzer.js.map +1 -0
- package/dist/workflow/audit-mode.d.ts +28 -0
- package/dist/workflow/audit-mode.d.ts.map +1 -0
- package/dist/workflow/audit-mode.js +169 -0
- package/dist/workflow/audit-mode.js.map +1 -0
- package/dist/workflow/audit-recovery.d.ts +61 -0
- package/dist/workflow/audit-recovery.d.ts.map +1 -0
- package/dist/workflow/audit-recovery.js +242 -0
- package/dist/workflow/audit-recovery.js.map +1 -0
- package/dist/workflow/audit-reporter.d.ts +65 -0
- package/dist/workflow/audit-reporter.d.ts.map +1 -0
- package/dist/workflow/audit-reporter.js +301 -0
- package/dist/workflow/audit-reporter.js.map +1 -0
- package/dist/workflow/audit-scanner.d.ts +87 -0
- package/dist/workflow/audit-scanner.d.ts.map +1 -0
- package/dist/workflow/audit-scanner.js +768 -0
- package/dist/workflow/audit-scanner.js.map +1 -0
- package/dist/workflow/db-setup-runner.d.ts +63 -0
- package/dist/workflow/db-setup-runner.d.ts.map +1 -0
- package/dist/workflow/db-setup-runner.js +336 -0
- package/dist/workflow/db-setup-runner.js.map +1 -0
- package/dist/workflow/db-state-machine.d.ts +30 -0
- package/dist/workflow/db-state-machine.d.ts.map +1 -0
- package/dist/workflow/db-state-machine.js +51 -0
- package/dist/workflow/db-state-machine.js.map +1 -0
- package/dist/workflow/index.d.ts +7 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +7 -0
- package/dist/workflow/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/db.ts +281 -0
- package/src/cli/commands/doctor.ts +273 -0
- package/src/cli/commands/index.ts +3 -0
- package/src/cli/commands/review.ts +187 -0
- package/src/cli/index.ts +6 -0
- package/src/cli/interactive.ts +174 -4
- package/src/generators/admin-wizard.ts +146 -0
- package/src/generators/all.ts +10 -3
- package/src/generators/database.ts +286 -0
- package/src/generators/fullstack.ts +26 -9
- package/src/generators/index.ts +12 -0
- package/src/generators/templates/admin-wizard-python.ts +431 -0
- package/src/generators/templates/admin-wizard-react.ts +560 -0
- package/src/generators/templates/database-docker.ts +227 -0
- package/src/generators/templates/database-python.ts +734 -0
- package/src/generators/templates/database-typescript.ts +238 -0
- package/src/generators/templates/fullstack.ts +29 -0
- package/src/generators/templates/index.ts +5 -0
- package/src/state/index.ts +28 -0
- package/src/types/audit.ts +294 -0
- package/src/types/database-runtime.ts +69 -0
- package/src/types/database.ts +84 -0
- package/src/types/index.ts +29 -0
- package/src/types/workflow.ts +20 -0
- package/src/workflow/audit-analyzer.ts +491 -0
- package/src/workflow/audit-mode.ts +240 -0
- package/src/workflow/audit-recovery.ts +284 -0
- package/src/workflow/audit-reporter.ts +370 -0
- package/src/workflow/audit-scanner.ts +873 -0
- package/src/workflow/db-setup-runner.ts +391 -0
- package/src/workflow/db-state-machine.ts +58 -0
- package/src/workflow/index.ts +7 -0
- package/tests/cli/commands/review.test.ts +52 -0
- package/tests/generators/admin-wizard-orchestrator.test.ts +64 -0
- package/tests/generators/admin-wizard-templates.test.ts +366 -0
- package/tests/generators/cross-phase-integration.test.ts +383 -0
- package/tests/generators/database.test.ts +456 -0
- package/tests/generators/fe-be-db-integration.test.ts +613 -0
- package/tests/types/audit.test.ts +250 -0
- package/tests/types/database-runtime.test.ts +158 -0
- package/tests/types/database.test.ts +187 -0
- package/tests/workflow/audit-analyzer.test.ts +281 -0
- package/tests/workflow/audit-mode.test.ts +114 -0
- package/tests/workflow/audit-recovery.test.ts +237 -0
- package/tests/workflow/audit-reporter.test.ts +254 -0
- package/tests/workflow/audit-scanner.test.ts +270 -0
- package/tests/workflow/db-setup-runner.test.ts +211 -0
- package/tests/workflow/db-state-machine.test.ts +117 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: popeye review
|
|
3
|
+
*
|
|
4
|
+
* Runs a post-build audit/review of the project, producing a structured
|
|
5
|
+
* report with findings and optional recovery tasks.
|
|
6
|
+
*
|
|
7
|
+
* Pattern follows doctor.ts command factory.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import {
|
|
13
|
+
printHeader,
|
|
14
|
+
printSuccess,
|
|
15
|
+
printError,
|
|
16
|
+
printWarning,
|
|
17
|
+
printInfo,
|
|
18
|
+
printKeyValue,
|
|
19
|
+
printSection,
|
|
20
|
+
} from '../output.js';
|
|
21
|
+
import { runAuditMode, type AuditModeRunOptions } from '../../workflow/audit-mode.js';
|
|
22
|
+
import type { AuditModeResult, ComponentKind } from '../../types/audit.js';
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Run audit (exported for testability + slash command reuse)
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Execute the audit and print results to the console.
|
|
30
|
+
*
|
|
31
|
+
* @param projectDir - Absolute path to the project directory.
|
|
32
|
+
* @param options - CLI options.
|
|
33
|
+
* @returns The audit result.
|
|
34
|
+
*/
|
|
35
|
+
export async function runReview(
|
|
36
|
+
projectDir: string,
|
|
37
|
+
options: {
|
|
38
|
+
depth?: number;
|
|
39
|
+
strict?: boolean;
|
|
40
|
+
format?: 'json' | 'md' | 'both';
|
|
41
|
+
recover?: boolean;
|
|
42
|
+
target?: string;
|
|
43
|
+
} = {}
|
|
44
|
+
): Promise<AuditModeResult> {
|
|
45
|
+
printHeader('Project Audit / Review');
|
|
46
|
+
|
|
47
|
+
const auditOptions: AuditModeRunOptions = {
|
|
48
|
+
projectDir,
|
|
49
|
+
depth: options.depth ?? 2,
|
|
50
|
+
runTests: true,
|
|
51
|
+
strict: options.strict ?? false,
|
|
52
|
+
format: options.format ?? 'both',
|
|
53
|
+
autoRecover: options.recover ?? true,
|
|
54
|
+
target: (options.target ?? 'all') as 'all' | ComponentKind,
|
|
55
|
+
onProgress: (stage, message) => {
|
|
56
|
+
printInfo(`[${stage}] ${message}`);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const result = await runAuditMode(auditOptions);
|
|
61
|
+
|
|
62
|
+
if (!result.success) {
|
|
63
|
+
printError(`Audit failed: ${result.error}`);
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Print summary
|
|
68
|
+
console.log();
|
|
69
|
+
printSection('Summary');
|
|
70
|
+
printKeyValue('Project', result.summary.projectName);
|
|
71
|
+
printKeyValue('Language', result.summary.language);
|
|
72
|
+
printKeyValue('Source files', result.summary.totalSourceFiles);
|
|
73
|
+
printKeyValue('Test files', result.summary.totalTestFiles);
|
|
74
|
+
printKeyValue('Lines of code', result.summary.totalLinesOfCode);
|
|
75
|
+
printKeyValue('Components', result.summary.componentCount);
|
|
76
|
+
|
|
77
|
+
// Print score
|
|
78
|
+
console.log();
|
|
79
|
+
printSection('Audit Score');
|
|
80
|
+
const score = result.audit.overallScore;
|
|
81
|
+
if (score >= 80) {
|
|
82
|
+
printSuccess(`Overall: ${score}/100`);
|
|
83
|
+
} else if (score >= 60) {
|
|
84
|
+
printWarning(`Overall: ${score}/100`);
|
|
85
|
+
} else {
|
|
86
|
+
printError(`Overall: ${score}/100`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Print finding counts
|
|
90
|
+
if (result.audit.criticalCount > 0) {
|
|
91
|
+
printError(`Critical: ${result.audit.criticalCount}`);
|
|
92
|
+
}
|
|
93
|
+
if (result.audit.majorCount > 0) {
|
|
94
|
+
printWarning(`Major: ${result.audit.majorCount}`);
|
|
95
|
+
}
|
|
96
|
+
if (result.audit.minorCount > 0) {
|
|
97
|
+
printInfo(`Minor: ${result.audit.minorCount}`);
|
|
98
|
+
}
|
|
99
|
+
if (result.audit.infoCount > 0) {
|
|
100
|
+
printInfo(`Info: ${result.audit.infoCount}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Recommendation
|
|
104
|
+
console.log();
|
|
105
|
+
const rec = result.audit.recommendation;
|
|
106
|
+
if (rec === 'pass') {
|
|
107
|
+
printSuccess(`Recommendation: ${rec}`);
|
|
108
|
+
} else if (rec === 'fix-and-recheck') {
|
|
109
|
+
printWarning(`Recommendation: ${rec}`);
|
|
110
|
+
} else {
|
|
111
|
+
printError(`Recommendation: ${rec}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Report paths
|
|
115
|
+
if (Object.keys(result.reportPaths).length > 0) {
|
|
116
|
+
console.log();
|
|
117
|
+
printSection('Reports');
|
|
118
|
+
if (result.reportPaths.auditMd) {
|
|
119
|
+
printInfo(`Markdown: ${result.reportPaths.auditMd}`);
|
|
120
|
+
}
|
|
121
|
+
if (result.reportPaths.auditJson) {
|
|
122
|
+
printInfo(`JSON: ${result.reportPaths.auditJson}`);
|
|
123
|
+
}
|
|
124
|
+
if (result.reportPaths.recoveryMd) {
|
|
125
|
+
printWarning(`Recovery plan: ${result.reportPaths.recoveryMd}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Recovery info
|
|
130
|
+
if (result.recovery) {
|
|
131
|
+
console.log();
|
|
132
|
+
printSection('Recovery Plan');
|
|
133
|
+
printWarning(
|
|
134
|
+
`${result.recovery.milestones.length} recovery milestone(s), estimated ${result.recovery.estimatedEffort}`
|
|
135
|
+
);
|
|
136
|
+
if (auditOptions.autoRecover) {
|
|
137
|
+
printSuccess('Recovery milestones injected — run /resume to execute.');
|
|
138
|
+
} else {
|
|
139
|
+
printInfo('Run without --no-recover to auto-inject recovery milestones.');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// Commander command factory
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Create the `popeye review` CLI command.
|
|
152
|
+
*
|
|
153
|
+
* @returns Commander command instance.
|
|
154
|
+
*/
|
|
155
|
+
export function createReviewCommand(): Command {
|
|
156
|
+
const cmd = new Command('review')
|
|
157
|
+
.alias('audit')
|
|
158
|
+
.description('Run a post-build audit/review of the project')
|
|
159
|
+
.argument('[directory]', 'Project directory', '.')
|
|
160
|
+
.option('-d, --depth <level>', 'Audit depth: 1=shallow, 2=standard, 3=deep', '2')
|
|
161
|
+
.option('-s, --strict', 'Enable strict mode (higher standards)', false)
|
|
162
|
+
.option('-f, --format <type>', 'Output format: json, md, both', 'both')
|
|
163
|
+
.option('--no-recover', 'Skip auto-injection of recovery milestones')
|
|
164
|
+
.option('-t, --target <kind>', 'Audit target: all, frontend, backend, website', 'all')
|
|
165
|
+
.action(async (directory: string, opts: Record<string, string | boolean>) => {
|
|
166
|
+
const projectDir = path.resolve(directory);
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const result = await runReview(projectDir, {
|
|
170
|
+
depth: parseInt(opts.depth as string, 10),
|
|
171
|
+
strict: opts.strict as boolean,
|
|
172
|
+
format: opts.format as 'json' | 'md' | 'both',
|
|
173
|
+
recover: opts.recover as boolean,
|
|
174
|
+
target: opts.target as string,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (!result.success) {
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
} catch (err) {
|
|
181
|
+
printError(err instanceof Error ? err.message : 'Audit failed');
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return cmd;
|
|
187
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -15,6 +15,9 @@ import {
|
|
|
15
15
|
createResetCommand,
|
|
16
16
|
createCancelCommand,
|
|
17
17
|
createConfigCommand,
|
|
18
|
+
createDbCommand,
|
|
19
|
+
createDoctorCommand,
|
|
20
|
+
createReviewCommand,
|
|
18
21
|
} from './commands/index.js';
|
|
19
22
|
import { startInteractiveMode } from './interactive.js';
|
|
20
23
|
import { printError } from './output.js';
|
|
@@ -54,6 +57,9 @@ export function createProgram(): Command {
|
|
|
54
57
|
program.addCommand(createResetCommand());
|
|
55
58
|
program.addCommand(createCancelCommand());
|
|
56
59
|
program.addCommand(createConfigCommand());
|
|
60
|
+
program.addCommand(createDbCommand());
|
|
61
|
+
program.addCommand(createDoctorCommand());
|
|
62
|
+
program.addCommand(createReviewCommand());
|
|
57
63
|
|
|
58
64
|
// Interactive mode command
|
|
59
65
|
program
|
package/src/cli/interactive.ts
CHANGED
|
@@ -844,6 +844,9 @@ function showHelp(): void {
|
|
|
844
844
|
['/new <idea>', 'Force start a new project (skips existing check)'],
|
|
845
845
|
['/resume', 'Resume interrupted project'],
|
|
846
846
|
['/overview [fix]', 'Project review with analysis; fix to auto-discover docs'],
|
|
847
|
+
['/db [action]', 'Database management (status/configure/apply)'],
|
|
848
|
+
['/doctor', 'Run database and project readiness checks'],
|
|
849
|
+
['/review', 'Run post-build audit/review with findings and recovery'],
|
|
847
850
|
['/clear', 'Clear screen'],
|
|
848
851
|
['/exit', 'Exit Popeye'],
|
|
849
852
|
];
|
|
@@ -1012,6 +1015,19 @@ async function handleInput(input: string, state: SessionState): Promise<boolean>
|
|
|
1012
1015
|
}
|
|
1013
1016
|
break;
|
|
1014
1017
|
|
|
1018
|
+
case '/db':
|
|
1019
|
+
await handleDbSlashCommand(state, args);
|
|
1020
|
+
break;
|
|
1021
|
+
|
|
1022
|
+
case '/doctor':
|
|
1023
|
+
await handleDoctorSlashCommand(state);
|
|
1024
|
+
break;
|
|
1025
|
+
|
|
1026
|
+
case '/review':
|
|
1027
|
+
case '/audit':
|
|
1028
|
+
await handleReviewSlashCommand(state, args);
|
|
1029
|
+
break;
|
|
1030
|
+
|
|
1015
1031
|
default:
|
|
1016
1032
|
printError(`Unknown command: ${cmd}`);
|
|
1017
1033
|
printInfo('Type /help for available commands');
|
|
@@ -1055,6 +1071,143 @@ async function handleStatus(state: SessionState): Promise<void> {
|
|
|
1055
1071
|
console.log(summary);
|
|
1056
1072
|
}
|
|
1057
1073
|
|
|
1074
|
+
/**
|
|
1075
|
+
* Handle /db slash command - database management
|
|
1076
|
+
*/
|
|
1077
|
+
async function handleDbSlashCommand(state: SessionState, args: string[]): Promise<void> {
|
|
1078
|
+
if (!state.projectDir) {
|
|
1079
|
+
printError('No active project. Create or resume a project first.');
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
const action = args[0] || 'status';
|
|
1084
|
+
|
|
1085
|
+
switch (action) {
|
|
1086
|
+
case 'status': {
|
|
1087
|
+
try {
|
|
1088
|
+
const { loadProject } = await import('../state/index.js');
|
|
1089
|
+
const { DEFAULT_DB_CONFIG } = await import('../types/database.js');
|
|
1090
|
+
const projectState = await loadProject(state.projectDir);
|
|
1091
|
+
const dbConfig = projectState.dbConfig || { ...DEFAULT_DB_CONFIG, designed: false };
|
|
1092
|
+
|
|
1093
|
+
console.log();
|
|
1094
|
+
printInfo('Database Status:');
|
|
1095
|
+
console.log(` Designed: ${dbConfig.designed ? 'Yes' : 'No'}`);
|
|
1096
|
+
console.log(` Status: ${dbConfig.status}`);
|
|
1097
|
+
console.log(` Mode: ${dbConfig.mode || 'not set'}`);
|
|
1098
|
+
console.log(` Vector: ${dbConfig.vectorRequired ? 'Yes' : 'No'}`);
|
|
1099
|
+
console.log(` Migrations: ${dbConfig.migrationsApplied}`);
|
|
1100
|
+
if (dbConfig.lastError) {
|
|
1101
|
+
printError(` Last Error: ${dbConfig.lastError}`);
|
|
1102
|
+
}
|
|
1103
|
+
console.log();
|
|
1104
|
+
} catch (err) {
|
|
1105
|
+
printError(err instanceof Error ? err.message : 'Failed to load project state');
|
|
1106
|
+
}
|
|
1107
|
+
break;
|
|
1108
|
+
}
|
|
1109
|
+
case 'configure': {
|
|
1110
|
+
printInfo('Use "popeye db configure" from the CLI for interactive configuration.');
|
|
1111
|
+
printInfo('Or set DATABASE_URL in apps/backend/.env manually.');
|
|
1112
|
+
break;
|
|
1113
|
+
}
|
|
1114
|
+
case 'apply': {
|
|
1115
|
+
printInfo('Use "popeye db apply" from the CLI to run the setup pipeline.');
|
|
1116
|
+
break;
|
|
1117
|
+
}
|
|
1118
|
+
default:
|
|
1119
|
+
printError(`Unknown db action: ${action}`);
|
|
1120
|
+
printInfo('Usage: /db [status|configure|apply]');
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
/**
|
|
1125
|
+
* Handle /doctor slash command - readiness checks
|
|
1126
|
+
*/
|
|
1127
|
+
async function handleDoctorSlashCommand(state: SessionState): Promise<void> {
|
|
1128
|
+
if (!state.projectDir) {
|
|
1129
|
+
printError('No active project. Create or resume a project first.');
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
try {
|
|
1134
|
+
const { runDoctorChecks } = await import('./commands/doctor.js');
|
|
1135
|
+
|
|
1136
|
+
console.log();
|
|
1137
|
+
printInfo('Running readiness checks...');
|
|
1138
|
+
console.log();
|
|
1139
|
+
|
|
1140
|
+
const result = await runDoctorChecks(state.projectDir);
|
|
1141
|
+
|
|
1142
|
+
for (const check of result.checks) {
|
|
1143
|
+
const label = check.passed ? '[PASS]' : check.severity === 'info' ? '[SKIP]' : '[FAIL]';
|
|
1144
|
+
if (check.passed) {
|
|
1145
|
+
printSuccess(` ${label} ${check.name}: ${check.message}`);
|
|
1146
|
+
} else if (check.severity === 'info') {
|
|
1147
|
+
printInfo(` ${label} ${check.name}: ${check.message}`);
|
|
1148
|
+
} else if (check.severity === 'warning') {
|
|
1149
|
+
printWarning(` ${label} ${check.name}: ${check.message}`);
|
|
1150
|
+
} else {
|
|
1151
|
+
printError(` ${label} ${check.name}: ${check.message}`);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
console.log();
|
|
1156
|
+
if (result.healthy) {
|
|
1157
|
+
printSuccess('All critical checks passed.');
|
|
1158
|
+
} else {
|
|
1159
|
+
printWarning('Some critical checks failed. See above for details.');
|
|
1160
|
+
}
|
|
1161
|
+
} catch (err) {
|
|
1162
|
+
printError(err instanceof Error ? err.message : 'Doctor checks failed');
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* Handle /review or /audit slash command - post-build project audit
|
|
1168
|
+
*/
|
|
1169
|
+
async function handleReviewSlashCommand(state: SessionState, args: string[] = []): Promise<void> {
|
|
1170
|
+
if (!state.projectDir) {
|
|
1171
|
+
printError('No active project. Create or resume a project first.');
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Parse CLI-style flags from args
|
|
1176
|
+
const options: {
|
|
1177
|
+
depth?: number;
|
|
1178
|
+
strict?: boolean;
|
|
1179
|
+
format?: 'json' | 'md' | 'both';
|
|
1180
|
+
recover?: boolean;
|
|
1181
|
+
target?: string;
|
|
1182
|
+
} = {};
|
|
1183
|
+
|
|
1184
|
+
for (let i = 0; i < args.length; i++) {
|
|
1185
|
+
const arg = args[i];
|
|
1186
|
+
if ((arg === '--depth' || arg === '-d') && args[i + 1]) {
|
|
1187
|
+
options.depth = parseInt(args[++i], 10);
|
|
1188
|
+
} else if (arg === '--strict' || arg === '-s') {
|
|
1189
|
+
options.strict = true;
|
|
1190
|
+
} else if ((arg === '--format' || arg === '-f') && args[i + 1]) {
|
|
1191
|
+
options.format = args[++i] as 'json' | 'md' | 'both';
|
|
1192
|
+
} else if (arg === '--no-recover') {
|
|
1193
|
+
options.recover = false;
|
|
1194
|
+
} else if (arg === '--recover') {
|
|
1195
|
+
options.recover = true;
|
|
1196
|
+
} else if ((arg === '--target' || arg === '-t') && args[i + 1]) {
|
|
1197
|
+
options.target = args[++i];
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
try {
|
|
1202
|
+
const { runReview } = await import('./commands/review.js');
|
|
1203
|
+
|
|
1204
|
+
console.log();
|
|
1205
|
+
await runReview(state.projectDir, options);
|
|
1206
|
+
} catch (err) {
|
|
1207
|
+
printError(err instanceof Error ? err.message : 'Audit failed');
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1058
1211
|
/**
|
|
1059
1212
|
* Handle /overview command - full project plan and milestone review
|
|
1060
1213
|
*/
|
|
@@ -1734,11 +1887,27 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
|
|
|
1734
1887
|
return;
|
|
1735
1888
|
}
|
|
1736
1889
|
|
|
1737
|
-
//
|
|
1738
|
-
|
|
1739
|
-
|
|
1890
|
+
// Reason: If there's already an active project with pending work (e.g., from /review recovery),
|
|
1891
|
+
// skip project discovery and go straight to resuming.
|
|
1892
|
+
if (state.projectDir) {
|
|
1893
|
+
const activeStatus = await getWorkflowStatus(state.projectDir);
|
|
1894
|
+
if (activeStatus.exists && activeStatus.state) {
|
|
1895
|
+
const { phase, status: pStatus } = activeStatus.state;
|
|
1896
|
+
const hasPendingWork = phase !== 'complete' || pStatus !== 'complete';
|
|
1897
|
+
if (hasPendingWork) {
|
|
1898
|
+
printInfo(`Resuming active project: ${activeStatus.state.name}`);
|
|
1899
|
+
// Fall through to the resume logic below (skip discovery)
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
// Only discover projects if no active project with pending work
|
|
1905
|
+
if (!state.projectDir || (await getWorkflowStatus(state.projectDir)).state?.phase === 'complete') {
|
|
1906
|
+
// Discover all projects (registered + scanned in current directory)
|
|
1907
|
+
console.log();
|
|
1908
|
+
printInfo('Scanning for projects...');
|
|
1740
1909
|
|
|
1741
|
-
|
|
1910
|
+
const { all: allProjects } = await discoverProjects(state.projectDir || process.cwd());
|
|
1742
1911
|
|
|
1743
1912
|
// If projects found, let user select one
|
|
1744
1913
|
if (allProjects.length > 0) {
|
|
@@ -1815,6 +1984,7 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
|
|
|
1815
1984
|
console.log();
|
|
1816
1985
|
printInfo(`Selected: ${selectedProject.name}`);
|
|
1817
1986
|
}
|
|
1987
|
+
} // end: project discovery block
|
|
1818
1988
|
|
|
1819
1989
|
// Now check for formal project state at the selected/current directory
|
|
1820
1990
|
if (!state.projectDir) {
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Wizard generator orchestration module
|
|
3
|
+
* Creates the admin wizard backend + frontend layers for fullstack projects
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { promises as fs } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import {
|
|
9
|
+
generateAdminAuthMiddleware,
|
|
10
|
+
generateMiddlewareInit,
|
|
11
|
+
generateAdminDbRoutes,
|
|
12
|
+
} from './templates/admin-wizard-python.js';
|
|
13
|
+
import {
|
|
14
|
+
generateUseAdminApiHook,
|
|
15
|
+
generateDbStatusBanner,
|
|
16
|
+
generateConnectionForm,
|
|
17
|
+
generateMigrationProgress,
|
|
18
|
+
generateDbSetupStepper,
|
|
19
|
+
generateAdminIndex,
|
|
20
|
+
} from './templates/admin-wizard-react.js';
|
|
21
|
+
|
|
22
|
+
/** Python dependencies required by the admin wizard */
|
|
23
|
+
export const ADMIN_WIZARD_PYTHON_DEPS = ['python-multipart>=0.0.7'];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a directory if it doesn't exist
|
|
27
|
+
*/
|
|
28
|
+
async function ensureDir(dirPath: string): Promise<void> {
|
|
29
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Write a file with content
|
|
34
|
+
*/
|
|
35
|
+
async function writeFile(filePath: string, content: string): Promise<void> {
|
|
36
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Generate the complete admin wizard layer (backend middleware/routes + frontend components)
|
|
41
|
+
*
|
|
42
|
+
* Creates middleware/, routes/admin_db.py in the backend, and admin/ components
|
|
43
|
+
* in the frontend. Also augments requirements.txt with admin dependencies.
|
|
44
|
+
*
|
|
45
|
+
* @param projectDir - Root project directory (contains apps/)
|
|
46
|
+
* @param packageName - Python package name (snake_case)
|
|
47
|
+
* @returns List of absolute file paths created
|
|
48
|
+
*/
|
|
49
|
+
export async function generateAdminWizardLayer(
|
|
50
|
+
projectDir: string,
|
|
51
|
+
packageName: string
|
|
52
|
+
): Promise<string[]> {
|
|
53
|
+
const backendDir = path.join(projectDir, 'apps', 'backend');
|
|
54
|
+
const srcPkgDir = path.join(backendDir, 'src', packageName);
|
|
55
|
+
const frontendDir = path.join(projectDir, 'apps', 'frontend');
|
|
56
|
+
const filesCreated: string[] = [];
|
|
57
|
+
|
|
58
|
+
// Ensure directories
|
|
59
|
+
await ensureDir(path.join(srcPkgDir, 'middleware'));
|
|
60
|
+
await ensureDir(path.join(frontendDir, 'src', 'admin'));
|
|
61
|
+
|
|
62
|
+
// Define all files to generate
|
|
63
|
+
const files: Array<{ path: string; content: string }> = [
|
|
64
|
+
// Backend: middleware
|
|
65
|
+
{
|
|
66
|
+
path: path.join(srcPkgDir, 'middleware', '__init__.py'),
|
|
67
|
+
content: generateMiddlewareInit(),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
path: path.join(srcPkgDir, 'middleware', 'admin_auth.py'),
|
|
71
|
+
content: generateAdminAuthMiddleware(),
|
|
72
|
+
},
|
|
73
|
+
// Backend: admin routes
|
|
74
|
+
{
|
|
75
|
+
path: path.join(srcPkgDir, 'routes', 'admin_db.py'),
|
|
76
|
+
content: generateAdminDbRoutes(packageName),
|
|
77
|
+
},
|
|
78
|
+
// Frontend: admin components
|
|
79
|
+
{
|
|
80
|
+
path: path.join(frontendDir, 'src', 'admin', 'useAdminApi.ts'),
|
|
81
|
+
content: generateUseAdminApiHook(),
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
path: path.join(frontendDir, 'src', 'admin', 'DbStatusBanner.tsx'),
|
|
85
|
+
content: generateDbStatusBanner(),
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
path: path.join(frontendDir, 'src', 'admin', 'ConnectionForm.tsx'),
|
|
89
|
+
content: generateConnectionForm(),
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
path: path.join(frontendDir, 'src', 'admin', 'MigrationProgress.tsx'),
|
|
93
|
+
content: generateMigrationProgress(),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
path: path.join(frontendDir, 'src', 'admin', 'DbSetupStepper.tsx'),
|
|
97
|
+
content: generateDbSetupStepper(),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
path: path.join(frontendDir, 'src', 'admin', 'index.ts'),
|
|
101
|
+
content: generateAdminIndex(),
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
// Write all files
|
|
106
|
+
for (const file of files) {
|
|
107
|
+
await writeFile(file.path, file.content);
|
|
108
|
+
filesCreated.push(file.path);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Augment requirements.txt with admin deps
|
|
112
|
+
const reqPath = path.join(backendDir, 'requirements.txt');
|
|
113
|
+
try {
|
|
114
|
+
const existingReqs = await fs.readFile(reqPath, 'utf-8');
|
|
115
|
+
if (!existingReqs.includes('# Admin')) {
|
|
116
|
+
const adminSection = `\n# Admin\n${ADMIN_WIZARD_PYTHON_DEPS.join('\n')}\n`;
|
|
117
|
+
await writeFile(reqPath, existingReqs.trimEnd() + adminSection);
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// requirements.txt doesn't exist yet - will be created by the main generator
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return filesCreated;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get the list of relative file paths generated by the admin wizard layer
|
|
128
|
+
*
|
|
129
|
+
* @param packageName - Python package name (snake_case)
|
|
130
|
+
* @returns List of relative file paths
|
|
131
|
+
*/
|
|
132
|
+
export function getAdminWizardFiles(packageName: string): string[] {
|
|
133
|
+
return [
|
|
134
|
+
// Backend
|
|
135
|
+
`apps/backend/src/${packageName}/middleware/__init__.py`,
|
|
136
|
+
`apps/backend/src/${packageName}/middleware/admin_auth.py`,
|
|
137
|
+
`apps/backend/src/${packageName}/routes/admin_db.py`,
|
|
138
|
+
// Frontend
|
|
139
|
+
'apps/frontend/src/admin/useAdminApi.ts',
|
|
140
|
+
'apps/frontend/src/admin/DbStatusBanner.tsx',
|
|
141
|
+
'apps/frontend/src/admin/ConnectionForm.tsx',
|
|
142
|
+
'apps/frontend/src/admin/MigrationProgress.tsx',
|
|
143
|
+
'apps/frontend/src/admin/DbSetupStepper.tsx',
|
|
144
|
+
'apps/frontend/src/admin/index.ts',
|
|
145
|
+
];
|
|
146
|
+
}
|
package/src/generators/all.ts
CHANGED
|
@@ -16,6 +16,9 @@ import {
|
|
|
16
16
|
generateUiPackage as generateUiPackageImpl,
|
|
17
17
|
} from './shared-packages.js';
|
|
18
18
|
import type { BrandColorOptions } from './shared-packages.js';
|
|
19
|
+
import { generateAllDockerComposeWithDb } from './templates/database-docker.js';
|
|
20
|
+
import { getAdminWizardFiles } from './admin-wizard.js';
|
|
21
|
+
import { getDatabaseFiles } from './database.js';
|
|
19
22
|
|
|
20
23
|
/**
|
|
21
24
|
* Options for all project generation
|
|
@@ -479,14 +482,14 @@ export async function generateAllProject(
|
|
|
479
482
|
path: path.join(projectDir, '.popeye', 'workspace.json'),
|
|
480
483
|
content: generateAllWorkspaceJson(projectName),
|
|
481
484
|
},
|
|
482
|
-
// Docker compose (override to include website)
|
|
485
|
+
// Docker compose (override to include website + postgres)
|
|
483
486
|
{
|
|
484
487
|
path: path.join(projectDir, 'docker-compose.yml'),
|
|
485
|
-
content:
|
|
488
|
+
content: generateAllDockerComposeWithDb(projectName),
|
|
486
489
|
},
|
|
487
490
|
{
|
|
488
491
|
path: path.join(projectDir, 'infra', 'docker', 'docker-compose.yml'),
|
|
489
|
-
content:
|
|
492
|
+
content: generateAllDockerComposeWithDb(projectName),
|
|
490
493
|
},
|
|
491
494
|
// README
|
|
492
495
|
{
|
|
@@ -562,6 +565,10 @@ export function getAllProjectFiles(projectName: string): string[] {
|
|
|
562
565
|
'packages/ui/src/button.tsx',
|
|
563
566
|
'packages/ui/src/card.tsx',
|
|
564
567
|
'packages/contracts/.gitkeep',
|
|
568
|
+
// Database layer
|
|
569
|
+
...getDatabaseFiles(packageName, 'sqlalchemy'),
|
|
570
|
+
// Admin wizard layer
|
|
571
|
+
...getAdminWizardFiles(packageName),
|
|
565
572
|
];
|
|
566
573
|
}
|
|
567
574
|
|