@fragments-sdk/cli 0.7.3 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/LICENSE +1 -4
  2. package/README.md +2 -0
  3. package/dist/bin.js +39 -16
  4. package/dist/bin.js.map +1 -1
  5. package/dist/{chunk-D34Q6A7S.js → chunk-AWYCDRPG.js} +8 -2
  6. package/dist/chunk-AWYCDRPG.js.map +1 -0
  7. package/dist/{chunk-R2YH7NLN.js → chunk-CR3XHBGM.js} +3 -3
  8. package/dist/{chunk-QPY4DUFB.js → chunk-EFQ7SIBX.js} +583 -108
  9. package/dist/chunk-EFQ7SIBX.js.map +1 -0
  10. package/dist/{chunk-UXLGIGSX.js → chunk-GIC3I2KZ.js} +2 -2
  11. package/dist/{chunk-R6IZZSE7.js → chunk-JZNATKQA.js} +9 -3
  12. package/dist/chunk-JZNATKQA.js.map +1 -0
  13. package/dist/{chunk-P33AKQJW.js → chunk-SFWZ4K7C.js} +8 -2
  14. package/dist/{chunk-P33AKQJW.js.map → chunk-SFWZ4K7C.js.map} +1 -1
  15. package/dist/{core-3NMNCLFW.js → core-T7BDYEGO.js} +3 -3
  16. package/dist/{discovery-AKGA6CJD.js → discovery-Z4RDDFVR.js} +2 -2
  17. package/dist/{generate-JAUEHKK7.js → generate-C2DKFCFJ.js} +5 -5
  18. package/dist/index.d.ts +28 -2
  19. package/dist/index.js +9 -7
  20. package/dist/index.js.map +1 -1
  21. package/dist/{init-DZQOT54X.js → init-O3FCHEPN.js} +26 -8
  22. package/dist/init-O3FCHEPN.js.map +1 -0
  23. package/dist/mcp-bin.js +3 -3
  24. package/dist/{scan-OJRCVKK2.js → scan-IYTZDUKG.js} +6 -6
  25. package/dist/{service-CFFBHW4X.js → service-VA6XKADO.js} +3 -3
  26. package/dist/{static-viewer-VA2JXSCX.js → static-viewer-5N42MBDR.js} +3 -3
  27. package/dist/{test-O7DZNKDC.js → test-OMMDWL2W.js} +4 -4
  28. package/dist/{tokens-N7THFD6J.js → tokens-6VJAHFIG.js} +5 -5
  29. package/dist/{viewer-QTR7QJMM.js → viewer-IVP5XC7U.js} +37 -17
  30. package/dist/viewer-IVP5XC7U.js.map +1 -0
  31. package/package.json +8 -2
  32. package/src/bin.ts +4 -0
  33. package/src/commands/add.ts +6 -0
  34. package/src/commands/init.ts +24 -4
  35. package/src/commands/validate.ts +24 -2
  36. package/src/core/config.ts +6 -0
  37. package/src/core/discovery.ts +7 -1
  38. package/src/core/index.ts +1 -0
  39. package/src/core/schema.ts +6 -0
  40. package/src/core/types.ts +21 -0
  41. package/src/index.ts +2 -1
  42. package/src/migrate/detect.ts +4 -0
  43. package/src/service/snippet-validation.test.ts +209 -0
  44. package/src/service/snippet-validation.ts +635 -0
  45. package/src/validators.ts +53 -5
  46. package/src/viewer/__tests__/viewer-integration.test.ts +8 -0
  47. package/src/viewer/components/App.tsx +63 -2
  48. package/src/viewer/components/CodePanel.naming.test.tsx +60 -0
  49. package/src/viewer/components/CodePanel.tsx +76 -468
  50. package/src/viewer/components/Layout.tsx +2 -2
  51. package/src/viewer/components/LeftSidebar.tsx +35 -77
  52. package/src/viewer/preview-frame.html +1 -1
  53. package/src/viewer/styles/globals.css +2 -1
  54. package/src/viewer/utils/a11y-fixes.ts +24 -9
  55. package/src/viewer/vite-plugin.ts +27 -2
  56. package/dist/chunk-D34Q6A7S.js.map +0 -1
  57. package/dist/chunk-QPY4DUFB.js.map +0 -1
  58. package/dist/chunk-R6IZZSE7.js.map +0 -1
  59. package/dist/init-DZQOT54X.js.map +0 -1
  60. package/dist/viewer-QTR7QJMM.js.map +0 -1
  61. /package/dist/{chunk-R2YH7NLN.js.map → chunk-CR3XHBGM.js.map} +0 -0
  62. /package/dist/{chunk-UXLGIGSX.js.map → chunk-GIC3I2KZ.js.map} +0 -0
  63. /package/dist/{core-3NMNCLFW.js.map → core-T7BDYEGO.js.map} +0 -0
  64. /package/dist/{discovery-AKGA6CJD.js.map → discovery-Z4RDDFVR.js.map} +0 -0
  65. /package/dist/{generate-JAUEHKK7.js.map → generate-C2DKFCFJ.js.map} +0 -0
  66. /package/dist/{scan-OJRCVKK2.js.map → scan-IYTZDUKG.js.map} +0 -0
  67. /package/dist/{service-CFFBHW4X.js.map → service-VA6XKADO.js.map} +0 -0
  68. /package/dist/{static-viewer-VA2JXSCX.js.map → static-viewer-5N42MBDR.js.map} +0 -0
  69. /package/dist/{test-O7DZNKDC.js.map → test-OMMDWL2W.js.map} +0 -0
  70. /package/dist/{tokens-N7THFD6J.js.map → tokens-6VJAHFIG.js.map} +0 -0
@@ -109,6 +109,14 @@ describe("virtual module @fragments/core import", () => {
109
109
 
110
110
  expect(content).toContain('"@fragments/viewer": viewerRoot');
111
111
  });
112
+
113
+ it("vite-plugin merges authored variant code from metadata fragments", async () => {
114
+ const pluginPath = resolve(viewerDir, "vite-plugin.ts");
115
+ const content = await readFile(pluginPath, "utf-8");
116
+
117
+ expect(content).toContain("if (metaVariant.code && !fragmentVariant.code)");
118
+ expect(content).toContain("fragmentVariant.code = metaVariant.code;");
119
+ });
112
120
  });
113
121
 
114
122
  describe("viewer HTML templates", () => {
@@ -28,11 +28,19 @@ import { useAllFigmaUrls } from "./FigmaEmbed.js";
28
28
  import { ActionCapture } from "./ActionCapture.js";
29
29
 
30
30
  // Fragments UI
31
- import { Header, Stack, Text, Separator, Tooltip, Button, EmptyState, Box, Alert, ScrollArea, Input } from "@fragments/ui";
31
+ import { Header, Stack, Text, Separator, Tooltip, Button, EmptyState, Box, Alert, ScrollArea, Input, ThemeToggle } from "@fragments/ui";
32
32
 
33
33
  // Icons
34
34
  import { EmptyIcon, ExternalLinkIcon, FigmaIcon, CompareIcon, CheckIcon, LinkIcon, GridIcon, DevicesIcon } from "./Icons.js";
35
35
 
36
+ function GitHubIcon() {
37
+ return (
38
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
39
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
40
+ </svg>
41
+ );
42
+ }
43
+
36
44
  // Hooks
37
45
  import { useAppState } from "../hooks/useAppState.js";
38
46
  import { useViewSettings } from "../hooks/useViewSettings.js";
@@ -475,13 +483,14 @@ function HeaderSearch({ value, onChange, inputRef }: HeaderSearchProps) {
475
483
  aria-label="Search components"
476
484
  size="sm"
477
485
  shortcut="⌘K"
478
- style={{ width: '220px' }}
486
+ style={{ width: '240px' }}
479
487
  />
480
488
  </Header.Search>
481
489
  );
482
490
  }
483
491
 
484
492
  function ViewerHeader({ showHealth, searchQuery, onSearchChange, searchInputRef }: ViewerHeaderProps) {
493
+ const { setTheme, resolvedTheme } = useTheme();
485
494
  return (
486
495
  <Header aria-label="Fragments viewer header">
487
496
  <Header.Trigger />
@@ -493,6 +502,32 @@ function ViewerHeader({ showHealth, searchQuery, onSearchChange, searchInputRef
493
502
  </Header.Brand>
494
503
  <HeaderSearch value={searchQuery} onChange={onSearchChange} inputRef={searchInputRef} />
495
504
  <Header.Spacer />
505
+ <Header.Actions>
506
+ <ThemeToggle
507
+ size="sm"
508
+ value={resolvedTheme}
509
+ onValueChange={(value) => setTheme(value)}
510
+ aria-label={`Theme: ${resolvedTheme}`}
511
+ />
512
+ <a
513
+ href="https://github.com/ConanMcN/fragments"
514
+ target="_blank"
515
+ rel="noopener noreferrer"
516
+ style={{
517
+ display: 'flex',
518
+ alignItems: 'center',
519
+ justifyContent: 'center',
520
+ width: '32px',
521
+ height: '32px',
522
+ borderRadius: 'var(--radius-md, 6px)',
523
+ color: 'var(--text-secondary)',
524
+ transition: 'background-color 150ms ease, color 150ms ease',
525
+ }}
526
+ aria-label="View on GitHub"
527
+ >
528
+ <GitHubIcon />
529
+ </a>
530
+ </Header.Actions>
496
531
  </Header>
497
532
  );
498
533
  }
@@ -510,6 +545,7 @@ function TopToolbar({
510
545
  onSearchChange,
511
546
  searchInputRef,
512
547
  }: TopToolbarProps) {
548
+ const { setTheme, resolvedTheme } = useTheme();
513
549
  return (
514
550
  <Header aria-label="Component preview toolbar">
515
551
  <Header.Trigger />
@@ -595,6 +631,31 @@ function TopToolbar({
595
631
  </Tooltip>
596
632
  </>
597
633
  )}
634
+ <Separator orientation="vertical" style={{ height: '16px' }} />
635
+ <ThemeToggle
636
+ size="sm"
637
+ value={resolvedTheme}
638
+ onValueChange={(value) => setTheme(value)}
639
+ aria-label={`Theme: ${resolvedTheme}`}
640
+ />
641
+ <a
642
+ href="https://github.com/ConanMcN/fragments"
643
+ target="_blank"
644
+ rel="noopener noreferrer"
645
+ style={{
646
+ display: 'flex',
647
+ alignItems: 'center',
648
+ justifyContent: 'center',
649
+ width: '32px',
650
+ height: '32px',
651
+ borderRadius: 'var(--radius-md, 6px)',
652
+ color: 'var(--text-secondary)',
653
+ transition: 'background-color 150ms ease, color 150ms ease',
654
+ }}
655
+ aria-label="View on GitHub"
656
+ >
657
+ <GitHubIcon />
658
+ </a>
598
659
  </Header.Actions>
599
660
  </Header>
600
661
  );
@@ -0,0 +1,60 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import type { FragmentVariant } from '../../core/index.js';
3
+
4
+ vi.mock('@fragments/ui', () => ({
5
+ CodeBlock: () => null,
6
+ }));
7
+
8
+ const {
9
+ __buildFallbackSnippetForTest,
10
+ __resolveDisplayedCodeForTest,
11
+ } = await import('./CodePanel.js');
12
+
13
+ function makeVariant(partial: Partial<FragmentVariant>): FragmentVariant {
14
+ return {
15
+ name: 'Default',
16
+ description: 'Default variant',
17
+ render: () => null,
18
+ ...partial,
19
+ };
20
+ }
21
+
22
+ describe('CodePanel authored code pipeline', () => {
23
+ it('prefers authored variant.code as-is', () => {
24
+ const variant = makeVariant({
25
+ code: `import { Button } from '@fragments-sdk/ui';\n\n<Button>Save</Button>`,
26
+ });
27
+
28
+ const resolved = __resolveDisplayedCodeForTest('Button', variant);
29
+
30
+ expect(resolved).toBe(`import { Button } from '@fragments-sdk/ui';\n\n<Button>Save</Button>`);
31
+ });
32
+
33
+ it('falls back to generated placeholder when variant.code is missing', () => {
34
+ const variant = makeVariant({
35
+ name: 'Primary',
36
+ args: {
37
+ variant: 'primary',
38
+ disabled: false,
39
+ children: 'Save Changes',
40
+ },
41
+ });
42
+
43
+ const code = __buildFallbackSnippetForTest('Button', variant);
44
+
45
+ expect(code).toContain("import { Button } from '@/components/Button';");
46
+ expect(code).toContain('TODO: Add explicit `code` for variant "Primary"');
47
+ expect(code).toContain("<Button variant='primary' disabled={false}>Save Changes</Button>");
48
+ });
49
+
50
+ it('does not synthesize runtime JSX output', () => {
51
+ const variant = makeVariant({
52
+ code: '<Button icon={/* @__PURE__ */ jsxDEV(GearIcon, {}, void 0)} />',
53
+ });
54
+
55
+ const resolved = __resolveDisplayedCodeForTest('Button', variant);
56
+
57
+ // CodePanel now treats authored code as source-of-truth only.
58
+ expect(resolved).toContain('jsxDEV');
59
+ });
60
+ });