popeye-cli 1.4.7 → 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.
Files changed (214) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +264 -63
  3. package/dist/adapters/gemini.d.ts +1 -0
  4. package/dist/adapters/gemini.d.ts.map +1 -1
  5. package/dist/adapters/gemini.js +9 -4
  6. package/dist/adapters/gemini.js.map +1 -1
  7. package/dist/adapters/grok.d.ts +1 -0
  8. package/dist/adapters/grok.d.ts.map +1 -1
  9. package/dist/adapters/grok.js +9 -4
  10. package/dist/adapters/grok.js.map +1 -1
  11. package/dist/adapters/openai.d.ts +1 -1
  12. package/dist/adapters/openai.d.ts.map +1 -1
  13. package/dist/adapters/openai.js +35 -9
  14. package/dist/adapters/openai.js.map +1 -1
  15. package/dist/cli/commands/create.d.ts.map +1 -1
  16. package/dist/cli/commands/create.js +54 -4
  17. package/dist/cli/commands/create.js.map +1 -1
  18. package/dist/cli/interactive.d.ts +29 -0
  19. package/dist/cli/interactive.d.ts.map +1 -1
  20. package/dist/cli/interactive.js +132 -7
  21. package/dist/cli/interactive.js.map +1 -1
  22. package/dist/generators/all.d.ts +8 -2
  23. package/dist/generators/all.d.ts.map +1 -1
  24. package/dist/generators/all.js +37 -316
  25. package/dist/generators/all.js.map +1 -1
  26. package/dist/generators/doc-parser.d.ts +64 -0
  27. package/dist/generators/doc-parser.d.ts.map +1 -0
  28. package/dist/generators/doc-parser.js +407 -0
  29. package/dist/generators/doc-parser.js.map +1 -0
  30. package/dist/generators/frontend-design-analyzer.d.ts +30 -0
  31. package/dist/generators/frontend-design-analyzer.d.ts.map +1 -0
  32. package/dist/generators/frontend-design-analyzer.js +208 -0
  33. package/dist/generators/frontend-design-analyzer.js.map +1 -0
  34. package/dist/generators/shared-packages.d.ts +45 -0
  35. package/dist/generators/shared-packages.d.ts.map +1 -0
  36. package/dist/generators/shared-packages.js +456 -0
  37. package/dist/generators/shared-packages.js.map +1 -0
  38. package/dist/generators/templates/index.d.ts +8 -0
  39. package/dist/generators/templates/index.d.ts.map +1 -1
  40. package/dist/generators/templates/index.js +8 -0
  41. package/dist/generators/templates/index.js.map +1 -1
  42. package/dist/generators/templates/website-components.d.ts +33 -0
  43. package/dist/generators/templates/website-components.d.ts.map +1 -0
  44. package/dist/generators/templates/website-components.js +303 -0
  45. package/dist/generators/templates/website-components.js.map +1 -0
  46. package/dist/generators/templates/website-config.d.ts +55 -0
  47. package/dist/generators/templates/website-config.d.ts.map +1 -0
  48. package/dist/generators/templates/website-config.js +425 -0
  49. package/dist/generators/templates/website-config.js.map +1 -0
  50. package/dist/generators/templates/website-conversion.d.ts +27 -0
  51. package/dist/generators/templates/website-conversion.d.ts.map +1 -0
  52. package/dist/generators/templates/website-conversion.js +326 -0
  53. package/dist/generators/templates/website-conversion.js.map +1 -0
  54. package/dist/generators/templates/website-landing.d.ts +24 -0
  55. package/dist/generators/templates/website-landing.d.ts.map +1 -0
  56. package/dist/generators/templates/website-landing.js +276 -0
  57. package/dist/generators/templates/website-landing.js.map +1 -0
  58. package/dist/generators/templates/website-layout.d.ts +42 -0
  59. package/dist/generators/templates/website-layout.d.ts.map +1 -0
  60. package/dist/generators/templates/website-layout.js +408 -0
  61. package/dist/generators/templates/website-layout.js.map +1 -0
  62. package/dist/generators/templates/website-pricing.d.ts +11 -0
  63. package/dist/generators/templates/website-pricing.d.ts.map +1 -0
  64. package/dist/generators/templates/website-pricing.js +313 -0
  65. package/dist/generators/templates/website-pricing.js.map +1 -0
  66. package/dist/generators/templates/website-sections.d.ts +102 -0
  67. package/dist/generators/templates/website-sections.d.ts.map +1 -0
  68. package/dist/generators/templates/website-sections.js +444 -0
  69. package/dist/generators/templates/website-sections.js.map +1 -0
  70. package/dist/generators/templates/website-seo.d.ts +76 -0
  71. package/dist/generators/templates/website-seo.d.ts.map +1 -0
  72. package/dist/generators/templates/website-seo.js +326 -0
  73. package/dist/generators/templates/website-seo.js.map +1 -0
  74. package/dist/generators/templates/website.d.ts +10 -83
  75. package/dist/generators/templates/website.d.ts.map +1 -1
  76. package/dist/generators/templates/website.js +12 -875
  77. package/dist/generators/templates/website.js.map +1 -1
  78. package/dist/generators/website-content-scanner.d.ts +37 -0
  79. package/dist/generators/website-content-scanner.d.ts.map +1 -0
  80. package/dist/generators/website-content-scanner.js +165 -0
  81. package/dist/generators/website-content-scanner.js.map +1 -0
  82. package/dist/generators/website-context.d.ts +119 -0
  83. package/dist/generators/website-context.d.ts.map +1 -0
  84. package/dist/generators/website-context.js +350 -0
  85. package/dist/generators/website-context.js.map +1 -0
  86. package/dist/generators/website-debug.d.ts +68 -0
  87. package/dist/generators/website-debug.d.ts.map +1 -0
  88. package/dist/generators/website-debug.js +93 -0
  89. package/dist/generators/website-debug.js.map +1 -0
  90. package/dist/generators/website.d.ts +5 -0
  91. package/dist/generators/website.d.ts.map +1 -1
  92. package/dist/generators/website.js +136 -11
  93. package/dist/generators/website.js.map +1 -1
  94. package/dist/generators/workspace-root.d.ts +27 -0
  95. package/dist/generators/workspace-root.d.ts.map +1 -0
  96. package/dist/generators/workspace-root.js +100 -0
  97. package/dist/generators/workspace-root.js.map +1 -0
  98. package/dist/state/index.d.ts +35 -0
  99. package/dist/state/index.d.ts.map +1 -1
  100. package/dist/state/index.js +40 -0
  101. package/dist/state/index.js.map +1 -1
  102. package/dist/types/consensus.d.ts +3 -0
  103. package/dist/types/consensus.d.ts.map +1 -1
  104. package/dist/types/consensus.js +1 -0
  105. package/dist/types/consensus.js.map +1 -1
  106. package/dist/types/website-strategy.d.ts +263 -0
  107. package/dist/types/website-strategy.d.ts.map +1 -0
  108. package/dist/types/website-strategy.js +105 -0
  109. package/dist/types/website-strategy.js.map +1 -0
  110. package/dist/types/workflow.d.ts +21 -0
  111. package/dist/types/workflow.d.ts.map +1 -1
  112. package/dist/types/workflow.js +8 -0
  113. package/dist/types/workflow.js.map +1 -1
  114. package/dist/upgrade/handlers.d.ts +15 -0
  115. package/dist/upgrade/handlers.d.ts.map +1 -1
  116. package/dist/upgrade/handlers.js +52 -0
  117. package/dist/upgrade/handlers.js.map +1 -1
  118. package/dist/workflow/auto-fix-bundler.d.ts +37 -0
  119. package/dist/workflow/auto-fix-bundler.d.ts.map +1 -0
  120. package/dist/workflow/auto-fix-bundler.js +320 -0
  121. package/dist/workflow/auto-fix-bundler.js.map +1 -0
  122. package/dist/workflow/auto-fix.d.ts.map +1 -1
  123. package/dist/workflow/auto-fix.js +10 -3
  124. package/dist/workflow/auto-fix.js.map +1 -1
  125. package/dist/workflow/consensus.d.ts.map +1 -1
  126. package/dist/workflow/consensus.js +2 -0
  127. package/dist/workflow/consensus.js.map +1 -1
  128. package/dist/workflow/execution-mode.d.ts.map +1 -1
  129. package/dist/workflow/execution-mode.js +18 -0
  130. package/dist/workflow/execution-mode.js.map +1 -1
  131. package/dist/workflow/index.d.ts +4 -0
  132. package/dist/workflow/index.d.ts.map +1 -1
  133. package/dist/workflow/index.js +37 -0
  134. package/dist/workflow/index.js.map +1 -1
  135. package/dist/workflow/overview.d.ts +89 -0
  136. package/dist/workflow/overview.d.ts.map +1 -0
  137. package/dist/workflow/overview.js +358 -0
  138. package/dist/workflow/overview.js.map +1 -0
  139. package/dist/workflow/plan-mode.d.ts +6 -4
  140. package/dist/workflow/plan-mode.d.ts.map +1 -1
  141. package/dist/workflow/plan-mode.js +148 -6
  142. package/dist/workflow/plan-mode.js.map +1 -1
  143. package/dist/workflow/website-strategy.d.ts +79 -0
  144. package/dist/workflow/website-strategy.d.ts.map +1 -0
  145. package/dist/workflow/website-strategy.js +310 -0
  146. package/dist/workflow/website-strategy.js.map +1 -0
  147. package/dist/workflow/website-updater.d.ts +17 -0
  148. package/dist/workflow/website-updater.d.ts.map +1 -0
  149. package/dist/workflow/website-updater.js +116 -0
  150. package/dist/workflow/website-updater.js.map +1 -0
  151. package/dist/workflow/workflow-logger.d.ts +1 -1
  152. package/dist/workflow/workflow-logger.d.ts.map +1 -1
  153. package/dist/workflow/workflow-logger.js.map +1 -1
  154. package/package.json +1 -1
  155. package/src/adapters/gemini.ts +10 -4
  156. package/src/adapters/grok.ts +10 -4
  157. package/src/adapters/openai.ts +38 -6
  158. package/src/cli/commands/create.ts +58 -4
  159. package/src/cli/interactive.ts +143 -7
  160. package/src/generators/all.ts +49 -332
  161. package/src/generators/doc-parser.ts +449 -0
  162. package/src/generators/frontend-design-analyzer.ts +261 -0
  163. package/src/generators/shared-packages.ts +500 -0
  164. package/src/generators/templates/index.ts +8 -0
  165. package/src/generators/templates/website-components.ts +330 -0
  166. package/src/generators/templates/website-config.ts +444 -0
  167. package/src/generators/templates/website-conversion.ts +341 -0
  168. package/src/generators/templates/website-landing.ts +331 -0
  169. package/src/generators/templates/website-layout.ts +443 -0
  170. package/src/generators/templates/website-pricing.ts +330 -0
  171. package/src/generators/templates/website-sections.ts +541 -0
  172. package/src/generators/templates/website-seo.ts +370 -0
  173. package/src/generators/templates/website.ts +38 -905
  174. package/src/generators/website-content-scanner.ts +208 -0
  175. package/src/generators/website-context.ts +493 -0
  176. package/src/generators/website-debug.ts +130 -0
  177. package/src/generators/website.ts +178 -20
  178. package/src/generators/workspace-root.ts +113 -0
  179. package/src/state/index.ts +56 -0
  180. package/src/types/consensus.ts +3 -0
  181. package/src/types/website-strategy.ts +243 -0
  182. package/src/types/workflow.ts +21 -0
  183. package/src/upgrade/handlers.ts +65 -0
  184. package/src/workflow/auto-fix-bundler.ts +392 -0
  185. package/src/workflow/auto-fix.ts +11 -3
  186. package/src/workflow/consensus.ts +2 -0
  187. package/src/workflow/execution-mode.ts +21 -0
  188. package/src/workflow/index.ts +37 -0
  189. package/src/workflow/overview.ts +475 -0
  190. package/src/workflow/plan-mode.ts +193 -8
  191. package/src/workflow/website-strategy.ts +379 -0
  192. package/src/workflow/website-updater.ts +142 -0
  193. package/src/workflow/workflow-logger.ts +1 -0
  194. package/tests/adapters/persona-switching.test.ts +63 -0
  195. package/tests/cli/project-naming.test.ts +136 -0
  196. package/tests/generators/doc-parser.test.ts +121 -0
  197. package/tests/generators/frontend-design-analyzer.test.ts +90 -0
  198. package/tests/generators/quality-gate.test.ts +183 -0
  199. package/tests/generators/shared-packages.test.ts +83 -0
  200. package/tests/generators/website-components.test.ts +159 -0
  201. package/tests/generators/website-config.test.ts +84 -0
  202. package/tests/generators/website-content-scanner.test.ts +181 -0
  203. package/tests/generators/website-context.test.ts +331 -0
  204. package/tests/generators/website-debug.test.ts +77 -0
  205. package/tests/generators/website-landing.test.ts +188 -0
  206. package/tests/generators/website-pricing.test.ts +98 -0
  207. package/tests/generators/website-sections.test.ts +245 -0
  208. package/tests/generators/website-seo-quality.test.ts +246 -0
  209. package/tests/generators/workspace-root.test.ts +105 -0
  210. package/tests/upgrade/handlers.test.ts +162 -0
  211. package/tests/workflow/auto-fix-bundler.test.ts +242 -0
  212. package/tests/workflow/overview.test.ts +392 -0
  213. package/tests/workflow/plan-mode.test.ts +111 -1
  214. package/tests/workflow/website-strategy.test.ts +246 -0
