popeye-cli 1.4.6 → 1.5.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 (140) hide show
  1. package/README.md +222 -63
  2. package/dist/adapters/gemini.d.ts +1 -0
  3. package/dist/adapters/gemini.d.ts.map +1 -1
  4. package/dist/adapters/gemini.js +9 -4
  5. package/dist/adapters/gemini.js.map +1 -1
  6. package/dist/adapters/grok.d.ts +1 -0
  7. package/dist/adapters/grok.d.ts.map +1 -1
  8. package/dist/adapters/grok.js +9 -4
  9. package/dist/adapters/grok.js.map +1 -1
  10. package/dist/adapters/openai.d.ts +1 -1
  11. package/dist/adapters/openai.d.ts.map +1 -1
  12. package/dist/adapters/openai.js +35 -9
  13. package/dist/adapters/openai.js.map +1 -1
  14. package/dist/cli/interactive.d.ts.map +1 -1
  15. package/dist/cli/interactive.js +42 -0
  16. package/dist/cli/interactive.js.map +1 -1
  17. package/dist/generators/all.d.ts +4 -1
  18. package/dist/generators/all.d.ts.map +1 -1
  19. package/dist/generators/all.js +2 -1
  20. package/dist/generators/all.js.map +1 -1
  21. package/dist/generators/doc-parser.d.ts +49 -0
  22. package/dist/generators/doc-parser.d.ts.map +1 -0
  23. package/dist/generators/doc-parser.js +336 -0
  24. package/dist/generators/doc-parser.js.map +1 -0
  25. package/dist/generators/templates/index.d.ts +4 -0
  26. package/dist/generators/templates/index.d.ts.map +1 -1
  27. package/dist/generators/templates/index.js +4 -0
  28. package/dist/generators/templates/index.js.map +1 -1
  29. package/dist/generators/templates/website-components.d.ts +33 -0
  30. package/dist/generators/templates/website-components.d.ts.map +1 -0
  31. package/dist/generators/templates/website-components.js +278 -0
  32. package/dist/generators/templates/website-components.js.map +1 -0
  33. package/dist/generators/templates/website-config.d.ts +41 -0
  34. package/dist/generators/templates/website-config.d.ts.map +1 -0
  35. package/dist/generators/templates/website-config.js +283 -0
  36. package/dist/generators/templates/website-config.js.map +1 -0
  37. package/dist/generators/templates/website-conversion.d.ts +27 -0
  38. package/dist/generators/templates/website-conversion.d.ts.map +1 -0
  39. package/dist/generators/templates/website-conversion.js +326 -0
  40. package/dist/generators/templates/website-conversion.js.map +1 -0
  41. package/dist/generators/templates/website-seo.d.ts +76 -0
  42. package/dist/generators/templates/website-seo.d.ts.map +1 -0
  43. package/dist/generators/templates/website-seo.js +326 -0
  44. package/dist/generators/templates/website-seo.js.map +1 -0
  45. package/dist/generators/templates/website.d.ts +14 -47
  46. package/dist/generators/templates/website.d.ts.map +1 -1
  47. package/dist/generators/templates/website.js +412 -499
  48. package/dist/generators/templates/website.js.map +1 -1
  49. package/dist/generators/website-context.d.ts +83 -0
  50. package/dist/generators/website-context.d.ts.map +1 -0
  51. package/dist/generators/website-context.js +190 -0
  52. package/dist/generators/website-context.js.map +1 -0
  53. package/dist/generators/website.d.ts +3 -0
  54. package/dist/generators/website.d.ts.map +1 -1
  55. package/dist/generators/website.js +73 -10
  56. package/dist/generators/website.js.map +1 -1
  57. package/dist/state/index.d.ts +27 -0
  58. package/dist/state/index.d.ts.map +1 -1
  59. package/dist/state/index.js +30 -0
  60. package/dist/state/index.js.map +1 -1
  61. package/dist/types/consensus.d.ts +3 -0
  62. package/dist/types/consensus.d.ts.map +1 -1
  63. package/dist/types/consensus.js +1 -0
  64. package/dist/types/consensus.js.map +1 -1
  65. package/dist/types/website-strategy.d.ts +263 -0
  66. package/dist/types/website-strategy.d.ts.map +1 -0
  67. package/dist/types/website-strategy.js +105 -0
  68. package/dist/types/website-strategy.js.map +1 -0
  69. package/dist/types/workflow.d.ts +15 -0
  70. package/dist/types/workflow.d.ts.map +1 -1
  71. package/dist/types/workflow.js +6 -0
  72. package/dist/types/workflow.js.map +1 -1
  73. package/dist/workflow/auto-fix.d.ts +7 -1
  74. package/dist/workflow/auto-fix.d.ts.map +1 -1
  75. package/dist/workflow/auto-fix.js +55 -3
  76. package/dist/workflow/auto-fix.js.map +1 -1
  77. package/dist/workflow/consensus.d.ts.map +1 -1
  78. package/dist/workflow/consensus.js +2 -0
  79. package/dist/workflow/consensus.js.map +1 -1
  80. package/dist/workflow/execution-mode.d.ts.map +1 -1
  81. package/dist/workflow/execution-mode.js +18 -0
  82. package/dist/workflow/execution-mode.js.map +1 -1
  83. package/dist/workflow/index.d.ts +3 -0
  84. package/dist/workflow/index.d.ts.map +1 -1
  85. package/dist/workflow/index.js +25 -0
  86. package/dist/workflow/index.js.map +1 -1
  87. package/dist/workflow/overview.d.ts +89 -0
  88. package/dist/workflow/overview.d.ts.map +1 -0
  89. package/dist/workflow/overview.js +354 -0
  90. package/dist/workflow/overview.js.map +1 -0
  91. package/dist/workflow/plan-mode.d.ts +2 -1
  92. package/dist/workflow/plan-mode.d.ts.map +1 -1
  93. package/dist/workflow/plan-mode.js +83 -5
  94. package/dist/workflow/plan-mode.js.map +1 -1
  95. package/dist/workflow/website-strategy.d.ts +70 -0
  96. package/dist/workflow/website-strategy.d.ts.map +1 -0
  97. package/dist/workflow/website-strategy.js +238 -0
  98. package/dist/workflow/website-strategy.js.map +1 -0
  99. package/dist/workflow/website-updater.d.ts +17 -0
  100. package/dist/workflow/website-updater.d.ts.map +1 -0
  101. package/dist/workflow/website-updater.js +105 -0
  102. package/dist/workflow/website-updater.js.map +1 -0
  103. package/dist/workflow/workflow-logger.d.ts +1 -1
  104. package/dist/workflow/workflow-logger.d.ts.map +1 -1
  105. package/dist/workflow/workflow-logger.js.map +1 -1
  106. package/package.json +1 -1
  107. package/src/adapters/gemini.ts +10 -4
  108. package/src/adapters/grok.ts +10 -4
  109. package/src/adapters/openai.ts +38 -6
  110. package/src/cli/interactive.ts +47 -0
  111. package/src/generators/all.ts +6 -1
  112. package/src/generators/doc-parser.ts +372 -0
  113. package/src/generators/templates/index.ts +4 -0
  114. package/src/generators/templates/website-components.ts +305 -0
  115. package/src/generators/templates/website-config.ts +291 -0
  116. package/src/generators/templates/website-conversion.ts +341 -0
  117. package/src/generators/templates/website-seo.ts +370 -0
  118. package/src/generators/templates/website.ts +451 -505
  119. package/src/generators/website-context.ts +265 -0
  120. package/src/generators/website.ts +109 -19
  121. package/src/state/index.ts +42 -0
  122. package/src/types/consensus.ts +3 -0
  123. package/src/types/website-strategy.ts +243 -0
  124. package/src/types/workflow.ts +15 -0
  125. package/src/workflow/auto-fix.ts +57 -3
  126. package/src/workflow/consensus.ts +2 -0
  127. package/src/workflow/execution-mode.ts +21 -0
  128. package/src/workflow/index.ts +25 -0
  129. package/src/workflow/overview.ts +469 -0
  130. package/src/workflow/plan-mode.ts +115 -4
  131. package/src/workflow/website-strategy.ts +305 -0
  132. package/src/workflow/website-updater.ts +131 -0
  133. package/src/workflow/workflow-logger.ts +1 -0
  134. package/tests/adapters/persona-switching.test.ts +63 -0
  135. package/tests/generators/website-components.test.ts +159 -0
  136. package/tests/generators/website-context.test.ts +222 -0
  137. package/tests/generators/website-seo-quality.test.ts +246 -0
  138. package/tests/workflow/auto-fix-enhanced.test.ts +61 -1
  139. package/tests/workflow/overview.test.ts +392 -0
  140. package/tests/workflow/website-strategy.test.ts +191 -0
