@shohojdhara/atomix 0.3.4 → 0.3.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 (114) hide show
  1. package/dist/atomix.css +9 -10
  2. package/dist/atomix.css.map +1 -0
  3. package/dist/atomix.min.css +15108 -11
  4. package/dist/atomix.min.css.map +1 -0
  5. package/dist/charts.d.ts +1929 -0
  6. package/dist/charts.js +6482 -0
  7. package/dist/charts.js.map +1 -0
  8. package/dist/core.d.ts +1289 -0
  9. package/dist/core.js +3357 -0
  10. package/dist/core.js.map +1 -0
  11. package/dist/forms.d.ts +1085 -0
  12. package/dist/forms.js +2450 -0
  13. package/dist/forms.js.map +1 -0
  14. package/dist/heavy.d.ts +636 -0
  15. package/dist/heavy.js +4550 -0
  16. package/dist/heavy.js.map +1 -0
  17. package/dist/index.d.ts +5161 -4990
  18. package/dist/index.esm.js +1457 -784
  19. package/dist/index.esm.js.map +1 -1
  20. package/dist/index.js +1473 -790
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.min.js +1 -1
  23. package/dist/index.min.js.map +1 -1
  24. package/dist/layout.d.ts +300 -0
  25. package/dist/layout.js +336 -0
  26. package/dist/layout.js.map +1 -0
  27. package/dist/theme.d.ts +1992 -0
  28. package/dist/theme.js +5348 -0
  29. package/dist/theme.js.map +1 -0
  30. package/package.json +66 -20
  31. package/scripts/atomix-cli.js +544 -16
  32. package/scripts/cli/__tests__/cli-commands.test.js +204 -0
  33. package/scripts/cli/__tests__/utils.test.js +201 -0
  34. package/scripts/cli/__tests__/vitest.config.js +26 -0
  35. package/scripts/cli/interactive-init.js +1 -1
  36. package/scripts/cli/token-manager.js +32 -7
  37. package/scripts/cli/utils.js +347 -0
  38. package/src/components/Accordion/Accordion.tsx +5 -54
  39. package/src/components/Accordion/index.ts +1 -1
  40. package/src/components/Avatar/Avatar.tsx +3 -3
  41. package/src/components/Badge/Badge.tsx +3 -3
  42. package/src/components/Breadcrumb/Breadcrumb.tsx +3 -3
  43. package/src/components/Card/ElevationCard.tsx +1 -1
  44. package/src/components/Chart/AnimatedChart.tsx +19 -17
  45. package/src/components/Chart/AreaChart.tsx +5 -1
  46. package/src/components/Chart/BarChart.tsx +1 -0
  47. package/src/components/Chart/BubbleChart.tsx +6 -5
  48. package/src/components/Chart/ChartToolbar.tsx +1 -0
  49. package/src/components/Chart/FunnelChart.tsx +1 -1
  50. package/src/components/Chart/RadarChart.tsx +19 -12
  51. package/src/components/Chart/ScatterChart.tsx +3 -3
  52. package/src/components/Chart/TreemapChart.tsx +2 -1
  53. package/src/components/Chart/WaterfallChart.tsx +0 -1
  54. package/src/components/Chart/types.ts +12 -2
  55. package/src/components/Chart/utils.ts +4 -3
  56. package/src/components/DataTable/DataTable.tsx +3 -3
  57. package/src/components/Dropdown/Dropdown.tsx +12 -9
  58. package/src/components/Footer/FooterSection.tsx +3 -3
  59. package/src/components/Form/Checkbox.tsx +3 -3
  60. package/src/components/Form/Input.tsx +4 -2
  61. package/src/components/Form/Radio.tsx +3 -3
  62. package/src/components/Form/Select.tsx +3 -3
  63. package/src/components/Form/Textarea.tsx +4 -2
  64. package/src/components/List/List.stories.tsx +3 -3
  65. package/src/components/List/List.tsx +3 -3
  66. package/src/components/List/ListGroup.tsx +3 -1
  67. package/src/components/Modal/Modal.tsx +3 -3
  68. package/src/components/Navigation/Menu/MegaMenu.tsx +9 -3
  69. package/src/components/Navigation/Menu/Menu.tsx +9 -3
  70. package/src/components/Pagination/Pagination.tsx +6 -5
  71. package/src/components/PhotoViewer/PhotoViewerImage.tsx +2 -2
  72. package/src/components/Popover/Popover.tsx +4 -4
  73. package/src/components/Progress/Progress.tsx +6 -2
  74. package/src/components/Rating/Rating.tsx +5 -2
  75. package/src/components/Slider/Slider.tsx +10 -9
  76. package/src/components/Spinner/Spinner.tsx +3 -3
  77. package/src/components/Tabs/Tabs.tsx +3 -3
  78. package/src/components/Tooltip/Tooltip.tsx +3 -3
  79. package/src/components/index.ts +5 -2
  80. package/src/layouts/MasonryGrid/MasonryGrid.tsx +2 -2
  81. package/src/lib/composables/useChartPerformance.ts +102 -78
  82. package/src/lib/composables/useChartScale.ts +10 -0
  83. package/src/lib/composables/useHero.ts +9 -2
  84. package/src/lib/composables/useHeroBackgroundSlider.ts +5 -3
  85. package/src/lib/composables/useSideMenu.ts +1 -0
  86. package/src/lib/composables/useVideoPlayer.ts +3 -2
  87. package/src/lib/config/loader.ts +55 -13
  88. package/src/lib/hooks/index.ts +0 -1
  89. package/src/lib/hooks/useComponentCustomization.ts +10 -14
  90. package/src/lib/hooks/usePerformanceMonitor.ts +149 -0
  91. package/src/lib/patterns/index.ts +2 -2
  92. package/src/lib/patterns/slots.tsx +2 -2
  93. package/src/lib/theme/composeTheme.ts +1 -1
  94. package/src/lib/theme/core/ThemeEngine.ts +8 -0
  95. package/src/lib/theme/core/ThemeValidator.ts +5 -2
  96. package/src/lib/theme/devtools/Inspector.tsx +1 -1
  97. package/src/lib/theme/devtools/LiveEditor.tsx +11 -5
  98. package/src/lib/theme/generateCSSVariables.ts +1 -1
  99. package/src/lib/theme/i18n/rtl.ts +2 -1
  100. package/src/lib/theme/runtime/ThemeApplicator.ts +28 -11
  101. package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +3 -3
  102. package/src/lib/theme/runtime/ThemeManager.ts +4 -0
  103. package/src/lib/theme-tools.ts +1 -1
  104. package/src/lib/types/components.ts +183 -34
  105. package/src/lib/types/partProps.ts +0 -16
  106. package/src/lib/utils/fontPreloader.ts +148 -0
  107. package/src/lib/utils/index.ts +11 -0
  108. package/src/lib/utils/memoryMonitor.ts +189 -0
  109. package/src/styles/01-settings/_settings.fonts.scss +2 -5
  110. package/src/styles/03-generic/_generated-root.css +22 -1
  111. package/src/styles/06-components/_components.navbar.scss +0 -6
  112. package/src/themes/themes.config.js +37 -4
  113. package/scripts/build-themes.js +0 -208
  114. package/src/components/AtomixGlass/atomixGLass.old.tsx +0 -1263
