git-watchtower 1.0.0 ā 1.2.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/README.md +39 -1
- package/bin/git-watchtower.js +478 -4
- package/package.json +37 -3
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Git Watchtower
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/git-watchtower)
|
|
4
|
+
[](https://www.npmjs.com/package/git-watchtower)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
3
7
|
Monitor and switch between git branches in real-time. Built for working with web based AI coding agents, like Claude Code Web & Codex.
|
|
4
8
|
|
|
5
9
|
- **Live branch monitoring** - Watches your remote for new commits, branches, and deletions
|
|
@@ -89,6 +93,40 @@ git-watchtower --help
|
|
|
89
93
|
### Static Site Mode (Default)
|
|
90
94
|
Serves static files with automatic live reload. Good for static HTML/CSS/JS sites, projects without a build step, quick prototyping.
|
|
91
95
|
|
|
96
|
+
#### Live Reload
|
|
97
|
+
|
|
98
|
+
The static server includes automatic live reload powered by Server-Sent Events (SSE). When you save a file, all connected browsers refresh instantly.
|
|
99
|
+
|
|
100
|
+
**How it works:**
|
|
101
|
+
1. A small script is automatically injected into HTML pages
|
|
102
|
+
2. The script opens an SSE connection to `/livereload`
|
|
103
|
+
3. When files change in your static directory, the server notifies all browsers
|
|
104
|
+
4. Browsers automatically reload to show your changes
|
|
105
|
+
|
|
106
|
+
**File watching behavior:**
|
|
107
|
+
- Uses Node.js native `fs.watch()` with recursive watching
|
|
108
|
+
- Changes are debounced (100ms) to prevent rapid reloads during saves
|
|
109
|
+
- Press `r` to manually trigger a reload for all connected browsers
|
|
110
|
+
|
|
111
|
+
**Ignored files:**
|
|
112
|
+
|
|
113
|
+
The file watcher automatically ignores certain files to prevent unnecessary reloads:
|
|
114
|
+
|
|
115
|
+
| Ignored | Reason |
|
|
116
|
+
|---------|--------|
|
|
117
|
+
| `.git/` directory | Git internals change frequently during commits, fetches, etc. |
|
|
118
|
+
| `.gitignore` patterns | Respects your project's ignore rules |
|
|
119
|
+
|
|
120
|
+
If a `.gitignore` file exists in your static directory (or project root), those patterns are used to filter file change events. This means changes to `node_modules/`, build artifacts, log files, and other ignored paths won't trigger reloads.
|
|
121
|
+
|
|
122
|
+
**Supported `.gitignore` patterns:**
|
|
123
|
+
- Simple filenames: `foo.txt`
|
|
124
|
+
- Wildcards: `*.log`, `file?.txt`
|
|
125
|
+
- Globstar: `**/logs`, `logs/**/*.log`
|
|
126
|
+
- Directory patterns: `node_modules/`, `dist/`
|
|
127
|
+
- Anchored patterns: `/build` (root only)
|
|
128
|
+
- Comments and blank lines are ignored
|
|
129
|
+
|
|
92
130
|
### Custom Server Command Mode
|
|
93
131
|
Runs your own dev server command (`next dev`, `npm run dev`, `vite`, etc.). Press `l` to view server logs, `R` to restart the server.
|
|
94
132
|
|
|
@@ -198,7 +236,7 @@ GIT_POLL_INTERVAL=10000 git-watchtower
|
|
|
198
236
|
|
|
199
237
|
## Requirements
|
|
200
238
|
|
|
201
|
-
- **Node.js**
|
|
239
|
+
- **Node.js** 18.0.0 or higher
|
|
202
240
|
- **Git** installed and in PATH
|
|
203
241
|
- **Git remote** configured (any name, defaults to `origin`)
|
|
204
242
|
- **Terminal** with ANSI color support
|
package/bin/git-watchtower.js
CHANGED
|
@@ -17,11 +17,14 @@
|
|
|
17
17
|
* - Network failure detection with offline indicator
|
|
18
18
|
* - Graceful shutdown handling
|
|
19
19
|
* - Support for custom dev server commands (Next.js, Vite, etc.)
|
|
20
|
+
* - Casino Mode: Vegas-style feedback with slot reels, marquee lights,
|
|
21
|
+
* and win celebrations based on diff size (toggle with 'c' or --casino)
|
|
20
22
|
*
|
|
21
23
|
* Usage:
|
|
22
24
|
* git-watchtower # Run with config or defaults
|
|
23
25
|
* git-watchtower --port 8080 # Override port
|
|
24
26
|
* git-watchtower --no-server # Branch monitoring only
|
|
27
|
+
* git-watchtower --casino # Enable casino mode
|
|
25
28
|
* git-watchtower --init # Run configuration wizard
|
|
26
29
|
* git-watchtower --version # Show version
|
|
27
30
|
*
|
|
@@ -39,8 +42,10 @@
|
|
|
39
42
|
* r - Force reload all browsers (static mode)
|
|
40
43
|
* R - Restart dev server (command mode)
|
|
41
44
|
* l - View server logs (command mode)
|
|
45
|
+
* o - Open live server in browser
|
|
42
46
|
* f - Fetch all branches + refresh sparklines
|
|
43
47
|
* s - Toggle sound notifications
|
|
48
|
+
* c - Toggle casino mode (Vegas-style feedback)
|
|
44
49
|
* i - Show server info (port, connections)
|
|
45
50
|
* 1-0 - Set visible branch count (1-10)
|
|
46
51
|
* +/- - Increase/decrease visible branches
|
|
@@ -50,9 +55,16 @@
|
|
|
50
55
|
const http = require('http');
|
|
51
56
|
const fs = require('fs');
|
|
52
57
|
const path = require('path');
|
|
53
|
-
const { exec, spawn } = require('child_process');
|
|
58
|
+
const { exec, execSync, spawn } = require('child_process');
|
|
54
59
|
const readline = require('readline');
|
|
55
60
|
|
|
61
|
+
// Casino mode - Vegas-style feedback effects
|
|
62
|
+
const casino = require('../src/casino');
|
|
63
|
+
const casinoSounds = require('../src/casino/sounds');
|
|
64
|
+
|
|
65
|
+
// Gitignore utilities for file watcher
|
|
66
|
+
const { loadGitignorePatterns, shouldIgnoreFile } = require('../src/utils/gitignore');
|
|
67
|
+
|
|
56
68
|
// Package info for --version
|
|
57
69
|
const PACKAGE_VERSION = '1.0.0';
|
|
58
70
|
|
|
@@ -267,9 +279,124 @@ async function runConfigurationWizard() {
|
|
|
267
279
|
console.log('\nā Configuration saved to ' + CONFIG_FILE_NAME);
|
|
268
280
|
console.log(' You can edit this file manually or delete it to reconfigure.\n');
|
|
269
281
|
|
|
282
|
+
// Ask user how to handle the new config file in git
|
|
283
|
+
await promptConfigFileHandling();
|
|
284
|
+
|
|
270
285
|
return config;
|
|
271
286
|
}
|
|
272
287
|
|
|
288
|
+
/**
|
|
289
|
+
* After creating .watchtowerrc.json, ask the user how to handle it in git.
|
|
290
|
+
* This prevents the new config file from dirtying the working directory
|
|
291
|
+
* and blocking branch switching.
|
|
292
|
+
*/
|
|
293
|
+
async function promptConfigFileHandling() {
|
|
294
|
+
// Check if we're in a git repo
|
|
295
|
+
try {
|
|
296
|
+
execSync('git rev-parse --git-dir', { cwd: PROJECT_ROOT, stdio: 'pipe' });
|
|
297
|
+
} catch {
|
|
298
|
+
return; // Not a git repo, nothing to do
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
console.log('How should the config file be handled in git?\n');
|
|
302
|
+
console.log(' 1. Keep local (ignore via .git/info/exclude) [recommended]');
|
|
303
|
+
console.log(' 2. Track in repo (commit ' + CONFIG_FILE_NAME + ')');
|
|
304
|
+
console.log(' 3. Add to .gitignore (you\'ll need to commit .gitignore)');
|
|
305
|
+
console.log(' 4. Do nothing (handle manually)\n');
|
|
306
|
+
|
|
307
|
+
const answer = await promptUser('Choice', '1');
|
|
308
|
+
|
|
309
|
+
switch (answer) {
|
|
310
|
+
case '1':
|
|
311
|
+
handleConfigExcludeLocal();
|
|
312
|
+
break;
|
|
313
|
+
case '2':
|
|
314
|
+
await handleConfigCommit();
|
|
315
|
+
break;
|
|
316
|
+
case '3':
|
|
317
|
+
handleConfigGitignore();
|
|
318
|
+
break;
|
|
319
|
+
case '4':
|
|
320
|
+
default:
|
|
321
|
+
console.log(' Skipped. Note: the config file may block branch switching until handled.\n');
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Add .watchtowerrc.json to .git/info/exclude (local-only gitignore)
|
|
328
|
+
*/
|
|
329
|
+
function handleConfigExcludeLocal() {
|
|
330
|
+
try {
|
|
331
|
+
const gitDir = execSync('git rev-parse --git-dir', { cwd: PROJECT_ROOT, encoding: 'utf8' }).trim();
|
|
332
|
+
const excludePath = path.join(PROJECT_ROOT, gitDir, 'info', 'exclude');
|
|
333
|
+
|
|
334
|
+
// Ensure the info directory exists
|
|
335
|
+
const infoDir = path.dirname(excludePath);
|
|
336
|
+
if (!fs.existsSync(infoDir)) {
|
|
337
|
+
fs.mkdirSync(infoDir, { recursive: true });
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Check if already excluded
|
|
341
|
+
if (fs.existsSync(excludePath)) {
|
|
342
|
+
const content = fs.readFileSync(excludePath, 'utf8');
|
|
343
|
+
if (content.includes(CONFIG_FILE_NAME)) {
|
|
344
|
+
console.log(' Already excluded in .git/info/exclude.\n');
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Append the exclusion
|
|
350
|
+
const line = '\n# Git Watchtower config (local)\n' + CONFIG_FILE_NAME + '\n';
|
|
351
|
+
fs.appendFileSync(excludePath, line, 'utf8');
|
|
352
|
+
console.log(' ā Added ' + CONFIG_FILE_NAME + ' to .git/info/exclude');
|
|
353
|
+
console.log(' Config file will be ignored locally without affecting the repo.\n');
|
|
354
|
+
} catch (e) {
|
|
355
|
+
console.error(' Warning: Could not update .git/info/exclude: ' + e.message);
|
|
356
|
+
console.log(' You may need to handle the config file manually.\n');
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Stage and commit .watchtowerrc.json
|
|
362
|
+
*/
|
|
363
|
+
async function handleConfigCommit() {
|
|
364
|
+
try {
|
|
365
|
+
execSync(`git add "${CONFIG_FILE_NAME}"`, { cwd: PROJECT_ROOT, stdio: 'pipe' });
|
|
366
|
+
execSync(`git commit -m "Add git-watchtower configuration"`, { cwd: PROJECT_ROOT, stdio: 'pipe' });
|
|
367
|
+
console.log(' ā Committed ' + CONFIG_FILE_NAME + ' to the repository.\n');
|
|
368
|
+
} catch (e) {
|
|
369
|
+
console.error(' Warning: Could not commit config file: ' + (e.message || 'unknown error'));
|
|
370
|
+
console.log(' You may need to commit it manually: git add ' + CONFIG_FILE_NAME + ' && git commit\n');
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Add .watchtowerrc.json to .gitignore
|
|
376
|
+
*/
|
|
377
|
+
function handleConfigGitignore() {
|
|
378
|
+
try {
|
|
379
|
+
const gitignorePath = path.join(PROJECT_ROOT, '.gitignore');
|
|
380
|
+
|
|
381
|
+
// Check if already in .gitignore
|
|
382
|
+
if (fs.existsSync(gitignorePath)) {
|
|
383
|
+
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
384
|
+
if (content.includes(CONFIG_FILE_NAME)) {
|
|
385
|
+
console.log(' Already listed in .gitignore.\n');
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const line = '\n# Git Watchtower config\n' + CONFIG_FILE_NAME + '\n';
|
|
391
|
+
fs.appendFileSync(gitignorePath, line, 'utf8');
|
|
392
|
+
console.log(' ā Added ' + CONFIG_FILE_NAME + ' to .gitignore');
|
|
393
|
+
console.log(' Note: You\'ll need to commit the .gitignore change.\n');
|
|
394
|
+
} catch (e) {
|
|
395
|
+
console.error(' Warning: Could not update .gitignore: ' + e.message);
|
|
396
|
+
console.log(' You may need to add it manually.\n');
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
273
400
|
async function ensureConfig(cliArgs) {
|
|
274
401
|
// Check if --init flag was passed (force reconfiguration)
|
|
275
402
|
if (cliArgs.init) {
|
|
@@ -498,6 +625,7 @@ const MAX_SERVER_LOG_LINES = 500;
|
|
|
498
625
|
// Dynamic settings
|
|
499
626
|
let visibleBranchCount = 7;
|
|
500
627
|
let soundEnabled = true;
|
|
628
|
+
let casinoModeEnabled = false;
|
|
501
629
|
|
|
502
630
|
// Server process management (for command mode)
|
|
503
631
|
let serverProcess = null;
|
|
@@ -525,6 +653,12 @@ function applyConfig(config) {
|
|
|
525
653
|
// UI settings
|
|
526
654
|
visibleBranchCount = config.visibleBranches || 7;
|
|
527
655
|
soundEnabled = config.soundEnabled !== false;
|
|
656
|
+
|
|
657
|
+
// Casino mode
|
|
658
|
+
casinoModeEnabled = config.casinoMode === true;
|
|
659
|
+
if (casinoModeEnabled) {
|
|
660
|
+
casino.enable();
|
|
661
|
+
}
|
|
528
662
|
}
|
|
529
663
|
|
|
530
664
|
// Server log management
|
|
@@ -540,6 +674,27 @@ function clearServerLog() {
|
|
|
540
674
|
serverLogBuffer = [];
|
|
541
675
|
}
|
|
542
676
|
|
|
677
|
+
// Open URL in default browser (cross-platform)
|
|
678
|
+
function openInBrowser(url) {
|
|
679
|
+
const platform = process.platform;
|
|
680
|
+
let command;
|
|
681
|
+
|
|
682
|
+
if (platform === 'darwin') {
|
|
683
|
+
command = `open "${url}"`;
|
|
684
|
+
} else if (platform === 'win32') {
|
|
685
|
+
command = `start "" "${url}"`;
|
|
686
|
+
} else {
|
|
687
|
+
// Linux and other Unix-like systems
|
|
688
|
+
command = `xdg-open "${url}"`;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
exec(command, (error) => {
|
|
692
|
+
if (error) {
|
|
693
|
+
addLog(`Failed to open browser: ${error.message}`, 'error');
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
|
|
543
698
|
// Command mode server management
|
|
544
699
|
function startServerProcess() {
|
|
545
700
|
if (SERVER_MODE !== 'command' || !SERVER_COMMAND) return;
|
|
@@ -678,6 +833,7 @@ const ansi = {
|
|
|
678
833
|
italic: `${CSI}3m`,
|
|
679
834
|
underline: `${CSI}4m`,
|
|
680
835
|
inverse: `${CSI}7m`,
|
|
836
|
+
blink: `${CSI}5m`,
|
|
681
837
|
|
|
682
838
|
// Foreground colors
|
|
683
839
|
black: `${CSI}30m`,
|
|
@@ -690,6 +846,15 @@ const ansi = {
|
|
|
690
846
|
white: `${CSI}37m`,
|
|
691
847
|
gray: `${CSI}90m`,
|
|
692
848
|
|
|
849
|
+
// Bright foreground colors
|
|
850
|
+
brightRed: `${CSI}91m`,
|
|
851
|
+
brightGreen: `${CSI}92m`,
|
|
852
|
+
brightYellow: `${CSI}93m`,
|
|
853
|
+
brightBlue: `${CSI}94m`,
|
|
854
|
+
brightMagenta: `${CSI}95m`,
|
|
855
|
+
brightCyan: `${CSI}96m`,
|
|
856
|
+
brightWhite: `${CSI}97m`,
|
|
857
|
+
|
|
693
858
|
// Background colors
|
|
694
859
|
bgBlack: `${CSI}40m`,
|
|
695
860
|
bgRed: `${CSI}41m`,
|
|
@@ -700,6 +865,15 @@ const ansi = {
|
|
|
700
865
|
bgCyan: `${CSI}46m`,
|
|
701
866
|
bgWhite: `${CSI}47m`,
|
|
702
867
|
|
|
868
|
+
// Bright background colors
|
|
869
|
+
bgBrightRed: `${CSI}101m`,
|
|
870
|
+
bgBrightGreen: `${CSI}102m`,
|
|
871
|
+
bgBrightYellow: `${CSI}103m`,
|
|
872
|
+
bgBrightBlue: `${CSI}104m`,
|
|
873
|
+
bgBrightMagenta: `${CSI}105m`,
|
|
874
|
+
bgBrightCyan: `${CSI}106m`,
|
|
875
|
+
bgBrightWhite: `${CSI}107m`,
|
|
876
|
+
|
|
703
877
|
// 256 colors
|
|
704
878
|
fg256: (n) => `${CSI}38;5;${n}m`,
|
|
705
879
|
bg256: (n) => `${CSI}48;5;${n}m`,
|
|
@@ -820,6 +994,32 @@ function execAsync(command, options = {}) {
|
|
|
820
994
|
});
|
|
821
995
|
}
|
|
822
996
|
|
|
997
|
+
/**
|
|
998
|
+
* Get diff stats between two commits
|
|
999
|
+
* @param {string} fromCommit - Starting commit
|
|
1000
|
+
* @param {string} toCommit - Ending commit (default HEAD)
|
|
1001
|
+
* @returns {Promise<{added: number, deleted: number}>}
|
|
1002
|
+
*/
|
|
1003
|
+
async function getDiffStats(fromCommit, toCommit = 'HEAD') {
|
|
1004
|
+
try {
|
|
1005
|
+
const { stdout } = await execAsync(`git diff --stat ${fromCommit}..${toCommit}`);
|
|
1006
|
+
// Parse the summary line: "X files changed, Y insertions(+), Z deletions(-)"
|
|
1007
|
+
const match = stdout.match(/(\d+) insertions?\(\+\).*?(\d+) deletions?\(-\)/);
|
|
1008
|
+
if (match) {
|
|
1009
|
+
return { added: parseInt(match[1], 10), deleted: parseInt(match[2], 10) };
|
|
1010
|
+
}
|
|
1011
|
+
// Try to match just insertions or just deletions
|
|
1012
|
+
const insertMatch = stdout.match(/(\d+) insertions?\(\+\)/);
|
|
1013
|
+
const deleteMatch = stdout.match(/(\d+) deletions?\(-\)/);
|
|
1014
|
+
return {
|
|
1015
|
+
added: insertMatch ? parseInt(insertMatch[1], 10) : 0,
|
|
1016
|
+
deleted: deleteMatch ? parseInt(deleteMatch[1], 10) : 0,
|
|
1017
|
+
};
|
|
1018
|
+
} catch (e) {
|
|
1019
|
+
return { added: 0, deleted: 0 };
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
823
1023
|
function formatTimeAgo(date) {
|
|
824
1024
|
const now = new Date();
|
|
825
1025
|
const diffMs = now - date;
|
|
@@ -859,6 +1059,47 @@ function padLeft(str, len) {
|
|
|
859
1059
|
return ' '.repeat(len - str.length) + str;
|
|
860
1060
|
}
|
|
861
1061
|
|
|
1062
|
+
// Casino mode funny messages
|
|
1063
|
+
const CASINO_WIN_MESSAGES = [
|
|
1064
|
+
"Here's your dopamine hit! š°",
|
|
1065
|
+
"The house always wins... and this is YOUR house!",
|
|
1066
|
+
"Cha-ching! Fresh code incoming!",
|
|
1067
|
+
"š² Lucky roll! New commits detected!",
|
|
1068
|
+
"Jackpot! Someone's been busy coding!",
|
|
1069
|
+
"š° Cashing out some fresh changes!",
|
|
1070
|
+
"The slot gods smile upon you!",
|
|
1071
|
+
"Winner winner, chicken dinner! š",
|
|
1072
|
+
"Your patience has been rewarded!",
|
|
1073
|
+
"šÆ Bullseye! Updates acquired!",
|
|
1074
|
+
"Dopamine delivery service! š¦",
|
|
1075
|
+
"The code fairy visited while you waited!",
|
|
1076
|
+
"š Wish granted: new commits!",
|
|
1077
|
+
"Variable reward unlocked! š",
|
|
1078
|
+
];
|
|
1079
|
+
|
|
1080
|
+
const CASINO_PULL_MESSAGES = [
|
|
1081
|
+
"Pulling the lever... š°",
|
|
1082
|
+
"Spinning the reels of fate...",
|
|
1083
|
+
"Checking if luck is on your side...",
|
|
1084
|
+
"Rolling the dice on git fetch...",
|
|
1085
|
+
"Summoning the code spirits...",
|
|
1086
|
+
"Consulting the commit oracle...",
|
|
1087
|
+
];
|
|
1088
|
+
|
|
1089
|
+
const CASINO_LOSS_MESSAGES = [
|
|
1090
|
+
"Better luck next merge!",
|
|
1091
|
+
"š² Snake eyes! Conflict detected!",
|
|
1092
|
+
"Busted! Time to resolve manually.",
|
|
1093
|
+
"The git gods are displeased...",
|
|
1094
|
+
];
|
|
1095
|
+
|
|
1096
|
+
function getCasinoMessage(type) {
|
|
1097
|
+
const messages = type === 'win' ? CASINO_WIN_MESSAGES
|
|
1098
|
+
: type === 'pull' ? CASINO_PULL_MESSAGES
|
|
1099
|
+
: CASINO_LOSS_MESSAGES;
|
|
1100
|
+
return messages[Math.floor(Math.random() * messages.length)];
|
|
1101
|
+
}
|
|
1102
|
+
|
|
862
1103
|
function addLog(message, type = 'info') {
|
|
863
1104
|
const timestamp = new Date().toLocaleTimeString();
|
|
864
1105
|
const icons = { info: 'ā', success: 'ā', warning: 'ā', error: 'ā', update: 'ā³' };
|
|
@@ -1043,6 +1284,9 @@ function clearArea(row, col, width, height) {
|
|
|
1043
1284
|
|
|
1044
1285
|
function renderHeader() {
|
|
1045
1286
|
const width = terminalWidth;
|
|
1287
|
+
// Header row: 1 normally, 2 when casino mode (row 1 is marquee)
|
|
1288
|
+
const headerRow = casinoModeEnabled ? 2 : 1;
|
|
1289
|
+
|
|
1046
1290
|
let statusIcon = { idle: ansi.green + 'ā', fetching: ansi.yellow + 'ā³', error: ansi.red + 'ā' }[pollingStatus];
|
|
1047
1291
|
|
|
1048
1292
|
// Override status for special states
|
|
@@ -1053,7 +1297,7 @@ function renderHeader() {
|
|
|
1053
1297
|
const soundIcon = soundEnabled ? ansi.green + 'š' : ansi.gray + 'š';
|
|
1054
1298
|
const projectName = path.basename(PROJECT_ROOT);
|
|
1055
1299
|
|
|
1056
|
-
write(ansi.moveTo(
|
|
1300
|
+
write(ansi.moveTo(headerRow, 1));
|
|
1057
1301
|
write(ansi.bgBlue + ansi.white + ansi.bold);
|
|
1058
1302
|
|
|
1059
1303
|
// Left side: Title + separator + project name
|
|
@@ -1065,6 +1309,9 @@ function renderHeader() {
|
|
|
1065
1309
|
// Warning badges (center area)
|
|
1066
1310
|
let badges = '';
|
|
1067
1311
|
let badgesVisibleLen = 0;
|
|
1312
|
+
|
|
1313
|
+
// Casino mode slot display moved to its own row below header (row 3)
|
|
1314
|
+
|
|
1068
1315
|
if (SERVER_MODE === 'command' && serverCrashed) {
|
|
1069
1316
|
const label = ' CRASHED ';
|
|
1070
1317
|
badges += ' ' + ansi.bgRed + ansi.white + label + ansi.bgBlue + ansi.white;
|
|
@@ -1124,7 +1371,8 @@ function renderHeader() {
|
|
|
1124
1371
|
}
|
|
1125
1372
|
|
|
1126
1373
|
function renderBranchList() {
|
|
1127
|
-
|
|
1374
|
+
// Start row: 3 normally, 4 when casino mode (row 1 is marquee, row 2 is header)
|
|
1375
|
+
const startRow = casinoModeEnabled ? 4 : 3;
|
|
1128
1376
|
const boxWidth = terminalWidth;
|
|
1129
1377
|
const contentWidth = boxWidth - 4; // Space between borders
|
|
1130
1378
|
const height = Math.min(visibleBranchCount * 2 + 4, Math.floor(terminalHeight * 0.5));
|
|
@@ -1278,6 +1526,49 @@ function renderActivityLog(startRow) {
|
|
|
1278
1526
|
return startRow + height;
|
|
1279
1527
|
}
|
|
1280
1528
|
|
|
1529
|
+
function renderCasinoStats(startRow) {
|
|
1530
|
+
if (!casinoModeEnabled) return startRow;
|
|
1531
|
+
|
|
1532
|
+
const boxWidth = terminalWidth;
|
|
1533
|
+
const height = 6; // Box with two content lines
|
|
1534
|
+
|
|
1535
|
+
// Don't draw if not enough space
|
|
1536
|
+
if (startRow + height > terminalHeight - 3) return startRow;
|
|
1537
|
+
|
|
1538
|
+
drawBox(startRow, 1, boxWidth, height, 'š° CASINO WINNINGS š°', ansi.brightMagenta);
|
|
1539
|
+
|
|
1540
|
+
// Clear content area
|
|
1541
|
+
for (let i = 1; i < height - 1; i++) {
|
|
1542
|
+
write(ansi.moveTo(startRow + i, 2));
|
|
1543
|
+
write(' '.repeat(boxWidth - 2));
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
const stats = casino.getStats();
|
|
1547
|
+
|
|
1548
|
+
// Net winnings color
|
|
1549
|
+
const netColor = stats.netWinnings >= 0 ? ansi.brightGreen : ansi.brightRed;
|
|
1550
|
+
const netSign = stats.netWinnings >= 0 ? '+' : '';
|
|
1551
|
+
|
|
1552
|
+
// Line 1: Line Changes | Poll Cost | Net Earnings
|
|
1553
|
+
write(ansi.moveTo(startRow + 2, 3));
|
|
1554
|
+
write('š Line Changes: ');
|
|
1555
|
+
write(ansi.brightGreen + '+' + stats.totalLinesAdded + ansi.reset);
|
|
1556
|
+
write(' / ');
|
|
1557
|
+
write(ansi.brightRed + '-' + stats.totalLinesDeleted + ansi.reset);
|
|
1558
|
+
write(' = ' + ansi.brightYellow + '$' + stats.totalLines + ansi.reset);
|
|
1559
|
+
write(' | šø Poll Cost: ' + ansi.brightRed + '$' + stats.totalPolls + ansi.reset);
|
|
1560
|
+
write(' | š° Net Earnings: ' + netColor + netSign + '$' + stats.netWinnings + ansi.reset);
|
|
1561
|
+
|
|
1562
|
+
// Line 2: House Edge | Vibes Quality | Luck Meter | Dopamine Hits
|
|
1563
|
+
write(ansi.moveTo(startRow + 3, 3));
|
|
1564
|
+
write('š° House Edge: ' + ansi.brightCyan + stats.houseEdge + '%' + ansi.reset);
|
|
1565
|
+
write(' | š Vibes: ' + stats.vibesQuality);
|
|
1566
|
+
write(' | š² Luck: ' + ansi.brightYellow + stats.luckMeter + '%' + ansi.reset);
|
|
1567
|
+
write(' | š§ Dopamine Hits: ' + ansi.brightGreen + stats.dopamineHits + ansi.reset);
|
|
1568
|
+
|
|
1569
|
+
return startRow + height;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1281
1572
|
function renderFooter() {
|
|
1282
1573
|
const row = terminalHeight - 1;
|
|
1283
1574
|
|
|
@@ -1294,6 +1585,7 @@ function renderFooter() {
|
|
|
1294
1585
|
// Mode-specific keys
|
|
1295
1586
|
if (!NO_SERVER) {
|
|
1296
1587
|
write(ansi.gray + '[l]' + ansi.reset + ansi.bgBlack + ' Logs ');
|
|
1588
|
+
write(ansi.gray + '[o]' + ansi.reset + ansi.bgBlack + ' Open ');
|
|
1297
1589
|
}
|
|
1298
1590
|
if (SERVER_MODE === 'static') {
|
|
1299
1591
|
write(ansi.gray + '[r]' + ansi.reset + ansi.bgBlack + ' Reload ');
|
|
@@ -1302,6 +1594,14 @@ function renderFooter() {
|
|
|
1302
1594
|
}
|
|
1303
1595
|
|
|
1304
1596
|
write(ansi.gray + '[±]' + ansi.reset + ansi.bgBlack + ' List:' + ansi.cyan + visibleBranchCount + ansi.reset + ansi.bgBlack + ' ');
|
|
1597
|
+
|
|
1598
|
+
// Casino mode toggle indicator
|
|
1599
|
+
if (casinoModeEnabled) {
|
|
1600
|
+
write(ansi.brightMagenta + '[c]' + ansi.reset + ansi.bgBlack + ' š° ');
|
|
1601
|
+
} else {
|
|
1602
|
+
write(ansi.gray + '[c]' + ansi.reset + ansi.bgBlack + ' Casino ');
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1305
1605
|
write(ansi.gray + '[q]' + ansi.reset + ansi.bgBlack + ' Quit ');
|
|
1306
1606
|
write(ansi.reset);
|
|
1307
1607
|
}
|
|
@@ -1722,11 +2022,84 @@ function render() {
|
|
|
1722
2022
|
write(ansi.moveToTop);
|
|
1723
2023
|
write(ansi.clearScreen);
|
|
1724
2024
|
|
|
2025
|
+
// Casino mode: top marquee border
|
|
2026
|
+
if (casinoModeEnabled) {
|
|
2027
|
+
write(ansi.moveTo(1, 1));
|
|
2028
|
+
write(casino.renderMarqueeLine(terminalWidth, 'top'));
|
|
2029
|
+
}
|
|
2030
|
+
|
|
1725
2031
|
renderHeader();
|
|
1726
2032
|
const logStart = renderBranchList();
|
|
1727
|
-
renderActivityLog(logStart);
|
|
2033
|
+
const statsStart = renderActivityLog(logStart);
|
|
2034
|
+
renderCasinoStats(statsStart);
|
|
1728
2035
|
renderFooter();
|
|
1729
2036
|
|
|
2037
|
+
// Casino mode: full border (top, bottom, left, right)
|
|
2038
|
+
if (casinoModeEnabled) {
|
|
2039
|
+
// Bottom marquee border
|
|
2040
|
+
write(ansi.moveTo(terminalHeight, 1));
|
|
2041
|
+
write(casino.renderMarqueeLine(terminalWidth, 'bottom'));
|
|
2042
|
+
|
|
2043
|
+
// Left and right side borders
|
|
2044
|
+
for (let row = 2; row < terminalHeight; row++) {
|
|
2045
|
+
// Left side
|
|
2046
|
+
write(ansi.moveTo(row, 1));
|
|
2047
|
+
write(casino.getMarqueeSideChar(row, terminalHeight, 'left'));
|
|
2048
|
+
// Right side
|
|
2049
|
+
write(ansi.moveTo(row, terminalWidth));
|
|
2050
|
+
write(casino.getMarqueeSideChar(row, terminalHeight, 'right'));
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
// Casino mode: slot reels on row 3 (below header) when polling or showing result
|
|
2055
|
+
if (casinoModeEnabled && casino.isSlotsActive()) {
|
|
2056
|
+
const slotDisplay = casino.getSlotReelDisplay();
|
|
2057
|
+
if (slotDisplay) {
|
|
2058
|
+
// Row 3: below header (row 1 is marquee, row 2 is header)
|
|
2059
|
+
const resultLabel = casino.getSlotResultLabel();
|
|
2060
|
+
let leftLabel, rightLabel;
|
|
2061
|
+
|
|
2062
|
+
if (casino.isSlotSpinning()) {
|
|
2063
|
+
leftLabel = ansi.bgBrightYellow + ansi.black + ansi.bold + ' POLLING ' + ansi.reset;
|
|
2064
|
+
rightLabel = '';
|
|
2065
|
+
} else if (resultLabel) {
|
|
2066
|
+
leftLabel = ansi.bgBrightGreen + ansi.black + ansi.bold + ' RESULT ' + ansi.reset;
|
|
2067
|
+
// Flash effect for jackpots, use result color for text
|
|
2068
|
+
const flash = resultLabel.isJackpot && (Math.floor(Date.now() / 150) % 2 === 0);
|
|
2069
|
+
const bgColor = flash ? ansi.bgBrightYellow : ansi.bgWhite;
|
|
2070
|
+
rightLabel = ' ' + bgColor + resultLabel.color + ansi.bold + ' ' + resultLabel.text + ' ' + ansi.reset;
|
|
2071
|
+
} else {
|
|
2072
|
+
leftLabel = ansi.bgBrightGreen + ansi.black + ansi.bold + ' RESULT ' + ansi.reset;
|
|
2073
|
+
rightLabel = '';
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
const fullDisplay = leftLabel + ' ' + slotDisplay + rightLabel;
|
|
2077
|
+
const col = Math.floor((terminalWidth - 70) / 2); // Center the display
|
|
2078
|
+
write(ansi.moveTo(3, Math.max(2, col)));
|
|
2079
|
+
write(fullDisplay);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
// Casino mode: win animation overlay
|
|
2084
|
+
if (casinoModeEnabled && casino.isWinAnimating()) {
|
|
2085
|
+
const winDisplay = casino.getWinDisplay(terminalWidth);
|
|
2086
|
+
if (winDisplay) {
|
|
2087
|
+
const row = Math.floor(terminalHeight / 2);
|
|
2088
|
+
write(ansi.moveTo(row, 1));
|
|
2089
|
+
write(winDisplay);
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
// Casino mode: loss animation overlay
|
|
2094
|
+
if (casinoModeEnabled && casino.isLossAnimating()) {
|
|
2095
|
+
const lossDisplay = casino.getLossDisplay(terminalWidth);
|
|
2096
|
+
if (lossDisplay) {
|
|
2097
|
+
const row = Math.floor(terminalHeight / 2);
|
|
2098
|
+
write(ansi.moveTo(row, 1));
|
|
2099
|
+
write(lossDisplay);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
|
|
1730
2103
|
if (flashMessage) {
|
|
1731
2104
|
renderFlash();
|
|
1732
2105
|
}
|
|
@@ -2122,6 +2495,12 @@ async function pollGitChanges() {
|
|
|
2122
2495
|
if (isPolling) return;
|
|
2123
2496
|
isPolling = true;
|
|
2124
2497
|
pollingStatus = 'fetching';
|
|
2498
|
+
|
|
2499
|
+
// Casino mode: start slot reels spinning (no sound - too annoying)
|
|
2500
|
+
if (casinoModeEnabled) {
|
|
2501
|
+
casino.startSlotReels(render);
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2125
2504
|
render();
|
|
2126
2505
|
|
|
2127
2506
|
const fetchStartTime = Date.now();
|
|
@@ -2229,9 +2608,33 @@ async function pollGitChanges() {
|
|
|
2229
2608
|
for (const branch of updatedBranches) {
|
|
2230
2609
|
addLog(`Update on ${branch.name}: ${branch.commit}`, 'update');
|
|
2231
2610
|
}
|
|
2611
|
+
|
|
2612
|
+
// Casino mode: add funny commentary
|
|
2613
|
+
if (casinoModeEnabled) {
|
|
2614
|
+
addLog(`š° ${getCasinoMessage('win')}`, 'success');
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2232
2617
|
const names = notifyBranches.map(b => b.name).join(', ');
|
|
2233
2618
|
showFlash(names);
|
|
2234
2619
|
playSound();
|
|
2620
|
+
|
|
2621
|
+
// Casino mode: trigger win effect based on number of updated branches
|
|
2622
|
+
if (casinoModeEnabled) {
|
|
2623
|
+
// Estimate line changes: more branches = bigger "win"
|
|
2624
|
+
// Each branch update counts as ~100 lines (placeholder until we calculate actual diff)
|
|
2625
|
+
const estimatedLines = notifyBranches.length * 100;
|
|
2626
|
+
const winLevel = casino.getWinLevel(estimatedLines);
|
|
2627
|
+
casino.stopSlotReels(true, render, winLevel); // Win - matching symbols + flash + label
|
|
2628
|
+
casino.triggerWin(estimatedLines, 0, render);
|
|
2629
|
+
if (winLevel) {
|
|
2630
|
+
casinoSounds.playForWinLevel(winLevel.key);
|
|
2631
|
+
}
|
|
2632
|
+
casino.recordPoll(true);
|
|
2633
|
+
}
|
|
2634
|
+
} else if (casinoModeEnabled) {
|
|
2635
|
+
// No updates - stop reels and show result briefly
|
|
2636
|
+
casino.stopSlotReels(false, render);
|
|
2637
|
+
casino.recordPoll(false);
|
|
2235
2638
|
}
|
|
2236
2639
|
|
|
2237
2640
|
// Remember which branch was selected before updating the list
|
|
@@ -2271,6 +2674,9 @@ async function pollGitChanges() {
|
|
|
2271
2674
|
addLog(`Auto-pulling changes for ${currentBranch}...`, 'update');
|
|
2272
2675
|
render();
|
|
2273
2676
|
|
|
2677
|
+
// Save the old commit for diff calculation (casino mode)
|
|
2678
|
+
const oldCommit = currentInfo.commit;
|
|
2679
|
+
|
|
2274
2680
|
try {
|
|
2275
2681
|
await execAsync(`git pull "${REMOTE_NAME}" "${currentBranch}"`);
|
|
2276
2682
|
addLog(`Pulled successfully from ${currentBranch}`, 'success');
|
|
@@ -2282,6 +2688,20 @@ async function pollGitChanges() {
|
|
|
2282
2688
|
previousBranchStates.set(currentBranch, newCommit.stdout.trim());
|
|
2283
2689
|
// Reload browsers
|
|
2284
2690
|
notifyClients();
|
|
2691
|
+
|
|
2692
|
+
// Casino mode: calculate actual diff and trigger win effect
|
|
2693
|
+
if (casinoModeEnabled && oldCommit) {
|
|
2694
|
+
const diffStats = await getDiffStats(oldCommit, 'HEAD');
|
|
2695
|
+
const totalLines = diffStats.added + diffStats.deleted;
|
|
2696
|
+
if (totalLines > 0) {
|
|
2697
|
+
casino.triggerWin(diffStats.added, diffStats.deleted, render);
|
|
2698
|
+
const winLevel = casino.getWinLevel(totalLines);
|
|
2699
|
+
if (winLevel) {
|
|
2700
|
+
addLog(`š° ${winLevel.label} +${diffStats.added}/-${diffStats.deleted} lines`, 'success');
|
|
2701
|
+
casinoSounds.playForWinLevel(winLevel.key);
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2285
2705
|
} catch (e) {
|
|
2286
2706
|
const errMsg = e.stderr || e.stdout || e.message || String(e);
|
|
2287
2707
|
if (isMergeConflict(errMsg)) {
|
|
@@ -2293,6 +2713,12 @@ async function pollGitChanges() {
|
|
|
2293
2713
|
'Auto-pull resulted in merge conflicts that need manual resolution.',
|
|
2294
2714
|
'Run: git status to see conflicts'
|
|
2295
2715
|
);
|
|
2716
|
+
// Casino mode: trigger loss effect
|
|
2717
|
+
if (casinoModeEnabled) {
|
|
2718
|
+
casino.triggerLoss('MERGE CONFLICT!', render);
|
|
2719
|
+
casinoSounds.playLoss();
|
|
2720
|
+
addLog(`š ${getCasinoMessage('loss')}`, 'error');
|
|
2721
|
+
}
|
|
2296
2722
|
} else if (isAuthError(errMsg)) {
|
|
2297
2723
|
addLog(`Authentication failed during pull`, 'error');
|
|
2298
2724
|
addLog(`Check your Git credentials`, 'warning');
|
|
@@ -2313,9 +2739,20 @@ async function pollGitChanges() {
|
|
|
2313
2739
|
}
|
|
2314
2740
|
|
|
2315
2741
|
pollingStatus = 'idle';
|
|
2742
|
+
// Casino mode: stop slot reels if still spinning (already handled above, just cleanup)
|
|
2743
|
+
if (casinoModeEnabled && casino.isSlotSpinning()) {
|
|
2744
|
+
casino.stopSlotReels(false, render);
|
|
2745
|
+
}
|
|
2316
2746
|
} catch (err) {
|
|
2317
2747
|
const errMsg = err.stderr || err.message || String(err);
|
|
2318
2748
|
|
|
2749
|
+
// Casino mode: stop slot reels and show loss on error
|
|
2750
|
+
if (casinoModeEnabled) {
|
|
2751
|
+
casino.stopSlotReels(false, render);
|
|
2752
|
+
casino.triggerLoss('BUST!', render);
|
|
2753
|
+
casinoSounds.playLoss();
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2319
2756
|
// Handle different error types
|
|
2320
2757
|
if (isNetworkError(errMsg)) {
|
|
2321
2758
|
consecutiveNetworkFailures++;
|
|
@@ -2449,13 +2886,26 @@ const server = http.createServer((req, res) => {
|
|
|
2449
2886
|
|
|
2450
2887
|
let fileWatcher = null;
|
|
2451
2888
|
let debounceTimer = null;
|
|
2889
|
+
let ignorePatterns = [];
|
|
2452
2890
|
|
|
2453
2891
|
function setupFileWatcher() {
|
|
2454
2892
|
if (fileWatcher) fileWatcher.close();
|
|
2455
2893
|
|
|
2894
|
+
// Load gitignore patterns before setting up the watcher
|
|
2895
|
+
ignorePatterns = loadGitignorePatterns([STATIC_DIR, PROJECT_ROOT]);
|
|
2896
|
+
if (ignorePatterns.length > 0) {
|
|
2897
|
+
addLog(`Loaded ${ignorePatterns.length} ignore patterns from .gitignore`, 'info');
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2456
2900
|
try {
|
|
2457
2901
|
fileWatcher = fs.watch(STATIC_DIR, { recursive: true }, (eventType, filename) => {
|
|
2458
2902
|
if (!filename) return;
|
|
2903
|
+
|
|
2904
|
+
// Skip ignored files (.git directory and gitignore patterns)
|
|
2905
|
+
if (shouldIgnoreFile(filename, ignorePatterns)) {
|
|
2906
|
+
return;
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2459
2909
|
clearTimeout(debounceTimer);
|
|
2460
2910
|
debounceTimer = setTimeout(() => {
|
|
2461
2911
|
addLog(`File changed: ${filename}`, 'info');
|
|
@@ -2716,6 +3166,15 @@ function setupKeyboardInput() {
|
|
|
2716
3166
|
}
|
|
2717
3167
|
break;
|
|
2718
3168
|
|
|
3169
|
+
case 'o': // Open live server in browser
|
|
3170
|
+
if (!NO_SERVER) {
|
|
3171
|
+
const serverUrl = `http://localhost:${PORT}`;
|
|
3172
|
+
addLog(`Opening ${serverUrl} in browser...`, 'info');
|
|
3173
|
+
openInBrowser(serverUrl);
|
|
3174
|
+
render();
|
|
3175
|
+
}
|
|
3176
|
+
break;
|
|
3177
|
+
|
|
2719
3178
|
case 'f':
|
|
2720
3179
|
addLog('Fetching all branches...', 'update');
|
|
2721
3180
|
await pollGitChanges();
|
|
@@ -2733,6 +3192,18 @@ function setupKeyboardInput() {
|
|
|
2733
3192
|
render();
|
|
2734
3193
|
break;
|
|
2735
3194
|
|
|
3195
|
+
case 'c': // Toggle casino mode
|
|
3196
|
+
casinoModeEnabled = casino.toggle();
|
|
3197
|
+
addLog(`Casino mode ${casinoModeEnabled ? 'š° ENABLED' : 'disabled'}`, casinoModeEnabled ? 'success' : 'info');
|
|
3198
|
+
if (casinoModeEnabled) {
|
|
3199
|
+
addLog(`Have you noticed this game has that 'variable rewards' thing going on? š¤š`, 'info');
|
|
3200
|
+
if (soundEnabled) {
|
|
3201
|
+
casinoSounds.playJackpot();
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
render();
|
|
3205
|
+
break;
|
|
3206
|
+
|
|
2736
3207
|
// Number keys to set visible branch count
|
|
2737
3208
|
case '1': case '2': case '3': case '4': case '5':
|
|
2738
3209
|
case '6': case '7': case '8': case '9':
|
|
@@ -2850,6 +3321,9 @@ async function start() {
|
|
|
2850
3321
|
const config = await ensureConfig(cliArgs);
|
|
2851
3322
|
applyConfig(config);
|
|
2852
3323
|
|
|
3324
|
+
// Set up casino mode render callback for animations
|
|
3325
|
+
casino.setRenderCallback(render);
|
|
3326
|
+
|
|
2853
3327
|
// Check for remote before starting TUI
|
|
2854
3328
|
const hasRemote = await checkRemoteExists();
|
|
2855
3329
|
if (!hasRemote) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-watchtower",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Terminal-based Git branch monitor with activity sparklines and optional dev server with live reload",
|
|
5
5
|
"main": "bin/git-watchtower.js",
|
|
6
6
|
"bin": {
|
|
@@ -18,8 +18,11 @@
|
|
|
18
18
|
"typecheck": "tsc --noEmit"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
22
|
+
"@semantic-release/git": "^10.0.1",
|
|
21
23
|
"@types/node": "^22.0.0",
|
|
22
24
|
"c8": "^10.1.2",
|
|
25
|
+
"semantic-release": "^25.0.3",
|
|
23
26
|
"typescript": "^5.7.0"
|
|
24
27
|
},
|
|
25
28
|
"keywords": [
|
|
@@ -34,7 +37,7 @@
|
|
|
34
37
|
"dashboard",
|
|
35
38
|
"sparklines"
|
|
36
39
|
],
|
|
37
|
-
"author": "",
|
|
40
|
+
"author": "drummel <drummel@gmail.com>",
|
|
38
41
|
"license": "MIT",
|
|
39
42
|
"repository": {
|
|
40
43
|
"type": "git",
|
|
@@ -51,5 +54,36 @@
|
|
|
51
54
|
"bin/git-watchtower.js",
|
|
52
55
|
"README.md",
|
|
53
56
|
"LICENSE"
|
|
54
|
-
]
|
|
57
|
+
],
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public",
|
|
60
|
+
"registry": "https://registry.npmjs.org/"
|
|
61
|
+
},
|
|
62
|
+
"release": {
|
|
63
|
+
"branches": [
|
|
64
|
+
"main"
|
|
65
|
+
],
|
|
66
|
+
"plugins": [
|
|
67
|
+
"@semantic-release/commit-analyzer",
|
|
68
|
+
"@semantic-release/release-notes-generator",
|
|
69
|
+
[
|
|
70
|
+
"@semantic-release/changelog",
|
|
71
|
+
{
|
|
72
|
+
"changelogFile": "CHANGELOG.md"
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"@semantic-release/npm",
|
|
76
|
+
"@semantic-release/github",
|
|
77
|
+
[
|
|
78
|
+
"@semantic-release/git",
|
|
79
|
+
{
|
|
80
|
+
"assets": [
|
|
81
|
+
"package.json",
|
|
82
|
+
"CHANGELOG.md"
|
|
83
|
+
],
|
|
84
|
+
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
]
|
|
88
|
+
}
|
|
55
89
|
}
|