@@ -1,184 +1,26 @@
1
1
  /**
2
- * Website templates for Next.js marketing sites
3
- * Generates SEO-ready Next.js App Router projects
2
+ * Website content templates for Next.js marketing sites
3
+ * Generates SEO-ready content pages with optional project context
4
+ * and strategy-driven marketing content
4
5
  */
5
- /**
6
- * Generate Next.js package.json
7
- */
8
- export function generateWebsitePackageJson(projectName) {
9
- return `{
10
- "name": "${projectName}-website",
11
- "version": "1.0.0",
12
- "private": true,
13
- "scripts": {
14
- "dev": "next dev -p 3001",
15
- "build": "next build",
16
- "start": "next start -p 3001",
17
- "lint": "next lint",
18
- "test": "vitest run",
19
- "test:watch": "vitest",
20
- "typecheck": "tsc --noEmit"
21
- },
22
- "dependencies": {
23
- "next": "^14.1.0",
24
- "react": "^18.2.0",
25
- "react-dom": "^18.2.0",
26
- "lucide-react": "^0.312.0",
27
- "clsx": "^2.1.0",
28
- "tailwind-merge": "^2.2.0"
29
- },
30
- "devDependencies": {
31
- "@types/node": "^20.11.0",
32
- "@types/react": "^18.2.0",
33
- "@types/react-dom": "^18.2.0",
34
- "autoprefixer": "^10.4.17",
35
- "postcss": "^8.4.33",
36
- "tailwindcss": "^3.4.1",
37
- "typescript": "^5.3.3",
38
- "@testing-library/react": "^14.1.2",
39
- "@vitejs/plugin-react": "^4.2.1",
40
- "vitest": "^1.2.0",
41
- "jsdom": "^24.0.0"
42
- }
43
- }
44
- `;
45
- }
46
- /**
47
- * Generate Next.js config
48
- */
49
- export function generateNextConfig() {
50
- return `/** @type {import('next').NextConfig} */
51
- const nextConfig = {
52
- // Enable React Strict Mode for better development
53
- reactStrictMode: true,
54
-
55
- // Image optimization
56
- images: {
57
- domains: [],
58
- formats: ['image/avif', 'image/webp'],
59
- },
60
-
61
- // Disable x-powered-by header
62
- poweredByHeader: false,
63
-
64
- // Trailing slash config
65
- trailingSlash: false,
66
-
67
- // Headers for security
68
- async headers() {
69
- return [
70
- {
71
- source: '/:path*',
72
- headers: [
73
- {
74
- key: 'X-DNS-Prefetch-Control',
75
- value: 'on',
76
- },
77
- {
78
- key: 'X-Content-Type-Options',
79
- value: 'nosniff',
80
- },
81
- ],
82
- },
83
- ];
84
- },
85
- };
86
-
87
- module.exports = nextConfig;
88
- `;
89
- }
90
- /**
91
- * Generate website tsconfig.json
92
- */
93
- export function generateWebsiteTsconfig() {
94
- return `{
95
- "compilerOptions": {
96
- "target": "ES2017",
97
- "lib": ["dom", "dom.iterable", "esnext"],
98
- "allowJs": true,
99
- "skipLibCheck": true,
100
- "strict": true,
101
- "noEmit": true,
102
- "esModuleInterop": true,
103
- "module": "esnext",
104
- "moduleResolution": "bundler",
105
- "resolveJsonModule": true,
106
- "isolatedModules": true,
107
- "jsx": "preserve",
108
- "incremental": true,
109
- "plugins": [
110
- {
111
- "name": "next"
112
- }
113
- ],
114
- "paths": {
115
- "@/*": ["./src/*"]
116
- }
117
- },
118
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
119
- "exclude": ["node_modules"]
120
- }
121
- `;
122
- }
123
- /**
124
- * Generate Tailwind config for website
125
- */
126
- export function generateWebsiteTailwindConfig() {
127
- return `import type { Config } from 'tailwindcss';
128
-
129
- const config: Config = {
130
- content: [
131
- './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
132
- './src/components/**/*.{js,ts,jsx,tsx,mdx}',
133
- './src/app/**/*.{js,ts,jsx,tsx,mdx}',
134
- ],
135
- theme: {
136
- extend: {
137
- colors: {
138
- primary: {
139
- 50: '#f0f9ff',
140
- 100: '#e0f2fe',
141
- 200: '#bae6fd',
142
- 300: '#7dd3fc',
143
- 400: '#38bdf8',
144
- 500: '#0ea5e9',
145
- 600: '#0284c7',
146
- 700: '#0369a1',
147
- 800: '#075985',
148
- 900: '#0c4a6e',
149
- },
150
- },
151
- fontFamily: {
152
- sans: ['var(--font-inter)', 'system-ui', 'sans-serif'],
153
- },
154
- },
155
- },
156
- plugins: [],
157
- };
158
-
159
- export default config;
160
- `;
161
- }
162
- /**
163
- * Generate PostCSS config for website
164
- */
165
- export function generateWebsitePostcssConfig() {
166
- return `module.exports = {
167
- plugins: {
168
- tailwindcss: {},
169
- autoprefixer: {},
170
- },
171
- };
172
- `;
173
- }
6
+ // Strategy data is accessed via context.strategy (WebsiteContentContext includes it)
174
7
  /**
175
8
  * Generate root layout.tsx with metadata
176
9
  */
