From 92a776b507b11dad4901744d93ba9c649f0555a4 Mon Sep 17 00:00:00 2001 From: Colten Krauter <18080897+coltenkrauter@users.noreply.github.com> Date: Tue, 1 Oct 2024 09:48:56 -0600 Subject: [PATCH] feat: readme validator --- .github/workflows/tests.yaml | 32 ++ README.md | 134 ++++++++- jest.config.js | 34 ++- package-lock.json | 502 ++++++++++++++------------------ package.json | 23 +- src/index.ts | 3 + src/package-json.ts | 70 +++++ src/readme-sections.ts | 100 +++++++ src/readme-validator.ts | 194 ++++++++++++ src/scripts/pre-commit.ts | 1 + src/scripts/readme-validator.ts | 14 + src/structures.ts | 158 ++++++++++ src/version.ts | 182 ++++++------ test/package-json.test.ts | 97 ++++++ test/readme-validator.test.ts | 191 ++++++++++++ test/version.test.ts | 201 ++++++------- tsconfig.eslint.json | 4 + tsconfig.jest.json | 7 + tsconfig.json | 62 ++-- 19 files changed, 1469 insertions(+), 540 deletions(-) create mode 100644 .github/workflows/tests.yaml create mode 100644 src/package-json.ts create mode 100644 src/readme-sections.ts create mode 100644 src/readme-validator.ts create mode 100644 src/scripts/readme-validator.ts create mode 100644 src/structures.ts create mode 100644 test/package-json.test.ts create mode 100644 test/readme-validator.test.ts create mode 100644 tsconfig.eslint.json create mode 100644 tsconfig.jest.json diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..85e66ea --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,32 @@ +name: Tests +run-name: Tests [${{ github.ref_name }}] triggered by [${{ github.event_name }}/${{ github.actor }}] + +on: + push: + branches: '*' + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install Dependencies + run: npm ci + + - name: Run Tests and Generate Coverage + run: npm test + + - name: Upload Test and Coverage Reports + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: ./coverage diff --git a/README.md b/README.md index 7574e3f..8c9cde8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,134 @@ -# Utils +
+ +![Code Size](https://img.shields.io/github/languages/code-size/krauters/utils) +![Commits per Month](https://img.shields.io/github/commit-activity/m/krauters/utils) +![Contributors](https://img.shields.io/github/contributors/krauters/utils) +![Forks](https://img.shields.io/github/forks/krauters/utils) +![GitHub Stars](https://img.shields.io/github/stars/krauters/utils) +![Install Size](https://img.shields.io/npm/npm/dw/@krauters%2Futils) +![GitHub Issues](https://img.shields.io/github/issues/krauters/utils) +![Last Commit](https://img.shields.io/github/last-commit/krauters/utils) +![License](https://img.shields.io/github/license/krauters/utils) +LinkedIn +[![npm version](https://img.shields.io/npm/v/@krauters%2Futils.svg?style=flat-square)](https://www.npmjs.org/package/@krauters/utils) +![Open PRs](https://img.shields.io/github/issues-pr/krauters/utils) +![Repo Size](https://img.shields.io/github/repo-size/krauters/utils) +![Version](https://img.shields.io/github/v/release/krauters/utils) +![visitors](https://visitor-badge.laobi.icu/badge?page_id=krauters/utils) + +
+ +![Code Size](https://img.shields.io/github/languages/code-size/krauters/utils) +![Commits per Month](https://img.shields.io/github/commit-activity/m/krauters/utils) +![Contributors](https://img.shields.io/github/contributors/krauters/utils) +![Forks](https://img.shields.io/github/forks/krauters/utils) +![GitHub Stars](https://img.shields.io/github/stars/krauters/utils) +![Install Size](https://img.shields.io/npm/npm/dw/@krauters%2Futils) +![GitHub Issues](https://img.shields.io/github/issues/krauters/utils) +![Last Commit](https://img.shields.io/github/last-commit/krauters/utils) +![License](https://img.shields.io/github/license/krauters/utils) +LinkedIn +[![npm version](https://img.shields.io/npm/v/@krauters%2Futils.svg?style=flat-square)](https://www.npmjs.org/package/@krauters/utils) +![Open PRs](https://img.shields.io/github/issues-pr/krauters/utils) +![Repo Size](https://img.shields.io/github/repo-size/krauters/utils) +![Version](https://img.shields.io/github/v/release/krauters/utils) +![visitors](https://visitor-badge.laobi.icu/badge?page_id=krauters/utils) + +
+ +![Code Size](https://img.shields.io/github/languages/code-size/krauters/utils) +![Commits per Month](https://img.shields.io/github/commit-activity/m/krauters/utils) +![Contributors](https://img.shields.io/github/contributors/krauters/utils) +![Forks](https://img.shields.io/github/forks/krauters/utils) +![GitHub Stars](https://img.shields.io/github/stars/krauters/utils) +![Install Size](https://img.shields.io/npm/npm/dw/@krauters%2Futils) +![GitHub Issues](https://img.shields.io/github/issues/krauters/utils) +![Last Commit](https://img.shields.io/github/last-commit/krauters/utils) +![License](https://img.shields.io/github/license/krauters/utils) +LinkedIn +[![npm version](https://img.shields.io/npm/v/@krauters%2Futils.svg?style=flat-square)](https://www.npmjs.org/package/@krauters/utils) +![Open PRs](https://img.shields.io/github/issues-pr/krauters/utils) +![Repo Size](https://img.shields.io/github/repo-size/krauters/utils) +![Version](https://img.shields.io/github/v/release/krauters/utils) +![visitors](https://visitor-badge.laobi.icu/badge?page_id=krauters/utils) + +
+ +![Code Size](https://img.shields.io/github/languages/code-size/krauters/utils) +![Commits per Month](https://img.shields.io/github/commit-activity/m/krauters/utils) +![Contributors](https://img.shields.io/github/contributors/krauters/utils) +![Forks](https://img.shields.io/github/forks/krauters/utils) +![GitHub Stars](https://img.shields.io/github/stars/krauters/utils) +![Install Size](https://img.shields.io/npm/npm/dw/@krauters%2Futils) +![GitHub Issues](https://img.shields.io/github/issues/krauters/utils) +![Last Commit](https://img.shields.io/github/last-commit/krauters/utils) +![License](https://img.shields.io/github/license/krauters/utils) +LinkedIn +[![npm version](https://img.shields.io/npm/v/@krauters%2Futils.svg?style=flat-square)](https://www.npmjs.org/package/@krauters/utils) +![Open PRs](https://img.shields.io/github/issues-pr/krauters/utils) +![Repo Size](https://img.shields.io/github/repo-size/krauters/utils) +![Version](https://img.shields.io/github/v/release/krauters/utils) +![visitors](https://visitor-badge.laobi.icu/badge?page_id=krauters/utils) + +
+ +![Code Size](https://img.shields.io/github/languages/code-size/krauters/utils) +![Commits per Month](https://img.shields.io/github/commit-activity/m/krauters/utils) +![Contributors](https://img.shields.io/github/contributors/krauters/utils) +![Forks](https://img.shields.io/github/forks/krauters/utils) +![GitHub Stars](https://img.shields.io/github/stars/krauters/utils) +![Install Size](https://img.shields.io/npm/npm/dw/@krauters%2Futils) +![GitHub Issues](https://img.shields.io/github/issues/krauters/utils) +![Last Commit](https://img.shields.io/github/last-commit/krauters/utils) +![License](https://img.shields.io/github/license/krauters/utils) +LinkedIn +[![npm version](https://img.shields.io/npm/v/@krauters%2Futils.svg?style=flat-square)](https://www.npmjs.org/package/@krauters/utils) +![Open PRs](https://img.shields.io/github/issues-pr/krauters/utils) +![Repo Size](https://img.shields.io/github/repo-size/krauters/utils) +![Version](https://img.shields.io/github/v/release/krauters/utils) +![visitors](https://visitor-badge.laobi.icu/badge?page_id=krauters/utils) + +
+ +![Code Size](https://img.shields.io/github/languages/code-size/krauters/utils) +![Commits per Month](https://img.shields.io/github/commit-activity/m/krauters/utils) +![Contributors](https://img.shields.io/github/contributors/krauters/utils) +![Forks](https://img.shields.io/github/forks/krauters/utils) +![GitHub Stars](https://img.shields.io/github/stars/krauters/utils) +![Install Size](https://img.shields.io/npm/npm/dw/@krauters%2Futils) +![GitHub Issues](https://img.shields.io/github/issues/krauters/utils) +![Last Commit](https://img.shields.io/github/last-commit/krauters/utils) +![License](https://img.shields.io/github/license/krauters/utils) +LinkedIn +[![npm version](https://img.shields.io/npm/v/@krauters%2Futils.svg?style=flat-square)](https://www.npmjs.org/package/@krauters/utils) +![Open PRs](https://img.shields.io/github/issues-pr/krauters/utils) +![Repo Size](https://img.shields.io/github/repo-size/krauters/utils) +![Version](https://img.shields.io/github/v/release/krauters/utils) +![visitors](https://visitor-badge.laobi.icu/badge?page_id=krauters/utils) + +
+ +![Code Size](https://img.shields.io/github/languages/code-size/krauters/utils) +![Commits per Month](https://img.shields.io/github/commit-activity/m/krauters/utils) +![Contributors](https://img.shields.io/github/contributors/krauters/utils) +![Forks](https://img.shields.io/github/forks/krauters/utils) +![GitHub Stars](https://img.shields.io/github/stars/krauters/utils) +![Install Size](https://img.shields.io/npm/npm/dw/@krauters%2Futils) +![GitHub Issues](https://img.shields.io/github/issues/krauters/utils) +![Last Commit](https://img.shields.io/github/last-commit/krauters/utils) +![License](https://img.shields.io/github/license/krauters/utils) +LinkedIn +[![npm version](https://img.shields.io/npm/v/@krauters%2Futils.svg?style=flat-square)](https://www.npmjs.org/package/@krauters/utils) +![Open PRs](https://img.shields.io/github/issues-pr/krauters/utils) +![Repo Size](https://img.shields.io/github/repo-size/krauters/utils) +![Version](https://img.shields.io/github/v/release/krauters/utils) +![visitors](https://visitor-badge.laobi.icu/badge?page_id=krauters/utils) + +
+ + +
# @krauters/utils + A versatile TypeScript utility package packed with reusable, type-safe functions, scripts useful for all kinds of TypeScript projects, and precommit scripts to streamline your development workflow. ## Husky @@ -17,8 +147,6 @@ This project uses a custom pre-commit hook to run `npm run bundle`. This ensures # ./husky/pre-commit #!/bin/sh -#!/bin/sh - MAIN_DIR=./node_modules/@krauters/utils/scripts/pre-commit . $MAIN_DIR/index.sh diff --git a/jest.config.js b/jest.config.js index afd2b9b..52c7f94 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,16 +1,20 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - testMatch: ['**/test/**/*.test.{ts,tsx}'], - collectCoverage: true, - coverageDirectory: 'coverage', - coverageThreshold: { - global: { - lines: 60, - statements: 60, - functions: 60, - branches: 60, - }, - }, - } - \ No newline at end of file + collectCoverage: true, + coverageDirectory: 'coverage', + coverageThreshold: { + global: { + branches: 60, + functions: 60, + lines: 60, + statements: 60, + }, + }, + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/test/**/*.test.{ts,tsx}'], + transform: { + '^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.jest.json' }], + }, +} diff --git a/package-lock.json b/package-lock.json index 11b41ae..f6cb87f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,24 +1,26 @@ { "name": "@krauters/utils", - "version": "0.0.1", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@krauters/utils", - "version": "0.0.1", + "version": "0.5.0", "license": "ISC", + "dependencies": { + "@types/node": "^22.7.5" + }, "devDependencies": { "@jest/globals": "^29.7.0", - "@krauters/eslint-config": "^0.6.0", + "@krauters/eslint-config": "^1.4.0", "@types/jest": "^29.5.13", "husky": "^9.1.6", "jest": "^29.7.0", "nodemon": "^3.1.7", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", - "typescript": "^5.6.2", - "typescript-eslint": "^8.7.0" + "typescript": "^5.6.3" } }, "node_modules/@ampproject/remapping": { @@ -36,13 +38,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", + "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.7", + "@babel/highlight": "^7.25.7", "picocolors": "^1.0.0" }, "engines": { @@ -50,9 +52,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", - "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.8.tgz", + "integrity": "sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==", "dev": true, "license": "MIT", "engines": { @@ -60,22 +62,22 @@ } }, "node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.8.tgz", + "integrity": "sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helpers": "^7.25.7", + "@babel/parser": "^7.25.8", + "@babel/template": "^7.25.7", + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.8", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -101,31 +103,31 @@ } }, "node_modules/@babel/generator": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", - "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", + "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.6", + "@babel/types": "^7.25.7", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", - "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz", + "integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", + "@babel/compat-data": "^7.25.7", + "@babel/helper-validator-option": "^7.25.7", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -144,30 +146,30 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", - "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", + "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", - "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", + "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.2" + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -177,9 +179,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", + "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", "dev": true, "license": "MIT", "engines": { @@ -187,23 +189,23 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", - "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", + "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", "dev": true, "license": "MIT", "engines": { @@ -211,9 +213,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", "dev": true, "license": "MIT", "engines": { @@ -221,9 +223,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", - "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz", + "integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==", "dev": true, "license": "MIT", "engines": { @@ -231,27 +233,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", - "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz", + "integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.6" + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", + "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", + "@babel/helper-validator-identifier": "^7.25.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -339,13 +341,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", - "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz", + "integrity": "sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.6" + "@babel/types": "^7.25.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -410,13 +412,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz", - "integrity": "sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.7.tgz", + "integrity": "sha512-AqVo+dguCgmpi/3mYBdu9lkngOBlQ2w2vnNpa6gfiCxQZLzV4ZbhsXitJ2Yblkoe1VQwtHSaNmIaGll/26YWRw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -452,13 +454,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", - "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.7.tgz", + "integrity": "sha512-ruZOnKO+ajVL/MVx+PwNBPOkrnXTXoWMtte1MBpegfCArhqOe3Bj52avVj1huLLxNKYKXYaSxZ2F+woK1ekXfw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -578,13 +580,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz", - "integrity": "sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.7.tgz", + "integrity": "sha512-rR+5FDjpCHqqZN2bzZm18bVYGaejGq5ZkpVCJLXor/+zlSrSoc4KWcHI0URVWjl/68Dyr1uwZUz/1njycEAv9g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -594,32 +596,32 @@ } }, "node_modules/@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", - "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", + "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/code-frame": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", - "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", + "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.6", - "@babel/parser": "^7.25.6", - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.6", + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -638,14 +640,14 @@ } }, "node_modules/@babel/types": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", - "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz", + "integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -743,7 +745,6 @@ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -768,7 +769,6 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -780,7 +780,6 @@ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -794,7 +793,6 @@ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -813,7 +811,6 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -827,7 +824,6 @@ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -839,7 +835,6 @@ "deprecated": "Use @eslint/config-array instead", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", @@ -855,7 +850,6 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -867,7 +861,6 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -881,7 +874,6 @@ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=12.22" }, @@ -896,8 +888,7 @@ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "deprecated": "Use @eslint/object-schema instead", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", @@ -1362,23 +1353,24 @@ } }, "node_modules/@krauters/eslint-config": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@krauters/eslint-config/-/eslint-config-0.6.0.tgz", - "integrity": "sha512-qjXH2jVp/oln8qFVdrM7fo7RdR+H0tcajYpmVInhWwzGGIGPOulz1DN7zHirTL7yjWiUSadG7fHSBMmpfsbHsg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@krauters/eslint-config/-/eslint-config-1.4.0.tgz", + "integrity": "sha512-8qr8s0wSCr3bSyCYSsJIDErp6jITtDajLq+FR+y80GslldKqymvBmDjS3TbzZUus7wyScu9anO7AHm0XjX4YpQ==", "dev": true, "license": "ISC", "dependencies": { - "@stylistic/eslint-plugin-ts": "^2.8.0", - "@typescript-eslint/parser": "^8.5.0", + "@stylistic/eslint-plugin-ts": "^2.9.0", + "@typescript-eslint/parser": "^8.8.1", + "eslint": "<9.0.0", "eslint-define-config": "^2.1.0", "eslint-import-resolver-typescript": "^3.6.3", "eslint-plugin-filenames": "^1.3.2", - "eslint-plugin-import": "^2.30.0", - "eslint-plugin-jsdoc": "^50.2.2", - "eslint-plugin-perfectionist": "^3.5.0", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsdoc": "^50.3.1", + "eslint-plugin-perfectionist": "^3.8.0", "eslint-plugin-prettier": "^5.2.1", - "typescript": "^5.6.2", - "typescript-eslint": "^8.5.0" + "typescript": "^5.6.3", + "typescript-eslint": "^8.8.1" } }, "node_modules/@nodelib/fs.scandir": { @@ -1477,15 +1469,15 @@ } }, "node_modules/@stylistic/eslint-plugin-ts": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-ts/-/eslint-plugin-ts-2.8.0.tgz", - "integrity": "sha512-VukJqkRlC2psLKoIHJ+4R3ZxLJfWeizGGX+X5ZxunjXo4MbxRNtwu5UvXuerABg4s2RV6Z3LFTdm0WvI4+RAMQ==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-ts/-/eslint-plugin-ts-2.9.0.tgz", + "integrity": "sha512-CxB73paAKlmaIDtOfKoIHlhNJVlyRMVobuBqdOc4wbVSqfhbgpCWuJYpBkV3ydGDKRfVWNJ9yg5b99lzZtrjhg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^8.4.0", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.1.0" + "@typescript-eslint/utils": "^8.8.0", + "eslint-visitor-keys": "^4.1.0", + "espree": "^10.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1623,10 +1615,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.7.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz", - "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==", - "dev": true, + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", "license": "MIT", "dependencies": { "undici-types": "~6.19.2" @@ -1657,17 +1648,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz", - "integrity": "sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.1.tgz", + "integrity": "sha512-xfvdgA8AP/vxHgtgU310+WBnLB4uJQ9XdyP17RebG26rLtDrQJV3ZYrcopX91GrHmMoH8bdSwMRh2a//TiJ1jQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.7.0", - "@typescript-eslint/type-utils": "8.7.0", - "@typescript-eslint/utils": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0", + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/type-utils": "8.8.1", + "@typescript-eslint/utils": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1691,16 +1682,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.7.0.tgz", - "integrity": "sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.1.tgz", + "integrity": "sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.7.0", - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/typescript-estree": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0", + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/typescript-estree": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", "debug": "^4.3.4" }, "engines": { @@ -1720,14 +1711,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz", - "integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.1.tgz", + "integrity": "sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0" + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1738,14 +1729,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz", - "integrity": "sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.1.tgz", + "integrity": "sha512-qSVnpcbLP8CALORf0za+vjLYj1Wp8HSoiI8zYU5tHxRVj30702Z1Yw4cLwfNKhTPWp5+P+k1pjmD5Zd1nhxiZA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.7.0", - "@typescript-eslint/utils": "8.7.0", + "@typescript-eslint/typescript-estree": "8.8.1", + "@typescript-eslint/utils": "8.8.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1763,9 +1754,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz", - "integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.1.tgz", + "integrity": "sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==", "dev": true, "license": "MIT", "engines": { @@ -1777,14 +1768,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz", - "integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.1.tgz", + "integrity": "sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1806,16 +1797,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz", - "integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.1.tgz", + "integrity": "sha512-/QkNJDbV0bdL7H7d0/y0qBbV2HTtf0TIyjSDTvvmQEzeVx8jEImEbLuOA4EsvE8gIgqMitns0ifb5uQhMj8d9w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.7.0", - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/typescript-estree": "8.7.0" + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/typescript-estree": "8.8.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1829,13 +1820,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz", - "integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.1.tgz", + "integrity": "sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.7.0", + "@typescript-eslint/types": "8.8.1", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -1864,8 +1855,7 @@ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/acorn": { "version": "8.12.1", @@ -1909,7 +1899,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2012,8 +2001,7 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "license": "Python-2.0", - "peer": true + "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { "version": "1.0.1", @@ -2431,9 +2419,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001664", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz", - "integrity": "sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==", + "version": "1.0.30001667", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz", + "integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==", "dev": true, "funding": [ { @@ -2752,8 +2740,7 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", @@ -2837,7 +2824,6 @@ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "esutils": "^2.0.2" }, @@ -2862,9 +2848,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.29", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz", - "integrity": "sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw==", + "version": "1.5.36", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.36.tgz", + "integrity": "sha512-HYTX8tKge/VNp6FGO+f/uVDmUkq+cEfcxYhKf15Akc4M5yxt5YmorwlAitKWjWhWQnKcDRBAQKXkhqqXMqcrjw==", "dev": true, "license": "ISC" }, @@ -3086,9 +3072,9 @@ "version": "8.57.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -3264,9 +3250,9 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", - "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", "dependencies": { @@ -3278,7 +3264,7 @@ "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.9.0", + "eslint-module-utils": "^2.12.0", "hasown": "^2.0.2", "is-core-module": "^2.15.1", "is-glob": "^4.0.3", @@ -3287,13 +3273,14 @@ "object.groupby": "^1.0.3", "object.values": "^1.2.0", "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { @@ -3354,9 +3341,9 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "50.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.3.0.tgz", - "integrity": "sha512-P7qDB/RckdKETpBM4CtjHRQ5qXByPmFhRi86sN3E+J+tySchq+RSOGGhI2hDIefmmKFuTi/1ACjqsnDJDDDfzg==", + "version": "50.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.3.1.tgz", + "integrity": "sha512-SY9oUuTMr6aWoJggUS40LtMjsRzJPB5ZT7F432xZIHK3EfHF+8i48GbUBpwanrtlL9l1gILNTHK9o8gEhYLcKA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -3380,14 +3367,14 @@ } }, "node_modules/eslint-plugin-perfectionist": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-3.7.0.tgz", - "integrity": "sha512-pemhfcR3LDbYVWeveHok9u048yR7GpsnfyPvn6RsDkp/UV7iqBV0y5K0aGb9ZJMsemOyWok7akxGzPLsz+mHKQ==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-3.8.0.tgz", + "integrity": "sha512-BYJWbQVOjvIGK9V1xUfn790HuvkePjxti8epOi1H6sdzo0N4RehBmQ8coHPbgA/f12BUG1NIoDtQhI9mUm+o2A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "^8.7.0", - "@typescript-eslint/utils": "^8.7.0", + "@typescript-eslint/types": "^8.8.0", + "@typescript-eslint/utils": "^8.8.0", "minimatch": "^9.0.5", "natural-compare-lite": "^1.4.0" }, @@ -3453,7 +3440,6 @@ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -3484,7 +3470,6 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3496,7 +3481,6 @@ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -3510,7 +3494,6 @@ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -3529,7 +3512,6 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3588,7 +3570,6 @@ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -3671,8 +3652,7 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fast-diff": { "version": "1.3.0", @@ -3723,8 +3703,7 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fastq": { "version": "1.17.1", @@ -3752,7 +3731,6 @@ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -3802,7 +3780,6 @@ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -3820,7 +3797,6 @@ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -3835,8 +3811,7 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/for-each": { "version": "0.3.3", @@ -4031,7 +4006,6 @@ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -4069,7 +4043,6 @@ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -4268,7 +4241,6 @@ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -4570,7 +4542,6 @@ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -5406,7 +5377,6 @@ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -5425,16 +5395,16 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { @@ -5442,8 +5412,7 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -5457,16 +5426,14 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", @@ -5487,7 +5454,6 @@ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "json-buffer": "3.0.1" } @@ -5518,7 +5484,6 @@ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -5540,7 +5505,6 @@ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -5577,8 +5541,7 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.snakecase": { "version": "4.1.1", @@ -5964,7 +5927,6 @@ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -5999,7 +5961,6 @@ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -6026,7 +5987,6 @@ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "callsites": "^3.0.0" }, @@ -6219,7 +6179,6 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -6309,7 +6268,6 @@ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -6373,16 +6331,16 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", - "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", + "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" + "set-function-name": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -6448,7 +6406,6 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -6491,7 +6448,6 @@ "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -6926,9 +6882,9 @@ } }, "node_modules/synckit": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", - "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", "dev": true, "license": "MIT", "dependencies": { @@ -6996,8 +6952,7 @@ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tmpl": { "version": "1.0.5", @@ -7194,7 +7149,6 @@ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -7218,7 +7172,6 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -7304,9 +7257,9 @@ } }, "node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -7318,15 +7271,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.7.0.tgz", - "integrity": "sha512-nEHbEYJyHwsuf7c3V3RS7Saq+1+la3i0ieR3qP0yjqWSzVmh8Drp47uOl9LjbPANac4S7EFSqvcYIKXUUwIfIQ==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.8.1.tgz", + "integrity": "sha512-R0dsXFt6t4SAFjUSKFjMh4pXDtq04SsFKCVGDP3ZOzNP7itF0jBcZYU4fMsZr4y7O7V7Nc751dDeESbe4PbQMQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.7.0", - "@typescript-eslint/parser": "8.7.0", - "@typescript-eslint/utils": "8.7.0" + "@typescript-eslint/eslint-plugin": "8.8.1", + "@typescript-eslint/parser": "8.8.1", + "@typescript-eslint/utils": "8.8.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7368,7 +7321,6 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, "license": "MIT" }, "node_modules/update-browserslist-db": { @@ -7408,7 +7360,6 @@ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "punycode": "^2.1.0" } @@ -7504,7 +7455,6 @@ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } diff --git a/package.json b/package.json index 3b2d37f..ba8b81f 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,20 @@ { "name": "@krauters/utils", "description": "A versatile TypeScript utility package packed with reusable, type-safe functions, scripts useful for all kinds of TypeScript projects, and precommit scripts to streamline your development workflow.", - "version": "0.4.0", - "main": "index.ts", + "version": "0.5.0", + "main": "dist/index.js", "type": "commonjs", "scripts": { - "build": "ts-node ./src/index.ts", + "build": "tsc", + "dev": "ts-node ./src/index.ts", "example-1": "ts-node ./example/1.ts", "fix": "npm run lint -- --fix", "lint": "npx eslint src/**", "prepare": "husky || true", - "start": "nodemon --inspect -e ts -w ./src -x npm run build", + "prepublishOnly": "npm ci && npm run build", + "start": "nodemon --inspect -e ts -w ./src -x npm run dev", "test": "npm run lint && jest", - "upgrade:all": "npx npm-check-updates -u && npm install" + "upgrade:all": "npx npm-check-updates --upgrade && npm install" }, "keywords": [ "typescript", @@ -24,14 +26,19 @@ "license": "ISC", "devDependencies": { "@jest/globals": "^29.7.0", - "@krauters/eslint-config": "^0.6.0", + "@krauters/eslint-config": "^1.4.0", "@types/jest": "^29.5.13", "husky": "^9.1.6", "jest": "^29.7.0", "nodemon": "^3.1.7", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", - "typescript": "^5.6.2", - "typescript-eslint": "^8.7.0" + "typescript": "^5.6.3" + }, + "files": [ + "dist" + ], + "dependencies": { + "@types/node": "^22.7.5" } } diff --git a/src/index.ts b/src/index.ts index 936df1c..9fcec15 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,4 @@ +export * from './package-json' +export * from './readme-validator' +export type * from './structures' export * from './version' diff --git a/src/package-json.ts b/src/package-json.ts new file mode 100644 index 0000000..eceee9f --- /dev/null +++ b/src/package-json.ts @@ -0,0 +1,70 @@ +import { existsSync, readFileSync } from 'fs' +import { dirname, join } from 'path' + +import type { PackageJsonType as PackageJsonType } from './structures' + +export class PackageJson { + /** + * Recursively searches for package.json starting from the given directory and moving up. + * + * @param dir - The directory to start searching from (defaults to current working directory). + * @returns The found PackageJson object. + * @throws {Error} If package.json is not found or cannot be read. + */ + static findPackageJson(dir: string = process.cwd()): PackageJsonType { + const packageJsonPath = dir.includes('package.json') ? dir : join(dir, 'package.json') + + if (existsSync(packageJsonPath)) { + return PackageJson.readPackageJson(packageJsonPath) + } + + const parentDir = dirname(dir) + if (parentDir === dir) { + throw new Error('No package.json found in this project hierarchy.') + } + + return PackageJson.findPackageJson(parentDir) + } + + /** + * Converts a package name to a formatted title. + * Removes the scope, splits by hyphens, capitalizes each word, and joins them with spaces. + * + * @param packageName - The original package name. + * @param packageNameScopeRegex - The regex to remove the scope from the package name. + * @returns The formatted title. + */ + static packageNameToTitle(packageName: string, packageNameScopeRegex: RegExp): string { + const nameWithoutScope: string = packageName.replace(packageNameScopeRegex, '') + + return nameWithoutScope + .split('-') + .map((word: string) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' ') + } + + /** + * Reads and parses the package.json file. + * + * @param packageJsonPath - The path to the package.json file. + * @returns The parsed PackageJson object. + * @throws Will throw an error if the file cannot be read or parsed. + */ + static readPackageJson(packageJsonPath: string): PackageJsonType { + try { + const packageJsonContent: string = readFileSync(packageJsonPath, 'utf8') + const packageJson: PackageJsonType = JSON.parse(packageJsonContent) + console.info(`Extracted package name [${packageJson.name}]`) + + return packageJson + } catch (error: unknown) { + if (error instanceof Error) { + throw new Error(`Failed to read package.json at [${packageJsonPath}] with error [${error.message}]`) + } else { + throw new Error(`Failed to read package.json at [${packageJsonPath}] with unknown error.`) + } + } + } +} + +export const { findPackageJson, packageNameToTitle, readPackageJson } = PackageJson diff --git a/src/readme-sections.ts b/src/readme-sections.ts new file mode 100644 index 0000000..8ba8fe9 --- /dev/null +++ b/src/readme-sections.ts @@ -0,0 +1,100 @@ +import { execSync } from 'child_process' + +import { type BadgeSectionOptions, BadgeType, BadgeURL, type PackageJsonType, type Section } from './structures' + +/** + * Provides custom sections to be added to the README, incorporating data from `packageJson`. + * + * @param packageJson - The package.json data to help enrich custom sections. + * @returns An array of custom sections. + */ +export function getCustomSections(packageJson: PackageJsonType): Section[] { + console.log(`Generating custom sections using package.json data for [${packageJson.name}]...`) + + return [ + { + content: + 'This project is licensed under the ISC License. Please see the [LICENSE](./LICENSE) file for more details.', + header: 'License', + required: false, + }, + { + content: [ + 'Thanks for spending time on this project.', + ``, + ` `, + '', + ].join('\n'), + header: '🥂 Thanks Contributors', + required: false, + }, + { + content: [ + 'We’ve got more than just this one in our toolbox – check out the rest of our `@krauters` collection on [npm/@krauters](https://www.npmjs.com/search?q=%40krauters).', + 'It’s the whole kit and caboodle you didn’t know you needed.', + ].join(' '), + header: '🔗 Other packages in the family', + required: false, + }, + ] +} + +/** + * Gets GitHub repository path from the local Git configuration via CLI. + * + * @returns The GitHub repository path in the format "owner/repo" or `undefined` if not found. + */ +export function getRepoPathFromGit(): string | undefined { + try { + const remoteUrl = execSync('git config --get remote.origin.url', { encoding: 'utf8' }).trim() + const match = /github\.com[:/](.+\/.+?)(\.git)?$/.exec(remoteUrl) + + return match ? match[1] : undefined + } catch (error: unknown) { + console.warn(`Warning: Could not determine Git repository information [${error}].`) + + return undefined + } +} + +/** + * Generates a badge section in Markdown/HTML format based on the selected `BadgeType`s. + * + * @param options - Configuration containing the list of `BadgeType`s, `packageJson`, `linkedInUsername`, and `repoPath`. + * @returns The generated Markdown/HTML badge section. + */ +export function getBadgeSection(options: BadgeSectionOptions): string { + const { badgeTypes, linkedInUsername, packageJson, repoPath = getRepoPathFromGit() } = options + const { name } = packageJson + const packageName = name.startsWith('@') ? name.replace('/', '%2F') : name + + if (!repoPath && badgeTypes.some((badge) => badge !== BadgeType.LinkedIn)) { + console.warn( + `Warning: One or more badges are enabled but 'repoPath' is not supplied. Badge URLs may not be generated correctly.`, + ) + } + + const badges = { + [BadgeType.CodeSize]: `![Code Size](${BadgeURL.Shields}github/${BadgeType.CodeSize}/${repoPath})`, + [BadgeType.CommitsPerMonth]: `![Commits per Month](${BadgeURL.Shields}github/${BadgeType.CommitsPerMonth}/${repoPath})`, + [BadgeType.Contributors]: `![Contributors](${BadgeURL.Shields}github/${BadgeType.Contributors}/${repoPath})`, + [BadgeType.Forks]: `![Forks](${BadgeURL.Shields}github/${BadgeType.Forks}/${repoPath})`, + [BadgeType.GitHubStars]: `![GitHub Stars](${BadgeURL.Shields}github/${BadgeType.GitHubStars}/${repoPath})`, + [BadgeType.InstallSize]: `![Install Size](${BadgeURL.Shields}npm/${BadgeType.InstallSize}/${packageName})`, + [BadgeType.Issues]: `![GitHub Issues](${BadgeURL.Shields}github/${BadgeType.Issues}/${repoPath})`, + [BadgeType.LastCommit]: `![Last Commit](${BadgeURL.Shields}github/${BadgeType.LastCommit}/${repoPath})`, + [BadgeType.License]: `![License](${BadgeURL.Shields}github/license/${repoPath})`, + [BadgeType.LinkedIn]: linkedInUsername + ? `LinkedIn` + : '', + [BadgeType.NpmVersion]: `[![npm version](${BadgeURL.Shields}npm/v/${packageName}.svg?style=flat-square)](https://www.npmjs.org/package/${packageJson.name})`, + [BadgeType.OpenPRs]: `![Open PRs](${BadgeURL.Shields}github/${BadgeType.OpenPRs}/${repoPath})`, + [BadgeType.RepoSize]: `![Repo Size](${BadgeURL.Shields}github/${BadgeType.RepoSize}/${repoPath})`, + [BadgeType.Version]: `![Version](${BadgeURL.Shields}github/${BadgeType.Version}/${repoPath})`, + [BadgeType.Visitors]: `![visitors](${BadgeURL.Visitors}${repoPath})`, + } + + const badgesSection = badgeTypes.filter((badgeType) => badges[badgeType]).map((badgeType) => badges[badgeType]) + + return badgesSection.length > 0 ? `
\n\n${badgesSection.join('\n')}\n\n
` : '' +} diff --git a/src/readme-validator.ts b/src/readme-validator.ts new file mode 100644 index 0000000..6a9eb4f --- /dev/null +++ b/src/readme-validator.ts @@ -0,0 +1,194 @@ +import { existsSync, readFileSync, writeFileSync } from 'fs' +import { join } from 'path' + +import { PackageJson } from './package-json' +import { + FileEncoding, + type PackageJsonType, + type ParsedReadme, + type Section, + type ValidateAndUpdateReadmeOptions, +} from './structures' + +export class ReadmeValidator { + /** + * Creates a missing section in the README content. + * + * @param readmeContent - The current content of the README file. + * @param section - The section to be added, containing header, content, or a placeholder. + * @returns The updated README content with the new section added. + */ + public static createSection(readmeContent: string, section: Section): string { + const { content, header, placeholder } = section + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const sectionContent = content || placeholder || 'Placeholder content here' + + console.log(`Creating section [${header}]...`) + + return `${readmeContent}\n## ${header}\n\n${sectionContent}\n` + } + + /** + * Loads the README content and package.json data. + * + * @param options - Contains paths to the package.json and the repository. + * @returns An object containing `packageJson`, `readmeContent`, and `readmePath`. + * @throws {Error} - Throws an error if the README.md file does not exist. + */ + public static load({ packageJsonPath, repoPath = '' }: Partial) { + const packageJson = PackageJson.findPackageJson(packageJsonPath) + const path = join(repoPath, 'README.md') + + console.log(`Loading README.md from [${path}]...`) + if (!existsSync(path)) { + throw new Error(`README.md file not found in the repository at [${path}]`) + } + + const content = readFileSync(path, FileEncoding.UTF8) + console.log(`Successfully loaded README.md`) + + return { content, packageJson, path } + } + + /** + * Saves the current readme content to the README.md file. + * + * @param path - The path to the README.md file. + * @param content - The content to be written to the README file. + */ + public static save(path: string, content: string) { + console.log(`Saving README.md to [${path}]...`) + writeFileSync(path, content, FileEncoding.UTF8) + console.log(`Successfully saved README.md`) + } + + /** + * Updates the content of an existing section in the README. + * + * @param readmeContent - The current content of the README file. + * @param section - The section containing the header and new content to update. + * @returns The updated README content with the new section content. + */ + public static updateSectionContent(readmeContent: string, section: Section): string { + const { content, header } = section + const updatedContent = `## ${header}\n\n${content}` + const sectionRegex = new RegExp(`## ${header}\\n[\\s\\S]*?(?=## |$)`, 'i') + + console.log(`Updating section [${header}]...`) + + return readmeContent.replace(sectionRegex, updatedContent) + } + + /** + * Orchestrates the process of validating and updating the README file. + * + * @param options - Configuration for validating and updating the README. + * @returns An object containing the main title, description, and the sections after processing. + * @throws {Error} - Throws an error if required sections are missing and `autoCreateMissing` is false. + */ + public static validateAndUpdate(options: ValidateAndUpdateReadmeOptions = {}): ParsedReadme { + const { content, packageJson, path } = ReadmeValidator.load(options) + const { autoCreateMissing, badgeSection = '', sections = [], updateContent, validateOnly } = options + let readmeContent = + badgeSection + ReadmeValidator.validateTitleAndDescription(packageJson, content, validateOnly) + + sections.forEach((section) => { + const { content, header, required } = section + const sectionExists = new RegExp(`## ${header}`, 'i').test(readmeContent) + + if (!sectionExists && required && !autoCreateMissing) { + throw new Error(`Validation failed. Required section [${header}] is missing.`) + } + + if (!sectionExists && autoCreateMissing) { + readmeContent = ReadmeValidator.createSection(readmeContent, section) + } + + if (sectionExists && updateContent && content) { + readmeContent = ReadmeValidator.updateSectionContent(readmeContent, section) + } + }) + + if (validateOnly) { + ReadmeValidator.validateSections(readmeContent, sections) + console.log('README.md has been successfully validated.') + } else { + ReadmeValidator.save(path, readmeContent) + console.log('README.md has been successfully validated and updated.') + } + + return { + description: packageJson.description ?? '', + sections, + title: `# ${packageJson.name}`, + } + } + + /** + * Validates that all required sections are present in the README content. + * + * @param readmeContent - The current content of the README file. + * @param sections - The list of sections to validate. + * @throws {Error} - Throws an error if any required section is missing from the README content. + */ + public static validateSections(readmeContent: string, sections: Section[]) { + sections.forEach(({ header, required }) => { + if (required && !new RegExp(`## ${header}`, 'i').test(readmeContent)) { + throw new Error(`Validation failed. Required section [${header}] is missing.`) + } + }) + } + + /** + * Ensures the main title and description are present in the README content. + * + * @param packageJson - The package.json data containing the name and description. + * @param readmeContent - The current content of the README file. + * @param validateOnly - Flag to indicate whether to only validate or add the title and description if missing. + * @returns The updated README content if `validateOnly` is false; otherwise, returns the original content. + * @throws {Error} - Throws an error if the title or description do not match when `validateOnly` is true. + */ + public static validateTitleAndDescription( + packageJson: PackageJsonType, + readmeContent: string, + validateOnly = false, + ): string { + const expectedTitle = `# ${packageJson.name}` + const expectedDescription = packageJson.description ?? '' + + const titleExists = readmeContent.includes(expectedTitle) + const descriptionExists = expectedDescription === '' || readmeContent.includes(expectedDescription) + + if (validateOnly) { + if (!titleExists || !descriptionExists) { + throw new Error( + `Validation failed. README does not match expected title and description from package.json.\n` + + `Expected Title [${expectedTitle}]\nExpected Description [${expectedDescription}]`, + ) + } + console.log(`Main title and description validated successfully for [${packageJson.name}].`) + + return readmeContent + } + + if (!titleExists) { + console.log(`Adding main title and description for [${packageJson.name}]...`) + + return `${expectedTitle}\n\n${expectedDescription}\n\n${readmeContent}` + } + + console.log(`Main title already exists for [${packageJson.name}]`) + + return readmeContent + } +} + +export const { + createSection, + load, + save, + updateSectionContent, + validateAndUpdate, + validateSections, + validateTitleAndDescription, +} = ReadmeValidator diff --git a/src/scripts/pre-commit.ts b/src/scripts/pre-commit.ts index 704c4d2..88180e0 100644 --- a/src/scripts/pre-commit.ts +++ b/src/scripts/pre-commit.ts @@ -1 +1,2 @@ import './version' +import './readme-validator' diff --git a/src/scripts/readme-validator.ts b/src/scripts/readme-validator.ts new file mode 100644 index 0000000..d0284f0 --- /dev/null +++ b/src/scripts/readme-validator.ts @@ -0,0 +1,14 @@ +import { PackageJson } from '../package-json' +import { getBadgeSection, getCustomSections } from '../readme-sections' +import { validateAndUpdate } from '../readme-validator' +import { BadgeType } from '../structures' + +const packageJson = PackageJson.findPackageJson() +const badgeSection = getBadgeSection({ + badgeTypes: Object.values(BadgeType), + linkedInUsername: 'coltenkrauter', + packageJson, +}) +const sections = getCustomSections(packageJson) + +validateAndUpdate({ badgeSection, sections }) diff --git a/src/structures.ts b/src/structures.ts new file mode 100644 index 0000000..04a55a0 --- /dev/null +++ b/src/structures.ts @@ -0,0 +1,158 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export interface PackageJsonType { + [key: string]: any + author?: Person | string + bin?: Bin | string + browser?: Browser | string + bugs?: Bugs | string + bundledDependencies?: string[] + config?: Record + contributors?: Person[] | string[] + cpu?: string[] + dependencies?: Dependencies + description?: string + devDependencies?: Dependencies + directories?: Directories + engines?: Engines + files?: string[] + homepage?: string + keywords?: string[] + license?: string + main?: string + man?: string | string[] + module?: string + name: string + optionalDependencies?: Dependencies + os?: string[] + peerDependencies?: Dependencies + private?: boolean + publishConfig?: PublishConfig + repository?: Repository | string + scripts?: Scripts + type?: 'commonjs' | 'module' + types?: string + version: string +} + +interface Bugs { + email?: string + url?: string +} + +interface Person { + email?: string + name?: string + url?: string +} + +interface Repository { + type: string + url: string +} + +type Scripts = Record + +type Dependencies = Record + +type Browser = Record + +type Bin = Record + +interface Directories { + bin?: string + doc?: string + example?: string + lib?: string + man?: string +} + +interface Engines { + [engine: string]: string | undefined + node?: string + npm?: string + yarn?: string +} + +interface PublishConfig { + [key: string]: any + access?: 'public' | 'restricted' + registry?: string +} + +export interface Section { + content?: string + header: string + placeholder?: string + required: boolean +} + +export interface ReadmeParserOptions { + autoCreateMissing?: boolean + updateContent?: boolean + validateOnly?: boolean +} + +export interface ParsedReadme { + description: string + sections: Section[] + title: string +} + +export interface Section { + content?: string + header: string + placeholder?: string + required: boolean +} + +export interface ParsedReadme { + description: string + sections: Section[] + title: string +} + +export enum FileEncoding { + UTF8 = 'utf8', +} + +export interface ValidateAndUpdateReadmeOptions { + autoCreateMissing?: boolean + badgeSection?: string + packageJsonPath?: string + repoPath?: string + sections?: Section[] + updateContent?: boolean + validateOnly?: boolean +} + +export enum BadgeURL { + LinkedIn = 'https://www.linkedin.com/in/', + Shields = 'https://img.shields.io/', + Visitors = 'https://visitor-badge.laobi.icu/badge?page_id=', +} + +export enum BadgeType { + CodeSize = 'languages/code-size', + CommitsPerMonth = 'commit-activity/m', + Contributors = 'contributors', + Forks = 'forks', + GitHubStars = 'stars', + InstallSize = 'npm/dw', + Issues = 'issues', + LastCommit = 'last-commit', + License = 'license', + LinkedIn = 'LinkedIn', + NpmVersion = 'npm', + OpenPRs = 'issues-pr', + RepoSize = 'repo-size', + Version = 'v/release', + Visitors = 'visitors', +} + +export interface BadgeSectionOptions { + badgeTypes: BadgeType[] + linkedInUsername?: string + packageJson: PackageJsonType + repoPath?: string +} diff --git a/src/version.ts b/src/version.ts index d6ac77c..e2a97f8 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,115 +1,105 @@ import { execSync } from 'child_process' -import fs from 'fs' -import path from 'path' -interface PackageJson { - version: string -} +import type { PackageJsonType as PackageJsonType } from './structures' -/** - * Retrieves the version from a specified Git reference's package.json. - * - * @param ref The Git reference (e.g., "HEAD", "HEAD~1") - * @returns The version string from the package.json - * @throws {Error} If fetching fails - */ -function getVersionFromGit(ref: string): string { - try { - const packageJsonContent: string = execSync(`git show ${ref}:package.json`, { encoding: 'utf8' }) - const data: PackageJson = JSON.parse(packageJsonContent) - - return data.version - } catch (error: unknown) { - throw new Error(`Failed fetching package.json from ${ref} with error [${error}]`) - } -} +import { PackageJson } from './package-json' -/** - * Recursively searches for package.json starting from the given directory and moving up. - * - * @param dir The directory to start searching from (defaults to current working directory) - * @returns The version string from the found package.json - * @throws {Error} If package.json is not found or cannot be read - */ -function getLocalVersion(dir: string = process.cwd()): string { - const packageJsonPath = path.join(dir, 'package.json') - - if (fs.existsSync(packageJsonPath)) { - try { - const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8') - const data: PackageJson = JSON.parse(packageJsonContent) +export class Version { + /** + * Compares the previous and current package.json versions. + * + * @param allowMatchWithoutError - If true, do not throw an error when versions match. + * @throws {Error} If versions are the same and `allowMatchWithoutError` is false. + */ + static compareVersions(allowMatchWithoutError = false): void { + const previous: string = Version.getPreviousVersion() + const current: string = Version.getLocalVersion() + const previousSha: string = Version.getCommitSha('HEAD~1') - return data.version - } catch (error: unknown) { - throw new Error(`Failed reading local package.json at ${packageJsonPath} with error [${error}]`) + if (previous !== current) { + console.log( + `Version changed from [${previous}] (commit: ${previousSha}) to [${current}] (latest local changes).`, + ) + + return } - } - // Move up to the parent directory - const parentDir = path.dirname(dir) - if (parentDir === dir) { - throw new Error('No package.json found in this project') - } + const message = `Version has not been changed: + Previous version: [${previous}] (commit: ${previousSha}) + Current version: [${current}] (latest local changes) + Please update the version before committing.` - return getLocalVersion(parentDir) -} + if (!allowMatchWithoutError) { + throw new Error(message) + } -/** - * Retrieves the commit SHA for a specified Git reference. - * - * @param ref The Git reference (e.g., "HEAD", "HEAD~1") - * @param short If true, fetch the short SHA (default is true) - * @returns The commit SHA - */ -function getCommitSha(ref: string, short = true): string { - try { - const command = `git rev-parse ${short ? '--short' : ''} ${ref}` - - return execSync(command, { encoding: 'utf8' }).trim() - } catch (error: unknown) { - throw new Error(`Failed to get commit SHA for ${ref} with error [${error}]`) + console.warn(message) } -} -/** - * Retrieves the version from the previous commit's package.json. - * - * @returns The previous version string - */ -function getPreviousVersion(): string { - return getVersionFromGit('HEAD~1') -} + /** + * Retrieves the commit SHA for a specified Git reference. + * + * @param ref - The Git reference (e.g., "HEAD", "HEAD~1"). + * @param short - If true, fetch the short SHA (default is true). + * @returns The commit SHA. + */ + static getCommitSha(ref: string, short = true): string { + try { + const command = `git rev-parse ${short ? '--short' : ''} ${ref}` -/** - * Compares the previous and current package.json versions. - * - * @param [allowMatchWithoutError=false] - If true, do not throw an error when versions match - * @throws {Error} If versions are the same and `allowMatchWithoutError` is false - * @returns - */ -function compareVersions(allowMatchWithoutError = false): void { - const previous: string = getPreviousVersion() - const current: string = getLocalVersion() - const previousSha = getCommitSha('HEAD~1') - - if (previous !== current) { - console.log( - `Version changed from [${previous}] (commit: ${previousSha}) to [${current}] (latest local changes).`, - ) - - return + return execSync(command, { encoding: 'utf8' }).trim() + } catch (error: unknown) { + if (error instanceof Error) { + throw new Error(`Failed to get commit SHA for ${ref} with error [${error.message}]`) + } else { + throw new Error(`Failed to get commit SHA for ${ref} with unknown error.`) + } + } } - const message = `Version has not been changed: - Previous version: [${previous}] (commit: ${previousSha}) - Current version: [${current}] (latest local changes) - Please update the version before committing.` + /** + * Recursively searches for package.json starting from the given directory and moving up. + * + * @param dir - The directory to start searching from (defaults to current working directory). + * @returns The version string from the found package.json. + * @throws {Error} If package.json is not found or cannot be read. + */ + static getLocalVersion(dir: string = process.cwd()): string { + const packageJson: PackageJsonType = PackageJson.findPackageJson(dir) + + return packageJson.version + } - if (!allowMatchWithoutError) { - throw new Error(message) + /** + * Retrieves the version from the previous commit's package.json. + * + * @returns The previous version string. + */ + static getPreviousVersion(): string { + return Version.getVersionFromGit('HEAD~1') } - console.warn(message) + /** + * Retrieves the version from a specified Git reference's package.json. + * + * @param ref - The Git reference (e.g., "HEAD", "HEAD~1"). + * @returns The version string from the package.json. + * @throws {Error} If fetching fails. + */ + static getVersionFromGit(ref: string): string { + try { + const packageJsonContent: string = execSync(`git show ${ref}:package.json`, { encoding: 'utf8' }) + const data: PackageJsonType = JSON.parse(packageJsonContent) + + return data.version + } catch (error: unknown) { + if (error instanceof Error) { + throw new Error(`Failed fetching package.json from ${ref} with error [${error.message}]`) + } else { + throw new Error(`Failed fetching package.json from ${ref} with unknown error.`) + } + } + } } -export { compareVersions, getLocalVersion as getCurrentVersion, getPreviousVersion } +export const { compareVersions, getCommitSha, getLocalVersion, getPreviousVersion, getVersionFromGit } = Version diff --git a/test/package-json.test.ts b/test/package-json.test.ts new file mode 100644 index 0000000..0debe3e --- /dev/null +++ b/test/package-json.test.ts @@ -0,0 +1,97 @@ +import { existsSync, mkdirSync, rmSync, writeFileSync } from 'fs' +import { join } from 'path' + +import { PackageJson } from '../src/package-json' + +describe('PackageJson', () => { + const testDir = join(__dirname, 'test-dir') + const packageJsonPath = join(testDir, 'package.json') + + const validPackageJsonContent = JSON.stringify({ + description: 'A test package.json file', + name: 'test-package', + version: '1.0.0', + }) + + const invalidPackageJsonContent = `{ + "name": "test-package", + "version": "1.0.0", + "description": "A test package.json file",` + + beforeAll(() => { + if (!existsSync(testDir)) { + mkdirSync(testDir) + } + }) + + afterAll(() => { + rmSync(testDir, { force: true, recursive: true }) + }) + + describe('readPackageJson', () => { + it('should read and parse a valid package.json file', () => { + writeFileSync(packageJsonPath, validPackageJsonContent, 'utf8') + + const packageJson = PackageJson.readPackageJson(packageJsonPath) + + expect(packageJson.name).toBe('test-package') + expect(packageJson.version).toBe('1.0.0') + expect(packageJson.description).toBe('A test package.json file') + }) + + it('should throw an error when reading an invalid package.json file', () => { + writeFileSync(packageJsonPath, invalidPackageJsonContent, 'utf8') + + expect(() => { + PackageJson.readPackageJson(packageJsonPath) + }).toThrow(/Failed to read package.json/) + }) + }) + + describe('findPackageJson', () => { + it('should find package.json in the current directory', () => { + writeFileSync(packageJsonPath, validPackageJsonContent, 'utf8') + + const packageJson = PackageJson.findPackageJson(testDir) + + expect(packageJson.name).toBe('test-package') + }) + + it('should find package.json in a parent directory', () => { + const nestedDir = join(testDir, 'nested') + mkdirSync(nestedDir) + + const packageJson = PackageJson.findPackageJson(nestedDir) + + expect(packageJson.name).toBe('test-package') + }) + + it('should throw an error if package.json is not found', () => { + rmSync(packageJsonPath) + + expect(() => { + PackageJson.findPackageJson('/test-root-dir') + }).toThrow('No package.json found in this project hierarchy.') + }) + }) + + describe('packageNameToTitle', () => { + it('should convert package name to title without scope', () => { + const packageName = 'test-package-name' + const regex = /^@.*\// + + const title = PackageJson.packageNameToTitle(packageName, regex) + + expect(title).toBe('Test Package Name') + }) + + it('should remove scope from package name and convert to title', () => { + const packageName = '@scope/test-package-name' + const regex = /^@.*\// + + const title = PackageJson.packageNameToTitle(packageName, regex) + + expect(title).toBe('Test Package Name') + }) + }) +}) diff --git a/test/readme-validator.test.ts b/test/readme-validator.test.ts new file mode 100644 index 0000000..b22c0e5 --- /dev/null +++ b/test/readme-validator.test.ts @@ -0,0 +1,191 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +import { existsSync, readFileSync, writeFileSync } from 'fs' +import { join } from 'path' + +import type { PackageJsonType } from '../src/structures' + +import { ReadmeValidator } from '../src/readme-validator' + +jest.mock('fs') + +const mockExistsSync = existsSync as jest.Mock +const mockReadFileSync = readFileSync as jest.Mock +const mockWriteFileSync = writeFileSync as jest.Mock +const mockPackageJson: PackageJsonType = { + description: 'A test package description', + name: 'test-package', + version: '1.0.0', +} + +const mockRepoPath = '/mock-repo' +const mockReadmePath = join(mockRepoPath, 'README.md') +const packageJsonPath = join(mockRepoPath, 'package.json') + +beforeEach(() => { + jest.resetAllMocks() + + mockExistsSync.mockImplementation((filePath) => filePath === mockReadmePath || filePath === packageJsonPath) + mockReadFileSync.mockImplementation((filePath) => { + if (filePath === mockReadmePath) + return '# test-package\n\nA test package description\n\n## Existing Section\n\nExisting content\n' + if (filePath === packageJsonPath) return JSON.stringify(mockPackageJson) + throw new Error(`File not found: ${filePath}`) + }) + + // eslint-disable-next-line @typescript-eslint/no-empty-function + mockWriteFileSync.mockImplementation(() => {}) +}) + +// eslint-disable-next-line max-lines-per-function +describe('ReadmeValidator', () => { + it('should throw an error if README.md does not exist', () => { + mockExistsSync.mockImplementation((filePath) => filePath === packageJsonPath) + + expect(() => ReadmeValidator.load({ packageJsonPath, repoPath: mockRepoPath })).toThrow( + 'README.md file not found in the repository', + ) + }) + + it('should initialize with package.json data and existing README content', () => { + const data = ReadmeValidator.load({ packageJsonPath, repoPath: mockRepoPath }) + expect(data).toBeDefined() + expect(mockReadFileSync).toHaveBeenCalledWith(mockReadmePath, 'utf8') + expect(mockReadFileSync).toHaveBeenCalledWith(packageJsonPath, 'utf8') + }) + + it('should validate missing required sections and throw an error', () => { + const { content } = ReadmeValidator.load({ packageJsonPath, repoPath: mockRepoPath }) + const sections = [ + { header: 'Missing Section', required: true }, + { header: 'Existing Section', required: true }, + ] + + expect(() => { + ReadmeValidator.validateSections(content, sections) + }).toThrow('Validation failed. Required section [Missing Section] is missing.') + }) + + it('should not throw an error when all required sections exist', () => { + const { content } = ReadmeValidator.load({ packageJsonPath, repoPath: mockRepoPath }) + const sections = [{ header: 'Existing Section', required: true }] + + expect(() => { + ReadmeValidator.validateSections(content, sections) + }).not.toThrow() + }) + + it('should auto-create missing sections when the option is enabled', () => { + ReadmeValidator.validateAndUpdate({ + autoCreateMissing: true, + packageJsonPath, + repoPath: mockRepoPath, + sections: [{ header: 'New Section', placeholder: 'Placeholder content here', required: true }], + }) + + expect(mockWriteFileSync).toHaveBeenCalledWith( + mockReadmePath, + expect.stringContaining('## New Section\n\nPlaceholder content here\n'), + 'utf8', + ) + }) + + it('should handle autoCreateMissing for multiple missing sections', () => { + const sections = [ + { header: 'First Missing Section', required: true }, + { header: 'Second Missing Section', required: false }, + ] + + ReadmeValidator.validateAndUpdate({ + autoCreateMissing: true, + packageJsonPath, + repoPath: mockRepoPath, + sections, + }) + expect(mockWriteFileSync).toHaveBeenCalledWith( + mockReadmePath, + expect.stringContaining('## First Missing Section\n\nPlaceholder content here\n'), + 'utf8', + ) + expect(mockWriteFileSync).toHaveBeenCalledWith( + mockReadmePath, + expect.stringContaining('## Second Missing Section\n\nPlaceholder content here\n'), + 'utf8', + ) + }) + + it('should replace existing section content when updateContent is true', () => { + const sections = [{ content: 'Updated content', header: 'Existing Section', required: true }] + + ReadmeValidator.validateAndUpdate({ packageJsonPath, repoPath: mockRepoPath, sections, updateContent: true }) + expect(mockWriteFileSync).toHaveBeenCalledWith( + mockReadmePath, + expect.stringContaining('## Existing Section\n\nUpdated content'), + 'utf8', + ) + }) + + it('should maintain existing sections when updateContent is false', () => { + const sections = [{ content: 'Should not overwrite', header: 'Existing Section', required: true }] + + ReadmeValidator.validateAndUpdate({ packageJsonPath, repoPath: mockRepoPath, sections, updateContent: false }) + expect(mockWriteFileSync).not.toHaveBeenCalledWith( + mockReadmePath, + expect.stringContaining('Should not overwrite'), + 'utf8', + ) + }) + + it('should include the main title and description if missing', () => { + mockReadFileSync.mockImplementation((filePath) => { + if (filePath === mockReadmePath) return '## Existing Section\n\nContent here\n' + if (filePath === packageJsonPath) return JSON.stringify(mockPackageJson) + throw new Error(`File not found: ${filePath}`) + }) + const sections = [{ header: 'Existing Section', required: true }] + ReadmeValidator.validateAndUpdate({ + autoCreateMissing: true, + packageJsonPath, + repoPath: mockRepoPath, + sections, + }) + + expect(mockWriteFileSync).toHaveBeenCalledWith( + mockReadmePath, + expect.stringContaining('# test-package\n\nA test package description\n'), + 'utf8', + ) + }) + + it('should handle an empty README correctly', () => { + mockReadFileSync.mockImplementation((filePath) => { + if (filePath === mockReadmePath) return '' + if (filePath === packageJsonPath) return JSON.stringify(mockPackageJson) + throw new Error(`File not found: ${filePath}`) + }) + + const sections = [{ content: 'Content for empty section', header: 'Empty Section', required: true }] + + ReadmeValidator.validateAndUpdate({ + autoCreateMissing: true, + packageJsonPath, + repoPath: mockRepoPath, + sections, + updateContent: true, + }) + expect(mockWriteFileSync).toHaveBeenCalledWith( + mockReadmePath, + expect.stringContaining('## Empty Section\n\nContent for empty section\n'), + 'utf8', + ) + }) + + it('should return the parsed readme data correctly', () => { + const sections = [{ header: 'Existing Section', required: true }] + const parsedReadme = ReadmeValidator.validateAndUpdate({ packageJsonPath, repoPath: mockRepoPath, sections }) + expect(parsedReadme).toEqual({ + description: 'A test package description', + sections: [{ header: 'Existing Section', required: true }], + title: '# test-package', + }) + }) +}) diff --git a/test/version.test.ts b/test/version.test.ts index 7a00367..95f637a 100644 --- a/test/version.test.ts +++ b/test/version.test.ts @@ -1,148 +1,119 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ - -import { beforeEach, describe, expect, it, jest } from '@jest/globals' import { execSync } from 'child_process' -import fs from 'fs' -import { compareVersions } from '../src/version' +import { PackageJson } from '../src/package-json' +import { Version } from '../src/version' jest.mock('child_process') -jest.mock('fs') +jest.mock('../src/package-json') -const mockedExecSync = execSync as jest.Mock -const mockedFs = fs as jest.Mocked +const mockedExecSync = execSync as jest.MockedFunction +const mockedFindPackageJson = PackageJson.findPackageJson as jest.MockedFunction -// eslint-disable-next-line max-lines-per-function -describe('compareVersions', () => { +describe('Version', () => { beforeEach(() => { - jest.clearAllMocks() + jest.resetAllMocks() }) - it('should log when versions differ', () => { - // Mock Git commands for fetching the previous version and commit SHA - mockedExecSync.mockImplementation((...args: unknown[]) => { - const command = args[0] as string - if (command === 'git show HEAD~1:package.json') { - return JSON.stringify({ version: '1.0.0' }) - } - if (command.startsWith('git rev-parse')) { - return 'abcd123' - } - throw new Error('Unexpected command') - }) - - // Mock reading the local package.json - mockedFs.readFileSync.mockReturnValue(JSON.stringify({ version: '1.0.1' })) - mockedFs.existsSync.mockReturnValue(true) - - const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {}) + describe('getCommitSha', () => { + it('should return the commit SHA', () => { + mockedExecSync.mockReturnValue('abc123\n') - compareVersions() + const sha = Version.getCommitSha('HEAD', true) - expect(mockedExecSync).toHaveBeenCalledWith('git show HEAD~1:package.json', { encoding: 'utf8' }) - expect(consoleLogSpy).toHaveBeenCalledWith( - 'Version changed from [1.0.0] (commit: abcd123) to [1.0.1] (latest local changes).', - ) - - consoleLogSpy.mockRestore() - }) - - it('should throw an error when versions are the same (default behavior)', () => { - // Mock Git commands for fetching the previous version and commit SHA - mockedExecSync.mockImplementation((...args: unknown[]) => { - const command = args[0] as string - if (command === 'git show HEAD~1:package.json') { - return JSON.stringify({ version: '1.0.0' }) - } - if (command.startsWith('git rev-parse')) { - return 'abcd123' - } - throw new Error('Unexpected command') + expect(mockedExecSync).toHaveBeenCalledWith('git rev-parse --short HEAD', { encoding: 'utf8' }) + expect(sha).toBe('abc123') }) - // Mock reading the local package.json - mockedFs.readFileSync.mockReturnValue(JSON.stringify({ version: '1.0.0' })) - mockedFs.existsSync.mockReturnValue(true) - - expect(() => { - compareVersions() - }).toThrow( - `Version has not been changed: - Previous version: [1.0.0] (commit: abcd123) - Current version: [1.0.0] (latest local changes) - Please update the version before committing.`, - ) - }) + it('should throw an error if execSync fails', () => { + mockedExecSync.mockImplementation(() => { + throw new Error('Command failed') + }) - it('should log a warning instead of throwing an error when versions are the same if allowMatchWithoutError is true', () => { - // Mock Git commands for fetching the previous version and commit SHA - mockedExecSync.mockImplementation((...args: unknown[]) => { - const command = args[0] as string - if (command === 'git show HEAD~1:package.json') { - return JSON.stringify({ version: '1.0.0' }) - } - if (command.startsWith('git rev-parse')) { - return 'abcd123' - } - throw new Error('Unexpected command') + expect(() => { + Version.getCommitSha('HEAD', true) + }).toThrow('Failed to get commit SHA for HEAD with error [Command failed]') }) + }) - // Mock reading the local package.json - mockedFs.readFileSync.mockReturnValue(JSON.stringify({ version: '1.0.0' })) - mockedFs.existsSync.mockReturnValue(true) - - const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}) - - compareVersions(true) + describe('getLocalVersion', () => { + it('should return the local package version', () => { + const packageJson = { name: 'test-package', version: '1.0.0' } + mockedFindPackageJson.mockReturnValue(packageJson) - expect(consoleWarnSpy).toHaveBeenCalledWith( - `Version has not been changed: - Previous version: [1.0.0] (commit: abcd123) - Current version: [1.0.0] (latest local changes) - Please update the version before committing.`, - ) + const version = Version.getLocalVersion() - consoleWarnSpy.mockRestore() + expect(mockedFindPackageJson).toHaveBeenCalled() + expect(version).toBe('1.0.0') + }) }) - it('should throw an error when fetching the previous package.json fails', () => { - mockedExecSync.mockImplementation((...args: unknown[]) => { - const command = args[0] as string - if (command === 'git show HEAD~1:package.json') { - throw new Error('Git command failed') - } + describe('getVersionFromGit', () => { + it('should return the version from git', () => { + const packageJsonContent = JSON.stringify({ name: 'test-package', version: '0.9.0' }) + mockedExecSync.mockReturnValue(packageJsonContent) - return 'abcd123' + const version = Version.getVersionFromGit('HEAD~1') + + expect(mockedExecSync).toHaveBeenCalledWith('git show HEAD~1:package.json', { encoding: 'utf8' }) + expect(version).toBe('0.9.0') }) - mockedFs.readFileSync.mockReturnValue(JSON.stringify({ version: '1.0.0' })) - mockedFs.existsSync.mockReturnValue(true) + it('should throw an error if execSync fails', () => { + mockedExecSync.mockImplementation(() => { + throw new Error('Command failed') + }) - expect(() => { - compareVersions() - }).toThrow('Failed fetching package.json from HEAD~1 with error [Error: Git command failed]') + expect(() => { + Version.getVersionFromGit('HEAD~1') + }).toThrow('Failed fetching package.json from HEAD~1 with error [Command failed]') + }) }) - it('should throw an error when reading the local package.json fails', () => { - mockedExecSync.mockImplementation((...args: unknown[]) => { - const command = args[0] as string - if (command === 'git show HEAD~1:package.json') { - return JSON.stringify({ version: '1.0.0' }) - } + describe('compareVersions', () => { + it('should log version change if versions are different', () => { + const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation() + jest.spyOn(Version, 'getPreviousVersion').mockReturnValue('0.9.0') + jest.spyOn(Version, 'getLocalVersion').mockReturnValue('1.0.0') + jest.spyOn(Version, 'getCommitSha').mockReturnValue('abc123') - return 'abcd123' + Version.compareVersions() + + expect(consoleLogSpy).toHaveBeenCalledWith( + 'Version changed from [0.9.0] (commit: abc123) to [1.0.0] (latest local changes).', + ) + consoleLogSpy.mockRestore() }) - // Mock reading the local package.json to throw an error - mockedFs.readFileSync.mockImplementation(() => { - throw new Error('Failed to read package.json') + it('should throw an error if versions are the same and allowMatchWithoutError is false', () => { + jest.spyOn(Version, 'getPreviousVersion').mockReturnValue('1.0.0') + jest.spyOn(Version, 'getLocalVersion').mockReturnValue('1.0.0') + jest.spyOn(Version, 'getCommitSha').mockReturnValue('abc123') + + expect(() => { + Version.compareVersions() + }).toThrow( + `Version has not been changed: + Previous version: [1.0.0] (commit: abc123) + Current version: [1.0.0] (latest local changes) + Please update the version before committing.`, + ) }) - mockedFs.existsSync.mockReturnValue(true) - expect(() => { - compareVersions() - }).toThrow( - 'Failed reading local package.json at /Users/coltenkrauter/Repositories/utils/package.json with error [Error: Failed to read package.json]', - ) + it('should warn if versions are the same and allowMatchWithoutError is true', () => { + const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation() + jest.spyOn(Version, 'getPreviousVersion').mockReturnValue('1.0.0') + jest.spyOn(Version, 'getLocalVersion').mockReturnValue('1.0.0') + jest.spyOn(Version, 'getCommitSha').mockReturnValue('abc123') + + Version.compareVersions(true) + + expect(consoleWarnSpy).toHaveBeenCalledWith( + `Version has not been changed: + Previous version: [1.0.0] (commit: abc123) + Current version: [1.0.0] (latest local changes) + Please update the version before committing.`, + ) + consoleWarnSpy.mockRestore() + }) }) }) diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 0000000..0e504ce --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules"], +} diff --git a/tsconfig.jest.json b/tsconfig.jest.json new file mode 100644 index 0000000..5ff11e8 --- /dev/null +++ b/tsconfig.jest.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["jest", "node"], + }, + "exclude": ["node_modules"], +} diff --git a/tsconfig.json b/tsconfig.json index e143645..1c6efea 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,29 +1,37 @@ { - "compilerOptions": { - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "jsx": "react", - "module": "CommonJS", - "moduleResolution": "node", - "outDir": "./dist", - "resolveJsonModule": true, - "rootDir": "./", - "skipLibCheck": true, - "strict": true, - "target": "ES6", - "types": ["jest", "node"] - }, - "exclude": ["node_modules"], - "include": [ - "*.ts", - "*.js", - "examples/**/*.ts", - "examples/**/*.tsx", - "src/**/*.ts", - "src/**/*.tsx", - "scripts/**/*.ts", - "scripts/**/*.tsx", - "test/**/*.ts", - "test/**/*.tsx" - ] + "compilerOptions": { + "target": "ES2022", + "module": "CommonJS", + "outDir": "dist", + "sourceMap": true, + "moduleResolution": "Node", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "allowJs": true, + "rootDir": "./", + "strictNullChecks": true, + "removeComments": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "types": ["node"] + }, + "include": [ + "**/*.ts", + "**/*.js" + ], + "exclude": [ + "node_modules", + ".vscode", + "dist", + "test", + "examples", + "coverage", + "*.js" + ] }