git-watchtower 1.9.20 → 1.10.1
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/README.md +61 -273
- package/bin/git-watchtower.js +273 -0
- package/package.json +1 -1
- package/src/cli/args.js +29 -0
- package/src/config/loader.js +8 -0
- package/src/config/schema.js +25 -0
- package/src/index.js +14 -0
- package/src/server/coordinator.js +519 -0
- package/src/server/web-ui.js +2478 -0
- package/src/server/web.js +537 -0
- package/src/ui/keybindings.js +2 -0
- package/src/ui/renderer.js +1 -0
package/bin/git-watchtower.js
CHANGED
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
* s - Toggle sound notifications
|
|
49
49
|
* c - Toggle casino mode (Vegas-style feedback)
|
|
50
50
|
* i - Show server info (port, connections)
|
|
51
|
+
* W - Toggle web dashboard (starts server + opens browser)
|
|
51
52
|
* 1-0 - Set visible branch count (1-10)
|
|
52
53
|
* +/- - Increase/decrease visible branches
|
|
53
54
|
* q/Esc - Quit (Esc also clears search)
|
|
@@ -98,6 +99,10 @@ const { getConfigPath, loadConfig: loadConfigFile, saveConfig: saveConfigFile, C
|
|
|
98
99
|
const { Store } = require('../src/state/store');
|
|
99
100
|
const store = new Store();
|
|
100
101
|
|
|
102
|
+
// Web dashboard server
|
|
103
|
+
const { WebDashboardServer } = require('../src/server/web');
|
|
104
|
+
const { Coordinator, Worker, generateProjectId, getActiveCoordinator, writeLock, removeLock } = require('../src/server/coordinator');
|
|
105
|
+
|
|
101
106
|
const PROJECT_ROOT = process.cwd();
|
|
102
107
|
|
|
103
108
|
function loadConfig() {
|
|
@@ -383,6 +388,15 @@ let sessionStartTime = null;
|
|
|
383
388
|
// Server process management (for command mode)
|
|
384
389
|
let serverProcess = null;
|
|
385
390
|
|
|
391
|
+
// Web dashboard
|
|
392
|
+
let WEB_ENABLED = false;
|
|
393
|
+
let WEB_PORT = 4000;
|
|
394
|
+
let webDashboard = null;
|
|
395
|
+
let coordinator = null;
|
|
396
|
+
let worker = null;
|
|
397
|
+
let projectId = null;
|
|
398
|
+
let webStateInterval = null;
|
|
399
|
+
|
|
386
400
|
function applyConfig(config) {
|
|
387
401
|
// Server settings
|
|
388
402
|
SERVER_MODE = config.server?.mode || 'static';
|
|
@@ -415,6 +429,12 @@ function applyConfig(config) {
|
|
|
415
429
|
if (casinoEnabled) {
|
|
416
430
|
casino.enable();
|
|
417
431
|
}
|
|
432
|
+
|
|
433
|
+
// Web dashboard
|
|
434
|
+
if (config.web) {
|
|
435
|
+
WEB_ENABLED = config.web.enabled === true;
|
|
436
|
+
WEB_PORT = config.web.port || 4000;
|
|
437
|
+
}
|
|
418
438
|
}
|
|
419
439
|
|
|
420
440
|
// Server log management
|
|
@@ -2775,6 +2795,20 @@ function setupKeyboardInput() {
|
|
|
2775
2795
|
break;
|
|
2776
2796
|
}
|
|
2777
2797
|
|
|
2798
|
+
case 'W': { // Toggle web dashboard
|
|
2799
|
+
if (webDashboard || worker) {
|
|
2800
|
+
const wasPort = stopWebDashboard();
|
|
2801
|
+
addLog(`Web dashboard stopped (was on :${wasPort})`, 'info');
|
|
2802
|
+
showFlash('Web dashboard stopped');
|
|
2803
|
+
render();
|
|
2804
|
+
} else {
|
|
2805
|
+
startWebDashboard(true).then(() => {
|
|
2806
|
+
showFlash(`Web dashboard on :${WEB_PORT}`);
|
|
2807
|
+
}).catch(() => {});
|
|
2808
|
+
}
|
|
2809
|
+
break;
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2778
2812
|
// Number keys to set visible branch count
|
|
2779
2813
|
case '1': case '2': case '3': case '4': case '5':
|
|
2780
2814
|
case '6': case '7': case '8': case '9':
|
|
@@ -2830,6 +2864,237 @@ function setupKeyboardInput() {
|
|
|
2830
2864
|
});
|
|
2831
2865
|
}
|
|
2832
2866
|
|
|
2867
|
+
// ============================================================================
|
|
2868
|
+
// Web Dashboard
|
|
2869
|
+
// ============================================================================
|
|
2870
|
+
|
|
2871
|
+
/**
|
|
2872
|
+
* Handle an action from the web dashboard.
|
|
2873
|
+
*/
|
|
2874
|
+
async function handleWebAction(action, payload) {
|
|
2875
|
+
const sendResult = (success, message) => {
|
|
2876
|
+
if (webDashboard) webDashboard.sendActionResult({ action, success, message });
|
|
2877
|
+
};
|
|
2878
|
+
|
|
2879
|
+
try {
|
|
2880
|
+
switch (action) {
|
|
2881
|
+
case 'switchBranch':
|
|
2882
|
+
if (payload.branch && payload.branch !== store.get('currentBranch')) {
|
|
2883
|
+
await switchToBranch(payload.branch);
|
|
2884
|
+
await pollGitChanges();
|
|
2885
|
+
sendResult(true, `Switched to ${payload.branch}`);
|
|
2886
|
+
}
|
|
2887
|
+
break;
|
|
2888
|
+
case 'pull':
|
|
2889
|
+
addLog('Force pulling (from web)...', 'update');
|
|
2890
|
+
render();
|
|
2891
|
+
await pullCurrentBranch();
|
|
2892
|
+
await pollGitChanges();
|
|
2893
|
+
sendResult(true, 'Pull complete');
|
|
2894
|
+
break;
|
|
2895
|
+
case 'fetch':
|
|
2896
|
+
addLog('Fetching all branches (from web)...', 'info');
|
|
2897
|
+
render();
|
|
2898
|
+
await pollGitChanges();
|
|
2899
|
+
await refreshAllSparklines();
|
|
2900
|
+
render();
|
|
2901
|
+
sendResult(true, 'Fetch complete');
|
|
2902
|
+
break;
|
|
2903
|
+
case 'undo': {
|
|
2904
|
+
const last = store.getLastSwitch();
|
|
2905
|
+
if (last) {
|
|
2906
|
+
await switchToBranch(last.from);
|
|
2907
|
+
store.popHistory();
|
|
2908
|
+
await pollGitChanges();
|
|
2909
|
+
sendResult(true, `Switched back to ${last.from}`);
|
|
2910
|
+
} else {
|
|
2911
|
+
sendResult(false, 'No switch to undo');
|
|
2912
|
+
}
|
|
2913
|
+
break;
|
|
2914
|
+
}
|
|
2915
|
+
case 'toggleSound': {
|
|
2916
|
+
const current = store.get('soundEnabled');
|
|
2917
|
+
store.setState({ soundEnabled: !current });
|
|
2918
|
+
render();
|
|
2919
|
+
sendResult(true, current ? 'Sound off' : 'Sound on');
|
|
2920
|
+
break;
|
|
2921
|
+
}
|
|
2922
|
+
case 'toggleCasino': {
|
|
2923
|
+
const casinoOn = store.get('casinoModeEnabled');
|
|
2924
|
+
store.setState({ casinoModeEnabled: !casinoOn });
|
|
2925
|
+
if (!casinoOn) casino.enable(); else casino.disable();
|
|
2926
|
+
render();
|
|
2927
|
+
sendResult(true, casinoOn ? 'Casino mode off' : 'Casino mode on');
|
|
2928
|
+
break;
|
|
2929
|
+
}
|
|
2930
|
+
case 'restartServer':
|
|
2931
|
+
if (SERVER_MODE === 'command') {
|
|
2932
|
+
addLog('Restarting server (from web)...', 'update');
|
|
2933
|
+
restartServerProcess();
|
|
2934
|
+
render();
|
|
2935
|
+
sendResult(true, 'Server restarting');
|
|
2936
|
+
} else {
|
|
2937
|
+
sendResult(false, 'Not in command mode');
|
|
2938
|
+
}
|
|
2939
|
+
break;
|
|
2940
|
+
case 'reloadBrowsers':
|
|
2941
|
+
if (SERVER_MODE === 'static') {
|
|
2942
|
+
addLog('Force reloading browsers (from web)...', 'update');
|
|
2943
|
+
notifyClients();
|
|
2944
|
+
render();
|
|
2945
|
+
sendResult(true, 'Browsers reloaded');
|
|
2946
|
+
} else {
|
|
2947
|
+
sendResult(false, 'Not in static mode');
|
|
2948
|
+
}
|
|
2949
|
+
break;
|
|
2950
|
+
case 'openBrowser':
|
|
2951
|
+
if (!NO_SERVER) {
|
|
2952
|
+
openInBrowser(`http://localhost:${PORT}`);
|
|
2953
|
+
sendResult(true, 'Opened in browser');
|
|
2954
|
+
}
|
|
2955
|
+
break;
|
|
2956
|
+
case 'preview':
|
|
2957
|
+
if (payload.branch) {
|
|
2958
|
+
const pvData = await getPreviewData(payload.branch);
|
|
2959
|
+
if (webDashboard) {
|
|
2960
|
+
webDashboard.sendPreview({ branch: payload.branch, ...pvData });
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
break;
|
|
2964
|
+
}
|
|
2965
|
+
} catch (err) {
|
|
2966
|
+
addLog(`Web action error: ${err.message}`, 'error');
|
|
2967
|
+
sendResult(false, err.message);
|
|
2968
|
+
render();
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
/**
|
|
2973
|
+
* Create and start the web dashboard, with coordinator support.
|
|
2974
|
+
* @param {boolean} openBrowser - Whether to auto-open the browser
|
|
2975
|
+
*/
|
|
2976
|
+
async function startWebDashboard(openBrowser) {
|
|
2977
|
+
projectId = generateProjectId(PROJECT_ROOT);
|
|
2978
|
+
|
|
2979
|
+
webDashboard = new WebDashboardServer({
|
|
2980
|
+
port: WEB_PORT,
|
|
2981
|
+
store,
|
|
2982
|
+
getExtraState: () => ({
|
|
2983
|
+
clientCount: clients.size,
|
|
2984
|
+
sessionStats: sessionStats.getStats(),
|
|
2985
|
+
}),
|
|
2986
|
+
onAction: handleWebAction,
|
|
2987
|
+
});
|
|
2988
|
+
webDashboard.setLocalProjectId(projectId);
|
|
2989
|
+
|
|
2990
|
+
// Resolve and cache the repo web URL for link building in the web UI
|
|
2991
|
+
getRemoteWebUrl(null).then((url) => {
|
|
2992
|
+
if (url) webDashboard.setRepoWebUrl(url);
|
|
2993
|
+
}).catch(() => {});
|
|
2994
|
+
|
|
2995
|
+
// Check if a coordinator is already running
|
|
2996
|
+
const existing = getActiveCoordinator();
|
|
2997
|
+
|
|
2998
|
+
if (existing) {
|
|
2999
|
+
// Connect as a worker to the existing coordinator
|
|
3000
|
+
try {
|
|
3001
|
+
worker = new Worker({
|
|
3002
|
+
id: projectId,
|
|
3003
|
+
projectPath: PROJECT_ROOT,
|
|
3004
|
+
projectName: path.basename(PROJECT_ROOT),
|
|
3005
|
+
socketPath: existing.socketPath,
|
|
3006
|
+
});
|
|
3007
|
+
worker.onCommand = (action, payload) => handleWebAction(action, payload);
|
|
3008
|
+
await worker.connect();
|
|
3009
|
+
addLog(`Joined web dashboard at http://localhost:${existing.port} (tab)`, 'success');
|
|
3010
|
+
|
|
3011
|
+
// Push state periodically
|
|
3012
|
+
webStateInterval = setInterval(() => {
|
|
3013
|
+
if (worker && worker.isConnected()) {
|
|
3014
|
+
worker.pushState(webDashboard.getSerializableState());
|
|
3015
|
+
} else {
|
|
3016
|
+
clearInterval(webStateInterval);
|
|
3017
|
+
webStateInterval = null;
|
|
3018
|
+
}
|
|
3019
|
+
}, 500);
|
|
3020
|
+
|
|
3021
|
+
// Don't start our own server — piggyback on the coordinator's.
|
|
3022
|
+
// Don't open browser either — the existing tab will show this project automatically.
|
|
3023
|
+
WEB_PORT = existing.port;
|
|
3024
|
+
render();
|
|
3025
|
+
return;
|
|
3026
|
+
} catch (err) {
|
|
3027
|
+
// Couldn't connect — become coordinator instead
|
|
3028
|
+
worker = null;
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
// We are the coordinator
|
|
3033
|
+
try {
|
|
3034
|
+
coordinator = new Coordinator();
|
|
3035
|
+
coordinator.onProjectsChanged = (projects) => {
|
|
3036
|
+
if (webDashboard) webDashboard.setProjects(projects);
|
|
3037
|
+
};
|
|
3038
|
+
coordinator.onActionRequest = (pId, action, payload) => {
|
|
3039
|
+
if (pId === projectId) {
|
|
3040
|
+
handleWebAction(action, payload);
|
|
3041
|
+
}
|
|
3042
|
+
};
|
|
3043
|
+
await coordinator.start();
|
|
3044
|
+
coordinator.registerLocal(projectId, PROJECT_ROOT, path.basename(PROJECT_ROOT), webDashboard.getSerializableState());
|
|
3045
|
+
|
|
3046
|
+
// Update coordinator with our latest state periodically
|
|
3047
|
+
webStateInterval = setInterval(() => {
|
|
3048
|
+
if (coordinator && webDashboard) {
|
|
3049
|
+
coordinator.updateLocal(projectId, webDashboard.getSerializableState());
|
|
3050
|
+
} else {
|
|
3051
|
+
clearInterval(webStateInterval);
|
|
3052
|
+
webStateInterval = null;
|
|
3053
|
+
}
|
|
3054
|
+
}, 500);
|
|
3055
|
+
|
|
3056
|
+
const { port } = await webDashboard.start();
|
|
3057
|
+
WEB_PORT = port;
|
|
3058
|
+
writeLock(process.pid, port, coordinator.socketPath);
|
|
3059
|
+
|
|
3060
|
+
addLog(`Web dashboard: http://localhost:${port}`, 'success');
|
|
3061
|
+
if (openBrowser) openInBrowser(`http://localhost:${port}`);
|
|
3062
|
+
render();
|
|
3063
|
+
} catch (err) {
|
|
3064
|
+
addLog(`Web dashboard failed: ${err.message}`, 'error');
|
|
3065
|
+
webDashboard = null;
|
|
3066
|
+
coordinator = null;
|
|
3067
|
+
render();
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
/**
|
|
3072
|
+
* Stop the web dashboard and coordinator/worker.
|
|
3073
|
+
*/
|
|
3074
|
+
function stopWebDashboard() {
|
|
3075
|
+
const wasPort = webDashboard ? webDashboard.port : WEB_PORT;
|
|
3076
|
+
|
|
3077
|
+
if (webStateInterval) {
|
|
3078
|
+
clearInterval(webStateInterval);
|
|
3079
|
+
webStateInterval = null;
|
|
3080
|
+
}
|
|
3081
|
+
if (worker) {
|
|
3082
|
+
worker.disconnect();
|
|
3083
|
+
worker = null;
|
|
3084
|
+
}
|
|
3085
|
+
if (coordinator) {
|
|
3086
|
+
coordinator.stop();
|
|
3087
|
+
coordinator = null;
|
|
3088
|
+
}
|
|
3089
|
+
if (webDashboard) {
|
|
3090
|
+
webDashboard.stop();
|
|
3091
|
+
webDashboard = null;
|
|
3092
|
+
}
|
|
3093
|
+
projectId = null;
|
|
3094
|
+
|
|
3095
|
+
return wasPort;
|
|
3096
|
+
}
|
|
3097
|
+
|
|
2833
3098
|
// ============================================================================
|
|
2834
3099
|
// Shutdown
|
|
2835
3100
|
// ============================================================================
|
|
@@ -2864,6 +3129,9 @@ async function shutdown() {
|
|
|
2864
3129
|
await Promise.race([serverClosePromise, timeoutPromise]);
|
|
2865
3130
|
}
|
|
2866
3131
|
|
|
3132
|
+
// Stop web dashboard and coordinator
|
|
3133
|
+
stopWebDashboard();
|
|
3134
|
+
|
|
2867
3135
|
// Flush telemetry
|
|
2868
3136
|
telemetry.capture('session_ended', {
|
|
2869
3137
|
duration_seconds: sessionStartTime ? Math.round((Date.now() - sessionStartTime) / 1000) : 0,
|
|
@@ -3032,6 +3300,11 @@ async function start() {
|
|
|
3032
3300
|
setupFileWatcher();
|
|
3033
3301
|
}
|
|
3034
3302
|
|
|
3303
|
+
// Start web dashboard if enabled
|
|
3304
|
+
if (WEB_ENABLED) {
|
|
3305
|
+
await startWebDashboard(true);
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3035
3308
|
// Setup keyboard input
|
|
3036
3309
|
setupKeyboardInput();
|
|
3037
3310
|
|
package/package.json
CHANGED
package/src/cli/args.js
CHANGED
|
@@ -20,6 +20,8 @@ const { version: PACKAGE_VERSION } = require('../../package.json');
|
|
|
20
20
|
* @property {number|null} visibleBranches - Visible branches override
|
|
21
21
|
* @property {boolean} init - Run configuration wizard
|
|
22
22
|
* @property {boolean} casino - Enable casino mode
|
|
23
|
+
* @property {boolean} web - Enable web dashboard mode
|
|
24
|
+
* @property {number|null} webPort - Web dashboard port override
|
|
23
25
|
*/
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -47,6 +49,9 @@ function parseArgs(argv, options = {}) {
|
|
|
47
49
|
// UI settings
|
|
48
50
|
sound: null,
|
|
49
51
|
visibleBranches: null,
|
|
52
|
+
// Web dashboard
|
|
53
|
+
web: false,
|
|
54
|
+
webPort: null,
|
|
50
55
|
// Actions
|
|
51
56
|
init: false,
|
|
52
57
|
casino: false,
|
|
@@ -108,6 +113,16 @@ function parseArgs(argv, options = {}) {
|
|
|
108
113
|
} else if (args[i] === '--casino') {
|
|
109
114
|
result.casino = true;
|
|
110
115
|
}
|
|
116
|
+
// Web dashboard
|
|
117
|
+
else if (args[i] === '--web' || args[i] === '-w') {
|
|
118
|
+
result.web = true;
|
|
119
|
+
} else if (args[i] === '--web-port') {
|
|
120
|
+
const webPortValue = parseInt(args[i + 1], 10);
|
|
121
|
+
if (!isNaN(webPortValue) && webPortValue > 0 && webPortValue < 65536) {
|
|
122
|
+
result.webPort = webPortValue;
|
|
123
|
+
}
|
|
124
|
+
i++;
|
|
125
|
+
}
|
|
111
126
|
// Actions and info
|
|
112
127
|
else if (args[i] === '--init') {
|
|
113
128
|
result.init = true;
|
|
@@ -175,6 +190,14 @@ function applyCliArgsToConfig(config, cliArgs) {
|
|
|
175
190
|
merged.casinoMode = true;
|
|
176
191
|
}
|
|
177
192
|
|
|
193
|
+
// Web dashboard
|
|
194
|
+
if (cliArgs.web) {
|
|
195
|
+
merged.web = { ...merged.web, enabled: true };
|
|
196
|
+
}
|
|
197
|
+
if (cliArgs.webPort !== null) {
|
|
198
|
+
merged.web = { ...merged.web, port: cliArgs.webPort };
|
|
199
|
+
}
|
|
200
|
+
|
|
178
201
|
return merged;
|
|
179
202
|
}
|
|
180
203
|
|
|
@@ -211,6 +234,10 @@ UI Options:
|
|
|
211
234
|
--visible-branches <n> Number of branches to display (default: 7)
|
|
212
235
|
--casino Enable casino mode
|
|
213
236
|
|
|
237
|
+
Web Dashboard:
|
|
238
|
+
-w, --web Launch web dashboard alongside TUI
|
|
239
|
+
--web-port <port> Web dashboard port (default: 4000)
|
|
240
|
+
|
|
214
241
|
General:
|
|
215
242
|
--init Run the configuration wizard
|
|
216
243
|
-v, --version Show version number
|
|
@@ -232,6 +259,8 @@ Examples:
|
|
|
232
259
|
git-watchtower --no-server # Branch monitoring only
|
|
233
260
|
git-watchtower -p 8080 # Override port
|
|
234
261
|
git-watchtower -m command -c "npm run dev" # Use custom dev server
|
|
262
|
+
git-watchtower --web # TUI + web dashboard on :4000
|
|
263
|
+
git-watchtower --web --web-port 8080 # Web dashboard on custom port
|
|
235
264
|
git-watchtower --no-sound --poll-interval 10000
|
|
236
265
|
`;
|
|
237
266
|
}
|
package/src/config/loader.js
CHANGED
|
@@ -147,6 +147,14 @@ function applyCliArgs(config, cliArgs) {
|
|
|
147
147
|
result.server.restartOnSwitch = cliArgs.restartOnSwitch;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
// Web dashboard
|
|
151
|
+
if (cliArgs.web) {
|
|
152
|
+
result.web = { ...result.web, enabled: true };
|
|
153
|
+
}
|
|
154
|
+
if (cliArgs.webPort !== undefined && cliArgs.webPort !== null) {
|
|
155
|
+
result.web = { ...result.web, port: cliArgs.webPort };
|
|
156
|
+
}
|
|
157
|
+
|
|
150
158
|
// Git settings
|
|
151
159
|
if (cliArgs.remote !== undefined && cliArgs.remote !== null) {
|
|
152
160
|
result.remoteName = cliArgs.remote;
|
package/src/config/schema.js
CHANGED
|
@@ -19,9 +19,16 @@ const { ConfigError, ValidationError } = require('../utils/errors');
|
|
|
19
19
|
* @property {boolean} restartOnSwitch - Restart on branch switch
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} WebConfig
|
|
24
|
+
* @property {boolean} enabled - Web dashboard enabled
|
|
25
|
+
* @property {number} port - Web dashboard port
|
|
26
|
+
*/
|
|
27
|
+
|
|
22
28
|
/**
|
|
23
29
|
* @typedef {Object} Config
|
|
24
30
|
* @property {ServerConfig} server - Server configuration
|
|
31
|
+
* @property {WebConfig} web - Web dashboard configuration
|
|
25
32
|
* @property {string} remoteName - Git remote name
|
|
26
33
|
* @property {boolean} autoPull - Auto-pull enabled
|
|
27
34
|
* @property {number} gitPollInterval - Polling interval in ms
|
|
@@ -47,6 +54,10 @@ const DEFAULTS = {
|
|
|
47
54
|
port: 3000,
|
|
48
55
|
restartOnSwitch: true,
|
|
49
56
|
},
|
|
57
|
+
web: {
|
|
58
|
+
enabled: false,
|
|
59
|
+
port: 4000,
|
|
60
|
+
},
|
|
50
61
|
remoteName: 'origin',
|
|
51
62
|
autoPull: true,
|
|
52
63
|
gitPollInterval: 5000,
|
|
@@ -71,6 +82,7 @@ const LIMITS = {
|
|
|
71
82
|
function getDefaultConfig() {
|
|
72
83
|
return {
|
|
73
84
|
server: { ...DEFAULTS.server },
|
|
85
|
+
web: { ...DEFAULTS.web },
|
|
74
86
|
remoteName: DEFAULTS.remoteName,
|
|
75
87
|
autoPull: DEFAULTS.autoPull,
|
|
76
88
|
gitPollInterval: DEFAULTS.gitPollInterval,
|
|
@@ -223,6 +235,19 @@ function validateConfig(config) {
|
|
|
223
235
|
}
|
|
224
236
|
}
|
|
225
237
|
|
|
238
|
+
// Validate web dashboard config
|
|
239
|
+
if (config.web) {
|
|
240
|
+
if (typeof config.web !== 'object') {
|
|
241
|
+
throw ConfigError.invalid('web must be an object');
|
|
242
|
+
}
|
|
243
|
+
if (config.web.enabled !== undefined) {
|
|
244
|
+
result.web.enabled = Boolean(config.web.enabled);
|
|
245
|
+
}
|
|
246
|
+
if (config.web.port !== undefined) {
|
|
247
|
+
result.web.port = validatePort(config.web.port);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
226
251
|
// Validate Git settings
|
|
227
252
|
if (config.remoteName !== undefined) {
|
|
228
253
|
if (typeof config.remoteName !== 'string' || !config.remoteName.trim()) {
|
package/src/index.js
CHANGED
|
@@ -33,6 +33,9 @@ const configLoader = require('./config/loader');
|
|
|
33
33
|
|
|
34
34
|
// Server management
|
|
35
35
|
const serverProcess = require('./server/process');
|
|
36
|
+
const serverWeb = require('./server/web');
|
|
37
|
+
const serverWebUi = require('./server/web-ui');
|
|
38
|
+
const serverCoordinator = require('./server/coordinator');
|
|
36
39
|
|
|
37
40
|
// Telemetry
|
|
38
41
|
const telemetryModule = require('./telemetry');
|
|
@@ -156,6 +159,17 @@ module.exports = {
|
|
|
156
159
|
ProcessManager: serverProcess.ProcessManager,
|
|
157
160
|
parseCommand: serverProcess.parseCommand,
|
|
158
161
|
|
|
162
|
+
// Web dashboard server
|
|
163
|
+
WebDashboardServer: serverWeb.WebDashboardServer,
|
|
164
|
+
DEFAULT_WEB_PORT: serverWeb.DEFAULT_WEB_PORT,
|
|
165
|
+
getWebDashboardHtml: serverWebUi.getWebDashboardHtml,
|
|
166
|
+
|
|
167
|
+
// Multi-instance coordinator
|
|
168
|
+
Coordinator: serverCoordinator.Coordinator,
|
|
169
|
+
Worker: serverCoordinator.Worker,
|
|
170
|
+
generateProjectId: serverCoordinator.generateProjectId,
|
|
171
|
+
getActiveCoordinator: serverCoordinator.getActiveCoordinator,
|
|
172
|
+
|
|
159
173
|
// CLI argument parsing
|
|
160
174
|
parseArgs: cliArgs.parseArgs,
|
|
161
175
|
applyCliArgsToConfig: cliArgs.applyCliArgsToConfig,
|