popeye-cli 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +4 -1
- package/CONTRIBUTING.md +10 -0
- package/README.md +111 -2
- package/dist/adapters/claude.d.ts +26 -2
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +257 -10
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/grok.d.ts +2 -1
- package/dist/adapters/grok.d.ts.map +1 -1
- package/dist/adapters/grok.js.map +1 -1
- package/dist/adapters/index.d.ts +8 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +12 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/openai.d.ts +2 -2
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js.map +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +25 -5
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +79 -6
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/all.d.ts +40 -0
- package/dist/generators/all.d.ts.map +1 -0
- package/dist/generators/all.js +826 -0
- package/dist/generators/all.js.map +1 -0
- package/dist/generators/fullstack.d.ts +9 -0
- package/dist/generators/fullstack.d.ts.map +1 -1
- package/dist/generators/fullstack.js.map +1 -1
- package/dist/generators/index.d.ts +3 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +33 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/templates/index.d.ts +2 -0
- package/dist/generators/templates/index.d.ts.map +1 -1
- package/dist/generators/templates/index.js +2 -0
- package/dist/generators/templates/index.js.map +1 -1
- package/dist/generators/templates/website.d.ts +85 -0
- package/dist/generators/templates/website.d.ts.map +1 -0
- package/dist/generators/templates/website.js +877 -0
- package/dist/generators/templates/website.js.map +1 -0
- package/dist/generators/website.d.ts +56 -0
- package/dist/generators/website.d.ts.map +1 -0
- package/dist/generators/website.js +269 -0
- package/dist/generators/website.js.map +1 -0
- package/dist/types/consensus.d.ts +8 -3
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -2
- package/dist/types/index.js.map +1 -1
- package/dist/types/project.d.ts +115 -1
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/project.js +41 -1
- package/dist/types/project.js.map +1 -1
- package/dist/types/workflow.d.ts +8 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +2 -2
- package/dist/types/workflow.js.map +1 -1
- package/dist/workflow/consensus.d.ts +2 -1
- package/dist/workflow/consensus.d.ts.map +1 -1
- package/dist/workflow/consensus.js.map +1 -1
- package/dist/workflow/execution-mode.d.ts +2 -0
- package/dist/workflow/execution-mode.d.ts.map +1 -1
- package/dist/workflow/execution-mode.js +20 -0
- package/dist/workflow/execution-mode.js.map +1 -1
- package/dist/workflow/index.d.ts +8 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +19 -0
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/milestone-workflow.d.ts +2 -0
- package/dist/workflow/milestone-workflow.d.ts.map +1 -1
- package/dist/workflow/milestone-workflow.js +17 -0
- package/dist/workflow/milestone-workflow.js.map +1 -1
- package/dist/workflow/plan-mode.d.ts +3 -3
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/plan-parser.d.ts +97 -0
- package/dist/workflow/plan-parser.d.ts.map +1 -0
- package/dist/workflow/plan-parser.js +235 -0
- package/dist/workflow/plan-parser.js.map +1 -0
- package/dist/workflow/plan-storage.d.ts +40 -12
- package/dist/workflow/plan-storage.d.ts.map +1 -1
- package/dist/workflow/plan-storage.js +47 -20
- package/dist/workflow/plan-storage.js.map +1 -1
- package/dist/workflow/seo-tests.d.ts +43 -0
- package/dist/workflow/seo-tests.d.ts.map +1 -0
- package/dist/workflow/seo-tests.js +192 -0
- package/dist/workflow/seo-tests.js.map +1 -0
- package/dist/workflow/separation-guard.d.ts +35 -0
- package/dist/workflow/separation-guard.d.ts.map +1 -0
- package/dist/workflow/separation-guard.js +154 -0
- package/dist/workflow/separation-guard.js.map +1 -0
- package/dist/workflow/task-workflow.d.ts +2 -0
- package/dist/workflow/task-workflow.d.ts.map +1 -1
- package/dist/workflow/task-workflow.js +19 -0
- package/dist/workflow/task-workflow.js.map +1 -1
- package/dist/workflow/test-runner.d.ts.map +1 -1
- package/dist/workflow/test-runner.js +128 -0
- package/dist/workflow/test-runner.js.map +1 -1
- package/dist/workflow/workspace-manager.d.ts +31 -20
- package/dist/workflow/workspace-manager.d.ts.map +1 -1
- package/dist/workflow/workspace-manager.js +38 -9
- package/dist/workflow/workspace-manager.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/claude.ts +289 -14
- package/src/adapters/grok.ts +2 -1
- package/src/adapters/index.ts +15 -0
- package/src/adapters/openai.ts +2 -2
- package/src/cli/commands/create.ts +25 -5
- package/src/cli/interactive.ts +76 -6
- package/src/generators/all.ts +897 -0
- package/src/generators/fullstack.ts +10 -0
- package/src/generators/index.ts +54 -0
- package/src/generators/templates/index.ts +2 -0
- package/src/generators/templates/website.ts +906 -0
- package/src/generators/website.ts +350 -0
- package/src/types/consensus.ts +9 -3
- package/src/types/index.ts +33 -0
- package/src/types/project.ts +139 -2
- package/src/types/workflow.ts +2 -2
- package/src/workflow/consensus.ts +3 -2
- package/src/workflow/execution-mode.ts +32 -0
- package/src/workflow/index.ts +20 -0
- package/src/workflow/milestone-workflow.ts +22 -0
- package/src/workflow/plan-mode.ts +3 -3
- package/src/workflow/plan-parser.ts +317 -0
- package/src/workflow/plan-storage.ts +69 -30
- package/src/workflow/seo-tests.ts +246 -0
- package/src/workflow/separation-guard.ts +200 -0
- package/src/workflow/task-workflow.ts +25 -0
- package/src/workflow/test-runner.ts +149 -0
- package/src/workflow/workspace-manager.ts +68 -31
|
@@ -0,0 +1,877 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Website templates for Next.js marketing sites
|
|
3
|
+
* Generates SEO-ready Next.js App Router projects
|
|
4
|
+
*/
|
|
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
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Generate root layout.tsx with metadata
|
|
176
|
+
*/
|
|
177
|
+
export function generateWebsiteLayout(projectName) {
|
|
178
|
+
const title = projectName
|
|
179
|
+
.split('-')
|
|
180
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
181
|
+
.join(' ');
|
|
182
|
+
return `import type { Metadata } from 'next';
|
|
183
|
+
import { Inter } from 'next/font/google';
|
|
184
|
+
import './globals.css';
|
|
185
|
+
|
|
186
|
+
const inter = Inter({
|
|
187
|
+
subsets: ['latin'],
|
|
188
|
+
variable: '--font-inter',
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
export const metadata: Metadata = {
|
|
192
|
+
title: {
|
|
193
|
+
default: '${title}',
|
|
194
|
+
template: '%s | ${title}',
|
|
195
|
+
},
|
|
196
|
+
description: '${title} - Your modern web application',
|
|
197
|
+
keywords: ['${projectName}', 'web app', 'nextjs'],
|
|
198
|
+
authors: [{ name: '${title} Team' }],
|
|
199
|
+
creator: '${title}',
|
|
200
|
+
openGraph: {
|
|
201
|
+
type: 'website',
|
|
202
|
+
locale: 'en_US',
|
|
203
|
+
url: 'https://${projectName}.com',
|
|
204
|
+
siteName: '${title}',
|
|
205
|
+
title: '${title}',
|
|
206
|
+
description: '${title} - Your modern web application',
|
|
207
|
+
},
|
|
208
|
+
twitter: {
|
|
209
|
+
card: 'summary_large_image',
|
|
210
|
+
title: '${title}',
|
|
211
|
+
description: '${title} - Your modern web application',
|
|
212
|
+
},
|
|
213
|
+
robots: {
|
|
214
|
+
index: true,
|
|
215
|
+
follow: true,
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export default function RootLayout({
|
|
220
|
+
children,
|
|
221
|
+
}: {
|
|
222
|
+
children: React.ReactNode;
|
|
223
|
+
}) {
|
|
224
|
+
return (
|
|
225
|
+
<html lang="en" className={inter.variable}>
|
|
226
|
+
<body className="min-h-screen bg-white antialiased">
|
|
227
|
+
{children}
|
|
228
|
+
</body>
|
|
229
|
+
</html>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
`;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Generate globals.css
|
|
236
|
+
*/
|
|
237
|
+
export function generateWebsiteGlobalsCss() {
|
|
238
|
+
return `@tailwind base;
|
|
239
|
+
@tailwind components;
|
|
240
|
+
@tailwind utilities;
|
|
241
|
+
|
|
242
|
+
@layer base {
|
|
243
|
+
:root {
|
|
244
|
+
--background: 0 0% 100%;
|
|
245
|
+
--foreground: 222.2 84% 4.9%;
|
|
246
|
+
--primary: 199 89% 48%;
|
|
247
|
+
--primary-foreground: 210 40% 98%;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
body {
|
|
251
|
+
@apply bg-background text-foreground;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
@layer components {
|
|
256
|
+
.container {
|
|
257
|
+
@apply mx-auto max-w-7xl px-4 sm:px-6 lg:px-8;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Generate landing page.tsx
|
|
264
|
+
*/
|
|
265
|
+
export function generateWebsiteLandingPage(projectName) {
|
|
266
|
+
const title = projectName
|
|
267
|
+
.split('-')
|
|
268
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
269
|
+
.join(' ');
|
|
270
|
+
return `import type { Metadata } from 'next';
|
|
271
|
+
import Link from 'next/link';
|
|
272
|
+
|
|
273
|
+
export const metadata: Metadata = {
|
|
274
|
+
title: 'Welcome',
|
|
275
|
+
description: 'Welcome to ${title} - Your modern web application',
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export default function HomePage() {
|
|
279
|
+
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">-></span>
|
|
304
|
+
</Link>
|
|
305
|
+
</div>
|
|
306
|
+
</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
|
|
316
|
+
</h2>
|
|
317
|
+
<p className="mt-4 text-lg text-gray-600">
|
|
318
|
+
All the features you need to build amazing products.
|
|
319
|
+
</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
|
+
))}
|
|
347
|
+
</div>
|
|
348
|
+
</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
|
+
© {new Date().getFullYear()} ${title}. All rights reserved.
|
|
357
|
+
</p>
|
|
358
|
+
</div>
|
|
359
|
+
</footer>
|
|
360
|
+
</main>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
`;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Generate pricing page
|
|
367
|
+
*/
|
|
368
|
+
export function generateWebsitePricingPage(projectName) {
|
|
369
|
+
const title = projectName
|
|
370
|
+
.split('-')
|
|
371
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
372
|
+
.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'],
|
|
386
|
+
cta: 'Get started',
|
|
387
|
+
featured: false,
|
|
388
|
+
},
|
|
389
|
+
{
|
|
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
|
+
],
|
|
399
|
+
cta: 'Start free trial',
|
|
400
|
+
featured: true,
|
|
401
|
+
},
|
|
402
|
+
{
|
|
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
|
+
],
|
|
412
|
+
cta: 'Contact sales',
|
|
413
|
+
featured: false,
|
|
414
|
+
},
|
|
415
|
+
];
|
|
416
|
+
|
|
417
|
+
export default function PricingPage() {
|
|
418
|
+
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>
|
|
429
|
+
|
|
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'
|
|
450
|
+
}\`}
|
|
451
|
+
>
|
|
452
|
+
{tier.description}
|
|
453
|
+
</p>
|
|
454
|
+
<p className="mt-6">
|
|
455
|
+
<span
|
|
456
|
+
className={\`text-4xl font-bold \${
|
|
457
|
+
tier.featured ? 'text-white' : 'text-gray-900'
|
|
458
|
+
}\`}
|
|
459
|
+
>
|
|
460
|
+
{tier.price}
|
|
461
|
+
</span>
|
|
462
|
+
{tier.price !== 'Custom' && (
|
|
463
|
+
<span
|
|
464
|
+
className={\`text-sm \${
|
|
465
|
+
tier.featured ? 'text-primary-100' : 'text-gray-600'
|
|
466
|
+
}\`}
|
|
467
|
+
>
|
|
468
|
+
/month
|
|
469
|
+
</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'
|
|
483
|
+
}\`}
|
|
484
|
+
viewBox="0 0 20 20"
|
|
485
|
+
fill="currentColor"
|
|
486
|
+
>
|
|
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
|
+
))}
|
|
508
|
+
</div>
|
|
509
|
+
</div>
|
|
510
|
+
</main>
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
`;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Generate sitemap.ts
|
|
517
|
+
*/
|
|
518
|
+
export function generateWebsiteSitemap(projectName) {
|
|
519
|
+
return `import { MetadataRoute } from 'next';
|
|
520
|
+
|
|
521
|
+
export default function sitemap(): MetadataRoute.Sitemap {
|
|
522
|
+
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://${projectName}.com';
|
|
523
|
+
|
|
524
|
+
return [
|
|
525
|
+
{
|
|
526
|
+
url: baseUrl,
|
|
527
|
+
lastModified: new Date(),
|
|
528
|
+
changeFrequency: 'weekly',
|
|
529
|
+
priority: 1,
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
url: \`\${baseUrl}/pricing\`,
|
|
533
|
+
lastModified: new Date(),
|
|
534
|
+
changeFrequency: 'monthly',
|
|
535
|
+
priority: 0.8,
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
url: \`\${baseUrl}/docs\`,
|
|
539
|
+
lastModified: new Date(),
|
|
540
|
+
changeFrequency: 'weekly',
|
|
541
|
+
priority: 0.8,
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
url: \`\${baseUrl}/blog\`,
|
|
545
|
+
lastModified: new Date(),
|
|
546
|
+
changeFrequency: 'daily',
|
|
547
|
+
priority: 0.7,
|
|
548
|
+
},
|
|
549
|
+
];
|
|
550
|
+
}
|
|
551
|
+
`;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Generate robots.ts
|
|
555
|
+
*/
|
|
556
|
+
export function generateWebsiteRobots(projectName) {
|
|
557
|
+
return `import { MetadataRoute } from 'next';
|
|
558
|
+
|
|
559
|
+
export default function robots(): MetadataRoute.Robots {
|
|
560
|
+
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://${projectName}.com';
|
|
561
|
+
|
|
562
|
+
return {
|
|
563
|
+
rules: [
|
|
564
|
+
{
|
|
565
|
+
userAgent: '*',
|
|
566
|
+
allow: '/',
|
|
567
|
+
disallow: ['/api/', '/admin/'],
|
|
568
|
+
},
|
|
569
|
+
],
|
|
570
|
+
sitemap: \`\${baseUrl}/sitemap.xml\`,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
`;
|
|
574
|
+
}
|
|
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
|
+
/**
|
|
624
|
+
* Generate website README
|
|
625
|
+
*/
|
|
626
|
+
export function generateWebsiteReadme(projectName) {
|
|
627
|
+
const title = projectName
|
|
628
|
+
.split('-')
|
|
629
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
630
|
+
.join(' ');
|
|
631
|
+
return `# ${title} Website
|
|
632
|
+
|
|
633
|
+
Next.js marketing website with SEO optimization.
|
|
634
|
+
|
|
635
|
+
## Getting Started
|
|
636
|
+
|
|
637
|
+
\`\`\`bash
|
|
638
|
+
# Install dependencies
|
|
639
|
+
npm install
|
|
640
|
+
|
|
641
|
+
# Run development server (port 3001)
|
|
642
|
+
npm run dev
|
|
643
|
+
|
|
644
|
+
# Build for production
|
|
645
|
+
npm run build
|
|
646
|
+
|
|
647
|
+
# Run production server
|
|
648
|
+
npm start
|
|
649
|
+
\`\`\`
|
|
650
|
+
|
|
651
|
+
## SEO Features
|
|
652
|
+
|
|
653
|
+
- Server-side rendering (SSR)
|
|
654
|
+
- Auto-generated sitemap
|
|
655
|
+
- robots.txt configuration
|
|
656
|
+
- OpenGraph and Twitter meta tags
|
|
657
|
+
- Structured data support
|
|
658
|
+
|
|
659
|
+
## Project Structure
|
|
660
|
+
|
|
661
|
+
\`\`\`
|
|
662
|
+
src/
|
|
663
|
+
app/
|
|
664
|
+
layout.tsx # Root layout with metadata
|
|
665
|
+
page.tsx # Landing page
|
|
666
|
+
pricing/ # Pricing page
|
|
667
|
+
docs/ # Documentation
|
|
668
|
+
blog/ # Blog
|
|
669
|
+
sitemap.ts # Auto-generated sitemap
|
|
670
|
+
robots.ts # robots.txt config
|
|
671
|
+
components/ # UI components
|
|
672
|
+
lib/ # Utilities
|
|
673
|
+
content/
|
|
674
|
+
blog/ # MDX blog posts
|
|
675
|
+
docs/ # MDX documentation
|
|
676
|
+
\`\`\`
|
|
677
|
+
|
|
678
|
+
## Development
|
|
679
|
+
|
|
680
|
+
- Port: 3001 (to avoid conflicts with frontend on 5173)
|
|
681
|
+
- API URL: Configure via NEXT_PUBLIC_APP_URL
|
|
682
|
+
`;
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Generate website spec JSON
|
|
686
|
+
*/
|
|
687
|
+
export function generateWebsiteSpec(projectName) {
|
|
688
|
+
const title = projectName
|
|
689
|
+
.split('-')
|
|
690
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
691
|
+
.join(' ');
|
|
692
|
+
return JSON.stringify({
|
|
693
|
+
version: '1.0',
|
|
694
|
+
brand: {
|
|
695
|
+
name: title,
|
|
696
|
+
tagline: 'Build something amazing',
|
|
697
|
+
colors: {
|
|
698
|
+
primary: '#0ea5e9',
|
|
699
|
+
secondary: '#64748b',
|
|
700
|
+
accent: '#f59e0b',
|
|
701
|
+
background: '#ffffff',
|
|
702
|
+
foreground: '#0f172a',
|
|
703
|
+
},
|
|
704
|
+
typography: {
|
|
705
|
+
headingFont: 'Inter',
|
|
706
|
+
bodyFont: 'Inter',
|
|
707
|
+
},
|
|
708
|
+
},
|
|
709
|
+
seo: {
|
|
710
|
+
title: title,
|
|
711
|
+
description: `${title} - Your modern web application`,
|
|
712
|
+
keywords: [projectName, 'web app', 'nextjs', 'saas'],
|
|
713
|
+
locale: 'en_US',
|
|
714
|
+
},
|
|
715
|
+
pages: [
|
|
716
|
+
{ name: 'Home', path: '/', type: 'landing' },
|
|
717
|
+
{ name: 'Pricing', path: '/pricing', type: 'pricing' },
|
|
718
|
+
{ name: 'Documentation', path: '/docs', type: 'docs' },
|
|
719
|
+
{ name: 'Blog', path: '/blog', type: 'blog' },
|
|
720
|
+
],
|
|
721
|
+
cta: {
|
|
722
|
+
primary: { text: 'Get Started', href: '/pricing' },
|
|
723
|
+
secondary: { text: 'Learn More', href: '/docs' },
|
|
724
|
+
},
|
|
725
|
+
features: {
|
|
726
|
+
analytics: true,
|
|
727
|
+
newsletter: false,
|
|
728
|
+
mdxBlog: true,
|
|
729
|
+
docsSearch: false,
|
|
730
|
+
},
|
|
731
|
+
}, null, 2);
|
|
732
|
+
}
|
|
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
|
+
/**
|
|
784
|
+
* Generate sample test for website
|
|
785
|
+
*/
|
|
786
|
+
export function generateWebsiteTest(projectName) {
|
|
787
|
+
const title = projectName
|
|
788
|
+
.split('-')
|
|
789
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
790
|
+
.join(' ');
|
|
791
|
+
return `import { describe, it, expect } from 'vitest';
|
|
792
|
+
import { render, screen } from '@testing-library/react';
|
|
793
|
+
import HomePage from '@/app/page';
|
|
794
|
+
|
|
795
|
+
describe('HomePage', () => {
|
|
796
|
+
it('renders the title', () => {
|
|
797
|
+
render(<HomePage />);
|
|
798
|
+
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('${title}');
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
it('renders the call-to-action buttons', () => {
|
|
802
|
+
render(<HomePage />);
|
|
803
|
+
expect(screen.getByRole('link', { name: /get started/i })).toBeInTheDocument();
|
|
804
|
+
expect(screen.getByRole('link', { name: /learn more/i })).toBeInTheDocument();
|
|
805
|
+
});
|
|
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
|
+
});
|
|
814
|
+
`;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Generate docs page placeholder
|
|
818
|
+
*/
|
|
819
|
+
export function generateWebsiteDocsPage() {
|
|
820
|
+
return `import type { Metadata } from 'next';
|
|
821
|
+
|
|
822
|
+
export const metadata: Metadata = {
|
|
823
|
+
title: 'Documentation',
|
|
824
|
+
description: 'Learn how to use our platform with comprehensive documentation.',
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
export default function DocsPage() {
|
|
828
|
+
return (
|
|
829
|
+
<main className="py-20">
|
|
830
|
+
<div className="container">
|
|
831
|
+
<h1 className="text-4xl font-bold text-gray-900">Documentation</h1>
|
|
832
|
+
<p className="mt-4 text-lg text-gray-600">
|
|
833
|
+
Documentation coming soon...
|
|
834
|
+
</p>
|
|
835
|
+
</div>
|
|
836
|
+
</main>
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
`;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Generate blog listing page placeholder
|
|
843
|
+
*/
|
|
844
|
+
export function generateWebsiteBlogPage() {
|
|
845
|
+
return `import type { Metadata } from 'next';
|
|
846
|
+
|
|
847
|
+
export const metadata: Metadata = {
|
|
848
|
+
title: 'Blog',
|
|
849
|
+
description: 'Latest news, updates, and insights from our team.',
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
export default function BlogPage() {
|
|
853
|
+
return (
|
|
854
|
+
<main className="py-20">
|
|
855
|
+
<div className="container">
|
|
856
|
+
<h1 className="text-4xl font-bold text-gray-900">Blog</h1>
|
|
857
|
+
<p className="mt-4 text-lg text-gray-600">
|
|
858
|
+
Blog posts coming soon...
|
|
859
|
+
</p>
|
|
860
|
+
</div>
|
|
861
|
+
</main>
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
`;
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Generate Next.js environment declaration
|
|
868
|
+
*/
|
|
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
|
+
`;
|
|
876
|
+
}
|
|
877
|
+
//# sourceMappingURL=website.js.map
|