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