@shohojdhara/atomix 0.5.0 → 0.5.2
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/atomix.config.ts +12 -0
- package/build-tools/webpack-loader.js +5 -4
- package/dist/atomix.css +230 -83
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +1 -1
- package/dist/atomix.min.css.map +1 -1
- package/dist/build-tools/webpack-loader.js +5 -4
- package/dist/charts.d.ts +24 -23
- package/dist/charts.js +271 -369
- package/dist/charts.js.map +1 -1
- package/dist/config.d.ts +624 -0
- package/dist/config.js +59 -0
- package/dist/config.js.map +1 -0
- package/dist/core.d.ts +3 -2
- package/dist/core.js +342 -382
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +4 -6
- package/dist/forms.js +233 -334
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +11 -2
- package/dist/heavy.js +406 -445
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +109 -65
- package/dist/index.esm.js +654 -748
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +621 -717
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/layout.js +59 -60
- package/dist/layout.js.map +1 -1
- package/dist/theme.js +4 -4
- package/dist/theme.js.map +1 -1
- package/package.json +24 -9
- package/scripts/atomix-cli.js +15 -1
- package/scripts/cli/__tests__/complexity-utils.test.js +24 -0
- package/scripts/cli/__tests__/detector.test.js +50 -0
- package/scripts/cli/__tests__/template-engine.test.js +23 -0
- package/scripts/cli/__tests__/test-setup.js +1 -133
- package/scripts/cli/commands/doctor.js +15 -3
- package/scripts/cli/commands/generate.js +113 -51
- package/scripts/cli/internal/ai-engine.js +30 -10
- package/scripts/cli/internal/complexity-utils.js +60 -0
- package/scripts/cli/internal/component-validator.js +49 -16
- package/scripts/cli/internal/generator.js +89 -36
- package/scripts/cli/internal/hook-generator.js +5 -2
- package/scripts/cli/internal/itcss-generator.js +16 -12
- package/scripts/cli/templates/next-templates.js +81 -30
- package/scripts/cli/templates/storybook-templates.js +12 -2
- package/scripts/cli/utils/detector.js +45 -7
- package/scripts/cli/utils/diagnostics.js +78 -0
- package/scripts/cli/utils/telemetry.js +13 -0
- package/src/components/Accordion/Accordion.stories.tsx +4 -0
- package/src/components/AtomixGlass/AtomixGlass.tsx +188 -128
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +63 -91
- package/src/components/AtomixGlass/PerformanceDashboard.tsx +153 -201
- package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +9 -6
- package/src/components/AtomixGlass/glass-utils.ts +51 -1
- package/src/components/AtomixGlass/stories/AnimationFeatures.stories.tsx +52 -46
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +573 -236
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +88 -41
- package/src/components/AtomixGlass/stories/argTypes.ts +19 -19
- package/src/components/AtomixGlass/stories/shared-components.tsx +7 -12
- package/src/components/AtomixGlass/stories/types.ts +3 -3
- package/src/components/Button/Button.tsx +114 -57
- package/src/components/Callout/Callout.tsx +4 -4
- package/src/components/Chart/ChartRenderer.tsx +1 -1
- package/src/components/Chart/DonutChart.tsx +11 -8
- package/src/components/EdgePanel/EdgePanel.tsx +119 -115
- package/src/components/Form/Select.tsx +4 -4
- package/src/components/List/List.tsx +4 -4
- package/src/components/Navigation/SideMenu/SideMenu.tsx +6 -6
- package/src/components/PhotoViewer/PhotoViewerImage.tsx +1 -1
- package/src/components/ProductReview/ProductReview.tsx +4 -2
- package/src/components/Rating/Rating.tsx +4 -2
- package/src/components/SectionIntro/SectionIntro.tsx +4 -2
- package/src/components/Steps/Steps.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +5 -5
- package/src/components/Testimonial/Testimonial.tsx +4 -2
- package/src/components/VideoPlayer/VideoPlayer.tsx +4 -2
- package/src/layouts/CssGrid/CssGrid.stories.tsx +464 -0
- package/src/layouts/CssGrid/CssGrid.tsx +215 -0
- package/src/layouts/CssGrid/index.ts +8 -0
- package/src/layouts/CssGrid/scripts/CssGrid.js +284 -0
- package/src/layouts/CssGrid/scripts/index.js +43 -0
- package/src/layouts/Grid/scripts/Container.js +139 -0
- package/src/layouts/Grid/scripts/Grid.js +184 -0
- package/src/layouts/Grid/scripts/GridCol.js +273 -0
- package/src/layouts/Grid/scripts/Row.js +154 -0
- package/src/layouts/Grid/scripts/index.js +48 -0
- package/src/layouts/MasonryGrid/MasonryGrid.tsx +71 -59
- package/src/lib/composables/atomix-glass/useGlassSize.ts +1 -1
- package/src/lib/composables/useAccordion.ts +5 -5
- package/src/lib/composables/useAtomixGlass.ts +111 -74
- package/src/lib/composables/useAtomixGlassStyles.ts +0 -2
- package/src/lib/composables/useBarChart.ts +2 -2
- package/src/lib/composables/useChart.ts +3 -2
- package/src/lib/composables/useChartToolbar.ts +48 -66
- package/src/lib/composables/useDataTable.ts +1 -1
- package/src/lib/composables/useDatePicker.ts +2 -2
- package/src/lib/composables/useEdgePanel.ts +45 -54
- package/src/lib/composables/useHeroBackgroundSlider.ts +5 -5
- package/src/lib/composables/usePhotoViewer.ts +2 -3
- package/src/lib/composables/usePieChart.ts +1 -1
- package/src/lib/composables/usePopover.ts +151 -139
- package/src/lib/composables/useSideMenu.ts +28 -41
- package/src/lib/composables/useSlider.ts +2 -6
- package/src/lib/composables/useTooltip.ts +2 -2
- package/src/lib/config/index.ts +39 -0
- package/src/lib/constants/components.ts +1 -0
- package/src/lib/theme/devtools/Comparator.tsx +1 -1
- package/src/lib/theme/devtools/Inspector.tsx +1 -1
- package/src/lib/theme/devtools/LiveEditor.tsx +1 -1
- package/src/lib/theme/runtime/ThemeProvider.tsx +1 -1
- package/src/lib/types/components.ts +1 -0
- package/src/styles/01-settings/_index.scss +1 -0
- package/src/styles/01-settings/_settings.atomix-glass.scss +174 -0
- package/src/styles/01-settings/_settings.masonry-grid.scss +42 -6
- package/src/styles/02-tools/_tools.glass.scss +6 -0
- package/src/styles/05-objects/_objects.masonry-grid.scss +162 -24
- package/src/styles/06-components/_components.atomix-glass.scss +160 -99
- package/scripts/cli/__tests__/README.md +0 -81
- package/scripts/cli/__tests__/basic.test.js +0 -116
- package/scripts/cli/__tests__/clean.test.js +0 -278
- package/scripts/cli/__tests__/component-generator.test.js +0 -332
- package/scripts/cli/__tests__/component-validator.test.js +0 -433
- package/scripts/cli/__tests__/generator.test.js +0 -613
- package/scripts/cli/__tests__/glass-motion.test.js +0 -256
- package/scripts/cli/__tests__/integration.test.js +0 -938
- package/scripts/cli/__tests__/migrate.test.js +0 -74
- package/scripts/cli/__tests__/security.test.js +0 -206
- package/scripts/cli/__tests__/theme-bridge.test.js +0 -507
- package/scripts/cli/__tests__/token-manager.test.js +0 -251
- package/scripts/cli/__tests__/token-provider.test.js +0 -361
- package/scripts/cli/__tests__/utils.test.js +0 -165
- package/src/components/AtomixGlass/stories/AnimationTests.stories.tsx +0 -95
- package/src/components/AtomixGlass/stories/CardExamples.stories.tsx +0 -212
- package/src/components/AtomixGlass/stories/Customization.stories.tsx +0 -131
- package/src/components/AtomixGlass/stories/DashboardExamples.stories.tsx +0 -348
- package/src/components/AtomixGlass/stories/EcommerceExamples.stories.tsx +0 -410
- package/src/components/AtomixGlass/stories/FormExamples.stories.tsx +0 -436
- package/src/components/AtomixGlass/stories/HeroExamples.stories.tsx +0 -264
- package/src/components/AtomixGlass/stories/InteractivePlayground.stories.tsx +0 -247
- package/src/components/AtomixGlass/stories/MobileUIExamples.stories.tsx +0 -418
- package/src/components/AtomixGlass/stories/ModalExamples.stories.tsx +0 -402
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +0 -1082
- package/src/components/AtomixGlass/stories/Overview.stories.tsx +0 -497
- package/src/components/AtomixGlass/stories/Performance.stories.tsx +0 -103
- package/src/components/AtomixGlass/stories/PresetGallery.stories.tsx +0 -335
- package/src/components/AtomixGlass/stories/Shaders.stories.tsx +0 -395
- package/src/components/AtomixGlass/stories/WidgetExamples.stories.tsx +0 -441
- package/src/components/TypedButton/TypedButton.stories.tsx +0 -59
- package/src/components/TypedButton/TypedButton.tsx +0 -39
- package/src/components/TypedButton/index.ts +0 -2
- package/src/lib/composables/useBreadcrumb.ts +0 -81
- package/src/lib/composables/useChartInteractions.ts +0 -123
- package/src/lib/composables/useChartPerformance.ts +0 -347
- package/src/lib/composables/useDropdown.ts +0 -338
- package/src/lib/composables/useModal.ts +0 -110
- package/src/lib/composables/useTypedButton.ts +0 -66
- package/src/lib/hooks/usePerformanceMonitor.ts +0 -148
- package/src/lib/utils/displacement-generator.ts +0 -92
- package/src/lib/utils/memoryMonitor.ts +0 -191
- package/src/styles/01-settings/_settings.testtypecheck.scss +0 -53
- package/src/styles/01-settings/_settings.typedbutton.scss +0 -53
- package/src/styles/06-components/_components.testbutton.scss +0 -212
- package/src/styles/06-components/_components.testtypecheck.scss +0 -212
- package/src/styles/06-components/_components.typedbutton.scss +0 -212
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shohojdhara/atomix",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Atomix Design System - A modern component library for web applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -15,8 +15,11 @@
|
|
|
15
15
|
"types": "dist/index.d.ts",
|
|
16
16
|
"style": "dist/atomix.css",
|
|
17
17
|
"sass": "src/styles/index.scss",
|
|
18
|
+
"unpkg": "dist/atomix.umd.min.js",
|
|
19
|
+
"jsdelivr": "dist/atomix.umd.min.js",
|
|
20
|
+
"browser": "dist/atomix.umd.min.js",
|
|
18
21
|
"bin": {
|
|
19
|
-
"atomix": "
|
|
22
|
+
"atomix": "scripts/atomix-cli.js"
|
|
20
23
|
},
|
|
21
24
|
"exports": {
|
|
22
25
|
".": {
|
|
@@ -111,12 +114,22 @@
|
|
|
111
114
|
"import": "./dist/heavy.js",
|
|
112
115
|
"default": "./dist/heavy.js"
|
|
113
116
|
},
|
|
117
|
+
"./cdn": {
|
|
118
|
+
"import": "./dist/atomix.umd.min.js",
|
|
119
|
+
"require": "./dist/atomix.umd.min.js",
|
|
120
|
+
"default": "./dist/atomix.umd.min.js"
|
|
121
|
+
},
|
|
114
122
|
"./build-tools": {
|
|
115
123
|
"types": "./build-tools/index.d.ts",
|
|
116
124
|
"import": "./build-tools/index.js",
|
|
117
125
|
"require": "./build-tools/index.js",
|
|
118
126
|
"default": "./build-tools/index.js"
|
|
119
127
|
},
|
|
128
|
+
"./config": {
|
|
129
|
+
"types": "./dist/config.d.ts",
|
|
130
|
+
"import": "./dist/config.js",
|
|
131
|
+
"default": "./dist/config.js"
|
|
132
|
+
},
|
|
120
133
|
"./package.json": "./package.json"
|
|
121
134
|
},
|
|
122
135
|
"dependencies": {
|
|
@@ -172,13 +185,14 @@
|
|
|
172
185
|
"@testing-library/react": "^14.0.0",
|
|
173
186
|
"@testing-library/user-event": "^14.0.0",
|
|
174
187
|
"@types/node": "^20.0.0",
|
|
175
|
-
"@types/react": "^
|
|
176
|
-
"@types/react-dom": "^
|
|
188
|
+
"@types/react": "^19.0.0",
|
|
189
|
+
"@types/react-dom": "^19.0.0",
|
|
177
190
|
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
178
191
|
"@typescript-eslint/parser": "^6.0.0",
|
|
179
192
|
"@vitejs/plugin-react": "^4.0.0",
|
|
180
193
|
"@vitest/coverage-v8": "^4.0.18",
|
|
181
194
|
"browserslist": "^4.23.0",
|
|
195
|
+
"chromatic": "^15.3.1",
|
|
182
196
|
"concurrently": "^8.0.0",
|
|
183
197
|
"eslint": "^8.0.0",
|
|
184
198
|
"eslint-plugin-import": "^2.29.0",
|
|
@@ -193,8 +207,8 @@
|
|
|
193
207
|
"postcss-import": "^15.0.0",
|
|
194
208
|
"postcss-preset-env": "^9.0.0",
|
|
195
209
|
"prettier": "^3.0.0",
|
|
196
|
-
"react": "^
|
|
197
|
-
"react-dom": "^
|
|
210
|
+
"react": "^19.0.0",
|
|
211
|
+
"react-dom": "^19.0.0",
|
|
198
212
|
"rimraf": "^5.0.0",
|
|
199
213
|
"rollup": "^3.0.0",
|
|
200
214
|
"rollup-plugin-dts": "^6.0.0",
|
|
@@ -207,7 +221,7 @@
|
|
|
207
221
|
"ts-node": "^10.9.0",
|
|
208
222
|
"tslib": "^2.6.0",
|
|
209
223
|
"tsx": "^4.21.0",
|
|
210
|
-
"typescript": "^
|
|
224
|
+
"typescript": "^6.0.2",
|
|
211
225
|
"vite": "^4.0.0",
|
|
212
226
|
"vitest": "^0.34.0"
|
|
213
227
|
},
|
|
@@ -220,6 +234,7 @@
|
|
|
220
234
|
"build:styles": "rollup -c rollup.config.styles.js",
|
|
221
235
|
"build:themes": "rollup -c rollup/config/themes.js",
|
|
222
236
|
"build:parallel": "concurrently \"npm:build:js\" \"npm:build:types\" \"npm:build:styles\"",
|
|
237
|
+
"build:umd": "rollup -c rollup.config.umd.js",
|
|
223
238
|
"build:cli": "rollup -c rollup.config.cli.js",
|
|
224
239
|
"build:cli:dev": "rollup -c rollup.config.cli.js --environment NODE_ENV:development",
|
|
225
240
|
"build:analyze": "ANALYZE=true rollup -c",
|
|
@@ -295,8 +310,8 @@
|
|
|
295
310
|
"homepage": "https://github.com/Shohojdhara/atomix#readme",
|
|
296
311
|
"peerDependencies": {
|
|
297
312
|
"@phosphor-icons/react": "2.1.10",
|
|
298
|
-
"react": "
|
|
299
|
-
"react-dom": "
|
|
313
|
+
"react": ">=18.0.0",
|
|
314
|
+
"react-dom": ">=18.0.0"
|
|
300
315
|
},
|
|
301
316
|
"engines": {
|
|
302
317
|
"node": ">=18.0.0",
|
package/scripts/atomix-cli.js
CHANGED
|
@@ -137,8 +137,22 @@ program
|
|
|
137
137
|
.option('-i, --interactive', 'Interactive mode', false)
|
|
138
138
|
.option('-p, --path <path>', 'Output path', './src/components')
|
|
139
139
|
.option('--prompt <prompt>', 'AI prompt for generating component')
|
|
140
|
-
.option('--
|
|
140
|
+
.option('--ai-provider <provider>', 'AI provider override (openai|anthropic)')
|
|
141
|
+
.option('--ai-model <model>', 'AI model override')
|
|
142
|
+
.option('--ai-temperature <temp>', 'AI creativity level 0.0-1.0', parseFloat, 0.7)
|
|
143
|
+
.option('--ai-preview', 'Preview AI output before writing files', false)
|
|
144
|
+
.option('--ai-max-tokens <tokens>', 'Maximum tokens for AI response', parseInt, 4000)
|
|
145
|
+
.option(
|
|
146
|
+
'--complexity <level>',
|
|
147
|
+
'Template complexity (React: simple|medium|complex; Next: simple|client|complex). Default is framework-specific.'
|
|
148
|
+
)
|
|
149
|
+
.option('--framework <name>', 'Force framework: react, next, or vanilla (skips detection)')
|
|
150
|
+
.option('--storybook', 'Generate Storybook story', true)
|
|
151
|
+
.option('--hook', 'Generate composable hook', true)
|
|
152
|
+
.option('--styles', 'Generate ITCSS styles', true)
|
|
153
|
+
.option('--tests', 'Generate unit test file', false)
|
|
141
154
|
.option('--validate', 'Validate after generation', true)
|
|
155
|
+
.option('--skip-validate', 'Skip validation after generation', false)
|
|
142
156
|
.action(async (type, name, options) => {
|
|
143
157
|
try {
|
|
144
158
|
await generateAction(type, name, options);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
resolveDefaultComplexity,
|
|
4
|
+
normalizeComplexityForFramework,
|
|
5
|
+
resolveEffectiveComplexity
|
|
6
|
+
} from '../internal/complexity-utils.js';
|
|
7
|
+
|
|
8
|
+
describe('complexity-utils', () => {
|
|
9
|
+
it('resolveDefaultComplexity: next is simple, react is medium', () => {
|
|
10
|
+
expect(resolveDefaultComplexity('next')).toBe('simple');
|
|
11
|
+
expect(resolveDefaultComplexity('react')).toBe('medium');
|
|
12
|
+
expect(resolveDefaultComplexity('vanilla')).toBe('medium');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('normalizeComplexityForFramework maps next+medium to simple', () => {
|
|
16
|
+
expect(normalizeComplexityForFramework('next', 'medium')).toBe('simple');
|
|
17
|
+
expect(normalizeComplexityForFramework('next', 'client')).toBe('client');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('resolveEffectiveComplexity uses explicit CLI value when set', () => {
|
|
21
|
+
expect(resolveEffectiveComplexity('react', 'simple')).toBe('simple');
|
|
22
|
+
expect(resolveEffectiveComplexity('next', undefined)).toBe('simple');
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdtempSync, writeFileSync, rmSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { detectFramework } from '../utils/detector.js';
|
|
6
|
+
|
|
7
|
+
function writePackage(root, deps = {}, devDeps = {}) {
|
|
8
|
+
writeFileSync(
|
|
9
|
+
join(root, 'package.json'),
|
|
10
|
+
JSON.stringify({ name: 'fixture', dependencies: deps, devDependencies: devDeps }, null, 2)
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('detectFramework', () => {
|
|
15
|
+
let dir;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
dir = mkdtempSync(join(tmpdir(), 'atomix-det-'));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
rmSync(dir, { recursive: true, force: true });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('returns vanilla without package.json', async () => {
|
|
26
|
+
expect(await detectFramework(join(dir, 'empty'))).toBe('vanilla');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('next dependency wins over react', async () => {
|
|
30
|
+
writePackage(dir, { react: '^18', next: '^14' });
|
|
31
|
+
expect(await detectFramework(dir)).toBe('next');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('vite + react is react (not next) when next.config absent', async () => {
|
|
35
|
+
writePackage(dir, { react: '^18', vite: '^5' });
|
|
36
|
+
writeFileSync(join(dir, 'vite.config.ts'), 'export default {}');
|
|
37
|
+
expect(await detectFramework(dir)).toBe('react');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('next.config.ts implies next without next in deps', async () => {
|
|
41
|
+
writePackage(dir, { react: '^18' });
|
|
42
|
+
writeFileSync(join(dir, 'next.config.ts'), 'export default {}');
|
|
43
|
+
expect(await detectFramework(dir)).toBe('next');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('respects framework override', async () => {
|
|
47
|
+
writePackage(dir, { react: '^18', next: '^14' });
|
|
48
|
+
expect(await detectFramework(dir, { framework: 'react' })).toBe('react');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { templateEngine } from '../internal/template-engine.js';
|
|
3
|
+
|
|
4
|
+
describe('templateEngine.selectTemplate', () => {
|
|
5
|
+
it('resolves react × medium × component', () => {
|
|
6
|
+
const fn = templateEngine.selectTemplate('react', 'medium', 'component');
|
|
7
|
+
expect(typeof fn).toBe('function');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('resolves next × simple × component', () => {
|
|
11
|
+
const fn = templateEngine.selectTemplate('next', 'simple', 'component');
|
|
12
|
+
expect(typeof fn).toBe('function');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('throws for next × medium', () => {
|
|
16
|
+
expect(() => templateEngine.selectTemplate('next', 'medium', 'component')).toThrow();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('resolves vanilla component without complexity validation', () => {
|
|
20
|
+
const fn = templateEngine.selectTemplate('vanilla', 'anything', 'component');
|
|
21
|
+
expect(typeof fn).toBe('function');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -1,135 +1,3 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* CLI test setup (node environment)
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
import { vi } from 'vitest';
|
|
6
|
-
|
|
7
|
-
// Mock external dependencies
|
|
8
|
-
vi.mock('ora', () => ({
|
|
9
|
-
default: vi.fn(() => ({
|
|
10
|
-
start: vi.fn(() => ({
|
|
11
|
-
succeed: vi.fn(),
|
|
12
|
-
fail: vi.fn(),
|
|
13
|
-
stop: vi.fn(),
|
|
14
|
-
text: ''
|
|
15
|
-
}))
|
|
16
|
-
}))
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
vi.mock('inquirer', () => ({
|
|
20
|
-
prompt: vi.fn()
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
|
-
vi.mock('chalk', () => ({
|
|
24
|
-
default: {
|
|
25
|
-
green: vi.fn((text) => text),
|
|
26
|
-
red: vi.fn((text) => text),
|
|
27
|
-
yellow: vi.fn((text) => text),
|
|
28
|
-
cyan: vi.fn((text) => text),
|
|
29
|
-
gray: vi.fn((text) => text),
|
|
30
|
-
blue: vi.fn((text) => text),
|
|
31
|
-
bold: {
|
|
32
|
-
green: vi.fn((text) => text),
|
|
33
|
-
red: vi.fn((text) => text),
|
|
34
|
-
yellow: vi.fn((text) => text),
|
|
35
|
-
cyan: vi.fn((text) => text),
|
|
36
|
-
blue: vi.fn((text) => text)
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
|
-
vi.mock('boxen', () => ({
|
|
42
|
-
default: vi.fn((text) => text)
|
|
43
|
-
}));
|
|
44
|
-
|
|
45
|
-
vi.mock('chokidar', () => ({
|
|
46
|
-
default: vi.fn(() => ({
|
|
47
|
-
on: vi.fn(),
|
|
48
|
-
close: vi.fn()
|
|
49
|
-
}))
|
|
50
|
-
}));
|
|
51
|
-
|
|
52
|
-
// Mock file system operations
|
|
53
|
-
vi.mock('fs/promises', async () => {
|
|
54
|
-
const actual = await vi.importActual('fs/promises');
|
|
55
|
-
return {
|
|
56
|
-
...actual,
|
|
57
|
-
writeFile: vi.fn(actual.writeFile),
|
|
58
|
-
readFile: vi.fn(actual.readFile),
|
|
59
|
-
mkdir: vi.fn(actual.mkdir),
|
|
60
|
-
access: vi.fn(actual.access),
|
|
61
|
-
stat: vi.fn(actual.stat),
|
|
62
|
-
rm: vi.fn(actual.rm)
|
|
63
|
-
};
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// Mock process.cwd for consistent test environment
|
|
67
|
-
const originalCwd = process.cwd;
|
|
68
|
-
|
|
69
|
-
beforeEach(() => {
|
|
70
|
-
// Reset all mocks before each test
|
|
71
|
-
vi.clearAllMocks();
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
afterEach(() => {
|
|
75
|
-
// Restore original process.cwd
|
|
76
|
-
process.cwd = originalCwd;
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// Global test utilities
|
|
80
|
-
global.createMockTempDir = () => '/tmp/atomix-test-' + Math.random().toString(36).substr(2, 9);
|
|
81
|
-
|
|
82
|
-
global.mockComponentStructure = (componentName, content = '') => ({
|
|
83
|
-
[`${componentName}.tsx`]: content || `
|
|
84
|
-
import React, { forwardRef } from 'react';
|
|
85
|
-
|
|
86
|
-
export const ${componentName} = forwardRef<HTMLDivElement, ${componentName}Props>(
|
|
87
|
-
({ children, className = '', ...props }, ref) => {
|
|
88
|
-
return (
|
|
89
|
-
<div ref={ref} className={className} {...props}>
|
|
90
|
-
{children}
|
|
91
|
-
</div>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
interface ${componentName}Props {
|
|
97
|
-
children?: React.ReactNode;
|
|
98
|
-
className?: string;
|
|
99
|
-
}
|
|
100
|
-
`,
|
|
101
|
-
'index.ts': `export { ${componentName} } from './${componentName}';`,
|
|
102
|
-
[`${componentName}.stories.tsx`]: `
|
|
103
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
104
|
-
import { ${componentName} } from './${componentName}';
|
|
105
|
-
|
|
106
|
-
const meta: Meta<typeof ${componentName}> = {
|
|
107
|
-
title: 'Components/${componentName}',
|
|
108
|
-
component: ${componentName},
|
|
109
|
-
parameters: {
|
|
110
|
-
layout: 'centered',
|
|
111
|
-
},
|
|
112
|
-
tags: ['autodocs'],
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
export default meta;
|
|
116
|
-
type Story = StoryObj<typeof meta>;
|
|
117
|
-
|
|
118
|
-
export const Default: Story = {
|
|
119
|
-
args: {
|
|
120
|
-
children: 'Test ${componentName}',
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
`,
|
|
124
|
-
[`${componentName}.test.tsx`]: `
|
|
125
|
-
import { render, screen } from '@testing-library/react';
|
|
126
|
-
import { ${componentName} } from './${componentName}';
|
|
127
|
-
|
|
128
|
-
describe('${componentName}', () => {
|
|
129
|
-
it('renders correctly', () => {
|
|
130
|
-
render(<${componentName}>Test</${componentName}>);
|
|
131
|
-
expect(screen.getByText('Test')).toBeInTheDocument();
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
`
|
|
135
|
-
});
|
|
@@ -4,7 +4,15 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { logger } from '../utils/logger.js';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
checkRuntimes,
|
|
9
|
+
checkProjectStructure,
|
|
10
|
+
checkConfig,
|
|
11
|
+
checkPermissions,
|
|
12
|
+
checkPlugins,
|
|
13
|
+
checkTokens,
|
|
14
|
+
checkGenerator
|
|
15
|
+
} from '../utils/diagnostics.js';
|
|
8
16
|
import chalk from 'chalk';
|
|
9
17
|
|
|
10
18
|
/** Short descriptions for each doctor check (for --explain) */
|
|
@@ -19,7 +27,10 @@ const DOCTOR_CHECK_DESCRIPTIONS = {
|
|
|
19
27
|
'Permissions: .': 'Read/write access to project root.',
|
|
20
28
|
'Permissions: src': 'Read/write access to src directory.',
|
|
21
29
|
'Plugins': 'Reports plugins registered in atomix.config.',
|
|
22
|
-
'Tokens': 'Design token discovery: src/styles/01-settings/_settings.*.scss (optional).'
|
|
30
|
+
'Tokens': 'Design token discovery: src/styles/01-settings/_settings.*.scss (optional).',
|
|
31
|
+
'Generator: output path': 'Writable directory for atomix generate (from config or ./src/components).',
|
|
32
|
+
'Storybook (optional)': 'Detects Storybook-related dependencies for generated stories.',
|
|
33
|
+
'Generator: template registry': 'Ensures CLI template engine exposes react, next, and vanilla.'
|
|
23
34
|
};
|
|
24
35
|
|
|
25
36
|
/**
|
|
@@ -46,6 +57,7 @@ export async function doctorAction(options = {}) {
|
|
|
46
57
|
const permissions = await checkPermissions();
|
|
47
58
|
const plugins = await checkPlugins();
|
|
48
59
|
const tokens = await checkTokens();
|
|
60
|
+
const generator = await checkGenerator();
|
|
49
61
|
|
|
50
62
|
spinner.stop();
|
|
51
63
|
|
|
@@ -55,7 +67,7 @@ export async function doctorAction(options = {}) {
|
|
|
55
67
|
margin: 1
|
|
56
68
|
});
|
|
57
69
|
|
|
58
|
-
const allResults = [...runtimes, ...structure, ...config, ...permissions, ...plugins, ...tokens];
|
|
70
|
+
const allResults = [...runtimes, ...structure, ...config, ...permissions, ...plugins, ...tokens, ...generator];
|
|
59
71
|
let issuesFound = false;
|
|
60
72
|
|
|
61
73
|
for (const result of allResults) {
|
|
@@ -11,6 +11,45 @@ import { filesystem } from '../internal/filesystem.js';
|
|
|
11
11
|
import { validateComponentName } from '../utils/validation.js';
|
|
12
12
|
import { hookManager } from '../internal/hooks.js';
|
|
13
13
|
import { validateComponent } from '../internal/validator.js';
|
|
14
|
+
import { configLoader } from '../internal/config-loader.js';
|
|
15
|
+
import { detectFramework } from '../utils/detector.js';
|
|
16
|
+
import { resolveDefaultComplexity } from '../internal/complexity-utils.js';
|
|
17
|
+
import { telemetry } from '../utils/telemetry.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Merge CLI options with atomix.config generator section (CLI wins).
|
|
21
|
+
* @param {Object} options - Commander options
|
|
22
|
+
* @returns {Promise<Object>}
|
|
23
|
+
*/
|
|
24
|
+
async function buildGenerateConfig(name, options) {
|
|
25
|
+
const cwd = process.cwd();
|
|
26
|
+
const loaded = await configLoader.load(cwd);
|
|
27
|
+
const gen = loaded.generator || {};
|
|
28
|
+
|
|
29
|
+
const outputPath = options.path ?? gen.outputPath ?? './src/components';
|
|
30
|
+
const framework = options.framework ?? gen.framework;
|
|
31
|
+
const prefix = loaded.prefix ?? 'atomix';
|
|
32
|
+
const storybookCssImport = gen.storybookCssImport;
|
|
33
|
+
const hookOutputDir = gen.hookOutputDir;
|
|
34
|
+
|
|
35
|
+
const complexity =
|
|
36
|
+
options.complexity !== undefined && options.complexity !== null && options.complexity !== ''
|
|
37
|
+
? options.complexity
|
|
38
|
+
: undefined;
|
|
39
|
+
|
|
40
|
+
const features = determineFeatures(options, gen.features || {});
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
name,
|
|
44
|
+
complexity,
|
|
45
|
+
features,
|
|
46
|
+
outputPath,
|
|
47
|
+
framework,
|
|
48
|
+
prefix,
|
|
49
|
+
storybookCssImport,
|
|
50
|
+
hookOutputDir
|
|
51
|
+
};
|
|
52
|
+
}
|
|
14
53
|
|
|
15
54
|
/**
|
|
16
55
|
* Action logic for generating components
|
|
@@ -19,16 +58,18 @@ import { validateComponent } from '../internal/validator.js';
|
|
|
19
58
|
* @param {Object} options - CLI options
|
|
20
59
|
*/
|
|
21
60
|
export async function generateAction(type, name, options) {
|
|
22
|
-
let config =
|
|
23
|
-
name,
|
|
24
|
-
complexity: options.complexity || 'medium',
|
|
25
|
-
features: determineFeatures(options),
|
|
26
|
-
outputPath: options.path || './src/components'
|
|
27
|
-
};
|
|
61
|
+
let config = await buildGenerateConfig(name, options);
|
|
28
62
|
|
|
29
63
|
if (options.interactive) {
|
|
30
|
-
|
|
31
|
-
if (!
|
|
64
|
+
const interactive = await promptInteractive();
|
|
65
|
+
if (!interactive) return;
|
|
66
|
+
config = {
|
|
67
|
+
...config,
|
|
68
|
+
...interactive,
|
|
69
|
+
name: interactive.name,
|
|
70
|
+
complexity: interactive.complexity,
|
|
71
|
+
features: interactive.features
|
|
72
|
+
};
|
|
32
73
|
}
|
|
33
74
|
|
|
34
75
|
// Pre-generation hook
|
|
@@ -49,7 +90,6 @@ export async function generateAction(type, name, options) {
|
|
|
49
90
|
const spinner = logger.spinner(`Generating ${type}: ${config.name}...`).start();
|
|
50
91
|
|
|
51
92
|
try {
|
|
52
|
-
// Validation
|
|
53
93
|
const nameValidation = await validateComponentName(config.name);
|
|
54
94
|
if (!nameValidation.isValid) {
|
|
55
95
|
throw new AtomixCLIError(
|
|
@@ -76,12 +116,23 @@ export async function generateAction(type, name, options) {
|
|
|
76
116
|
);
|
|
77
117
|
}
|
|
78
118
|
|
|
79
|
-
|
|
119
|
+
const detectedFw = await detectFramework(process.cwd(), { framework: config.framework });
|
|
120
|
+
telemetry.recordExtra({
|
|
121
|
+
generateType: type,
|
|
122
|
+
framework: detectedFw,
|
|
123
|
+
componentName: config.name
|
|
124
|
+
});
|
|
125
|
+
|
|
80
126
|
let path;
|
|
81
127
|
if (options.prompt) {
|
|
82
128
|
path = await generator.generateAIComponent(config.name, options.prompt, {
|
|
83
129
|
...config,
|
|
84
|
-
logger: { debug: (msg) => logger.debug(msg) }
|
|
130
|
+
logger: { debug: (msg) => logger.debug(msg) },
|
|
131
|
+
aiProvider: options.aiProvider,
|
|
132
|
+
aiModel: options.aiModel,
|
|
133
|
+
aiTemperature: options.aiTemperature,
|
|
134
|
+
aiPreview: options.aiPreview,
|
|
135
|
+
aiMaxTokens: options.aiMaxTokens
|
|
85
136
|
});
|
|
86
137
|
} else {
|
|
87
138
|
path = await generator.generateComponent(config.name, {
|
|
@@ -92,9 +143,8 @@ export async function generateAction(type, name, options) {
|
|
|
92
143
|
|
|
93
144
|
spinner.succeed(`Generated component ${config.name} at ${path}`);
|
|
94
145
|
|
|
95
|
-
if (options.validate) {
|
|
146
|
+
if (!options.skipValidate && options.validate !== false) {
|
|
96
147
|
let report = await generator.validate(config.name, path);
|
|
97
|
-
// Component-scoped A11y and token validation (Phase 2: design system creator)
|
|
98
148
|
if (type === 'component') {
|
|
99
149
|
const componentReport = await validateComponent(config.name, process.cwd());
|
|
100
150
|
const componentIssues = componentReport.issues.map(
|
|
@@ -104,7 +154,6 @@ export async function generateAction(type, name, options) {
|
|
|
104
154
|
report.valid = report.valid && componentReport.valid;
|
|
105
155
|
}
|
|
106
156
|
|
|
107
|
-
// Validation hook
|
|
108
157
|
try {
|
|
109
158
|
report = await hookManager.trigger('onValidate', report);
|
|
110
159
|
} catch (error) {
|
|
@@ -117,7 +166,6 @@ export async function generateAction(type, name, options) {
|
|
|
117
166
|
}
|
|
118
167
|
}
|
|
119
168
|
|
|
120
|
-
// Post-build hook (using generated path as asset)
|
|
121
169
|
try {
|
|
122
170
|
await hookManager.trigger('postBuild', [path]);
|
|
123
171
|
} catch (error) {
|
|
@@ -125,11 +173,9 @@ export async function generateAction(type, name, options) {
|
|
|
125
173
|
}
|
|
126
174
|
|
|
127
175
|
logger.box(`🎉 Component ${config.name} ready!\nRun: atomix validate component ${config.name}`);
|
|
128
|
-
|
|
129
176
|
} catch (error) {
|
|
130
177
|
spinner.fail('Generation failed');
|
|
131
|
-
|
|
132
|
-
// Enhance error context for known error types
|
|
178
|
+
|
|
133
179
|
if (error.code === 'TEMPLATE_NOT_FOUND') {
|
|
134
180
|
error.suggestions.push('Run `atomix doctor` to check template availability');
|
|
135
181
|
} else if (error.code === 'FRAMEWORK_DETECTION_FAILED') {
|
|
@@ -137,48 +183,62 @@ export async function generateAction(type, name, options) {
|
|
|
137
183
|
} else if (error.code === 'FILE_WRITE_FAILED') {
|
|
138
184
|
error.suggestions.push('Check disk space and file permissions');
|
|
139
185
|
}
|
|
140
|
-
|
|
186
|
+
|
|
141
187
|
throw error;
|
|
142
188
|
}
|
|
143
189
|
}
|
|
144
190
|
|
|
145
191
|
/**
|
|
146
|
-
* Determine which features to enable based on CLI options
|
|
147
192
|
* @param {Object} options - CLI options
|
|
148
|
-
* @
|
|
193
|
+
* @param {Object} genFeatures - config.generator.features
|
|
149
194
|
*/
|
|
150
|
-
function determineFeatures(options) {
|
|
195
|
+
function determineFeatures(options, genFeatures = {}) {
|
|
196
|
+
let storybook = genFeatures.storybook !== false;
|
|
197
|
+
let hook = genFeatures.hook !== false;
|
|
198
|
+
let styles = genFeatures.styles !== false;
|
|
199
|
+
let tests = genFeatures.tests === true;
|
|
200
|
+
|
|
201
|
+
if (options.storybook === false) storybook = false;
|
|
202
|
+
if (options.hook === false) hook = false;
|
|
203
|
+
if (options.styles === false) styles = false;
|
|
204
|
+
if (options.tests) tests = true;
|
|
205
|
+
|
|
151
206
|
const features = [];
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (options.hook !== false) {
|
|
159
|
-
features.push('hook');
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (options.styles !== false) {
|
|
163
|
-
features.push('styles');
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Optional features (enabled by flag)
|
|
167
|
-
if (options.tests === true) {
|
|
168
|
-
features.push('tests');
|
|
169
|
-
}
|
|
170
|
-
|
|
207
|
+
if (storybook) features.push('storybook');
|
|
208
|
+
if (hook) features.push('hook');
|
|
209
|
+
if (styles) features.push('styles');
|
|
210
|
+
if (tests) features.push('tests');
|
|
211
|
+
|
|
171
212
|
return features;
|
|
172
213
|
}
|
|
173
214
|
|
|
174
215
|
/**
|
|
175
216
|
* Interactive mode handler
|
|
176
|
-
* Prompts user for component configuration
|
|
177
217
|
* @returns {Promise<Object>} Configuration object
|
|
178
218
|
*/
|
|
179
219
|
async function promptInteractive() {
|
|
180
220
|
logger.info('🎨 Interactive Component Generator');
|
|
181
|
-
|
|
221
|
+
|
|
222
|
+
const cwd = process.cwd();
|
|
223
|
+
const loaded = await configLoader.load(cwd);
|
|
224
|
+
const gen = loaded.generator || {};
|
|
225
|
+
|
|
226
|
+
const framework = await detectFramework(cwd, {});
|
|
227
|
+
const defaultComplexity = resolveDefaultComplexity(framework);
|
|
228
|
+
const complexityChoices =
|
|
229
|
+
framework === 'next'
|
|
230
|
+
? [
|
|
231
|
+
{ name: 'simple', value: 'simple' },
|
|
232
|
+
{ name: 'client', value: 'client' },
|
|
233
|
+
{ name: 'complex', value: 'complex' }
|
|
234
|
+
]
|
|
235
|
+
: framework === 'vanilla'
|
|
236
|
+
? [{ name: 'default', value: 'medium' }]
|
|
237
|
+
: Object.keys(COMPLEXITY_LEVELS).map((k) => ({
|
|
238
|
+
name: k.toLowerCase(),
|
|
239
|
+
value: k.toLowerCase()
|
|
240
|
+
}));
|
|
241
|
+
|
|
182
242
|
const answers = await inquirer.prompt([
|
|
183
243
|
{
|
|
184
244
|
type: 'input',
|
|
@@ -192,18 +252,15 @@ async function promptInteractive() {
|
|
|
192
252
|
{
|
|
193
253
|
type: 'list',
|
|
194
254
|
name: 'complexity',
|
|
195
|
-
message:
|
|
196
|
-
choices:
|
|
197
|
-
|
|
198
|
-
value: k.toLowerCase()
|
|
199
|
-
})),
|
|
200
|
-
default: 'medium'
|
|
255
|
+
message: `Complexity level (detected: ${framework}):`,
|
|
256
|
+
choices: complexityChoices,
|
|
257
|
+
default: defaultComplexity
|
|
201
258
|
},
|
|
202
259
|
{
|
|
203
260
|
type: 'checkbox',
|
|
204
261
|
name: 'features',
|
|
205
262
|
message: 'Select features:',
|
|
206
|
-
choices: Object.keys(COMPONENT_FEATURES).map(k => ({
|
|
263
|
+
choices: Object.keys(COMPONENT_FEATURES).map((k) => ({
|
|
207
264
|
name: `${k.toLowerCase()}${COMPONENT_FEATURES[k].default ? ' (default)' : ''}`,
|
|
208
265
|
value: COMPONENT_FEATURES[k].name,
|
|
209
266
|
checked: COMPONENT_FEATURES[k].default
|
|
@@ -213,6 +270,11 @@ async function promptInteractive() {
|
|
|
213
270
|
|
|
214
271
|
return {
|
|
215
272
|
...answers,
|
|
216
|
-
outputPath: './src/components'
|
|
273
|
+
outputPath: gen.outputPath || './src/components',
|
|
274
|
+
framework: undefined,
|
|
275
|
+
complexity: answers.complexity,
|
|
276
|
+
prefix: loaded.prefix || 'atomix',
|
|
277
|
+
storybookCssImport: gen.storybookCssImport,
|
|
278
|
+
hookOutputDir: gen.hookOutputDir
|
|
217
279
|
};
|
|
218
280
|
}
|