From 3a0b60bb94aef207c758bf058240a44b50a5d380 Mon Sep 17 00:00:00 2001 From: Kuzminov Alexander Date: Sun, 12 Apr 2026 19:21:02 +0300 Subject: [PATCH] feat: monorepo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What's new ### Zero dependencies — for real - Removed `semver` package — implemented own `inc()` + `satisfies()` with 595 cross-validated tests against original - Removed `npm version` command — direct file writing for `package.json`, `package-lock.json` (v1/v2/v3), `npm-shrinkwrap.json` - Removed `eslint` (8 packages) — migrated to `oxlint` with type-aware linting via `tsgolint` - Removed `ts-node` — using `tsx` ### Workspace / Monorepo support - Auto-detect workspaces from `package.json` (npm, yarn, bun) and `pnpm-workspace.yaml` (pnpm) - Independent versioning — each package bumped based on its own changes - Topological sort — dependencies released before dependents - Per-package CHANGELOG.md, git tags (`@org/pkg@1.2.0`), GitHub/GitLab releases - Single git commit for all version bumps - Cross-package dependency cascade with 3 modes: - `--cascade full` (default) — always bump + publish dependents - `--cascade lazy` — bump only when version range doesn't cover new version - `--cascade none` — only release packages with actual code changes - Smart range handling: preserves `^`, `~`, exact, `>=` prefixes - `workspace:*` / `workspace:^` / `workspace:~` protocol support - `--workspace` to select specific packages, `--contents` to publish from subdirectory ### Bun / pnpm / yarn support - Auto-detect package manager by lockfile for publish - Registry per PM: `--registry` (npm/pnpm/bun), `YARN_REGISTRY` + `YARN_NPM_REGISTRY_SERVER` (yarn) - Multiple registries in one run — `publish-github publish-npmjs --publish-custom URL` - Lifecycle scripts (`preversion`, `version`, `postversion`) run via shell directly — no dependency on any PM - `npm_package_version` env variable available in scripts ### New features - `dry-run` — preview what would happen without making changes (shows changed vs skipped packages) - `verbose` — detailed output with commits, tags, changelog preview - `provenance` — npm provenance attestation support - Config in `package.json` — `"simple-release": { "contents": "dist", "cascade": "full" }` - GitHub Action — `uses: askuzminov/simple-release@v1` - OIDC Trusted Publishing support for npmjs.org (no tokens needed) - GitHub App support for protected branch bypass ### Bug fixes - `getDate()` now returns `YYYY-MM-DD` (zero-padded) instead of `YYYY-M-D` - `sp()` handles ENOENT errors (command not found) - GitLab release logs correctly say "Gitlab" instead of "Github" - `arg()` no longer reads out of bounds for trailing options - `markdown.ts` `last()` safe on empty arrays ### Robustness - Git tag already exists — skip instead of crash - No commits in repo — fallback to empty tree hash - Detached HEAD — warning before git operations - Partial publish failure — continue all registries, report failures, exit 1 - Preversion script modifies package.json — re-read after hook - Symlink recursion in workspace discovery — cycle detection via `realpathSync` - BOM in package.json — `parseJSON()` strips UTF-8 BOM - Windows support — `cmd /c` for lifecycle scripts, `NUL` for empty tree hash ### CI / Auth - Updated workflow: `actions/checkout@v4`, `actions/setup-node@v4`, Node 22 - GitHub App setup guide for protected branches (no service accounts needed) - OIDC Trusted Publishing guide for npmjs.org (no token rotation) - npm 2FA / passkey documentation ### Tests - 906 tests (was 0), 0 lint errors, 0 warnings - Unit tests: semver, workspace discovery, topological sort, cascade deps, satisfies, lifecycle scripts, CLI flags, changelog, config parsing, extractPrefix, resolveWorkspace - Integration tests: real git repo — getHash, getTag, getCommitsRaw, parseCommits, writeGit, writeWorkspaceGit, detectPackageChanges, checkBranch, sp(), parseJSON, full single-package flow - Cross-validated: 595 tests comparing our `inc()` against original `semver` package ### Documentation - Complete README rewrite with comparison table, cascade mode guide, decision tree - GitHub App setup guide, OIDC Trusted Publishing guide - Migration guide from Lerna - Edge cases, environment variables, generated CHANGELOG example - npm 2FA / passkey / security key documentation --- .eslintrc.js | 255 -- .github/workflows/release.yml | 40 +- .oxlintrc.json | 112 + README.md | 899 +++++- action.yml | 26 + llms-full.txt | 976 ++++++ llms.txt | 119 + package-lock.json | 5465 +++++++++++---------------------- package.json | 77 +- src/arg.ts | 23 +- src/changelog.ts | 8 +- src/changes.ts | 122 + src/config.ts | 2 - src/git.ts | 45 +- src/index.ts | 274 +- src/lint.ts | 2 - src/log.ts | 1 - src/markdown.ts | 2 +- src/npm.ts | 82 +- src/package.ts | 89 +- src/release.ts | 87 +- src/semver.ts | 165 + src/types.ts | 9 + src/utils.ts | 34 +- src/workspace.ts | 193 ++ tests/changelog.test.ts | 78 + tests/changes.test.ts | 439 +++ tests/config.test.ts | 202 ++ tests/git.test.ts | 62 + tests/integration.test.ts | 532 ++++ tests/log.test.ts | 58 + tests/markdown.test.ts | 114 + tests/npm.test.ts | 121 + tests/package.test.ts | 359 +++ tests/parser.test.ts | 146 + tests/semver.test.ts | 287 ++ tests/utils.test.ts | 265 ++ tests/workspace.test.ts | 262 ++ tsconfig.build.json | 8 + tsconfig.json | 9 +- 40 files changed, 7904 insertions(+), 4145 deletions(-) delete mode 100644 .eslintrc.js create mode 100644 .oxlintrc.json create mode 100644 action.yml create mode 100644 llms-full.txt create mode 100644 llms.txt create mode 100644 src/changes.ts create mode 100644 src/semver.ts create mode 100644 src/workspace.ts create mode 100644 tests/changelog.test.ts create mode 100644 tests/changes.test.ts create mode 100644 tests/config.test.ts create mode 100644 tests/git.test.ts create mode 100644 tests/integration.test.ts create mode 100644 tests/log.test.ts create mode 100644 tests/markdown.test.ts create mode 100644 tests/npm.test.ts create mode 100644 tests/package.test.ts create mode 100644 tests/parser.test.ts create mode 100644 tests/semver.test.ts create mode 100644 tests/utils.test.ts create mode 100644 tests/workspace.test.ts diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index ad81e5a..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,255 +0,0 @@ -module.exports = { - env: { - browser: true, - node: true, - }, - ignorePatterns: ['.eslintrc.js', '**/dist/**', '**/node_modules/**'], - extends: [ - 'eslint:recommended', - 'plugin:import/typescript', - 'plugin:prettier/recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:@typescript-eslint/recommended-requiring-type-checking', - 'prettier', - ], - parser: '@typescript-eslint/parser', - parserOptions: { - project: 'tsconfig.json', - sourceType: 'module', - }, - plugins: ['@typescript-eslint', 'import', 'unicorn', 'prefer-arrow'], - settings: { - 'import/resolver': { - node: { - extensions: ['.ts', '.tsx', '.json', '.js'], - moduleDirectory: ['src', 'node_modules'], - }, - }, - }, - rules: { - 'prettier/prettier': 'error', - '@typescript-eslint/adjacent-overload-signatures': 'error', - '@typescript-eslint/array-type': [ - 'error', - { - default: 'array', - }, - ], - '@typescript-eslint/ban-types': [ - 'error', - { - types: { - Object: { - message: 'Avoid using the `Object` type. Did you mean `object`?', - }, - Function: { - message: 'Avoid using the `Function` type. Prefer a specific function type, like `() => void`.', - }, - Boolean: { - message: 'Avoid using the `Boolean` type. Did you mean `boolean`?', - }, - Number: { - message: 'Avoid using the `Number` type. Did you mean `number`?', - }, - String: { - message: 'Avoid using the `String` type. Did you mean `string`?', - }, - Symbol: { - message: 'Avoid using the `Symbol` type. Did you mean `symbol`?', - }, - }, - }, - ], - '@typescript-eslint/consistent-type-assertions': 'off', - '@typescript-eslint/dot-notation': 'error', - '@typescript-eslint/explicit-member-accessibility': [ - 'error', - { - accessibility: 'no-public', - }, - ], - '@typescript-eslint/indent': 'off', - '@typescript-eslint/member-delimiter-style': [ - 'error', - { - multiline: { - delimiter: 'semi', - requireLast: true, - }, - singleline: { - delimiter: 'semi', - requireLast: false, - }, - }, - ], - '@typescript-eslint/no-empty-function': 'off', - '@typescript-eslint/no-empty-interface': 'error', - '@typescript-eslint/no-explicit-any': 'warn', - '@typescript-eslint/no-misused-new': 'error', - '@typescript-eslint/no-namespace': 'error', - '@typescript-eslint/no-parameter-properties': 'off', - '@typescript-eslint/no-this-alias': 'error', - '@typescript-eslint/no-unused-expressions': [ - 'error', - { - allowShortCircuit: true, - }, - ], - '@typescript-eslint/no-use-before-define': 'off', - '@typescript-eslint/no-var-requires': 'error', - '@typescript-eslint/prefer-for-of': 'error', - '@typescript-eslint/prefer-function-type': 'error', - '@typescript-eslint/prefer-namespace-keyword': 'error', - '@typescript-eslint/quotes': [ - 'error', - 'single', - { - avoidEscape: true, - }, - ], - '@typescript-eslint/semi': ['error', 'always'], - '@typescript-eslint/strict-boolean-expressions': ['error', {}], - '@typescript-eslint/triple-slash-reference': [ - 'error', - { - path: 'always', - types: 'prefer-import', - lib: 'always', - }, - ], - '@typescript-eslint/type-annotation-spacing': 'off', - '@typescript-eslint/unified-signatures': 'error', - '@typescript-eslint/unbound-method': 'off', - '@typescript-eslint/no-unsafe-member-access': 'error', - '@typescript-eslint/no-unsafe-call': 'error', - '@typescript-eslint/no-unsafe-assignment': 'error', - '@typescript-eslint/no-unsafe-return': 'error', - '@typescript-eslint/restrict-template-expressions': 'error', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-unused-vars': ['off'], - '@typescript-eslint/no-for-in-array': ['off'], - '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true, ignoreIIFE: true }], - '@typescript-eslint/no-misused-promises': 'error', - 'arrow-parens': ['off', 'always'], - 'brace-style': ['off', 'off'], - camelcase: 'warn', - 'comma-dangle': 'off', - 'no-unused-vars': 'off', - complexity: [ - 'warn', - { - max: 20, - }, - ], - 'constructor-super': 'error', - 'default-case': 'error', - 'eol-last': 'off', - eqeqeq: ['error', 'smart'], - 'guard-for-in': 'off', - 'id-blacklist': [ - 'warn', - 'any', - 'Number', - 'number', - 'String', - 'string', - 'Boolean', - 'boolean', - 'Undefined', - 'undefined', - ], - 'id-match': 'error', - 'import/order': [ - 'error', - { - groups: [ - ['builtin', 'external'], - ['internal', 'unknown', 'object'], - ['parent', 'sibling', 'index'], - ], - 'newlines-between': 'always', - alphabetize: { order: 'asc', caseInsensitive: true }, - }, - ], - 'linebreak-style': ['error', 'unix'], - 'max-classes-per-file': ['error', 1], - 'max-len': [ - 'error', - { - code: 120, - }, - ], - 'new-parens': 'off', - 'newline-per-chained-call': 'off', - 'no-bitwise': 'off', - 'no-caller': 'error', - 'no-cond-assign': 'error', - 'no-console': 'error', - 'no-debugger': 'error', - 'no-duplicate-case': 'error', - 'no-duplicate-imports': 'error', - 'no-empty': 'off', - 'no-eval': 'error', - 'no-extra-bind': 'error', - 'no-extra-semi': 'off', - 'no-fallthrough': 'error', - 'no-invalid-this': 'off', - 'no-irregular-whitespace': 'off', - 'no-multiple-empty-lines': [ - 'error', - { - max: 1, - }, - ], - 'no-new-func': 'error', - 'no-new-wrappers': 'error', - 'no-redeclare': 'error', - 'no-return-await': 'error', - 'no-sequences': 'error', - 'no-shadow': [ - 'error', - { - hoist: 'all', - }, - ], - 'no-sparse-arrays': 'error', - 'no-template-curly-in-string': 'error', - 'no-throw-literal': 'error', - 'no-trailing-spaces': 'off', - 'no-undef-init': 'error', - 'no-underscore-dangle': 'error', - 'no-unsafe-finally': 'error', - 'no-unused-labels': 'error', - 'no-constant-condition': ['error', { checkLoops: false }], - 'no-var': 'error', - 'object-shorthand': 'error', - 'one-var': ['error', 'never'], - 'prefer-arrow/prefer-arrow-functions': 'off', - 'prefer-const': 'error', - 'prefer-object-spread': 'error', - 'prefer-template': 'error', - 'quote-props': ['error', 'as-needed'], - radix: 'error', - 'space-before-function-paren': [ - 'error', - { - anonymous: 'always', - asyncArrow: 'always', - named: 'never', - }, - ], - 'space-in-parens': ['off', 'never'], - 'spaced-comment': [ - 'error', - 'always', - { - markers: ['/'], - }, - ], - 'unicorn/filename-case': 'error', - 'use-isnan': 'warn', - 'valid-typeof': 'off', - 'prefer-rest-params': 'warn', - 'sort-imports': ['error', { ignoreDeclarationSort: true }], - }, -}; diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 12b44a6..fad8301 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,24 +13,34 @@ jobs: name: Release runs-on: ubuntu-latest if: "!contains(github.event.head_commit.message, 'skip ci')" - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + permissions: + contents: write + packages: write + id-token: write steps: + - name: Generate App Token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} - name: Checkout - uses: actions/checkout@v3 - with: { fetch-depth: 0 } + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 24 + - name: Setup NPM + run: echo '//npm.pkg.github.com/:_authToken=${{ github.token }}' >> ~/.npmrc - name: Set Branch id: branch run: | BNAME="$(echo -n ${GITHUB_REF##*/} | sed 's#/#-#g' | tr '[:upper:]' '[:lower:]')" BID="$GITHUB_RUN_NUMBER-$(date +%s)" echo "BRANCH_NAME=$BNAME" >> $GITHUB_ENV - echo "APP_BUILD=$BID-$GITHUB_SHA" >> $GITHUB_ENV echo "BUILD_ID=$BID" >> $GITHUB_ENV - name: Setup GIT run: | @@ -38,25 +48,23 @@ jobs: git config --local user.name "GitHub Action" mkdir -p ~/.ssh ssh-keyscan github.com > ~/.ssh/known_hosts - - name: Setup NPM - run: | - echo "Setup NPM" - echo 'always-auth=true' > ~/.npmrc - echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' >> ~/.npmrc - echo '//npm.pkg.github.com/:_authToken=${GH_TOKEN}' >> ~/.npmrc - name: Install dependencies run: npm ci - name: Check run: | npm run check-types npm run lint + - name: Test + run: npm test - name: Build run: npm run build - - name: Release GITHUB + - name: Release + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | if [[ $BRANCH_NAME == 'master' ]]; then echo "Release production" - npm run release publish-github publish-npmjs + npm run release -- publish-github publish-npmjs provenance else echo "Release canary" npm run release -- publish-github prerelease=$BRANCH_NAME.$BUILD_ID diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..d192e97 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,112 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["typescript", "unicorn", "import", "oxc"], + "categories": { + "correctness": "error" + }, + "options": { + "typeAware": true + }, + "ignorePatterns": ["dist/**", "node_modules/**"], + "env": { + "builtin": true, + "node": true, + "browser": true + }, + "rules": { + // === TypeScript rules === + "typescript/adjacent-overload-signatures": "error", + "typescript/array-type": ["error", { "default": "array" }], + "typescript/no-empty-interface": "error", + "typescript/no-explicit-any": "warn", + "typescript/no-misused-new": "error", + "typescript/no-namespace": "error", + "typescript/no-this-alias": "error", + "typescript/no-var-requires": "error", + "typescript/prefer-for-of": "error", + "typescript/prefer-function-type": "error", + "typescript/prefer-namespace-keyword": "error", + "typescript/triple-slash-reference": "error", + "typescript/unified-signatures": "error", + "typescript/no-restricted-types": [ + "error", + { + "types": { + "Object": { "message": "Avoid using the `Object` type. Did you mean `object`?" }, + "Function": { + "message": "Avoid using the `Function` type. Prefer a specific function type, like `() => void`." + }, + "Boolean": { "message": "Avoid using the `Boolean` type. Did you mean `boolean`?" }, + "Number": { "message": "Avoid using the `Number` type. Did you mean `number`?" }, + "String": { "message": "Avoid using the `String` type. Did you mean `string`?" }, + "Symbol": { "message": "Avoid using the `Symbol` type. Did you mean `symbol`?" } + } + } + ], + + // Type-aware typescript rules + "typescript/dot-notation": "error", + "typescript/strict-boolean-expressions": "error", + "typescript/no-unsafe-member-access": "error", + "typescript/no-unsafe-call": "error", + "typescript/no-unsafe-assignment": "error", + "typescript/no-unsafe-return": "error", + "typescript/restrict-template-expressions": "error", + "typescript/no-floating-promises": "error", + "typescript/no-misused-promises": "error", + + // === ESLint core rules === + "eslint/constructor-super": "error", + "eslint/default-case": "error", + "eslint/eqeqeq": "error", + "eslint/no-caller": "error", + "eslint/no-cond-assign": "error", + "eslint/no-console": "error", + "eslint/no-constant-condition": "error", + "eslint/no-debugger": "error", + "eslint/no-duplicate-case": "error", + "eslint/no-duplicate-imports": "error", + "eslint/no-eval": "error", + "eslint/no-extra-bind": "error", + "eslint/no-fallthrough": "error", + "eslint/no-new-func": "error", + "eslint/no-new-wrappers": "error", + "eslint/no-redeclare": "error", + "eslint/no-sequences": "error", + "eslint/no-shadow": "error", + "eslint/no-sparse-arrays": "error", + "eslint/no-template-curly-in-string": "error", + "eslint/no-throw-literal": "error", + "eslint/no-unsafe-finally": "error", + "eslint/no-unused-labels": "error", + "eslint/no-unused-expressions": "error", + "eslint/no-var": "error", + "eslint/object-shorthand": "error", + "eslint/prefer-const": "error", + "eslint/prefer-object-spread": "error", + "eslint/prefer-template": "error", + "eslint/radix": "error", + "eslint/use-isnan": "warn", + "eslint/complexity": ["warn", { "max": 20 }], + "eslint/max-classes-per-file": ["error", { "max": 1 }], + "eslint/sort-imports": ["error", { "ignoreDeclarationSort": true }], + "eslint/prefer-rest-params": "warn", + + // === Unicorn rules === + "unicorn/filename-case": "error" + }, + "overrides": [ + { + "files": ["tests/**"], + "rules": { + "typescript/no-floating-promises": "off" + } + }, + { + "files": ["src/lint.ts", "src/log.ts"], + "rules": { + "eslint/no-console": "off" + } + } + ] +} diff --git a/README.md b/README.md index c7a29a9..30d125f 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,21 @@ # Simple release +[![npm](https://img.shields.io/npm/v/@askuzminov/simple-release)](https://www.npmjs.com/package/@askuzminov/simple-release) +[![license](https://img.shields.io/npm/l/@askuzminov/simple-release)](https://github.com/askuzminov/simple-release/blob/master/LICENSE) + Full auto pipeline for simple releases your packages. - Collect git history for auto release - Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for commit guidelines - Generate CHANGELOG.md -- Update package.json, package-lock.json +- Update package.json, package-lock.json, npm-shrinkwrap.json - Parse body commits as Markdown, support utf-8 and emoji 🚀 - Commit, tag and push new version -- Upload release on Github -- Upload package on Github -- Upload package on Npmjs.org +- Upload release on Github / Gitlab +- Upload package on Github / Npmjs.org +- Workspace / monorepo support (npm, yarn, pnpm, bun) +- Auto-detect package manager for publish +- Lifecycle scripts support (preversion, version, postversion) - Zero dependencies ## Install @@ -19,9 +24,17 @@ Full auto pipeline for simple releases your packages. npm i @askuzminov/simple-release ``` -## CLI +## Init + +Setup initial version in package.json before first release: + +| Start with | fix → | feat → | break → | +| ---------------------- | ------- | ------- | ------- | +| `"version": "0.0.0"` | `0.0.1` | `0.1.0` | `1.0.0` | +| `"version": "0.0.0-0"` | `0.0.0` | `0.0.0` | `0.0.0` | +| `"version": "1.0.0-0"` | `1.0.0` | `1.0.0` | `1.0.0` | -package.json +## CLI ```json "scripts": { @@ -35,87 +48,89 @@ npm run release ## Commands -- **help** -> get command list - - ```bash - simple-release help - ``` - -- **prerelease** -> only up version, not git changes and release - - ```bash - simple-release prerelease - ``` - -- **prerelease=SOME.NEW.VERSION** -> only up version with custom ID - - ```bash - simple-release prerelease=1.2.0 - ``` +### Release control + +| Command | Description | +| ------------------- | ----------------------------------------------------------------------------------------------------------- | +| `help` | Get command list | +| `prerelease` | Only up version, no git changes and release | +| `prerelease=ID` | Only up version with custom prerelease ID | +| `enable-prerelease` | Force full process for prerelease | +| `disable-push` | Prevent git push | +| `disable-git` | Prevent git commit and tag | +| `disable-md` | Prevent write CHANGELOG.md | +| `disable-github` | Prevent Github / Gitlab release | +| `dry-run` | Show what would happen without making changes | +| `verbose` | Show detailed output (commits, changelog preview) | +| `provenance` | Generate [provenance attestation](https://docs.npmjs.com/generating-provenance-statements/) when publishing | -- **enable-prerelease** -> force full process +```bash +# Release without git push +simple-release disable-push publish-npmjs - ```bash - simple-release enable-prerelease - ``` +# Only bump version, no git, no publish +simple-release disable-git disable-github -- **disable-push** -> prevent git push +# Preview what would happen +simple-release dry-run - ```bash - simple-release disable-push - ``` +# Detailed output +simple-release verbose publish-npmjs +``` -- **disable-git** -> prevent git commit and tag +### Publish - ```bash - simple-release disable-git - ``` +| Command | Description | +| ---------------------- | -------------------------- | +| `publish-github` | Publish in Github registry | +| `publish-npmjs` | Publish in Npmjs registry | +| `--publish-custom URL` | Publish in custom registry | -- **disable-md** -> prevent write CHANGELOG.md - - ```bash - simple-release disable-md - ``` +```bash +simple-release publish-github publish-npmjs +simple-release --publish-custom https://your_domain/npm/ --publish-custom https://other_domain/npm/ -- **disable-github** -> prevent github release +# Publish to all registries at once +simple-release publish-github publish-npmjs --publish-custom https://your_domain/npm/ +``` - ```bash - simple-release disable-github - ``` +### Mode -- **publish-github** -> publish in github registry +```bash +simple-release --mode publish # Default +simple-release --mode current-version # Return current version +simple-release --mode next-version # Return next version +simple-release --mode has-changes # Return true | false +``` - ```bash - simple-release publish-github - ``` +Usage in scripts: -- **publish-npmjs** -> publish in npmjs registry +```bash +# Get current version +VERSION=$(simple-release --mode current-version) - ```bash +# Conditional release +if [ "$(simple-release --mode has-changes)" = "true" ]; then simple-release publish-npmjs - ``` +fi +``` -- **--publish-custom** -> publish in list of customs registries +### Prerelease - ```bash - simple-release --publish-custom https://your_domain_name/npm/ --publish-custom https://other_domain_name/api/npm - ``` +```bash +# Prerelease with auto ID: 1.2.3 → 1.2.4-pre.0 +simple-release prerelease -- **--mode** -> Mode: +# Prerelease with custom ID: 1.2.3 → 1.2.4-beta.0 +simple-release prerelease=beta - - publish (default); - - current-version (return current version); - - next-version (return next version); - - has-changes (return true | false). +# Full release process for prerelease (git, changelog, publish) +simple-release enable-prerelease prerelease=beta publish-npmjs +``` - ```bash - simple-release --mode publish # Default - simple-release --mode current-version # Return current npm version - simple-release --mode next-version # Return next npm version - simple-release --mode has-changes # Return true | false - ``` +### Filtering -- **--match** -> Match only needed tags in git history +- **--match** - Match only needed tags in git history Using [glob(7)](https://man7.org/linux/man-pages/man7/glob.7.html) @@ -124,18 +139,16 @@ npm run release simple-release --match='v[0-9]*' ``` -- **--file** -> Filter files for include/exclude +- **--file** - Filter files for include/exclude By default, all files are included except those described in `.gitignore` Include: - - folder - folder/file - folder/\*.css Exclude: - - :!folder - :!folder/\*file - :(exclude)folder @@ -146,7 +159,7 @@ npm run release simple-release --file=src --file=types --file='folder/*.css' --file=':!dist' ``` -- **--version** -> Custom format for version, default `v{VERSION}` +- **--version** - Custom format for version, default `v{VERSION}` ```bash simple-release --version v{VERSION} @@ -154,7 +167,7 @@ npm run release simple-release --version @my-org/my-lib@{VERSION} ``` -- **--source-repo** -> Custom path to links for sourcecode +- **--source-repo** - Custom path to links for sourcecode Default from `package.json`: `repository.url` @@ -164,7 +177,7 @@ npm run release simple-release --source-repo https://github.com/askuzminov/simple-release.git ``` -- **--release-repo** -> Custom path to links for release notes +- **--release-repo** - Custom path to links for release notes Default from `package.json`: `repository.url` @@ -174,28 +187,263 @@ npm run release simple-release --release-repo https://github.com/askuzminov/simple-release.git ``` +## Workspace / Monorepo + +Workspaces are auto-detected from: + +- `package.json` `workspaces` field (npm, yarn, bun) +- `pnpm-workspace.yaml` (pnpm) + +When workspaces are detected, simple-release will: + +1. Discover all packages +2. Detect changes per package (git log scoped to package directory) +3. Bump versions independently for changed packages +4. Update cross-package dependencies +5. Create a single git commit with all version bumps +6. Create separate git tags per package (`@org/pkg@1.2.0`) +7. Push once +8. Create releases and publish for each changed package + +### Workspace commands + +| Command | Description | +| ------------------ | -------------------------------------------- | +| `no-workspace` | Force single-package mode even in a monorepo | +| `--workspace NAME` | Select specific packages | +| `--contents DIR` | Publish from subdirectory (e.g. `dist`) | +| `--cascade MODE` | Cross-package dependency updates | + +### Cascade modes + +| Mode | Behavior | Best for | +| ---------------- | ---------------------------------------------- | ----------------------------------------------- | +| `full` (default) | Always bump + publish dependents | Enterprise, strict CI, npm registry consistency | +| `lazy` | Bump only when range doesn't cover new version | Libraries with semver ranges, fewer releases | +| `none` | Only release packages with actual code changes | Manual control, independent teams | + +`devDependencies` and `peerDependencies` are never cascaded in any mode. + +#### How `full` works + +When `@org/core@1.1.0` is released: + +- `@org/app` depends on `"@org/core": "^1.0.0"` → update to `"^1.1.0"` + patch bump + publish +- `@org/app` depends on `"@org/core": "workspace:*"` → patch bump + publish (PM resolves version) +- Always guarantees npm registry has the latest dependency versions + +#### How `lazy` works + +When `@org/core@1.1.0` is released: + +- `@org/app` depends on `"@org/core": "^1.0.0"` → **skip** (`^1.0.0` already covers `1.1.0`) +- `@org/app` depends on `"@org/core": "~1.0.0"` → **bump** (`~1.0.0` doesn't cover `1.1.0`) +- `@org/app` depends on `"@org/core": "1.0.0"` → **bump** (exact, doesn't cover `1.1.0`) +- `@org/app` depends on `"@org/core": "workspace:*"` → **bump** (exact, always broken) +- `@org/app` depends on `"@org/core": "workspace:^"` → **skip** (resolves to `^1.0.0`, covers `1.1.0`) +- `@org/app` depends on `"@org/core": "workspace:~"` → **bump** (resolves to `~1.0.0`, doesn't cover `1.1.0`) + +When `@org/core@2.0.0` (major) is released: + +- `@org/app` depends on `"@org/core": "^1.0.0"` → **bump** (`^1.0.0` doesn't cover `2.0.0`) +- `@org/app` depends on `"@org/core": "workspace:^"` → **bump** (resolves to `^1.0.0`, doesn't cover `2.0.0`) + +#### How `none` works + +- Only packages with actual git changes are released +- Cross-package dependencies are not updated +- Use when each team owns their package and manages deps manually + +#### Choosing a mode + +```text +Do your packages have consumers outside the monorepo (published to npm)? +├─ Yes +│ Do you use semver ranges (^, ~) between packages? +│ ├─ Yes → lazy (fewer releases, ranges handle compatibility) +│ └─ No (exact or workspace:*) → full (always keep npm in sync) +└─ No (internal only / private packages) + └─ none (no need to publish dependents) +``` + +### Example monorepo setup + +npm / yarn / bun: + +```text +my-monorepo/ + package.json # { "workspaces": ["packages/*"] } + packages/ + core/ + package.json # { "name": "@org/core", "version": "1.0.0" } + utils/ + package.json # { "name": "@org/utils", "version": "2.0.0" } +``` + +pnpm: + +```text +my-monorepo/ + package.json + pnpm-workspace.yaml # packages: \n - 'packages/*' + packages/ + core/ + package.json # { "name": "@org/core", "version": "1.0.0" } + utils/ + package.json # { "name": "@org/utils", "version": "2.0.0" } +``` + +```bash +# Release all changed packages +simple-release publish-npmjs + +# Release specific packages only +simple-release --workspace @org/core publish-npmjs + +# Publish from dist/ subdirectory +simple-release --contents dist publish-npmjs + +# Disable cascade +simple-release --cascade none publish-npmjs +``` + +## Package Manager Support + +The package manager is auto-detected by lockfile: + +| Lockfile | Package Manager | +| --------------------------------------- | --------------- | +| package-lock.json / npm-shrinkwrap.json | npm | +| bun.lock / bun.lockb | bun | +| pnpm-lock.yaml | pnpm | +| yarn.lock | yarn | + +The detected PM is used for `publish`. All other operations (version bump, lifecycle scripts, git) are PM-independent. + +> Registry is passed via `--registry` flag (npm, pnpm, bun) or `YARN_REGISTRY` env var (yarn). Multi-registry publishing works with all package managers. + +## Publishing to npmjs.org + +### Authentication options + +| Method | Token lifetime | 2FA | Best for | +| ---------------------------------------------------------------------------------- | ------------------ | ---------- | -------------------------- | +| [Granular Access Token](https://docs.npmjs.com/creating-and-viewing-access-tokens) | Up to 90 days | Bypass 2FA | CI with token rotation | +| [OIDC Trusted Publishing](https://docs.npmjs.com/trusted-publishers/) | Per-job (no token) | Not needed | GitHub Actions / GitLab CI | + +> Classic npm tokens were [revoked in December 2025](https://github.blog/changelog/2025-12-09-npm-classic-tokens-revoked-session-based-auth-and-cli-token-management-now-available/). Use granular tokens or OIDC instead. + +### Granular Access Token + +1. Create token at [npmjs.com/settings/~/tokens](https://www.npmjs.com/settings/~/tokens) +2. Set permissions: **Read and write**, select packages, enable **Bypass 2FA** +3. Add to CI secrets as `NPM_TOKEN` + +```bash +echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' >> ~/.npmrc +simple-release publish-npmjs +``` + +### OIDC Trusted Publishing (no tokens) + +Setup on npmjs.com: **Package → Settings → Trusted publishing → GitHub Actions** (specify repo and workflow file). Then: + +```yml +permissions: + contents: write + id-token: write + +steps: + - uses: actions/checkout@v4 + with: { fetch-depth: 0 } + + - uses: actions/setup-node@v4 + with: + node-version: 24 + registry-url: https://registry.npmjs.org + + - run: npx @askuzminov/simple-release publish-npmjs +``` + +Provenance attestations are generated **automatically** with OIDC — no `provenance` flag needed. + +> OIDC requires npm >= 11.5.1. Node 24+ includes it. Node 22 ships with npm 10 — upgrade npm or use Node 24. + +### Granular Token + Provenance + +No npmjs.org setup needed. Use `provenance` flag with a granular token: + +```yml +permissions: + contents: write + id-token: write # Required for provenance + +steps: + - uses: actions/checkout@v4 + with: { fetch-depth: 0 } + + - uses: actions/setup-node@v4 + with: + node-version: 24 + registry-url: https://registry.npmjs.org + + - run: npx @askuzminov/simple-release publish-npmjs provenance + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} +``` + +> **Note:** Provenance and OIDC apply only to **npmjs.org**. GitHub Packages and GitLab registries use their own auth (`GITHUB_TOKEN`, `CI_JOB_TOKEN`) and are not affected by these changes. + +## Lifecycle Scripts + +Lifecycle scripts from `package.json` are executed in this order: + +1. `preversion` - before version bump (e.g. run tests) +2. Version is written to package.json + lockfiles +3. `version` - after version bump (e.g. build) +4. Git commit, tag, push +5. `postversion` - after git operations (e.g. cleanup) + +Scripts are executed via shell directly, not through a package manager. The `npm_package_version` environment variable is set to the new version. + +```json +"scripts": { + "preversion": "npm test", + "version": "npm run build", + "postversion": "echo Released!" +} +``` + ## Lint -Check commit message on: +Check commit message with husky: + +Husky v9+ (`.husky/commit-msg`): + +```bash +simple-release-lint $1 +``` + +Husky v4 (`package.json`): ```json "husky": { "hooks": { "commit-msg": "simple-release-lint" } -}, +} ``` -Schemas of message: +### Schemas of message - `: ` - simple variant - `(scope): ` - with some scope - `!: ` - breaking change - `(scope)!: ` - breaking change with scope -Example with body +Example with body: -```bash +```text feat(ABC-123): New solution - Add [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) @@ -204,7 +452,7 @@ feat(ABC-123): New solution Example with breaking change: -```bash +```text refactor(core): Migrate on new solution BREAKING CHANGES: should upgrade version @@ -213,24 +461,26 @@ Some specs... ## Available types -- **break** - `MAJOR` Breaking changes -- **feat** - `MINOR` Features -- **build** - `PATCH` Build system or external dependencies -- **chore** - `PATCH` Chore -- **ci** - `PATCH` Continuous Integration -- **docs** - `PATCH` Documentation -- **fix** - `PATCH` Bug Fixes -- **perf** - `PATCH` Performance -- **refactor** - `PATCH` Refactoring -- **revert** - `PATCH` Revert code -- **style** - `PATCH` Styles and formatting -- **test** - `PATCH` Tests +| Type | Bump | Description | +| ------------ | ------- | ------------------------------------- | +| **break** | `MAJOR` | Breaking changes | +| **feat** | `MINOR` | Features | +| **build** | `PATCH` | Build system or external dependencies | +| **chore** | `PATCH` | Chore | +| **ci** | `PATCH` | Continuous Integration | +| **docs** | `PATCH` | Documentation | +| **fix** | `PATCH` | Bug Fixes | +| **perf** | `PATCH` | Performance | +| **refactor** | `PATCH` | Refactoring | +| **revert** | `PATCH` | Revert code | +| **style** | `PATCH` | Styles and formatting | +| **test** | `PATCH` | Tests | For `MAJOR` can be used `!` (example `refactor!: new lib`). For `MAJOR` also can be used `BREAKING CHANGES:` or `BREAKING CHANGE:` in description of commit. -## Ignored commits in lint +### Ignored commits - Merge pull request - Merge remote-tracking branch @@ -245,60 +495,443 @@ For `MAJOR` also can be used `BREAKING CHANGES:` or `BREAKING CHANGE:` in descri - fixup - squash -## Scripts +## Config in package.json -Used standard scripts for . +Instead of repeating CLI flags, configure defaults in `package.json`: ```json -"scripts": { - "preversion": "npm test", - "version": "npm run build", - "postversion": "npm run clean" +{ + "simple-release": { + "contents": "dist", + "cascade": "full" + } } ``` -And when enable publish, used standard scripts for . +CLI flags always override config values. -See full list of scripts in . +## Exit codes -## Init v1.0.0 +| Code | Meaning | +| ---- | ---------------------------------------------------- | +| `0` | Success (or no changes found) | +| `1` | Error (git failure, script failure, publish failure) | -Setup package.json +`--mode has-changes` returns exit code `0` in both cases — use the stdout output (`true`/`false`) to determine result. -```json -"version": "1.0.0-0", +## How it works + +```text +git log → parse conventional commits → calculate version bump + ↓ +preversion script → bump package.json + lockfiles → version script + ↓ +generate CHANGELOG.md + ↓ +git add → git commit → git tag → git push + ↓ +postversion script → github/gitlab release → npm publish ``` -## Example CI +Single package: one commit + one tag + +```text +commit: chore(release): v1.2.3 [skip ci] +tag: v1.2.3 +``` + +Monorepo: one commit + tag per changed package + +```text +commit: chore(release): publish [skip ci] + - @org/core@1.2.0 + - @org/utils@3.1.0 +tags: @org/core@1.2.0 + @org/utils@3.1.0 +``` + +## Generated CHANGELOG.md + +```markdown +# Changelog + +All notable changes to this project will be documented in this file. See [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for commit guidelines. + +## [v1.2.0](https://github.com/user/repo/compare/v1.1.0...v1.2.0) (2026-04-10) + +### Features + +- **auth**: add OAuth2 support ([abc1234](https://github.com/user/repo/commit/abc1234...)) +- new dashboard page ([def5678](https://github.com/user/repo/commit/def5678...)) + +### Bug Fixes + +- **api**: fix rate limiting ([fed8765](https://github.com/user/repo/commit/fed8765...)) +``` + +## Environment variables + +### Github + +| Variable | Required | Description | +| ------------------- | ------------ | ------------------------------------ | +| `GH_TOKEN` | For releases | Github token (PAT or `GITHUB_TOKEN`) | +| `GITHUB_SERVER_URL` | No | Default: `https://github.com` | +| `GITHUB_REPOSITORY` | No | Fallback repo (e.g. `user/repo`) | + +`GITHUB_TOKEN` (native) works if workflow has `permissions: contents: write`. +For protected branches with required reviews, `GITHUB_TOKEN` cannot push directly. Options: + +| Approach | Pros | Cons | +| --------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------- | +| [GitHub App](#github-app-setup) (recommended) | Bypass branch protection, reusable across repos, no personal account | One-time setup | +| PAT (Fine-grained) | Simple setup | Tied to personal account, max 1 year expiry | +| `disable-push` + PR | Works with `GITHUB_TOKEN` | Requires manual merge of release PR | +| Deploy key with write access | No personal account needed | Cannot create GitHub Releases via API | + +### GitHub App setup + +One-time setup, works across all your repos: + +**1. Create App:** + +GitHub → Settings → Developer settings → [GitHub Apps → New](https://github.com/settings/apps/new) + +- App name: `simple-release-bot` +- Webhook: **uncheck** Active +- Permissions: + - Repository → **Contents: Read and write** (git push, tags, releases) + - Repository → **Metadata: Read-only** (required) +- Where can this app be installed: **Only on this account** + +**2. Install App:** + +App page → Install App → select repos (all or specific) + +> If you add new permissions later, go to Installation → **Accept new permissions** to apply them. + +**3. Generate private key:** + +App page → General → Private keys → **Generate a private key** → save `.pem` file + +**4. Add secrets to each repo** (or use `gh` CLI for bulk): ```bash -echo "Setup NPM" -echo 'always-auth=true' > ~/.npmrc -echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' >> ~/.npmrc -echo '//npm.pkg.github.com/:_authToken=${GH_TOKEN}' >> ~/.npmrc - -if [[ $BRANCH_NAME == 'master' ]]; then - echo "Release production" - npx @askuzminov/simple-release publish-github publish-npmjs -else - echo "Release canary" - npx @askuzminov/simple-release publish-github publish-npmjs prerelease=$BRANCH_NAME.$BUILD_ID -fi +# Get App ID from app page → General → About +APP_ID=123456 + +# Add to all repos at once +for repo in my-repo-1 my-repo-2 my-repo-3; do + gh secret set APP_ID --repo yourname/$repo --body "$APP_ID" + gh secret set APP_PRIVATE_KEY --repo yourname/$repo < app-private-key.pem +done ``` -### Github actions +**5. Allow bypass in branch protection:** -Use fetch-depth for full +Repo → Settings → Branches → Edit rule → Allow specified actors to bypass → add your App + +**6. Workflow** (copy to any repo): ```yml +- name: Generate App Token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} +``` + +Use `${{ steps.app-token.outputs.token }}` as `GH_TOKEN` for releases. For GitHub Packages publish, use `${{ github.token }}` (App tokens [cannot publish packages](https://github.com/orgs/community/discussions/78090)). + +See [full workflow example](#github-actions). + +### Gitlab + +| Variable | Required | Description | +| ---------------------- | ------------ | ----------------------------- | +| `CI_JOB_TOKEN` | For releases | Gitlab job token | +| `CI_SERVER_HOST` | For releases | Gitlab domain | +| `CI_PROJECT_ID` | For releases | Gitlab project ID | +| `CI_JOB_ID` | Auto | Presence triggers Gitlab mode | +| `CI_SERVER_URL` | No | Gitlab server URL | +| `CI_PROJECT_NAMESPACE` | No | Fallback namespace | +| `CI_PROJECT_NAME` | No | Fallback project name | + +> Gitlab is auto-detected when `CI_JOB_ID` is set (present in all Gitlab CI pipelines). + +`CI_JOB_TOKEN` inherits permissions of the user who triggered the pipeline. For protected branches, use a [Group/Project Access Token](https://docs.gitlab.com/ee/user/group/settings/group_access_tokens.html) with Maintainer role (see [Gitlab CI example](#gitlab-ci)). + +## Edge cases + +**First release (no tags exist):** +Full git history from initial commit is used. All commits will appear in the first CHANGELOG entry. + +**No changes detected:** +Nothing happens — no version bump, no commit, no publish. Exit code 0. + +**Prerelease to release transition:** + +```text +1.0.0 → prerelease=beta → 1.0.1-beta.0 → prerelease=beta → 1.0.1-beta.1 → (release) → 1.0.1 +``` + +**Multiple breaking changes:** +Only one major bump per release, regardless of how many breaking commits. + +**Unknown commit types:** +Commits that don't match any known type (e.g. `update: something`) are grouped under "Others changes" with a patch bump. + +**Empty commit body:** +Supported — only the title line is required. + +**`[skip ci]` in release commit:** +Added automatically to prevent CI loops: `chore(release): v1.2.3 [skip ci]` + +**`package-lock.json` missing:** +Silently skipped — works fine without lockfiles. + +**Monorepo package without changes:** +Skipped entirely — no bump, no tag, no publish for unchanged packages. + +**Lifecycle script fails:** +Process stops immediately. No git commit, no publish. Fix the issue and run again. + +**Git push fails:** +Tags and commits are created locally but not pushed. Run `git push && git push --tags` manually after fixing. + +**Publish partially fails (git already pushed):** +Re-running the workflow won't help — no changes detected after `[skip ci]` commit. Publish failed packages manually: + +```bash +# Check which version was released +git log --oneline -1 + +# Publish manually to the failed registry +cd dist # if using --contents +npm publish --registry https://registry.npmjs.org --tag latest +``` + +**GitHub Release not created (git already pushed):** +Create release manually on GitHub → Releases → Draft new release → select existing tag. + +## Requirements + +- Node.js >= 10.12 (or Bun) +- Git +- Works on Linux, macOS, Windows + +## Why simple-release? + +| | simple-release | lerna (9+) | changesets | semantic-release | +| ------------------------- | ------------------ | -------------------- | --------------------------------------------------------- | -------------------------------------------------------------- | +| Dependencies | 0 | 50+ (requires Nx) | 20+ | 30+ | +| Setup | 1 line | lerna.json + nx.json | Config files | Plugins | +| Monorepo | Built-in | Built-in | Built-in | [Plugin](https://github.com/pmowrer/semantic-release-monorepo) | +| Independent versioning | Built-in | Built-in | Built-in | Plugin | +| Topological sort | Built-in | Built-in | [No](https://github.com/changesets/changesets/issues/238) | No | +| Cross-package cascade | full / lazy / none | Full only | Full only | No | +| Conventional commits | Built-in | Built-in | Manual | Plugin | +| Commit lint | Built-in | commitlint | commitlint | commitlint | +| Changelog | Built-in | Built-in | Built-in | Plugin | +| Github + Gitlab | Built-in | Github only | Github only | Plugin | +| npm / bun / pnpm / yarn | All built-in | npm | npm / pnpm / yarn | npm | +| Lifecycle scripts | Built-in (PM-free) | Via npm | No | No | +| Prerelease / canary | Built-in | Built-in | Built-in | Plugin | +| Provenance / OIDC | Built-in | v9+ | Via npm | Plugin | +| Multiple registries | Built-in | One at a time | One at a time | One at a time | +| Publish from subdirectory | `--contents` | `contents` | No | No | +| Node.js | >= 10.12 | >= 20.19 | >= 18 | >= 18 | + +## Migration from Lerna + +### 1. Add workspaces to root package.json + +```diff + { + "name": "@org/monorepo", ++ "workspaces": ["packages/*"], + "devDependencies": { +- "lerna": "^6.0.0", ++ "@askuzminov/simple-release": "^1" + }, + "scripts": { +- "lerna:publish": "lerna publish --create-release github", +- "lerna:canary": "lerna publish --canary --amend --allow-branch feature/*" ++ "release": "simple-release --contents dist publish-github", ++ "release:canary": "simple-release --contents dist publish-github prerelease=$BRANCH_NAME.$BUILD_ID" + } + } +``` + +### 2. Replace commitlint with simple-release-lint + +`.husky/commit-msg`: + +```diff +-npx commitlint --edit $1 ++simple-release-lint $1 +``` + +Remove `commitlint` and `@commitlint/*` from devDependencies. + +### 3. Delete lerna config + +```bash +rm lerna.json +``` + +### 4. Mapping lerna options + +| lerna.json | simple-release | Notes | +| ------------------------------------------------ | --------------------------------------------------- | ------------------------------------------ | +| `"version": "independent"` | Default | Always independent | +| `"conventionalCommits": true` | Default | Always conventional commits | +| `"contents": "dist"` | `--contents dist` | Publish from subdirectory | +| `"registry": "https://npm.pkg.github.com"` | `publish-github` | Or `--publish-custom URL` | +| `"createRelease": "github"` | Default | Always creates release when `GH_TOKEN` set | +| `"message": "chore(release): publish [skip ci]"` | Default | Same format built-in | +| `"ignoreChanges": ["**/*.spec.ts"]` | `--file ':!**/*.spec.ts'` | Git pathspec | +| `"allowBranch": ["master"]` | CI-level | Run release only on master in CI | +| `lerna publish --canary --preid=ID` | `prerelease=ID` | Canary releases | +| `lerna run build` | `npm run build -ws` or `"version": "npm run build"` | Via npm workspaces or lifecycle | + +### 5. Update CI + +```diff + # Production release +-npm run lerna:publish ++npm run release + + # Canary release +-npm run lerna:canary ++npm run release:canary +``` + +### 6. Cross-package dependencies + +Lerna updates cross-package deps automatically. simple-release does the same: + +- `--cascade full` (default) — always bump + publish dependents (like lerna) +- `--cascade lazy` — bump only when range doesn't cover new version +- `--cascade none` — manual management + +### 7. Packages not tracked by lerna + +If some packages were excluded from `lerna.json`, use `--workspace` to select specific packages: + +```bash +# Release only specific packages +simple-release --workspace @org/core --workspace @org/utils --contents dist publish-github +``` + +## Example CI + +### Github Actions + +Full example with GitHub App + OIDC (recommended): + +```yml +name: Release + +on: + push: + branches: [master, feature/*] + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +jobs: + release: + name: Release + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, 'skip ci')" + permissions: + contents: write + packages: write + id-token: write + steps: + - name: Generate App Token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + - name: Setup NPM + run: echo '//npm.pkg.github.com/:_authToken=${{ steps.app-token.outputs.token }}' >> ~/.npmrc + - name: Setup GIT + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + mkdir -p ~/.ssh + ssh-keyscan github.com > ~/.ssh/known_hosts + - name: Install dependencies + run: npm ci + - name: Check + run: | + npm run check-types + npm run lint + - name: Test + run: npm test + - name: Build + run: npm run build + - name: Release + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + BRANCH_NAME: # set via your branch detection step + BUILD_ID: # set via your build ID step + run: | + if [[ $BRANCH_NAME == 'master' ]]; then + npm run release -- publish-github publish-npmjs provenance + else + npm run release -- publish-github prerelease=$BRANCH_NAME.$BUILD_ID + fi +``` + +Using the action: + +```yml +- uses: actions/checkout@v4 with: { fetch-depth: 0 } + +- uses: askuzminov/simple-release@v1 + with: + args: 'publish-npmjs publish-github' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` -### Setup GIT example +Minimal example with npx (no protected branch push): + +```yml +- uses: actions/checkout@v4 + with: { fetch-depth: 0 } -Setup branch for push +- uses: actions/setup-node@v4 + with: { node-version: 24 } + +- run: npx @askuzminov/simple-release publish-npmjs provenance + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` + +### Setup GIT ```bash git config --global push.default current @@ -308,26 +941,14 @@ mkdir -p ~/.ssh ssh-keyscan github.com > ~/.ssh/known_hosts ``` -### Setup custom gitlab example - -Add in .gitignore - -```md -.npmrc -``` +### Gitlab CI -Setup package.json - -```json - "scripts": { - "release": "simple-release" - }, -``` +Add `.npmrc` to `.gitignore`. -Setup .gitlab-ci.yml +Setup `.gitlab-ci.yml`: ```yml -image: node:18 +image: node:24 stages: - publish @@ -364,10 +985,8 @@ deploy: npm test - | if [[ $CI_COMMIT_REF_NAME == 'master' ]]; then - echo "Release production" npm run release -- --publish-custom "https://${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/" else - echo "Release canary" npm run release -- --publish-custom "https://${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/" prerelease=$CI_COMMIT_REF_SLUG.$CI_JOB_ID fi ``` diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..c681245 --- /dev/null +++ b/action.yml @@ -0,0 +1,26 @@ +name: 'Simple Release' +description: 'Auto release pipeline for npm packages with zero dependencies' +branding: + icon: 'package' + color: 'green' + +inputs: + args: + description: 'Arguments to pass to simple-release (e.g. "publish-npmjs publish-github")' + required: false + default: '' + node-version: + description: 'Node.js version to use' + required: false + default: '22' + +runs: + using: 'composite' + steps: + - uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node-version }} + + - name: Run simple-release + shell: bash + run: npx @askuzminov/simple-release ${{ inputs.args }} diff --git a/llms-full.txt b/llms-full.txt new file mode 100644 index 0000000..c8a1e7e --- /dev/null +++ b/llms-full.txt @@ -0,0 +1,976 @@ +# Simple release + +[![npm](https://img.shields.io/npm/v/@askuzminov/simple-release)](https://www.npmjs.com/package/@askuzminov/simple-release) +[![license](https://img.shields.io/npm/l/@askuzminov/simple-release)](https://github.com/askuzminov/simple-release/blob/master/LICENSE) + +Full auto pipeline for simple releases your packages. + +- Collect git history for auto release +- Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for commit guidelines +- Generate CHANGELOG.md +- Update package.json, package-lock.json, npm-shrinkwrap.json +- Parse body commits as Markdown, support utf-8 and emoji 🚀 +- Commit, tag and push new version +- Upload release on Github / Gitlab +- Upload package on Github / Npmjs.org +- Workspace / monorepo support (npm, yarn, pnpm, bun) +- Auto-detect package manager for publish +- Lifecycle scripts support (preversion, version, postversion) +- Zero dependencies + +## Install + +```bash +npm i @askuzminov/simple-release +``` + +## Init + +Setup initial version in package.json before first release: + +| Start with | fix → | feat → | break → | +| --- | --- | --- | --- | +| `"version": "0.0.0"` | `0.0.1` | `0.1.0` | `1.0.0` | +| `"version": "0.0.0-0"` | `0.0.0` | `0.0.0` | `0.0.0` | +| `"version": "1.0.0-0"` | `1.0.0` | `1.0.0` | `1.0.0` | + +## CLI + +```json +"scripts": { + "release": "simple-release" +} +``` + +```bash +npm run release +``` + +## Commands + +### Release control + +| Command | Description | +| --- | --- | +| `help` | Get command list | +| `prerelease` | Only up version, no git changes and release | +| `prerelease=ID` | Only up version with custom prerelease ID | +| `enable-prerelease` | Force full process for prerelease | +| `disable-push` | Prevent git push | +| `disable-git` | Prevent git commit and tag | +| `disable-md` | Prevent write CHANGELOG.md | +| `disable-github` | Prevent Github / Gitlab release | +| `dry-run` | Show what would happen without making changes | +| `verbose` | Show detailed output (commits, changelog preview) | +| `provenance` | Generate [provenance attestation](https://docs.npmjs.com/generating-provenance-statements/) when publishing | + +```bash +# Release without git push +simple-release disable-push publish-npmjs + +# Only bump version, no git, no publish +simple-release disable-git disable-github + +# Preview what would happen +simple-release dry-run + +# Detailed output +simple-release verbose publish-npmjs +``` + +### Publish + +| Command | Description | +| --- | --- | +| `publish-github` | Publish in Github registry | +| `publish-npmjs` | Publish in Npmjs registry | +| `--publish-custom URL` | Publish in custom registry | + +```bash +simple-release publish-github publish-npmjs +simple-release --publish-custom https://your_domain/npm/ --publish-custom https://other_domain/npm/ + +# Publish to all registries at once +simple-release publish-github publish-npmjs --publish-custom https://your_domain/npm/ +``` + +### Mode + +```bash +simple-release --mode publish # Default +simple-release --mode current-version # Return current version +simple-release --mode next-version # Return next version +simple-release --mode has-changes # Return true | false +``` + +Usage in scripts: + +```bash +# Get current version +VERSION=$(simple-release --mode current-version) + +# Conditional release +if [ "$(simple-release --mode has-changes)" = "true" ]; then + simple-release publish-npmjs +fi +``` + +### Prerelease + +```bash +# Prerelease with auto ID: 1.2.3 → 1.2.4-pre.0 +simple-release prerelease + +# Prerelease with custom ID: 1.2.3 → 1.2.4-beta.0 +simple-release prerelease=beta + +# Full release process for prerelease (git, changelog, publish) +simple-release enable-prerelease prerelease=beta publish-npmjs +``` + +### Filtering + +- **--match** - Match only needed tags in git history + + Using [glob(7)](https://man7.org/linux/man-pages/man7/glob.7.html) + + ```bash + simple-release --match 'v[0-9]*' + simple-release --match='v[0-9]*' + ``` + +- **--file** - Filter files for include/exclude + + By default, all files are included except those described in `.gitignore` + + Include: + + - folder + - folder/file + - folder/\*.css + + Exclude: + + - :!folder + - :!folder/\*file + - :(exclude)folder + - :(exclude,icase)SUB + + ```bash + simple-release --file src --file types --file 'folder/*.css' --file ':!dist' + simple-release --file=src --file=types --file='folder/*.css' --file=':!dist' + ``` + +- **--version** - Custom format for version, default `v{VERSION}` + + ```bash + simple-release --version v{VERSION} + simple-release --version=v{VERSION} + simple-release --version @my-org/my-lib@{VERSION} + ``` + +- **--source-repo** - Custom path to links for sourcecode + + Default from `package.json`: `repository.url` + + ```bash + simple-release --source-repo myorg/somepackage + simple-release --source-repo https://github.com/askuzminov/simple-release + simple-release --source-repo https://github.com/askuzminov/simple-release.git + ``` + +- **--release-repo** - Custom path to links for release notes + + Default from `package.json`: `repository.url` + + ```bash + simple-release --release-repo myorg/somepackage + simple-release --release-repo https://github.com/askuzminov/simple-release + simple-release --release-repo https://github.com/askuzminov/simple-release.git + ``` + +## Workspace / Monorepo + +Workspaces are auto-detected from: + +- `package.json` `workspaces` field (npm, yarn, bun) +- `pnpm-workspace.yaml` (pnpm) + +When workspaces are detected, simple-release will: + +1. Discover all packages +2. Detect changes per package (git log scoped to package directory) +3. Bump versions independently for changed packages +4. Update cross-package dependencies +5. Create a single git commit with all version bumps +6. Create separate git tags per package (`@org/pkg@1.2.0`) +7. Push once +8. Create releases and publish for each changed package + +### Workspace commands + +| Command | Description | +| --- | --- | +| `no-workspace` | Force single-package mode even in a monorepo | +| `--workspace NAME` | Select specific packages | +| `--contents DIR` | Publish from subdirectory (e.g. `dist`) | +| `--cascade MODE` | Cross-package dependency updates | + +### Cascade modes + +| Mode | Behavior | Best for | +| --- | --- | --- | +| `full` (default) | Always bump + publish dependents | Enterprise, strict CI, npm registry consistency | +| `lazy` | Bump only when range doesn't cover new version | Libraries with semver ranges, fewer releases | +| `none` | Only release packages with actual code changes | Manual control, independent teams | + +`devDependencies` and `peerDependencies` are never cascaded in any mode. + +#### How `full` works + +When `@org/core@1.1.0` is released: + +- `@org/app` depends on `"@org/core": "^1.0.0"` → update to `"^1.1.0"` + patch bump + publish +- `@org/app` depends on `"@org/core": "workspace:*"` → patch bump + publish (PM resolves version) +- Always guarantees npm registry has the latest dependency versions + +#### How `lazy` works + +When `@org/core@1.1.0` is released: + +- `@org/app` depends on `"@org/core": "^1.0.0"` → **skip** (`^1.0.0` already covers `1.1.0`) +- `@org/app` depends on `"@org/core": "~1.0.0"` → **bump** (`~1.0.0` doesn't cover `1.1.0`) +- `@org/app` depends on `"@org/core": "1.0.0"` → **bump** (exact, doesn't cover `1.1.0`) +- `@org/app` depends on `"@org/core": "workspace:*"` → **bump** (exact, always broken) +- `@org/app` depends on `"@org/core": "workspace:^"` → **skip** (resolves to `^1.0.0`, covers `1.1.0`) +- `@org/app` depends on `"@org/core": "workspace:~"` → **bump** (resolves to `~1.0.0`, doesn't cover `1.1.0`) + +When `@org/core@2.0.0` (major) is released: + +- `@org/app` depends on `"@org/core": "^1.0.0"` → **bump** (`^1.0.0` doesn't cover `2.0.0`) +- `@org/app` depends on `"@org/core": "workspace:^"` → **bump** (resolves to `^1.0.0`, doesn't cover `2.0.0`) + +#### How `none` works + +- Only packages with actual git changes are released +- Cross-package dependencies are not updated +- Use when each team owns their package and manages deps manually + +#### Choosing a mode + +```text +Do your packages have consumers outside the monorepo (published to npm)? +├─ Yes +│ Do you use semver ranges (^, ~) between packages? +│ ├─ Yes → lazy (fewer releases, ranges handle compatibility) +│ └─ No (exact or workspace:*) → full (always keep npm in sync) +└─ No (internal only / private packages) + └─ none (no need to publish dependents) +``` + +### Example monorepo setup + +npm / yarn / bun: + +```text +my-monorepo/ + package.json # { "workspaces": ["packages/*"] } + packages/ + core/ + package.json # { "name": "@org/core", "version": "1.0.0" } + utils/ + package.json # { "name": "@org/utils", "version": "2.0.0" } +``` + +pnpm: + +```text +my-monorepo/ + package.json + pnpm-workspace.yaml # packages: \n - 'packages/*' + packages/ + core/ + package.json # { "name": "@org/core", "version": "1.0.0" } + utils/ + package.json # { "name": "@org/utils", "version": "2.0.0" } +``` + +```bash +# Release all changed packages +simple-release publish-npmjs + +# Release specific packages only +simple-release --workspace @org/core publish-npmjs + +# Publish from dist/ subdirectory +simple-release --contents dist publish-npmjs + +# Disable cascade +simple-release --cascade none publish-npmjs +``` + +## Package Manager Support + +The package manager is auto-detected by lockfile: + +| Lockfile | Package Manager | +| --- | --- | +| package-lock.json / npm-shrinkwrap.json | npm | +| bun.lock / bun.lockb | bun | +| pnpm-lock.yaml | pnpm | +| yarn.lock | yarn | + +The detected PM is used for `publish`. All other operations (version bump, lifecycle scripts, git) are PM-independent. + +> Registry is passed via `--registry` flag (npm, pnpm, bun) or `YARN_REGISTRY` env var (yarn). Multi-registry publishing works with all package managers. + +## Publishing to npmjs.org + +### Authentication options + +| Method | Token lifetime | 2FA | Best for | +| --- | --- | --- | --- | +| [Granular Access Token](https://docs.npmjs.com/creating-and-viewing-access-tokens) | Up to 90 days | Bypass 2FA | CI with token rotation | +| [OIDC Trusted Publishing](https://docs.npmjs.com/trusted-publishers/) | Per-job (no token) | Not needed | GitHub Actions / GitLab CI | + +> Classic npm tokens were [revoked in December 2025](https://github.blog/changelog/2025-12-09-npm-classic-tokens-revoked-session-based-auth-and-cli-token-management-now-available/). Use granular tokens or OIDC instead. + +### Granular Access Token + +1. Create token at [npmjs.com/settings/~/tokens](https://www.npmjs.com/settings/~/tokens) +2. Set permissions: **Read and write**, select packages, enable **Bypass 2FA** +3. Add to CI secrets as `NPM_TOKEN` + +```bash +echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' >> ~/.npmrc +simple-release publish-npmjs +``` + +### OIDC Trusted Publishing (no tokens) + +Setup on npmjs.com: **Package → Settings → Trusted publishing → GitHub Actions** (specify repo and workflow file). Then: + +```yml +permissions: + contents: write + id-token: write + +steps: + - uses: actions/checkout@v4 + with: { fetch-depth: 0 } + + - uses: actions/setup-node@v4 + with: + node-version: 22 + registry-url: https://registry.npmjs.org + + - run: npx @askuzminov/simple-release publish-npmjs +``` + +Provenance attestations are generated **automatically** with OIDC — no `provenance` flag needed. + +### Granular Token + Provenance + +No npmjs.org setup needed. Use `provenance` flag with a granular token: + +```yml +permissions: + contents: write + id-token: write # Required for provenance + +steps: + - uses: actions/checkout@v4 + with: { fetch-depth: 0 } + + - uses: actions/setup-node@v4 + with: + node-version: 22 + registry-url: https://registry.npmjs.org + + - run: npx @askuzminov/simple-release publish-npmjs provenance + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} +``` + +> **Note:** Provenance and OIDC apply only to **npmjs.org**. GitHub Packages and GitLab registries use their own auth (`GITHUB_TOKEN`, `CI_JOB_TOKEN`) and are not affected by these changes. + +## Lifecycle Scripts + +Lifecycle scripts from `package.json` are executed in this order: + +1. `preversion` - before version bump (e.g. run tests) +2. Version is written to package.json + lockfiles +3. `version` - after version bump (e.g. build) +4. Git commit, tag, push +5. `postversion` - after git operations (e.g. cleanup) + +Scripts are executed via shell directly, not through a package manager. The `npm_package_version` environment variable is set to the new version. + +```json +"scripts": { + "preversion": "npm test", + "version": "npm run build", + "postversion": "echo Released!" +} +``` + +## Lint + +Check commit message with husky: + +Husky v9+ (`.husky/commit-msg`): + +```bash +simple-release-lint $1 +``` + +Husky v4 (`package.json`): + +```json +"husky": { + "hooks": { + "commit-msg": "simple-release-lint" + } +} +``` + +### Schemas of message + +- `: ` - simple variant +- `(scope): ` - with some scope +- `!: ` - breaking change +- `(scope)!: ` - breaking change with scope + +Example with body: + +```text +feat(ABC-123): New solution + +- Add [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) +- **Some** *specs* added... +``` + +Example with breaking change: + +```text +refactor(core): Migrate on new solution + +BREAKING CHANGES: should upgrade version +Some specs... +``` + +## Available types + +| Type | Bump | Description | +| --- | --- | --- | +| **break** | `MAJOR` | Breaking changes | +| **feat** | `MINOR` | Features | +| **build** | `PATCH` | Build system or external dependencies | +| **chore** | `PATCH` | Chore | +| **ci** | `PATCH` | Continuous Integration | +| **docs** | `PATCH` | Documentation | +| **fix** | `PATCH` | Bug Fixes | +| **perf** | `PATCH` | Performance | +| **refactor** | `PATCH` | Refactoring | +| **revert** | `PATCH` | Revert code | +| **style** | `PATCH` | Styles and formatting | +| **test** | `PATCH` | Tests | + +For `MAJOR` can be used `!` (example `refactor!: new lib`). + +For `MAJOR` also can be used `BREAKING CHANGES:` or `BREAKING CHANGE:` in description of commit. + +### Ignored commits + +- Merge pull request +- Merge remote-tracking branch +- Automatic merge +- Auto-merged ... in ... +- Auto-merged ... into ... +- Merged ... in ... +- Merged ... into ... +- Merge branch +- Revert +- revert +- fixup +- squash + +## Config in package.json + +Instead of repeating CLI flags, configure defaults in `package.json`: + +```json +{ + "simple-release": { + "contents": "dist", + "cascade": "full" + } +} +``` + +CLI flags always override config values. + +## Exit codes + +| Code | Meaning | +| --- | --- | +| `0` | Success (or no changes found) | +| `1` | Error (git failure, script failure, publish failure) | + +`--mode has-changes` returns exit code `0` in both cases — use the stdout output (`true`/`false`) to determine result. + +## How it works + +```text +git log → parse conventional commits → calculate version bump + ↓ +preversion script → bump package.json + lockfiles → version script + ↓ +generate CHANGELOG.md + ↓ +git add → git commit → git tag → git push + ↓ +postversion script → github/gitlab release → npm publish +``` + +Single package: one commit + one tag + +```text +commit: chore(release): v1.2.3 [skip ci] +tag: v1.2.3 +``` + +Monorepo: one commit + tag per changed package + +```text +commit: chore(release): publish [skip ci] + - @org/core@1.2.0 + - @org/utils@3.1.0 +tags: @org/core@1.2.0 + @org/utils@3.1.0 +``` + +## Generated CHANGELOG.md + +```markdown +# Changelog + +All notable changes to this project will be documented in this file. See [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for commit guidelines. + +## [v1.2.0](https://github.com/user/repo/compare/v1.1.0...v1.2.0) (2026-04-10) + +### Features + +- **auth**: add OAuth2 support ([abc1234](https://github.com/user/repo/commit/abc1234...)) +- new dashboard page ([def5678](https://github.com/user/repo/commit/def5678...)) + +### Bug Fixes + +- **api**: fix rate limiting ([fed8765](https://github.com/user/repo/commit/fed8765...)) +``` + +## Environment variables + +### Github + +| Variable | Required | Description | +| --- | --- | --- | +| `GH_TOKEN` | For releases | Github token (PAT or `GITHUB_TOKEN`) | +| `GITHUB_SERVER_URL` | No | Default: `https://github.com` | +| `GITHUB_REPOSITORY` | No | Fallback repo (e.g. `user/repo`) | + +`GITHUB_TOKEN` (native) works if workflow has `permissions: contents: write`. +For protected branches with required reviews, `GITHUB_TOKEN` cannot push directly. Options: + +| Approach | Pros | Cons | +| --- | --- | --- | +| [GitHub App](#github-app-setup) (recommended) | Bypass branch protection, reusable across repos, no personal account | One-time setup | +| PAT (Fine-grained) | Simple setup | Tied to personal account, max 1 year expiry | +| `disable-push` + PR | Works with `GITHUB_TOKEN` | Requires manual merge of release PR | +| Deploy key with write access | No personal account needed | Cannot create GitHub Releases via API | + +### GitHub App setup + +One-time setup, works across all your repos: + +**1. Create App:** + +GitHub → Settings → Developer settings → [GitHub Apps → New](https://github.com/settings/apps/new) + +- App name: `simple-release-bot` +- Webhook: **uncheck** Active +- Permissions: + - Repository → **Contents: Read and write** (git push, tags, releases) + - Repository → **Metadata: Read-only** (required) + - Repository → **Packages: Read and write** (GitHub Packages publish) +- Where can this app be installed: **Only on this account** + +**2. Install App:** + +App page → Install App → select repos (all or specific) + +**3. Generate private key:** + +App page → General → Private keys → **Generate a private key** → save `.pem` file + +**4. Add secrets to each repo** (or use `gh` CLI for bulk): + +```bash +# Get App ID from app page → General → About +APP_ID=123456 + +# Add to all repos at once +for repo in my-repo-1 my-repo-2 my-repo-3; do + gh secret set APP_ID --repo yourname/$repo --body "$APP_ID" + gh secret set APP_PRIVATE_KEY --repo yourname/$repo < app-private-key.pem +done +``` + +**5. Allow bypass in branch protection:** + +Repo → Settings → Branches → Edit rule → Allow specified actors to bypass → add your App + +**6. Workflow** (copy to any repo): + +```yml +- name: Generate App Token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + +- name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} +``` + +Use `${{ steps.app-token.outputs.token }}` as `GH_TOKEN` for releases and GitHub Packages auth. + +See [full workflow example](#github-actions). + +### Gitlab + +| Variable | Required | Description | +| --- | --- | --- | +| `CI_JOB_TOKEN` | For releases | Gitlab job token | +| `CI_SERVER_HOST` | For releases | Gitlab domain | +| `CI_PROJECT_ID` | For releases | Gitlab project ID | +| `CI_JOB_ID` | Auto | Presence triggers Gitlab mode | +| `CI_SERVER_URL` | No | Gitlab server URL | +| `CI_PROJECT_NAMESPACE` | No | Fallback namespace | +| `CI_PROJECT_NAME` | No | Fallback project name | + +> Gitlab is auto-detected when `CI_JOB_ID` is set (present in all Gitlab CI pipelines). + +`CI_JOB_TOKEN` inherits permissions of the user who triggered the pipeline. For protected branches, use a [Group/Project Access Token](https://docs.gitlab.com/ee/user/group/settings/group_access_tokens.html) with Maintainer role (see [Gitlab CI example](#gitlab-ci)). + +## Edge cases + +**First release (no tags exist):** +Full git history from initial commit is used. All commits will appear in the first CHANGELOG entry. + +**No changes detected:** +Nothing happens — no version bump, no commit, no publish. Exit code 0. + +**Prerelease to release transition:** + +```text +1.0.0 → prerelease=beta → 1.0.1-beta.0 → prerelease=beta → 1.0.1-beta.1 → (release) → 1.0.1 +``` + +**Multiple breaking changes:** +Only one major bump per release, regardless of how many breaking commits. + +**Unknown commit types:** +Commits that don't match any known type (e.g. `update: something`) are grouped under "Others changes" with a patch bump. + +**Empty commit body:** +Supported — only the title line is required. + +**`[skip ci]` in release commit:** +Added automatically to prevent CI loops: `chore(release): v1.2.3 [skip ci]` + +**`package-lock.json` missing:** +Silently skipped — works fine without lockfiles. + +**Monorepo package without changes:** +Skipped entirely — no bump, no tag, no publish for unchanged packages. + +**Lifecycle script fails:** +Process stops immediately. No git commit, no publish. Fix the issue and run again. + +**Git push fails:** +Tags and commits are created locally but not pushed. Run `git push && git push --tags` manually after fixing. + +## Requirements + +- Node.js >= 10.12 (or Bun) +- Git +- Works on Linux, macOS, Windows + +## Why simple-release? + +| | simple-release | lerna (9+) | changesets | semantic-release | +| --- | --- | --- | --- | --- | +| Dependencies | 0 | 50+ (requires Nx) | 20+ | 30+ | +| Setup | 1 line | lerna.json + nx.json | Config files | Plugins | +| Monorepo | Built-in | Built-in | Built-in | [Plugin](https://github.com/pmowrer/semantic-release-monorepo) | +| Independent versioning | Built-in | Built-in | Built-in | Plugin | +| Topological sort | Built-in | Built-in | [No](https://github.com/changesets/changesets/issues/238) | No | +| Cross-package cascade | full / lazy / none | Full only | Full only | No | +| Conventional commits | Built-in | Built-in | Manual | Plugin | +| Commit lint | Built-in | commitlint | commitlint | commitlint | +| Changelog | Built-in | Built-in | Built-in | Plugin | +| Github + Gitlab | Built-in | Github only | Github only | Plugin | +| npm / bun / pnpm / yarn | All built-in | npm | npm / pnpm / yarn | npm | +| Lifecycle scripts | Built-in (PM-free) | Via npm | No | No | +| Prerelease / canary | Built-in | Built-in | Built-in | Plugin | +| Provenance / OIDC | Built-in | v9+ | Via npm | Plugin | +| Multiple registries | Built-in | One at a time | One at a time | One at a time | +| Publish from subdirectory | `--contents` | `contents` | No | No | +| Node.js | >= 10.12 | >= 20.19 | >= 18 | >= 18 | + +## Migration from Lerna + +### 1. Add workspaces to root package.json + +```diff + { + "name": "@org/monorepo", ++ "workspaces": ["packages/*"], + "devDependencies": { +- "lerna": "^6.0.0", ++ "@askuzminov/simple-release": "^1" + }, + "scripts": { +- "lerna:publish": "lerna publish --create-release github", +- "lerna:canary": "lerna publish --canary --amend --allow-branch feature/*" ++ "release": "simple-release --contents dist publish-github", ++ "release:canary": "simple-release --contents dist publish-github prerelease=$BRANCH_NAME.$BUILD_ID" + } + } +``` + +### 2. Replace commitlint with simple-release-lint + +`.husky/commit-msg`: + +```diff +-npx commitlint --edit $1 ++simple-release-lint $1 +``` + +Remove `commitlint` and `@commitlint/*` from devDependencies. + +### 3. Delete lerna config + +```bash +rm lerna.json +``` + +### 4. Mapping lerna options + +| lerna.json | simple-release | Notes | +| --- | --- | --- | +| `"version": "independent"` | Default | Always independent | +| `"conventionalCommits": true` | Default | Always conventional commits | +| `"contents": "dist"` | `--contents dist` | Publish from subdirectory | +| `"registry": "https://npm.pkg.github.com"` | `publish-github` | Or `--publish-custom URL` | +| `"createRelease": "github"` | Default | Always creates release when `GH_TOKEN` set | +| `"message": "chore(release): publish [skip ci]"` | Default | Same format built-in | +| `"ignoreChanges": ["**/*.spec.ts"]` | `--file ':!**/*.spec.ts'` | Git pathspec | +| `"allowBranch": ["master"]` | CI-level | Run release only on master in CI | +| `lerna publish --canary --preid=ID` | `prerelease=ID` | Canary releases | +| `lerna run build` | `npm run build -ws` or `"version": "npm run build"` | Via npm workspaces or lifecycle | + +### 5. Update CI + +```diff + # Production release +-npm run lerna:publish ++npm run release + + # Canary release +-npm run lerna:canary ++npm run release:canary +``` + +### 6. Cross-package dependencies + +Lerna updates cross-package deps automatically. simple-release does the same: + +- `--cascade full` (default) — always bump + publish dependents (like lerna) +- `--cascade lazy` — bump only when range doesn't cover new version +- `--cascade none` — manual management + +### 7. Packages not tracked by lerna + +If some packages were excluded from `lerna.json`, use `--workspace` to select specific packages: + +```bash +# Release only specific packages +simple-release --workspace @org/core --workspace @org/utils --contents dist publish-github +``` + +## Example CI + +### Github Actions + +Full example with GitHub App + OIDC (recommended): + +```yml +name: Release + +on: + push: + branches: [master, feature/*] + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +jobs: + release: + name: Release + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, 'skip ci')" + permissions: + contents: write + packages: write + id-token: write + steps: + - name: Generate App Token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Setup NPM + run: echo '//npm.pkg.github.com/:_authToken=${{ steps.app-token.outputs.token }}' >> ~/.npmrc + - name: Setup GIT + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + mkdir -p ~/.ssh + ssh-keyscan github.com > ~/.ssh/known_hosts + - name: Install dependencies + run: npm ci + - name: Check + run: | + npm run check-types + npm run lint + - name: Test + run: npm test + - name: Build + run: npm run build + - name: Release + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + BRANCH_NAME: # set via your branch detection step + BUILD_ID: # set via your build ID step + run: | + if [[ $BRANCH_NAME == 'master' ]]; then + npm run release -- publish-github publish-npmjs provenance + else + npm run release -- publish-github prerelease=$BRANCH_NAME.$BUILD_ID + fi +``` + +Using the action: + +```yml +- uses: actions/checkout@v4 + with: { fetch-depth: 0 } + +- uses: askuzminov/simple-release@v1 + with: + args: 'publish-npmjs publish-github' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` + +Minimal example with npx (no protected branch push): + +```yml +- uses: actions/checkout@v4 + with: { fetch-depth: 0 } + +- uses: actions/setup-node@v4 + with: { node-version: 22 } + +- run: npx @askuzminov/simple-release publish-npmjs provenance + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` + +### Setup GIT + +```bash +git config --global push.default current +git config --local user.email "bot@ci.com" +git config --local user.name "CI" +mkdir -p ~/.ssh +ssh-keyscan github.com > ~/.ssh/known_hosts +``` + +### Gitlab CI + +Add `.npmrc` to `.gitignore`. + +Setup `.gitlab-ci.yml`: + +```yml +image: node:22 + +stages: + - publish + +deploy: + stage: publish + only: + - branches + variables: + # Setup group or project access token with role "Maintainer" + # https://docs.gitlab.com/ee/user/group/settings/group_access_tokens.html + CI_JOB_TOKEN: $CI_TOKEN + # Setup strategy for clean history + GIT_STRATEGY: clone + # Setup depth for full history + GIT_DEPTH: 0 + script: + - | + # Setup GIT + git config --local user.email "ci@example.com" + git config --local user.name "ci" + # Allow ci to push branch + git remote set-url origin "https://ci:$CI_JOB_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME.git" + # Avoid problem with detached branch + git checkout "$CI_COMMIT_REF_NAME" + - | + # Setup .npmrc + echo "//$CI_SERVER_HOST/api/v4/projects/$CI_PROJECT_ID/packages/npm/:_authToken=$CI_JOB_TOKEN" > .npmrc + echo "@$CI_PROJECT_NAMESPACE:registry=https://$CI_SERVER_HOST/api/v4/projects/$CI_PROJECT_ID/packages/npm/" >> .npmrc + - | + # Prepare your package + npm ci + npm run lint + npm test + - | + if [[ $CI_COMMIT_REF_NAME == 'master' ]]; then + npm run release -- --publish-custom "https://${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/" + else + npm run release -- --publish-custom "https://${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/" prerelease=$CI_COMMIT_REF_SLUG.$CI_JOB_ID + fi +``` diff --git a/llms.txt b/llms.txt new file mode 100644 index 0000000..a6e0a9e --- /dev/null +++ b/llms.txt @@ -0,0 +1,119 @@ +# simple-release + +> Zero-dependency CLI for automated npm package releases with conventional commits, monorepo/workspace support, and multi-registry publishing. + +## Install + +```bash +npm i @askuzminov/simple-release +``` + +## Quick start + +```json +{ + "scripts": { + "release": "simple-release publish-npmjs" + } +} +``` + +## Commands + +### Release control +- `help` — command list +- `prerelease` — only bump version +- `prerelease=ID` — bump with custom prerelease ID (e.g. `prerelease=beta`) +- `enable-prerelease` — force full process for prerelease +- `disable-push` — no git push +- `disable-git` — no git commit/tag +- `disable-md` — no CHANGELOG.md +- `disable-github` — no GitHub/GitLab release +- `dry-run` — preview without changes +- `verbose` — detailed output +- `provenance` — npm provenance attestation + +### Publish +- `publish-github` — GitHub Packages registry +- `publish-npmjs` — npmjs.org registry +- `--publish-custom URL` — custom registry + +### Mode +- `--mode publish` (default) +- `--mode current-version` — stdout current version +- `--mode next-version` — stdout next version +- `--mode has-changes` — stdout true/false + +### Options +- `--match PATTERN` — filter git tags (glob) +- `--file PATH` — filter files (git pathspec, repeatable) +- `--version FORMAT` — version format, default `v{VERSION}` +- `--source-repo URL` — custom source repo for links +- `--release-repo URL` — custom release repo for links + +### Workspace (monorepo) +- `no-workspace` — force single-package mode +- `--workspace NAME` — select specific packages (repeatable) +- `--contents DIR` — publish from subdirectory +- `--cascade MODE` — cross-package deps: `full` (default), `lazy`, `none` + +## Commit format + +``` +(scope)!: description +``` + +Types: `break` (MAJOR), `feat` (MINOR), `fix` `build` `chore` `ci` `docs` `perf` `refactor` `revert` `style` `test` (PATCH) + +Breaking change: `!` after type/scope, or `BREAKING CHANGES:` in body. + +## Workspace example + +```json +{ + "workspaces": ["packages/*"], + "scripts": { + "release": "simple-release --contents dist --cascade full publish-npmjs" + } +} +``` + +Auto-detects: `package.json` workspaces (npm/yarn/bun), `pnpm-workspace.yaml` (pnpm). + +## Cascade modes + +- `full` — always bump + publish dependents +- `lazy` — bump only when range doesn't cover new version (e.g. `^1.0.0` covers `1.1.0` → skip) +- `none` — only packages with actual code changes + +## Environment variables + +- `GH_TOKEN` — GitHub token for releases and packages +- `CI_JOB_TOKEN` — GitLab token (auto-detected via `CI_JOB_ID`) + +## Lifecycle scripts + +Order: `preversion` → write version → `version` → git commit/tag/push → `postversion` + +## Config in package.json + +```json +{ + "simple-release": { + "contents": "dist", + "cascade": "full" + } +} +``` + +## Lint + +```bash +# .husky/commit-msg +simple-release-lint $1 +``` + +## Links + +- [Full documentation](https://github.com/askuzminov/simple-release) +- [npm](https://www.npmjs.com/package/@askuzminov/simple-release) diff --git a/package-lock.json b/package-lock.json index 506b1ae..3180360 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,4179 +13,2452 @@ "simple-release-lint": "dist/cjs/lint.js" }, "devDependencies": { - "@types/node": "^18.16.3", - "@typescript-eslint/eslint-plugin": "^5.59.1", - "@typescript-eslint/parser": "^5.59.1", - "eslint": "^8.39.0", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-prefer-arrow": "^1.2.3", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-unicorn": "^46.0.0", - "husky": "^8.0.3", - "lint-staged": "^13.2.2", - "prettier": "~2.8.8", - "ts-node": "^10.9.1", - "typescript": "~5.0.4" + "@types/node": "18.19.130", + "@types/semver": "7.7.1", + "husky": "9.1.7", + "lint-staged": "16.4.0", + "oxc-parser": "0.124.0", + "oxfmt": "0.44.0", + "oxlint": "1.59.0", + "oxlint-tsgolint": "0.20.0", + "prettier": "3.8.2", + "semver": "7.7.4", + "tsx": "4.21.0", + "typescript": "6.0.2" }, "engines": { "node": ">=10" } }, - "node_modules/@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", "dev": true, + "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "@babel/highlight": "^7.10.4" + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", "dev": true, - "engines": { - "node": ">=6.9.0" + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, + "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "tslib": "^2.4.0" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=4" + "node": ">=18" } }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=4" + "node": ">=18" } }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "color-name": "1.1.3" + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=4" + "node": ">=18" } }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=4" + "node": ">=18" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "node": ">=18" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz", - "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", - "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.5.1", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=18" } }, - "node_modules/@eslint/js": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", - "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=18" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10.10.0" + "node": ">=18" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=18" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 8" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true - }, - "node_modules/@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, - "node_modules/@types/node": { - "version": "18.16.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", - "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==", - "dev": true - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", - "dev": true + "node": ">=18" + } }, - "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.1.tgz", - "integrity": "sha512-AVi0uazY5quFB9hlp2Xv+ogpfpk77xzsgsIEWyVS7uK/c7MZ5tw7ZPbapa0SbfkqE0fsAMkz5UwtgMLVk2BQAg==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.1", - "@typescript-eslint/type-utils": "5.59.1", - "@typescript-eslint/utils": "5.59.1", - "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/@typescript-eslint/parser": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.1.tgz", - "integrity": "sha512-nzjFAN8WEu6yPRDizIFyzAfgK7nybPodMNFGNH0M9tei2gYnYszRDqVA0xlnRjkl7Hkx2vYrEdb6fP2a21cG1g==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.59.1", - "@typescript-eslint/types": "5.59.1", - "@typescript-eslint/typescript-estree": "5.59.1", - "debug": "^4.3.4" - }, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.1.tgz", - "integrity": "sha512-mau0waO5frJctPuAzcxiNWqJR5Z8V0190FTSqRw1Q4Euop6+zTwHAf8YIXNwDOT29tyUDrQ65jSg9aTU/H0omA==", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.59.1", - "@typescript-eslint/visitor-keys": "5.59.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=18" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.1.tgz", - "integrity": "sha512-ZMWQ+Oh82jWqWzvM3xU+9y5U7MEMVv6GLioM3R5NJk6uvP47kZ7YvlgSHJ7ERD6bOY7Q4uxWm25c76HKEwIjZw==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.1", - "@typescript-eslint/utils": "5.59.1", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@typescript-eslint/types": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.1.tgz", - "integrity": "sha512-dg0ICB+RZwHlysIy/Dh1SP+gnXNzwd/KS0JprD3Lmgmdq+dJAJnUPe1gNG34p0U19HvRlGX733d/KqscrGC1Pg==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=18" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.1.tgz", - "integrity": "sha512-lYLBBOCsFltFy7XVqzX0Ju+Lh3WPIAWxYpmH/Q7ZoqzbscLiCW00LeYCdsUnnfnj29/s1WovXKh2gwCoinHNGA==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.59.1", - "@typescript-eslint/visitor-keys": "5.59.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/@typescript-eslint/utils": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.1.tgz", - "integrity": "sha512-MkTe7FE+K1/GxZkP5gRj3rCztg45bEhsd8HYjczBuYm+qFHP5vtZmjx3B0yUCDotceQ4sHgTyz60Ycl225njmA==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", + "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.1", - "@typescript-eslint/types": "5.59.1", - "@typescript-eslint/typescript-estree": "5.59.1", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "@tybys/wasm-util": "^0.10.1" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.124.0.tgz", + "integrity": "sha512-+R9zCafSL8ovjokdPtorUp3sXrh8zQ2AC2L0ivXNvlLR0WS+5WdPkNVrnENq5UvzagM4Xgl0NPsJKz3Hv9+y8g==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.1.tgz", - "integrity": "sha512-6waEYwBTCWryx0VJmP7JaM4FpipLsFl9CvYf2foAE8Qh/Y0s+bxWysciwOs0LTBED4JCaNxTZ5rGadB14M6dwA==", + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.124.0.tgz", + "integrity": "sha512-ULHC/gVZ+nP4pd3kNNQTYaQ/e066BW/KuY5qUsvwkVWwOUQGDg+WpfyVOmQ4xfxoue6cMlkKkJ+ntdzfDXpNlg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.59.1", - "eslint-visitor-keys": "^3.3.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.124.0.tgz", + "integrity": "sha512-fGJ2hw7bnbUYn6UvTjp0m4WJ9zXz3cohgcwcgeo7gUZehpPNpvcVEVeIVHNmHnAuAw/ysf4YJR8DA1E+xCA4Lw==", + "cpu": [ + "arm64" + ], "dev": true, - "bin": { - "acorn": "bin/acorn" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.4.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.124.0.tgz", + "integrity": "sha512-j0+re9pgps5BH2Tk3fm59Hi3QuLP3C4KhqXi6A+wRHHHJWDFR8mc/KI9mBrfk2JRT+15doGo+zv1eN75/9DuOw==", + "cpu": [ + "x64" + ], "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.124.0.tgz", + "integrity": "sha512-0k5mS0npnrhKy72UfF51lpOZ2ESoPWn6gdFw+RdeRWcokraDW1O2kSx3laQ+yk7cCEavQdJSpWCYS/GvBbUCXQ==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=0.4.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.124.0.tgz", + "integrity": "sha512-P/i4eguRWvAUfGdfhQYg1jpwYkyUV6D3gefIH7HhmRl1Ph6P4IqTIEVcyJr1i/3vr1V5OHU4wonH6/ue/Qzvrw==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.124.0.tgz", + "integrity": "sha512-/ameqFQH5fFP+66Atr8Ynv/2rYe4utcU7L4MoWS5JtrFLVO78g4qDLavyIlJxa6caSwYOvG/eO3c/DXqY5/6Rw==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.124.0.tgz", + "integrity": "sha512-gNeyEcXTtfrRCbj2EfxWU85Fs0wIX3p44Y3twnvuMfkWlLrb9M1Z25AYNSKjJM+fdAjeeQCjw0on47zFuBYwQw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.124.0.tgz", + "integrity": "sha512-uvG7v4Tz9S8/PVqY0SP0DLHxo4hZGe+Pv2tGVnwcsjKCCUPjplbrFVvDzXq+kOaEoUkiCY0Kt1hlZ6FDJ1LKNQ==", + "cpu": [ + "arm64" + ], "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.124.0.tgz", + "integrity": "sha512-t7KZaaUhfp2au0MRpoENEFqwLKYDdptEry6V7pTAVdPEcFG4P6ii8yeGU9m6p5vb+b8WEKmdpGMNXBEYy7iJdw==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/clean-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz", - "integrity": "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", - "dev": true, - "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "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 - }, - "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", - "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", - "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.39.0", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", - "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", - "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", - "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", - "dev": true, - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-prefer-arrow": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prefer-arrow/-/eslint-plugin-prefer-arrow-1.2.3.tgz", - "integrity": "sha512-J9I5PKCOJretVuiZRGvPQxCbllxGAV/viI20JO3LYblAodofBxyMnZAJ+WGeClHgANnSJberTNoFWWjrWKBuXQ==", - "dev": true, - "peerDependencies": { - "eslint": ">=2.0.0" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", - "dev": true, - "dependencies": { - "prettier-linter-helpers": "^1.0.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "eslint": ">=7.28.0", - "prettier": ">=2.0.0" - }, - "peerDependenciesMeta": { - "eslint-config-prettier": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-unicorn": { - "version": "46.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-46.0.0.tgz", - "integrity": "sha512-j07WkC+PFZwk8J33LYp6JMoHa1lXc1u6R45pbSAipjpfpb7KIGr17VE2D685zCxR5VL4cjrl65kTJflziQWMDA==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.19.1", - "@eslint-community/eslint-utils": "^4.1.2", - "ci-info": "^3.6.1", - "clean-regexp": "^1.0.0", - "esquery": "^1.4.0", - "indent-string": "^4.0.0", - "is-builtin-module": "^3.2.0", - "jsesc": "^3.0.2", - "lodash": "^4.17.21", - "pluralize": "^8.0.0", - "read-pkg-up": "^7.0.1", - "regexp-tree": "^0.1.24", - "regjsparser": "^0.9.1", - "safe-regex": "^2.1.1", - "semver": "^7.3.8", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=14.18" - }, - "funding": { - "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1" - }, - "peerDependencies": { - "eslint": ">=8.28.0" - } - }, - "node_modules/eslint-plugin-unicorn/node_modules/ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint-plugin-unicorn/node_modules/safe-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", - "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", - "dev": true, - "dependencies": { - "regexp-tree": "~0.1.1" - } - }, - "node_modules/eslint-plugin-unicorn/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", - "dev": true, - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", - "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globals/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", - "dev": true, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/husky": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", - "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", - "dev": true, - "bin": { - "husky": "lib/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-builtin-module/node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", - "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "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, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "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 - }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, - "node_modules/lint-staged": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.2.2.tgz", - "integrity": "sha512-71gSwXKy649VrSU09s10uAT0rWCcY3aewhMaHyl2N84oBk4Xs9HgxvUp3AYu+bNsK4NrOYYxvSgg7FyGJ+jGcA==", - "dev": true, - "dependencies": { - "chalk": "5.2.0", - "cli-truncate": "^3.1.0", - "commander": "^10.0.0", - "debug": "^4.3.4", - "execa": "^7.0.0", - "lilconfig": "2.1.0", - "listr2": "^5.0.7", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-inspect": "^1.12.3", - "pidtree": "^0.6.0", - "string-argv": "^0.3.1", - "yaml": "^2.2.2" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" - }, - "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/lint-staged" - } - }, - "node_modules/lint-staged/node_modules/chalk": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", - "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/yaml": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", - "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/listr2": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.8.tgz", - "integrity": "sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==", - "dev": true, - "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.19", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.8.0", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } - } - }, - "node_modules/listr2/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/listr2/node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/listr2/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/listr2/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/listr2/node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/listr2/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/listr2/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-update/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-update/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.124.0.tgz", + "integrity": "sha512-eurGGaxHZiIQ+fBSageS8TAkRqZgdOiBeqNrWAqAPup9hXBTmQ0WcBjwsLElf+3jvDL9NhnX0dOgOqPfsjSjdg==", + "cpu": [ + "riscv64" + ], "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.124.0.tgz", + "integrity": "sha512-d1V7/ll1i/LhqE/gZy6Wbz6evlk0egh2XKkwMI3epiojtbtUwQSLIER0Y3yDBBocPuWOjJdvmjtEmPTTLXje/w==", + "cpu": [ + "riscv64" + ], "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.124.0.tgz", + "integrity": "sha512-w1+cBvriUteOpox6ATqCFVkpGL47PFdcfCPGmgUZbd78Fw44U0gQkc+kVGvAOTvGrptMYgwomD1c6OTVvkrpGg==", + "cpu": [ + "s390x" + ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.124.0.tgz", + "integrity": "sha512-RRB1evQiXRtMCsQQiAh9U0H3HzguLpE0ytfStuhRgmOj7tqUCOVxkHsvM9geZjAax6NqVRj7VXx32qjjkZPsBw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8.6" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.124.0.tgz", + "integrity": "sha512-asVYN0qmSHlCU8H9Q47SmeJ/Z5EG4IWCC+QGxkfFboI5qh15aLlJnHmnrV61MwQRPXGnVC/sC3qKhrUyqGxUqw==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.124.0.tgz", + "integrity": "sha512-nhwuxm6B8pn9lzAzMUfa571L5hCXYwQo8C8cx5aGOuHWCzruR8gPJnRRXGBci+uGaIIQEZDyU/U6HDgrSp/JlQ==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=4" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.124.0.tgz", + "integrity": "sha512-LWuq4Dl9tff7n+HjJcqoBjDlVCtruc0shgtdtGM+rTUIE9aFxHA/P+wCYR+aWMjN8m9vNaRME/sKXErmhmeKrA==", + "cpu": [ + "wasm32" + ], "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "brace-expansion": "^1.1.7" + "@napi-rs/wasm-runtime": "^1.1.2" }, "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "node": ">=14.0.0" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.124.0.tgz", + "integrity": "sha512-aOh3Lf3AeH0dgzT4yBXcArFZ8VhqNXwZ/xlN0GqBtgVaGoHOOqL2YHlcVIgT+ghsXPVR2PTtYgBiQ1CNK7jp5A==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=0.10.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.124.0.tgz", + "integrity": "sha512-sib5xC0nz/+SCpaETBuHBz4SXS02KuG5HtyOcHsO/SK5ZvLRGhOZx0elDKawjb6adFkD7dQCqpXUS25wY6ELKQ==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.124.0.tgz", + "integrity": "sha512-UgojtjGUgZgAZQYt7SC6VO65OVdxEkRe2q+2vbHJO//18qw3Hrk6UvHGQKldsQKgbVcIBT/YBrt85YberiYIPQ==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "node_modules/@oxc-project/types": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", + "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", "dev": true, + "license": "MIT", "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "node_modules/@oxfmt/binding-android-arm-eabi": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.44.0.tgz", + "integrity": "sha512-5UvghMd9SA/yvKTWCAxMAPXS1d2i054UeOf4iFjZjfayTwCINcC3oaSXjtbZfCaEpxgJod7XiOjTtby5yEv/BQ==", + "cpu": [ + "arm" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 0.4" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "node_modules/@oxfmt/binding-android-arm64": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm64/-/binding-android-arm64-0.44.0.tgz", + "integrity": "sha512-IVudM1BWfvrYO++Khtzr8q9n5Rxu7msUvoFMqzGJVdX7HfUXUDHwaH2zHZNB58svx2J56pmCUzophyaPFkcG/A==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "node_modules/@oxfmt/binding-darwin-arm64": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-arm64/-/binding-darwin-arm64-0.44.0.tgz", + "integrity": "sha512-eWCLAIKAHfx88EqEP1Ga2yz7qVcqDU5lemn4xck+07bH182hDdprOHjbogyk0In1Djys3T0/pO2JepFnRJ41Mg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "node_modules/@oxfmt/binding-darwin-x64": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-x64/-/binding-darwin-x64-0.44.0.tgz", + "integrity": "sha512-eHTBznHLM49++dwz07MblQ2cOXyIgeedmE3Wgy4ptUESj38/qYZyRi1MPwC9olQJWssMeY6WI3UZ7YmU5ggvyQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/onetime/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "node_modules/@oxfmt/binding-freebsd-x64": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-freebsd-x64/-/binding-freebsd-x64-0.44.0.tgz", + "integrity": "sha512-jLMmbj0u0Ft43QpkUVr/0v1ZfQCGWAvU+WznEHcN3wZC/q6ox7XeSJtk9P36CCpiDSUf3sGnzbIuG1KdEMEDJQ==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "node_modules/@oxfmt/binding-linux-arm-gnueabihf": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.44.0.tgz", + "integrity": "sha512-n+A/u/ByK1qV8FVGOwyaSpw5NPNl0qlZfgTBqHeGIqr8Qzq1tyWZ4lAaxPoe5mZqE3w88vn3+jZtMxriHPE7tg==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.8.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/@oxfmt/binding-linux-arm-musleabihf": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.44.0.tgz", + "integrity": "sha512-5eax+FkxyCqAi3Rw0mrZFr7+KTt/XweFsbALR+B5ljWBLBl8nHe4ADrUnb1gLEfQCJLl+Ca5FIVD4xEt95AwIw==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/@oxfmt/binding-linux-arm64-gnu": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.44.0.tgz", + "integrity": "sha512-58l8JaHxSGOmOMOG2CIrNsnkRJAj0YcHQCmvNACniOa/vd1iRHhlPajczegzS5jwMENlqgreyiTR9iNlke8qCw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "node_modules/@oxfmt/binding-linux-arm64-musl": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.44.0.tgz", + "integrity": "sha512-AlObQIXyVRZ96LbtVljtFq0JqH5B92NU+BQeDFrXWBUWlCKAM0wF5GLfIhCLT5kQ3Sl+U0YjRJ7Alqj5hGQaCg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "node_modules/@oxfmt/binding-linux-ppc64-gnu": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.44.0.tgz", + "integrity": "sha512-YcFE8/q/BbrCiIiM5piwbkA6GwJc5QqhMQp2yDrqQ2fuVkZ7CInb1aIijZ/k8EXc72qXMSwKpVlBv1w/MsGO/A==", + "cpu": [ + "ppc64" + ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "node_modules/@oxfmt/binding-linux-riscv64-gnu": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.44.0.tgz", + "integrity": "sha512-eOdzs6RqkRzuqNHUX5C8ISN5xfGh4xDww8OEd9YAmc3OWN8oAe5bmlIqQ+rrHLpv58/0BuU48bxkhnIGjA/ATQ==", + "cpu": [ + "riscv64" + ], "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "node_modules/@oxfmt/binding-linux-riscv64-musl": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.44.0.tgz", + "integrity": "sha512-YBgNTxntD/QvlFUfgvh8bEdwOhXiquX8gaofZJAwYa/Xp1S1DQrFVZEeck7GFktr24DztsSp8N8WtWCBwxs0Hw==", + "cpu": [ + "riscv64" + ], "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/@oxfmt/binding-linux-s390x-gnu": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.44.0.tgz", + "integrity": "sha512-GLIh1R6WHWshl/i4QQDNgj0WtT25aRO4HNUWEoitxiywyRdhTFmFEYT2rXlcl9U6/26vhmOqG5cRlMLG3ocaIA==", + "cpu": [ + "s390x" + ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "node_modules/@oxfmt/binding-linux-x64-gnu": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.44.0.tgz", + "integrity": "sha512-gZOpgTlOsLcLfAF9qgpTr7FIIFSKnQN3hDf/0JvQ4CIwMY7h+eilNjxq/CorqvYcEOu+LRt1W4ZS7KccEHLOdA==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/@oxfmt/binding-linux-x64-musl": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-musl/-/binding-linux-x64-musl-0.44.0.tgz", + "integrity": "sha512-1CyS9JTB+pCUFYFI6pkQGGZaT/AY5gnhHVrQQLhFba6idP9AzVYm1xbdWfywoldTYvjxQJV6x4SuduCIfP3W+A==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "node_modules/@oxfmt/binding-openharmony-arm64": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-openharmony-arm64/-/binding-openharmony-arm64-0.44.0.tgz", + "integrity": "sha512-bmEv70Ak6jLr1xotCbF5TxIKjsmQaiX+jFRtnGtfA03tJPf6VG3cKh96S21boAt3JZc+Vjx8PYcDuLj39vM2Pw==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/@oxfmt/binding-win32-arm64-msvc": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.44.0.tgz", + "integrity": "sha512-yWzB+oCpSnP/dmw85eFLAT5o35Ve5pkGS2uF/UCISpIwDqf1xa7OpmtomiqY/Vzg8VyvMbuf6vroF2khF/+1Vg==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "node_modules/@oxfmt/binding-win32-ia32-msvc": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.44.0.tgz", + "integrity": "sha512-TcWpo18xEIE3AmIG2kpr3kz5IEhQgnx0lazl2+8L+3eTopOAUevQcmlr4nhguImNWz0OMeOZrYZOhJNCf16nlQ==", + "cpu": [ + "ia32" + ], "dev": true, - "bin": { - "pidtree": "bin/pidtree.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=0.10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "node_modules/@oxfmt/binding-win32-x64-msvc": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.44.0.tgz", + "integrity": "sha512-oj8aLkPJZppIM4CMQNsyir9ybM1Xw/CfGPTSsTnzpVGyljgfbdP0EVUlURiGM0BDrmw5psQ6ArmGCcUY/yABaQ==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=4" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "node_modules/@oxlint-tsgolint/darwin-arm64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-arm64/-/darwin-arm64-0.20.0.tgz", + "integrity": "sha512-KKQcIHZHMxqpHUA1VXIbOG6chNCFkUWbQy6M+AFVtPKkA/3xAeJkJ3njoV66bfzwPHRcWQO+kcj5XqtbkjakoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint-tsgolint/darwin-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-x64/-/darwin-x64-0.20.0.tgz", + "integrity": "sha512-7HeVMuclGfG+NLZi2ybY0T4fMI7/XxO/208rJk+zEIloKkVnlh11Wd241JMGwgNFXn+MLJbOqOfojDb2Dt4L1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint-tsgolint/linux-arm64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-arm64/-/linux-arm64-0.20.0.tgz", + "integrity": "sha512-zxhUwz+WSxE6oWlZLK2z2ps9yC6ebmgoYmjAl0Oa48+GqkZ56NVgo+wb8DURNv6xrggzHStQxqQxe3mK51HZag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint-tsgolint/linux-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-x64/-/linux-x64-0.20.0.tgz", + "integrity": "sha512-/1l6FnahC9im8PK+Ekkx/V3yetO/PzZnJegE2FXcv/iXEhbeVxP/ouiTYcUQu9shT1FWJCSNti1VJHH+21Y1dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint-tsgolint/win32-arm64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-arm64/-/win32-arm64-0.20.0.tgz", + "integrity": "sha512-oPZ5Yz8sVdo7P/5q+i3IKeix31eFZ55JAPa1+RGPoe9PoaYVsdMvR6Jvib6YtrqoJnFPlg3fjEjlEPL8VBKYJA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxlint-tsgolint/win32-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-x64/-/win32-x64-0.20.0.tgz", + "integrity": "sha512-4stx8RHj3SP9vQyRF/yZbz5igtPvYMEUR8CUoha4BVNZihi39DpCR8qkU7lpjB5Ga1DRMo2pHaA4bdTOMaY4mw==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.59.0.tgz", + "integrity": "sha512-etYDw/UaEv936AQUd/CRMBVd+e+XuuU6wC+VzOv1STvsTyZenLChepLWqLtnyTTp4YMlM22ypzogDDwqYxv5cg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 0.8.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.59.0.tgz", + "integrity": "sha512-TgLc7XVLKH2a4h8j3vn1MDjfK33i9MY60f/bKhRGWyVzbk5LCZ4X01VZG7iHrMmi5vYbAp8//Ponigx03CLsdw==", + "cpu": [ + "arm64" + ], "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.59.0.tgz", + "integrity": "sha512-DXyFPf5ZKldMLloRHx/B9fsxsiTQomaw7cmEW3YIJko2HgCh+GUhp9gGYwHrqlLJPsEe3dYj9JebjX92D3j3AA==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "fast-diff": "^1.1.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.59.0.tgz", + "integrity": "sha512-LgvrsdgVLX1qWqIEmNsSmMXJhpAWdtUQ0M+oR0CySwi+9IHWyOGuIL8w8+u/kbZNMyZr4WUyYB5i0+D+AKgkLg==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.59.0.tgz", + "integrity": "sha512-bOJhqX/ny4hrFuTPlyk8foSRx/vLRpxJh0jOOKN2NWW6FScXHPAA5rQbrwdQPcgGB5V8Ua51RS03fke8ssBcug==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.59.0.tgz", + "integrity": "sha512-vVUXxYMF9trXCsz4m9H6U0IjehosVHxBzVgJUxly1uz4W1PdDyicaBnpC0KRXsHYretLVe+uS9pJy8iM57Kujw==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.59.0.tgz", + "integrity": "sha512-TULQW8YBPGRWg5yZpFPL54HLOnJ3/HiX6VenDPi6YfxB/jlItwSMFh3/hCeSNbh+DAMaE1Py0j5MOaivHkI/9Q==", + "cpu": [ + "arm" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.59.0.tgz", + "integrity": "sha512-Gt54Y4eqSgYJ90xipm24xeyaPV854706o/kiT8oZvUt3VDY7qqxdqyGqchMaujd87ib+/MXvnl9WkK8Cc1BExg==", + "cpu": [ + "arm64" + ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/regexp-tree": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.25.tgz", - "integrity": "sha512-szcL3aqw+vEeuxhL1AMYRyeMP+goYF5I/guaH10uJX5xbGyeQeNPPneaj3ZWVmGLCDxrVaaYekkr5R12gk4dJw==", + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.59.0.tgz", + "integrity": "sha512-3CtsKp7NFB3OfqQzbuAecrY7GIZeiv7AD+xutU4tefVQzlfmTI7/ygWLrvkzsDEjTlMq41rYHxgsn6Yh8tybmA==", + "cpu": [ + "arm64" + ], "dev": true, - "bin": { - "regexp-tree": "bin/regexp-tree" + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.59.0.tgz", + "integrity": "sha512-K0diOpT3ncDmOfl9I1HuvpEsAuTxkts0VYwIv/w6Xiy9CdwyPBVX88Ga9l8VlGgMrwBMnSY4xIvVlVY/fkQk7Q==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" - }, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.59.0.tgz", + "integrity": "sha512-xAU7+QDU6kTJJ7mJLOGgo7oOjtAtkKyFZ0Yjdb5cEo3DiCCPFLvyr08rWiQh6evZ7RiUTf+o65NY/bqttzJiQQ==", + "cpu": [ + "riscv64" + ], "dev": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.59.0.tgz", + "integrity": "sha512-KUmZmKlTTyauOnvUNVxK7G40sSSx0+w5l1UhaGsC6KPpOYHenx2oqJTnabmpLJicok7IC+3Y6fXAUOMyexaeJQ==", + "cpu": [ + "riscv64" + ], "dev": true, - "bin": { - "jsesc": "bin/jsesc" + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.59.0.tgz", + "integrity": "sha512-4usRxC8gS0PGdkHnRmwJt/4zrQNZyk6vL0trCxwZSsAKM+OxhB8nKiR+mhjdBbl8lbMh2gc3bZpNN/ik8c4c2A==", + "cpu": [ + "s390x" + ], "dev": true, - "dependencies": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.59.0.tgz", + "integrity": "sha512-s/rNE2gDmbwAOOP493xk2X7M8LZfI1LJFSSW1+yanz3vuQCFPiHkx4GY+O1HuLUDtkzGlhtMrIcxxzyYLv308w==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=4" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.59.0.tgz", + "integrity": "sha512-+yYj1udJa2UvvIUmEm0IcKgc0UlPMgz0nsSTvkPL2y6n0uU5LgIHSwVu4AHhrve6j9BpVSoRksnz8c9QcvITJA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/restore-cursor/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.59.0.tgz", + "integrity": "sha512-bUplUb48LYsB3hHlQXP2ZMOenpieWoOyppLAnnAhuPag3MGPnt+7caxE3w/Vl9wpQsTA3gzLntQi9rxWrs7Xqg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.59.0.tgz", + "integrity": "sha512-/HLsLuz42rWl7h7ePdmMTpHm2HIDmPtcEMYgm5BBEHiEiuNOrzMaUpd2z7UnNni5LGN9obJy2YoAYBLXQwazrA==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.59.0.tgz", + "integrity": "sha512-rUPy+JnanpPwV/aJCPnxAD1fW50+XPI0VkWr7f0vEbqcdsS8NpB24Rw6RsS7SdpFv8Dw+8ugCwao5nCFbqOUSg==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.59.0.tgz", + "integrity": "sha512-xkE7puteDS/vUyRngLXW0t8WgdWoS/tfxXjhP/P7SMqPDx+hs44SpssO3h3qmTqECYEuXBUPzcAw5257Ka+ofA==", + "cpu": [ + "x64" ], - "dependencies": { - "queue-microtask": "^1.2.2" + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "tslib": "^2.1.0" + "tslib": "^2.4.0" } }, - "node_modules/rxjs/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "dev": true - }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "undici-types": "~5.26.4" } }, - "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", "dev": true, - "bin": { - "semver": "bin/semver" - } + "license": "MIT" }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "dev": true, + "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" + "environment": "^1.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "license": "MIT", + "engines": { + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/slice-ansi": { + "node_modules/cli-cursor": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" + "restore-cursor": "^5.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, "engines": { - "node": ">=12" + "node": ">=20" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" } }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/spdx-license-ids": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", - "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", - "dev": true + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" }, - "node_modules/string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.6.19" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "node_modules/get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" + "resolve-pkg-maps": "^1.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/typicode" } }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "node_modules/lint-staged": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz", + "integrity": "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "commander": "^14.0.3", + "listr2": "^9.0.5", + "picomatch": "^4.0.3", + "string-argv": "^0.3.2", + "tinyexec": "^1.0.4", + "yaml": "^2.8.2" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": ">=12" + "node": ">=20.17" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://opencollective.com/lint-staged" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, "engines": { - "node": ">=4" + "node": ">=20.0.0" } }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, + "license": "MIT", "dependencies": { - "min-indent": "^1.0.0" + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "mimic-function": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "node_modules/oxc-parser": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.124.0.tgz", + "integrity": "sha512-h07SFj/tp2U3cf3+LFX6MmOguQiM9ahwpGs0ZK5CGhgL8p4kk24etrJKsEzhXAvo7mfvoKTZooZ5MLKAPRmJ1g==", "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.124.0" + }, "engines": { - "node": ">= 0.4" + "node": "^20.19.0 || >=22.12.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.124.0", + "@oxc-parser/binding-android-arm64": "0.124.0", + "@oxc-parser/binding-darwin-arm64": "0.124.0", + "@oxc-parser/binding-darwin-x64": "0.124.0", + "@oxc-parser/binding-freebsd-x64": "0.124.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.124.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.124.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.124.0", + "@oxc-parser/binding-linux-arm64-musl": "0.124.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.124.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.124.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.124.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.124.0", + "@oxc-parser/binding-linux-x64-gnu": "0.124.0", + "@oxc-parser/binding-linux-x64-musl": "0.124.0", + "@oxc-parser/binding-openharmony-arm64": "0.124.0", + "@oxc-parser/binding-wasm32-wasi": "0.124.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.124.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.124.0", + "@oxc-parser/binding-win32-x64-msvc": "0.124.0" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/oxfmt": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/oxfmt/-/oxfmt-0.44.0.tgz", + "integrity": "sha512-lnncqvHewyRvaqdrnntVIrZV2tEddz8lbvPsQzG/zlkfvgZkwy0HP1p/2u1aCDToeg1jb9zBpbJdfkV73Itw+w==", "dev": true, + "license": "MIT", "dependencies": { - "is-number": "^7.0.0" + "tinypool": "2.1.0" + }, + "bin": { + "oxfmt": "bin/oxfmt" }, "engines": { - "node": ">=8.0" + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxfmt/binding-android-arm-eabi": "0.44.0", + "@oxfmt/binding-android-arm64": "0.44.0", + "@oxfmt/binding-darwin-arm64": "0.44.0", + "@oxfmt/binding-darwin-x64": "0.44.0", + "@oxfmt/binding-freebsd-x64": "0.44.0", + "@oxfmt/binding-linux-arm-gnueabihf": "0.44.0", + "@oxfmt/binding-linux-arm-musleabihf": "0.44.0", + "@oxfmt/binding-linux-arm64-gnu": "0.44.0", + "@oxfmt/binding-linux-arm64-musl": "0.44.0", + "@oxfmt/binding-linux-ppc64-gnu": "0.44.0", + "@oxfmt/binding-linux-riscv64-gnu": "0.44.0", + "@oxfmt/binding-linux-riscv64-musl": "0.44.0", + "@oxfmt/binding-linux-s390x-gnu": "0.44.0", + "@oxfmt/binding-linux-x64-gnu": "0.44.0", + "@oxfmt/binding-linux-x64-musl": "0.44.0", + "@oxfmt/binding-openharmony-arm64": "0.44.0", + "@oxfmt/binding-win32-arm64-msvc": "0.44.0", + "@oxfmt/binding-win32-ia32-msvc": "0.44.0", + "@oxfmt/binding-win32-x64-msvc": "0.44.0" } }, - "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "node_modules/oxlint": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.59.0.tgz", + "integrity": "sha512-0xBLeGGjP4vD9pygRo8iuOkOzEU1MqOnfiOl7KYezL/QvWL8NUg6n03zXc7ZVqltiOpUxBk2zgHI3PnRIEdAvw==", "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, + "license": "MIT", "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.59.0", + "@oxlint/binding-android-arm64": "1.59.0", + "@oxlint/binding-darwin-arm64": "1.59.0", + "@oxlint/binding-darwin-x64": "1.59.0", + "@oxlint/binding-freebsd-x64": "1.59.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.59.0", + "@oxlint/binding-linux-arm-musleabihf": "1.59.0", + "@oxlint/binding-linux-arm64-gnu": "1.59.0", + "@oxlint/binding-linux-arm64-musl": "1.59.0", + "@oxlint/binding-linux-ppc64-gnu": "1.59.0", + "@oxlint/binding-linux-riscv64-gnu": "1.59.0", + "@oxlint/binding-linux-riscv64-musl": "1.59.0", + "@oxlint/binding-linux-s390x-gnu": "1.59.0", + "@oxlint/binding-linux-x64-gnu": "1.59.0", + "@oxlint/binding-linux-x64-musl": "1.59.0", + "@oxlint/binding-openharmony-arm64": "1.59.0", + "@oxlint/binding-win32-arm64-msvc": "1.59.0", + "@oxlint/binding-win32-ia32-msvc": "1.59.0", + "@oxlint/binding-win32-x64-msvc": "1.59.0" }, "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" + "oxlint-tsgolint": ">=0.18.0" }, "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { + "oxlint-tsgolint": { "optional": true } } }, - "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "node_modules/oxlint-tsgolint": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/oxlint-tsgolint/-/oxlint-tsgolint-0.20.0.tgz", + "integrity": "sha512-/Uc9TQyN1l8w9QNvXtVHYtz+SzDJHKpb5X0UnHodl0BVzijUPk0LPlDOHAvogd1UI+iy9ZSF6gQxEqfzUxCULQ==", "dev": true, - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "license": "MIT", + "bin": { + "tsgolint": "bin/tsgolint.js" + }, + "optionalDependencies": { + "@oxlint-tsgolint/darwin-arm64": "0.20.0", + "@oxlint-tsgolint/darwin-x64": "0.20.0", + "@oxlint-tsgolint/linux-arm64": "0.20.0", + "@oxlint-tsgolint/linux-x64": "0.20.0", + "@oxlint-tsgolint/win32-arm64": "0.20.0", + "@oxlint-tsgolint/win32-x64": "0.20.0" } }, - "node_modules/tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", - "dev": true - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=12" }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "node_modules/prettier": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.2.tgz", + "integrity": "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==", "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">= 0.8.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, - "engines": { - "node": ">=10" - }, + "license": "MIT", "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, + "license": "ISC", "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "semver": "bin/semver.js" }, "engines": { - "node": ">=12.20" + "node": ">=10" } }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "license": "ISC", + "engines": { + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", "dev": true, + "license": "MIT", "dependencies": { - "punycode": "^2.1.0" + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "license": "MIT", + "engines": { + "node": ">=0.6.19" } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/string-width": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz", + "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", "dev": true, + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" + "node": ">=20" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, + "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">= 0.4" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "node_modules/tinyexec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/tinypool": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.1.0.tgz", + "integrity": "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==", "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": "^20.0.0 || >=22.0.0" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "engines": { - "node": ">=8" - } + "license": "0BSD", + "optional": true }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, "engines": { - "node": ">=8" + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" } }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": ">=8" + "node": ">=14.17" } }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">=10" + "node": ">= 14.6" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/eemeli" } } } diff --git a/package.json b/package.json index 36bc7fd..37f3767 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,29 @@ { "name": "@askuzminov/simple-release", "version": "1.2.0", - "description": "", + "description": "Zero-dependency auto release pipeline for npm packages — conventional commits, monorepo/workspace, changelog, multi-registry publish", + "keywords": [ + "release", + "version", + "publish", + "conventional-commits", + "changelog", + "semver", + "monorepo", + "workspace", + "lerna", + "changesets", + "semantic-release", + "npm", + "bun", + "pnpm", + "yarn", + "github", + "gitlab", + "ci", + "automation", + "zero-dependencies" + ], "author": { "name": "Kuzminov Alexander", "url": "https://github.com/askuzminov" @@ -36,46 +58,45 @@ "dist" ], "engines": { - "node": ">=10" + "node": ">=10.12" }, "scripts": { - "check-types": "tsc --noEmit", + "test": "node --import tsx --test tests/*.test.ts", + "check-types": "tsc --noEmit && tsc -p tsconfig.build.json --noEmit", "check-types:watch": "npm run check-types -- --watch", - "build:cjs": "tsc", - "build:esm": "tsc --module es2015 --outDir ./dist/esm/", - "build:types": "tsc --outDir ./dist/types/ --declaration --emitDeclarationOnly", + "build:cjs": "tsc -p tsconfig.build.json", + "build:esm": "tsc -p tsconfig.build.json --module es2015 --outDir ./dist/esm/", + "build:types": "tsc -p tsconfig.build.json --outDir ./dist/types/ --declaration --emitDeclarationOnly", "build": "npm run build:cjs && npm run build:esm && npm run build:types", "lint:prettier": "prettier --write \"@(src)/**/*.{ts,tsx,js,json,css,md,html,yml}\"", - "lint": "eslint --cache --cache-location \".cache/eslint\" '**/*.{js,ts,tsx}'", - "lint:fix": "npm run lint -- --fix", + "lint": "oxlint", + "lint:fix": "oxlint --fix", "release": "node ./dist/cjs/index.js --file src --file package.json", "lint-staged": "lint-staged", - "dev:no-git": "ts-node -T src disable-git disable-github --file src --file package.json", - "dev:no-push": "ts-node -T src disable-push disable-github --file src --file package.json", - "dev:current-version": "ts-node -T src --mode current-version --file src prerelease=next", - "dev:next-version": "ts-node -T src --mode next-version --file src prerelease=next", - "dev:has-changes": "ts-node -T src --mode has-changes --file src prerelease=next", + "dev:no-git": "node --import tsx src disable-git disable-github --file src --file package.json", + "dev:no-push": "node --import tsx src disable-push disable-github --file src --file package.json", + "dev:current-version": "node --import tsx src --mode current-version --file src prerelease=next", + "dev:next-version": "node --import tsx src --mode next-version --file src prerelease=next", + "dev:has-changes": "node --import tsx src --mode has-changes --file src prerelease=next", "prepare": "if test \"$CI\" != \"true\" ; then husky install ; fi" }, "devDependencies": { - "@types/node": "^18.16.3", - "@typescript-eslint/eslint-plugin": "^5.59.1", - "@typescript-eslint/parser": "^5.59.1", - "eslint": "^8.39.0", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-prefer-arrow": "^1.2.3", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-unicorn": "^46.0.0", - "husky": "^8.0.3", - "lint-staged": "^13.2.2", - "prettier": "~2.8.8", - "ts-node": "^10.9.1", - "typescript": "~5.0.4" + "@types/node": "18.19.130", + "@types/semver": "7.7.1", + "husky": "9.1.7", + "lint-staged": "16.4.0", + "oxc-parser": "0.124.0", + "oxfmt": "0.44.0", + "oxlint": "1.59.0", + "oxlint-tsgolint": "0.20.0", + "prettier": "3.8.2", + "semver": "7.7.4", + "tsx": "4.21.0", + "typescript": "6.0.2" }, "lint-staged": { "*.{ts,tsx}": [ - "eslint --fix", + "oxlint --fix", "prettier --write" ], "*.{css,less,sass}": [ diff --git a/src/arg.ts b/src/arg.ts index c3bb32a..bd66c1a 100644 --- a/src/arg.ts +++ b/src/arg.ts @@ -18,6 +18,13 @@ export const ARG = arg<{ '--version'?: string; '--source-repo'?: string; '--release-repo'?: string; + '--workspace': string[]; + 'no-workspace': boolean; + '--contents'?: string; + '--cascade': 'full' | 'lazy' | 'none'; + 'dry-run': boolean; + verbose: boolean; + provenance: boolean; }>({ help: false, prerelease: false, @@ -35,6 +42,13 @@ export const ARG = arg<{ '--version': undefined, '--source-repo': undefined, '--release-repo': undefined, + '--workspace': [], + 'no-workspace': false, + '--contents': undefined, + '--cascade': 'full', + 'dry-run': false, + verbose: false, + provenance: false, }); if (ARG.help) { @@ -63,6 +77,13 @@ if (ARG.help) { log('info', '--file', "Filter files for include/exclude, for example: --file=src --file=types --file ':!dist'"); log('info', '--version', 'Custom format for version, for example: --version v{VERSION}'); log('info', '--source-repo', 'Custom path to links for sourcecode: --source-repo myorg/somepackage'); - log('info', '--release-repo', 'Custom path to links for release notes: --source-repo myorg/somepackage'); + log('info', '--release-repo', 'Custom path to links for release notes: --release-repo myorg/somepackage'); + log('info', '--workspace', 'Select specific packages in monorepo: --workspace @org/pkg1 --workspace pkg2'); + log('info', 'no-workspace', 'Force single-package mode even in monorepo'); + log('info', '--contents', 'Publish from subdirectory: --contents dist'); + log('info', '--cascade', 'Cross-package deps: auto (default), lazy, none'); + log('info', 'dry-run', 'Show what would happen without making changes'); + log('info', 'verbose', 'Show detailed output'); + log('info', 'provenance', 'Generate provenance attestation when publishing (npm/npmjs.org)'); process.exit(0); } diff --git a/src/changelog.ts b/src/changelog.ts index 100c8a7..f4a6daf 100644 --- a/src/changelog.ts +++ b/src/changelog.ts @@ -3,10 +3,10 @@ import { join } from 'path'; import { CHANGELOG_FILE } from './config'; -export async function getChangelog(title: string, path = CHANGELOG_FILE) { +export async function getChangelog(title: string, dir = process.cwd()) { let changelog = ''; try { - changelog = await fs.readFile(join(process.cwd(), path), 'utf8'); + changelog = await fs.readFile(join(dir, CHANGELOG_FILE), 'utf8'); } catch { // } @@ -18,6 +18,6 @@ export async function getChangelog(title: string, path = CHANGELOG_FILE) { return changelog; } -export async function writeChangelog(text: string, path = CHANGELOG_FILE) { - await fs.writeFile(join(process.cwd(), path), text, 'utf8'); +export async function writeChangelog(text: string, dir = process.cwd()) { + await fs.writeFile(join(dir, CHANGELOG_FILE), text, 'utf8'); } diff --git a/src/changes.ts b/src/changes.ts new file mode 100644 index 0000000..b2307d0 --- /dev/null +++ b/src/changes.ts @@ -0,0 +1,122 @@ +import { promises as fs } from 'fs'; +import { join } from 'path'; + +import { getCommitsRaw, getTag, parseCommits } from './git'; +import { log } from './log'; +import { detectIndent, writeVersion } from './package'; +import { parse } from './parser'; +import { inc, satisfies } from './semver'; +import { PackageContext, ParseConfig } from './types'; +import { isText } from './utils'; + +export function resolveWorkspace(ws: string, version: string): string { + const protocol = ws.slice('workspace:'.length); + if (protocol === '*') { + return version; + } + if (protocol === '^' || protocol === '~') { + return `${protocol}${version}`; + } + return version; +} + +export function extractPrefix(range: string): string { + const match = /^[~^>=<]*/.exec(range); + return match !== null ? match[0] : ''; +} + +export interface PackageChanges { + ctx: PackageContext; + config: ParseConfig; + tag: string; +} + +export async function detectPackageChanges( + ctx: PackageContext, + hash: string, + url: string +): Promise { + const tag = await getTag(`${ctx.tagPrefix}*`); + + const rawCommits = await getCommitsRaw(tag, hash, ctx.files); + const commits = parseCommits(rawCommits); + const config = parse(commits, url); + + if (config.isEmpty) { + log('info', ctx.name, 'No changes'); + return null; + } + + log('ok', ctx.name, `Changes detected (${config.isMajor ? 'major' : config.isMinor ? 'minor' : 'patch'})`); + return { ctx, config, tag }; +} + +export async function cascadeDeps( + allPackages: PackageContext[], + releases: { name: string; version: string; ctx: PackageContext }[], + mode: 'full' | 'lazy' = 'full' +): Promise { + const released: Record = {}; + for (const r of releases) { + released[r.name] = r.version; + } + const oldVersions: Record = {}; + for (const p of allPackages) { + oldVersions[p.name] = p.version; + } + + for (const pkg of allPackages) { + if (pkg.name in released) { + continue; + } + + let fileUpdated = false; + let needsBump = false; + + for (const depType of ['dependencies'] as const) { + const deps = pkg.pack[depType]; + if (typeof deps !== 'object' || deps === null) { + continue; + } + const depsRecord = deps as Record; + + for (const name of Object.keys(released)) { + const newVersion = released[name]; + const current = depsRecord[name]; + if (!isText(current)) { + continue; + } + const resolved = current.startsWith('workspace:') + ? resolveWorkspace(current, oldVersions[name] ?? '0.0.0') + : current; + if (mode === 'lazy' && satisfies(newVersion, resolved)) { + log('info', pkg.name, `${depType}["${name}"] ${resolved} covers ${newVersion}, skipping`); + continue; + } + if (current.startsWith('workspace:')) { + needsBump = true; + continue; + } + const prefix = extractPrefix(current); + depsRecord[name] = `${prefix}${newVersion}`; + fileUpdated = true; + log('ok', pkg.name, `Updated ${depType}["${name}"] → ${prefix}${newVersion}`); + } + } + + if (fileUpdated) { + const pkgPath = join(pkg.dir, 'package.json'); + const raw = await fs.readFile(pkgPath, 'utf8'); + const indent = detectIndent(raw); + await fs.writeFile(pkgPath, `${JSON.stringify(pkg.pack, undefined, indent)}\n`, 'utf8'); + } + + if (fileUpdated || needsBump) { + const next = inc(pkg.version, 'patch') ?? pkg.version; + await writeVersion(next, pkg.dir); + releases.push({ name: pkg.name, version: next, ctx: pkg }); + released[pkg.name] = next; + log('ok', pkg.name, `Cascade bump ${pkg.version} → ${next}`); + } + } +} diff --git a/src/config.ts b/src/config.ts index f96b582..f33476e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -14,7 +14,6 @@ export const { export const isGITLAB = typeof CI_JOB_ID === 'string' && CI_JOB_ID !== ''; export const TITLE = - // eslint-disable-next-line max-len '# Changelog\n\nAll notable changes to this project will be documented in this file. See [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for commit guidelines.\n'; export const CHANGELOG_FILE = 'CHANGELOG.md'; @@ -37,7 +36,6 @@ export const whitelist: Record = { export const OTHERS = 'Others changes'; export const rIgnore = - // eslint-disable-next-line max-len /^((Merge pull request)|(Merge remote-tracking branch)|(Automatic merge)|((Auto-merged|Merged) (.*?) (in|into) )|(Merge branch)|(R|r)evert|fixup|squash)/; export const rRepo = /([^/.]+)[/.]+([^/.]+)(?:\.[^/.]+)?$/; diff --git a/src/git.ts b/src/git.ts index 0ba195a..7bf499d 100644 --- a/src/git.ts +++ b/src/git.ts @@ -6,10 +6,16 @@ import { isText, sp } from './utils'; const DELIM = '###DELIM%!@^%$###'; const ESCAPE = '###ESCAPE%!@^%$###'; -export const fetchAll = () => sp('git', ['fetch', 'remote', '--tags']); - export const getHash = () => sp('git', ['rev-parse', 'HEAD']); +export async function checkBranch(): Promise { + try { + await sp('git', ['symbolic-ref', 'HEAD']); + } catch { + log('warn', 'Git', 'Detached HEAD detected. Git commit/push may fail. Checkout a branch first.'); + } +} + export const getTag = async (match?: string): Promise => { try { return await sp('git', [ @@ -22,7 +28,13 @@ export const getTag = async (match?: string): Promise => { } catch (e) { log('warn', 'Tags', 'Error getting tags', e); log('warn', 'Tags', 'Get all history'); - return sp('git', ['rev-list', '--max-parents=0', 'HEAD']); + try { + return await sp('git', ['rev-list', '--max-parents=0', 'HEAD']); + } catch { + log('warn', 'Tags', 'No commits found, using empty tree'); + const nullDevice = process.platform === 'win32' ? 'NUL' : '/dev/null'; + return sp('git', ['hash-object', '-t', 'tree', nullDevice]); + } } }; @@ -30,7 +42,6 @@ export const getCommitsRaw = (from: string, to: string, files?: string[]) => sp('git', [ 'log', `${from}..${to}`, - // eslint-disable-next-line max-len `--pretty=format:{ "short": ${ESCAPE}%h${ESCAPE}, "hash": ${ESCAPE}%H${ESCAPE}, "title": ${ESCAPE}%s${ESCAPE}, "body": ${ESCAPE}%b${ESCAPE} }${DELIM}`, ...(files ? ['--'].concat(files) : []), ]); @@ -72,12 +83,34 @@ export const writeGit = async (version: string) => { log('ok', 'Git', `Commit version ${version}`); await sp('git', ['commit', '-m', `chore(release): ${version} [skip ci]`], { stdio: 'inherit' }); - log('ok', 'Git', `Tag version ${version}`); - await sp('git', ['tag', '-a', version, '-m', `Release ${version}`], { stdio: 'inherit' }); + await createTag(version); }; +async function createTag(tag: string): Promise { + const exists = await sp('git', ['tag', '-l', tag]).then(out => out !== ''); + if (exists) { + log('warn', 'Git', `Tag ${tag} already exists, skipping`); + return; + } + log('ok', 'Git', `Tag ${tag}`); + await sp('git', ['tag', '-a', tag, '-m', `Release ${tag}`], { stdio: 'inherit' }); +} + export const pushGit = async () => { log('ok', 'Git', 'Push tags'); await sp('git', ['push'], { stdio: 'inherit' }); await sp('git', ['push', '--tags'], { stdio: 'inherit' }); }; + +export async function writeWorkspaceGit(releases: { name: string; version: string }[]): Promise { + await sp('git', ['add', '-A'], { stdio: 'inherit' }); + + const lines = releases.map(r => `- ${r.name}@${r.version}`).join('\n'); + const msg = `chore(release): publish [skip ci]\n\n${lines}`; + log('ok', 'Git', `Commit workspace release`); + await sp('git', ['commit', '-m', msg], { stdio: 'inherit' }); + + for (const r of releases) { + await createTag(`${r.name}@${r.version}`); + } +} diff --git a/src/index.ts b/src/index.ts index a13300a..bea7361 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,57 +5,134 @@ process.on('unhandledRejection', error => { process.exit(1); }); +import { join } from 'path'; + import { ARG } from './arg'; import { getChangelog, writeChangelog } from './changelog'; +import { cascadeDeps, detectPackageChanges, PackageChanges } from './changes'; import { TITLE } from './config'; -import { addChangelog, getCommitsRaw, getHash, getTag, parseCommits, pushGit, writeGit } from './git'; +import { + addChangelog, + checkBranch, + getCommitsRaw, + getHash, + getTag, + parseCommits, + pushGit, + writeGit, + writeWorkspaceGit, +} from './git'; import { log } from './log'; import { makeMD } from './markdown'; -import { getNextVersion, nextVersion, publish } from './npm'; -import { getPack, getVersion } from './package'; +import { getNextVersion, getVersionType, nextVersion, publish } from './npm'; +import { getPack, getVersion, readPackageConfig, runScript, writeVersion } from './package'; import { parse } from './parser'; import { release } from './release'; -import { formatTitle, getDate, getRepo, getURL, parseRepo } from './utils'; +import { inc } from './semver'; +import { PackageContext } from './types'; +import { formatTitle, getDate, getRepo, getURL, isText, parseRepo } from './utils'; +import { discoverWorkspaces } from './workspace'; + +const DRY = ARG['dry-run']; +const VERBOSE = ARG.verbose; + +function output(value: string): void { + process.stdout.write(process.stdout.isTTY ? `${value}\n` : value); +} async function run() { + if (DRY) { + log('info', 'Dry run', 'No changes will be made'); + } + if (ARG['--mode'] === 'current-version') { - const current = await getVersion(); - process.stdout.write(current); + output(await getVersion()); return; } + const pack = await getPack(); + const repo = getRepo(pack); + const sourceRepo = parseRepo(ARG['--source-repo']) ?? repo; + const url = getURL(sourceRepo); + + // Read config from package.json "simple-release" field + applyPackageConfig(pack); + + if (!ARG['disable-git'] && !DRY) { + await checkBranch(); + } + + if (!ARG['no-workspace']) { + const workspaces = await discoverWorkspaces(process.cwd()); + if (workspaces !== null) { + await runWorkspace(workspaces, pack, url); + return; + } + } + + await runSingle(pack, url); +} + +function applyPackageConfig(pack: Awaited>): void { + const config = readPackageConfig(pack); + if (isText(config.contents) && !isText(ARG['--contents'])) { + ARG['--contents'] = config.contents; + } + if (isText(config.cascade) && ARG['--cascade'] === 'full') { + ARG['--cascade'] = config.cascade as (typeof ARG)['--cascade']; + } + if (VERBOSE) { + log('info', 'Config', `Loaded from package.json: ${JSON.stringify(config)}`); + } +} + +async function runSingle(pack: Awaited>, url: string) { const tag = await getTag(ARG['--match']); const hash = await getHash(); const date = getDate(); const rawCommits = await getCommitsRaw(tag, hash, ARG['--file']); const commits = parseCommits(rawCommits); - const pack = await getPack(); const repo = getRepo(pack); - const sourceRepo = parseRepo(ARG['--source-repo']) ?? repo; const releaseRepo = parseRepo(ARG['--release-repo']) ?? repo; - const url = getURL(sourceRepo); const config = parse(commits, url); + if (VERBOSE) { + log('info', 'Git', `Tag: ${tag}, Hash: ${hash.slice(0, 7)}, Commits: ${commits.length}`); + } + if (ARG['--mode'] === 'has-changes') { - process.stdout.write(config.isEmpty ? 'false' : 'true'); + output(config.isEmpty ? 'false' : 'true'); return; } if (ARG['--mode'] === 'next-version') { - const next = await getNextVersion(config, ARG.prerelease); - process.stdout.write(next); + output(await getNextVersion(config, ARG.prerelease)); return; } if (config.isEmpty) { log('warn', 'Git', 'No change found in GIT'); } else { + if (DRY) { + const next = await getNextVersion(config, ARG.prerelease); + const version = formatTitle(next, ARG['--version']); + const md = makeMD({ config, version, tag, date, url }); + const current = await getVersion(); + log('ok', 'Dry run', `${current} → ${next} (${getVersionType(config, ARG.prerelease)})`); + if (VERBOSE) { + log('info', 'Dry run', `Changelog:\n${md}`); + } + return; + } + await nextVersion(config, ARG.prerelease); const version = formatTitle(await getVersion(), ARG['--version']); const md = makeMD({ config, version, tag, date, url }); log('ok', 'Version', version); - log('ok', 'Markdown', md); + if (VERBOSE) { + log('info', 'Markdown', md); + } if (ARG.prerelease === false || ARG['enable-prerelease']) { if (!ARG['disable-md']) { @@ -80,18 +157,177 @@ async function run() { } } - if (ARG['publish-github']) { - await publish('https://npm.pkg.github.com', ARG.prerelease); + const errors: string[] = []; + const pkgName = isText(pack.name) ? (pack.name as string) : 'root'; + await publishPackage( + { dir: process.cwd(), name: pkgName, version: '', pack, tagPrefix: 'v', files: [] }, + undefined, + errors + ); + + await runScript(pack, 'postversion', await getVersion()); + + if (errors.length > 0) { + log('error', 'Publish', `Failed: ${errors.join(', ')}`); + process.exit(1); } + } +} - if (ARG['publish-npmjs']) { - await publish('https://registry.npmjs.org', ARG.prerelease); +async function runWorkspace(packages: PackageContext[], rootPack: Awaited>, url: string) { + const hash = await getHash(); + const date = getDate(); + const repo = getRepo(rootPack); + const releaseRepo = parseRepo(ARG['--release-repo']) ?? repo; + + const selected = ARG['--workspace'].length > 0 ? packages.filter(p => ARG['--workspace'].includes(p.name)) : packages; + + if (selected.length === 0) { + log('warn', 'Workspace', 'No packages matched'); + return; + } + + log('ok', 'Workspace', `Found ${selected.length} package(s): ${selected.map(p => p.name).join(', ')}`); + + const changed: PackageChanges[] = []; + for (const ctx of selected) { + const result = await detectPackageChanges(ctx, hash, url); + if (result !== null) { + changed.push(result); } + } + + if (ARG['--mode'] === 'has-changes') { + output(changed.length > 0 ? 'true' : 'false'); + return; + } - for (const customPublish of ARG['--publish-custom']) { - await publish(customPublish, ARG.prerelease); + if (changed.length === 0) { + log('warn', 'Workspace', 'No changes in any package'); + return; + } + + if (DRY) { + await dryRunWorkspace(changed, packages, date, url); + return; + } + + const releases = await bumpPackages(changed, date, url); + + if (ARG['--cascade'] !== 'none') { + await cascadeDeps(packages, releases, ARG['--cascade']); + } + + if (ARG.prerelease === false || ARG['enable-prerelease']) { + if (!ARG['disable-git']) { + await writeWorkspaceGit(releases); + + if (!ARG['disable-push']) { + await pushGit(); + } + } + + if (!ARG['disable-github']) { + for (const r of releases) { + await release(releaseRepo, `${r.name}@${r.version}`, r.md); + } + } + } + + const publishErrors: string[] = []; + for (const r of releases) { + await publishPackage(r.ctx, ARG['--contents'], publishErrors); + await runScript(r.ctx.pack, 'postversion', r.version, r.ctx.dir); + } + + if (publishErrors.length > 0) { + log('error', 'Publish', `Failed: ${publishErrors.join(', ')}`); + process.exit(1); + } +} + +async function dryRunWorkspace(changed: PackageChanges[], allPackages: PackageContext[], date: string, url: string) { + const changedNames: Record = {}; + for (const c of changed) { + changedNames[c.ctx.name] = true; + } + + log('info', 'Dry run', '--- Changed packages (will be released) ---'); + for (const { ctx, config, tag } of changed) { + const preid = ARG.prerelease; + const current = ctx.version; + const bump = getVersionType(config, preid); + const next = inc(current, bump, isText(preid) ? preid : preid === true ? 'pre' : undefined) ?? current; + const version = formatTitle(next, ARG['--version']); + const md = makeMD({ config, version, tag, date, url }); + + log('ok', ctx.name, `${current} → ${next} (${bump})`); + if (VERBOSE) { + log('info', ctx.name, `Changelog:\n${md}`); + } + } + + const skipped = allPackages.filter(p => changedNames[p.name] !== true); + if (skipped.length > 0) { + log('info', 'Dry run', '--- Unchanged packages (will be skipped) ---'); + for (const pkg of skipped) { + log('info', pkg.name, `${pkg.version} (no changes)`); } } + + log('info', 'Dry run', `${changed.length} released, ${skipped.length} skipped, ${allPackages.length} total`); +} + +async function bumpPackages(changed: PackageChanges[], date: string, url: string) { + const releases: { name: string; version: string; ctx: PackageContext; md: string }[] = []; + + for (const { ctx, config, tag } of changed) { + const preid = ARG.prerelease; + const current = ctx.version; + const bump = getVersionType(config, preid); + const next = inc(current, bump, isText(preid) ? preid : preid === true ? 'pre' : undefined) ?? current; + + log('ok', ctx.name, `${current} → ${next}`); + + await writeVersion(next, ctx.dir); + + const version = formatTitle(next, ARG['--version']); + const md = makeMD({ config, version, tag, date, url }); + + if (!ARG['disable-md']) { + const changelog = await getChangelog(TITLE, ctx.dir); + await writeChangelog(`${TITLE}${md}${changelog}`, ctx.dir); + } + + releases.push({ name: ctx.name, version: next, ctx, md }); + } + + return releases; +} + +async function publishPackage(ctx: PackageContext, contents?: string, errors?: string[]): Promise { + const dir = isText(contents) ? join(ctx.dir, contents) : ctx.dir; + + if (ARG['publish-github']) { + await publish('https://npm.pkg.github.com', ARG.prerelease, dir, ARG.provenance).catch(e => { + log('error', 'Publish', `${ctx.name} failed: npm.pkg.github.com — ${e}`); + errors?.push(`${ctx.name} → npm.pkg.github.com`); + }); + } + + if (ARG['publish-npmjs']) { + await publish('https://registry.npmjs.org', ARG.prerelease, dir, ARG.provenance).catch(e => { + log('error', 'Publish', `${ctx.name} failed: registry.npmjs.org — ${e}`); + errors?.push(`${ctx.name} → registry.npmjs.org`); + }); + } + + for (const customPublish of ARG['--publish-custom']) { + await publish(customPublish, ARG.prerelease, dir, ARG.provenance).catch(e => { + log('error', 'Publish', `${ctx.name} failed: ${customPublish} — ${e}`); + errors?.push(`${ctx.name} → ${customPublish}`); + }); + } } void run(); diff --git a/src/lint.ts b/src/lint.ts index 9fafb3e..f4850f4 100644 --- a/src/lint.ts +++ b/src/lint.ts @@ -1,7 +1,5 @@ #!/usr/bin/env node -/* eslint-disable no-console */ - import { readFileSync } from 'fs'; import { join } from 'path'; diff --git a/src/log.ts b/src/log.ts index 8914ac6..7f76834 100644 --- a/src/log.ts +++ b/src/log.ts @@ -7,6 +7,5 @@ const colors = { }; export function log(type: 'error' | 'warn' | 'ok' | 'info', title: string, ...message: unknown[]) { - // eslint-disable-next-line no-console console[type === 'ok' ? 'info' : type](`\x1b[${colors[type]}${title}:\x1b[${colors.reset}`, ...message); } diff --git a/src/markdown.ts b/src/markdown.ts index 28dae6f..23e2ad4 100644 --- a/src/markdown.ts +++ b/src/markdown.ts @@ -1,7 +1,7 @@ import { whitelist } from './config'; import { Markdown } from './types'; -const last = (arr: string[]) => arr[arr.length - 1].slice(-1) !== '\n'; +const last = (arr: string[]) => arr.length > 0 && arr[arr.length - 1].slice(-1) !== '\n'; export function makeMD({ url, config, version, tag, date }: Markdown) { const md = ['', `## ${url ? `[${version}](${url}/compare/${tag}...${version})` : version} (${date})`]; diff --git a/src/npm.ts b/src/npm.ts index 0c949b9..1c61df1 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -1,25 +1,16 @@ -import { inc } from 'semver'; +import { existsSync } from 'fs'; +import { join } from 'path'; import { log } from './log'; -import { getVersion } from './package'; +import { getVersion, writeVersion } from './package'; +import { inc } from './semver'; import { ParseConfig } from './types'; -import { isText, sp } from './utils'; - -const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'; +import { isText, retry, sp } from './utils'; export async function nextVersion(config: ParseConfig, preid?: string | boolean) { - const version = getVersionType(config, preid); - log('ok', 'Version', version); - const params = ['version', version]; - - if (typeof preid === 'string') { - log('ok', 'Version', `preid ${preid}`); - params.push(`--preid=${preid}`); - } - - params.push('--no-git-tag-version'); - - await sp(npmCmd, params, { stdio: 'inherit' }); + const next = await getNextVersion(config, preid); + log('ok', 'Version', next); + await writeVersion(next); } export async function getNextVersion(config: ParseConfig, preid?: string | boolean): Promise { @@ -35,11 +26,54 @@ export function getVersionType(config: ParseConfig, preid?: string | boolean) { }` as const; } -export async function publish(registry: string, preid?: string | boolean) { - log('ok', 'Publish', `Start publish in registry ${registry}`); - await sp( - npmCmd, - ['publish', '--tag', isText(preid) || preid === true ? 'canary' : 'latest', '--registry', registry], - { stdio: 'inherit' } - ); +function cmd(name: string): string { + return process.platform === 'win32' && name !== 'bun' ? `${name}.cmd` : name; +} + +export function detectPM(): string { + const cwd = process.cwd(); + if (existsSync(join(cwd, 'package-lock.json')) || existsSync(join(cwd, 'npm-shrinkwrap.json'))) { + return cmd('npm'); + } + if (existsSync(join(cwd, 'bun.lock')) || existsSync(join(cwd, 'bun.lockb'))) { + return cmd('bun'); + } + if (existsSync(join(cwd, 'pnpm-lock.yaml'))) { + return cmd('pnpm'); + } + if (existsSync(join(cwd, 'yarn.lock'))) { + return cmd('yarn'); + } + return cmd('npm'); +} + +export async function publish(registry: string, preid?: string | boolean, cwd?: string, provenance?: boolean) { + const pm = detectPM(); + const pmName = pm.replace(/\.cmd$/, ''); + log('ok', 'Publish', `Start publish via ${pmName} to ${registry}`); + const tag = isText(preid) || preid === true ? 'canary' : 'latest'; + const args = ['publish', '--tag', tag]; + + if (provenance === true) { + args.push('--provenance'); + } + + const env = { ...process.env }; + + switch (pmName) { + case 'npm': + case 'pnpm': + case 'bun': + args.push('--registry', registry); + break; + case 'yarn': + env.YARN_REGISTRY = registry; + env.YARN_NPM_REGISTRY_SERVER = registry; + break; + default: + args.push('--registry', registry); + break; + } + + await retry(() => sp(pm, args, { stdio: 'inherit', cwd, env })); } diff --git a/src/package.ts b/src/package.ts index 4f3bf27..04ccf16 100644 --- a/src/package.ts +++ b/src/package.ts @@ -1,14 +1,91 @@ import { promises as fs } from 'fs'; import { join } from 'path'; +import { log } from './log'; import { Pack } from './types'; -import { isText } from './utils'; +import { isText, parseJSON, sp } from './utils'; -export const getPack = async (file = 'package.json') => - JSON.parse(await fs.readFile(join(process.cwd(), file), 'utf8')) as Pack; +export async function getPack(dir = process.cwd(), file = 'package.json'): Promise { + return parseJSON(await fs.readFile(join(dir, file), 'utf8')); +} -export const getVersion = async (): Promise => { - const v = (await getPack()).version; +export async function getVersion(dir = process.cwd()): Promise { + const v = (await getPack(dir)).version; return isText(v) ? v : '0.0.0'; -}; +} + +const LOCK_FILES = ['package-lock.json', 'npm-shrinkwrap.json'] as const; + +export async function writeVersion(version: string, dir = process.cwd()): Promise { + const pkgPath = join(dir, 'package.json'); + const raw = await fs.readFile(pkgPath, 'utf8'); + const pkg = parseJSON(raw); + + await runScript(pkg, 'preversion', version, dir); + + // Re-read after preversion hook — script may have modified package.json + const freshRaw = await fs.readFile(pkgPath, 'utf8'); + const freshIndent = detectIndent(freshRaw); + const freshPkg = parseJSON(freshRaw); + freshPkg.version = version; + await fs.writeFile(pkgPath, `${JSON.stringify(freshPkg, undefined, freshIndent)}\n`, 'utf8'); + + for (const file of LOCK_FILES) { + await updateLockfile(file, version, dir); + } + + await runScript(freshPkg, 'version', version, dir); +} + +export async function runScript(pack: Pack, name: string, version: string, cwd = process.cwd()): Promise { + const scripts = typeof pack.scripts === 'object' && pack.scripts !== null ? pack.scripts : {}; + const script = scripts[name]; + if (!isText(script)) { + return; + } + log('ok', 'Version', `Running ${name}: ${script}`); + const isWin = process.platform === 'win32'; + const env = { ...process.env, npm_package_version: version }; + await sp(isWin ? 'cmd' : 'sh', [isWin ? '/c' : '-c', script], { stdio: 'inherit', env, cwd }); +} + +async function updateLockfile(file: string, version: string, dir = process.cwd()): Promise { + const filePath = join(dir, file); + let raw: string; + + try { + raw = await fs.readFile(filePath, 'utf8'); + } catch { + return; + } + + const indent = detectIndent(raw); + const lock = parseJSON>(raw); + + lock.version = version; + + const packages = lock.packages as Record> | undefined; + if (typeof packages === 'object' && packages !== null && '' in packages) { + packages[''].version = version; + } + + await fs.writeFile(filePath, `${JSON.stringify(lock, undefined, indent)}\n`, 'utf8'); + log('ok', 'Version', `Updated ${file}`); +} + +export function detectIndent(content: string): number { + const match = /^[ ]+/m.exec(content); + return match !== null ? match[0].length : 2; +} + +export function readPackageConfig(pack: Pack): { contents?: string; cascade?: string } { + const config = pack['simple-release'] as Record | undefined; + if (typeof config !== 'object' || config === null) { + return {}; + } + return { + contents: typeof config.contents === 'string' ? config.contents : undefined, + cascade: typeof config.cascade === 'string' ? config.cascade : undefined, + }; +} diff --git a/src/release.ts b/src/release.ts index 1b3991c..c7da7aa 100644 --- a/src/release.ts +++ b/src/release.ts @@ -20,21 +20,20 @@ export async function release( log('warn', 'Package', 'No repository.url in package.json'); } else { try { - log('ok', 'Github', `Run release for ${releaseRepo.user}/${releaseRepo.repository}/`); + log('ok', 'Gitlab', `Run release for ${releaseRepo.user}/${releaseRepo.repository}/`); const out = await gitlabRelease({ domain: CI_SERVER_HOST, token: CI_JOB_TOKEN, projectID: CI_PROJECT_ID, setup: { - // eslint-disable-next-line camelcase tag_name: version, name: version + (ARG['enable-prerelease'] ? '-pre' : ''), description: md, }, }); - log('ok', 'Github', out); + log('ok', 'Gitlab', out); } catch (e) { - log('error', 'Github', e); + log('error', 'Gitlab', e); } } } else { @@ -49,7 +48,6 @@ export async function release( token: GH_TOKEN, path: `/repos/${releaseRepo.user}/${releaseRepo.repository}/releases`, setup: { - // eslint-disable-next-line camelcase tag_name: version, name: version, body: md, @@ -64,37 +62,54 @@ export async function release( } } -export function githubRelease({ token, path, setup }: GithubRelease) { - return http( - { - host: 'api.github.com', - port: 443, - path, - method: 'POST', - headers: { - Authorization: `token ${token}`, - Accept: 'application/json', - 'user-agent': 'Github-Releaser', - }, - }, - setup - ); +const ghHeaders = (token: string) => ({ + Authorization: `token ${token}`, + Accept: 'application/json', + 'user-agent': 'Github-Releaser', +}); + +export async function githubRelease({ token, path, setup }: GithubRelease) { + try { + return await http({ host: 'api.github.com', port: 443, path, method: 'POST', headers: ghHeaders(token) }, setup); + } catch (e) { + // Release may already exist — try to update + const tagPath = `${path}/tags/${encodeURIComponent(setup.tag_name)}`; + try { + const existing = await http( + { host: 'api.github.com', port: 443, path: tagPath, method: 'GET', headers: ghHeaders(token) }, + {} + ); + const id = (JSON.parse(String(existing)) as { id: number }).id; + log('info', 'Github', `Release exists, updating ${setup.tag_name}`); + return await http( + { host: 'api.github.com', port: 443, path: `${path}/${id}`, method: 'PATCH', headers: ghHeaders(token) }, + setup + ); + } catch { + throw e; + } + } } -export function gitlabRelease({ domain, token, projectID, setup }: GitlabRelease) { - return http( - { - host: domain, - port: 443, - path: `/api/v4/projects/${projectID}/releases`, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'PRIVATE-TOKEN': token, - Accept: 'application/json', - 'user-agent': 'Gitlab-Releaser', - }, - }, - setup - ); +export async function gitlabRelease({ domain, token, projectID, setup }: GitlabRelease) { + const glHeaders = { + 'Content-Type': 'application/json', + 'PRIVATE-TOKEN': token, + Accept: 'application/json', + 'user-agent': 'Gitlab-Releaser', + }; + const path = `/api/v4/projects/${projectID}/releases`; + + try { + return await http({ host: domain, port: 443, path, method: 'POST', headers: glHeaders }, setup); + } catch (e) { + // Release may already exist — try to update + const tagPath = `${path}/${encodeURIComponent(setup.tag_name)}`; + try { + log('info', 'Gitlab', `Release exists, updating ${setup.tag_name}`); + return await http({ host: domain, port: 443, path: tagPath, method: 'PUT', headers: glHeaders }, setup); + } catch { + throw e; + } + } } diff --git a/src/semver.ts b/src/semver.ts new file mode 100644 index 0000000..d5cffb2 --- /dev/null +++ b/src/semver.ts @@ -0,0 +1,165 @@ +type Release = 'major' | 'minor' | 'patch' | 'premajor' | 'preminor' | 'prepatch' | 'prerelease'; + +interface SemVer { + major: number; + minor: number; + patch: number; + prerelease: (string | number)[]; +} + +function parse(version: string): SemVer | undefined { + const m = /^v?(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/.exec(version.trim()); + if (m === null) { + return undefined; + } + return { + major: Number(m[1]), + minor: Number(m[2]), + patch: Number(m[3]), + prerelease: m[4] !== undefined ? m[4].split('.').map(p => (/^\d+$/.test(p) ? Number(p) : p)) : [], + }; +} + +function format(v: SemVer): string { + const base = `${v.major}.${v.minor}.${v.patch}`; + return v.prerelease.length > 0 ? `${base}-${v.prerelease.join('.')}` : base; +} + +function pre(v: SemVer, identifier?: string): void { + if (identifier !== undefined) { + if (v.prerelease.length > 0 && v.prerelease[0] === identifier) { + if (typeof v.prerelease[1] !== 'number') { + v.prerelease = [identifier, 0]; + } else { + v.prerelease = [identifier, v.prerelease[1] + 1]; + } + } else { + v.prerelease = [identifier, 0]; + } + } else if (v.prerelease.length === 0) { + v.prerelease = [0]; + } else { + let found = false; + for (let i = v.prerelease.length - 1; i >= 0; i--) { + if (typeof v.prerelease[i] === 'number') { + (v.prerelease[i] as number)++; + found = true; + break; + } + } + if (!found) { + v.prerelease.push(0); + } + } +} + +export function inc(version: string, release: Release, identifier?: string): string | undefined { + const v = parse(version); + if (v === undefined) { + return undefined; + } + + switch (release) { + case 'major': + if (v.minor !== 0 || v.patch !== 0 || v.prerelease.length === 0) { + v.major++; + } + v.minor = 0; + v.patch = 0; + v.prerelease = []; + break; + + case 'minor': + if (v.patch !== 0 || v.prerelease.length === 0) { + v.minor++; + } + v.patch = 0; + v.prerelease = []; + break; + + case 'patch': + if (v.prerelease.length === 0) { + v.patch++; + } + v.prerelease = []; + break; + + case 'premajor': + v.prerelease = []; + v.major++; + v.minor = 0; + v.patch = 0; + pre(v, identifier); + break; + + case 'preminor': + v.prerelease = []; + v.minor++; + v.patch = 0; + pre(v, identifier); + break; + + case 'prepatch': + v.prerelease = []; + v.patch++; + pre(v, identifier); + break; + + case 'prerelease': + if (v.prerelease.length === 0) { + v.patch++; + } + pre(v, identifier); + break; + + default: + return undefined; + } + + return format(v); +} + +function gte(a: SemVer, b: SemVer): boolean { + if (a.major !== b.major) return a.major > b.major; + if (a.minor !== b.minor) return a.minor > b.minor; + return a.patch >= b.patch; +} + +export function satisfies(version: string, range: string): boolean { + if (range.startsWith('workspace:')) { + return true; + } + + const match = /^([~^>=<]*)(.+)$/.exec(range.trim()); + if (match === null) { + return false; + } + + const prefix = match[1]; + const v = parse(version); + const r = parse(match[2]); + if (v === undefined || r === undefined) { + return false; + } + + if (prefix === '' || prefix === '=') { + return v.major === r.major && v.minor === r.minor && v.patch === r.patch; + } + + if (prefix === '>=') { + return gte(v, r); + } + + if (prefix === '^') { + if (r.major === 0) { + return v.major === 0 && v.minor === r.minor && v.patch >= r.patch; + } + return v.major === r.major && gte(v, r); + } + + if (prefix === '~') { + return v.major === r.major && v.minor === r.minor && v.patch >= r.patch; + } + + return false; +} diff --git a/src/types.ts b/src/types.ts index ba094fa..a425907 100644 --- a/src/types.ts +++ b/src/types.ts @@ -56,3 +56,12 @@ export interface Markdown { export type ParseConfig = ReturnType; export type Repo = ReturnType; + +export interface PackageContext { + dir: string; + name: string; + version: string; + pack: Pack; + tagPrefix: string; + files: string[]; +} diff --git a/src/utils.ts b/src/utils.ts index 1348145..b52a489 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,7 @@ import { SpawnOptions, spawn } from 'child_process'; import { RequestOptions, request } from 'https'; +import { log } from './log'; import { CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, @@ -28,13 +29,15 @@ export function getURL(repo: Repo): string { ? `${CI_SERVER_URL}/${repo.user}/${repo.repository}/-` : `${CI_SERVER_URL}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}/-` : repo - ? `${GITHUB_SERVER_URL}/${repo.user}/${repo.repository}` - : `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}`; + ? `${GITHUB_SERVER_URL}/${repo.user}/${repo.repository}` + : `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}`; } export function getDate() { const date = new Date(); - return `${date.getUTCFullYear()}-${date.getUTCMonth() + 1}-${date.getUTCDate()}`; + const m = String(date.getUTCMonth() + 1).padStart(2, '0'); + const d = String(date.getUTCDate()).padStart(2, '0'); + return `${date.getUTCFullYear()}-${m}-${d}`; } export function sp(command: string, args: string[] = [], options: SpawnOptions = {}) { @@ -51,6 +54,8 @@ export function sp(command: string, args: string[] = [], options: SpawnOptions = error += data.toString(); }); + stream.on('error', reject); + stream.on('exit', code => { if (code === null || code === 0) { resolve(result.trim()); @@ -75,7 +80,7 @@ export function arg>(def: T): T { if (vals.length > 1 || typeof argv[key] === 'boolean') { const val = vals[1]; writeVal(key, val); - } else if (key in argv) { + } else if (key in argv && i + 1 < ar.length) { const val = ar[++i]; writeVal(key, val); } @@ -100,6 +105,10 @@ export function isText(value: unknown): value is string { return typeof value === 'string' && value !== ''; } +export function parseJSON(content: string): T { + return JSON.parse(content.charCodeAt(0) === 0xfeff ? content.slice(1) : content) as T; +} + export function isNumber(value: unknown): value is number { return typeof value === 'number'; } @@ -110,6 +119,23 @@ export function formatTitle(version: string, title?: string): string { return isText(title) ? title.replace(rVersion, version) : `v${version}`; } +export async function retry(fn: () => Promise, attempts = 3, delay = 5000): Promise { + for (let i = 1; i <= attempts; i++) { + try { + return await fn(); + } catch (e) { + if (i < attempts) { + const ms = i * delay; + log('warn', 'Retry', `Attempt ${i} failed, retrying in ${ms / 1000}s...`); + await new Promise(r => setTimeout(r, ms)); + } else { + throw e; + } + } + } + throw new Error('Unreachable'); +} + export function http(options: RequestOptions, body: object) { return new Promise((resolve, reject) => { const req = request(options, res => { diff --git a/src/workspace.ts b/src/workspace.ts new file mode 100644 index 0000000..6957a0e --- /dev/null +++ b/src/workspace.ts @@ -0,0 +1,193 @@ +import { existsSync, promises as fs, readdirSync, realpathSync } from 'fs'; +import { join, resolve } from 'path'; + +import { Pack, PackageContext } from './types'; +import { isText, parseJSON } from './utils'; + +export async function discoverWorkspaces(rootDir: string): Promise { + const globs = await getWorkspaceGlobs(rootDir); + if (globs === null) { + return null; + } + + const dirs = resolveGlobs(rootDir, globs); + const contexts: PackageContext[] = []; + + for (const dir of dirs) { + const pkgPath = join(dir, 'package.json'); + if (!existsSync(pkgPath)) { + continue; + } + + const pack = parseJSON(await fs.readFile(pkgPath, 'utf8')); + const name = isText(pack.name) ? pack.name : ''; + const version = isText(pack.version) ? pack.version : '0.0.0'; + + if (!isText(name)) { + continue; + } + + contexts.push({ + dir, + name, + version, + pack, + tagPrefix: `${name}@`, + files: [dir], + }); + } + + return contexts.length > 0 ? topoSort(contexts) : null; +} + +export function topoSort(packages: PackageContext[]): PackageContext[] { + const byName: Record = {}; + for (const p of packages) { + byName[p.name] = p; + } + const visited: Record = {}; + const sorted: PackageContext[] = []; + + function visit(pkg: PackageContext): void { + if (visited[pkg.name] === true) { + return; + } + visited[pkg.name] = true; + + for (const depType of ['dependencies', 'devDependencies', 'peerDependencies'] as const) { + const deps = pkg.pack[depType]; + if (typeof deps !== 'object' || deps === null) { + continue; + } + for (const name of Object.keys(deps as Record)) { + const dep = byName[name]; + if (dep !== undefined) { + visit(dep); + } + } + } + + sorted.push(pkg); + } + + for (const pkg of packages) { + visit(pkg); + } + + return sorted; +} + +async function getWorkspaceGlobs(rootDir: string): Promise { + const pkgPath = join(rootDir, 'package.json'); + if (existsSync(pkgPath)) { + const raw = await fs.readFile(pkgPath, 'utf8'); + const pkg = parseJSON>(raw); + const ws = pkg.workspaces; + + if (Array.isArray(ws)) { + return ws.filter((g): g is string => typeof g === 'string'); + } + + if (typeof ws === 'object' && ws !== null && 'packages' in ws) { + const packages = (ws as Record).packages; + if (Array.isArray(packages)) { + return packages.filter((g): g is string => typeof g === 'string'); + } + } + } + + const pnpmPath = join(rootDir, 'pnpm-workspace.yaml'); + if (existsSync(pnpmPath)) { + const raw = await fs.readFile(pnpmPath, 'utf8'); + return parsePnpmWorkspace(raw); + } + + return null; +} + +export function parsePnpmWorkspace(content: string): string[] { + const result: string[] = []; + let inPackages = false; + + for (const line of content.split('\n')) { + const trimmed = line.trim(); + + if (trimmed === 'packages:') { + inPackages = true; + continue; + } + + if (inPackages) { + if (trimmed.startsWith('- ')) { + const value = trimmed.slice(2).replace(/^['"]|['"]$/g, ''); + if (isText(value)) { + result.push(value); + } + } else if (trimmed !== '' && !trimmed.startsWith('#')) { + break; + } + } + } + + return result; +} + +export function resolveGlobs(rootDir: string, globs: string[]): string[] { + const dirs: string[] = []; + + for (const glob of globs) { + if (glob.includes('**')) { + resolveDeep(rootDir, glob, dirs); + } else if (glob.includes('*')) { + const parts = glob.split('*'); + const prefix = parts[0]; + const parentDir = resolve(rootDir, prefix); + if (!existsSync(parentDir)) { + continue; + } + for (const entry of readdirSync(parentDir, { withFileTypes: true })) { + if (entry.isDirectory()) { + dirs.push(join(parentDir, entry.name)); + } + } + } else { + const dir = resolve(rootDir, glob); + if (existsSync(dir)) { + dirs.push(dir); + } + } + } + + return dirs.filter((d, i) => dirs.indexOf(d) === i).sort(); +} + +function resolveDeep(rootDir: string, glob: string, dirs: string[]): void { + const parts = glob.split('**'); + const prefix = parts[0]; + const startDir = resolve(rootDir, prefix); + if (!existsSync(startDir)) { + return; + } + walkDirs(startDir, dirs); +} + +function walkDirs(dir: string, dirs: string[], seen: Record = {}): void { + let real: string; + try { + real = realpathSync(dir); + } catch { + return; + } + if (seen[real] === true) { + return; + } + seen[real] = true; + + for (const entry of readdirSync(dir, { withFileTypes: true })) { + if (entry.isDirectory() && entry.name !== 'node_modules' && !entry.name.startsWith('.')) { + const child = join(dir, entry.name); + dirs.push(child); + walkDirs(child, dirs, seen); + } + } +} diff --git a/tests/changelog.test.ts b/tests/changelog.test.ts new file mode 100644 index 0000000..172781d --- /dev/null +++ b/tests/changelog.test.ts @@ -0,0 +1,78 @@ +import { afterEach, beforeEach, describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { promises as fs } from 'node:fs'; +import { mkdtemp, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { getChangelog, writeChangelog } from '../src/changelog'; + +let tempDir: string; +let originalCwd: string; + +beforeEach(async () => { + originalCwd = process.cwd(); + tempDir = await mkdtemp(join(tmpdir(), 'sr-test-')); + process.chdir(tempDir); +}); + +afterEach(async () => { + process.chdir(originalCwd); + await rm(tempDir, { recursive: true, force: true }); +}); + +describe('getChangelog', () => { + const TITLE = '# Changelog\n'; + + it('should return empty string when file does not exist', async () => { + const result = await getChangelog(TITLE); + assert.equal(result, ''); + }); + + it('should return content without title when file starts with title', async () => { + const content = `${TITLE}\n## v1.0.0\n- feature`; + await fs.writeFile(join(tempDir, 'CHANGELOG.md'), content, 'utf8'); + const result = await getChangelog(TITLE); + assert.equal(result, '\n## v1.0.0\n- feature'); + }); + + it('should return full content when file does not start with title', async () => { + const content = '## v1.0.0\n- feature'; + await fs.writeFile(join(tempDir, 'CHANGELOG.md'), content, 'utf8'); + const result = await getChangelog(TITLE); + assert.equal(result, content); + }); + + it('should accept custom dir', async () => { + const subDir = join(tempDir, 'sub'); + await fs.mkdir(subDir, { recursive: true }); + const content = 'custom changelog'; + await fs.writeFile(join(subDir, 'CHANGELOG.md'), content, 'utf8'); + const result = await getChangelog('', subDir); + assert.equal(result, content); + }); +}); + +describe('writeChangelog', () => { + it('should write content to CHANGELOG.md', async () => { + const content = '# Changelog\n\n## v1.0.0\n- feature\n'; + await writeChangelog(content); + const written = await fs.readFile(join(tempDir, 'CHANGELOG.md'), 'utf8'); + assert.equal(written, content); + }); + + it('should overwrite existing content', async () => { + await fs.writeFile(join(tempDir, 'CHANGELOG.md'), 'old content', 'utf8'); + await writeChangelog('new content'); + const written = await fs.readFile(join(tempDir, 'CHANGELOG.md'), 'utf8'); + assert.equal(written, 'new content'); + }); + + it('should accept custom dir', async () => { + const subDir = join(tempDir, 'sub'); + await fs.mkdir(subDir, { recursive: true }); + await writeChangelog('custom content', subDir); + const written = await fs.readFile(join(subDir, 'CHANGELOG.md'), 'utf8'); + assert.equal(written, 'custom content'); + }); +}); diff --git a/tests/changes.test.ts b/tests/changes.test.ts new file mode 100644 index 0000000..cc329bb --- /dev/null +++ b/tests/changes.test.ts @@ -0,0 +1,439 @@ +import { afterEach, beforeEach, describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { promises as fs } from 'node:fs'; +import { mkdtemp, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { cascadeDeps, extractPrefix, resolveWorkspace } from '../src/changes'; +import { Pack, PackageContext } from '../src/types'; + +let tempDir: string; + +beforeEach(async () => { + tempDir = await mkdtemp(join(tmpdir(), 'sr-changes-')); +}); + +afterEach(async () => { + await rm(tempDir, { recursive: true, force: true }); +}); + +function makeCtx(name: string, version: string, pack?: Partial): PackageContext { + const dir = join(tempDir, name); + return { + dir, + name, + version, + pack: { version, ...pack } as Pack, + tagPrefix: `${name}@`, + files: [dir], + }; +} + +async function writeJson(dir: string, file: string, data: object): Promise { + await fs.mkdir(dir, { recursive: true }); + await fs.writeFile(join(dir, file), `${JSON.stringify(data, undefined, 2)}\n`, 'utf8'); +} + +async function readJson(dir: string, file: string): Promise { + return JSON.parse(await fs.readFile(join(dir, file), 'utf8')) as T; +} + +describe('cascadeDeps', () => { + it('should update dependencies version', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '1.0.0', { + dependencies: { 'pkg-a': '^0.9.0' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases); + + const result = await readJson(pkgB.dir, 'package.json'); + const deps = result.dependencies as Record; + assert.equal(deps['pkg-a'], '^1.1.0'); + }); + + it('should bump dependent package version in auto mode', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '1.0.0', { + dependencies: { 'pkg-a': '^0.9.0' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases); + + assert.equal(releases.length, 2); + assert.equal(releases[1].name, 'pkg-b'); + assert.equal(releases[1].version, '1.0.1'); + }); + + it('should not update devDependencies in auto mode', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '1.0.0', { + devDependencies: { 'pkg-a': '^0.9.0' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases); + + const result = await readJson(pkgB.dir, 'package.json'); + const devDeps = result.devDependencies as Record; + assert.equal(devDeps['pkg-a'], '^0.9.0'); + }); + + it('should skip already released packages', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0', { + dependencies: { 'pkg-a': '^0.9.0' }, + }); + await writeJson(pkgA.dir, 'package.json', pkgA.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA], releases); + + assert.equal(releases.length, 1); + }); + + it('should skip packages without matching dependency', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgC = makeCtx('pkg-c', '1.0.0', { + dependencies: { 'other-pkg': '^1.0.0' }, + }); + await writeJson(pkgC.dir, 'package.json', pkgC.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgC], releases); + + const result = await readJson(pkgC.dir, 'package.json'); + const deps = result.dependencies as Record; + assert.equal(deps['other-pkg'], '^1.0.0'); + }); + + it('should cascade through chain: A→B→C', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '2.0.0', { + dependencies: { 'pkg-a': '^0.9.0' }, + }); + const pkgC = makeCtx('pkg-c', '3.0.0', { + dependencies: { 'pkg-b': '^1.0.0' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + await writeJson(pkgC.dir, 'package.json', pkgC.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB, pkgC], releases); + + assert.equal(releases.length, 3); + assert.equal(releases[1].name, 'pkg-b'); + assert.equal(releases[1].version, '2.0.1'); + assert.equal(releases[2].name, 'pkg-c'); + assert.equal(releases[2].version, '3.0.1'); + }); + + it('should not update devDependencies', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '1.0.0', { + devDependencies: { 'pkg-a': '^0.9.0' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases); + + const result = await readJson(pkgB.dir, 'package.json'); + const devDeps = result.devDependencies as Record; + assert.equal(devDeps['pkg-a'], '^0.9.0'); + assert.equal(releases.length, 1); + }); + + describe('preserves version prefix', () => { + it('should preserve ^ prefix', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '1.0.0', { + dependencies: { 'pkg-a': '^1.0.0' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases); + + const result = await readJson(pkgB.dir, 'package.json'); + const deps = result.dependencies as Record; + assert.equal(deps['pkg-a'], '^1.1.0'); + }); + + it('should preserve ~ prefix', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '1.0.0', { + dependencies: { 'pkg-a': '~1.0.0' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases); + + const result = await readJson(pkgB.dir, 'package.json'); + const deps = result.dependencies as Record; + assert.equal(deps['pkg-a'], '~1.1.0'); + }); + + it('should preserve exact version (no prefix)', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '1.0.0', { + dependencies: { 'pkg-a': '1.0.0' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases); + + const result = await readJson(pkgB.dir, 'package.json'); + const deps = result.dependencies as Record; + assert.equal(deps['pkg-a'], '1.1.0'); + }); + + it('should bump workspace:* dependent in auto mode (PM resolves at publish)', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '2.0.0', { + dependencies: { 'pkg-a': 'workspace:*' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases); + + assert.equal(releases.length, 2); + assert.equal(releases[1].name, 'pkg-b'); + assert.equal(releases[1].version, '2.0.1'); + const result = await readJson(pkgB.dir, 'package.json'); + const deps = result.dependencies as Record; + assert.equal(deps['pkg-a'], 'workspace:*'); + }); + + it('should bump workspace:* dependent in all mode', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '2.0.0', { + dependencies: { 'pkg-a': 'workspace:*' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases); + + assert.equal(releases.length, 2); + assert.equal(releases[1].name, 'pkg-b'); + assert.equal(releases[1].version, '2.0.1'); + const result = await readJson(pkgB.dir, 'package.json'); + const deps = result.dependencies as Record; + assert.equal(deps['pkg-a'], 'workspace:*'); + }); + + it('should preserve >= prefix', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '1.0.0', { + dependencies: { 'pkg-a': '>=1.0.0' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases); + + const result = await readJson(pkgB.dir, 'package.json'); + const deps = result.dependencies as Record; + assert.equal(deps['pkg-a'], '>=1.1.0'); + }); + }); + + describe('lazy mode', () => { + it('should skip when ^ range covers new version (minor bump)', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '1.0.0', { + dependencies: { 'pkg-a': '^1.0.0' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases, 'lazy'); + + assert.equal(releases.length, 1); + const result = await readJson(pkgB.dir, 'package.json'); + const deps = result.dependencies as Record; + assert.equal(deps['pkg-a'], '^1.0.0'); + }); + + it('should bump when ^ range does not cover new version (major bump)', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '1.0.0', { + dependencies: { 'pkg-a': '^1.0.0' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '2.0.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases, 'lazy'); + + assert.equal(releases.length, 2); + assert.equal(releases[1].name, 'pkg-b'); + const result = await readJson(pkgB.dir, 'package.json'); + const deps = result.dependencies as Record; + assert.equal(deps['pkg-a'], '^2.0.0'); + }); + + it('should bump when ~ range does not cover minor bump', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '1.0.0', { + dependencies: { 'pkg-a': '~1.0.0' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases, 'lazy'); + + assert.equal(releases.length, 2); + const result = await readJson(pkgB.dir, 'package.json'); + const deps = result.dependencies as Record; + assert.equal(deps['pkg-a'], '~1.1.0'); + }); + + it('should skip when ~ range covers patch bump', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '1.0.0', { + dependencies: { 'pkg-a': '~1.0.0' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.0.1', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases, 'lazy'); + + assert.equal(releases.length, 1); + }); + + it('should always bump exact version', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '1.0.0', { + dependencies: { 'pkg-a': '1.0.0' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.0.1', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases, 'lazy'); + + assert.equal(releases.length, 2); + const result = await readJson(pkgB.dir, 'package.json'); + const deps = result.dependencies as Record; + assert.equal(deps['pkg-a'], '1.0.1'); + }); + + it('should bump workspace:* deps (exact)', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '2.0.0', { + dependencies: { 'pkg-a': 'workspace:*' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases, 'lazy'); + + assert.equal(releases.length, 2); + assert.equal(releases[1].version, '2.0.1'); + }); + + it('should skip workspace:^ when minor bump (range covers)', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '2.0.0', { + dependencies: { 'pkg-a': 'workspace:^' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases, 'lazy'); + + assert.equal(releases.length, 1); + }); + + it('should bump workspace:^ when major bump (range broken)', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '2.0.0', { + dependencies: { 'pkg-a': 'workspace:^' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '2.0.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases, 'lazy'); + + assert.equal(releases.length, 2); + assert.equal(releases[1].version, '2.0.1'); + }); + + it('should skip workspace:~ when patch bump (range covers)', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '2.0.0', { + dependencies: { 'pkg-a': 'workspace:~' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.0.1', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases, 'lazy'); + + assert.equal(releases.length, 1); + }); + + it('should bump workspace:~ when minor bump (range broken)', async () => { + const pkgA = makeCtx('pkg-a', '1.0.0'); + const pkgB = makeCtx('pkg-b', '2.0.0', { + dependencies: { 'pkg-a': 'workspace:~' }, + }); + await writeJson(pkgB.dir, 'package.json', pkgB.pack); + + const releases = [{ name: 'pkg-a', version: '1.1.0', ctx: pkgA }]; + await cascadeDeps([pkgA, pkgB], releases, 'lazy'); + + assert.equal(releases.length, 2); + assert.equal(releases[1].version, '2.0.1'); + }); + }); +}); + +describe('extractPrefix', () => { + it('should extract ^', () => { + assert.equal(extractPrefix('^1.0.0'), '^'); + }); + + it('should extract ~', () => { + assert.equal(extractPrefix('~1.0.0'), '~'); + }); + + it('should extract empty for exact', () => { + assert.equal(extractPrefix('1.0.0'), ''); + }); + + it('should extract >=', () => { + assert.equal(extractPrefix('>=1.0.0'), '>='); + }); + + it('should extract empty for workspace protocol', () => { + assert.equal(extractPrefix('workspace:*'), ''); + assert.equal(extractPrefix('workspace:^1.0.0'), ''); + assert.equal(extractPrefix('workspace:~1.0.0'), ''); + }); +}); + +describe('resolveWorkspace', () => { + it('should resolve workspace:* to exact version', () => { + assert.equal(resolveWorkspace('workspace:*', '1.2.3'), '1.2.3'); + }); + + it('should resolve workspace:^ to caret range', () => { + assert.equal(resolveWorkspace('workspace:^', '1.2.3'), '^1.2.3'); + }); + + it('should resolve workspace:~ to tilde range', () => { + assert.equal(resolveWorkspace('workspace:~', '1.2.3'), '~1.2.3'); + }); + + it('should resolve unknown workspace protocol to exact', () => { + assert.equal(resolveWorkspace('workspace:>=', '1.2.3'), '1.2.3'); + }); +}); diff --git a/tests/config.test.ts b/tests/config.test.ts new file mode 100644 index 0000000..60a2905 --- /dev/null +++ b/tests/config.test.ts @@ -0,0 +1,202 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +import { CHANGELOG_FILE, OTHERS, TITLE, rBreak, rIgnore, rParse, rRepo, whitelist } from '../src/config'; + +describe('rParse', () => { + it('should match simple commit', () => { + const m = rParse.exec('feat: add feature'); + assert.ok(m); + assert.equal(m[1], 'feat'); + assert.equal(m[2], undefined); + assert.equal(m[3], undefined); + assert.equal(m[4], 'add feature'); + }); + + it('should match scoped commit', () => { + const m = rParse.exec('fix(core): bug fix'); + assert.ok(m); + assert.equal(m[1], 'fix'); + assert.equal(m[2], 'core'); + assert.equal(m[3], undefined); + assert.equal(m[4], 'bug fix'); + }); + + it('should match breaking change with !', () => { + const m = rParse.exec('feat!: breaking feature'); + assert.ok(m); + assert.equal(m[1], 'feat'); + assert.equal(m[3], '!'); + assert.equal(m[4], 'breaking feature'); + }); + + it('should match scoped breaking change', () => { + const m = rParse.exec('fix(api)!: breaking fix'); + assert.ok(m); + assert.equal(m[1], 'fix'); + assert.equal(m[2], 'api'); + assert.equal(m[3], '!'); + assert.equal(m[4], 'breaking fix'); + }); + + it('should match scope with special chars', () => { + const m = rParse.exec('feat(my-scope/sub): description'); + assert.ok(m); + assert.equal(m[1], 'feat'); + assert.equal(m[2], 'my-scope/sub'); + assert.equal(m[4], 'description'); + }); + + it('should not match non-conventional commit', () => { + assert.equal(rParse.exec('random commit message'), null); + }); + + it('should match empty type with empty string', () => { + const m = rParse.exec(': no type'); + assert.ok(m); + assert.equal(m[1], ''); + assert.equal(m[4], 'no type'); + }); +}); + +describe('rBreak', () => { + it('should match BREAKING CHANGES:', () => { + assert.ok(rBreak.test('BREAKING CHANGES: something')); + }); + + it('should match BREAKING CHANGE: (singular)', () => { + assert.ok(rBreak.test('BREAKING CHANGE: something')); + }); + + it('should be case insensitive', () => { + assert.ok(rBreak.test('breaking changes: something')); + assert.ok(rBreak.test('Breaking Change: something')); + }); + + it('should not match unrelated text', () => { + assert.ok(!rBreak.test('no breaking here')); + }); +}); + +describe('rIgnore', () => { + it('should match Merge pull request', () => { + assert.ok(rIgnore.test('Merge pull request #1 from branch')); + }); + + it('should match Merge remote-tracking branch', () => { + assert.ok(rIgnore.test('Merge remote-tracking branch origin/main')); + }); + + it('should match Merge branch', () => { + assert.ok(rIgnore.test('Merge branch main')); + }); + + it('should match Automatic merge', () => { + assert.ok(rIgnore.test('Automatic merge from feature-branch')); + }); + + it('should match Auto-merged', () => { + assert.ok(rIgnore.test('Auto-merged feature in main')); + }); + + it('should match Merged', () => { + assert.ok(rIgnore.test('Merged feature into main')); + }); + + it('should match Revert', () => { + assert.ok(rIgnore.test('Revert "some commit"')); + assert.ok(rIgnore.test('revert some commit')); + }); + + it('should match fixup', () => { + assert.ok(rIgnore.test('fixup! feat: something')); + }); + + it('should match squash', () => { + assert.ok(rIgnore.test('squash! feat: something')); + }); + + it('should not match regular commit', () => { + assert.ok(!rIgnore.test('feat: new feature')); + assert.ok(!rIgnore.test('fix: bug fix')); + }); +}); + +describe('rRepo', () => { + it('should match github https URL with .git', () => { + const m = rRepo.exec('https://github.com/user/repo.git'); + assert.ok(m); + assert.equal(m[1], 'user'); + assert.equal(m[2], 'repo'); + }); + + it('should match github https URL without .git', () => { + const m = rRepo.exec('https://github.com/user/repo'); + assert.ok(m); + assert.equal(m[1], 'user'); + assert.equal(m[2], 'repo'); + }); + + it('should match SSH URL (colon included in user)', () => { + const m = rRepo.exec('git@github.com:user/repo.git'); + assert.ok(m); + assert.equal(m[1], 'com:user'); + assert.equal(m[2], 'repo'); + }); + + it('should match gitlab URL', () => { + const m = rRepo.exec('https://gitlab.com/org/project.git'); + assert.ok(m); + assert.equal(m[1], 'org'); + assert.equal(m[2], 'project'); + }); +}); + +describe('whitelist', () => { + it('should have all expected commit types', () => { + const types = [ + 'break', + 'feat', + 'build', + 'chore', + 'ci', + 'docs', + 'fix', + 'perf', + 'refactor', + 'revert', + 'style', + 'test', + ]; + for (const type of types) { + assert.ok(type in whitelist, `missing type: ${type}`); + assert.equal(typeof whitelist[type], 'string'); + } + }); + + it('should map break to Breaking changes', () => { + assert.equal(whitelist.break, 'Breaking changes'); + }); + + it('should map feat to Features', () => { + assert.equal(whitelist.feat, 'Features'); + }); + + it('should map fix to Bug Fixes', () => { + assert.equal(whitelist.fix, 'Bug Fixes'); + }); +}); + +describe('constants', () => { + it('OTHERS should be defined', () => { + assert.equal(OTHERS, 'Others changes'); + }); + + it('TITLE should contain Changelog header', () => { + assert.ok(TITLE.startsWith('# Changelog')); + }); + + it('CHANGELOG_FILE should be CHANGELOG.md', () => { + assert.equal(CHANGELOG_FILE, 'CHANGELOG.md'); + }); +}); diff --git a/tests/git.test.ts b/tests/git.test.ts new file mode 100644 index 0000000..2f06c98 --- /dev/null +++ b/tests/git.test.ts @@ -0,0 +1,62 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +import { parseCommits } from '../src/git'; + +const DELIM = '###DELIM%!@^%$###'; +const ESCAPE = '###ESCAPE%!@^%$###'; + +function wrap(value: string): string { + return `${ESCAPE}${value}${ESCAPE}`; +} + +function makeRawCommit(short: string, hash: string, title: string, body: string): string { + return `{ "short": ${wrap(short)}, "hash": ${wrap(hash)}, "title": ${wrap(title)}, "body": ${wrap(body)} }`; +} + +describe('parseCommits', () => { + it('should parse single commit', () => { + const raw = makeRawCommit('abc1234', 'abc1234567890', 'feat: add feature', '') + DELIM; + const result = parseCommits(raw); + assert.equal(result.length, 1); + assert.equal(result[0].short, 'abc1234'); + assert.equal(result[0].hash, 'abc1234567890'); + assert.equal(result[0].title, 'feat: add feature'); + assert.equal(result[0].body, ''); + }); + + it('should parse multiple commits and reverse order', () => { + const raw = + makeRawCommit('aaa', 'aaa111', 'feat: first', '') + + DELIM + + makeRawCommit('bbb', 'bbb222', 'fix: second', '') + + DELIM; + const result = parseCommits(raw); + assert.equal(result.length, 2); + assert.equal(result[0].title, 'fix: second'); + assert.equal(result[1].title, 'feat: first'); + }); + + it('should handle commit body', () => { + const raw = makeRawCommit('abc', 'abcfull', 'feat: thing', 'some body text') + DELIM; + const result = parseCommits(raw); + assert.equal(result[0].body, 'some body text'); + }); + + it('should handle special characters in title', () => { + const raw = makeRawCommit('abc', 'abcfull', 'fix: handle "quoted" and \\backslash', '') + DELIM; + const result = parseCommits(raw); + assert.equal(result[0].title, 'fix: handle "quoted" and \\backslash'); + }); + + it('should return empty array for empty input', () => { + const result = parseCommits(''); + assert.equal(result.length, 0); + }); + + it('should handle multiline body', () => { + const raw = makeRawCommit('abc', 'abcfull', 'feat: thing', 'line1\nline2\nline3') + DELIM; + const result = parseCommits(raw); + assert.equal(result[0].body, 'line1\nline2\nline3'); + }); +}); diff --git a/tests/integration.test.ts b/tests/integration.test.ts new file mode 100644 index 0000000..2c8f3fb --- /dev/null +++ b/tests/integration.test.ts @@ -0,0 +1,532 @@ +import { afterEach, beforeEach, describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { execSync } from 'node:child_process'; +import { promises as fs, mkdirSync, writeFileSync } from 'node:fs'; +import { mkdtemp, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { checkBranch, getCommitsRaw, getHash, getTag, parseCommits, writeGit, writeWorkspaceGit } from '../src/git'; +import { writeVersion } from '../src/package'; +import { Pack, PackageContext } from '../src/types'; +import { parseJSON, retry, sp } from '../src/utils'; +import { parse } from '../src/parser'; +import { detectPackageChanges } from '../src/changes'; + +let tempDir: string; +let originalCwd: string; + +function git(args: string, cwd?: string): string { + return execSync(`git ${args}`, { cwd: cwd ?? tempDir, encoding: 'utf8', stdio: 'pipe' }).trim(); +} + +function writeJson(dir: string, file: string, data: object): void { + mkdirSync(dir, { recursive: true }); + writeFileSync(join(dir, file), `${JSON.stringify(data, undefined, 2)}\n`, 'utf8'); +} + +function commit(msg: string, files?: string[]): void { + if (files !== undefined) { + for (const f of files) { + git(`add "${f}"`); + } + } else { + git('add -A'); + } + git(`commit --allow-empty -m "${msg}"`); +} + +beforeEach(async () => { + originalCwd = process.cwd(); + tempDir = await mkdtemp(join(tmpdir(), 'sr-int-')); + git('init'); + git('config user.email "test@test.com"'); + git('config user.name "Test"'); + writeJson(tempDir, 'package.json', { name: 'test-pkg', version: '1.0.0' }); + commit('chore: init'); +}); + +afterEach(async () => { + process.chdir(originalCwd); + await rm(tempDir, { recursive: true, force: true }); +}); + +describe('git integration', () => { + describe('getHash', () => { + it('should return current HEAD hash', async () => { + process.chdir(tempDir); + const hash = await getHash(); + assert.match(hash, /^[a-f0-9]{40}$/); + }); + }); + + describe('getTag', () => { + it('should return latest tag', async () => { + process.chdir(tempDir); + git('tag v1.0.0'); + commit('feat: new feature'); + const tag = await getTag(); + assert.equal(tag, 'v1.0.0'); + }); + + it('should return tag matching pattern', async () => { + process.chdir(tempDir); + git('tag v1.0.0'); + git('tag other-tag'); + commit('feat: new feature'); + const tag = await getTag('v*'); + assert.equal(tag, 'v1.0.0'); + }); + + it('should fallback to first commit when no tags', async () => { + process.chdir(tempDir); + commit('feat: second'); + const tag = await getTag(); + assert.match(tag, /^[a-f0-9]{40}$/); + }); + }); + + describe('getCommitsRaw + parseCommits', () => { + it('should return commits between tag and HEAD', async () => { + process.chdir(tempDir); + git('tag v1.0.0'); + writeJson(tempDir, 'src.ts', { code: true }); + commit('feat: add feature'); + writeJson(tempDir, 'src2.ts', { code: true }); + commit('fix: bug fix'); + const hash = await getHash(); + const raw = await getCommitsRaw('v1.0.0', hash); + const commits = parseCommits(raw); + assert.equal(commits.length, 2); + }); + + it('should filter by file path', async () => { + process.chdir(tempDir); + git('tag v1.0.0'); + await fs.mkdir(join(tempDir, 'src'), { recursive: true }); + await fs.mkdir(join(tempDir, 'docs'), { recursive: true }); + await fs.writeFile(join(tempDir, 'src', 'index.ts'), 'code', 'utf8'); + commit('feat: src change', ['src/index.ts']); + await fs.writeFile(join(tempDir, 'docs', 'readme.md'), 'docs', 'utf8'); + commit('docs: readme', ['docs/readme.md']); + const hash = await getHash(); + const raw = await getCommitsRaw('v1.0.0', hash, ['src']); + const commits = parseCommits(raw); + assert.equal(commits.length, 1); + assert.ok(commits[0].title.includes('src change')); + }); + }); + + describe('writeGit', () => { + it('should create commit and tag', async () => { + process.chdir(tempDir); + await fs.writeFile(join(tempDir, 'package.json'), JSON.stringify({ version: '1.1.0' }, undefined, 2), 'utf8'); + await writeGit('v1.1.0'); + const logOutput = git('log --oneline -1'); + assert.ok(logOutput.includes('chore(release): v1.1.0')); + const tags = git('tag'); + assert.ok(tags.includes('v1.1.0')); + }); + + it('should handle missing package-lock.json', async () => { + process.chdir(tempDir); + await fs.writeFile(join(tempDir, 'package.json'), JSON.stringify({ version: '1.1.0' }, undefined, 2), 'utf8'); + await writeGit('v1.1.0'); + const logOutput = git('log --oneline -1'); + assert.ok(logOutput.includes('v1.1.0')); + }); + }); + + describe('writeWorkspaceGit', () => { + it('should create single commit with multiple tags', async () => { + process.chdir(tempDir); + await fs.writeFile(join(tempDir, 'package.json'), JSON.stringify({ version: '2.0.0' }, undefined, 2), 'utf8'); + await writeWorkspaceGit([ + { name: '@org/core', version: '1.1.0' }, + { name: '@org/utils', version: '2.0.0' }, + ]); + + const logOutput = git('log --oneline -1'); + assert.ok(logOutput.includes('chore(release): publish')); + + const logBody = git('log -1 --format=%b'); + assert.ok(logBody.includes('@org/core@1.1.0')); + assert.ok(logBody.includes('@org/utils@2.0.0')); + + const tags = git('tag'); + assert.ok(tags.includes('@org/core@1.1.0')); + assert.ok(tags.includes('@org/utils@2.0.0')); + }); + + it('should not capture gitignored files', async () => { + process.chdir(tempDir); + await fs.writeFile(join(tempDir, '.gitignore'), 'secret.env\n', 'utf8'); + git('add .gitignore'); + commit('chore: add gitignore'); + + await fs.writeFile(join(tempDir, 'secret.env'), 'TOKEN=abc', 'utf8'); + await fs.writeFile(join(tempDir, 'package.json'), JSON.stringify({ version: '2.0.0' }, undefined, 2), 'utf8'); + await writeWorkspaceGit([{ name: 'pkg', version: '1.0.0' }]); + + const diff = git('show --stat HEAD'); + assert.ok(!diff.includes('secret.env')); + }); + }); +}); + +describe('detectPackageChanges integration', () => { + it('should detect changes in package directory', async () => { + process.chdir(tempDir); + const pkgDir = join(tempDir, 'packages', 'core'); + await fs.mkdir(pkgDir, { recursive: true }); + await fs.writeFile(join(pkgDir, 'package.json'), JSON.stringify({ name: '@org/core', version: '1.0.0' }), 'utf8'); + await fs.writeFile(join(pkgDir, 'index.ts'), 'export const a = 1;', 'utf8'); + commit('feat: init core'); + git('tag @org/core@1.0.0'); + + await fs.writeFile(join(pkgDir, 'index.ts'), 'export const a = 2;', 'utf8'); + commit('fix: fix core bug', ['packages/core/index.ts']); + + const hash = await getHash(); + const ctx: PackageContext = { + dir: pkgDir, + name: '@org/core', + version: '1.0.0', + pack: { name: '@org/core', version: '1.0.0' }, + tagPrefix: '@org/core@', + files: [pkgDir], + }; + + const result = await detectPackageChanges(ctx, hash, ''); + assert.ok(result !== null); + assert.equal(result.ctx.name, '@org/core'); + assert.equal(result.config.isEmpty, false); + }); + + it('should return null when no changes in package directory', async () => { + process.chdir(tempDir); + const coreDir = join(tempDir, 'packages', 'core'); + const utilsDir = join(tempDir, 'packages', 'utils'); + await fs.mkdir(coreDir, { recursive: true }); + await fs.mkdir(utilsDir, { recursive: true }); + await fs.writeFile(join(coreDir, 'package.json'), JSON.stringify({ name: '@org/core', version: '1.0.0' }), 'utf8'); + await fs.writeFile(join(coreDir, 'index.ts'), 'core', 'utf8'); + await fs.writeFile( + join(utilsDir, 'package.json'), + JSON.stringify({ name: '@org/utils', version: '1.0.0' }), + 'utf8' + ); + await fs.writeFile(join(utilsDir, 'index.ts'), 'utils', 'utf8'); + commit('feat: init both'); + git('tag @org/utils@1.0.0'); + + // Only change core, not utils + await fs.writeFile(join(coreDir, 'index.ts'), 'core v2', 'utf8'); + commit('fix: fix core only', ['packages/core/index.ts']); + + const hash = await getHash(); + const ctx: PackageContext = { + dir: utilsDir, + name: '@org/utils', + version: '1.0.0', + pack: { name: '@org/utils', version: '1.0.0' }, + tagPrefix: '@org/utils@', + files: [utilsDir], + }; + + const result = await detectPackageChanges(ctx, hash, ''); + assert.equal(result, null); + }); + + it('should detect major change from conventional commit', async () => { + process.chdir(tempDir); + const pkgDir = join(tempDir, 'packages', 'api'); + await fs.mkdir(pkgDir, { recursive: true }); + await fs.writeFile(join(pkgDir, 'package.json'), JSON.stringify({ name: '@org/api', version: '1.0.0' }), 'utf8'); + await fs.writeFile(join(pkgDir, 'index.ts'), 'v1', 'utf8'); + commit('feat: init api'); + git('tag @org/api@1.0.0'); + + await fs.writeFile(join(pkgDir, 'index.ts'), 'v2', 'utf8'); + commit('feat!: breaking api change', ['packages/api/index.ts']); + + const hash = await getHash(); + const ctx: PackageContext = { + dir: pkgDir, + name: '@org/api', + version: '1.0.0', + pack: { name: '@org/api', version: '1.0.0' }, + tagPrefix: '@org/api@', + files: [pkgDir], + }; + + const result = await detectPackageChanges(ctx, hash, ''); + assert.ok(result !== null); + assert.equal(result.config.isMajor, true); + }); +}); + +describe('full single-package flow', () => { + it('should parse commits and determine version bump', async () => { + process.chdir(tempDir); + git('tag v1.0.0'); + + await fs.writeFile(join(tempDir, 'src.ts'), 'feature', 'utf8'); + commit('feat: new feature'); + await fs.writeFile(join(tempDir, 'src2.ts'), 'fix', 'utf8'); + commit('fix: bug fix'); + + const hash = await getHash(); + const tag = await getTag(); + const raw = await getCommitsRaw(tag, hash); + const commits = parseCommits(raw); + const config = parse(commits, ''); + + assert.equal(config.isEmpty, false); + assert.equal(config.isMinor, true); + assert.equal(config.isMajor, false); + assert.ok('feat' in config.groups); + assert.ok('fix' in config.groups); + }); + + it('should detect breaking change in body', async () => { + process.chdir(tempDir); + git('tag v1.0.0'); + + await fs.writeFile(join(tempDir, 'src.ts'), 'refactor', 'utf8'); + git('add -A'); + git('commit -m "refactor: big change" -m "BREAKING CHANGES: removed old API"'); + + const hash = await getHash(); + const raw = await getCommitsRaw('v1.0.0', hash); + const commits = parseCommits(raw); + const config = parse(commits, ''); + + assert.equal(config.isMajor, true); + }); +}); + +describe('sp() error handling', () => { + it('should reject on non-existent command', async () => { + await assert.rejects(() => sp('nonexistent-command-12345', []), { code: 'ENOENT' }); + }); + + it('should reject on non-zero exit code', async () => { + await assert.rejects(() => sp('node', ['-e', 'process.exit(1)'])); + }); + + it('should resolve stdout on success', async () => { + const result = await sp('node', ['-e', 'process.stdout.write("hello")']); + assert.equal(result, 'hello'); + }); + + it('should trim output', async () => { + const result = await sp('node', ['-e', 'process.stdout.write(" hello ")']); + assert.equal(result, 'hello'); + }); +}); + +describe('createTag (duplicate tag)', () => { + it('should skip if tag already exists', async () => { + process.chdir(tempDir); + git('tag v1.0.0'); + await fs.writeFile(join(tempDir, 'package.json'), JSON.stringify({ version: '1.0.1' }, undefined, 2), 'utf8'); + // writeGit tries to create tag v1.0.0 again — should not throw + await writeGit('v1.0.0'); + const tags = git('tag -l v1.0.0'); + assert.ok(tags.includes('v1.0.0')); + }); + + it('should create tag if it does not exist', async () => { + process.chdir(tempDir); + await fs.writeFile(join(tempDir, 'package.json'), JSON.stringify({ version: '2.0.0' }, undefined, 2), 'utf8'); + await writeGit('v2.0.0'); + const tags = git('tag -l v2.0.0'); + assert.ok(tags.includes('v2.0.0')); + }); +}); + +describe('getTag fallback (no tags, no commits)', () => { + it('should handle repo with commits but no tags', async () => { + process.chdir(tempDir); + commit('feat: second commit'); + const tag = await getTag(); + assert.match(tag, /^[a-f0-9]{40}$/); + }); +}); + +describe('checkBranch', () => { + it('should not throw on normal branch', async () => { + process.chdir(tempDir); + await checkBranch(); + }); + + it('should warn on detached HEAD', async () => { + process.chdir(tempDir); + commit('feat: extra'); + const branch = git('rev-parse --abbrev-ref HEAD'); + const hash = git('rev-parse HEAD'); + git(`checkout ${hash} --quiet`); + // Should not throw, just warn + await checkBranch(); + git(`checkout ${branch} --quiet`); + }); +}); + +describe('parseJSON', () => { + it('should parse JSON with BOM', () => { + const result = parseJSON('\uFEFF{"version":"1.0.0"}'); + assert.equal(result.version, '1.0.0'); + }); + + it('should parse JSON without BOM', () => { + const result = parseJSON('{"version":"2.0.0"}'); + assert.equal(result.version, '2.0.0'); + }); + + it('should throw on invalid JSON', () => { + assert.throws(() => parseJSON('not json')); + }); +}); + +describe('writeVersion after preversion hook modifies package.json', () => { + it('should preserve hook changes except version', async () => { + process.chdir(tempDir); + const pkgPath = join(tempDir, 'package.json'); + const pkgData = { + name: 'test', + version: '1.0.0', + scripts: { + preversion: `node -e "const p=require('./package.json');p.description='added';require('fs').writeFileSync('./package.json',JSON.stringify(p,null,2)+'\\n')"`, + }, + }; + await fs.writeFile(pkgPath, `${JSON.stringify(pkgData, undefined, 2)}\n`, 'utf8'); + + await writeVersion('2.0.0'); + + const result = JSON.parse(await fs.readFile(pkgPath, 'utf8')) as Pack; + assert.equal(result.version, '2.0.0'); + assert.equal(result.description, 'added'); + }); +}); + +describe('partial publish failure', () => { + it('should collect errors from publishPackage', async () => { + const errors: string[] = []; + const failingPromise = Promise.reject('network error'); + await failingPromise.catch(e => { + errors.push(`registry — ${e}`); + }); + assert.equal(errors.length, 1); + assert.ok(errors[0].includes('network error')); + }); +}); + +describe('retry', () => { + it('should succeed on first attempt', async () => { + let calls = 0; + const result = await retry(async () => { + calls++; + return 'ok'; + }); + assert.equal(result, 'ok'); + assert.equal(calls, 1); + }); + + it('should retry on failure and succeed', async () => { + let calls = 0; + const result = await retry( + async () => { + calls++; + if (calls < 3) { + throw new Error('fail'); + } + return 'ok'; + }, + 3, + 0 + ); + assert.equal(result, 'ok'); + assert.equal(calls, 3); + }); + + it('should throw after all attempts fail', async () => { + let calls = 0; + await assert.rejects( + () => + retry( + async () => { + calls++; + throw new Error('always fail'); + }, + 2, + 0 + ), + { message: 'always fail' } + ); + assert.equal(calls, 2); + }); +}); + +describe('release create-or-update pattern', () => { + it('should succeed on first create', async () => { + const calls: string[] = []; + const mockCreate = async () => { + calls.push('create'); + return 'created'; + }; + + const result = await mockCreate(); + assert.equal(result, 'created'); + assert.deepEqual(calls, ['create']); + }); + + it('should fall back to update when create fails', async () => { + const calls: string[] = []; + const mockCreate = async () => { + calls.push('create'); + throw new Error('422'); + }; + const mockGet = async () => { + calls.push('get'); + return { id: 42 }; + }; + const mockUpdate = async (id: number) => { + calls.push(`update:${id}`); + return 'updated'; + }; + + let result: string; + try { + result = await mockCreate(); + } catch { + const existing = await mockGet(); + result = await mockUpdate(existing.id); + } + + assert.equal(result, 'updated'); + assert.deepEqual(calls, ['create', 'get', 'update:42']); + }); + + it('should throw original error when update also fails', async () => { + const originalError = new Error('network error'); + const mockCreate = async () => { + throw originalError; + }; + const mockUpdate = async () => { + throw new Error('also failed'); + }; + + try { + await mockCreate(); + assert.fail('should throw'); + } catch (e) { + try { + await mockUpdate(); + assert.fail('should throw'); + } catch { + assert.equal(e, originalError); + } + } + }); +}); diff --git a/tests/log.test.ts b/tests/log.test.ts new file mode 100644 index 0000000..299fe59 --- /dev/null +++ b/tests/log.test.ts @@ -0,0 +1,58 @@ +import { afterEach, describe, it, mock } from 'node:test'; +import assert from 'node:assert/strict'; + +import { log } from '../src/log'; + +describe('log', () => { + afterEach(() => { + mock.restoreAll(); + }); + + it('should call console.info for ok type', () => { + const fn = mock.method(console, 'info'); + log('ok', 'Test', 'message'); + assert.equal(fn.mock.callCount(), 1); + const args = fn.mock.calls[0].arguments; + assert.ok(String(args[0]).includes('Test:')); + assert.equal(args[1], 'message'); + }); + + it('should call console.error for error type', () => { + const fn = mock.method(console, 'error'); + log('error', 'Err', 'details'); + assert.equal(fn.mock.callCount(), 1); + const args = fn.mock.calls[0].arguments; + assert.ok(String(args[0]).includes('Err:')); + }); + + it('should call console.warn for warn type', () => { + const fn = mock.method(console, 'warn'); + log('warn', 'Warning', 'info'); + assert.equal(fn.mock.callCount(), 1); + const args = fn.mock.calls[0].arguments; + assert.ok(String(args[0]).includes('Warning:')); + }); + + it('should call console.info for info type', () => { + const fn = mock.method(console, 'info'); + log('info', 'Info', 'data'); + assert.equal(fn.mock.callCount(), 1); + }); + + it('should pass multiple message arguments', () => { + const fn = mock.method(console, 'info'); + log('ok', 'Multi', 'a', 'b', 'c'); + const args = fn.mock.calls[0].arguments; + assert.equal(args[1], 'a'); + assert.equal(args[2], 'b'); + assert.equal(args[3], 'c'); + }); + + it('should include ANSI color codes', () => { + const fn = mock.method(console, 'error'); + log('error', 'Red', 'text'); + const args = fn.mock.calls[0].arguments; + assert.ok(String(args[0]).includes('\x1b[31m')); + assert.ok(String(args[0]).includes('\x1b[0m')); + }); +}); diff --git a/tests/markdown.test.ts b/tests/markdown.test.ts new file mode 100644 index 0000000..412772f --- /dev/null +++ b/tests/markdown.test.ts @@ -0,0 +1,114 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +import { makeMD } from '../src/markdown'; + +function makeConfig(groups: Record, flags: { isMajor?: boolean; isMinor?: boolean } = {}) { + return { + groups, + isMajor: flags.isMajor ?? false, + isMinor: flags.isMinor ?? false, + isEmpty: Object.keys(groups).length === 0, + }; +} + +describe('makeMD', () => { + it('should generate markdown with version link when URL provided', () => { + const md = makeMD({ + url: 'https://github.com/user/repo', + config: makeConfig({ feat: ['add feature'] }), + version: 'v1.0.0', + tag: 'v0.9.0', + date: '2024-1-15', + }); + assert.ok(md.includes('## [v1.0.0](https://github.com/user/repo/compare/v0.9.0...v1.0.0) (2024-1-15)')); + }); + + it('should generate markdown without link when URL is empty', () => { + const md = makeMD({ + url: '', + config: makeConfig({ feat: ['add feature'] }), + version: 'v1.0.0', + tag: 'v0.9.0', + date: '2024-1-15', + }); + assert.ok(md.includes('## v1.0.0 (2024-1-15)')); + }); + + it('should use whitelist title for known types', () => { + const md = makeMD({ + url: '', + config: makeConfig({ feat: ['feature'], fix: ['bugfix'] }), + version: 'v1.0.0', + tag: 'v0.9.0', + date: '2024-1-1', + }); + assert.ok(md.includes('### Features')); + assert.ok(md.includes('### Bug Fixes')); + }); + + it('should use raw type name for unknown types', () => { + const md = makeMD({ + url: '', + config: makeConfig({ custom: ['something'] }), + version: 'v1.0.0', + tag: 'v0.9.0', + date: '2024-1-1', + }); + assert.ok(md.includes('### custom')); + }); + + it('should list items with dash prefix', () => { + const md = makeMD({ + url: '', + config: makeConfig({ feat: ['first feature', 'second feature'] }), + version: 'v1.0.0', + tag: 'v0.9.0', + date: '2024-1-1', + }); + assert.ok(md.includes('- first feature')); + assert.ok(md.includes('- second feature')); + }); + + it('should separate groups with blank lines', () => { + const md = makeMD({ + url: '', + config: makeConfig({ feat: ['feature'], fix: ['bugfix'] }), + version: 'v1.0.0', + tag: 'v0.9.0', + date: '2024-1-1', + }); + const lines = md.split('\n'); + const featIdx = lines.findIndex(l => l.includes('### Features')); + const fixIdx = lines.findIndex(l => l.includes('### Bug Fixes')); + assert.ok(featIdx < fixIdx); + assert.ok(lines.slice(featIdx, fixIdx).some(l => l === '')); + }); + + it('should produce complete markdown structure', () => { + const md: string = makeMD({ + url: 'https://github.com/user/repo', + config: makeConfig( + { feat: ['add stuff ([abc](https://github.com/user/repo/commit/abc123))'] }, + { isMinor: true } + ), + version: 'v1.1.0', + tag: 'v1.0.0', + date: '2024-6-15', + }); + assert.ok(md.startsWith('\n## ')); + assert.ok(md.endsWith('\n')); + }); + + it('should handle empty groups', () => { + const md = makeMD({ + url: '', + config: makeConfig({}), + version: 'v1.0.0', + tag: 'v0.9.0', + date: '2024-01-01', + }); + assert.ok(md.includes('## v1.0.0')); + assert.ok(!md.includes('###')); + }); +}); diff --git a/tests/npm.test.ts b/tests/npm.test.ts new file mode 100644 index 0000000..36be7c3 --- /dev/null +++ b/tests/npm.test.ts @@ -0,0 +1,121 @@ +import { afterEach, beforeEach, describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { promises as fs } from 'node:fs'; +import { mkdtemp, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { detectPM, getVersionType } from '../src/npm'; + +function makeConfig(flags: { isMajor?: boolean; isMinor?: boolean } = {}) { + return { + groups: {}, + isMajor: flags.isMajor ?? false, + isMinor: flags.isMinor ?? false, + isEmpty: false, + }; +} + +describe('getVersionType', () => { + it('should return major for breaking changes', () => { + assert.equal(getVersionType(makeConfig({ isMajor: true })), 'major'); + }); + + it('should return minor for features', () => { + assert.equal(getVersionType(makeConfig({ isMinor: true })), 'minor'); + }); + + it('should return patch for fixes/other', () => { + assert.equal(getVersionType(makeConfig()), 'patch'); + }); + + it('should prefer major over minor', () => { + assert.equal(getVersionType(makeConfig({ isMajor: true, isMinor: true })), 'major'); + }); + + it('should return premajor with string preid', () => { + assert.equal(getVersionType(makeConfig({ isMajor: true }), 'beta'), 'premajor'); + }); + + it('should return preminor with string preid', () => { + assert.equal(getVersionType(makeConfig({ isMinor: true }), 'alpha'), 'preminor'); + }); + + it('should return prepatch with string preid', () => { + assert.equal(getVersionType(makeConfig(), 'rc'), 'prepatch'); + }); + + it('should return premajor with boolean true preid', () => { + assert.equal(getVersionType(makeConfig({ isMajor: true }), true), 'premajor'); + }); + + it('should return prepatch with boolean true preid', () => { + assert.equal(getVersionType(makeConfig(), true), 'prepatch'); + }); + + it('should not add pre prefix with false preid', () => { + assert.equal(getVersionType(makeConfig({ isMinor: true }), false), 'minor'); + }); +}); + +describe('detectPM', () => { + let tempDir: string; + let originalCwd: string; + + beforeEach(async () => { + originalCwd = process.cwd(); + tempDir = await mkdtemp(join(tmpdir(), 'sr-pm-')); + process.chdir(tempDir); + }); + + afterEach(async () => { + process.chdir(originalCwd); + await rm(tempDir, { recursive: true, force: true }); + }); + + it('should detect npm by default', () => { + const pm = detectPM(); + assert.ok(pm.startsWith('npm')); + }); + + it('should detect bun from bun.lock', async () => { + await fs.writeFile(join(tempDir, 'bun.lock'), '', 'utf8'); + assert.equal(detectPM(), 'bun'); + }); + + it('should detect bun from bun.lockb', async () => { + await fs.writeFile(join(tempDir, 'bun.lockb'), '', 'utf8'); + assert.equal(detectPM(), 'bun'); + }); + + it('should detect pnpm from pnpm-lock.yaml', async () => { + await fs.writeFile(join(tempDir, 'pnpm-lock.yaml'), '', 'utf8'); + const pm = detectPM(); + assert.ok(pm.startsWith('pnpm')); + }); + + it('should detect yarn from yarn.lock', async () => { + await fs.writeFile(join(tempDir, 'yarn.lock'), '', 'utf8'); + const pm = detectPM(); + assert.ok(pm.startsWith('yarn')); + }); + + it('should prefer npm when package-lock.json exists alongside others', async () => { + await fs.writeFile(join(tempDir, 'package-lock.json'), '', 'utf8'); + await fs.writeFile(join(tempDir, 'bun.lock'), '', 'utf8'); + const pm = detectPM(); + assert.ok(pm.startsWith('npm')); + }); + + it('should detect npm from package-lock.json', async () => { + await fs.writeFile(join(tempDir, 'package-lock.json'), '', 'utf8'); + const pm = detectPM(); + assert.ok(pm.startsWith('npm')); + }); + + it('should detect npm from npm-shrinkwrap.json', async () => { + await fs.writeFile(join(tempDir, 'npm-shrinkwrap.json'), '', 'utf8'); + const pm = detectPM(); + assert.ok(pm.startsWith('npm')); + }); +}); diff --git a/tests/package.test.ts b/tests/package.test.ts new file mode 100644 index 0000000..5f47c53 --- /dev/null +++ b/tests/package.test.ts @@ -0,0 +1,359 @@ +import { afterEach, beforeEach, describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { existsSync, promises as fs } from 'node:fs'; +import { mkdtemp, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { Pack } from '../src/types'; +import { detectIndent, getPack, getVersion, readPackageConfig, runScript, writeVersion } from '../src/package'; + +interface LockFile { + name?: string; + version: string; + lockfileVersion?: number; + requires?: boolean; + packages?: Record; +} + +async function writeJson(dir: string, file: string, data: object, indent = 2): Promise { + await fs.mkdir(dir, { recursive: true }); + await fs.writeFile(join(dir, file), `${JSON.stringify(data, undefined, indent)}\n`, 'utf8'); +} + +async function readJson(dir: string, file: string): Promise { + return JSON.parse(await fs.readFile(join(dir, file), 'utf8')) as T; +} + +let tempDir: string; +let originalCwd: string; + +beforeEach(async () => { + originalCwd = process.cwd(); + tempDir = await mkdtemp(join(tmpdir(), 'sr-test-')); + process.chdir(tempDir); +}); + +afterEach(async () => { + process.chdir(originalCwd); + await rm(tempDir, { recursive: true, force: true }); +}); + +describe('getPack', () => { + it('should read and parse package.json', async () => { + const pkg = { name: 'test-pkg', version: '1.0.0' }; + await fs.writeFile(join(tempDir, 'package.json'), JSON.stringify(pkg), 'utf8'); + const result = await getPack(); + assert.equal(result.name, 'test-pkg'); + assert.equal(result.version, '1.0.0'); + }); + + it('should read custom file', async () => { + const pkg = { name: 'custom', version: '2.0.0' }; + await fs.writeFile(join(tempDir, 'custom.json'), JSON.stringify(pkg), 'utf8'); + const result = await getPack(tempDir, 'custom.json'); + assert.equal(result.name, 'custom'); + }); + + it('should throw for missing file', async () => { + await assert.rejects(() => getPack(), { code: 'ENOENT' }); + }); +}); + +describe('getVersion', () => { + it('should return version from package.json', async () => { + await fs.writeFile(join(tempDir, 'package.json'), JSON.stringify({ version: '1.2.3' }), 'utf8'); + const version = await getVersion(); + assert.equal(version, '1.2.3'); + }); + + it('should return 0.0.0 when version is missing', async () => { + await fs.writeFile(join(tempDir, 'package.json'), JSON.stringify({ name: 'no-version' }), 'utf8'); + const version = await getVersion(); + assert.equal(version, '0.0.0'); + }); + + it('should return 0.0.0 when version is empty string', async () => { + await fs.writeFile(join(tempDir, 'package.json'), JSON.stringify({ version: '' }), 'utf8'); + const version = await getVersion(); + assert.equal(version, '0.0.0'); + }); +}); + +describe('writeVersion', () => { + it('should update version in package.json', async () => { + await writeJson(tempDir, 'package.json', { name: 'test', version: '1.0.0' }); + await writeVersion('2.0.0'); + const pkg = await readJson(tempDir, 'package.json'); + assert.equal(pkg.version, '2.0.0'); + }); + + it('should preserve indent in package.json', async () => { + await writeJson(tempDir, 'package.json', { name: 'test', version: '1.0.0' }, 4); + await writeVersion('2.0.0'); + const raw = await fs.readFile(join(tempDir, 'package.json'), 'utf8'); + assert.ok(raw.includes(' "name"')); + }); + + it('should end file with newline', async () => { + await writeJson(tempDir, 'package.json', { version: '1.0.0' }); + await writeVersion('2.0.0'); + const raw = await fs.readFile(join(tempDir, 'package.json'), 'utf8'); + assert.ok(raw.endsWith('\n')); + }); + + it('should update package-lock.json v1 (top-level version only)', async () => { + await writeJson(tempDir, 'package.json', { version: '1.0.0' }); + await writeJson(tempDir, 'package-lock.json', { name: 'test', version: '1.0.0', lockfileVersion: 1 }); + await writeVersion('2.0.0'); + const result = await readJson(tempDir, 'package-lock.json'); + assert.equal(result.version, '2.0.0'); + }); + + it('should update package-lock.json v2 (both top-level and packages[""])', async () => { + await writeJson(tempDir, 'package.json', { version: '1.0.0' }); + await writeJson(tempDir, 'package-lock.json', { + name: 'test', + version: '1.0.0', + lockfileVersion: 2, + packages: { '': { name: 'test', version: '1.0.0' } }, + }); + await writeVersion('2.0.0'); + const result = await readJson(tempDir, 'package-lock.json'); + assert.equal(result.version, '2.0.0'); + assert.equal(result.packages?.[''].version, '2.0.0'); + }); + + it('should update package-lock.json v3 (both top-level and packages[""])', async () => { + await writeJson(tempDir, 'package.json', { version: '1.0.0' }); + await writeJson(tempDir, 'package-lock.json', { + name: 'test', + version: '1.0.0', + lockfileVersion: 3, + requires: true, + packages: { '': { name: 'test', version: '1.0.0' } }, + }); + await writeVersion('2.0.0'); + const result = await readJson(tempDir, 'package-lock.json'); + assert.equal(result.version, '2.0.0'); + assert.equal(result.packages?.[''].version, '2.0.0'); + }); + + it('should update npm-shrinkwrap.json', async () => { + await writeJson(tempDir, 'package.json', { version: '1.0.0' }); + await writeJson(tempDir, 'npm-shrinkwrap.json', { + name: 'test', + version: '1.0.0', + lockfileVersion: 2, + packages: { '': { version: '1.0.0' } }, + }); + await writeVersion('2.0.0'); + const result = await readJson(tempDir, 'npm-shrinkwrap.json'); + assert.equal(result.version, '2.0.0'); + assert.equal(result.packages?.[''].version, '2.0.0'); + }); + + it('should skip missing lockfiles without error', async () => { + await writeJson(tempDir, 'package.json', { version: '1.0.0' }); + await writeVersion('2.0.0'); + const pkg = await readJson(tempDir, 'package.json'); + assert.equal(pkg.version, '2.0.0'); + }); + + it('should preserve indent in lockfile', async () => { + await writeJson(tempDir, 'package.json', { version: '1.0.0' }); + await writeJson(tempDir, 'package-lock.json', { name: 'test', version: '1.0.0', lockfileVersion: 3 }, 4); + await writeVersion('2.0.0'); + const raw = await fs.readFile(join(tempDir, 'package-lock.json'), 'utf8'); + assert.ok(raw.includes(' "name"')); + }); + + it('should update both package-lock.json and npm-shrinkwrap.json', async () => { + await writeJson(tempDir, 'package.json', { version: '1.0.0' }); + await writeJson(tempDir, 'package-lock.json', { + version: '1.0.0', + lockfileVersion: 3, + packages: { '': { version: '1.0.0' } }, + }); + await writeJson(tempDir, 'npm-shrinkwrap.json', { version: '1.0.0', lockfileVersion: 1 }); + await writeVersion('3.0.0'); + const lock = await readJson(tempDir, 'package-lock.json'); + const shrink = await readJson(tempDir, 'npm-shrinkwrap.json'); + assert.equal(lock.version, '3.0.0'); + assert.equal(lock.packages?.[''].version, '3.0.0'); + assert.equal(shrink.version, '3.0.0'); + }); + + it('should run preversion script before writing', async () => { + const marker = join(tempDir, 'preversion-ran'); + await writeJson(tempDir, 'package.json', { + version: '1.0.0', + scripts: { preversion: `touch ${marker}` }, + }); + await writeVersion('2.0.0'); + assert.ok(existsSync(marker)); + }); + + it('should run version script after writing', async () => { + const marker = join(tempDir, 'version-ran'); + await writeJson(tempDir, 'package.json', { + version: '1.0.0', + scripts: { version: `touch ${marker}` }, + }); + await writeVersion('2.0.0'); + assert.ok(existsSync(marker)); + }); + + it('should not run postversion (called from index.ts after git)', async () => { + const marker = join(tempDir, 'postversion-ran'); + await writeJson(tempDir, 'package.json', { + version: '1.0.0', + scripts: { postversion: `touch ${marker}` }, + }); + await writeVersion('2.0.0'); + assert.ok(!existsSync(marker)); + }); + + it('should run preversion and version in order', async () => { + const log = join(tempDir, 'lifecycle.log'); + await writeJson(tempDir, 'package.json', { + version: '1.0.0', + scripts: { + preversion: `echo pre >> ${log}`, + version: `echo ver >> ${log}`, + }, + }); + await writeVersion('2.0.0'); + const content = await fs.readFile(log, 'utf8'); + assert.equal(content.trim(), 'pre\nver'); + }); + + it('should skip lifecycle scripts when none defined', async () => { + await writeJson(tempDir, 'package.json', { version: '1.0.0' }); + await writeVersion('2.0.0'); + const pkg = await readJson(tempDir, 'package.json'); + assert.equal(pkg.version, '2.0.0'); + }); + + it('should write version before running version script', async () => { + const out = join(tempDir, 'version-check'); + await writeJson(tempDir, 'package.json', { + version: '1.0.0', + scripts: { + version: `node -e "const p=require('./package.json');require('fs').writeFileSync('${out}',p.version)"`, + }, + }); + await writeVersion('2.0.0'); + const captured = await fs.readFile(out, 'utf8'); + assert.equal(captured, '2.0.0'); + }); + + it('should pass npm_package_version env to lifecycle scripts', async () => { + const out = join(tempDir, 'env-check'); + await writeJson(tempDir, 'package.json', { + version: '1.0.0', + scripts: { version: `node -e "require('fs').writeFileSync('${out}', process.env.npm_package_version || '')"` }, + }); + await writeVersion('3.0.0'); + const captured = await fs.readFile(out, 'utf8'); + assert.equal(captured, '3.0.0'); + }); + + it('should write version in custom dir', async () => { + const subDir = join(tempDir, 'packages', 'my-pkg'); + await writeJson(subDir, 'package.json', { version: '1.0.0' }); + await writeVersion('2.0.0', subDir); + const pkg = await readJson(subDir, 'package.json'); + assert.equal(pkg.version, '2.0.0'); + }); + + it('should run lifecycle scripts with custom cwd', async () => { + const subDir = join(tempDir, 'packages', 'my-pkg'); + const marker = join(subDir, 'script-ran'); + await writeJson(subDir, 'package.json', { + version: '1.0.0', + scripts: { version: `touch script-ran` }, + }); + await writeVersion('2.0.0', subDir); + assert.ok(existsSync(marker)); + }); +}); + +describe('runScript', () => { + it('should run script with custom cwd', async () => { + const subDir = join(tempDir, 'sub'); + await fs.mkdir(subDir, { recursive: true }); + const marker = join(subDir, 'ran'); + await runScript({ scripts: { test: 'touch ran' } } as Pack, 'test', '1.0.0', subDir); + assert.ok(existsSync(marker)); + }); + + it('should skip non-existent script', async () => { + await runScript({} as Pack, 'missing', '1.0.0', tempDir); + }); + + it('should provide npm_package_version env', async () => { + const out = join(tempDir, 'ver'); + await runScript( + { + scripts: { check: `node -e "require('fs').writeFileSync('ver', process.env.npm_package_version || '')"` }, + } as Pack, + 'check', + '5.0.0', + tempDir + ); + const captured = await fs.readFile(out, 'utf8'); + assert.equal(captured, '5.0.0'); + }); +}); + +describe('detectIndent', () => { + it('should detect 2-space indent', () => { + assert.equal(detectIndent('{\n "name": "test"\n}'), 2); + }); + + it('should detect 4-space indent', () => { + assert.equal(detectIndent('{\n "name": "test"\n}'), 4); + }); + + it('should default to 2 when no indent found', () => { + assert.equal(detectIndent('{"name":"test"}'), 2); + }); +}); + +describe('readPackageConfig', () => { + it('should return empty object when no config', () => { + const result = readPackageConfig({ version: '1.0.0' }); + assert.deepEqual(result, {}); + }); + + it('should return empty object when config is not an object', () => { + const result = readPackageConfig({ version: '1.0.0', 'simple-release': 'invalid' }); + assert.deepEqual(result, {}); + }); + + it('should read contents', () => { + const result = readPackageConfig({ version: '1.0.0', 'simple-release': { contents: 'dist' } }); + assert.equal(result.contents, 'dist'); + }); + + it('should read cascade', () => { + const result = readPackageConfig({ version: '1.0.0', 'simple-release': { cascade: 'all' } }); + assert.equal(result.cascade, 'all'); + }); + + it('should read both', () => { + const result = readPackageConfig({ version: '1.0.0', 'simple-release': { contents: 'lib', cascade: 'none' } }); + assert.equal(result.contents, 'lib'); + assert.equal(result.cascade, 'none'); + }); + + it('should ignore non-string values', () => { + const result = readPackageConfig({ + version: '1.0.0', + 'simple-release': { contents: 123, cascade: true }, + } as unknown as Pack); + assert.equal(result.contents, undefined); + assert.equal(result.cascade, undefined); + }); +}); diff --git a/tests/parser.test.ts b/tests/parser.test.ts new file mode 100644 index 0000000..4f9e341 --- /dev/null +++ b/tests/parser.test.ts @@ -0,0 +1,146 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +import { OTHERS } from '../src/config'; +import { parse, parseItem } from '../src/parser'; +import { RawLog } from '../src/types'; + +function makeLog(title: string, body = ''): RawLog { + return { short: 'abc1234', hash: 'abc1234567890abcdef', title, body }; +} + +describe('parseItem', () => { + it('should parse standard conventional commit', () => { + const result = parseItem(makeLog('feat: add new feature')); + assert.equal(result.type, 'feat'); + assert.equal(result.scope, undefined); + assert.equal(result.content, 'add new feature'); + assert.equal(result.major, false); + assert.equal(result.shortHash, 'abc1234'); + assert.equal(result.hash, 'abc1234567890abcdef'); + }); + + it('should parse scoped commit', () => { + const result = parseItem(makeLog('fix(core): fix bug')); + assert.equal(result.type, 'fix'); + assert.equal(result.scope, 'core'); + assert.equal(result.content, 'fix bug'); + assert.equal(result.major, false); + }); + + it('should parse breaking change with !', () => { + const result = parseItem(makeLog('feat!: breaking feature')); + assert.equal(result.type, 'feat'); + assert.equal(result.major, true); + assert.equal(result.content, 'breaking feature'); + }); + + it('should parse scoped breaking change with !', () => { + const result = parseItem(makeLog('fix(api)!: breaking fix')); + assert.equal(result.type, 'fix'); + assert.equal(result.scope, 'api'); + assert.equal(result.major, true); + }); + + it('should parse non-conventional commit as OTHERS', () => { + const result = parseItem(makeLog('random text')); + assert.equal(result.type, OTHERS); + assert.equal(result.content, 'random text'); + assert.equal(result.major, false); + }); + + it('should preserve body', () => { + const result = parseItem(makeLog('feat: add feature', 'Some details')); + assert.equal(result.body, 'Some details'); + }); +}); + +describe('parse', () => { + const URL = 'https://github.com/user/repo'; + + it('should return isEmpty for empty commits', () => { + const result = parse([], URL); + assert.equal(result.isEmpty, true); + assert.equal(result.isMajor, false); + assert.equal(result.isMinor, false); + assert.deepEqual(result.groups, {}); + }); + + it('should detect minor (feat) commits', () => { + const result = parse([makeLog('feat: new feature')], URL); + assert.equal(result.isEmpty, false); + assert.equal(result.isMinor, true); + assert.equal(result.isMajor, false); + assert.ok('feat' in result.groups); + assert.equal(result.groups.feat.length, 1); + }); + + it('should detect major via ! marker', () => { + const result = parse([makeLog('feat!: breaking')], URL); + assert.equal(result.isMajor, true); + }); + + it('should detect major via break type', () => { + const result = parse([makeLog('break: removed API')], URL); + assert.equal(result.isMajor, true); + assert.ok('break' in result.groups); + }); + + it('should detect major via BREAKING CHANGES in content', () => { + const result = parse([makeLog('chore: BREAKING CHANGE: removed feature')], URL); + assert.equal(result.isMajor, true); + }); + + it('should detect major via BREAKING CHANGES in body', () => { + const result = parse([makeLog('feat: new api', 'BREAKING CHANGES: old api removed')], URL); + assert.equal(result.isMajor, true); + }); + + it('should include commit hash link when URL provided', () => { + const result = parse([makeLog('fix: bug')], URL); + const item = result.groups.fix[0]; + assert.ok(item.includes('[abc1234]')); + assert.ok(item.includes(`${URL}/commit/abc1234567890abcdef`)); + }); + + it('should not include link when URL is empty', () => { + const result = parse([makeLog('fix: bug')], ''); + const item = result.groups.fix[0]; + assert.ok(!item.includes('[abc1234]')); + }); + + it('should include scope in output', () => { + const result = parse([makeLog('fix(core): bug')], URL); + const item = result.groups.fix[0]; + assert.ok(item.startsWith('**core**: bug')); + }); + + it('should include body indented', () => { + const result = parse([makeLog('fix: bug', 'line1\nline2')], URL); + const item = result.groups.fix[0]; + assert.ok(item.includes('\n\n line1\n line2\n')); + }); + + it('should group commits by type', () => { + const commits = [makeLog('feat: feature'), makeLog('fix: bugfix'), makeLog('feat: another feature')]; + const result = parse(commits, URL); + assert.equal(result.groups.feat.length, 2); + assert.equal(result.groups.fix.length, 1); + }); + + it('should sort groups alphabetically with OTHERS last', () => { + const commits = [makeLog('random text'), makeLog('fix: bug'), makeLog('feat: feature')]; + const result = parse(commits, URL); + const keys = Object.keys(result.groups); + assert.equal(keys[0], 'feat'); + assert.equal(keys[1], 'fix'); + assert.equal(keys[2], OTHERS); + }); + + it('should handle patch-only commits (no feat, no break)', () => { + const result = parse([makeLog('fix: bugfix'), makeLog('docs: update readme')], URL); + assert.equal(result.isMajor, false); + assert.equal(result.isMinor, false); + assert.equal(result.isEmpty, false); + }); +}); diff --git a/tests/semver.test.ts b/tests/semver.test.ts new file mode 100644 index 0000000..95a022c --- /dev/null +++ b/tests/semver.test.ts @@ -0,0 +1,287 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +import { inc as semverInc } from 'semver'; + +import { inc, satisfies } from '../src/semver'; + +describe('inc', () => { + describe('invalid input', () => { + it('should return null for invalid version', () => { + assert.equal(inc('not-a-version', 'major'), undefined); + }); + + it('should return null for empty string', () => { + assert.equal(inc('', 'major'), undefined); + }); + }); + + describe('major', () => { + it('should bump major', () => { + assert.equal(inc('1.2.3', 'major'), '2.0.0'); + }); + + it('should bump major from 0.x', () => { + assert.equal(inc('0.5.0', 'major'), '1.0.0'); + }); + + it('should drop prerelease and bump major when minor or patch non-zero', () => { + assert.equal(inc('1.2.3-beta.1', 'major'), '2.0.0'); + }); + + it('should drop prerelease without bump when already at major boundary', () => { + assert.equal(inc('1.0.0-5', 'major'), '1.0.0'); + }); + + it('should drop prerelease without bump when at major boundary with identifier', () => { + assert.equal(inc('2.0.0-beta.3', 'major'), '2.0.0'); + }); + }); + + describe('minor', () => { + it('should bump minor', () => { + assert.equal(inc('1.2.3', 'minor'), '1.3.0'); + }); + + it('should bump minor when patch is zero', () => { + assert.equal(inc('1.2.0', 'minor'), '1.3.0'); + }); + + it('should drop prerelease and bump minor when patch non-zero', () => { + assert.equal(inc('1.2.1-alpha.0', 'minor'), '1.3.0'); + }); + + it('should drop prerelease without bump when already at minor boundary', () => { + assert.equal(inc('1.2.0-5', 'minor'), '1.2.0'); + }); + }); + + describe('patch', () => { + it('should bump patch', () => { + assert.equal(inc('1.2.3', 'patch'), '1.2.4'); + }); + + it('should drop prerelease without bump', () => { + assert.equal(inc('1.2.3-0', 'patch'), '1.2.3'); + }); + + it('should drop named prerelease without bump', () => { + assert.equal(inc('1.2.3-beta.1', 'patch'), '1.2.3'); + }); + }); + + describe('premajor', () => { + it('should bump premajor without identifier', () => { + assert.equal(inc('1.2.3', 'premajor'), '2.0.0-0'); + }); + + it('should bump premajor with identifier', () => { + assert.equal(inc('1.2.3', 'premajor', 'beta'), '2.0.0-beta.0'); + }); + + it('should bump premajor from prerelease', () => { + assert.equal(inc('1.0.0-beta.1', 'premajor', 'beta'), '2.0.0-beta.0'); + }); + }); + + describe('preminor', () => { + it('should bump preminor without identifier', () => { + assert.equal(inc('1.2.3', 'preminor'), '1.3.0-0'); + }); + + it('should bump preminor with identifier', () => { + assert.equal(inc('1.2.3', 'preminor', 'beta'), '1.3.0-beta.0'); + }); + + it('should bump preminor from prerelease', () => { + assert.equal(inc('1.2.3-beta.1', 'preminor', 'alpha'), '1.3.0-alpha.0'); + }); + }); + + describe('prepatch', () => { + it('should bump prepatch without identifier', () => { + assert.equal(inc('1.2.3', 'prepatch'), '1.2.4-0'); + }); + + it('should bump prepatch with identifier', () => { + assert.equal(inc('1.2.3', 'prepatch', 'beta'), '1.2.4-beta.0'); + }); + + it('should bump prepatch from prerelease', () => { + assert.equal(inc('1.2.3-beta.1', 'prepatch'), '1.2.4-0'); + }); + }); + + describe('prerelease', () => { + it('should bump patch and add prerelease when no prerelease exists', () => { + assert.equal(inc('1.2.3', 'prerelease'), '1.2.4-0'); + }); + + it('should bump patch and add identifier when no prerelease exists', () => { + assert.equal(inc('1.2.3', 'prerelease', 'beta'), '1.2.4-beta.0'); + }); + + it('should increment numeric prerelease', () => { + assert.equal(inc('1.2.3-0', 'prerelease'), '1.2.3-1'); + }); + + it('should increment named prerelease counter', () => { + assert.equal(inc('1.2.3-beta.0', 'prerelease', 'beta'), '1.2.3-beta.1'); + }); + + it('should reset prerelease when identifier changes', () => { + assert.equal(inc('1.2.3-alpha.0', 'prerelease', 'beta'), '1.2.3-beta.0'); + }); + + it('should reset prerelease when identifier has non-numeric second part', () => { + assert.equal(inc('1.2.3-beta.foo', 'prerelease', 'beta'), '1.2.3-beta.0'); + }); + + it('should increment last numeric in prerelease without identifier', () => { + assert.equal(inc('1.2.3-beta.1', 'prerelease'), '1.2.3-beta.2'); + }); + + it('should append 0 when no numeric in prerelease and no identifier', () => { + assert.equal(inc('1.2.3-beta', 'prerelease'), '1.2.3-beta.0'); + }); + }); + + describe('v prefix', () => { + it('should handle v prefix', () => { + assert.equal(inc('v1.2.3', 'patch'), '1.2.4'); + }); + + it('should handle v prefix with prerelease', () => { + assert.equal(inc('v1.0.0-rc.1', 'prerelease', 'rc'), '1.0.0-rc.2'); + }); + }); + + describe('whitespace', () => { + it('should handle leading/trailing whitespace', () => { + assert.equal(inc(' 1.2.3 ', 'minor'), '1.3.0'); + }); + }); +}); + +describe('inc matches semver package', () => { + const versions = [ + '0.0.0', + '0.0.1', + '0.1.0', + '1.0.0', + '1.2.3', + '1.0.0-0', + '1.0.0-5', + '1.2.0-5', + '1.2.3-0', + '1.2.3-1', + '1.2.3-beta', + '1.2.3-beta.0', + '1.2.3-beta.1', + '1.2.3-beta.foo', + '1.2.3-alpha.0', + '2.0.0-beta.3', + '0.5.0', + ]; + + const releases = ['major', 'minor', 'patch', 'premajor', 'preminor', 'prepatch', 'prerelease'] as const; + + const identifiers = [undefined, 'beta', 'alpha', 'rc', 'pre']; + + for (const version of versions) { + for (const release of releases) { + for (const identifier of identifiers) { + const label = `inc('${version}', '${release}'${identifier !== undefined ? `, '${identifier}'` : ''})`; + it(label, () => { + const ours = inc(version, release, identifier) ?? null; + const original = + identifier !== undefined ? semverInc(version, release, identifier) : semverInc(version, release); + assert.equal(ours, original, label); + }); + } + } + } +}); + +describe('satisfies', () => { + describe('caret ^', () => { + it('should satisfy minor bump', () => { + assert.equal(satisfies('1.1.0', '^1.0.0'), true); + }); + + it('should satisfy patch bump', () => { + assert.equal(satisfies('1.0.1', '^1.0.0'), true); + }); + + it('should not satisfy major bump', () => { + assert.equal(satisfies('2.0.0', '^1.0.0'), false); + }); + + it('should not satisfy lower version', () => { + assert.equal(satisfies('0.9.0', '^1.0.0'), false); + }); + + it('should handle ^0.x (minor is major)', () => { + assert.equal(satisfies('0.1.1', '^0.1.0'), true); + assert.equal(satisfies('0.2.0', '^0.1.0'), false); + }); + }); + + describe('tilde ~', () => { + it('should satisfy patch bump', () => { + assert.equal(satisfies('1.0.1', '~1.0.0'), true); + }); + + it('should not satisfy minor bump', () => { + assert.equal(satisfies('1.1.0', '~1.0.0'), false); + }); + + it('should not satisfy major bump', () => { + assert.equal(satisfies('2.0.0', '~1.0.0'), false); + }); + }); + + describe('exact', () => { + it('should satisfy exact match', () => { + assert.equal(satisfies('1.0.0', '1.0.0'), true); + }); + + it('should not satisfy different version', () => { + assert.equal(satisfies('1.0.1', '1.0.0'), false); + }); + }); + + describe('>=', () => { + it('should satisfy higher version', () => { + assert.equal(satisfies('2.0.0', '>=1.0.0'), true); + }); + + it('should satisfy equal version', () => { + assert.equal(satisfies('1.0.0', '>=1.0.0'), true); + }); + + it('should not satisfy lower version', () => { + assert.equal(satisfies('0.9.0', '>=1.0.0'), false); + }); + }); + + describe('workspace', () => { + it('should always satisfy workspace:*', () => { + assert.equal(satisfies('99.0.0', 'workspace:*'), true); + }); + + it('should always satisfy workspace:^', () => { + assert.equal(satisfies('99.0.0', 'workspace:^1.0.0'), true); + }); + }); + + describe('invalid', () => { + it('should return false for invalid version', () => { + assert.equal(satisfies('not-a-version', '^1.0.0'), false); + }); + + it('should return false for invalid range', () => { + assert.equal(satisfies('1.0.0', 'not-a-range'), false); + }); + }); +}); diff --git a/tests/utils.test.ts b/tests/utils.test.ts new file mode 100644 index 0000000..3f22ebd --- /dev/null +++ b/tests/utils.test.ts @@ -0,0 +1,265 @@ +import { afterEach, beforeEach, describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +import { arg, formatTitle, getDate, getRepo, getURL, isNumber, isText, parseRepo } from '../src/utils'; + +describe('isText', () => { + it('should return true for non-empty strings', () => { + assert.equal(isText('hello'), true); + assert.equal(isText('0'), true); + assert.equal(isText(' '), true); + }); + + it('should return false for empty string', () => { + assert.equal(isText(''), false); + }); + + it('should return false for non-strings', () => { + assert.equal(isText(undefined), false); + assert.equal(isText(null), false); + assert.equal(isText(0), false); + assert.equal(isText(false), false); + assert.equal(isText({}), false); + }); +}); + +describe('isNumber', () => { + it('should return true for numbers', () => { + assert.equal(isNumber(0), true); + assert.equal(isNumber(1), true); + assert.equal(isNumber(-1), true); + assert.equal(isNumber(1.5), true); + assert.equal(isNumber(NaN), true); + }); + + it('should return false for non-numbers', () => { + assert.equal(isNumber('1'), false); + assert.equal(isNumber(undefined), false); + assert.equal(isNumber(null), false); + assert.equal(isNumber(true), false); + }); +}); + +describe('formatTitle', () => { + it('should return v-prefixed version by default', () => { + assert.equal(formatTitle('1.0.0'), 'v1.0.0'); + }); + + it('should return v-prefixed version for undefined title', () => { + assert.equal(formatTitle('1.0.0', undefined), 'v1.0.0'); + }); + + it('should return v-prefixed version for empty string title', () => { + assert.equal(formatTitle('1.0.0', ''), 'v1.0.0'); + }); + + it('should replace {VERSION} in custom title', () => { + assert.equal(formatTitle('1.0.0', 'v{VERSION}'), 'v1.0.0'); + assert.equal(formatTitle('1.0.0', 'release-{VERSION}'), 'release-1.0.0'); + assert.equal(formatTitle('2.0.0', '{VERSION}-rc'), '2.0.0-rc'); + }); + + it('should replace multiple {VERSION} occurrences', () => { + assert.equal(formatTitle('1.0.0', '{VERSION}+{VERSION}'), '1.0.0+1.0.0'); + }); +}); + +describe('parseRepo', () => { + it('should parse github https URL', () => { + assert.deepEqual(parseRepo('https://github.com/user/repo.git'), { user: 'user', repository: 'repo' }); + }); + + it('should parse URL without .git', () => { + assert.deepEqual(parseRepo('https://github.com/user/repo'), { user: 'user', repository: 'repo' }); + }); + + it('should parse SSH URL (colon included in user)', () => { + assert.deepEqual(parseRepo('git@github.com:user/repo.git'), { user: 'com:user', repository: 'repo' }); + }); + + it('should return undefined for undefined', () => { + assert.equal(parseRepo(undefined), undefined); + }); + + it('should return undefined for empty string', () => { + assert.equal(parseRepo(''), undefined); + }); +}); + +describe('getRepo', () => { + it('should extract repo from pack with object repository', () => { + assert.deepEqual(getRepo({ repository: { url: 'https://github.com/user/repo.git' } }), { + user: 'user', + repository: 'repo', + }); + }); + + it('should return undefined for pack without repository', () => { + assert.equal(getRepo({}), undefined); + }); + + it('should return undefined for string repository', () => { + assert.equal(getRepo({ repository: 'not-an-object' }), undefined); + }); +}); + +describe('getURL', () => { + it('should return github URL with repo', () => { + const result = getURL({ user: 'myuser', repository: 'myrepo' }); + assert.equal(result, 'https://github.com/myuser/myrepo'); + }); + + it('should return github URL without repo (uses env default)', () => { + const result = getURL(undefined); + assert.ok(result.startsWith('https://github.com')); + }); +}); + +describe('getDate', () => { + it('should return date in YYYY-MM-DD format', () => { + const date = getDate(); + assert.match(date, /^\d{4}-\d{2}-\d{2}$/); + }); + + it('should return current UTC date', () => { + const date = getDate(); + const now = new Date(); + const m = String(now.getUTCMonth() + 1).padStart(2, '0'); + const d = String(now.getUTCDate()).padStart(2, '0'); + assert.equal(date, `${now.getUTCFullYear()}-${m}-${d}`); + }); +}); + +describe('arg', () => { + let originalArgv: string[]; + + beforeEach(() => { + originalArgv = process.argv; + }); + + afterEach(() => { + process.argv = originalArgv; + }); + + it('should return defaults with no args', () => { + process.argv = ['node', 'script']; + const result = arg({ flag: false, '--option': '' }); + assert.equal(result.flag, false); + assert.equal(result['--option'], ''); + }); + + it('should parse boolean flag', () => { + process.argv = ['node', 'script', 'flag']; + const result = arg({ flag: false }); + assert.equal(result.flag, true); + }); + + it('should parse string option with next arg', () => { + process.argv = ['node', 'script', '--option', 'value']; + const result = arg({ '--option': '' }); + assert.equal(result['--option'], 'value'); + }); + + it('should parse string option with = syntax', () => { + process.argv = ['node', 'script', '--option=value']; + const result = arg({ '--option': '' }); + assert.equal(result['--option'], 'value'); + }); + + it('should parse array option', () => { + process.argv = ['node', 'script', '--items', 'a', '--items', 'b']; + const result = arg({ '--items': [] as string[] }); + assert.deepEqual(result['--items'], ['a', 'b']); + }); + + it('should parse boolean with =value syntax (prerelease)', () => { + process.argv = ['node', 'script', 'prerelease=next']; + const result = arg<{ prerelease: boolean | string }>({ prerelease: false }); + assert.equal(result.prerelease, 'next'); + }); + + it('should ignore unknown args', () => { + process.argv = ['node', 'script', 'unknown', '--other', 'val']; + const result = arg({ flag: false }); + assert.equal(result.flag, false); + }); + + it('should handle array with = syntax', () => { + process.argv = ['node', 'script', '--items=a', '--items=b']; + const result = arg({ '--items': [] as string[] }); + assert.deepEqual(result['--items'], ['a', 'b']); + }); + + it('should ignore string option at end without value', () => { + process.argv = ['node', 'script', '--option']; + const result = arg({ '--option': 'default' }); + assert.equal(result['--option'], 'default'); + }); + + it('should ignore array option at end without value', () => { + process.argv = ['node', 'script', '--items']; + const result = arg({ '--items': [] as string[] }); + assert.deepEqual(result['--items'], []); + }); + + it('should parse string option followed by another option at end', () => { + process.argv = ['node', 'script', '--option', 'value', '--flag']; + const result = arg({ '--option': '', '--flag': false }); + assert.equal(result['--option'], 'value'); + assert.equal(result['--flag'], true); + }); + + it('should parse --workspace array', () => { + process.argv = ['node', 'script', '--workspace', '@org/pkg-a', '--workspace', 'pkg-b']; + const result = arg({ '--workspace': [] as string[] }); + assert.deepEqual(result['--workspace'], ['@org/pkg-a', 'pkg-b']); + }); + + it('should parse no-workspace boolean', () => { + process.argv = ['node', 'script', 'no-workspace']; + const result = arg({ 'no-workspace': false }); + assert.equal(result['no-workspace'], true); + }); + + it('should parse --contents string', () => { + process.argv = ['node', 'script', '--contents', 'dist']; + const result = arg({ '--contents': '' }); + assert.equal(result['--contents'], 'dist'); + }); + + it('should parse --cascade with = syntax', () => { + process.argv = ['node', 'script', '--cascade=all']; + const result = arg({ '--cascade': 'full' }); + assert.equal(result['--cascade'], 'all'); + }); + + it('should default --cascade to auto', () => { + process.argv = ['node', 'script']; + const result = arg({ '--cascade': 'full' }); + assert.equal(result['--cascade'], 'full'); + }); + + it('should parse dry-run flag', () => { + process.argv = ['node', 'script', 'dry-run']; + const result = arg({ 'dry-run': false }); + assert.equal(result['dry-run'], true); + }); + + it('should default dry-run to false', () => { + process.argv = ['node', 'script']; + const result = arg({ 'dry-run': false }); + assert.equal(result['dry-run'], false); + }); + + it('should parse verbose flag', () => { + process.argv = ['node', 'script', 'verbose']; + const result = arg({ verbose: false }); + assert.equal(result.verbose, true); + }); + + it('should default verbose to false', () => { + process.argv = ['node', 'script']; + const result = arg({ verbose: false }); + assert.equal(result.verbose, false); + }); +}); diff --git a/tests/workspace.test.ts b/tests/workspace.test.ts new file mode 100644 index 0000000..e3b67f5 --- /dev/null +++ b/tests/workspace.test.ts @@ -0,0 +1,262 @@ +import { afterEach, beforeEach, describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { promises as fs } from 'node:fs'; +import { mkdtemp, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { PackageContext } from '../src/types'; +import { discoverWorkspaces, parsePnpmWorkspace, resolveGlobs, topoSort } from '../src/workspace'; + +let tempDir: string; + +beforeEach(async () => { + tempDir = await mkdtemp(join(tmpdir(), 'sr-ws-')); +}); + +afterEach(async () => { + await rm(tempDir, { recursive: true, force: true }); +}); + +async function writeJson(dir: string, file: string, data: object): Promise { + await fs.mkdir(dir, { recursive: true }); + await fs.writeFile(join(dir, file), `${JSON.stringify(data, undefined, 2)}\n`, 'utf8'); +} + +describe('parsePnpmWorkspace', () => { + it('should parse simple packages list', () => { + const result = parsePnpmWorkspace('packages:\n - "packages/*"\n - "apps/*"\n'); + assert.deepEqual(result, ['packages/*', 'apps/*']); + }); + + it('should parse single-quoted values', () => { + const result = parsePnpmWorkspace("packages:\n - 'packages/*'\n"); + assert.deepEqual(result, ['packages/*']); + }); + + it('should parse unquoted values', () => { + const result = parsePnpmWorkspace('packages:\n - packages/*\n'); + assert.deepEqual(result, ['packages/*']); + }); + + it('should stop at next key', () => { + const result = parsePnpmWorkspace('packages:\n - packages/*\nother:\n - foo\n'); + assert.deepEqual(result, ['packages/*']); + }); + + it('should return empty for no packages key', () => { + const result = parsePnpmWorkspace('other:\n - foo\n'); + assert.deepEqual(result, []); + }); + + it('should skip comments', () => { + const result = parsePnpmWorkspace('packages:\n # comment\n - packages/*\n'); + assert.deepEqual(result, ['packages/*']); + }); +}); + +describe('resolveGlobs', () => { + it('should resolve simple wildcard', async () => { + await fs.mkdir(join(tempDir, 'packages', 'pkg-a'), { recursive: true }); + await fs.mkdir(join(tempDir, 'packages', 'pkg-b'), { recursive: true }); + const result = resolveGlobs(tempDir, ['packages/*']); + assert.equal(result.length, 2); + assert.ok(result.some(d => d.endsWith('pkg-a'))); + assert.ok(result.some(d => d.endsWith('pkg-b'))); + }); + + it('should resolve exact directory', async () => { + await fs.mkdir(join(tempDir, 'ui'), { recursive: true }); + const result = resolveGlobs(tempDir, ['ui']); + assert.equal(result.length, 1); + assert.ok(result[0].endsWith('ui')); + }); + + it('should resolve deep wildcard', async () => { + await fs.mkdir(join(tempDir, 'src', 'packages', 'core'), { recursive: true }); + await fs.mkdir(join(tempDir, 'src', 'packages', 'utils'), { recursive: true }); + const result = resolveGlobs(tempDir, ['src/**']); + assert.ok(result.length >= 3); + }); + + it('should skip non-existent dirs', () => { + const result = resolveGlobs(tempDir, ['nonexistent/*']); + assert.equal(result.length, 0); + }); + + it('should deduplicate', async () => { + await fs.mkdir(join(tempDir, 'packages', 'pkg-a'), { recursive: true }); + const result = resolveGlobs(tempDir, ['packages/*', 'packages/*']); + assert.equal(result.length, 1); + }); +}); + +describe('discoverWorkspaces', () => { + it('should return null when no workspaces', async () => { + await writeJson(tempDir, 'package.json', { name: 'root', version: '1.0.0' }); + const result = await discoverWorkspaces(tempDir); + assert.equal(result, null); + }); + + it('should discover npm workspaces (array format)', async () => { + await writeJson(tempDir, 'package.json', { + name: 'root', + version: '1.0.0', + workspaces: ['packages/*'], + }); + await writeJson(join(tempDir, 'packages', 'pkg-a'), 'package.json', { name: '@org/pkg-a', version: '1.0.0' }); + await writeJson(join(tempDir, 'packages', 'pkg-b'), 'package.json', { name: '@org/pkg-b', version: '2.0.0' }); + const result = await discoverWorkspaces(tempDir); + assert.ok(result !== null); + assert.equal(result.length, 2); + assert.equal(result[0].name, '@org/pkg-a'); + assert.equal(result[0].tagPrefix, '@org/pkg-a@'); + assert.equal(result[1].name, '@org/pkg-b'); + assert.equal(result[1].version, '2.0.0'); + }); + + it('should discover yarn/bun workspaces (object format)', async () => { + await writeJson(tempDir, 'package.json', { + name: 'root', + version: '1.0.0', + workspaces: { packages: ['packages/*'] }, + }); + await writeJson(join(tempDir, 'packages', 'my-lib'), 'package.json', { name: 'my-lib', version: '0.1.0' }); + const result = await discoverWorkspaces(tempDir); + assert.ok(result !== null); + assert.equal(result.length, 1); + assert.equal(result[0].name, 'my-lib'); + assert.equal(result[0].tagPrefix, 'my-lib@'); + }); + + it('should discover pnpm workspaces', async () => { + await writeJson(tempDir, 'package.json', { name: 'root', version: '1.0.0' }); + await fs.writeFile(join(tempDir, 'pnpm-workspace.yaml'), "packages:\n - 'packages/*'\n", 'utf8'); + await writeJson(join(tempDir, 'packages', 'core'), 'package.json', { name: '@app/core', version: '3.0.0' }); + const result = await discoverWorkspaces(tempDir); + assert.ok(result !== null); + assert.equal(result.length, 1); + assert.equal(result[0].name, '@app/core'); + }); + + it('should skip directories without package.json', async () => { + await writeJson(tempDir, 'package.json', { + name: 'root', + version: '1.0.0', + workspaces: ['packages/*'], + }); + await fs.mkdir(join(tempDir, 'packages', 'no-pkg'), { recursive: true }); + await writeJson(join(tempDir, 'packages', 'has-pkg'), 'package.json', { name: 'has-pkg', version: '1.0.0' }); + const result = await discoverWorkspaces(tempDir); + assert.ok(result !== null); + assert.equal(result.length, 1); + assert.equal(result[0].name, 'has-pkg'); + }); + + it('should skip packages without name', async () => { + await writeJson(tempDir, 'package.json', { + name: 'root', + version: '1.0.0', + workspaces: ['packages/*'], + }); + await writeJson(join(tempDir, 'packages', 'no-name'), 'package.json', { version: '1.0.0' }); + await writeJson(join(tempDir, 'packages', 'has-name'), 'package.json', { name: 'has-name', version: '1.0.0' }); + const result = await discoverWorkspaces(tempDir); + assert.ok(result !== null); + assert.equal(result.length, 1); + }); + + it('should return null when workspaces resolve to no packages', async () => { + await writeJson(tempDir, 'package.json', { + name: 'root', + version: '1.0.0', + workspaces: ['empty/*'], + }); + await fs.mkdir(join(tempDir, 'empty'), { recursive: true }); + const result = await discoverWorkspaces(tempDir); + assert.equal(result, null); + }); + + it('should return packages in topological order', async () => { + await writeJson(tempDir, 'package.json', { + name: 'root', + version: '1.0.0', + workspaces: ['packages/*'], + }); + await writeJson(join(tempDir, 'packages', 'app'), 'package.json', { + name: '@org/app', + version: '1.0.0', + dependencies: { '@org/core': '^1.0.0', '@org/utils': '^1.0.0' }, + }); + await writeJson(join(tempDir, 'packages', 'core'), 'package.json', { + name: '@org/core', + version: '1.0.0', + dependencies: { '@org/utils': '^1.0.0' }, + }); + await writeJson(join(tempDir, 'packages', 'utils'), 'package.json', { + name: '@org/utils', + version: '1.0.0', + }); + const result = await discoverWorkspaces(tempDir); + assert.ok(result !== null); + const names = result.map(p => p.name); + assert.ok(names.indexOf('@org/utils') < names.indexOf('@org/core')); + assert.ok(names.indexOf('@org/core') < names.indexOf('@org/app')); + }); +}); + +describe('topoSort', () => { + function makePkg(name: string, deps?: Record): PackageContext { + return { + dir: `/tmp/${name}`, + name, + version: '1.0.0', + pack: { version: '1.0.0', ...(deps ? { dependencies: deps } : {}) }, + tagPrefix: `${name}@`, + files: [`/tmp/${name}`], + }; + } + + it('should sort dependencies before dependents', () => { + const a = makePkg('a'); + const b = makePkg('b', { a: '^1.0.0' }); + const c = makePkg('c', { b: '^1.0.0' }); + const result = topoSort([c, b, a]); + const names = result.map(p => p.name); + assert.deepEqual(names, ['a', 'b', 'c']); + }); + + it('should handle no dependencies', () => { + const a = makePkg('a'); + const b = makePkg('b'); + const result = topoSort([a, b]); + assert.equal(result.length, 2); + }); + + it('should handle diamond dependency', () => { + const a = makePkg('a'); + const b = makePkg('b', { a: '^1.0.0' }); + const c = makePkg('c', { a: '^1.0.0' }); + const d = makePkg('d', { b: '^1.0.0', c: '^1.0.0' }); + const result = topoSort([d, c, b, a]); + const names = result.map(p => p.name); + assert.ok(names.indexOf('a') < names.indexOf('b')); + assert.ok(names.indexOf('a') < names.indexOf('c')); + assert.ok(names.indexOf('b') < names.indexOf('d')); + assert.ok(names.indexOf('c') < names.indexOf('d')); + }); + + it('should handle circular dependencies without infinite loop', () => { + const a = makePkg('a', { b: '^1.0.0' }); + const b = makePkg('b', { a: '^1.0.0' }); + const result = topoSort([a, b]); + assert.equal(result.length, 2); + }); + + it('should ignore external dependencies', () => { + const a = makePkg('a', { lodash: '^4.0.0', react: '^18.0.0' }); + const result = topoSort([a]); + assert.equal(result.length, 1); + assert.equal(result[0].name, 'a'); + }); +}); diff --git a/tsconfig.build.json b/tsconfig.build.json index b90fc83..68f033a 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,4 +1,12 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "ignoreDeprecations": "6.0", + "target": "es5", + "lib": ["es5", "dom"], + "module": "commonjs", + "moduleResolution": "node", + "rootDir": "./src" + }, "include": ["src"] } diff --git a/tsconfig.json b/tsconfig.json index 81af2b6..d0bc7b3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,12 @@ { "compilerOptions": { - "target": "es5", - "lib": ["es5", "dom"], + "target": "es2020", + "lib": ["es2020"], + "types": ["node"], "strict": true, "allowJs": true, - "module": "commonjs", - "moduleResolution": "node", + "module": "node16", + "moduleResolution": "node16", "esModuleInterop": true, "noUnusedLocals": true, "noUnusedParameters": true,