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.
Files changed (114) 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 +3 -2
  5. package/dist/adapters/claude.d.ts.map +1 -1
  6. package/dist/adapters/claude.js +214 -0
  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 +47 -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/workflow/consensus.d.ts +2 -1
  58. package/dist/workflow/consensus.d.ts.map +1 -1
  59. package/dist/workflow/consensus.js.map +1 -1
  60. package/dist/workflow/index.d.ts +6 -0
  61. package/dist/workflow/index.d.ts.map +1 -1
  62. package/dist/workflow/index.js +8 -0
  63. package/dist/workflow/index.js.map +1 -1
  64. package/dist/workflow/plan-mode.d.ts +3 -3
  65. package/dist/workflow/plan-mode.d.ts.map +1 -1
  66. package/dist/workflow/plan-mode.js.map +1 -1
  67. package/dist/workflow/plan-parser.d.ts +97 -0
  68. package/dist/workflow/plan-parser.d.ts.map +1 -0
  69. package/dist/workflow/plan-parser.js +235 -0
  70. package/dist/workflow/plan-parser.js.map +1 -0
  71. package/dist/workflow/plan-storage.d.ts +40 -12
  72. package/dist/workflow/plan-storage.d.ts.map +1 -1
  73. package/dist/workflow/plan-storage.js +47 -20
  74. package/dist/workflow/plan-storage.js.map +1 -1
  75. package/dist/workflow/seo-tests.d.ts +43 -0
  76. package/dist/workflow/seo-tests.d.ts.map +1 -0
  77. package/dist/workflow/seo-tests.js +192 -0
  78. package/dist/workflow/seo-tests.js.map +1 -0
  79. package/dist/workflow/separation-guard.d.ts +35 -0
  80. package/dist/workflow/separation-guard.d.ts.map +1 -0
  81. package/dist/workflow/separation-guard.js +154 -0
  82. package/dist/workflow/separation-guard.js.map +1 -0
  83. package/dist/workflow/test-runner.d.ts.map +1 -1
  84. package/dist/workflow/test-runner.js +128 -0
  85. package/dist/workflow/test-runner.js.map +1 -1
  86. package/dist/workflow/workspace-manager.d.ts +31 -20
  87. package/dist/workflow/workspace-manager.d.ts.map +1 -1
  88. package/dist/workflow/workspace-manager.js +38 -9
  89. package/dist/workflow/workspace-manager.js.map +1 -1
  90. package/package.json +1 -1
  91. package/src/adapters/claude.ts +221 -4
  92. package/src/adapters/grok.ts +2 -1
  93. package/src/adapters/index.ts +15 -0
  94. package/src/adapters/openai.ts +2 -2
  95. package/src/cli/commands/create.ts +25 -5
  96. package/src/cli/interactive.ts +47 -6
  97. package/src/generators/all.ts +897 -0
  98. package/src/generators/fullstack.ts +10 -0
  99. package/src/generators/index.ts +54 -0
  100. package/src/generators/templates/index.ts +2 -0
  101. package/src/generators/templates/website.ts +906 -0
  102. package/src/generators/website.ts +350 -0
  103. package/src/types/consensus.ts +9 -3
  104. package/src/types/index.ts +33 -0
  105. package/src/types/project.ts +139 -2
  106. package/src/workflow/consensus.ts +3 -2
  107. package/src/workflow/index.ts +8 -0
  108. package/src/workflow/plan-mode.ts +3 -3
  109. package/src/workflow/plan-parser.ts +317 -0
  110. package/src/workflow/plan-storage.ts +69 -30
  111. package/src/workflow/seo-tests.ts +246 -0
  112. package/src/workflow/separation-guard.ts +200 -0
  113. package/src/workflow/test-runner.ts +149 -0
  114. 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