@@ -0,0 +1,444 @@
1
+ /**
2
+ * Website configuration templates (non-content)
3
+ * Package configs, build tools, Docker, vitest, and env declarations
4
+ */
5
+
6
+ /**
7
+ * Generate Next.js package.json
8
+ */
9
+ export function generateWebsitePackageJson(projectName: string): string {
10
+ return `{
11
+ "name": "${projectName}-website",
12
+ "version": "1.0.0",
13
+ "private": true,
14
+ "scripts": {
15
+ "dev": "next dev -p 3001",
16
+ "build": "next build",
17
+ "start": "next start -p 3001",
18
+ "lint": "next lint",
19
+ "test": "vitest run",
20
+ "test:watch": "vitest",
21
+ "typecheck": "tsc --noEmit"
22
+ },
23
+ "dependencies": {
24
+ "next": "^14.1.0",
25
+ "react": "^18.2.0",
26
+ "react-dom": "^18.2.0",
27
+ "lucide-react": "^0.312.0",
28
+ "clsx": "^2.1.0",
29
+ "tailwind-merge": "^2.2.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.11.0",
33
+ "@types/react": "^18.2.0",
34
+ "@types/react-dom": "^18.2.0",
35
+ "autoprefixer": "^10.4.17",
36
+ "postcss": "^8.4.33",
37
+ "tailwindcss": "^3.4.1",
38
+ "typescript": "^5.3.3",
39
+ "@testing-library/react": "^14.1.2",
40
+ "@vitejs/plugin-react": "^4.2.1",
41
+ "vitest": "^1.2.0",
42
+ "jsdom": "^24.0.0"
43
+ }
44
+ }
45
+ `;
46
+ }
47
+
48
+ /**
49
+ * Generate Next.js config
50
+ */
51
+ export function generateNextConfig(): string {
52
+ return `/** @type {import('next').NextConfig} */
53
+ const nextConfig = {
54
+ // Enable React Strict Mode for better development
55
+ reactStrictMode: true,
56
+
57
+ // Image optimization
58
+ images: {
59
+ domains: [],
60
+ formats: ['image/avif', 'image/webp'],
61
+ },
62
+
63
+ // Disable x-powered-by header
64
+ poweredByHeader: false,
65
+
66
+ // Trailing slash config
67
+ trailingSlash: false,
68
+
69
+ // Headers for security
70
+ async headers() {
71
+ return [
72
+ {
73
+ source: '/:path*',
74
+ headers: [
75
+ {
76
+ key: 'X-DNS-Prefetch-Control',
77
+ value: 'on',
78
+ },
79
+ {
80
+ key: 'X-Content-Type-Options',
81
+ value: 'nosniff',
82
+ },
83
+ ],
84
+ },
85
+ ];
86
+ },
87
+ };
88
+
89
+ module.exports = nextConfig;
90
+ `;
91
+ }
92
+
93
+ /**
94
+ * Generate website tsconfig.json
95
+ */
96
+ export function generateWebsiteTsconfig(): string {
97
+ return `{
98
+ "compilerOptions": {
99
+ "target": "ES2017",
100
+ "lib": ["dom", "dom.iterable", "esnext"],
101
+ "allowJs": true,
102
+ "skipLibCheck": true,
103
+ "strict": true,
104
+ "noEmit": true,
105
+ "esModuleInterop": true,
106
+ "module": "esnext",
107
+ "moduleResolution": "bundler",
108
+ "resolveJsonModule": true,
109
+ "isolatedModules": true,
110
+ "jsx": "preserve",
111
+ "incremental": true,
112
+ "plugins": [
113
+ {
114
+ "name": "next"
115
+ }
116
+ ],
117
+ "paths": {
118
+ "@/*": ["./src/*"]
119
+ }
120
+ },
121
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
122
+ "exclude": ["node_modules"]
123
+ }
124
+ `;
125
+ }
126
+
127
+ /**
128
+ * Options for generating the Tailwind config
129
+ */
130
+ export interface TailwindConfigOptions {
131
+ /** Primary brand color (hex) to generate color scale from */
132
+ primaryColor?: string;
133
+ /** Whether to import workspace design-tokens preset */
134
+ workspaceMode?: boolean;
135
+ /** Project name (for workspace preset import path) */
136
+ projectName?: string;
137
+ }
138
+
139
+ /**
140
+ * Generate Tailwind config for website
141
+ *
142
+ * @param options - Optional configuration for brand colors and workspace mode
143
+ * @returns Tailwind config source code
144
+ */
145
+ export function generateWebsiteTailwindConfig(options?: TailwindConfigOptions): string {
146
+ const colorScale = options?.primaryColor
147
+ ? generateInlineColorScale(options.primaryColor)
148
+ : defaultColorScale();
149
+
150
+ const presetImport = options?.workspaceMode && options?.projectName
151
+ ? `import designPreset from '@${options.projectName}/design-tokens/tailwind';\n`
152
+ : '';
153
+
154
+ const presetsBlock = options?.workspaceMode && options?.projectName
155
+ ? ` presets: [designPreset],\n`
156
+ : '';
157
+
158
+ return `import type { Config } from 'tailwindcss';
159
+ ${presetImport}
160
+ const config: Config = {
161
+ ${presetsBlock} content: [
162
+ './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
163
+ './src/components/**/*.{js,ts,jsx,tsx,mdx}',
164
+ './src/app/**/*.{js,ts,jsx,tsx,mdx}',
165
+ ],
166
+ theme: {
167
+ extend: {
168
+ colors: {
169
+ primary: {
170
+ ${colorScale}
171
+ },
172
+ background: 'hsl(var(--background))',
173
+ foreground: 'hsl(var(--foreground))',
174
+ muted: {
175
+ DEFAULT: 'hsl(var(--muted))',
176
+ foreground: 'hsl(var(--muted-foreground))',
177
+ },
178
+ accent: {
179
+ DEFAULT: 'hsl(var(--accent))',
180
+ foreground: 'hsl(var(--accent-foreground))',
181
+ },
182
+ card: {
183
+ DEFAULT: 'hsl(var(--card))',
184
+ foreground: 'hsl(var(--card-foreground))',
185
+ },
186
+ border: 'hsl(var(--border))',
187
+ ring: 'hsl(var(--ring))',
188
+ },
189
+ fontFamily: {
190
+ sans: ['var(--font-inter)', 'system-ui', 'sans-serif'],
191
+ },
192
+ borderColor: {
193
+ DEFAULT: 'hsl(var(--border))',
194
+ },
195
+ borderRadius: {
196
+ lg: 'var(--radius)',
197
+ md: 'calc(var(--radius) - 2px)',
198
+ sm: 'calc(var(--radius) - 4px)',
199
+ },
200
+ keyframes: {
201
+ fadeIn: {
202
+ '0%': { opacity: '0' },
203
+ '100%': { opacity: '1' },
204
+ },
205
+ slideUp: {
206
+ '0%': { opacity: '0', transform: 'translateY(20px)' },
207
+ '100%': { opacity: '1', transform: 'translateY(0)' },
208
+ },
209
+ },
210
+ animation: {
211
+ fadeIn: 'fadeIn 0.5s ease-out',
212
+ slideUp: 'slideUp 0.5s ease-out',
213
+ },
214
+ },
215
+ },
216
+ plugins: [],
217
+ };
218
+
219
+ export default config;
220
+ `;
221
+ }
222
+
223
+ /**
224
+ * Generate inline color scale entries from a hex color
225
+ */
226
+ function generateInlineColorScale(hex: string): string {
227
+ const rgb = hexToRgb(hex);
228
+ if (!rgb) return defaultColorScale();
229
+
230
+ const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
231
+ const stops = [
232
+ { key: '50', lightness: 0.95 },
233
+ { key: '100', lightness: 0.90 },
234
+ { key: '200', lightness: 0.80 },
235
+ { key: '300', lightness: 0.68 },
236
+ { key: '400', lightness: 0.56 },
237
+ { key: '500', lightness: 0.48 },
238
+ { key: '600', lightness: 0.40 },
239
+ { key: '700', lightness: 0.32 },
240
+ { key: '800', lightness: 0.24 },
241
+ { key: '900', lightness: 0.15 },
242
+ ];
243
+
244
+ return stops
245
+ .map(stop => ` ${stop.key}: '${hslToHex(hsl.h, hsl.s, stop.lightness)}',`)
246
+ .join('\n');
247
+ }
248
+
249
+ function defaultColorScale(): string {
250
+ return ` 50: '#f0f9ff',
251
+ 100: '#e0f2fe',
252
+ 200: '#bae6fd',
253
+ 300: '#7dd3fc',
254
+ 400: '#38bdf8',
255
+ 500: '#0ea5e9',
256
+ 600: '#0284c7',
257
+ 700: '#0369a1',
258
+ 800: '#075985',
259
+ 900: '#0c4a6e',`;
260
+ }
261
+
262
+ // --- Color conversion helpers (inline to avoid circular deps) ---
263
+
264
+ function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
265
+ const cleaned = hex.replace(/^#/, '');
266
+ if (cleaned.length !== 6) return null;
267
+ const r = parseInt(cleaned.substring(0, 2), 16);
268
+ const g = parseInt(cleaned.substring(2, 4), 16);
269
+ const b = parseInt(cleaned.substring(4, 6), 16);
270
+ if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
271
+ return { r, g, b };
272
+ }
273
+
274
+ function rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } {
275
+ const rn = r / 255;
276
+ const gn = g / 255;
277
+ const bn = b / 255;
278
+ const max = Math.max(rn, gn, bn);
279
+ const min = Math.min(rn, gn, bn);
280
+ const l = (max + min) / 2;
281
+ let h = 0;
282
+ let s = 0;
283
+ if (max !== min) {
284
+ const d = max - min;
285
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
286
+ if (max === rn) h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6;
287
+ else if (max === gn) h = ((bn - rn) / d + 2) / 6;
288
+ else h = ((rn - gn) / d + 4) / 6;
289
+ }
290
+ return { h, s, l };
291
+ }
292
+
293
+ function hslToHex(h: number, s: number, l: number): string {
294
+ const hue2rgb = (p: number, q: number, t: number): number => {
295
+ let tn = t;
296
+ if (tn < 0) tn += 1;
297
+ if (tn > 1) tn -= 1;
298
+ if (tn < 1 / 6) return p + (q - p) * 6 * tn;
299
+ if (tn < 1 / 2) return q;
300
+ if (tn < 2 / 3) return p + (q - p) * (2 / 3 - tn) * 6;
301
+ return p;
302
+ };
303
+ let r: number, g: number, b: number;
304
+ if (s === 0) {
305
+ r = g = b = l;
306
+ } else {
307
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
308
+ const p = 2 * l - q;
309
+ r = hue2rgb(p, q, h + 1 / 3);
310
+ g = hue2rgb(p, q, h);
311
+ b = hue2rgb(p, q, h - 1 / 3);
312
+ }
313
+ const toHex = (n: number): string => {
314
+ const val = Math.round(n * 255).toString(16);
315
+ return val.length === 1 ? '0' + val : val;
316
+ };
317
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
318
+ }
319
+
320
+ /**
321
+ * Generate PostCSS config for website
322
+ */
323
+ export function generateWebsitePostcssConfig(): string {
324
+ return `module.exports = {
325
+ plugins: {
326
+ tailwindcss: {},
327
+ autoprefixer: {},
328
+ },
329
+ };
330
+ `;
331
+ }
332
+
333
+ /**
334
+ * Generate website Dockerfile
335
+ */
336
+ export function generateWebsiteDockerfile(): string {
337
+ return `# Build stage
338
+ FROM node:20-alpine AS builder
339
+
340
+ WORKDIR /app
341
+
342
+ # Copy package files
343
+ COPY package*.json ./
344
+
345
+ # Install dependencies
346
+ RUN npm ci
347
+
348
+ # Copy source
349
+ COPY . .
350
+
351
+ # Build
352
+ RUN npm run build
353
+
354
+ # Production stage
355
+ FROM node:20-alpine AS runner
356
+
357
+ WORKDIR /app
358
+
359
+ ENV NODE_ENV=production
360
+ ENV NEXT_TELEMETRY_DISABLED=1
361
+
362
+ # Create non-root user
363
+ RUN addgroup --system --gid 1001 nodejs
364
+ RUN adduser --system --uid 1001 nextjs
365
+
366
+ # Copy built assets
367
+ COPY --from=builder /app/public ./public
368
+ COPY --from=builder /app/.next/standalone ./
369
+ COPY --from=builder /app/.next/static ./.next/static
370
+
371
+ USER nextjs
372
+
373
+ EXPOSE 3000
374
+
375
+ ENV PORT=3000
376
+ ENV HOSTNAME="0.0.0.0"
377
+
378
+ CMD ["node", "server.js"]
379
+ `;
380
+ }
381
+
382
+ /**
383
+ * Generate vitest config for website
384
+ */
385
+ export function generateWebsiteVitestConfig(): string {
386
+ return `import { defineConfig } from 'vitest/config';
387
+ import react from '@vitejs/plugin-react';
388
+ import path from 'path';
389
+
390
+ export default defineConfig({
391
+ plugins: [react()],
392
+ test: {
393
+ environment: 'jsdom',
394
+ include: ['**/*.test.{ts,tsx}'],
395
+ globals: true,
396
+ setupFiles: ['./tests/setup.ts'],
397
+ },
398
+ resolve: {
399
+ alias: {
400
+ '@': path.resolve(__dirname, './src'),
401
+ },
402
+ },
403
+ });
404
+ `;
405
+ }
406
+
407
+ /**
408
+ * Generate vitest setup for website
409
+ */
410
+ export function generateWebsiteVitestSetup(): string {
411
+ return `import '@testing-library/jest-dom';
412
+
413
+ // Mock next/navigation
414
+ vi.mock('next/navigation', () => ({
415
+ useRouter: () => ({
416
+ push: vi.fn(),
417
+ replace: vi.fn(),
418
+ prefetch: vi.fn(),
419
+ }),
420
+ useSearchParams: () => new URLSearchParams(),
421
+ usePathname: () => '/',
422
+ }));
423
+
424
+ // Mock next/image
425
+ vi.mock('next/image', () => ({
426
+ default: (props: Record<string, unknown>) => {
427
+ // eslint-disable-next-line @next/next/no-img-element, jsx-a11y/alt-text
428
+ return <img {...props} />;
429
+ },
430
+ }));
431
+ `;
432
+ }
433
+
434
+ /**
435
+ * Generate Next.js environment declaration
436
+ */
437
+ export function generateWebsiteNextEnv(): string {
438
+ return `/// <reference types="next" />
439
+ /// <reference types="next/image-types/global" />
440
+
441
+ // NOTE: This file should not be edited
442
+ // see https://nextjs.org/docs/basic-features/typescript for more information.
443
+ `;
444
+ }