mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 02:11:28 +00:00
feat(ui): add central ui abstraction layer for cli styling
- create src/utils/ui.ts with semantic colors, status indicators, boxes, tables, spinners - add ui types (SemanticColor, BoxOptions, TableOptions, SpinnerOptions) to src/types/utils.ts - add deprecation notice to src/utils/helpers.ts for legacy color functions - add unit tests for ui module (11 test cases) - install chalk@5.6.2, boxen@8.0.1, gradient-string@3.0.0 features: - semantic color system (success, error, warning, info, primary, secondary) - ascii-only status indicators: [OK], [X], [!], [i] - tty-aware with NO_COLOR/FORCE_COLOR support - styled boxes with boxen (round, double, bold borders) - formatted tables with cli-table3 - animated spinners with ora (fallback to plain text in non-tty) - gradient text for headers (cyan-to-blue) - lazy esm module loading for chalk/boxen/gradient-string
This commit is contained in:
@@ -4,7 +4,10 @@
|
||||
"": {
|
||||
"name": "@kaitranntt/ccs",
|
||||
"dependencies": {
|
||||
"boxen": "^8.0.1",
|
||||
"chalk": "^5.6.2",
|
||||
"cli-table3": "^0.6.5",
|
||||
"gradient-string": "^3.0.0",
|
||||
"ora": "^9.0.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -164,6 +167,8 @@
|
||||
|
||||
"@types/normalize-package-data": ["@types/normalize-package-data@2.4.4", "", {}, "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="],
|
||||
|
||||
"@types/tinycolor2": ["@types/tinycolor2@1.4.6", "", {}, "sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.48.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/type-utils": "8.48.0", "@typescript-eslint/utils": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.48.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.48.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ=="],
|
||||
@@ -196,6 +201,8 @@
|
||||
|
||||
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
||||
|
||||
"ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
|
||||
|
||||
"ansi-escapes": ["ansi-escapes@7.2.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||
@@ -216,6 +223,8 @@
|
||||
|
||||
"bottleneck": ["bottleneck@2.19.5", "", {}, "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="],
|
||||
|
||||
"boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
@@ -224,9 +233,9 @@
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
|
||||
"camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
"char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="],
|
||||
|
||||
@@ -234,6 +243,8 @@
|
||||
|
||||
"clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="],
|
||||
|
||||
"cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
|
||||
|
||||
"cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
|
||||
|
||||
"cli-highlight": ["cli-highlight@2.1.11", "", { "dependencies": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", "mz": "^2.4.0", "parse5": "^5.1.1", "parse5-htmlparser2-tree-adapter": "^6.0.0", "yargs": "^16.0.0" }, "bin": { "highlight": "bin/highlight" } }, "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg=="],
|
||||
@@ -296,7 +307,7 @@
|
||||
|
||||
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
"emojilib": ["emojilib@2.4.0", "", {}, "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw=="],
|
||||
|
||||
@@ -390,6 +401,8 @@
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"gradient-string": ["gradient-string@3.0.0", "", { "dependencies": { "chalk": "^5.3.0", "tinygradient": "^1.1.5" } }, "sha512-frdKI4Qi8Ihp4C6wZNB565de/THpIaw3DjP5ku87M+N9rNSGmPTjfkq61SdRXB7eCaL8O1hkKDvf6CDMtOzIAg=="],
|
||||
|
||||
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
|
||||
|
||||
"handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="],
|
||||
@@ -706,7 +719,7 @@
|
||||
|
||||
"stream-combiner2": ["stream-combiner2@1.1.1", "", { "dependencies": { "duplexer2": "~0.1.0", "readable-stream": "^2.0.2" } }, "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw=="],
|
||||
|
||||
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
@@ -746,10 +759,14 @@
|
||||
|
||||
"time-span": ["time-span@5.1.0", "", { "dependencies": { "convert-hrtime": "^5.0.0" } }, "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA=="],
|
||||
|
||||
"tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="],
|
||||
|
||||
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"tinygradient": ["tinygradient@1.1.5", "", { "dependencies": { "@types/tinycolor2": "^1.4.0", "tinycolor2": "^1.0.0" } }, "sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"traverse": ["traverse@0.6.8", "", {}, "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA=="],
|
||||
@@ -760,7 +777,7 @@
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
|
||||
"type-fest": ["type-fest@5.2.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-xxCJm+Bckc6kQBknN7i9fnP/xobQRsRQxR01CztFkp/h++yfVxUUcmMgfR2HttJx/dpWjS9ubVuyspJv24Q9DA=="],
|
||||
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||
|
||||
"typescript": ["typescript@5.3.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw=="],
|
||||
|
||||
@@ -792,13 +809,15 @@
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
|
||||
|
||||
"workerpool": ["workerpool@9.3.4", "", {}, "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
||||
|
||||
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
@@ -820,18 +839,12 @@
|
||||
|
||||
"@commitlint/config-validator/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
|
||||
|
||||
"@commitlint/format/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
"@commitlint/load/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
"@commitlint/parse/conventional-changelog-angular": ["conventional-changelog-angular@7.0.0", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ=="],
|
||||
|
||||
"@commitlint/parse/conventional-commits-parser": ["conventional-commits-parser@5.0.0", "", { "dependencies": { "JSONStream": "^1.3.5", "is-text-path": "^2.0.0", "meow": "^12.0.1", "split2": "^4.0.0" }, "bin": { "conventional-commits-parser": "cli.mjs" } }, "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA=="],
|
||||
|
||||
"@commitlint/top-level/find-up": ["find-up@7.0.0", "", { "dependencies": { "locate-path": "^7.2.0", "path-exists": "^5.0.0", "unicorn-magic": "^0.1.0" } }, "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g=="],
|
||||
|
||||
"@commitlint/types/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
@@ -858,12 +871,20 @@
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"cli-highlight/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="],
|
||||
|
||||
"cli-table3/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"conventional-changelog-writer/meow": ["meow@13.2.0", "", {}, "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA=="],
|
||||
|
||||
"conventional-commits-parser/meow": ["meow@13.2.0", "", {}, "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA=="],
|
||||
@@ -872,6 +893,8 @@
|
||||
|
||||
"env-ci/execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="],
|
||||
|
||||
"eslint/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"fdir/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
@@ -888,12 +911,10 @@
|
||||
|
||||
"load-json-file/parse-json": ["parse-json@4.0.0", "", { "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } }, "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw=="],
|
||||
|
||||
"log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"log-symbols/is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
|
||||
|
||||
"make-asynchronous/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||
|
||||
"marked-terminal/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
"mocha/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"npm/@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
|
||||
@@ -1210,8 +1231,6 @@
|
||||
|
||||
"npm/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
|
||||
|
||||
"ora/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
"ora/log-symbols": ["log-symbols@7.0.1", "", { "dependencies": { "is-unicode-supported": "^2.0.0", "yoctocolors": "^2.1.1" } }, "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg=="],
|
||||
|
||||
"ora/string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg=="],
|
||||
@@ -1224,8 +1243,12 @@
|
||||
|
||||
"rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
|
||||
|
||||
"read-package-up/type-fest": ["type-fest@5.2.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-xxCJm+Bckc6kQBknN7i9fnP/xobQRsRQxR01CztFkp/h++yfVxUUcmMgfR2HttJx/dpWjS9ubVuyspJv24Q9DA=="],
|
||||
|
||||
"read-pkg/parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="],
|
||||
|
||||
"read-pkg/type-fest": ["type-fest@5.2.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-xxCJm+Bckc6kQBknN7i9fnP/xobQRsRQxR01CztFkp/h++yfVxUUcmMgfR2HttJx/dpWjS9ubVuyspJv24Q9DA=="],
|
||||
|
||||
"readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
||||
|
||||
"restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
|
||||
@@ -1246,7 +1269,7 @@
|
||||
|
||||
"signale/figures": ["figures@2.0.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA=="],
|
||||
|
||||
"string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
@@ -1262,10 +1285,16 @@
|
||||
|
||||
"tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"yargs-unparser/camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
|
||||
|
||||
"@commitlint/config-validator/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
|
||||
"@commitlint/parse/conventional-commits-parser/split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
||||
@@ -1304,14 +1333,26 @@
|
||||
|
||||
"@semantic-release/release-notes-generator/read-package-up/read-pkg": ["read-pkg@9.0.1", "", { "dependencies": { "@types/normalize-package-data": "^2.4.3", "normalize-package-data": "^6.0.0", "parse-json": "^8.0.0", "type-fest": "^4.6.0", "unicorn-magic": "^0.1.0" } }, "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA=="],
|
||||
|
||||
"@semantic-release/release-notes-generator/read-package-up/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"cli-highlight/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"cli-highlight/yargs/cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="],
|
||||
|
||||
"cli-highlight/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"cli-highlight/yargs/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="],
|
||||
|
||||
"cli-table3/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"cli-table3/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"env-ci/execa/get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="],
|
||||
@@ -1328,8 +1369,12 @@
|
||||
|
||||
"env-ci/execa/strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="],
|
||||
|
||||
"eslint/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"log-symbols/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"mocha/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"npm/@sigstore/sign/proc-log": ["proc-log@5.0.0", "", {}, "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ=="],
|
||||
@@ -1370,8 +1415,6 @@
|
||||
|
||||
"semantic-release/yargs/cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="],
|
||||
|
||||
"semantic-release/yargs/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"semantic-release/yargs/yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
|
||||
|
||||
"signale/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
|
||||
@@ -1384,11 +1427,13 @@
|
||||
|
||||
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
"yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"@commitlint/top-level/find-up/locate-path/p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="],
|
||||
|
||||
@@ -1404,8 +1449,18 @@
|
||||
|
||||
"@semantic-release/release-notes-generator/read-package-up/read-pkg/unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="],
|
||||
|
||||
"ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"cli-highlight/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"cli-highlight/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"cli-highlight/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"cli-highlight/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"cli-table3/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"env-ci/execa/npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
|
||||
|
||||
"env-ci/execa/onetime/mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
|
||||
@@ -1424,23 +1479,21 @@
|
||||
|
||||
"semantic-release/execa/npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
|
||||
|
||||
"semantic-release/yargs/cliui/wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
||||
|
||||
"semantic-release/yargs/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
"signale/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
|
||||
|
||||
"signale/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
|
||||
|
||||
"yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"@commitlint/top-level/find-up/locate-path/p-locate/p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="],
|
||||
|
||||
"@semantic-release/release-notes-generator/read-package-up/read-pkg/normalize-package-data/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="],
|
||||
|
||||
"cli-highlight/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"pkg-conf/find-up/locate-path/p-locate/p-limit": ["p-limit@1.3.0", "", { "dependencies": { "p-try": "^1.0.0" } }, "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q=="],
|
||||
"cli-highlight/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"semantic-release/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
"pkg-conf/find-up/locate-path/p-locate/p-limit": ["p-limit@1.3.0", "", { "dependencies": { "p-try": "^1.0.0" } }, "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q=="],
|
||||
|
||||
"signale/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ CCS (Claude Code Switch) v4.5.0 is a lightweight CLI wrapper enabling instant pr
|
||||
|
||||
## Version Evolution
|
||||
|
||||
### v4.5.0 Architecture (Current, Phase 02 Complete - 2025-11-28)
|
||||
### v4.5.0 Architecture (Current, Phase 02 Complete - 2025-11-28, UI Phase 1 - 2025-12-01)
|
||||
- **Total LOC**: ~8,477 lines (JavaScript/TypeScript)
|
||||
- **Main File**: src/ccs.ts - 593 lines (**44.6% reduction** from 1,071 lines)
|
||||
- **Key Features**: AI delegation, stream-JSON output, shell completion, doctor diagnostics, sync command
|
||||
@@ -271,13 +271,14 @@ src/ # TypeScript source files (Phase 02 Modular Archite
|
||||
│ ├── instance-manager.ts # Instance lifecycle (~220 lines)
|
||||
│ ├── recovery-manager.ts # Auto-recovery (~80 lines)
|
||||
│ └── shared-manager.ts # Shared symlinking (~50 lines)
|
||||
├── utils/ # Utilities (expanded in v4.x + Phase 02)
|
||||
├── utils/ # Utilities (expanded in v4.x + Phase 02, UI Phase 1)
|
||||
│ ├── claude-detector.ts # CLI detection (~70 lines)
|
||||
│ ├── claude-dir-installer.ts # .claude/ installer (v4.1.1, ~150 lines)
|
||||
│ ├── claude-symlink-manager.ts # Selective symlinks (v4.1, ~200 lines)
|
||||
│ ├── config-manager.ts # Config management (~80 lines)
|
||||
│ ├── shell-executor.ts # 1.5KB - Cross-platform execution (Phase 02 NEW)
|
||||
│ ├── package-manager-detector.ts # 3.8KB - Package manager detection (Phase 02 NEW)
|
||||
│ ├── ui.ts # 5.2KB - Central UI abstraction (UI Phase 1 NEW)
|
||||
│ ├── delegation-validator.js # Delegation validation (v4.0, ~100 lines)
|
||||
│ ├── error-codes.js # Error codes (~50 lines)
|
||||
│ ├── error-manager.js # Error handling (~200 lines)
|
||||
@@ -424,7 +425,23 @@ ccs.js: execClaude(["command"], {CLAUDE_CONFIG_DIR: instancePath})
|
||||
Claude CLI: Read credentials from instance, execute
|
||||
```
|
||||
|
||||
## Key Features (v4.5.0 - Phase 02 Complete)
|
||||
## Key Features (v4.5.0 - Phase 02 Complete, UI Phase 1)
|
||||
|
||||
### 0. Central UI Abstraction Layer (UI Phase 1 - 2025-12-01)
|
||||
- **New Module**: src/utils/ui.ts (5.2KB) - Semantic, TTY-aware CLI styling
|
||||
- **Dependencies**: chalk@5.6.2, boxen@8.0.1, gradient-string@3.0.0, cli-table3@0.6.5, ora@5.4.1
|
||||
- **Features**:
|
||||
- Semantic color system (success, error, warning, info, dim, primary, secondary, command, path)
|
||||
- ASCII-only status indicators ([OK], [X], [!], [i]) - NO EMOJIS per CLAUDE.md
|
||||
- TTY-aware output (respects NO_COLOR, FORCE_COLOR env vars)
|
||||
- Box rendering (with fallback ASCII renderer)
|
||||
- Table rendering via cli-table3
|
||||
- Spinner/progress (ora wrapper with fallback)
|
||||
- Section headers with optional gradient
|
||||
- Lazy loading of ESM modules for CommonJS compatibility
|
||||
- **Type Support**: SemanticColor, BoxOptions, TableOptions, SpinnerOptions, SpinnerController
|
||||
- **Compliance**: Strict CLAUDE.md adherence (no emojis, TTY-aware, NO_COLOR respect)
|
||||
- **Fallback Architecture**: Works in non-TTY environments with graceful degradation
|
||||
|
||||
### 1. npm-First Installation (Phase 02 - 2025-11-28)
|
||||
- **Recommended method**: All users directed to npm installation
|
||||
@@ -552,6 +569,14 @@ Claude CLI: Read credentials from instance, execute
|
||||
|
||||
## Summary
|
||||
|
||||
**CCS UI Phase 1 Achievements (2025-12-01)**:
|
||||
- **Central UI Module**: Introduced src/utils/ui.ts for semantic, TTY-aware CLI styling
|
||||
- **CLAUDE.md Compliance**: ASCII-only indicators, NO_COLOR respect, TTY detection
|
||||
- **ESM Compatibility**: Lazy loading strategy for chalk, boxen, gradient-string, ora in CommonJS project
|
||||
- **Fallback Architecture**: Works in non-TTY (pipes/CI) with graceful degradation to plain text
|
||||
- **Type System**: Complete TypeScript definitions for all UI functions
|
||||
- **Color Palette**: Professional cyan-to-blue gradient (#00ECFA to #0099FF)
|
||||
|
||||
**CCS Phase 02 Achievements (2025-11-28)**:
|
||||
- **npm-First Distribution**: Deprecated native shell installers, all users directed to npm
|
||||
- **Modular Command Architecture**: 6 specialized command handlers with single responsibility principle
|
||||
|
||||
@@ -70,7 +70,10 @@
|
||||
"postinstall": "node scripts/postinstall.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"boxen": "^8.0.1",
|
||||
"chalk": "^5.6.2",
|
||||
"cli-table3": "^0.6.5",
|
||||
"gradient-string": "^3.0.0",
|
||||
"ora": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -41,3 +41,64 @@ export interface TerminalInfo {
|
||||
* Helper result types
|
||||
*/
|
||||
export type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
|
||||
|
||||
// =============================================================================
|
||||
// UI TYPES (Phase 1: CLI UI/UX Enhancement)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Semantic color names for UI elements
|
||||
*/
|
||||
export type SemanticColor =
|
||||
| 'success' // green - [OK] messages
|
||||
| 'error' // red - [X] messages
|
||||
| 'warning' // yellow - [!] messages
|
||||
| 'info' // cyan - [i] messages
|
||||
| 'dim' // gray - secondary text
|
||||
| 'primary' // #00ECFA - headers, emphasis
|
||||
| 'secondary' // #0099FF - links, accents
|
||||
| 'command' // yellow italic - command examples
|
||||
| 'path'; // cyan underline - file paths
|
||||
|
||||
/**
|
||||
* Box style options for boxen wrapper
|
||||
*/
|
||||
export interface BoxOptions {
|
||||
title?: string;
|
||||
titleAlignment?: 'left' | 'center' | 'right';
|
||||
padding?: number;
|
||||
margin?: number;
|
||||
borderColor?: string;
|
||||
borderStyle?: 'single' | 'double' | 'round' | 'bold' | 'classic';
|
||||
}
|
||||
|
||||
/**
|
||||
* Table style options for cli-table3 wrapper
|
||||
*/
|
||||
export interface TableOptions {
|
||||
head?: string[];
|
||||
colWidths?: number[];
|
||||
wordWrap?: boolean;
|
||||
style?: 'unicode' | 'ascii';
|
||||
}
|
||||
|
||||
/**
|
||||
* Spinner options for ora wrapper
|
||||
*/
|
||||
export interface SpinnerOptions {
|
||||
text: string;
|
||||
color?: string;
|
||||
prefixText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spinner control interface
|
||||
*/
|
||||
export interface SpinnerController {
|
||||
succeed: (msg?: string) => void;
|
||||
fail: (msg?: string) => void;
|
||||
warn: (msg?: string) => void;
|
||||
info: (msg?: string) => void;
|
||||
update: (text: string) => void;
|
||||
stop: () => void;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,20 @@ import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { ColorName, TerminalInfo } from '../types';
|
||||
|
||||
// =============================================================================
|
||||
// DEPRECATION NOTICE
|
||||
// =============================================================================
|
||||
// The color functions in this file are deprecated in favor of the new UI layer.
|
||||
// Please use `import { ui } from './ui'` instead for:
|
||||
// - ui.color(text, semantic) - semantic colors
|
||||
// - ui.ok(), ui.fail(), ui.warn(), ui.info() - status indicators
|
||||
// - ui.box(), ui.table() - containers
|
||||
// - ui.header(), ui.subheader() - section headers
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* TTY-aware color detection (matches lib/ccs bash logic)
|
||||
* @deprecated Use `import { ui } from './ui'` instead
|
||||
*/
|
||||
function getColors(): Record<ColorName, string> {
|
||||
const forcedColors = process.env.FORCE_COLOR;
|
||||
@@ -33,6 +45,7 @@ export const colors = getColors();
|
||||
|
||||
/**
|
||||
* Helper: Apply color to text (returns plain text if colors disabled)
|
||||
* @deprecated Use `import { ui } from './ui'` and `ui.color()` instead
|
||||
*/
|
||||
export function colored(text: string, colorName: ColorName = 'reset'): string {
|
||||
const currentColors = getColors();
|
||||
|
||||
+491
@@ -0,0 +1,491 @@
|
||||
/**
|
||||
* Central UI Abstraction Layer
|
||||
*
|
||||
* Provides semantic, TTY-aware styling for CLI output.
|
||||
* Wraps chalk, boxen, cli-table3, ora with consistent API.
|
||||
*
|
||||
* Constraints:
|
||||
* - NO EMOJIS (ASCII only: [OK], [X], [!], [i])
|
||||
* - TTY-aware (plain text in pipes/CI)
|
||||
* - Respects NO_COLOR environment variable
|
||||
*
|
||||
* @module utils/ui
|
||||
*/
|
||||
|
||||
import type {
|
||||
BoxOptions,
|
||||
TableOptions,
|
||||
SpinnerOptions,
|
||||
SemanticColor,
|
||||
SpinnerController,
|
||||
} from '../types/utils';
|
||||
|
||||
// =============================================================================
|
||||
// ESM MODULE TYPES & LAZY LOADING
|
||||
// =============================================================================
|
||||
|
||||
// Type definitions for dynamically imported ESM modules
|
||||
type ChalkInstance = typeof import('chalk').default;
|
||||
type BoxenFunction = typeof import('boxen').default;
|
||||
type GradientStringInstance = typeof import('gradient-string').default;
|
||||
// ora v9 is ESM-only, imported dynamically at runtime
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type OraModule = any;
|
||||
|
||||
// Module cache for lazy loading
|
||||
let chalkModule: ChalkInstance | null = null;
|
||||
let boxenModule: BoxenFunction | null = null;
|
||||
let gradientModule: GradientStringInstance | null = null;
|
||||
let oraModule: OraModule | null = null;
|
||||
|
||||
// Initialization state
|
||||
let initialized = false;
|
||||
|
||||
// =============================================================================
|
||||
// COLOR PALETTE (Professional cyan-to-blue)
|
||||
// =============================================================================
|
||||
|
||||
const COLORS = {
|
||||
primary: '#00ECFA', // Bright cyan
|
||||
secondary: '#0099FF', // Sky blue
|
||||
neutral: '#808080', // Gray
|
||||
} as const;
|
||||
|
||||
// =============================================================================
|
||||
// INITIALIZATION
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Initialize UI dependencies (call once at startup)
|
||||
* Uses dynamic imports for ESM packages in CommonJS project
|
||||
*/
|
||||
export async function initUI(): Promise<void> {
|
||||
if (initialized) return;
|
||||
|
||||
try {
|
||||
// Dynamic import for ESM-only packages
|
||||
const [chalkImport, boxenImport, gradientImport, oraImport] = await Promise.all([
|
||||
import('chalk'),
|
||||
import('boxen'),
|
||||
import('gradient-string'),
|
||||
import('ora'),
|
||||
]);
|
||||
|
||||
chalkModule = chalkImport.default;
|
||||
boxenModule = boxenImport.default;
|
||||
gradientModule = gradientImport.default;
|
||||
oraModule = oraImport.default;
|
||||
initialized = true;
|
||||
} catch (_e) {
|
||||
// Fallback: UI works without colors if imports fail
|
||||
console.error('[!] UI initialization failed, using plain text mode');
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TTY & COLOR DETECTION
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Check if colors should be used
|
||||
* Respects NO_COLOR and FORCE_COLOR environment variables
|
||||
*/
|
||||
function useColors(): boolean {
|
||||
// FORCE_COLOR overrides all checks
|
||||
if (process.env.FORCE_COLOR) return true;
|
||||
// NO_COLOR disables colors
|
||||
if (process.env.NO_COLOR) return false;
|
||||
// Otherwise, check if TTY
|
||||
return !!process.stdout.isTTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if interactive mode (TTY + not CI)
|
||||
*/
|
||||
export function isInteractive(): boolean {
|
||||
return !!process.stdout.isTTY && !process.env.CI && !process.env.NO_COLOR;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// COLOR SYSTEM
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Apply semantic color to text
|
||||
*/
|
||||
export function color(text: string, semantic: SemanticColor): string {
|
||||
if (!chalkModule || !useColors()) return text;
|
||||
|
||||
switch (semantic) {
|
||||
case 'success':
|
||||
return chalkModule.green.bold(text);
|
||||
case 'error':
|
||||
return chalkModule.red.bold(text);
|
||||
case 'warning':
|
||||
return chalkModule.yellow(text);
|
||||
case 'info':
|
||||
return chalkModule.cyan(text);
|
||||
case 'dim':
|
||||
return chalkModule.gray(text);
|
||||
case 'primary':
|
||||
return chalkModule.hex(COLORS.primary).bold(text);
|
||||
case 'secondary':
|
||||
return chalkModule.hex(COLORS.secondary)(text);
|
||||
case 'command':
|
||||
return chalkModule.yellow.italic(text);
|
||||
case 'path':
|
||||
return chalkModule.cyan.underline(text);
|
||||
default:
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply gradient to text (for headers)
|
||||
* Uses cyan-to-blue gradient for professional look
|
||||
*/
|
||||
export function gradientText(text: string): string {
|
||||
if (!gradientModule || !useColors()) return text;
|
||||
return gradientModule([COLORS.primary, COLORS.secondary])(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bold text
|
||||
*/
|
||||
export function bold(text: string): string {
|
||||
if (!chalkModule || !useColors()) return text;
|
||||
return chalkModule.bold(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dim text
|
||||
*/
|
||||
export function dim(text: string): string {
|
||||
if (!chalkModule || !useColors()) return text;
|
||||
return chalkModule.dim(text);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// STATUS INDICATORS (ASCII only - NO EMOJIS)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Success indicator: [OK]
|
||||
*/
|
||||
export function ok(message: string): string {
|
||||
return `${color('[OK]', 'success')} ${message}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error indicator: [X]
|
||||
*/
|
||||
export function fail(message: string): string {
|
||||
return `${color('[X]', 'error')} ${message}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning indicator: [!]
|
||||
*/
|
||||
export function warn(message: string): string {
|
||||
return `${color('[!]', 'warning')} ${message}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Info indicator: [i]
|
||||
*/
|
||||
export function info(message: string): string {
|
||||
return `${color('[i]', 'info')} ${message}`;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// BOX RENDERING
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Fallback ASCII box renderer (when boxen not available)
|
||||
*/
|
||||
function renderAsciiBox(content: string, options: BoxOptions): string {
|
||||
const lines = content.split('\n');
|
||||
const maxLen = Math.max(...lines.map((l) => l.length), (options.title?.length || 0) + 4);
|
||||
const width = maxLen + 4;
|
||||
|
||||
let result = '';
|
||||
const padding = options.padding ?? 1;
|
||||
|
||||
// Top border with optional title
|
||||
if (options.title) {
|
||||
const titlePad = Math.floor((width - options.title.length - 4) / 2);
|
||||
result +=
|
||||
'+' +
|
||||
'-'.repeat(titlePad) +
|
||||
' ' +
|
||||
options.title +
|
||||
' ' +
|
||||
'-'.repeat(width - titlePad - options.title.length - 4) +
|
||||
'+\n';
|
||||
} else {
|
||||
result += '+' + '-'.repeat(width - 2) + '+\n';
|
||||
}
|
||||
|
||||
// Padding top
|
||||
for (let i = 0; i < padding; i++) {
|
||||
result += '|' + ' '.repeat(width - 2) + '|\n';
|
||||
}
|
||||
|
||||
// Content
|
||||
for (const line of lines) {
|
||||
const pad = width - line.length - 4;
|
||||
result += '| ' + line + ' '.repeat(Math.max(0, pad)) + ' |\n';
|
||||
}
|
||||
|
||||
// Padding bottom
|
||||
for (let i = 0; i < padding; i++) {
|
||||
result += '|' + ' '.repeat(width - 2) + '|\n';
|
||||
}
|
||||
|
||||
// Bottom border
|
||||
result += '+' + '-'.repeat(width - 2) + '+';
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render content in a styled box
|
||||
*/
|
||||
export function box(content: string, options: BoxOptions = {}): string {
|
||||
if (!boxenModule) {
|
||||
return renderAsciiBox(content, options);
|
||||
}
|
||||
|
||||
const borderColor = useColors() ? options.borderColor || COLORS.primary : undefined;
|
||||
|
||||
return boxenModule(content, {
|
||||
padding: options.padding ?? 1,
|
||||
margin: options.margin ?? 0,
|
||||
borderStyle: options.borderStyle || 'round',
|
||||
borderColor,
|
||||
title: options.title,
|
||||
titleAlignment: options.titleAlignment || 'center',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render error box (red border)
|
||||
*/
|
||||
export function errorBox(content: string, title = 'ERROR'): string {
|
||||
return box(content, {
|
||||
title,
|
||||
borderColor: 'red',
|
||||
borderStyle: 'round',
|
||||
padding: 1,
|
||||
margin: 1,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render info box (primary color border)
|
||||
*/
|
||||
export function infoBox(content: string, title?: string): string {
|
||||
return box(content, {
|
||||
title,
|
||||
borderColor: COLORS.primary,
|
||||
borderStyle: 'round',
|
||||
padding: 1,
|
||||
});
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TABLE RENDERING
|
||||
// =============================================================================
|
||||
|
||||
// cli-table3 is CommonJS, use dynamic import pattern
|
||||
|
||||
const Table = require('cli-table3');
|
||||
|
||||
/**
|
||||
* Create styled table
|
||||
*/
|
||||
export function table(rows: string[][], options: TableOptions = {}): string {
|
||||
// Build table configuration
|
||||
const tableConfig: Record<string, unknown> = {
|
||||
wordWrap: options.wordWrap ?? true,
|
||||
chars:
|
||||
options.style === 'ascii'
|
||||
? {
|
||||
top: '-',
|
||||
'top-mid': '+',
|
||||
'top-left': '+',
|
||||
'top-right': '+',
|
||||
bottom: '-',
|
||||
'bottom-mid': '+',
|
||||
'bottom-left': '+',
|
||||
'bottom-right': '+',
|
||||
left: '|',
|
||||
'left-mid': '+',
|
||||
mid: '-',
|
||||
'mid-mid': '+',
|
||||
right: '|',
|
||||
'right-mid': '+',
|
||||
middle: '|',
|
||||
}
|
||||
: {
|
||||
top: '─',
|
||||
'top-mid': '┬',
|
||||
'top-left': '┌',
|
||||
'top-right': '┐',
|
||||
bottom: '─',
|
||||
'bottom-mid': '┴',
|
||||
'bottom-left': '└',
|
||||
'bottom-right': '┘',
|
||||
left: '│',
|
||||
'left-mid': '├',
|
||||
mid: '─',
|
||||
'mid-mid': '┼',
|
||||
right: '│',
|
||||
'right-mid': '┤',
|
||||
middle: '│',
|
||||
},
|
||||
};
|
||||
|
||||
// Only add head if provided (cli-table3 requires head length to match rows)
|
||||
if (options.head && options.head.length > 0) {
|
||||
tableConfig.head = options.head.map((h: string) => color(h, 'primary'));
|
||||
}
|
||||
|
||||
// Only add colWidths if provided
|
||||
if (options.colWidths) {
|
||||
tableConfig.colWidths = options.colWidths;
|
||||
}
|
||||
|
||||
const tableInstance = new Table(tableConfig);
|
||||
|
||||
rows.forEach((row) => tableInstance.push(row));
|
||||
return tableInstance.toString();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SPINNER / PROGRESS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Create and start a spinner
|
||||
* Falls back to plain text output in non-TTY environments
|
||||
*/
|
||||
export async function spinner(options: SpinnerOptions | string): Promise<SpinnerController> {
|
||||
const opts = typeof options === 'string' ? { text: options } : options;
|
||||
const isEnabled = isInteractive();
|
||||
|
||||
// Lazy load ora if not already loaded
|
||||
if (!oraModule && isEnabled) {
|
||||
try {
|
||||
oraModule = (await import('ora')).default;
|
||||
} catch (_e) {
|
||||
// Fallback to plain text
|
||||
}
|
||||
}
|
||||
|
||||
if (oraModule && isEnabled) {
|
||||
const s = oraModule({
|
||||
text: opts.text,
|
||||
color: 'cyan',
|
||||
prefixText: opts.prefixText,
|
||||
isEnabled,
|
||||
}).start();
|
||||
|
||||
return {
|
||||
succeed: (msg?: string) => s.succeed(msg || `${color('[OK]', 'success')} ${opts.text}`),
|
||||
fail: (msg?: string) => s.fail(msg || `${color('[X]', 'error')} ${opts.text}`),
|
||||
warn: (msg?: string) => s.warn(msg || `${color('[!]', 'warning')} ${opts.text}`),
|
||||
info: (msg?: string) => s.info(msg || `${color('[i]', 'info')} ${opts.text}`),
|
||||
update: (text: string) => {
|
||||
s.text = text;
|
||||
},
|
||||
stop: () => s.stop(),
|
||||
};
|
||||
}
|
||||
|
||||
// Fallback: plain text (non-TTY)
|
||||
console.log(`[i] ${opts.text}...`);
|
||||
return {
|
||||
succeed: (msg?: string) => console.log(ok(msg || opts.text)),
|
||||
fail: (msg?: string) => console.log(fail(msg || opts.text)),
|
||||
warn: (msg?: string) => console.log(warn(msg || opts.text)),
|
||||
info: (msg?: string) => console.log(info(msg || opts.text)),
|
||||
update: (_text: string) => {
|
||||
/* no-op in non-TTY */
|
||||
},
|
||||
stop: () => {
|
||||
/* no-op */
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SECTION HEADERS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Print section header with optional gradient
|
||||
*/
|
||||
export function header(text: string, useGradient = true): string {
|
||||
if (useGradient && useColors()) {
|
||||
return gradientText(text);
|
||||
}
|
||||
return bold(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print subsection header
|
||||
*/
|
||||
export function subheader(text: string): string {
|
||||
return color(text, 'primary');
|
||||
}
|
||||
|
||||
/**
|
||||
* Print horizontal rule
|
||||
*/
|
||||
export function hr(char = '─', width = 60): string {
|
||||
if (!useColors()) {
|
||||
return '-'.repeat(width);
|
||||
}
|
||||
return dim(char.repeat(width));
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// UNIFIED EXPORT OBJECT
|
||||
// =============================================================================
|
||||
|
||||
export const ui = {
|
||||
// Initialization
|
||||
init: initUI,
|
||||
isInteractive,
|
||||
|
||||
// Colors
|
||||
color,
|
||||
gradientText,
|
||||
bold,
|
||||
dim,
|
||||
|
||||
// Status indicators (ASCII only)
|
||||
ok,
|
||||
fail,
|
||||
warn,
|
||||
info,
|
||||
|
||||
// Containers
|
||||
box,
|
||||
errorBox,
|
||||
infoBox,
|
||||
table,
|
||||
|
||||
// Progress
|
||||
spinner,
|
||||
|
||||
// Headers
|
||||
header,
|
||||
subheader,
|
||||
hr,
|
||||
} as const;
|
||||
|
||||
export default ui;
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* UI Module Unit Tests
|
||||
*
|
||||
* Tests the central UI abstraction layer for CLI styling
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
describe('UI Module', function () {
|
||||
let ui;
|
||||
|
||||
before(async function () {
|
||||
// Dynamic import for ESM module - import the built dist file
|
||||
const uiModule = await import('../../../dist/utils/ui.js');
|
||||
ui = uiModule;
|
||||
|
||||
// Initialize UI dependencies
|
||||
await ui.initUI();
|
||||
});
|
||||
|
||||
describe('Color System', function () {
|
||||
it('should apply semantic colors to text', function () {
|
||||
const result = ui.color('test message', 'success');
|
||||
assert.ok(result.includes('test message'), 'should contain original text');
|
||||
});
|
||||
|
||||
it('should return plain text when NO_COLOR is set', function () {
|
||||
const originalNoColor = process.env.NO_COLOR;
|
||||
process.env.NO_COLOR = '1';
|
||||
|
||||
const result = ui.color('test', 'success');
|
||||
assert.strictEqual(result, 'test', 'should return unmodified text');
|
||||
|
||||
// Restore
|
||||
if (originalNoColor === undefined) {
|
||||
delete process.env.NO_COLOR;
|
||||
} else {
|
||||
process.env.NO_COLOR = originalNoColor;
|
||||
}
|
||||
});
|
||||
|
||||
it('should apply bold formatting', function () {
|
||||
const result = ui.bold('bold text');
|
||||
assert.ok(result.includes('bold text'), 'should contain original text');
|
||||
});
|
||||
|
||||
it('should apply dim formatting', function () {
|
||||
const result = ui.dim('dim text');
|
||||
assert.ok(result.includes('dim text'), 'should contain original text');
|
||||
});
|
||||
|
||||
it('should apply gradient to text', function () {
|
||||
const result = ui.gradientText('gradient header');
|
||||
assert.ok(result.includes('gradient header'), 'should contain original text');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Status Indicators', function () {
|
||||
it('should format success message with [OK]', function () {
|
||||
const result = ui.ok('Operation successful');
|
||||
assert.ok(result.includes('[OK]'), 'should include [OK] indicator');
|
||||
assert.ok(result.includes('Operation successful'), 'should include message');
|
||||
});
|
||||
|
||||
it('should format error message with [X]', function () {
|
||||
const result = ui.fail('Operation failed');
|
||||
assert.ok(result.includes('[X]'), 'should include [X] indicator');
|
||||
assert.ok(result.includes('Operation failed'), 'should include message');
|
||||
});
|
||||
|
||||
it('should format warning message with [!]', function () {
|
||||
const result = ui.warn('Warning detected');
|
||||
assert.ok(result.includes('[!]'), 'should include [!] indicator');
|
||||
assert.ok(result.includes('Warning detected'), 'should include message');
|
||||
});
|
||||
|
||||
it('should format info message with [i]', function () {
|
||||
const result = ui.info('Information');
|
||||
assert.ok(result.includes('[i]'), 'should include [i] indicator');
|
||||
assert.ok(result.includes('Information'), 'should include message');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Box Rendering', function () {
|
||||
it('should render box with content', function () {
|
||||
const result = ui.box('Box content');
|
||||
assert.ok(result.includes('Box content'), 'should include content');
|
||||
});
|
||||
|
||||
it('should render error box', function () {
|
||||
const result = ui.errorBox('Error message');
|
||||
assert.ok(result.includes('Error message'), 'should include error message');
|
||||
assert.ok(result.includes('ERROR'), 'should include ERROR title');
|
||||
});
|
||||
|
||||
it('should render info box', function () {
|
||||
const result = ui.infoBox('Info message', 'Title');
|
||||
assert.ok(result.includes('Info message'), 'should include info message');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Table Rendering', function () {
|
||||
it('should render table with rows and headers', function () {
|
||||
const rows = [
|
||||
['Row1', 'Data1'],
|
||||
['Row2', 'Data2'],
|
||||
];
|
||||
const result = ui.table(rows, { head: ['Column1', 'Column2'] });
|
||||
|
||||
assert.ok(result.includes('Row1'), 'should include first row');
|
||||
assert.ok(result.includes('Data1'), 'should include first data');
|
||||
assert.ok(result.includes('Row2'), 'should include second row');
|
||||
assert.ok(result.includes('Column1'), 'should include header');
|
||||
});
|
||||
|
||||
it('should render table without headers', function () {
|
||||
const rows = [
|
||||
['Test', 'Value'],
|
||||
['Test2', 'Value2'],
|
||||
];
|
||||
const result = ui.table(rows);
|
||||
|
||||
// Should contain data and unicode box characters
|
||||
assert.ok(result.includes('Test'), 'should include data');
|
||||
assert.ok(result.includes('│') || result.includes('|'), 'should include vertical separators');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Headers', function () {
|
||||
it('should format section header', function () {
|
||||
const result = ui.header('Section Title');
|
||||
assert.ok(result.includes('Section Title'), 'should include title text');
|
||||
});
|
||||
|
||||
it('should format subsection header', function () {
|
||||
const result = ui.subheader('Subsection');
|
||||
assert.ok(result.includes('Subsection'), 'should include subsection text');
|
||||
});
|
||||
|
||||
it('should render horizontal rule', function () {
|
||||
const result = ui.hr('─', 40);
|
||||
assert.ok(result.length >= 40, 'should be at least specified width');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Interactive Detection', function () {
|
||||
it('should detect interactive mode', function () {
|
||||
// This depends on test environment
|
||||
const result = ui.isInteractive();
|
||||
assert.strictEqual(typeof result, 'boolean', 'should return boolean');
|
||||
});
|
||||
});
|
||||
|
||||
describe('NO_COLOR Compliance', function () {
|
||||
it('should disable colors when NO_COLOR is set', function () {
|
||||
const originalNoColor = process.env.NO_COLOR;
|
||||
process.env.NO_COLOR = '1';
|
||||
|
||||
// All color functions should return plain text
|
||||
assert.strictEqual(ui.color('text', 'success'), 'text');
|
||||
assert.strictEqual(ui.bold('text'), 'text');
|
||||
assert.strictEqual(ui.dim('text'), 'text');
|
||||
assert.strictEqual(ui.gradientText('text'), 'text');
|
||||
|
||||
// Restore
|
||||
if (originalNoColor === undefined) {
|
||||
delete process.env.NO_COLOR;
|
||||
} else {
|
||||
process.env.NO_COLOR = originalNoColor;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user