popeye-cli 1.5.0 → 1.6.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/CHANGELOG.md +54 -0
- package/README.md +50 -8
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +54 -4
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/interactive.d.ts +29 -0
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +90 -7
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/all.d.ts +4 -1
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +36 -316
- package/dist/generators/all.js.map +1 -1
- package/dist/generators/doc-parser.d.ts +18 -3
- package/dist/generators/doc-parser.d.ts.map +1 -1
- package/dist/generators/doc-parser.js +81 -10
- package/dist/generators/doc-parser.js.map +1 -1
- package/dist/generators/frontend-design-analyzer.d.ts +30 -0
- package/dist/generators/frontend-design-analyzer.d.ts.map +1 -0
- package/dist/generators/frontend-design-analyzer.js +208 -0
- package/dist/generators/frontend-design-analyzer.js.map +1 -0
- package/dist/generators/shared-packages.d.ts +45 -0
- package/dist/generators/shared-packages.d.ts.map +1 -0
- package/dist/generators/shared-packages.js +456 -0
- package/dist/generators/shared-packages.js.map +1 -0
- package/dist/generators/templates/index.d.ts +4 -0
- package/dist/generators/templates/index.d.ts.map +1 -1
- package/dist/generators/templates/index.js +4 -0
- package/dist/generators/templates/index.js.map +1 -1
- package/dist/generators/templates/website-components.d.ts.map +1 -1
- package/dist/generators/templates/website-components.js +36 -11
- package/dist/generators/templates/website-components.js.map +1 -1
- package/dist/generators/templates/website-config.d.ts +15 -1
- package/dist/generators/templates/website-config.d.ts.map +1 -1
- package/dist/generators/templates/website-config.js +155 -13
- package/dist/generators/templates/website-config.js.map +1 -1
- package/dist/generators/templates/website-landing.d.ts +24 -0
- package/dist/generators/templates/website-landing.d.ts.map +1 -0
- package/dist/generators/templates/website-landing.js +276 -0
- package/dist/generators/templates/website-landing.js.map +1 -0
- package/dist/generators/templates/website-layout.d.ts +42 -0
- package/dist/generators/templates/website-layout.d.ts.map +1 -0
- package/dist/generators/templates/website-layout.js +408 -0
- package/dist/generators/templates/website-layout.js.map +1 -0
- package/dist/generators/templates/website-pricing.d.ts +11 -0
- package/dist/generators/templates/website-pricing.d.ts.map +1 -0
- package/dist/generators/templates/website-pricing.js +313 -0
- package/dist/generators/templates/website-pricing.js.map +1 -0
- package/dist/generators/templates/website-sections.d.ts +102 -0
- package/dist/generators/templates/website-sections.d.ts.map +1 -0
- package/dist/generators/templates/website-sections.js +444 -0
- package/dist/generators/templates/website-sections.js.map +1 -0
- package/dist/generators/templates/website.d.ts +10 -50
- package/dist/generators/templates/website.d.ts.map +1 -1
- package/dist/generators/templates/website.js +12 -788
- package/dist/generators/templates/website.js.map +1 -1
- package/dist/generators/website-content-scanner.d.ts +37 -0
- package/dist/generators/website-content-scanner.d.ts.map +1 -0
- package/dist/generators/website-content-scanner.js +165 -0
- package/dist/generators/website-content-scanner.js.map +1 -0
- package/dist/generators/website-context.d.ts +38 -2
- package/dist/generators/website-context.d.ts.map +1 -1
- package/dist/generators/website-context.js +179 -19
- package/dist/generators/website-context.js.map +1 -1
- package/dist/generators/website-debug.d.ts +68 -0
- package/dist/generators/website-debug.d.ts.map +1 -0
- package/dist/generators/website-debug.js +93 -0
- package/dist/generators/website-debug.js.map +1 -0
- package/dist/generators/website.d.ts +2 -0
- package/dist/generators/website.d.ts.map +1 -1
- package/dist/generators/website.js +66 -4
- package/dist/generators/website.js.map +1 -1
- package/dist/generators/workspace-root.d.ts +27 -0
- package/dist/generators/workspace-root.d.ts.map +1 -0
- package/dist/generators/workspace-root.js +100 -0
- package/dist/generators/workspace-root.js.map +1 -0
- package/dist/state/index.d.ts +8 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +10 -0
- package/dist/state/index.js.map +1 -1
- package/dist/types/workflow.d.ts +6 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +2 -0
- package/dist/types/workflow.js.map +1 -1
- package/dist/upgrade/handlers.d.ts +15 -0
- package/dist/upgrade/handlers.d.ts.map +1 -1
- package/dist/upgrade/handlers.js +52 -0
- package/dist/upgrade/handlers.js.map +1 -1
- package/dist/workflow/auto-fix-bundler.d.ts +37 -0
- package/dist/workflow/auto-fix-bundler.d.ts.map +1 -0
- package/dist/workflow/auto-fix-bundler.js +320 -0
- package/dist/workflow/auto-fix-bundler.js.map +1 -0
- package/dist/workflow/auto-fix.d.ts.map +1 -1
- package/dist/workflow/auto-fix.js +10 -3
- package/dist/workflow/auto-fix.js.map +1 -1
- package/dist/workflow/index.d.ts +1 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +12 -0
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/overview.d.ts.map +1 -1
- package/dist/workflow/overview.js +4 -0
- package/dist/workflow/overview.js.map +1 -1
- package/dist/workflow/plan-mode.d.ts +4 -3
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +69 -5
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/website-strategy.d.ts +9 -0
- package/dist/workflow/website-strategy.d.ts.map +1 -1
- package/dist/workflow/website-strategy.js +73 -1
- package/dist/workflow/website-strategy.js.map +1 -1
- package/dist/workflow/website-updater.d.ts.map +1 -1
- package/dist/workflow/website-updater.js +15 -4
- package/dist/workflow/website-updater.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/create.ts +58 -4
- package/src/cli/interactive.ts +96 -7
- package/src/generators/all.ts +44 -332
- package/src/generators/doc-parser.ts +87 -10
- package/src/generators/frontend-design-analyzer.ts +261 -0
- package/src/generators/shared-packages.ts +500 -0
- package/src/generators/templates/index.ts +4 -0
- package/src/generators/templates/website-components.ts +36 -11
- package/src/generators/templates/website-config.ts +166 -13
- package/src/generators/templates/website-landing.ts +331 -0
- package/src/generators/templates/website-layout.ts +443 -0
- package/src/generators/templates/website-pricing.ts +330 -0
- package/src/generators/templates/website-sections.ts +541 -0
- package/src/generators/templates/website.ts +38 -851
- package/src/generators/website-content-scanner.ts +208 -0
- package/src/generators/website-context.ts +248 -20
- package/src/generators/website-debug.ts +130 -0
- package/src/generators/website.ts +71 -3
- package/src/generators/workspace-root.ts +113 -0
- package/src/state/index.ts +14 -0
- package/src/types/workflow.ts +6 -0
- package/src/upgrade/handlers.ts +65 -0
- package/src/workflow/auto-fix-bundler.ts +392 -0
- package/src/workflow/auto-fix.ts +11 -3
- package/src/workflow/index.ts +12 -0
- package/src/workflow/overview.ts +6 -0
- package/src/workflow/plan-mode.ts +81 -7
- package/src/workflow/website-strategy.ts +75 -1
- package/src/workflow/website-updater.ts +17 -6
- package/tests/cli/project-naming.test.ts +136 -0
- package/tests/generators/doc-parser.test.ts +121 -0
- package/tests/generators/frontend-design-analyzer.test.ts +90 -0
- package/tests/generators/quality-gate.test.ts +183 -0
- package/tests/generators/shared-packages.test.ts +83 -0
- package/tests/generators/website-components.test.ts +1 -1
- package/tests/generators/website-config.test.ts +84 -0
- package/tests/generators/website-content-scanner.test.ts +181 -0
- package/tests/generators/website-context.test.ts +109 -0
- package/tests/generators/website-debug.test.ts +77 -0
- package/tests/generators/website-landing.test.ts +188 -0
- package/tests/generators/website-pricing.test.ts +98 -0
- package/tests/generators/website-sections.test.ts +245 -0
- package/tests/generators/workspace-root.test.ts +105 -0
- package/tests/upgrade/handlers.test.ts +162 -0
- package/tests/workflow/auto-fix-bundler.test.ts +242 -0
- package/tests/workflow/plan-mode.test.ts +111 -1
- package/tests/workflow/website-strategy.test.ts +55 -0
package/src/generators/all.ts
CHANGED
|
@@ -10,6 +10,12 @@ import type { GenerationResult } from './python.js';
|
|
|
10
10
|
import { generateFullstackProject } from './fullstack.js';
|
|
11
11
|
import { generateWebsiteProject } from './website.js';
|
|
12
12
|
import type { WebsiteContentContext } from './website-context.js';
|
|
13
|
+
import { buildWebsiteContext, validateWebsiteContext } from './website-context.js';
|
|
14
|
+
import {
|
|
15
|
+
generateDesignTokensPackage as generateDesignTokensPackageImpl,
|
|
16
|
+
generateUiPackage as generateUiPackageImpl,
|
|
17
|
+
} from './shared-packages.js';
|
|
18
|
+
import type { BrandColorOptions } from './shared-packages.js';
|
|
13
19
|
|
|
14
20
|
/**
|
|
15
21
|
* Options for all project generation
|
|
@@ -344,346 +350,25 @@ Generated by [Popeye CLI](https://github.com/popeye-cli/popeye)
|
|
|
344
350
|
|
|
345
351
|
/**
|
|
346
352
|
* Generate design tokens package
|
|
353
|
+
* Delegates to shared-packages.ts with optional brand color passthrough
|
|
347
354
|
*/
|
|
348
|
-
export function generateDesignTokensPackage(
|
|
355
|
+
export function generateDesignTokensPackage(
|
|
356
|
+
projectName: string,
|
|
357
|
+
brandColors?: BrandColorOptions
|
|
358
|
+
): {
|
|
349
359
|
files: Array<{ path: string; content: string }>;
|
|
350
360
|
} {
|
|
351
|
-
return
|
|
352
|
-
files: [
|
|
353
|
-
{
|
|
354
|
-
path: 'package.json',
|
|
355
|
-
content: JSON.stringify(
|
|
356
|
-
{
|
|
357
|
-
name: `@${projectName}/design-tokens`,
|
|
358
|
-
version: '1.0.0',
|
|
359
|
-
type: 'module',
|
|
360
|
-
main: './dist/index.js',
|
|
361
|
-
types: './dist/index.d.ts',
|
|
362
|
-
exports: {
|
|
363
|
-
'.': './dist/index.js',
|
|
364
|
-
'./tailwind': './dist/tailwind-preset.js',
|
|
365
|
-
},
|
|
366
|
-
scripts: {
|
|
367
|
-
build: 'tsc',
|
|
368
|
-
dev: 'tsc --watch',
|
|
369
|
-
},
|
|
370
|
-
devDependencies: {
|
|
371
|
-
typescript: '^5.3.3',
|
|
372
|
-
},
|
|
373
|
-
},
|
|
374
|
-
null,
|
|
375
|
-
2
|
|
376
|
-
),
|
|
377
|
-
},
|
|
378
|
-
{
|
|
379
|
-
path: 'tsconfig.json',
|
|
380
|
-
content: JSON.stringify(
|
|
381
|
-
{
|
|
382
|
-
compilerOptions: {
|
|
383
|
-
target: 'ES2020',
|
|
384
|
-
module: 'ESNext',
|
|
385
|
-
moduleResolution: 'bundler',
|
|
386
|
-
declaration: true,
|
|
387
|
-
outDir: './dist',
|
|
388
|
-
strict: true,
|
|
389
|
-
esModuleInterop: true,
|
|
390
|
-
skipLibCheck: true,
|
|
391
|
-
},
|
|
392
|
-
include: ['src'],
|
|
393
|
-
},
|
|
394
|
-
null,
|
|
395
|
-
2
|
|
396
|
-
),
|
|
397
|
-
},
|
|
398
|
-
{
|
|
399
|
-
path: 'src/index.ts',
|
|
400
|
-
content: `/**
|
|
401
|
-
* Design tokens for ${projectName}
|
|
402
|
-
*/
|
|
403
|
-
|
|
404
|
-
export * from './colors.js';
|
|
405
|
-
export * from './typography.js';
|
|
406
|
-
`,
|
|
407
|
-
},
|
|
408
|
-
{
|
|
409
|
-
path: 'src/colors.ts',
|
|
410
|
-
content: `/**
|
|
411
|
-
* Color palette
|
|
412
|
-
*/
|
|
413
|
-
|
|
414
|
-
export const colors = {
|
|
415
|
-
primary: {
|
|
416
|
-
50: '#f0f9ff',
|
|
417
|
-
100: '#e0f2fe',
|
|
418
|
-
200: '#bae6fd',
|
|
419
|
-
300: '#7dd3fc',
|
|
420
|
-
400: '#38bdf8',
|
|
421
|
-
500: '#0ea5e9',
|
|
422
|
-
600: '#0284c7',
|
|
423
|
-
700: '#0369a1',
|
|
424
|
-
800: '#075985',
|
|
425
|
-
900: '#0c4a6e',
|
|
426
|
-
},
|
|
427
|
-
secondary: {
|
|
428
|
-
50: '#f8fafc',
|
|
429
|
-
100: '#f1f5f9',
|
|
430
|
-
200: '#e2e8f0',
|
|
431
|
-
300: '#cbd5e1',
|
|
432
|
-
400: '#94a3b8',
|
|
433
|
-
500: '#64748b',
|
|
434
|
-
600: '#475569',
|
|
435
|
-
700: '#334155',
|
|
436
|
-
800: '#1e293b',
|
|
437
|
-
900: '#0f172a',
|
|
438
|
-
},
|
|
439
|
-
} as const;
|
|
440
|
-
|
|
441
|
-
export type ColorScale = typeof colors.primary;
|
|
442
|
-
export type Colors = typeof colors;
|
|
443
|
-
`,
|
|
444
|
-
},
|
|
445
|
-
{
|
|
446
|
-
path: 'src/typography.ts',
|
|
447
|
-
content: `/**
|
|
448
|
-
* Typography settings
|
|
449
|
-
*/
|
|
450
|
-
|
|
451
|
-
export const typography = {
|
|
452
|
-
fontFamily: {
|
|
453
|
-
sans: ['Inter', 'system-ui', 'sans-serif'],
|
|
454
|
-
mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
|
|
455
|
-
},
|
|
456
|
-
fontSize: {
|
|
457
|
-
xs: ['0.75rem', { lineHeight: '1rem' }],
|
|
458
|
-
sm: ['0.875rem', { lineHeight: '1.25rem' }],
|
|
459
|
-
base: ['1rem', { lineHeight: '1.5rem' }],
|
|
460
|
-
lg: ['1.125rem', { lineHeight: '1.75rem' }],
|
|
461
|
-
xl: ['1.25rem', { lineHeight: '1.75rem' }],
|
|
462
|
-
'2xl': ['1.5rem', { lineHeight: '2rem' }],
|
|
463
|
-
'3xl': ['1.875rem', { lineHeight: '2.25rem' }],
|
|
464
|
-
'4xl': ['2.25rem', { lineHeight: '2.5rem' }],
|
|
465
|
-
'5xl': ['3rem', { lineHeight: '1' }],
|
|
466
|
-
'6xl': ['3.75rem', { lineHeight: '1' }],
|
|
467
|
-
},
|
|
468
|
-
} as const;
|
|
469
|
-
|
|
470
|
-
export type Typography = typeof typography;
|
|
471
|
-
`,
|
|
472
|
-
},
|
|
473
|
-
{
|
|
474
|
-
path: 'src/tailwind-preset.ts',
|
|
475
|
-
content: `/**
|
|
476
|
-
* Tailwind CSS preset with design tokens
|
|
477
|
-
*/
|
|
478
|
-
|
|
479
|
-
import { colors } from './colors.js';
|
|
480
|
-
import { typography } from './typography.js';
|
|
481
|
-
|
|
482
|
-
export const preset = {
|
|
483
|
-
theme: {
|
|
484
|
-
extend: {
|
|
485
|
-
colors,
|
|
486
|
-
fontFamily: typography.fontFamily,
|
|
487
|
-
fontSize: typography.fontSize,
|
|
488
|
-
},
|
|
489
|
-
},
|
|
490
|
-
};
|
|
491
|
-
|
|
492
|
-
export default preset;
|
|
493
|
-
`,
|
|
494
|
-
},
|
|
495
|
-
],
|
|
496
|
-
};
|
|
361
|
+
return generateDesignTokensPackageImpl(projectName, brandColors);
|
|
497
362
|
}
|
|
498
363
|
|
|
499
364
|
/**
|
|
500
365
|
* Generate UI components package
|
|
366
|
+
* Delegates to shared-packages.ts
|
|
501
367
|
*/
|
|
502
368
|
export function generateUiPackage(projectName: string): {
|
|
503
369
|
files: Array<{ path: string; content: string }>;
|
|
504
370
|
} {
|
|
505
|
-
return
|
|
506
|
-
files: [
|
|
507
|
-
{
|
|
508
|
-
path: 'package.json',
|
|
509
|
-
content: JSON.stringify(
|
|
510
|
-
{
|
|
511
|
-
name: `@${projectName}/ui`,
|
|
512
|
-
version: '1.0.0',
|
|
513
|
-
type: 'module',
|
|
514
|
-
main: './dist/index.js',
|
|
515
|
-
types: './dist/index.d.ts',
|
|
516
|
-
exports: {
|
|
517
|
-
'.': './dist/index.js',
|
|
518
|
-
'./button': './dist/button.js',
|
|
519
|
-
'./card': './dist/card.js',
|
|
520
|
-
},
|
|
521
|
-
scripts: {
|
|
522
|
-
build: 'tsc',
|
|
523
|
-
dev: 'tsc --watch',
|
|
524
|
-
},
|
|
525
|
-
dependencies: {
|
|
526
|
-
clsx: '^2.1.0',
|
|
527
|
-
'tailwind-merge': '^2.2.0',
|
|
528
|
-
},
|
|
529
|
-
peerDependencies: {
|
|
530
|
-
react: '>=18.0.0',
|
|
531
|
-
'react-dom': '>=18.0.0',
|
|
532
|
-
},
|
|
533
|
-
devDependencies: {
|
|
534
|
-
'@types/react': '^18.2.0',
|
|
535
|
-
'@types/react-dom': '^18.2.0',
|
|
536
|
-
typescript: '^5.3.3',
|
|
537
|
-
},
|
|
538
|
-
},
|
|
539
|
-
null,
|
|
540
|
-
2
|
|
541
|
-
),
|
|
542
|
-
},
|
|
543
|
-
{
|
|
544
|
-
path: 'tsconfig.json',
|
|
545
|
-
content: JSON.stringify(
|
|
546
|
-
{
|
|
547
|
-
compilerOptions: {
|
|
548
|
-
target: 'ES2020',
|
|
549
|
-
module: 'ESNext',
|
|
550
|
-
moduleResolution: 'bundler',
|
|
551
|
-
declaration: true,
|
|
552
|
-
outDir: './dist',
|
|
553
|
-
strict: true,
|
|
554
|
-
esModuleInterop: true,
|
|
555
|
-
skipLibCheck: true,
|
|
556
|
-
jsx: 'react-jsx',
|
|
557
|
-
},
|
|
558
|
-
include: ['src'],
|
|
559
|
-
},
|
|
560
|
-
null,
|
|
561
|
-
2
|
|
562
|
-
),
|
|
563
|
-
},
|
|
564
|
-
{
|
|
565
|
-
path: 'src/index.ts',
|
|
566
|
-
content: `/**
|
|
567
|
-
* Shared UI components for ${projectName}
|
|
568
|
-
*/
|
|
569
|
-
|
|
570
|
-
export * from './button.js';
|
|
571
|
-
export * from './card.js';
|
|
572
|
-
export * from './utils.js';
|
|
573
|
-
`,
|
|
574
|
-
},
|
|
575
|
-
{
|
|
576
|
-
path: 'src/utils.ts',
|
|
577
|
-
content: `import { clsx, type ClassValue } from 'clsx';
|
|
578
|
-
import { twMerge } from 'tailwind-merge';
|
|
579
|
-
|
|
580
|
-
export function cn(...inputs: ClassValue[]) {
|
|
581
|
-
return twMerge(clsx(inputs));
|
|
582
|
-
}
|
|
583
|
-
`,
|
|
584
|
-
},
|
|
585
|
-
{
|
|
586
|
-
path: 'src/button.tsx',
|
|
587
|
-
content: `import * as React from 'react';
|
|
588
|
-
import { cn } from './utils.js';
|
|
589
|
-
|
|
590
|
-
export interface ButtonProps
|
|
591
|
-
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
592
|
-
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
|
|
593
|
-
size?: 'sm' | 'md' | 'lg';
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
597
|
-
({ className, variant = 'primary', size = 'md', ...props }, ref) => {
|
|
598
|
-
return (
|
|
599
|
-
<button
|
|
600
|
-
className={cn(
|
|
601
|
-
'inline-flex items-center justify-center rounded-md font-medium transition-colors',
|
|
602
|
-
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
|
|
603
|
-
'disabled:pointer-events-none disabled:opacity-50',
|
|
604
|
-
{
|
|
605
|
-
// Variants
|
|
606
|
-
'bg-primary-600 text-white hover:bg-primary-500': variant === 'primary',
|
|
607
|
-
'bg-secondary-100 text-secondary-900 hover:bg-secondary-200': variant === 'secondary',
|
|
608
|
-
'border border-secondary-300 bg-transparent hover:bg-secondary-50': variant === 'outline',
|
|
609
|
-
'bg-transparent hover:bg-secondary-100': variant === 'ghost',
|
|
610
|
-
// Sizes
|
|
611
|
-
'h-8 px-3 text-sm': size === 'sm',
|
|
612
|
-
'h-10 px-4 text-sm': size === 'md',
|
|
613
|
-
'h-12 px-6 text-base': size === 'lg',
|
|
614
|
-
},
|
|
615
|
-
className
|
|
616
|
-
)}
|
|
617
|
-
ref={ref}
|
|
618
|
-
{...props}
|
|
619
|
-
/>
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
);
|
|
623
|
-
|
|
624
|
-
Button.displayName = 'Button';
|
|
625
|
-
`,
|
|
626
|
-
},
|
|
627
|
-
{
|
|
628
|
-
path: 'src/card.tsx',
|
|
629
|
-
content: `import * as React from 'react';
|
|
630
|
-
import { cn } from './utils.js';
|
|
631
|
-
|
|
632
|
-
export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
633
|
-
|
|
634
|
-
export const Card = React.forwardRef<HTMLDivElement, CardProps>(
|
|
635
|
-
({ className, ...props }, ref) => (
|
|
636
|
-
<div
|
|
637
|
-
ref={ref}
|
|
638
|
-
className={cn(
|
|
639
|
-
'rounded-lg border border-secondary-200 bg-white shadow-sm',
|
|
640
|
-
className
|
|
641
|
-
)}
|
|
642
|
-
{...props}
|
|
643
|
-
/>
|
|
644
|
-
)
|
|
645
|
-
);
|
|
646
|
-
|
|
647
|
-
Card.displayName = 'Card';
|
|
648
|
-
|
|
649
|
-
export const CardHeader = React.forwardRef<
|
|
650
|
-
HTMLDivElement,
|
|
651
|
-
React.HTMLAttributes<HTMLDivElement>
|
|
652
|
-
>(({ className, ...props }, ref) => (
|
|
653
|
-
<div
|
|
654
|
-
ref={ref}
|
|
655
|
-
className={cn('flex flex-col space-y-1.5 p-6', className)}
|
|
656
|
-
{...props}
|
|
657
|
-
/>
|
|
658
|
-
));
|
|
659
|
-
|
|
660
|
-
CardHeader.displayName = 'CardHeader';
|
|
661
|
-
|
|
662
|
-
export const CardTitle = React.forwardRef<
|
|
663
|
-
HTMLParagraphElement,
|
|
664
|
-
React.HTMLAttributes<HTMLHeadingElement>
|
|
665
|
-
>(({ className, ...props }, ref) => (
|
|
666
|
-
<h3
|
|
667
|
-
ref={ref}
|
|
668
|
-
className={cn('text-lg font-semibold leading-none tracking-tight', className)}
|
|
669
|
-
{...props}
|
|
670
|
-
/>
|
|
671
|
-
));
|
|
672
|
-
|
|
673
|
-
CardTitle.displayName = 'CardTitle';
|
|
674
|
-
|
|
675
|
-
export const CardContent = React.forwardRef<
|
|
676
|
-
HTMLDivElement,
|
|
677
|
-
React.HTMLAttributes<HTMLDivElement>
|
|
678
|
-
>(({ className, ...props }, ref) => (
|
|
679
|
-
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
|
|
680
|
-
));
|
|
681
|
-
|
|
682
|
-
CardContent.displayName = 'CardContent';
|
|
683
|
-
`,
|
|
684
|
-
},
|
|
685
|
-
],
|
|
686
|
-
};
|
|
371
|
+
return generateUiPackageImpl(projectName);
|
|
687
372
|
}
|
|
688
373
|
|
|
689
374
|
/**
|
|
@@ -711,6 +396,30 @@ export async function generateAllProject(
|
|
|
711
396
|
await ensureDir(path.join(projectDir, 'packages', 'contracts'));
|
|
712
397
|
await ensureDir(path.join(projectDir, '.popeye'));
|
|
713
398
|
|
|
399
|
+
// Auto-build content context if not provided
|
|
400
|
+
let contentContext = options.contentContext;
|
|
401
|
+
let contextWarning: string | undefined;
|
|
402
|
+
if (!contentContext) {
|
|
403
|
+
try {
|
|
404
|
+
contentContext = await buildWebsiteContext(projectDir, projectName);
|
|
405
|
+
} catch (e) {
|
|
406
|
+
contextWarning = e instanceof Error ? e.message : 'Unknown error building website context';
|
|
407
|
+
// Proceed with defaults, but warning is logged below
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (contextWarning) {
|
|
411
|
+
// Log warning so user sees it, but don't block generation
|
|
412
|
+
console.warn(`[website-context] Warning: ${contextWarning}`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Soft validation: log quality issues without blocking monorepo generation
|
|
416
|
+
if (contentContext) {
|
|
417
|
+
const validation = validateWebsiteContext(contentContext, projectName);
|
|
418
|
+
for (const issue of [...validation.issues, ...validation.warnings]) {
|
|
419
|
+
console.warn(`[website-context] ${issue}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
714
423
|
// Generate fullstack first (creates apps/frontend and apps/backend)
|
|
715
424
|
const fullstackResult = await generateFullstackProject(spec, outputDir);
|
|
716
425
|
if (!fullstackResult.success) {
|
|
@@ -724,7 +433,7 @@ export async function generateAllProject(
|
|
|
724
433
|
workspaceMode: true,
|
|
725
434
|
skipDocker: false, // Website needs its own Dockerfile
|
|
726
435
|
skipReadme: false,
|
|
727
|
-
contentContext:
|
|
436
|
+
contentContext: contentContext,
|
|
728
437
|
});
|
|
729
438
|
if (!websiteResult.success) {
|
|
730
439
|
return {
|
|
@@ -734,8 +443,11 @@ export async function generateAllProject(
|
|
|
734
443
|
}
|
|
735
444
|
filesCreated.push(...websiteResult.filesCreated);
|
|
736
445
|
|
|
737
|
-
// Generate shared packages
|
|
738
|
-
const
|
|
446
|
+
// Generate shared packages (pass brand colors if available)
|
|
447
|
+
const brandColors: BrandColorOptions | undefined = contentContext?.brand?.primaryColor
|
|
448
|
+
? { primaryColor: contentContext.brand.primaryColor }
|
|
449
|
+
: undefined;
|
|
450
|
+
const designTokens = generateDesignTokensPackage(projectName, brandColors);
|
|
739
451
|
for (const file of designTokens.files) {
|
|
740
452
|
const filePath = path.join(projectDir, 'packages', 'design-tokens', file.path);
|
|
741
453
|
await ensureDir(path.dirname(filePath));
|
|
@@ -24,36 +24,86 @@ export function stripCodeFences(text: string): string {
|
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Extract the real product name from docs or specification
|
|
27
|
-
*
|
|
27
|
+
*
|
|
28
|
+
* Priority chain (first match wins):
|
|
29
|
+
* 1. Parsed docs: "# ProductName -- tagline" heading (picks shortest)
|
|
30
|
+
* 2. Specification: "# ProductName" heading
|
|
31
|
+
* 3. Specification: "Product:" / "Name:" / "App:" label
|
|
32
|
+
* 4. package.json "name" field from workspace root (passed via specification context)
|
|
33
|
+
* 5. undefined (caller falls back to directory name)
|
|
28
34
|
*/
|
|
29
|
-
export function extractProductName(
|
|
30
|
-
|
|
35
|
+
export function extractProductName(
|
|
36
|
+
docs: string,
|
|
37
|
+
specification?: string,
|
|
38
|
+
packageJsonName?: string
|
|
39
|
+
): string | undefined {
|
|
40
|
+
// 1. Collect all "# Name -- tagline" headings, pick the shortest name
|
|
31
41
|
// Reason: sub-documents like "Gateco UI Color System" are longer than "Gateco"
|
|
32
|
-
const headingPattern = /^#\s+([A-Z][a-zA-Z0-9]+(?:[ \t]+[A-Z][a-zA-Z0-9]+)*)(?:[ \t]*[
|
|
42
|
+
const headingPattern = /^#\s+([A-Z][a-zA-Z0-9]+(?:[ \t]+[A-Z][a-zA-Z0-9]+)*)(?:[ \t]*(?:--|[—–|:])[ \t])/gm;
|
|
33
43
|
const candidates: string[] = [];
|
|
34
44
|
let match;
|
|
35
45
|
while ((match = headingPattern.exec(docs)) !== null) {
|
|
36
46
|
candidates.push(match[1].trim());
|
|
37
47
|
}
|
|
38
48
|
if (candidates.length > 0) {
|
|
39
|
-
// Prefer shortest name (product name is typically 1-2 words)
|
|
40
49
|
candidates.sort((a, b) => a.length - b.length);
|
|
41
50
|
return candidates[0];
|
|
42
51
|
}
|
|
43
52
|
|
|
44
|
-
//
|
|
53
|
+
// 2. "# ProductName" heading in specification (standalone heading, not sub-doc)
|
|
54
|
+
// Reason: Exclude common section headings like "Overview", "Introduction", "Summary"
|
|
55
|
+
if (specification) {
|
|
56
|
+
const commonHeadings = /^(Overview|Introduction|Summary|Features|Architecture|Requirements|Setup|Installation|Configuration|Specification|Appendix|Conclusion|References)$/i;
|
|
57
|
+
const specHeading = specification.match(/^#\s+([A-Z][a-zA-Z0-9]+(?:\s+[A-Z][a-zA-Z0-9]+)*)\s*$/m);
|
|
58
|
+
if (specHeading && !commonHeadings.test(specHeading[1].trim())) {
|
|
59
|
+
return specHeading[1].trim();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 3. "Product:" / "Name:" / "App:" label in specification
|
|
45
64
|
if (specification) {
|
|
46
65
|
const nameMatch = specification.match(/\*\*Project\s+Name\*\*:\s*(.+)/i);
|
|
47
66
|
if (nameMatch) return nameMatch[1].trim();
|
|
67
|
+
|
|
68
|
+
const labelMatch = specification.match(/(?:^|\n)\s*(?:Product|Name|App)\s*:\s*(.+)/i);
|
|
69
|
+
if (labelMatch) {
|
|
70
|
+
const value = labelMatch[1].trim().replace(/\*\*/g, '');
|
|
71
|
+
if (value.length > 0 && value.length < 60) return value;
|
|
72
|
+
}
|
|
48
73
|
}
|
|
49
74
|
|
|
50
|
-
//
|
|
75
|
+
// 4. "# ProductName" in spec/product doc sections only
|
|
51
76
|
const sectionHeading = docs.match(/^---\s+\S*(?:spec|product)\S*\s+---\n#\s+([A-Z][a-zA-Z0-9]+)/im);
|
|
52
77
|
if (sectionHeading) return sectionHeading[1].trim();
|
|
53
78
|
|
|
79
|
+
// 5. package.json name (strip @scope/ prefix and convert to title case)
|
|
80
|
+
if (packageJsonName) {
|
|
81
|
+
const cleaned = packageJsonName
|
|
82
|
+
.replace(/^@[^/]+\//, '') // Strip scope
|
|
83
|
+
.replace(/[-_]/g, ' ')
|
|
84
|
+
.replace(/\b\w/g, (c) => c.toUpperCase())
|
|
85
|
+
.trim();
|
|
86
|
+
if (cleaned.length > 0) return cleaned;
|
|
87
|
+
}
|
|
88
|
+
|
|
54
89
|
return undefined;
|
|
55
90
|
}
|
|
56
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Check if a product name looks like a directory name rather than a real product name
|
|
94
|
+
* Used by the quality gate to flag suspicious names
|
|
95
|
+
*
|
|
96
|
+
* @param name - The product name to check
|
|
97
|
+
* @returns True if the name looks suspicious (likely a directory name)
|
|
98
|
+
*/
|
|
99
|
+
export function isSuspiciousProductName(name: string): boolean {
|
|
100
|
+
const suspiciousNames = ['my-app', 'my-project', 'project', 'app', 'website', 'frontend'];
|
|
101
|
+
if (suspiciousNames.includes(name.toLowerCase())) return true;
|
|
102
|
+
// Hyphenated lowercase strings like "read-all-files" are likely directory names
|
|
103
|
+
if (/^[a-z]+-[a-z]+(-[a-z]+)*$/.test(name)) return true;
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
57
107
|
/**
|
|
58
108
|
* Extract a tagline from docs (text after em-dash in first heading)
|
|
59
109
|
* When productName is provided, prefer tagline from the heading containing that name
|
|
@@ -123,16 +173,37 @@ export function extractDescription(docs: string, specification?: string): string
|
|
|
123
173
|
return undefined;
|
|
124
174
|
}
|
|
125
175
|
|
|
176
|
+
/** Regex to filter out dev-task items that aren't real product features */
|
|
177
|
+
const DEV_TASK_VERBS = /^(?:implement|fix|refactor|add tests|upgrade|migrate|configure|setup|install|deploy|debug|create|build|write|update|remove|delete)/i;
|
|
178
|
+
|
|
126
179
|
/**
|
|
127
180
|
* Extract features from docs and specification
|
|
128
|
-
*
|
|
181
|
+
* Docs-first: extracts from docs only; falls back to specification if empty
|
|
182
|
+
* Filters out dev-task items (implement, fix, refactor, etc.)
|
|
129
183
|
*/
|
|
130
184
|
export function extractFeatures(
|
|
131
185
|
docs: string,
|
|
132
186
|
specification?: string
|
|
187
|
+
): Array<{ title: string; description: string }> {
|
|
188
|
+
// Docs-first: try docs only, then fall back to spec
|
|
189
|
+
// Reason: specification often contains dev tasks, not user-facing features
|
|
190
|
+
const docsFeatures = extractFeaturesFromSource(docs);
|
|
191
|
+
if (docsFeatures.length > 0) return docsFeatures;
|
|
192
|
+
|
|
193
|
+
if (specification) {
|
|
194
|
+
return extractFeaturesFromSource(specification);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Extract features from a single text source
|
|
202
|
+
*/
|
|
203
|
+
function extractFeaturesFromSource(
|
|
204
|
+
source: string
|
|
133
205
|
): Array<{ title: string; description: string }> {
|
|
134
206
|
const features: Array<{ title: string; description: string }> = [];
|
|
135
|
-
const source = docs + '\n' + (specification || '');
|
|
136
207
|
|
|
137
208
|
// Split into sections by heading
|
|
138
209
|
const sectionPattern = /^#{1,3}\s+(.+)$/gm;
|
|
@@ -173,13 +244,17 @@ export function extractFeatures(
|
|
|
173
244
|
// Try "**bold title** - description" pattern
|
|
174
245
|
const boldWithDesc = text.match(/^\*\*(.+?)\*\*\s*[-–:]\s*(.+)/);
|
|
175
246
|
if (boldWithDesc) {
|
|
247
|
+
const title = boldWithDesc[1].trim();
|
|
248
|
+
// Filter out dev-task items
|
|
249
|
+
if (DEV_TASK_VERBS.test(title)) continue;
|
|
176
250
|
features.push({
|
|
177
|
-
title
|
|
251
|
+
title,
|
|
178
252
|
description: boldWithDesc[2].trim().slice(0, 150),
|
|
179
253
|
});
|
|
180
254
|
} else if (/^\*\*.+\*\*/.test(text)) {
|
|
181
255
|
// Bold title with no trailing description: "**Vector DB agnostic**"
|
|
182
256
|
const title = text.replace(/\*\*/g, '').trim();
|
|
257
|
+
if (DEV_TASK_VERBS.test(title)) continue;
|
|
183
258
|
if (title.length > 3 && title.length < 80) {
|
|
184
259
|
features.push({ title, description: title });
|
|
185
260
|
}
|
|
@@ -187,6 +262,8 @@ export function extractFeatures(
|
|
|
187
262
|
const cleaned = text.replace(/\*\*/g, '');
|
|
188
263
|
// Split on sentence-level delimiters only; keep hyphens in compound words
|
|
189
264
|
const titlePart = cleaned.split(/[.,:;—–]/)[0].trim();
|
|
265
|
+
// Filter out dev-task items
|
|
266
|
+
if (DEV_TASK_VERBS.test(titlePart)) continue;
|
|
190
267
|
if (titlePart.length > 3 && titlePart.length < 60) {
|
|
191
268
|
features.push({
|
|
192
269
|
title: titlePart,
|