From ee46f8b734dbde8d65dc3aae81f4e9ff60ffda5f Mon Sep 17 00:00:00 2001 From: rahuld109 Date: Thu, 18 Dec 2025 21:20:45 -0800 Subject: [PATCH 1/2] refactor: replace kleur with Node.js native styleText - Add src/utils/styleText.js utility using node:util styleText - Update all commands to use internal styleText instead of kleur - Remove kleur dependency from package.json Closes #637 --- bin/index.js | 2 +- package.json | 1 - src/commands/config.js | 4 +++- src/commands/http.js | 2 +- src/commands/lang.js | 4 +++- src/commands/scanner.js | 2 +- src/commands/scorecard.js | 4 +++- src/commands/summary.js | 4 +++- src/commands/verify.js | 4 +++- src/utils/styleText.js | 40 +++++++++++++++++++++++++++++++++++++++ 10 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 src/utils/styleText.js diff --git a/bin/index.js b/bin/index.js index 6e0d37d9..4a06d02b 100755 --- a/bin/index.js +++ b/bin/index.js @@ -8,7 +8,6 @@ import { createRequire } from "node:module"; import { fileURLToPath } from "node:url"; // Import Third-party Dependencies -import kleur from "kleur"; import sade from "sade"; import semver from "semver"; import * as i18n from "@nodesecure/i18n"; @@ -17,6 +16,7 @@ import { loadRegistryURLFromLocalSystem } from "@nodesecure/npm-registry-sdk"; // Import Internal Dependencies import * as commands from "../src/commands/index.js"; +import kleur from "../src/utils/styleText.js"; // CONSTANTS const __dirname = path.dirname(fileURLToPath(import.meta.url)); diff --git a/package.json b/package.json index a91f6cb7..1ec2d186 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,6 @@ "highlightjs-line-numbers.js": "^2.8.0", "ini": "^6.0.0", "json-diff-ts": "^4.8.1", - "kleur": "^4.1.5", "lit": "^3.3.1", "ms": "^2.1.3", "open": "^11.0.0", diff --git a/src/commands/config.js b/src/commands/config.js index 13efe88d..2c7c271b 100644 --- a/src/commands/config.js +++ b/src/commands/config.js @@ -7,7 +7,9 @@ import { spawn } from "node:child_process"; // Import Third-party Dependencies import * as RC from "@nodesecure/rc"; -import kleur from "kleur"; + +// Import Internal Dependencies +import kleur from "../utils/styleText.js"; const K_HOME_PATH = path.join(os.homedir(), "nodesecure"); diff --git a/src/commands/http.js b/src/commands/http.js index 3521a68f..26d0a8eb 100644 --- a/src/commands/http.js +++ b/src/commands/http.js @@ -4,7 +4,6 @@ import path from "node:path"; import crypto from "node:crypto"; // Import Third-party Dependencies -import kleur from "kleur"; import open from "open"; import * as SemVer from "semver"; import * as i18n from "@nodesecure/i18n"; @@ -18,6 +17,7 @@ import { // Import Internal Dependencies import english from "../../i18n/english.js"; import french from "../../i18n/french.js"; +import kleur from "../utils/styleText.js"; // CONSTANTS const kRequiredScannerRange = ">=5.1.0"; diff --git a/src/commands/lang.js b/src/commands/lang.js index 25f2af76..c897ddf7 100644 --- a/src/commands/lang.js +++ b/src/commands/lang.js @@ -1,7 +1,9 @@ // Import Third-party Dependencies import * as i18n from "@nodesecure/i18n"; import { select } from "@topcli/prompts"; -import kleur from "kleur"; + +// Import Internal Dependencies +import kleur from "../utils/styleText.js"; export async function set() { const langs = await i18n.getLanguages(); diff --git a/src/commands/scanner.js b/src/commands/scanner.js index 8ddc1574..46147f2d 100644 --- a/src/commands/scanner.js +++ b/src/commands/scanner.js @@ -4,7 +4,6 @@ import path from "node:path"; import events from "node:events"; // Import Third-party Dependencies -import kleur from "kleur"; import semver from "semver"; import filenamify from "filenamify"; import { Spinner } from "@topcli/spinner"; @@ -14,6 +13,7 @@ import * as scanner from "@nodesecure/scanner"; import { cache } from "@nodesecure/server"; // Import Internal Dependencies +import kleur from "../utils/styleText.js"; import * as http from "./http.js"; import { parseContacts } from "./parsers/contacts.js"; diff --git a/src/commands/scorecard.js b/src/commands/scorecard.js index dd57b7fd..4123b60e 100644 --- a/src/commands/scorecard.js +++ b/src/commands/scorecard.js @@ -3,11 +3,13 @@ import fs from "node:fs"; // Import Third-party Dependencies import cliui from "@topcli/cliui"; -import kleur from "kleur"; import * as scorecard from "@nodesecure/ossf-scorecard-sdk"; import ini from "ini"; import { Ok, Err } from "@openally/result"; +// Import Internal Dependencies +import kleur from "../utils/styleText.js"; + // VARS const { yellow, grey, cyan, white } = kleur; diff --git a/src/commands/summary.js b/src/commands/summary.js index a0c9dc75..11ef6933 100644 --- a/src/commands/summary.js +++ b/src/commands/summary.js @@ -4,10 +4,12 @@ import path from "node:path"; // Import Third-party Dependencies import cliui from "@topcli/cliui"; -import kleur from "kleur"; import * as i18n from "@nodesecure/i18n"; import { formatBytes } from "@nodesecure/utils"; +// Import Internal Dependencies +import kleur from "../utils/styleText.js"; + // VARS const { yellow, grey, white, green, cyan, red } = kleur; diff --git a/src/commands/verify.js b/src/commands/verify.js index 2cc26319..31614b00 100644 --- a/src/commands/verify.js +++ b/src/commands/verify.js @@ -1,9 +1,11 @@ // Import Third-party Dependencies import cliui from "@topcli/cliui"; -import kleur from "kleur"; import { verify } from "@nodesecure/scanner"; import { formatBytes, locationToString } from "@nodesecure/utils"; +// Import Internal Dependencies +import kleur from "../utils/styleText.js"; + // VARS const { yellow, grey, white, green, cyan, red, magenta } = kleur; diff --git a/src/utils/styleText.js b/src/utils/styleText.js new file mode 100644 index 00000000..c8c1e6e2 --- /dev/null +++ b/src/utils/styleText.js @@ -0,0 +1,40 @@ +// Import Node.js Dependencies +import { styleText } from "node:util"; + +/** + * @typedef {import("node:util").ForegroundColors} ForegroundColors + * @typedef {import("node:util").BackgroundColors} BackgroundColors + * @typedef {import("node:util").Modifiers} Modifiers + * @typedef {ForegroundColors | BackgroundColors | Modifiers} StyleName + */ + +/** + * @typedef {((text: string) => string) & Record} Formatter + */ + +/** + * Creates a chainable formatter for terminal styling using Node.js styleText + * @param {StyleName[]} styles - Array of styles to apply + * @returns {Formatter} + */ +function createFormatter(styles = []) { + function fn(text) { + // When called without arguments, return the formatter for chaining + if (text === undefined) { + return formatter; + } + + // Convert to string since styleText only accepts strings + return styleText(styles, String(text)); + } + + const formatter = new Proxy(fn, { + get: (_, prop) => createFormatter([...styles, prop]) + }); + + return formatter; +} + +const formatter = createFormatter(); + +export default formatter; From 7f5421df0e682d840d769261940449e317d0a986 Mon Sep 17 00:00:00 2001 From: rahuld109 Date: Thu, 18 Dec 2025 22:04:21 -0800 Subject: [PATCH 2/2] test: add unit tests for styleText utility --- test/utils/styleText.test.js | 82 ++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 test/utils/styleText.test.js diff --git a/test/utils/styleText.test.js b/test/utils/styleText.test.js new file mode 100644 index 00000000..7b42cf8d --- /dev/null +++ b/test/utils/styleText.test.js @@ -0,0 +1,82 @@ +// Import Node.js Dependencies +import assert from "node:assert"; +import { describe, it } from "node:test"; + +// Import Internal Dependencies +import kleur from "../../src/utils/styleText.js"; + +describe("styleText utility", () => { + describe("direct style calls", () => { + it("should apply a single style", () => { + const result = kleur.red("hello"); + assert.ok(result.includes("hello"), "should contain the text"); + assert.strictEqual(typeof result, "string", "should return a string"); + }); + + it("should apply bold modifier", () => { + const result = kleur.bold("hello"); + assert.ok(result.includes("hello"), "should contain the text"); + }); + + it("should apply multiple styles via chaining property access", () => { + const result = kleur.red.bold("hello"); + assert.ok(result.includes("hello"), "should contain the text"); + }); + }); + + describe("chaining with empty call", () => { + it("should support kleur.color() empty call then method", () => { + const result = kleur.green().bold("hello"); + assert.ok(result.includes("hello"), "should contain the text"); + }); + + it("should support multiple empty calls in chain", () => { + const result = kleur.cyan().bold().underline("hello"); + assert.ok(result.includes("hello"), "should contain the text"); + }); + }); + + describe("non-string value handling", () => { + it("should convert numbers to strings", () => { + const result = kleur.yellow(42); + assert.ok(result.includes("42"), "should contain the number as string"); + }); + + it("should handle zero", () => { + const result = kleur.red(0); + assert.ok(result.includes("0"), "should contain zero as string"); + }); + + it("should handle negative numbers", () => { + const result = kleur.blue(-5); + assert.ok(result.includes("-5"), "should contain negative number as string"); + }); + }); + + describe("destructuring support", () => { + it("should work with destructured colors", () => { + const { red, green, blue } = kleur; + assert.ok(red("test").includes("test"), "red should work"); + assert.ok(green("test").includes("test"), "green should work"); + assert.ok(blue("test").includes("test"), "blue should work"); + }); + + it("should work with destructured modifiers", () => { + const { bold, italic } = kleur; + assert.ok(bold("test").includes("test"), "bold should work"); + assert.ok(italic("test").includes("test"), "italic should work"); + }); + }); + + describe("edge cases", () => { + it("should handle empty string", () => { + const result = kleur.red(""); + assert.strictEqual(typeof result, "string", "should return a string"); + }); + + it("should handle special characters", () => { + const result = kleur.green("hello\nworld"); + assert.ok(result.includes("hello\nworld"), "should preserve special characters"); + }); + }); +});