visualifyjs 2.5.3-2.dev → 3.0.0

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 (252) hide show
  1. package/.claude/mem/TIMELINE.md +36 -0
  2. package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-external-script-solution.md +24 -0
  3. package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-session-summary.md +43 -0
  4. package/.claude/mem/notes/2026-02-11-cli-fix-editor-command-alias.md +26 -0
  5. package/.claude/mem/notes/2026-02-11-phase-3-developer-experience-completed.md +51 -0
  6. package/.claude/mem/notes/2026-02-11-phase-4-web-workers-implementation-complete.md +59 -0
  7. package/.claude/mem/notes/2026-02-11-visualify-phase-2-3d-visualization-complete.md +50 -0
  8. package/.claude/mem/notes/2026-02-11-visualify-phase-2-committed-ready-for-phase-3.md +33 -0
  9. package/.claude/mem/notes/2026-02-11-visualify-phase-3-complete-developer-experience.md +52 -0
  10. package/.claude/mem/notes/2026-02-11-visualify-repository-cleanup-complete.md +28 -0
  11. package/.claude/mem/notes/2026-02-18-codebase-cleanup-docsify-plugin-documentation.md +37 -0
  12. package/.claude/mem/notes/2026-02-19-css-grid-layout-fix-displaycontents-on-vcontroller.md +18 -0
  13. package/.claude/mem/notes/2026-02-19-docsify-plugin-fixes-latex-and-visualify-code-bloc.md +26 -0
  14. package/.claude/mem/notes/2026-02-19-page-mode-docs-update-decisions.md +23 -0
  15. package/.claude/mem/notes/2026-02-19-react-context-infinite-re-render-loop-fix-pattern.md +31 -0
  16. package/.claude/mem/notes/2026-02-19-version-300-bump-and-build-fixes.md +32 -0
  17. package/.claude/mem/notes/2026-02-19-visualify-build-deployment-architecture-bug-fixes.md +25 -0
  18. package/.claude/mem/notes/2026-02-19-visualify-dist-iife-self-contained-build-config.md +30 -0
  19. package/.claude/mem/notes/2026-02-19-visualify-infinite-loop-i18n-fixes.md +31 -0
  20. package/.claude/mem/notes/2026-02-19-visualify-v3-bundle-splitting-docs-restructuring.md +32 -0
  21. package/.claude/mem/notes/2026-02-20-bundle-externalization-final-architecture.md +29 -0
  22. package/.claude/mem/notes/2026-02-20-chromium-page-fix-unstable-keys-and-double-event-b.md +27 -0
  23. package/.claude/mem/notes/2026-02-20-console-cleanup-bundle-optimization-commit.md +20 -0
  24. package/.claude/mem/notes/2026-02-20-dotbio-dot-plot-fix-useeffect-dependency.md +21 -0
  25. package/.claude/mem/notes/2026-02-20-public-folder-cleanup-and-readme-rewrite.md +25 -0
  26. package/.claude/mem/notes/2026-02-20-v300-release-and-beta-channel-strategy.md +29 -0
  27. package/.claude/mem/notes/2026-02-20-visium-background-image-unknown-legend-fix.md +19 -0
  28. package/.claude/mem/notes/2026-02-20-visualify-cdn-loader-bundle-externalization.md +34 -0
  29. package/.claude/mem/sessions/session-2026-02-20-031524.md +54 -0
  30. package/.claude/settings.local.json +21 -0
  31. package/.github/workflows/static.yml.bak +51 -51
  32. package/.sisyphus/boulder.json +65 -0
  33. package/.sisyphus/plans/phase-4-advanced-optimizations.md +217 -0
  34. package/LICENSE +674 -674
  35. package/README.md +94 -59
  36. package/config-overrides.js +31 -31
  37. package/dist/stats.html +4949 -0
  38. package/dist/visualify-3d.esm.js +1 -0
  39. package/dist/visualify-3d.js +1 -0
  40. package/dist/visualify-core.esm.js +1 -0
  41. package/dist/visualify-core.js +1 -0
  42. package/dist/visualify-docs.esm.js +1 -0
  43. package/dist/visualify-docs.js +1 -0
  44. package/dist/visualify-loader.js +1 -0
  45. package/dist/visualify-pages.esm.js +1 -0
  46. package/dist/visualify-pages.js +1 -0
  47. package/dist/visualify-portal.esm.js +1 -0
  48. package/dist/visualify-portal.js +1 -0
  49. package/dist/visualify-shared.js +26571 -0
  50. package/dist/visualify.js +1 -188
  51. package/docs/CHANGELOG.md +148 -0
  52. package/docs/cli/commands.md +513 -0
  53. package/docs/configuration/visualify-json.md +474 -0
  54. package/docs/docs/3d-visualization.md +374 -0
  55. package/docs/docs/CLI.md +303 -34
  56. package/docs/docs/README.md +65 -65
  57. package/docs/docs/Rechart/bar.md +190 -190
  58. package/docs/docs/Rechart/funnel.md +241 -241
  59. package/docs/docs/Rechart/line.md +355 -355
  60. package/docs/docs/Rechart/pie.md +225 -225
  61. package/docs/docs/Rechart/radar.md +253 -253
  62. package/docs/docs/Rechart/scatter.md +298 -298
  63. package/docs/docs/_404.md +51 -51
  64. package/docs/docs/_coverpage.md +11 -11
  65. package/docs/docs/_sidebar.md +54 -44
  66. package/docs/docs/components/dotBio.md +87 -34
  67. package/docs/docs/components/echart.md +171 -82
  68. package/docs/docs/components/html.md +61 -34
  69. package/docs/docs/components/macaron.md +156 -145
  70. package/docs/docs/components/markdown.md +42 -0
  71. package/docs/docs/components/more.md +183 -142
  72. package/docs/docs/components/plotly.md +132 -62
  73. package/docs/docs/components/scatterL.md +171 -70
  74. package/docs/docs/components/visium.md +112 -57
  75. package/docs/docs/configuration.md +121 -121
  76. package/docs/docs/deploy.md +31 -31
  77. package/docs/docs/docsify-plugin.md +655 -0
  78. package/docs/docs/hmr.md +165 -0
  79. package/docs/docs/i18n.md +332 -0
  80. package/docs/docs/log.md +30 -9
  81. package/docs/docs/more-pages.md +23 -23
  82. package/docs/docs/quickstart.md +148 -124
  83. package/docs/docs/rechart-attributes.md +74 -74
  84. package/docs/docs/rechart-basic-usage.md +160 -162
  85. package/docs/docs/theme.md +5 -5
  86. package/docs/docs/typescript.md +306 -0
  87. package/docs/docs/visual-editor.md +359 -0
  88. package/docs/index.html +85 -71
  89. package/docs/manifest.json +23 -23
  90. package/docs/migration/v3-migration.md +392 -0
  91. package/docs/static/css/fluff-stuff.css +169 -169
  92. package/docs/static/css/font-awesome.min.css +4 -4
  93. package/docs/static/css/visualify.css +6 -25
  94. package/docs/static/js/3d-viz-examples.js +181 -0
  95. package/docs/static/js/configuration.js +630 -448
  96. package/docs/static/js/visualify.js +1 -188
  97. package/package.json +106 -84
  98. package/rollup.config.mjs +766 -76
  99. package/src/_css/404.css +115 -115
  100. package/src/_css/App.css +37 -37
  101. package/src/_css/autoSuggestion.css +26 -26
  102. package/src/_css/circular-progress.css +32 -32
  103. package/src/_css/index.css +36 -36
  104. package/src/_css/modern.css +350 -25
  105. package/src/_media/corner.svg +8 -8
  106. package/src/_media/download.svg +3 -3
  107. package/src/_media/logo.svg +14 -14
  108. package/src/_test/App.test.js +15 -15
  109. package/src/_utils/reportWebVitals.js +13 -13
  110. package/src/a11y/README.md +177 -0
  111. package/src/a11y/aria-labels.js +339 -0
  112. package/src/a11y/color-contrast.js +535 -0
  113. package/src/a11y/index.js +197 -0
  114. package/src/a11y/keyboard-nav.js +523 -0
  115. package/src/a11y/styles.css +165 -0
  116. package/src/cli/commands/dev.js +214 -0
  117. package/src/cli/commands/docs.js +521 -0
  118. package/src/cli/commands/edit.js +379 -0
  119. package/src/cli/commands/init.js +213 -0
  120. package/src/cli/commands/portal.js +236 -0
  121. package/src/cli/dev-server.js +530 -0
  122. package/src/cli/hmr.js +456 -0
  123. package/src/cli/index.js +180 -0
  124. package/src/cli/utils/config.js +207 -0
  125. package/src/cli/utils/logger.js +241 -0
  126. package/src/config/defaults.ts +122 -0
  127. package/src/config/index.ts +72 -0
  128. package/src/config/loader.ts +478 -0
  129. package/src/config/schema.ts +227 -0
  130. package/src/config/validator.ts +337 -0
  131. package/src/core/appContext.js +34 -27
  132. package/src/core/components/Bar.js +383 -0
  133. package/src/core/components/Bar3D.js +473 -0
  134. package/src/core/components/LargeDatasetChart.js +296 -0
  135. package/src/core/components/Line3D.js +310 -0
  136. package/src/core/components/Scatter.js +392 -188
  137. package/src/core/components/Scatter3D.js +455 -0
  138. package/src/core/components/ScatterBio.js +601 -572
  139. package/src/core/components/Surface3D.js +326 -0
  140. package/src/core/components/ThreeCustom.js +648 -0
  141. package/src/core/components/ThreeScene.js +459 -0
  142. package/src/core/components/VisiumPlot.js +191 -165
  143. package/src/core/components/browser.js +42 -42
  144. package/src/core/components/dotplot.js +413 -413
  145. package/src/core/components/html.js +29 -29
  146. package/src/core/components/list.js +178 -178
  147. package/src/core/components/macaron.js +206 -201
  148. package/src/core/components/markdown.js +56 -56
  149. package/src/core/components/parser.scatterBio.js +582 -587
  150. package/src/core/components/ratio.js +82 -80
  151. package/src/core/components/scatterL.js +206 -173
  152. package/src/core/components/searchbar.js +156 -131
  153. package/src/core/components/selection.js +310 -193
  154. package/src/core/components/timeline.js +236 -281
  155. package/src/core/components/visium.js +114 -97
  156. package/src/core/data-processor.js +413 -0
  157. package/src/core/fetch/condfetch.js +82 -82
  158. package/src/core/fetch/fetch.js +92 -92
  159. package/src/core/fetch/json.js +29 -29
  160. package/src/core/fetch/vfetch.js +42 -42
  161. package/src/core/hmr-client.js +724 -0
  162. package/src/core/liveEditor.js +44 -44
  163. package/src/core/modules/codeEditorWithPreview.js +104 -104
  164. package/src/core/modules/echarts/common.js +20 -20
  165. package/src/core/modules/echarts/gl.js +228 -0
  166. package/src/core/modules/echarts/presetHandler.js +41 -41
  167. package/src/core/modules/echarts/presets/esodev.chromium.js +172 -172
  168. package/src/core/modules/echarts/presets/esodev.codex.js +130 -130
  169. package/src/core/modules/echarts/presets/esodev.visium.js +123 -123
  170. package/src/core/modules/echarts/presets/mmtrbc.js +186 -186
  171. package/src/core/modules/echarts.js +70 -71
  172. package/src/core/modules/echartsUtils.js +43 -43
  173. package/src/core/modules/echartswitcher.js +227 -152
  174. package/src/core/modules/replotly/presetHandler.js +24 -24
  175. package/src/core/modules/replotly/presets/minimum.js +18 -18
  176. package/src/core/modules/replotly/presets/mmtrbc.dot.js +114 -114
  177. package/src/core/modules/replotly/presets/mmtrbc.violin.js +100 -100
  178. package/src/core/modules/replotly.js +74 -71
  179. package/src/core/modules/threejs/Camera.js +373 -0
  180. package/src/core/modules/threejs/Lighting.js +459 -0
  181. package/src/core/modules/threejs/Renderer.js +364 -0
  182. package/src/core/modules/threejs/Scene.js +266 -0
  183. package/src/core/modules/threejs/index.js +155 -0
  184. package/src/core/pages/404.js +50 -50
  185. package/src/core/pages/error.js +27 -27
  186. package/src/core/pages/jsonPage.js +62 -62
  187. package/src/core/pages/loading.js +44 -44
  188. package/src/core/parser/echart.data.js +204 -183
  189. package/src/core/parser/echart.features.js +125 -125
  190. package/src/core/parser/echart.general.js +147 -147
  191. package/src/core/parser/echart.hilbert.js +57 -57
  192. package/src/core/parser/echart.parser.js +210 -210
  193. package/src/core/parser/echart.series.js +67 -67
  194. package/src/core/parser/echart.types.js +76 -76
  195. package/src/core/parser/plotly.config.js +10 -10
  196. package/src/core/parser/plotly.data.js +132 -132
  197. package/src/core/parser/plotly.layout.js +9 -9
  198. package/src/core/parser/plotly.violin.js +18 -18
  199. package/src/core/recharts.js +361 -62
  200. package/src/core/router/alias.js +49 -49
  201. package/src/core/router/jsonRouter.js +31 -31
  202. package/src/core/themes/modern.js +32 -32
  203. package/src/core/themes/themeSelector.js +33 -33
  204. package/src/core/visualify.js +213 -47
  205. package/src/core/widgets/circularProgress.js +23 -23
  206. package/src/core/widgets/controller.js +116 -83
  207. package/src/core/widgets/errorBoundary.js +36 -36
  208. package/src/core/widgets/footer.js +185 -177
  209. package/src/core/widgets/header.js +238 -234
  210. package/src/core/widgets/layout/Grid.js +31 -31
  211. package/src/core/widgets/layout.js +36 -36
  212. package/src/core/widgets/mapping.js +56 -42
  213. package/src/core/workers/data-worker.js +349 -0
  214. package/src/core/workers/worker-pool.js +396 -0
  215. package/src/docsify/bundle.js +215 -0
  216. package/src/docsify/markdown.js +271 -0
  217. package/src/docsify/plugin.js +268 -0
  218. package/src/editor/README.md +172 -0
  219. package/src/editor/components/ChartBuilder.jsx +341 -0
  220. package/src/editor/components/ChartTypeSidebar.jsx +91 -0
  221. package/src/editor/components/Editor.jsx +367 -0
  222. package/src/editor/components/Preview.jsx +446 -0
  223. package/src/editor/components/PropertyPanel.jsx +468 -0
  224. package/src/editor/components/StatusBar.jsx +85 -0
  225. package/src/editor/context/EditorContext.js +248 -0
  226. package/src/editor/hooks/useDebounce.js +32 -0
  227. package/src/editor/index.js +315 -0
  228. package/src/editor/styles/editor.css +637 -0
  229. package/src/editor/utils/chartValidator.js +263 -0
  230. package/src/entries/charts3d.js +70 -0
  231. package/src/entries/core.js +78 -0
  232. package/src/entries/docs.js +154 -0
  233. package/src/entries/pages.js +93 -0
  234. package/src/entries/portal.js +204 -0
  235. package/src/entries/shared.js +50 -0
  236. package/src/i18n/formatters.js +455 -0
  237. package/src/i18n/index.js +169 -0
  238. package/src/i18n/locales/ar.json +137 -0
  239. package/src/i18n/locales/de.json +137 -0
  240. package/src/i18n/locales/en.json +137 -0
  241. package/src/i18n/locales/es.json +137 -0
  242. package/src/i18n/locales/he.json +137 -0
  243. package/src/i18n/locales/zh.json +137 -0
  244. package/src/i18n/rtl.css +183 -0
  245. package/src/index.js +82 -62
  246. package/src/loader.js +103 -0
  247. package/src/setupTests.js +5 -5
  248. package/tsconfig.json +51 -0
  249. package/types/charts.d.ts +569 -0
  250. package/types/components.d.ts +441 -0
  251. package/types/config.d.ts +199 -0
  252. package/types/index.d.ts +353 -0
