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.
- package/CHANGELOG.md +54 -0
- package/README.md +264 -63
- package/dist/adapters/gemini.d.ts +1 -0
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +9 -4
- package/dist/adapters/gemini.js.map +1 -1
- package/dist/adapters/grok.d.ts +1 -0
- package/dist/adapters/grok.d.ts.map +1 -1
- package/dist/adapters/grok.js +9 -4
- package/dist/adapters/grok.js.map +1 -1
- package/dist/adapters/openai.d.ts +1 -1
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +35 -9
- package/dist/adapters/openai.js.map +1 -1
- 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 +132 -7
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/all.d.ts +8 -2
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +37 -316
- package/dist/generators/all.js.map +1 -1
- package/dist/generators/doc-parser.d.ts +64 -0
- package/dist/generators/doc-parser.d.ts.map +1 -0
- package/dist/generators/doc-parser.js +407 -0
- package/dist/generators/doc-parser.js.map +1 -0
- 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 +8 -0
- package/dist/generators/templates/index.d.ts.map +1 -1
- package/dist/generators/templates/index.js +8 -0
- package/dist/generators/templates/index.js.map +1 -1
- package/dist/generators/templates/website-components.d.ts +33 -0
- package/dist/generators/templates/website-components.d.ts.map +1 -0
- package/dist/generators/templates/website-components.js +303 -0
- package/dist/generators/templates/website-components.js.map +1 -0
- package/dist/generators/templates/website-config.d.ts +55 -0
- package/dist/generators/templates/website-config.d.ts.map +1 -0
- package/dist/generators/templates/website-config.js +425 -0
- package/dist/generators/templates/website-config.js.map +1 -0
- package/dist/generators/templates/website-conversion.d.ts +27 -0
- package/dist/generators/templates/website-conversion.d.ts.map +1 -0
- package/dist/generators/templates/website-conversion.js +326 -0
- package/dist/generators/templates/website-conversion.js.map +1 -0
- 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-seo.d.ts +76 -0
- package/dist/generators/templates/website-seo.d.ts.map +1 -0
- package/dist/generators/templates/website-seo.js +326 -0
- package/dist/generators/templates/website-seo.js.map +1 -0
- package/dist/generators/templates/website.d.ts +10 -83
- package/dist/generators/templates/website.d.ts.map +1 -1
- package/dist/generators/templates/website.js +12 -875
- 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 +119 -0
- package/dist/generators/website-context.d.ts.map +1 -0
- package/dist/generators/website-context.js +350 -0
- package/dist/generators/website-context.js.map +1 -0
- 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 +5 -0
- package/dist/generators/website.d.ts.map +1 -1
- package/dist/generators/website.js +136 -11
- 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 +35 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +40 -0
- package/dist/state/index.js.map +1 -1
- package/dist/types/consensus.d.ts +3 -0
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +1 -0
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/website-strategy.d.ts +263 -0
- package/dist/types/website-strategy.d.ts.map +1 -0
- package/dist/types/website-strategy.js +105 -0
- package/dist/types/website-strategy.js.map +1 -0
- package/dist/types/workflow.d.ts +21 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +8 -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/consensus.d.ts.map +1 -1
- package/dist/workflow/consensus.js +2 -0
- package/dist/workflow/consensus.js.map +1 -1
- package/dist/workflow/execution-mode.d.ts.map +1 -1
- package/dist/workflow/execution-mode.js +18 -0
- package/dist/workflow/execution-mode.js.map +1 -1
- package/dist/workflow/index.d.ts +4 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +37 -0
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/overview.d.ts +89 -0
- package/dist/workflow/overview.d.ts.map +1 -0
- package/dist/workflow/overview.js +358 -0
- package/dist/workflow/overview.js.map +1 -0
- package/dist/workflow/plan-mode.d.ts +6 -4
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +148 -6
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/website-strategy.d.ts +79 -0
- package/dist/workflow/website-strategy.d.ts.map +1 -0
- package/dist/workflow/website-strategy.js +310 -0
- package/dist/workflow/website-strategy.js.map +1 -0
- package/dist/workflow/website-updater.d.ts +17 -0
- package/dist/workflow/website-updater.d.ts.map +1 -0
- package/dist/workflow/website-updater.js +116 -0
- package/dist/workflow/website-updater.js.map +1 -0
- package/dist/workflow/workflow-logger.d.ts +1 -1
- package/dist/workflow/workflow-logger.d.ts.map +1 -1
- package/dist/workflow/workflow-logger.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/gemini.ts +10 -4
- package/src/adapters/grok.ts +10 -4
- package/src/adapters/openai.ts +38 -6
- package/src/cli/commands/create.ts +58 -4
- package/src/cli/interactive.ts +143 -7
- package/src/generators/all.ts +49 -332
- package/src/generators/doc-parser.ts +449 -0
- package/src/generators/frontend-design-analyzer.ts +261 -0
- package/src/generators/shared-packages.ts +500 -0
- package/src/generators/templates/index.ts +8 -0
- package/src/generators/templates/website-components.ts +330 -0
- package/src/generators/templates/website-config.ts +444 -0
- package/src/generators/templates/website-conversion.ts +341 -0
- 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-seo.ts +370 -0
- package/src/generators/templates/website.ts +38 -905
- package/src/generators/website-content-scanner.ts +208 -0
- package/src/generators/website-context.ts +493 -0
- package/src/generators/website-debug.ts +130 -0
- package/src/generators/website.ts +178 -20
- package/src/generators/workspace-root.ts +113 -0
- package/src/state/index.ts +56 -0
- package/src/types/consensus.ts +3 -0
- package/src/types/website-strategy.ts +243 -0
- package/src/types/workflow.ts +21 -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/consensus.ts +2 -0
- package/src/workflow/execution-mode.ts +21 -0
- package/src/workflow/index.ts +37 -0
- package/src/workflow/overview.ts +475 -0
- package/src/workflow/plan-mode.ts +193 -8
- package/src/workflow/website-strategy.ts +379 -0
- package/src/workflow/website-updater.ts +142 -0
- package/src/workflow/workflow-logger.ts +1 -0
- package/tests/adapters/persona-switching.test.ts +63 -0
- 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 +159 -0
- 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 +331 -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/website-seo-quality.test.ts +246 -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/overview.test.ts +392 -0
- package/tests/workflow/plan-mode.test.ts +111 -1
- package/tests/workflow/website-strategy.test.ts +246 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for upgrade handler content context builder
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
6
|
+
import { promises as fs } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import { buildUpgradeContentContext } from '../../src/upgrade/handlers.js';
|
|
10
|
+
|
|
11
|
+
// Mock external dependencies to isolate unit behavior
|
|
12
|
+
vi.mock('../../src/generators/website-context.js', () => ({
|
|
13
|
+
buildWebsiteContext: vi.fn(),
|
|
14
|
+
resolveBrandAssets: vi.fn(),
|
|
15
|
+
validateWebsiteContext: vi.fn(),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
vi.mock('../../src/generators/workspace-root.js', () => ({
|
|
19
|
+
resolveWorkspaceRoot: vi.fn(),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
vi.mock('../../src/workflow/website-strategy.js', () => ({
|
|
23
|
+
loadWebsiteStrategy: vi.fn(),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
vi.mock('../../src/state/persistence.js', () => ({
|
|
27
|
+
loadState: vi.fn(),
|
|
28
|
+
saveState: vi.fn(),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
import { buildWebsiteContext, resolveBrandAssets, validateWebsiteContext } from '../../src/generators/website-context.js';
|
|
32
|
+
import { resolveWorkspaceRoot } from '../../src/generators/workspace-root.js';
|
|
33
|
+
import { loadWebsiteStrategy } from '../../src/workflow/website-strategy.js';
|
|
34
|
+
import { loadState } from '../../src/state/persistence.js';
|
|
35
|
+
|
|
36
|
+
const mockBuildWebsiteContext = vi.mocked(buildWebsiteContext);
|
|
37
|
+
const mockResolveBrandAssets = vi.mocked(resolveBrandAssets);
|
|
38
|
+
const mockValidateWebsiteContext = vi.mocked(validateWebsiteContext);
|
|
39
|
+
const mockResolveWorkspaceRoot = vi.mocked(resolveWorkspaceRoot);
|
|
40
|
+
const mockLoadWebsiteStrategy = vi.mocked(loadWebsiteStrategy);
|
|
41
|
+
const mockLoadState = vi.mocked(loadState);
|
|
42
|
+
|
|
43
|
+
let tmpDir: string;
|
|
44
|
+
|
|
45
|
+
beforeEach(async () => {
|
|
46
|
+
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'popeye-handlers-test-'));
|
|
47
|
+
vi.clearAllMocks();
|
|
48
|
+
|
|
49
|
+
// Default mock returns
|
|
50
|
+
mockBuildWebsiteContext.mockResolvedValue({
|
|
51
|
+
productName: 'TestProject',
|
|
52
|
+
features: [{ title: 'Feature 1', description: 'A great feature' }],
|
|
53
|
+
rawDocs: '# Test docs',
|
|
54
|
+
});
|
|
55
|
+
mockResolveBrandAssets.mockResolvedValue({
|
|
56
|
+
logoSource: null,
|
|
57
|
+
faviconSource: null,
|
|
58
|
+
targets: [],
|
|
59
|
+
});
|
|
60
|
+
mockResolveWorkspaceRoot.mockResolvedValue(tmpDir);
|
|
61
|
+
mockLoadWebsiteStrategy.mockResolvedValue(null);
|
|
62
|
+
mockLoadState.mockResolvedValue(null);
|
|
63
|
+
mockValidateWebsiteContext.mockReturnValue({
|
|
64
|
+
passed: true,
|
|
65
|
+
issues: [],
|
|
66
|
+
warnings: [],
|
|
67
|
+
contentScore: 100,
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
afterEach(async () => {
|
|
72
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('buildUpgradeContentContext', () => {
|
|
76
|
+
it('should build context from user docs and return features', async () => {
|
|
77
|
+
const { context, warning } = await buildUpgradeContentContext(tmpDir, 'TestProject');
|
|
78
|
+
|
|
79
|
+
expect(warning).toBeUndefined();
|
|
80
|
+
expect(context).toBeDefined();
|
|
81
|
+
expect(context!.productName).toBe('TestProject');
|
|
82
|
+
expect(context!.features).toHaveLength(1);
|
|
83
|
+
expect(context!.features[0].title).toBe('Feature 1');
|
|
84
|
+
|
|
85
|
+
// Verify buildWebsiteContext was called with correct args
|
|
86
|
+
expect(mockBuildWebsiteContext).toHaveBeenCalledWith(tmpDir, 'TestProject');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should apply brand context from state when available', async () => {
|
|
90
|
+
mockLoadState.mockResolvedValue({
|
|
91
|
+
name: 'TestProject',
|
|
92
|
+
language: 'all',
|
|
93
|
+
brandContext: {
|
|
94
|
+
primaryColor: '#2563EB',
|
|
95
|
+
logoPath: '/path/to/logo.png',
|
|
96
|
+
},
|
|
97
|
+
} as any);
|
|
98
|
+
|
|
99
|
+
const { context } = await buildUpgradeContentContext(tmpDir, 'TestProject');
|
|
100
|
+
|
|
101
|
+
expect(context!.brand).toBeDefined();
|
|
102
|
+
expect(context!.brand!.primaryColor).toBe('#2563EB');
|
|
103
|
+
expect(context!.brand!.logoPath).toBe('/path/to/logo.png');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should load website strategy when available', async () => {
|
|
107
|
+
const mockStrategy = {
|
|
108
|
+
icp: { title: 'Developer', painPoints: ['slow builds'] },
|
|
109
|
+
messaging: { headline: 'Build faster', subheadline: 'Ship more' },
|
|
110
|
+
};
|
|
111
|
+
mockLoadWebsiteStrategy.mockResolvedValue({
|
|
112
|
+
strategy: mockStrategy as any,
|
|
113
|
+
metadata: { inputHash: 'abc', generatedAt: '2024-01-01', version: '1.0' },
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const { context } = await buildUpgradeContentContext(tmpDir, 'TestProject');
|
|
117
|
+
|
|
118
|
+
expect(context!.strategy).toBeDefined();
|
|
119
|
+
expect(context!.strategy).toBe(mockStrategy);
|
|
120
|
+
expect(mockLoadWebsiteStrategy).toHaveBeenCalledWith(tmpDir);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should resolve brand assets using workspace root', async () => {
|
|
124
|
+
const wsRoot = '/resolved/workspace/root';
|
|
125
|
+
mockResolveWorkspaceRoot.mockResolvedValue(wsRoot);
|
|
126
|
+
|
|
127
|
+
const { context } = await buildUpgradeContentContext(tmpDir, 'TestProject');
|
|
128
|
+
|
|
129
|
+
expect(context).toBeDefined();
|
|
130
|
+
expect(mockResolveWorkspaceRoot).toHaveBeenCalledWith(tmpDir);
|
|
131
|
+
expect(mockResolveBrandAssets).toHaveBeenCalledWith(wsRoot, context!.brand);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should return warning on error without crashing', async () => {
|
|
135
|
+
mockBuildWebsiteContext.mockRejectedValue(new Error('Docs directory not found'));
|
|
136
|
+
|
|
137
|
+
const { context, warning } = await buildUpgradeContentContext(tmpDir, 'TestProject');
|
|
138
|
+
|
|
139
|
+
expect(context).toBeUndefined();
|
|
140
|
+
expect(warning).toBe('Docs directory not found');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should handle non-Error throws gracefully', async () => {
|
|
144
|
+
mockBuildWebsiteContext.mockRejectedValue('unexpected string error');
|
|
145
|
+
|
|
146
|
+
const { context, warning } = await buildUpgradeContentContext(tmpDir, 'TestProject');
|
|
147
|
+
|
|
148
|
+
expect(context).toBeUndefined();
|
|
149
|
+
expect(warning).toBe('Unknown error building website context');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should work when no state or strategy exists', async () => {
|
|
153
|
+
// Default mocks already return null for state and strategy
|
|
154
|
+
const { context, warning } = await buildUpgradeContentContext(tmpDir, 'TestProject');
|
|
155
|
+
|
|
156
|
+
expect(warning).toBeUndefined();
|
|
157
|
+
expect(context).toBeDefined();
|
|
158
|
+
expect(context!.strategy).toBeUndefined();
|
|
159
|
+
// Brand should still be whatever buildWebsiteContext returns
|
|
160
|
+
expect(context!.productName).toBe('TestProject');
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for auto-fix-bundler: CSS/PostCSS/Tailwind/webpack error parsing and config discovery
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import { promises as fs } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import {
|
|
10
|
+
parseBundlerErrors,
|
|
11
|
+
findRelatedConfigs,
|
|
12
|
+
parseMultiFileResponse,
|
|
13
|
+
} from '../../src/workflow/auto-fix-bundler.js';
|
|
14
|
+
|
|
15
|
+
let tempDir: string;
|
|
16
|
+
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'popeye-bundler-'));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(async () => {
|
|
22
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('parseBundlerErrors', () => {
|
|
26
|
+
it('should parse Tailwind CSS "class does not exist" errors', () => {
|
|
27
|
+
const output = `./src/app/globals.css:1:1
|
|
28
|
+
Syntax error: /Users/test/project/apps/website/src/app/globals.css The \`bg-background\` class does not exist. If \`bg-background\` is a custom class, make sure it is defined within a \`@layer\` directive.
|
|
29
|
+
|
|
30
|
+
> 1 | @tailwind base;
|
|
31
|
+
| ^
|
|
32
|
+
2 | @tailwind components;
|
|
33
|
+
3 | @tailwind utilities;`;
|
|
34
|
+
|
|
35
|
+
const errors = parseBundlerErrors(output);
|
|
36
|
+
|
|
37
|
+
expect(errors.length).toBeGreaterThanOrEqual(1);
|
|
38
|
+
// Should find either the css pattern or the file:line:col pattern
|
|
39
|
+
const cssError = errors.find(e => e.type === 'css' || e.type === 'syntax');
|
|
40
|
+
expect(cssError).toBeDefined();
|
|
41
|
+
expect(cssError!.message).toContain('bg-background');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should parse CSS syntax error with absolute path', () => {
|
|
45
|
+
const output = `Syntax error: /Users/test/apps/website/src/globals.css The \`text-foreground\` class does not exist. If \`text-foreground\` is a custom class, make sure it is defined within a \`@layer\` directive.
|
|
46
|
+
|
|
47
|
+
Some other lines here.`;
|
|
48
|
+
|
|
49
|
+
const errors = parseBundlerErrors(output);
|
|
50
|
+
|
|
51
|
+
expect(errors.length).toBe(1);
|
|
52
|
+
expect(errors[0].type).toBe('css');
|
|
53
|
+
expect(errors[0].file).toContain('globals.css');
|
|
54
|
+
expect(errors[0].message).toContain('text-foreground');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should parse module not found errors', () => {
|
|
58
|
+
const output = `Module not found: Can't resolve '@acme/design-tokens/tailwind' in '/Users/test/apps/website'
|
|
59
|
+
|
|
60
|
+
https://nextjs.org/docs/messages/module-not-found
|
|
61
|
+
|
|
62
|
+
Import trace for requested module:
|
|
63
|
+
./src/app/globals.css`;
|
|
64
|
+
|
|
65
|
+
const errors = parseBundlerErrors(output);
|
|
66
|
+
|
|
67
|
+
expect(errors.length).toBeGreaterThanOrEqual(1);
|
|
68
|
+
const moduleError = errors.find(e => e.type === 'module-not-found');
|
|
69
|
+
expect(moduleError).toBeDefined();
|
|
70
|
+
expect(moduleError!.message).toContain('@acme/design-tokens/tailwind');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should parse file:line:col reference for non-TS files', () => {
|
|
74
|
+
const output = `./src/app/globals.css:1:1
|
|
75
|
+
Syntax error: Something went wrong with the CSS processing.
|
|
76
|
+
|
|
77
|
+
> 1 | @tailwind base;
|
|
78
|
+
| ^`;
|
|
79
|
+
|
|
80
|
+
const errors = parseBundlerErrors(output);
|
|
81
|
+
|
|
82
|
+
expect(errors.length).toBeGreaterThanOrEqual(1);
|
|
83
|
+
const refError = errors.find(e => e.type === 'syntax');
|
|
84
|
+
expect(refError).toBeDefined();
|
|
85
|
+
expect(refError!.file).toContain('globals.css');
|
|
86
|
+
expect(refError!.line).toBe(1);
|
|
87
|
+
expect(refError!.column).toBe(1);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should parse webpack build failure with import traces', () => {
|
|
91
|
+
const output = `Build failed because of webpack errors
|
|
92
|
+
|
|
93
|
+
Import trace for requested module:
|
|
94
|
+
./src/app/globals.css
|
|
95
|
+
./src/components/Layout.tsx`;
|
|
96
|
+
|
|
97
|
+
const errors = parseBundlerErrors(output);
|
|
98
|
+
|
|
99
|
+
expect(errors.length).toBeGreaterThanOrEqual(1);
|
|
100
|
+
expect(errors.some(e => e.file.includes('globals.css'))).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should strip ANSI color codes before parsing', () => {
|
|
104
|
+
const output = `\x1b[31mSyntax error: /path/to/file.css The \`bg-primary\` class does not exist.\x1b[0m`;
|
|
105
|
+
|
|
106
|
+
const errors = parseBundlerErrors(output);
|
|
107
|
+
|
|
108
|
+
expect(errors.length).toBe(1);
|
|
109
|
+
expect(errors[0].message).toContain('bg-primary');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should de-duplicate errors from the same file', () => {
|
|
113
|
+
const output = `Syntax error: /path/to/globals.css The \`bg-background\` class does not exist.
|
|
114
|
+
|
|
115
|
+
Syntax error: /path/to/globals.css The \`bg-background\` class does not exist.`;
|
|
116
|
+
|
|
117
|
+
const errors = parseBundlerErrors(output);
|
|
118
|
+
|
|
119
|
+
// Should de-duplicate by file
|
|
120
|
+
expect(errors.length).toBe(1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should return empty array for TypeScript-only errors', () => {
|
|
124
|
+
const output = `src/index.ts(10,5): error TS2304: Cannot find name 'foo'
|
|
125
|
+
src/utils.ts(25,12): error TS2339: Property 'bar' does not exist`;
|
|
126
|
+
|
|
127
|
+
const errors = parseBundlerErrors(output);
|
|
128
|
+
|
|
129
|
+
expect(errors.length).toBe(0);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should return empty array for generic npm errors', () => {
|
|
133
|
+
const output = `npm ERR! code ELIFECYCLE
|
|
134
|
+
npm ERR! errno 1
|
|
135
|
+
npm ERR! Exit status 1`;
|
|
136
|
+
|
|
137
|
+
const errors = parseBundlerErrors(output);
|
|
138
|
+
|
|
139
|
+
expect(errors.length).toBe(0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should return empty array for empty output', () => {
|
|
143
|
+
expect(parseBundlerErrors('')).toEqual([]);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('findRelatedConfigs', () => {
|
|
148
|
+
it('should find tailwind and postcss configs in app directory', async () => {
|
|
149
|
+
const appDir = path.join(tempDir, 'apps', 'website');
|
|
150
|
+
await fs.mkdir(appDir, { recursive: true });
|
|
151
|
+
await fs.writeFile(path.join(appDir, 'tailwind.config.ts'), 'export default {}');
|
|
152
|
+
await fs.writeFile(path.join(appDir, 'postcss.config.js'), 'module.exports = {}');
|
|
153
|
+
|
|
154
|
+
const configs = await findRelatedConfigs(tempDir, 'apps/website/src/globals.css');
|
|
155
|
+
|
|
156
|
+
expect(configs.length).toBeGreaterThanOrEqual(2);
|
|
157
|
+
expect(configs.some(c => c.path.includes('tailwind.config.ts'))).toBe(true);
|
|
158
|
+
expect(configs.some(c => c.path.includes('postcss.config.js'))).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should search project root as well', async () => {
|
|
162
|
+
await fs.writeFile(path.join(tempDir, 'package.json'), '{"name": "test"}');
|
|
163
|
+
|
|
164
|
+
const configs = await findRelatedConfigs(tempDir, 'src/globals.css');
|
|
165
|
+
|
|
166
|
+
expect(configs.some(c => c.path.includes('package.json'))).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should return empty array when no configs exist', async () => {
|
|
170
|
+
const configs = await findRelatedConfigs(tempDir, 'src/globals.css');
|
|
171
|
+
|
|
172
|
+
expect(configs).toEqual([]);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should cap config file content at 4000 chars', async () => {
|
|
176
|
+
const largeContent = 'x'.repeat(10000);
|
|
177
|
+
await fs.writeFile(path.join(tempDir, 'package.json'), largeContent);
|
|
178
|
+
|
|
179
|
+
const configs = await findRelatedConfigs(tempDir, 'src/globals.css');
|
|
180
|
+
|
|
181
|
+
const pkg = configs.find(c => c.path.includes('package.json'));
|
|
182
|
+
expect(pkg).toBeDefined();
|
|
183
|
+
expect(pkg!.content.length).toBeLessThanOrEqual(4000);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('parseMultiFileResponse', () => {
|
|
188
|
+
it('should parse single file response', () => {
|
|
189
|
+
const response = `FILE: /path/to/tailwind.config.ts
|
|
190
|
+
\`\`\`typescript
|
|
191
|
+
import type { Config } from 'tailwindcss';
|
|
192
|
+
export default { content: [] } satisfies Config;
|
|
193
|
+
\`\`\``;
|
|
194
|
+
|
|
195
|
+
const results = parseMultiFileResponse(response);
|
|
196
|
+
|
|
197
|
+
expect(results.length).toBe(1);
|
|
198
|
+
expect(results[0].targetPath).toBe('/path/to/tailwind.config.ts');
|
|
199
|
+
expect(results[0].content).toContain('tailwindcss');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should parse multiple file responses', () => {
|
|
203
|
+
const response = `I'll fix both files:
|
|
204
|
+
|
|
205
|
+
FILE: /path/to/tailwind.config.ts
|
|
206
|
+
\`\`\`
|
|
207
|
+
export default { colors: { background: 'hsl(var(--background))' } };
|
|
208
|
+
\`\`\`
|
|
209
|
+
|
|
210
|
+
FILE: /path/to/globals.css
|
|
211
|
+
\`\`\`
|
|
212
|
+
@tailwind base;
|
|
213
|
+
@tailwind components;
|
|
214
|
+
@tailwind utilities;
|
|
215
|
+
\`\`\``;
|
|
216
|
+
|
|
217
|
+
const results = parseMultiFileResponse(response);
|
|
218
|
+
|
|
219
|
+
expect(results.length).toBe(2);
|
|
220
|
+
expect(results[0].targetPath).toContain('tailwind.config.ts');
|
|
221
|
+
expect(results[1].targetPath).toContain('globals.css');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should skip files with very short content', () => {
|
|
225
|
+
const response = `FILE: /path/to/file.ts
|
|
226
|
+
\`\`\`
|
|
227
|
+
tiny
|
|
228
|
+
\`\`\``;
|
|
229
|
+
|
|
230
|
+
const results = parseMultiFileResponse(response);
|
|
231
|
+
|
|
232
|
+
expect(results.length).toBe(0);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should return empty array for unparseable response', () => {
|
|
236
|
+
const response = `I'm not sure how to fix this error.`;
|
|
237
|
+
|
|
238
|
+
const results = parseMultiFileResponse(response);
|
|
239
|
+
|
|
240
|
+
expect(results.length).toBe(0);
|
|
241
|
+
});
|
|
242
|
+
});
|