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.
- 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 -241
- 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 -298
- package/docs/docs/_404.md +51 -51
- package/docs/docs/_coverpage.md +11 -11
- package/docs/docs/_sidebar.md +54 -44
- 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 -121
- 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 -9
- package/docs/docs/more-pages.md +23 -23
- package/docs/docs/quickstart.md +148 -124
- 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 -587
- 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 -147
- 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
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loading with merging logic
|
|
3
|
+
* @module loader
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import defaults from './defaults';
|
|
9
|
+
import { validateConfig } from './validator';
|
|
10
|
+
import {
|
|
11
|
+
VisualifyConfig,
|
|
12
|
+
PartialConfig,
|
|
13
|
+
LoadConfigOptions,
|
|
14
|
+
ConfigCacheEntry,
|
|
15
|
+
CacheStats,
|
|
16
|
+
ConfigWatcher,
|
|
17
|
+
ConfigReloadCallback,
|
|
18
|
+
} from '../../types';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Cache for parsed configurations to avoid re-reading files
|
|
22
|
+
*/
|
|
23
|
+
const configCache = new Map<string, ConfigCacheEntry>();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* File watchers for development mode
|
|
27
|
+
*/
|
|
28
|
+
const fileWatchers = new Map<string, fs.FSWatcher>();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Callbacks to invoke on config reload
|
|
32
|
+
*/
|
|
33
|
+
const reloadCallbacks = new Map<string, Set<ConfigReloadCallback>>();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Configuration file names to search for
|
|
37
|
+
*/
|
|
38
|
+
const CONFIG_FILES: readonly string[] = ['visualify.json', '.visualify.json'];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Environment variable prefix for Visualify config
|
|
42
|
+
*/
|
|
43
|
+
const ENV_PREFIX = 'VISUALIFY_';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Error thrown when configuration loading fails
|
|
47
|
+
*/
|
|
48
|
+
class ConfigLoadError extends Error {
|
|
49
|
+
code: string;
|
|
50
|
+
filepath?: string;
|
|
51
|
+
originalError?: Error;
|
|
52
|
+
|
|
53
|
+
constructor(message: string, code: string, filepath?: string, originalError?: Error) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.name = 'ConfigLoadError';
|
|
56
|
+
this.code = code;
|
|
57
|
+
this.filepath = filepath;
|
|
58
|
+
this.originalError = originalError;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Find configuration file in the given directory
|
|
64
|
+
* @param cwd - Current working directory to search
|
|
65
|
+
* @returns Path to config file or null if not found
|
|
66
|
+
*/
|
|
67
|
+
function findConfigFile(cwd: string): string | null {
|
|
68
|
+
for (const filename of CONFIG_FILES) {
|
|
69
|
+
const filepath = path.join(cwd, filename);
|
|
70
|
+
if (fs.existsSync(filepath)) {
|
|
71
|
+
return filepath;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Read and parse a JSON file
|
|
79
|
+
* @param filepath - Path to the JSON file
|
|
80
|
+
* @returns Parsed JSON content
|
|
81
|
+
* @throws ConfigLoadError if file cannot be read or parsed
|
|
82
|
+
*/
|
|
83
|
+
function readJsonFile(filepath: string): unknown {
|
|
84
|
+
const content = fs.readFileSync(filepath, 'utf-8');
|
|
85
|
+
try {
|
|
86
|
+
return JSON.parse(content);
|
|
87
|
+
} catch (parseError) {
|
|
88
|
+
throw new ConfigLoadError(
|
|
89
|
+
`Invalid JSON in ${filepath}: ${(parseError as Error).message}`,
|
|
90
|
+
'INVALID_JSON',
|
|
91
|
+
filepath,
|
|
92
|
+
parseError as Error
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Load configuration from file
|
|
99
|
+
* @param cwd - Current working directory
|
|
100
|
+
* @returns Parsed configuration or null if no file found
|
|
101
|
+
*/
|
|
102
|
+
function loadConfigFile(cwd: string): PartialConfig | null {
|
|
103
|
+
const filepath = findConfigFile(cwd);
|
|
104
|
+
|
|
105
|
+
if (!filepath) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check cache first
|
|
110
|
+
const cached = configCache.get(filepath);
|
|
111
|
+
if (cached) {
|
|
112
|
+
const stats = fs.statSync(filepath);
|
|
113
|
+
if (cached.mtime >= stats.mtime) {
|
|
114
|
+
return cached.config as PartialConfig;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const config = readJsonFile(filepath) as PartialConfig;
|
|
119
|
+
|
|
120
|
+
// Update cache
|
|
121
|
+
const stats = fs.statSync(filepath);
|
|
122
|
+
configCache.set(filepath, {
|
|
123
|
+
config: config as VisualifyConfig,
|
|
124
|
+
mtime: stats.mtime,
|
|
125
|
+
filepath,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return config;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Parse environment variable value
|
|
133
|
+
* @param value - Raw environment variable value
|
|
134
|
+
* @returns Parsed value (boolean, number, null, or string)
|
|
135
|
+
*/
|
|
136
|
+
function parseEnvValue(value: string): unknown {
|
|
137
|
+
if (value === 'true') return true;
|
|
138
|
+
if (value === 'false') return false;
|
|
139
|
+
if (value === 'null') return null;
|
|
140
|
+
if (value !== '' && !isNaN(Number(value))) return Number(value);
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Load configuration from environment variables
|
|
146
|
+
* Environment variables should be prefixed with VISUALIFY_
|
|
147
|
+
* Nested properties use double underscore: VISUALIFY_DOCS__THEME
|
|
148
|
+
* @returns Configuration from environment
|
|
149
|
+
*/
|
|
150
|
+
function loadEnvironmentConfig(): PartialConfig {
|
|
151
|
+
const config: PartialConfig = {};
|
|
152
|
+
|
|
153
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
154
|
+
if (!key || !value || !key.startsWith(ENV_PREFIX)) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Remove prefix and split by double underscore for nesting
|
|
159
|
+
const pathParts = key
|
|
160
|
+
.slice(ENV_PREFIX.length)
|
|
161
|
+
.toLowerCase()
|
|
162
|
+
.split('__');
|
|
163
|
+
|
|
164
|
+
// Parse value
|
|
165
|
+
const parsedValue = parseEnvValue(value);
|
|
166
|
+
|
|
167
|
+
// Build nested object
|
|
168
|
+
pathParts.reduce((acc: Record<string, unknown>, part, index) => {
|
|
169
|
+
if (index === pathParts.length - 1) {
|
|
170
|
+
acc[part] = parsedValue;
|
|
171
|
+
return acc;
|
|
172
|
+
}
|
|
173
|
+
if (!acc[part]) acc[part] = {};
|
|
174
|
+
return acc[part] as Record<string, unknown>;
|
|
175
|
+
}, config as Record<string, unknown>);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return config;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Check if value is a plain object
|
|
183
|
+
* @param value - Value to check
|
|
184
|
+
* @returns True if value is a plain object
|
|
185
|
+
*/
|
|
186
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
187
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Deep merge objects (arrays are concatenated, not merged)
|
|
192
|
+
* @param target - Target object
|
|
193
|
+
* @param sources - Source objects to merge
|
|
194
|
+
* @returns Merged object
|
|
195
|
+
*/
|
|
196
|
+
function deepMerge<T extends Record<string, unknown>>(
|
|
197
|
+
target: T,
|
|
198
|
+
...sources: Array<Partial<T> | undefined | null>
|
|
199
|
+
): T {
|
|
200
|
+
if (!sources.length) return target;
|
|
201
|
+
|
|
202
|
+
const source = sources.shift();
|
|
203
|
+
if (source === undefined || source === null) {
|
|
204
|
+
return deepMerge(target, ...sources);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!isPlainObject(target) || !isPlainObject(source)) {
|
|
208
|
+
return deepMerge(target, ...sources);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for (const key of Object.keys(source)) {
|
|
212
|
+
const sourceValue = source[key];
|
|
213
|
+
if (sourceValue === undefined) continue;
|
|
214
|
+
|
|
215
|
+
const targetValue = target[key];
|
|
216
|
+
|
|
217
|
+
if (Array.isArray(sourceValue)) {
|
|
218
|
+
(target as Record<string, unknown>)[key] = Array.isArray(targetValue)
|
|
219
|
+
? [...targetValue, ...sourceValue]
|
|
220
|
+
: [...sourceValue];
|
|
221
|
+
} else if (isPlainObject(sourceValue)) {
|
|
222
|
+
(target as Record<string, unknown>)[key] = deepMerge(
|
|
223
|
+
isPlainObject(targetValue) ? targetValue : {},
|
|
224
|
+
sourceValue
|
|
225
|
+
);
|
|
226
|
+
} else {
|
|
227
|
+
(target as Record<string, unknown>)[key] = sourceValue;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return deepMerge(target, ...sources);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Normalize dot-notation keys to nested objects
|
|
236
|
+
* @param overrides - Object with potential dot-notation keys
|
|
237
|
+
* @returns Normalized object with nested structure
|
|
238
|
+
*/
|
|
239
|
+
function normalizeOverrides(
|
|
240
|
+
overrides: Record<string, unknown>
|
|
241
|
+
): Record<string, unknown> {
|
|
242
|
+
const normalized: Record<string, unknown> = {};
|
|
243
|
+
|
|
244
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
245
|
+
if (key.includes('.')) {
|
|
246
|
+
const parts = key.split('.');
|
|
247
|
+
parts.reduce((acc, part, index) => {
|
|
248
|
+
if (index === parts.length - 1) {
|
|
249
|
+
acc[part] = value;
|
|
250
|
+
return acc;
|
|
251
|
+
}
|
|
252
|
+
if (!acc[part]) acc[part] = {};
|
|
253
|
+
return acc[part] as Record<string, unknown>;
|
|
254
|
+
}, normalized);
|
|
255
|
+
} else {
|
|
256
|
+
normalized[key] = value;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return normalized;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Load and merge configuration from all sources
|
|
265
|
+
* Merging order: defaults < file < environment < CLI args
|
|
266
|
+
*
|
|
267
|
+
* @param cwd - Current working directory (default: process.cwd())
|
|
268
|
+
* @param overrides - CLI argument overrides
|
|
269
|
+
* @param options - Loading options
|
|
270
|
+
* @returns Merged configuration object
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* ```typescript
|
|
274
|
+
* // Load with defaults
|
|
275
|
+
* const config = await loadConfig();
|
|
276
|
+
*
|
|
277
|
+
* // Load with CLI overrides
|
|
278
|
+
* const config = await loadConfig(process.cwd(), {
|
|
279
|
+
* mode: 'docs',
|
|
280
|
+
* 'docs.theme': 'modern'
|
|
281
|
+
* });
|
|
282
|
+
*
|
|
283
|
+
* // Load without validation (for partial configs)
|
|
284
|
+
* const config = await loadConfig(cwd, {}, { validate: false });
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
async function loadConfig(
|
|
288
|
+
cwd: string = process.cwd(),
|
|
289
|
+
overrides: Record<string, unknown> = {},
|
|
290
|
+
options: LoadConfigOptions = {}
|
|
291
|
+
): Promise<VisualifyConfig> {
|
|
292
|
+
const {
|
|
293
|
+
validate = true,
|
|
294
|
+
skipFile = false,
|
|
295
|
+
skipEnvironment = false,
|
|
296
|
+
} = options;
|
|
297
|
+
|
|
298
|
+
// Start with defaults
|
|
299
|
+
let config: Record<string, unknown> = { ...defaults };
|
|
300
|
+
|
|
301
|
+
// Merge with file config
|
|
302
|
+
if (!skipFile) {
|
|
303
|
+
const fileConfig = loadConfigFile(cwd);
|
|
304
|
+
if (fileConfig) {
|
|
305
|
+
config = deepMerge(config, fileConfig);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Merge with environment config
|
|
310
|
+
if (!skipEnvironment) {
|
|
311
|
+
const envConfig = loadEnvironmentConfig();
|
|
312
|
+
config = deepMerge(config, envConfig);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Merge with CLI overrides
|
|
316
|
+
if (overrides && Object.keys(overrides).length > 0) {
|
|
317
|
+
const normalizedOverrides = normalizeOverrides(overrides);
|
|
318
|
+
config = deepMerge(config, normalizedOverrides);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Validate final config
|
|
322
|
+
if (validate) {
|
|
323
|
+
const validation = validateConfig(config, { source: 'merged' });
|
|
324
|
+
if (!validation.valid) {
|
|
325
|
+
const error = new ConfigLoadError(
|
|
326
|
+
validation.summary || 'Configuration validation failed',
|
|
327
|
+
'CONFIG_VALIDATION_ERROR'
|
|
328
|
+
);
|
|
329
|
+
// Attach errors to the error object for programmatic access
|
|
330
|
+
Object.defineProperty(error, 'errors', {
|
|
331
|
+
value: validation.errors,
|
|
332
|
+
enumerable: false,
|
|
333
|
+
writable: false,
|
|
334
|
+
configurable: false,
|
|
335
|
+
});
|
|
336
|
+
throw error;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return config as VisualifyConfig;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Watch configuration file for changes
|
|
345
|
+
* @param cwd - Current working directory (default: process.cwd())
|
|
346
|
+
* @param callback - Callback to invoke on change
|
|
347
|
+
* @returns Watcher control object with stop() method
|
|
348
|
+
*
|
|
349
|
+
* @example
|
|
350
|
+
* ```typescript
|
|
351
|
+
* const watcher = watchConfig(process.cwd(), (config, error) => {
|
|
352
|
+
* if (error) {
|
|
353
|
+
* console.error('Config reload error:', error);
|
|
354
|
+
* } else {
|
|
355
|
+
* console.log('Config reloaded:', config);
|
|
356
|
+
* }
|
|
357
|
+
* });
|
|
358
|
+
*
|
|
359
|
+
* // Stop watching
|
|
360
|
+
* watcher.stop();
|
|
361
|
+
* ```
|
|
362
|
+
*/
|
|
363
|
+
function watchConfig(
|
|
364
|
+
cwd: string = process.cwd(),
|
|
365
|
+
callback: ConfigReloadCallback
|
|
366
|
+
): ConfigWatcher {
|
|
367
|
+
const filepath = findConfigFile(cwd);
|
|
368
|
+
|
|
369
|
+
if (!filepath) {
|
|
370
|
+
throw new ConfigLoadError(
|
|
371
|
+
`No config file found in ${cwd}`,
|
|
372
|
+
'CONFIG_FILE_NOT_FOUND'
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Store callback
|
|
377
|
+
if (!reloadCallbacks.has(filepath)) {
|
|
378
|
+
reloadCallbacks.set(filepath, new Set());
|
|
379
|
+
}
|
|
380
|
+
reloadCallbacks.get(filepath)!.add(callback);
|
|
381
|
+
|
|
382
|
+
// Create watcher if not exists
|
|
383
|
+
if (!fileWatchers.has(filepath)) {
|
|
384
|
+
const watcher = fs.watch(filepath, (eventType) => {
|
|
385
|
+
if (eventType === 'change') {
|
|
386
|
+
// Clear cache for this file
|
|
387
|
+
configCache.delete(filepath);
|
|
388
|
+
|
|
389
|
+
// Reload config
|
|
390
|
+
loadConfig(cwd)
|
|
391
|
+
.then((newConfig) => {
|
|
392
|
+
const callbacks = reloadCallbacks.get(filepath);
|
|
393
|
+
if (callbacks) {
|
|
394
|
+
callbacks.forEach((cb) => {
|
|
395
|
+
try {
|
|
396
|
+
cb(newConfig, null);
|
|
397
|
+
} catch (err) {
|
|
398
|
+
console.error('Config reload callback error:', err);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
})
|
|
403
|
+
.catch((error) => {
|
|
404
|
+
const callbacks = reloadCallbacks.get(filepath);
|
|
405
|
+
if (callbacks) {
|
|
406
|
+
callbacks.forEach((cb) => cb(null, error as Error));
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
fileWatchers.set(filepath, watcher);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
stop: () => {
|
|
417
|
+
const callbacks = reloadCallbacks.get(filepath);
|
|
418
|
+
if (callbacks) {
|
|
419
|
+
callbacks.delete(callback);
|
|
420
|
+
if (callbacks.size === 0) {
|
|
421
|
+
// No more callbacks, stop watcher
|
|
422
|
+
const watcher = fileWatchers.get(filepath);
|
|
423
|
+
if (watcher) {
|
|
424
|
+
watcher.close();
|
|
425
|
+
fileWatchers.delete(filepath);
|
|
426
|
+
}
|
|
427
|
+
reloadCallbacks.delete(filepath);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
filepath,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Clear the configuration cache
|
|
437
|
+
*/
|
|
438
|
+
function clearCache(): void {
|
|
439
|
+
configCache.clear();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Get cache statistics
|
|
444
|
+
* @returns Cache statistics
|
|
445
|
+
*/
|
|
446
|
+
function getCacheStats(): CacheStats {
|
|
447
|
+
return {
|
|
448
|
+
size: configCache.size,
|
|
449
|
+
files: Array.from(configCache.keys()),
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export {
|
|
454
|
+
loadConfig,
|
|
455
|
+
watchConfig,
|
|
456
|
+
findConfigFile,
|
|
457
|
+
loadConfigFile,
|
|
458
|
+
loadEnvironmentConfig,
|
|
459
|
+
clearCache,
|
|
460
|
+
getCacheStats,
|
|
461
|
+
deepMerge,
|
|
462
|
+
ConfigLoadError,
|
|
463
|
+
CONFIG_FILES,
|
|
464
|
+
ENV_PREFIX,
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
export default {
|
|
468
|
+
loadConfig,
|
|
469
|
+
watchConfig,
|
|
470
|
+
findConfigFile,
|
|
471
|
+
loadConfigFile,
|
|
472
|
+
loadEnvironmentConfig,
|
|
473
|
+
clearCache,
|
|
474
|
+
getCacheStats,
|
|
475
|
+
deepMerge,
|
|
476
|
+
CONFIG_FILES,
|
|
477
|
+
ENV_PREFIX,
|
|
478
|
+
};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema for visualify.json validation
|
|
3
|
+
* @module schema
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* JSON Schema definition for Visualify.js configuration
|
|
8
|
+
* Used with AJV for validation
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import schema from './schema';
|
|
13
|
+
* import Ajv from 'ajv';
|
|
14
|
+
*
|
|
15
|
+
* const ajv = new Ajv();
|
|
16
|
+
* const validate = ajv.compile(schema);
|
|
17
|
+
* const valid = validate(config);
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
const schema = {
|
|
21
|
+
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
22
|
+
$id: 'https://visualify.js.org/schema/config.json',
|
|
23
|
+
title: 'Visualify Configuration',
|
|
24
|
+
description: 'Configuration schema for Visualify.js applications',
|
|
25
|
+
type: 'object' as const,
|
|
26
|
+
required: ['version'] as const,
|
|
27
|
+
additionalProperties: false,
|
|
28
|
+
properties: {
|
|
29
|
+
/**
|
|
30
|
+
* Schema version for compatibility checking
|
|
31
|
+
*/
|
|
32
|
+
version: {
|
|
33
|
+
type: 'string' as const,
|
|
34
|
+
enum: ['3.0.0'] as const,
|
|
35
|
+
description: 'Configuration schema version',
|
|
36
|
+
default: '3.0.0',
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Application mode
|
|
41
|
+
*/
|
|
42
|
+
mode: {
|
|
43
|
+
type: 'string' as const,
|
|
44
|
+
enum: ['docs', 'portal', 'hybrid', 'auto'] as const,
|
|
45
|
+
description: 'Application operating mode',
|
|
46
|
+
default: 'auto',
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Documentation configuration
|
|
51
|
+
*/
|
|
52
|
+
docs: {
|
|
53
|
+
type: 'object' as const,
|
|
54
|
+
description: 'Documentation settings',
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
properties: {
|
|
57
|
+
basePath: {
|
|
58
|
+
type: 'string' as const,
|
|
59
|
+
description: 'Base path for documentation files',
|
|
60
|
+
default: './docs',
|
|
61
|
+
minLength: 1,
|
|
62
|
+
},
|
|
63
|
+
theme: {
|
|
64
|
+
type: 'string' as const,
|
|
65
|
+
description: 'Documentation theme',
|
|
66
|
+
default: 'vue',
|
|
67
|
+
minLength: 1,
|
|
68
|
+
},
|
|
69
|
+
plugins: {
|
|
70
|
+
type: 'array' as const,
|
|
71
|
+
description: 'List of plugin modules to load',
|
|
72
|
+
default: [] as const,
|
|
73
|
+
items: {
|
|
74
|
+
type: 'string' as const,
|
|
75
|
+
minLength: 1,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Portal configuration
|
|
83
|
+
*/
|
|
84
|
+
portal: {
|
|
85
|
+
type: 'object' as const,
|
|
86
|
+
description: 'Portal settings',
|
|
87
|
+
additionalProperties: false,
|
|
88
|
+
properties: {
|
|
89
|
+
homepage: {
|
|
90
|
+
type: 'string' as const,
|
|
91
|
+
description: 'Homepage configuration file',
|
|
92
|
+
default: 'home.json',
|
|
93
|
+
minLength: 1,
|
|
94
|
+
},
|
|
95
|
+
theme: {
|
|
96
|
+
type: 'string' as const,
|
|
97
|
+
description: 'Portal theme',
|
|
98
|
+
default: 'modern',
|
|
99
|
+
minLength: 1,
|
|
100
|
+
},
|
|
101
|
+
dataSources: {
|
|
102
|
+
type: 'array' as const,
|
|
103
|
+
description: 'Data sources for the portal',
|
|
104
|
+
default: [] as const,
|
|
105
|
+
items: {
|
|
106
|
+
type: 'object' as const,
|
|
107
|
+
required: ['name', 'type'] as const,
|
|
108
|
+
properties: {
|
|
109
|
+
name: {
|
|
110
|
+
type: 'string' as const,
|
|
111
|
+
minLength: 1,
|
|
112
|
+
},
|
|
113
|
+
type: {
|
|
114
|
+
type: 'string' as const,
|
|
115
|
+
enum: ['json', 'csv', 'api', 'websocket'] as const,
|
|
116
|
+
},
|
|
117
|
+
url: {
|
|
118
|
+
type: 'string' as const,
|
|
119
|
+
format: 'uri',
|
|
120
|
+
},
|
|
121
|
+
path: {
|
|
122
|
+
type: 'string' as const,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
additionalProperties: true,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Visualization configuration
|
|
133
|
+
*/
|
|
134
|
+
visualization: {
|
|
135
|
+
type: 'object' as const,
|
|
136
|
+
description: 'Visualization settings',
|
|
137
|
+
additionalProperties: false,
|
|
138
|
+
properties: {
|
|
139
|
+
defaultLibrary: {
|
|
140
|
+
type: 'string' as const,
|
|
141
|
+
enum: ['echarts', 'plotly'] as const,
|
|
142
|
+
description: 'Default charting library',
|
|
143
|
+
default: 'echarts',
|
|
144
|
+
},
|
|
145
|
+
enable3D: {
|
|
146
|
+
type: 'boolean' as const,
|
|
147
|
+
description: 'Enable 3D visualization capabilities',
|
|
148
|
+
default: false,
|
|
149
|
+
},
|
|
150
|
+
webWorkers: {
|
|
151
|
+
type: 'boolean' as const,
|
|
152
|
+
description: 'Use Web Workers for rendering',
|
|
153
|
+
default: false,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Internationalization (i18n) configuration
|
|
160
|
+
*/
|
|
161
|
+
i18n: {
|
|
162
|
+
type: 'object' as const,
|
|
163
|
+
description: 'Internationalization settings',
|
|
164
|
+
additionalProperties: false,
|
|
165
|
+
properties: {
|
|
166
|
+
locale: {
|
|
167
|
+
type: 'string' as const,
|
|
168
|
+
description: 'Locale setting (auto for browser detection)',
|
|
169
|
+
default: 'auto',
|
|
170
|
+
enum: ['auto', 'en', 'zh', 'es', 'de', 'ar', 'he'] as const,
|
|
171
|
+
},
|
|
172
|
+
fallbackLocale: {
|
|
173
|
+
type: 'string' as const,
|
|
174
|
+
description: 'Fallback locale when translation is missing',
|
|
175
|
+
default: 'en',
|
|
176
|
+
enum: ['en', 'zh', 'es', 'de', 'ar', 'he'] as const,
|
|
177
|
+
},
|
|
178
|
+
enableRTL: {
|
|
179
|
+
type: 'boolean' as const,
|
|
180
|
+
description: 'Enable RTL (Right-to-Left) layout support',
|
|
181
|
+
default: true,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
} as const;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Type derived from the schema for compile-time validation
|
|
190
|
+
* This ensures the schema matches the VisualifyConfig interface
|
|
191
|
+
*/
|
|
192
|
+
export type SchemaType = typeof schema;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Schema default values extracted for programmatic use
|
|
196
|
+
*/
|
|
197
|
+
export const SCHEMA_DEFAULTS = {
|
|
198
|
+
version: '3.0.0',
|
|
199
|
+
mode: 'auto',
|
|
200
|
+
docs: {
|
|
201
|
+
basePath: './docs',
|
|
202
|
+
theme: 'vue',
|
|
203
|
+
plugins: [] as string[],
|
|
204
|
+
},
|
|
205
|
+
portal: {
|
|
206
|
+
homepage: 'home.json',
|
|
207
|
+
theme: 'modern',
|
|
208
|
+
dataSources: [] as Array<{
|
|
209
|
+
name: string;
|
|
210
|
+
type: 'json' | 'csv' | 'api' | 'websocket';
|
|
211
|
+
url?: string;
|
|
212
|
+
path?: string;
|
|
213
|
+
}>,
|
|
214
|
+
},
|
|
215
|
+
visualization: {
|
|
216
|
+
defaultLibrary: 'echarts' as const,
|
|
217
|
+
enable3D: false,
|
|
218
|
+
webWorkers: false,
|
|
219
|
+
},
|
|
220
|
+
i18n: {
|
|
221
|
+
locale: 'auto' as const,
|
|
222
|
+
fallbackLocale: 'en' as const,
|
|
223
|
+
enableRTL: true,
|
|
224
|
+
},
|
|
225
|
+
} as const;
|
|
226
|
+
|
|
227
|
+
export default schema;
|