popeye-cli 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -0
- package/README.md +50 -8
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +54 -4
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/interactive.d.ts +29 -0
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +90 -7
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/all.d.ts +4 -1
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +36 -316
- package/dist/generators/all.js.map +1 -1
- package/dist/generators/doc-parser.d.ts +18 -3
- package/dist/generators/doc-parser.d.ts.map +1 -1
- package/dist/generators/doc-parser.js +81 -10
- package/dist/generators/doc-parser.js.map +1 -1
- package/dist/generators/frontend-design-analyzer.d.ts +30 -0
- package/dist/generators/frontend-design-analyzer.d.ts.map +1 -0
- package/dist/generators/frontend-design-analyzer.js +208 -0
- package/dist/generators/frontend-design-analyzer.js.map +1 -0
- package/dist/generators/shared-packages.d.ts +45 -0
- package/dist/generators/shared-packages.d.ts.map +1 -0
- package/dist/generators/shared-packages.js +456 -0
- package/dist/generators/shared-packages.js.map +1 -0
- package/dist/generators/templates/index.d.ts +4 -0
- package/dist/generators/templates/index.d.ts.map +1 -1
- package/dist/generators/templates/index.js +4 -0
- package/dist/generators/templates/index.js.map +1 -1
- package/dist/generators/templates/website-components.d.ts.map +1 -1
- package/dist/generators/templates/website-components.js +36 -11
- package/dist/generators/templates/website-components.js.map +1 -1
- package/dist/generators/templates/website-config.d.ts +15 -1
- package/dist/generators/templates/website-config.d.ts.map +1 -1
- package/dist/generators/templates/website-config.js +155 -13
- package/dist/generators/templates/website-config.js.map +1 -1
- package/dist/generators/templates/website-landing.d.ts +24 -0
- package/dist/generators/templates/website-landing.d.ts.map +1 -0
- package/dist/generators/templates/website-landing.js +276 -0
- package/dist/generators/templates/website-landing.js.map +1 -0
- package/dist/generators/templates/website-layout.d.ts +42 -0
- package/dist/generators/templates/website-layout.d.ts.map +1 -0
- package/dist/generators/templates/website-layout.js +408 -0
- package/dist/generators/templates/website-layout.js.map +1 -0
- package/dist/generators/templates/website-pricing.d.ts +11 -0
- package/dist/generators/templates/website-pricing.d.ts.map +1 -0
- package/dist/generators/templates/website-pricing.js +313 -0
- package/dist/generators/templates/website-pricing.js.map +1 -0
- package/dist/generators/templates/website-sections.d.ts +102 -0
- package/dist/generators/templates/website-sections.d.ts.map +1 -0
- package/dist/generators/templates/website-sections.js +444 -0
- package/dist/generators/templates/website-sections.js.map +1 -0
- package/dist/generators/templates/website.d.ts +10 -50
- package/dist/generators/templates/website.d.ts.map +1 -1
- package/dist/generators/templates/website.js +12 -788
- package/dist/generators/templates/website.js.map +1 -1
- package/dist/generators/website-content-scanner.d.ts +37 -0
- package/dist/generators/website-content-scanner.d.ts.map +1 -0
- package/dist/generators/website-content-scanner.js +165 -0
- package/dist/generators/website-content-scanner.js.map +1 -0
- package/dist/generators/website-context.d.ts +38 -2
- package/dist/generators/website-context.d.ts.map +1 -1
- package/dist/generators/website-context.js +179 -19
- package/dist/generators/website-context.js.map +1 -1
- package/dist/generators/website-debug.d.ts +68 -0
- package/dist/generators/website-debug.d.ts.map +1 -0
- package/dist/generators/website-debug.js +93 -0
- package/dist/generators/website-debug.js.map +1 -0
- package/dist/generators/website.d.ts +2 -0
- package/dist/generators/website.d.ts.map +1 -1
- package/dist/generators/website.js +66 -4
- package/dist/generators/website.js.map +1 -1
- package/dist/generators/workspace-root.d.ts +27 -0
- package/dist/generators/workspace-root.d.ts.map +1 -0
- package/dist/generators/workspace-root.js +100 -0
- package/dist/generators/workspace-root.js.map +1 -0
- package/dist/state/index.d.ts +8 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +10 -0
- package/dist/state/index.js.map +1 -1
- package/dist/types/workflow.d.ts +6 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +2 -0
- package/dist/types/workflow.js.map +1 -1
- package/dist/upgrade/handlers.d.ts +15 -0
- package/dist/upgrade/handlers.d.ts.map +1 -1
- package/dist/upgrade/handlers.js +52 -0
- package/dist/upgrade/handlers.js.map +1 -1
- package/dist/workflow/auto-fix-bundler.d.ts +37 -0
- package/dist/workflow/auto-fix-bundler.d.ts.map +1 -0
- package/dist/workflow/auto-fix-bundler.js +320 -0
- package/dist/workflow/auto-fix-bundler.js.map +1 -0
- package/dist/workflow/auto-fix.d.ts.map +1 -1
- package/dist/workflow/auto-fix.js +10 -3
- package/dist/workflow/auto-fix.js.map +1 -1
- package/dist/workflow/index.d.ts +1 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +12 -0
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/overview.d.ts.map +1 -1
- package/dist/workflow/overview.js +4 -0
- package/dist/workflow/overview.js.map +1 -1
- package/dist/workflow/plan-mode.d.ts +4 -3
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +69 -5
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/website-strategy.d.ts +9 -0
- package/dist/workflow/website-strategy.d.ts.map +1 -1
- package/dist/workflow/website-strategy.js +73 -1
- package/dist/workflow/website-strategy.js.map +1 -1
- package/dist/workflow/website-updater.d.ts.map +1 -1
- package/dist/workflow/website-updater.js +15 -4
- package/dist/workflow/website-updater.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/create.ts +58 -4
- package/src/cli/interactive.ts +96 -7
- package/src/generators/all.ts +44 -332
- package/src/generators/doc-parser.ts +87 -10
- package/src/generators/frontend-design-analyzer.ts +261 -0
- package/src/generators/shared-packages.ts +500 -0
- package/src/generators/templates/index.ts +4 -0
- package/src/generators/templates/website-components.ts +36 -11
- package/src/generators/templates/website-config.ts +166 -13
- package/src/generators/templates/website-landing.ts +331 -0
- package/src/generators/templates/website-layout.ts +443 -0
- package/src/generators/templates/website-pricing.ts +330 -0
- package/src/generators/templates/website-sections.ts +541 -0
- package/src/generators/templates/website.ts +38 -851
- package/src/generators/website-content-scanner.ts +208 -0
- package/src/generators/website-context.ts +248 -20
- package/src/generators/website-debug.ts +130 -0
- package/src/generators/website.ts +71 -3
- package/src/generators/workspace-root.ts +113 -0
- package/src/state/index.ts +14 -0
- package/src/types/workflow.ts +6 -0
- package/src/upgrade/handlers.ts +65 -0
- package/src/workflow/auto-fix-bundler.ts +392 -0
- package/src/workflow/auto-fix.ts +11 -3
- package/src/workflow/index.ts +12 -0
- package/src/workflow/overview.ts +6 -0
- package/src/workflow/plan-mode.ts +81 -7
- package/src/workflow/website-strategy.ts +75 -1
- package/src/workflow/website-updater.ts +17 -6
- package/tests/cli/project-naming.test.ts +136 -0
- package/tests/generators/doc-parser.test.ts +121 -0
- package/tests/generators/frontend-design-analyzer.test.ts +90 -0
- package/tests/generators/quality-gate.test.ts +183 -0
- package/tests/generators/shared-packages.test.ts +83 -0
- package/tests/generators/website-components.test.ts +1 -1
- package/tests/generators/website-config.test.ts +84 -0
- package/tests/generators/website-content-scanner.test.ts +181 -0
- package/tests/generators/website-context.test.ts +109 -0
- package/tests/generators/website-debug.test.ts +77 -0
- package/tests/generators/website-landing.test.ts +188 -0
- package/tests/generators/website-pricing.test.ts +98 -0
- package/tests/generators/website-sections.test.ts +245 -0
- package/tests/generators/workspace-root.test.ts +105 -0
- package/tests/upgrade/handlers.test.ts +162 -0
- package/tests/workflow/auto-fix-bundler.test.ts +242 -0
- package/tests/workflow/plan-mode.test.ts +111 -1
- package/tests/workflow/website-strategy.test.ts +55 -0
|
@@ -48,6 +48,10 @@ import {
|
|
|
48
48
|
generateLeadCaptureEnvExample,
|
|
49
49
|
} from './templates/website-conversion.js';
|
|
50
50
|
import type { WebsiteContentContext } from './website-context.js';
|
|
51
|
+
import { validateWebsiteContextOrThrow } from './website-context.js';
|
|
52
|
+
import { scanGeneratedContent } from './website-content-scanner.js';
|
|
53
|
+
import { printDebugTrace, isDebugEnabled } from './website-debug.js';
|
|
54
|
+
import type { WebsiteDebugTrace } from './website-debug.js';
|
|
51
55
|
|
|
52
56
|
/**
|
|
53
57
|
* Project generation result
|
|
@@ -75,6 +79,8 @@ export interface WebsiteGeneratorOptions {
|
|
|
75
79
|
skipReadme?: boolean;
|
|
76
80
|
/** Content context from user docs for populating templates */
|
|
77
81
|
contentContext?: WebsiteContentContext;
|
|
82
|
+
/** Skip content validation (scaffold-only use) */
|
|
83
|
+
skipValidation?: boolean;
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
/**
|
|
@@ -111,6 +117,7 @@ export async function generateWebsiteProject(
|
|
|
111
117
|
skipDocker = false,
|
|
112
118
|
skipReadme = false,
|
|
113
119
|
contentContext,
|
|
120
|
+
skipValidation = false,
|
|
114
121
|
} = options;
|
|
115
122
|
|
|
116
123
|
const projectName = spec.name || 'my-project';
|
|
@@ -140,6 +147,51 @@ export async function generateWebsiteProject(
|
|
|
140
147
|
await ensureDir(path.join(projectDir, '.popeye'));
|
|
141
148
|
}
|
|
142
149
|
|
|
150
|
+
// Validate content context quality gate
|
|
151
|
+
if (!skipValidation) {
|
|
152
|
+
const validationContext = contentContext || {
|
|
153
|
+
productName: projectName,
|
|
154
|
+
features: [],
|
|
155
|
+
rawDocs: '',
|
|
156
|
+
};
|
|
157
|
+
validateWebsiteContextOrThrow(validationContext, projectName);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Debug trace
|
|
161
|
+
if (isDebugEnabled() && contentContext) {
|
|
162
|
+
const trace: WebsiteDebugTrace = {
|
|
163
|
+
workspaceRoot: projectDir,
|
|
164
|
+
docsFound: contentContext.rawDocs
|
|
165
|
+
? contentContext.rawDocs.split(/^--- .+ ---$/m).filter(Boolean).map((s, i) => ({
|
|
166
|
+
path: `doc-${i}`,
|
|
167
|
+
size: s.length,
|
|
168
|
+
}))
|
|
169
|
+
: [],
|
|
170
|
+
brandAssets: {
|
|
171
|
+
logoPath: contentContext.brand?.logoPath,
|
|
172
|
+
logoOutputPath: contentContext.brandAssets?.logoOutputPath || 'public/brand/logo.svg',
|
|
173
|
+
},
|
|
174
|
+
productName: {
|
|
175
|
+
value: contentContext.productName,
|
|
176
|
+
source: contentContext.rawDocs ? 'docs' : 'directory',
|
|
177
|
+
},
|
|
178
|
+
primaryColor: {
|
|
179
|
+
value: contentContext.brand?.primaryColor,
|
|
180
|
+
source: contentContext.brand?.primaryColor ? 'brand-docs' : 'defaults',
|
|
181
|
+
},
|
|
182
|
+
strategyStatus: contentContext.strategy ? 'success' : 'skipped',
|
|
183
|
+
templateValues: {
|
|
184
|
+
headline: contentContext.strategy?.messaging.headline,
|
|
185
|
+
features: contentContext.features.length,
|
|
186
|
+
pricingTiers: contentContext.pricing?.length || 0,
|
|
187
|
+
},
|
|
188
|
+
sectionsRendered: [],
|
|
189
|
+
validationPassed: true,
|
|
190
|
+
validationIssues: [],
|
|
191
|
+
};
|
|
192
|
+
printDebugTrace(trace);
|
|
193
|
+
}
|
|
194
|
+
|
|
143
195
|
// Generate and write files
|
|
144
196
|
const files: Array<{ path: string; content: string }> = [
|
|
145
197
|
// Config files
|
|
@@ -157,7 +209,11 @@ export async function generateWebsiteProject(
|
|
|
157
209
|
},
|
|
158
210
|
{
|
|
159
211
|
path: path.join(projectDir, 'tailwind.config.ts'),
|
|
160
|
-
content: generateWebsiteTailwindConfig(
|
|
212
|
+
content: generateWebsiteTailwindConfig({
|
|
213
|
+
primaryColor: contentContext?.brand?.primaryColor,
|
|
214
|
+
workspaceMode,
|
|
215
|
+
projectName: workspaceMode ? projectName : undefined,
|
|
216
|
+
}),
|
|
161
217
|
},
|
|
162
218
|
{
|
|
163
219
|
path: path.join(projectDir, 'postcss.config.js'),
|
|
@@ -313,11 +369,11 @@ export async function generateWebsiteProject(
|
|
|
313
369
|
});
|
|
314
370
|
}
|
|
315
371
|
|
|
316
|
-
// Copy logo to public/ if brand context has one
|
|
372
|
+
// Copy logo to public/brand/ if brand context has one
|
|
317
373
|
if (contentContext?.brand?.logoPath) {
|
|
318
374
|
try {
|
|
319
375
|
const logoExt = path.extname(contentContext.brand.logoPath);
|
|
320
|
-
const destPath = path.join(projectDir, 'public', `logo${logoExt}`);
|
|
376
|
+
const destPath = path.join(projectDir, 'public', 'brand', `logo${logoExt}`);
|
|
321
377
|
await fs.copyFile(contentContext.brand.logoPath, destPath);
|
|
322
378
|
filesCreated.push(destPath);
|
|
323
379
|
} catch {
|
|
@@ -347,6 +403,18 @@ export async function generateWebsiteProject(
|
|
|
347
403
|
filesCreated.push(file.path);
|
|
348
404
|
}
|
|
349
405
|
|
|
406
|
+
// Post-generation content scan for placeholder fingerprints
|
|
407
|
+
try {
|
|
408
|
+
const scanResult = await scanGeneratedContent(projectDir);
|
|
409
|
+
if (scanResult.issues.length > 0) {
|
|
410
|
+
for (const issue of scanResult.issues) {
|
|
411
|
+
console.warn(`[content-scan] ${issue.severity}: ${issue.message} in ${issue.file}`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
} catch {
|
|
415
|
+
// Non-blocking: scan failures should not stop generation
|
|
416
|
+
}
|
|
417
|
+
|
|
350
418
|
return {
|
|
351
419
|
success: true,
|
|
352
420
|
projectDir,
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace root detection
|
|
3
|
+
* Resolves the workspace root directory by walking up the directory tree,
|
|
4
|
+
* looking for Popeye config, monorepo indicators, or package.json with workspaces
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { promises as fs } from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the workspace root directory from a given working directory
|
|
12
|
+
*
|
|
13
|
+
* Heuristic priority:
|
|
14
|
+
* 1. Walk ancestors: first dir containing `.popeye/` -> workspace root
|
|
15
|
+
* 2. First dir containing `package.json` with "workspaces" field
|
|
16
|
+
* 3. First dir containing `pnpm-workspace.yaml` or `turbo.json`
|
|
17
|
+
* 4. `cwd` (fallback)
|
|
18
|
+
*
|
|
19
|
+
* @param cwd - The current working directory
|
|
20
|
+
* @returns The resolved workspace root path
|
|
21
|
+
*/
|
|
22
|
+
export async function resolveWorkspaceRoot(cwd: string): Promise<string> {
|
|
23
|
+
let current = path.resolve(cwd);
|
|
24
|
+
const root = path.parse(current).root;
|
|
25
|
+
|
|
26
|
+
while (current !== root) {
|
|
27
|
+
// Check for .popeye/ directory
|
|
28
|
+
if (await dirExists(path.join(current, '.popeye'))) {
|
|
29
|
+
return current;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check for package.json with "workspaces" field
|
|
33
|
+
const pkgJsonPath = path.join(current, 'package.json');
|
|
34
|
+
if (await fileExists(pkgJsonPath)) {
|
|
35
|
+
try {
|
|
36
|
+
const content = await fs.readFile(pkgJsonPath, 'utf-8');
|
|
37
|
+
const pkg = JSON.parse(content);
|
|
38
|
+
if (pkg.workspaces) {
|
|
39
|
+
return current;
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// Invalid JSON, skip
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check for pnpm-workspace.yaml or turbo.json
|
|
47
|
+
if (
|
|
48
|
+
(await fileExists(path.join(current, 'pnpm-workspace.yaml'))) ||
|
|
49
|
+
(await fileExists(path.join(current, 'turbo.json')))
|
|
50
|
+
) {
|
|
51
|
+
return current;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
current = path.dirname(current);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return cwd;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Build a list of directories to scan for docs and brand assets
|
|
62
|
+
* Includes workspace root, its parent, and relevant subdirectories
|
|
63
|
+
*
|
|
64
|
+
* @param cwd - The current working directory
|
|
65
|
+
* @returns Array of directories to scan (deduplicated)
|
|
66
|
+
*/
|
|
67
|
+
export async function getScanDirectories(cwd: string): Promise<string[]> {
|
|
68
|
+
const workspaceRoot = await resolveWorkspaceRoot(cwd);
|
|
69
|
+
const parentDir = path.dirname(workspaceRoot);
|
|
70
|
+
|
|
71
|
+
const candidates = [workspaceRoot, parentDir];
|
|
72
|
+
const subdirs = ['docs', 'brand', 'assets'];
|
|
73
|
+
|
|
74
|
+
for (const base of [workspaceRoot, parentDir]) {
|
|
75
|
+
for (const sub of subdirs) {
|
|
76
|
+
candidates.push(path.join(base, sub));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Deduplicate by resolved path and filter to existing directories
|
|
81
|
+
const seen = new Set<string>();
|
|
82
|
+
const result: string[] = [];
|
|
83
|
+
|
|
84
|
+
for (const dir of candidates) {
|
|
85
|
+
const resolved = path.resolve(dir);
|
|
86
|
+
if (seen.has(resolved)) continue;
|
|
87
|
+
seen.add(resolved);
|
|
88
|
+
|
|
89
|
+
if (await dirExists(resolved)) {
|
|
90
|
+
result.push(resolved);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function fileExists(filePath: string): Promise<boolean> {
|
|
98
|
+
try {
|
|
99
|
+
const stat = await fs.stat(filePath);
|
|
100
|
+
return stat.isFile();
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function dirExists(dirPath: string): Promise<boolean> {
|
|
107
|
+
try {
|
|
108
|
+
const stat = await fs.stat(dirPath);
|
|
109
|
+
return stat.isDirectory();
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
package/src/state/index.ts
CHANGED
|
@@ -352,6 +352,20 @@ export async function storeUserDocs(
|
|
|
352
352
|
return updateState(projectDir, { userDocs });
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
+
/**
|
|
356
|
+
* Store discovered source document paths in project state
|
|
357
|
+
*
|
|
358
|
+
* @param projectDir - The project root directory
|
|
359
|
+
* @param sourceDocPaths - Array of absolute paths to doc files
|
|
360
|
+
* @returns The updated state
|
|
361
|
+
*/
|
|
362
|
+
export async function storeSourceDocPaths(
|
|
363
|
+
projectDir: string,
|
|
364
|
+
sourceDocPaths: string[]
|
|
365
|
+
): Promise<ProjectState> {
|
|
366
|
+
return updateState(projectDir, { sourceDocPaths });
|
|
367
|
+
}
|
|
368
|
+
|
|
355
369
|
/**
|
|
356
370
|
* Store brand context in project state
|
|
357
371
|
*
|
package/src/types/workflow.ts
CHANGED
|
@@ -201,6 +201,10 @@ export interface ProjectState {
|
|
|
201
201
|
};
|
|
202
202
|
/** Path to website strategy JSON file (relative to .popeye/) */
|
|
203
203
|
websiteStrategy?: string;
|
|
204
|
+
/** Error message from website strategy generation (for visibility) */
|
|
205
|
+
strategyError?: string;
|
|
206
|
+
/** Absolute paths to discovered source documentation files */
|
|
207
|
+
sourceDocPaths?: string[];
|
|
204
208
|
}
|
|
205
209
|
|
|
206
210
|
/**
|
|
@@ -244,6 +248,8 @@ export const ProjectStateSchema = z.object({
|
|
|
244
248
|
primaryColor: z.string().optional(),
|
|
245
249
|
}).optional(),
|
|
246
250
|
websiteStrategy: z.string().optional(),
|
|
251
|
+
strategyError: z.string().optional(),
|
|
252
|
+
sourceDocPaths: z.array(z.string()).optional(),
|
|
247
253
|
});
|
|
248
254
|
|
|
249
255
|
/**
|
package/src/upgrade/handlers.ts
CHANGED
|
@@ -21,6 +21,10 @@ import {
|
|
|
21
21
|
generateRootDockerCompose,
|
|
22
22
|
} from '../generators/templates/fullstack.js';
|
|
23
23
|
import { loadState, saveState } from '../state/persistence.js';
|
|
24
|
+
import { buildWebsiteContext, resolveBrandAssets, validateWebsiteContext } from '../generators/website-context.js';
|
|
25
|
+
import type { WebsiteContentContext } from '../generators/website-context.js';
|
|
26
|
+
import { resolveWorkspaceRoot } from '../generators/workspace-root.js';
|
|
27
|
+
import { loadWebsiteStrategy } from '../workflow/website-strategy.js';
|
|
24
28
|
import type { UpgradeResult } from './index.js';
|
|
25
29
|
|
|
26
30
|
/**
|
|
@@ -42,6 +46,57 @@ async function pathExists(filePath: string): Promise<boolean> {
|
|
|
42
46
|
}
|
|
43
47
|
}
|
|
44
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Build website content context from user docs, brand assets, and strategy
|
|
51
|
+
*
|
|
52
|
+
* Replicates the context-building pattern from website-updater.ts so that
|
|
53
|
+
* upgrade-generated websites get real content instead of TODO placeholders.
|
|
54
|
+
*
|
|
55
|
+
* @param projectDir - Project directory (workspace root)
|
|
56
|
+
* @param projectName - Project name
|
|
57
|
+
* @returns Content context and optional warning
|
|
58
|
+
*/
|
|
59
|
+
export async function buildUpgradeContentContext(
|
|
60
|
+
projectDir: string,
|
|
61
|
+
projectName: string,
|
|
62
|
+
): Promise<{ context?: WebsiteContentContext; warning?: string }> {
|
|
63
|
+
try {
|
|
64
|
+
// Build context from user docs (scans projectDir + parent via getScanDirectories)
|
|
65
|
+
const context = await buildWebsiteContext(projectDir, projectName);
|
|
66
|
+
|
|
67
|
+
// Apply brand context from state if available
|
|
68
|
+
const state = await loadState(projectDir);
|
|
69
|
+
if (state?.brandContext?.primaryColor) {
|
|
70
|
+
context.brand = { ...context.brand, primaryColor: state.brandContext.primaryColor };
|
|
71
|
+
}
|
|
72
|
+
if (state?.brandContext?.logoPath) {
|
|
73
|
+
context.brand = { ...context.brand, logoPath: state.brandContext.logoPath };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Resolve brand assets using workspace root
|
|
77
|
+
const workspaceRoot = await resolveWorkspaceRoot(projectDir);
|
|
78
|
+
context.brandAssets = await resolveBrandAssets(workspaceRoot, context.brand);
|
|
79
|
+
|
|
80
|
+
// Load website strategy if available
|
|
81
|
+
const strategyData = await loadWebsiteStrategy(projectDir);
|
|
82
|
+
if (strategyData) {
|
|
83
|
+
context.strategy = strategyData.strategy;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Soft validation: include quality warnings in the return value
|
|
87
|
+
const validation = validateWebsiteContext(context, projectName);
|
|
88
|
+
const validationWarnings = [...validation.issues, ...validation.warnings]
|
|
89
|
+
.map((w) => `[quality-gate] ${w}`);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
context,
|
|
93
|
+
warning: validationWarnings.length > 0 ? validationWarnings.join('; ') : undefined,
|
|
94
|
+
};
|
|
95
|
+
} catch (e) {
|
|
96
|
+
return { warning: e instanceof Error ? e.message : 'Unknown error building website context' };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
45
100
|
/**
|
|
46
101
|
* Update state.json language field
|
|
47
102
|
*
|
|
@@ -297,11 +352,21 @@ export async function upgradeFullstackToAll(
|
|
|
297
352
|
openaiModel: 'gpt-4o',
|
|
298
353
|
};
|
|
299
354
|
|
|
355
|
+
// Build content context from user docs, brand assets, and strategy
|
|
356
|
+
const { context: contentContext, warning } = await buildUpgradeContentContext(
|
|
357
|
+
projectDir,
|
|
358
|
+
projectName,
|
|
359
|
+
);
|
|
360
|
+
if (warning) {
|
|
361
|
+
console.warn(`[upgrade] Website context warning: ${warning}`);
|
|
362
|
+
}
|
|
363
|
+
|
|
300
364
|
const result = await generateWebsiteProject(spec, projectDir, {
|
|
301
365
|
baseDir: websiteDir,
|
|
302
366
|
workspaceMode: true,
|
|
303
367
|
skipDocker: true,
|
|
304
368
|
skipReadme: true,
|
|
369
|
+
contentContext,
|
|
305
370
|
});
|
|
306
371
|
|
|
307
372
|
if (!result.success) {
|