@auto-engineer/component-implementor-react 1.95.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/.turbo/turbo-build.log +5 -0
- package/.turbo/turbo-test.log +14 -0
- package/.turbo/turbo-type-check.log +4 -0
- package/CHANGELOG.md +109 -0
- package/LICENSE +10 -0
- package/dist/src/commands/implement-component.d.ts +45 -0
- package/dist/src/commands/implement-component.d.ts.map +1 -0
- package/dist/src/commands/implement-component.js +124 -0
- package/dist/src/commands/implement-component.js.map +1 -0
- package/dist/src/commands/implement-component.test.d.ts +2 -0
- package/dist/src/commands/implement-component.test.d.ts.map +1 -0
- package/dist/src/commands/implement-component.test.js +130 -0
- package/dist/src/commands/implement-component.test.js.map +1 -0
- package/dist/src/extract-code-block.d.ts +2 -0
- package/dist/src/extract-code-block.d.ts.map +1 -0
- package/dist/src/extract-code-block.js +7 -0
- package/dist/src/extract-code-block.js.map +1 -0
- package/dist/src/extract-code-block.test.d.ts +2 -0
- package/dist/src/extract-code-block.test.d.ts.map +1 -0
- package/dist/src/extract-code-block.test.js +28 -0
- package/dist/src/extract-code-block.test.js.map +1 -0
- package/dist/src/generate-component.d.ts +15 -0
- package/dist/src/generate-component.d.ts.map +1 -0
- package/dist/src/generate-component.js +39 -0
- package/dist/src/generate-component.js.map +1 -0
- package/dist/src/generate-component.test.d.ts +2 -0
- package/dist/src/generate-component.test.d.ts.map +1 -0
- package/dist/src/generate-component.test.js +77 -0
- package/dist/src/generate-component.test.js.map +1 -0
- package/dist/src/generate-story.d.ts +14 -0
- package/dist/src/generate-story.d.ts.map +1 -0
- package/dist/src/generate-story.js +35 -0
- package/dist/src/generate-story.js.map +1 -0
- package/dist/src/generate-story.test.d.ts +2 -0
- package/dist/src/generate-story.test.d.ts.map +1 -0
- package/dist/src/generate-story.test.js +62 -0
- package/dist/src/generate-story.test.js.map +1 -0
- package/dist/src/generate-test.d.ts +14 -0
- package/dist/src/generate-test.d.ts.map +1 -0
- package/dist/src/generate-test.js +38 -0
- package/dist/src/generate-test.js.map +1 -0
- package/dist/src/generate-test.test.d.ts +2 -0
- package/dist/src/generate-test.test.d.ts.map +1 -0
- package/dist/src/generate-test.test.js +66 -0
- package/dist/src/generate-test.test.js.map +1 -0
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/ketchup-plan.md +13 -0
- package/package.json +25 -0
- package/src/commands/implement-component.test.ts +151 -0
- package/src/commands/implement-component.ts +190 -0
- package/src/extract-code-block.test.ts +33 -0
- package/src/extract-code-block.ts +6 -0
- package/src/generate-component.test.ts +93 -0
- package/src/generate-component.ts +57 -0
- package/src/generate-story.test.ts +75 -0
- package/src/generate-story.ts +51 -0
- package/src/generate-test.test.ts +78 -0
- package/src/generate-test.ts +55 -0
- package/src/index.ts +11 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createModelFromEnv } from '@auto-engineer/model-factory';
|
|
2
|
+
import { generateText } from 'ai';
|
|
3
|
+
import { extractCodeBlock } from './extract-code-block';
|
|
4
|
+
|
|
5
|
+
type SpecDeltas = {
|
|
6
|
+
structure: string[];
|
|
7
|
+
rendering: string[];
|
|
8
|
+
interaction: string[];
|
|
9
|
+
styling: string[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type GenerateStoryInput = {
|
|
13
|
+
componentName: string;
|
|
14
|
+
specDeltas: SpecDeltas;
|
|
15
|
+
componentCode: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function buildSpecSection(heading: string, items: string[]): string {
|
|
19
|
+
if (items.length === 0) return '';
|
|
20
|
+
return `## ${heading}\n${items.map((i) => `- ${i}`).join('\n')}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function buildPrompt(input: GenerateStoryInput): string {
|
|
24
|
+
const sections = [
|
|
25
|
+
buildSpecSection('Structure', input.specDeltas.structure),
|
|
26
|
+
buildSpecSection('Rendering', input.specDeltas.rendering),
|
|
27
|
+
buildSpecSection('Interaction', input.specDeltas.interaction),
|
|
28
|
+
buildSpecSection('Styling', input.specDeltas.styling),
|
|
29
|
+
]
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.join('\n\n');
|
|
32
|
+
|
|
33
|
+
return `Component: ${input.componentName}\n\n${sections}\n\n## Component Code\n\`\`\`tsx\n${input.componentCode}\n\`\`\``;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const SYSTEM_PROMPT = `You are a Storybook expert. Write a Storybook story file (.stories.tsx) for the described React component.
|
|
37
|
+
|
|
38
|
+
Rules:
|
|
39
|
+
- Use CSF3 format (export default meta, named story exports)
|
|
40
|
+
- Import Meta and StoryObj from @storybook/react
|
|
41
|
+
- Create stories that showcase different states from the spec
|
|
42
|
+
- Return ONLY the story file code, no commentary`;
|
|
43
|
+
|
|
44
|
+
export async function generateStoryFile(input: GenerateStoryInput): Promise<string> {
|
|
45
|
+
const { text } = await generateText({
|
|
46
|
+
model: createModelFromEnv(),
|
|
47
|
+
system: SYSTEM_PROMPT,
|
|
48
|
+
prompt: buildPrompt(input),
|
|
49
|
+
});
|
|
50
|
+
return extractCodeBlock(text);
|
|
51
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { generateTestFile } from './generate-test';
|
|
3
|
+
|
|
4
|
+
vi.mock('ai', () => ({
|
|
5
|
+
generateText: vi.fn(),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
vi.mock('@auto-engineer/model-factory', () => ({
|
|
9
|
+
createModelFromEnv: vi.fn(() => 'mock-model'),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
import { generateText } from 'ai';
|
|
13
|
+
|
|
14
|
+
const specDeltas = {
|
|
15
|
+
structure: ['renders a Button element', 'accepts children prop'],
|
|
16
|
+
rendering: ['shows loading spinner when loading=true'],
|
|
17
|
+
interaction: ['calls onClick when clicked'],
|
|
18
|
+
styling: ['applies primary variant by default'],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe('generateTestFile', () => {
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
vi.clearAllMocks();
|
|
24
|
+
});
|
|
25
|
+
it('returns generated test code from AI with spec deltas in prompt', async () => {
|
|
26
|
+
const mockGenerateText = vi.mocked(generateText);
|
|
27
|
+
mockGenerateText.mockResolvedValue({
|
|
28
|
+
text: '```tsx\nimport { render } from "@testing-library/react";\n```',
|
|
29
|
+
} as Awaited<ReturnType<typeof generateText>>);
|
|
30
|
+
|
|
31
|
+
const result = await generateTestFile({
|
|
32
|
+
componentName: 'Button',
|
|
33
|
+
specDeltas,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(result).toBe('import { render } from "@testing-library/react";');
|
|
37
|
+
expect(mockGenerateText).toHaveBeenCalledWith({
|
|
38
|
+
model: 'mock-model',
|
|
39
|
+
system: expect.stringContaining('vitest'),
|
|
40
|
+
prompt: expect.stringContaining('renders a Button element'),
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('includes all spec delta sections in the prompt', async () => {
|
|
45
|
+
const mockGenerateText = vi.mocked(generateText);
|
|
46
|
+
mockGenerateText.mockResolvedValue({
|
|
47
|
+
text: 'test code',
|
|
48
|
+
} as Awaited<ReturnType<typeof generateText>>);
|
|
49
|
+
|
|
50
|
+
await generateTestFile({ componentName: 'Button', specDeltas });
|
|
51
|
+
|
|
52
|
+
const prompt = mockGenerateText.mock.calls[0][0].prompt as string;
|
|
53
|
+
expect(prompt).toContain('## Structure');
|
|
54
|
+
expect(prompt).toContain('renders a Button element');
|
|
55
|
+
expect(prompt).toContain('## Rendering');
|
|
56
|
+
expect(prompt).toContain('shows loading spinner when loading=true');
|
|
57
|
+
expect(prompt).toContain('## Interaction');
|
|
58
|
+
expect(prompt).toContain('calls onClick when clicked');
|
|
59
|
+
expect(prompt).toContain('## Styling');
|
|
60
|
+
expect(prompt).toContain('applies primary variant by default');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('includes existing component code as context when provided', async () => {
|
|
64
|
+
const mockGenerateText = vi.mocked(generateText);
|
|
65
|
+
mockGenerateText.mockResolvedValue({
|
|
66
|
+
text: 'test code',
|
|
67
|
+
} as Awaited<ReturnType<typeof generateText>>);
|
|
68
|
+
|
|
69
|
+
await generateTestFile({
|
|
70
|
+
componentName: 'Button',
|
|
71
|
+
specDeltas,
|
|
72
|
+
existingComponent: 'export function Button() { return <button />; }',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const prompt = mockGenerateText.mock.calls[0][0].prompt as string;
|
|
76
|
+
expect(prompt).toContain('export function Button() { return <button />; }');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createModelFromEnv } from '@auto-engineer/model-factory';
|
|
2
|
+
import { generateText } from 'ai';
|
|
3
|
+
import { extractCodeBlock } from './extract-code-block';
|
|
4
|
+
|
|
5
|
+
type SpecDeltas = {
|
|
6
|
+
structure: string[];
|
|
7
|
+
rendering: string[];
|
|
8
|
+
interaction: string[];
|
|
9
|
+
styling: string[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type GenerateTestInput = {
|
|
13
|
+
componentName: string;
|
|
14
|
+
specDeltas: SpecDeltas;
|
|
15
|
+
existingComponent?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function buildSpecSection(heading: string, items: string[]): string {
|
|
19
|
+
if (items.length === 0) return '';
|
|
20
|
+
return `## ${heading}\n${items.map((i) => `- ${i}`).join('\n')}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function buildPrompt(input: GenerateTestInput): string {
|
|
24
|
+
const sections = [
|
|
25
|
+
buildSpecSection('Structure', input.specDeltas.structure),
|
|
26
|
+
buildSpecSection('Rendering', input.specDeltas.rendering),
|
|
27
|
+
buildSpecSection('Interaction', input.specDeltas.interaction),
|
|
28
|
+
buildSpecSection('Styling', input.specDeltas.styling),
|
|
29
|
+
]
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.join('\n\n');
|
|
32
|
+
|
|
33
|
+
const existingContext = input.existingComponent
|
|
34
|
+
? `\n\n## Existing Component\n\`\`\`tsx\n${input.existingComponent}\n\`\`\``
|
|
35
|
+
: '';
|
|
36
|
+
|
|
37
|
+
return `Component: ${input.componentName}\n\n${sections}${existingContext}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const SYSTEM_PROMPT = `You are a React testing expert. Write a vitest test file for the described component using @testing-library/react.
|
|
41
|
+
|
|
42
|
+
Rules:
|
|
43
|
+
- Import from vitest (describe, it, expect) and @testing-library/react (render, screen, fireEvent)
|
|
44
|
+
- Use describe/it blocks
|
|
45
|
+
- Test each specified behavior
|
|
46
|
+
- Return ONLY the test file code, no commentary`;
|
|
47
|
+
|
|
48
|
+
export async function generateTestFile(input: GenerateTestInput): Promise<string> {
|
|
49
|
+
const { text } = await generateText({
|
|
50
|
+
model: createModelFromEnv(),
|
|
51
|
+
system: SYSTEM_PROMPT,
|
|
52
|
+
prompt: buildPrompt(input),
|
|
53
|
+
});
|
|
54
|
+
return extractCodeBlock(text);
|
|
55
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { commandHandler as implementComponentHandler } from './commands/implement-component';
|
|
2
|
+
|
|
3
|
+
export const COMMANDS = [implementComponentHandler];
|
|
4
|
+
export { implementComponentHandler };
|
|
5
|
+
export type {
|
|
6
|
+
ComponentImplementationFailedEvent,
|
|
7
|
+
ComponentImplementedEvent,
|
|
8
|
+
ImplementComponentCommand,
|
|
9
|
+
ImplementComponentEvents,
|
|
10
|
+
} from './commands/implement-component';
|
|
11
|
+
export { handleImplementComponent } from './commands/implement-component';
|
package/tsconfig.json
ADDED
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
passWithNoTests: true,
|
|
6
|
+
include: ['src/**/*.test.ts'],
|
|
7
|
+
coverage: {
|
|
8
|
+
provider: 'v8',
|
|
9
|
+
include: ['src/**/*.ts'],
|
|
10
|
+
exclude: ['src/index.ts', 'src/**/*.test.ts'],
|
|
11
|
+
thresholds: { statements: 100, branches: 100, functions: 100, lines: 100 },
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
});
|