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)
+
+[![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)
+
+[![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)
+
+[![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)
+
+[![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)
+
+[![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)
+
+[![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)
+
+[![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
+ ? ``
+ : '',
+ [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"
+ ]
}