popeye-cli 1.0.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 +25 -0
- package/.prettierrc +8 -0
- package/README.md +320 -0
- package/dist/adapters/claude.d.ts +82 -0
- package/dist/adapters/claude.d.ts.map +1 -0
- package/dist/adapters/claude.js +230 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/openai.d.ts +48 -0
- package/dist/adapters/openai.d.ts.map +1 -0
- package/dist/adapters/openai.js +257 -0
- package/dist/adapters/openai.js.map +1 -0
- package/dist/auth/claude.d.ts +44 -0
- package/dist/auth/claude.d.ts.map +1 -0
- package/dist/auth/claude.js +139 -0
- package/dist/auth/claude.js.map +1 -0
- package/dist/auth/index.d.ts +61 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +141 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/keychain.d.ts +66 -0
- package/dist/auth/keychain.d.ts.map +1 -0
- package/dist/auth/keychain.js +125 -0
- package/dist/auth/keychain.js.map +1 -0
- package/dist/auth/openai-entry.d.ts +9 -0
- package/dist/auth/openai-entry.d.ts.map +1 -0
- package/dist/auth/openai-entry.js +410 -0
- package/dist/auth/openai-entry.js.map +1 -0
- package/dist/auth/openai.d.ts +71 -0
- package/dist/auth/openai.d.ts.map +1 -0
- package/dist/auth/openai.js +212 -0
- package/dist/auth/openai.js.map +1 -0
- package/dist/auth/server.d.ts +32 -0
- package/dist/auth/server.d.ts.map +1 -0
- package/dist/auth/server.js +213 -0
- package/dist/auth/server.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +10 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +162 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/config.d.ts +10 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +215 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/create.d.ts +10 -0
- package/dist/cli/commands/create.d.ts.map +1 -0
- package/dist/cli/commands/create.js +240 -0
- package/dist/cli/commands/create.js.map +1 -0
- package/dist/cli/commands/index.d.ts +10 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/index.js +10 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/resume.d.ts +18 -0
- package/dist/cli/commands/resume.d.ts.map +1 -0
- package/dist/cli/commands/resume.js +241 -0
- package/dist/cli/commands/resume.js.map +1 -0
- package/dist/cli/commands/status.d.ts +18 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +154 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/index.d.ts +17 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +71 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/interactive.d.ts +9 -0
- package/dist/cli/interactive.d.ts.map +1 -0
- package/dist/cli/interactive.js +330 -0
- package/dist/cli/interactive.js.map +1 -0
- package/dist/cli/output.d.ts +182 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +355 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/config/defaults.d.ts +57 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +103 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +138 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +244 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +220 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +141 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/generators/index.d.ts +101 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +200 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/python.d.ts +48 -0
- package/dist/generators/python.d.ts.map +1 -0
- package/dist/generators/python.js +262 -0
- package/dist/generators/python.js.map +1 -0
- package/dist/generators/templates/index.d.ts +6 -0
- package/dist/generators/templates/index.d.ts.map +1 -0
- package/dist/generators/templates/index.js +6 -0
- package/dist/generators/templates/index.js.map +1 -0
- package/dist/generators/templates/python.d.ts +53 -0
- package/dist/generators/templates/python.d.ts.map +1 -0
- package/dist/generators/templates/python.js +454 -0
- package/dist/generators/templates/python.js.map +1 -0
- package/dist/generators/templates/typescript.d.ts +53 -0
- package/dist/generators/templates/typescript.d.ts.map +1 -0
- package/dist/generators/templates/typescript.js +394 -0
- package/dist/generators/templates/typescript.js.map +1 -0
- package/dist/generators/typescript.d.ts +64 -0
- package/dist/generators/typescript.d.ts.map +1 -0
- package/dist/generators/typescript.js +271 -0
- package/dist/generators/typescript.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/state/index.d.ts +168 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +338 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/persistence.d.ts +91 -0
- package/dist/state/persistence.d.ts.map +1 -0
- package/dist/state/persistence.js +201 -0
- package/dist/state/persistence.js.map +1 -0
- package/dist/types/cli.d.ts +132 -0
- package/dist/types/cli.d.ts.map +1 -0
- package/dist/types/cli.js +17 -0
- package/dist/types/cli.js.map +1 -0
- package/dist/types/consensus.d.ts +111 -0
- package/dist/types/consensus.d.ts.map +1 -0
- package/dist/types/consensus.js +29 -0
- package/dist/types/consensus.js.map +1 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/project.d.ts +73 -0
- package/dist/types/project.d.ts.map +1 -0
- package/dist/types/project.js +55 -0
- package/dist/types/project.js.map +1 -0
- package/dist/types/workflow.d.ts +236 -0
- package/dist/types/workflow.d.ts.map +1 -0
- package/dist/types/workflow.js +74 -0
- package/dist/types/workflow.js.map +1 -0
- package/dist/workflow/consensus.d.ts +89 -0
- package/dist/workflow/consensus.d.ts.map +1 -0
- package/dist/workflow/consensus.js +220 -0
- package/dist/workflow/consensus.js.map +1 -0
- package/dist/workflow/execution-mode.d.ts +82 -0
- package/dist/workflow/execution-mode.d.ts.map +1 -0
- package/dist/workflow/execution-mode.js +346 -0
- package/dist/workflow/execution-mode.js.map +1 -0
- package/dist/workflow/index.d.ts +110 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +283 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/plan-mode.d.ts +83 -0
- package/dist/workflow/plan-mode.d.ts.map +1 -0
- package/dist/workflow/plan-mode.js +241 -0
- package/dist/workflow/plan-mode.js.map +1 -0
- package/dist/workflow/test-runner.d.ts +87 -0
- package/dist/workflow/test-runner.d.ts.map +1 -0
- package/dist/workflow/test-runner.js +273 -0
- package/dist/workflow/test-runner.js.map +1 -0
- package/eslint.config.js +25 -0
- package/package.json +66 -0
- package/src/adapters/claude.ts +298 -0
- package/src/adapters/openai.ts +300 -0
- package/src/auth/claude.ts +166 -0
- package/src/auth/index.ts +171 -0
- package/src/auth/keychain.ts +138 -0
- package/src/auth/openai-entry.ts +410 -0
- package/src/auth/openai.ts +260 -0
- package/src/auth/server.ts +252 -0
- package/src/cli/commands/auth.ts +194 -0
- package/src/cli/commands/config.ts +241 -0
- package/src/cli/commands/create.ts +308 -0
- package/src/cli/commands/index.ts +10 -0
- package/src/cli/commands/resume.ts +304 -0
- package/src/cli/commands/status.ts +189 -0
- package/src/cli/index.ts +90 -0
- package/src/cli/interactive.ts +418 -0
- package/src/cli/output.ts +410 -0
- package/src/config/defaults.ts +114 -0
- package/src/config/index.ts +315 -0
- package/src/config/schema.ts +164 -0
- package/src/generators/index.ts +251 -0
- package/src/generators/python.ts +318 -0
- package/src/generators/templates/index.ts +6 -0
- package/src/generators/templates/python.ts +465 -0
- package/src/generators/templates/typescript.ts +417 -0
- package/src/generators/typescript.ts +340 -0
- package/src/index.ts +13 -0
- package/src/state/index.ts +454 -0
- package/src/state/persistence.ts +230 -0
- package/src/types/cli.ts +146 -0
- package/src/types/consensus.ts +116 -0
- package/src/types/index.ts +64 -0
- package/src/types/project.ts +85 -0
- package/src/types/workflow.ts +149 -0
- package/src/workflow/consensus.ts +299 -0
- package/src/workflow/execution-mode.ts +517 -0
- package/src/workflow/index.ts +396 -0
- package/src/workflow/plan-mode.ts +356 -0
- package/src/workflow/test-runner.ts +345 -0
- package/tests/adapters/openai.test.ts +145 -0
- package/tests/config/config.test.ts +208 -0
- package/tests/generators/generators.test.ts +185 -0
- package/tests/types/consensus.test.ts +152 -0
- package/tests/types/project.test.ts +134 -0
- package/tests/workflow/consensus.test.ts +221 -0
- package/tests/workflow/test-runner.test.ts +214 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project generators module
|
|
3
|
+
* Provides unified API for generating Python and TypeScript projects
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ProjectSpec, OutputLanguage } from '../types/project.js';
|
|
7
|
+
import {
|
|
8
|
+
generatePythonProject,
|
|
9
|
+
validatePythonProject,
|
|
10
|
+
addPythonModule,
|
|
11
|
+
getPythonProjectFiles,
|
|
12
|
+
type GenerationResult,
|
|
13
|
+
} from './python.js';
|
|
14
|
+
import {
|
|
15
|
+
generateTypeScriptProject,
|
|
16
|
+
validateTypeScriptProject,
|
|
17
|
+
addTypeScriptModule,
|
|
18
|
+
getTypeScriptProjectFiles,
|
|
19
|
+
} from './typescript.js';
|
|
20
|
+
|
|
21
|
+
// Re-export (explicitly to avoid name conflicts)
|
|
22
|
+
export {
|
|
23
|
+
generatePythonProject,
|
|
24
|
+
validatePythonProject,
|
|
25
|
+
addPythonModule,
|
|
26
|
+
getPythonProjectFiles,
|
|
27
|
+
type GenerationResult,
|
|
28
|
+
} from './python.js';
|
|
29
|
+
export {
|
|
30
|
+
generateTypeScriptProject,
|
|
31
|
+
validateTypeScriptProject,
|
|
32
|
+
addTypeScriptModule,
|
|
33
|
+
getTypeScriptProjectFiles,
|
|
34
|
+
addDependency,
|
|
35
|
+
updateScripts,
|
|
36
|
+
} from './typescript.js';
|
|
37
|
+
export * from './templates/index.js';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Generate a project based on the specification
|
|
41
|
+
*
|
|
42
|
+
* @param spec - Project specification
|
|
43
|
+
* @param outputDir - Output directory
|
|
44
|
+
* @returns Generation result
|
|
45
|
+
*/
|
|
46
|
+
export async function generateProject(
|
|
47
|
+
spec: ProjectSpec,
|
|
48
|
+
outputDir: string
|
|
49
|
+
): Promise<GenerationResult> {
|
|
50
|
+
switch (spec.language) {
|
|
51
|
+
case 'python':
|
|
52
|
+
return generatePythonProject(spec, outputDir);
|
|
53
|
+
case 'typescript':
|
|
54
|
+
return generateTypeScriptProject(spec, outputDir);
|
|
55
|
+
default:
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
projectDir: outputDir,
|
|
59
|
+
filesCreated: [],
|
|
60
|
+
error: `Unsupported language: ${spec.language}`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Validate a project structure
|
|
67
|
+
*
|
|
68
|
+
* @param projectDir - Project directory
|
|
69
|
+
* @param language - Project language
|
|
70
|
+
* @returns Validation result
|
|
71
|
+
*/
|
|
72
|
+
export async function validateProject(
|
|
73
|
+
projectDir: string,
|
|
74
|
+
language: OutputLanguage
|
|
75
|
+
): Promise<{
|
|
76
|
+
valid: boolean;
|
|
77
|
+
missingFiles: string[];
|
|
78
|
+
}> {
|
|
79
|
+
switch (language) {
|
|
80
|
+
case 'python':
|
|
81
|
+
return validatePythonProject(projectDir);
|
|
82
|
+
case 'typescript':
|
|
83
|
+
return validateTypeScriptProject(projectDir);
|
|
84
|
+
default:
|
|
85
|
+
return {
|
|
86
|
+
valid: false,
|
|
87
|
+
missingFiles: ['Unknown language'],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Add a module to an existing project
|
|
94
|
+
*
|
|
95
|
+
* @param projectDir - Project directory
|
|
96
|
+
* @param moduleName - Module name
|
|
97
|
+
* @param language - Project language
|
|
98
|
+
* @returns Files created
|
|
99
|
+
*/
|
|
100
|
+
export async function addModule(
|
|
101
|
+
projectDir: string,
|
|
102
|
+
moduleName: string,
|
|
103
|
+
language: OutputLanguage
|
|
104
|
+
): Promise<string[]> {
|
|
105
|
+
switch (language) {
|
|
106
|
+
case 'python':
|
|
107
|
+
return addPythonModule(projectDir, moduleName);
|
|
108
|
+
case 'typescript':
|
|
109
|
+
return addTypeScriptModule(projectDir, moduleName);
|
|
110
|
+
default:
|
|
111
|
+
throw new Error(`Unsupported language: ${language}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get the list of files that would be generated for a project
|
|
117
|
+
*
|
|
118
|
+
* @param projectName - Project name
|
|
119
|
+
* @param language - Project language
|
|
120
|
+
* @returns List of relative file paths
|
|
121
|
+
*/
|
|
122
|
+
export function getProjectFiles(projectName: string, language: OutputLanguage): string[] {
|
|
123
|
+
switch (language) {
|
|
124
|
+
case 'python':
|
|
125
|
+
return getPythonProjectFiles(projectName);
|
|
126
|
+
case 'typescript':
|
|
127
|
+
return getTypeScriptProjectFiles(projectName);
|
|
128
|
+
default:
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get the default test command for a language
|
|
135
|
+
*
|
|
136
|
+
* @param language - Project language
|
|
137
|
+
* @returns Test command
|
|
138
|
+
*/
|
|
139
|
+
export function getTestCommand(language: OutputLanguage): string {
|
|
140
|
+
switch (language) {
|
|
141
|
+
case 'python':
|
|
142
|
+
return 'python -m pytest tests/ -v';
|
|
143
|
+
case 'typescript':
|
|
144
|
+
return 'npm test';
|
|
145
|
+
default:
|
|
146
|
+
return 'echo "No test command configured"';
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get the default build command for a language
|
|
152
|
+
*
|
|
153
|
+
* @param language - Project language
|
|
154
|
+
* @returns Build command
|
|
155
|
+
*/
|
|
156
|
+
export function getBuildCommand(language: OutputLanguage): string {
|
|
157
|
+
switch (language) {
|
|
158
|
+
case 'python':
|
|
159
|
+
return 'python -m pip install -e .';
|
|
160
|
+
case 'typescript':
|
|
161
|
+
return 'npm run build';
|
|
162
|
+
default:
|
|
163
|
+
return 'echo "No build command configured"';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get the default lint command for a language
|
|
169
|
+
*
|
|
170
|
+
* @param language - Project language
|
|
171
|
+
* @returns Lint command
|
|
172
|
+
*/
|
|
173
|
+
export function getLintCommand(language: OutputLanguage): string {
|
|
174
|
+
switch (language) {
|
|
175
|
+
case 'python':
|
|
176
|
+
return 'ruff check src/ tests/';
|
|
177
|
+
case 'typescript':
|
|
178
|
+
return 'npm run lint';
|
|
179
|
+
default:
|
|
180
|
+
return 'echo "No lint command configured"';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get the file extension for a language
|
|
186
|
+
*
|
|
187
|
+
* @param language - Project language
|
|
188
|
+
* @returns File extension (without dot)
|
|
189
|
+
*/
|
|
190
|
+
export function getFileExtension(language: OutputLanguage): string {
|
|
191
|
+
switch (language) {
|
|
192
|
+
case 'python':
|
|
193
|
+
return 'py';
|
|
194
|
+
case 'typescript':
|
|
195
|
+
return 'ts';
|
|
196
|
+
default:
|
|
197
|
+
return 'txt';
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get the source directory name for a language
|
|
203
|
+
*
|
|
204
|
+
* @param language - Project language
|
|
205
|
+
* @returns Source directory name
|
|
206
|
+
*/
|
|
207
|
+
export function getSourceDir(_language: OutputLanguage): string {
|
|
208
|
+
return 'src';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get the test directory name for a language
|
|
213
|
+
*
|
|
214
|
+
* @param language - Project language
|
|
215
|
+
* @returns Test directory name
|
|
216
|
+
*/
|
|
217
|
+
export function getTestDir(_language: OutputLanguage): string {
|
|
218
|
+
return 'tests';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Check if a project directory already exists and has content
|
|
223
|
+
*
|
|
224
|
+
* @param projectDir - Project directory
|
|
225
|
+
* @returns True if directory exists and has content
|
|
226
|
+
*/
|
|
227
|
+
export async function projectDirExists(projectDir: string): Promise<boolean> {
|
|
228
|
+
const { promises: fs } = await import('node:fs');
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
const files = await fs.readdir(projectDir);
|
|
232
|
+
return files.length > 0;
|
|
233
|
+
} catch {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Clean up a failed project generation
|
|
240
|
+
*
|
|
241
|
+
* @param projectDir - Project directory
|
|
242
|
+
*/
|
|
243
|
+
export async function cleanupProject(projectDir: string): Promise<void> {
|
|
244
|
+
const { promises: fs } = await import('node:fs');
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
await fs.rm(projectDir, { recursive: true, force: true });
|
|
248
|
+
} catch {
|
|
249
|
+
// Ignore errors
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python project generator
|
|
3
|
+
* Creates complete Python project scaffolding
|
|
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 {
|
|
10
|
+
generatePyprojectToml,
|
|
11
|
+
generateRequirementsTxt,
|
|
12
|
+
generateMainInit,
|
|
13
|
+
generateMainPy,
|
|
14
|
+
generateConftest,
|
|
15
|
+
generateTestMain,
|
|
16
|
+
generateDockerfile,
|
|
17
|
+
generateDockerCompose,
|
|
18
|
+
generateGitignore,
|
|
19
|
+
generateEnvExample,
|
|
20
|
+
generateReadme,
|
|
21
|
+
generateMakefile,
|
|
22
|
+
} from './templates/python.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Project generation result
|
|
26
|
+
*/
|
|
27
|
+
export interface GenerationResult {
|
|
28
|
+
success: boolean;
|
|
29
|
+
projectDir: string;
|
|
30
|
+
filesCreated: string[];
|
|
31
|
+
error?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create a directory if it doesn't exist
|
|
36
|
+
*/
|
|
37
|
+
async function ensureDir(dirPath: string): Promise<void> {
|
|
38
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Write a file with content
|
|
43
|
+
*/
|
|
44
|
+
async function writeFile(filePath: string, content: string): Promise<void> {
|
|
45
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Convert project name to Python package name
|
|
50
|
+
*/
|
|
51
|
+
function toPythonPackageName(name: string): string {
|
|
52
|
+
return name.toLowerCase().replace(/-/g, '_').replace(/[^a-z0-9_]/g, '');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generate a complete Python project
|
|
57
|
+
*
|
|
58
|
+
* @param spec - Project specification
|
|
59
|
+
* @param outputDir - Output directory
|
|
60
|
+
* @returns Generation result
|
|
61
|
+
*/
|
|
62
|
+
export async function generatePythonProject(
|
|
63
|
+
spec: ProjectSpec,
|
|
64
|
+
outputDir: string
|
|
65
|
+
): Promise<GenerationResult> {
|
|
66
|
+
const projectName = spec.name || 'my-project';
|
|
67
|
+
const packageName = toPythonPackageName(projectName);
|
|
68
|
+
const projectDir = path.join(outputDir, projectName);
|
|
69
|
+
const filesCreated: string[] = [];
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
// Create project directory structure
|
|
73
|
+
await ensureDir(projectDir);
|
|
74
|
+
await ensureDir(path.join(projectDir, 'src', packageName));
|
|
75
|
+
await ensureDir(path.join(projectDir, 'tests'));
|
|
76
|
+
await ensureDir(path.join(projectDir, 'data'));
|
|
77
|
+
|
|
78
|
+
// Generate and write files
|
|
79
|
+
const files: Array<{ path: string; content: string }> = [
|
|
80
|
+
// Root files
|
|
81
|
+
{
|
|
82
|
+
path: path.join(projectDir, 'pyproject.toml'),
|
|
83
|
+
content: generatePyprojectToml(projectName),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
path: path.join(projectDir, 'requirements.txt'),
|
|
87
|
+
content: generateRequirementsTxt(),
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
path: path.join(projectDir, '.gitignore'),
|
|
91
|
+
content: generateGitignore(),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
path: path.join(projectDir, '.env.example'),
|
|
95
|
+
content: generateEnvExample(),
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
path: path.join(projectDir, 'README.md'),
|
|
99
|
+
content: generateReadme(projectName, spec.idea),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
path: path.join(projectDir, 'Makefile'),
|
|
103
|
+
content: generateMakefile(projectName),
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
path: path.join(projectDir, 'Dockerfile'),
|
|
107
|
+
content: generateDockerfile(projectName),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
path: path.join(projectDir, 'docker-compose.yml'),
|
|
111
|
+
content: generateDockerCompose(projectName),
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// Source files
|
|
115
|
+
{
|
|
116
|
+
path: path.join(projectDir, 'src', packageName, '__init__.py'),
|
|
117
|
+
content: generateMainInit(projectName),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
path: path.join(projectDir, 'src', packageName, 'main.py'),
|
|
121
|
+
content: generateMainPy(projectName),
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
path: path.join(projectDir, 'src', '__init__.py'),
|
|
125
|
+
content: '# Source root\n',
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
// Test files
|
|
129
|
+
{
|
|
130
|
+
path: path.join(projectDir, 'tests', '__init__.py'),
|
|
131
|
+
content: '# Tests package\n',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
path: path.join(projectDir, 'tests', 'conftest.py'),
|
|
135
|
+
content: generateConftest(),
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
path: path.join(projectDir, 'tests', 'test_main.py'),
|
|
139
|
+
content: generateTestMain(projectName),
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
// Data placeholder
|
|
143
|
+
{
|
|
144
|
+
path: path.join(projectDir, 'data', '.gitkeep'),
|
|
145
|
+
content: '',
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
// Write all files
|
|
150
|
+
for (const file of files) {
|
|
151
|
+
await writeFile(file.path, file.content);
|
|
152
|
+
filesCreated.push(file.path);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
success: true,
|
|
157
|
+
projectDir,
|
|
158
|
+
filesCreated,
|
|
159
|
+
};
|
|
160
|
+
} catch (error) {
|
|
161
|
+
return {
|
|
162
|
+
success: false,
|
|
163
|
+
projectDir,
|
|
164
|
+
filesCreated,
|
|
165
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get the list of files that would be generated
|
|
172
|
+
*
|
|
173
|
+
* @param projectName - Project name
|
|
174
|
+
* @returns List of relative file paths
|
|
175
|
+
*/
|
|
176
|
+
export function getPythonProjectFiles(projectName: string): string[] {
|
|
177
|
+
const packageName = toPythonPackageName(projectName);
|
|
178
|
+
|
|
179
|
+
return [
|
|
180
|
+
'pyproject.toml',
|
|
181
|
+
'requirements.txt',
|
|
182
|
+
'.gitignore',
|
|
183
|
+
'.env.example',
|
|
184
|
+
'README.md',
|
|
185
|
+
'Makefile',
|
|
186
|
+
'Dockerfile',
|
|
187
|
+
'docker-compose.yml',
|
|
188
|
+
`src/${packageName}/__init__.py`,
|
|
189
|
+
`src/${packageName}/main.py`,
|
|
190
|
+
'src/__init__.py',
|
|
191
|
+
'tests/__init__.py',
|
|
192
|
+
'tests/conftest.py',
|
|
193
|
+
'tests/test_main.py',
|
|
194
|
+
'data/.gitkeep',
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Validate a Python project structure
|
|
200
|
+
*
|
|
201
|
+
* @param projectDir - Project directory
|
|
202
|
+
* @returns Validation result
|
|
203
|
+
*/
|
|
204
|
+
export async function validatePythonProject(projectDir: string): Promise<{
|
|
205
|
+
valid: boolean;
|
|
206
|
+
missingFiles: string[];
|
|
207
|
+
}> {
|
|
208
|
+
const missingFiles: string[] = [];
|
|
209
|
+
|
|
210
|
+
const requiredFiles = [
|
|
211
|
+
'pyproject.toml',
|
|
212
|
+
'requirements.txt',
|
|
213
|
+
'README.md',
|
|
214
|
+
'src',
|
|
215
|
+
'tests',
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
for (const file of requiredFiles) {
|
|
219
|
+
const filePath = path.join(projectDir, file);
|
|
220
|
+
try {
|
|
221
|
+
await fs.access(filePath);
|
|
222
|
+
} catch {
|
|
223
|
+
missingFiles.push(file);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
valid: missingFiles.length === 0,
|
|
229
|
+
missingFiles,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Add a new Python module to an existing project
|
|
235
|
+
*
|
|
236
|
+
* @param projectDir - Project directory
|
|
237
|
+
* @param moduleName - Module name
|
|
238
|
+
* @returns Files created
|
|
239
|
+
*/
|
|
240
|
+
export async function addPythonModule(
|
|
241
|
+
projectDir: string,
|
|
242
|
+
moduleName: string
|
|
243
|
+
): Promise<string[]> {
|
|
244
|
+
const packageName = toPythonPackageName(moduleName);
|
|
245
|
+
const filesCreated: string[] = [];
|
|
246
|
+
|
|
247
|
+
// Find the src directory
|
|
248
|
+
const srcDir = path.join(projectDir, 'src');
|
|
249
|
+
const dirs = await fs.readdir(srcDir);
|
|
250
|
+
const packageDir = dirs.find((d) => !d.startsWith('.') && d !== '__init__.py');
|
|
251
|
+
|
|
252
|
+
if (!packageDir) {
|
|
253
|
+
throw new Error('Could not find package directory in src/');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const moduleDir = path.join(srcDir, packageDir, packageName);
|
|
257
|
+
await ensureDir(moduleDir);
|
|
258
|
+
|
|
259
|
+
// Create module files
|
|
260
|
+
const initContent = `"""
|
|
261
|
+
${moduleName} module.
|
|
262
|
+
"""
|
|
263
|
+
`;
|
|
264
|
+
await writeFile(path.join(moduleDir, '__init__.py'), initContent);
|
|
265
|
+
filesCreated.push(path.join(moduleDir, '__init__.py'));
|
|
266
|
+
|
|
267
|
+
const moduleContent = `"""
|
|
268
|
+
${moduleName} implementation.
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
import logging
|
|
272
|
+
|
|
273
|
+
logger = logging.getLogger(__name__)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class ${moduleName.charAt(0).toUpperCase() + moduleName.slice(1).replace(/-/g, '')}:
|
|
277
|
+
"""
|
|
278
|
+
${moduleName} class.
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
def __init__(self) -> None:
|
|
282
|
+
"""Initialize ${moduleName}."""
|
|
283
|
+
logger.info(f"Initializing {self.__class__.__name__}")
|
|
284
|
+
|
|
285
|
+
def run(self) -> None:
|
|
286
|
+
"""
|
|
287
|
+
Run the ${moduleName} logic.
|
|
288
|
+
|
|
289
|
+
This method should be implemented with the actual functionality.
|
|
290
|
+
"""
|
|
291
|
+
raise NotImplementedError("Implement this method")
|
|
292
|
+
`;
|
|
293
|
+
await writeFile(path.join(moduleDir, 'module.py'), moduleContent);
|
|
294
|
+
filesCreated.push(path.join(moduleDir, 'module.py'));
|
|
295
|
+
|
|
296
|
+
// Create test file
|
|
297
|
+
const testDir = path.join(projectDir, 'tests');
|
|
298
|
+
const testContent = `"""
|
|
299
|
+
Tests for ${moduleName} module.
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
import pytest
|
|
303
|
+
|
|
304
|
+
# from src.${packageDir}.${packageName}.module import ${moduleName.charAt(0).toUpperCase() + moduleName.slice(1).replace(/-/g, '')}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class Test${moduleName.charAt(0).toUpperCase() + moduleName.slice(1).replace(/-/g, '')}:
|
|
308
|
+
"""Test cases for ${moduleName}."""
|
|
309
|
+
|
|
310
|
+
def test_placeholder(self) -> None:
|
|
311
|
+
"""Placeholder test."""
|
|
312
|
+
assert True
|
|
313
|
+
`;
|
|
314
|
+
await writeFile(path.join(testDir, `test_${packageName}.py`), testContent);
|
|
315
|
+
filesCreated.push(path.join(testDir, `test_${packageName}.py`));
|
|
316
|
+
|
|
317
|
+
return filesCreated;
|
|
318
|
+
}
|