177
- export function generateWebsiteLayout(projectName) {
10
+ export function generateWebsiteLayout(projectName, context) {
178
11
  const title = projectName
179
12
  .split('-')
180
13
  .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
181
14
  .join(' ');
15
+ const strategy = context?.strategy;
16
+ const displayName = context?.productName || title;
17
+ const desc = strategy?.messaging.longDescription
18
+ || context?.description
19
+ || `${displayName} - Your modern web application`;
20
+ // SEO keywords from strategy or defaults
21
+ const keywords = strategy?.seoStrategy.primaryKeywords
22
+ ? strategy.seoStrategy.primaryKeywords.map(k => `'${escapeJsx(k)}'`).join(', ')
23
+ : `'${projectName}', 'web app', 'nextjs'`;
182
24
  return `import type { Metadata } from 'next';
183
25
  import { Inter } from 'next/font/google';
184
26
  import './globals.css';
@@ -188,27 +30,30 @@ const inter = Inter({
188
30
  variable: '--font-inter',
189
31
  });
190
32
 
33
+ const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://${projectName}.com';
34
+
191
35
  export const metadata: Metadata = {
36
+ metadataBase: new URL(BASE_URL),
192
37
  title: {
193
- default: '${title}',
194
- template: '%s | ${title}',
38
+ default: '${escapeJsx(displayName)}',
39
+ template: '%s | ${escapeJsx(displayName)}',
195
40
  },
196
- description: '${title} - Your modern web application',
197
- keywords: ['${projectName}', 'web app', 'nextjs'],
198
- authors: [{ name: '${title} Team' }],
199
- creator: '${title}',
41
+ description: '${escapeJsx(desc)}',
42
+ keywords: [${keywords}],
43
+ authors: [{ name: '${escapeJsx(displayName)} Team' }],
44
+ creator: '${escapeJsx(displayName)}',
200
45
  openGraph: {
201
46
  type: 'website',
202
47
  locale: 'en_US',
203
- url: 'https://${projectName}.com',
204
- siteName: '${title}',
205
- title: '${title}',
206
- description: '${title} - Your modern web application',
48
+ url: BASE_URL,
49
+ siteName: '${escapeJsx(displayName)}',
50
+ title: '${escapeJsx(displayName)}',
51
+ description: '${escapeJsx(desc)}',
207
52
  },
208
53
  twitter: {
209
54
  card: 'summary_large_image',
210
- title: '${title}',
211
- description: '${title} - Your modern web application',
55
+ title: '${escapeJsx(displayName)}',
56
+ description: '${escapeJsx(desc)}',
212
57
  },
213
58
  robots: {
214
59
  index: true,
@@ -232,9 +77,13 @@ export default function RootLayout({
232
77
  `;
233
78
  }
234
79
  /**
235
- * Generate globals.css
80
+ * Generate globals.css with optional brand colors
236
81
  */
237
- export function generateWebsiteGlobalsCss() {
82
+ export function generateWebsiteGlobalsCss(context) {
83
+ // Convert hex to HSL for CSS custom properties if brand color provided
84
+ const primaryHsl = context?.brand?.primaryColor
85
+ ? hexToHslString(context.brand.primaryColor)
86
+ : '199 89% 48%';
238
87
  return `@tailwind base;
239
88
  @tailwind components;
240
89
  @tailwind utilities;
@@ -243,7 +92,7 @@ export function generateWebsiteGlobalsCss() {
243
92
  :root {
244
93
  --background: 0 0% 100%;
245
94
  --foreground: 222.2 84% 4.9%;
246
- --primary: 199 89% 48%;
95
+ --primary: ${primaryHsl};
247
96
  --primary-foreground: 210 40% 98%;
248
97
  }
249
98
 
@@ -260,254 +109,391 @@ export function generateWebsiteGlobalsCss() {
260
109
  `;
261
110
  }
262
111
  /**
263
- * Generate landing page.tsx
112
+ * Generate landing page.tsx with optional context-driven content
113
+ * When strategy is available, uses strategy messaging, trust signals, and CTAs
264
114
  */
265
- export function generateWebsiteLandingPage(projectName) {
115
+ export function generateWebsiteLandingPage(projectName, context) {
266
116
  const title = projectName
267
117
  .split('-')
268
118
  .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
269
119
  .join(' ');
120
+ const strategy = context?.strategy;
121
+ const displayName = context?.productName || title;
122
+ // Strategy-driven or context-driven hero
123
+ const headline = strategy?.messaging.headline || displayName;
124
+ const subheadline = strategy?.messaging.subheadline || '';
125
+ const heroText = strategy?.messaging.longDescription
126
+ ? escapeJsx(strategy.messaging.longDescription)
127
+ : context?.description
128
+ ? escapeJsx(context.description)
129
+ : null;
130
+ const features = context?.features && context.features.length > 0
131
+ ? context.features.slice(0, 6)
132
+ : null;
133
+ // CTAs from strategy or defaults
134
+ const primaryCtaText = strategy?.conversionStrategy.primaryCta.text || 'Get started';
135
+ const primaryCtaHref = strategy?.conversionStrategy.primaryCta.href || '/pricing';
136
+ const secondaryCtaText = strategy?.conversionStrategy.secondaryCta.text || 'Learn more';
137
+ const secondaryCtaHref = strategy?.conversionStrategy.secondaryCta.href || '/docs';
138
+ // Build hero paragraph
139
+ const heroParagraph = heroText
140
+ ? ` ${heroText}`
141
+ : ` {/* TODO: populate from project specification */}`;
142
+ // Build features array
143
+ const featuresBlock = features
144
+ ? features.map((f) => ` {\n title: '${escapeJsx(f.title)}',\n description: '${escapeJsx(f.description)}',\n }`).join(',\n')
145
+ : ` {\n title: 'Feature 1',\n description: '/* TODO: populate from project specification */',\n },\n {\n title: 'Feature 2',\n description: '/* TODO: populate from project specification */',\n },\n {\n title: 'Feature 3',\n description: '/* TODO: populate from project specification */',\n }`;
146
+ // Trust signals from strategy
147
+ const trustSignals = strategy?.conversionStrategy.trustSignals || [];
148
+ const trustSignalsBlock = trustSignals.length > 0
149
+ ? trustSignals.map(s => ` '${escapeJsx(s)}'`).join(',\n')
150
+ : '';
151
+ // Social proof from strategy
152
+ const socialProof = strategy?.conversionStrategy.socialProof || [];
153
+ const socialProofBlock = socialProof.length > 0
154
+ ? socialProof.map(s => ` '${escapeJsx(s)}'`).join(',\n')
155
+ : '';
156
+ // Build optional sections
157
+ const trustSection = trustSignals.length > 0 ? `
158
+ {/* Trust Signals */}
159
+ <section className="border-y border-gray-100 bg-gray-50 py-12">
160
+ <div className="container">
161
+ <div className="flex flex-wrap items-center justify-center gap-x-8 gap-y-4">
162
+ {[
163
+ ${trustSignalsBlock}
164
+ ].map((signal) => (
165
+ <p key={signal} className="text-sm font-medium text-gray-600">{signal}</p>
166
+ ))}
167
+ </div>
168
+ </div>
169
+ </section>
170
+ ` : '';
171
+ const socialProofSection = socialProof.length > 0 ? `
172
+ {/* Social Proof */}
173
+ <section className="py-16 sm:py-24">
174
+ <div className="container">
175
+ <h2 className="text-center text-3xl font-bold tracking-tight text-gray-900">
176
+ Trusted by teams everywhere
177
+ </h2>
178
+ <div className="mx-auto mt-12 grid max-w-4xl grid-cols-1 gap-8 md:grid-cols-2">
179
+ {[
180
+ ${socialProofBlock}
181
+ ].map((quote, i) => (
182
+ <blockquote key={i} className="rounded-2xl border border-gray-200 p-6">
183
+ <p className="text-gray-700">&ldquo;{quote}&rdquo;</p>
184
+ </blockquote>
185
+ ))}
186
+ </div>
187
+ </div>
188
+ </section>
189
+ ` : '';
190
+ // Metadata: strategy-driven or default
191
+ const metaTitle = strategy?.seoStrategy.titleTemplates?.home || 'Welcome';
192
+ const metaDesc = strategy?.seoStrategy.metaDescriptions?.home || `Welcome to ${displayName}`;
270
193
  return `import type { Metadata } from 'next';
271
194
  import Link from 'next/link';
195
+ import Header from '@/components/Header';
196
+ import Footer from '@/components/Footer';
197
+ import JsonLd from '@/components/JsonLd';
272
198
 
273
199
  export const metadata: Metadata = {
274
- title: 'Welcome',
275
- description: 'Welcome to ${title} - Your modern web application',
200
+ title: '${escapeJsx(metaTitle)}',
201
+ description: '${escapeJsx(metaDesc)}',
202
+ };
203
+
204
+ const ORG_SCHEMA = {
205
+ '@context': 'https://schema.org',
206
+ '@type': 'Organization',
207
+ name: '${escapeJsx(displayName)}',
208
+ url: process.env.NEXT_PUBLIC_SITE_URL || 'https://${projectName}.com',
209
+ };
210
+
211
+ const PRODUCT_SCHEMA = {
212
+ '@context': 'https://schema.org',
213
+ '@type': 'SoftwareApplication',
214
+ name: '${escapeJsx(displayName)}',
215
+ applicationCategory: 'BusinessApplication',
216
+ operatingSystem: 'Web',
276
217
  };
277
218
 
278
219
  export default function HomePage() {
279
220
  return (
280
- <main className="flex min-h-screen flex-col">
281
- {/* Hero Section */}
282
- <section className="relative overflow-hidden bg-gradient-to-b from-primary-50 to-white py-20 sm:py-32">
283
- <div className="container">
284
- <div className="mx-auto max-w-2xl text-center">
285
- <h1 className="text-4xl font-bold tracking-tight text-gray-900 sm:text-6xl">
286
- ${title}
287
- </h1>
288
- <p className="mt-6 text-lg leading-8 text-gray-600">
289
- Build something amazing with our modern, scalable platform.
290
- Get started today and see the difference.
291
- </p>
292
- <div className="mt-10 flex items-center justify-center gap-x-6">
293
- <Link
294
- href="/pricing"
295
- className="rounded-md bg-primary-600 px-6 py-3 text-sm font-semibold text-white shadow-sm hover:bg-primary-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600"
296
- >
297
- Get started
298
- </Link>
299
- <Link
300
- href="/docs"
301
- className="text-sm font-semibold leading-6 text-gray-900 hover:text-primary-600"
302
- >
303
- Learn more <span aria-hidden="true">-&gt;</span>
304
- </Link>
221
+ <>
222
+ <Header />
223
+ <JsonLd schema={ORG_SCHEMA} />
224
+ <JsonLd schema={PRODUCT_SCHEMA} />
225
+ <main className="flex min-h-screen flex-col">
226
+ {/* Hero Section */}
227
+ <section className="relative overflow-hidden bg-gradient-to-b from-primary-50 to-white py-20 sm:py-32">
228
+ <div className="container">
229
+ <div className="mx-auto max-w-2xl text-center">
230
+ <h1 className="text-4xl font-bold tracking-tight text-gray-900 sm:text-6xl">
231
+ ${escapeJsx(headline)}
232
+ </h1>
233
+ ${subheadline ? ` <p className="mt-4 text-xl font-medium text-primary-600">\n ${escapeJsx(subheadline)}\n </p>` : ''}
234
+ <p className="mt-6 text-lg leading-8 text-gray-600">
235
+ ${heroParagraph}
236
+ </p>
237
+ <div className="mt-10 flex items-center justify-center gap-x-6">
238
+ <Link
239
+ href="${escapeJsx(primaryCtaHref)}"
240
+ className="rounded-md bg-primary-600 px-6 py-3 text-sm font-semibold text-white shadow-sm hover:bg-primary-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600"
241
+ >
242
+ ${escapeJsx(primaryCtaText)}
243
+ </Link>
244
+ <Link
245
+ href="${escapeJsx(secondaryCtaHref)}"
246
+ className="text-sm font-semibold leading-6 text-gray-900 hover:text-primary-600"
247
+ >
248
+ ${escapeJsx(secondaryCtaText)} <span aria-hidden="true">-&gt;</span>
249
+ </Link>
250
+ </div>
305
251
  </div>
306
252
  </div>
307
- </div>
308
- </section>
309
-
310
- {/* Features Section */}
311
- <section className="py-20 sm:py-32">
312
- <div className="container">
313
- <div className="mx-auto max-w-2xl text-center">
314
- <h2 className="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">
315
- Everything you need
253
+ </section>
254
+ ${trustSection}
255
+ {/* Features Section */}
256
+ <section id="features" className="py-20 sm:py-32">
257
+ <div className="container">
258
+ <div className="mx-auto max-w-2xl text-center">
259
+ <h2 className="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">
260
+ Everything you need
261
+ </h2>
262
+ <p className="mt-4 text-lg text-gray-600">
263
+ {/* TODO: populate section subtitle from project specification */}
264
+ </p>
265
+ </div>
266
+ <div className="mx-auto mt-16 max-w-5xl">
267
+ <div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
268
+ {[
269
+ ${featuresBlock}
270
+ ].map((feature) => (
271
+ <div
272
+ key={feature.title}
273
+ className="rounded-2xl border border-gray-200 p-8"
274
+ >
275
+ <h3 className="text-lg font-semibold text-gray-900">
276
+ {feature.title}
277
+ </h3>
278
+ <p className="mt-2 text-gray-600">{feature.description}</p>
279
+ </div>
280
+ ))}
281
+ </div>
282
+ </div>
283
+ </div>
284
+ </section>
285
+ ${socialProofSection}
286
+ {/* CTA Section */}
287
+ <section className="bg-primary-600 py-16 sm:py-24">
288
+ <div className="container text-center">
289
+ <h2 className="text-3xl font-bold tracking-tight text-white sm:text-4xl">
290
+ Ready to get started?
316
291
  </h2>
317
- <p className="mt-4 text-lg text-gray-600">
318
- All the features you need to build amazing products.
292
+ <p className="mt-4 text-lg text-primary-100">
293
+ ${strategy?.messaging.elevatorPitch ? escapeJsx(strategy.messaging.elevatorPitch) : 'Start building today.'}
319
294
  </p>
320
- </div>
321
- <div className="mx-auto mt-16 max-w-5xl">
322
- <div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
323
- {[
324
- {
325
- title: 'Fast',
326
- description: 'Optimized for speed and performance.',
327
- },
328
- {
329
- title: 'Secure',
330
- description: 'Built with security best practices.',
331
- },
332
- {
333
- title: 'Scalable',
334
- description: 'Grows with your business needs.',
335
- },
336
- ].map((feature) => (
337
- <div
338
- key={feature.title}
339
- className="rounded-2xl border border-gray-200 p-8"
340
- >
341
- <h3 className="text-lg font-semibold text-gray-900">
342
- {feature.title}
343
- </h3>
344
- <p className="mt-2 text-gray-600">{feature.description}</p>
345
- </div>
346
- ))}
295
+ <div className="mt-8">
296
+ <Link
297
+ href="${escapeJsx(primaryCtaHref)}"
298
+ className="rounded-md bg-white px-6 py-3 text-sm font-semibold text-primary-600 shadow-sm hover:bg-primary-50"
299
+ >
300
+ ${escapeJsx(primaryCtaText)}
301
+ </Link>
347
302
  </div>
348
303
  </div>
349
- </div>
350
- </section>
351
-
352
- {/* Footer */}
353
- <footer className="border-t border-gray-200 py-12">
354
- <div className="container">
355
- <p className="text-center text-sm text-gray-500">
356
- &copy; {new Date().getFullYear()} ${title}. All rights reserved.
357
- </p>
358
- </div>
359
- </footer>
360
- </main>
304
+ </section>
305
+ </main>
306
+ <Footer />
307
+ </>
361
308
  );
362
309
  }
363
310
  `;
364
311
  }
365
312
  /**
366
- * Generate pricing page
313
+ * Generate pricing page with optional context-driven tiers and FAQ
367
314
  */
368
- export function generateWebsitePricingPage(projectName) {
315
+ export function generateWebsitePricingPage(projectName, context) {
369
316
  const title = projectName
370
317
  .split('-')
371
318
  .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
372
319
  .join(' ');
373
- return `import type { Metadata } from 'next';
374
-
375
- export const metadata: Metadata = {
376
- title: 'Pricing',
377
- description: 'Choose the perfect plan for your needs - ${title}',
378
- };
379
-
380
- const tiers = [
381
- {
382
- name: 'Free',
383
- price: '$0',
384
- description: 'Perfect for getting started',
385
- features: ['Up to 3 projects', 'Basic support', 'Community access'],
320
+ const strategy = context?.strategy;
321
+ const displayName = context?.productName || title;
322
+ const tiers = context?.pricing && context.pricing.length > 0
323
+ ? context.pricing
324
+ : null;
325
+ // Build tiers array
326
+ const tiersBlock = tiers
327
+ ? tiers.map((t) => {
328
+ const featuresStr = t.features.map((f) => ` '${escapeJsx(f)}'`).join(',\n');
329
+ return ` {
330
+ name: '${escapeJsx(t.name)}',
331
+ price: '${escapeJsx(t.price)}',
332
+ description: '${escapeJsx(t.description)}',
333
+ features: [
334
+ ${featuresStr}
335
+ ],
336
+ cta: '${escapeJsx(t.cta)}',
337
+ featured: ${t.featured ? 'true' : 'false'},
338
+ }`;
339
+ }).join(',\n')
340
+ : ` {
341
+ name: '/* TODO: tier name */',
342
+ price: '/* TODO */',
343
+ description: '/* TODO: populate from project specification */',
344
+ features: ['/* TODO: populate from project specification */'],
386
345
  cta: 'Get started',
387
346
  featured: false,
388
347
  },
389
348
  {
390
- name: 'Pro',
391
- price: '$29',
392
- description: 'For growing teams',
393
- features: [
394
- 'Unlimited projects',
395
- 'Priority support',
396
- 'Advanced analytics',
397
- 'Custom integrations',
398
- ],
349
+ name: '/* TODO: tier name */',
350
+ price: '/* TODO */',
351
+ description: '/* TODO: populate from project specification */',
352
+ features: ['/* TODO: populate from project specification */'],
399
353
  cta: 'Start free trial',
400
354
  featured: true,
401
355
  },
402
356
  {
403
- name: 'Enterprise',
404
- price: 'Custom',
405
- description: 'For large organizations',
406
- features: [
407
- 'Everything in Pro',
408
- 'Dedicated support',
409
- 'SLA guarantee',
410
- 'Custom contracts',
411
- ],
357
+ name: '/* TODO: tier name */',
358
+ price: '/* TODO */',
359
+ description: '/* TODO: populate from project specification */',
360
+ features: ['/* TODO: populate from project specification */'],
412
361
  cta: 'Contact sales',
413
362
  featured: false,
414
- },
363
+ }`;
364
+ // Pricing metadata from strategy or defaults
365
+ const metaTitle = strategy?.seoStrategy.titleTemplates?.pricing || 'Pricing';
366
+ const metaDesc = strategy?.seoStrategy.metaDescriptions?.pricing || `Choose the perfect plan for your needs - ${displayName}`;
367
+ // Enterprise CTA from strategy
368
+ const enterpriseCtaText = strategy?.conversionStrategy.primaryCta.text || 'Contact Sales';
369
+ return `import type { Metadata } from 'next';
370
+ import Link from 'next/link';
371
+ import Header from '@/components/Header';
372
+ import Footer from '@/components/Footer';
373
+
374
+ export const metadata: Metadata = {
375
+ title: '${escapeJsx(metaTitle)}',
376
+ description: '${escapeJsx(metaDesc)}',
377
+ };
378
+
379
+ const tiers = [
380
+ ${tiersBlock}
415
381
  ];
416
382
 
417
383
  export default function PricingPage() {
418
384
  return (
419
- <main className="py-20 sm:py-32">
420
- <div className="container">
421
- <div className="mx-auto max-w-2xl text-center">
422
- <h1 className="text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl">
423
- Simple, transparent pricing
424
- </h1>
425
- <p className="mt-6 text-lg text-gray-600">
426
- Choose the plan that works best for you.
427
- </p>
428
- </div>
385
+ <>
386
+ <Header />
387
+ <main className="py-20 sm:py-32">
388
+ <div className="container">
389
+ <div className="mx-auto max-w-2xl text-center">
390
+ <h1 className="text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl">
391
+ Simple, transparent pricing
392
+ </h1>
393
+ <p className="mt-6 text-lg text-gray-600">
394
+ Choose the plan that works best for you.
395
+ </p>
396
+ </div>
429
397
 
430
- <div className="mx-auto mt-16 grid max-w-lg grid-cols-1 gap-8 lg:max-w-5xl lg:grid-cols-3">
431
- {tiers.map((tier) => (
432
- <div
433
- key={tier.name}
434
- className={\`rounded-2xl p-8 \${
435
- tier.featured
436
- ? 'bg-primary-600 text-white ring-2 ring-primary-600'
437
- : 'border border-gray-200 bg-white'
438
- }\`}
439
- >
440
- <h2
441
- className={\`text-lg font-semibold \${
442
- tier.featured ? 'text-white' : 'text-gray-900'
443
- }\`}
444
- >
445
- {tier.name}
446
- </h2>
447
- <p
448
- className={\`mt-2 text-sm \${
449
- tier.featured ? 'text-primary-100' : 'text-gray-600'
398
+ <div className="mx-auto mt-16 grid max-w-lg grid-cols-1 gap-8 lg:max-w-5xl lg:grid-cols-3">
399
+ {tiers.map((tier) => (
400
+ <div
401
+ key={tier.name}
402
+ className={\`rounded-2xl p-8 \${
403
+ tier.featured
404
+ ? 'bg-primary-600 text-white ring-2 ring-primary-600'
405
+ : 'border border-gray-200 bg-white'
450
406
  }\`}
451
407
  >
452
- {tier.description}
453
- </p>
454
- <p className="mt-6">
455
- <span
456
- className={\`text-4xl font-bold \${
408
+ <h2
409
+ className={\`text-lg font-semibold \${
457
410
  tier.featured ? 'text-white' : 'text-gray-900'
458
411
  }\`}
459
412
  >
460
- {tier.price}
461
- </span>
462
- {tier.price !== 'Custom' && (
413
+ {tier.name}
414
+ </h2>
415
+ <p
416
+ className={\`mt-2 text-sm \${
417
+ tier.featured ? 'text-primary-100' : 'text-gray-600'
418
+ }\`}
419
+ >
420
+ {tier.description}
421
+ </p>
422
+ <p className="mt-6">
463
423
  <span
464
- className={\`text-sm \${
465
- tier.featured ? 'text-primary-100' : 'text-gray-600'
424
+ className={\`text-4xl font-bold \${
425
+ tier.featured ? 'text-white' : 'text-gray-900'
466
426
  }\`}
467
427
  >
468
- /month
428
+ {tier.price}
469
429
  </span>
470
- )}
471
- </p>
472
- <ul className="mt-8 space-y-4">
473
- {tier.features.map((feature) => (
474
- <li
475
- key={feature}
476
- className={\`flex text-sm \${
477
- tier.featured ? 'text-primary-100' : 'text-gray-600'
478
- }\`}
479
- >
480
- <svg
481
- className={\`h-5 w-5 flex-shrink-0 \${
482
- tier.featured ? 'text-white' : 'text-primary-600'
430
+ {tier.price !== 'Custom' && (
431
+ <span
432
+ className={\`text-sm \${
433
+ tier.featured ? 'text-primary-100' : 'text-gray-600'
483
434
  }\`}
484
- viewBox="0 0 20 20"
485
- fill="currentColor"
486
435
  >
487
- <path
488
- fillRule="evenodd"
489
- d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
490
- clipRule="evenodd"
491
- />
492
- </svg>
493
- <span className="ml-3">{feature}</span>
494
- </li>
495
- ))}
496
- </ul>
497
- <button
498
- className={\`mt-8 w-full rounded-md px-4 py-2 text-sm font-semibold \${
499
- tier.featured
500
- ? 'bg-white text-primary-600 hover:bg-primary-50'
501
- : 'bg-primary-600 text-white hover:bg-primary-500'
502
- }\`}
503
- >
504
- {tier.cta}
505
- </button>
506
- </div>
507
- ))}
436
+ /month
437
+ </span>
438
+ )}
439
+ </p>
440
+ <ul className="mt-8 space-y-4">
441
+ {tier.features.map((feature) => (
442
+ <li
443
+ key={feature}
444
+ className={\`flex text-sm \${
445
+ tier.featured ? 'text-primary-100' : 'text-gray-600'
446
+ }\`}
447
+ >
448
+ <svg
449
+ className={\`h-5 w-5 flex-shrink-0 \${
450
+ tier.featured ? 'text-white' : 'text-primary-600'
451
+ }\`}
452
+ viewBox="0 0 20 20"
453
+ fill="currentColor"
454
+ >
455
+ <path
456
+ fillRule="evenodd"
457
+ d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
458
+ clipRule="evenodd"
459
+ />
460
+ </svg>
461
+ <span className="ml-3">{feature}</span>
462
+ </li>
463
+ ))}
464
+ </ul>
465
+ <button
466
+ className={\`mt-8 w-full rounded-md px-4 py-2 text-sm font-semibold \${
467
+ tier.featured
468
+ ? 'bg-white text-primary-600 hover:bg-primary-50'
469
+ : 'bg-primary-600 text-white hover:bg-primary-500'
470
+ }\`}
471
+ >
472
+ {tier.cta}
473
+ </button>
474
+ </div>
475
+ ))}
476
+ </div>
477
+
478
+ {/* Enterprise CTA */}
479
+ <div className="mx-auto mt-16 max-w-2xl text-center">
480
+ <h2 className="text-2xl font-bold text-gray-900">
481
+ Need a custom plan?
482
+ </h2>
483
+ <p className="mt-4 text-gray-600">
484
+ Contact our sales team for enterprise pricing and custom solutions.
485
+ </p>
486
+ <Link
487
+ href="/contact"
488
+ className="mt-6 inline-block rounded-md border border-primary-600 px-6 py-3 text-sm font-semibold text-primary-600 hover:bg-primary-50"
489
+ >
490
+ ${escapeJsx(enterpriseCtaText)}
491
+ </Link>
492
+ </div>
508
493
  </div>
509
- </div>
510
- </main>
494
+ </main>
495
+ <Footer />
496
+ </>
511
497
  );
512
498
  }
513
499
  `;
@@ -572,54 +558,6 @@ export default function robots(): MetadataRoute.Robots {
572
558
  }
573
559
  `;
574
560
  }
575
- /**
576
- * Generate website Dockerfile
577
- */
578
- export function generateWebsiteDockerfile() {
579
- return `# Build stage
580
- FROM node:20-alpine AS builder
581
-
582
- WORKDIR /app
583
-
584
- # Copy package files
585
- COPY package*.json ./
586
-
587
- # Install dependencies
588
- RUN npm ci
589
-
590
- # Copy source
591
- COPY . .
592
-
593
- # Build
594
- RUN npm run build
595
-
596
- # Production stage
597
- FROM node:20-alpine AS runner
598
-
599
- WORKDIR /app
600
-
601
- ENV NODE_ENV=production
602
- ENV NEXT_TELEMETRY_DISABLED=1
603
-
604
- # Create non-root user
605
- RUN addgroup --system --gid 1001 nodejs
606
- RUN adduser --system --uid 1001 nextjs
607
-
608
- # Copy built assets
609
- COPY --from=builder /app/public ./public
610
- COPY --from=builder /app/.next/standalone ./
611
- COPY --from=builder /app/.next/static ./.next/static
612
-
613
- USER nextjs
614
-
615
- EXPOSE 3000
616
-
617
- ENV PORT=3000
618
- ENV HOSTNAME="0.0.0.0"
619
-
620
- CMD ["node", "server.js"]
621
- `;
622
- }
623
561
  /**
624
562
  * Generate website README
625
563
  */
@@ -682,20 +620,23 @@ content/
682
620
  `;
683
621
  }
684
622
  /**
685
- * Generate website spec JSON
623
+ * Generate website spec JSON with optional context
686
624
  */
687
- export function generateWebsiteSpec(projectName) {
625
+ export function generateWebsiteSpec(projectName, context) {
688
626
  const title = projectName
689
627
  .split('-')
690
628
  .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
691
629
  .join(' ');
630
+ const displayName = context?.productName || title;
631
+ const tagline = context?.tagline || context?.description || 'Build something amazing';
632
+ const primaryColor = context?.brand?.primaryColor || '#0ea5e9';
692
633
  return JSON.stringify({
693
634
  version: '1.0',
694
635
  brand: {
695
- name: title,
696
- tagline: 'Build something amazing',
636
+ name: displayName,
637
+ tagline,
697
638
  colors: {
698
- primary: '#0ea5e9',
639
+ primary: primaryColor,
699
640
  secondary: '#64748b',
700
641
  accent: '#f59e0b',
701
642
  background: '#ffffff',
@@ -707,8 +648,8 @@ export function generateWebsiteSpec(projectName) {
707
648
  },
708
649
  },
709
650
  seo: {
710
- title: title,
711
- description: `${title} - Your modern web application`,
651
+ title: displayName,
652
+ description: context?.description || `${displayName} - Your modern web application`,
712
653
  keywords: [projectName, 'web app', 'nextjs', 'saas'],
713
654
  locale: 'en_US',
714
655
  },
@@ -730,56 +671,6 @@ export function generateWebsiteSpec(projectName) {
730
671
  },
731
672
  }, null, 2);
732
673
  }
733
- /**
734
- * Generate vitest config for website
735
- */
736
- export function generateWebsiteVitestConfig() {
737
- return `import { defineConfig } from 'vitest/config';
738
- import react from '@vitejs/plugin-react';
739
- import path from 'path';
740
-
741
- export default defineConfig({
742
- plugins: [react()],
743
- test: {
744
- environment: 'jsdom',
745
- include: ['**/*.test.{ts,tsx}'],
746
- globals: true,
747
- setupFiles: ['./tests/setup.ts'],
748
- },
749
- resolve: {
750
- alias: {
751
- '@': path.resolve(__dirname, './src'),
752
- },
753
- },
754
- });
755
- `;
756
- }
757
- /**
758
- * Generate vitest setup for website
759
- */
760
- export function generateWebsiteVitestSetup() {
761
- return `import '@testing-library/jest-dom';
762
-
763
- // Mock next/navigation
764
- vi.mock('next/navigation', () => ({
765
- useRouter: () => ({
766
- push: vi.fn(),
767
- replace: vi.fn(),
768
- prefetch: vi.fn(),
769
- }),
770
- useSearchParams: () => new URLSearchParams(),
771
- usePathname: () => '/',
772
- }));
773
-
774
- // Mock next/image
775
- vi.mock('next/image', () => ({
776
- default: (props: Record<string, unknown>) => {
777
- // eslint-disable-next-line @next/next/no-img-element, jsx-a11y/alt-text
778
- return <img {...props} />;
779
- },
780
- }));
781
- `;
782
- }
783
674
  /**
784
675
  * Generate sample test for website
785
676
  */
@@ -803,13 +694,6 @@ describe('HomePage', () => {
803
694
  expect(screen.getByRole('link', { name: /get started/i })).toBeInTheDocument();
804
695
  expect(screen.getByRole('link', { name: /learn more/i })).toBeInTheDocument();
805
696
  });
806
-
807
- it('renders feature cards', () => {
808
- render(<HomePage />);
809
- expect(screen.getByText('Fast')).toBeInTheDocument();
810
- expect(screen.getByText('Secure')).toBeInTheDocument();
811
- expect(screen.getByText('Scalable')).toBeInTheDocument();
812
- });
813
697
  });
814
698
  `;
815
699
  }
@@ -864,14 +748,43 @@ export default function BlogPage() {
864
748
  `;
865
749
  }
866
750
  /**
867
- * Generate Next.js environment declaration
751
+ * Escape a string for safe use inside JSX template literals
868
752
  */
869
- export function generateWebsiteNextEnv() {
870
- return `/// <reference types="next" />
871
- /// <reference types="next/image-types/global" />
872
-
873
- // NOTE: This file should not be edited
874
- // see https://nextjs.org/docs/basic-features/typescript for more information.
875
- `;
753
+ function escapeJsx(str) {
754
+ return str
755
+ .replace(/\\/g, '\\\\')
756
+ .replace(/'/g, "\\'")
757
+ .replace(/`/g, '\\`')
758
+ .replace(/\$/g, '\\$');
759
+ }
760
+ /**
761
+ * Convert hex color to HSL string for CSS custom properties
762
+ * Returns format: "H S% L%"
763
+ */
764
+ function hexToHslString(hex) {
765
+ // Remove # prefix
766
+ const h = hex.replace('#', '');
767
+ const r = parseInt(h.substring(0, 2), 16) / 255;
768
+ const g = parseInt(h.substring(2, 4), 16) / 255;
769
+ const b = parseInt(h.substring(4, 6), 16) / 255;
770
+ const max = Math.max(r, g, b);
771
+ const min = Math.min(r, g, b);
772
+ const l = (max + min) / 2;
773
+ if (max === min) {
774
+ return `0 0% ${Math.round(l * 100)}%`;
775
+ }
776
+ const d = max - min;
777
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
778
+ let hue = 0;
779
+ if (max === r) {
780
+ hue = ((g - b) / d + (g < b ? 6 : 0)) / 6;
781
+ }
782
+ else if (max === g) {
783
+ hue = ((b - r) / d + 2) / 6;
784
+ }
785
+ else {
786
+ hue = ((r - g) / d + 4) / 6;
787
+ }
788
+ return `${Math.round(hue * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`;
876
789
  }
877
790
  //# sourceMappingURL=website.js.map