popeye-cli 1.0.1 → 1.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/.env.example +24 -1
- package/CONTRIBUTING.md +275 -0
- package/OPEN_SOURCE_MANIFESTO.md +172 -0
- package/README.md +832 -123
- package/dist/adapters/claude.d.ts +19 -4
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +908 -42
- 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/grok.d.ts +73 -0
- package/dist/adapters/grok.d.ts.map +1 -0
- package/dist/adapters/grok.js +430 -0
- package/dist/adapters/grok.js.map +1 -0
- package/dist/adapters/openai.d.ts +1 -1
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +47 -8
- 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/grok.d.ts +73 -0
- package/dist/auth/grok.d.ts.map +1 -0
- package/dist/auth/grok.js +211 -0
- package/dist/auth/grok.js.map +1 -0
- package/dist/auth/index.d.ts +14 -7
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +41 -6
- 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/commands/auth.d.ts +1 -1
- package/dist/cli/commands/auth.d.ts.map +1 -1
- package/dist/cli/commands/auth.js +79 -8
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +15 -4
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +1494 -114
- package/dist/cli/interactive.js.map +1 -1
- package/dist/config/defaults.d.ts +9 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +19 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/index.d.ts +19 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +33 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +47 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +29 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/generators/fullstack.d.ts +32 -0
- package/dist/generators/fullstack.d.ts.map +1 -0
- package/dist/generators/fullstack.js +497 -0
- package/dist/generators/fullstack.js.map +1 -0
- package/dist/generators/index.d.ts +4 -3
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +15 -1
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/python.d.ts +17 -1
- package/dist/generators/python.d.ts.map +1 -1
- package/dist/generators/python.js +34 -20
- package/dist/generators/python.js.map +1 -1
- package/dist/generators/templates/fullstack.d.ts +113 -0
- package/dist/generators/templates/fullstack.d.ts.map +1 -0
- package/dist/generators/templates/fullstack.js +1004 -0
- package/dist/generators/templates/fullstack.js.map +1 -0
- package/dist/generators/typescript.d.ts +19 -1
- package/dist/generators/typescript.d.ts.map +1 -1
- package/dist/generators/typescript.js +37 -20
- 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 +8 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/cli.js.map +1 -1
- package/dist/types/consensus.d.ts +186 -4
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +35 -3
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/project.d.ts +76 -0
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/project.js +1 -1
- package/dist/types/project.js.map +1 -1
- package/dist/types/workflow.d.ts +217 -16
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +40 -1
- 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 +70 -2
- package/dist/workflow/consensus.d.ts.map +1 -1
- package/dist/workflow/consensus.js +872 -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 +80 -3
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +767 -49
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/plan-storage.d.ts +386 -0
- package/dist/workflow/plan-storage.d.ts.map +1 -0
- package/dist/workflow/plan-storage.js +878 -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 +386 -0
- package/dist/workflow/task-workflow.js.map +1 -0
- package/dist/workflow/test-runner.d.ts +9 -0
- package/dist/workflow/test-runner.d.ts.map +1 -1
- package/dist/workflow/test-runner.js +101 -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/dist/workflow/workspace-manager.d.ts +342 -0
- package/dist/workflow/workspace-manager.d.ts.map +1 -0
- package/dist/workflow/workspace-manager.js +733 -0
- package/dist/workflow/workspace-manager.js.map +1 -0
- package/package.json +2 -2
- package/src/adapters/claude.ts +1067 -47
- package/src/adapters/gemini.ts +373 -0
- package/src/adapters/grok.ts +492 -0
- package/src/adapters/openai.ts +48 -9
- package/src/auth/claude.ts +120 -78
- package/src/auth/gemini.ts +207 -0
- package/src/auth/grok.ts +255 -0
- package/src/auth/index.ts +47 -9
- package/src/auth/keychain.ts +95 -28
- package/src/auth/openai.ts +29 -36
- package/src/cli/commands/auth.ts +89 -10
- package/src/cli/commands/create.ts +13 -4
- package/src/cli/interactive.ts +1774 -142
- package/src/config/defaults.ts +19 -2
- package/src/config/index.ts +36 -1
- package/src/config/schema.ts +30 -1
- package/src/generators/fullstack.ts +551 -0
- package/src/generators/index.ts +25 -1
- package/src/generators/python.ts +65 -20
- package/src/generators/templates/fullstack.ts +1047 -0
- package/src/generators/typescript.ts +69 -20
- package/src/state/index.ts +713 -4
- package/src/state/registry.ts +278 -0
- package/src/types/cli.ts +8 -0
- package/src/types/consensus.ts +197 -6
- package/src/types/project.ts +82 -1
- package/src/types/workflow.ts +90 -1
- package/src/workflow/auto-fix.ts +340 -0
- package/src/workflow/consensus.ts +1180 -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 +924 -50
- package/src/workflow/plan-storage.ts +1282 -0
- package/src/workflow/project-verification.ts +471 -0
- package/src/workflow/task-workflow.ts +528 -0
- package/src/workflow/test-runner.ts +120 -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/src/workflow/workspace-manager.ts +912 -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,551 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fullstack project generator
|
|
3
|
+
* Orchestrates Python and TypeScript generators for monorepo structure
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { promises as fs } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import type { ProjectSpec } from '../types/project.js';
|
|
9
|
+
import type { GenerationResult } from './python.js';
|
|
10
|
+
import {
|
|
11
|
+
generateWorkspaceJson,
|
|
12
|
+
generateRootDockerCompose,
|
|
13
|
+
generateRootReadme,
|
|
14
|
+
generateRootGitignore,
|
|
15
|
+
generateFrontendReadme,
|
|
16
|
+
generateBackendReadme,
|
|
17
|
+
generateUiSpec,
|
|
18
|
+
generateViteConfigReact,
|
|
19
|
+
generateTailwindConfig,
|
|
20
|
+
generatePostcssConfig,
|
|
21
|
+
generateMainCss,
|
|
22
|
+
generateAppTsx,
|
|
23
|
+
generateMainTsx,
|
|
24
|
+
generateIndexHtml,
|
|
25
|
+
generateFrontendPackageJson,
|
|
26
|
+
generateFrontendTsconfig,
|
|
27
|
+
generateFrontendTsconfigNode,
|
|
28
|
+
generateFrontendDockerfile,
|
|
29
|
+
generateNginxConfig,
|
|
30
|
+
generateFrontendTest,
|
|
31
|
+
generateVitestSetup,
|
|
32
|
+
generateFrontendVitestConfig,
|
|
33
|
+
generateFastAPIMain,
|
|
34
|
+
generateBackendDockerfile,
|
|
35
|
+
generateFastAPIRequirements,
|
|
36
|
+
} from './templates/fullstack.js';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create a directory if it doesn't exist
|
|
40
|
+
*/
|
|
41
|
+
async function ensureDir(dirPath: string): Promise<void> {
|
|
42
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Write a file with content
|
|
47
|
+
*/
|
|
48
|
+
async function writeFile(filePath: string, content: string): Promise<void> {
|
|
49
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Convert project name to Python package name
|
|
54
|
+
*/
|
|
55
|
+
function toPythonPackageName(name: string): string {
|
|
56
|
+
return name.toLowerCase().replace(/-/g, '_').replace(/[^a-z0-9_]/g, '');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generate a complete fullstack project (React frontend + FastAPI backend)
|
|
61
|
+
*
|
|
62
|
+
* @param spec - Project specification
|
|
63
|
+
* @param outputDir - Output directory
|
|
64
|
+
* @returns Generation result
|
|
65
|
+
*/
|
|
66
|
+
export async function generateFullstackProject(
|
|
67
|
+
spec: ProjectSpec,
|
|
68
|
+
outputDir: string
|
|
69
|
+
): Promise<GenerationResult> {
|
|
70
|
+
const projectName = spec.name || 'my-project';
|
|
71
|
+
const projectDir = path.join(outputDir, projectName);
|
|
72
|
+
const packageName = toPythonPackageName(projectName);
|
|
73
|
+
const filesCreated: string[] = [];
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
// Create root directory structure
|
|
77
|
+
await ensureDir(projectDir);
|
|
78
|
+
await ensureDir(path.join(projectDir, 'apps'));
|
|
79
|
+
await ensureDir(path.join(projectDir, 'apps', 'frontend'));
|
|
80
|
+
await ensureDir(path.join(projectDir, 'apps', 'frontend', 'src'));
|
|
81
|
+
await ensureDir(path.join(projectDir, 'apps', 'frontend', 'tests'));
|
|
82
|
+
await ensureDir(path.join(projectDir, 'apps', 'frontend', 'public'));
|
|
83
|
+
await ensureDir(path.join(projectDir, 'apps', 'backend'));
|
|
84
|
+
await ensureDir(path.join(projectDir, 'apps', 'backend', 'src', packageName));
|
|
85
|
+
await ensureDir(path.join(projectDir, 'apps', 'backend', 'tests'));
|
|
86
|
+
await ensureDir(path.join(projectDir, 'packages', 'contracts'));
|
|
87
|
+
await ensureDir(path.join(projectDir, 'infra', 'docker'));
|
|
88
|
+
await ensureDir(path.join(projectDir, 'docs'));
|
|
89
|
+
await ensureDir(path.join(projectDir, '.popeye'));
|
|
90
|
+
|
|
91
|
+
// Generate root-level files
|
|
92
|
+
const rootFiles: Array<{ path: string; content: string }> = [
|
|
93
|
+
// Root config
|
|
94
|
+
{
|
|
95
|
+
path: path.join(projectDir, '.popeye', 'workspace.json'),
|
|
96
|
+
content: generateWorkspaceJson(projectName),
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
path: path.join(projectDir, '.popeye', 'ui-spec.json'),
|
|
100
|
+
content: generateUiSpec(projectName),
|
|
101
|
+
},
|
|
102
|
+
// Docker
|
|
103
|
+
{
|
|
104
|
+
path: path.join(projectDir, 'infra', 'docker', 'docker-compose.yml'),
|
|
105
|
+
content: generateRootDockerCompose(projectName),
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
path: path.join(projectDir, 'docker-compose.yml'),
|
|
109
|
+
content: generateRootDockerCompose(projectName),
|
|
110
|
+
},
|
|
111
|
+
// Documentation
|
|
112
|
+
{
|
|
113
|
+
path: path.join(projectDir, 'README.md'),
|
|
114
|
+
content: generateRootReadme(projectName, spec.idea),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
path: path.join(projectDir, '.gitignore'),
|
|
118
|
+
content: generateRootGitignore(),
|
|
119
|
+
},
|
|
120
|
+
// Docs placeholders
|
|
121
|
+
{
|
|
122
|
+
path: path.join(projectDir, 'docs', 'PLAN.md'),
|
|
123
|
+
content: `# ${projectName} - Development Plan\n\nGenerated by Popeye CLI.\n`,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
path: path.join(projectDir, 'docs', 'WORKFLOW_LOG.md'),
|
|
127
|
+
content: `# ${projectName} - Workflow Log\n\nGenerated by Popeye CLI.\n`,
|
|
128
|
+
},
|
|
129
|
+
// Packages placeholder
|
|
130
|
+
{
|
|
131
|
+
path: path.join(projectDir, 'packages', 'contracts', '.gitkeep'),
|
|
132
|
+
content: '',
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
// Write root files
|
|
137
|
+
for (const file of rootFiles) {
|
|
138
|
+
await writeFile(file.path, file.content);
|
|
139
|
+
filesCreated.push(file.path);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Generate frontend (React + Vite + Tailwind)
|
|
143
|
+
const frontendDir = path.join(projectDir, 'apps', 'frontend');
|
|
144
|
+
const frontendFiles: Array<{ path: string; content: string }> = [
|
|
145
|
+
// Config files
|
|
146
|
+
{
|
|
147
|
+
path: path.join(frontendDir, 'package.json'),
|
|
148
|
+
content: generateFrontendPackageJson(projectName),
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
path: path.join(frontendDir, 'tsconfig.json'),
|
|
152
|
+
content: generateFrontendTsconfig(),
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
path: path.join(frontendDir, 'tsconfig.node.json'),
|
|
156
|
+
content: generateFrontendTsconfigNode(),
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
path: path.join(frontendDir, 'vite.config.ts'),
|
|
160
|
+
content: generateViteConfigReact(),
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
path: path.join(frontendDir, 'vitest.config.ts'),
|
|
164
|
+
content: generateFrontendVitestConfig(),
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
path: path.join(frontendDir, 'tailwind.config.ts'),
|
|
168
|
+
content: generateTailwindConfig(),
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
path: path.join(frontendDir, 'postcss.config.js'),
|
|
172
|
+
content: generatePostcssConfig(),
|
|
173
|
+
},
|
|
174
|
+
// Entry files
|
|
175
|
+
{
|
|
176
|
+
path: path.join(frontendDir, 'index.html'),
|
|
177
|
+
content: generateIndexHtml(projectName),
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
path: path.join(frontendDir, 'src', 'main.tsx'),
|
|
181
|
+
content: generateMainTsx(),
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
path: path.join(frontendDir, 'src', 'App.tsx'),
|
|
185
|
+
content: generateAppTsx(projectName),
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
path: path.join(frontendDir, 'src', 'index.css'),
|
|
189
|
+
content: generateMainCss(),
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
path: path.join(frontendDir, 'src', 'vite-env.d.ts'),
|
|
193
|
+
content: '/// <reference types="vite/client" />\n',
|
|
194
|
+
},
|
|
195
|
+
// Test files
|
|
196
|
+
{
|
|
197
|
+
path: path.join(frontendDir, 'tests', 'setup.ts'),
|
|
198
|
+
content: generateVitestSetup(),
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
path: path.join(frontendDir, 'tests', 'App.test.tsx'),
|
|
202
|
+
content: generateFrontendTest(projectName),
|
|
203
|
+
},
|
|
204
|
+
// Docker
|
|
205
|
+
{
|
|
206
|
+
path: path.join(frontendDir, 'Dockerfile'),
|
|
207
|
+
content: generateFrontendDockerfile(),
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
path: path.join(frontendDir, 'nginx.conf'),
|
|
211
|
+
content: generateNginxConfig(),
|
|
212
|
+
},
|
|
213
|
+
// Documentation
|
|
214
|
+
{
|
|
215
|
+
path: path.join(frontendDir, 'README.md'),
|
|
216
|
+
content: generateFrontendReadme(projectName),
|
|
217
|
+
},
|
|
218
|
+
// Environment
|
|
219
|
+
{
|
|
220
|
+
path: path.join(frontendDir, '.env.example'),
|
|
221
|
+
content: 'VITE_API_URL=http://localhost:8000\n',
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
path: path.join(frontendDir, '.gitignore'),
|
|
225
|
+
content: 'node_modules/\ndist/\n.env\n.env.local\ncoverage/\n',
|
|
226
|
+
},
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
// Write frontend files
|
|
230
|
+
for (const file of frontendFiles) {
|
|
231
|
+
await writeFile(file.path, file.content);
|
|
232
|
+
filesCreated.push(file.path);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Generate backend (FastAPI)
|
|
236
|
+
const backendDir = path.join(projectDir, 'apps', 'backend');
|
|
237
|
+
const backendFiles: Array<{ path: string; content: string }> = [
|
|
238
|
+
// Config files
|
|
239
|
+
{
|
|
240
|
+
path: path.join(backendDir, 'pyproject.toml'),
|
|
241
|
+
content: generatePyprojectToml(projectName, packageName),
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
path: path.join(backendDir, 'requirements.txt'),
|
|
245
|
+
content: generateFastAPIRequirements(),
|
|
246
|
+
},
|
|
247
|
+
// Source files
|
|
248
|
+
{
|
|
249
|
+
path: path.join(backendDir, 'src', '__init__.py'),
|
|
250
|
+
content: '# Source root\n',
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
path: path.join(backendDir, 'src', packageName, '__init__.py'),
|
|
254
|
+
content: `"""${projectName} backend package."""\n\n__version__ = "1.0.0"\n`,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
path: path.join(backendDir, 'src', packageName, 'main.py'),
|
|
258
|
+
content: generateFastAPIMain(projectName),
|
|
259
|
+
},
|
|
260
|
+
// Test files
|
|
261
|
+
{
|
|
262
|
+
path: path.join(backendDir, 'tests', '__init__.py'),
|
|
263
|
+
content: '# Tests package\n',
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
path: path.join(backendDir, 'tests', 'conftest.py'),
|
|
267
|
+
content: generateConftest(),
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
path: path.join(backendDir, 'tests', 'test_main.py'),
|
|
271
|
+
content: generateBackendTest(projectName, packageName),
|
|
272
|
+
},
|
|
273
|
+
// Docker
|
|
274
|
+
{
|
|
275
|
+
path: path.join(backendDir, 'Dockerfile'),
|
|
276
|
+
content: generateBackendDockerfile(projectName),
|
|
277
|
+
},
|
|
278
|
+
// Documentation
|
|
279
|
+
{
|
|
280
|
+
path: path.join(backendDir, 'README.md'),
|
|
281
|
+
content: generateBackendReadme(projectName),
|
|
282
|
+
},
|
|
283
|
+
// Environment
|
|
284
|
+
{
|
|
285
|
+
path: path.join(backendDir, '.env.example'),
|
|
286
|
+
content: 'DEBUG=true\nDATABASE_URL=sqlite:///./data/app.db\n',
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
path: path.join(backendDir, '.gitignore'),
|
|
290
|
+
content: '__pycache__/\n*.py[cod]\nvenv/\n.venv/\n.env\n.coverage\nhtmlcov/\n.pytest_cache/\ndata/\n',
|
|
291
|
+
},
|
|
292
|
+
// Makefile
|
|
293
|
+
{
|
|
294
|
+
path: path.join(backendDir, 'Makefile'),
|
|
295
|
+
content: generateBackendMakefile(packageName),
|
|
296
|
+
},
|
|
297
|
+
];
|
|
298
|
+
|
|
299
|
+
// Write backend files
|
|
300
|
+
for (const file of backendFiles) {
|
|
301
|
+
await writeFile(file.path, file.content);
|
|
302
|
+
filesCreated.push(file.path);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
success: true,
|
|
307
|
+
projectDir,
|
|
308
|
+
filesCreated,
|
|
309
|
+
};
|
|
310
|
+
} catch (error) {
|
|
311
|
+
return {
|
|
312
|
+
success: false,
|
|
313
|
+
projectDir,
|
|
314
|
+
filesCreated,
|
|
315
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Generate pyproject.toml for FastAPI backend
|
|
322
|
+
*/
|
|
323
|
+
function generatePyprojectToml(projectName: string, _packageName: string): string {
|
|
324
|
+
return `[build-system]
|
|
325
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
326
|
+
build-backend = "setuptools.build_meta"
|
|
327
|
+
|
|
328
|
+
[project]
|
|
329
|
+
name = "${projectName}-backend"
|
|
330
|
+
version = "1.0.0"
|
|
331
|
+
description = "Backend API for ${projectName}"
|
|
332
|
+
readme = "README.md"
|
|
333
|
+
requires-python = ">=3.10"
|
|
334
|
+
license = {text = "MIT"}
|
|
335
|
+
dependencies = [
|
|
336
|
+
"fastapi>=0.109.0",
|
|
337
|
+
"uvicorn[standard]>=0.27.0",
|
|
338
|
+
"pydantic>=2.5.0",
|
|
339
|
+
"pydantic-settings>=2.1.0",
|
|
340
|
+
]
|
|
341
|
+
|
|
342
|
+
[project.optional-dependencies]
|
|
343
|
+
dev = [
|
|
344
|
+
"pytest>=7.4.0",
|
|
345
|
+
"pytest-asyncio>=0.23.0",
|
|
346
|
+
"httpx>=0.26.0",
|
|
347
|
+
"ruff>=0.1.0",
|
|
348
|
+
]
|
|
349
|
+
|
|
350
|
+
[tool.setuptools.packages.find]
|
|
351
|
+
where = ["src"]
|
|
352
|
+
|
|
353
|
+
[tool.pytest.ini_options]
|
|
354
|
+
asyncio_mode = "auto"
|
|
355
|
+
testpaths = ["tests"]
|
|
356
|
+
python_files = ["test_*.py"]
|
|
357
|
+
|
|
358
|
+
[tool.ruff]
|
|
359
|
+
line-length = 100
|
|
360
|
+
target-version = "py310"
|
|
361
|
+
|
|
362
|
+
[tool.ruff.lint]
|
|
363
|
+
select = ["E", "F", "I", "N", "W"]
|
|
364
|
+
`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Generate conftest.py for backend tests
|
|
369
|
+
*/
|
|
370
|
+
function generateConftest(): string {
|
|
371
|
+
return `"""
|
|
372
|
+
Test configuration and fixtures.
|
|
373
|
+
"""
|
|
374
|
+
|
|
375
|
+
import pytest
|
|
376
|
+
from httpx import AsyncClient
|
|
377
|
+
from httpx import ASGITransport
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
@pytest.fixture
|
|
381
|
+
def anyio_backend():
|
|
382
|
+
"""Use asyncio backend for pytest-anyio."""
|
|
383
|
+
return "asyncio"
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
@pytest.fixture
|
|
387
|
+
async def client():
|
|
388
|
+
"""Create async test client."""
|
|
389
|
+
from src.backend.main import app
|
|
390
|
+
|
|
391
|
+
transport = ASGITransport(app=app)
|
|
392
|
+
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
|
393
|
+
yield client
|
|
394
|
+
`;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Generate test file for backend
|
|
399
|
+
*/
|
|
400
|
+
function generateBackendTest(projectName: string, _packageName: string): string {
|
|
401
|
+
return `"""
|
|
402
|
+
Tests for ${projectName} backend API.
|
|
403
|
+
"""
|
|
404
|
+
|
|
405
|
+
import pytest
|
|
406
|
+
from httpx import AsyncClient
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
@pytest.mark.anyio
|
|
410
|
+
async def test_health_check(client: AsyncClient):
|
|
411
|
+
"""Test health check endpoint."""
|
|
412
|
+
response = await client.get("/health")
|
|
413
|
+
assert response.status_code == 200
|
|
414
|
+
data = response.json()
|
|
415
|
+
assert data["status"] == "healthy"
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
@pytest.mark.anyio
|
|
419
|
+
async def test_root(client: AsyncClient):
|
|
420
|
+
"""Test root endpoint."""
|
|
421
|
+
response = await client.get("/")
|
|
422
|
+
assert response.status_code == 200
|
|
423
|
+
data = response.json()
|
|
424
|
+
assert "message" in data
|
|
425
|
+
assert "docs" in data
|
|
426
|
+
`;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Generate Makefile for backend
|
|
431
|
+
*/
|
|
432
|
+
function generateBackendMakefile(packageName: string): string {
|
|
433
|
+
return `.PHONY: dev test lint format clean install
|
|
434
|
+
|
|
435
|
+
install:
|
|
436
|
+
\tpip install -e ".[dev]"
|
|
437
|
+
|
|
438
|
+
dev:
|
|
439
|
+
\tuvicorn src.${packageName}.main:app --reload --port 8000
|
|
440
|
+
|
|
441
|
+
test:
|
|
442
|
+
\tpytest -v
|
|
443
|
+
|
|
444
|
+
test-cov:
|
|
445
|
+
\tpytest --cov=src/${packageName} --cov-report=html
|
|
446
|
+
|
|
447
|
+
lint:
|
|
448
|
+
\truff check src/ tests/
|
|
449
|
+
|
|
450
|
+
format:
|
|
451
|
+
\truff format src/ tests/
|
|
452
|
+
|
|
453
|
+
clean:
|
|
454
|
+
\trm -rf __pycache__ .pytest_cache .coverage htmlcov .ruff_cache
|
|
455
|
+
\tfind . -type d -name "__pycache__" -exec rm -rf {} +
|
|
456
|
+
`;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get the list of files that would be generated for a fullstack project
|
|
461
|
+
*
|
|
462
|
+
* @param projectName - Project name
|
|
463
|
+
* @returns List of relative file paths
|
|
464
|
+
*/
|
|
465
|
+
export function getFullstackProjectFiles(projectName: string): string[] {
|
|
466
|
+
const packageName = toPythonPackageName(projectName);
|
|
467
|
+
|
|
468
|
+
return [
|
|
469
|
+
// Root
|
|
470
|
+
'.popeye/workspace.json',
|
|
471
|
+
'.popeye/ui-spec.json',
|
|
472
|
+
'infra/docker/docker-compose.yml',
|
|
473
|
+
'docker-compose.yml',
|
|
474
|
+
'README.md',
|
|
475
|
+
'.gitignore',
|
|
476
|
+
'docs/PLAN.md',
|
|
477
|
+
'docs/WORKFLOW_LOG.md',
|
|
478
|
+
'packages/contracts/.gitkeep',
|
|
479
|
+
// Frontend
|
|
480
|
+
'apps/frontend/package.json',
|
|
481
|
+
'apps/frontend/tsconfig.json',
|
|
482
|
+
'apps/frontend/tsconfig.node.json',
|
|
483
|
+
'apps/frontend/vite.config.ts',
|
|
484
|
+
'apps/frontend/vitest.config.ts',
|
|
485
|
+
'apps/frontend/tailwind.config.ts',
|
|
486
|
+
'apps/frontend/postcss.config.js',
|
|
487
|
+
'apps/frontend/index.html',
|
|
488
|
+
'apps/frontend/src/main.tsx',
|
|
489
|
+
'apps/frontend/src/App.tsx',
|
|
490
|
+
'apps/frontend/src/index.css',
|
|
491
|
+
'apps/frontend/src/vite-env.d.ts',
|
|
492
|
+
'apps/frontend/tests/setup.ts',
|
|
493
|
+
'apps/frontend/tests/App.test.tsx',
|
|
494
|
+
'apps/frontend/Dockerfile',
|
|
495
|
+
'apps/frontend/nginx.conf',
|
|
496
|
+
'apps/frontend/README.md',
|
|
497
|
+
'apps/frontend/.env.example',
|
|
498
|
+
'apps/frontend/.gitignore',
|
|
499
|
+
// Backend
|
|
500
|
+
'apps/backend/pyproject.toml',
|
|
501
|
+
'apps/backend/requirements.txt',
|
|
502
|
+
'apps/backend/src/__init__.py',
|
|
503
|
+
`apps/backend/src/${packageName}/__init__.py`,
|
|
504
|
+
`apps/backend/src/${packageName}/main.py`,
|
|
505
|
+
'apps/backend/tests/__init__.py',
|
|
506
|
+
'apps/backend/tests/conftest.py',
|
|
507
|
+
'apps/backend/tests/test_main.py',
|
|
508
|
+
'apps/backend/Dockerfile',
|
|
509
|
+
'apps/backend/README.md',
|
|
510
|
+
'apps/backend/.env.example',
|
|
511
|
+
'apps/backend/.gitignore',
|
|
512
|
+
'apps/backend/Makefile',
|
|
513
|
+
];
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Validate a fullstack project structure
|
|
518
|
+
*
|
|
519
|
+
* @param projectDir - Project directory
|
|
520
|
+
* @returns Validation result
|
|
521
|
+
*/
|
|
522
|
+
export async function validateFullstackProject(projectDir: string): Promise<{
|
|
523
|
+
valid: boolean;
|
|
524
|
+
missingFiles: string[];
|
|
525
|
+
}> {
|
|
526
|
+
const missingFiles: string[] = [];
|
|
527
|
+
|
|
528
|
+
const requiredPaths = [
|
|
529
|
+
'apps/frontend/package.json',
|
|
530
|
+
'apps/frontend/src',
|
|
531
|
+
'apps/backend/pyproject.toml',
|
|
532
|
+
'apps/backend/src',
|
|
533
|
+
'.popeye/workspace.json',
|
|
534
|
+
'docker-compose.yml',
|
|
535
|
+
'README.md',
|
|
536
|
+
];
|
|
537
|
+
|
|
538
|
+
for (const file of requiredPaths) {
|
|
539
|
+
const filePath = path.join(projectDir, file);
|
|
540
|
+
try {
|
|
541
|
+
await fs.access(filePath);
|
|
542
|
+
} catch {
|
|
543
|
+
missingFiles.push(file);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return {
|
|
548
|
+
valid: missingFiles.length === 0,
|
|
549
|
+
missingFiles,
|
|
550
|
+
};
|
|
551
|
+
}
|
package/src/generators/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Project generators module
|
|
3
|
-
* Provides unified API for generating Python and
|
|
3
|
+
* Provides unified API for generating Python, TypeScript, and Fullstack projects
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { ProjectSpec, OutputLanguage } from '../types/project.js';
|
|
@@ -17,6 +17,11 @@ import {
|
|
|
17
17
|
addTypeScriptModule,
|
|
18
18
|
getTypeScriptProjectFiles,
|
|
19
19
|
} from './typescript.js';
|
|
20
|
+
import {
|
|
21
|
+
generateFullstackProject,
|
|
22
|
+
validateFullstackProject,
|
|
23
|
+
getFullstackProjectFiles,
|
|
24
|
+
} from './fullstack.js';
|
|
20
25
|
|
|
21
26
|
// Re-export (explicitly to avoid name conflicts)
|
|
22
27
|
export {
|
|
@@ -25,6 +30,7 @@ export {
|
|
|
25
30
|
addPythonModule,
|
|
26
31
|
getPythonProjectFiles,
|
|
27
32
|
type GenerationResult,
|
|
33
|
+
type PythonGeneratorOptions,
|
|
28
34
|
} from './python.js';
|
|
29
35
|
export {
|
|
30
36
|
generateTypeScriptProject,
|
|
@@ -33,7 +39,13 @@ export {
|
|
|
33
39
|
getTypeScriptProjectFiles,
|
|
34
40
|
addDependency,
|
|
35
41
|
updateScripts,
|
|
42
|
+
type TypeScriptGeneratorOptions,
|
|
36
43
|
} from './typescript.js';
|
|
44
|
+
export {
|
|
45
|
+
generateFullstackProject,
|
|
46
|
+
validateFullstackProject,
|
|
47
|
+
getFullstackProjectFiles,
|
|
48
|
+
} from './fullstack.js';
|
|
37
49
|
export * from './templates/index.js';
|
|
38
50
|
|
|
39
51
|
/**
|
|
@@ -52,6 +64,8 @@ export async function generateProject(
|
|
|
52
64
|
return generatePythonProject(spec, outputDir);
|
|
53
65
|
case 'typescript':
|
|
54
66
|
return generateTypeScriptProject(spec, outputDir);
|
|
67
|
+
case 'fullstack':
|
|
68
|
+
return generateFullstackProject(spec, outputDir);
|
|
55
69
|
default:
|
|
56
70
|
return {
|
|
57
71
|
success: false,
|
|
@@ -81,6 +95,8 @@ export async function validateProject(
|
|
|
81
95
|
return validatePythonProject(projectDir);
|
|
82
96
|
case 'typescript':
|
|
83
97
|
return validateTypeScriptProject(projectDir);
|
|
98
|
+
case 'fullstack':
|
|
99
|
+
return validateFullstackProject(projectDir);
|
|
84
100
|
default:
|
|
85
101
|
return {
|
|
86
102
|
valid: false,
|
|
@@ -125,6 +141,8 @@ export function getProjectFiles(projectName: string, language: OutputLanguage):
|
|
|
125
141
|
return getPythonProjectFiles(projectName);
|
|
126
142
|
case 'typescript':
|
|
127
143
|
return getTypeScriptProjectFiles(projectName);
|
|
144
|
+
case 'fullstack':
|
|
145
|
+
return getFullstackProjectFiles(projectName);
|
|
128
146
|
default:
|
|
129
147
|
return [];
|
|
130
148
|
}
|
|
@@ -142,6 +160,8 @@ export function getTestCommand(language: OutputLanguage): string {
|
|
|
142
160
|
return 'python -m pytest tests/ -v';
|
|
143
161
|
case 'typescript':
|
|
144
162
|
return 'npm test';
|
|
163
|
+
case 'fullstack':
|
|
164
|
+
return 'cd apps/backend && pytest && cd ../frontend && npm test';
|
|
145
165
|
default:
|
|
146
166
|
return 'echo "No test command configured"';
|
|
147
167
|
}
|
|
@@ -159,6 +179,8 @@ export function getBuildCommand(language: OutputLanguage): string {
|
|
|
159
179
|
return 'python -m pip install -e .';
|
|
160
180
|
case 'typescript':
|
|
161
181
|
return 'npm run build';
|
|
182
|
+
case 'fullstack':
|
|
183
|
+
return 'cd apps/backend && pip install -e . && cd ../frontend && npm run build';
|
|
162
184
|
default:
|
|
163
185
|
return 'echo "No build command configured"';
|
|
164
186
|
}
|
|
@@ -176,6 +198,8 @@ export function getLintCommand(language: OutputLanguage): string {
|
|
|
176
198
|
return 'ruff check src/ tests/';
|
|
177
199
|
case 'typescript':
|
|
178
200
|
return 'npm run lint';
|
|
201
|
+
case 'fullstack':
|
|
202
|
+
return 'cd apps/backend && ruff check . && cd ../frontend && npm run lint';
|
|
179
203
|
default:
|
|
180
204
|
return 'echo "No lint command configured"';
|
|
181
205
|
}
|