diff --git a/.changeset/early-ads-appear.md b/.changeset/early-ads-appear.md new file mode 100644 index 00000000..a4e948ef --- /dev/null +++ b/.changeset/early-ads-appear.md @@ -0,0 +1,5 @@ +--- +"@nodesecure/size-satisfies": major +--- + +Migrate codebase to TypeScript diff --git a/tsconfig.json b/tsconfig.json index 9649c49b..938287ba 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,9 @@ }, { "path": "./workspaces/server" + }, + { + "path": "./workspaces/size-satisfies" } ] } diff --git a/workspaces/size-satisfies/README.md b/workspaces/size-satisfies/README.md index 9108fef4..f6cf3bcd 100644 --- a/workspaces/size-satisfies/README.md +++ b/workspaces/size-satisfies/README.md @@ -7,11 +7,11 @@ Scorecard](https://api.securityscorecards.dev/projects/github.com/NodeSecure/cli ![size](https://img.shields.io/github/languages/code-size/NodeSecure/size-satisfies?style=for-the-badge) [![build](https://img.shields.io/github/actions/workflow/status/NodeSecure/cli/size-satisfies.yml?style=for-the-badge)](https://github.com/NodeSecure/cli/actions?query=workflow%3A%22Size+Satisfies+CI%22) -Same as SemVer.satisfies but for file size! +Check whether a file size satisfies a given constraint — like `semver.satisfies` but for bytes. ## Requirements -- [Node.js](https://nodejs.org/en/) v20 or higher +- [Node.js](https://nodejs.org/en/) v18 or higher ## Getting Started @@ -26,25 +26,68 @@ $ yarn add @nodesecure/size-satisfies ## Usage example ```js -import { strict } from "assert"; -import sizeSatisfies from "size-satisfies"; +import sizeSatisfies from "@nodesecure/size-satisfies"; + +// String sizes are parsed using the `bytes` package (B, KB, MB, GB, …) +console.log(sizeSatisfies(">= 45KB", "20MB")); // true — 20 MB is >= 45 KB +console.log(sizeSatisfies("<= 45KB", "10B")); // true — 10 B is <= 45 KB +console.log(sizeSatisfies("= 1MB", "1MB")); // true — exact match +console.log(sizeSatisfies("> 45KB", "45KB")); // false — not strictly greater + +// Numeric sizes are treated as bytes +console.log(sizeSatisfies(">= 45KB", 46080)); // true — 46 080 B == 45 KB +console.log(sizeSatisfies("= 45KB", 2000)); // false — 2 000 B != 45 KB +console.log(sizeSatisfies("< 45KB", 0)); // true — 0 B < 45 KB + +// Invalid patterns always return false +console.log(sizeSatisfies("", "45KB")); // false — empty pattern +console.log(sizeSatisfies("45KB", "45KB")); // false — missing operator +console.log(sizeSatisfies("!! 45KB", "45KB")); // false — unknown operator + +// Unparseable sizes fall back to 0 +console.log(sizeSatisfies("> 0KB", "not_a_size")); // false — 0 > 0 is false +console.log(sizeSatisfies("= 0KB", "not_a_size")); // true — 0 == 0 +console.log(sizeSatisfies(">= not_a_size", "10KB")); // true — 10 KB >= 0 +``` + +## API -const { strictEqual } = strict; +### `sizeSatisfies(pattern, size)` -strictEqual(sizeSatisfies(">= 45KB", "20MB"), true); -strictEqual(sizeSatisfies("= 1MB", "1MB"), true); -strictEqual(sizeSatisfies("= 1MB", 2000), false); +```ts +function sizeSatisfies(pattern: string, size: number | string): boolean ``` -The first argument of the `sizeSatisfies` method is the pattern with the operator + size. Available operators are `>=`, `<=`, `>`, `<`, `=`. +Returns `true` when `size` satisfies the constraint expressed by `pattern`, `false` otherwise. -## API +#### `pattern` + +A string composed of an **operator** followed by a **size value** (with an optional space between them): + +``` +">= 45KB" +"< 1MB" +"= 512B" +``` + +| Operator | Meaning | +|----------|--------------------------| +| `>=` | greater than or equal to | +| `<=` | less than or equal to | +| `>` | strictly greater than | +| `<` | strictly less than | +| `=` | exactly equal to | + +The size value in the pattern is parsed by the [`bytes`](https://www.npmjs.com/package/bytes) package and therefore supports the same units: `B`, `KB`, `MB`, `GB`, `TB`, `PB` (case-insensitive). An unparseable size value falls back to `0`. + +A pattern that is empty, has no operator, or uses an unrecognised operator causes the function to return `false` immediately. -### sizeSatisfies(pattern: string, size: number | string): boolean +#### `size` -When the size is a string we convert it to a bytes number. When the argument is a number we consider the value as bytes. +The actual size to test against the pattern. -Invalid pattern will always return **false**. +- **`number`** — interpreted as a raw byte count. +- **`string`** — parsed by the [`bytes`](https://www.npmjs.com/package/bytes) package (e.g. `"45KB"`, `"1.5MB"`). An unparseable string falls back to `0`. ## License diff --git a/workspaces/size-satisfies/index.d.ts b/workspaces/size-satisfies/index.d.ts deleted file mode 100644 index d4512cf8..00000000 --- a/workspaces/size-satisfies/index.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare function sizeSatisfies(pattern: string, size: number | string): boolean; - -export = sizeSatisfies; diff --git a/workspaces/size-satisfies/package.json b/workspaces/size-satisfies/package.json index 8981f4c8..a0f793ba 100644 --- a/workspaces/size-satisfies/package.json +++ b/workspaces/size-satisfies/package.json @@ -3,10 +3,13 @@ "version": "1.1.0", "description": "Same as SemVer.satisfies but for file size!", "type": "module", - "exports": "./index.js", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "scripts": { - "lint": "eslint index.js", - "test-only": "node --test test/test.js", + "build": "tsc", + "prepublishOnly": "npm run build", + "lint": "eslint src/index.ts test", + "test-only": "node --test test/test.ts", "test": "npm run lint && npm run test-only" }, "publishConfig": { @@ -20,8 +23,7 @@ "directory": "workspaces/size-satisfies" }, "files": [ - "index.js", - "index.d.ts" + "src" ], "keywords": [ "size", @@ -42,6 +44,9 @@ }, "homepage": "https://github.com/NodeSecure/cli/master/workspaces/size-satisfies#readme", "dependencies": { - "bytes": "^3.1.2" + "bytes": "3.1.2" + }, + "devDependencies": { + "@types/bytes": "3.1.5" } } diff --git a/workspaces/size-satisfies/index.js b/workspaces/size-satisfies/src/index.ts similarity index 51% rename from workspaces/size-satisfies/index.js rename to workspaces/size-satisfies/src/index.ts index ff65d0cb..c7aa9fb2 100644 --- a/workspaces/size-satisfies/index.js +++ b/workspaces/size-satisfies/src/index.ts @@ -8,23 +8,29 @@ const kOperators = { ">": (lh, rh) => lh > rh, "<": (lh, rh) => lh < rh, "=": (lh, rh) => lh === rh -}; +} satisfies Record boolean>; + +export function sizeSatisfies( + pattern: string, + size: number | string +): boolean { + const localSize = typeof size === "number" ? + size : + bytes(size) ?? 0; -/** - * @function sizeSatisfies - * @param {!string} pattern - * @param {!(number | string)} size - * @returns {boolean} - */ -function sizeSatisfies(pattern, size) { - const localSize = typeof size === "number" ? size : bytes(size); const regexResult = /^(?[><=]{1,2})\s*(?.*)/g.exec(pattern); - if (regexResult === null) { + if ( + regexResult === null || + regexResult.groups === undefined + ) { return false; } const { operator, patternSize } = regexResult.groups; - return kOperators[operator](localSize, bytes(patternSize)); + return kOperators[operator as keyof typeof kOperators]( + localSize, + bytes(patternSize) ?? 0 + ); } export default sizeSatisfies; diff --git a/workspaces/size-satisfies/test/test.js b/workspaces/size-satisfies/test/test.js deleted file mode 100644 index e9c27fe7..00000000 --- a/workspaces/size-satisfies/test/test.js +++ /dev/null @@ -1,22 +0,0 @@ -// Import Node.js Dependencies -import { test } from "node:test"; -import assert from "node:assert"; - -// Import Internal Dependencies -import sizeSatisfies from "../index.js"; - -test("invalid operator always return false", () => { - assert.strictEqual(sizeSatisfies("!! 45KB", "45KB"), false); -}); - -test("assert sizeSatisfies", () => { - assert.strictEqual(sizeSatisfies(">= 45KB", "20MB"), true); - assert.strictEqual(sizeSatisfies("<= 45KB", "10B"), true); - assert.strictEqual(sizeSatisfies("= 45KB", "45KB"), true); - assert.strictEqual(sizeSatisfies("= 45KB", "46KB"), false); - assert.strictEqual(sizeSatisfies("= 45KB", 2000), false); - assert.strictEqual(sizeSatisfies("> 45KB", "46KB"), true); - assert.strictEqual(sizeSatisfies("> 45KB", "45KB"), false); - assert.strictEqual(sizeSatisfies("< 45KB", "44KB"), true); -}); - diff --git a/workspaces/size-satisfies/test/test.ts b/workspaces/size-satisfies/test/test.ts new file mode 100644 index 00000000..5148c552 --- /dev/null +++ b/workspaces/size-satisfies/test/test.ts @@ -0,0 +1,74 @@ +// Import Node.js Dependencies +import { test } from "node:test"; +import assert from "node:assert"; + +// Import Internal Dependencies +import sizeSatisfies from "../src/index.ts"; + +test("return false for empty pattern", () => { + assert.strictEqual(sizeSatisfies("", "45KB"), false); +}); + +test("return false for pattern without operator", () => { + assert.strictEqual(sizeSatisfies("45KB", "45KB"), false); +}); + +test("return false for invalid operator", () => { + assert.strictEqual(sizeSatisfies("!! 45KB", "45KB"), false); +}); + +test("pattern with only an operator treats patternSize as 0", () => { + assert.strictEqual(sizeSatisfies(">=", "45KB"), true); + assert.strictEqual(sizeSatisfies(">", "0KB"), false); + assert.strictEqual(sizeSatisfies("=", "0KB"), true); + assert.strictEqual(sizeSatisfies("<", "45KB"), false); +}); + +test("size as a numeric value", () => { + assert.strictEqual(sizeSatisfies(">= 45KB", 46080), true); + assert.strictEqual(sizeSatisfies("= 45KB", 46080), true); + assert.strictEqual(sizeSatisfies("= 45KB", 2000), false); + assert.strictEqual(sizeSatisfies("< 45KB", 0), true); +}); + +test("size as an unparseable string falls back to 0", () => { + assert.strictEqual(sizeSatisfies("> 0KB", "not_a_size"), false); + assert.strictEqual(sizeSatisfies("= 0KB", "not_a_size"), true); + assert.strictEqual(sizeSatisfies("<= 10KB", "not_a_size"), true); +}); + +test("unparseable patternSize falls back to 0", () => { + assert.strictEqual(sizeSatisfies(">= not_a_size", "10KB"), true); + assert.strictEqual(sizeSatisfies("= not_a_size", 0), true); + assert.strictEqual(sizeSatisfies("> not_a_size", 0), false); +}); + +test(">= operator", () => { + assert.strictEqual(sizeSatisfies(">= 45KB", "20MB"), true); + assert.strictEqual(sizeSatisfies(">= 45KB", "45KB"), true); + assert.strictEqual(sizeSatisfies(">= 45KB", "10B"), false); +}); + +test("<= operator", () => { + assert.strictEqual(sizeSatisfies("<= 45KB", "10B"), true); + assert.strictEqual(sizeSatisfies("<= 45KB", "45KB"), true); + assert.strictEqual(sizeSatisfies("<= 45KB", "20MB"), false); +}); + +test("= operator", () => { + assert.strictEqual(sizeSatisfies("= 45KB", "45KB"), true); + assert.strictEqual(sizeSatisfies("= 45KB", "46KB"), false); +}); + +test("> operator", () => { + assert.strictEqual(sizeSatisfies("> 45KB", "46KB"), true); + assert.strictEqual(sizeSatisfies("> 45KB", "45KB"), false); + assert.strictEqual(sizeSatisfies("> 45KB", "44KB"), false); +}); + +test("< operator", () => { + assert.strictEqual(sizeSatisfies("< 45KB", "44KB"), true); + assert.strictEqual(sizeSatisfies("< 45KB", "45KB"), false); + assert.strictEqual(sizeSatisfies("< 45KB", "46KB"), false); +}); + diff --git a/workspaces/size-satisfies/tsconfig.json b/workspaces/size-satisfies/tsconfig.json new file mode 100644 index 00000000..6f6a6d5a --- /dev/null +++ b/workspaces/size-satisfies/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +}