visualifyjs 2.5.3 → 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.
- package/.claude/mem/TIMELINE.md +36 -0
- package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-external-script-solution.md +24 -0
- package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-session-summary.md +43 -0
- package/.claude/mem/notes/2026-02-11-cli-fix-editor-command-alias.md +26 -0
- package/.claude/mem/notes/2026-02-11-phase-3-developer-experience-completed.md +51 -0
- package/.claude/mem/notes/2026-02-11-phase-4-web-workers-implementation-complete.md +59 -0
- package/.claude/mem/notes/2026-02-11-visualify-phase-2-3d-visualization-complete.md +50 -0
- package/.claude/mem/notes/2026-02-11-visualify-phase-2-committed-ready-for-phase-3.md +33 -0
- package/.claude/mem/notes/2026-02-11-visualify-phase-3-complete-developer-experience.md +52 -0
- package/.claude/mem/notes/2026-02-11-visualify-repository-cleanup-complete.md +28 -0
- package/.claude/mem/notes/2026-02-18-codebase-cleanup-docsify-plugin-documentation.md +37 -0
- package/.claude/mem/notes/2026-02-19-css-grid-layout-fix-displaycontents-on-vcontroller.md +18 -0
- package/.claude/mem/notes/2026-02-19-docsify-plugin-fixes-latex-and-visualify-code-bloc.md +26 -0
- package/.claude/mem/notes/2026-02-19-page-mode-docs-update-decisions.md +23 -0
- package/.claude/mem/notes/2026-02-19-react-context-infinite-re-render-loop-fix-pattern.md +31 -0
- package/.claude/mem/notes/2026-02-19-version-300-bump-and-build-fixes.md +32 -0
- package/.claude/mem/notes/2026-02-19-visualify-build-deployment-architecture-bug-fixes.md +25 -0
- package/.claude/mem/notes/2026-02-19-visualify-dist-iife-self-contained-build-config.md +30 -0
- package/.claude/mem/notes/2026-02-19-visualify-infinite-loop-i18n-fixes.md +31 -0
- package/.claude/mem/notes/2026-02-19-visualify-v3-bundle-splitting-docs-restructuring.md +32 -0
- package/.claude/mem/notes/2026-02-20-bundle-externalization-final-architecture.md +29 -0
- package/.claude/mem/notes/2026-02-20-chromium-page-fix-unstable-keys-and-double-event-b.md +27 -0
- package/.claude/mem/notes/2026-02-20-console-cleanup-bundle-optimization-commit.md +20 -0
- package/.claude/mem/notes/2026-02-20-dotbio-dot-plot-fix-useeffect-dependency.md +21 -0
- package/.claude/mem/notes/2026-02-20-public-folder-cleanup-and-readme-rewrite.md +25 -0
- package/.claude/mem/notes/2026-02-20-v300-release-and-beta-channel-strategy.md +29 -0
- package/.claude/mem/notes/2026-02-20-visium-background-image-unknown-legend-fix.md +19 -0
- package/.claude/mem/notes/2026-02-20-visualify-cdn-loader-bundle-externalization.md +34 -0
- package/.claude/mem/sessions/session-2026-02-20-031524.md +54 -0
- package/.claude/settings.local.json +21 -0
- package/.github/workflows/static.yml.bak +51 -51
- package/.sisyphus/boulder.json +65 -0
- package/.sisyphus/plans/phase-4-advanced-optimizations.md +217 -0
- package/LICENSE +674 -674
- package/README.md +94 -59
- package/config-overrides.js +31 -31
- package/dist/stats.html +4949 -0
- package/dist/visualify-3d.esm.js +1 -0
- package/dist/visualify-3d.js +1 -0
- package/dist/visualify-core.esm.js +1 -0
- package/dist/visualify-core.js +1 -0
- package/dist/visualify-docs.esm.js +1 -0
- package/dist/visualify-docs.js +1 -0
- package/dist/visualify-loader.js +1 -0
- package/dist/visualify-pages.esm.js +1 -0
- package/dist/visualify-pages.js +1 -0
- package/dist/visualify-portal.esm.js +1 -0
- package/dist/visualify-portal.js +1 -0
- package/dist/visualify-shared.js +26571 -0
- package/dist/visualify.js +1 -188
- package/docs/CHANGELOG.md +148 -0
- package/docs/cli/commands.md +513 -0
- package/docs/configuration/visualify-json.md +474 -0
- package/docs/docs/3d-visualization.md +374 -0
- package/docs/docs/CLI.md +303 -34
- package/docs/docs/README.md +65 -65
- package/docs/docs/Rechart/bar.md +190 -190
- package/docs/docs/Rechart/funnel.md +241 -193
- package/docs/docs/Rechart/line.md +355 -355
- package/docs/docs/Rechart/pie.md +225 -225
- package/docs/docs/Rechart/radar.md +253 -253
- package/docs/docs/Rechart/scatter.md +298 -0
- package/docs/docs/_404.md +51 -51
- package/docs/docs/_coverpage.md +11 -11
- package/docs/docs/_sidebar.md +54 -43
- package/docs/docs/components/dotBio.md +87 -34
- package/docs/docs/components/echart.md +171 -82
- package/docs/docs/components/html.md +61 -34
- package/docs/docs/components/macaron.md +156 -145
- package/docs/docs/components/markdown.md +42 -0
- package/docs/docs/components/more.md +183 -142
- package/docs/docs/components/plotly.md +132 -62
- package/docs/docs/components/scatterL.md +171 -70
- package/docs/docs/components/visium.md +112 -57
- package/docs/docs/configuration.md +121 -123
- package/docs/docs/deploy.md +31 -31
- package/docs/docs/docsify-plugin.md +655 -0
- package/docs/docs/hmr.md +165 -0
- package/docs/docs/i18n.md +332 -0
- package/docs/docs/log.md +30 -1
- package/docs/docs/more-pages.md +23 -23
- package/docs/docs/quickstart.md +148 -119
- package/docs/docs/rechart-attributes.md +74 -74
- package/docs/docs/rechart-basic-usage.md +160 -162
- package/docs/docs/theme.md +5 -5
- package/docs/docs/typescript.md +306 -0
- package/docs/docs/visual-editor.md +359 -0
- package/docs/index.html +85 -71
- package/docs/manifest.json +23 -23
- package/docs/migration/v3-migration.md +392 -0
- package/docs/static/css/fluff-stuff.css +169 -169
- package/docs/static/css/font-awesome.min.css +4 -4
- package/docs/static/css/visualify.css +6 -25
- package/docs/static/js/3d-viz-examples.js +181 -0
- package/docs/static/js/configuration.js +630 -448
- package/docs/static/js/visualify.js +1 -188
- package/package.json +106 -84
- package/rollup.config.mjs +766 -76
- package/src/_css/404.css +115 -115
- package/src/_css/App.css +37 -37
- package/src/_css/autoSuggestion.css +26 -26
- package/src/_css/circular-progress.css +32 -32
- package/src/_css/index.css +36 -36
- package/src/_css/modern.css +350 -25
- package/src/_media/corner.svg +8 -8
- package/src/_media/download.svg +3 -3
- package/src/_media/logo.svg +14 -14
- package/src/_test/App.test.js +15 -15
- package/src/_utils/reportWebVitals.js +13 -13
- package/src/a11y/README.md +177 -0
- package/src/a11y/aria-labels.js +339 -0
- package/src/a11y/color-contrast.js +535 -0
- package/src/a11y/index.js +197 -0
- package/src/a11y/keyboard-nav.js +523 -0
- package/src/a11y/styles.css +165 -0
- package/src/cli/commands/dev.js +214 -0
- package/src/cli/commands/docs.js +521 -0
- package/src/cli/commands/edit.js +379 -0
- package/src/cli/commands/init.js +213 -0
- package/src/cli/commands/portal.js +236 -0
- package/src/cli/dev-server.js +530 -0
- package/src/cli/hmr.js +456 -0
- package/src/cli/index.js +180 -0
- package/src/cli/utils/config.js +207 -0
- package/src/cli/utils/logger.js +241 -0
- package/src/config/defaults.ts +122 -0
- package/src/config/index.ts +72 -0
- package/src/config/loader.ts +478 -0
- package/src/config/schema.ts +227 -0
- package/src/config/validator.ts +337 -0
- package/src/core/appContext.js +34 -27
- package/src/core/components/Bar.js +383 -0
- package/src/core/components/Bar3D.js +473 -0
- package/src/core/components/LargeDatasetChart.js +296 -0
- package/src/core/components/Line3D.js +310 -0
- package/src/core/components/Scatter.js +392 -188
- package/src/core/components/Scatter3D.js +455 -0
- package/src/core/components/ScatterBio.js +601 -572
- package/src/core/components/Surface3D.js +326 -0
- package/src/core/components/ThreeCustom.js +648 -0
- package/src/core/components/ThreeScene.js +459 -0
- package/src/core/components/VisiumPlot.js +191 -165
- package/src/core/components/browser.js +42 -42
- package/src/core/components/dotplot.js +413 -413
- package/src/core/components/html.js +29 -29
- package/src/core/components/list.js +178 -178
- package/src/core/components/macaron.js +206 -201
- package/src/core/components/markdown.js +56 -56
- package/src/core/components/parser.scatterBio.js +582 -579
- package/src/core/components/ratio.js +82 -80
- package/src/core/components/scatterL.js +206 -173
- package/src/core/components/searchbar.js +156 -131
- package/src/core/components/selection.js +310 -193
- package/src/core/components/timeline.js +236 -281
- package/src/core/components/visium.js +114 -97
- package/src/core/data-processor.js +413 -0
- package/src/core/fetch/condfetch.js +82 -82
- package/src/core/fetch/fetch.js +92 -92
- package/src/core/fetch/json.js +29 -29
- package/src/core/fetch/vfetch.js +42 -42
- package/src/core/hmr-client.js +724 -0
- package/src/core/liveEditor.js +44 -44
- package/src/core/modules/codeEditorWithPreview.js +104 -104
- package/src/core/modules/echarts/common.js +20 -20
- package/src/core/modules/echarts/gl.js +228 -0
- package/src/core/modules/echarts/presetHandler.js +41 -41
- package/src/core/modules/echarts/presets/esodev.chromium.js +172 -172
- package/src/core/modules/echarts/presets/esodev.codex.js +130 -130
- package/src/core/modules/echarts/presets/esodev.visium.js +123 -123
- package/src/core/modules/echarts/presets/mmtrbc.js +186 -186
- package/src/core/modules/echarts.js +70 -71
- package/src/core/modules/echartsUtils.js +43 -43
- package/src/core/modules/echartswitcher.js +227 -152
- package/src/core/modules/replotly/presetHandler.js +24 -24
- package/src/core/modules/replotly/presets/minimum.js +18 -18
- package/src/core/modules/replotly/presets/mmtrbc.dot.js +114 -114
- package/src/core/modules/replotly/presets/mmtrbc.violin.js +100 -100
- package/src/core/modules/replotly.js +74 -71
- package/src/core/modules/threejs/Camera.js +373 -0
- package/src/core/modules/threejs/Lighting.js +459 -0
- package/src/core/modules/threejs/Renderer.js +364 -0
- package/src/core/modules/threejs/Scene.js +266 -0
- package/src/core/modules/threejs/index.js +155 -0
- package/src/core/pages/404.js +50 -50
- package/src/core/pages/error.js +27 -27
- package/src/core/pages/jsonPage.js +62 -62
- package/src/core/pages/loading.js +44 -44
- package/src/core/parser/echart.data.js +204 -183
- package/src/core/parser/echart.features.js +125 -125
- package/src/core/parser/echart.general.js +147 -143
- package/src/core/parser/echart.hilbert.js +57 -57
- package/src/core/parser/echart.parser.js +210 -210
- package/src/core/parser/echart.series.js +67 -67
- package/src/core/parser/echart.types.js +76 -76
- package/src/core/parser/plotly.config.js +10 -10
- package/src/core/parser/plotly.data.js +132 -132
- package/src/core/parser/plotly.layout.js +9 -9
- package/src/core/parser/plotly.violin.js +18 -18
- package/src/core/recharts.js +361 -62
- package/src/core/router/alias.js +49 -49
- package/src/core/router/jsonRouter.js +31 -31
- package/src/core/themes/modern.js +32 -32
- package/src/core/themes/themeSelector.js +33 -33
- package/src/core/visualify.js +213 -47
- package/src/core/widgets/circularProgress.js +23 -23
- package/src/core/widgets/controller.js +116 -83
- package/src/core/widgets/errorBoundary.js +36 -36
- package/src/core/widgets/footer.js +185 -177
- package/src/core/widgets/header.js +238 -234
- package/src/core/widgets/layout/Grid.js +31 -31
- package/src/core/widgets/layout.js +36 -36
- package/src/core/widgets/mapping.js +56 -42
- package/src/core/workers/data-worker.js +349 -0
- package/src/core/workers/worker-pool.js +396 -0
- package/src/docsify/bundle.js +215 -0
- package/src/docsify/markdown.js +271 -0
- package/src/docsify/plugin.js +268 -0
- package/src/editor/README.md +172 -0
- package/src/editor/components/ChartBuilder.jsx +341 -0
- package/src/editor/components/ChartTypeSidebar.jsx +91 -0
- package/src/editor/components/Editor.jsx +367 -0
- package/src/editor/components/Preview.jsx +446 -0
- package/src/editor/components/PropertyPanel.jsx +468 -0
- package/src/editor/components/StatusBar.jsx +85 -0
- package/src/editor/context/EditorContext.js +248 -0
- package/src/editor/hooks/useDebounce.js +32 -0
- package/src/editor/index.js +315 -0
- package/src/editor/styles/editor.css +637 -0
- package/src/editor/utils/chartValidator.js +263 -0
- package/src/entries/charts3d.js +70 -0
- package/src/entries/core.js +78 -0
- package/src/entries/docs.js +154 -0
- package/src/entries/pages.js +93 -0
- package/src/entries/portal.js +204 -0
- package/src/entries/shared.js +50 -0
- package/src/i18n/formatters.js +455 -0
- package/src/i18n/index.js +169 -0
- package/src/i18n/locales/ar.json +137 -0
- package/src/i18n/locales/de.json +137 -0
- package/src/i18n/locales/en.json +137 -0
- package/src/i18n/locales/es.json +137 -0
- package/src/i18n/locales/he.json +137 -0
- package/src/i18n/locales/zh.json +137 -0
- package/src/i18n/rtl.css +183 -0
- package/src/index.js +82 -62
- package/src/loader.js +103 -0
- package/src/setupTests.js +5 -5
- package/tsconfig.json +51 -0
- package/types/charts.d.ts +569 -0
- package/types/components.d.ts +441 -0
- package/types/config.d.ts +199 -0
- 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
|
+
};
|
package/src/cli/index.js
ADDED
|
@@ -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
|
+
};
|