From 3560df5d46dd0a837da3fd0cafef756db1ea2153 Mon Sep 17 00:00:00 2001 From: Luis Henrique Mulinari Date: Mon, 8 Dec 2025 10:30:49 -0300 Subject: [PATCH] Fix terminal line wrapping by vendoring single-line-log The @wwa/single-line-log package is unmaintained and contains a bug that causes line clearing to fail when output wraps across multiple physical terminal lines, resulting in stacked output. This change vendors the fixed version locally with the fix from freeall/single-line-log#21, which uses '\r' instead of '\x1b[1000D' for cursor positioning to ensure consistent behavior across all terminal emulators. Changes: - Remove @wwa/single-line-log dependency - Add src/lib/cli/single-log-line.ts with MIT license preserved - Update all imports to use local implementation - Add string-width@8.1.0 as direct dependency Related PR: - https://github.com/freeall/single-line-log/pull/21 --- npm-shrinkwrap.json | 301 +++++++++++++++++++++++++++---- package.json | 2 +- src/bin/vip-sync.js | 2 +- src/lib/cli/progress.ts | 2 +- src/lib/cli/single-log-line.ts | 99 ++++++++++ src/lib/media-import/progress.ts | 3 +- src/lib/validations/sql.ts | 2 +- 7 files changed, 368 insertions(+), 43 deletions(-) create mode 100644 src/lib/cli/single-log-line.ts diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 2407f6451..e5b7e0efd 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -13,7 +13,6 @@ "@apollo/client": "3.3.6", "@automattic/vip-search-replace": "^2.0.0", "@json2csv/plainjs": "^7.0.3", - "@wwa/single-line-log": "^1.1.4", "args": "5.0.3", "chalk": "^5.6.2", "check-disk-space": "3.4.0", @@ -40,6 +39,7 @@ "socket.io-stream": "npm:@wwa/socket.io-stream@^0.10.0", "socks-proxy-agent": "^8.0.5", "ssh2": "1.17.0", + "string-width": "8.1.0", "tar": "^7.4.0", "update-notifier": "7.3.1", "xml2js": "^0.6.2" @@ -4704,15 +4704,6 @@ "node": ">=8" } }, - "node_modules/@wwa/single-line-log": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@wwa/single-line-log/-/single-line-log-1.1.4.tgz", - "integrity": "sha512-xlpwffB2aMrqStfYgW31vQiUQojpoFfGymp09VVnB2pfEV28pNkHtd5l7uNesijimML2KvVXAo6jxaQvMBpeyA==", - "license": "MIT", - "dependencies": { - "string-width": "^4.2.2" - } - }, "node_modules/acorn": { "version": "8.11.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", @@ -4759,6 +4750,20 @@ "string-width": "^4.1.0" } }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -5699,6 +5704,20 @@ "node": ">= 10" } }, + "node_modules/cli-columns/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cli-table3": { "version": "0.6.5", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", @@ -5713,6 +5732,20 @@ "@colors/colors": "1.5.0" } }, + "node_modules/cli-table3/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -5723,6 +5756,20 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -7792,9 +7839,10 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", - "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "license": "MIT", "engines": { "node": ">=18" }, @@ -11193,6 +11241,20 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/lando/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/lando/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -13094,17 +13156,19 @@ } }, "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/string-width-cjs": { @@ -13121,6 +13185,33 @@ "node": ">=8" } }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", @@ -14240,6 +14331,20 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -14270,6 +14375,20 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -14433,6 +14552,20 @@ "node": ">=12" } }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -17587,14 +17720,6 @@ "tslib": "^1.14.1" } }, - "@wwa/single-line-log": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@wwa/single-line-log/-/single-line-log-1.1.4.tgz", - "integrity": "sha512-xlpwffB2aMrqStfYgW31vQiUQojpoFfGymp09VVnB2pfEV28pNkHtd5l7uNesijimML2KvVXAo6jxaQvMBpeyA==", - "requires": { - "string-width": "^4.2.2" - } - }, "acorn": { "version": "8.11.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", @@ -17627,6 +17752,18 @@ "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", "requires": { "string-width": "^4.1.0" + }, + "dependencies": { + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } } }, "ansi-colors": { @@ -18266,6 +18403,18 @@ "requires": { "string-width": "^4.2.3", "strip-ansi": "^6.0.1" + }, + "dependencies": { + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } } }, "cli-table3": { @@ -18275,6 +18424,18 @@ "requires": { "@colors/colors": "1.5.0", "string-width": "^4.2.0" + }, + "dependencies": { + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } } }, "cliui": { @@ -18285,6 +18446,18 @@ "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } } }, "clone": { @@ -19768,9 +19941,9 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-east-asian-width": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", - "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==" }, "get-intrinsic": { "version": "1.3.0", @@ -22065,6 +22238,16 @@ "brace-expansion": "^2.0.1" } }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -23404,13 +23587,27 @@ } }, "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==" + }, + "strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "requires": { + "ansi-regex": "^6.0.1" + } + } } }, "string-width-cjs": { @@ -24204,6 +24401,16 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } } } }, @@ -24237,6 +24444,16 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } } } }, @@ -24335,6 +24552,16 @@ "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } } } }, diff --git a/package.json b/package.json index fa9831930..b1754f8a1 100644 --- a/package.json +++ b/package.json @@ -142,7 +142,6 @@ "@apollo/client": "3.3.6", "@automattic/vip-search-replace": "^2.0.0", "@json2csv/plainjs": "^7.0.3", - "@wwa/single-line-log": "^1.1.4", "args": "5.0.3", "chalk": "^5.6.2", "check-disk-space": "3.4.0", @@ -169,6 +168,7 @@ "socket.io-stream": "npm:@wwa/socket.io-stream@^0.10.0", "socks-proxy-agent": "^8.0.5", "ssh2": "1.17.0", + "string-width": "8.1.0", "tar": "^7.4.0", "update-notifier": "7.3.1", "xml2js": "^0.6.2" diff --git a/src/bin/vip-sync.js b/src/bin/vip-sync.js index d38c8e856..d62bf67f2 100755 --- a/src/bin/vip-sync.js +++ b/src/bin/vip-sync.js @@ -1,6 +1,5 @@ #!/usr/bin/env node -import { stdout } from '@wwa/single-line-log'; import chalk from 'chalk'; import gql from 'graphql-tag'; @@ -8,6 +7,7 @@ import API from '../lib/api'; import app from '../lib/api/app'; import command from '../lib/cli/command'; import { formatEnvironment } from '../lib/cli/format'; +import { stdout } from '../lib/cli/single-log-line'; import { trackEvent } from '../lib/tracker'; const appQuery = `id,name,environments{ diff --git a/src/lib/cli/progress.ts b/src/lib/cli/progress.ts index ff68f5984..942c141a3 100644 --- a/src/lib/cli/progress.ts +++ b/src/lib/cli/progress.ts @@ -1,6 +1,6 @@ -import { stdout as singleLogLine } from '@wwa/single-line-log'; import { EOL } from 'node:os'; +import { stdout as singleLogLine } from './single-log-line'; import { getGlyphForStatus, RunningSprite } from '../../lib/cli/format'; const PRINT_INTERVAL = process.env.DEBUG ? 5000 : 200; // How often the report is printed. Mainly affects the "spinner" animation. diff --git a/src/lib/cli/single-log-line.ts b/src/lib/cli/single-log-line.ts new file mode 100644 index 000000000..5e600df11 --- /dev/null +++ b/src/lib/cli/single-log-line.ts @@ -0,0 +1,99 @@ +/** + * single-line-log + * Copyright (c) Tobias Baunbæk + * + * This file is adapted from https://github.com/freeall/single-line-log + * + * NOTE: This file was copied locally because the original repository is no longer + * maintained and contains a bug that causes line clearing to fail on most terminals + * when output wraps across multiple physical lines. The fix uses '\r' instead of + * '\x1b[1000D' for cursor positioning and outputs '\n' in clear(). + * + * See: https://github.com/freeall/single-line-log/pull/21 + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import stringWidth from 'string-width'; + +const MOVE_UP = Buffer.from( '1b5b3141', 'hex' ).toString(); +const CLEAR_LINE = Buffer.from( '1b5b304b', 'hex' ).toString(); + +interface LogFunction { + ( ...args: unknown[] ): void; + clear: () => void; +} + +const singleLogLine = ( stream: NodeJS.WriteStream ): LogFunction => { + if ( ! stream || typeof stream.write !== 'function' ) { + throw new TypeError( 'Invalid stream provided' ); + } + + const write = stream.write; + let str: string | null; + + stream.write = function ( data: unknown, ...args: unknown[] ) { + if ( str && data !== str ) { + str = null; + } + return write.apply( this, [ data, ...args ] as Parameters< typeof write > ); + }; + + if ( stream === process.stderr || stream === process.stdout ) { + process.on( 'exit', function () { + if ( str !== null ) { + stream.write( '' ); + } + } ); + } + + let prevLineCount = 0; + const log = function ( ...args: unknown[] ): void { + str = ''; + const nextStr = Array.prototype.join.call( args, ' ' ); + + // Move to beginning of line and clear, then move up for each previous line + for ( let i = 0; i < prevLineCount; i++ ) { + str += '\r' + CLEAR_LINE + ( i < prevLineCount - 1 ? MOVE_UP : '' ); + } + + // Actual log output + str += nextStr; + stream.write( str ); + + // How many lines to remove on next clear screen + const prevLines = nextStr.split( '\n' ); + prevLineCount = 0; + for ( let i = 0; i < prevLines.length; i++ ) { + prevLineCount += Math.ceil( stringWidth( prevLines[ i ] ) / stream.columns ) || 1; + } + } as LogFunction; + + log.clear = function () { + stream.write( '\n' ); + }; + + return log; +}; + +export default singleLogLine; +export const stdout = singleLogLine( process.stdout ); +export const stderr = singleLogLine( process.stderr ); diff --git a/src/lib/media-import/progress.ts b/src/lib/media-import/progress.ts index a8f790d3a..9a2dc16b0 100644 --- a/src/lib/media-import/progress.ts +++ b/src/lib/media-import/progress.ts @@ -1,8 +1,7 @@ -import { stdout as singleLogLine } from '@wwa/single-line-log'; - import { AppEnvironmentMediaImportStatus } from '../../graphqlTypes'; import { RunningSprite } from '../../lib/cli/format'; import { getGlyphForStatus } from '../../lib/media-import/status'; +import { stdout as singleLogLine } from '../cli/single-log-line'; const PRINT_INTERVAL = process.env.DEBUG ? 5000 : 200; // How often the report is printed. Mainly affects the "spinner" animation. diff --git a/src/lib/validations/sql.ts b/src/lib/validations/sql.ts index 1f63a20b6..b44e62c46 100644 --- a/src/lib/validations/sql.ts +++ b/src/lib/validations/sql.ts @@ -1,4 +1,4 @@ -import { stdout as log } from '@wwa/single-line-log'; +import { stdout as log } from '../cli/single-log-line'; import chalk from 'chalk'; import path from 'node:path';