@@ -0,0 +1,204 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { execSync } from 'child_process';
3
+ import { existsSync, mkdirSync, rmSync, readFileSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { dirname } from 'path';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+ const CLI_PATH = join(__dirname, '../../atomix-cli.js');
11
+ const TEST_DIR = join(__dirname, 'test-output');
12
+
13
+ describe('Atomix CLI Commands', () => {
14
+ beforeEach(() => {
15
+ // Create test directory
16
+ if (!existsSync(TEST_DIR)) {
17
+ mkdirSync(TEST_DIR, { recursive: true });
18
+ }
19
+ });
20
+
21
+ afterEach(() => {
22
+ // Clean up test directory
23
+ if (existsSync(TEST_DIR)) {
24
+ rmSync(TEST_DIR, { recursive: true, force: true });
25
+ }
26
+ });
27
+
28
+ describe('atomix --help', () => {
29
+ it('should display help information', () => {
30
+ const output = execSync(`node ${CLI_PATH} --help`).toString();
31
+ expect(output).toContain('Atomix Design System CLI');
32
+ expect(output).toContain('Commands:');
33
+ expect(output).toContain('build-theme');
34
+ expect(output).toContain('generate');
35
+ expect(output).toContain('validate');
36
+ });
37
+ });
38
+
39
+ describe('atomix --version', () => {
40
+ it('should display version number', () => {
41
+ const output = execSync(`node ${CLI_PATH} --version`).toString();
42
+ expect(output).toMatch(/\d+\.\d+\.\d+/);
43
+ });
44
+ });
45
+
46
+ describe('atomix generate component', () => {
47
+ const componentPath = join(TEST_DIR, 'TestComponent');
48
+
49
+ it('should create component files with valid name', () => {
50
+ execSync(`node ${CLI_PATH} generate component TestButton --path ${TEST_DIR}`, {
51
+ cwd: process.cwd()
52
+ });
53
+
54
+ const buttonPath = join(TEST_DIR, 'TestButton');
55
+
56
+ // Check if files are created
57
+ expect(existsSync(join(buttonPath, 'TestButton.tsx'))).toBe(true);
58
+ expect(existsSync(join(buttonPath, 'index.ts'))).toBe(true);
59
+ expect(existsSync(join(buttonPath, '_testbutton.scss'))).toBe(true);
60
+ expect(existsSync(join(buttonPath, 'TestButton.stories.tsx'))).toBe(true);
61
+
62
+ // Check component content
63
+ const componentContent = readFileSync(join(buttonPath, 'TestButton.tsx'), 'utf8');
64
+ expect(componentContent).toContain('export const TestButton');
65
+ expect(componentContent).toContain('TestButtonProps');
66
+ expect(componentContent).toContain('forwardRef');
67
+ });
68
+
69
+ it('should reject invalid component names', () => {
70
+ expect(() => {
71
+ execSync(`node ${CLI_PATH} generate component test-button --path ${TEST_DIR}`, {
72
+ cwd: process.cwd()
73
+ });
74
+ }).toThrow();
75
+ });
76
+
77
+ it('should create test file when --test flag is used', () => {
78
+ execSync(`node ${CLI_PATH} generate component TestCard --test --path ${TEST_DIR}`, {
79
+ cwd: process.cwd()
80
+ });
81
+
82
+ const cardPath = join(TEST_DIR, 'TestCard');
83
+ expect(existsSync(join(cardPath, 'TestCard.test.tsx'))).toBe(true);
84
+
85
+ const testContent = readFileSync(join(cardPath, 'TestCard.test.tsx'), 'utf8');
86
+ expect(testContent).toContain('describe(\'TestCard\'');
87
+ expect(testContent).toContain('vitest');
88
+ });
89
+ });
90
+
91
+ describe('atomix generate token', () => {
92
+ it('should generate color tokens', () => {
93
+ const settingsPath = join(TEST_DIR, 'src/styles/01-settings');
94
+ mkdirSync(settingsPath, { recursive: true });
95
+
96
+ execSync(`node ${CLI_PATH} generate token colors`, {
97
+ cwd: TEST_DIR
98
+ });
99
+
100
+ const tokenFile = join(settingsPath, '_settings.colors.custom.scss');
101
+ expect(existsSync(tokenFile)).toBe(true);
102
+
103
+ const content = readFileSync(tokenFile, 'utf8');
104
+ expect(content).toContain('Custom Color Tokens');
105
+ expect(content).toContain('$custom-primary-6');
106
+ expect(content).toContain('$custom-success');
107
+ });
108
+
109
+ it('should reject invalid token categories', () => {
110
+ expect(() => {
111
+ execSync(`node ${CLI_PATH} generate token invalid-category`, {
112
+ cwd: TEST_DIR
113
+ });
114
+ }).toThrow();
115
+ });
116
+ });
117
+
118
+ describe('atomix validate', () => {
119
+ it('should validate tokens when --tokens flag is used', () => {
120
+ // This is a mock test - in real implementation, you'd set up token files
121
+ const output = execSync(`node ${CLI_PATH} validate --tokens`, {
122
+ cwd: process.cwd()
123
+ }).toString();
124
+
125
+ // Should complete without throwing
126
+ expect(output).toBeDefined();
127
+ });
128
+ });
129
+
130
+ describe('atomix doctor', () => {
131
+ it('should run diagnostics successfully', () => {
132
+ const output = execSync(`node ${CLI_PATH} doctor`).toString();
133
+ expect(output).toContain('Atomix Doctor Report');
134
+ expect(output).toContain('Node.js Version');
135
+ expect(output).toContain('Atomix Installation');
136
+ });
137
+ });
138
+
139
+ describe('atomix theme create', () => {
140
+ it('should create CSS theme with valid name', () => {
141
+ const themesPath = join(TEST_DIR, 'themes');
142
+ mkdirSync(themesPath, { recursive: true });
143
+
144
+ execSync(`node ${CLI_PATH} theme create test-theme --output ${themesPath}`, {
145
+ cwd: process.cwd()
146
+ });
147
+
148
+ const themePath = join(themesPath, 'test-theme');
149
+ expect(existsSync(join(themePath, 'index.scss'))).toBe(true);
150
+ expect(existsSync(join(themePath, 'README.md'))).toBe(true);
151
+
152
+ const themeContent = readFileSync(join(themePath, 'index.scss'), 'utf8');
153
+ expect(themeContent).toContain('data-theme="test-theme"');
154
+ expect(themeContent).toContain('--atomix-color-primary');
155
+ });
156
+
157
+ it('should create JavaScript theme when --type js is used', () => {
158
+ const themesPath = join(TEST_DIR, 'themes');
159
+ mkdirSync(themesPath, { recursive: true });
160
+
161
+ execSync(`node ${CLI_PATH} theme create js-theme --type js --output ${themesPath}`, {
162
+ cwd: process.cwd()
163
+ });
164
+
165
+ const themePath = join(themesPath, 'js-theme');
166
+ expect(existsSync(join(themePath, 'index.ts'))).toBe(true);
167
+
168
+ const themeContent = readFileSync(join(themePath, 'index.ts'), 'utf8');
169
+ expect(themeContent).toContain('jsTheme');
170
+ expect(themeContent).toContain('createTheme');
171
+ });
172
+
173
+ it('should reject invalid theme names', () => {
174
+ expect(() => {
175
+ execSync(`node ${CLI_PATH} theme create InvalidTheme --output ${TEST_DIR}`, {
176
+ cwd: process.cwd()
177
+ });
178
+ }).toThrow();
179
+ });
180
+ });
181
+ });
182
+
183
+ describe('CLI Error Handling', () => {
184
+ it('should show suggestions for invalid commands', () => {
185
+ try {
186
+ execSync(`node ${CLI_PATH} invalidcommand`);
187
+ } catch (error) {
188
+ const output = error.stdout?.toString() || error.stderr?.toString();
189
+ expect(output).toContain('unknown command');
190
+ }
191
+ });
192
+
193
+ it('should handle missing required arguments', () => {
194
+ expect(() => {
195
+ execSync(`node ${CLI_PATH} generate`);
196
+ }).toThrow();
197
+ });
198
+
199
+ it('should respect --debug flag', () => {
200
+ const output = execSync(`node ${CLI_PATH} --debug --help`).toString();
201
+ // Debug mode should be enabled
202
+ expect(process.env.ATOMIX_DEBUG).toBe('true');
203
+ });
204
+ });
@@ -0,0 +1,201 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import {
3
+ validatePath,
4
+ validateComponentName,
5
+ validateThemeName,
6
+ sanitizeInput,
7
+ isValidColor,
8
+ checkNodeVersion,
9
+ formatFileSize
10
+ } from '../utils.js';
11
+ import { join, resolve } from 'path';
12
+
13
+ describe('CLI Utils', () => {
14
+ describe('validatePath', () => {
15
+ const basePath = '/home/user/project';
16
+
17
+ it('should validate safe paths within project', () => {
18
+ const result = validatePath('src/components', basePath);
19
+ expect(result.isValid).toBe(true);
20
+ expect(result.safePath).toBe(resolve(basePath, 'src/components'));
21
+ });
22
+
23
+ it('should reject paths outside project directory', () => {
24
+ const result = validatePath('../../etc/passwd', basePath);
25
+ expect(result.isValid).toBe(false);
26
+ expect(result.error).toContain('outside the project directory');
27
+ });
28
+
29
+ it('should reject paths to sensitive files', () => {
30
+ const sensitiveFiles = ['.env', '.git/config', 'private/key.pem', 'secret.key'];
31
+
32
+ sensitiveFiles.forEach(file => {
33
+ const result = validatePath(file, basePath);
34
+ expect(result.isValid).toBe(false);
35
+ expect(result.error).toContain('sensitive path');
36
+ });
37
+ });
38
+
39
+ it('should handle absolute paths correctly', () => {
40
+ const absolutePath = join(basePath, 'src/components');
41
+ const result = validatePath(absolutePath, basePath);
42
+ expect(result.isValid).toBe(true);
43
+ expect(result.safePath).toBe(absolutePath);
44
+ });
45
+ });
46
+
47
+ describe('validateComponentName', () => {
48
+ it('should accept valid PascalCase names', () => {
49
+ const validNames = ['Button', 'CardHeader', 'MyComponent123'];
50
+
51
+ validNames.forEach(name => {
52
+ const result = validateComponentName(name);
53
+ expect(result.isValid).toBe(true);
54
+ });
55
+ });
56
+
57
+ it('should reject invalid component names', () => {
58
+ const invalidNames = [
59
+ 'button', // lowercase
60
+ 'Button-Test', // contains hyphen
61
+ 'Button_Test', // contains underscore
62
+ '123Button', // starts with number
63
+ 'A', // too short
64
+ '', // empty
65
+ ];
66
+
67
+ invalidNames.forEach(name => {
68
+ const result = validateComponentName(name);
69
+ expect(result.isValid).toBe(false);
70
+ });
71
+ });
72
+
73
+ it('should reject reserved words', () => {
74
+ const reserved = ['Component', 'React', 'Fragment'];
75
+
76
+ reserved.forEach(name => {
77
+ const result = validateComponentName(name);
78
+ expect(result.isValid).toBe(false);
79
+ expect(result.error).toContain('reserved word');
80
+ });
81
+ });
82
+ });
83
+
84
+ describe('validateThemeName', () => {
85
+ it('should accept valid kebab-case names', () => {
86
+ const validNames = ['dark-theme', 'light-mode', 'custom-theme-2'];
87
+
88
+ validNames.forEach(name => {
89
+ const result = validateThemeName(name);
90
+ expect(result.isValid).toBe(true);
91
+ });
92
+ });
93
+
94
+ it('should reject invalid theme names', () => {
95
+ const invalidNames = [
96
+ 'DarkTheme', // PascalCase
97
+ 'dark_theme', // underscore
98
+ '123-theme', // starts with number
99
+ 'theme-', // ends with hyphen
100
+ 'theme--dark', // consecutive hyphens
101
+ '', // empty
102
+ ];
103
+
104
+ invalidNames.forEach(name => {
105
+ const result = validateThemeName(name);
106
+ expect(result.isValid).toBe(false);
107
+ });
108
+ });
109
+ });
110
+
111
+ describe('sanitizeInput', () => {
112
+ it('should remove dangerous shell characters', () => {
113
+ const dangerous = 'test; rm -rf /';
114
+ const sanitized = sanitizeInput(dangerous);
115
+ expect(sanitized).toBe('test rm -rf /');
116
+ expect(sanitized).not.toContain(';');
117
+ });
118
+
119
+ it('should remove multiple dangerous characters', () => {
120
+ const input = 'cmd1 && cmd2 | cmd3 `evil` $var > file < input \\ ';
121
+ const sanitized = sanitizeInput(input);
122
+ expect(sanitized).toBe('cmd1 cmd2 cmd3 evil var file input');
123
+ });
124
+
125
+ it('should handle non-string inputs', () => {
126
+ expect(sanitizeInput(123)).toBe('123');
127
+ expect(sanitizeInput(null)).toBe('null');
128
+ expect(sanitizeInput(undefined)).toBe('undefined');
129
+ });
130
+ });
131
+
132
+ describe('isValidColor', () => {
133
+ it('should validate hex colors', () => {
134
+ const validHex = ['#FFF', '#FFFF', '#FFFFFF', '#FFFFFFFF', '#abc123'];
135
+
136
+ validHex.forEach(color => {
137
+ expect(isValidColor(color)).toBe(true);
138
+ });
139
+ });
140
+
141
+ it('should validate CSS color functions', () => {
142
+ const validFunctions = [
143
+ 'rgb(255, 255, 255)',
144
+ 'rgba(0, 0, 0, 0.5)',
145
+ 'hsl(120, 100%, 50%)',
146
+ 'hsla(240, 100%, 50%, 0.3)',
147
+ 'var(--atomix-color-primary)',
148
+ ];
149
+
150
+ validFunctions.forEach(color => {
151
+ expect(isValidColor(color)).toBe(true);
152
+ });
153
+ });
154
+
155
+ it('should reject invalid colors', () => {
156
+ const invalid = ['#GGG', '#12', 'red', '255,255,255', 'notacolor'];
157
+
158
+ invalid.forEach(color => {
159
+ expect(isValidColor(color)).toBe(false);
160
+ });
161
+ });
162
+ });
163
+
164
+ describe('checkNodeVersion', () => {
165
+ it('should check Node version compatibility', () => {
166
+ const currentVersion = process.version.substring(1);
167
+ const [major, minor, patch] = currentVersion.split('.').map(Number);
168
+
169
+ // Should pass for current version
170
+ const result1 = checkNodeVersion(currentVersion);
171
+ expect(result1.compatible).toBe(true);
172
+
173
+ // Should fail for higher version
174
+ const higherVersion = `${major + 1}.0.0`;
175
+ const result2 = checkNodeVersion(higherVersion);
176
+ expect(result2.compatible).toBe(false);
177
+
178
+ // Should pass for lower version
179
+ if (major > 0) {
180
+ const lowerVersion = `${major - 1}.0.0`;
181
+ const result3 = checkNodeVersion(lowerVersion);
182
+ expect(result3.compatible).toBe(true);
183
+ }
184
+ });
185
+ });
186
+
187
+ describe('formatFileSize', () => {
188
+ it('should format file sizes correctly', () => {
189
+ expect(formatFileSize(0)).toBe('0 B');
190
+ expect(formatFileSize(512)).toBe('512.00 B');
191
+ expect(formatFileSize(1024)).toBe('1.00 KB');
192
+ expect(formatFileSize(1048576)).toBe('1.00 MB');
193
+ expect(formatFileSize(1073741824)).toBe('1.00 GB');
194
+ });
195
+
196
+ it('should handle decimal values', () => {
197
+ expect(formatFileSize(1536)).toBe('1.50 KB'); // 1.5 KB
198
+ expect(formatFileSize(2621440)).toBe('2.50 MB'); // 2.5 MB
199
+ });
200
+ });
201
+ });
@@ -0,0 +1,26 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ coverage: {
8
+ provider: 'v8',
9
+ reporter: ['text', 'json', 'html'],
10
+ exclude: [
11
+ 'node_modules/**',
12
+ '**/__tests__/**',
13
+ '**/test-output/**',
14
+ ],
15
+ include: [
16
+ '../*.js',
17
+ ],
18
+ thresholds: {
19
+ lines: 80,
20
+ functions: 80,
21
+ branches: 80,
22
+ statements: 80,
23
+ },
24
+ },
25
+ },
26
+ });
@@ -4,7 +4,7 @@
4
4
 
5
5
  import inquirer from 'inquirer';
6
6
  import chalk from 'chalk';
7
- import { writeFile, mkdir } from 'fs/promises';
7
+ import { readFile, writeFile, mkdir } from 'fs/promises';
8
8
  import { join } from 'path';
9
9
  import { existsSync } from 'fs';
10
10
  import boxen from 'boxen';
@@ -433,13 +433,38 @@ export async function importTokens(filePath, options = {}) {
433
433
 
434
434
  case 'js':
435
435
  case 'ts':
436
- // Extract tokens from JS/TS export
437
- const match = content.match(/export\s+(?:const|default)\s+\w*\s*=\s*({[\s\S]*})/);
438
- if (match) {
439
- // Simple eval (be careful in production)
440
- tokens = eval(`(${match[1]})`);
441
- } else {
442
- throw new Error('Could not parse JavaScript/TypeScript tokens');
436
+ // Parse tokens safely without eval
437
+ try {
438
+ // Try to parse as JSON first (common case)
439
+ const jsonMatch = content.match(/export\s+(?:const|default)\s+\w*\s*=\s*({[\s\S]*})/);
440
+ if (jsonMatch) {
441
+ // Extract the object literal and clean it up
442
+ let objectStr = jsonMatch[1];
443
+
444
+ // Remove trailing semicolon if present
445
+ objectStr = objectStr.replace(/;\s*$/, '');
446
+
447
+ // Try direct JSON parsing first
448
+ try {
449
+ tokens = JSON.parse(objectStr);
450
+ } catch {
451
+ // If that fails, try to convert JS object to JSON
452
+ // This handles single quotes and unquoted keys
453
+ objectStr = objectStr
454
+ // Replace single quotes with double quotes
455
+ .replace(/'/g, '"')
456
+ // Add quotes to unquoted keys
457
+ .replace(/(\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":')
458
+ // Handle trailing commas
459
+ .replace(/,(\s*[}\]])/g, '$1');
460
+
461
+ tokens = JSON.parse(objectStr);
462
+ }
463
+ } else {
464
+ throw new Error('Could not find token export in JavaScript/TypeScript file');
465
+ }
466
+ } catch (error) {
467
+ throw new Error(`Could not parse JavaScript/TypeScript tokens: ${error.message}`);
443
468
  }
444
469
  break;
445
470