@shohojdhara/atomix 0.5.1 → 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 +138 -17
- 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 +23 -23
- package/dist/charts.js +40 -37
- 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 +2 -2
- package/dist/core.js +111 -50
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +3 -6
- package/dist/forms.js +2 -2
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +1 -1
- package/dist/heavy.js +173 -111
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +98 -65
- package/dist/index.esm.js +427 -422
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +394 -391
- 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 +14 -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 +3 -0
- 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/AtomixGlassContainer.tsx +1 -1
- package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +219 -0
- package/src/components/AtomixGlass/glass-utils.ts +1 -1
- 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 +3 -3
- 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/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/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 +4 -4
- 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/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": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"jsdelivr": "dist/atomix.umd.min.js",
|
|
20
20
|
"browser": "dist/atomix.umd.min.js",
|
|
21
21
|
"bin": {
|
|
22
|
-
"atomix": "
|
|
22
|
+
"atomix": "scripts/atomix-cli.js"
|
|
23
23
|
},
|
|
24
24
|
"exports": {
|
|
25
25
|
".": {
|
|
@@ -125,6 +125,11 @@
|
|
|
125
125
|
"require": "./build-tools/index.js",
|
|
126
126
|
"default": "./build-tools/index.js"
|
|
127
127
|
},
|
|
128
|
+
"./config": {
|
|
129
|
+
"types": "./dist/config.d.ts",
|
|
130
|
+
"import": "./dist/config.js",
|
|
131
|
+
"default": "./dist/config.js"
|
|
132
|
+
},
|
|
128
133
|
"./package.json": "./package.json"
|
|
129
134
|
},
|
|
130
135
|
"dependencies": {
|
|
@@ -180,8 +185,8 @@
|
|
|
180
185
|
"@testing-library/react": "^14.0.0",
|
|
181
186
|
"@testing-library/user-event": "^14.0.0",
|
|
182
187
|
"@types/node": "^20.0.0",
|
|
183
|
-
"@types/react": "^
|
|
184
|
-
"@types/react-dom": "^
|
|
188
|
+
"@types/react": "^19.0.0",
|
|
189
|
+
"@types/react-dom": "^19.0.0",
|
|
185
190
|
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
186
191
|
"@typescript-eslint/parser": "^6.0.0",
|
|
187
192
|
"@vitejs/plugin-react": "^4.0.0",
|
|
@@ -202,8 +207,8 @@
|
|
|
202
207
|
"postcss-import": "^15.0.0",
|
|
203
208
|
"postcss-preset-env": "^9.0.0",
|
|
204
209
|
"prettier": "^3.0.0",
|
|
205
|
-
"react": "^
|
|
206
|
-
"react-dom": "^
|
|
210
|
+
"react": "^19.0.0",
|
|
211
|
+
"react-dom": "^19.0.0",
|
|
207
212
|
"rimraf": "^5.0.0",
|
|
208
213
|
"rollup": "^3.0.0",
|
|
209
214
|
"rollup-plugin-dts": "^6.0.0",
|
|
@@ -216,7 +221,7 @@
|
|
|
216
221
|
"ts-node": "^10.9.0",
|
|
217
222
|
"tslib": "^2.6.0",
|
|
218
223
|
"tsx": "^4.21.0",
|
|
219
|
-
"typescript": "^
|
|
224
|
+
"typescript": "^6.0.2",
|
|
220
225
|
"vite": "^4.0.0",
|
|
221
226
|
"vitest": "^0.34.0"
|
|
222
227
|
},
|
|
@@ -305,8 +310,8 @@
|
|
|
305
310
|
"homepage": "https://github.com/Shohojdhara/atomix#readme",
|
|
306
311
|
"peerDependencies": {
|
|
307
312
|
"@phosphor-icons/react": "2.1.10",
|
|
308
|
-
"react": "
|
|
309
|
-
"react-dom": "
|
|
313
|
+
"react": ">=18.0.0",
|
|
314
|
+
"react-dom": ">=18.0.0"
|
|
310
315
|
},
|
|
311
316
|
"engines": {
|
|
312
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
|
+
});
|
|
@@ -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
|
}
|
|
@@ -17,9 +17,15 @@ export class AIEngine {
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Generate component code based on prompt
|
|
20
|
+
* @param {string} name - Component name
|
|
21
|
+
* @param {string} prompt - User prompt for AI
|
|
22
|
+
* @param {Object} options - Override options (provider, model, temperature, maxTokens)
|
|
20
23
|
*/
|
|
21
|
-
async generateComponent(name, prompt) {
|
|
22
|
-
const provider = this.config.provider || 'openai';
|
|
24
|
+
async generateComponent(name, prompt, options = {}) {
|
|
25
|
+
const provider = options.provider || this.config.provider || 'openai';
|
|
26
|
+
const model = options.model || this.config.model;
|
|
27
|
+
const temperature = options.temperature ?? this.config.temperature ?? 0.7;
|
|
28
|
+
const maxTokens = options.maxTokens || this.config.maxTokens || 4000;
|
|
23
29
|
const apiKey = this.config.apiKey || process.env.ATOMIX_AI_API_KEY;
|
|
24
30
|
|
|
25
31
|
if (!apiKey) {
|
|
@@ -31,9 +37,9 @@ export class AIEngine {
|
|
|
31
37
|
try {
|
|
32
38
|
let response;
|
|
33
39
|
if (provider === 'openai') {
|
|
34
|
-
response = await this.callOpenAI(name, prompt, apiKey);
|
|
40
|
+
response = await this.callOpenAI(name, prompt, apiKey, model, temperature, maxTokens);
|
|
35
41
|
} else if (provider === 'anthropic') {
|
|
36
|
-
response = await this.callAnthropic(name, prompt, apiKey);
|
|
42
|
+
response = await this.callAnthropic(name, prompt, apiKey, model, temperature, maxTokens);
|
|
37
43
|
} else {
|
|
38
44
|
throw new Error(`Unsupported AI provider: ${provider}`);
|
|
39
45
|
}
|
|
@@ -47,9 +53,15 @@ export class AIEngine {
|
|
|
47
53
|
|
|
48
54
|
/**
|
|
49
55
|
* Call OpenAI API
|
|
56
|
+
* @param {string} name - Component name
|
|
57
|
+
* @param {string} prompt - User prompt
|
|
58
|
+
* @param {string} apiKey - API key
|
|
59
|
+
* @param {string} [modelOverride] - Model override
|
|
60
|
+
* @param {number} [temperature] - Temperature (0.0-1.0)
|
|
61
|
+
* @param {number} [maxTokens] - Max tokens
|
|
50
62
|
*/
|
|
51
|
-
async callOpenAI(name, prompt, apiKey) {
|
|
52
|
-
const model =
|
|
63
|
+
async callOpenAI(name, prompt, apiKey, modelOverride, temperature, maxTokens) {
|
|
64
|
+
const model = modelOverride || 'gpt-4';
|
|
53
65
|
const systemPrompt = this.getSystemPrompt(name);
|
|
54
66
|
|
|
55
67
|
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
@@ -64,7 +76,8 @@ export class AIEngine {
|
|
|
64
76
|
{ role: 'system', content: systemPrompt },
|
|
65
77
|
{ role: 'user', content: prompt }
|
|
66
78
|
],
|
|
67
|
-
temperature:
|
|
79
|
+
temperature: temperature,
|
|
80
|
+
max_tokens: maxTokens
|
|
68
81
|
})
|
|
69
82
|
});
|
|
70
83
|
|
|
@@ -79,9 +92,15 @@ export class AIEngine {
|
|
|
79
92
|
|
|
80
93
|
/**
|
|
81
94
|
* Call Anthropic API
|
|
95
|
+
* @param {string} name - Component name
|
|
96
|
+
* @param {string} prompt - User prompt
|
|
97
|
+
* @param {string} apiKey - API key
|
|
98
|
+
* @param {string} [modelOverride] - Model override
|
|
99
|
+
* @param {number} [temperature] - Temperature (0.0-1.0)
|
|
100
|
+
* @param {number} [maxTokens] - Max tokens
|
|
82
101
|
*/
|
|
83
|
-
async callAnthropic(name, prompt, apiKey) {
|
|
84
|
-
const model =
|
|
102
|
+
async callAnthropic(name, prompt, apiKey, modelOverride, temperature, maxTokens) {
|
|
103
|
+
const model = modelOverride || 'claude-3-sonnet-20240229';
|
|
85
104
|
const systemPrompt = this.getSystemPrompt(name);
|
|
86
105
|
|
|
87
106
|
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
@@ -93,7 +112,8 @@ export class AIEngine {
|
|
|
93
112
|
},
|
|
94
113
|
body: JSON.stringify({
|
|
95
114
|
model: model,
|
|
96
|
-
max_tokens:
|
|
115
|
+
max_tokens: maxTokens,
|
|
116
|
+
temperature: temperature,
|
|
97
117
|
system: systemPrompt,
|
|
98
118
|
messages: [
|
|
99
119
|
{ role: 'user', content: prompt }
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-aware default complexity and normalization for component templates.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** @typedef {'react' | 'next' | 'vanilla'} Framework */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Default complexity when the user does not pass --complexity (framework-specific).
|
|
9
|
+
* @param {Framework} framework
|
|
10
|
+
* @returns {'simple' | 'medium' | 'complex' | 'client'}
|
|
11
|
+
*/
|
|
12
|
+
export function resolveDefaultComplexity(framework) {
|
|
13
|
+
const f = String(framework).toLowerCase();
|
|
14
|
+
if (f === 'next') return 'simple';
|
|
15
|
+
if (f === 'vanilla') return 'medium';
|
|
16
|
+
return 'medium';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Map requested complexity to a value valid for selectTemplate (Next has no "medium").
|
|
21
|
+
* @param {Framework} framework
|
|
22
|
+
* @param {string} [complexity]
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
export function normalizeComplexityForFramework(framework, complexity) {
|
|
26
|
+
const f = String(framework).toLowerCase();
|
|
27
|
+
const c = (complexity || resolveDefaultComplexity(f)).toLowerCase();
|
|
28
|
+
|
|
29
|
+
if (f === 'vanilla') {
|
|
30
|
+
return c;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (f === 'next') {
|
|
34
|
+
const valid = ['simple', 'client', 'complex'];
|
|
35
|
+
if (valid.includes(c)) return c;
|
|
36
|
+
if (c === 'medium') return 'simple';
|
|
37
|
+
return 'simple';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (f === 'react') {
|
|
41
|
+
const valid = ['simple', 'medium', 'complex'];
|
|
42
|
+
if (valid.includes(c)) return c;
|
|
43
|
+
return 'medium';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return c;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolve effective complexity: use explicit value when provided, else framework default.
|
|
51
|
+
* @param {Framework} framework
|
|
52
|
+
* @param {string|undefined} explicitComplexity - undefined when CLI did not pass --complexity
|
|
53
|
+
*/
|
|
54
|
+
export function resolveEffectiveComplexity(framework, explicitComplexity) {
|
|
55
|
+
const f = String(framework).toLowerCase();
|
|
56
|
+
if (explicitComplexity === undefined || explicitComplexity === '') {
|
|
57
|
+
return normalizeComplexityForFramework(f, resolveDefaultComplexity(f));
|
|
58
|
+
}
|
|
59
|
+
return normalizeComplexityForFramework(f, explicitComplexity);
|
|
60
|
+
}
|