git-watchtower 2.3.24 → 2.3.26
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/package.json +1 -1
- package/src/ui/ansi.js +62 -8
- package/src/utils/browser.js +30 -5
package/package.json
CHANGED
package/src/ui/ansi.js
CHANGED
|
@@ -602,25 +602,79 @@ function style(text, ...styles) {
|
|
|
602
602
|
}
|
|
603
603
|
|
|
604
604
|
/**
|
|
605
|
-
*
|
|
605
|
+
* Walk graphemes up to a column budget, returning the longest prefix
|
|
606
|
+
* whose visible width does not exceed `maxCols`. Drops one grapheme
|
|
607
|
+
* past the budget (mirrors how truncate() consumes — never mid-cut
|
|
608
|
+
* a wide char or surrogate pair).
|
|
609
|
+
* @param {string} str
|
|
610
|
+
* @param {number} maxCols
|
|
611
|
+
* @returns {string}
|
|
612
|
+
* @private
|
|
613
|
+
*/
|
|
614
|
+
function _sliceByColumns(str, maxCols) {
|
|
615
|
+
if (maxCols <= 0) return '';
|
|
616
|
+
let acc = '';
|
|
617
|
+
let used = 0;
|
|
618
|
+
if (_graphemeSegmenter) {
|
|
619
|
+
for (const { segment } of _graphemeSegmenter.segment(str)) {
|
|
620
|
+
const w = visibleLength(segment);
|
|
621
|
+
if (used + w > maxCols) break;
|
|
622
|
+
acc += segment;
|
|
623
|
+
used += w;
|
|
624
|
+
}
|
|
625
|
+
} else {
|
|
626
|
+
for (const ch of str) {
|
|
627
|
+
const w = _codePointWidth(ch.codePointAt(0));
|
|
628
|
+
if (used + w > maxCols) break;
|
|
629
|
+
acc += ch;
|
|
630
|
+
used += w;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return acc;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Pad a string on the right to a target visible-column width.
|
|
638
|
+
* Truncates (without adding an ellipsis) when the input already
|
|
639
|
+
* exceeds the budget.
|
|
640
|
+
*
|
|
641
|
+
* Width is measured in display columns via visibleLength, NOT UTF-16
|
|
642
|
+
* `.length`. ASCII-only inputs keep their previous behaviour because
|
|
643
|
+
* `.length === visibleLength` for them. CJK / wide emoji / ZWJ
|
|
644
|
+
* clusters now pad by the columns they actually render in, and the
|
|
645
|
+
* truncate path slices on grapheme boundaries so a wide character
|
|
646
|
+
* isn't cut mid-byte.
|
|
647
|
+
*
|
|
648
|
+
* SGR-styled strings (e.g. `\x1b[31mhi\x1b[0m`) previously had their
|
|
649
|
+
* raw-byte length compared against `len`, so the colour wrapper made
|
|
650
|
+
* the string look "too long" and `substring(0, len)` cut the escape
|
|
651
|
+
* sequence in half — leaving the terminal in a stuck colour state.
|
|
652
|
+
* Now the width comparison uses visibleLength which strips ANSI
|
|
653
|
+
* before measuring, and the slice helper preserves SGR runs because
|
|
654
|
+
* they have width 0.
|
|
655
|
+
*
|
|
606
656
|
* @param {string} str - String to pad
|
|
607
|
-
* @param {number} len - Target
|
|
657
|
+
* @param {number} len - Target visible-column width
|
|
608
658
|
* @returns {string}
|
|
609
659
|
*/
|
|
610
660
|
function padRight(str, len) {
|
|
611
|
-
|
|
612
|
-
|
|
661
|
+
const cols = visibleLength(str);
|
|
662
|
+
if (cols >= len) return _sliceByColumns(str, len);
|
|
663
|
+
return str + ' '.repeat(len - cols);
|
|
613
664
|
}
|
|
614
665
|
|
|
615
666
|
/**
|
|
616
|
-
* Pad a string on the left
|
|
667
|
+
* Pad a string on the left to a target visible-column width.
|
|
668
|
+
* Same column-vs-codeunit semantics as {@link padRight}.
|
|
669
|
+
*
|
|
617
670
|
* @param {string} str - String to pad
|
|
618
|
-
* @param {number} len - Target
|
|
671
|
+
* @param {number} len - Target visible-column width
|
|
619
672
|
* @returns {string}
|
|
620
673
|
*/
|
|
621
674
|
function padLeft(str, len) {
|
|
622
|
-
|
|
623
|
-
|
|
675
|
+
const cols = visibleLength(str);
|
|
676
|
+
if (cols >= len) return _sliceByColumns(str, len);
|
|
677
|
+
return ' '.repeat(len - cols) + str;
|
|
624
678
|
}
|
|
625
679
|
|
|
626
680
|
/**
|
package/src/utils/browser.js
CHANGED
|
@@ -7,17 +7,42 @@ const { execFile } = require('child_process');
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Validate that a string is a safe URL to pass to OS open commands.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
*
|
|
11
|
+
* Rejects:
|
|
12
|
+
* - non-string / empty input
|
|
13
|
+
* - schemes other than http(s) / file
|
|
14
|
+
* - control characters in any input (would mangle argv parsing on every
|
|
15
|
+
* open-tool we target)
|
|
16
|
+
* - on Windows only: cmd.exe shell metacharacters (`& | < > ^ " ! %`)
|
|
17
|
+
* because the Windows path passes the URL through `cmd.exe /c start`
|
|
18
|
+
* to invoke the `start` shell builtin. macOS (`open`) and Linux
|
|
19
|
+
* (`xdg-open`) go through `execFile` with an args array — no shell
|
|
20
|
+
* involved — so those characters are safe to pass verbatim there.
|
|
21
|
+
*
|
|
22
|
+
* The platform-aware split fixes a real-world bug: legitimate URLs
|
|
23
|
+
* containing `&` (query separators), `%` (percent-encoding — produced
|
|
24
|
+
* by `encodeURIComponent` for any branch name with `/`), or `!` were
|
|
25
|
+
* being rejected on macOS/Linux where they pose no shell-injection
|
|
26
|
+
* risk. The TUI's "open branch on web" action silently no-op'd for
|
|
27
|
+
* any branch like `feat/my-thing` because its built URL contained
|
|
28
|
+
* `feat%2Fmy-thing`.
|
|
29
|
+
*
|
|
12
30
|
* @param {string} url
|
|
31
|
+
* @param {string} [platform=process.platform] - Override for tests.
|
|
13
32
|
* @returns {boolean}
|
|
14
33
|
*/
|
|
15
|
-
function isSafeUrl(url) {
|
|
34
|
+
function isSafeUrl(url, platform) {
|
|
35
|
+
if (platform === undefined) platform = process.platform;
|
|
16
36
|
if (!url || typeof url !== 'string') return false;
|
|
17
37
|
// Must start with http://, https://, or file://
|
|
18
38
|
if (!/^https?:\/\/|^file:\/\//i.test(url)) return false;
|
|
19
|
-
// Reject
|
|
20
|
-
|
|
39
|
+
// Reject embedded control bytes regardless of platform — every
|
|
40
|
+
// open-tool's argv parser would mangle them, and there is no
|
|
41
|
+
// legitimate reason for a URL to contain them.
|
|
42
|
+
// eslint-disable-next-line no-control-regex
|
|
43
|
+
if (/[\x00-\x1f\x7f]/.test(url)) return false;
|
|
44
|
+
// Windows-only: reject the chars cmd.exe interprets in unquoted args.
|
|
45
|
+
if (platform === 'win32' && /[&|<>^"!%]/.test(url)) return false;
|
|
21
46
|
return true;
|
|
22
47
|
}
|
|
23
48
|
|