@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.
Files changed (176) hide show
  1. package/atomix.config.ts +58 -1
  2. package/dist/atomix.css +172 -157
  3. package/dist/atomix.css.map +1 -1
  4. package/dist/atomix.min.css +4 -4
  5. package/dist/atomix.min.css.map +1 -1
  6. package/dist/charts.d.ts +33 -0
  7. package/dist/charts.js +1274 -164
  8. package/dist/charts.js.map +1 -1
  9. package/dist/core.d.ts +33 -10
  10. package/dist/core.js +1099 -83
  11. package/dist/core.js.map +1 -1
  12. package/dist/forms.d.ts +33 -0
  13. package/dist/forms.js +2106 -1050
  14. package/dist/forms.js.map +1 -1
  15. package/dist/heavy.d.ts +42 -1
  16. package/dist/heavy.js +1663 -638
  17. package/dist/heavy.js.map +1 -1
  18. package/dist/index.d.ts +442 -270
  19. package/dist/index.esm.js +1947 -680
  20. package/dist/index.esm.js.map +1 -1
  21. package/dist/index.js +1982 -712
  22. package/dist/index.js.map +1 -1
  23. package/dist/index.min.js +1 -1
  24. package/dist/index.min.js.map +1 -1
  25. package/package.json +6 -3
  26. package/scripts/atomix-cli.js +136 -1827
  27. package/scripts/cli/__tests__/basic.test.js +3 -2
  28. package/scripts/cli/__tests__/clean.test.js +278 -0
  29. package/scripts/cli/__tests__/component-validator.test.js +433 -0
  30. package/scripts/cli/__tests__/generator.test.js +613 -0
  31. package/scripts/cli/__tests__/glass-motion.test.js +256 -0
  32. package/scripts/cli/__tests__/integration.test.js +719 -108
  33. package/scripts/cli/__tests__/migrate.test.js +74 -0
  34. package/scripts/cli/__tests__/security.test.js +206 -0
  35. package/scripts/cli/__tests__/test-setup.js +3 -1
  36. package/scripts/cli/__tests__/theme-bridge.test.js +507 -0
  37. package/scripts/cli/__tests__/token-provider.test.js +361 -0
  38. package/scripts/cli/__tests__/utils.test.js +5 -5
  39. package/scripts/cli/commands/benchmark.js +105 -0
  40. package/scripts/cli/commands/build-theme.js +115 -0
  41. package/scripts/cli/commands/clean.js +109 -0
  42. package/scripts/cli/commands/doctor.js +88 -0
  43. package/scripts/cli/commands/generate.js +218 -0
  44. package/scripts/cli/commands/init.js +73 -0
  45. package/scripts/cli/commands/migrate.js +106 -0
  46. package/scripts/cli/commands/sync-tokens.js +206 -0
  47. package/scripts/cli/commands/theme-bridge.js +248 -0
  48. package/scripts/cli/commands/tokens.js +157 -0
  49. package/scripts/cli/commands/validate.js +194 -0
  50. package/scripts/cli/internal/ai-engine.js +156 -0
  51. package/scripts/cli/internal/compiler.js +114 -0
  52. package/scripts/cli/internal/component-validator.js +443 -0
  53. package/scripts/cli/internal/config-loader.js +162 -0
  54. package/scripts/cli/internal/filesystem.js +158 -0
  55. package/scripts/cli/internal/generator.js +430 -0
  56. package/scripts/cli/internal/glass-generator.js +398 -0
  57. package/scripts/cli/internal/hook-generator.js +369 -0
  58. package/scripts/cli/internal/hooks.js +61 -0
  59. package/scripts/cli/internal/itcss-generator.js +565 -0
  60. package/scripts/cli/internal/motion-generator.js +679 -0
  61. package/scripts/cli/internal/template-engine.js +301 -0
  62. package/scripts/cli/internal/theme-bridge.js +664 -0
  63. package/scripts/cli/internal/tokens/engine.js +122 -0
  64. package/scripts/cli/internal/tokens/provider.js +34 -0
  65. package/scripts/cli/internal/tokens/providers/figma.js +50 -0
  66. package/scripts/cli/internal/tokens/providers/style-dictionary.js +48 -0
  67. package/scripts/cli/internal/tokens/providers/w3c.js +48 -0
  68. package/scripts/cli/internal/tokens/token-provider.js +443 -0
  69. package/scripts/cli/internal/tokens/token-validator.js +513 -0
  70. package/scripts/cli/internal/validator.js +276 -0
  71. package/scripts/cli/internal/wizard.js +115 -0
  72. package/scripts/cli/mappings.js +23 -0
  73. package/scripts/cli/migration-tools.js +164 -94
  74. package/scripts/cli/plugins/style-dictionary.js +46 -0
  75. package/scripts/cli/templates/README.md +525 -95
  76. package/scripts/cli/templates/common-templates.js +40 -14
  77. package/scripts/cli/templates/components/react-component.ts +282 -0
  78. package/scripts/cli/templates/config/project-config.ts +112 -0
  79. package/scripts/cli/templates/hooks/use-component.ts +477 -0
  80. package/scripts/cli/templates/index.js +19 -4
  81. package/scripts/cli/templates/index.ts +171 -0
  82. package/scripts/cli/templates/next-templates.js +72 -0
  83. package/scripts/cli/templates/react-templates.js +70 -126
  84. package/scripts/cli/templates/scss-templates.js +35 -35
  85. package/scripts/cli/templates/stories/storybook-story.ts +241 -0
  86. package/scripts/cli/templates/styles/scss-component.ts +255 -0
  87. package/scripts/cli/templates/tests/vitest-test.ts +229 -0
  88. package/scripts/cli/templates/token-templates.js +337 -1
  89. package/scripts/cli/templates/tokens/token-generators.ts +1088 -0
  90. package/scripts/cli/templates/types/component-types.ts +145 -0
  91. package/scripts/cli/templates/utils/testing-utils.ts +144 -0
  92. package/scripts/cli/templates/vanilla-templates.js +39 -0
  93. package/scripts/cli/token-manager.js +8 -2
  94. package/scripts/cli/utils/cache-manager.js +240 -0
  95. package/scripts/cli/utils/detector.js +46 -0
  96. package/scripts/cli/utils/diagnostics.js +289 -0
  97. package/scripts/cli/utils/error.js +89 -0
  98. package/scripts/cli/utils/helpers.js +67 -0
  99. package/scripts/cli/utils/logger.js +75 -0
  100. package/scripts/cli/utils/security.js +302 -0
  101. package/scripts/cli/utils/telemetry.js +115 -0
  102. package/scripts/cli/utils/validation.js +37 -0
  103. package/scripts/cli/utils.js +28 -341
  104. package/src/components/Accordion/Accordion.stories.tsx +0 -18
  105. package/src/components/Accordion/Accordion.test.tsx +0 -17
  106. package/src/components/Accordion/Accordion.tsx +0 -4
  107. package/src/components/AtomixGlass/AtomixGlass.test.tsx +37 -3
  108. package/src/components/AtomixGlass/AtomixGlass.tsx +143 -31
  109. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +129 -31
  110. package/src/components/AtomixGlass/PerformanceDashboard.tsx +219 -0
  111. package/src/components/AtomixGlass/README.md +25 -10
  112. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +216 -0
  113. package/src/components/AtomixGlass/animation-system.ts +578 -0
  114. package/src/components/AtomixGlass/shader-utils.ts +4 -1
  115. package/src/components/AtomixGlass/stories/Overview.stories.tsx +157 -6
  116. package/src/components/AtomixGlass/stories/Phase1-Animation.stories.tsx +653 -0
  117. package/src/components/AtomixGlass/stories/Phase1-Test.stories.tsx +95 -0
  118. package/src/components/AtomixGlass/stories/Playground.stories.tsx +51 -51
  119. package/src/components/AtomixGlass/stories/shared-components.tsx +6 -0
  120. package/src/components/Avatar/Avatar.tsx +1 -1
  121. package/src/components/Button/Button.stories.disabled-link.tsx +10 -0
  122. package/src/components/Button/Button.stories.tsx +10 -0
  123. package/src/components/Button/Button.test.tsx +16 -11
  124. package/src/components/Button/Button.tsx +4 -4
  125. package/src/components/Card/Card.tsx +1 -1
  126. package/src/components/Dropdown/Dropdown.tsx +12 -12
  127. package/src/components/Form/Select.tsx +62 -3
  128. package/src/components/Modal/Modal.tsx +14 -3
  129. package/src/components/Navigation/Navbar/Navbar.tsx +44 -0
  130. package/src/components/Slider/Slider.stories.tsx +3 -3
  131. package/src/components/Slider/Slider.tsx +38 -0
  132. package/src/components/Steps/Steps.tsx +3 -3
  133. package/src/components/Tabs/Tabs.tsx +77 -8
  134. package/src/components/Testimonial/Testimonial.tsx +1 -1
  135. package/src/components/TypedButton/TypedButton.stories.tsx +59 -0
  136. package/src/components/TypedButton/TypedButton.tsx +39 -0
  137. package/src/components/TypedButton/index.ts +2 -0
  138. package/src/components/VideoPlayer/VideoPlayer.tsx +11 -4
  139. package/src/lib/composables/index.ts +4 -7
  140. package/src/lib/composables/types.ts +45 -0
  141. package/src/lib/composables/useAccordion.ts +0 -7
  142. package/src/lib/composables/useAtomixGlass.ts +148 -6
  143. package/src/lib/composables/useAtomixGlassStyles.ts +9 -7
  144. package/src/lib/composables/useChartExport.ts +3 -13
  145. package/src/lib/composables/useDropdown.ts +66 -0
  146. package/src/lib/composables/useFocusTrap.ts +80 -0
  147. package/src/lib/composables/usePerformanceMonitor.ts +448 -0
  148. package/src/lib/composables/useResponsiveGlass.presets.ts +192 -0
  149. package/src/lib/composables/useResponsiveGlass.ts +441 -0
  150. package/src/lib/composables/useTooltip.ts +16 -0
  151. package/src/lib/composables/useTypedButton.ts +66 -0
  152. package/src/lib/config/index.ts +62 -5
  153. package/src/lib/constants/components.ts +62 -7
  154. package/src/lib/theme/devtools/__tests__/useHistory.test.tsx +150 -0
  155. package/src/lib/theme/tokens/centralized-tokens.ts +120 -0
  156. package/src/lib/theme/utils/__tests__/domUtils.test.ts +101 -0
  157. package/src/lib/types/components.ts +37 -11
  158. package/src/lib/types/glass.ts +35 -0
  159. package/src/lib/types/index.ts +1 -0
  160. package/src/lib/utils/displacement-generator.ts +1 -1
  161. package/src/styles/01-settings/_settings.testtypecheck.scss +53 -0
  162. package/src/styles/01-settings/_settings.typedbutton.scss +53 -0
  163. package/src/styles/06-components/_components.atomix-glass.scss +17 -21
  164. package/src/styles/06-components/_components.edge-panel.scss +1 -5
  165. package/src/styles/06-components/_components.modal.scss +1 -4
  166. package/src/styles/06-components/_components.navbar.scss +1 -1
  167. package/src/styles/06-components/_components.testbutton.scss +212 -0
  168. package/src/styles/06-components/_components.testtypecheck.scss +212 -0
  169. package/src/styles/06-components/_components.tooltip.scss +9 -5
  170. package/src/styles/06-components/_components.typedbutton.scss +212 -0
  171. package/src/styles/99-utilities/_index.scss +1 -0
  172. package/src/styles/99-utilities/_utilities.text.scss +1 -1
  173. package/src/styles/99-utilities/_utilities.touch-target.scss +36 -0
  174. package/scripts/cli/component-generator.js +0 -564
  175. package/scripts/cli/interactive-init.js +0 -357
  176. package/src/styles/06-components/old.chart.styles.scss +0 -2788
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Component Type Templates
3
+ * Templates for generating TypeScript type definitions
4
+ */
5
+
6
+ /**
7
+ * Generates TypeScript type definitions for a component
8
+ */
9
+ export const typesTemplate = (name: string): string => `import { ReactNode, HTMLAttributes, AriaAttributes } from 'react';
10
+
11
+ /**
12
+ * Size variants for components
13
+ */
14
+ export type ${name}Size = 'sm' | 'md' | 'lg';
15
+
16
+ /**
17
+ * Color variants for components
18
+ */
19
+ export type ${name}Variant = 'primary' | 'secondary' | 'success' | 'error' | 'warning';
20
+
21
+ /**
22
+ * Glass effect configuration
23
+ */
24
+ export interface GlassConfig {
25
+ displacementScale?: number;
26
+ blurAmount?: number;
27
+ saturation?: number;
28
+ elasticity?: number;
29
+ }
30
+
31
+ /**
32
+ * Props for the ${name} component
33
+ */
34
+ export interface ${name}Props extends HTMLAttributes<HTMLDivElement>, AriaAttributes {
35
+ /** Content to be rendered inside the component */
36
+ children?: ReactNode;
37
+
38
+ /** Additional CSS classes */
39
+ className?: string;
40
+
41
+ /** Size variant */
42
+ size?: ${name}Size;
43
+
44
+ /** Color variant */
45
+ variant?: ${name}Variant;
46
+
47
+ /** Whether the component is disabled */
48
+ disabled?: boolean;
49
+
50
+ /** Whether to apply glass morphism effect */
51
+ glass?: boolean | GlassConfig;
52
+
53
+ /** Click handler */
54
+ onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
55
+
56
+ /** Hover handler */
57
+ onHover?: (event: React.MouseEvent<HTMLDivElement>) => void;
58
+
59
+ /** Focus handler */
60
+ onFocus?: (event: React.FocusEvent<HTMLDivElement>) => void;
61
+
62
+ /** Blur handler */
63
+ onBlur?: (event: React.FocusEvent<HTMLDivElement>) => void;
64
+
65
+ /** State change handler */
66
+ onStateChange?: (state: ${name}State) => void;
67
+ }
68
+
69
+ /**
70
+ * State interface for ${name} component
71
+ */
72
+ export interface ${name}State {
73
+ isOpen?: boolean;
74
+ isActive?: boolean;
75
+ isSelected?: boolean;
76
+ }
77
+ `;
78
+
79
+ /**
80
+ * Generates constants file for a component
81
+ */
82
+ export const constantsTemplate = (name: string): string => `/**
83
+ * ${name} Component Constants
84
+ */
85
+
86
+ export const ${name.toUpperCase()} = {
87
+ /** Base CSS class name */
88
+ BASE_CLASS: 'c-${name.toLowerCase()}',
89
+
90
+ /** Class name prefixes */
91
+ PREFIX: 'c-${name.toLowerCase()}--',
92
+
93
+ /** Element prefixes */
94
+ ELEMENT_PREFIX: '${name.toLowerCase()}__',
95
+
96
+ /** Available sizes */
97
+ SIZES: ['sm', 'md', 'lg'] as const,
98
+
99
+ /** Available variants */
100
+ VARIANTS: ['primary', 'secondary', 'success', 'error', 'warning'] as const,
101
+
102
+ /** Default props */
103
+ DEFAULTS: {
104
+ SIZE: 'md' as const,
105
+ VARIANT: 'primary' as const,
106
+ DISABLED: false,
107
+ GLASS: false,
108
+ },
109
+
110
+ /** CSS class names */
111
+ CLASSES: {
112
+ LOADING: 'is-loading',
113
+ DISABLED: 'is-disabled',
114
+ ACTIVE: 'is-active',
115
+ SELECTED: 'is-selected',
116
+ OPEN: 'is-open',
117
+ CLOSED: 'is-closed',
118
+ FOCUS: 'has-focus',
119
+ GLASS: 'has-glass',
120
+ },
121
+
122
+ /** Data attributes */
123
+ DATA: {
124
+ STATE: 'data-state',
125
+ DISABLED: 'aria-disabled',
126
+ LOADING: 'aria-busy',
127
+ },
128
+ } as const;
129
+
130
+ export type ${name}Sizes = typeof ${name.toUpperCase()}.SIZES[number];
131
+ export type ${name}Variants = typeof ${name.toUpperCase()}.VARIANTS[number];
132
+ `;
133
+
134
+ /**
135
+ * All type templates
136
+ */
137
+ export const componentTypeTemplates = {
138
+ types: typesTemplate,
139
+ constants: constantsTemplate,
140
+ };
141
+
142
+ /**
143
+ * Type for component type templates object
144
+ */
145
+ export type ComponentTypeTemplates = typeof componentTypeTemplates;
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Testing Utilities Templates
3
+ * Templates for generating test utilities and helpers
4
+ */
5
+
6
+ /**
7
+ * Generates a test utilities file with common helpers
8
+ */
9
+ export const testingUtilsTemplate = (): string => `import { render, RenderOptions, screen, waitFor } from '@testing-library/react';
10
+ import { ReactElement, ReactNode } from 'react';
11
+ import userEvent from '@testing-library/user-event';
12
+
13
+ /**
14
+ * Custom render options
15
+ */
16
+ interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
17
+ wrapper?: React.ComponentType<{ children: ReactElement }>;
18
+ providerProps?: Record<string, any>;
19
+ }
20
+
21
+ /**
22
+ * Custom render function with Atomix providers
23
+ */
24
+ export function renderWithProviders(
25
+ ui: ReactElement,
26
+ { wrapper: Wrapper, providerProps, ...renderOptions }: CustomRenderOptions = {}
27
+ ) {
28
+ function WrapperComponent({ children }: { children: ReactElement }) {
29
+ return Wrapper ? <Wrapper {...providerProps}>{children}</Wrapper> : children;
30
+ }
31
+
32
+ return render(ui, { wrapper: WrapperComponent, ...renderOptions });
33
+ }
34
+
35
+ /**
36
+ * Mock IntersectionObserver
37
+ */
38
+ export const mockIntersectionObserver = () => {
39
+ global.IntersectionObserver = class IntersectionObserver {
40
+ observe = vi.fn();
41
+ unobserve = vi.fn();
42
+ disconnect = vi.fn();
43
+ } as any;
44
+ };
45
+
46
+ /**
47
+ * Mock ResizeObserver
48
+ */
49
+ export const mockResizeObserver = () => {
50
+ global.ResizeObserver = class ResizeObserver {
51
+ observe = vi.fn();
52
+ unobserve = vi.fn();
53
+ disconnect = vi.fn();
54
+ } as any;
55
+ };
56
+
57
+ /**
58
+ * Wait for component to be ready
59
+ */
60
+ export const waitForReady = async (timeout = 100) => {
61
+ await waitFor(() => expect(screen.getByRole('document')).toBeInTheDocument(), { timeout });
62
+ };
63
+
64
+ /**
65
+ * Create a mock event
66
+ */
67
+ export const createMockEvent = (type: string, data: any = {}) => {
68
+ return new Event(type, { bubbles: true, cancelable: true, ...data });
69
+ };
70
+
71
+ /**
72
+ * Simulate keyboard events
73
+ */
74
+ export const simulateKeyboard = async (element: HTMLElement, key: string) => {
75
+ const user = userEvent.setup();
76
+ await user.keyboard(key);
77
+ };
78
+
79
+ /**
80
+ * Mock CSS custom properties
81
+ */
82
+ export const mockCSSCustomProperties = () => {
83
+ Object.defineProperty(document.documentElement.style, '--test-property', {
84
+ value: 'test-value',
85
+ writable: true,
86
+ });
87
+ };
88
+
89
+ /**
90
+ * Reset all mocks
91
+ */
92
+ export const resetMocks = () => {
93
+ vi.clearAllMocks();
94
+ vi.resetAllMocks();
95
+ vi.restoreAllMocks();
96
+ };
97
+ `;
98
+
99
+ /**
100
+ * Generates a test setup file
101
+ */
102
+ export const testSetupTemplate = (): string => `import '@testing-library/jest-dom';
103
+ import { cleanup } from '@testing-library/react';
104
+ import { afterEach, vi } from 'vitest';
105
+
106
+ // Cleanup after each test
107
+ afterEach(() => {
108
+ cleanup();
109
+ });
110
+
111
+ // Mock window.matchMedia
112
+ Object.defineProperty(window, 'matchMedia', {
113
+ writable: true,
114
+ value: vi.fn().mockImplementation(query => ({
115
+ matches: false,
116
+ media: query,
117
+ onchange: null,
118
+ addListener: vi.fn(),
119
+ removeListener: vi.fn(),
120
+ addEventListener: vi.fn(),
121
+ removeEventListener: vi.fn(),
122
+ dispatchEvent: vi.fn(),
123
+ })),
124
+ });
125
+
126
+ // Mock requestAnimationFrame
127
+ global.requestAnimationFrame = (callback) => setTimeout(callback, 0);
128
+
129
+ // Mock cancelAnimationFrame
130
+ global.cancelAnimationFrame = (id) => clearTimeout(id);
131
+ `;
132
+
133
+ /**
134
+ * All testing utilities templates
135
+ */
136
+ export const testingUtilsTemplates = {
137
+ utils: testingUtilsTemplate,
138
+ setup: testSetupTemplate,
139
+ };
140
+
141
+ /**
142
+ * Type for testing utilities templates object
143
+ */
144
+ export type TestingUtilsTemplates = typeof testingUtilsTemplates;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Vanilla Component Templates
3
+ * Pure HTML/CSS/JS snippets
4
+ */
5
+
6
+ export const vanillaTemplates = {
7
+ /**
8
+ * Simple HTML Snippet
9
+ */
10
+ simple: (name) => `<div class="${name.toLowerCase()}">
11
+ <h1>${name} Component</h1>
12
+ </div>
13
+ `,
14
+
15
+ /**
16
+ * Full Vanilla Component (HTML + JS)
17
+ */
18
+ component: (name) => `<div class="${name.toLowerCase()}" id="${name.toLowerCase()}-root">
19
+ <h1>${name} Component</h1>
20
+ </div>
21
+
22
+ <script>
23
+ (function() {
24
+ const root = document.getElementById('${name.toLowerCase()}-root');
25
+ console.log('${name} initialized');
26
+ })();
27
+ </script>
28
+ `,
29
+
30
+ /**
31
+ * CSS Snippet
32
+ */
33
+ styles: (name) => `.${name.toLowerCase()} {
34
+ display: block;
35
+ padding: 1rem;
36
+ border: 1px solid var(--atomix-border-color, #ccc);
37
+ }
38
+ `
39
+ };
@@ -280,7 +280,7 @@ export async function listTokens(categories = Object.keys(tokenCategories)) {
280
280
  // Show summary
281
281
  if (categoryCount === 0) {
282
282
  console.log(safeChalkCall(chalk, 'yellow', '\n⚠️ No design tokens found'));
283
- console.log(safeChalkCall(chalk, 'gray', 'Make sure you are in an Atomix project directory'));
283
+ console.log(safeChalkCall(chalk, 'gray', 'Create a theme or add token files in src/styles/01-settings (e.g. _settings.colors.scss). See Atomix token docs.'));
284
284
  } else {
285
285
  console.log(safeChalkCall(chalk, 'bold.cyan', '\n📐 Design Tokens\n'));
286
286
 
@@ -430,6 +430,13 @@ export async function exportTokens(format = 'json', outputPath = null) {
430
430
  }
431
431
  }
432
432
 
433
+ const categoryCount = Object.keys(allTokens).length;
434
+ if (categoryCount === 0) {
435
+ safeSpinnerCall(spinner, 'warn', safeChalkCall(chalk, 'yellow', 'No design tokens found'));
436
+ console.log(safeChalkCall(chalk, 'gray', 'Create a theme or add token files in src/styles/01-settings. See Atomix token docs.'));
437
+ return { path: null, tokens: allTokens };
438
+ }
439
+
433
440
  let output;
434
441
  let filename;
435
442
 
@@ -501,7 +508,6 @@ export async function exportTokens(format = 'json', outputPath = null) {
501
508
  safeSpinnerCall(spinner, 'succeed', safeChalkCall(chalk, 'green', `✓ Exported tokens to ${finalPath}`));
502
509
 
503
510
  // Show summary
504
- const categoryCount = Object.keys(allTokens).length;
505
511
  const tokenCount = Object.values(allTokens).reduce(
506
512
  (sum, cat) => sum + Object.keys(cat).length,
507
513
  0
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Atomix CLI Cache Manager
3
+ * Utilities for identifying and managing cache files and build artifacts
4
+ */
5
+
6
+ import { readdir, stat, rm } from 'fs/promises';
7
+ import { join, extname } from 'path';
8
+ import { filesystem } from '../internal/filesystem.js';
9
+ import { logger } from '../utils/logger.js';
10
+ import chalk from 'chalk';
11
+
12
+ // File extensions that should NEVER be deleted (source files)
13
+ const PROTECTED_EXTENSIONS = new Set([
14
+ '.js', '.jsx', '.ts', '.tsx',
15
+ '.scss', '.css', '.sass',
16
+ '.json', '.md', '.html',
17
+ '.yml', '.yaml'
18
+ ]);
19
+
20
+ // Default directories to clean
21
+ const DEFAULT_CLEAN_TARGETS = [
22
+ 'dist',
23
+ '.atomix',
24
+ 'node_modules/.cache'
25
+ ];
26
+
27
+ // Additional targets for --all flag
28
+ const ALL_CLEAN_TARGETS = [
29
+ ...DEFAULT_CLEAN_TARGETS,
30
+ 'node_modules'
31
+ ];
32
+
33
+ /**
34
+ * Cache Manager - Safe file cleanup utilities
35
+ */
36
+ export const cacheManager = {
37
+ /**
38
+ * Identify files and directories that can be safely cleaned
39
+ * @param {object} options - Clean options
40
+ * @returns {Promise<string[]>} List of paths to clean
41
+ */
42
+ async identifyTargets(options = {}) {
43
+ const targets = [];
44
+ const cleanScope = options.all ? ALL_CLEAN_TARGETS : DEFAULT_CLEAN_TARGETS;
45
+
46
+ for (const target of cleanScope) {
47
+ const fullPath = join(process.cwd(), target);
48
+ const exists = await filesystem.exists(fullPath);
49
+
50
+ if (exists) {
51
+ targets.push({
52
+ path: fullPath,
53
+ relativePath: target,
54
+ type: await this.getPathType(fullPath)
55
+ });
56
+ }
57
+ }
58
+
59
+ // Add log files if not in cache-only mode
60
+ if (!options.cache) {
61
+ const logFiles = await this.findLogFiles();
62
+ targets.push(...logFiles);
63
+ }
64
+
65
+ return targets;
66
+ },
67
+
68
+ /**
69
+ * Determine if a path is a file or directory
70
+ * @param {string} path - Path to check
71
+ * @returns {Promise<'file'|'directory'>}
72
+ */
73
+ async getPathType(path) {
74
+ try {
75
+ const stats = await stat(path);
76
+ return stats.isDirectory() ? 'directory' : 'file';
77
+ } catch {
78
+ return 'unknown';
79
+ }
80
+ },
81
+
82
+ /**
83
+ * Find all log files in the project
84
+ * @returns {Promise<Array>} List of log file paths
85
+ */
86
+ async findLogFiles() {
87
+ const logFiles = [];
88
+ const root = process.cwd();
89
+
90
+ try {
91
+ const entries = await readdir(root, { withFileTypes: true });
92
+
93
+ for (const entry of entries) {
94
+ if (entry.isFile() && extname(entry.name) === '.log') {
95
+ logFiles.push({
96
+ path: join(root, entry.name),
97
+ relativePath: entry.name,
98
+ type: 'file'
99
+ });
100
+ }
101
+ }
102
+ } catch (error) {
103
+ logger.debug('Error scanning for log files:', error.message);
104
+ }
105
+
106
+ return logFiles;
107
+ },
108
+
109
+ /**
110
+ * Check if a file extension is protected (source file)
111
+ * @param {string} filePath - Path to check
112
+ * @returns {boolean} True if protected
113
+ */
114
+ isProtected(filePath) {
115
+ const ext = extname(filePath);
116
+ return PROTECTED_EXTENSIONS.has(ext);
117
+ },
118
+
119
+ /**
120
+ * Safely delete a file or directory
121
+ * @param {string} path - Path to delete
122
+ * @param {object} options - Delete options
123
+ * @param {boolean} options.skipValidation - Skip path validation (for tests)
124
+ */
125
+ async deletePath(path, options = {}) {
126
+ // Safety check: never delete protected files
127
+ if (this.isProtected(path)) {
128
+ throw new Error(`Cannot delete protected source file: ${path}`);
129
+ }
130
+
131
+ // Validate path is within project (unless skipping for tests)
132
+ if (!options.skipValidation) {
133
+ const validation = filesystem.validatePath(path);
134
+ if (!validation.isValid) {
135
+ throw new Error(`Invalid path: ${validation.error}`);
136
+ }
137
+ }
138
+
139
+ if (process.env.ATOMIX_DRY_RUN === 'true') {
140
+ logger.info(`[DRY RUN] Would delete: ${path}`);
141
+ return true;
142
+ }
143
+
144
+ try {
145
+ await rm(path, {
146
+ recursive: true,
147
+ force: true,
148
+ maxRetries: 3,
149
+ retryDelay: 100
150
+ });
151
+ return true;
152
+ } catch (error) {
153
+ throw new Error(`Failed to delete ${path}: ${error.message}`);
154
+ }
155
+ },
156
+
157
+ /**
158
+ * Calculate total size of files/directories
159
+ * @param {Array} targets - List of target paths
160
+ * @returns {Promise<number>} Total size in bytes
161
+ */
162
+ async calculateSize(targets) {
163
+ let totalSize = 0;
164
+
165
+ const calculateDirSize = async (dirPath) => {
166
+ try {
167
+ const entries = await readdir(dirPath, { withFileTypes: true });
168
+
169
+ for (const entry of entries) {
170
+ const fullPath = join(dirPath, entry.name);
171
+
172
+ if (entry.isDirectory()) {
173
+ totalSize += await calculateDirSize(fullPath);
174
+ } else {
175
+ const stats = await stat(fullPath);
176
+ totalSize += stats.size;
177
+ }
178
+ }
179
+ } catch (error) {
180
+ logger.debug('Error calculating size:', error.message);
181
+ }
182
+
183
+ return totalSize;
184
+ };
185
+
186
+ for (const target of targets) {
187
+ if (target.type === 'directory') {
188
+ await calculateDirSize(target.path);
189
+ } else {
190
+ const stats = await stat(target.path);
191
+ totalSize += stats.size;
192
+ }
193
+ }
194
+
195
+ return totalSize;
196
+ },
197
+
198
+ /**
199
+ * Format bytes to human-readable string
200
+ * @param {number} bytes - Size in bytes
201
+ * @returns {string} Formatted size
202
+ */
203
+ formatBytes(bytes) {
204
+ if (bytes === 0) return '0 Bytes';
205
+
206
+ const k = 1024;
207
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
208
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
209
+
210
+ // Handle edge case where size is larger than TB
211
+ const unit = sizes[i] || 'PB';
212
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + unit;
213
+ },
214
+
215
+ /**
216
+ * Display dry-run preview of what would be cleaned
217
+ * @param {Array} targets - Files/dirs to clean
218
+ */
219
+ displayDryRun(targets) {
220
+ if (targets.length === 0) {
221
+ logger.box('✨ Nothing to clean! Your project is already tidy.', {
222
+ borderColor: 'green'
223
+ });
224
+ return;
225
+ }
226
+
227
+ const fileList = targets
228
+ .map(t => ` • ${t.relativePath} (${t.type})`)
229
+ .join('\n');
230
+
231
+ logger.box(
232
+ `⚠️ Dry Run Mode - The following would be deleted:\n\n${fileList}`,
233
+ {
234
+ borderColor: 'yellow'
235
+ }
236
+ );
237
+
238
+ logger.info(`\nTotal: ${targets.length} item(s)\n`);
239
+ }
240
+ };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Atomix CLI Project Detector
3
+ * Automatically detects the framework environment (React, Next.js, Vanilla)
4
+ */
5
+
6
+ import { readFile } from 'fs/promises';
7
+ import { join } from 'path';
8
+ import { existsSync } from 'fs';
9
+
10
+ /**
11
+ * Detect the framework from package.json and project structure
12
+ * @param {string} projectRoot - The root directory of the project
13
+ * @returns {Promise<'react' | 'next' | 'vanilla'>}
14
+ */
15
+ export async function detectFramework(projectRoot = process.cwd()) {
16
+ try {
17
+ const packageJsonPath = join(projectRoot, 'package.json');
18
+
19
+ if (!existsSync(packageJsonPath)) {
20
+ return 'vanilla';
21
+ }
22
+
23
+ const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'));
24
+ const allDeps = {
25
+ ...(packageJson.dependencies || {}),
26
+ ...(packageJson.devDependencies || {})
27
+ };
28
+
29
+ if (allDeps.next) {
30
+ return 'next';
31
+ }
32
+
33
+ if (allDeps.react) {
34
+ return 'react';
35
+ }
36
+
37
+ // Check for framework specific files
38
+ if (existsSync(join(projectRoot, 'next.config.js')) || existsSync(join(projectRoot, 'next.config.mjs'))) {
39
+ return 'next';
40
+ }
41
+
42
+ return 'vanilla';
43
+ } catch (error) {
44
+ return 'vanilla';
45
+ }
46
+ }