git-watchtower 1.9.19 → 1.10.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/bin/git-watchtower.js +277 -2
- 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 +2474 -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
|
|
@@ -1413,9 +1433,11 @@ async function switchToBranch(branchName, recordHistory = true) {
|
|
|
1413
1433
|
store.setState({ currentBranch: safeBranchName, isDetachedHead: false });
|
|
1414
1434
|
|
|
1415
1435
|
// Clear NEW flag when branch becomes current
|
|
1416
|
-
const
|
|
1436
|
+
const branches = store.get('branches');
|
|
1437
|
+
const branchInfo = branches.find(b => b.name === safeBranchName);
|
|
1417
1438
|
if (branchInfo && branchInfo.isNew) {
|
|
1418
1439
|
branchInfo.isNew = false;
|
|
1440
|
+
store.setState({ branches: [...branches] });
|
|
1419
1441
|
}
|
|
1420
1442
|
|
|
1421
1443
|
// Record in history (for undo)
|
|
@@ -1870,10 +1892,10 @@ async function pollGitChanges() {
|
|
|
1870
1892
|
await execGit(['pull', REMOTE_NAME, autoPullBranchName], { cwd: PROJECT_ROOT, timeout: 60000 });
|
|
1871
1893
|
addLog(`Pulled successfully from ${autoPullBranchName}`, 'success');
|
|
1872
1894
|
currentInfo.hasUpdates = false;
|
|
1873
|
-
store.setState({ hasMergeConflict: false });
|
|
1874
1895
|
// Update the stored commit to the new one
|
|
1875
1896
|
const newCommit = await execGit(['rev-parse', '--short', 'HEAD'], { cwd: PROJECT_ROOT });
|
|
1876
1897
|
currentInfo.commit = newCommit.stdout.trim();
|
|
1898
|
+
store.setState({ hasMergeConflict: false, branches: [...store.get('branches')] });
|
|
1877
1899
|
previousBranchStates.set(autoPullBranchName, newCommit.stdout.trim());
|
|
1878
1900
|
// Reload browsers
|
|
1879
1901
|
notifyClients();
|
|
@@ -2773,6 +2795,20 @@ function setupKeyboardInput() {
|
|
|
2773
2795
|
break;
|
|
2774
2796
|
}
|
|
2775
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
|
+
|
|
2776
2812
|
// Number keys to set visible branch count
|
|
2777
2813
|
case '1': case '2': case '3': case '4': case '5':
|
|
2778
2814
|
case '6': case '7': case '8': case '9':
|
|
@@ -2828,6 +2864,237 @@ function setupKeyboardInput() {
|
|
|
2828
2864
|
});
|
|
2829
2865
|
}
|
|
2830
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
|
+
|
|
2831
3098
|
// ============================================================================
|
|
2832
3099
|
// Shutdown
|
|
2833
3100
|
// ============================================================================
|
|
@@ -2862,6 +3129,9 @@ async function shutdown() {
|
|
|
2862
3129
|
await Promise.race([serverClosePromise, timeoutPromise]);
|
|
2863
3130
|
}
|
|
2864
3131
|
|
|
3132
|
+
// Stop web dashboard and coordinator
|
|
3133
|
+
stopWebDashboard();
|
|
3134
|
+
|
|
2865
3135
|
// Flush telemetry
|
|
2866
3136
|
telemetry.capture('session_ended', {
|
|
2867
3137
|
duration_seconds: sessionStartTime ? Math.round((Date.now() - sessionStartTime) / 1000) : 0,
|
|
@@ -3030,6 +3300,11 @@ async function start() {
|
|
|
3030
3300
|
setupFileWatcher();
|
|
3031
3301
|
}
|
|
3032
3302
|
|
|
3303
|
+
// Start web dashboard if enabled
|
|
3304
|
+
if (WEB_ENABLED) {
|
|
3305
|
+
await startWebDashboard(true);
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3033
3308
|
// Setup keyboard input
|
|
3034
3309
|
setupKeyboardInput();
|
|
3035
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,
|