package/src/cli/hmr.js ADDED
@@ -0,0 +1,456 @@
1
+ /**
2
+ * @fileoverview Hot Module Replacement (HMR) engine for Visualify CLI
3
+ * @module cli/hmr
4
+ *
5
+ * Provides file watching, change detection, and WebSocket communication
6
+ * for instant feedback during development without full page reloads.
7
+ */
8
+
9
+ const chokidar = require('chokidar');
10
+ const path = require('path');
11
+ const fs = require('fs').promises;
12
+ const logger = require('./utils/logger');
13
+ const { loadConfig } = require('./utils/config');
14
+
15
+ /**
16
+ * Default HMR configuration
17
+ * @readonly
18
+ * @type {Object}
19
+ */
20
+ const DEFAULT_HMR_CONFIG = {
21
+ enabled: true,
22
+ debounceMs: 300,
23
+ watchPatterns: ['visualify.json', '**/*.json'],
24
+ ignoredPatterns: [
25
+ '**/node_modules/**',
26
+ '**/dist/**',
27
+ '**/build/**',
28
+ '**/.git/**',
29
+ '**/coverage/**',
30
+ ],
31
+ };
32
+
33
+ /**
34
+ * HMR Engine class - manages file watching and WebSocket communication
35
+ */
36
+ class HMREngine {
37
+ /**
38
+ * Create an HMR engine instance
39
+ * @param {Object} options - Configuration options
40
+ * @param {string} options.rootDir - Root directory to watch
41
+ * @param {number} [options.debounceMs=300] - Debounce time in milliseconds
42
+ * @param {string[]} [options.watchPatterns] - File patterns to watch
43
+ * @param {string[]} [options.ignoredPatterns] - Patterns to ignore
44
+ */
45
+ constructor(options = {}) {
46
+ this.rootDir = options.rootDir || process.cwd();
47
+ this.config = {
48
+ ...DEFAULT_HMR_CONFIG,
49
+ ...options,
50
+ };
51
+
52
+ this.watcher = null;
53
+ this.clients = new Set();
54
+ this.pendingChanges = new Map();
55
+ this.debounceTimers = new Map();
56
+ this.isRunning = false;
57
+ this.currentConfig = null;
58
+
59
+ // Bind methods to preserve context
60
+ this.handleFileChange = this.handleFileChange.bind(this);
61
+ this.handleFileAdd = this.handleFileAdd.bind(this);
62
+ this.handleFileUnlink = this.handleFileUnlink.bind(this);
63
+ this.handleError = this.handleError.bind(this);
64
+ }
65
+
66
+ /**
67
+ * Start the HMR engine
68
+ * @returns {Promise<void>}
69
+ */
70
+ async start() {
71
+ if (this.isRunning) {
72
+ logger.warn('HMR engine is already running');
73
+ return;
74
+ }
75
+
76
+ logger.debug('Starting HMR engine...');
77
+
78
+ try {
79
+ // Load initial config
80
+ this.currentConfig = await loadConfig();
81
+
82
+ // Initialize file watcher
83
+ await this.initializeWatcher();
84
+
85
+ this.isRunning = true;
86
+ logger.success('HMR engine started');
87
+ } catch (err) {
88
+ logger.error('Failed to start HMR engine:', err.message);
89
+ throw err;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Stop the HMR engine and cleanup resources
95
+ * @returns {Promise<void>}
96
+ */
97
+ async stop() {
98
+ if (!this.isRunning) {
99
+ return;
100
+ }
101
+
102
+ logger.debug('Stopping HMR engine...');
103
+
104
+ // Clear all pending debounce timers
105
+ for (const [filePath, timer] of this.debounceTimers) {
106
+ clearTimeout(timer);
107
+ logger.debug(`Cleared debounce timer for: ${filePath}`);
108
+ }
109
+ this.debounceTimers.clear();
110
+ this.pendingChanges.clear();
111
+
112
+ // Close file watcher
113
+ if (this.watcher) {
114
+ await this.watcher.close();
115
+ this.watcher = null;
116
+ logger.debug('File watcher closed');
117
+ }
118
+
119
+ // Close all client connections
120
+ this.clients.clear();
121
+
122
+ this.isRunning = false;
123
+ logger.success('HMR engine stopped');
124
+ }
125
+
126
+ /**
127
+ * Initialize the file watcher
128
+ * @private
129
+ * @returns {Promise<void>}
130
+ */
131
+ async initializeWatcher() {
132
+ const watchPaths = this.config.watchPatterns.map((pattern) =>
133
+ path.resolve(this.rootDir, pattern),
134
+ );
135
+
136
+ logger.debug('Watching patterns:', watchPaths);
137
+ logger.debug('Ignored patterns:', this.config.ignoredPatterns);
138
+
139
+ this.watcher = chokidar.watch(watchPaths, {
140
+ ignored: this.config.ignoredPatterns,
141
+ persistent: true,
142
+ ignoreInitial: true,
143
+ awaitWriteFinish: {
144
+ stabilityThreshold: 100,
145
+ pollInterval: 100,
146
+ },
147
+ });
148
+
149
+ // Set up event handlers
150
+ this.watcher
151
+ .on('change', this.handleFileChange)
152
+ .on('add', this.handleFileAdd)
153
+ .on('unlink', this.handleFileUnlink)
154
+ .on('error', this.handleError);
155
+
156
+ // Wait for watcher to be ready
157
+ await new Promise((resolve, reject) => {
158
+ this.watcher.once('ready', resolve);
159
+ this.watcher.once('error', reject);
160
+ });
161
+
162
+ logger.debug('File watcher is ready');
163
+ }
164
+
165
+ /**
166
+ * Handle file change event
167
+ * @private
168
+ * @param {string} filePath - Path to the changed file
169
+ */
170
+ async handleFileChange(filePath) {
171
+ logger.debug(`File changed: ${filePath}`);
172
+ this.debounceChange(filePath, 'change');
173
+ }
174
+
175
+ /**
176
+ * Handle file add event
177
+ * @private
178
+ * @param {string} filePath - Path to the added file
179
+ */
180
+ async handleFileAdd(filePath) {
181
+ logger.debug(`File added: ${filePath}`);
182
+ this.debounceChange(filePath, 'add');
183
+ }
184
+
185
+ /**
186
+ * Handle file unlink event
187
+ * @private
188
+ * @param {string} filePath - Path to the removed file
189
+ */
190
+ async handleFileUnlink(filePath) {
191
+ logger.debug(`File removed: ${filePath}`);
192
+ this.debounceChange(filePath, 'unlink');
193
+ }
194
+
195
+ /**
196
+ * Handle watcher error
197
+ * @private
198
+ * @param {Error} err - The error object
199
+ */
200
+ handleError(err) {
201
+ logger.error('HMR watcher error:', err.message);
202
+ this.broadcast({
203
+ type: 'error',
204
+ message: `Watcher error: ${err.message}`,
205
+ });
206
+ }
207
+
208
+ /**
209
+ * Debounce file changes to avoid rapid updates
210
+ * @private
211
+ * @param {string} filePath - Path to the changed file
212
+ * @param {string} changeType - Type of change (change, add, unlink)
213
+ */
214
+ debounceChange(filePath, changeType) {
215
+ // Clear existing timer for this file
216
+ const existingTimer = this.debounceTimers.get(filePath);
217
+ if (existingTimer) {
218
+ clearTimeout(existingTimer);
219
+ }
220
+
221
+ // Set new timer
222
+ const timer = setTimeout(async () => {
223
+ this.debounceTimers.delete(filePath);
224
+ await this.processChange(filePath, changeType);
225
+ }, this.config.debounceMs);
226
+
227
+ this.debounceTimers.set(filePath, timer);
228
+ }
229
+
230
+ /**
231
+ * Process a debounced file change
232
+ * @private
233
+ * @param {string} filePath - Path to the changed file
234
+ * @param {string} changeType - Type of change
235
+ */
236
+ async processChange(filePath, changeType) {
237
+ try {
238
+ // Check if it's the main config file
239
+ const isMainConfig = path.basename(filePath) === 'visualify.json';
240
+
241
+ if (isMainConfig) {
242
+ await this.handleMainConfigChange(filePath, changeType);
243
+ } else {
244
+ await this.handleComponentChange(filePath, changeType);
245
+ }
246
+ } catch (err) {
247
+ logger.error(`Error processing change for ${filePath}:`, err.message);
248
+ this.broadcast({
249
+ type: 'error',
250
+ file: filePath,
251
+ message: err.message,
252
+ details: err.stack,
253
+ });
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Handle changes to the main visualify.json config
259
+ * @private
260
+ * @param {string} filePath - Path to the config file
261
+ * @param {string} changeType - Type of change
262
+ */
263
+ async handleMainConfigChange(filePath, changeType) {
264
+ logger.info('Main config changed, reloading...');
265
+
266
+ if (changeType === 'unlink') {
267
+ this.broadcast({
268
+ type: 'error',
269
+ file: filePath,
270
+ message: 'Main configuration file was deleted',
271
+ });
272
+ return;
273
+ }
274
+
275
+ try {
276
+ // Validate and load new config
277
+ const newConfig = await loadConfig(filePath);
278
+
279
+ // Store previous config for comparison
280
+ const previousConfig = this.currentConfig;
281
+ this.currentConfig = newConfig;
282
+
283
+ // Broadcast update to all clients
284
+ this.broadcast({
285
+ type: 'update',
286
+ file: filePath,
287
+ configType: 'main',
288
+ config: newConfig,
289
+ previousConfig,
290
+ });
291
+
292
+ logger.success('Main config updated and broadcasted');
293
+ } catch (err) {
294
+ logger.error('Failed to reload main config:', err.message);
295
+ this.broadcast({
296
+ type: 'error',
297
+ file: filePath,
298
+ message: `Config error: ${err.message}`,
299
+ isSyntaxError: err instanceof SyntaxError,
300
+ });
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Handle changes to component JSON files
306
+ * @private
307
+ * @param {string} filePath - Path to the component file
308
+ * @param {string} changeType - Type of change
309
+ */
310
+ async handleComponentChange(filePath, changeType) {
311
+ logger.info(`Component file changed: ${path.basename(filePath)}`);
312
+
313
+ if (changeType === 'unlink') {
314
+ this.broadcast({
315
+ type: 'update',
316
+ file: filePath,
317
+ configType: 'component',
318
+ changeType: 'removed',
319
+ });
320
+ return;
321
+ }
322
+
323
+ try {
324
+ // Read and validate the component JSON
325
+ const content = await fs.readFile(filePath, 'utf-8');
326
+ const componentConfig = JSON.parse(content);
327
+
328
+ // Broadcast update to all clients
329
+ this.broadcast({
330
+ type: 'update',
331
+ file: filePath,
332
+ configType: 'component',
333
+ changeType: 'updated',
334
+ config: componentConfig,
335
+ });
336
+
337
+ logger.success(`Component ${path.basename(filePath)} updated`);
338
+ } catch (err) {
339
+ logger.error(`Failed to reload component ${filePath}:`, err.message);
340
+ this.broadcast({
341
+ type: 'error',
342
+ file: filePath,
343
+ message: `Component error: ${err.message}`,
344
+ isSyntaxError: err instanceof SyntaxError,
345
+ });
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Add a WebSocket client connection
351
+ * @param {WebSocket} client - The WebSocket client
352
+ */
353
+ addClient(client) {
354
+ this.clients.add(client);
355
+ logger.debug(`Client connected. Total clients: ${this.clients.size}`);
356
+
357
+ // Send initial ready message
358
+ this.sendToClient(client, {
359
+ type: 'connected',
360
+ message: 'HMR connected',
361
+ timestamp: Date.now(),
362
+ });
363
+
364
+ // Handle client disconnect
365
+ client.on('close', () => {
366
+ this.removeClient(client);
367
+ });
368
+
369
+ client.on('error', (err) => {
370
+ logger.debug('Client WebSocket error:', err.message);
371
+ this.removeClient(client);
372
+ });
373
+ }
374
+
375
+ /**
376
+ * Remove a WebSocket client connection
377
+ * @param {WebSocket} client - The WebSocket client
378
+ */
379
+ removeClient(client) {
380
+ this.clients.delete(client);
381
+ logger.debug(`Client disconnected. Total clients: ${this.clients.size}`);
382
+ }
383
+
384
+ /**
385
+ * Send a message to a specific client
386
+ * @param {WebSocket} client - The WebSocket client
387
+ * @param {Object} message - The message to send
388
+ */
389
+ sendToClient(client, message) {
390
+ if (client.readyState === 1) {
391
+ // WebSocket.OPEN
392
+ try {
393
+ client.send(JSON.stringify(message));
394
+ } catch (err) {
395
+ logger.debug('Failed to send message to client:', err.message);
396
+ this.removeClient(client);
397
+ }
398
+ }
399
+ }
400
+
401
+ /**
402
+ * Broadcast a message to all connected clients
403
+ * @param {Object} message - The message to broadcast
404
+ */
405
+ broadcast(message) {
406
+ const messageStr = JSON.stringify({
407
+ ...message,
408
+ timestamp: Date.now(),
409
+ });
410
+
411
+ let sentCount = 0;
412
+ for (const client of this.clients) {
413
+ if (client.readyState === 1) {
414
+ // WebSocket.OPEN
415
+ try {
416
+ client.send(messageStr);
417
+ sentCount++;
418
+ } catch (err) {
419
+ logger.debug('Failed to broadcast to client:', err.message);
420
+ this.removeClient(client);
421
+ }
422
+ }
423
+ }
424
+
425
+ logger.debug(`Broadcasted message to ${sentCount} clients`);
426
+ }
427
+
428
+ /**
429
+ * Get current HMR status
430
+ * @returns {Object} Status information
431
+ */
432
+ getStatus() {
433
+ return {
434
+ isRunning: this.isRunning,
435
+ clientCount: this.clients.size,
436
+ pendingChanges: this.pendingChanges.size,
437
+ debounceTimers: this.debounceTimers.size,
438
+ watchPatterns: this.config.watchPatterns,
439
+ };
440
+ }
441
+ }
442
+
443
+ /**
444
+ * Create and configure an HMR engine instance
445
+ * @param {Object} options - Configuration options
446
+ * @returns {HMREngine} The configured HMR engine
447
+ */
448
+ function createHMREngine(options = {}) {
449
+ return new HMREngine(options);
450
+ }
451
+
452
+ module.exports = {
453
+ HMREngine,
454
+ createHMREngine,
455
+ DEFAULT_HMR_CONFIG,
456
+ };
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @fileoverview Visualify CLI entry point
5
+ * @module cli/index
6
+ *
7
+ * Visualify.js CLI - Unified documentation and data visualization platform
8
+ * Transforms the separate React visualization library and CLI tool into a unified platform.
9
+ */
10
+
11
+ const { Command } = require('commander');
12
+ const chalk = require('chalk');
13
+ const logger = require('./utils/logger');
14
+ const { createDevCommand } = require('./commands/dev');
15
+ const { createDocsCommand } = require('./commands/docs');
16
+ const { createPortalCommand } = require('./commands/portal');
17
+ const { createInitCommand } = require('./commands/init');
18
+ const { createEditCommand } = require('./commands/edit');
19
+
20
+ /**
21
+ * CLI version from package.json
22
+ * @readonly
23
+ */
24
+ const VERSION = '3.0.0-1.dev';
25
+
26
+ /**
27
+ * Program name
28
+ * @readonly
29
+ */
30
+ const PROGRAM_NAME = 'visualify';
31
+
32
+ /**
33
+ * Program description
34
+ * @readonly
35
+ */
36
+ const PROGRAM_DESCRIPTION = 'Visualify.js - Documentation and Data Visualization Platform';
37
+
38
+ /**
39
+ * Create and configure the CLI program
40
+ * @returns {Command} The configured CLI program
41
+ */
42
+ function createProgram() {
43
+ const program = new Command();
44
+
45
+ program
46
+ .name(PROGRAM_NAME)
47
+ .description(PROGRAM_DESCRIPTION)
48
+ .version(VERSION, '-v, --version', 'Display version number')
49
+ .option('--verbose', 'Enable verbose logging')
50
+ .configureHelp({
51
+ sortSubcommands: true,
52
+ showGlobalOptions: true,
53
+ });
54
+
55
+ // Add global error handling
56
+ program.exitOverride();
57
+
58
+ // Add commands
59
+ program.addCommand(createDevCommand());
60
+ program.addCommand(createDocsCommand());
61
+ program.addCommand(createPortalCommand());
62
+ program.addCommand(createInitCommand());
63
+ program.addCommand(createEditCommand());
64
+
65
+ // Add help text
66
+ program.addHelpText(
67
+ 'beforeAll',
68
+ chalk.cyan(`
69
+ ╔══════════════════════════════════════════════════════════╗
70
+ ║ ║
71
+ ║ ${chalk.bold('Visualify.js')} - Documentation & Visualization Platform ║
72
+ ║ ║
73
+ ╚══════════════════════════════════════════════════════════╝
74
+ `)
75
+ );
76
+
77
+ program.addHelpText(
78
+ 'after',
79
+ `
80
+ ${chalk.bold('Examples:')}
81
+ $ ${PROGRAM_NAME} dev Start dev server (auto-detect mode)
82
+ $ ${PROGRAM_NAME} dev docs Start docs dev server
83
+ $ ${PROGRAM_NAME} docs build Build static documentation
84
+ $ ${PROGRAM_NAME} portal dev Start portal dev server
85
+ $ ${PROGRAM_NAME} init my-project Initialize new project
86
+ $ ${PROGRAM_NAME} edit Open visual editor
87
+ $ ${PROGRAM_NAME} edit my-config.json Edit specific config file
88
+
89
+ ${chalk.bold('Configuration:')}
90
+ Create a ${chalk.cyan('visualify.json')} or ${chalk.cyan('.visualify.json')} file to configure:
91
+ {
92
+ "mode": "auto" | "docs" | "portal",
93
+ "port": 3000,
94
+ "host": "localhost"
95
+ }
96
+
97
+ ${chalk.dim('For more help, visit: https://visualify.pharmacy.arizona.edu')}
98
+ `
99
+ );
100
+
101
+ return program;
102
+ }
103
+
104
+ /**
105
+ * Main entry point
106
+ * @returns {Promise<void>}
107
+ */
108
+ async function main() {
109
+ const program = createProgram();
110
+
111
+ try {
112
+ // Parse global options before command execution
113
+ program.hook('preAction', (thisCommand) => {
114
+ const options = thisCommand.opts();
115
+ if (options.verbose) {
116
+ logger.enableVerbose();
117
+ logger.debug('Verbose mode enabled');
118
+ }
119
+ });
120
+
121
+ await program.parseAsync(process.argv);
122
+ } catch (err) {
123
+ if (err.code === 'commander.help') {
124
+ process.exit(0);
125
+ }
126
+ if (err.code === 'commander.version') {
127
+ process.exit(0);
128
+ }
129
+ if (err.code === 'commander.helpDisplayed') {
130
+ process.exit(0);
131
+ }
132
+ if (err.code === 'commander.unknownOption') {
133
+ logger.error(err.message);
134
+ process.exit(1);
135
+ }
136
+ if (err.code === 'commander.missingArgument') {
137
+ logger.error(err.message);
138
+ process.exit(1);
139
+ }
140
+
141
+ // Unexpected error
142
+ logger.error('Unexpected error:', err.message);
143
+ logger.debug('Stack trace:', err.stack);
144
+ process.exit(1);
145
+ }
146
+ }
147
+
148
+ // Handle uncaught errors
149
+ process.on('uncaughtException', (err) => {
150
+ logger.error('Uncaught exception:', err.message);
151
+ logger.debug('Stack trace:', err.stack);
152
+ process.exit(1);
153
+ });
154
+
155
+ process.on('unhandledRejection', (reason, promise) => {
156
+ logger.error('Unhandled rejection at:', promise);
157
+ logger.error('Reason:', reason);
158
+ process.exit(1);
159
+ });
160
+
161
+ // Cleanup on exit
162
+ process.on('SIGINT', () => {
163
+ logger.newline();
164
+ logger.info('Interrupted by user');
165
+ process.exit(0);
166
+ });
167
+
168
+ process.on('SIGTERM', () => {
169
+ logger.info('Received SIGTERM, shutting down...');
170
+ process.exit(0);
171
+ });
172
+
173
+ // Run the CLI
174
+ main();
175
+
176
+ module.exports = {
177
+ createProgram,
178
+ VERSION,
179
+ PROGRAM_NAME,
180
+ };