@discourser/design-system 0.15.1 → 0.18.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/dist/{chunk-UNWXE6UB.cjs → chunk-2P7Z5PVP.cjs} +817 -16
- package/dist/chunk-2P7Z5PVP.cjs.map +1 -0
- package/dist/{chunk-ABC7N32K.cjs → chunk-PFWU7QSM.cjs} +464 -8
- package/dist/chunk-PFWU7QSM.cjs.map +1 -0
- package/dist/{chunk-GD6Q2FUE.js → chunk-QC7LGFM3.js} +808 -18
- package/dist/chunk-QC7LGFM3.js.map +1 -0
- package/dist/{chunk-SBKRSXSZ.js → chunk-SNUJBT5R.js} +464 -8
- package/dist/chunk-SNUJBT5R.js.map +1 -0
- package/dist/components/Accordion.figma.d.ts +2 -0
- package/dist/components/Accordion.figma.d.ts.map +1 -0
- package/dist/components/Breadcrumb.d.ts +2 -0
- package/dist/components/Breadcrumb.d.ts.map +1 -1
- package/dist/components/Breadcrumb.figma.d.ts +2 -0
- package/dist/components/Breadcrumb.figma.d.ts.map +1 -0
- package/dist/components/ContentCard/ContentCard.d.ts +13 -0
- package/dist/components/ContentCard/ContentCard.d.ts.map +1 -0
- package/dist/components/ContentCard/ContentCard.figma.d.ts +2 -0
- package/dist/components/ContentCard/ContentCard.figma.d.ts.map +1 -0
- package/dist/components/ContentCard/index.d.ts +2 -0
- package/dist/components/ContentCard/index.d.ts.map +1 -0
- package/dist/components/{Heading.d.ts → Header.d.ts} +3 -3
- package/dist/components/Header.d.ts.map +1 -0
- package/dist/components/Header.figma.d.ts +2 -0
- package/dist/components/Header.figma.d.ts.map +1 -0
- package/dist/components/Icons/AccountIcon.d.ts +6 -0
- package/dist/components/Icons/AccountIcon.d.ts.map +1 -0
- package/dist/components/Icons/AudienceIcon.d.ts +6 -0
- package/dist/components/Icons/AudienceIcon.d.ts.map +1 -0
- package/dist/components/Icons/AudienceIcon.figma.d.ts +2 -0
- package/dist/components/Icons/AudienceIcon.figma.d.ts.map +1 -0
- package/dist/components/Icons/AudioSpeakerIcon.d.ts +6 -0
- package/dist/components/Icons/AudioSpeakerIcon.d.ts.map +1 -0
- package/dist/components/Icons/AudioSpeakerIcon.figma.d.ts +2 -0
- package/dist/components/Icons/AudioSpeakerIcon.figma.d.ts.map +1 -0
- package/dist/components/Icons/BookmarkPlusIcon.d.ts +6 -0
- package/dist/components/Icons/BookmarkPlusIcon.d.ts.map +1 -0
- package/dist/components/Icons/BookmarkPlusIcon.figma.d.ts +2 -0
- package/dist/components/Icons/BookmarkPlusIcon.figma.d.ts.map +1 -0
- package/dist/components/Icons/ChevronUpIcon.d.ts +6 -0
- package/dist/components/Icons/ChevronUpIcon.d.ts.map +1 -0
- package/dist/components/Icons/ClipBoardIcon.d.ts +6 -0
- package/dist/components/Icons/ClipBoardIcon.d.ts.map +1 -0
- package/dist/components/Icons/ClipBoardIcon.figma.d.ts +2 -0
- package/dist/components/Icons/ClipBoardIcon.figma.d.ts.map +1 -0
- package/dist/components/Icons/ClockIcon.d.ts.map +1 -1
- package/dist/components/Icons/DashboardIcon.d.ts +6 -0
- package/dist/components/Icons/DashboardIcon.d.ts.map +1 -0
- package/dist/components/Icons/DiscourserLogo.d.ts +6 -0
- package/dist/components/Icons/DiscourserLogo.d.ts.map +1 -0
- package/dist/components/Icons/DiscourserLogo.figma.d.ts +2 -0
- package/dist/components/Icons/DiscourserLogo.figma.d.ts.map +1 -0
- package/dist/components/Icons/ExitStudioIcon.d.ts +6 -0
- package/dist/components/Icons/ExitStudioIcon.d.ts.map +1 -0
- package/dist/components/Icons/ExitStudioIcon.figma.d.ts +2 -0
- package/dist/components/Icons/ExitStudioIcon.figma.d.ts.map +1 -0
- package/dist/components/Icons/GripDotsVerticalIcon.d.ts.map +1 -1
- package/dist/components/Icons/HelpIcon.d.ts +6 -0
- package/dist/components/Icons/HelpIcon.d.ts.map +1 -0
- package/dist/components/Icons/MicrophoneIcon.d.ts +6 -0
- package/dist/components/Icons/MicrophoneIcon.d.ts.map +1 -0
- package/dist/components/Icons/MicrophoneIcon.figma.d.ts +2 -0
- package/dist/components/Icons/MicrophoneIcon.figma.d.ts.map +1 -0
- package/dist/components/Icons/NotebookIcon.d.ts +6 -0
- package/dist/components/Icons/NotebookIcon.d.ts.map +1 -0
- package/dist/components/Icons/NotebookPenIcon.d.ts +6 -0
- package/dist/components/Icons/NotebookPenIcon.d.ts.map +1 -0
- package/dist/components/Icons/NotebookPenIcon.figma.d.ts +2 -0
- package/dist/components/Icons/NotebookPenIcon.figma.d.ts.map +1 -0
- package/dist/components/Icons/PausePlayIcon.d.ts +6 -0
- package/dist/components/Icons/PausePlayIcon.d.ts.map +1 -0
- package/dist/components/Icons/PausePlayIcon.figma.d.ts +2 -0
- package/dist/components/Icons/PausePlayIcon.figma.d.ts.map +1 -0
- package/dist/components/Icons/PlayIcon.d.ts +6 -0
- package/dist/components/Icons/PlayIcon.d.ts.map +1 -0
- package/dist/components/Icons/PlayIcon.figma.d.ts +2 -0
- package/dist/components/Icons/PlayIcon.figma.d.ts.map +1 -0
- package/dist/components/Icons/RecordIcon.d.ts +6 -0
- package/dist/components/Icons/RecordIcon.d.ts.map +1 -0
- package/dist/components/Icons/RecordIcon.figma.d.ts +2 -0
- package/dist/components/Icons/RecordIcon.figma.d.ts.map +1 -0
- package/dist/components/Icons/RepeatQuestionIcon.d.ts +6 -0
- package/dist/components/Icons/RepeatQuestionIcon.d.ts.map +1 -0
- package/dist/components/Icons/RepeatQuestionIcon.figma.d.ts +2 -0
- package/dist/components/Icons/RepeatQuestionIcon.figma.d.ts.map +1 -0
- package/dist/components/Icons/RightArrowIcon.d.ts +6 -0
- package/dist/components/Icons/RightArrowIcon.d.ts.map +1 -0
- package/dist/components/Icons/ScenarioIcon.d.ts +6 -0
- package/dist/components/Icons/ScenarioIcon.d.ts.map +1 -0
- package/dist/components/Icons/ScrollTextIcon.d.ts +6 -0
- package/dist/components/Icons/ScrollTextIcon.d.ts.map +1 -0
- package/dist/components/Icons/ScrollTextIcon.figma.d.ts +2 -0
- package/dist/components/Icons/ScrollTextIcon.figma.d.ts.map +1 -0
- package/dist/components/Icons/SparklesIcon.d.ts +6 -0
- package/dist/components/Icons/SparklesIcon.d.ts.map +1 -0
- package/dist/components/Icons/SparklesIcon.figma.d.ts +2 -0
- package/dist/components/Icons/SparklesIcon.figma.d.ts.map +1 -0
- package/dist/components/Icons/SpeechIcon.d.ts +6 -0
- package/dist/components/Icons/SpeechIcon.d.ts.map +1 -0
- package/dist/components/Icons/SpeechIcon.figma.d.ts +2 -0
- package/dist/components/Icons/SpeechIcon.figma.d.ts.map +1 -0
- package/dist/components/Icons/StopPlayIcon.d.ts +6 -0
- package/dist/components/Icons/StopPlayIcon.d.ts.map +1 -0
- package/dist/components/Icons/StopPlayIcon.figma.d.ts +2 -0
- package/dist/components/Icons/StopPlayIcon.figma.d.ts.map +1 -0
- package/dist/components/Icons/TimerIcon.d.ts +6 -0
- package/dist/components/Icons/TimerIcon.d.ts.map +1 -0
- package/dist/components/Icons/TimerIcon.figma.d.ts +2 -0
- package/dist/components/Icons/TimerIcon.figma.d.ts.map +1 -0
- package/dist/components/Icons/UserProfileIcon.d.ts +6 -0
- package/dist/components/Icons/UserProfileIcon.d.ts.map +1 -0
- package/dist/components/Icons/UserProfileIcon.figma.d.ts +2 -0
- package/dist/components/Icons/UserProfileIcon.figma.d.ts.map +1 -0
- package/dist/components/Icons/index.d.ts +26 -1
- package/dist/components/Icons/index.d.ts.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.d.ts +3 -0
- package/dist/components/NavigationMenu/NavigationMenu.d.ts.map +1 -0
- package/dist/components/NavigationMenu/NavigationMenu.figma.d.ts +2 -0
- package/dist/components/NavigationMenu/NavigationMenu.figma.d.ts.map +1 -0
- package/dist/components/NavigationMenu/index.d.ts +3 -0
- package/dist/components/NavigationMenu/index.d.ts.map +1 -0
- package/dist/components/NavigationMenu/types.d.ts +25 -0
- package/dist/components/NavigationMenu/types.d.ts.map +1 -0
- package/dist/components/QuickStartPage/QuickStartPage.d.ts +21 -0
- package/dist/components/QuickStartPage/QuickStartPage.d.ts.map +1 -0
- package/dist/components/QuickStartPage/index.d.ts +3 -0
- package/dist/components/QuickStartPage/index.d.ts.map +1 -0
- package/dist/components/ScenarioQueue/ScenarioQueue.figma.d.ts +2 -0
- package/dist/components/ScenarioQueue/ScenarioQueue.figma.d.ts.map +1 -0
- package/dist/components/ScenarioSettings/ScenarioSettings.d.ts +3 -0
- package/dist/components/ScenarioSettings/ScenarioSettings.d.ts.map +1 -0
- package/dist/components/ScenarioSettings/ScenarioSettings.figma.d.ts +2 -0
- package/dist/components/ScenarioSettings/ScenarioSettings.figma.d.ts.map +1 -0
- package/dist/components/ScenarioSettings/index.d.ts +3 -0
- package/dist/components/ScenarioSettings/index.d.ts.map +1 -0
- package/dist/components/ScenarioSettings/types.d.ts +54 -0
- package/dist/components/ScenarioSettings/types.d.ts.map +1 -0
- package/dist/components/index.cjs +86 -42
- package/dist/components/index.d.ts +14 -3
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/figma-codex/config.d.ts +8 -0
- package/dist/figma-codex/config.d.ts.map +1 -0
- package/dist/figma-codex/fixtures/CompoundComponent/CompoundComponent.d.ts +6 -0
- package/dist/figma-codex/fixtures/CompoundComponent/CompoundComponent.d.ts.map +1 -0
- package/dist/figma-codex/fixtures/CompoundComponent/index.d.ts +2 -0
- package/dist/figma-codex/fixtures/CompoundComponent/index.d.ts.map +1 -0
- package/dist/figma-codex/fixtures/CompoundComponent.figma.d.ts +2 -0
- package/dist/figma-codex/fixtures/CompoundComponent.figma.d.ts.map +1 -0
- package/dist/figma-codex/fixtures/SimpleComponent.d.ts +8 -0
- package/dist/figma-codex/fixtures/SimpleComponent.d.ts.map +1 -0
- package/dist/figma-codex/fixtures/SimpleComponent.figma.d.ts +2 -0
- package/dist/figma-codex/fixtures/SimpleComponent.figma.d.ts.map +1 -0
- package/dist/figma-codex/generate.d.ts +6 -0
- package/dist/figma-codex/generate.d.ts.map +1 -0
- package/dist/figma-codex/parser.d.ts +18 -0
- package/dist/figma-codex/parser.d.ts.map +1 -0
- package/dist/figma-codex/resolver.d.ts +5 -0
- package/dist/figma-codex/resolver.d.ts.map +1 -0
- package/dist/figma-codex/schema.d.ts +60 -0
- package/dist/figma-codex/schema.d.ts.map +1 -0
- package/dist/figma-codex/writer.d.ts +8 -0
- package/dist/figma-codex/writer.d.ts.map +1 -0
- package/dist/figma-codex.json +679 -0
- package/dist/index.cjs +90 -46
- package/dist/index.js +2 -2
- package/dist/preset/index.cjs +2 -2
- package/dist/preset/index.d.ts.map +1 -1
- package/dist/preset/index.js +1 -1
- package/dist/preset/recipes/accordion.d.ts.map +1 -1
- package/dist/preset/recipes/breadcrumb.d.ts.map +1 -1
- package/dist/preset/recipes/content-card.d.ts +2 -0
- package/dist/preset/recipes/content-card.d.ts.map +1 -0
- package/dist/preset/recipes/index.d.ts +4 -0
- package/dist/preset/recipes/index.d.ts.map +1 -1
- package/dist/preset/recipes/navigation-menu.d.ts +2 -0
- package/dist/preset/recipes/navigation-menu.d.ts.map +1 -0
- package/dist/preset/recipes/scenario-settings.d.ts +2 -0
- package/dist/preset/recipes/scenario-settings.d.ts.map +1 -0
- package/package.json +26 -2
- package/src/components/Accordion.figma.tsx +20 -0
- package/src/components/Breadcrumb.figma.tsx +18 -0
- package/src/components/Breadcrumb.tsx +33 -15
- package/src/components/ContentCard/ContentCard.figma.tsx +21 -0
- package/src/components/ContentCard/ContentCard.test.tsx +197 -0
- package/src/components/ContentCard/ContentCard.tsx +19 -0
- package/src/components/ContentCard/index.ts +13 -0
- package/src/components/Header.figma.tsx +25 -0
- package/src/components/{Heading.tsx → Header.tsx} +2 -2
- package/src/components/Icons/AccountIcon.tsx +26 -0
- package/src/components/Icons/AudienceIcon.figma.tsx +10 -0
- package/src/components/Icons/AudienceIcon.tsx +20 -0
- package/src/components/Icons/AudioSpeakerIcon.figma.tsx +10 -0
- package/src/components/Icons/AudioSpeakerIcon.tsx +26 -0
- package/src/components/Icons/BookmarkPlusIcon.figma.tsx +10 -0
- package/src/components/Icons/BookmarkPlusIcon.tsx +26 -0
- package/src/components/Icons/ChevronUpIcon.tsx +24 -0
- package/src/components/Icons/ClipBoardIcon.figma.tsx +10 -0
- package/src/components/Icons/ClipBoardIcon.tsx +61 -0
- package/src/components/Icons/ClockIcon.tsx +6 -6
- package/src/components/Icons/DashboardIcon.tsx +47 -0
- package/src/components/Icons/Discourser-Logo.svg +14 -0
- package/src/components/Icons/DiscourserLogo.figma.tsx +10 -0
- package/src/components/Icons/DiscourserLogo.tsx +72 -0
- package/src/components/Icons/ExitStudioIcon.figma.tsx +10 -0
- package/src/components/Icons/ExitStudioIcon.tsx +34 -0
- package/src/components/Icons/GripDotsVerticalIcon.tsx +6 -6
- package/src/components/Icons/HelpIcon.tsx +26 -0
- package/src/components/Icons/MicrophoneIcon.figma.tsx +10 -0
- package/src/components/Icons/MicrophoneIcon.tsx +40 -0
- package/src/components/Icons/NotebookIcon.tsx +26 -0
- package/src/components/Icons/NotebookPenIcon.figma.tsx +10 -0
- package/src/components/Icons/NotebookPenIcon.tsx +26 -0
- package/src/components/Icons/PausePlayIcon.figma.tsx +10 -0
- package/src/components/Icons/PausePlayIcon.tsx +41 -0
- package/src/components/Icons/PlayIcon.figma.tsx +10 -0
- package/src/components/Icons/PlayIcon.tsx +33 -0
- package/src/components/Icons/RecordIcon.figma.tsx +10 -0
- package/src/components/Icons/RecordIcon.tsx +41 -0
- package/src/components/Icons/RepeatQuestionIcon.figma.tsx +10 -0
- package/src/components/Icons/RepeatQuestionIcon.tsx +26 -0
- package/src/components/Icons/RightArrowIcon.tsx +23 -0
- package/src/components/Icons/ScenarioIcon.tsx +26 -0
- package/src/components/Icons/ScrollTextIcon.figma.tsx +10 -0
- package/src/components/Icons/ScrollTextIcon.tsx +26 -0
- package/src/components/Icons/SparklesIcon.figma.tsx +10 -0
- package/src/components/Icons/SparklesIcon.tsx +26 -0
- package/src/components/Icons/SpeechIcon.figma.tsx +10 -0
- package/src/components/Icons/SpeechIcon.tsx +26 -0
- package/src/components/Icons/StopPlayIcon.figma.tsx +10 -0
- package/src/components/Icons/StopPlayIcon.tsx +35 -0
- package/src/components/Icons/TimerIcon.figma.tsx +10 -0
- package/src/components/Icons/TimerIcon.tsx +26 -0
- package/src/components/Icons/UserProfileIcon.figma.tsx +10 -0
- package/src/components/Icons/UserProfileIcon.tsx +26 -0
- package/src/components/Icons/index.ts +39 -2
- package/src/components/NavigationMenu/NavigationMenu.figma.tsx +26 -0
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +524 -0
- package/src/components/NavigationMenu/NavigationMenu.tsx +102 -0
- package/src/components/NavigationMenu/index.ts +2 -0
- package/src/components/NavigationMenu/types.ts +27 -0
- package/src/components/QuickStartPage/QuickStartPage.tsx +627 -0
- package/src/components/QuickStartPage/index.ts +2 -0
- package/src/components/ScenarioQueue/ScenarioQueue.figma.tsx +37 -0
- package/src/components/ScenarioSettings/ScenarioSettings.figma.tsx +12 -0
- package/src/components/ScenarioSettings/ScenarioSettings.test.tsx +406 -0
- package/src/components/ScenarioSettings/ScenarioSettings.tsx +386 -0
- package/src/components/ScenarioSettings/index.ts +11 -0
- package/src/components/ScenarioSettings/types.ts +70 -0
- package/src/components/__tests__/Breadcrumb.test.tsx +94 -0
- package/src/components/index.ts +38 -4
- package/src/figma-codex/README.md +186 -0
- package/src/figma-codex/__tests__/config.test.ts +63 -0
- package/src/figma-codex/__tests__/generate.test.ts +78 -0
- package/src/figma-codex/__tests__/parser.test.ts +138 -0
- package/src/figma-codex/__tests__/resolver.test.ts +196 -0
- package/src/figma-codex/__tests__/writer.test.ts +111 -0
- package/src/figma-codex/config.ts +42 -0
- package/src/figma-codex/fixtures/CompoundComponent/CompoundComponent.tsx +17 -0
- package/src/figma-codex/fixtures/CompoundComponent/index.ts +1 -0
- package/src/figma-codex/fixtures/CompoundComponent.figma.tsx +14 -0
- package/src/figma-codex/fixtures/SimpleComponent.figma.tsx +10 -0
- package/src/figma-codex/fixtures/SimpleComponent.tsx +10 -0
- package/src/figma-codex/fixtures/expected-output.json +78 -0
- package/src/figma-codex/generate.ts +106 -0
- package/src/figma-codex/parser.ts +138 -0
- package/src/figma-codex/resolver.ts +280 -0
- package/src/figma-codex/schema.ts +79 -0
- package/src/figma-codex/writer.ts +54 -0
- package/src/preset/index.ts +6 -0
- package/src/preset/recipes/accordion.ts +8 -5
- package/src/preset/recipes/breadcrumb.ts +34 -2
- package/src/preset/recipes/content-card.ts +124 -0
- package/src/preset/recipes/index.ts +4 -0
- package/src/preset/recipes/navigation-menu.ts +97 -0
- package/src/preset/recipes/scenario-settings.ts +182 -0
- package/src/test/setup.ts +12 -9
- package/dist/chunk-ABC7N32K.cjs.map +0 -1
- package/dist/chunk-GD6Q2FUE.js.map +0 -1
- package/dist/chunk-SBKRSXSZ.js.map +0 -1
- package/dist/chunk-UNWXE6UB.cjs.map +0 -1
- package/dist/components/Heading.d.ts.map +0 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
3
|
+
import { mkdirSync, rmSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { writeManifest } from '../writer';
|
|
7
|
+
import type { ComponentEntry } from '../schema';
|
|
8
|
+
|
|
9
|
+
const sampleEntry: ComponentEntry = {
|
|
10
|
+
name: 'Button',
|
|
11
|
+
type: 'simple',
|
|
12
|
+
figma: {
|
|
13
|
+
fileKey: 'ABC123',
|
|
14
|
+
nodeId: '1:2',
|
|
15
|
+
url: 'https://www.figma.com/design/ABC123/F?node-id=1-2',
|
|
16
|
+
},
|
|
17
|
+
imports: {
|
|
18
|
+
primary: "import { Button } from '@discourser/design-system/Button'",
|
|
19
|
+
namedExports: ['Button'],
|
|
20
|
+
subpath: '@discourser/design-system/Button',
|
|
21
|
+
},
|
|
22
|
+
props: [{ name: 'label', type: 'string', required: true }],
|
|
23
|
+
example: '<Button label="Click me" />',
|
|
24
|
+
sourcePath: 'src/components/Button.tsx',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe('writeManifest', () => {
|
|
28
|
+
let tmpDir: string;
|
|
29
|
+
let outputPath: string;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
tmpDir = join(tmpdir(), `figma-codex-writer-${Date.now()}`);
|
|
33
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
34
|
+
outputPath = join(tmpDir, 'figma-codex.json');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('writes valid JSON to the output path', () => {
|
|
42
|
+
writeManifest(
|
|
43
|
+
{ Button: sampleEntry },
|
|
44
|
+
{ packageName: '@discourser/design-system', outputPath },
|
|
45
|
+
tmpDir,
|
|
46
|
+
);
|
|
47
|
+
const raw = readFileSync(outputPath, 'utf-8');
|
|
48
|
+
expect(() => JSON.parse(raw)).not.toThrow();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('includes schema version 1.0.0', () => {
|
|
52
|
+
writeManifest(
|
|
53
|
+
{ Button: sampleEntry },
|
|
54
|
+
{ packageName: '@discourser/design-system', outputPath },
|
|
55
|
+
tmpDir,
|
|
56
|
+
);
|
|
57
|
+
const manifest = JSON.parse(readFileSync(outputPath, 'utf-8'));
|
|
58
|
+
expect(manifest.version).toBe('1.0.0');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('includes generatedAt ISO timestamp', () => {
|
|
62
|
+
writeManifest(
|
|
63
|
+
{ Button: sampleEntry },
|
|
64
|
+
{ packageName: '@discourser/design-system', outputPath },
|
|
65
|
+
tmpDir,
|
|
66
|
+
);
|
|
67
|
+
const manifest = JSON.parse(readFileSync(outputPath, 'utf-8'));
|
|
68
|
+
expect(manifest.generatedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('includes all provided components', () => {
|
|
72
|
+
writeManifest(
|
|
73
|
+
{ Button: sampleEntry },
|
|
74
|
+
{ packageName: '@discourser/design-system', outputPath },
|
|
75
|
+
tmpDir,
|
|
76
|
+
);
|
|
77
|
+
const manifest = JSON.parse(readFileSync(outputPath, 'utf-8'));
|
|
78
|
+
expect(manifest.components).toHaveProperty('Button');
|
|
79
|
+
expect(manifest.components.Button.name).toBe('Button');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('uses 2-space indentation', () => {
|
|
83
|
+
writeManifest(
|
|
84
|
+
{ Button: sampleEntry },
|
|
85
|
+
{ packageName: '@discourser/design-system', outputPath },
|
|
86
|
+
tmpDir,
|
|
87
|
+
);
|
|
88
|
+
const raw = readFileSync(outputPath, 'utf-8');
|
|
89
|
+
expect(raw).toContain(' "version"');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('collects figmaFiles from all components', () => {
|
|
93
|
+
writeManifest(
|
|
94
|
+
{ Button: sampleEntry },
|
|
95
|
+
{ packageName: '@discourser/design-system', outputPath },
|
|
96
|
+
tmpDir,
|
|
97
|
+
);
|
|
98
|
+
const manifest = JSON.parse(readFileSync(outputPath, 'utf-8'));
|
|
99
|
+
expect(manifest.figmaFiles).toHaveProperty('ABC123');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('handles empty component list without throwing', () => {
|
|
103
|
+
expect(() =>
|
|
104
|
+
writeManifest(
|
|
105
|
+
{},
|
|
106
|
+
{ packageName: '@discourser/design-system', outputPath },
|
|
107
|
+
tmpDir,
|
|
108
|
+
),
|
|
109
|
+
).not.toThrow();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export interface FigmaCodexConfig {
|
|
5
|
+
include: string[];
|
|
6
|
+
outputPath: string;
|
|
7
|
+
packageName: string;
|
|
8
|
+
tsconfig: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const DEFAULTS: FigmaCodexConfig = {
|
|
12
|
+
include: ['src/components/**/*.figma.tsx'],
|
|
13
|
+
outputPath: 'dist/figma-codex.json',
|
|
14
|
+
packageName: '',
|
|
15
|
+
tsconfig: 'tsconfig.json',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function loadConfig(
|
|
19
|
+
projectRoot: string = process.cwd(),
|
|
20
|
+
): FigmaCodexConfig {
|
|
21
|
+
let userConfig: Partial<FigmaCodexConfig> = {};
|
|
22
|
+
|
|
23
|
+
const configPath = join(projectRoot, 'figma-codex.config.json');
|
|
24
|
+
if (existsSync(configPath)) {
|
|
25
|
+
userConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let packageName = userConfig.packageName ?? '';
|
|
29
|
+
if (!packageName) {
|
|
30
|
+
const pkgPath = join(projectRoot, 'package.json');
|
|
31
|
+
if (existsSync(pkgPath)) {
|
|
32
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
33
|
+
packageName = pkg.name ?? '';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
...DEFAULTS,
|
|
39
|
+
...userConfig,
|
|
40
|
+
packageName,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
// Test fixture — resolver uses text-based regex, imports not required to be valid.
|
|
3
|
+
// Globals declared below satisfy TypeScript; the resolver reads this file as raw text.
|
|
4
|
+
import type { FC, ComponentProps } from 'react';
|
|
5
|
+
|
|
6
|
+
declare const ark: {
|
|
7
|
+
div: FC<ComponentProps<'div'>>;
|
|
8
|
+
h2: FC<ComponentProps<'h2'>>;
|
|
9
|
+
span: FC<ComponentProps<'span'>>;
|
|
10
|
+
};
|
|
11
|
+
declare function withProvider<T>(el: T, slot: string): T;
|
|
12
|
+
declare function withContext<T>(el: T, slot: string): T;
|
|
13
|
+
|
|
14
|
+
export type RootProps = ComponentProps<'div'>;
|
|
15
|
+
export const Root = withProvider(ark.div, 'root');
|
|
16
|
+
export const Header = withContext(ark.div, 'header');
|
|
17
|
+
export const Body = withContext(ark.div, 'body');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Root, Header, Body, type RootProps } from './CompoundComponent';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import figma from '@figma/code-connect';
|
|
2
|
+
import * as CompoundComponent from './CompoundComponent/index';
|
|
3
|
+
|
|
4
|
+
figma.connect(
|
|
5
|
+
CompoundComponent.Root,
|
|
6
|
+
'https://www.figma.com/design/ABC123/TestFile?node-id=5-10',
|
|
7
|
+
{
|
|
8
|
+
example: () => (
|
|
9
|
+
<CompoundComponent.Root>
|
|
10
|
+
<CompoundComponent.Header>Title</CompoundComponent.Header>
|
|
11
|
+
</CompoundComponent.Root>
|
|
12
|
+
),
|
|
13
|
+
},
|
|
14
|
+
);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import figma from '@figma/code-connect';
|
|
2
|
+
import { SimpleComponent } from './SimpleComponent';
|
|
3
|
+
|
|
4
|
+
figma.connect(
|
|
5
|
+
SimpleComponent,
|
|
6
|
+
'https://www.figma.com/design/ABC123/TestFile?node-id=1-2',
|
|
7
|
+
{
|
|
8
|
+
example: () => <SimpleComponent label="Hello" />,
|
|
9
|
+
},
|
|
10
|
+
);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"packageName": "@test/design-system",
|
|
4
|
+
"generatedAt": "2026-02-27T19:21:46.956Z",
|
|
5
|
+
"gitHash": "3fd0a961",
|
|
6
|
+
"figmaFiles": {
|
|
7
|
+
"ABC123": {
|
|
8
|
+
"fileKey": "ABC123"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"components": {
|
|
12
|
+
"CompoundComponent": {
|
|
13
|
+
"name": "CompoundComponent",
|
|
14
|
+
"type": "compound",
|
|
15
|
+
"figma": {
|
|
16
|
+
"fileKey": "ABC123",
|
|
17
|
+
"nodeId": "5:10",
|
|
18
|
+
"url": "https://www.figma.com/design/ABC123/TestFile?node-id=5-10"
|
|
19
|
+
},
|
|
20
|
+
"imports": {
|
|
21
|
+
"primary": "import * as CompoundComponent from '@test/design-system/CompoundComponent'",
|
|
22
|
+
"namedExports": [
|
|
23
|
+
"CompoundComponent.Root",
|
|
24
|
+
"CompoundComponent.Header",
|
|
25
|
+
"CompoundComponent.Body"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"props": [],
|
|
29
|
+
"subComponents": [
|
|
30
|
+
{
|
|
31
|
+
"name": "Root",
|
|
32
|
+
"element": "div"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "Header",
|
|
36
|
+
"element": "div"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "Body",
|
|
40
|
+
"element": "div"
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"example": "<CompoundComponent.Root>\n <CompoundComponent.Header>Title</CompoundComponent.Header>\n </CompoundComponent.Root>",
|
|
44
|
+
"sourcePath": "src/figma-codex/fixtures/CompoundComponent/index.ts"
|
|
45
|
+
},
|
|
46
|
+
"SimpleComponent": {
|
|
47
|
+
"name": "SimpleComponent",
|
|
48
|
+
"type": "simple",
|
|
49
|
+
"figma": {
|
|
50
|
+
"fileKey": "ABC123",
|
|
51
|
+
"nodeId": "1:2",
|
|
52
|
+
"url": "https://www.figma.com/design/ABC123/TestFile?node-id=1-2"
|
|
53
|
+
},
|
|
54
|
+
"imports": {
|
|
55
|
+
"primary": "import { SimpleComponent } from '@test/design-system/SimpleComponent'",
|
|
56
|
+
"namedExports": [
|
|
57
|
+
"SimpleComponent"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"props": [
|
|
61
|
+
{
|
|
62
|
+
"name": "label",
|
|
63
|
+
"type": "string",
|
|
64
|
+
"required": true,
|
|
65
|
+
"description": "The display label"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "size",
|
|
69
|
+
"type": "'sm' | 'md' | 'lg'",
|
|
70
|
+
"required": false,
|
|
71
|
+
"description": "Optional size variant"
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
"example": "<SimpleComponent label=\"Hello\" />",
|
|
75
|
+
"sourcePath": "src/figma-codex/fixtures/SimpleComponent.tsx"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join, resolve, isAbsolute } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { parseFigmaFile, ParseError } from './parser';
|
|
5
|
+
import { resolveComponent } from './resolver';
|
|
6
|
+
import { writeManifest } from './writer';
|
|
7
|
+
import { loadConfig, type FigmaCodexConfig } from './config';
|
|
8
|
+
import type { ComponentEntry } from './schema';
|
|
9
|
+
|
|
10
|
+
/** Recursively find all .figma.tsx files under a directory */
|
|
11
|
+
function findFigmaFiles(rootDir: string): string[] {
|
|
12
|
+
const files: string[] = [];
|
|
13
|
+
|
|
14
|
+
function walk(dir: string) {
|
|
15
|
+
let entries;
|
|
16
|
+
try {
|
|
17
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
18
|
+
} catch {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
|
|
23
|
+
const full = join(dir, entry.name);
|
|
24
|
+
if (entry.isDirectory()) walk(full);
|
|
25
|
+
else if (entry.name.endsWith('.figma.tsx')) files.push(full);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
walk(rootDir);
|
|
30
|
+
return files;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface GenerateOptions extends FigmaCodexConfig {
|
|
34
|
+
projectRoot?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function generate(options: GenerateOptions): void {
|
|
38
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
39
|
+
|
|
40
|
+
// Resolve include patterns to base directories then scan recursively
|
|
41
|
+
const figmaFiles: string[] = [];
|
|
42
|
+
for (const pattern of options.include) {
|
|
43
|
+
// Extract the literal prefix before the first '*' as the base directory to walk.
|
|
44
|
+
// Patterns starting with '*' (e.g. '**/*.figma.tsx') fall back to projectRoot.
|
|
45
|
+
// Patterns like 'src/components/**/*.figma.tsx' correctly resolve to 'src/components/'.
|
|
46
|
+
const starIdx = pattern.indexOf('*');
|
|
47
|
+
const baseDir =
|
|
48
|
+
starIdx > 0
|
|
49
|
+
? isAbsolute(pattern.slice(0, starIdx))
|
|
50
|
+
? pattern.slice(0, starIdx).replace(/\/$/, '')
|
|
51
|
+
: join(projectRoot, pattern.slice(0, starIdx).replace(/\/$/, ''))
|
|
52
|
+
: projectRoot;
|
|
53
|
+
const found = findFigmaFiles(baseDir);
|
|
54
|
+
figmaFiles.push(...found);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (figmaFiles.length === 0) {
|
|
58
|
+
console.warn(
|
|
59
|
+
'[figma-codex] No .figma.tsx files found. Check your include patterns.',
|
|
60
|
+
);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const components: Record<string, ComponentEntry> = {};
|
|
65
|
+
|
|
66
|
+
for (const filePath of figmaFiles) {
|
|
67
|
+
try {
|
|
68
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
69
|
+
const parsed = parseFigmaFile(content, filePath);
|
|
70
|
+
const entry = resolveComponent(parsed, options);
|
|
71
|
+
components[entry.name] = entry;
|
|
72
|
+
console.log(`[figma-codex] ✓ ${entry.name} (${entry.type})`);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
if (err instanceof ParseError) {
|
|
75
|
+
console.warn(`[figma-codex] ⚠ Skipping ${filePath}: ${err.message}`);
|
|
76
|
+
} else {
|
|
77
|
+
console.warn(`[figma-codex] ⚠ Skipping ${filePath}: ${String(err)}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (Object.keys(components).length === 0) {
|
|
83
|
+
throw new Error('[figma-codex] No components successfully parsed.');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
writeManifest(
|
|
87
|
+
components,
|
|
88
|
+
{ packageName: options.packageName, outputPath: options.outputPath },
|
|
89
|
+
projectRoot,
|
|
90
|
+
);
|
|
91
|
+
console.log(
|
|
92
|
+
`[figma-codex] Generated ${Object.keys(components).length} components → ${options.outputPath}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// CLI entry point
|
|
97
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
98
|
+
if (process.argv[1] && resolve(process.argv[1]) === resolve(currentFile)) {
|
|
99
|
+
try {
|
|
100
|
+
const config = loadConfig();
|
|
101
|
+
generate({ ...config, projectRoot: process.cwd() });
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.error(String(err));
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
export class ParseError extends Error {
|
|
2
|
+
constructor(
|
|
3
|
+
message: string,
|
|
4
|
+
public readonly filePath: string,
|
|
5
|
+
) {
|
|
6
|
+
super(`ParseError: ${message} (${filePath})`);
|
|
7
|
+
this.name = 'ParseError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ParsedFigmaFile {
|
|
12
|
+
filePath: string;
|
|
13
|
+
importStyle: 'named' | 'namespace';
|
|
14
|
+
componentName: string;
|
|
15
|
+
importSource: string;
|
|
16
|
+
connectSubComponent?: string;
|
|
17
|
+
figmaUrl: string;
|
|
18
|
+
figmaFileKey: string;
|
|
19
|
+
figmaNodeId: string;
|
|
20
|
+
example: string;
|
|
21
|
+
propsMapping: Record<string, string>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseNodeId(url: string): string {
|
|
25
|
+
const match = url.match(/node-id=([^&'"]+)/);
|
|
26
|
+
if (!match) return '';
|
|
27
|
+
return match[1].replace(/-/g, ':');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseFileKey(url: string): string {
|
|
31
|
+
const match = url.match(/figma\.com\/design\/([^/?]+)/);
|
|
32
|
+
return match?.[1] ?? '';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function extractExample(content: string): string {
|
|
36
|
+
const exampleIdx = content.indexOf('example:');
|
|
37
|
+
if (exampleIdx === -1) return '';
|
|
38
|
+
|
|
39
|
+
const arrowIdx = content.indexOf('=>', exampleIdx);
|
|
40
|
+
if (arrowIdx === -1) return '';
|
|
41
|
+
|
|
42
|
+
const afterArrow = content.slice(arrowIdx + 2).trimStart();
|
|
43
|
+
|
|
44
|
+
if (afterArrow.startsWith('(')) {
|
|
45
|
+
// Multi-line: track balanced parens
|
|
46
|
+
let depth = 0;
|
|
47
|
+
let i = 0;
|
|
48
|
+
for (; i < afterArrow.length; i++) {
|
|
49
|
+
if (afterArrow[i] === '(') depth++;
|
|
50
|
+
else if (afterArrow[i] === ')') {
|
|
51
|
+
depth--;
|
|
52
|
+
if (depth === 0) break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const inner = afterArrow.slice(1, i).trim();
|
|
56
|
+
return inner;
|
|
57
|
+
} else {
|
|
58
|
+
// Single-line: grab to end of line, strip trailing comma
|
|
59
|
+
const lineEnd = afterArrow.indexOf('\n');
|
|
60
|
+
const line = lineEnd === -1 ? afterArrow : afterArrow.slice(0, lineEnd);
|
|
61
|
+
return line.replace(/,\s*$/, '').trim();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function extractPropsMapping(content: string): Record<string, string> {
|
|
66
|
+
const result: Record<string, string> = {};
|
|
67
|
+
const propsBlockMatch = content.match(/props:\s*\{([^}]+)\}/);
|
|
68
|
+
if (!propsBlockMatch) return result;
|
|
69
|
+
|
|
70
|
+
const block = propsBlockMatch[1];
|
|
71
|
+
const propNames = block.match(/(\w+)\s*:/g);
|
|
72
|
+
if (propNames) {
|
|
73
|
+
for (const p of propNames) {
|
|
74
|
+
result[p.replace(':', '').trim()] = 'figma.enum';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function parseFigmaFile(
|
|
81
|
+
content: string,
|
|
82
|
+
filePath: string,
|
|
83
|
+
): ParsedFigmaFile {
|
|
84
|
+
// Try namespace import: import * as X from './path'
|
|
85
|
+
const nsMatch = content.match(
|
|
86
|
+
/import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/,
|
|
87
|
+
);
|
|
88
|
+
// Try named import: import { X } from './path' (skip figma import itself)
|
|
89
|
+
const namedMatches = [
|
|
90
|
+
...content.matchAll(/import\s+\{\s*(\w+)\s*\}\s+from\s+['"]([^'"]+)['"]/g),
|
|
91
|
+
];
|
|
92
|
+
// Filter out the figma import itself
|
|
93
|
+
const namedMatch = namedMatches.find(
|
|
94
|
+
(m) => !m[2].includes('figma') && !m[2].includes('@figma'),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
if (!nsMatch && !namedMatch) {
|
|
98
|
+
throw new ParseError('No component import found', filePath);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const connectMatch = content.match(
|
|
102
|
+
/figma\.connect\(\s*(\w+)(?:\.(\w+))?\s*,\s*['"]([^'"]+)['"]/,
|
|
103
|
+
);
|
|
104
|
+
if (!connectMatch) {
|
|
105
|
+
throw new ParseError('No figma.connect() call found', filePath);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const figmaUrl = connectMatch[3];
|
|
109
|
+
const connectSubComponent = connectMatch[2];
|
|
110
|
+
|
|
111
|
+
if (nsMatch) {
|
|
112
|
+
return {
|
|
113
|
+
filePath,
|
|
114
|
+
importStyle: 'namespace',
|
|
115
|
+
componentName: nsMatch[1],
|
|
116
|
+
importSource: nsMatch[2],
|
|
117
|
+
connectSubComponent,
|
|
118
|
+
figmaUrl,
|
|
119
|
+
figmaFileKey: parseFileKey(figmaUrl),
|
|
120
|
+
figmaNodeId: parseNodeId(figmaUrl),
|
|
121
|
+
example: extractExample(content),
|
|
122
|
+
propsMapping: extractPropsMapping(content),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
filePath,
|
|
128
|
+
importStyle: 'named',
|
|
129
|
+
componentName: namedMatch![1],
|
|
130
|
+
importSource: namedMatch![2],
|
|
131
|
+
connectSubComponent,
|
|
132
|
+
figmaUrl,
|
|
133
|
+
figmaFileKey: parseFileKey(figmaUrl),
|
|
134
|
+
figmaNodeId: parseNodeId(figmaUrl),
|
|
135
|
+
example: extractExample(content),
|
|
136
|
+
propsMapping: extractPropsMapping(content),
|
|
137
|
+
};
|
|
138
|
+
}
|