@shohojdhara/atomix 0.4.7 → 0.4.9
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 +58 -1
- package/dist/atomix.css +172 -157
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +4 -4
- package/dist/atomix.min.css.map +1 -1
- package/dist/charts.d.ts +33 -0
- package/dist/charts.js +1274 -164
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +33 -10
- package/dist/core.js +1099 -83
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +33 -0
- package/dist/forms.js +2106 -1050
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +42 -1
- package/dist/heavy.js +1663 -638
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +442 -270
- package/dist/index.esm.js +1947 -680
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1982 -712
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +6 -3
- package/scripts/atomix-cli.js +136 -1827
- package/scripts/cli/__tests__/basic.test.js +3 -2
- package/scripts/cli/__tests__/clean.test.js +278 -0
- package/scripts/cli/__tests__/component-validator.test.js +433 -0
- package/scripts/cli/__tests__/generator.test.js +613 -0
- package/scripts/cli/__tests__/glass-motion.test.js +256 -0
- package/scripts/cli/__tests__/integration.test.js +719 -108
- package/scripts/cli/__tests__/migrate.test.js +74 -0
- package/scripts/cli/__tests__/security.test.js +206 -0
- package/scripts/cli/__tests__/test-setup.js +3 -1
- package/scripts/cli/__tests__/theme-bridge.test.js +507 -0
- package/scripts/cli/__tests__/token-provider.test.js +361 -0
- package/scripts/cli/__tests__/utils.test.js +5 -5
- package/scripts/cli/commands/benchmark.js +105 -0
- package/scripts/cli/commands/build-theme.js +115 -0
- package/scripts/cli/commands/clean.js +109 -0
- package/scripts/cli/commands/doctor.js +88 -0
- package/scripts/cli/commands/generate.js +218 -0
- package/scripts/cli/commands/init.js +73 -0
- package/scripts/cli/commands/migrate.js +106 -0
- package/scripts/cli/commands/sync-tokens.js +206 -0
- package/scripts/cli/commands/theme-bridge.js +248 -0
- package/scripts/cli/commands/tokens.js +157 -0
- package/scripts/cli/commands/validate.js +194 -0
- package/scripts/cli/internal/ai-engine.js +156 -0
- package/scripts/cli/internal/compiler.js +114 -0
- package/scripts/cli/internal/component-validator.js +443 -0
- package/scripts/cli/internal/config-loader.js +162 -0
- package/scripts/cli/internal/filesystem.js +158 -0
- package/scripts/cli/internal/generator.js +430 -0
- package/scripts/cli/internal/glass-generator.js +398 -0
- package/scripts/cli/internal/hook-generator.js +369 -0
- package/scripts/cli/internal/hooks.js +61 -0
- package/scripts/cli/internal/itcss-generator.js +565 -0
- package/scripts/cli/internal/motion-generator.js +679 -0
- package/scripts/cli/internal/template-engine.js +301 -0
- package/scripts/cli/internal/theme-bridge.js +664 -0
- package/scripts/cli/internal/tokens/engine.js +122 -0
- package/scripts/cli/internal/tokens/provider.js +34 -0
- package/scripts/cli/internal/tokens/providers/figma.js +50 -0
- package/scripts/cli/internal/tokens/providers/style-dictionary.js +48 -0
- package/scripts/cli/internal/tokens/providers/w3c.js +48 -0
- package/scripts/cli/internal/tokens/token-provider.js +443 -0
- package/scripts/cli/internal/tokens/token-validator.js +513 -0
- package/scripts/cli/internal/validator.js +276 -0
- package/scripts/cli/internal/wizard.js +115 -0
- package/scripts/cli/mappings.js +23 -0
- package/scripts/cli/migration-tools.js +164 -94
- package/scripts/cli/plugins/style-dictionary.js +46 -0
- package/scripts/cli/templates/README.md +525 -95
- package/scripts/cli/templates/common-templates.js +40 -14
- package/scripts/cli/templates/components/react-component.ts +282 -0
- package/scripts/cli/templates/config/project-config.ts +112 -0
- package/scripts/cli/templates/hooks/use-component.ts +477 -0
- package/scripts/cli/templates/index.js +19 -4
- package/scripts/cli/templates/index.ts +171 -0
- package/scripts/cli/templates/next-templates.js +72 -0
- package/scripts/cli/templates/react-templates.js +70 -126
- package/scripts/cli/templates/scss-templates.js +35 -35
- package/scripts/cli/templates/stories/storybook-story.ts +241 -0
- package/scripts/cli/templates/styles/scss-component.ts +255 -0
- package/scripts/cli/templates/tests/vitest-test.ts +229 -0
- package/scripts/cli/templates/token-templates.js +337 -1
- package/scripts/cli/templates/tokens/token-generators.ts +1088 -0
- package/scripts/cli/templates/types/component-types.ts +145 -0
- package/scripts/cli/templates/utils/testing-utils.ts +144 -0
- package/scripts/cli/templates/vanilla-templates.js +39 -0
- package/scripts/cli/token-manager.js +8 -2
- package/scripts/cli/utils/cache-manager.js +240 -0
- package/scripts/cli/utils/detector.js +46 -0
- package/scripts/cli/utils/diagnostics.js +289 -0
- package/scripts/cli/utils/error.js +89 -0
- package/scripts/cli/utils/helpers.js +67 -0
- package/scripts/cli/utils/logger.js +75 -0
- package/scripts/cli/utils/security.js +302 -0
- package/scripts/cli/utils/telemetry.js +115 -0
- package/scripts/cli/utils/validation.js +37 -0
- package/scripts/cli/utils.js +28 -341
- package/src/components/Accordion/Accordion.stories.tsx +0 -18
- package/src/components/Accordion/Accordion.test.tsx +0 -17
- package/src/components/Accordion/Accordion.tsx +0 -4
- package/src/components/AtomixGlass/AtomixGlass.test.tsx +37 -3
- package/src/components/AtomixGlass/AtomixGlass.tsx +143 -31
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +129 -31
- package/src/components/AtomixGlass/PerformanceDashboard.tsx +219 -0
- package/src/components/AtomixGlass/README.md +25 -10
- package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +216 -0
- package/src/components/AtomixGlass/animation-system.ts +578 -0
- package/src/components/AtomixGlass/shader-utils.ts +4 -1
- package/src/components/AtomixGlass/stories/Overview.stories.tsx +157 -6
- package/src/components/AtomixGlass/stories/Phase1-Animation.stories.tsx +653 -0
- package/src/components/AtomixGlass/stories/Phase1-Test.stories.tsx +95 -0
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +51 -51
- package/src/components/AtomixGlass/stories/shared-components.tsx +6 -0
- package/src/components/Avatar/Avatar.tsx +1 -1
- package/src/components/Button/Button.stories.disabled-link.tsx +10 -0
- package/src/components/Button/Button.stories.tsx +10 -0
- package/src/components/Button/Button.test.tsx +16 -11
- package/src/components/Button/Button.tsx +4 -4
- package/src/components/Card/Card.tsx +1 -1
- package/src/components/Dropdown/Dropdown.tsx +12 -12
- package/src/components/Form/Select.tsx +62 -3
- package/src/components/Modal/Modal.tsx +14 -3
- package/src/components/Navigation/Navbar/Navbar.tsx +44 -0
- package/src/components/Slider/Slider.stories.tsx +3 -3
- package/src/components/Slider/Slider.tsx +38 -0
- package/src/components/Steps/Steps.tsx +3 -3
- package/src/components/Tabs/Tabs.tsx +77 -8
- package/src/components/Testimonial/Testimonial.tsx +1 -1
- package/src/components/TypedButton/TypedButton.stories.tsx +59 -0
- package/src/components/TypedButton/TypedButton.tsx +39 -0
- package/src/components/TypedButton/index.ts +2 -0
- package/src/components/VideoPlayer/VideoPlayer.tsx +11 -4
- package/src/lib/composables/index.ts +4 -7
- package/src/lib/composables/types.ts +45 -0
- package/src/lib/composables/useAccordion.ts +0 -7
- package/src/lib/composables/useAtomixGlass.ts +148 -6
- package/src/lib/composables/useAtomixGlassStyles.ts +9 -7
- package/src/lib/composables/useChartExport.ts +3 -13
- package/src/lib/composables/useDropdown.ts +66 -0
- package/src/lib/composables/useFocusTrap.ts +80 -0
- package/src/lib/composables/usePerformanceMonitor.ts +448 -0
- package/src/lib/composables/useResponsiveGlass.presets.ts +192 -0
- package/src/lib/composables/useResponsiveGlass.ts +441 -0
- package/src/lib/composables/useTooltip.ts +16 -0
- package/src/lib/composables/useTypedButton.ts +66 -0
- package/src/lib/config/index.ts +62 -5
- package/src/lib/constants/components.ts +62 -7
- package/src/lib/theme/devtools/__tests__/useHistory.test.tsx +150 -0
- package/src/lib/theme/tokens/centralized-tokens.ts +120 -0
- package/src/lib/theme/utils/__tests__/domUtils.test.ts +101 -0
- package/src/lib/types/components.ts +37 -11
- package/src/lib/types/glass.ts +35 -0
- package/src/lib/types/index.ts +1 -0
- package/src/lib/utils/displacement-generator.ts +1 -1
- package/src/styles/01-settings/_settings.testtypecheck.scss +53 -0
- package/src/styles/01-settings/_settings.typedbutton.scss +53 -0
- package/src/styles/06-components/_components.atomix-glass.scss +17 -21
- package/src/styles/06-components/_components.edge-panel.scss +1 -5
- package/src/styles/06-components/_components.modal.scss +1 -4
- package/src/styles/06-components/_components.navbar.scss +1 -1
- package/src/styles/06-components/_components.testbutton.scss +212 -0
- package/src/styles/06-components/_components.testtypecheck.scss +212 -0
- package/src/styles/06-components/_components.tooltip.scss +9 -5
- package/src/styles/06-components/_components.typedbutton.scss +212 -0
- package/src/styles/99-utilities/_index.scss +1 -0
- package/src/styles/99-utilities/_utilities.text.scss +1 -1
- package/src/styles/99-utilities/_utilities.touch-target.scss +36 -0
- package/scripts/cli/component-generator.js +0 -564
- package/scripts/cli/interactive-init.js +0 -357
- package/src/styles/06-components/old.chart.styles.scss +0 -2788
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atomix CLI Diagnostics Utility
|
|
3
|
+
* Shared logic for environment and project health checks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
import { existsSync, accessSync, constants, readdirSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { readFile } from 'fs/promises';
|
|
10
|
+
import { logger } from './logger.js';
|
|
11
|
+
import { configLoader } from '../internal/config-loader.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check Node.js and NPM versions
|
|
15
|
+
*/
|
|
16
|
+
export async function checkRuntimes() {
|
|
17
|
+
const results = [];
|
|
18
|
+
|
|
19
|
+
// Node.js version check
|
|
20
|
+
const nodeVersion = process.version;
|
|
21
|
+
const minNodeVersion = 'v18.0.0';
|
|
22
|
+
const nodeOk = parseInt(nodeVersion.slice(1)) >= 18;
|
|
23
|
+
|
|
24
|
+
results.push({
|
|
25
|
+
name: 'Node.js',
|
|
26
|
+
status: nodeOk ? 'pass' : 'fail',
|
|
27
|
+
message: `Current: ${nodeVersion}, Required: >=${minNodeVersion}`,
|
|
28
|
+
suggestion: nodeOk ? null : 'Please upgrade Node.js to version 18 or higher.'
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// NPM version check
|
|
32
|
+
try {
|
|
33
|
+
const npmVersion = execSync('npm -v').toString().trim();
|
|
34
|
+
const npmOk = parseInt(npmVersion.split('.')[0]) >= 8;
|
|
35
|
+
results.push({
|
|
36
|
+
name: 'NPM',
|
|
37
|
+
status: npmOk ? 'pass' : 'fail',
|
|
38
|
+
message: `Current: ${npmVersion}, Required: >=8.0.0`,
|
|
39
|
+
suggestion: npmOk ? null : 'Please upgrade NPM to version 8 or higher.'
|
|
40
|
+
});
|
|
41
|
+
} catch (error) {
|
|
42
|
+
results.push({
|
|
43
|
+
name: 'NPM',
|
|
44
|
+
status: 'fail',
|
|
45
|
+
message: 'NPM not found in system path',
|
|
46
|
+
suggestion: 'Please install NPM (comes with Node.js).'
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return results;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Check CLI plugins
|
|
55
|
+
*/
|
|
56
|
+
export async function checkPlugins() {
|
|
57
|
+
const results = [];
|
|
58
|
+
const config = await configLoader.load();
|
|
59
|
+
|
|
60
|
+
if (!config.plugins || config.plugins.length === 0) {
|
|
61
|
+
results.push({
|
|
62
|
+
name: 'Plugins',
|
|
63
|
+
status: 'pass',
|
|
64
|
+
message: 'No plugins registered',
|
|
65
|
+
suggestion: null
|
|
66
|
+
});
|
|
67
|
+
return results;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (const pluginEntry of config.plugins) {
|
|
71
|
+
const pluginName = typeof pluginEntry === 'string' ? pluginEntry : pluginEntry.name;
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
// In a real scenario, we'd check if the module is resolvable
|
|
75
|
+
// For now, we'll just report their presence
|
|
76
|
+
results.push({
|
|
77
|
+
name: `Plugin: ${pluginName}`,
|
|
78
|
+
status: 'pass',
|
|
79
|
+
message: 'Registered in configuration',
|
|
80
|
+
suggestion: null
|
|
81
|
+
});
|
|
82
|
+
} catch (error) {
|
|
83
|
+
results.push({
|
|
84
|
+
name: `Plugin: ${pluginName}`,
|
|
85
|
+
status: 'fail',
|
|
86
|
+
message: `Error checking plugin: ${error.message}`,
|
|
87
|
+
suggestion: 'Verify the plugin path or package name.'
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return results;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if running in the Atomix monorepo (internal development)
|
|
97
|
+
*/
|
|
98
|
+
async function isAtomixMonorepo(projectRoot) {
|
|
99
|
+
try {
|
|
100
|
+
const packageJsonPath = join(projectRoot, 'package.json');
|
|
101
|
+
if (!existsSync(packageJsonPath)) return false;
|
|
102
|
+
|
|
103
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'));
|
|
104
|
+
// Check if this is the Atomix monorepo by package name or structure
|
|
105
|
+
const packageName = packageJson.name || '';
|
|
106
|
+
const isAtomixPackage = packageName.includes('atomix') &&
|
|
107
|
+
(packageName.startsWith('@shohojdhara/') ||
|
|
108
|
+
packageName.startsWith('@atomix/') ||
|
|
109
|
+
packageName === 'atomix');
|
|
110
|
+
|
|
111
|
+
return isAtomixPackage || existsSync(join(projectRoot, 'packages'));
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Check project structure
|
|
119
|
+
*/
|
|
120
|
+
export async function checkProjectStructure(projectRoot = process.cwd()) {
|
|
121
|
+
const results = [];
|
|
122
|
+
const requiredDirs = ['src'];
|
|
123
|
+
const recommendedDirs = ['themes', 'docs'];
|
|
124
|
+
const internalDirs = ['scripts/cli']; // Only required for Atomix monorepo development
|
|
125
|
+
|
|
126
|
+
const isMonorepo = await isAtomixMonorepo(projectRoot);
|
|
127
|
+
|
|
128
|
+
// Check required directories (always checked)
|
|
129
|
+
for (const dir of requiredDirs) {
|
|
130
|
+
const dirPath = join(projectRoot, dir);
|
|
131
|
+
const exists = existsSync(dirPath);
|
|
132
|
+
results.push({
|
|
133
|
+
name: `Directory: ${dir}`,
|
|
134
|
+
status: exists ? 'pass' : 'fail',
|
|
135
|
+
message: exists ? 'Exists' : 'Missing',
|
|
136
|
+
suggestion: exists ? null : `Create the '${dir}' directory in your project root.`
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Check internal directories (only for Atomix monorepo)
|
|
141
|
+
for (const dir of internalDirs) {
|
|
142
|
+
const dirPath = join(projectRoot, dir);
|
|
143
|
+
const exists = existsSync(dirPath);
|
|
144
|
+
|
|
145
|
+
if (isMonorepo) {
|
|
146
|
+
// In monorepo, this is required
|
|
147
|
+
results.push({
|
|
148
|
+
name: `Directory: ${dir}`,
|
|
149
|
+
status: exists ? 'pass' : 'fail',
|
|
150
|
+
message: exists ? 'Exists' : 'Missing',
|
|
151
|
+
suggestion: exists ? null : `Create the '${dir}' directory in your project root.`
|
|
152
|
+
});
|
|
153
|
+
} else {
|
|
154
|
+
// In external projects, skip this check entirely (don't show as fail/warn/pass)
|
|
155
|
+
// This directory is only needed for internal Atomix development
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check recommended directories
|
|
160
|
+
for (const dir of recommendedDirs) {
|
|
161
|
+
const dirPath = join(projectRoot, dir);
|
|
162
|
+
const exists = existsSync(dirPath);
|
|
163
|
+
results.push({
|
|
164
|
+
name: `Directory: ${dir} (Recommended)`,
|
|
165
|
+
status: exists ? 'pass' : 'warn',
|
|
166
|
+
message: exists ? 'Exists' : 'Missing',
|
|
167
|
+
suggestion: exists ? null : `Consider creating a '${dir}' directory for better organization.`
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return results;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Check configuration file
|
|
176
|
+
*/
|
|
177
|
+
export async function checkConfig(projectRoot = process.cwd()) {
|
|
178
|
+
const results = [];
|
|
179
|
+
const configFiles = ['atomix.config.ts', 'atomix.config.js'];
|
|
180
|
+
let found = false;
|
|
181
|
+
|
|
182
|
+
for (const file of configFiles) {
|
|
183
|
+
const configPath = join(projectRoot, file);
|
|
184
|
+
if (existsSync(configPath)) {
|
|
185
|
+
found = true;
|
|
186
|
+
try {
|
|
187
|
+
// Simple syntax check by reading it
|
|
188
|
+
await readFile(configPath, 'utf8');
|
|
189
|
+
results.push({
|
|
190
|
+
name: 'Configuration',
|
|
191
|
+
status: 'pass',
|
|
192
|
+
message: `Found ${file} and readable`,
|
|
193
|
+
suggestion: null
|
|
194
|
+
});
|
|
195
|
+
} catch (error) {
|
|
196
|
+
results.push({
|
|
197
|
+
name: 'Configuration',
|
|
198
|
+
status: 'fail',
|
|
199
|
+
message: `Error reading ${file}: ${error.message}`,
|
|
200
|
+
suggestion: 'Check file permissions and syntax.'
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!found) {
|
|
208
|
+
results.push({
|
|
209
|
+
name: 'Configuration',
|
|
210
|
+
status: 'warn',
|
|
211
|
+
message: 'No atomix.config.ts or atomix.config.js found',
|
|
212
|
+
suggestion: 'Run `atomix init` to create a default configuration.'
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return results;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Check design tokens (01-settings). CLI discovers tokens from src/styles/01-settings/_settings.*.scss
|
|
221
|
+
* and optionally from atomix.config tokenEngine / tokens entry.
|
|
222
|
+
*/
|
|
223
|
+
export async function checkTokens(projectRoot = process.cwd()) {
|
|
224
|
+
const results = [];
|
|
225
|
+
const settingsDir = join(projectRoot, 'src/styles/01-settings');
|
|
226
|
+
|
|
227
|
+
if (!existsSync(settingsDir)) {
|
|
228
|
+
results.push({
|
|
229
|
+
name: 'Tokens',
|
|
230
|
+
status: 'warn',
|
|
231
|
+
message: 'None configured (optional)',
|
|
232
|
+
suggestion: 'Add token files or a theme for design token support. Token discovery: src/styles/01-settings/_settings.*.scss. See Atomix token docs.'
|
|
233
|
+
});
|
|
234
|
+
return results;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const files = readdirSync(settingsDir);
|
|
239
|
+
const tokenFiles = files.filter((f) => f.startsWith('_settings.') && f.endsWith('.scss'));
|
|
240
|
+
const count = tokenFiles.length;
|
|
241
|
+
results.push({
|
|
242
|
+
name: 'Tokens',
|
|
243
|
+
status: 'pass',
|
|
244
|
+
message: count ? `Found ${count} token categor${count === 1 ? 'y' : 'ies'}` : 'None configured (optional)',
|
|
245
|
+
suggestion: count ? null : 'Add _settings.*.scss in src/styles/01-settings for design tokens.'
|
|
246
|
+
});
|
|
247
|
+
} catch (error) {
|
|
248
|
+
results.push({
|
|
249
|
+
name: 'Tokens',
|
|
250
|
+
status: 'warn',
|
|
251
|
+
message: `Could not read 01-settings: ${error.message}`,
|
|
252
|
+
suggestion: null
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return results;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Check directory permissions
|
|
261
|
+
*/
|
|
262
|
+
export async function checkPermissions(projectRoot = process.cwd()) {
|
|
263
|
+
const results = [];
|
|
264
|
+
const dirsToCheck = ['.', 'src'];
|
|
265
|
+
|
|
266
|
+
for (const dir of dirsToCheck) {
|
|
267
|
+
const dirPath = join(projectRoot, dir);
|
|
268
|
+
if (!existsSync(dirPath)) continue;
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
accessSync(dirPath, constants.R_OK | constants.W_OK);
|
|
272
|
+
results.push({
|
|
273
|
+
name: `Permissions: ${dir}`,
|
|
274
|
+
status: 'pass',
|
|
275
|
+
message: 'Read/Write access granted',
|
|
276
|
+
suggestion: null
|
|
277
|
+
});
|
|
278
|
+
} catch (error) {
|
|
279
|
+
results.push({
|
|
280
|
+
name: `Permissions: ${dir}`,
|
|
281
|
+
status: 'fail',
|
|
282
|
+
message: 'Insufficient permissions',
|
|
283
|
+
suggestion: `Grant read/write permissions to the '${dir}' directory.`
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return results;
|
|
289
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atomix CLI Error Class
|
|
3
|
+
* Standardized error handling with actionable suggestions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Common CLI Error Categories
|
|
8
|
+
*/
|
|
9
|
+
export const ErrorCategory = {
|
|
10
|
+
CONFIG: 'CONFIG_ERROR',
|
|
11
|
+
VALIDATION: 'VALIDATION_ERROR',
|
|
12
|
+
GENERATION: 'GENERATION_ERROR',
|
|
13
|
+
ENVIRONMENT: 'ENVIRONMENT_ERROR',
|
|
14
|
+
FILESYSTEM: 'FILESYSTEM_ERROR',
|
|
15
|
+
INVALID_PATH: 'INVALID_PATH'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Suggestions for common error codes
|
|
20
|
+
*/
|
|
21
|
+
const COMMON_SUGGESTIONS = {
|
|
22
|
+
[ErrorCategory.CONFIG]: [
|
|
23
|
+
'Run `atomix init` to create a new configuration file.',
|
|
24
|
+
'Check if `atomix.config.ts` has syntax errors.',
|
|
25
|
+
'Ensure you have exported the configuration correctly.'
|
|
26
|
+
],
|
|
27
|
+
[ErrorCategory.ENVIRONMENT]: [
|
|
28
|
+
'Run `atomix doctor` to check your environment health.',
|
|
29
|
+
'Ensure Node.js version is >=18.0.0.',
|
|
30
|
+
'Check if all peer dependencies are installed.'
|
|
31
|
+
],
|
|
32
|
+
[ErrorCategory.FILESYSTEM]: [
|
|
33
|
+
'Check if you have write permissions for the target directory.',
|
|
34
|
+
'Ensure the path provided is valid and within the project root.',
|
|
35
|
+
'Verify if the file exists and is not locked by another process.'
|
|
36
|
+
],
|
|
37
|
+
[ErrorCategory.INVALID_PATH]: [
|
|
38
|
+
'Source must be a directory. Pass the project root (e.g. . or ./my-app).',
|
|
39
|
+
'Do not pass a file path (e.g. package.json).'
|
|
40
|
+
]
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export class AtomixCLIError extends Error {
|
|
44
|
+
/**
|
|
45
|
+
* @param {string} message - Human-readable error message
|
|
46
|
+
* @param {string} code - Unique error code (e.g., ErrorCategory.CONFIG)
|
|
47
|
+
* @param {string[]} suggestions - Specific steps to resolve the issue
|
|
48
|
+
*/
|
|
49
|
+
constructor(message, code, suggestions = []) {
|
|
50
|
+
super(message);
|
|
51
|
+
this.name = 'AtomixCLIError';
|
|
52
|
+
this.code = code;
|
|
53
|
+
this.suggestions = suggestions.length > 0 ? suggestions : (COMMON_SUGGESTIONS[code] || []);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
import chalk from 'chalk';
|
|
58
|
+
import { telemetry } from './telemetry.js';
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Global CLI error handler
|
|
62
|
+
* @param {Error} error - The error object to handle
|
|
63
|
+
* @param {object} spinner - Optional ora spinner to stop
|
|
64
|
+
*/
|
|
65
|
+
export async function handleCLIError(error, spinner = null) {
|
|
66
|
+
if (spinner) {
|
|
67
|
+
spinner.fail('Operation failed');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Log failure to telemetry
|
|
71
|
+
await telemetry.stop(false, { error: error.message, code: error.code });
|
|
72
|
+
|
|
73
|
+
console.error(chalk.bold.red(`\n❌ ${error.message}`));
|
|
74
|
+
|
|
75
|
+
if (error instanceof AtomixCLIError && error.suggestions && error.suggestions.length > 0) {
|
|
76
|
+
console.log(chalk.yellow('\n💡 Suggestions:'));
|
|
77
|
+
error.suggestions.forEach((suggestion, index) => {
|
|
78
|
+
console.log(chalk.gray(` ${index + 1}. ${suggestion}`));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (process.env.ATOMIX_DEBUG === 'true' && error.stack) {
|
|
83
|
+
console.error(chalk.gray('\nStack trace:'));
|
|
84
|
+
console.error(chalk.gray(error.stack));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Single exit point for CLI failure; commands should throw, not call process.exit(1).
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atomix CLI Helper Utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { access } from 'fs/promises';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Sanitizes user input to prevent injection attacks
|
|
9
|
+
*/
|
|
10
|
+
export function sanitizeInput(input) {
|
|
11
|
+
if (typeof input !== 'string') return String(input);
|
|
12
|
+
return input.replace(/[;&|`$<>\\"']/g, '').replace(/\0/g, '').trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Checks Node.js version compatibility
|
|
17
|
+
*/
|
|
18
|
+
export function checkNodeVersion(requiredVersion = '18.0.0') {
|
|
19
|
+
const currentVersion = process.version.substring(1);
|
|
20
|
+
const current = currentVersion.split('.').map(Number);
|
|
21
|
+
const required = requiredVersion.split('.').map(Number);
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < required.length; i++) {
|
|
24
|
+
if (current[i] < required[i]) return { compatible: false, current: currentVersion, required: requiredVersion };
|
|
25
|
+
if (current[i] > required[i]) break;
|
|
26
|
+
}
|
|
27
|
+
return { compatible: true, current: currentVersion, required: requiredVersion };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Formats file size
|
|
32
|
+
*/
|
|
33
|
+
export function formatFileSize(bytes) {
|
|
34
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
35
|
+
if (bytes === 0) return '0 B';
|
|
36
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
37
|
+
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${sizes[i]}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Checks if running in CI environment
|
|
42
|
+
*/
|
|
43
|
+
export function isCI() {
|
|
44
|
+
return !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.JENKINS_URL);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Checks if CLI should run in non-interactive mode (no prompts)
|
|
49
|
+
* Use for CI/scripts: set ATOMIX_NON_INTERACTIVE=1 or run in CI
|
|
50
|
+
*/
|
|
51
|
+
export function isNonInteractive() {
|
|
52
|
+
return !!(process.env.ATOMIX_NON_INTERACTIVE === '1' || process.env.ATOMIX_NON_INTERACTIVE === 'true' || isCI());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if a file exists at the given path
|
|
57
|
+
* @param {string} path - File path to check
|
|
58
|
+
* @returns {Promise<boolean>}
|
|
59
|
+
*/
|
|
60
|
+
export async function fileExists(path) {
|
|
61
|
+
try {
|
|
62
|
+
await access(path);
|
|
63
|
+
return true;
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atomix CLI Logger
|
|
3
|
+
* Standardized logging with support for spinners and debug levels
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import boxen from 'boxen';
|
|
9
|
+
|
|
10
|
+
const DEBUG = process.env.ATOMIX_DEBUG === 'true';
|
|
11
|
+
|
|
12
|
+
export const logger = {
|
|
13
|
+
/**
|
|
14
|
+
* Log a debug message (only visible in debug mode)
|
|
15
|
+
*/
|
|
16
|
+
debug: (message, data = null) => {
|
|
17
|
+
if (DEBUG) {
|
|
18
|
+
console.log(chalk.gray(`[DEBUG] ${message}`));
|
|
19
|
+
if (data) {
|
|
20
|
+
console.log(chalk.gray(JSON.stringify(data, null, 2)));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Log an info message
|
|
27
|
+
*/
|
|
28
|
+
info: (message) => {
|
|
29
|
+
console.log(chalk.blue(message));
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Log a success message
|
|
34
|
+
*/
|
|
35
|
+
success: (message) => {
|
|
36
|
+
console.log(chalk.green(`✓ ${message}`));
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Log a warning message
|
|
41
|
+
*/
|
|
42
|
+
warn: (message) => {
|
|
43
|
+
console.log(chalk.yellow(`! ${message}`));
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Log an error message
|
|
48
|
+
*/
|
|
49
|
+
error: (message) => {
|
|
50
|
+
console.error(chalk.bold.red(`\n❌ ${message}`));
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Create and return a spinner
|
|
55
|
+
*/
|
|
56
|
+
spinner: (text) => {
|
|
57
|
+
return ora({
|
|
58
|
+
text,
|
|
59
|
+
color: 'cyan'
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Create a boxed message
|
|
65
|
+
*/
|
|
66
|
+
box: (message, options = {}) => {
|
|
67
|
+
console.log(boxen(message, {
|
|
68
|
+
padding: 1,
|
|
69
|
+
margin: 1,
|
|
70
|
+
borderStyle: 'round',
|
|
71
|
+
borderColor: 'cyan',
|
|
72
|
+
...options
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
};
|