popeye-cli 1.2.1 → 1.3.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 +4 -1
- package/CONTRIBUTING.md +10 -0
- package/README.md +111 -2
- package/dist/adapters/claude.d.ts +3 -2
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +214 -0
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/grok.d.ts +2 -1
- package/dist/adapters/grok.d.ts.map +1 -1
- package/dist/adapters/grok.js.map +1 -1
- package/dist/adapters/index.d.ts +8 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +12 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/openai.d.ts +2 -2
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js.map +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +25 -5
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +47 -6
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/all.d.ts +40 -0
- package/dist/generators/all.d.ts.map +1 -0
- package/dist/generators/all.js +826 -0
- package/dist/generators/all.js.map +1 -0
- package/dist/generators/fullstack.d.ts +9 -0
- package/dist/generators/fullstack.d.ts.map +1 -1
- package/dist/generators/fullstack.js.map +1 -1
- package/dist/generators/index.d.ts +3 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +33 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/templates/index.d.ts +2 -0
- package/dist/generators/templates/index.d.ts.map +1 -1
- package/dist/generators/templates/index.js +2 -0
- package/dist/generators/templates/index.js.map +1 -1
- package/dist/generators/templates/website.d.ts +85 -0
- package/dist/generators/templates/website.d.ts.map +1 -0
- package/dist/generators/templates/website.js +877 -0
- package/dist/generators/templates/website.js.map +1 -0
- package/dist/generators/website.d.ts +56 -0
- package/dist/generators/website.d.ts.map +1 -0
- package/dist/generators/website.js +269 -0
- package/dist/generators/website.js.map +1 -0
- package/dist/types/consensus.d.ts +8 -3
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -2
- package/dist/types/index.js.map +1 -1
- package/dist/types/project.d.ts +115 -1
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/project.js +41 -1
- package/dist/types/project.js.map +1 -1
- package/dist/workflow/consensus.d.ts +2 -1
- package/dist/workflow/consensus.d.ts.map +1 -1
- package/dist/workflow/consensus.js.map +1 -1
- package/dist/workflow/index.d.ts +6 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +8 -0
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/plan-mode.d.ts +3 -3
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/plan-parser.d.ts +97 -0
- package/dist/workflow/plan-parser.d.ts.map +1 -0
- package/dist/workflow/plan-parser.js +235 -0
- package/dist/workflow/plan-parser.js.map +1 -0
- package/dist/workflow/plan-storage.d.ts +40 -12
- package/dist/workflow/plan-storage.d.ts.map +1 -1
- package/dist/workflow/plan-storage.js +47 -20
- package/dist/workflow/plan-storage.js.map +1 -1
- package/dist/workflow/seo-tests.d.ts +43 -0
- package/dist/workflow/seo-tests.d.ts.map +1 -0
- package/dist/workflow/seo-tests.js +192 -0
- package/dist/workflow/seo-tests.js.map +1 -0
- package/dist/workflow/separation-guard.d.ts +35 -0
- package/dist/workflow/separation-guard.d.ts.map +1 -0
- package/dist/workflow/separation-guard.js +154 -0
- package/dist/workflow/separation-guard.js.map +1 -0
- package/dist/workflow/test-runner.d.ts.map +1 -1
- package/dist/workflow/test-runner.js +128 -0
- package/dist/workflow/test-runner.js.map +1 -1
- package/dist/workflow/workspace-manager.d.ts +31 -20
- package/dist/workflow/workspace-manager.d.ts.map +1 -1
- package/dist/workflow/workspace-manager.js +38 -9
- package/dist/workflow/workspace-manager.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/claude.ts +221 -4
- package/src/adapters/grok.ts +2 -1
- package/src/adapters/index.ts +15 -0
- package/src/adapters/openai.ts +2 -2
- package/src/cli/commands/create.ts +25 -5
- package/src/cli/interactive.ts +47 -6
- package/src/generators/all.ts +897 -0
- package/src/generators/fullstack.ts +10 -0
- package/src/generators/index.ts +54 -0
- package/src/generators/templates/index.ts +2 -0
- package/src/generators/templates/website.ts +906 -0
- package/src/generators/website.ts +350 -0
- package/src/types/consensus.ts +9 -3
- package/src/types/index.ts +33 -0
- package/src/types/project.ts +139 -2
- package/src/workflow/consensus.ts +3 -2
- package/src/workflow/index.ts +8 -0
- package/src/workflow/plan-mode.ts +3 -3
- package/src/workflow/plan-parser.ts +317 -0
- package/src/workflow/plan-storage.ts +69 -30
- package/src/workflow/seo-tests.ts +246 -0
- package/src/workflow/separation-guard.ts +200 -0
- package/src/workflow/test-runner.ts +149 -0
- package/src/workflow/workspace-manager.ts +68 -31
|
@@ -0,0 +1,826 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All project generator (FE + BE + Website)
|
|
3
|
+
* Orchestrates Python, TypeScript, and Website generators for complete monorepo
|
|
4
|
+
*/
|
|
5
|
+
import { promises as fs } from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { generateFullstackProject } from './fullstack.js';
|
|
8
|
+
import { generateWebsiteProject } from './website.js';
|
|
9
|
+
/**
|
|
10
|
+
* Create a directory if it doesn't exist
|
|
11
|
+
*/
|
|
12
|
+
async function ensureDir(dirPath) {
|
|
13
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Write a file with content
|
|
17
|
+
*/
|
|
18
|
+
async function writeFile(filePath, content) {
|
|
19
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Convert project name to Python package name
|
|
23
|
+
*/
|
|
24
|
+
function toPythonPackageName(name) {
|
|
25
|
+
return name.toLowerCase().replace(/-/g, '_').replace(/[^a-z0-9_]/g, '');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Generate workspace.json for "all" projects
|
|
29
|
+
*/
|
|
30
|
+
function generateAllWorkspaceJson(projectName) {
|
|
31
|
+
const packageName = toPythonPackageName(projectName);
|
|
32
|
+
const config = {
|
|
33
|
+
version: '1.0',
|
|
34
|
+
apps: {
|
|
35
|
+
frontend: {
|
|
36
|
+
name: 'frontend',
|
|
37
|
+
path: 'apps/frontend',
|
|
38
|
+
language: 'typescript',
|
|
39
|
+
commands: {
|
|
40
|
+
test: 'npm test',
|
|
41
|
+
lint: 'npm run lint',
|
|
42
|
+
build: 'npm run build',
|
|
43
|
+
dev: 'npm run dev',
|
|
44
|
+
typecheck: 'npm run typecheck',
|
|
45
|
+
},
|
|
46
|
+
docker: {
|
|
47
|
+
dockerfile: 'apps/frontend/Dockerfile',
|
|
48
|
+
imageName: `${projectName}-frontend`,
|
|
49
|
+
context: 'apps/frontend',
|
|
50
|
+
},
|
|
51
|
+
dependsOn: ['packages/design-tokens', 'packages/ui'],
|
|
52
|
+
contextRoots: ['apps/frontend/src'],
|
|
53
|
+
uiSpec: '.popeye/ui-spec.json',
|
|
54
|
+
},
|
|
55
|
+
backend: {
|
|
56
|
+
name: 'backend',
|
|
57
|
+
path: 'apps/backend',
|
|
58
|
+
language: 'python',
|
|
59
|
+
commands: {
|
|
60
|
+
test: 'pytest -v',
|
|
61
|
+
lint: 'ruff check .',
|
|
62
|
+
build: 'pip install -e .',
|
|
63
|
+
dev: `uvicorn src.${packageName}.main:app --reload --port 8000`,
|
|
64
|
+
},
|
|
65
|
+
docker: {
|
|
66
|
+
dockerfile: 'apps/backend/Dockerfile',
|
|
67
|
+
imageName: `${projectName}-backend`,
|
|
68
|
+
context: 'apps/backend',
|
|
69
|
+
},
|
|
70
|
+
contextRoots: ['apps/backend/src'],
|
|
71
|
+
},
|
|
72
|
+
website: {
|
|
73
|
+
name: 'website',
|
|
74
|
+
path: 'apps/website',
|
|
75
|
+
language: 'typescript',
|
|
76
|
+
commands: {
|
|
77
|
+
test: 'npm test',
|
|
78
|
+
lint: 'npm run lint',
|
|
79
|
+
build: 'npm run build',
|
|
80
|
+
dev: 'npm run dev',
|
|
81
|
+
typecheck: 'npm run typecheck',
|
|
82
|
+
},
|
|
83
|
+
docker: {
|
|
84
|
+
dockerfile: 'apps/website/Dockerfile',
|
|
85
|
+
imageName: `${projectName}-website`,
|
|
86
|
+
context: 'apps/website',
|
|
87
|
+
},
|
|
88
|
+
dependsOn: ['packages/design-tokens'],
|
|
89
|
+
contextRoots: ['apps/website/src'],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
shared: {
|
|
93
|
+
contracts: 'packages/contracts',
|
|
94
|
+
ui: 'packages/ui',
|
|
95
|
+
designTokens: 'packages/design-tokens',
|
|
96
|
+
},
|
|
97
|
+
commands: {
|
|
98
|
+
testAll: 'npm run test --workspaces --if-present && cd apps/backend && pytest',
|
|
99
|
+
lintAll: 'npm run lint --workspaces --if-present && cd apps/backend && ruff check .',
|
|
100
|
+
buildAll: 'npm run build -w packages/design-tokens && npm run build -w packages/ui && npm run build --workspaces --if-present',
|
|
101
|
+
devAll: 'concurrently "npm run dev -w apps/frontend" "npm run dev -w apps/website" "cd apps/backend && make dev"',
|
|
102
|
+
},
|
|
103
|
+
docker: {
|
|
104
|
+
composePath: 'infra/docker/docker-compose.yml',
|
|
105
|
+
rootComposeSymlink: true,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
return JSON.stringify(config, null, 2);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Generate root package.json for npm workspaces
|
|
112
|
+
*/
|
|
113
|
+
function generateRootPackageJson(projectName) {
|
|
114
|
+
return JSON.stringify({
|
|
115
|
+
name: `@${projectName}/root`,
|
|
116
|
+
private: true,
|
|
117
|
+
workspaces: ['apps/*', 'packages/*'],
|
|
118
|
+
scripts: {
|
|
119
|
+
dev: 'concurrently "npm run dev -w apps/frontend" "npm run dev -w apps/website"',
|
|
120
|
+
'dev:all': 'concurrently "npm run dev -w apps/frontend" "npm run dev -w apps/website" "cd apps/backend && make dev"',
|
|
121
|
+
build: 'npm run build -w packages/design-tokens && npm run build -w packages/ui && npm run build --workspaces --if-present',
|
|
122
|
+
test: 'npm run test --workspaces --if-present',
|
|
123
|
+
'test:all': 'npm run test --workspaces --if-present && cd apps/backend && pytest',
|
|
124
|
+
lint: 'npm run lint --workspaces --if-present',
|
|
125
|
+
'lint:all': 'npm run lint --workspaces --if-present && cd apps/backend && ruff check .',
|
|
126
|
+
typecheck: 'npm run typecheck --workspaces --if-present',
|
|
127
|
+
},
|
|
128
|
+
devDependencies: {
|
|
129
|
+
concurrently: '^8.2.0',
|
|
130
|
+
},
|
|
131
|
+
engines: {
|
|
132
|
+
node: '>=18.0.0',
|
|
133
|
+
},
|
|
134
|
+
}, null, 2);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Generate docker-compose.yml for "all" projects
|
|
138
|
+
*/
|
|
139
|
+
function generateAllDockerCompose(projectName) {
|
|
140
|
+
return `version: '3.8'
|
|
141
|
+
|
|
142
|
+
services:
|
|
143
|
+
frontend:
|
|
144
|
+
build:
|
|
145
|
+
context: apps/frontend
|
|
146
|
+
dockerfile: Dockerfile
|
|
147
|
+
ports:
|
|
148
|
+
- "3000:80"
|
|
149
|
+
depends_on:
|
|
150
|
+
- backend
|
|
151
|
+
environment:
|
|
152
|
+
- VITE_API_URL=http://backend:8000
|
|
153
|
+
networks:
|
|
154
|
+
- ${projectName}-network
|
|
155
|
+
|
|
156
|
+
backend:
|
|
157
|
+
build:
|
|
158
|
+
context: apps/backend
|
|
159
|
+
dockerfile: Dockerfile
|
|
160
|
+
ports:
|
|
161
|
+
- "8000:8000"
|
|
162
|
+
environment:
|
|
163
|
+
- DEBUG=false
|
|
164
|
+
- FRONTEND_URL=http://frontend:80
|
|
165
|
+
- WEBSITE_URL=http://website:3000
|
|
166
|
+
volumes:
|
|
167
|
+
- backend-data:/app/data
|
|
168
|
+
networks:
|
|
169
|
+
- ${projectName}-network
|
|
170
|
+
|
|
171
|
+
website:
|
|
172
|
+
build:
|
|
173
|
+
context: apps/website
|
|
174
|
+
dockerfile: Dockerfile
|
|
175
|
+
ports:
|
|
176
|
+
- "3001:3000"
|
|
177
|
+
environment:
|
|
178
|
+
- NODE_ENV=production
|
|
179
|
+
- NEXT_PUBLIC_APP_URL=http://localhost:3000
|
|
180
|
+
networks:
|
|
181
|
+
- ${projectName}-network
|
|
182
|
+
|
|
183
|
+
networks:
|
|
184
|
+
${projectName}-network:
|
|
185
|
+
driver: bridge
|
|
186
|
+
|
|
187
|
+
volumes:
|
|
188
|
+
backend-data:
|
|
189
|
+
`;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Generate root README for "all" projects
|
|
193
|
+
*/
|
|
194
|
+
function generateAllRootReadme(projectName, idea) {
|
|
195
|
+
const title = projectName
|
|
196
|
+
.split('-')
|
|
197
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
198
|
+
.join(' ');
|
|
199
|
+
return `# ${title}
|
|
200
|
+
|
|
201
|
+
${idea}
|
|
202
|
+
|
|
203
|
+
## Architecture
|
|
204
|
+
|
|
205
|
+
This is a monorepo containing:
|
|
206
|
+
|
|
207
|
+
- **Frontend App** (\`apps/frontend\`): React + Vite + Tailwind CSS
|
|
208
|
+
- **Backend API** (\`apps/backend\`): FastAPI (Python)
|
|
209
|
+
- **Marketing Website** (\`apps/website\`): Next.js (SEO-optimized)
|
|
210
|
+
- **Shared Packages** (\`packages/\`): Design tokens, UI components, API contracts
|
|
211
|
+
|
|
212
|
+
## Getting Started
|
|
213
|
+
|
|
214
|
+
### Prerequisites
|
|
215
|
+
|
|
216
|
+
- Node.js 18+
|
|
217
|
+
- Python 3.10+
|
|
218
|
+
- npm
|
|
219
|
+
|
|
220
|
+
### Installation
|
|
221
|
+
|
|
222
|
+
\`\`\`bash
|
|
223
|
+
# Install all dependencies
|
|
224
|
+
npm install
|
|
225
|
+
|
|
226
|
+
# Install backend dependencies
|
|
227
|
+
cd apps/backend && pip install -e ".[dev]"
|
|
228
|
+
\`\`\`
|
|
229
|
+
|
|
230
|
+
### Development
|
|
231
|
+
|
|
232
|
+
\`\`\`bash
|
|
233
|
+
# Run all apps in development mode
|
|
234
|
+
npm run dev:all
|
|
235
|
+
|
|
236
|
+
# Or run individual apps:
|
|
237
|
+
npm run dev -w apps/frontend # Frontend on :5173
|
|
238
|
+
npm run dev -w apps/website # Website on :3001
|
|
239
|
+
cd apps/backend && make dev # Backend on :8000
|
|
240
|
+
\`\`\`
|
|
241
|
+
|
|
242
|
+
### Testing
|
|
243
|
+
|
|
244
|
+
\`\`\`bash
|
|
245
|
+
# Run all tests
|
|
246
|
+
npm run test:all
|
|
247
|
+
|
|
248
|
+
# Run frontend tests
|
|
249
|
+
npm run test -w apps/frontend
|
|
250
|
+
|
|
251
|
+
# Run backend tests
|
|
252
|
+
cd apps/backend && pytest
|
|
253
|
+
|
|
254
|
+
# Run website tests
|
|
255
|
+
npm run test -w apps/website
|
|
256
|
+
\`\`\`
|
|
257
|
+
|
|
258
|
+
### Building
|
|
259
|
+
|
|
260
|
+
\`\`\`bash
|
|
261
|
+
# Build all apps
|
|
262
|
+
npm run build
|
|
263
|
+
\`\`\`
|
|
264
|
+
|
|
265
|
+
### Docker
|
|
266
|
+
|
|
267
|
+
\`\`\`bash
|
|
268
|
+
# Build and run with Docker Compose
|
|
269
|
+
docker-compose up --build
|
|
270
|
+
|
|
271
|
+
# Services:
|
|
272
|
+
# - Frontend: http://localhost:3000
|
|
273
|
+
# - Backend: http://localhost:8000
|
|
274
|
+
# - Website: http://localhost:3001
|
|
275
|
+
\`\`\`
|
|
276
|
+
|
|
277
|
+
## Project Structure
|
|
278
|
+
|
|
279
|
+
\`\`\`
|
|
280
|
+
${projectName}/
|
|
281
|
+
apps/
|
|
282
|
+
frontend/ # React + Vite SPA
|
|
283
|
+
backend/ # FastAPI Python API
|
|
284
|
+
website/ # Next.js marketing site
|
|
285
|
+
packages/
|
|
286
|
+
design-tokens/ # Shared colors, typography
|
|
287
|
+
ui/ # Shared UI components
|
|
288
|
+
contracts/ # API contracts (OpenAPI)
|
|
289
|
+
infra/
|
|
290
|
+
docker/ # Docker configuration
|
|
291
|
+
docs/
|
|
292
|
+
PLAN.md # Development plan
|
|
293
|
+
WORKFLOW_LOG.md # Progress log
|
|
294
|
+
.popeye/
|
|
295
|
+
workspace.json # Workspace configuration
|
|
296
|
+
ui-spec.json # UI design spec (frontend)
|
|
297
|
+
website-spec.json # Website design spec
|
|
298
|
+
\`\`\`
|
|
299
|
+
|
|
300
|
+
## Links
|
|
301
|
+
|
|
302
|
+
- Frontend: http://localhost:5173 (dev) / http://localhost:3000 (prod)
|
|
303
|
+
- Backend API: http://localhost:8000
|
|
304
|
+
- Website: http://localhost:3001
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
Generated by [Popeye CLI](https://github.com/popeye-cli/popeye)
|
|
309
|
+
`;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Generate design tokens package
|
|
313
|
+
*/
|
|
314
|
+
function generateDesignTokensPackage(projectName) {
|
|
315
|
+
return {
|
|
316
|
+
files: [
|
|
317
|
+
{
|
|
318
|
+
path: 'package.json',
|
|
319
|
+
content: JSON.stringify({
|
|
320
|
+
name: `@${projectName}/design-tokens`,
|
|
321
|
+
version: '1.0.0',
|
|
322
|
+
type: 'module',
|
|
323
|
+
main: './dist/index.js',
|
|
324
|
+
types: './dist/index.d.ts',
|
|
325
|
+
exports: {
|
|
326
|
+
'.': './dist/index.js',
|
|
327
|
+
'./tailwind': './dist/tailwind-preset.js',
|
|
328
|
+
},
|
|
329
|
+
scripts: {
|
|
330
|
+
build: 'tsc',
|
|
331
|
+
dev: 'tsc --watch',
|
|
332
|
+
},
|
|
333
|
+
devDependencies: {
|
|
334
|
+
typescript: '^5.3.3',
|
|
335
|
+
},
|
|
336
|
+
}, null, 2),
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
path: 'tsconfig.json',
|
|
340
|
+
content: JSON.stringify({
|
|
341
|
+
compilerOptions: {
|
|
342
|
+
target: 'ES2020',
|
|
343
|
+
module: 'ESNext',
|
|
344
|
+
moduleResolution: 'bundler',
|
|
345
|
+
declaration: true,
|
|
346
|
+
outDir: './dist',
|
|
347
|
+
strict: true,
|
|
348
|
+
esModuleInterop: true,
|
|
349
|
+
skipLibCheck: true,
|
|
350
|
+
},
|
|
351
|
+
include: ['src'],
|
|
352
|
+
}, null, 2),
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
path: 'src/index.ts',
|
|
356
|
+
content: `/**
|
|
357
|
+
* Design tokens for ${projectName}
|
|
358
|
+
*/
|
|
359
|
+
|
|
360
|
+
export * from './colors.js';
|
|
361
|
+
export * from './typography.js';
|
|
362
|
+
`,
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
path: 'src/colors.ts',
|
|
366
|
+
content: `/**
|
|
367
|
+
* Color palette
|
|
368
|
+
*/
|
|
369
|
+
|
|
370
|
+
export const colors = {
|
|
371
|
+
primary: {
|
|
372
|
+
50: '#f0f9ff',
|
|
373
|
+
100: '#e0f2fe',
|
|
374
|
+
200: '#bae6fd',
|
|
375
|
+
300: '#7dd3fc',
|
|
376
|
+
400: '#38bdf8',
|
|
377
|
+
500: '#0ea5e9',
|
|
378
|
+
600: '#0284c7',
|
|
379
|
+
700: '#0369a1',
|
|
380
|
+
800: '#075985',
|
|
381
|
+
900: '#0c4a6e',
|
|
382
|
+
},
|
|
383
|
+
secondary: {
|
|
384
|
+
50: '#f8fafc',
|
|
385
|
+
100: '#f1f5f9',
|
|
386
|
+
200: '#e2e8f0',
|
|
387
|
+
300: '#cbd5e1',
|
|
388
|
+
400: '#94a3b8',
|
|
389
|
+
500: '#64748b',
|
|
390
|
+
600: '#475569',
|
|
391
|
+
700: '#334155',
|
|
392
|
+
800: '#1e293b',
|
|
393
|
+
900: '#0f172a',
|
|
394
|
+
},
|
|
395
|
+
} as const;
|
|
396
|
+
|
|
397
|
+
export type ColorScale = typeof colors.primary;
|
|
398
|
+
export type Colors = typeof colors;
|
|
399
|
+
`,
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
path: 'src/typography.ts',
|
|
403
|
+
content: `/**
|
|
404
|
+
* Typography settings
|
|
405
|
+
*/
|
|
406
|
+
|
|
407
|
+
export const typography = {
|
|
408
|
+
fontFamily: {
|
|
409
|
+
sans: ['Inter', 'system-ui', 'sans-serif'],
|
|
410
|
+
mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
|
|
411
|
+
},
|
|
412
|
+
fontSize: {
|
|
413
|
+
xs: ['0.75rem', { lineHeight: '1rem' }],
|
|
414
|
+
sm: ['0.875rem', { lineHeight: '1.25rem' }],
|
|
415
|
+
base: ['1rem', { lineHeight: '1.5rem' }],
|
|
416
|
+
lg: ['1.125rem', { lineHeight: '1.75rem' }],
|
|
417
|
+
xl: ['1.25rem', { lineHeight: '1.75rem' }],
|
|
418
|
+
'2xl': ['1.5rem', { lineHeight: '2rem' }],
|
|
419
|
+
'3xl': ['1.875rem', { lineHeight: '2.25rem' }],
|
|
420
|
+
'4xl': ['2.25rem', { lineHeight: '2.5rem' }],
|
|
421
|
+
'5xl': ['3rem', { lineHeight: '1' }],
|
|
422
|
+
'6xl': ['3.75rem', { lineHeight: '1' }],
|
|
423
|
+
},
|
|
424
|
+
} as const;
|
|
425
|
+
|
|
426
|
+
export type Typography = typeof typography;
|
|
427
|
+
`,
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
path: 'src/tailwind-preset.ts',
|
|
431
|
+
content: `/**
|
|
432
|
+
* Tailwind CSS preset with design tokens
|
|
433
|
+
*/
|
|
434
|
+
|
|
435
|
+
import { colors } from './colors.js';
|
|
436
|
+
import { typography } from './typography.js';
|
|
437
|
+
|
|
438
|
+
export const preset = {
|
|
439
|
+
theme: {
|
|
440
|
+
extend: {
|
|
441
|
+
colors,
|
|
442
|
+
fontFamily: typography.fontFamily,
|
|
443
|
+
fontSize: typography.fontSize,
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
export default preset;
|
|
449
|
+
`,
|
|
450
|
+
},
|
|
451
|
+
],
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Generate UI components package
|
|
456
|
+
*/
|
|
457
|
+
function generateUiPackage(projectName) {
|
|
458
|
+
return {
|
|
459
|
+
files: [
|
|
460
|
+
{
|
|
461
|
+
path: 'package.json',
|
|
462
|
+
content: JSON.stringify({
|
|
463
|
+
name: `@${projectName}/ui`,
|
|
464
|
+
version: '1.0.0',
|
|
465
|
+
type: 'module',
|
|
466
|
+
main: './dist/index.js',
|
|
467
|
+
types: './dist/index.d.ts',
|
|
468
|
+
exports: {
|
|
469
|
+
'.': './dist/index.js',
|
|
470
|
+
'./button': './dist/button.js',
|
|
471
|
+
'./card': './dist/card.js',
|
|
472
|
+
},
|
|
473
|
+
scripts: {
|
|
474
|
+
build: 'tsc',
|
|
475
|
+
dev: 'tsc --watch',
|
|
476
|
+
},
|
|
477
|
+
dependencies: {
|
|
478
|
+
clsx: '^2.1.0',
|
|
479
|
+
'tailwind-merge': '^2.2.0',
|
|
480
|
+
},
|
|
481
|
+
peerDependencies: {
|
|
482
|
+
react: '>=18.0.0',
|
|
483
|
+
'react-dom': '>=18.0.0',
|
|
484
|
+
},
|
|
485
|
+
devDependencies: {
|
|
486
|
+
'@types/react': '^18.2.0',
|
|
487
|
+
'@types/react-dom': '^18.2.0',
|
|
488
|
+
typescript: '^5.3.3',
|
|
489
|
+
},
|
|
490
|
+
}, null, 2),
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
path: 'tsconfig.json',
|
|
494
|
+
content: JSON.stringify({
|
|
495
|
+
compilerOptions: {
|
|
496
|
+
target: 'ES2020',
|
|
497
|
+
module: 'ESNext',
|
|
498
|
+
moduleResolution: 'bundler',
|
|
499
|
+
declaration: true,
|
|
500
|
+
outDir: './dist',
|
|
501
|
+
strict: true,
|
|
502
|
+
esModuleInterop: true,
|
|
503
|
+
skipLibCheck: true,
|
|
504
|
+
jsx: 'react-jsx',
|
|
505
|
+
},
|
|
506
|
+
include: ['src'],
|
|
507
|
+
}, null, 2),
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
path: 'src/index.ts',
|
|
511
|
+
content: `/**
|
|
512
|
+
* Shared UI components for ${projectName}
|
|
513
|
+
*/
|
|
514
|
+
|
|
515
|
+
export * from './button.js';
|
|
516
|
+
export * from './card.js';
|
|
517
|
+
export * from './utils.js';
|
|
518
|
+
`,
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
path: 'src/utils.ts',
|
|
522
|
+
content: `import { clsx, type ClassValue } from 'clsx';
|
|
523
|
+
import { twMerge } from 'tailwind-merge';
|
|
524
|
+
|
|
525
|
+
export function cn(...inputs: ClassValue[]) {
|
|
526
|
+
return twMerge(clsx(inputs));
|
|
527
|
+
}
|
|
528
|
+
`,
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
path: 'src/button.tsx',
|
|
532
|
+
content: `import * as React from 'react';
|
|
533
|
+
import { cn } from './utils.js';
|
|
534
|
+
|
|
535
|
+
export interface ButtonProps
|
|
536
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
537
|
+
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
|
|
538
|
+
size?: 'sm' | 'md' | 'lg';
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
542
|
+
({ className, variant = 'primary', size = 'md', ...props }, ref) => {
|
|
543
|
+
return (
|
|
544
|
+
<button
|
|
545
|
+
className={cn(
|
|
546
|
+
'inline-flex items-center justify-center rounded-md font-medium transition-colors',
|
|
547
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
|
|
548
|
+
'disabled:pointer-events-none disabled:opacity-50',
|
|
549
|
+
{
|
|
550
|
+
// Variants
|
|
551
|
+
'bg-primary-600 text-white hover:bg-primary-500': variant === 'primary',
|
|
552
|
+
'bg-secondary-100 text-secondary-900 hover:bg-secondary-200': variant === 'secondary',
|
|
553
|
+
'border border-secondary-300 bg-transparent hover:bg-secondary-50': variant === 'outline',
|
|
554
|
+
'bg-transparent hover:bg-secondary-100': variant === 'ghost',
|
|
555
|
+
// Sizes
|
|
556
|
+
'h-8 px-3 text-sm': size === 'sm',
|
|
557
|
+
'h-10 px-4 text-sm': size === 'md',
|
|
558
|
+
'h-12 px-6 text-base': size === 'lg',
|
|
559
|
+
},
|
|
560
|
+
className
|
|
561
|
+
)}
|
|
562
|
+
ref={ref}
|
|
563
|
+
{...props}
|
|
564
|
+
/>
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
Button.displayName = 'Button';
|
|
570
|
+
`,
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
path: 'src/card.tsx',
|
|
574
|
+
content: `import * as React from 'react';
|
|
575
|
+
import { cn } from './utils.js';
|
|
576
|
+
|
|
577
|
+
export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
578
|
+
|
|
579
|
+
export const Card = React.forwardRef<HTMLDivElement, CardProps>(
|
|
580
|
+
({ className, ...props }, ref) => (
|
|
581
|
+
<div
|
|
582
|
+
ref={ref}
|
|
583
|
+
className={cn(
|
|
584
|
+
'rounded-lg border border-secondary-200 bg-white shadow-sm',
|
|
585
|
+
className
|
|
586
|
+
)}
|
|
587
|
+
{...props}
|
|
588
|
+
/>
|
|
589
|
+
)
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
Card.displayName = 'Card';
|
|
593
|
+
|
|
594
|
+
export const CardHeader = React.forwardRef<
|
|
595
|
+
HTMLDivElement,
|
|
596
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
597
|
+
>(({ className, ...props }, ref) => (
|
|
598
|
+
<div
|
|
599
|
+
ref={ref}
|
|
600
|
+
className={cn('flex flex-col space-y-1.5 p-6', className)}
|
|
601
|
+
{...props}
|
|
602
|
+
/>
|
|
603
|
+
));
|
|
604
|
+
|
|
605
|
+
CardHeader.displayName = 'CardHeader';
|
|
606
|
+
|
|
607
|
+
export const CardTitle = React.forwardRef<
|
|
608
|
+
HTMLParagraphElement,
|
|
609
|
+
React.HTMLAttributes<HTMLHeadingElement>
|
|
610
|
+
>(({ className, ...props }, ref) => (
|
|
611
|
+
<h3
|
|
612
|
+
ref={ref}
|
|
613
|
+
className={cn('text-lg font-semibold leading-none tracking-tight', className)}
|
|
614
|
+
{...props}
|
|
615
|
+
/>
|
|
616
|
+
));
|
|
617
|
+
|
|
618
|
+
CardTitle.displayName = 'CardTitle';
|
|
619
|
+
|
|
620
|
+
export const CardContent = React.forwardRef<
|
|
621
|
+
HTMLDivElement,
|
|
622
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
623
|
+
>(({ className, ...props }, ref) => (
|
|
624
|
+
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
|
|
625
|
+
));
|
|
626
|
+
|
|
627
|
+
CardContent.displayName = 'CardContent';
|
|
628
|
+
`,
|
|
629
|
+
},
|
|
630
|
+
],
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Generate a complete "all" project (FE + BE + Website)
|
|
635
|
+
*
|
|
636
|
+
* @param spec - Project specification
|
|
637
|
+
* @param outputDir - Output directory
|
|
638
|
+
* @returns Generation result
|
|
639
|
+
*/
|
|
640
|
+
export async function generateAllProject(spec, outputDir) {
|
|
641
|
+
const projectName = spec.name || 'my-project';
|
|
642
|
+
const projectDir = path.join(outputDir, projectName);
|
|
643
|
+
const filesCreated = [];
|
|
644
|
+
try {
|
|
645
|
+
// Create root structure
|
|
646
|
+
await ensureDir(projectDir);
|
|
647
|
+
await ensureDir(path.join(projectDir, 'packages'));
|
|
648
|
+
await ensureDir(path.join(projectDir, 'packages', 'design-tokens', 'src'));
|
|
649
|
+
await ensureDir(path.join(projectDir, 'packages', 'ui', 'src'));
|
|
650
|
+
await ensureDir(path.join(projectDir, 'packages', 'contracts'));
|
|
651
|
+
await ensureDir(path.join(projectDir, '.popeye'));
|
|
652
|
+
// Generate fullstack first (creates apps/frontend and apps/backend)
|
|
653
|
+
const fullstackResult = await generateFullstackProject(spec, outputDir);
|
|
654
|
+
if (!fullstackResult.success) {
|
|
655
|
+
return fullstackResult;
|
|
656
|
+
}
|
|
657
|
+
filesCreated.push(...fullstackResult.filesCreated);
|
|
658
|
+
// Generate website app
|
|
659
|
+
const websiteResult = await generateWebsiteProject(spec, projectDir, {
|
|
660
|
+
baseDir: path.join(projectDir, 'apps', 'website'),
|
|
661
|
+
workspaceMode: true,
|
|
662
|
+
skipDocker: false, // Website needs its own Dockerfile
|
|
663
|
+
skipReadme: false,
|
|
664
|
+
});
|
|
665
|
+
if (!websiteResult.success) {
|
|
666
|
+
return {
|
|
667
|
+
...websiteResult,
|
|
668
|
+
filesCreated,
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
filesCreated.push(...websiteResult.filesCreated);
|
|
672
|
+
// Generate shared packages
|
|
673
|
+
const designTokens = generateDesignTokensPackage(projectName);
|
|
674
|
+
for (const file of designTokens.files) {
|
|
675
|
+
const filePath = path.join(projectDir, 'packages', 'design-tokens', file.path);
|
|
676
|
+
await ensureDir(path.dirname(filePath));
|
|
677
|
+
await writeFile(filePath, file.content);
|
|
678
|
+
filesCreated.push(filePath);
|
|
679
|
+
}
|
|
680
|
+
const uiPackage = generateUiPackage(projectName);
|
|
681
|
+
for (const file of uiPackage.files) {
|
|
682
|
+
const filePath = path.join(projectDir, 'packages', 'ui', file.path);
|
|
683
|
+
await ensureDir(path.dirname(filePath));
|
|
684
|
+
await writeFile(filePath, file.content);
|
|
685
|
+
filesCreated.push(filePath);
|
|
686
|
+
}
|
|
687
|
+
// Contracts placeholder
|
|
688
|
+
await writeFile(path.join(projectDir, 'packages', 'contracts', '.gitkeep'), '');
|
|
689
|
+
filesCreated.push(path.join(projectDir, 'packages', 'contracts', '.gitkeep'));
|
|
690
|
+
// Override root files for "all" project
|
|
691
|
+
const rootFiles = [
|
|
692
|
+
// Root package.json (npm workspaces)
|
|
693
|
+
{
|
|
694
|
+
path: path.join(projectDir, 'package.json'),
|
|
695
|
+
content: generateRootPackageJson(projectName),
|
|
696
|
+
},
|
|
697
|
+
// Workspace config
|
|
698
|
+
{
|
|
699
|
+
path: path.join(projectDir, '.popeye', 'workspace.json'),
|
|
700
|
+
content: generateAllWorkspaceJson(projectName),
|
|
701
|
+
},
|
|
702
|
+
// Docker compose (override to include website)
|
|
703
|
+
{
|
|
704
|
+
path: path.join(projectDir, 'docker-compose.yml'),
|
|
705
|
+
content: generateAllDockerCompose(projectName),
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
path: path.join(projectDir, 'infra', 'docker', 'docker-compose.yml'),
|
|
709
|
+
content: generateAllDockerCompose(projectName),
|
|
710
|
+
},
|
|
711
|
+
// README
|
|
712
|
+
{
|
|
713
|
+
path: path.join(projectDir, 'README.md'),
|
|
714
|
+
content: generateAllRootReadme(projectName, spec.idea),
|
|
715
|
+
},
|
|
716
|
+
];
|
|
717
|
+
for (const file of rootFiles) {
|
|
718
|
+
await writeFile(file.path, file.content);
|
|
719
|
+
// Only add if not already in list (avoid duplicates)
|
|
720
|
+
if (!filesCreated.includes(file.path)) {
|
|
721
|
+
filesCreated.push(file.path);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return {
|
|
725
|
+
success: true,
|
|
726
|
+
projectDir,
|
|
727
|
+
filesCreated,
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
catch (error) {
|
|
731
|
+
return {
|
|
732
|
+
success: false,
|
|
733
|
+
projectDir,
|
|
734
|
+
filesCreated,
|
|
735
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Get the list of files that would be generated for an "all" project
|
|
741
|
+
*
|
|
742
|
+
* @param projectName - Project name
|
|
743
|
+
* @returns List of relative file paths
|
|
744
|
+
*/
|
|
745
|
+
export function getAllProjectFiles(projectName) {
|
|
746
|
+
const packageName = toPythonPackageName(projectName);
|
|
747
|
+
return [
|
|
748
|
+
// Root
|
|
749
|
+
'package.json',
|
|
750
|
+
'.popeye/workspace.json',
|
|
751
|
+
'.popeye/ui-spec.json',
|
|
752
|
+
'infra/docker/docker-compose.yml',
|
|
753
|
+
'docker-compose.yml',
|
|
754
|
+
'README.md',
|
|
755
|
+
'.gitignore',
|
|
756
|
+
'docs/PLAN.md',
|
|
757
|
+
'docs/WORKFLOW_LOG.md',
|
|
758
|
+
// Frontend (same as fullstack)
|
|
759
|
+
'apps/frontend/package.json',
|
|
760
|
+
'apps/frontend/src/main.tsx',
|
|
761
|
+
'apps/frontend/src/App.tsx',
|
|
762
|
+
// Backend (same as fullstack)
|
|
763
|
+
'apps/backend/pyproject.toml',
|
|
764
|
+
`apps/backend/src/${packageName}/main.py`,
|
|
765
|
+
// Website
|
|
766
|
+
'apps/website/package.json',
|
|
767
|
+
'apps/website/next.config.mjs',
|
|
768
|
+
'apps/website/src/app/layout.tsx',
|
|
769
|
+
'apps/website/src/app/page.tsx',
|
|
770
|
+
'apps/website/src/app/sitemap.ts',
|
|
771
|
+
'apps/website/src/app/robots.ts',
|
|
772
|
+
// Shared packages
|
|
773
|
+
'packages/design-tokens/package.json',
|
|
774
|
+
'packages/design-tokens/src/index.ts',
|
|
775
|
+
'packages/design-tokens/src/colors.ts',
|
|
776
|
+
'packages/design-tokens/src/tailwind-preset.ts',
|
|
777
|
+
'packages/ui/package.json',
|
|
778
|
+
'packages/ui/src/index.ts',
|
|
779
|
+
'packages/ui/src/button.tsx',
|
|
780
|
+
'packages/ui/src/card.tsx',
|
|
781
|
+
'packages/contracts/.gitkeep',
|
|
782
|
+
];
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Validate an "all" project structure
|
|
786
|
+
*
|
|
787
|
+
* @param projectDir - Project directory
|
|
788
|
+
* @returns Validation result
|
|
789
|
+
*/
|
|
790
|
+
export async function validateAllProject(projectDir) {
|
|
791
|
+
const missingFiles = [];
|
|
792
|
+
const requiredPaths = [
|
|
793
|
+
// Root
|
|
794
|
+
'package.json',
|
|
795
|
+
'.popeye/workspace.json',
|
|
796
|
+
'docker-compose.yml',
|
|
797
|
+
'README.md',
|
|
798
|
+
// Frontend
|
|
799
|
+
'apps/frontend/package.json',
|
|
800
|
+
'apps/frontend/src',
|
|
801
|
+
// Backend
|
|
802
|
+
'apps/backend/pyproject.toml',
|
|
803
|
+
'apps/backend/src',
|
|
804
|
+
// Website
|
|
805
|
+
'apps/website/package.json',
|
|
806
|
+
'apps/website/src/app/layout.tsx',
|
|
807
|
+
'apps/website/src/app/sitemap.ts',
|
|
808
|
+
// Shared packages
|
|
809
|
+
'packages/design-tokens/package.json',
|
|
810
|
+
'packages/ui/package.json',
|
|
811
|
+
];
|
|
812
|
+
for (const file of requiredPaths) {
|
|
813
|
+
const filePath = path.join(projectDir, file);
|
|
814
|
+
try {
|
|
815
|
+
await fs.access(filePath);
|
|
816
|
+
}
|
|
817
|
+
catch {
|
|
818
|
+
missingFiles.push(file);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
return {
|
|
822
|
+
valid: missingFiles.length === 0,
|
|
823
|
+
missingFiles,
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
//# sourceMappingURL=all.js.map
|