[
  {
    "path": ".editorconfig",
    "content": "# editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.{md,jade}]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  env: {\n    es6: true,\n    mocha: true,\n    node: true,\n  },\n  extends: ['standard', 'eslint:recommended', 'plugin:import/typescript', 'raine', 'prettier'],\n  overrides: [\n    {\n      files: ['**/*.ts'],\n      parser: '@typescript-eslint/parser',\n      parserOptions: {\n        ecmaVersion: 2018,\n        sourceType: 'module',\n        project: './tsconfig.json',\n      },\n      extends: ['plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/recommended'],\n      globals: {\n        Atomics: 'readonly',\n        SharedArrayBuffer: 'readonly',\n      },\n      plugins: ['@typescript-eslint'],\n      rules: {\n        '@typescript-eslint/no-explicit-any': 'off',\n        '@typescript-eslint/no-non-null-assertion': 'off',\n        '@typescript-eslint/no-use-before-define': 'error',\n        '@typescript-eslint/no-unused-vars': [\n          'error',\n          {\n            caughtErrors: 'none',\n            // using destructuring to omit properties from objects\n            destructuredArrayIgnorePattern: '^_',\n            argsIgnorePattern: '^_',\n            varsIgnorePattern: '^_',\n          },\n        ],\n        '@typescript-eslint/array-type': [\n          'error',\n          {\n            default: 'array',\n          },\n        ],\n      },\n    },\n  ],\n  plugins: ['jsdoc'],\n  rules: {\n    'jsdoc/require-jsdoc': [\n      'error',\n      {\n        contexts: ['VariableDeclarator > ArrowFunctionExpression'],\n        require: {\n          ClassDeclaration: true,\n          ClassExpression: true,\n        },\n      },\n    ],\n  },\n}\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Enforce Unix newlines\n* text=auto eol=lf\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "## Filing an issue\n\nMake sure you read the list of [known issues](https://github.com/raineorshine/npm-check-updates#known-issues) and search for [similar issues](https://github.com/raineorshine/npm-check-updates/issues) before filing an issue.\n\n## Known Issues\n\n- If `ncu` prints output that does not seem related to this package, it may be conflicting with another executable such as `ncu-weather-cli` or Nvidia CUDA. Try using the long name instead: `npm-check-updates`.\n- Windows: If npm-check-updates hangs, try setting the package file explicitly: `ncu --packageFile package.json`. You can run `ncu --loglevel verbose` to confirm that it was incorrectly waiting for stdin. See [#136](https://github.com/raineorshine/npm-check-updates/issues/136#issuecomment-155721102).\n\nWhen filing an issue, please include:\n\n- node version\n- npm version\n- npm-check-updates version\n- the relevant package names and their specified versions from your package file\n- ...or the output from `npm -g ls --depth=0` if using global mode\n\n## Executable Stack Trace\n\nThe Vite Build uses SSR to bundle all dependencies for efficiency. There currently is no source map for `./build/cli.js`. To execute npm-check-updates with an accurate stack trace run the following\n\n```sh\ngit clone https://github.com/raineorshine/npm-check-updates /MY_PROJECTS\nnpx tsx /MY_PROJECTS/npm-check-updates/src/bin/cli.ts\n```\n\n## Design Guidelines\n\nThe _raison d'être_ of npm-check-updates is to upgrade package.json dependencies to the latest versions, ignoring specified versions. Suggested features that do not fit within this objective will be considered out of scope.\n\nnpm-check-updates maintains a balance between minimalism and customizability. The default execution with no options will always produce simple, clean output. If you would like to add additional information to ncu's output, you may propose a new value for the `--format` option.\n\n## Adding a new CLI or module option\n\nAll of ncu's options are generated from [/src/cli-options.ts](https://github.com/raineorshine/npm-check-updates/blob/main/src/cli-options.ts). You can add a new option to this file and then run `npm run build` to automatically generate README, CLI help text, and TypeScript definitions.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n---\n\n- [] I have searched for [similar issues](https://github.com/raineorshine/npm-check-updates/issues)\n\n---\n\n## Steps to Reproduce\n\n.ncurc:\n\n<!-- If you use a .ncurc config file, specify it here. ncu options have a dramatic effect on its behavior. -->\n\n```js\n\n```\n\nDependencies:\n\n<!-- If the suggested upgrades are not what you expect, make sure to list your package.json dependencies here so the issue can be reproduced. -->\n\n```json\n\n```\n\nSteps:\n\n<!-- The exact steps taken to arrive at the unexpected behavior. -->\n\n## Current Behavior\n\n<!-- Describe the existing (incorrect) behavior. -->\n\n## Expected Behavior\n\n<!-- Describe the desired behavior. -->\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "name: 'CodeQL'\n\non:\n  push:\n    branches:\n      - main\n      - '!dependabot/**'\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches:\n      - main\n  schedule:\n    - cron: '0 2 * * 5'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v2\n        with:\n          languages: 'javascript'\n\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v2\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint\n\non:\n  push:\n    branches:\n      - main\n      - '!dependabot/**'\n  pull_request:\n    branches:\n      - '**'\n\nenv:\n  FORCE_COLOR: 2\n  NODE: 24\n\npermissions:\n  contents: read\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Clone repository\n        uses: actions/checkout@v3\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version: ${{ env.NODE }}\n          cache: npm\n\n      - name: Install npm dependencies\n        run: npm ci\n\n      - name: Run lint\n        run: npm run lint\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags:\n      - 'v*'\n\njobs:\n  release:\n    name: Release\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v5\n\n      - name: Release\n        uses: softprops/action-gh-release@v2\n        with:\n          generate_release_notes: true\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Tests\n\non:\n  push:\n    branches:\n      - main\n      - '!dependabot/**'\n  pull_request:\n    branches:\n      - '**'\n\nenv:\n  FORCE_COLOR: 2\n\npermissions:\n  contents: read\n\njobs:\n  run:\n    permissions:\n      contents: read # for actions/checkout to fetch code\n\n    name: Node ${{ matrix.node }} on ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        node: [20, 22]\n        os: [ubuntu-latest, windows-latest]\n\n    steps:\n      - name: Clone repository\n        uses: actions/checkout@v4\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node }}\n          cache: npm\n\n      - name: Enable corepack\n        run: corepack enable\n\n      - name: Install npm dependencies\n        run: npm ci\n\n      - name: Build\n        run: npm run build\n\n      - name: Unit Tests\n        run: npm run test:unit\n\n      - name: Bun Tests\n        run: npm run test:bun\n\n      - name: E2E Tests\n        run: npm run test:e2e\n        if: startsWith(matrix.os, 'ubuntu') && matrix.node == 20\n"
  },
  {
    "path": ".gitignore",
    "content": "lib-cov\n*.seed\n*.log\n*.csv\n*.dat\n*.out\n*.pid\n*.gz\n\npids\nlogs\nresults\n\nnpm-debug.log\nnode_modules\nbuild\n\n.idea\n*.iml\nyarn.lock\n.DS_store\n\n# test files\n/test/temp_package*.json\n/test/.ncurc.json\n\n# Agent files\n.claude\n"
  },
  {
    "path": ".hooks/post-commit",
    "content": "#!/usr/bin/env bash\nSHORT_SHA=$(git rev-parse HEAD)\nLINT_LOG=\"$TMPDIR\"/lint.\"$SHORT_SHA\".log\n\n# strip color\nstrip() {\n  sed -r \"s/\\x1B\\[([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGK]//g\"\n}\n\n# display a notification\nnotify() {\n  # if osascript is not supported, do nothing\n  if [ -f /usr/bin/osascript ]; then\n    # read back in the lint errors\n    ERRORS=$(sed 1,4d \"$LINT_LOG\")\n\n    # Trigger apple- or OSA-script on supported platforms\n    /usr/bin/osascript -e \"display notification \\\"$ERRORS\\\" with title \\\"$*\\\"\"\n  fi\n\n  # clean up\n  rm \"$LINT_LOG\"\n}\n\n# ensure failed lint exit code passes through sed\nset -o pipefail\n\n# Do NOT run this when rebasing or we can't get the branch\nbranch=$(git branch --show-current)\nif [ -z \"$branch\" ]; then\n  exit 0\nfi\n\n# Lint in the background, not blocking the terminal and piping all output to a file.\n# If the lint fails, trigger a notification (on supported platforms) with at least the first error shown.\n# We pipe output so that the terminal (tmux, vim, emacs etc.) isn't borked by stray output.\nnpm run lint:src | strip &>\"$LINT_LOG\" || notify \"Lint Error\" &\n"
  },
  {
    "path": ".hooks/pre-push",
    "content": "#!/bin/sh\nfail=0\n\nnpm run lint || fail=1\nnpm run prettier -- --check || fail=1\n\nif [ \"$fail\" -ne 0 ]; then\n  exit 1\nfi\n"
  },
  {
    "path": ".markdownlint.js",
    "content": "module.exports = {\n  // use code indentation rather than code fencing so that extended help can be used for both the CLI and README\n  'code-block-style': 0,\n  'first-line-heading': 0,\n  'line-length': 0,\n  'no-bare-urls': 0,\n  'no-duplicate-heading': {\n    siblings_only: true,\n  },\n  // inline HTML used to create tables without headers\n  'no-inline-html': 0,\n  'commands-show-output': 0,\n}\n"
  },
  {
    "path": ".ncurc.js",
    "content": "module.exports = {\n  format: 'group',\n  reject: [\n    // breaking\n    'eslint',\n    'eslint-plugin-n',\n    'eslint-plugin-promise',\n    // esm only modules\n    '@types/chai',\n    '@types/chai-as-promised',\n    '@types/remote-git-tags',\n    'camelcase',\n    'chai-as-promised',\n    'find-up',\n    'chai',\n    'p-map',\n    'remote-git-tags',\n    'untildify',\n  ],\n}\n"
  },
  {
    "path": ".npmrc",
    "content": "script-shell = bash"
  },
  {
    "path": ".prettierignore",
    "content": "build/"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n  \"arrowParens\": \"avoid\",\n  \"importOrder\": [\"^\\\\.\\\\./\", \"^\\\\./\"],\n  \"importOrderSortSpecifiers\": true,\n  \"overrides\": [{ \"files\": \"*.ts\", \"options\": { \"parser\": \"typescript\" } }],\n  \"plugins\": [\"@trivago/prettier-plugin-sort-imports\"],\n  \"printWidth\": 120,\n  \"semi\": false,\n  \"singleQuote\": true,\n  \"tabWidth\": 2,\n  \"trailingComma\": \"all\",\n  \"useTabs\": false\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n  \"editor.formatOnSave\": true\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nThis file only documents **major version** releases. For smaller releases, you're stuck reading the [commit history](https://github.com/raineorshine/npm-check-updates/commits/main).\n\n## [18.0.0] - 2025-04-21\n\n### Breaking\n\nThe **only** breaking change in v18 is with the `-g/--global` flag.\n\n`npm-check-updates -g` will now auto-detect your package manager based on the execution path. Previously, it defaulted to `npm`.\n\n- `yarn dlx ncu -g --packageManager yarn` → `yarn dlx ncu -g`\n- `pnpm dlx ncu --global --packageManager pnpm` → `pnpm dlx ncu -g`\n- `bunx ncu -g--packageManager pnpm` → `bunx ncu -g`\n\nIf for some reason you were running `ncu -g` with an alternative package manager and relying on it checking the global `npm` packages, you will need to now explicitly specify npm:\n\n- `ncu -g` → `ncu -g--packageManager npm`\n\nThanks to @LuisFerLCC for the improvement (#1514).\n\n<https://github.com/raineorshine/npm-check-updates/compare/v17.1.18...v18.0.0>\n\n## [17.0.0] - 2024-07-31\n\n### Breaking\n\n- Require node >= 18.18.0\n- Deprecated versions are no longer excluded by default, as it requires fetching package info for every published version, significantly slowing down upgrades.\n  - You can opt in with `--no-deprecated` in the CLI or `deprecated: false` in your `ncurc` config.\n- In workspaces mode, `--root` is now set by default (#1353)\n  - To **not** check the root package.json, use `--no-root`.\n- If you have a [packageManager](https://nodejs.org/api/packages.html#packagemanager) field in your package.json, it is now upgraded by default (#1390)\n  - Use `--dep prod,dev,optional` for the old behavior.\n\n<https://github.com/raineorshine/npm-check-updates/compare/v16.14.20...v17.0.0>\n\n## [16.0.0] - 2022-07-23\n\n### Breaking\n\n- Automatic detection of package data on stdin has been removed. This feature was deprecated in `v14.0.0`. Add `--stdin` for old behavior.\n- Wild card filters now apply to scoped packages. Previously, `ncu -f '*vite*'` would not include `@vitejs/plugin-react`. Now, filters will match any part of the package name, including the scope. Use a more specific glob or regex expression for old behavior.\n\n<https://github.com/raineorshine/npm-check-updates/compare/v15.3.4...v16.0.0>\n\n## [15.0.0] - 2022-06-30\n\n### Breaking\n\n- node >= 14.14 is now required (#1145)\n  - Needed to upgrade `update-notifier` with has a moderate severity vulnerability\n- yarn autodetect has been improved (#1148)\n  - This is a patch, though _technically_ it is breaking. In the obscure case where `--packageManager` is not given, there is no `package-lock.json` in the current folder, and there is a `yarn.lock` in an ancestor directory, npm-check-updates will now use yarn.\n  - More practically, if you needed to specify `--packageManager yarn` explicitly before, you may not have to now\n\n<https://github.com/raineorshine/npm-check-updates/compare/v14.1.1...v15.0.0>\n\n## [14.0.0] - 2022-06-16\n\n### Breaking\n\nPrerelease versions are now \"upgraded\" to versions with a different [preid](https://docs.npmjs.com/cli/v8/commands/npm-version#preid).\n\nFor example, if you have a dependency at `1.3.3-next.1` and the version fetched by ncu is `1.2.3-dev.2`, ncu **will** suggest an \"upgrade\" to `1.2.3-dev.2`. This is because prerelease versions with different preids are incomparable. Since they are incomparable, ncu now assumes the fetched version is desired.\n\nSince this change affects only prereleases, there is no impact on default `ncu` usage that fetches the `latest` version. With `--pre 1` or `--target newest` or `--target greatest`, this change could affect which version is suggested if versions with different preids are published. The change was made to support the new `--target @[tag]` feature.\n\nIf you have a use case where this change is not what is desired, please [report an issue](https://github.com/raineorshine/npm-check-updates/issues/new). The intention is for zero disruption to current usage.\n\n### Features\n\n- You can now upgrade to a specific tag, e.g. `--target @next`. Thanks to [IMalyugin](https://github.com/IMalyugin).\n\n<https://github.com/raineorshine/npm-check-updates/compare/v13.1.5...v14.0.0>\n\n## [13.0.0] - 2022-05-15\n\n### Breaking\n\n- node >= 14 is now required\n- Several options which have long been deprecated have been removed:\n  - `--greatest` - Instead use `--target greatest`\n  - `--newest` - Instead use `--target newest`\n  - `--ownerChanged` - Instead use `--format ownerChanged`\n  - `--semverLevel` - Renamed to `--target`\n\n<https://github.com/raineorshine/npm-check-updates/compare/v12.5.12...v13.0.0>\n\n## [12.0.0] - 2021-11-01\n\n### Breaking\n\n- node >= 12 is required. Time to upgrade that old-ass server you never touch.\n- `peerDependencies` are now excluded by default. Peer dependencies should use the **lowest** possible version that works. The old behavior encouraged a bad practice of upgrading peer dependencies. You can use `--dep prod,dev,optional,peer` for the old behavior ([#951](https://github.com/raineorshine/npm-check-updates/issues/951)).\n- Dependencies with `>` will be converted to `>=`. The old behavior was causing upgrades to `> [latest]` which was impossible ([#957](https://github.com/raineorshine/npm-check-updates/issues/957)).\n\n## Other\n\n- TypeScript! There is a new build process, so if you have any issues with the executable or types, please report. It should be a non-breaking change if I did it correctly ([#888](https://github.com/raineorshine/npm-check-updates/issues/888)).\n- WHen using `npm-check-updates` as a module, `vm` (versionmanager) is no longer exported. It was previously exposed for testing purposes, but was never part of the official API.\n\n<https://github.com/raineorshine/npm-check-updates/compare/v11.8.5...v12.0.0>\n\n## [11.0.0] - 2021-01-20\n\n### Breaking\n\n- `--packageFile` - Now interprets its argument as a glob pattern. It is possible that a previously supplied argument may be interpreted differently now (though I'm not aware of specific instances). Due to our conservative release policy we are releasing as a major version upgrade and allowing developers to assess for themselves.\n\n### Features\n\n- `--deep` - Run recursively in current working directory. Alias of `--packageFile '**/package.json'`.\n\nSee: [#785](https://github.com/raineorshine/npm-check-updates/issues/785)\n\n<https://github.com/raineorshine/npm-check-updates/compare/v10.3.1...v11.0.0>\n\n## [10.0.0] - 2020-11-08\n\n### Breaking\n\n- Specifying both the `--filter` option and argument filters will now throw an error. Use one or the other. Previously the arguments would override the `--filter` option, which made for a confusing result when accidentally not quoting the option in the shell. This change is only breaking for those who are relying on the incorrect behavior of argument filters overriding `--filter`.\n\nSee: [#759](https://github.com/raineorshine/npm-check-updates/issues/759#issuecomment-723587297)\n\n<https://github.com/raineorshine/npm-check-updates/compare/v9.2.4...v10.0.0>\n\n## [9.0.0] - 2020-09-10\n\n### Breaking\n\n- Versions marked as `deprecated` in npm are now ignored by default. If the latest version is deprecated, the next highest non-deprecated version will be suggested. Use `--deprecated` to include deprecated versions (old behavior).\n\n<https://github.com/raineorshine/npm-check-updates/compare/v8.1.1...v9.0.0>\n\n## [8.0.0] - 2020-08-29\n\n### Breaking\n\n- `--semverLevel major` is now `--target minor`. `--semverLevel minor` is now `--target patch`. This change was made to provide more intuitive semantics for `--semverLevel` (now `--target`). Most people assumed it meant the inclusive upper bound, so now it reflects that. [a2111f4c2](https://github.com/raineorshine/npm-check-updates/commits/a2111f4c2)\n- Programmatic usage: `run` now defaults to `silent: true` instead of `loglevel: 'silent`, unless `loglevel` is explicitly specified. If you overrode `silent` or `loglevel`, this may affect the logging behavior. [423e024](https://github.com/raineorshine/npm-check-updates/commits/423e024)\n\n### Deprecated\n\nOptions that controlled the target version (upper bound) of upgrades have been consolidated under `--target`. The old options are aliased with a deprecation warning and will be removed in the next major version. No functionality has been removed.\n\n- `--greatest`: Renamed to `--target greatest`\n- `--newest`: Renamed to `--target newest`\n- `--semverLevel`: Renamed to `--target`\n\nSee: [7eca5bf3](https://github.com/raineorshine/npm-check-updates/commits/7eca5bf3)\n\n### Features\n\n#### Doctor Mode\n\n[#722](https://github.com/raineorshine/npm-check-updates/pull/722)\n\nUsage: `ncu --doctor [-u] [options]`\n\nIteratively installs upgrades and runs tests to identify breaking upgrades. Add `-u` to execute (modifies your package file, lock file, and node_modules).\n\nTo be more precise:\n\n1. Runs `npm install` and `npm test` to ensure tests are currently passing.\n2. Runs `ncu -u` to optimistically upgrade all dependencies.\n3. If tests pass, hurray!\n4. If tests fail, restores package file and lock file.\n5. For each dependency, install upgrade and run tests.\n6. When the breaking upgrade is found, saves partially upgraded package.json (not including the breaking upgrade) and exits.\n\nExample:\n\n```sh\n$ ncu --doctor -u\nnpm install\nnpm run test\nncu -u\nnpm install\nnpm run test\nFailing tests found:\n/projects/myproject/test.js:13\n  throw new Error('Test failed!')\n  ^\nNow let’s identify the culprit, shall we?\nRestoring package.json\nRestoring package-lock.json\nnpm install\nnpm install --no-save react@16.0.0\nnpm run test\n  ✓ react 15.0.0 → 16.0.0\nnpm install --no-save react-redux@7.0.0\nnpm run test\n  ✗ react-redux 6.0.0 → 7.0.0\nSaving partially upgraded package.json\n```\n\n#### GitHub URLs\n\nAdded support for GitHub URLs.\n\nSee: [f0aa792a4](https://github.com/raineorshine/npm-check-updates/commits/f0aa792a4)\n\nExample:\n\n```json\n{\n  \"dependencies\": {\n    \"chalk\": \"https://github.com/chalk/chalk#v2.0.0\"\n  }\n}\n```\n\n#### npm aliases\n\nAdded support for npm aliases.\n\nSee: [0f6f35c](https://github.com/raineorshine/npm-check-updates/commits/0f6f35c)\n\nExample:\n\n```json\n{\n  \"dependencies\": {\n    \"request\": \"npm:postman-request@2.88.1-postman.16\"\n  }\n}\n```\n\n#### Owner Changed\n\n[#621](https://github.com/raineorshine/npm-check-updates/pull/621)\n\nUsage: `ncu --ownerChanged`\n\nCheck if the npm user that published the package has changed between current and upgraded version.\n\nOutput values:\n\n- Owner changed: `*owner changed*`\n- Owner has not changed: _no output_\n- Owner information not available: `*unknown*`\n\nExample:\n\n```sh\n$ ncu --ownerChanged\nChecking /tmp/package.json\n[====================] 1/1 100%\n\n mocha  ^7.1.0  →  ^8.1.3  *owner changed*\n\nRun ncu -u to upgrade package.json\n```\n\n### Commits\n\n<https://github.com/raineorshine/npm-check-updates/compare/v7.1.1...v8.0.0>\n\n## [7.0.0] - 2020-06-09\n\n### Breaking\n\n- Removed bower support (4e4b47fd3bb567435b456906d0106ef442bf46fe)\n\n### Patch\n\n- Fix use of \"<\" with single digit versions (f04d00e550ce606893bee77b78ef2a0b2a50246a)\n\n### Other\n\n- Change eslint configuration\n- Update dependencies\n- Replace cint methods with native methods\n- Add CI via GitHub Actions workflow\n\n<https://github.com/raineorshine/npm-check-updates/compare/v6.0.2...v7.0.0>\n\n## [6.0.0] - 2020-05-14\n\n### Breaking\n\n- `--semverLevel` now supports version ranges. This is a breaking change since version ranges are no longer ignored by `--semverLevel`, which may result in some dependencies having new suggested updates.\n\nIf you are not using `--semverLevel`, NO CHANGE! 😅\n\n<https://github.com/raineorshine/npm-check-updates/compare/v5.0.0...v6.0.0>\n\n## [5.0.0] - 2020-05-11\n\n### Breaking\n\n~node >= 8~\nnode >= 10.17\n\nBump minimum node version to `v10.17.0` due to `move-file` #651\n\nIf `ncu` was working for you on `v4.x`, then `v5.0.0` will still work. Just doing a major version bump since ncu's officially supported node version is changing. `v4` should be patched to be compatible with node `v8`, but I'll hold off unless someone requests it.\n\n<https://github.com/raineorshine/npm-check-updates/compare/v4.1.2...v5.0.0>\n\n## [4.0.0] - 2019-12-10\n\nncu v3 excluded prerelease versions (`-alpha`, `-beta`, etc) from the remote by default, as publishing prerelease versions to `latest` is unconventional and not recommended. Prereleases versions can be included by specifying `--pre` (and is implied in options `--greatest` and `--newest`).\n\nHowever, when you are already specifying a prerelease version in your package.json dependencies, then clearly you want ncu to find newer prerelease versions. This is now default in v4, albeit with the conservative approach of sticking to the `latest` tag.\n\n### Migration\n\nNo effect for most users.\n\nIf a prerelease version is published on the `latest` tag, and you specify a prerelease version in your package.json, ncu will now suggest upgrades for it.\n\nIf a prerelease version is published on a different tag, there is no change from ncu v3; you will still need `--pre`, `--greatest`, or `--newest` to get prerelease upgrades.\n\n<https://github.com/raineorshine/npm-check-updates/compare/v3.2.2...v4.0.0>\n\n## [3.0.0] - 2019-03-07\n\n### Breaking\n\n#### node < 8 deprecated\n\nThe required node version has been updated to allow the use of newer JavaScript features and reduce maintenance efforts for old versions.\n\n#### System npm used\n\nIn ncu v2, an internally packaged npm was used for version lookups. When this became out-of-date and differed considerably from the system npm problems would occur. In ncu v3, the system-installed npm will be used for all lookups. This comes with the maintenance cost of needing to upgrade ncu whenever the output format of npm changes.\n\n#### Installed modules ignored\n\nIn ncu v2, out-of-date dependencies in package.json that were installed up-to-date (e.g. `^1.0.0` specified and `1.0.1` installed) were ignored by ncu. Installed modules are now completely ignored and ncu only consider your package.json. This change was made to better match users’ expectations.\n\n#### Existing version ranges that satisfy latest are ignored (-a by default)\n\nIn ncu v2, if you had `^1.0.0` in your package.json, a newly released `1.0.1` would be ignored by ncu. The logic was that `^1.0.0` is a range that includes `1.0.1`, so you don’t really need to change the version specified in your package.json, you just need to run `npm update`. While logical, that turned out to be quite confusing to users. In ncu v3, the package.json will always be upgraded if there is a newer version (same as `-a` in v2). The old default behavior is available via the `--minimal` option.\n\n#### Prerelease versions ignored\n\nIn ncu v2, any version published to the `latest` tag was assumed to be a stable release version. In practice, occasional package authors would accidentally or unconventionally publish `-alpha`, `-beta`, and `-rc` versions to the `latest` tag. While I still consider this a bad practice, ncu v3 now ignores these prerelease versions by default to better match users’ expectations. The old behavior is available via the `--pre 1` option. (When `--newest` or `--greatest` are set, `--pre 1` is set by default, and can be disabled with `--pre 0`).\n\n#### Options changed: `-m`, `--prod`, `--dev`, `--peer`\n\nIn order to only target one or more dependency sections, ncu now uses the `--dep` option instead of separate options for each section.\n\n`--prod` is now `--dep prod`\n`--dev` is now `--dep dev`\n`--dev --peer` is now `--dep dev,peer` etc\n\nThe `--packageManager` alias has changed from `-m` to `-p` to make room for `--minimal` as `-m`.\n\n<https://github.com/raineorshine/npm-check-updates/compare/v2.15.0...v3.0.0>\n\n## [2.0.0] - 2005-08-14\n\nv2 has a few important differences from v1:\n\n- Newer published versions that satisfy the specified range are _not_ upgraded by default (e.g. `1.0.0` to `1.1.0`). This change was made because `npm update` handles upgrades within the satisfied range just fine, and npm-check-updates is primarily intended to provide functionality not otherwise provided by npm itself. These satisfied dependencies will still be shown when you run npm-check-updates, albeit with a short explanation. **For the old behavior, add the -ua/--upgradeAll option.**\n- The command-line argument now specifies a package name filter (e.g. `ncu /^gulp-/`). For the old behavior (specifying an alternative package.json), pipe the package.json through stdin.\n- Use the easier-to-type `ncu` instead of `npm-check-updates`. `npm-check-updates` is preserved for backwards-compatibility.\n- Allow packageData to be specified as an option\n- Colored table output\n- Add -a/--upgradeAll\n- Add -e/--error-level option\n- Add -j/--json and --jsonFlat flags for json output\n- Add -r/--registry option for specifying third-party npm registry\n- Add -t/--greatest option to search for the highest versions instead of the default latest stable versions.\n- Remove -f/--filter option and move to command-line argument\n- Replace < and <= with ^\n- Automatically look for the closest descendant package.json if not found in current directory\n- Add ncu alias\n- Export functionality to allow for programmatic use\n- Bug fixes and refactoring\n- Full unit test coverage!\n\n<https://github.com/raineorshine/npm-check-updates/compare/v1.5.1...v2.0.0>\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM        node:latest\n\nRUN         npm install -g npm-check-updates\n\nWORKDIR     /app\n\nENTRYPOINT  [\"npm-check-updates\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2025 Tomas Junnonen\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# npm-check-updates\n\n[![npm version](https://img.shields.io/npm/v/npm-check-updates)](https://www.npmjs.com/package/npm-check-updates)\n[![Build Status](https://img.shields.io/github/actions/workflow/status/raineorshine/npm-check-updates/test.yml?branch=main&label=tests&logo=github)](https://github.com/raineorshine/npm-check-updates/actions?query=workflow%3ATests+branch%3Amain)\n\n**npm-check-updates upgrades your package.json dependencies to the _latest_ versions, ignoring specified versions.**\n\n- maintains existing semantic versioning _policies_, i.e. `\"react\": \"^17.0.2\"` to `\"react\": \"^18.3.1\"`.\n- _only_ modifies package.json file. Run `npm install` to update your installed packages and package-lock.json.\n- sensible defaults, but highly customizable\n- compatible with npm, yarn, pnpm, deno, and bun\n- CLI and module usage\n\n<img width=\"500\" alt=\"example output\" src=\"https://github.com/user-attachments/assets/4808618b-ac20-4fc0-92e0-a777de70a2b6\">\n\n$${\\color{red}Red}$$ major upgrade (and all [major version zero](https://semver.org/#spec-item-4))<br/>\n$${\\color{cyan}Cyan}$$ minor upgrade<br/>\n$${\\color{green}Green}$$ patch upgrade<br/>\n\n## Installation\n\nInstall globally to use `npm-check-updates` or the shorter `ncu`:\n\n```sh\nnpm install -g npm-check-updates\n```\n\nOr run with [npx](https://docs.npmjs.com/cli/v7/commands/npx) (only the long form is supported):\n\n```sh\nnpx npm-check-updates\n```\n\n## Usage\n\nCheck the latest versions of all project dependencies:\n\n```sh\n$ ncu\nChecking package.json\n[====================] 5/5 100%\n\n eslint             7.32.0  →    8.0.0\n prettier           ^2.7.1  →   ^3.0.0\n svelte            ^3.48.0  →  ^3.51.0\n typescript         >3.0.0  →   >4.0.0\n untildify          <4.0.0  →   ^4.0.0\n webpack               4.x  →      5.x\n\nRun ncu -u to upgrade package.json\n```\n\nUpgrade a project's package file:\n\n> **Make sure your package file is in version control and all changes have been committed. This _will_ overwrite your package file.**\n\n```sh\n$ ncu -u\nUpgrading package.json\n[====================] 1/1 100%\n\n express           4.12.x  →   4.13.x\n\nRun npm install to install new versions.\n\n$ npm install      # update installed packages and package-lock.json\n```\n\nCheck global packages:\n\n```sh\nncu -g\n```\n\n## Interactive Mode\n\nChoose which packages to update in interactive mode:\n\n```sh\nncu --interactive\nncu -i\n```\n\n![ncu --interactive](https://user-images.githubusercontent.com/750276/175337598-cdbb2c46-64f8-44f5-b54e-4ad74d7b52b4.png)\n\nCombine with `--format group` for a truly _luxe_ experience:\n\n![ncu --interactive --format group](https://user-images.githubusercontent.com/750276/175336533-539261e4-5cf1-458f-9fbb-a7be2b477ebb.png)\n\n### Keys\n\n- <kbd>↑</kbd><kbd>↓</kbd> Select a package\n- <kbd>Space</kbd> Toggle selection\n- <kbd>a</kbd> Toggle all\n- <kbd>Enter</kbd> Upgrade\n\n## Filter packages\n\nFilter packages using the `--filter` option or adding additional cli arguments:\n\n```sh\n# upgrade only mocha\nncu mocha\nncu -f mocha\nncu --filter mocha\n\n# upgrade only chalk, mocha, and react\nncu chalk mocha react\nncu chalk, mocha, react\nncu -f \"chalk mocha react\"\n```\n\nFilter with wildcards or regex:\n\n```sh\n# upgrade packages that start with \"react-\"\nncu react-*\nncu \"/^react-.*$/\"\n```\n\nExclude specific packages with the `--reject` option or prefixing a filter with `!`. Supports strings, wildcards, globs, comma-or-space-delimited lists, and regex:\n\n```sh\n# upgrade everything except nodemon\nncu \\!nodemon\nncu -x nodemon\nncu --reject nodemon\n\n# upgrade packages that do not start with \"react-\".\nncu \\!react-*\nncu '/^(?!react-).*$/' # mac/linux\nncu \"/^(?!react-).*$/\" # windows\n```\n\nAdvanced filters: [filter](https://github.com/raineorshine/npm-check-updates#filter), [filterResults](https://github.com/raineorshine/npm-check-updates#filterresults), [filterVersion](https://github.com/raineorshine/npm-check-updates#filterversion)\n\n## How dependency updates are determined\n\n- Direct dependencies are updated to the latest stable version:\n  - `2.0.1` → `2.2.0`\n  - `1.2` → `1.3`\n  - `0.1.0` → `1.0.1`\n- Range operators are preserved and the version is updated:\n  - `^1.2.0` → `^2.0.0`\n  - `1.x` → `2.x`\n  - `>0.2.0` → `>0.3.0`\n- \"Less than\" is replaced with a wildcard:\n  - `<2.0.0` → `^3.0.0`\n  - `1.0.0 < 2.0.0` → `^3.0.0`\n- \"Any version\" is preserved:\n  - `*` → `*`\n- Prerelease versions are ignored by default.\n  - Use `--pre` to include prerelease versions (e.g. `alpha`, `beta`, `build1235`)\n- Choose what level to upgrade to:\n  - With `--target semver`, update according to your specified [semver](https://semver.org/) version ranges:\n    - `^1.1.0` → `^1.9.99`\n  - With `--target minor`, strictly update the patch and minor versions (including major version zero):\n    - `0.1.0` → `0.2.1`\n  - With `--target patch`, strictly update the patch version (including major version zero):\n    - `0.1.0` → `0.1.2`\n  - With `--target @next`, update to the version published on the `next` tag:\n    - `0.1.0` -> `0.1.1-next.1`\n\n## Options\n\nOptions are merged with the following precedence:\n\n1. Command line options\n2. Local [Config File](#config-file) (current working directory)\n3. Project Config File (next to package.json)\n4. User Config File (`$HOME`)\n\nOptions that take no arguments can be negated by prefixing them with `--no-`, e.g. `--no-peer`.\n\n<!-- BEGIN Options -->\n<!-- Do not edit this section by hand. It is auto-generated in build-options.ts. Run \"npm run build\" or \"npm run build:options\" to build. -->\n\n<table>\n  <tr>\n    <td>--cache</td>\n    <td>Cache versions to a local cache file. Default <code>--cacheFile</code> is ~/.ncu-cache.json and default <code>--cacheExpiration</code> is 10 minutes.</td>\n  </tr>\n  <tr>\n    <td>--cacheClear</td>\n    <td>Clear the default cache, or the cache file specified by <code>--cacheFile</code>.</td>\n  </tr>\n  <tr>\n    <td>--cacheExpiration &lt;min&gt;</td>\n    <td>Cache expiration in minutes. Only works with <code>--cache</code>. (default: 10)</td>\n  </tr>\n  <tr>\n    <td>--cacheFile &lt;path&gt;</td>\n    <td>Filepath for the cache file. Only works with <code>--cache</code>. (default: \"~/.ncu-cache.json\")</td>\n  </tr>\n  <tr>\n    <td>--color</td>\n    <td>Force color in terminal.</td>\n  </tr>\n  <tr>\n    <td>--concurrency &lt;n&gt;</td>\n    <td>Max number of concurrent HTTP requests to registry. (default: 8)</td>\n  </tr>\n  <tr>\n    <td>--configFileName &lt;s&gt;</td>\n    <td>Config file name. (default: .ncurc.{json,yml,js,cjs})</td>\n  </tr>\n  <tr>\n    <td>--configFilePath &lt;path&gt;</td>\n    <td>Directory of .ncurc config file. (default: directory of <code>packageFile</code>)</td>\n  </tr>\n  <tr>\n    <td><a href=\"#cooldown\">-c, --cooldown &lt;period&gt;</a></td>\n    <td>Sets a minimum age for package versions to be considered for upgrade. Accepts a number (days) or a string with a unit: \"7d\" (days), \"12h\" (hours), \"30m\" (minutes). Reduces the risk of installing newly published, potentially compromised packages.</td>\n  </tr>\n  <tr>\n    <td>--cwd &lt;path&gt;</td>\n    <td>Working directory in which npm will be executed.</td>\n  </tr>\n  <tr>\n    <td>--deep</td>\n    <td>Run recursively in current working directory. Alias of (<code>--packageFile '**/package.json'</code>).</td>\n  </tr>\n  <tr>\n    <td>--dep &lt;value&gt;</td>\n    <td>Check one or more sections of dependencies only: dev, optional, peer, prod, or packageManager (comma-delimited). (default: [\"prod\",\"dev\",\"optional\",\"packageManager\"])</td>\n  </tr>\n  <tr>\n    <td>--deprecated</td>\n    <td>Include deprecated packages. Use <code>--no-deprecated</code> to exclude deprecated packages (20–25% slower). (default: true)</td>\n  </tr>\n  <tr>\n    <td><a href=\"#doctor\">-d, --doctor</a></td>\n    <td>Iteratively installs upgrades and runs tests to identify breaking upgrades. Requires <code>-u</code> to execute.</td>\n  </tr>\n  <tr>\n    <td>--doctorInstall &lt;command&gt;</td>\n    <td>Specifies the install script to use in doctor mode. (default: <code>npm install</code> or the equivalent for your package manager)</td>\n  </tr>\n  <tr>\n    <td>--doctorTest &lt;command&gt;</td>\n    <td>Specifies the test script to use in doctor mode. (default: <code>npm test</code>)</td>\n  </tr>\n  <tr>\n    <td>--enginesNode</td>\n    <td>Include only packages that satisfy engines.node as specified in the package file.</td>\n  </tr>\n  <tr>\n    <td>-e, --errorLevel &lt;n&gt;</td>\n    <td>Set the error level. 1: exits with error code 0 if no errors occur. 2: exits with error code 0 if no packages need updating (useful for continuous integration). (default: 1)</td>\n  </tr>\n  <tr>\n    <td><a href=\"#filter\">-f, --filter &lt;p&gt;</a></td>\n    <td>Include only package names matching the given string, wildcard, glob, comma-or-space-delimited list, /regex/, or predicate function.</td>\n  </tr>\n  <tr>\n    <td><a href=\"#filterresults\">filterResults &lt;fn&gt;</a></td>\n    <td>Filters results based on a user provided predicate function after fetching new versions.</td>\n  </tr>\n  <tr>\n    <td><a href=\"#filterversion\">--filterVersion &lt;p&gt;</a></td>\n    <td>Filter on package version using comma-or-space-delimited list, /regex/, or predicate function.</td>\n  </tr>\n  <tr>\n    <td><a href=\"#format\">--format &lt;value&gt;</a></td>\n    <td>Modify the output formatting or show additional information. Specify one or more comma-delimited values: dep, group, ownerChanged, repo, time, lines, installedVersion. (default: [])</td>\n  </tr>\n  <tr>\n    <td>-g, --global</td>\n    <td>Check global packages instead of in the current project.</td>\n  </tr>\n  <tr>\n    <td><a href=\"#groupfunction\">groupFunction &lt;fn&gt;</a></td>\n    <td>Customize how packages are divided into groups when using <code>--format group</code>.</td>\n  </tr>\n  <tr>\n    <td><a href=\"#install\">--install &lt;value&gt;</a></td>\n    <td>Control the auto-install behavior: always, never, prompt. (default: \"prompt\")</td>\n  </tr>\n  <tr>\n    <td>-i, --interactive</td>\n    <td>Enable interactive prompts for each dependency; implies <code>-u</code> unless one of the json options are set.</td>\n  </tr>\n  <tr>\n    <td>-j, --jsonAll</td>\n    <td>Output new package file instead of human-readable message.</td>\n  </tr>\n  <tr>\n    <td>--jsonDeps</td>\n    <td>Like <code>jsonAll</code> but only lists <code>dependencies</code>, <code>devDependencies</code>, <code>optionalDependencies</code>, etc of the new package data.</td>\n  </tr>\n  <tr>\n    <td>--jsonUpgraded</td>\n    <td>Output upgraded dependencies in json.</td>\n  </tr>\n  <tr>\n    <td>-l, --loglevel &lt;n&gt;</td>\n    <td>Amount to log: silent, error, minimal, warn, info, verbose, silly. (default: \"warn\")</td>\n  </tr>\n  <tr>\n    <td>--mergeConfig</td>\n    <td>Merges nested configs with the root config file for <code>--deep</code> or <code>--packageFile</code> options. (default: false)</td>\n  </tr>\n  <tr>\n    <td>-m, --minimal</td>\n    <td>Do not upgrade newer versions that are already satisfied by the version range according to semver.</td>\n  </tr>\n  <tr>\n    <td>--packageData &lt;value&gt;</td>\n    <td>Package file data (you can also use stdin).</td>\n  </tr>\n  <tr>\n    <td>--packageFile &lt;path|glob&gt;</td>\n    <td>Package file(s) location. (default: ./package.json)</td>\n  </tr>\n  <tr>\n    <td><a href=\"#packagemanager\">-p, --packageManager &lt;s&gt;</a></td>\n    <td>npm, yarn, pnpm, deno, bun, staticRegistry (default: npm).</td>\n  </tr>\n  <tr>\n    <td><a href=\"#peer\">--peer</a></td>\n    <td>Check peer dependencies of installed packages and filter updates to compatible versions.</td>\n  </tr>\n  <tr>\n    <td>--pre &lt;n&gt;</td>\n    <td>Include prerelease versions, e.g. -alpha.0, -beta.5, -rc.2. Automatically set to 1 when <code>--target</code> is newest or greatest, or when the current version is a prerelease. (default: 0)</td>\n  </tr>\n  <tr>\n    <td>--prefix &lt;path&gt;</td>\n    <td>Current working directory of npm.</td>\n  </tr>\n  <tr>\n    <td>-r, --registry &lt;uri&gt;</td>\n    <td>Specify the registry to use when looking up package versions.</td>\n  </tr>\n  <tr>\n    <td><a href=\"#registrytype\">--registryType &lt;type&gt;</a></td>\n    <td>Specify whether --registry refers to a full npm registry or a simple JSON file or url: npm, json. (default: npm)</td>\n  </tr>\n  <tr>\n    <td><a href=\"#reject\">-x, --reject &lt;p&gt;</a></td>\n    <td>Exclude packages matching the given string, wildcard, glob, comma-or-space-delimited list, /regex/, or predicate function.</td>\n  </tr>\n  <tr>\n    <td><a href=\"#rejectversion\">--rejectVersion &lt;p&gt;</a></td>\n    <td>Exclude package.json versions using comma-or-space-delimited list, /regex/, or predicate function.</td>\n  </tr>\n  <tr>\n    <td>--removeRange</td>\n    <td>Remove version ranges from the final package version.</td>\n  </tr>\n  <tr>\n    <td>--retry &lt;n&gt;</td>\n    <td>Number of times to retry failed requests for package info. (default: 3)</td>\n  </tr>\n  <tr>\n    <td>--root</td>\n    <td>Runs updates on the root project in addition to specified workspaces. Only allowed with <code>--workspace</code> or <code>--workspaces</code>. (default: true)</td>\n  </tr>\n  <tr>\n    <td>-s, --silent</td>\n    <td>Don't output anything. Alias for <code>--loglevel</code> silent.</td>\n  </tr>\n  <tr>\n    <td>--stdin</td>\n    <td>Read package.json from stdin.</td>\n  </tr>\n  <tr>\n    <td><a href=\"#target\">-t, --target &lt;value&gt;</a></td>\n    <td>Determines the version to upgrade to: latest, newest, greatest, minor, patch, semver, <code>@[tag]</code>, or [function]. (default: latest)</td>\n  </tr>\n  <tr>\n    <td>--timeout &lt;ms&gt;</td>\n    <td>Global timeout in milliseconds. (default: no global timeout and 30 seconds per npm-registry-fetch)</td>\n  </tr>\n  <tr>\n    <td>-u, --upgrade</td>\n    <td>Overwrite package file with upgraded versions instead of just outputting to console.</td>\n  </tr>\n  <tr>\n    <td>--verbose</td>\n    <td>Log additional information for debugging. Alias for <code>--loglevel</code> verbose.</td>\n  </tr>\n  <tr>\n    <td>--workspace &lt;s&gt;</td>\n    <td>Run on one or more specified workspaces. Add <code>--no-root</code> to exclude the root project. (default: [])</td>\n  </tr>\n  <tr>\n    <td>-w, --workspaces</td>\n    <td>Run on all workspaces. Add <code>--no-root</code> to exclude the root project.</td>\n  </tr>\n</table>\n\n<!-- END Options -->\n\n## Advanced Options\n\nSome options have advanced usage, or allow per-package values by specifying a function in your .ncurc.js file.\n\nRun `ncu --help [OPTION]` to view advanced help for a specific option, or see below:\n\n<!-- BEGIN Advanced Options -->\n<!-- Do not edit this section by hand. It is auto-generated in build-options.ts. Run \"npm run build\" or \"npm run build:options\" to build. -->\n\n## cooldown\n\nUsage:\n\n    ncu --cooldown [period]\n    ncu -c [period]\n\nThe cooldown option helps protect against supply chain attacks by requiring package versions to be published at least the given amount of time before considering them for upgrade.\n\nThe value can be a plain number (days) or a string with a unit suffix:\n\n    --cooldown 7       7 days\n    --cooldown 7d      7 days (same as above)\n    --cooldown 12h     12 hours\n    --cooldown 30m     30 minutes\n\nNote that previous stable versions will not be suggested. The package will be completely ignored if its latest published version is within the cooldown period. This is due to a limitation of the npm registry, which does not provide a way to query previous stable versions.\n\nExample:\n\nLet's examine how cooldown works with a package that has these versions available:\n\n    1.0.0          Released 7 days ago    (initial version)\n    1.1.0          Released 6 days ago    (minor update)\n    1.1.1          Released 5 days ago    (patch update)\n    1.2.0          Released 5 days ago    (minor update)\n    2.0.0-beta.1   Released 5 days ago    (beta release)\n    1.2.1          Released 4 days ago    (patch update)\n    1.3.0          Released 4 days ago    (minor update) [latest]\n    2.0.0-beta.2   Released 3 days ago    (beta release)\n    2.0.0-beta.3   Released 2 days ago    (beta release) [beta]\n\nWith default target (latest):\n\n```js\n$ ncu --cooldown 5\n```\n\nNo update will be suggested because:\n\n- Latest version (1.3.0) is only 4 days old.\n- Cooldown requires versions to be at least 5 days old\n- Use `--cooldown 4` or lower to allow this update\n\nWith `@beta`/`@tag` target:\n\n```js\n$ ncu --cooldown 3 --target @beta\n```\n\nNo update will be suggested because:\n\n- Current beta (2.0.0-beta.3) is only 2 days old\n- Cooldown requires versions to be at least 3 days old\n- Use `--cooldown 2` or lower to allow this update\n\nWith other targets:\n\n```js\n$ ncu --cooldown 5 --target greatest|newest|minor|patch|semver\n```\n\nEach target will select the best version that is at least 5 days old:\n\n    greatest → 1.2.0        (highest version number outside cooldown)\n    newest   → 2.0.0-beta.1 (most recently published version outside cooldown)\n    minor    → 1.2.0        (highest minor version outside cooldown)\n    patch    → 1.1.1        (highest patch version outside cooldown)\n\nNote for latest/tag targets:\n\n> :warning: For packages that update frequently (e.g. daily releases), using a long cooldown period (7+ days) with the default `--target latest` or `--target @tag` may prevent all updates since new versions will be published before older ones meet the cooldown requirement. Please consider this when setting your cooldown period.\n\nYou can also provide a custom function in your .ncurc.js file or when importing npm-check-updates as a module.\n\n> :warning: The predicate function is only available in .ncurc.js or when importing npm-check-updates as a module, not on the command line. To convert a JSON config to a JS config, follow the instructions at https://github.com/raineorshine/npm-check-updates#config-functions.\n\n```js\n/** Set cooldown to 3 days but skip it for `@my-company` packages.\n  @param packageName     The name of the dependency.\n  @returns               Cooldown days restriction for given package.\n*/\ncooldown: packageName => (packageName.startsWith('@my-company') ? 0 : 3)\n```\n\n## doctor\n\nUsage:\n\n    ncu --doctor -u\n    ncu --no-doctor\n    ncu -du\n\nIteratively installs upgrades and runs your project's tests to identify breaking upgrades. Reverts broken upgrades and updates package.json with working upgrades.\n\nRequires `-u` to execute (modifies your package file, lock file, and node_modules)\n\nTo be more precise:\n\n1. Runs `npm install` and `npm test` to ensure tests are currently passing.\n2. Runs `ncu -u` to optimistically upgrade all dependencies.\n3. If tests pass, hurray!\n4. If tests fail, restores package file and lock file.\n5. For each dependency, install upgrade and run tests.\n6. Prints broken upgrades with test error.\n7. Saves working upgrades to package.json.\n\nAdditional options:\n\n<table>\n  <tr><td>--doctorInstall</td><td>specify a custom install script (default: `npm install` or `yarn`)</td></tr>\n  <tr><td>--doctorTest</td><td>specify a custom test script (default: `npm test`)</td></tr>\n</table>\n\nExample:\n\n    $ ncu --doctor -u\n    Running tests before upgrading\n    npm install\n    npm run test\n    Upgrading all dependencies and re-running tests\n    ncu -u\n    npm install\n    npm run test\n    Tests failed\n    Identifying broken dependencies\n    npm install\n    npm install --no-save react@16.0.0\n    npm run test\n      ✓ react 15.0.0 → 16.0.0\n    npm install --no-save react-redux@7.0.0\n    npm run test\n      ✗ react-redux 6.0.0 → 7.0.0\n\n    /projects/myproject/test.js:13\n      throw new Error('Test failed!')\n      ^\n\n    npm install --no-save react-dnd@11.1.3\n    npm run test\n      ✓ react-dnd 10.0.0 → 11.1.3\n    Saving partially upgraded package.json\n\n## filter\n\nUsage:\n\n    ncu --filter [p]\n    ncu -f [p]\n\nInclude only package names matching the given string, wildcard, glob, comma-or-space-delimited list, /regex/, or predicate function. Only included packages will be checked with `--peer`.\n\n`--filter` runs _before_ new versions are fetched, in contrast to `--filterResults` which runs _after_.\n\nYou can also specify a custom function in your .ncurc.js file, or when importing npm-check-updates as a module.\n\n> :warning: The predicate function is only available in .ncurc.js or when importing npm-check-updates as a module, not on the command line. To convert a JSON config to a JS config, follow the instructions at https://github.com/raineorshine/npm-check-updates#config-functions.\n\n```js\n/**\n  @param name     The name of the dependency.\n  @param semver   A parsed Semver array of the current version.\n    (See: https://git.coolaj86.com/coolaj86/semver-utils.js#semverutils-parse-semverstring)\n  @returns        True if the package should be included, false if it should be excluded.\n*/\nfilter: (name, semver) => {\n  if (name.startsWith('@myorg/')) {\n    return false\n  }\n  return true\n}\n```\n\n## filterResults\n\nFilters results based on a user provided predicate function after fetching new versions.\n\n`filterResults` runs _after_ new versions are fetched, in contrast to `filter`, `reject`, `filterVersion`, and `rejectVersion`, which run _before_. This allows you to exclude upgrades with `filterResults` based on how the version has changed (e.g. a major version change).\n\n> :warning: The predicate function is only available in .ncurc.js or when importing npm-check-updates as a module, not on the command line. To convert a JSON config to a JS config, follow the instructions at https://github.com/raineorshine/npm-check-updates#config-functions.\n\n```js\n/** Exclude major version updates. Note this could also be achieved with --target semver.\n  @param {string} packageName               The name of the dependency.\n  @param {string} current                   Current version declaration (may be a range).\n  @param {SemVer[]} currentVersionSemver    Current version declaration in semantic versioning format (may be a range).\n  @param {string} upgraded                  Upgraded version.\n  @param {SemVer} upgradedVersionSemver     Upgraded version in semantic versioning format.\n  @returns {boolean}                        Return true if the upgrade should be kept; otherwise, it will be ignored.\n*/\nfilterResults: (packageName, { current, currentVersionSemver, upgraded, upgradedVersionSemver }) => {\n  const currentMajor = parseInt(currentVersionSemver[0]?.major, 10)\n  const upgradedMajor = parseInt(upgradedVersionSemver?.major, 10)\n  if (currentMajor && upgradedMajor) {\n    return currentMajor >= upgradedMajor\n  }\n  return true\n}\n```\n\nFor the SemVer type definition, see: https://git.coolaj86.com/coolaj86/semver-utils.js#semverutils-parse-semverstring\n\n## filterVersion\n\nUsage:\n\n    ncu --filterVersion [p]\n\nInclude only versions matching the given string, wildcard, glob, comma-or-space-delimited list, /regex/, or predicate function.\n\n`--filterVersion` runs _before_ new versions are fetched, in contrast to `--filterResults` which runs _after_.\n\nYou can also specify a custom function in your .ncurc.js file, or when importing npm-check-updates as a module.\n\n> :warning: The predicate function is only available in .ncurc.js or when importing npm-check-updates as a module, not on the command line. To convert a JSON config to a JS config, follow the instructions at https://github.com/raineorshine/npm-check-updates#config-functions. This function is an alias for the `filter` option function.\n\n```js\n/**\n  @param name     The name of the dependency.\n  @param semver   A parsed Semver array of the current version.\n    (See: https://git.coolaj86.com/coolaj86/semver-utils.js#semverutils-parse-semverstring)\n  @returns        True if the package should be included, false if it should be excluded.\n*/\nfilterVersion: (name, semver) => {\n  if (name.startsWith('@myorg/') && parseInt(semver[0]?.major) > 5) {\n    return false\n  }\n  return true\n}\n```\n\n## format\n\nUsage:\n\n    ncu --format [value]\n\nModify the output formatting or show additional information. Specify one or more comma-delimited values.\n\n<table>\n  <tr><td>dep</td><td>Prints the dependency type (dev, peer, optional) of each package.</td></tr>\n  <tr><td>group</td><td>Groups packages by major, minor, patch, and major version zero updates.</td></tr>\n  <tr><td>homepage</td><td>Displays links to the package's homepage if specified in its package.json.</td></tr>\n  <tr><td>installedVersion</td><td>Prints the exact current version number instead of a range.</td></tr>\n  <tr><td>lines</td><td>Prints name@version on separate lines. Useful for piping to npm install.</td></tr>\n  <tr><td>ownerChanged</td><td>Shows if the package owner has changed.</td></tr>\n  <tr><td>repo</td><td>Infers and displays links to the package's source code repository. Requires packages to be installed.</td></tr>\n  <tr><td>diff</td><td>Display link to compare the changes between package versions.</td></tr>\n  <tr><td>time</td><td>Shows the publish time of each upgrade.</td></tr>\n</table>\n\n## groupFunction\n\nCustomize how packages are divided into groups when using `--format group`.\n\nOnly available in .ncurc.js or when importing npm-check-updates as a module, not on the command line. To convert a JSON config to a JS config, follow the instructions at https://github.com/raineorshine/npm-check-updates#config-functions.\n\n```js\n/**\n  @param name             The name of the dependency.\n  @param defaultGroup     The predefined group name which will be used by default.\n  @param currentSpec      The current version range in your package.json.\n  @param upgradedSpec     The upgraded version range that will be written to your package.json.\n  @param upgradedVersion  The upgraded version number returned by the registry.\n  @returns                A predefined group name ('major' | 'minor' | 'patch' | 'majorVersionZero' | 'none') or a custom string to create your own group.\n*/\ngroupFunction: (name, defaultGroup, currentSpec, upgradedSpec, upgradedVersion) => {\n  if (name === 'typescript' && defaultGroup === 'minor') {\n    return 'major'\n  }\n  if (name.startsWith('@myorg/')) {\n    return 'My Org'\n  }\n  return defaultGroup\n}\n```\n\n## install\n\nUsage:\n\n    ncu --install [value]\n\nDefault: prompt\n\nControl the auto-install behavior.\n\n<table>\n  <tr><td>always</td><td>Runs your package manager's install command automatically after upgrading.</td></tr>\n  <tr><td>never</td><td>Does not install and does not prompt.</td></tr>\n  <tr><td>prompt</td><td>Shows a message after upgrading that recommends an install, but does not install. In interactive mode, prompts for install. (default)</td></tr>\n</table>\n\n## packageManager\n\nUsage:\n\n    ncu --packageManager [s]\n    ncu -p [s]\n\nSpecifies the package manager to use when looking up versions.\n\n<table>\n  <tr><td>npm</td><td>System-installed npm. Default.</td></tr>\n  <tr><td>yarn</td><td>System-installed yarn. Automatically used if yarn.lock is present.</td></tr>\n  <tr><td>pnpm</td><td>System-installed pnpm. Automatically used if pnpm-lock.yaml is present.</td></tr>\n  <tr><td>bun</td><td>System-installed bun. Automatically used if bun.lock or bun.lockb is present.</td></tr>\n</table>\n\n## peer\n\nUsage:\n\n    ncu --peer\n    ncu --no-peer\n\nCheck peer dependencies of installed packages and filter updates to compatible versions.\n\nExample:\n\nThe following example demonstrates how `--peer` works, and how it uses peer dependencies from upgraded modules.\n\nThe package ncu-test-peer-update has two versions published:\n\n- 1.0.0 has peer dependency `\"ncu-test-return-version\": \"1.0.x\"`\n- 1.1.0 has peer dependency `\"ncu-test-return-version\": \"1.1.x\"`\n\nOur test app has the following dependencies:\n\n    \"ncu-test-peer-update\": \"1.0.0\",\n    \"ncu-test-return-version\": \"1.0.0\"\n\nThe latest versions of these packages are:\n\n    \"ncu-test-peer-update\": \"1.1.0\",\n    \"ncu-test-return-version\": \"2.0.0\"\n\nWith `--peer`:\n\nncu upgrades packages to the highest version that still adheres to the peer dependency constraints:\n\n    ncu-test-peer-update     1.0.0  →  1.1.0\n    ncu-test-return-version  1.0.0  →  1.1.0\n\nWithout `--peer`:\n\nAs a comparison: without using the `--peer` option, ncu will suggest the latest versions, ignoring peer dependencies:\n\n    ncu-test-peer-update     1.0.0  →  1.1.0\n    ncu-test-return-version  1.0.0  →  2.0.0\n\n## registryType\n\nUsage:\n\n    ncu --registryType [type]\n\nSpecify whether `--registry` refers to a full npm registry or a simple JSON file.\n\n<table>\n  <tr><td>npm</td><td>Default npm registry</td></tr>\n  <tr><td>json</td><td>Checks versions from a file or url to a simple JSON registry. Must include the `--registry` option.\n\nExample:\n\n    // local file\n    $ ncu --registryType json --registry ./registry.json\n\n    // url\n    $ ncu --registryType json --registry https://api.mydomain/registry.json\n\n    // you can omit --registryType when the registry ends in .json\n    $ ncu --registry ./registry.json\n    $ ncu --registry https://api.mydomain/registry.json\n\nregistry.json:\n\n    {\n      \"prettier\": \"2.7.1\",\n      \"typescript\": \"4.7.4\"\n    }\n\n</td></tr>\n</table>\n\n## reject\n\nUsage:\n\n    ncu --reject [p]\n    ncu -x [p]\n\nThe inverse of `--filter`. Exclude package names matching the given string, wildcard, glob, comma-or-space-delimited list, /regex/, or predicate function. This will also exclude them from the `--peer` check.\n\n`--reject` runs _before_ new versions are fetched, in contrast to `--filterResults` which runs _after_.\n\nYou can also specify a custom function in your .ncurc.js file, or when importing npm-check-updates as a module.\n\n> :warning: The predicate function is only available in .ncurc.js or when importing npm-check-updates as a module, not on the command line. To convert a JSON config to a JS config, follow the instructions at https://github.com/raineorshine/npm-check-updates#config-functions.\n\n```js\n/**\n  @param name     The name of the dependency.\n  @param semver   A parsed Semver array of the current version.\n    (See: https://git.coolaj86.com/coolaj86/semver-utils.js#semverutils-parse-semverstring)\n  @returns        True if the package should be excluded, false if it should be included.\n*/\nreject: (name, semver) => {\n  if (name.startsWith('@myorg/')) {\n    return true\n  }\n  return false\n}\n```\n\n## rejectVersion\n\nUsage:\n\n    ncu --rejectVersion [p]\n\nThe inverse of `--filterVersion`. Exclude versions matching the given string, wildcard, glob, comma-or-space-delimited list, /regex/, or predicate function.\n\n`--rejectVersion` runs _before_ new versions are fetched, in contrast to `--filterResults` which runs _after_.\n\nYou can also specify a custom function in your .ncurc.js file, or when importing npm-check-updates as a module.\n\n> :warning: The predicate function is only available in .ncurc.js or when importing npm-check-updates as a module, not on the command line. To convert a JSON config to a JS config, follow the instructions at https://github.com/raineorshine/npm-check-updates#config-functions. This function is an alias for the reject option function.\n\n```js\n/**\n  @param name     The name of the dependency.\n  @param semver   A parsed Semver array of the current version.\n    (See: https://git.coolaj86.com/coolaj86/semver-utils.js#semverutils-parse-semverstring)\n  @returns        True if the package should be excluded, false if it should be included.\n*/\nrejectVersion: (name, semver) => {\n  if (name.startsWith('@myorg/') && parseInt(semver[0]?.major) > 5) {\n    return true\n  }\n  return false\n}\n```\n\n## target\n\nUsage:\n\n    ncu --target [value]\n    ncu -t [value]\n\nDetermines the version to upgrade to. (default: \"latest\")\n\n<table>\n  <tr><td>greatest</td><td>Upgrade to the highest version number published, regardless of release date or tag. Includes prereleases.</td></tr>\n  <tr><td>latest</td><td>Upgrade to whatever the package's \"latest\" git tag points to. Excludes prereleases unless --pre is specified.</td></tr>\n  <tr><td>minor</td><td>Upgrade to the highest minor version without bumping the major version.</td></tr>\n  <tr><td>newest</td><td>Upgrade to the version with the most recent publish date, even if there are other version numbers that are higher. Includes prereleases.</td></tr>\n  <tr><td>patch</td><td>Upgrade to the highest patch version without bumping the minor or major versions.</td></tr>\n  <tr><td>semver</td><td>Upgrade to the highest version within the semver range specified in your package.json.</td></tr>\n  <tr><td>@[tag]</td><td>Upgrade to the version published to a specific tag, e.g. 'next' or 'beta'.</td></tr>\n</table>\n\ne.g.\n\n    ncu --target semver\n\nYou can also specify a custom function in your .ncurc.js file, or when importing npm-check-updates as a module.\n\n> :warning: The predicate function is only available in .ncurc.js or when importing npm-check-updates as a module, not on the command line. To convert a JSON config to a JS config, follow the instructions at https://github.com/raineorshine/npm-check-updates#config-functions.\n\n```js\n/** Upgrade major version zero to the next minor version, and everything else to latest.\n  @param name     The name of the dependency.\n  @param semver   A parsed Semver object of the upgraded version.\n    (See: https://git.coolaj86.com/coolaj86/semver-utils.js#semverutils-parse-semverstring)\n  @returns        One of the valid target values (specified in the table above).\n*/\ntarget: (name, semver) => {\n  if (parseInt(semver[0]?.major) === '0') return 'minor'\n  return 'latest'\n}\n```\n\n<!-- END Advanced Options -->\n\n## Config File\n\nAdd a `.ncurc.{json,yml,js,cjs}` file to your project directory to specify configuration information.\n\nFor example, `.ncurc.json`:\n\n```json\n{\n  \"upgrade\": true,\n  \"filter\": \"svelte\",\n  \"reject\": [\"@types/estree\", \"ts-node\"]\n}\n```\n\nOptions are merged with the following precedence:\n\n1. Command line options\n2. Local Config File (current working directory)\n3. Project Config File (next to package.json)\n4. User Config File (`$HOME`)\n\nYou can also specify a custom config file name or path using the `--configFileName` or `--configFilePath` command line options.\n\n### Config Functions\n\nSome options offer more advanced configuration using a function definition. These include [filter](https://github.com/raineorshine/npm-check-updates#filter), [filterVersion](https://github.com/raineorshine/npm-check-updates#filterversion), [filterResults](https://github.com/raineorshine/npm-check-updates#filterresults), [reject](https://github.com/raineorshine/npm-check-updates#reject), [rejectVersion](https://github.com/raineorshine/npm-check-updates#rejectversion), and [groupFunction](https://github.com/raineorshine/npm-check-updates#groupfunction). To define an options function, convert the config file to a JS file by adding the `.js` extension and setting module.exports:\n\nFor example, `.ncurc.js`:\n\n```js\n/** @type {import('npm-check-updates').RcOptions } */\nmodule.exports = {\n  upgrade: true,\n  filter: name => name.startsWith('@myorg/'),\n}\n```\n\nAlternatively, you can use the defineConfig helper which should provide intellisense without the need for jsdoc annotations:\n\n```js\nconst { defineConfig } = require('npm-check-updates')\n\nmodule.exports = defineConfig({\n  upgrade: true,\n  filter: name => name.startsWith('@myorg/'),\n})\n```\n\n### JSON Schema\n\nIf you write `.ncurc` config files using json or yaml, you can add the JSON Schema to your IDE settings for completions.\n\ne.g. for VS Code:\n\n```json\n  \"json.schemas\": [\n    {\n      \"fileMatch\": [\n        \".ncurc\",\n        \".ncurc.json\",\n      ],\n      \"url\": \"https://raw.githubusercontent.com/raineorshine/npm-check-updates/main/src/types/RunOptions.json\"\n    }\n  ],\n  \"yaml.schemas\": {\n    \"https://raw.githubusercontent.com/raineorshine/npm-check-updates/main/src/types/RunOptions.json\": [\n        \".ncurc.yml\",\n    ]\n  },\n```\n\n## Module/Programmatic Usage\n\nnpm-check-updates can be imported as a module:\n\n```js\nimport ncu from 'npm-check-updates'\n\nconst upgraded = await ncu.run({\n  // Pass any cli option\n  packageFile: '../package.json',\n  upgrade: true,\n  // Defaults:\n  // jsonUpgraded: true,\n  // silent: true,\n})\n\nconsole.log(upgraded) // { \"mypackage\": \"^2.0.0\", ... }\n```\n\n## Contributing\n\nContributions are happily accepted. I respond to all PR's and can offer guidance on where to make changes. For contributing tips see [CONTRIBUTING.md](https://github.com/raineorshine/npm-check-updates/blob/main/.github/CONTRIBUTING.md).\n\n## Problems?\n\n[File an issue](https://github.com/raineorshine/npm-check-updates/issues). Please [search existing issues](https://github.com/raineorshine/npm-check-updates/issues?utf8=%E2%9C%93&q=is%3Aissue) first.\n"
  },
  {
    "path": "deploy.md",
    "content": "# Deployment Instructions\n\n- Have you created tests?\n- Have you updated the README?\n\n```bash\nnpm version [minor]\ngit push && git push --tags\nnpm publish [--tag unstable]\n```\n\n- Update the release history\n  <https://github.com/raineorshine/npm-check-updates/releases>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"npm-check-updates\",\n  \"version\": \"19.6.5\",\n  \"author\": \"Tomas Junnonen <tomas1@gmail.com>\",\n  \"license\": \"Apache-2.0\",\n  \"contributors\": [\n    \"Raine Revere (https://github.com/raineorshine)\",\n    \"Imamuzzaki Abu Salam <imamuzzaki@gmail.com>\"\n  ],\n  \"description\": \"Find newer versions of dependencies than what your package.json allows\",\n  \"keywords\": [\n    \"dependencies\",\n    \"npm\",\n    \"package.json\",\n    \"update\",\n    \"upgrade\",\n    \"versions\"\n  ],\n  \"engines\": {\n    \"node\": \">=20.0.0\",\n    \"npm\": \">=8.12.1\"\n  },\n  \"main\": \"build/index.js\",\n  \"types\": \"build/index.d.ts\",\n  \"scripts\": {\n    \"build\": \"rimraf build && npm run build:options && vite build\",\n    \"build:options\": \"vite-node src/scripts/build-options.ts\",\n    \"build:analyze\": \"rimraf build && npm run build:options && ANALYZER=true vite build\",\n    \"lint\": \"cross-env FORCE_COLOR=1 npm-run-all --parallel --aggregate-output lint:*\",\n    \"lint:lockfile\": \"lockfile-lint\",\n    \"lint:markdown\": \"markdownlint \\\"**/*.md\\\" --ignore \\\"**/node_modules/**/*.md\\\" --ignore build --config .markdownlint.js\",\n    \"lint:src\": \"eslint --cache --cache-location node_modules/.cache/.eslintcache --ignore-path .gitignore --report-unused-disable-directives .\",\n    \"prepare\": \"src/scripts/install-hooks\",\n    \"prepublishOnly\": \"npm run build\",\n    \"prettier\": \"prettier . --check\",\n    \"prettier:fix\": \"prettier . --write\",\n    \"test\": \"npm run test:unit && npm run test:e2e\",\n    \"test:bun\": \"test/bun-install.sh && mocha test/bun\",\n    \"test:unit\": \"mocha test test/package-managers/*\",\n    \"test:e2e\": \"./test/e2e.sh\",\n    \"ncu\": \"node build/cli.js\"\n  },\n  \"bin\": {\n    \"npm-check-updates\": \"build/cli.js\",\n    \"ncu\": \"build/cli.js\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/raineorshine/npm-check-updates.git\"\n  },\n  \"homepage\": \"https://github.com/raineorshine/npm-check-updates\",\n  \"bugs\": {\n    \"url\": \"https://github.com/raineorshine/npm-check-updates/issues\"\n  },\n  \"overrides\": {\n    \"ip\": \"2.0.1\",\n    \"jsonparse\": \"https://github.com/ARitz-Cracker/jsonparse/tree/patch-1\",\n    \"@yarnpkg/parsers\": \"2.6.0\"\n  },\n  \"devDependencies\": {\n    \"@trivago/prettier-plugin-sort-imports\": \"^5.2.2\",\n    \"@types/bun\": \"^1.2.23\",\n    \"@types/chai\": \"^4.3.19\",\n    \"@types/chai-as-promised\": \"^8.0.0\",\n    \"@types/chai-string\": \"^1.4.5\",\n    \"@types/cli-table\": \"^0.3.4\",\n    \"@types/hosted-git-info\": \"^3.0.5\",\n    \"@types/ini\": \"^4.1.1\",\n    \"@types/js-yaml\": \"^4.0.9\",\n    \"@types/jsonlines\": \"^0.1.5\",\n    \"@types/lodash\": \"^4.17.20\",\n    \"@types/mocha\": \"^10.0.10\",\n    \"@types/node\": \"^24.5.2\",\n    \"@types/npm-registry-fetch\": \"^8.0.8\",\n    \"@types/parse-github-url\": \"^1.0.3\",\n    \"@types/picomatch\": \"^4.0.2\",\n    \"@types/progress\": \"^2.0.7\",\n    \"@types/prompts\": \"^2.4.9\",\n    \"@types/semver\": \"^7.7.1\",\n    \"@types/semver-utils\": \"^1.1.3\",\n    \"@types/sinon\": \"^17.0.4\",\n    \"@types/update-notifier\": \"^6.0.8\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.44.1\",\n    \"@typescript-eslint/parser\": \"^8.44.1\",\n    \"camelcase\": \"^6.3.0\",\n    \"chai\": \"^4.3.10\",\n    \"chai-as-promised\": \"^7.1.2\",\n    \"chai-string\": \"^1.6.0\",\n    \"chalk\": \"^5.6.2\",\n    \"cli-table3\": \"^0.6.5\",\n    \"commander\": \"^14.0.1\",\n    \"cross-env\": \"^10.0.0\",\n    \"dequal\": \"^2.0.3\",\n    \"eslint\": \"^8.57.0\",\n    \"eslint-config-prettier\": \"^10.1.8\",\n    \"eslint-config-raine\": \"^0.5.0\",\n    \"eslint-config-standard\": \"^17.1.0\",\n    \"eslint-plugin-import\": \"^2.32.0\",\n    \"eslint-plugin-jsdoc\": \"^60.5.0\",\n    \"eslint-plugin-n\": \"^16.6.2\",\n    \"eslint-plugin-promise\": \"^6.6.0\",\n    \"fast-glob\": \"^3.3.3\",\n    \"fast-memoize\": \"^2.5.2\",\n    \"find-up\": \"5.0.0\",\n    \"fp-and-or\": \"^1.0.2\",\n    \"hosted-git-info\": \"^9.0.0\",\n    \"ini\": \"^5.0.0\",\n    \"js-yaml\": \"^4.1.0\",\n    \"jsonc-parser\": \"^3.3.1\",\n    \"jsonlines\": \"^0.1.1\",\n    \"lockfile-lint\": \"^4.14.1\",\n    \"lodash\": \"^4.17.21\",\n    \"markdownlint-cli\": \"^0.45.0\",\n    \"mocha\": \"^11.7.2\",\n    \"npm-registry-fetch\": \"^19.0.0\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"p-map\": \"^4.0.0\",\n    \"parse-github-url\": \"^1.0.3\",\n    \"picomatch\": \"^4.0.3\",\n    \"prettier\": \"^3.6.2\",\n    \"progress\": \"^2.0.3\",\n    \"prompts-ncu\": \"^3.0.2\",\n    \"rc-config-loader\": \"^4.1.3\",\n    \"rfdc\": \"^1.4.1\",\n    \"rimraf\": \"^6.0.1\",\n    \"rollup-plugin-node-externals\": \"^8.1.1\",\n    \"semver\": \"^7.7.2\",\n    \"semver-utils\": \"^1.1.4\",\n    \"should\": \"^13.2.3\",\n    \"sinon\": \"^21.0.0\",\n    \"source-map-support\": \"^0.5.21\",\n    \"spawn-please\": \"^3.0.0\",\n    \"strip-ansi\": \"^7.1.2\",\n    \"ts-node\": \"^10.9.2\",\n    \"typescript\": \"^5.9.2\",\n    \"typescript-json-schema\": \"^0.65.1\",\n    \"untildify\": \"^4.0.0\",\n    \"update-notifier\": \"^7.3.1\",\n    \"verdaccio\": \"^6.1.6\",\n    \"vite\": \"^7.1.7\",\n    \"vite-bundle-analyzer\": \"^1.2.3\",\n    \"vite-node\": \"^3.2.4\",\n    \"vite-plugin-dts\": \"^4.5.4\",\n    \"yaml\": \"^2.8.2\",\n    \"yarn\": \"^1.22.22\",\n    \"zod\": \"^4.3.5\"\n  },\n  \"files\": [\n    \"build\",\n    \"!**/test/**\"\n  ],\n  \"lockfile-lint\": {\n    \"allowed-schemes\": [\n      \"https:\",\n      \"git+ssh:\"\n    ],\n    \"allowed-hosts\": [\n      \"npm\",\n      \"github.com\"\n    ],\n    \"empty-hostname\": false,\n    \"type\": \"npm \",\n    \"path\": \"package-lock.json\"\n  },\n  \"mocha\": {\n    \"check-leaks\": true,\n    \"extension\": [\n      \"test.ts\"\n    ],\n    \"require\": [\n      \"source-map-support/register\",\n      \"ts-node/register\"\n    ],\n    \"timeout\": 60000,\n    \"trace-deprecation\": true,\n    \"trace-warnings\": true,\n    \"use_strict\": true\n  }\n}\n"
  },
  {
    "path": "src/bin/cli.ts",
    "content": "#!/usr/bin/env node\nimport { Help, Option, program } from 'commander'\nimport createCloneDeep from 'rfdc'\nimport semver from 'semver'\nimport pkg from '../../package.json'\nimport cliOptions, { renderExtendedHelp } from '../cli-options'\nimport ncu from '../index'\nimport { chalkInit } from '../lib/chalk'\n// async global contexts are only available in esm modules -> function\nimport getNcuRc from '../lib/getNcuRc'\nimport { pickBy } from '../lib/pick'\n\nconst optionVersionDescription = 'Output the version number of npm-check-updates.'\n\n/** Removes inline code ticks. */\nconst uncode = (s: string) => s.replace(/`/g, '')\n\nconst cloneDeep = createCloneDeep()\n\n;(async () => {\n  // importing update-notifier dynamically as esm modules are only allowed to be dynamically imported inside of cjs modules\n  const { default: updateNotifier } = await import('update-notifier')\n\n  // check if a new version of ncu is available and print an update notification\n  //\n  // For testing from specific versions, use:\n  //\n  // updateNotifier({\n  //   pkg: {\n  //     name: 'npm-check-updates',\n  //     version: x.y.z\n  //   },\n  //   updateCheckInterval: 0\n  // })\n\n  const notifier = updateNotifier({ pkg })\n  if (notifier.update && notifier.update.latest !== pkg.version) {\n    const { default: chalk } = await import('chalk')\n\n    // generate release urls for all the major versions from the current version up to the latest\n    const currentMajor = semver.parse(notifier.update.current)?.major\n    const latestMajor = semver.parse(notifier.update.latest)?.major\n    const majorVersions =\n      // Greater than or equal to (>=) will always return false if either operant is NaN or undefined.\n      // Without this condition, it can result in a RangeError: Invalid array length.\n      // See: https://github.com/raineorshine/npm-check-updates/issues/1200\n      currentMajor && latestMajor && latestMajor >= currentMajor\n        ? new Array(latestMajor - currentMajor).fill(0).map((x, i) => currentMajor + i + 1)\n        : []\n    const releaseUrls = majorVersions.map(majorVersion => `${pkg.homepage ?? ''}/releases/tag/v${majorVersion}.0.0`)\n\n    // for non-major updates, generate a URL to view all commits since the current version\n    const compareUrl = `${pkg.homepage ?? ''}/compare/v${notifier.update.current}...v${notifier.update.latest}`\n\n    notifier.notify({\n      defer: false,\n      isGlobal: true,\n      message: `Update available ${chalk.dim('{currentVersion}')}${chalk.reset(' → ')}${\n        notifier.update.type === 'major'\n          ? chalk.red('{latestVersion}')\n          : notifier.update.type === 'minor'\n            ? chalk.yellow('{latestVersion}')\n            : chalk.green('{latestVersion}')\n      }\nRun ${chalk.cyan('{updateCommand}')} to update\n${chalk.dim.underline(\n  notifier.update.type === 'major' ? releaseUrls.map(url => chalk.dim.underline(url)).join('\\n') : compareUrl,\n)}`,\n    })\n  }\n\n  // manually detect option-specific help\n  // https://github.com/raineorshine/npm-check-updates/issues/787\n  const rawArgs = process.argv.slice(2)\n  const indexHelp = rawArgs.findIndex(arg => arg === '--help' || arg === '-h')\n  if (indexHelp !== -1 && rawArgs[indexHelp + 1]) {\n    const helpOption = rawArgs[indexHelp + 1].replace(/^-*/, '')\n    if (helpOption === 'help' || helpOption === 'h') {\n      console.info('Would you like some help with your help?')\n    } else {\n      await chalkInit()\n      const nonHelpArgs = [...rawArgs.slice(0, indexHelp), ...rawArgs.slice(indexHelp + 1)]\n      nonHelpArgs.forEach(arg => {\n        // match option by long or short\n        const query = arg.replace(/^-*/, '')\n        const option = cliOptions.find(\n          option =>\n            query === option.long ||\n            query === option.short ||\n            (query === `no-${option.long}` && option.type === 'boolean'),\n        )\n        if (option) {\n          console.info(renderExtendedHelp(option) + '\\n')\n        } else if (query === 'version' || query === 'v' || query === 'V') {\n          console.info(\n            renderExtendedHelp({\n              long: 'version',\n              short: 'v',\n              description: optionVersionDescription,\n              // do not pass boolean or it will print --no-version\n              type: 'string',\n            }) + '\\n',\n          )\n        } else {\n          console.info(`Unknown option: ${arg}`)\n        }\n      })\n    }\n    process.exit(0)\n  }\n\n  // a set of options that only work in an rc config file, not on the command line\n  const noCli = new Set(cliOptions.filter(option => option.cli === false).map(option => `--${option.long}`))\n\n  // start commander program\n  program\n    .description('[filter] is a list or regex of package names to check (all others will be ignored).')\n    .usage('[options] [filter]')\n    // See: boolean optional arg below\n    .configureHelp({\n      optionTerm: option =>\n        option.long && noCli.has(option.long)\n          ? option.long.replace('--', '') + '*'\n          : option.long === '--version'\n            ? // add -v to version help to cover the alias added below\n              '-v, -V, --version'\n            : option.flags.replace('[bool]', ''),\n      optionDescription: option =>\n        option.long === '--version'\n          ? optionVersionDescription\n          : option.long === '--help'\n            ? `You're lookin' at it. Run \"ncu --help <option>\" for a specific option.`\n            : Help.prototype.optionDescription(option),\n    })\n    // add hidden -v alias for --V/--version\n    .addOption(new Option('-v, --versionAlias').hideHelp())\n    .on('option:versionAlias', () => {\n      console.info(pkg.version)\n      process.exit(0)\n    })\n\n  // add cli options\n  cliOptions.forEach(({ long, short, arg, description, default: defaultValue, help, parse, type }) => {\n    const flags = `${short ? `-${short}, ` : ''}--${long}${arg ? ` <${arg}>` : ''}`\n    // format description for cli by removing inline code ticks\n    // point to help in description if extended help text is available\n    const descriptionFormatted = `${uncode(description)}${help ? ` Run \"ncu --help ${long}\" for details.` : ''}`\n\n    // handle 3rd/4th argument polymorphism\n    program.option(flags, descriptionFormatted, parse || defaultValue, parse ? defaultValue : undefined)\n\n    // add --no- prefixed boolean options\n    // necessary for overriding booleans set to true in the ncurc\n    if (type === 'boolean') {\n      program.addOption(new Option(`--no-${long}`).default(false).hideHelp())\n    }\n  })\n\n  // set version option at the end\n  program.version(pkg.version)\n\n  // commander mutates its optionValues with program.parse\n  // In order to call program.parse again and parse the rc file options, we need to clear commander's internal optionValues\n  // Otherwise array options will be duplicated\n  const defaultOptionValues = cloneDeep((program as any)._optionValues)\n  program.allowExcessArguments(true)\n  program.parse(process.argv)\n\n  const programOpts = program.opts()\n  const programArgs = process.argv.slice(2)\n\n  const { color, configFileName, configFilePath, global, packageFile, mergeConfig } = programOpts\n\n  // Force color on all chalk instances.\n  // See: /src/lib/chalk.ts\n  await chalkInit(color)\n\n  // load .ncurc\n  // Do not load when tests are running (can be overridden if configFilePath is set explicitly, or --mergeConfig option specified)\n  const rcResult =\n    !process.env.NCU_TESTS || configFilePath || mergeConfig\n      ? await getNcuRc({\n          configFileName,\n          configFilePath,\n          global,\n          packageFile,\n          options: { ...programOpts, cli: true },\n        })\n      : null\n\n  // override rc args with program args\n  const rcArgs = (rcResult?.args || []).filter(\n    (arg, i, args) =>\n      (typeof arg !== 'string' || !arg.startsWith('-') || !programArgs.includes(arg)) &&\n      (typeof args[i - 1] !== 'string' || !args[i - 1].startsWith('-') || !programArgs.includes(args[i - 1])),\n  )\n\n  // insert config arguments into command line arguments so they can all be parsed by commander\n  const combinedArguments = [...process.argv.slice(0, 2), ...rcArgs, ...programArgs]\n\n  // See defaultOptionValues comment above\n  ;(program as any)._optionValues = defaultOptionValues\n  program.parse(combinedArguments)\n  const combinedProgramOpts = program.opts()\n\n  // filter out undefined program options and combine cli options with config file options\n  const options = {\n    ...(rcResult && Object.keys(rcResult.config).length > 0 ? { rcConfigPath: rcResult.filePath } : null),\n    ...pickBy(program.opts(), (value: unknown) => value !== undefined),\n    args: program.args,\n    ...(combinedProgramOpts.filter ? { filter: combinedProgramOpts.filter } : null),\n    ...(combinedProgramOpts.reject ? { reject: combinedProgramOpts.reject } : null),\n  }\n\n  // NOTE: Options handling and defaults go in initOptions in index.js\n\n  ncu(options, { cli: true })\n})()\n"
  },
  {
    "path": "src/cli-options.ts",
    "content": "import path from 'path'\nimport { defaultCacheFile } from './lib/cache'\nimport chalk from './lib/chalk'\nimport parseCooldown from './lib/parseCooldown'\nimport { sortBy } from './lib/sortBy'\nimport table from './lib/table'\nimport CLIOption from './types/CLIOption'\nimport ExtendedHelp from './types/ExtendedHelp'\nimport { Index } from './types/IndexType'\n\n/** Valid strings for the --target option. Indicates the desired version to upgrade to. */\nconst supportedVersionTargets = ['latest', 'newest', 'greatest', 'minor', 'patch', 'semver']\n\n/** Pads the left side of each line in a string. */\nconst padLeft = (s: string, n: number) =>\n  s\n    .split('\\n')\n    .map(line => `${''.padStart(n, ' ')}${line}`)\n    .join('\\n')\n\n/** Formats a code block for CLI or markdown. */\nconst codeBlock = (code: string, { markdown }: { markdown?: boolean } = {}) =>\n  `${markdown ? '```js\\n' : ''}${padLeft(code, markdown ? 0 : 4)}${markdown ? '\\n```' : ''}`\n\n/** Removes inline code ticks. */\nconst uncode = (s: string) => s.replace(/`/g, '')\n\n/** Parses a number from a string or number input. Throws if the value is not a number. */\nconst parseNumberOption =\n  (optionName: string) =>\n  (value: unknown): number => {\n    if (typeof value === 'number') {\n      return value\n    } else if (typeof value === 'string') {\n      const parsed = parseInt(value, 10)\n      if (!isNaN(parsed)) {\n        return parsed\n      }\n    }\n\n    throw new Error(`${optionName} must be a number`)\n  }\n\n/** Renders the extended help for an option with usage information. */\nexport const renderExtendedHelp = (option: CLIOption, { markdown }: { markdown?: boolean } = {}) => {\n  let output = ''\n  if (option.cli !== false) {\n    // add -u to doctor option\n    output = `Usage:\n\n    ncu --${option.long}${option.arg ? ` [${option.arg}]` : ''}${option.long === 'doctor' ? ' -u' : ''}\\n`\n  }\n  if (option.type === 'boolean') {\n    output += `    ncu --no-${option.long}\\n`\n  }\n  if (option.short) {\n    // add -u to doctor option\n    output += `    ncu -${option.short}${option.arg ? ` [${option.arg}]` : ''}${option.long === 'doctor' ? 'u' : ''}\\n`\n  }\n\n  if (option.default !== undefined && !(Array.isArray(option.default) && option.default.length === 0)) {\n    output += `\\nDefault: ${option.default}\\n`\n  }\n  if (option.help) {\n    const helpText =\n      typeof option.help === 'function'\n        ? markdown\n          ? option.help({ markdown })\n          : uncode(option.help({ markdown }))\n        : option.help\n    output += `\\n${helpText.trim()}\\n\\n`\n  } else if (option.description) {\n    const description = markdown ? option.description : uncode(option.description)\n    output += `\\n${description.replace(/`/g, '')}\\n`\n  }\n\n  return output.trim()\n}\n\n/** Extended help for the --doctor option. */\nconst extendedHelpDoctor: ExtendedHelp = ({\n  markdown,\n}) => `Iteratively installs upgrades and runs your project's tests to identify breaking upgrades. Reverts broken upgrades and updates package.json with working upgrades.\n\n${chalk.yellow('Requires `-u` to execute')} (modifies your package file, lock file, and node_modules)\n\nTo be more precise:\n\n1. Runs \\`npm install\\` and \\`npm test\\` to ensure tests are currently passing.\n2. Runs \\`ncu -u\\` to optimistically upgrade all dependencies.\n3. If tests pass, hurray!\n4. If tests fail, restores package file and lock file.\n5. For each dependency, install upgrade and run tests.\n6. Prints broken upgrades with test error.\n7. Saves working upgrades to package.json.\n\nAdditional options:\n\n${table({\n  markdown,\n  rows: [\n    [chalk.cyan('--doctorInstall'), 'specify a custom install script (default: `npm install` or `yarn`)'],\n    [chalk.cyan('--doctorTest'), 'specify a custom test script (default: `npm test`)'],\n  ],\n})}\n\nExample:\n\n    $ ncu --doctor -u\n    Running tests before upgrading\n    npm install\n    npm run test\n    Upgrading all dependencies and re-running tests\n    ncu -u\n    npm install\n    npm run test\n    Tests failed\n    Identifying broken dependencies\n    npm install\n    npm install --no-save react@16.0.0\n    npm run test\n      ✓ react 15.0.0 → 16.0.0\n    npm install --no-save react-redux@7.0.0\n    npm run test\n      ✗ react-redux 6.0.0 → 7.0.0\n\n    /projects/myproject/test.js:13\n      throw new Error('Test failed!')\n      ^\n\n    npm install --no-save react-dnd@11.1.3\n    npm run test\n      ✓ react-dnd 10.0.0 → 11.1.3\n    Saving partially upgraded package.json\n`\n\n/** Extended help for the filterResults option. */\nconst extendedHelpFilterResults: ExtendedHelp = ({ markdown }) => {\n  /** If markdown, surround inline code with backticks. */\n  const codeInline = (code: string) => (markdown ? `\\`${code}\\`` : code)\n\n  return `Filters results based on a user provided predicate function after fetching new versions.\n\n${codeInline('filterResults')} runs _after_ new versions are fetched, in contrast to ${codeInline(\n    'filter',\n  )}, ${codeInline('reject')}, ${codeInline('filterVersion')}, and ${codeInline(\n    'rejectVersion',\n  )}, which run _before_. This allows you to exclude upgrades with ${codeInline(\n    'filterResults',\n  )} based on how the version has changed (e.g. a major version change).\n\n> :warning: The predicate function is only available in .ncurc.js or when importing npm-check-updates as a module, not on the command line. To convert a JSON config to a JS config, follow the instructions at https://github.com/raineorshine/npm-check-updates#config-functions.\n\n${codeBlock(\n  `${chalk.gray(`/** Exclude major version updates. Note this could also be achieved with --target semver.\n  @param {string} packageName               The name of the dependency.\n  @param {string} current                   Current version declaration (may be a range).\n  @param {SemVer[]} currentVersionSemver    Current version declaration in semantic versioning format (may be a range).\n  @param {string} upgraded                  Upgraded version.\n  @param {SemVer} upgradedVersionSemver     Upgraded version in semantic versioning format.\n  @returns {boolean}                        Return true if the upgrade should be kept; otherwise, it will be ignored.\n*/`)}\n${chalk.green('filterResults')}: (packageName, { current, currentVersionSemver, upgraded, upgradedVersionSemver }) ${chalk.cyan(\n    '=>',\n  )} {\n  ${chalk.cyan('const')} currentMajor ${chalk.red('=')} parseInt(currentVersionSemver[${chalk.cyan('0')}]?.major, ${chalk.cyan(\n    '10',\n  )})\n  ${chalk.cyan('const')} upgradedMajor ${chalk.red('=')} parseInt(upgradedVersionSemver?.major, ${chalk.cyan('10')})\n  ${chalk.red('if')} (currentMajor ${chalk.red('&&')} upgradedMajor) {\n    ${chalk.red('return')} currentMajor ${chalk.red('>=')} upgradedMajor\n  }\n  ${chalk.red('return')} ${chalk.cyan('true')}\n}`,\n  { markdown },\n)}\n\nFor the SemVer type definition, see: https://git.coolaj86.com/coolaj86/semver-utils.js#semverutils-parse-semverstring\n\n`\n}\n\n/** Extended help for the --format option. */\nconst extendedHelpFormat: ExtendedHelp = ({ markdown }) => {\n  const header =\n    'Modify the output formatting or show additional information. Specify one or more comma-delimited values.'\n  const tableString = table({\n    colAligns: ['right', 'left'],\n    markdown,\n    rows: [\n      ['dep', `Prints the dependency type (dev, peer, optional) of each package.`],\n      ['group', `Groups packages by major, minor, patch, and major version zero updates.`],\n      ['homepage', `Displays links to the package's homepage if specified in its package.json.`],\n      ['installedVersion', 'Prints the exact current version number instead of a range.'],\n      ['lines', 'Prints name@version on separate lines. Useful for piping to npm install.'],\n      ['ownerChanged', `Shows if the package owner has changed.`],\n      ['repo', `Infers and displays links to the package's source code repository. Requires packages to be installed.`],\n      ['diff', `Display link to compare the changes between package versions.`],\n      ['time', 'Shows the publish time of each upgrade.'],\n    ],\n  })\n\n  return `${header}\\n\\n${padLeft(tableString, markdown ? 0 : 4)}\n`\n}\n\n/** Extended help for the --install option. */\nconst extendedHelpInstall: ExtendedHelp = ({ markdown }) => {\n  const header = 'Control the auto-install behavior.'\n  const tableString = table({\n    colAligns: ['right', 'left'],\n    markdown,\n    rows: [\n      ['always', `Runs your package manager's install command automatically after upgrading.`],\n      ['never', `Does not install and does not prompt.`],\n      [\n        'prompt',\n        `Shows a message after upgrading that recommends an install, but does not install. In interactive mode, prompts for install. (default)`,\n      ],\n    ],\n  })\n\n  return `${header}\\n\\n${padLeft(tableString, markdown ? 0 : 4)}\n`\n}\n\n/** Extended help for the --filter option. */\nconst extendedHelpFilterFunction: ExtendedHelp = ({ markdown }) => {\n  /** If markdown, surround inline code with backticks. */\n  const codeInline = (code: string) => (markdown ? `\\`${code}\\`` : code)\n\n  return `Include only package names matching the given string, wildcard, glob, comma-or-space-delimited list, /regex/, or predicate function. Only included packages will be checked with ${codeInline(\n    '--peer',\n  )}.\n\n${codeInline('--filter')} runs _before_ new versions are fetched, in contrast to ${codeInline(\n    '--filterResults',\n  )} which runs _after_.\n\nYou can also specify a custom function in your .ncurc.js file, or when importing npm-check-updates as a module.\n\n> :warning: The predicate function is only available in .ncurc.js or when importing npm-check-updates as a module, not on the command line. To convert a JSON config to a JS config, follow the instructions at https://github.com/raineorshine/npm-check-updates#config-functions.\n\n${codeBlock(\n  `${chalk.gray(`/**\n  @param name     The name of the dependency.\n  @param semver   A parsed Semver array of the current version.\n    (See: https://git.coolaj86.com/coolaj86/semver-utils.js#semverutils-parse-semverstring)\n  @returns        True if the package should be included, false if it should be excluded.\n*/`)}\n${chalk.green('filter')}: (name, semver) ${chalk.cyan('=>')} {\n  ${chalk.red('if')} (name.startsWith(${chalk.yellow(`'@myorg/'`)})) {\n    ${chalk.red('return')} ${chalk.cyan('false')}\n  }\n  ${chalk.red('return')} ${chalk.cyan('true')}\n}`,\n  { markdown },\n)}\n\n`\n}\n\n/** Extended help for the --filterVersion option. */\nconst extendedHelpFilterVersionFunction: ExtendedHelp = ({ markdown }) => {\n  /** If markdown, surround inline code with backticks. */\n  const codeInline = (code: string) => (markdown ? `\\`${code}\\`` : code)\n\n  return `Include only versions matching the given string, wildcard, glob, comma-or-space-delimited list, /regex/, or predicate function.\n\n${codeInline('--filterVersion')} runs _before_ new versions are fetched, in contrast to ${codeInline(\n    '--filterResults',\n  )} which runs _after_.\n\nYou can also specify a custom function in your .ncurc.js file, or when importing npm-check-updates as a module.\n\n> :warning: The predicate function is only available in .ncurc.js or when importing npm-check-updates as a module, not on the command line. To convert a JSON config to a JS config, follow the instructions at https://github.com/raineorshine/npm-check-updates#config-functions. This function is an alias for the ${codeInline('filter')} option function.\n\n${codeBlock(\n  `${chalk.gray(`/**\n  @param name     The name of the dependency.\n  @param semver   A parsed Semver array of the current version.\n    (See: https://git.coolaj86.com/coolaj86/semver-utils.js#semverutils-parse-semverstring)\n  @returns        True if the package should be included, false if it should be excluded.\n*/`)}\n${chalk.green('filterVersion')}: (name, semver) ${chalk.cyan('=>')} {\n  ${chalk.red('if')} (name.startsWith(${chalk.yellow(`'@myorg/'`)}) ${chalk.red(\n    '&&',\n  )} parseInt(semver[0]?.major) ${chalk.cyan('>')} ${chalk.cyan(`5`)}) {\n    ${chalk.red('return')} ${chalk.cyan('false')}\n  }\n  ${chalk.red('return')} ${chalk.cyan('true')}\n}`,\n  { markdown },\n)}\n\n`\n}\n\n/** Extended help for the --reject option. */\nconst extendedHelpRejectFunction: ExtendedHelp = ({ markdown }) => {\n  /** If markdown, surround inline code with backticks. */\n  const codeInline = (code: string) => (markdown ? `\\`${code}\\`` : code)\n\n  return `The inverse of ${codeInline(\n    '--filter',\n  )}. Exclude package names matching the given string, wildcard, glob, comma-or-space-delimited list, /regex/, or predicate function. This will also exclude them from the ${codeInline(\n    '--peer',\n  )} check.\n\n${codeInline('--reject')} runs _before_ new versions are fetched, in contrast to ${codeInline(\n    '--filterResults',\n  )} which runs _after_.\n\nYou can also specify a custom function in your .ncurc.js file, or when importing npm-check-updates as a module.\n\n> :warning: The predicate function is only available in .ncurc.js or when importing npm-check-updates as a module, not on the command line. To convert a JSON config to a JS config, follow the instructions at https://github.com/raineorshine/npm-check-updates#config-functions.\n\n${codeBlock(\n  `${chalk.gray(`/**\n  @param name     The name of the dependency.\n  @param semver   A parsed Semver array of the current version.\n    (See: https://git.coolaj86.com/coolaj86/semver-utils.js#semverutils-parse-semverstring)\n  @returns        True if the package should be excluded, false if it should be included.\n*/`)}\n${chalk.green('reject')}: (name, semver) ${chalk.cyan('=>')} {\n  ${chalk.red('if')} (name.startsWith(${chalk.yellow(`'@myorg/'`)})) {\n    ${chalk.red('return')} ${chalk.cyan('true')}\n  }\n  ${chalk.red('return')} ${chalk.cyan('false')}\n}`,\n  { markdown },\n)}\n\n`\n}\n\n/** Extended help for the --rejectVersion option. */\nconst extendedHelpRejectVersionFunction: ExtendedHelp = ({ markdown }) => {\n  /** If markdown, surround inline code with backticks. */\n  const codeInline = (code: string) => (markdown ? `\\`${code}\\`` : code)\n\n  return `The inverse of ${codeInline(\n    '--filterVersion',\n  )}. Exclude versions matching the given string, wildcard, glob, comma-or-space-delimited list, /regex/, or predicate function.\n\n${codeInline('--rejectVersion')} runs _before_ new versions are fetched, in contrast to ${codeInline(\n    '--filterResults',\n  )} which runs _after_.\n\nYou can also specify a custom function in your .ncurc.js file, or when importing npm-check-updates as a module.\n\n> :warning: The predicate function is only available in .ncurc.js or when importing npm-check-updates as a module, not on the command line. To convert a JSON config to a JS config, follow the instructions at https://github.com/raineorshine/npm-check-updates#config-functions. This function is an alias for the reject option function.\n\n${codeBlock(\n  `${chalk.gray(`/**\n  @param name     The name of the dependency.\n  @param semver   A parsed Semver array of the current version.\n    (See: https://git.coolaj86.com/coolaj86/semver-utils.js#semverutils-parse-semverstring)\n  @returns        True if the package should be excluded, false if it should be included.\n*/`)}\n${chalk.green('rejectVersion')}: (name, semver) ${chalk.cyan('=>')} {\n  ${chalk.red('if')} (name.startsWith(${chalk.yellow(`'@myorg/'`)}) ${chalk.red(\n    '&&',\n  )} parseInt(semver[0]?.major) ${chalk.cyan('>')} ${chalk.cyan(`5`)}) {\n    ${chalk.red('return')} ${chalk.cyan('true')}\n  }\n  ${chalk.red('return')} ${chalk.cyan('false')}\n}`,\n  { markdown },\n)}\n\n`\n}\n\n/** Extended help for the --group option. */\nconst extendedHelpGroupFunction: ExtendedHelp = ({ markdown }) => {\n  return `Customize how packages are divided into groups when using \\`--format group\\`.\n\nOnly available in .ncurc.js or when importing npm-check-updates as a module, not on the command line. To convert a JSON config to a JS config, follow the instructions at https://github.com/raineorshine/npm-check-updates#config-functions.\n\n${codeBlock(\n  `${chalk.gray(`/**\n  @param name             The name of the dependency.\n  @param defaultGroup     The predefined group name which will be used by default.\n  @param currentSpec      The current version range in your package.json.\n  @param upgradedSpec     The upgraded version range that will be written to your package.json.\n  @param upgradedVersion  The upgraded version number returned by the registry.\n  @returns                A predefined group name ('major' | 'minor' | 'patch' | 'majorVersionZero' | 'none') or a custom string to create your own group.\n*/`)}\n${chalk.green('groupFunction')}: (name, defaultGroup, currentSpec, upgradedSpec, upgradedVersion) ${chalk.cyan('=>')} {\n  ${chalk.red('if')} (name ${chalk.red('===')} ${chalk.yellow(`'typescript'`)} ${chalk.red(\n    '&&',\n  )} defaultGroup ${chalk.red('===')} ${chalk.yellow(`'minor'`)}) {\n    ${chalk.red('return')} ${chalk.yellow(`'major'`)}\n  }\n  ${chalk.red('if')} (name.startsWith(${chalk.yellow(`'@myorg/'`)})) {\n    ${chalk.red('return')} ${chalk.yellow(`'My Org'`)}\n  }\n  ${chalk.red('return')} defaultGroup\n}`,\n  { markdown },\n)}\n\n`\n}\n\n/** Extended help for the --target option. */\nconst extendedHelpTarget: ExtendedHelp = ({ markdown }) => {\n  const header = 'Determines the version to upgrade to. (default: \"latest\")'\n  const tableString = table({\n    colAligns: ['right', 'left'],\n    markdown,\n    rows: [\n      [\n        'greatest',\n        `Upgrade to the highest version number published, regardless of release date or tag. Includes prereleases.`,\n      ],\n      [\n        'latest',\n        `Upgrade to whatever the package's \"latest\" git tag points to. Excludes prereleases unless --pre is specified.`,\n      ],\n      ['minor', 'Upgrade to the highest minor version without bumping the major version.'],\n      [\n        'newest',\n        `Upgrade to the version with the most recent publish date, even if there are other version numbers that are higher. Includes prereleases.`,\n      ],\n      ['patch', `Upgrade to the highest patch version without bumping the minor or major versions.`],\n      ['semver', `Upgrade to the highest version within the semver range specified in your package.json.`],\n      ['@[tag]', `Upgrade to the version published to a specific tag, e.g. 'next' or 'beta'.`],\n    ],\n  })\n\n  return `${header}\n\n${padLeft(tableString, markdown ? 0 : 4)}\n\ne.g.\n\n${codeBlock(`ncu --target semver`)}\n\nYou can also specify a custom function in your .ncurc.js file, or when importing npm-check-updates as a module.\n\n> :warning: The predicate function is only available in .ncurc.js or when importing npm-check-updates as a module, not on the command line. To convert a JSON config to a JS config, follow the instructions at https://github.com/raineorshine/npm-check-updates#config-functions.\n\n${codeBlock(\n  `${chalk.gray(`/** Upgrade major version zero to the next minor version, and everything else to latest.\n  @param name     The name of the dependency.\n  @param semver   A parsed Semver object of the upgraded version.\n    (See: https://git.coolaj86.com/coolaj86/semver-utils.js#semverutils-parse-semverstring)\n  @returns        One of the valid target values (specified in the table above).\n*/`)}\n${chalk.green('target')}: (name, semver) ${chalk.cyan('=>')} {\n  ${chalk.red('if')} (parseInt(semver[0]?.major) ${chalk.red('===')} ${chalk.yellow(\"'0'\")}) ${chalk.red(\n    'return',\n  )} ${chalk.yellow(\"'minor'\")}\n  ${chalk.red('return')} ${chalk.yellow(\"'latest'\")}\n}`,\n  { markdown },\n)}\n`\n}\n\n/** Extended help for the --packageManager option. */\nconst extendedHelpPackageManager: ExtendedHelp = ({ markdown }) => {\n  const header = 'Specifies the package manager to use when looking up versions.'\n  const tableString = table({\n    colAligns: ['right', 'left'],\n    markdown,\n    rows: [\n      ['npm', `System-installed npm. Default.`],\n      ['yarn', `System-installed yarn. Automatically used if yarn.lock is present.`],\n      ['pnpm', `System-installed pnpm. Automatically used if pnpm-lock.yaml is present.`],\n      ['bun', `System-installed bun. Automatically used if bun.lock or bun.lockb is present.`],\n    ],\n  })\n\n  return `${header}\\n\\n${padLeft(tableString, markdown ? 0 : 4)}\n`\n}\n\n/** Extended help for the --registryType option. */\nconst extendedHelpRegistryType: ExtendedHelp = ({ markdown }) => {\n  /** If markdown, surround inline code with backticks. */\n  const codeInline = (code: string) => (markdown ? `\\`${code}\\`` : code)\n\n  const header = `Specify whether ${codeInline('--registry')} refers to a full npm registry or a simple JSON file.`\n  const tableString = table({\n    colAligns: ['right', 'left'],\n    markdown,\n    rows: [\n      ['npm', `Default npm registry`],\n      [\n        'json',\n        `Checks versions from a file or url to a simple JSON registry. Must include the ${chalk.cyan(\n          '`--registry`',\n        )} option.\n\nExample:\n\n    ${chalk.gray('// local file')}\n    ${chalk.cyan('$')} ncu --registryType json --registry ./registry.json\n\n    ${chalk.gray('// url')}\n    ${chalk.cyan('$')} ncu --registryType json --registry https://api.mydomain/registry.json\n\n    ${chalk.gray('// you can omit --registryType when the registry ends in .json')}\n    ${chalk.cyan('$')} ncu --registry ./registry.json\n    ${chalk.cyan('$')} ncu --registry https://api.mydomain/registry.json\n\nregistry.json:\n\n    {\n      \"prettier\": \"2.7.1\",\n      \"typescript\": \"4.7.4\"\n    }\n\n`,\n      ],\n    ],\n  })\n\n  return `${header}\\n\\n${padLeft(tableString, markdown ? 0 : 4)}\n`\n}\n\n/** Extended help for the --peer option. */\nconst extendedHelpPeer: ExtendedHelp = ({ markdown }) => {\n  /** If markdown, surround inline code with backticks. */\n  const codeInline = (code: string) => (markdown ? `\\`${code}\\`` : code)\n  return `Check peer dependencies of installed packages and filter updates to compatible versions.\n\n${chalk.bold('Example')}:\n\nThe following example demonstrates how \\`--peer\\` works, and how it uses peer dependencies from upgraded modules.\n\nThe package ${chalk.bold('ncu-test-peer-update')} has two versions published:\n\n- 1.0.0 has peer dependency ${codeInline('\"ncu-test-return-version\": \"1.0.x\"')}\n- 1.1.0 has peer dependency ${codeInline('\"ncu-test-return-version\": \"1.1.x\"')}\n\nOur test app has the following dependencies:\n\n    \"ncu-test-peer-update\": \"1.0.0\",\n    \"ncu-test-return-version\": \"1.0.0\"\n\nThe latest versions of these packages are:\n\n    \"ncu-test-peer-update\": \"1.1.0\",\n    \"ncu-test-return-version\": \"2.0.0\"\n\n${chalk.bold('With `--peer`')}:\n\nncu upgrades packages to the highest version that still adheres to the peer dependency constraints:\n\n    ncu-test-peer-update     1.0.0  →  1.${chalk.cyan('1.0')}\n    ncu-test-return-version  1.0.0  →  1.${chalk.cyan('1.0')}\n\n${chalk.bold('Without `--peer`')}:\n\nAs a comparison: without using the \\`--peer\\` option, ncu will suggest the latest versions, ignoring peer dependencies:\n\n    ncu-test-peer-update     1.0.0  →  1.${chalk.cyan('1.0')}\n    ncu-test-return-version  1.0.0  →  ${chalk.red('2.0.0')}\n`\n}\n\n/** Extended help for the --cooldown option. */\nconst extendedHelpCooldown: ExtendedHelp = ({ markdown }) => {\n  return `The cooldown option helps protect against supply chain attacks by requiring package versions to be published at least the given amount of time before considering them for upgrade.\n\nThe value can be a plain number (days) or a string with a unit suffix:\n\n    --cooldown 7       7 days\n    --cooldown 7d      7 days (same as above)\n    --cooldown 12h     12 hours\n    --cooldown 30m     30 minutes\n\nNote that previous stable versions will ${chalk.bold('not')} be suggested. The package will be completely ignored if its latest published version is within the cooldown period. This is due to a limitation of the npm registry, which does not provide a way to query previous stable versions.\n\n${chalk.bold('Example')}:\n\nLet's examine how cooldown works with a package that has these versions available:\n\n    1.0.0          Released 7 days ago    (initial version)\n    1.1.0          Released 6 days ago    (minor update)\n    1.1.1          Released 5 days ago    (patch update)\n    1.2.0          Released 5 days ago    (minor update)\n    2.0.0-beta.1   Released 5 days ago    (beta release)\n    1.2.1          Released 4 days ago    (patch update)\n    1.3.0          Released 4 days ago    (minor update) [latest]\n    2.0.0-beta.2   Released 3 days ago    (beta release)\n    2.0.0-beta.3   Released 2 days ago    (beta release) [beta]\n\n${chalk.bold('With default target (latest)')}:\n\n${codeBlock(`${chalk.cyan('$')} ncu --cooldown 5`, { markdown })}\n\nNo update will be suggested because:\n\n- Latest version (1.3.0) is only 4 days old.\n- Cooldown requires versions to be at least 5 days old\n- Use \\`--cooldown 4\\` or lower to allow this update\n\n${chalk.bold('With `@beta`/`@tag` target')}:\n\n${codeBlock(`${chalk.cyan('$')} ncu --cooldown 3 --target @beta`, { markdown })}\n\nNo update will be suggested because:\n\n- Current beta (2.0.0-beta.3) is only 2 days old\n- Cooldown requires versions to be at least 3 days old\n- Use \\`--cooldown 2\\` or lower to allow this update\n\n${chalk.bold('With other targets')}:\n\n${codeBlock(`${chalk.cyan('$')} ncu --cooldown 5 --target greatest|newest|minor|patch|semver`, { markdown })}\n\nEach target will select the best version that is at least 5 days old:\n\n    greatest → 1.2.0        (highest version number outside cooldown)\n    newest   → 2.0.0-beta.1 (most recently published version outside cooldown)\n    minor    → 1.2.0        (highest minor version outside cooldown)\n    patch    → 1.1.1        (highest patch version outside cooldown)\n\n${chalk.bold('Note for latest/tag targets')}:\n\n> :warning: For packages that update frequently (e.g. daily releases), using a long cooldown period (7+ days) with the default \\`--target latest\\` or \\`--target @tag\\` may prevent all updates since new versions will be published before older ones meet the cooldown requirement. Please consider this when setting your cooldown period.\n\nYou can also provide a custom function in your .ncurc.js file or when importing npm-check-updates as a module.\n\n> :warning: The predicate function is only available in .ncurc.js or when importing npm-check-updates as a module, not on the command line. To convert a JSON config to a JS config, follow the instructions at https://github.com/raineorshine/npm-check-updates#config-functions.\n\n${codeBlock(\n  `${chalk.gray(`/** Set cooldown to 3 days but skip it for \\`@my-company\\` packages.\n  @param packageName     The name of the dependency.\n  @returns               Cooldown days restriction for given package.\n*/`)}\n${chalk.green('cooldown')}: packageName ${chalk.cyan('=>')} (packageName.startsWith(${chalk.yellow(\"'@my-company'\")}) ? ${chalk.cyan('0')} : ${chalk.cyan('3')})`,\n  { markdown },\n)}\n`\n}\n\n// store CLI options separately from bin file so that they can be used to build type definitions\nconst cliOptions: CLIOption[] = [\n  {\n    long: 'cache',\n    description: `Cache versions to a local cache file. Default \\`--cacheFile\\` is ${defaultCacheFile} and default \\`--cacheExpiration\\` is 10 minutes.`,\n    type: 'boolean',\n  },\n  {\n    long: 'cacheClear',\n    description: 'Clear the default cache, or the cache file specified by `--cacheFile`.',\n    type: 'boolean',\n  },\n  {\n    long: 'cacheExpiration',\n    arg: 'min',\n    description: 'Cache expiration in minutes. Only works with `--cache`.',\n    parse: parseNumberOption('cacheExpiration'),\n    default: 10,\n    type: 'number',\n  },\n  {\n    long: 'cacheFile',\n    arg: 'path',\n    description: 'Filepath for the cache file. Only works with `--cache`.',\n    parse: value => {\n      if (typeof value !== 'string') {\n        throw new Error('cacheFile must be a string')\n      }\n      return path.isAbsolute(value) ? value : path.join(process.cwd(), value)\n    },\n    default: defaultCacheFile,\n    type: 'string',\n  },\n  {\n    long: 'color',\n    description: 'Force color in terminal.',\n    type: 'boolean',\n  },\n  {\n    long: 'concurrency',\n    arg: 'n',\n    description: 'Max number of concurrent HTTP requests to registry.',\n    parse: parseNumberOption('concurrency'),\n    default: 8,\n    type: 'number',\n  },\n  {\n    long: 'configFileName',\n    arg: 's',\n    description: 'Config file name. (default: .ncurc.{json,yml,js,cjs})',\n    type: 'string',\n  },\n  {\n    long: 'configFilePath',\n    arg: 'path',\n    description: 'Directory of .ncurc config file. (default: directory of `packageFile`)',\n    type: 'string',\n  },\n  {\n    long: 'cwd',\n    arg: 'path',\n    description: 'Working directory in which npm will be executed.',\n    type: 'string',\n  },\n  {\n    long: 'deep',\n    description: `Run recursively in current working directory. Alias of (\\`--packageFile '**/package.json'\\`).`,\n    type: 'boolean',\n  },\n  {\n    long: 'dep',\n    arg: 'value',\n    description:\n      'Check one or more sections of dependencies only: dev, optional, peer, prod, or packageManager (comma-delimited).',\n    default: ['prod', 'dev', 'optional', 'packageManager'],\n    parse: value => (value && typeof value === 'string' ? value.split(',') : value),\n    type: 'string | readonly string[]',\n  },\n  {\n    long: 'deprecated',\n    default: true,\n    description: 'Include deprecated packages. Use `--no-deprecated` to exclude deprecated packages (20–25% slower).',\n    type: 'boolean',\n  },\n  {\n    long: 'doctor',\n    short: 'd',\n    description:\n      'Iteratively installs upgrades and runs tests to identify breaking upgrades. Requires `-u` to execute.',\n    type: 'boolean',\n    help: extendedHelpDoctor,\n  },\n  {\n    long: 'doctorInstall',\n    arg: 'command',\n    description:\n      'Specifies the install script to use in doctor mode. (default: `npm install` or the equivalent for your package manager)',\n    type: 'string',\n  },\n  {\n    long: 'doctorTest',\n    arg: 'command',\n    description: 'Specifies the test script to use in doctor mode. (default: `npm test`)',\n    type: 'string',\n  },\n  {\n    long: 'enginesNode',\n    description: 'Include only packages that satisfy engines.node as specified in the package file.',\n    type: 'boolean',\n  },\n  {\n    long: 'errorLevel',\n    short: 'e',\n    arg: 'n',\n    description:\n      'Set the error level. 1: exits with error code 0 if no errors occur. 2: exits with error code 0 if no packages need updating (useful for continuous integration).',\n    parse: parseNumberOption('errorLevel'),\n    default: 1,\n    type: 'number',\n  },\n  {\n    long: 'filter',\n    short: 'f',\n    arg: 'p',\n    description:\n      'Include only package names matching the given string, wildcard, glob, comma-or-space-delimited list, /regex/, or predicate function.',\n    type: 'string | RegExp | readonly (string | RegExp)[] | FilterFunction',\n    parse: (value, accum) => [...(accum || []), value],\n    help: extendedHelpFilterFunction,\n  },\n  {\n    long: 'filterResults',\n    arg: 'fn',\n    cli: false,\n    description: `Filters results based on a user provided predicate function after fetching new versions.`,\n    type: 'FilterResultsFunction',\n    help: extendedHelpFilterResults,\n  },\n  {\n    long: 'filterVersion',\n    arg: 'p',\n    description: 'Filter on package version using comma-or-space-delimited list, /regex/, or predicate function.',\n    type: 'string | RegExp | readonly (string | RegExp)[] | FilterFunction',\n    parse: (value, accum) => [...(accum || []), value],\n    help: extendedHelpFilterVersionFunction,\n  },\n  {\n    long: 'format',\n    arg: 'value',\n    description:\n      'Modify the output formatting or show additional information. Specify one or more comma-delimited values: dep, group, ownerChanged, repo, time, lines, installedVersion.',\n    parse: value => (typeof value === 'string' ? value.split(',') : value),\n    default: [],\n    type: 'readonly string[]',\n    choices: ['dep', 'group', 'homepage', 'ownerChanged', 'repo', 'diff', 'time', 'lines', 'installedVersion'],\n    help: extendedHelpFormat,\n  },\n  {\n    long: 'global',\n    short: 'g',\n    description: 'Check global packages instead of in the current project.',\n    type: 'boolean',\n  },\n  {\n    long: 'groupFunction',\n    arg: 'fn',\n    cli: false,\n    description: `Customize how packages are divided into groups when using \\`--format group\\`.`,\n    type: 'GroupFunction',\n    help: extendedHelpGroupFunction,\n  },\n  {\n    long: 'install',\n    arg: 'value',\n    description: 'Control the auto-install behavior: always, never, prompt.',\n    help: extendedHelpInstall,\n    default: 'prompt',\n    choices: ['always', 'never', 'prompt'],\n    type: `'always' | 'never' | 'prompt'`,\n  },\n  {\n    long: 'interactive',\n    short: 'i',\n    description: 'Enable interactive prompts for each dependency; implies `-u` unless one of the json options are set.',\n    type: 'boolean',\n  },\n  {\n    // program.json is set to true in programInit if any options that begin with 'json' are true\n    long: 'jsonAll',\n    short: 'j',\n    description: 'Output new package file instead of human-readable message.',\n    type: 'boolean',\n  },\n  {\n    long: 'jsonDeps',\n    description:\n      'Like `jsonAll` but only lists `dependencies`, `devDependencies`, `optionalDependencies`, etc of the new package data.',\n    type: 'boolean',\n  },\n  {\n    long: 'jsonUpgraded',\n    description: 'Output upgraded dependencies in json.',\n    type: 'boolean',\n  },\n  {\n    long: 'loglevel',\n    short: 'l',\n    arg: 'n',\n    description: 'Amount to log: silent, error, minimal, warn, info, verbose, silly.',\n    default: 'warn',\n    type: 'string',\n  },\n  {\n    long: 'mergeConfig',\n    description: `Merges nested configs with the root config file for \\`--deep\\` or \\`--packageFile\\` options. (default: false)`,\n    type: 'boolean',\n  },\n  {\n    long: 'minimal',\n    short: 'm',\n    description: 'Do not upgrade newer versions that are already satisfied by the version range according to semver.',\n    type: 'boolean',\n  },\n  {\n    long: 'packageData',\n    arg: 'value',\n    description: 'Package file data (you can also use stdin).',\n    type: 'string | PackageFile',\n  },\n  {\n    long: 'packageFile',\n    arg: 'path|glob',\n    description: 'Package file(s) location. (default: ./package.json)',\n    type: 'string',\n  },\n  {\n    long: 'packageManager',\n    short: 'p',\n    arg: 's',\n    description: 'npm, yarn, pnpm, deno, bun, staticRegistry (default: npm).',\n    help: extendedHelpPackageManager,\n    type: `'npm' | 'yarn' | 'pnpm' | 'deno' | 'bun' | 'staticRegistry'`,\n  },\n  {\n    long: 'peer',\n    description: 'Check peer dependencies of installed packages and filter updates to compatible versions.',\n    type: 'boolean',\n    help: extendedHelpPeer,\n  },\n  {\n    long: 'pre',\n    arg: 'n',\n    description:\n      'Include prerelease versions, e.g. -alpha.0, -beta.5, -rc.2. Automatically set to 1 when `--target` is newest or greatest, or when the current version is a prerelease. (default: 0)',\n    parse: (value: unknown): boolean => {\n      if (typeof value === 'number') {\n        return !!value\n      } else if (typeof value === 'string') {\n        return !!parseInt(value, 10)\n      } else {\n        throw new Error('pre must be a number')\n      }\n    },\n    type: 'number',\n  },\n  {\n    long: 'prefix',\n    arg: 'path',\n    description: 'Current working directory of npm.',\n    type: 'string',\n  },\n  {\n    long: 'registry',\n    short: 'r',\n    arg: 'uri',\n    description: 'Specify the registry to use when looking up package versions.',\n    type: 'string',\n  },\n  {\n    long: 'registryType',\n    arg: 'type',\n    description:\n      'Specify whether --registry refers to a full npm registry or a simple JSON file or url: npm, json. (default: npm)',\n    help: extendedHelpRegistryType,\n    type: `'npm' | 'json'`,\n  },\n  {\n    long: 'reject',\n    short: 'x',\n    arg: 'p',\n    description:\n      'Exclude packages matching the given string, wildcard, glob, comma-or-space-delimited list, /regex/, or predicate function.',\n    type: 'string | RegExp | readonly (string | RegExp)[] | FilterFunction',\n    parse: (value, accum) => [...(accum || []), value],\n    help: extendedHelpRejectFunction,\n  },\n  {\n    long: 'rejectVersion',\n    arg: 'p',\n    description: 'Exclude package.json versions using comma-or-space-delimited list, /regex/, or predicate function.',\n    type: 'string | RegExp | readonly (string | RegExp)[] | FilterFunction',\n    parse: (value, accum) => [...(accum || []), value],\n    help: extendedHelpRejectVersionFunction,\n  },\n  {\n    long: 'removeRange',\n    description: 'Remove version ranges from the final package version.',\n    type: 'boolean',\n  },\n  {\n    long: 'root',\n    default: true,\n    description:\n      'Runs updates on the root project in addition to specified workspaces. Only allowed with `--workspace` or `--workspaces`.',\n    type: 'boolean',\n  },\n  {\n    long: 'retry',\n    arg: 'n',\n    description: 'Number of times to retry failed requests for package info.',\n    parse: parseNumberOption('retry'),\n    default: 3,\n    type: 'number',\n  },\n  {\n    long: 'silent',\n    short: 's',\n    description: \"Don't output anything. Alias for `--loglevel` silent.\",\n    type: 'boolean',\n  },\n  {\n    long: 'stdin',\n    description: 'Read package.json from stdin.',\n    type: 'string',\n  },\n  {\n    long: 'target',\n    short: 't',\n    arg: 'value',\n    description: `Determines the version to upgrade to: latest, newest, greatest, minor, patch, semver, \\`@[tag]\\`, or [function]. (default: latest)`,\n    help: extendedHelpTarget,\n    // eslint-disable-next-line no-template-curly-in-string\n    type: `${supportedVersionTargets.map(s => `'${s}'`).join(' | ')} | ${'`@${string}`'} | TargetFunction`,\n  },\n  {\n    long: 'timeout',\n    arg: 'ms',\n    description: 'Global timeout in milliseconds. (default: no global timeout and 30 seconds per npm-registry-fetch)',\n    parse: parseNumberOption('timeout'),\n    type: 'number',\n  },\n  {\n    long: 'upgrade',\n    short: 'u',\n    description: 'Overwrite package file with upgraded versions instead of just outputting to console.',\n    type: 'boolean',\n  },\n  {\n    long: 'verbose',\n    description: 'Log additional information for debugging. Alias for `--loglevel` verbose.',\n    type: 'boolean',\n  },\n  {\n    long: 'workspace',\n    arg: 's',\n    parse: (value, accum) => [...accum, value],\n    default: [],\n    description: 'Run on one or more specified workspaces. Add `--no-root` to exclude the root project.',\n    type: 'readonly string[]',\n  },\n  {\n    long: 'workspaces',\n    short: 'w',\n    description: 'Run on all workspaces. Add `--no-root` to exclude the root project.',\n    type: 'boolean',\n  },\n  {\n    long: 'cooldown',\n    short: 'c',\n    arg: 'period',\n    description:\n      'Sets a minimum age for package versions to be considered for upgrade. Accepts a number (days) or a string with a unit: \"7d\" (days), \"12h\" (hours), \"30m\" (minutes). Reduces the risk of installing newly published, potentially compromised packages.',\n    type: `number | string | CooldownFunction`,\n    help: extendedHelpCooldown,\n    parse: value => {\n      if (typeof value === 'number' || typeof value === 'function') {\n        return value\n      } else if (typeof value === 'string') {\n        const days = parseCooldown(value)\n        return days !== null ? days : parseInt(value, 10)\n      } else {\n        throw new Error('cooldown must be a number, string, or function')\n      }\n    },\n  },\n]\n\n// put cliOptions into an object for O(1) lookups\nexport const cliOptionsMap = cliOptions.reduce(\n  (accum, option) => ({\n    ...accum,\n    ...(option.short ? { [option.short]: option } : null),\n    ...(option.long ? { [option.long]: option } : null),\n  }),\n  {} as Index<CLIOption>,\n)\n\nconst cliOptionsSorted = sortBy(cliOptions, v => v.long)\n\nexport default cliOptionsSorted\n"
  },
  {
    "path": "src/index.ts",
    "content": "import path from 'path'\nimport prompts from 'prompts-ncu'\nimport pkg from '../package.json'\nimport { cliOptionsMap } from './cli-options'\nimport { cacheClear } from './lib/cache'\nimport chalk, { chalkInit } from './lib/chalk'\nimport determinePackageManager from './lib/determinePackageManager'\nimport doctor from './lib/doctor'\nimport findPackage from './lib/findPackage'\nimport getAllPackages from './lib/getAllPackages'\nimport getNcuRc from './lib/getNcuRc'\nimport initOptions from './lib/initOptions'\nimport { print, printJson } from './lib/logging'\nimport mergeOptions from './lib/mergeOptions'\nimport programError from './lib/programError'\nimport runGlobal from './lib/runGlobal'\nimport runLocal from './lib/runLocal'\nimport spawnCommand from './lib/spawnCommand'\nimport { Index } from './types/IndexType'\nimport { Options } from './types/Options'\nimport { PackageFile } from './types/PackageFile'\nimport { PackageInfo } from './types/PackageInfo'\nimport { RunOptions } from './types/RunOptions'\nimport { VersionSpec } from './types/VersionSpec'\n\nexport { default as defineConfig } from './lib/defineConfig'\nexport type { RcOptions } from './types/RcOptions'\n\n// allow prompt injection from environment variable for testing purposes\nif (process.env.INJECT_PROMPTS) {\n  prompts.inject(JSON.parse(process.env.INJECT_PROMPTS))\n}\n\n/** Tracks the (first) unhandled rejection so the process can exit with an error code at the end. This allows other errors to be logged before the process exits. */\nlet unhandledRejectionError = false\n\n// Use `node --trace-uncaught ...` to show where the exception was thrown.\n// See: https://nodejs.org/api/process.html#event-unhandledrejection\nprocess.on('unhandledRejection', (reason: string | Error) => {\n  // do not rethrow, as there may be other errors to print out\n  console.error(reason)\n\n  // ensure the process exits with a non-zero code at the end\n  unhandledRejectionError = true\n})\n\n/**\n * Volta is a tool for managing JavaScript tooling like Node and npm. Volta has\n * its own system for installing global packages which circumvents npm, so\n * commands like `npm ls -g` do not accurately reflect what is installed.\n *\n * The ability to use `npm ls -g` is tracked in this Volta issue: https://github.com/volta-cli/volta/issues/1012\n */\nconst noVolta = (options: Options) => {\n  // The first check is for macOS/Linux and the second check is for Windows\n  if (options.global && (!!process.env.VOLTA_HOME || process.env.PATH?.includes('\\\\Volta'))) {\n    const message =\n      'It appears you are using Volta. `npm-check-updates --global` ' +\n      'cannot be used with Volta because Volta has its own system for ' +\n      'managing global packages which circumvents npm.\\n\\n' +\n      'If you are still receiving this message after uninstalling Volta, ' +\n      'ensure your PATH does not contain an entry for Volta and your ' +\n      'shell profile does not define VOLTA_HOME. You may need to reboot ' +\n      'for changes to your shell profile to take effect.'\n\n    print(options, message, 'error')\n    process.exit(1)\n  }\n}\n\n/** Returns the package manager that should be used to install packages after running \"ncu -u\". Uses the same detection logic as the main package manager determination. */\nconst getPackageManagerForInstall = async (options: Options, packageFile: string) => {\n  // create options context for the package file location\n  const installOptions: Options = {\n    ...options,\n    cwd: options.cwd || path.resolve(packageFile, '..'),\n    packageFile,\n  }\n\n  // when packageManager is set to staticRegistry, we need to infer the package manager from lock files\n  if (options.packageManager === 'staticRegistry') {\n    return await determinePackageManager({ ...installOptions, packageManager: undefined })\n  } else if (options.packageManager && options.packageManager !== 'npm') {\n    return options.packageManager\n  }\n\n  // use the same logic as the main package manager detection\n  return await determinePackageManager(installOptions)\n}\n\n/** Returns if analysis contains upgrades */\nconst someUpgraded = (pkgs: string[], analysis: Index<PackageFile> | PackageFile, options: Options) => {\n  // deep mode analysis is of type Index<PackageFile>\n  // non-deep mode analysis is of type <PackageFile>, so we normalize it to Index<PackageFile>\n  const analysisNormalized: Index<PackageFile> =\n    !options.deep && !options.workspaces && !options.workspace\n      ? { [pkgs[0]]: analysis as PackageFile }\n      : (analysis as Index<PackageFile>)\n\n  return Object.values(analysisNormalized).some(upgrades => Object.keys(upgrades).length > 0)\n}\n\n/** Either suggest an install command based on the package manager, or in interactive mode, prompt to auto-install. */\nconst install = async (\n  pkgs: string[],\n  analysis: Index<PackageFile> | PackageFile,\n  options: Options,\n): Promise<unknown> => {\n  if (options.install === 'never') {\n    print(options, '')\n    return\n  }\n\n  // if no packages were upgraded (i.e. all dependencies deselected in interactive mode), then bail without suggesting an install.\n  // normalize the analysis for one or many packages\n  if (!someUpgraded(pkgs, analysis, options)) return\n\n  // for the purpose of the install hint, just use the package manager used in the first sub-project\n  // if auto-installing, the actual package manager in each sub-project will be used\n  const packageManager = await getPackageManagerForInstall(options, pkgs[0])\n\n  // by default, show an install hint after upgrading\n  // this will be disabled in interactive mode if the user chooses to have npm-check-updates execute the install command\n  const installHint = `Run ${chalk.cyan(packageManager + ' install')}${\n    pkgs.length > 1 && !options.workspace && !options.workspaces ? ' in each project directory' : ''\n  } to install new versions`\n\n  // Disable interactive mode when running doctor EXCEPT when running tests.\n  // Otherwise running doctor mode on npm-check-updates itself will cause interactive.test.ts to fail.\n  const isInteractive = options.interactive && (process.env.NCU_TESTS || !process.env.NCU_DOCTOR)\n\n  // prompt the user if they want ncu to run \"npm install\"\n  let response\n  if (isInteractive && options.install === 'prompt') {\n    print(options, '')\n    response = await prompts({\n      type: 'confirm',\n      name: 'value',\n      message: `${installHint}?`,\n      initial: true,\n      // allow Ctrl+C to kill the process\n      onState: (state: any) => {\n        if (state.aborted) {\n          process.nextTick(() => process.exit(1))\n        }\n      },\n    })\n  }\n\n  // auto-install\n  if (options.install === 'always' || (isInteractive && response.value)) {\n    if (options.install === 'always') {\n      print(options, '')\n    }\n    print(options, 'Installing dependencies...')\n\n    // only run npm install once in the root when in workspace mode\n    // npm install will install packages for all workspaces\n    const isWorkspace = options.workspaces || !!options.workspace?.length\n    const pkgsNormalized = isWorkspace ? ['package.json'] : pkgs\n\n    for await (const pkgFile of pkgsNormalized) {\n      const packageManager = await getPackageManagerForInstall(options, pkgFile)\n      const cwd = options.cwd || path.resolve(pkgFile, '..')\n      let stdout = ''\n      try {\n        await spawnCommand(\n          packageManager,\n          ['install'],\n          {\n            stdout: (data: string) => {\n              stdout += data\n            },\n            stderr: (data: string) => {\n              console.error(chalk.red(data.toString()))\n            },\n          },\n          {\n            cwd,\n            // spawnCommand takes the native SpawnOptions type, but the env property is missing some of the type definitions for environment variables that npm-check-updates uses. Cast to any to allow these extra environment variables.\n            // npm_config_strict_peer_dependencies is expected to be a string for some reason\n            env: {\n              ...process.env,\n              ...(options.color !== false ? { FORCE_COLOR: true } : null),\n              // With spawn, pnpm install will fail with ERR_PNPM_PEER_DEP_ISSUES  Unmet peer dependencies.\n              // When pnpm install is run directly from the terminal, this error does not occur.\n              // When pnpm install is run from a simple spawn script, this error does not occur.\n              // The issue only seems to be when pnpm install is executed from npm-check-updates, but it's not clear what configuration or environmental factors are causing this.\n              // For now, turn off strict-peer-dependencies on pnpm auto-install.\n              // See: https://github.com/raineorshine/npm-check-updates/issues/1191\n              ...(packageManager === 'pnpm' ? { npm_config_strict_peer_dependencies: false } : null),\n            } as any,\n          },\n        )\n        print(options, stdout)\n        print(options, 'Done')\n      } catch (err: any) {\n        // sometimes packages print errors to stdout instead of stderr\n        // if there is nothing on stderr, reject with stdout\n        console.error(err?.message || err || stdout)\n\n        // use a program error to exit with a non-zero code rather than throwing a new Error and allowing it to bubble up to the \"this is a bug and should be reported message\".\n        programError(\n          options,\n          'Install failed. This is not a bug in npm-check-updates. The most common causes are invalid peer dependencies, networking issues, or failing postinstall scripts. Consider using --peer to filter updates to compatible versions (takes longer) or --doctor to identify breaking upgrades.',\n        )\n      }\n    }\n  }\n  // show the install hint unless auto-install occurred\n  else if (!isInteractive) {\n    print(options, `\\n${installHint}.`)\n  }\n}\n\n/** Runs the dependency upgrades. Loads the ncurc, finds the package file, and handles --deep. */\nasync function runUpgrades(options: Options, timeout?: NodeJS.Timeout): Promise<Index<string> | PackageFile | void> {\n  const [selectedPackageInfos, workspacePackages]: [PackageInfo[], string[]] = await getAllPackages(options)\n\n  const packageFilepaths: string[] = selectedPackageInfos.map((packageInfo: PackageInfo) => packageInfo.filepath)\n\n  // enable deep mode if --deep, --workspace, --workspaces, or if multiple package files are found\n  const isWorkspace = options.workspaces || !!options.workspace?.length\n  options.deep = options.deep || isWorkspace || selectedPackageInfos.length > 1\n\n  let analysis: Index<PackageFile> | PackageFile | void\n  if (options.global) {\n    const analysis = await runGlobal(options)\n    clearTimeout(timeout)\n    return analysis\n  } else if (options.deep) {\n    analysis = await selectedPackageInfos.reduce(\n      async (previousPromise, packageInfo: PackageInfo) => {\n        const packages = await previousPromise\n        // copy object to prevent share .ncurc options between different packageFile, to prevent unpredictable behavior\n        const rcResult = await getNcuRc({ packageFile: packageInfo.filepath, options })\n        let rcConfig = rcResult.config\n        if (options.mergeConfig && Object.keys(rcConfig).length) {\n          // Merge config options.\n          rcConfig = mergeOptions(options, rcConfig)\n        }\n        const pkgOptions: Options = {\n          ...options,\n          ...rcConfig,\n          packageFile: packageInfo.filepath,\n          workspacePackages,\n        }\n        // For virtual catalog files (like package.json#catalog), use the PackageInfo data directly\n        // since the virtual file doesn't exist on disk\n        let pkgData: string | null\n        let pkgFile: string\n        let indexKey: string\n\n        if (packageInfo.filepath.includes('#') || packageInfo.name === 'catalogs') {\n          // Virtual catalog file or catalog package - use PackageInfo data\n          pkgData = packageInfo.pkgFile\n          pkgFile = packageInfo.filepath\n          // For synthetic catalog files, use the actual underlying file path as the index key\n          indexKey = packageInfo.filepath.includes('#catalog')\n            ? packageInfo.filepath.replace('#catalog', '')\n            : packageInfo.filepath\n\n          // Print the same message as findPackage for consistency\n          const relPathToPackage = path.resolve(indexKey)\n          print(pkgOptions, `${pkgOptions.upgrade ? 'Upgrading' : 'Checking'} ${relPathToPackage} catalog dependencies`)\n        } else {\n          // Regular file - read from disk\n          const result = await findPackage(pkgOptions)\n          pkgData = result.pkgData\n          pkgFile = result.pkgFile || packageInfo.filepath\n          indexKey = pkgFile\n        }\n        return {\n          ...packages,\n          // index by relative path if cwd was specified\n          [pkgOptions.cwd\n            ? path\n                .relative(path.resolve(pkgOptions.cwd), indexKey)\n                // convert Windows path to *nix path for consistency\n                .replace(/\\\\/g, '/')\n            : indexKey]: await runLocal(pkgOptions, pkgData, pkgFile),\n        }\n      },\n      Promise.resolve({} as Index<PackageFile> | PackageFile),\n    )\n    if (options.json) {\n      printJson(options, analysis)\n    }\n  } else {\n    // mutate packageFile when glob pattern finds only single package\n    if (\n      selectedPackageInfos.length === 1 &&\n      selectedPackageInfos[0].filepath !== (options.packageFile || 'package.json')\n    ) {\n      options.packageFile = selectedPackageInfos[0].filepath\n    }\n    const { pkgData, pkgFile } = await findPackage(options)\n    analysis = await runLocal(options, pkgData, pkgFile)\n  }\n  clearTimeout(timeout)\n\n  if (options.errorLevel === 2 && someUpgraded(packageFilepaths, analysis, options)) {\n    programError(options, '\\nDependencies not up-to-date')\n  }\n\n  // suggest install command or auto-install\n  if (options.upgrade) {\n    // deno does not have an install command\n    // The closest equivalent is deno cache, but it is optional.\n    // See: https://deno.land/manual@v1.30.3/references/cheatsheet#nodejs---deno-cheatsheet\n    if (options.packageManager === 'deno') {\n      print(options, '')\n    } else {\n      await install(packageFilepaths, analysis, options)\n    }\n  }\n\n  return analysis\n}\n\n/** Main entry point.\n *\n * @returns Promise<\n * PackageFile                    Default returns upgraded package file.\n * | Index<VersionSpec>    --jsonUpgraded returns only upgraded dependencies.\n * | void                         --global upgrade returns void.\n * >\n */\nexport async function run(\n  runOptions: RunOptions = {},\n  { cli }: { cli?: boolean } = {},\n): Promise<PackageFile | Index<VersionSpec> | void> {\n  const options = await initOptions(runOptions, { cli })\n\n  // ensure that the process exits with an error code if there was an unhandled rejection\n  const bugsUrl = pkg.bugs.url\n  process.on('exit', () => {\n    if (unhandledRejectionError) {\n      programError(options, `Unhandled Rejection! This is a bug and should be reported: ${bugsUrl}`)\n    }\n  })\n\n  // chalk may already have been initialized in cli.ts, but when imported as a module\n  // chalkInit is idempotent\n  await chalkInit(options.color)\n\n  noVolta(options)\n\n  print(options, 'Initializing', 'verbose')\n\n  if (options.cacheClear) {\n    await cacheClear(options)\n  }\n\n  let timeout: NodeJS.Timeout | undefined\n  let timeoutPromise: Promise<void> = new Promise(() => null)\n  if (options.timeout) {\n    const timeoutMs = typeof options.timeout === 'string' ? Number.parseInt(options.timeout, 10) : options.timeout\n    timeoutPromise = new Promise((resolve, reject) => {\n      timeout = setTimeout(() => {\n        // must catch the error and reject explicitly since we are in a setTimeout\n        const error = `Exceeded global timeout of ${timeoutMs}ms`\n        reject(error)\n        try {\n          programError(options, error)\n        } catch (e) {\n          /* noop */\n        }\n      }, timeoutMs)\n    })\n  }\n\n  // doctor mode\n  if (options.doctor) {\n    // execute with -u\n    if (options.upgrade) {\n      // we have to pass run directly since it would be a circular require if doctor included this file\n      return Promise.race([timeoutPromise, doctor(run, options)])\n    }\n    // print help otherwise\n    else {\n      const help =\n        typeof cliOptionsMap.doctor.help === 'function' ? cliOptionsMap.doctor.help({}) : cliOptionsMap.doctor.help\n      print(options, `Usage: ncu --doctor\\n\\n${help}`, 'warn')\n    }\n  }\n  // normal mode\n  else {\n    return Promise.race([timeoutPromise, runUpgrades(options, timeout)])\n  }\n}\n\nexport default run\n\nexport type { RunOptions }\n"
  },
  {
    "path": "src/lib/cache.ts",
    "content": "import fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { CacheData, Cacher } from '../types/Cacher'\nimport { Index } from '../types/IndexType'\nimport { Options } from '../types/Options'\nimport { Version } from '../types/Version'\nimport { print } from './logging'\n\nexport const CACHE_DELIMITER = '___'\n\n/**\n * Check if cache is expired if timestamp is set\n *\n * @param cacheData\n * @param cacheExpiration\n * @returns\n */\nfunction checkCacheExpiration(cacheData: CacheData, cacheExpiration = 10) {\n  if (typeof cacheData.timestamp !== 'number') {\n    return false\n  }\n\n  const unixMinuteMS = 60 * 1000\n  const expirationLimit = cacheData.timestamp + cacheExpiration * unixMinuteMS\n  return expirationLimit < Date.now()\n}\n\nexport const defaultCacheFilename = '.ncu-cache.json'\nexport const defaultCacheFile = `~/${defaultCacheFilename}`\nexport const resolvedDefaultCacheFile = path.join(os.homedir(), defaultCacheFilename)\n\n/** Resolve the cache file path based on os/homedir. */\nexport function resolveCacheFile(optionsCacheFile: string) {\n  return optionsCacheFile === defaultCacheFile ? resolvedDefaultCacheFile : optionsCacheFile\n}\n\n/** Clear the default cache, or the cache file specified by --cacheFile. */\nexport async function cacheClear(options: Options) {\n  if (!options.cacheFile) {\n    return\n  }\n\n  await fs.promises.rm(resolveCacheFile(options.cacheFile), { force: true })\n}\n\n/**\n * The cacher stores key (name + target) - value (new version) pairs\n * for quick updates across `ncu` calls.\n *\n * @returns\n */\nexport default async function cacher(options: Omit<Options, 'cacher'>): Promise<Cacher | undefined> {\n  if (!options.cache || !options.cacheFile) {\n    return\n  }\n\n  const cacheFile = resolveCacheFile(options.cacheFile)\n  let cacheData: CacheData = {}\n  const cacheHits = new Set<string>()\n\n  try {\n    cacheData = JSON.parse(await fs.promises.readFile(cacheFile, 'utf-8'))\n\n    const expired = checkCacheExpiration(cacheData, options.cacheExpiration)\n    if (expired) {\n      // reset cache\n      fs.promises.rm(cacheFile, { force: true })\n      cacheData = {}\n    }\n  } catch (error) {\n    // ignore file read/parse/remove errors\n  }\n\n  if (typeof cacheData.timestamp !== 'number') {\n    cacheData.timestamp = Date.now()\n  }\n  if (!cacheData.packages) {\n    cacheData.packages = {}\n  }\n  if (!cacheData.peers) {\n    cacheData.peers = {}\n  }\n\n  return {\n    get: (name: string, target: string) => {\n      if (!cacheData.packages) return\n      const key = `${name}${CACHE_DELIMITER}${target}`\n      const cached = cacheData.packages[key]\n      if (cached && !key.includes(cached)) {\n        cacheHits.add(name)\n      }\n      return cached\n    },\n    set: (name: string, target: string, version: string) => {\n      if (!cacheData.packages) return\n      const key = `${name}${CACHE_DELIMITER}${target}`\n      cacheData.packages[key] = version\n    },\n    getPeers: (name: string, version: Version) => {\n      if (!cacheData.peers) return\n      const key = `${name}${CACHE_DELIMITER}${version}`\n      const cached = cacheData.peers[key]\n      if (cached) {\n        cacheHits.add(name)\n      }\n      return cached\n    },\n    setPeers: (name: string, version: Version, peers: Index<string>) => {\n      const key = `${name}${CACHE_DELIMITER}${version}`\n      if (!cacheData.peers) return\n      cacheData.peers[key] = peers\n    },\n    save: async () => {\n      await fs.promises.writeFile(cacheFile, JSON.stringify(cacheData))\n    },\n    log: (peers?: boolean) => {\n      const cacheCount = cacheHits.size\n      if (cacheCount === 0) return\n\n      print(\n        options,\n        `\\nUsing ${cacheCount} cached package ${peers ? 'peer' : 'version'}${cacheCount > 1 ? 's' : ''}`,\n        'warn',\n      )\n      print(options, cacheHits, 'verbose')\n      cacheHits.clear()\n    },\n  } satisfies Cacher\n}\n"
  },
  {
    "path": "src/lib/chalk.ts",
    "content": "/*\n\nThis chalk wrapper allows synchronous chalk.COLOR(...) syntax with special support for:\n\n1) dynamic import as pure ESM module\n2) force color on all instances\n3) disable color on all instances\n\nCall await chalkInit(color) at the beginning of execution and the chalk instance will be available everywhere.\n\nIt is a hacky solution, but it is the easiest way to import and pass the color option to all chalk instances without brutalizing the syntax.\n\n*/\nimport keyValueBy from './keyValueBy'\n\ntype ChalkMethod = ((s: any) => string) & { bold: (s: any) => string }\n\nconst chalkMethods = {\n  blue: true,\n  bold: true,\n  cyan: true,\n  gray: true,\n  green: true,\n  magenta: true,\n  red: true,\n  yellow: true,\n}\n\n// A chalk instance that passes strings through as-is, without color. Used with color: null. */\nconst chalkNoop = keyValueBy(chalkMethods, name => ({ [name]: (s: any) => s.toString() })) as Record<\n  keyof typeof chalkMethods,\n  ChalkMethod\n>\n\n// a global Promise of a chalk instance that can optionally force or ignore color\nlet chalkInstance: Record<keyof typeof chalkMethods, ChalkMethod>\n\n/** Initializes the global chalk instance with an optional flag for forced color. Idempotent. */\nexport const chalkInit = async (color?: boolean | null) => {\n  const chalkModule = await import('chalk')\n  const { default: chalkDefault, Chalk } = chalkModule\n  chalkInstance = color === true ? new Chalk({ level: 1 }) : color === null ? chalkNoop : chalkDefault\n}\n\n/** Asserts that chalk has been imported. */\nconst assertChalk = () => {\n  if (!chalkInstance) {\n    throw new Error(\n      `Chalk has not been imported yet. Chalk is a dynamic import and requires that you await { chalkInit } from './lib/chalk'.`,\n    )\n  }\n}\n\n// generate an async method for each chalk method that calls a chalk instance with global.color for forced color\nconst chalkGlobal = keyValueBy(chalkMethods, name => {\n  /** Chained bold method. */\n  const bold = (s: any) => {\n    assertChalk()\n    return chalkInstance[name as keyof typeof chalkInstance].bold(s)\n  }\n  /** Chalk method. */\n  const method = (s: any) => {\n    assertChalk()\n    return chalkInstance[name as keyof typeof chalkInstance](s)\n  }\n  method.bold = bold\n  return {\n    [name]: method,\n  }\n}) as Record<keyof typeof chalkMethods, ChalkMethod>\n\nexport default chalkGlobal\n"
  },
  {
    "path": "src/lib/defineConfig.ts",
    "content": "import type { RcOptions } from '../types/RcOptions'\n\n/**\n * TypeScript helper for .npmrc config file. Similar to vite and eslint's\n * defineConfig helper\n */\nfunction defineConfig(config: RcOptions) {\n  return config\n}\n\nexport default defineConfig\n"
  },
  {
    "path": "src/lib/determinePackageManager.ts",
    "content": "import fs from 'fs/promises'\nimport { Index } from '../types/IndexType'\nimport { Options } from '../types/Options'\nimport { PackageManagerName } from '../types/PackageManagerName'\nimport findLockfile from './findLockfile'\n\n// map lockfiles to package managers\nconst packageManagerLockfileMap: Index<PackageManagerName> = {\n  'package-lock': 'npm',\n  yarn: 'yarn',\n  'pnpm-lock': 'pnpm',\n  deno: 'deno',\n  bun: 'bun',\n}\n\n/**\n * Get the package manager being used to run the command.\n * When checking global packages, we need to do it this way since there is no\n * lockfile in the global directory.\n */\nconst getRunningPackageManager = (): PackageManagerName => {\n  const userAgent = process.env.npm_config_user_agent ?? ''\n  const execpath = process.env.npm_execpath ?? ''\n\n  if (\n    userAgent.startsWith('yarn/') ||\n    execpath.includes('yarn') ||\n    __dirname.includes('/yarn/') ||\n    __dirname.includes('\\\\Yarn\\\\')\n  )\n    return 'yarn'\n  if (\n    userAgent.startsWith('pnpm/') ||\n    execpath.includes('pnpm') ||\n    __dirname.includes('/pnpm/') ||\n    __dirname.includes('\\\\pnpm\\\\')\n  )\n    return 'pnpm'\n  if (\n    userAgent.startsWith('bun/') ||\n    typeof Bun !== 'undefined' ||\n    process.versions.bun ||\n    __dirname.includes('/.bun/') ||\n    __dirname.includes('\\\\.bun\\\\')\n  )\n    return 'bun'\n\n  return 'npm'\n}\n\n/**\n * If the packageManager option was not provided, look at the lockfiles to\n * determine which package manager is being used.\n */\nconst determinePackageManager = async (\n  options: Options,\n  // only for testing\n  readdir: (_path: string) => Promise<string[]> = fs.readdir,\n): Promise<PackageManagerName> => {\n  if (options.packageManager) return options.packageManager\n  else if (options.global) return getRunningPackageManager()\n\n  const lockfileName = (await findLockfile(options, readdir))?.filename\n  return lockfileName ? packageManagerLockfileMap[lockfileName.split('.')[0]] : 'npm'\n}\n\nexport default determinePackageManager\n"
  },
  {
    "path": "src/lib/doctor.ts",
    "content": "import fs from 'fs/promises'\nimport spawn from 'spawn-please'\nimport { printUpgrades } from '../lib/logging'\nimport spawnBun from '../package-managers/bun'\nimport spawnNpm from '../package-managers/npm'\nimport spawnPnpm from '../package-managers/pnpm'\nimport spawnYarn from '../package-managers/yarn'\nimport { Index } from '../types/IndexType'\nimport { Options } from '../types/Options'\nimport { PackageFile } from '../types/PackageFile'\nimport { PackageInfo } from '../types/PackageInfo'\nimport { SpawnOptions } from '../types/SpawnOptions'\nimport { SpawnPleaseOptions } from '../types/SpawnPleaseOptions'\nimport { VersionSpec } from '../types/VersionSpec'\nimport chalk, { chalkInit } from './chalk'\nimport loadPackageInfoFromFile from './loadPackageInfoFromFile'\nimport upgradePackageData from './upgradePackageData'\n\ntype Run = (options?: Options) => Promise<PackageFile | Index<VersionSpec> | void>\n\n/** Run npm, yarn, pnpm, or bun. */\nconst npm = (\n  args: string[],\n  options: Options,\n  print?: boolean,\n  { spawnOptions, spawnPleaseOptions }: { spawnOptions?: SpawnOptions; spawnPleaseOptions?: SpawnPleaseOptions } = {},\n): Promise<string> => {\n  if (print) {\n    console.log(chalk.blue([options.packageManager, ...args].join(' ')))\n  }\n\n  const spawnOptionsMerged = {\n    cwd: options.cwd || process.cwd(),\n    env: {\n      ...process.env,\n      // TODO: Why does CI break pnpm install?\n      ...(options.packageManager !== 'pnpm' ? { CI: '1' } : null),\n      FORCE_COLOR: '1',\n      ...spawnOptions?.env,\n    },\n    ...spawnOptions,\n  }\n\n  const npmOptions = {\n    ...(options.global ? { global: true } : null),\n    ...(options.prefix ? { prefix: options.prefix } : null),\n  }\n\n  return (\n    options.packageManager === 'pnpm'\n      ? spawnPnpm\n      : options.packageManager === 'yarn'\n        ? spawnYarn\n        : options.packageManager === 'bun'\n          ? spawnBun\n          : spawnNpm\n  )(args, npmOptions, spawnPleaseOptions, spawnOptionsMerged)\n}\n\n/** Load and validate package file and tests. */\nconst loadPackageFileForDoctor = async (options: Options): Promise<PackageInfo> => {\n  // assert no --packageData or --packageFile\n  if (options.packageData || options.packageFile) {\n    console.error(\n      '--packageData and --packageFile are not allowed with --doctor. You must execute \"ncu --doctor\" in a directory with a package file so it can install dependencies and test them.',\n    )\n    process.exit(1)\n  }\n\n  let packageInfo: PackageInfo\n  // assert package.json\n  try {\n    packageInfo = await loadPackageInfoFromFile(options, 'package.json')\n  } catch (e) {\n    console.error('Missing or invalid package.json')\n    process.exit(1)\n  }\n\n  // assert npm script \"test\" (unless a custom test script is specified)\n  if (!options.doctorTest && !packageInfo.pkg.scripts?.test) {\n    console.error(\n      'No npm \"test\" script defined. You must define a \"test\" script in the \"scripts\" section of your package.json to use --doctor.',\n    )\n    process.exit(1)\n  }\n\n  return packageInfo\n}\n\n/** Iteratively installs upgrades and runs tests to identify breaking upgrades. */\n// we have to pass run directly since it would be a circular require if doctor included this file\nconst doctor = async (run: Run, options: Options): Promise<void> => {\n  await chalkInit()\n\n  // bun lockFileName defaults to bun.lock but will be overwritten to bun.lockb if detected at the readFile step below\n  let lockFileName: 'package-lock.json' | 'yarn.lock' | 'pnpm-lock.yaml' | 'bun.lock' | 'bun.lockb' =\n    options.packageManager === 'yarn'\n      ? 'yarn.lock'\n      : options.packageManager === 'pnpm'\n        ? 'pnpm-lock.yaml'\n        : options.packageManager === 'bun'\n          ? 'bun.lock'\n          : 'package-lock.json'\n  const { pkg, pkgFile }: PackageInfo = await loadPackageFileForDoctor(options)\n\n  // flatten all deps into one so we can iterate over them\n  const allDependencies: Index<VersionSpec> = {\n    ...pkg.dependencies,\n    ...pkg.devDependencies,\n    ...pkg.optionalDependencies,\n  }\n\n  /** Install dependencies using \"npm run install\" or a custom script given by --doctorInstall. */\n  const runInstall = async (): Promise<void> => {\n    if (options.doctorInstall) {\n      const [installCommand, ...testArgs] = options.doctorInstall.split(' ')\n      console.log(chalk.blue(options.doctorInstall))\n      await spawn(installCommand, testArgs)\n    } else {\n      await npm(['install'], { packageManager: options.packageManager }, true)\n    }\n  }\n\n  /** Run the tests using \"npm run test\" or a custom script given by --doctorTest. */\n  const runTests = async (): Promise<void> => {\n    const spawnPleaseOptions = {\n      stderr: (data: string): void => {\n        console.error(chalk.red(data.toString()))\n      },\n      // Test runners typically write to stdout, so we need to print stdout.\n      // Otherwise test failures will be silenced.\n      stdout: (data: string): void => {\n        process.stdout.write(data.toString())\n      },\n    }\n\n    if (options.doctorTest) {\n      const regexp = /\"(.+?)\"|'(.+?)'|[^ ]+/g\n      const matches = options.doctorTest.matchAll(regexp)\n      let groups: string[] = []\n      for (const match of matches) {\n        groups = [...groups, match[2] || match[1] || match[0]]\n      }\n      const [testCommand, ...testArgs] = groups\n      console.log(chalk.blue(options.doctorTest))\n      await spawn(testCommand, testArgs, spawnPleaseOptions)\n    } else {\n      await npm(\n        ['run', 'test'],\n        {\n          packageManager: options.packageManager,\n        },\n        true,\n        { spawnPleaseOptions },\n      )\n    }\n  }\n\n  console.log(`Running tests before upgrading`)\n\n  // initial install\n  await runInstall()\n\n  // save lock file if there is one\n  let lockFile = ''\n  try {\n    lockFile = await fs.readFile(lockFileName, 'utf-8')\n  } catch (e) {\n    // try bun.lockb if bun.lock was not found\n    // set lockFileName so the rest of doctor mode uses bun.lockb for lock file updating and restoration\n    if (options.packageManager === 'bun') {\n      lockFileName = 'bun.lockb'\n      try {\n        lockFile = await fs.readFile(lockFileName, 'utf-8')\n      } catch (e) {}\n    }\n  }\n\n  // make sure current tests pass before we begin\n  try {\n    await runTests()\n  } catch (e) {\n    console.error('Tests failed before we even got started!')\n    process.exit(1)\n  }\n\n  if (!options.interactive) {\n    console.log(`Upgrading all dependencies and re-running tests`)\n  }\n\n  // upgrade all dependencies\n  // save upgrades for later in case we need to iterate\n  console.log(\n    chalk.blue(\n      'ncu ' +\n        process.argv\n          .slice(2)\n          .filter(arg => arg !== '--doctor')\n          .join(' '),\n    ),\n  )\n  process.env.NCU_DOCTOR = '1'\n  const upgrades: Index<VersionSpec> = (await run({\n    ...options,\n    silent: true,\n    // --doctor triggers the initial call to doctor, but the internal call needs to executes npm-check-updates normally in order to upgrade the dependencies\n    doctor: false,\n  })) as Index<VersionSpec>\n\n  if (Object.keys(upgrades || {}).length === 0) {\n    console.log('All dependencies are up-to-date ' + chalk.green.bold(':)'))\n    return\n  }\n\n  // track if installing dependencies was successful\n  // this allows us to skip re-installing when it fails and proceed straight to installing individual dependencies\n  let installAllSuccess = false\n\n  // run tests on all upgrades\n  try {\n    // install after all upgrades\n    await runInstall()\n    installAllSuccess = true\n\n    // run tests after all upgrades\n    await runTests()\n\n    console.log(`${chalk.green('✓')} Tests pass`)\n\n    await printUpgrades(options, {\n      current: allDependencies,\n      upgraded: upgrades,\n      total: Object.keys(upgrades || {}).length,\n    })\n\n    console.log(`\\n${options.interactive ? 'Chosen' : 'All'} dependencies upgraded and installed ${chalk.green(':)')}`)\n  } catch {\n    console.error(chalk.red(installAllSuccess ? 'Tests failed' : 'Install failed'))\n    console.log(`Identifying broken dependencies`)\n\n    // restore package file, lockFile and re-install\n    await fs.writeFile('package.json', pkgFile)\n\n    if (lockFile) {\n      await fs.writeFile(lockFileName, lockFile)\n    } else {\n      await fs.rm(lockFileName, { recursive: true, force: true })\n    }\n\n    // save the last package file with passing tests\n    let lastPkgFile = pkgFile\n\n    // re-install after restoring package file and lock file\n    // only re-install if the tests failed, not if npm install failed\n    if (installAllSuccess) {\n      try {\n        await runInstall()\n      } catch (e) {\n        const installCommand = (options.packageManager || 'npm') + ' install'\n        throw new Error(\n          `Error: Doctor mode was about to test individual upgrades, but ${chalk.cyan(\n            installCommand,\n          )} failed after rolling back to your existing package and lock files. This is unexpected since the initial install before any upgrades succeeded. Either npm failed to revert a partial install, or failed anomalously on the second run. Please check your internet connection and retry. If doctor mode fails consistently, report a bug with your complete list of dependency versions at https://github.com/raineorshine/npm-check-updates/issues.`,\n        )\n      }\n    }\n\n    // iterate upgrades\n    let name: string, version: VersionSpec\n    for ([name, version] of Object.entries(upgrades)) {\n      try {\n        // install single dependency\n        await npm(\n          [\n            ...(options.packageManager === 'yarn' ||\n            options.packageManager === 'pnpm' ||\n            options.packageManager === 'bun'\n              ? ['add']\n              : ['install', '--no-save']),\n            `${name}@${version}`,\n          ],\n          { packageManager: options.packageManager },\n          true,\n        )\n\n        // if there is a prepare script, we need to run it manually since --no-save does not run prepare automatically\n        // https://github.com/raineorshine/npm-check-updates/issues/1170\n        if (pkg.scripts?.prepare) {\n          try {\n            await npm(['run', 'prepare'], { packageManager: options.packageManager }, true)\n          } catch (e) {\n            console.error(chalk.red('Prepare script failed'))\n            throw e\n          }\n        }\n\n        // run tests after individual upgrade\n        await runTests()\n        console.log(`  ${chalk.green('✓')} ${name} ${allDependencies[name]} → ${version}`)\n\n        // save upgraded package data so that passing versions can still be saved even when there is a failure\n        lastPkgFile = await upgradePackageData(\n          lastPkgFile,\n          { [name]: allDependencies[name] },\n          { [name]: version },\n          options,\n        )\n\n        // save working lock file\n        lockFile = await fs.readFile(lockFileName, 'utf-8')\n      } catch (e) {\n        // print failing package\n        console.error(`  ${chalk.red('✗')} ${name} ${allDependencies[name]} → ${version}\\n`)\n        console.error(chalk.red(e))\n\n        // restore last good lock file\n        await fs.writeFile(lockFileName, lockFile)\n\n        // restore package.json since yarn and pnpm do not have the --no-save option\n        if (\n          options.packageManager === 'yarn' ||\n          options.packageManager === 'pnpm' ||\n          options.packageManager === 'bun'\n        ) {\n          await fs.writeFile('package.json', lastPkgFile)\n        }\n      }\n    }\n\n    // silently restore last passing package file and lock file\n    // only print message if package file is updated\n    if (lastPkgFile !== pkgFile) {\n      console.log('Saving partially upgraded package.json')\n      await fs.writeFile('package.json', lastPkgFile)\n    }\n\n    // re-install from restored package.json and lockfile\n    await runInstall()\n  }\n}\n\nexport default doctor\n"
  },
  {
    "path": "src/lib/exists.ts",
    "content": "import fs from 'fs/promises'\n\n/** Returns true if a file exists. */\nconst exists = (path: string) =>\n  fs.stat(path).then(\n    () => true,\n    () => false,\n  )\n\nexport default exists\n"
  },
  {
    "path": "src/lib/figgy-pudding/LICENSE.md",
    "content": "ISC License\n\nCopyright (c) npm, Inc.\n\nPermission to use, copy, modify, and/or distribute this software for\nany purpose with or without fee is hereby granted, provided that the\nabove copyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE COPYRIGHT HOLDER DISCLAIMS\nALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE\nCOPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR\nCONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\nOF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE\nOR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE\nUSE OR PERFORMANCE OF THIS SOFTWARE.\n"
  },
  {
    "path": "src/lib/figgy-pudding/README.md",
    "content": "# Note: pending imminent deprecation\n\n**This module will be deprecated once npm v7 is released. Please do not rely\non it more than absolutely necessary (ie, only if you are depending on\nit for use with npm v6 internal dependencies).**\n\n---\n\n## figgy-pudding [![npm version](https://img.shields.io/npm/v/figgy-pudding.svg)](https://npm.im/figgy-pudding) [![license](https://img.shields.io/npm/l/figgy-pudding.svg)](https://npm.im/figgy-pudding) [![Travis](https://img.shields.io/travis/npm/figgy-pudding.svg)](https://travis-ci.org/npm/figgy-pudding) [![Coverage Status](https://coveralls.io/repos/github/npm/figgy-pudding/badge.svg?branch=latest)](https://coveralls.io/github/npm/figgy-pudding?branch=latest)\n\n[`figgy-pudding`](https://github.com/npm/figgy-pudding) is a small JavaScript\nlibrary for managing and composing cascading options objects -- hiding what\nneeds to be hidden from each layer, without having to do a lot of manual munging\nand passing of options.\n\n### The God Object is Dead\n\n### Now Bring Us Some Figgy Pudding\n\n## Install\n\n`$ npm install figgy-pudding`\n\n### Example\n\n```javascript\n// print-package.js\nconst fetch = require('./fetch.js')\nconst puddin = require('figgy-pudding')\n\nconst PrintOpts = puddin({\n  json: { default: false },\n})\n\nasync function printPkg(name, opts) {\n  // Expected pattern is to call this in every interface function. If `opts` is\n  // not passed in, it will automatically create an (empty) object for it.\n  opts = PrintOpts(opts)\n  const uri = `https://registry.npmjs.com/${name}`\n  const res = await fetch(\n    uri,\n    opts.concat({\n      // Add or override any passed-in configs and pass them down.\n      log: customLogger,\n    }),\n  )\n  // The following would throw an error, because it's not in PrintOpts:\n  // console.log(opts.log)\n  if (opts.json) {\n    return res.json()\n  } else {\n    return res.text()\n  }\n}\n\nconsole.log(\n  await printPkg('figgy', {\n    // Pass in *all* configs at the toplevel, as a regular object.\n    json: true,\n    cache: './tmp-cache',\n  }),\n)\n```\n\n```javascript\n// fetch.js\nconst puddin = require('figgy-pudding')\n\nconst FetchOpts = puddin({\n  log: { default: require('npmlog') },\n  cache: {}\n})\n\nmodule.exports = async function (..., opts) {\n  opts = FetchOpts(opts)\n}\n```\n\n### Features\n\n- hide options from layer that didn't ask for it\n- shared multi-layer options\n- make sure `opts` argument is available\n- transparent key access like normal keys, through a Proxy. No need for`.get()`!\n- default values\n- key aliases\n- arbitrary key filter functions\n- key/value iteration\n- serialization\n- 100% test coverage using `tap --100`\n\n### API\n\n#### `figgyPudding({ key: { default: val } | String }, [opts]) -> PuddingFactory`\n\nDefines an Options constructor that can be used to collect only the needed\noptions.\n\nAn optional `default` property for specs can be used to specify default values\nif nothing was passed in.\n\nIf the value for a spec is a string, it will be treated as an alias to that\nother key.\n\n##### Example\n\n```javascript\nconst MyAppOpts = figgyPudding({\n  lg: 'log',\n  log: {\n    default: () => require('npmlog'),\n  },\n  cache: {},\n})\n```\n\n#### `> PuddingFactory(...providers) -> FiggyPudding{}`\n\nInstantiates an options object defined by `figgyPudding()`, which uses\n`providers`, in order, to find requested properties.\n\nEach provider can be either a plain object, a `Map`-like object (that is, one\nwith a `.get()` method) or another figgyPudding `Opts` object.\n\nWhen nesting `Opts` objects, their properties will not become available to the\nnew object, but any further nested `Opts` that reference that property _will_ be\nable to read from their grandparent, as long as they define that key. Default\nvalues for nested `Opts` parents will be used, if found.\n\n##### Example\n\n```javascript\nconst ReqOpts = figgyPudding({\n  follow: {},\n})\n\nconst opts = ReqOpts({\n  follow: true,\n  log: require('npmlog'),\n})\n\nopts.follow // => true\nopts.log // => Error: ReqOpts does not define `log`\n\nconst MoreOpts = figgyPudding({\n  log: {},\n})\nMoreOpts(opts).log // => npmlog object (passed in from original plain obj)\nMoreOpts(opts).follow // => Error: MoreOpts does not define `follow`\n```\n\n#### `> opts.get(key) -> Value`\n\nGets a value from the options object.\n\n##### Example\n\n```js\nconst opts = MyOpts(config)\nopts.get('foo') // value of `foo`\nopts.foo // Proxy-based access through `.get()`\n```\n\n#### `> opts.concat(...moreProviders) -> FiggyPudding{}`\n\nCreates a new opts object of the same type as `opts` with additional providers.\nProviders further to the right shadow providers to the left, with properties in\nthe original `opts` being shadows by the new providers.\n\n##### Example\n\n```js\nconst opts = MyOpts({ x: 1 })\nopts.get('x') // 1\nopts.concat({ x: 2 }).get('x') // 2\nopts.get('x') // 1 (original opts object left intact)\n```\n\n#### `> opts.toJSON() -> Value`\n\nConverts `opts` to a plain, JSON-stringifiable JavaScript value. Used internally\nby JavaScript to get `JSON.stringify()` working.\n\nOnly keys that are readable by the current pudding type will be serialized.\n\n##### Example\n\n```js\nconst opts = MyOpts({ x: 1 })\nopts.toJSON() // {x: 1}\nJSON.stringify(opts) // '{\"x\":1}'\n```\n\n#### `> opts.forEach((value, key, opts) => {}, thisArg) -> undefined`\n\nIterates over the values of `opts`, limited to the keys readable by the current\npudding type. `thisArg` will be used to set the `this` argument when calling the\n`fn`.\n\n##### Example\n\n```js\nconst opts = MyOpts({ x: 1, y: 2 })\nopts.forEach((value, key) => console.log(key, '=', value))\n```\n\n#### `> opts.entries() -> Iterator<[[key, value], ...]>`\n\nReturns an iterator that iterates over the keys and values in `opts`, limited to\nthe keys readable by the current pudding type. Each iteration returns an array\nof `[key, value]`.\n\n##### Example\n\n```js\nconst opts = MyOpts({x: 1, y: 2})\n[...opts({x: 1, y: 2}).entries()] // [['x', 1], ['y', 2]]\n```\n\n#### `> opts[Symbol.iterator]() -> Iterator<[[key, value], ...]>`\n\nReturns an iterator that iterates over the keys and values in `opts`, limited to\nthe keys readable by the current pudding type. Each iteration returns an array\nof `[key, value]`. Makes puddings work natively with JS iteration mechanisms.\n\n##### Example\n\n```js\nconst opts = MyOpts({x: 1, y: 2})\n[...opts({x: 1, y: 2})] // [['x', 1], ['y', 2]]\nfor (let [key, value] of opts({x: 1, y: 2})) {\n  console.log(key, '=', value)\n}\n```\n\n#### `> opts.keys() -> Iterator<[key, ...]>`\n\nReturns an iterator that iterates over the keys in `opts`, limited to the keys\nreadable by the current pudding type.\n\n##### Example\n\n```js\nconst opts = MyOpts({x: 1, y: 2})\n[...opts({x: 1, y: 2}).keys()] // ['x', 'y']\n```\n\n#### `> opts.values() -> Iterator<[value, ...]>`\n\nReturns an iterator that iterates over the values in `opts`, limited to the keys\nreadable by the current pudding type.\n\n##### Example\n\n```js\nconst opts = MyOpts({x: 1, y: 2})\n[...opts({x: 1, y: 2}).values()] // [1, 2]\n```\n"
  },
  {
    "path": "src/lib/figgy-pudding/index.js",
    "content": "/* eslint-disable */\n\n/*\n\nThis is stripped down version of the deprecated figgy-pudding. It is used by libnpmconfig, which is also deprecated and has been brought into the codebase to avoid deprecation warnings.\n\nhttps://github.com/npm/figgy-pudding\n\n*/\n\nclass FiggyPudding {\n  constructor(specs, opts, providers) {\n    this.__specs = specs || {}\n    this.__opts = opts || {}\n    this.__providers = reverse(providers.filter(x => x != null && typeof x === 'object'))\n    this.__isFiggyPudding = true\n  }\n  get(key) {\n    return pudGet(this, key, true)\n  }\n  toJSON() {\n    const obj = {}\n    this.forEach((val, key) => {\n      obj[key] = val\n    })\n    return obj\n  }\n  forEach(fn, thisArg = this) {\n    for (let [key, value] of this.entries()) {\n      fn.call(thisArg, value, key, this)\n    }\n  }\n  *entries(_matcher) {\n    for (let key of Object.keys(this.__specs)) {\n      yield [key, this.get(key)]\n    }\n    const matcher = _matcher || this.__opts.other\n    if (matcher) {\n      const seen = new Set()\n      for (let p of this.__providers) {\n        const iter = p.entries ? p.entries(matcher) : entries(p)\n        for (let [key, val] of iter) {\n          if (matcher(key) && !seen.has(key)) {\n            seen.add(key)\n            yield [key, val]\n          }\n        }\n      }\n    }\n  }\n  concat(...moreConfig) {\n    return new Proxy(\n      new FiggyPudding(this.__specs, this.__opts, reverse(this.__providers).concat(moreConfig)),\n      proxyHandler,\n    )\n  }\n}\n\nfunction pudGet(pud, key, validate) {\n  let spec = pud.__specs[key]\n  if (!spec) {\n    spec = {}\n  }\n  let ret\n  for (let p of pud.__providers) {\n    ret = tryGet(key, p)\n    if (ret !== undefined) {\n      break\n    }\n  }\n  if (ret === undefined && spec.default !== undefined) {\n    if (typeof spec.default === 'function') {\n      return spec.default(pud)\n    } else {\n      return spec.default\n    }\n  } else {\n    return ret\n  }\n}\n\nfunction tryGet(key, p) {\n  let ret\n  if (p.__isFiggyPudding) {\n    ret = pudGet(p, key, false)\n  } else {\n    ret = p[key]\n  }\n  return ret\n}\n\nconst proxyHandler = {\n  get(obj, prop) {\n    if (typeof prop === 'symbol' || prop.slice(0, 2) === '__' || prop in FiggyPudding.prototype) {\n      return obj[prop]\n    }\n    return obj.get(prop)\n  },\n}\n\nexport default function figgyPudding(specs, opts) {\n  function factory(...providers) {\n    return new Proxy(new FiggyPudding(specs, opts, providers), proxyHandler)\n  }\n  return factory\n}\n\nfunction reverse(arr) {\n  const ret = []\n  arr.forEach(x => ret.unshift(x))\n  return ret\n}\n\nfunction entries(obj) {\n  return Object.keys(obj).map(k => [k, obj[k]])\n}\n"
  },
  {
    "path": "src/lib/filterAndReject.ts",
    "content": "import { and, or } from 'fp-and-or'\nimport identity from 'lodash/identity'\nimport picomatch from 'picomatch'\nimport { parseRange } from 'semver-utils'\nimport { FilterPattern } from '../types/FilterPattern'\nimport { Maybe } from '../types/Maybe'\nimport { VersionSpec } from '../types/VersionSpec'\n\n/**\n * Creates a filter function from a given filter string.\n * Supports strings, wildcards, comma-or-space-delimited lists, and regexes.\n * The filter function *may* throw an exception if the filter pattern is invalid.\n *\n * @param [filterPattern]\n * @returns\n */\nfunction composeFilter(filterPattern: FilterPattern): (name: string, versionSpec?: string) => boolean {\n  let predicate: (name: string, versionSpec?: string) => boolean\n\n  // no filter\n  if (!filterPattern) {\n    predicate = identity\n  }\n  // string\n  else if (typeof filterPattern === 'string') {\n    // RegExp string\n    if (filterPattern[0] === '/' && filterPattern.at(-1) === '/') {\n      const regexp = new RegExp(filterPattern.slice(1, -1))\n      predicate = (dependencyName: string) => regexp.test(dependencyName)\n    }\n    // glob string\n    else {\n      const patterns = filterPattern.split(/[\\s,]+/)\n      predicate = (dependencyName: string) => {\n        /** Returns true if the pattern matches an unscoped dependency name. */\n        const matchUnscoped = (pattern: string) => picomatch(pattern)(dependencyName)\n\n        /** Returns true if the pattern matches a scoped dependency name. */\n        const matchScoped = (pattern: string) =>\n          !pattern.includes('/') &&\n          dependencyName.includes('/') &&\n          picomatch(pattern)(dependencyName.replace(/\\//g, '_'))\n\n        // return true if any of the provided patterns match the dependency name\n        return patterns.some(or(matchUnscoped, matchScoped))\n      }\n    }\n  }\n  // array\n  else if (Array.isArray(filterPattern)) {\n    predicate = (dependencyName: string, versionSpec?: string) =>\n      filterPattern.some(subpattern => composeFilter(subpattern)(dependencyName, versionSpec))\n  }\n  // raw RegExp\n  else if (filterPattern instanceof RegExp) {\n    predicate = (dependencyName: string) => filterPattern.test(dependencyName)\n  }\n  // function\n  else if (typeof filterPattern === 'function') {\n    predicate = (dependencyName: string, versionSpec?: string) =>\n      filterPattern(dependencyName, parseRange((versionSpec as string) ?? dependencyName))\n  } else {\n    throw new TypeError('Invalid filter. Must be a RegExp, array, or comma-or-space-delimited list.')\n  }\n\n  // limit the arity to 1 to avoid passing the value\n  return predicate\n}\n\n/**\n * Composes a filter function from filter, reject, filterVersion, and rejectVersion patterns. The filter function *may* throw an exception if the filter pattern is invalid.\n *\n * @param [filter]\n * @param [reject]\n * @param [filterVersion]\n * @param [rejectVersion]\n */\nfunction filterAndReject(\n  filter: Maybe<FilterPattern>,\n  reject: Maybe<FilterPattern>,\n  filterVersion: Maybe<FilterPattern>,\n  rejectVersion: Maybe<FilterPattern>,\n) {\n  return and(\n    // filter dep\n    (dependencyName: VersionSpec, version: string) =>\n      and(filter ? composeFilter(filter) : true, reject ? (...args) => !composeFilter(reject)(...args) : true)(\n        dependencyName,\n        version,\n      ),\n    // filter version\n    (dependencyName: VersionSpec, version: string) =>\n      and(\n        filterVersion ? composeFilter(filterVersion) : true,\n        rejectVersion ? (...args) => !composeFilter(rejectVersion)(...args) : true,\n      )(version),\n  )\n}\n\nexport default filterAndReject\n"
  },
  {
    "path": "src/lib/filterObject.ts",
    "content": "import { Index } from '../types/IndexType'\nimport keyValueBy from './keyValueBy'\n\n/** Filters an object by a predicate. Does not catch exceptions thrown by the predicate. */\nconst filterObject = <T>(obj: Index<T>, predicate: (key: string, value: T) => boolean) =>\n  keyValueBy(obj, (key, value) => (predicate(key, value) ? { [key]: value } : null))\n\nexport default filterObject\n"
  },
  {
    "path": "src/lib/findLockfile.ts",
    "content": "import fs from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport { Options } from '../types/Options'\n\nconst lockFileNames = [\n  'package-lock.json',\n  'yarn.lock',\n  'pnpm-lock.yaml',\n  'deno.json',\n  'deno.jsonc',\n  'bun.lock',\n  'bun.lockb',\n]\n\n/**\n * Goes up the filesystem tree until it finds a lock file. (e.g. \"package-lock.json\", \"yarn.lock\", etc.)\n *\n * @param readdir This is only a parameter so that it can be used in tests.\n * @returns The path of the directory that contains the lockfile and the\n * filename of the lockfile.\n */\nexport default async function findLockfile(\n  options: Pick<Options, 'cwd' | 'packageFile'>,\n  readdir: (_path: string) => Promise<string[]> = fs.readdir,\n): Promise<{ directoryPath: string; filename: string } | null> {\n  try {\n    // Get boundaries to stop searching.\n    const homeDir = os.homedir()\n    const tempDir = os.tmpdir()\n\n    // 1. explicit cwd\n    // 2. same directory as package file\n    // 3. current directory\n    let currentPath = options.cwd ? options.cwd : options.packageFile ? path.dirname(options.packageFile) : '.'\n    currentPath = path.resolve(currentPath)\n\n    while (true) {\n      const files = await readdir(currentPath)\n\n      for (const filename of lockFileNames) {\n        if (files.includes(filename)) {\n          return { directoryPath: currentPath, filename }\n        }\n      }\n\n      const pathParent = path.resolve(currentPath, '..')\n      if (\n        // Stop if we have reached the root of the file system.\n        pathParent === currentPath ||\n        // Stop if we have reached the root of a user's home directory.\n        pathParent === homeDir ||\n        // Stop if we have reached the root of the temporary directory.\n        pathParent === tempDir\n      ) {\n        break\n      }\n\n      currentPath = pathParent\n    }\n  } catch (e) {\n    // if readdirSync fails, return null\n  }\n\n  return null\n}\n"
  },
  {
    "path": "src/lib/findPackage.ts",
    "content": "import findUp from 'find-up'\nimport fs from 'fs/promises'\nimport { text } from 'node:stream/consumers'\nimport path from 'path'\nimport { print } from '../lib/logging'\nimport { Options } from '../types/Options'\nimport chalk from './chalk'\nimport programError from './programError'\n\n/**\n * Finds the package file and data.\n *\n * Searches as follows:\n * --packageData flag\n * --packageFile flag\n * --stdin\n * --findUp\n */\nasync function findPackage(options: Options): Promise<{\n  pkgData: string | null\n  pkgFile: string | null\n  pkgPath: string | null\n}> {\n  let pkgData\n  let pkgFile = null\n  const pkgPath = options.packageFile || 'package.json'\n\n  /** Reads the contents of a package file. */\n  function getPackageDataFromFile(pkgFile: string | null | undefined, pkgFileName: string): Promise<string> {\n    // exit if no pkgFile to read from fs\n    if (pkgFile != null) {\n      const relPathToPackage = path.resolve(pkgFile)\n      print(options, `${options.upgrade ? 'Upgrading' : 'Checking'} ${relPathToPackage}`)\n    } else {\n      programError(\n        options,\n        `${chalk.red(\n          `No ${pkgFileName}`,\n        )}\\n\\nPlease add a ${pkgFileName} to the current directory, specify the ${chalk.cyan(\n          '--packageFile',\n        )} or ${chalk.cyan('--packageData')} options, or pipe a ${pkgFileName} to stdin and specify ${chalk.cyan(\n          '--stdin',\n        )}.`,\n        { color: false },\n      )\n    }\n\n    return fs.readFile(pkgFile!, 'utf-8').catch(e => {\n      programError(options, e)\n    })\n  }\n\n  print(options, 'Running in local mode', 'verbose')\n  print(options, 'Finding package file data', 'verbose')\n\n  // get the package data from the various input possibilities\n  if (options.packageData) {\n    pkgFile = null\n    pkgData = Promise.resolve(options.packageData)\n  } else if (options.packageFile) {\n    pkgFile = options.packageFile\n    pkgData = getPackageDataFromFile(pkgFile, pkgPath)\n  } else if (options.stdin) {\n    print(options, 'Waiting for package data on stdin', 'verbose')\n\n    // get data from stdin\n    // trim stdin to account for \\r\\n\n    const stdinData = await text(process.stdin)\n    const data = stdinData.trim().length > 0 ? stdinData : null\n\n    // if no stdin content fall back to searching for package.json from pwd and up to root\n    pkgFile = data || !pkgPath ? null : await findUp(pkgPath)\n    pkgData = data || getPackageDataFromFile(await pkgFile, pkgPath)\n  } else {\n    // find the closest package starting from the current working directory and going up to the root\n    pkgFile = pkgPath\n      ? await findUp(\n          !options.packageFile && options.packageManager === 'deno' ? ['deno.json', 'deno.jsonc'] : pkgPath,\n          {\n            cwd: options.cwd || process.cwd(),\n          },\n        )\n      : null\n    pkgData = getPackageDataFromFile(pkgFile, pkgPath)\n  }\n\n  const pkgDataResolved = await pkgData\n\n  return {\n    pkgData: pkgDataResolved,\n    pkgFile: pkgFile || null,\n    pkgPath,\n  }\n}\n\nexport default findPackage\n"
  },
  {
    "path": "src/lib/getAllPackages.ts",
    "content": "import glob, { type Options as GlobOptions } from 'fast-glob'\nimport fs from 'fs/promises'\nimport yaml from 'js-yaml'\nimport path from 'path'\nimport untildify from 'untildify'\nimport { Index } from '../types/IndexType'\nimport { Options } from '../types/Options'\nimport { PackageFile } from '../types/PackageFile'\nimport { PackageInfo } from '../types/PackageInfo'\nimport { VersionSpec } from '../types/VersionSpec'\nimport findPackage from './findPackage'\nimport loadPackageInfoFromFile from './loadPackageInfoFromFile'\nimport programError from './programError'\n\ntype PnpmWorkspaces =\n  | string[]\n  | { packages: string[]; catalog?: Index<VersionSpec>; catalogs?: Index<Index<VersionSpec>> }\n\ntype YarnConfig = { catalog?: Index<VersionSpec>; catalogs?: Index<Index<VersionSpec>> }\n\nconst globOptions: GlobOptions = {\n  ignore: ['**/node_modules/**'],\n}\n\n/** Reads, parses, and resolves workspaces from a pnpm-workspace file at the same path as the package file. */\nconst readPnpmWorkspaces = async (pkgPath: string): Promise<PnpmWorkspaces | null> => {\n  const pnpmWorkspacesPath = path.join(path.dirname(pkgPath), 'pnpm-workspace.yaml')\n  let pnpmWorkspaceFile: string\n  try {\n    pnpmWorkspaceFile = await fs.readFile(pnpmWorkspacesPath, 'utf-8')\n  } catch {\n    return null\n  }\n  return yaml.load(pnpmWorkspaceFile) as PnpmWorkspaces\n}\n\n/** Reads, parses, and resolves catalog information from the yarn config file at the same path as the package file. */\nconst readYarnConfig = async (pkgPath: string): Promise<YarnConfig | null> => {\n  const yarnConfigPath = path.join(path.dirname(pkgPath), '.yarnrc.yml')\n  let yarnConfig: string\n  try {\n    yarnConfig = await fs.readFile(yarnConfigPath, 'utf-8')\n  } catch {\n    return null\n  }\n  return yaml.load(yarnConfig) as YarnConfig\n}\n\n/** Gets catalog dependencies from both pnpm-workspace.yaml and package.json files. */\nconst readCatalogDependencies = async (options: Options, pkgPath: string): Promise<Index<VersionSpec> | null> => {\n  const catalogDependencies: Index<VersionSpec> = {}\n\n  // Read from pnpm-workspace.yaml if the package manager is pnpm\n  if (options.packageManager === 'pnpm') {\n    const pnpmWorkspaces = await readPnpmWorkspaces(pkgPath)\n    if (pnpmWorkspaces && !Array.isArray(pnpmWorkspaces)) {\n      // Handle both singular 'catalog' and plural 'catalogs'\n      if (pnpmWorkspaces.catalog) {\n        Object.assign(catalogDependencies, pnpmWorkspaces.catalog)\n      }\n      if (pnpmWorkspaces.catalogs) {\n        Object.assign(catalogDependencies, ...Object.values(pnpmWorkspaces.catalogs))\n      }\n    }\n  }\n\n  if (options.packageManager === 'yarn') {\n    const yarnConfig = await readYarnConfig(pkgPath)\n    if (yarnConfig) {\n      if (yarnConfig.catalog) {\n        Object.assign(catalogDependencies, yarnConfig.catalog)\n      }\n      if (yarnConfig.catalogs) {\n        Object.assign(catalogDependencies, ...Object.values(yarnConfig.catalogs))\n      }\n    }\n  }\n\n  // Read from package.json (for Bun and modern pnpm)\n  const packageData: PackageFile & {\n    catalog?: Index<VersionSpec>\n    catalogs?: Index<Index<VersionSpec>>\n    workspaces?: string[] | { packages: string[]; catalog?: Index<VersionSpec>; catalogs?: Index<Index<VersionSpec>> }\n  } = JSON.parse(await fs.readFile(pkgPath, 'utf-8'))\n\n  Object.assign(catalogDependencies, packageData.catalog, ...Object.values(packageData.catalogs ?? {}))\n\n  // Workspaces catalogs (Bun format)\n  if (packageData.workspaces && !Array.isArray(packageData.workspaces)) {\n    Object.assign(\n      catalogDependencies,\n      packageData.workspaces.catalog,\n      ...Object.values(packageData.workspaces.catalogs ?? {}),\n    )\n  }\n\n  return Object.keys(catalogDependencies).length > 0 ? catalogDependencies : null\n}\n\n/**\n * Gets all workspace packages information.\n *\n * @param options the application options, used to determine which packages to return.\n * @param defaultPackageFilename the default package filename\n * @returns a list of PackageInfo objects, one for each workspace file\n */\nasync function getWorkspacePackageInfos(\n  options: Options,\n  defaultPackageFilename: string,\n  rootPackageFile: string,\n  cwd: string,\n): Promise<[PackageInfo[], string[]]> {\n  // use silent; otherwise, there will be a duplicate \"Checking\" message\n  const { pkgData, pkgPath } = await findPackage({ ...options, packageFile: rootPackageFile, loglevel: 'silent' })\n  const rootPkg: PackageFile = typeof pkgData === 'string' ? JSON.parse(pkgData) : pkgData\n\n  const workspacesObject = rootPkg.workspaces || (await readPnpmWorkspaces(pkgPath || ''))\n  const workspaces = Array.isArray(workspacesObject) ? workspacesObject : workspacesObject?.packages\n\n  if (!workspaces) {\n    programError(\n      options,\n      `workspaces property missing from package.json. --workspace${\n        options.workspaces ? 's' : ''\n      } only works when you specify a \"workspaces\" property in your package.json.`,\n    )\n  }\n\n  // build a glob from the workspaces\n  // FIXME: the following workspaces check is redundant\n  const workspacePackageGlob: string[] = (workspaces || []).map(workspace =>\n    path\n      .join(cwd, workspace, 'package.json')\n      // convert Windows path to *nix path for globby\n      .replace(/\\\\/g, '/'),\n  )\n\n  // e.g. [packages/a/package.json, ...]\n  const allWorkspacePackageFilepaths: string[] = glob.sync(workspacePackageGlob, globOptions)\n\n  // Get the package names from the package files.\n  // If a package does not have a name, use the folder name.\n  // These will be used to filter out local workspace packages so they are not fetched from the registry.\n  const allWorkspacePackageInfos: PackageInfo[] = await Promise.all(\n    allWorkspacePackageFilepaths.map(async (filepath: string): Promise<PackageInfo> => {\n      const info: PackageInfo = await loadPackageInfoFromFile(options, filepath)\n      info.name = info.pkg.name || filepath.split('/').slice(-2)[0]\n      return info\n    }),\n  )\n\n  // Workspace package names\n  // These will be used to filter out local workspace packages so they are not fetched from the registry.\n  const allWorkspacePackageNames: string[] = allWorkspacePackageInfos.map(\n    (packageInfo: PackageInfo): string => packageInfo.name || '',\n  )\n\n  const filterWorkspaces = options.workspaces !== true\n  if (!filterWorkspaces) {\n    // --workspaces\n    return [allWorkspacePackageInfos, allWorkspacePackageNames]\n  }\n\n  // add workspace packages\n  // --workspace\n  const selectedWorkspacePackageInfos: PackageInfo[] = allWorkspacePackageInfos.filter((packageInfo: PackageInfo) =>\n    options.workspace?.some((workspace: string) =>\n      workspaces?.some(\n        (workspacePattern: string) =>\n          packageInfo.name === workspace ||\n          packageInfo.filepath ===\n            path.join(cwd, path.dirname(workspacePattern), workspace, defaultPackageFilename).replace(/\\\\/g, '/'),\n      ),\n    ),\n  )\n  return [selectedWorkspacePackageInfos, allWorkspacePackageNames]\n}\n\n/**\n * Gets catalog package info from pnpm-workspace.yaml or package.json.\n *\n * @param options the application options\n * @param pkgPath the package file path (already resolved)\n * @returns PackageInfo for catalog dependencies or null if no catalogs exist\n */\nasync function getCatalogPackageInfo(options: Options, pkgPath: string): Promise<PackageInfo | null> {\n  if (!pkgPath) {\n    return null\n  }\n\n  const catalogDependencies = await readCatalogDependencies(options, pkgPath)\n  if (!catalogDependencies) {\n    return null\n  }\n\n  // Create a synthetic package info for catalog dependencies\n  const catalogPackageFile: PackageFile = {\n    name: 'catalog-dependencies',\n    version: '1.0.0',\n    dependencies: catalogDependencies,\n  }\n\n  // Determine the correct file path for catalogs. For pnpm, use pnpm-workspace.yaml.\n  // For Bun catalogs in package.json, use a virtual path to avoid conflicts with root package.\n  const catalogFilePath =\n    options.packageManager === 'pnpm'\n      ? path.join(path.dirname(pkgPath), 'pnpm-workspace.yaml')\n      : options.packageManager === 'yarn'\n        ? path.join(path.dirname(pkgPath), '.yarnrc.yml')\n        : `${pkgPath}#catalog`\n\n  // Create synthetic file content that matches the synthetic PackageFile\n  const syntheticFileContent = JSON.stringify(catalogPackageFile, null, 2)\n\n  const catalogPackageInfo: PackageInfo = {\n    filepath: catalogFilePath,\n    pkg: catalogPackageFile,\n    pkgFile: syntheticFileContent,\n    name: 'catalogs',\n  }\n\n  return catalogPackageInfo\n}\n\n/**\n * Gets all local packages, including workspaces (depending on -w, -ws, and -root).\n *\n * @param options the application options, used to determine which packages to return.\n * @returns PackageInfo[] an array of all package infos to be considered for updating\n */\nasync function getAllPackages(options: Options): Promise<[PackageInfo[], string[]]> {\n  const defaultPackageFilename = options.packageFile || 'package.json'\n  const cwd = options.cwd ? untildify(options.cwd) : './'\n  const rootPackageFile = options.packageFile || (options.cwd ? path.join(cwd, 'package.json') : 'package.json')\n\n  const useWorkspaces: boolean =\n    options.workspaces === true || (options.workspace !== undefined && options.workspace.length !== 0)\n\n  let packageInfos: PackageInfo[] = []\n\n  // Find the package file with globby.\n  // When in workspaces mode, only include the root project package file when --root is used.\n  const getBasePackageFile: boolean = !useWorkspaces || options.root === true\n  if (getBasePackageFile) {\n    // we are either:\n    // * NOT a workspace\n    // * a workspace and have requested an upgrade of the workspace-root\n    const globPattern = rootPackageFile.replace(/\\\\/g, '/')\n    const rootPackagePaths = glob.sync(globPattern, globOptions)\n    // realistically there should only be zero or one\n    const rootPackages = await Promise.all(\n      rootPackagePaths.map(\n        async (packagePath: string): Promise<PackageInfo> => await loadPackageInfoFromFile(options, packagePath),\n      ),\n    )\n    packageInfos = [...packageInfos, ...rootPackages]\n  }\n\n  if (!useWorkspaces) {\n    return [packageInfos, []]\n  }\n\n  // Read catalog dependencies first so we can resolve references\n  let catalogPackageInfo: PackageInfo | null = null\n\n  if (useWorkspaces) {\n    const { pkgPath: workspacePkgPath } = await findPackage({\n      ...options,\n      packageFile: rootPackageFile,\n      loglevel: 'silent',\n    })\n    if (workspacePkgPath) {\n      catalogPackageInfo = await getCatalogPackageInfo(options, workspacePkgPath)\n    }\n  }\n\n  // workspaces\n  const [workspacePackageInfos, workspaceNames]: [PackageInfo[], string[]] = await getWorkspacePackageInfos(\n    options,\n    defaultPackageFilename,\n    rootPackageFile,\n    cwd,\n  )\n\n  // Don't resolve catalog references in workspace packages - leave them as \"catalog:*\"\n  // Only the catalog definitions themselves should be updated\n  packageInfos = [...packageInfos, ...workspacePackageInfos]\n\n  // Add catalog package info for version checking (only if there are catalogs)\n  if (catalogPackageInfo) {\n    packageInfos = [...packageInfos, catalogPackageInfo]\n  }\n\n  return [packageInfos, workspaceNames]\n}\n\nexport default getAllPackages\n"
  },
  {
    "path": "src/lib/getCurrentDependencies.ts",
    "content": "import * as semver from 'semver'\nimport { Index } from '../types/IndexType'\nimport { Options } from '../types/Options'\nimport { PackageFile } from '../types/PackageFile'\nimport { VersionSpec } from '../types/VersionSpec'\nimport filterAndReject from './filterAndReject'\nimport filterObject from './filterObject'\nimport { keyValueBy } from './keyValueBy'\nimport programError from './programError'\nimport resolveDepSections from './resolveDepSections'\n\n/** Returns true if spec1 is greater than spec2, ignoring invalid version ranges. */\nconst isGreaterThanSafe = (spec1: VersionSpec, spec2: VersionSpec) =>\n  // not a valid range to compare (e.g. github url)\n  semver.validRange(spec1) &&\n  semver.validRange(spec2) &&\n  // otherwise return true if spec2 is smaller than spec1\n  semver.gt(semver.minVersion(spec1)!, semver.minVersion(spec2)!)\n\n/** Parses the packageManager field into a { [name]: version } pair. */\nconst parsePackageManager = (pkgData: PackageFile) => {\n  if (!pkgData.packageManager) return {}\n  const [name, version] = pkgData.packageManager.split('@')\n  return { [name]: version }\n}\n/**\n * Get the current dependencies from the package file.\n *\n * @param [pkgData={}] Object with dependencies, devDependencies, peerDependencies, and/or optionalDependencies properties.\n * @param [options={}]\n * @param options.dep\n * @param options.filter\n * @param options.reject\n * @returns Promised {packageName: version} collection\n */\nfunction getCurrentDependencies(pkgData: PackageFile = {}, options: Options = {}) {\n  const depSections = resolveDepSections(options.dep)\n\n  // get all dependencies from the selected sections\n  // if a dependency appears in more than one section, take the lowest version number\n  const allDependencies = depSections.reduce((accum, depSection) => {\n    return {\n      ...accum,\n      ...(depSection === 'packageManager'\n        ? parsePackageManager(pkgData)\n        : filterObject(\n            (pkgData[depSection] as Index<string>) || {},\n            (dep, spec) => !isGreaterThanSafe(spec, accum[dep]),\n          )),\n    }\n  }, {} as Index<VersionSpec>)\n\n  // filter & reject dependencies and versions\n  const workspacePackageMap = keyValueBy(options.workspacePackages || [])\n  let filteredDependencies: Index<VersionSpec> = {}\n  try {\n    filteredDependencies = filterObject(\n      filterObject(allDependencies, name => !workspacePackageMap[name]),\n      filterAndReject(\n        options.filter || null,\n        options.reject || null,\n        options.filterVersion || null,\n        options.rejectVersion || null,\n      ),\n    )\n  } catch (err: any) {\n    programError(options, 'Invalid filter: ' + err.message || err)\n  }\n\n  return filteredDependencies\n}\n\nexport default getCurrentDependencies\n"
  },
  {
    "path": "src/lib/getEnginesNodeFromRegistry.ts",
    "content": "import ProgressBar from 'progress'\nimport { Index } from '../types/IndexType'\nimport { Options } from '../types/Options'\nimport { Version } from '../types/Version'\nimport { VersionSpec } from '../types/VersionSpec'\nimport getPackageManager from './getPackageManager'\n\n/**\n * Get the engines.node versions from the NPM repository based on the version target.\n *\n * @param packageMap   An object whose keys are package name and values are version\n * @param [options={}] Options.\n * @returns Promised {packageName: engines.node} collection\n */\nasync function getEnginesNodeFromRegistry(packageMap: Index<Version>, options: Options) {\n  const packageManager = getPackageManager(options, options.packageManager)\n  if (!packageManager.getEngines) return {}\n\n  const numItems = Object.keys(packageMap).length\n  let bar: ProgressBar\n  if (!options.json && options.loglevel !== 'silent' && options.loglevel !== 'verbose' && numItems > 0) {\n    bar = new ProgressBar('[:bar] :current/:total :percent', { total: numItems, width: 20 })\n    bar.render()\n  }\n\n  return Object.entries(packageMap).reduce(async (accumPromise, [pkg, version]) => {\n    const enginesNode = (await packageManager.getEngines!(pkg, version, options)).node\n    if (bar) {\n      bar.tick()\n    }\n    const accum = await accumPromise\n    return { ...accum, [pkg]: enginesNode }\n  }, Promise.resolve<Index<VersionSpec | undefined>>({}))\n}\n\nexport default getEnginesNodeFromRegistry\n"
  },
  {
    "path": "src/lib/getIgnoredUpgradesDueToEnginesNode.ts",
    "content": "import { minVersion, satisfies } from 'semver'\nimport { IgnoredUpgradeDueToEnginesNode } from '../types/IgnoredUpgradeDueToEnginesNode'\nimport { Index } from '../types/IndexType'\nimport { Maybe } from '../types/Maybe'\nimport { Options } from '../types/Options'\nimport { Version } from '../types/Version'\nimport { VersionSpec } from '../types/VersionSpec'\nimport getEnginesNodeFromRegistry from './getEnginesNodeFromRegistry'\nimport keyValueBy from './keyValueBy'\nimport upgradePackageDefinitions from './upgradePackageDefinitions'\n\n/** Checks if package.json min node version satisfies given package engine.node spec */\nconst satisfiesNodeEngine = (enginesNode: Maybe<VersionSpec>, optionsEnginesNodeMinVersion: Version) =>\n  !enginesNode || satisfies(optionsEnginesNodeMinVersion, enginesNode)\n\n/** Get all upgrades that are ignored due to incompatible engines.node. */\nexport async function getIgnoredUpgradesDueToEnginesNode(\n  current: Index<VersionSpec>,\n  upgraded: Index<VersionSpec>,\n  options: Options = {},\n) {\n  if (!options.nodeEngineVersion) return {}\n  const optionsEnginesNodeMinVersion = minVersion(options.nodeEngineVersion)?.version\n  if (!optionsEnginesNodeMinVersion) return {}\n  const [upgradedLatestVersions, latestVersionResults] = await upgradePackageDefinitions(current, {\n    ...options,\n    enginesNode: false,\n    nodeEngineVersion: undefined,\n    loglevel: 'silent',\n  })\n\n  // Use the latest versions since getEnginesNodeFromRegistry requires exact versions.\n  // Filter down to only the upgraded latest versions, as there is no point in checking the engines.node for packages that have been filtered out, e.g. by options.minimal or options.filterResults.\n  const latestVersions = keyValueBy(latestVersionResults, (dep, result) =>\n    upgradedLatestVersions[dep] && result?.version\n      ? {\n          [dep]: result.version,\n        }\n      : null,\n  )\n  const enginesNodes = await getEnginesNodeFromRegistry(latestVersions, options)\n  return Object.entries(upgradedLatestVersions)\n    .filter(\n      ([pkgName, newVersion]) =>\n        upgraded[pkgName] !== newVersion && !satisfiesNodeEngine(enginesNodes[pkgName], optionsEnginesNodeMinVersion),\n    )\n    .reduce(\n      (accum, [pkgName, newVersion]) => ({\n        ...accum,\n        [pkgName]: {\n          from: current[pkgName],\n          to: newVersion,\n          enginesNode: enginesNodes[pkgName]!,\n        },\n      }),\n      {} as Index<IgnoredUpgradeDueToEnginesNode>,\n    )\n}\n\nexport default getIgnoredUpgradesDueToEnginesNode\n"
  },
  {
    "path": "src/lib/getIgnoredUpgradesDueToPeerDeps.ts",
    "content": "import { intersects, minVersion, satisfies, validRange } from 'semver'\nimport { IgnoredUpgradeDueToPeerDeps } from '../types/IgnoredUpgradeDueToPeerDeps'\nimport { Index } from '../types/IndexType'\nimport { Options } from '../types/Options'\nimport { Version } from '../types/Version'\nimport { VersionSpec } from '../types/VersionSpec'\nimport getPeerDependenciesFromRegistry from './getPeerDependenciesFromRegistry'\nimport upgradePackageDefinitions from './upgradePackageDefinitions'\n\n/** Get all upgrades that are ignored due to incompatible peer dependencies. */\nexport async function getIgnoredUpgradesDueToPeerDeps(\n  current: Index<VersionSpec>,\n  upgraded: Index<VersionSpec>,\n  upgradedPeerDependencies: Index<Index<Version>>,\n  options: Options = {},\n) {\n  const upgradedPackagesWithPeerRestriction = {\n    ...current,\n    ...upgraded,\n  }\n  const [upgradedLatestVersions, latestVersionResults] = await upgradePackageDefinitions(current, {\n    ...options,\n    peer: false,\n    peerDependencies: undefined,\n    loglevel: 'silent',\n  })\n  const upgradedPeerDependenciesLatest = await getPeerDependenciesFromRegistry(\n    Object.fromEntries(\n      Object.entries(upgradedLatestVersions).map(([packageName, versionSpec]) => {\n        return [\n          packageName,\n          // git urls and other non-semver versions are ignored.\n          // Make sure versionSpec is a valid semver range; otherwise, minVersion will throw.\n          validRange(versionSpec) ? (minVersion(versionSpec)?.version ?? versionSpec) : versionSpec,\n        ]\n      }),\n    ),\n    options,\n  )\n  return Object.entries(upgradedLatestVersions)\n    .filter(([pkgName, newVersion]) => upgraded[pkgName] !== newVersion)\n    .reduce((accum, [pkgName, newVersion]) => {\n      let reason = Object.entries(upgradedPeerDependencies)\n        .filter(\n          ([, peers]) =>\n            peers[pkgName] !== undefined &&\n            latestVersionResults[pkgName]?.version &&\n            !satisfies(latestVersionResults[pkgName].version!, peers[pkgName]),\n        )\n        .reduce(\n          (accumReason, [peerPkg, peers]) => ({\n            ...accumReason,\n            [peerPkg]: !validRange(peers[pkgName])\n              ? `a range that semver does not understand: ${peers[pkgName]}. This range does not work with semver.satisfies or semver.intersects, which npm-check-updates relies on to determine peer dependency compatibility. Either this is a mistake in ${peerPkg}, or it relies on a new syntax that is not compatible with the semver package.`\n              : peers[pkgName],\n          }),\n          {} as Index<string>,\n        )\n      if (Object.keys(reason).length === 0) {\n        const peersOfPkg = upgradedPeerDependenciesLatest?.[pkgName] || {}\n        reason = Object.entries(peersOfPkg)\n          .filter(\n            ([peer, peerSpec]) =>\n              upgradedPackagesWithPeerRestriction[peer] &&\n              !(!validRange(peerSpec) || intersects(upgradedPackagesWithPeerRestriction[peer], peerSpec)),\n          )\n          .reduce(\n            (accumReason, [peerPkg, peerSpec]) => ({ ...accumReason, [pkgName]: `${peerPkg} ${peerSpec}` }),\n            {} as Index<string>,\n          )\n      }\n      return {\n        ...accum,\n        [pkgName]: {\n          from: current[pkgName],\n          to: newVersion,\n          reason,\n        },\n      }\n    }, {} as Index<IgnoredUpgradeDueToPeerDeps>)\n}\n\nexport default getIgnoredUpgradesDueToPeerDeps\n"
  },
  {
    "path": "src/lib/getInstalledPackages.ts",
    "content": "import { Index } from '../types/IndexType'\nimport { Options } from '../types/Options'\nimport { Version } from '../types/Version'\nimport { VersionSpec } from '../types/VersionSpec'\nimport filterAndReject from './filterAndReject'\nimport filterObject from './filterObject'\nimport getPackageManager from './getPackageManager'\nimport programError from './programError'\nimport { isWildPart } from './version-util'\n\n/**\n * @param [options]\n * @param options.cwd\n * @param options.filter\n * @param options.global\n * @param options.packageManager\n * @param options.prefix\n * @param options.reject\n */\nasync function getInstalledPackages(options: Options = {}) {\n  const packages = await getPackageManager(options, options.packageManager).list?.({\n    cwd: options.cwd,\n    prefix: options.prefix,\n    global: options.global,\n  })\n\n  if (!packages) {\n    programError(options, 'Unable to retrieve package list')\n  }\n\n  // filter out undefined packages or those with a wildcard\n  const filterFunction = filterAndReject(options.filter, options.reject, options.filterVersion, options.rejectVersion)\n  let filteredPackages: Index<VersionSpec> = {}\n  try {\n    filteredPackages = filterObject(\n      packages,\n      (dep: VersionSpec, version: Version) => !!version && !isWildPart(version) && filterFunction(dep, version),\n    )\n  } catch (err: any) {\n    programError(options, 'Invalid filter: ' + err.message || err)\n  }\n\n  return filteredPackages\n}\n\nexport default getInstalledPackages\n"
  },
  {
    "path": "src/lib/getNcuRc.ts",
    "content": "import os from 'os'\nimport path from 'path'\nimport { rcFile } from 'rc-config-loader'\nimport { cliOptionsMap } from '../cli-options'\nimport { Options } from '../types/Options'\nimport { RcOptions } from '../types/RcOptions'\nimport programError from './programError'\n\n/** Loads the .ncurc config file. */\nasync function getNcuRc({\n  configFileName,\n  configFilePath,\n  packageFile,\n  global,\n  options,\n}: {\n  configFileName?: string\n  configFilePath?: string\n  /** If true, does not look in package directory. */\n  global?: boolean\n  packageFile?: string\n  options: Options\n}) {\n  const { default: chalkDefault, Chalk } = await import('chalk')\n  const chalk = options?.color ? new Chalk({ level: 1 }) : chalkDefault\n\n  const rawResult = rcFile<RcOptions>('ncurc', {\n    configFileName: configFileName || '.ncurc',\n    defaultExtension: ['.json', '.yml', '.js'],\n    cwd: configFilePath || (global ? os.homedir() : packageFile ? path.dirname(packageFile) : undefined),\n  })\n\n  // ensure a file was found if expected\n  const filePath = rawResult?.filePath\n  if (configFileName && !filePath) {\n    programError(options, `Config file ${configFileName} not found in ${configFilePath || process.cwd()}`)\n  }\n\n  // convert the config to valid options by removing $schema and parsing format\n  const { $schema: _, ...rawConfig } = rawResult?.config || {}\n  const config: Options = rawConfig\n  if (typeof config.format === 'string') config.format = cliOptionsMap.format.parse!(config.format)\n\n  // validate arguments here to provide a better error message\n  const unknownOptions = Object.keys(config).filter(arg => !cliOptionsMap[arg])\n  if (unknownOptions.length > 0) {\n    console.error(\n      chalk.red(`Unknown option${unknownOptions.length === 1 ? '' : 's'} found in config file:`),\n      chalk.gray(unknownOptions.join(', ')),\n    )\n    console.info('Using config file ' + filePath)\n    console.info(`You can change the config file path with ${chalk.blue('--configFilePath')}`)\n  }\n\n  // flatten config object into command line arguments to be read by commander\n  const args = Object.entries(config).flatMap(([name, value]): any[] => {\n    // render boolean options as a single parameter\n    // an option is considered boolean if its type is explicitly set to boolean, or if it is has a proper JavaScript boolean value\n    if (typeof value === 'boolean' || cliOptionsMap[name]?.type === 'boolean') {\n      // if the boolean option is true, include only the nullary option --${name}; otherwise, exclude it\n      return value ? [`--${name}`] : []\n    }\n    // otherwise render as a 2-tuple with name and value\n    return [`--${name}`, value]\n  })\n\n  return { filePath, args, config }\n}\n\nexport default getNcuRc\n"
  },
  {
    "path": "src/lib/getPackageJson.ts",
    "content": "import fs from 'fs/promises'\nimport path from 'path'\nimport { PackageFile } from '../types/PackageFile'\nimport exists from './exists'\n\n/** Gets the package.json contents of an installed package. */\nasync function getPackageJson(\n  packageName: string,\n  {\n    pkgFile,\n  }: {\n    /** Specify the package file location to add to the node_modules search paths. Needed in workspaces/deep mode. */\n    pkgFile?: string\n  } = {},\n): Promise<PackageFile | null> {\n  const requirePaths = require.resolve.paths(packageName) || []\n  const pkgFileNodeModules = pkgFile ? [path.join(path.dirname(pkgFile), 'node_modules')] : []\n  const localNodeModules = [path.join(process.cwd(), 'node_modules')]\n  const nodeModulePaths = [...pkgFileNodeModules, ...localNodeModules, ...requirePaths]\n\n  for (const basePath of nodeModulePaths) {\n    const packageJsonPath = path.join(basePath, packageName, 'package.json')\n    if (await exists(packageJsonPath)) {\n      try {\n        const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'))\n        return packageJson\n      } catch (e) {}\n    }\n  }\n\n  return null\n}\n\nexport default getPackageJson\n"
  },
  {
    "path": "src/lib/getPackageManager.ts",
    "content": "import packageManagers from '../package-managers'\nimport { Maybe } from '../types/Maybe'\nimport { Options } from '../types/Options'\nimport { PackageManager } from '../types/PackageManager'\nimport programError from './programError'\n\n/**\n * Resolves the package manager from a string or object. Throws an error if an invalid packageManager is provided.\n *\n * @param packageManagerNameOrObject\n * @param packageManagerNameOrObject.global\n * @param packageManagerNameOrObject.packageManager\n * @returns\n */\nfunction getPackageManager(options: Options, name: Maybe<string>): PackageManager {\n  // default to npm\n  if (!name || name === 'deno') {\n    return packageManagers.npm\n  } else if (options.registryType === 'json') {\n    return packageManagers.staticRegistry\n  }\n\n  if (!packageManagers[name]) {\n    programError(options, `Invalid package manager: ${name}`)\n  }\n\n  return packageManagers[name]\n}\n\nexport default getPackageManager\n"
  },
  {
    "path": "src/lib/getPackageVersion.ts",
    "content": "import { PackageFile } from '../types/PackageFile'\nimport getPackageJson from './getPackageJson'\n\n/**\n * @param packageName A package name as listed in package.json's dependencies list\n * @param packageJson Optional param to specify an object representation of a package.json file instead of loading from node_modules\n * @returns The package version or null if a version could not be determined\n */\nasync function getPackageVersion(\n  packageName: string,\n  packageJson?: PackageFile,\n  {\n    pkgFile,\n  }: {\n    /** Specify the package file location to add to the node_modules search paths. Needed in workspaces/deep mode. */\n    pkgFile?: string\n  } = {},\n) {\n  if (packageJson) {\n    return packageJson.version\n  }\n\n  const loadedPackageJson = await getPackageJson(packageName, { pkgFile })\n  return loadedPackageJson?.version ?? null\n}\n\nexport default getPackageVersion\n"
  },
  {
    "path": "src/lib/getPeerDependenciesFromRegistry.ts",
    "content": "import pMap from 'p-map'\nimport ProgressBar from 'progress'\nimport { Index } from '../types/IndexType'\nimport { Options } from '../types/Options'\nimport { Version } from '../types/Version'\nimport getPackageManager from './getPackageManager'\n\ntype CircularData =\n  | {\n      isCircular: true\n      offendingPackage: string\n    }\n  | {\n      isCircular: false\n    }\n\n/**\n * Checks if the specified package will create a loop of peer dependencies by traversing all paths to find a cycle\n *\n * If a cycle was found, the offending peer dependency of the specified package is returned\n */\nfunction isCircularPeer(peerDependencies: Index<Index<string>>, packageName: string): CircularData {\n  let queue = [[packageName]]\n  while (queue.length > 0) {\n    const nextQueue: string[][] = []\n    for (const path of queue) {\n      const parents = Object.keys(peerDependencies[path[0]] ?? {})\n      for (const name of parents) {\n        if (name === path.at(-1)) {\n          return {\n            isCircular: true,\n            offendingPackage: path[0],\n          }\n        }\n        nextQueue.push([name, ...path])\n      }\n    }\n    queue = nextQueue\n  }\n  return {\n    isCircular: false,\n  }\n}\n\n/**\n * Get the latest or greatest versions from the NPM repository based on the version target.\n *\n * @param packageMap   An object whose keys are package name and values are version\n * @param [options={}] Options.\n * @returns Promised {packageName: peer dependencies} collection\n */\nasync function getPeerDependenciesFromRegistry(packageMap: Index<Version>, options: Options) {\n  const packageManager = getPackageManager(options, options.packageManager)\n  if (!packageManager.getPeerDependencies) return {}\n\n  const numItems = Object.keys(packageMap).length\n  let bar: ProgressBar\n  if (!options.json && options.loglevel !== 'silent' && options.loglevel !== 'verbose' && numItems > 0) {\n    bar = new ProgressBar('[:bar] :current/:total :percent', { total: numItems, width: 20 })\n    bar.render()\n  }\n\n  const packageEntries = Object.entries(packageMap)\n\n  /**\n   * Fetches peer dependencies for a package\n   * @param pkg - The package name\n   * @param version - The package version\n   * @returns Promise that resolves to package name and its peer dependencies\n   */\n  const getPeerDepsForPackage = async ([pkg, version]: [string, Version]): Promise<{\n    pkg: string\n    dependencies: Index<string>\n  }> => {\n    let dependencies: Index<string>\n    const cached = options.cacher?.getPeers(pkg, version)\n    if (cached) {\n      dependencies = cached\n    } else {\n      dependencies = await packageManager.getPeerDependencies!(pkg, version, { cwd: options.cwd })\n      options.cacher?.setPeers(pkg, version, dependencies)\n    }\n    if (bar) {\n      bar.tick()\n    }\n    return { pkg, dependencies }\n  }\n\n  const results = await pMap(packageEntries, getPeerDepsForPackage, { concurrency: options.concurrency })\n\n  const accum: Index<Index<string>> = {}\n  for (const { pkg, dependencies } of results) {\n    accum[pkg] = dependencies\n    const circularData = isCircularPeer(accum, pkg)\n    if (circularData.isCircular) {\n      delete accum[pkg][circularData.offendingPackage]\n    }\n  }\n\n  await options.cacher?.save()\n  options.cacher?.log(true)\n\n  return accum\n}\n\nexport default getPeerDependenciesFromRegistry\n"
  },
  {
    "path": "src/lib/getPreferredWildcard.ts",
    "content": "import { Index } from '../types/IndexType'\nimport { sortBy } from './sortBy'\nimport { WILDCARDS } from './version-util'\n\n/**\n *\n * @param dependencies A dependencies collection\n * @returns Returns whether the user prefers ^, ~, .*, or .x\n * (simply counts the greatest number of occurrences) or `null` if\n * given no dependencies.\n */\nfunction getPreferredWildcard(dependencies: Index<string | null>) {\n  // if there are no dependencies, return null.\n  if (Object.keys(dependencies).length === 0) {\n    return null\n  }\n\n  // group the dependencies by wildcard\n  const groups = Object.values(dependencies).reduce<Record<string, (string | null)[]>>((acc, dep) => {\n    const wildcard = WILDCARDS.find((wildcard: string) => dep && dep.includes(wildcard))\n    if (wildcard !== undefined) {\n      acc[wildcard] ||= []\n      acc[wildcard].push(dep)\n    }\n    return acc\n  }, {})\n\n  const arrOfGroups = Object.entries(groups).map(([wildcard, instances]) => ({ wildcard, instances }))\n\n  // reverse sort the groups so that the wildcard with the most appearances is at the head, then return it.\n  const sorted = sortBy(arrOfGroups, wildcardObject => -wildcardObject.instances.length)\n\n  return sorted.length > 0 ? sorted[0].wildcard : null\n}\n\nexport default getPreferredWildcard\n"
  },
  {
    "path": "src/lib/getRepoUrl.ts",
    "content": "import hostedGitInfo from 'hosted-git-info'\nimport { URL } from 'url'\nimport { PackageFile } from '../types/PackageFile'\nimport { PackageFileRepository } from '../types/PackageFileRepository'\nimport getPackageJson from './getPackageJson'\n\n/** Gets the repo url of an installed package. */\nasync function getPackageRepo(\n  packageName: string,\n  {\n    pkgFile,\n  }: {\n    /** Specify the package file location to add to the node_modules search paths. Needed in workspaces/deep mode. */\n    pkgFile?: string\n  } = {},\n): Promise<string | PackageFileRepository | null> {\n  const packageJson = await getPackageJson(packageName, { pkgFile })\n  return packageJson?.repository ?? null\n}\n\n/**\n * @param packageName A package name as listed in package.json's dependencies list\n * @param packageJson Optional param to specify an object representation of a package.json file instead of loading from node_modules\n * @returns A valid url to the root of the package's source or null if a url could not be determined\n */\nasync function getRepoUrl(\n  packageName: string,\n  packageJson?: PackageFile,\n  {\n    pkgFile,\n  }: {\n    /** See: getPackageRepo pkgFile param. */\n    pkgFile?: string\n  } = {},\n) {\n  const repositoryMetadata: string | PackageFileRepository | null = !packageJson\n    ? await getPackageRepo(packageName, { pkgFile })\n    : packageJson.repository\n      ? packageJson.repository\n      : null\n\n  if (!repositoryMetadata) return null\n\n  let gitURL\n  let directory = ''\n\n  // It may be a string instead of an object\n  if (typeof repositoryMetadata === 'string') {\n    gitURL = repositoryMetadata\n    try {\n      // It may already be a valid Repo URL\n      const url = new URL(gitURL)\n      // Some packages put a full URL in this field although it's not spec compliant. Let's detect that and use it if present\n      if (url.protocol === 'https:' || url.protocol === 'http:') {\n        return gitURL\n      }\n    } catch (e) {}\n  } else if (typeof repositoryMetadata.url === 'string') {\n    gitURL = repositoryMetadata.url\n    if (typeof repositoryMetadata.directory === 'string') {\n      directory = repositoryMetadata.directory\n    }\n  }\n\n  if (typeof gitURL === 'string' && typeof directory === 'string') {\n    const hostedGitURL = hostedGitInfo.fromUrl(gitURL)?.browse(directory)\n    if (hostedGitURL !== undefined) {\n      // Remove the default branch path (/tree/HEAD) from a git url\n      return hostedGitURL.replace(/\\/$/, '').replace(/\\/tree\\/HEAD$/, '')\n    }\n    return gitURL\n  }\n  return null\n}\n\nexport default getRepoUrl\n"
  },
  {
    "path": "src/lib/initOptions.ts",
    "content": "import { dequal } from 'dequal'\nimport propertyOf from 'lodash/propertyOf'\nimport cliOptions from '../cli-options'\nimport { print } from '../lib/logging'\nimport packageManagers from '../package-managers'\nimport { FilterPattern } from '../types/FilterPattern'\nimport { Options } from '../types/Options'\nimport { RunOptions } from '../types/RunOptions'\nimport { Target } from '../types/Target'\nimport cacher from './cache'\nimport determinePackageManager from './determinePackageManager'\nimport exists from './exists'\nimport keyValueBy from './keyValueBy'\nimport parseCooldown from './parseCooldown'\nimport programError from './programError'\n\nfunction parseFilterExpression(filterExpression: string[] | undefined): string[] | undefined\nfunction parseFilterExpression(filterExpression: FilterPattern | undefined): FilterPattern | undefined\n/** Trims and filters out empty values from a filter expression. */\nfunction parseFilterExpression(filterExpression: FilterPattern | undefined): FilterPattern | undefined {\n  if (typeof filterExpression === 'string') {\n    return filterExpression.trim()\n  } else if (\n    Array.isArray(filterExpression) &&\n    (filterExpression.length === 0 || typeof filterExpression[0] === 'string')\n  ) {\n    const filtered = filterExpression.map(s => (typeof s === 'string' ? s.trim() : s)).filter(x => x)\n    return filtered.length > 0 ? filtered : undefined\n  } else {\n    return filterExpression\n  }\n}\n\n/** Checks if a string is a valid URL. */\nfunction isValidUrl(url: string): boolean {\n  try {\n    // eslint-disable-next-line no-new\n    new URL(url)\n    return true\n  } catch {\n    return false\n  }\n}\n\n/** Initializes, validates, sets defaults, and consolidates program options. */\nasync function initOptions(runOptions: RunOptions, { cli }: { cli?: boolean } = {}): Promise<Options> {\n  const { default: chalkDefault, Chalk } = await import('chalk')\n  const chalk = runOptions.color ? new Chalk({ level: 1 }) : chalkDefault\n\n  // if not executed on the command-line (i.e. executed as a node module), set the defaults\n  if (!cli) {\n    // set cli defaults since they are not set by commander in this case\n    const cliDefaults = cliOptions.reduce(\n      (acc, curr) => ({\n        ...acc,\n        ...(curr.default != null ? { [curr.long]: curr.default } : null),\n      }),\n      {},\n    )\n\n    // set default options that are specific to module usage\n    const moduleDefaults: Options = {\n      jsonUpgraded: true,\n      silent: runOptions.silent || (runOptions.loglevel === undefined && !runOptions.verbose),\n      args: [],\n    }\n\n    runOptions = { ...cliDefaults, ...moduleDefaults, ...runOptions }\n  }\n\n  // convert packageData to string to convert RunOptions to Options\n  const options: Options = {\n    ...runOptions,\n    ...(runOptions.packageData && typeof runOptions.packageData !== 'string'\n      ? { packageData: JSON.stringify(runOptions.packageData, null, 2) as any }\n      : null),\n    cli,\n  }\n\n  // consolidate loglevel\n  const loglevel =\n    options.silent || options.format?.includes('lines') ? 'silent' : options.verbose ? 'verbose' : options.loglevel\n\n  const json = Object.keys(options)\n    .filter(option => option.startsWith('json'))\n    .some(propertyOf(options))\n\n  if (!json && loglevel !== 'silent' && options.rcConfigPath && !options.doctor) {\n    print(options, `Using config file ${options.rcConfigPath}`)\n  }\n\n  // warn about deprecated options\n  const deprecatedOptions = cliOptions.filter(\n    ({ long, deprecated }) =>\n      (deprecated && options[long as keyof Options]) ||\n      // special case to deprecate a value but not the entire option\n      (long === 'packageManager' && options.packageManager === 'staticRegistry'),\n  )\n  if (deprecatedOptions.length > 0) {\n    deprecatedOptions.forEach(({ long, description }) => {\n      const deprecationMessage =\n        long === 'packageManager'\n          ? '--packageManager staticRegistry is deprecated. Use --registryType json.'\n          : `--${long}: ${description}`\n      print(options, chalk.yellow(deprecationMessage), 'warn')\n    })\n    print(options, '', 'warn')\n  }\n\n  // validate options with predefined choices\n  cliOptions.forEach(({ long, choices }) => {\n    if (!choices || choices.length === 0) return\n    const value = options[long as keyof Options]\n    const values = Array.isArray(value) ? value : [value]\n    if (values.length === 0) return\n    // make sure the option value is valid\n    // if an array of values is given, make sure each one is a valid choice\n    if (values.every(value => !choices.includes(value))) {\n      programError(options, `Invalid option value: --${long} ${value}. Valid values are: ${choices.join(', ')}.`)\n    }\n  })\n\n  // validate options.cwd\n  if (options.cwd && !(await exists(options.cwd))) {\n    programError(options, `No such directory: ${options.cwd}`)\n  }\n\n  // trim filter args\n  // disallow non-matching filter and args\n  const args = parseFilterExpression(options.args)\n  const filter = parseFilterExpression(options.filter)\n  const filterVersion = parseFilterExpression(options.filterVersion)\n  const reject = parseFilterExpression(options.reject)\n  const rejectVersion = parseFilterExpression(options.rejectVersion)\n  const registryType = options.registryType || (options.registry?.endsWith('.json') ? 'json' : 'npm')\n\n  // convert to string for comparison purposes\n  // otherwise ['a b'] will not match ['a', 'b']\n  if (options.filter && args && !dequal(args.join(' '), Array.isArray(filter) ? filter.join(' ') : filter)) {\n    programError(\n      options,\n      'Cannot specify a filter using both --filter and args. Did you forget to quote an argument?\\nSee: https://github.com/raineorshine/npm-check-updates/issues/759#issuecomment-723587297',\n    )\n  }\n  // disallow packageFile and --deep\n  else if (options.packageFile && options.deep) {\n    programError(\n      options,\n      `Cannot specify both --packageFile and --deep. --deep is an alias for --packageFile '**/package.json'`,\n    )\n  }\n  // disallow --format lines and --jsonUpgraded\n  else if (options.format?.includes('lines') && options.jsonUpgraded) {\n    programError(options, 'Cannot specify both --format lines and --jsonUpgraded.')\n  } else if (options.format?.includes('lines') && options.jsonAll) {\n    programError(options, 'Cannot specify both --format lines and --jsonAll.')\n  } else if (options.format?.includes('lines') && options.format.length > 1) {\n    programError(options, 'Cannot use --format lines with other formatting options.')\n  }\n  // disallow --workspace and --workspaces\n  else if (options.workspace?.length && options.workspaces) {\n    programError(options, 'Cannot specify both --workspace and --workspaces.')\n  }\n  // disallow --workspace(s) and --deep\n  else if (options.deep && (options.workspace?.length || options.workspaces)) {\n    programError(options, `Cannot specify both --deep and --workspace${options.workspaces ? 's' : ''}.`)\n  }\n  // disallow --workspace(s) and --doctor\n  else if (options.doctor && (options.workspace?.length || options.workspaces)) {\n    programError(options, `Doctor mode is not currently supported with --workspace${options.workspaces ? 's' : ''}.`)\n  }\n  // disallow missing registry path when using registryType\n  else if (options.packageManager === 'staticRegistry' && !options.registry) {\n    programError(\n      options,\n      'When --package-manager staticRegistry is specified, you must provide the path for the registry file with --registry.',\n    )\n  } else if (options.registryType === 'json' && !options.registry) {\n    programError(\n      options,\n      'When --registryType json is specified, you must provide the path for the registry file with --registry. Run \"ncu --help registryType\" for details.',\n    )\n  } else if (registryType !== 'json' && options.registry && !isValidUrl(options.registry)) {\n    programError(options, `--registry must be a valid URL. Invalid value: \"${options.registry}\"`)\n  }\n\n  if (options.cooldown != null) {\n    // Normalize string formats (\"7d\", \"12h\", \"30m\") to a fractional number of days.\n    if (typeof options.cooldown === 'string') {\n      const days = parseCooldown(options.cooldown)\n      if (days === null) {\n        programError(\n          options,\n          `Invalid cooldown value: \"${options.cooldown}\". Use a number (days) or a string like \"7d\", \"12h\", or \"30m\".`,\n        )\n      } else {\n        options.cooldown = days\n      }\n    }\n\n    const isValidNumber = typeof options.cooldown === 'number' && !isNaN(options.cooldown) && options.cooldown >= 0\n    const isValidFunction = typeof options.cooldown === 'function'\n\n    if (!isValidNumber && !isValidFunction) {\n      programError(\n        options,\n        'Cooldown must be a non-negative number (days), a string like \"7d\", \"12h\", or \"30m\", or a predicate function.',\n      )\n    }\n  }\n\n  const target: Target = options.target || 'latest'\n\n  const autoPre = target === 'newest' || target === 'greatest'\n\n  const packageManager = await determinePackageManager(options)\n\n  const resolvedOptions: Options = {\n    ...options,\n    ...(options.deep ? { packageFile: '**/package.json' } : null),\n    ...(packageManager === 'deno' ? { dep: ['imports'] } : null),\n    ...(options.format && options.format.length > 0 ? { format: options.format } : null),\n    filter: args || filter,\n    filterVersion,\n    // add shortcut for any keys that start with 'json'\n    json,\n    loglevel,\n    minimal: options.minimal === undefined ? false : options.minimal,\n    // default to false, except when newest or greatest are set\n    // this is overridden on a per-dependency basis in queryVersions to allow prereleases to be upgraded to newer prereleases\n    ...(options.pre != null || autoPre ? { pre: options.pre != null ? !!options.pre : autoPre } : null),\n    reject,\n    rejectVersion,\n    target,\n    // imply upgrade in interactive mode when json is not specified as the output\n    ...(options.interactive && options.upgrade === undefined ? { upgrade: !json } : null),\n    packageManager,\n    ...(options.prefix\n      ? {\n          // use the npm prefix if the package manager does not define defaultPrefix\n          prefix: await (packageManagers[packageManager || '']?.defaultPrefix || packageManagers.npm.defaultPrefix!)(\n            options,\n          ),\n        }\n      : null),\n    registryType,\n  }\n  resolvedOptions.cacher = await cacher(resolvedOptions)\n\n  // remove undefined values\n  const resolvedOptionsFiltered: Options = keyValueBy(\n    resolvedOptions as { [key: string]: Options[keyof Options] },\n    (key, value) => (value !== undefined ? { [key]: value } : null),\n  )\n\n  // print 'Using yarn/pnpm/etc' when autodetected\n  // use resolved options so that options.json is set\n  if (!options.packageManager && packageManager !== 'npm') {\n    print(resolvedOptionsFiltered, `Using ${packageManager}`)\n  }\n\n  return resolvedOptionsFiltered\n}\n\nexport default initOptions\n"
  },
  {
    "path": "src/lib/isUpgradeable.ts",
    "content": "import * as semver from 'semver'\nimport semverutils from 'semver-utils'\nimport { Version } from '../types/Version'\nimport { VersionSpec } from '../types/VersionSpec'\nimport { fixPseudoVersion, isComparable, isWildCard, stringify } from './version-util'\n\n/**\n * Check if a version satisfies the latest, and is not beyond the latest). Ignores `v` prefix.\n *\n * @param current\n * @param latest\n * @param downgrade  Allow downgrading\n * @returns\n */\nfunction isUpgradeable(current: VersionSpec, latest: Version, { downgrade }: { downgrade?: boolean } = {}): boolean {\n  // do not upgrade non-npm version declarations (such as git tags)\n  // do not upgrade wildcards\n  if (!semver.validRange(current) || isWildCard(current)) {\n    return false\n  }\n\n  // remove the constraint (e.g. ^1.0.1 -> 1.0.1) to allow upgrades that satisfy the range, but are out of date\n  const [range] = semverutils.parseRange(current)\n  if (!range) {\n    throw new Error(\n      `\"${current}\" could not be parsed by semver-utils. This is probably a bug. Please file an issue at https://github.com/raineorshine/npm-check-updates.`,\n    )\n  }\n\n  // allow upgrading of pseudo versions such as \"v1\" or \"1.0\"\n  const latestNormalized = fixPseudoVersion(latest)\n\n  const version = stringify(range)\n  const isValidCurrent = Boolean(semver.validRange(version))\n  const isValidLatest = Boolean(semver.valid(latestNormalized))\n\n  // make sure it is a valid range\n  // not upgradeable if the latest version satisfies the current range\n  // not upgradeable if the specified version is newer than the latest (indicating a prerelease version)\n  // NOTE: When \"<\" is specified with a single digit version, e.g. \"<7\", and has the same major version as the latest, e.g. \"7\", isSatisfied(latest, version) will return true since it ignores the \"<\". In this case, test the original range (current) rather than the versionUtil output (version).\n  return (\n    isValidCurrent &&\n    isValidLatest &&\n    // allow an upgrade if two prerelease versions can't be compared by semver\n    (!isComparable(latestNormalized, version) ||\n      (!semver.satisfies(latestNormalized, range.operator === '<' ? current : version) &&\n        (downgrade || !semver.ltr(latestNormalized, version))))\n  )\n}\n\nexport default isUpgradeable\n"
  },
  {
    "path": "src/lib/keyValueBy.ts",
    "content": "import { Index } from '../types/IndexType'\n\ntype KeyValueGenerator<K, V, R> = (key: K, value: V, accum: Index<R>) => Index<R> | null\ntype ArrayKeyValueGenerator<T, R> = KeyValueGenerator<T, number, R>\ntype ObjectKeyValueGenerator<T, R> = KeyValueGenerator<string, T, R>\n\nexport function keyValueBy<T>(arr: T[]): Index<true>\nexport function keyValueBy<T, R>(arr: T[], keyValue: KeyValueGenerator<T, number, R>, initialValue?: Index<R>): Index<R>\nexport function keyValueBy<T, R>(\n  obj: Index<T>,\n  keyValue: KeyValueGenerator<string, T, R>,\n  initialValue?: Index<R>,\n): Index<R>\n\n/** Generates an object from an array or object. Simpler than reduce or _.transform. The KeyValueGenerator passes (key, value) if the input is an object, and (value, i) if it is an array. The return object from each iteration is merged into the accumulated object. Return null to skip an item. */\nexport function keyValueBy<T, R = true>(\n  input: T[] | Index<T>,\n  // if no keyValue is given, sets all values to true\n  keyValue?: ArrayKeyValueGenerator<T, R> | ObjectKeyValueGenerator<T, R>,\n  accum: Index<R> = {},\n): Index<R> {\n  const isArray = Array.isArray(input)\n  keyValue = keyValue || ((key: T): Index<R> => ({ [key as unknown as string]: true as unknown as R }))\n  // considerably faster than Array.prototype.reduce\n  Object.entries(input || {}).forEach(([key, value], i) => {\n    const o = isArray\n      ? (keyValue as ArrayKeyValueGenerator<T, R>)(value, i, accum)\n      : (keyValue as ObjectKeyValueGenerator<T, R>)(key, value, accum)\n    Object.entries(o || {}).forEach(entry => {\n      accum[entry[0]] = entry[1]\n    })\n  })\n\n  return accum\n}\n\nexport default keyValueBy\n"
  },
  {
    "path": "src/lib/libnpmconfig/LICENSE",
    "content": "Copyright npm, Inc\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
  },
  {
    "path": "src/lib/libnpmconfig/README.md",
    "content": "# libnpmconfig\n\n[![npm version](https://img.shields.io/npm/v/libnpmconfig.svg)](https://npm.im/libnpmconfig)\n[![license](https://img.shields.io/npm/l/libnpmconfig.svg)](https://npm.im/libnpmconfig)\n[![Travis](https://img.shields.io/travis/npm/libnpmconfig.svg)](https://travis-ci.org/npm/libnpmconfig)\n[![Coverage Status](https://coveralls.io/repos/github/npm/libnpmconfig/badge.svg?branch=latest)](https://coveralls.io/github/npm/libnpmconfig?branch=latest)\n\n[`libnpmconfig`](https://github.com/npm/libnpmconfig) is a Node.js library for\nprogrammatically managing npm's configuration files and data.\n\n## Table of Contents\n\n- [Example](#example)\n- [Install](#install)\n- [Contributing](#contributing)\n- [API](#api)\n\n## Example\n\n```js\nconst config = require('libnpmconfig')\n\nconsole.log(\n  'configured registry:',\n  config.read({\n    registry: 'https://default.registry/',\n  }),\n)\n// => configured registry: https://registry.npmjs.org\n```\n\n## Install\n\n`$ npm install libnpmconfig`\n\n### Contributing\n\nThe npm team enthusiastically welcomes contributions and project participation!\nThere's a bunch of things you can do if you want to contribute! The\n[Contributor Guide](https://github.com/npm/cli/blob/latest/CONTRIBUTING.md)\noutlines the process for community interaction and contribution. Please don't\nhesitate to jump in if you'd like to, or even ask us questions if something\nisn't clear.\n\nAll participants and maintainers in this project are expected to follow the\n[npm Code of Conduct](https://www.npmjs.com/policies/conduct), and just\ngenerally be excellent to each other.\n\nPlease refer to the [Changelog](CHANGELOG.md) for project history details, too.\n\nHappy hacking!\n\n### API\n\n#### `read(cliOpts, builtinOpts)`\n\nReads configurations from the filesystem and the env and returns a\n[`figgy-pudding`](https://npm.im/figgy-pudding) object with the configuration\nvalues.\n\nIf `cliOpts` is provided, it will be merged with the returned config pudding,\nshadowing any read values. These are intended as CLI-provided options. Do your\nown `process.argv` parsing, though.\n\nIf `builtinOpts.cwd` is provided, it will be used instead of `process.cwd()` as\nthe starting point for config searching.\n"
  },
  {
    "path": "src/lib/libnpmconfig/index.js",
    "content": "/*\n\nThis is a copy of the deprecated libnpmconfig library. It has been brought into the codebase to avoid deprecation warnings.\n\nhttps://github.com/npm/libnpmconfig\n\n*/\nimport findUp from 'find-up'\nimport ini from 'ini'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport figgyPudding from '../figgy-pudding'\n\nconst NpmConfig = figgyPudding(\n  {},\n  {\n    // Open up the pudding object.\n    other() {\n      return true\n    },\n  },\n)\n\nconst ConfigOpts = figgyPudding({\n  cache: { default: path.join(process.env.HOME || os.homedir(), '.npm') },\n  configNames: { default: ['npmrc', '.npmrc'] },\n  envPrefix: { default: /^npm_config_/i },\n  cwd: { default: () => process.cwd() },\n  globalconfig: {\n    default: () => path.join(getGlobalPrefix(), 'etc', 'npmrc'),\n  },\n  userconfig: { default: path.join(process.env.HOME || os.homedir(), '.npmrc') },\n})\n\n/** Gets the npm config. */\nfunction getNpmConfig(_opts, _builtin) {\n  const builtin = ConfigOpts(_builtin)\n  const env = {}\n  Object.keys(process.env).forEach(key => {\n    if (!key.match(builtin.envPrefix)) return\n    const newKey = key\n      .toLowerCase()\n      .replace(builtin.envPrefix, '')\n      .replace(/(?!^)_/g, '-')\n    env[newKey] = process.env[key]\n  })\n  const cli = NpmConfig(_opts)\n  const userConfPath = builtin.userconfig || cli.userconfig || env.userconfig\n  const user = userConfPath && maybeReadIni(userConfPath)\n  const globalConfPath = builtin.globalconfig || cli.globalconfig || env.globalconfig\n  const global = globalConfPath && maybeReadIni(globalConfPath)\n  const projConfPath = findUp.sync(builtin.configNames, { cwd: builtin.cwd })\n  let proj = {}\n  if (projConfPath && projConfPath !== userConfPath) {\n    proj = maybeReadIni(projConfPath)\n  }\n  const newOpts = NpmConfig(builtin, global, user, proj, env, cli)\n  if (newOpts.cache) {\n    return newOpts.concat({\n      cache: path.resolve(\n        cli.cache || env.cache\n          ? builtin.cwd\n          : proj.cache\n            ? path.dirname(projConfPath)\n            : user.cache\n              ? path.dirname(userConfPath)\n              : global.cache\n                ? path.dirname(globalConfPath)\n                : path.dirname(userConfPath),\n        newOpts.cache,\n      ),\n    })\n  } else {\n    return newOpts\n  }\n}\n\n/** Try to read the given ini file. */\nfunction maybeReadIni(f) {\n  let txt\n  try {\n    txt = fs.readFileSync(f, 'utf8')\n  } catch (err) {\n    if (err.code === 'ENOENT') {\n      return ''\n    } else {\n      throw err\n    }\n  }\n  return ini.parse(txt)\n}\n\n/** Get the global node PREFIX. */\nfunction getGlobalPrefix() {\n  if (process.env.PREFIX) {\n    return process.env.PREFIX\n  } else if (process.platform === 'win32') {\n    // c:\\node\\node.exe --> prefix=c:\\node\\\n    return path.dirname(process.execPath)\n  } else {\n    // /usr/local/bin/node --> prefix=/usr/local\n    let pref = path.dirname(path.dirname(process.execPath))\n    // destdir only is respected on Unix\n    if (process.env.DESTDIR) {\n      pref = path.join(process.env.DESTDIR, pref)\n    }\n    return pref\n  }\n}\n\nexport default getNpmConfig\n"
  },
  {
    "path": "src/lib/loadPackageInfoFromFile.ts",
    "content": "import fs from 'fs/promises'\nimport { Options } from '../types/Options'\nimport { PackageFile } from '../types/PackageFile'\nimport { PackageInfo } from '../types/PackageInfo'\nimport programError from './programError'\n\n/** Load and parse a package file. */\nconst loadPackageInfoFromFile = async (options: Options, filepath: string): Promise<PackageInfo> => {\n  let pkg: PackageFile, pkgFile: string\n\n  // assert package.json\n  try {\n    pkgFile = await fs.readFile(filepath, 'utf-8')\n    pkg = JSON.parse(pkgFile)\n  } catch (e) {\n    programError(options, `Missing or invalid ${filepath}`)\n  }\n\n  return {\n    name: undefined, // defined by workspace code only\n    pkg,\n    pkgFile,\n    filepath,\n  }\n}\n\nexport default loadPackageInfoFromFile\n"
  },
  {
    "path": "src/lib/logging.ts",
    "content": "/**\n * Loggin functions.\n */\nimport Table from 'cli-table3'\nimport fs from 'fs/promises'\nimport { IgnoredUpgradeDueToEnginesNode } from '../types/IgnoredUpgradeDueToEnginesNode'\nimport { IgnoredUpgradeDueToPeerDeps } from '../types/IgnoredUpgradeDueToPeerDeps'\nimport { Index } from '../types/IndexType'\nimport { Options } from '../types/Options'\nimport { VersionResult } from '../types/VersionResult'\nimport { VersionSpec } from '../types/VersionSpec'\nimport chalk from './chalk'\nimport filterObject from './filterObject'\nimport getPackageJson from './getPackageJson'\nimport getPackageVersion from './getPackageVersion'\nimport getRepoUrl from './getRepoUrl'\nimport {\n  colorizeDiff,\n  getDependencyGroups,\n  getGitHubUrlTag,\n  isGitHubUrl,\n  isNpmAlias,\n  parseNpmAlias,\n} from './version-util'\n\ntype LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'verbose' | 'silly' | null\n\n// maps string levels to numeric levels\nconst logLevels = {\n  silent: 0,\n  error: 1,\n  minimal: 2,\n  warn: 3,\n  info: 4,\n  verbose: 5,\n  silly: 6,\n}\n\n/** Returns true if the dependency spec is not fetchable from the registry and is ignored. */\nconst isFetchable = (spec: VersionSpec) =>\n  !spec.startsWith('file:') &&\n  !spec.startsWith('link:') &&\n  !spec.startsWith('workspace:') &&\n  // short github urls that are ignored, e.g. raineorshine/foo\n  !/^[^/:@]+\\/\\w+/.test(spec)\n\n/**\n * Prints a message if it is included within options.loglevel.\n *\n * @param options    Command line options. These will be compared to the loglevel parameter to determine if the message gets printed.\n * @param message    The message to print\n * @param loglevel   silent|error|warn|info|verbose|silly\n * @param method     The console method to call. Default: 'log'.\n */\nexport function print(\n  options: Options,\n  message: any,\n  loglevel: LogLevel = null,\n  method: 'log' | 'warn' | 'info' | 'error' = 'log',\n) {\n  // not in json mode\n  // not silent\n  // not at a loglevel under minimum specified\n  if (\n    !options.json &&\n    options.loglevel !== 'silent' &&\n    (loglevel == null ||\n      logLevels[(options.loglevel ?? 'warn') as unknown as keyof typeof logLevels] >= logLevels[loglevel])\n  ) {\n    console[method](message)\n  }\n}\n\n/** Pretty print a JSON object. */\nexport function printJson(options: Options, object: any) {\n  if (options.loglevel !== 'silent') {\n    console.log(JSON.stringify(object, null, 2))\n  }\n}\n\n/** Print JSON object keys as string joined by character. */\nexport function printSimpleJoinedString(object: any, join: string) {\n  console.log(\n    Object.keys(object)\n      .map(pkg => pkg + '@' + object[pkg])\n      .join(join),\n  )\n}\n\n/** Prints an object sorted by key. */\nexport function printSorted<T extends { [key: string]: any }>(options: Options, obj: T, loglevel: LogLevel) {\n  const sortedKeys = Object.keys(obj).sort() as (keyof T)[]\n  const objSorted = sortedKeys.reduce<T>((accum, key) => {\n    accum[key] = obj[key]\n    return accum\n  }, {} as T)\n  print(options, objSorted, loglevel)\n}\n\n/** Create a table with the appropriate columns and alignment to render dependency upgrades. */\nfunction renderDependencyTable(rows: string[][]) {\n  const table = new Table({\n    colAligns: ['left', 'right', 'right', 'right', 'left', 'left'],\n    chars: {\n      top: '',\n      'top-mid': '',\n      'top-left': '',\n      'top-right': '',\n      bottom: '',\n      'bottom-mid': '',\n      'bottom-left': '',\n      'bottom-right': '',\n      left: '',\n      'left-mid': '',\n      mid: '',\n      'mid-mid': '',\n      right: '',\n      'right-mid': '',\n      middle: '',\n    },\n  })\n\n  table.push(...rows)\n\n  // when border is removed, whitespace remains\n  // trim the end of each line to remove whitespace\n  // this makes no difference visually, but the whitespace interacts poorly with .editorconfig in tests\n  return table\n    .toString()\n    .split('\\n')\n    .map(line => line.trimEnd())\n    .join('\\n')\n}\n\n/**\n * Extract just the version number from a package.json dep\n *\n * @param dep Raw dependency, could be version / npm: string / Git url\n */\nfunction getVersion(dep: string): string {\n  return isGitHubUrl(dep) ? getGitHubUrlTag(dep)! : isNpmAlias(dep) ? parseNpmAlias(dep)![1] : dep\n}\n\n/**\n * Renders a color-coded table of upgrades.\n *\n * @param args\n * @param args.from\n * @param args.to\n * @param args.ownersChangedDeps\n * @param args.format\n */\nexport async function toDependencyTable({\n  from: fromDeps,\n  to: toDeps,\n  format,\n  ownersChangedDeps,\n  pkgFile,\n  time,\n}: {\n  from: Index<VersionSpec>\n  to: Index<VersionSpec>\n  format?: readonly string[]\n  ownersChangedDeps?: Index<boolean>\n  /** See: logging/getPackageRepo pkgFile param. */\n  pkgFile?: string\n  time?: Index<string>\n}) {\n  const pkg = format?.includes('dep') && pkgFile ? JSON.parse(await fs.readFile(pkgFile, 'utf-8')) : null\n  const table = renderDependencyTable(\n    await Promise.all(\n      Object.keys(toDeps)\n        .sort()\n        .map(async dep => {\n          const from =\n            (format?.includes('installedVersion')\n              ? await getPackageVersion(dep, undefined, { pkgFile })\n              : fromDeps[dep]) || ''\n          const depType =\n            dep in (pkg?.devDependencies ?? {})\n              ? 'dev'\n              : dep in (pkg?.peerDependencies ?? {})\n                ? 'peer'\n                : dep in (pkg?.optionalDependencies ?? {})\n                  ? 'optional'\n                  : ''\n          const toRaw = toDeps[dep] || ''\n          const to = getVersion(toRaw)\n          const ownerChanged = ownersChangedDeps\n            ? dep in ownersChangedDeps\n              ? ownersChangedDeps[dep]\n                ? '*owner changed*'\n                : ''\n              : '*unknown*'\n            : ''\n          const toColorized = colorizeDiff(getVersion(from), to)\n          const homepageUrl = format?.includes('homepage')\n            ? (await getPackageJson(dep, { pkgFile }))?.homepage || ''\n            : ''\n          const repoUrl = format?.includes('repo') ? (await getRepoUrl(dep, undefined, { pkgFile })) || '' : ''\n          const diffUrl = format?.includes('diff')\n            ? `${process.env.NCU_DIFF || 'https://npmdiff.dev'}/${encodeURIComponent(dep)}/${from.replace(/^\\W+/, '')}/${to.replace(/^\\W+/, '')}`\n            : ''\n          const publishTime = format?.includes('time') && time?.[dep] ? time[dep] : ''\n          return [\n            dep,\n            ...(format?.includes('dep') ? [depType ? chalk.gray(depType) : ''] : []),\n            from,\n            '→',\n            toColorized,\n            ownerChanged,\n            ...[homepageUrl, repoUrl, diffUrl, publishTime].filter(x => x),\n          ]\n        }),\n    ),\n  )\n  return table\n}\n\n/**\n * Renders one or more color-coded tables with all upgrades. Supports different formats from the --format option.\n *\n * @param args\n * @param args.current\n * @param args.upgraded\n * @param args.ownersChangedDeps\n * @param options\n */\nexport async function printUpgradesTable(\n  {\n    current,\n    upgraded,\n    ownersChangedDeps,\n    pkgFile,\n    time,\n  }: {\n    current: Index<VersionSpec>\n    upgraded: Index<VersionSpec>\n    ownersChangedDeps?: Index<boolean>\n    pkgFile?: string\n    time?: Index<string>\n  },\n  options: Options,\n) {\n  // group\n  if (options.format?.includes('group')) {\n    const groups = getDependencyGroups(upgraded, current, options)\n\n    for (const { heading, packages } of groups) {\n      print(options, '\\n' + heading)\n      print(\n        options,\n        await toDependencyTable({\n          from: current,\n          to: packages,\n          format: options.format,\n          ownersChangedDeps,\n          pkgFile,\n          time,\n        }),\n      )\n    }\n  } else {\n    if (options.format?.includes('lines')) {\n      printSimpleJoinedString(upgraded, '\\n')\n    } else {\n      print(\n        options,\n        await toDependencyTable({\n          from: current,\n          to: upgraded,\n          format: options.format,\n          ownersChangedDeps,\n          pkgFile,\n          time,\n        }),\n      )\n    }\n  }\n}\n\n/** Prints errors. */\nfunction printErrors(options: Options, errors?: Index<string>) {\n  if (!errors) return\n  if (Object.keys(errors).length > 0) {\n    const errorTable = new Table({\n      colAligns: ['left', 'right', 'right', 'right', 'left', 'left'],\n      chars: {\n        top: '',\n        'top-mid': '',\n        'top-left': '',\n        'top-right': '',\n        bottom: '',\n        'bottom-mid': '',\n        'bottom-left': '',\n        'bottom-right': '',\n        left: '',\n        'left-mid': '',\n        mid: '',\n        'mid-mid': '',\n        right: '',\n        'right-mid': '',\n        middle: '',\n      },\n    })\n\n    errorTable.push(...Object.entries(errors).map(([dep, error]) => [dep, chalk.yellow(error)]))\n\n    print(options, '\\n' + errorTable.toString())\n  }\n}\n\n/**\n * @param args.current -\n * @param args.latest -\n * @param args.upgraded -\n * @param args.total -\n * @param args.ownersChangedDeps -\n */\nexport async function printUpgrades(\n  options: Options,\n  {\n    current,\n    latest,\n    upgraded,\n    total,\n    ownersChangedDeps,\n    pkgFile,\n    time,\n    errors,\n  }: {\n    // Current package versions\n    current: Index<VersionSpec>\n    // Latest package versions according to the target. This is only used to detect an empty result from npm.\n    latest?: Index<VersionResult>\n    // Upgraded package specifications\n    upgraded: Index<VersionSpec>\n    // The total number of all possible upgrades. This is used to differentiate \"no dependencies\" from \"no upgrades\"\n    total: number\n    // Boolean flag per dependency which announces if package owner changed. Only used by --format ownerChanged\n    ownersChangedDeps?: Index<boolean>\n    // See: logging/getPackageRepo pkgFile param\n    pkgFile?: string\n    // Time published if options.format includes \"time\"\n    time?: Index<string>\n    // Any errors that were encountered when fetching versions.\n    errors?: Index<string>\n  },\n) {\n  if (!options.format?.includes('group')) {\n    print(options, '')\n  }\n\n  const smiley = chalk.green.bold(':)')\n  const numErrors = Object.keys(errors || {}).length\n  const target = typeof options.target === 'string' ? options.target : 'target'\n  const numUpgraded = Object.keys(upgraded).length\n  if (numUpgraded === 0 && total === 0 && numErrors === 0) {\n    if (Object.keys(current).length === 0) {\n      print(options, 'No dependencies.')\n    } else if (\n      latest &&\n      Object.keys(latest).length === 0 &&\n      // some specs are ignored by ncu, like the file: protocol, so they should be ignored when detecting fetch issues\n      Object.values(filterObject(current, (name, spec) => isFetchable(spec))).length > 0\n    ) {\n      print(\n        options,\n        `No package versions were returned. This may be a problem with your installed ${\n          options.packageManager\n        }, the npm registry, or your Internet connection. Make sure ${chalk.cyan(\n          'npx pacote packument ncu-test-v2',\n        )} is working before reporting an issue.`,\n      )\n    } else if (options.global) {\n      print(options, `All global packages are up-to-date ${smiley}`)\n    } else {\n      print(options, `All dependencies match the ${target} package versions ${smiley}`)\n    }\n  } else if (numUpgraded === 0 && total > 0) {\n    print(options, `No dependencies upgraded ${smiley}`)\n  }\n  // print table\n  else if (numUpgraded > 0) {\n    await printUpgradesTable(\n      {\n        current,\n        upgraded,\n        ownersChangedDeps,\n        pkgFile,\n        time,\n      },\n      options,\n    )\n  }\n\n  printErrors(options, errors)\n}\n\n/** Print updates that were ignored due to incompatible peer dependencies. */\nexport function printIgnoredUpdatesDueToPeerDeps(options: Options, ignoredUpdates: Index<IgnoredUpgradeDueToPeerDeps>) {\n  print(options, `\\nIgnored incompatible updates (peer dependencies):\\n`)\n  const table = renderDependencyTable(\n    Object.entries(ignoredUpdates).map(([pkgName, { from, to, reason }]) => {\n      const strReason =\n        'reason: ' +\n        Object.entries(reason)\n          .map(([pkgReason, requirement]) => pkgReason + ' requires ' + requirement)\n          .join(', ')\n      return [pkgName, from, '→', colorizeDiff(from, to), strReason]\n    }),\n  )\n  print(options, table)\n}\n\n/** Print updates that were ignored due to incompatible engines.node. */\nexport function printIgnoredUpdatesDueToEnginesNode(\n  options: Options,\n  ignoredUpdates: Index<IgnoredUpgradeDueToEnginesNode>,\n) {\n  print(options, `\\nIgnored incompatible updates (engines node):\\n`)\n  const table = renderDependencyTable(\n    Object.entries(ignoredUpdates).map(([pkgName, { from, to, enginesNode }]) => [\n      pkgName,\n      from,\n      '→',\n      colorizeDiff(from, to),\n      `reason: requires node ${enginesNode}`,\n    ]),\n  )\n  print(options, table)\n}\n"
  },
  {
    "path": "src/lib/mergeOptions.ts",
    "content": "import { Options } from '../types/Options'\n\ntype OptionKey = keyof Options\n\n/** Merges two arrays into one, removing duplicates. */\nfunction mergeArrays(arr1: any[], arr2: any[]) {\n  return Array.from(new Set([...(arr1 || []), ...(arr2 || [])]))\n}\n\n/**\n * Shallow merge (specific or all) properties.\n * If some properties both are arrays, then merge them also.\n */\nfunction mergeOptions(rawOptions1: Options | null, rawOptions2: Options | null) {\n  const options1: Options = rawOptions1 || {}\n  const options2: Options = rawOptions2 || {}\n  const result = { ...options1, ...options2 }\n  ;(Object.keys(result) as OptionKey[]).forEach(key => {\n    if (Array.isArray(options1[key]) && Array.isArray(options2[key])) {\n      result[key] = mergeArrays(options1[key] as any[], options2[key] as any[]) as any\n    }\n  })\n  return result\n}\n\nexport default mergeOptions\n"
  },
  {
    "path": "src/lib/parseCooldown.ts",
    "content": "/**\n * Parses a cooldown string (e.g. \"6d\", \"12h\", \"30m\") into a number of days.\n * Returns `null` if the string does not match a valid format.\n */\nfunction parseCooldown(s: string): number | null {\n  const match = s.match(/^(\\d+(?:\\.\\d+)?)(d|h|m)$/)\n  if (!match) return null\n\n  const value = parseFloat(match[1])\n  const unit = match[2]\n\n  if (unit === 'd') return value\n  if (unit === 'h') return value / 24\n  // unit === 'm'\n  return value / (24 * 60)\n}\n\nexport default parseCooldown\n"
  },
  {
    "path": "src/lib/pick.ts",
    "content": "/** Creates an object composed of the picked `object` properties. */\nexport function pick<T extends object, U extends keyof T>(obj: T, props: U[]): Pick<T, U> {\n  const newObject = {} as Pick<T, U>\n\n  for (const prop of props) {\n    newObject[prop] = obj[prop]\n  }\n\n  return newObject\n}\n\n/**\n * Creates an object composed of the `object` properties `predicate` returns\n * truthy for. The predicate is invoked with two arguments: (value, key).\n */\nexport function pickBy<R, K extends keyof R>(\n  object: R | null | undefined,\n  predicate: (value: R[K], key: keyof R) => any,\n): Record<K, R[K]> {\n  const newObject = {} as Record<K, R[K]>\n\n  for (const [key, value] of Object.entries<R[K]>(object ?? {})) {\n    const _key = key as K\n    if (predicate(value, _key)) {\n      newObject[_key] = value\n    }\n  }\n\n  return newObject\n}\n"
  },
  {
    "path": "src/lib/programError.ts",
    "content": "import { print } from '../lib/logging'\nimport { Options } from '../types/Options'\nimport chalk from './chalk'\n\n/** Print an error. Exit the process if in CLI mode. */\nfunction programError(\n  options: Options,\n  message: string,\n  {\n    color = true,\n  }: {\n    // defaults to true, which uses chalk.red on the whole error message.\n    // set to false to provide your own coloring.\n    color?: boolean\n  } = {},\n): never {\n  if (options.cli) {\n    print(options, color ? chalk.red(message) : message, null, 'error')\n    process.exit(1)\n  } else {\n    throw new Error(message)\n  }\n}\n\nexport default programError\n"
  },
  {
    "path": "src/lib/queryVersions.ts",
    "content": "import pMap from 'p-map'\nimport ProgressBar from 'progress'\nimport { parseRange } from 'semver-utils'\nimport packageManagers from '../package-managers'\nimport { GetVersion } from '../types/GetVersion'\nimport { Index } from '../types/IndexType'\nimport { Options } from '../types/Options'\nimport { supportedVersionTargets } from '../types/Target'\nimport { VersionResult } from '../types/VersionResult'\nimport { VersionSpec } from '../types/VersionSpec'\nimport getPackageManager from './getPackageManager'\nimport keyValueBy from './keyValueBy'\nimport programError from './programError'\nimport { createNpmAlias, isGitHubUrl, isPre, parseNpmAlias } from './version-util'\n\n/**\n * Get the latest or greatest versions from the NPM repository based on the version target.\n *\n * @param packageMap   An object whose keys are package name and values are current versions. May include npm aliases, i.e. { \"package\": \"npm:other-package@1.0.0\" }\n * @param [options={}] Options. Default: { target: 'latest' }.\n * @returns Promised {packageName: version} collection\n */\nasync function queryVersions(packageMap: Index<VersionSpec>, options: Options = {}): Promise<Index<VersionResult>> {\n  const { default: chalkDefault, Chalk } = await import('chalk')\n  const chalk = options.color ? new Chalk({ level: 1 }) : chalkDefault\n  const packageList = Object.keys(packageMap)\n  const globalPackageManager = getPackageManager(options, options.packageManager)\n\n  let bar: ProgressBar | undefined\n  if (!options.json && options.loglevel !== 'silent' && options.loglevel !== 'verbose' && packageList.length > 0) {\n    bar = new ProgressBar('[:bar] :current/:total :percent', { total: packageList.length, width: 20 })\n    bar.render()\n  }\n\n  /**\n   * Ignore 404 errors from getPackageVersion by having them return `null`\n   * instead of rejecting.\n   *\n   * @param dep\n   * @returns\n   */\n  async function getPackageVersionProtected(dep: VersionSpec): Promise<VersionResult> {\n    const npmAlias = parseNpmAlias(packageMap[dep])\n    const [name, version] = npmAlias || [dep, packageMap[dep]]\n    const targetOption = options.target || 'latest'\n    const targetString = typeof targetOption === 'string' ? targetOption : targetOption(name, parseRange(version))\n    const [target, distTag] = targetString.startsWith('@')\n      ? ['distTag', targetString.slice(1)]\n      : [targetString, 'latest']\n\n    const cached = options.cacher?.get(name, target)\n    if (cached) {\n      bar?.tick()\n\n      return {\n        version: cached,\n      }\n    }\n\n    let versionResult: VersionResult\n    const isGitHubDependency = isGitHubUrl(packageMap[dep])\n\n    // use gitTags package manager for git urls (for this dependency only)\n    const packageManager = isGitHubDependency ? packageManagers.gitTags : globalPackageManager\n    const packageManagerName = isGitHubDependency ? 'github urls' : options.packageManager || 'npm'\n\n    const getPackageVersion = packageManager[target as keyof typeof packageManager] as GetVersion\n\n    if (!getPackageVersion) {\n      const packageManagerSupportedVersionTargets = supportedVersionTargets.filter(t => t in packageManager)\n      programError(\n        options,\n        chalk.red(`\\nUnsupported target \"${target}\" using ${packageManagerName}`) +\n          `\\nSupported version targets are: ` +\n          packageManagerSupportedVersionTargets.join(', ') +\n          (!isGitHubDependency ? ', and tags (e.g. @next)' : ''),\n        { color: false },\n      )\n    }\n\n    try {\n      versionResult = await getPackageVersion(name, version, {\n        ...options,\n        distTag,\n        // upgrade prereleases to newer prereleases by default\n        // allow downgrading when explicit tag is used\n        pre: options.pre != null ? options.pre : targetString.startsWith('@') || isPre(version),\n        retry: options.retry ?? 2,\n      }).catch(reason => {\n        // This might happen if a (private) package cannot be accessed due to a missing or invalid token.\n        return { error: reason?.body?.error || reason.toString() }\n      })\n\n      versionResult.version =\n        !isGitHubDependency && npmAlias && versionResult?.version\n          ? createNpmAlias(name, versionResult.version)\n          : (versionResult?.version ?? null)\n    } catch (err: any) {\n      const errorMessage = err ? (err.message || err).toString() : ''\n      if (errorMessage.match(/E504|Gateway Timeout/i)) {\n        return {\n          error: `${errorMessage}. All ${options.retry} retry attempts failed.`,\n        }\n      } else if (errorMessage.match(/E400|E404|ENOTFOUND|404 Not Found|400 Bad Request/i)) {\n        return {\n          error: `${errorMessage.replace(/ - Not found$/i, '')}. All ${\n            options.retry\n          } retry attempts failed. Either your internet connection is down, the registry is inaccessible, the authentication credentials are invalid, or the package does not exist.`,\n        }\n      } else if (err.code === 'ERR_INVALID_URL') {\n        return {\n          error: errorMessage || 'Invalid URL',\n        }\n      } else {\n        // print a hint about the --timeout option for network timeout errors\n        if (!process.env.NCU_TESTS && /(Response|network) timeout/i.test(errorMessage)) {\n          console.error(\n            '\\n\\n' +\n              chalk.red(\n                'FetchError: Request Timeout. npm-registry-fetch defaults to 30000 (30 seconds). Try setting the --timeout option (in milliseconds) to override this.',\n              ) +\n              '\\n',\n          )\n        }\n\n        throw err\n      }\n    }\n\n    bar?.tick()\n\n    if (versionResult.version) {\n      options.cacher?.set(name, target, versionResult.version)\n    }\n\n    return versionResult\n  }\n\n  const versionResultList = await pMap(packageList, getPackageVersionProtected, { concurrency: options.concurrency })\n\n  // save cacher only after pMap handles cacher.set\n  await options.cacher?.save()\n  options.cacher?.log()\n\n  const versionResultObject = keyValueBy(versionResultList, (versionResult, i) =>\n    versionResult.version || versionResult.error\n      ? {\n          [packageList[i]]: versionResult,\n        }\n      : null,\n  )\n\n  return versionResultObject\n}\n\nexport default queryVersions\n"
  },
  {
    "path": "src/lib/resolveDepSections.ts",
    "content": "import { cliOptionsMap } from '../cli-options'\nimport { Index } from '../types/IndexType'\nimport { PackageFile } from '../types/PackageFile'\n\n// dependency section aliases that will be resolved to the full name\nconst depAliases: Index<keyof PackageFile> = {\n  dev: 'devDependencies',\n  peer: 'peerDependencies',\n  prod: 'dependencies',\n  optional: 'optionalDependencies',\n}\n\n/** Gets a list of dependency sections based on options.dep. */\nconst resolveDepSections = (dep?: string | readonly string[]): (keyof PackageFile)[] => {\n  // parse dep string and set default\n  const depOptions: string[] = dep ? (typeof dep === 'string' ? dep.split(',') : dep) : cliOptionsMap.dep.default\n\n  // map the dependency section option to a full dependency section name\n  const depSections = depOptions.map(name => depAliases[name] || name)\n\n  return depSections\n}\n\nexport default resolveDepSections\n"
  },
  {
    "path": "src/lib/runGlobal.ts",
    "content": "import { print, printJson, printSorted, printUpgrades } from '../lib/logging'\nimport { Index } from '../types/IndexType'\nimport { Options } from '../types/Options'\nimport chalk from './chalk'\nimport getInstalledPackages from './getInstalledPackages'\nimport { keyValueBy } from './keyValueBy'\nimport programError from './programError'\nimport upgradePackageDefinitions from './upgradePackageDefinitions'\n\n/** Checks global dependencies for upgrades. */\nasync function runGlobal(options: Options): Promise<Index<string> | void> {\n  print(options, '\\nOptions:', 'verbose')\n  printSorted(options, options, 'verbose')\n\n  print(options, '\\nGetting installed packages', 'verbose')\n  let globalPackages: Index<string> = {}\n  try {\n    const { cli, cwd, filter, filterVersion, global, packageManager, prefix, reject, rejectVersion } = options\n\n    globalPackages = await getInstalledPackages({\n      cli,\n      cwd,\n      filter,\n      filterVersion,\n      global,\n      packageManager,\n      prefix,\n      reject,\n      rejectVersion,\n    })\n  } catch (e: any) {\n    programError(options, e.message)\n  }\n\n  print(options, 'globalPackages:', 'verbose')\n  print(options, globalPackages, 'verbose')\n  print(options, '', 'verbose')\n  print(options, `Fetching ${options.target} versions`, 'verbose')\n\n  const [upgraded, latest] = await upgradePackageDefinitions(globalPackages, options)\n  print(options, latest, 'verbose')\n\n  const time = keyValueBy(latest, (key, result) => (result.time ? { [key]: result.time } : null))\n\n  const upgradedPackageNames = Object.keys(upgraded)\n  await printUpgrades(options, {\n    current: globalPackages,\n    upgraded,\n    latest,\n    total: upgradedPackageNames.length,\n    time,\n  })\n\n  const instruction = upgraded ? upgradedPackageNames.map(pkg => pkg + '@' + upgraded[pkg]).join(' ') : '[package]'\n\n  if (options.json) {\n    // since global packages do not have a package.json, return the upgraded deps directly (no version range replacements)\n    printJson(options, upgraded)\n  } else if (instruction.length) {\n    const upgradeCmd =\n      options.packageManager === 'yarn'\n        ? 'yarn global upgrade'\n        : options.packageManager === 'pnpm'\n          ? 'pnpm -g add'\n          : options.packageManager === 'bun'\n            ? 'bun add -g'\n            : 'npm -g install'\n\n    print(\n      options,\n      '\\n' +\n        chalk.cyan('ncu') +\n        ' itself cannot upgrade global packages. Run the following to upgrade all global packages: \\n\\n' +\n        chalk.cyan(`${upgradeCmd} ` + instruction) +\n        '\\n',\n    )\n  }\n\n  // if errorLevel is 2, exit with non-zero error code\n  if (options.cli && options.errorLevel === 2 && upgradedPackageNames.length > 0) {\n    process.exit(1)\n  }\n  return upgraded\n}\n\nexport default runGlobal\n"
  },
  {
    "path": "src/lib/runLocal.ts",
    "content": "import fs from 'fs/promises'\nimport prompts from 'prompts-ncu'\nimport nodeSemver from 'semver'\nimport { parseDocument } from 'yaml'\nimport { DependencyGroup } from '../types/DependencyGroup'\nimport { Index } from '../types/IndexType'\nimport { Maybe } from '../types/Maybe'\nimport { Options } from '../types/Options'\nimport { PackageFile } from '../types/PackageFile'\nimport { Version } from '../types/Version'\nimport { VersionSpec } from '../types/VersionSpec'\nimport chalk from './chalk'\nimport getCurrentDependencies from './getCurrentDependencies'\nimport { getIgnoredUpgradesDueToEnginesNode } from './getIgnoredUpgradesDueToEnginesNode'\nimport getIgnoredUpgradesDueToPeerDeps from './getIgnoredUpgradesDueToPeerDeps'\nimport getPackageManager from './getPackageManager'\nimport getPeerDependenciesFromRegistry from './getPeerDependenciesFromRegistry'\nimport keyValueBy from './keyValueBy'\nimport {\n  print,\n  printIgnoredUpdatesDueToEnginesNode,\n  printIgnoredUpdatesDueToPeerDeps,\n  printJson,\n  printSorted,\n  printUpgrades,\n  toDependencyTable,\n} from './logging'\nimport { pick } from './pick'\nimport programError from './programError'\nimport resolveDepSections from './resolveDepSections'\nimport upgradePackageData from './upgradePackageData'\nimport upgradePackageDefinitions from './upgradePackageDefinitions'\nimport parseJson from './utils/parseJson'\nimport { getDependencyGroups } from './version-util'\n\nconst INTERACTIVE_HINT = `\n  ↑/↓: Select a package\n  Space: Toggle selection\n  a: Toggle all\n  Enter: Upgrade`\n\n/**\n * Fetches how many options per page can be listed in the dependency table.\n *\n * @param groups - found dependency groups.\n * @returns the amount of options that can be displayed per page.\n */\nfunction getOptionsPerPage(showHint: boolean, groups?: DependencyGroup[]): number {\n  const hintRows = showHint ? INTERACTIVE_HINT.split('\\n').length : 0\n  return process.stdout.rows ? Math.max(3, process.stdout.rows - hintRows - 1 - (groups?.length ?? 0) * 2) : 50\n}\n\n/**\n * Return a promise which resolves to object storing package owner changed status for each dependency.\n *\n * @param fromVersion current packages version.\n * @param toVersion target packages version.\n * @param options\n * @returns\n */\nexport async function getOwnerPerDependency(fromVersion: Index<Version>, toVersion: Index<Version>, options: Options) {\n  const packageManager = getPackageManager(options, options.packageManager)\n  return await Object.keys(toVersion).reduce(\n    async (accum, dep) => {\n      const from = fromVersion[dep] || null\n      const to = toVersion[dep] || null\n      const ownerChanged = await packageManager.packageAuthorChanged!(dep, from!, to!, options)\n      return {\n        ...(await accum),\n        [dep]: ownerChanged,\n      }\n    },\n    {} as Promise<Index<boolean>>,\n  )\n}\n\n/** Prompts the user to choose which upgrades to upgrade. */\nconst chooseUpgrades = async (\n  oldDependencies: Index<string>,\n  newDependencies: Index<string>,\n  pkgFile: Maybe<string>,\n  options: Options,\n): Promise<Index<string>> => {\n  let chosenDeps: string[] = []\n\n  // Hide the interactive hint if the terminal is small.  This gives more space for the scrollable list of available updates\n  const showHint = process.stdout.rows > 18\n\n  // use toDependencyTable to create choices that are properly padded to align vertically\n  const table = await toDependencyTable({\n    from: oldDependencies,\n    to: newDependencies,\n    format: options.format,\n    pkgFile: pkgFile || undefined,\n  })\n\n  const formattedLines = keyValueBy(table.toString().split('\\n'), line => {\n    const dep = line.trim().split(' ')[0]\n    return {\n      [dep]: line.trim(),\n    }\n  })\n\n  // do not prompt if there are no dependencies\n  // prompts will crash if passed an empty list of choices\n  if (Object.keys(newDependencies).length > 0) {\n    print(options, '')\n\n    if (options.format?.includes('group')) {\n      const groups = getDependencyGroups(newDependencies, oldDependencies, options)\n\n      const choices = groups.flatMap(({ heading, groupName, packages }) => {\n        return [\n          { title: '\\n' + heading, heading: true },\n          ...Object.keys(packages)\n            .sort()\n            .map(dep => ({\n              title: formattedLines[dep],\n              value: dep,\n              selected: ['patch', 'minor'].includes(groupName),\n            })),\n        ]\n      })\n\n      const response = await prompts({\n        choices: [...choices, { title: ' ', heading: true }],\n        hint: showHint && INTERACTIVE_HINT,\n        instructions: false,\n        message: 'Choose which packages to update',\n        name: 'value',\n        optionsPerPage: getOptionsPerPage(showHint, groups),\n        type: 'multiselect',\n        onState: (state: any) => {\n          if (state.aborted) {\n            process.nextTick(() => process.exit(1))\n          }\n        },\n      })\n\n      chosenDeps = response.value\n    } else {\n      const choices = Object.keys(newDependencies)\n        .sort()\n        .map(dep => ({\n          title: formattedLines[dep],\n          value: dep,\n          selected: true,\n        }))\n\n      const response = await prompts({\n        choices: [...choices, { title: ' ', heading: true }],\n        hint: showHint && INTERACTIVE_HINT + '\\n',\n        instructions: false,\n        message: 'Choose which packages to update',\n        name: 'value',\n        optionsPerPage: getOptionsPerPage(showHint),\n        type: 'multiselect',\n        onState: (state: any) => {\n          if (state.aborted) {\n            process.nextTick(() => process.exit(1))\n          }\n        },\n      })\n\n      chosenDeps = response.value\n    }\n  }\n\n  return keyValueBy(chosenDeps, dep => ({ [dep]: newDependencies[dep] }))\n}\n\n/** Checks local project dependencies for upgrades. */\nexport default async function runLocal(\n  options: Options,\n  pkgData?: Maybe<string>,\n  pkgFile?: Maybe<string>,\n): Promise<PackageFile | Index<VersionSpec>> {\n  print(options, '\\nOptions:', 'verbose')\n  printSorted(options, options, 'verbose')\n\n  let pkg: PackageFile\n\n  try {\n    if (!pkgData) {\n      programError(options, 'Missing package data')\n    } else {\n      pkg = parseJson(pkgData)\n    }\n  } catch (e: any) {\n    programError(\n      options,\n      `Invalid package file${pkgFile ? `: ${pkgFile}` : ' from stdin'}. Error details:\\n${e.message}`,\n    )\n  }\n\n  const current = getCurrentDependencies(pkg, options)\n\n  print(options, '\\nCurrent versions:', 'verbose')\n  print(options, current, 'verbose')\n\n  if (options.enginesNode) {\n    options.nodeEngineVersion = pkg?.engines?.node\n  }\n\n  if (options.peer) {\n    options.peerDependencies = await getPeerDependenciesFromRegistry(\n      Object.fromEntries(\n        Object.entries(current).map(([packageName, versionSpec]) => {\n          return [\n            packageName,\n            // git urls and other non-semver versions are ignored.\n            // Make sure versionSpec is a valid semver range; otherwise, minVersion will throw.\n            nodeSemver.validRange(versionSpec)\n              ? (nodeSemver.minVersion(versionSpec)?.version ?? versionSpec)\n              : versionSpec,\n          ]\n        }),\n      ),\n      options,\n    )\n  }\n\n  const [upgraded, latestResults, upgradedPeerDependencies] = await upgradePackageDefinitions(current, options)\n  const latest = keyValueBy(latestResults, (key, result) => (result.version ? { [key]: result.version } : null))\n  const errors = keyValueBy(latestResults, (key, result) => (result.error ? { [key]: result.error } : null))\n  const time = keyValueBy(latestResults, (key, result) => (result.time ? { [key]: result.time } : null))\n\n  if (options.peer) {\n    print(options, '\\nupgradedPeerDependencies:', 'verbose')\n    print(options, upgradedPeerDependencies, 'verbose')\n  }\n\n  print(\n    options,\n    `\\n${\n      typeof options.target === 'string' ? `${options.target[0].toUpperCase()}${options.target.slice(1)}` : 'Fetched'\n    } versions:`,\n    'verbose',\n  )\n  print(options, latest, 'verbose')\n\n  print(options, '\\nUpgraded versions:', 'verbose')\n  print(options, upgraded, 'verbose')\n\n  // filter out satisfied deps when using --minimal\n  const filteredUpgraded = options.minimal\n    ? keyValueBy(upgraded, (dep, version) =>\n        !nodeSemver.satisfies(latest[dep], current[dep]) ? { [dep]: version } : null,\n      )\n    : upgraded\n\n  const ownersChangedDeps = (options.format || []).includes('ownerChanged')\n    ? await getOwnerPerDependency(current, filteredUpgraded, options)\n    : undefined\n\n  const chosenUpgraded = options.interactive\n    ? await chooseUpgrades(current, filteredUpgraded, pkgFile, options)\n    : filteredUpgraded\n\n  if (!options.json || options.deep) {\n    await printUpgrades(\n      // in interactive mode, do not group upgrades afterwards since the prompts are grouped\n      options.interactive\n        ? { ...options, format: (options.format || []).filter(formatType => formatType !== 'group') }\n        : options,\n      {\n        current,\n        upgraded: chosenUpgraded,\n        total: Object.keys(upgraded).length,\n        latest: latestResults,\n        ownersChangedDeps,\n        pkgFile: pkgFile || undefined,\n        errors,\n        time,\n      },\n    )\n    if (options.peer) {\n      const ignoredUpdates = await getIgnoredUpgradesDueToPeerDeps(\n        current,\n        upgraded,\n        upgradedPeerDependencies!,\n        options,\n      )\n      if (Object.keys(ignoredUpdates).length > 0) {\n        printIgnoredUpdatesDueToPeerDeps(options, ignoredUpdates)\n      }\n    }\n    if (options.enginesNode) {\n      const ignoredUpdates = await getIgnoredUpgradesDueToEnginesNode(current, upgraded, options)\n      if (Object.keys(ignoredUpdates).length > 0) {\n        printIgnoredUpdatesDueToEnginesNode(options, ignoredUpdates)\n      }\n    }\n  }\n\n  const newPkgData = await upgradePackageData(pkgData, current, chosenUpgraded, options, pkgFile || undefined)\n\n  const output: PackageFile | Index<VersionSpec> = options.jsonAll\n    ? pkgFile?.endsWith('.yaml') || pkgFile?.endsWith('.yml')\n      ? parseDocument(newPkgData).toJSON()\n      : (parseJson(newPkgData) as PackageFile)\n    : options.jsonDeps && pkgFile?.endsWith('.json')\n      ? pick(parseJson(newPkgData) as PackageFile, resolveDepSections(options.dep))\n      : chosenUpgraded\n\n  // will be overwritten with the result of fs.writeFile so that the return promise waits for the package file to be written\n  let writePromise\n\n  if (options.json && !options.deep) {\n    printJson(options, output)\n  }\n\n  if (Object.keys(filteredUpgraded).length > 0) {\n    // if there is a package file, write the new package data\n    // otherwise, suggest ncu -u\n    if (pkgFile) {\n      if (options.upgrade) {\n        // do not await until the end\n        writePromise = fs.writeFile(pkgFile.replace('#catalog', ''), newPkgData)\n      } else {\n        const ncuCmd = process.env.npm_lifecycle_event === 'npx' ? 'npx npm-check-updates' : 'ncu'\n        // quote arguments with spaces\n        const argv = process.argv\n          .slice(2)\n          .map(arg => (arg.includes(' ') ? `\"${arg}\"` : arg))\n          .join(' ')\n        const ncuOptions = argv ? ' ' + argv : argv\n        const upgradeHint = `\\nRun ${chalk.cyan(`${ncuCmd}${ncuOptions} -u`)} to upgrade ${\n          options.packageFile || 'package.json'\n        }`\n        print(options, upgradeHint)\n      }\n    }\n  }\n\n  await writePromise\n\n  return output\n}\n"
  },
  {
    "path": "src/lib/sortBy.ts",
    "content": "/**\n * Creates an array of elements, sorted in ascending order by the results of\n * running each element in a collection through each iteratee. This method\n * performs a stable sort, that is, it preserves the original sort order of\n * equal elements. The iteratees are invoked with one argument: (value).\n */\nexport function sortBy<T>(collection: T[] | null | undefined, selector: (item: T) => any): T[] {\n  if (!collection) return []\n  return collection\n    .map(item => ({ item, key: selector(item) }))\n    .sort((a, b) => (a.key > b.key ? 1 : a.key < b.key ? -1 : 0))\n    .map(({ item }) => item)\n}\n"
  },
  {
    "path": "src/lib/spawnCommand.ts",
    "content": "import { SpawnOptions } from 'child_process'\nimport spawn from 'spawn-please'\nimport { SpawnPleaseOptions } from '../types/SpawnPleaseOptions'\n\n/**\n * Spawn a command. On Windows, prefer `<command>.cmd` but fall back to `<command>` when the\n * `.cmd` shim is not available (e.g. mise, scoop).\n */\nasync function spawnCommand(\n  command: string,\n  args: string[],\n  spawnPleaseOptions?: SpawnPleaseOptions,\n  spawnOptions?: SpawnOptions,\n) {\n  if (process.platform !== 'win32' || command === 'bun') {\n    return spawn(command, args, spawnPleaseOptions, spawnOptions)\n  }\n\n  try {\n    return spawn(`${command}.cmd`, args, spawnPleaseOptions, spawnOptions)\n  } catch (e) {\n    if ((e as NodeJS.ErrnoException).code === 'ENOENT') {\n      return spawn(command, args, spawnPleaseOptions, spawnOptions)\n    }\n\n    throw e\n  }\n}\n\nexport default spawnCommand\n"
  },
  {
    "path": "src/lib/table.ts",
    "content": "import Table from 'cli-table3'\nimport wrap from './wrap'\n\n/** Wraps the second column in a list of 2-column cli-table rows. */\nconst wrapRows = (rows: string[][]) => rows.map(([col1, col2]) => [col1, wrap(col2)])\n\n/** Renders an HTML row. */\nconst row = (cells: string[]) => '\\n  <tr>' + cells.map(cell => `<td>${cell}</td>`).join('') + '</tr>'\n\n/** Renders a table for the CLI or markdown. */\nconst table = ({\n  colAligns,\n  markdown,\n  rows,\n}: {\n  colAligns?: ('left' | 'right')[]\n  markdown?: boolean\n  rows: string[][]\n}): string => {\n  // return HTML table for GitHub-flavored markdown\n  if (markdown) {\n    return `<table>${rows.map(row).join('')}\\n</table>`\n  }\n  // otherwise use cli-table3\n  else {\n    const t = new Table({ ...(colAligns ? { colAligns } : null) })\n    t.push(...(markdown ? rows : wrapRows(rows)))\n    return t.toString()\n  }\n}\n\nexport default table\n"
  },
  {
    "path": "src/lib/upgradeDependencies.ts",
    "content": "import flow from 'lodash/flow'\nimport { parseRange } from 'semver-utils'\nimport { Index } from '../types/IndexType'\nimport { Options } from '../types/Options'\nimport { Version } from '../types/Version'\nimport { VersionSpec } from '../types/VersionSpec'\nimport filterObject from './filterObject'\nimport getPreferredWildcard from './getPreferredWildcard'\nimport isUpgradeable from './isUpgradeable'\nimport { pickBy } from './pick'\nimport * as versionUtil from './version-util'\n\ninterface UpgradeSpec {\n  current: VersionSpec\n  currentParsed: VersionSpec | null\n  latest: Version\n  latestParsed: Version | null\n}\n\n/**\n * Upgrade a dependencies collection based on latest available versions. Supports npm aliases.\n *\n * @param currentDependencies current dependencies collection object\n * @param latestVersions latest available versions collection object\n * @param [options={}]\n * @returns upgraded dependency collection object\n */\nfunction upgradeDependencies(\n  currentDependencies: Index<VersionSpec | null>,\n  latestVersions: Index<Version>,\n  options: Options = {},\n): Index<VersionSpec> {\n  const targetOption = options.target || 'latest'\n\n  // filter out dependencies with empty values\n  currentDependencies = filterObject(currentDependencies, (key, value) => !!value)\n\n  // get the preferred wildcard and bind it to upgradeDependencyDeclaration\n  const wildcard = getPreferredWildcard(currentDependencies) || versionUtil.DEFAULT_WILDCARD\n\n  /** Upgrades a single dependency. */\n  const upgradeDep = (current: VersionSpec, latest: Version) =>\n    versionUtil.upgradeDependencyDeclaration(current, latest, {\n      wildcard,\n      removeRange: options.removeRange,\n    })\n\n  return flow([\n    // only include packages for which a latest version was fetched\n    (deps: Index<VersionSpec>): Index<VersionSpec> =>\n      pickBy(deps, (current, packageName) => packageName in latestVersions),\n    // unpack npm alias and git urls\n    (deps: Index<VersionSpec>): Index<UpgradeSpec> =>\n      Object.entries(deps).reduce<Index<UpgradeSpec>>((acc, [packageName, current]) => {\n        const latest = latestVersions[packageName]\n        let currentParsed = null\n        let latestParsed = null\n\n        // parse npm alias\n        if (versionUtil.isNpmAlias(current)) {\n          currentParsed = versionUtil.parseNpmAlias(current)![1]\n        }\n        if (versionUtil.isNpmAlias(latest)) {\n          latestParsed = versionUtil.parseNpmAlias(latest)![1]\n        }\n\n        // \"branch\" is also used for tags (refers to everything after the hash character)\n        if (versionUtil.isGitHubUrl(current)) {\n          const currentTag = versionUtil.getGitHubUrlTag(current)!\n          const [currentSemver] = parseRange(currentTag)\n          currentParsed = versionUtil.stringify(currentSemver)\n        }\n\n        if (versionUtil.isGitHubUrl(latest)) {\n          const latestTag = versionUtil.getGitHubUrlTag(latest)!\n          const [latestSemver] = parseRange(latestTag)\n          latestParsed = versionUtil.stringify(latestSemver)\n        }\n\n        acc[packageName] = { current, currentParsed, latest, latestParsed }\n        return acc\n      }, {}),\n    // pick the packages that are upgradeable\n    (deps: Index<UpgradeSpec>): Index<UpgradeSpec> =>\n      pickBy(deps, ({ current, currentParsed, latest, latestParsed }: UpgradeSpec, name) => {\n        // allow downgrades from prereleases when explicit tag is given\n        const downgrade: boolean =\n          versionUtil.isPre(current) &&\n          (typeof targetOption === 'string' ? targetOption : targetOption(name, parseRange(current))).startsWith('@')\n        return isUpgradeable(currentParsed || current, latestParsed || latest, { downgrade })\n      }),\n    // pack embedded versions: npm aliases and git urls\n    (deps: Index<UpgradeSpec>): Index<Version | null> =>\n      Object.entries(deps).reduce<Index<Version | null>>(\n        (acc, [packageName, { current, currentParsed, latest, latestParsed }]) => {\n          const upgraded = upgradeDep(currentParsed || current, latestParsed || latest)\n\n          acc[packageName] = versionUtil.isNpmAlias(current)\n            ? versionUtil.upgradeNpmAlias(current, upgraded)\n            : versionUtil.isGitHubUrl(current)\n              ? versionUtil.upgradeGitHubUrl(current, upgraded)\n              : upgraded\n          return acc\n        },\n        {},\n      ),\n  ])(currentDependencies)\n}\n\nexport default upgradeDependencies\n"
  },
  {
    "path": "src/lib/upgradeJsonCatalogDependencies.ts",
    "content": "import fs from 'fs/promises'\nimport { Index } from '../types/IndexType'\nimport { VersionSpec } from '../types/VersionSpec'\n\n/**\n * @returns String safe for use in `new RegExp()`\n */\nfunction escapeRegexp(s: string) {\n  return s.replace(/[-/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&')\n}\n\n/**\n * Upgrade catalog dependencies in a JSON file (e.g., package.json for Bun).\n */\nexport async function upgradeJsonCatalogDependencies(\n  filePath: string,\n  current: Index<VersionSpec>,\n  upgraded: Index<VersionSpec>,\n): Promise<string> {\n  const fileContent = await fs.readFile(filePath, 'utf-8')\n\n  // Use regex replacement to maintain JSON formatting\n  return Object.entries(upgraded)\n    .filter(([dep]) => current[dep])\n    .reduce((content, [dep, newVersion]) => {\n      const currentVersion = current[dep]\n\n      // Match catalog and catalogs sections in JSON (both top-level and within workspaces)\n      const catalogPattern = `(\"${escapeRegexp(dep)}\"\\\\s*:\\\\s*\")(${escapeRegexp(currentVersion)})(\")`\n      const catalogRegex = new RegExp(catalogPattern, 'g')\n\n      return content.replace(catalogRegex, `$1${newVersion}$3`)\n    }, fileContent)\n}\n"
  },
  {
    "path": "src/lib/upgradePackageData.ts",
    "content": "import fs from 'fs/promises'\nimport path from 'path'\nimport { parseDocument } from 'yaml'\nimport { CatalogsConfig } from '../types/CatalogConfig'\nimport { Index } from '../types/IndexType'\nimport { Options } from '../types/Options'\nimport { PackageFile } from '../types/PackageFile'\nimport { VersionSpec } from '../types/VersionSpec'\nimport resolveDepSections from './resolveDepSections'\nimport { upgradeJsonCatalogDependencies } from './upgradeJsonCatalogDependencies'\nimport { updateYamlCatalogDependencies } from './upgradeYamlCatalogDependencies'\n\n/**\n * @returns String safe for use in `new RegExp()`\n */\nfunction escapeRegexp(s: string) {\n  return s.replace(/[-/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&') // Thanks Stack Overflow!\n}\n\n/**\n * Upgrade the dependency declarations in the package data.\n *\n * @param pkgData The package.json data, as utf8 text\n * @param oldDependencies Old dependencies {package: range}\n * @param newDependencies New dependencies {package: range}\n * @param options Options object\n * @param pkgFile Optional path to the package file\n * @returns The updated package data, as utf8 text\n * @description Side Effect: prompts\n */\nasync function upgradePackageData(\n  pkgData: string,\n  current: Index<VersionSpec>,\n  upgraded: Index<VersionSpec>,\n  options: Options,\n  pkgFile?: string,\n) {\n  // Check if this is a catalog file (pnpm-workspace.yaml or package.json with catalogs)\n  if (pkgFile) {\n    const fileName = path.basename(pkgFile)\n    const fileExtension = path.extname(pkgFile)\n\n    // Handle synthetic catalog files (package.json#catalog format)\n    if (pkgFile.includes('#catalog')) {\n      // This is a synthetic catalog file, we need to read and update the actual file\n      const actualFilePath = pkgFile.replace('#catalog', '')\n      const actualFileExtension = path.extname(actualFilePath)\n\n      if (actualFileExtension === '.json') {\n        // Bun format: update package.json catalogs and return the updated content\n        return upgradeJsonCatalogDependencies(actualFilePath, current, upgraded)\n      }\n    }\n\n    // Handle yaml catalog files\n    if (fileName === 'pnpm-workspace.yaml' || fileName === '.yarnrc.yml') {\n      const yamlContent = await fs.readFile(pkgFile, 'utf-8')\n      const catalogData: CatalogsConfig = CatalogsConfig.parse(parseDocument(yamlContent).toJSON())\n\n      // Reconstruct the list of updates to apply unfortunately we lost the path information during extraction before\n      const reconstructedUpdates: { path: string[]; newValue: string }[] = []\n\n      if (catalogData.catalogs) {\n        Object.entries(catalogData.catalogs).forEach(([catalogName, catalog]) => {\n          Object.entries(upgraded).forEach(([dep, version]) => {\n            if (catalog[dep]) {\n              reconstructedUpdates.push({ path: ['catalogs', catalogName, dep], newValue: version })\n            }\n          })\n        })\n      }\n\n      if (catalogData.catalog) {\n        Object.entries(upgraded).forEach(([dep, version]) => {\n          if (catalogData.catalog?.[dep]) {\n            reconstructedUpdates.push({ path: ['catalog', dep], newValue: version })\n          }\n        })\n      }\n\n      let updatedContent = yamlContent\n      reconstructedUpdates.forEach(upgrade => {\n        const updatedYaml = updateYamlCatalogDependencies({\n          fileContent: updatedContent,\n          upgrade,\n          options,\n          filePath: pkgFile,\n        })\n        if (updatedYaml) {\n          updatedContent = updatedYaml\n        }\n      })\n\n      return updatedContent\n    }\n\n    // Handle package.json catalog files (check if content contains catalog/catalogs at root level or in workspaces)\n    if (fileExtension === '.json') {\n      const parsed = JSON.parse(pkgData)\n      const hasTopLevelCatalogs = parsed.catalog || parsed.catalogs\n      const hasWorkspacesCatalogs =\n        parsed.workspaces &&\n        !Array.isArray(parsed.workspaces) &&\n        (parsed.workspaces.catalog || parsed.workspaces.catalogs)\n\n      if (hasTopLevelCatalogs || hasWorkspacesCatalogs) {\n        return upgradeJsonCatalogDependencies(pkgFile, current, upgraded)\n      }\n    }\n  }\n\n  // Always include overrides since any upgraded dependencies needed to be upgraded in overrides as well.\n  // https://github.com/raineorshine/npm-check-updates/issues/1332\n  const depSections = [...resolveDepSections(options.dep), 'overrides']\n\n  // iterate through each dependency section\n  const sectionRegExp = new RegExp(`\"(${depSections.join(`|`)})\"s*:[^}]*`, 'g')\n  let newPkgData = pkgData.replace(sectionRegExp, section => {\n    // replace each upgraded dependency in the section\n    return Object.entries(upgraded).reduce((updatedSection, [dep]) => {\n      // const expression = `\"${dep}\"\\\\s*:\\\\s*\"(${escapeRegexp(current[dep])})\"`\n      const expression = `\"${dep}\"\\\\s*:\\\\s*(\"|{\\\\s*\".\"\\\\s*:\\\\s*\")(${escapeRegexp(current[dep])})\"`\n      const regExp = new RegExp(expression, 'g')\n      return updatedSection.replace(regExp, (match, child) => `\"${dep}${child ? `\": ${child}` : ': '}${upgraded[dep]}\"`)\n    }, section)\n  })\n\n  if (depSections.includes('packageManager')) {\n    const pkg = JSON.parse(pkgData) as PackageFile\n    if (pkg.packageManager) {\n      const [name] = pkg.packageManager.split('@')\n      if (upgraded[name]) {\n        newPkgData = newPkgData.replace(\n          /\"packageManager\"\\s*:\\s*\".*?@[^\"]*\"/,\n          `\"packageManager\": \"${name}@${upgraded[name]}\"`,\n        )\n      }\n    }\n  }\n\n  return newPkgData\n}\n\nexport default upgradePackageData\n"
  },
  {
    "path": "src/lib/upgradePackageDefinitions.ts",
    "content": "import { dequal } from 'dequal'\nimport { intersects, satisfies, validRange } from 'semver'\nimport { parse, parseRange } from 'semver-utils'\nimport { Index } from '../types/IndexType'\nimport { Options } from '../types/Options'\nimport { VersionResult } from '../types/VersionResult'\nimport { VersionSpec } from '../types/VersionSpec'\nimport getPeerDependenciesFromRegistry from './getPeerDependenciesFromRegistry'\nimport keyValueBy from './keyValueBy'\nimport { pickBy } from './pick'\nimport queryVersions from './queryVersions'\nimport upgradeDependencies from './upgradeDependencies'\n\ntype CheckIfInPeerViolationResult = {\n  violated: boolean\n  filteredUpgradedDependencies: Index<VersionSpec>\n  upgradedPeerDependencies: Index<Index<VersionSpec>>\n}\n\n/**\n * Check if the peer dependencies constraints of each upgraded package, are in violation,\n * thus rendering the upgrade to be invalid\n *\n * @returns Whether there was any violation, and the upgrades that are not in violation\n */\nconst checkIfInPeerViolation = (\n  currentDependencies: Index<VersionSpec>,\n  filteredUpgradedDependencies: Index<VersionSpec>,\n  upgradedPeerDependencies: Index<Index<VersionSpec>>,\n): CheckIfInPeerViolationResult => {\n  const upgradedDependencies = { ...currentDependencies, ...filteredUpgradedDependencies }\n  const filteredUpgradedDependenciesAfterPeers = pickBy(filteredUpgradedDependencies, (spec, dep) => {\n    const peerDeps = upgradedPeerDependencies[dep]\n    if (!peerDeps) {\n      return true\n    }\n    return Object.entries(peerDeps).every(\n      ([peer, peerSpec]) =>\n        upgradedDependencies[peer] === undefined ||\n        !validRange(peerSpec) ||\n        intersects(upgradedDependencies[peer], peerSpec),\n    )\n  })\n  const violated =\n    Object.keys(filteredUpgradedDependencies).length > Object.keys(filteredUpgradedDependenciesAfterPeers).length\n  let filteredUpgradedPeerDependencies = upgradedPeerDependencies\n  if (violated) {\n    filteredUpgradedPeerDependencies = pickBy(\n      upgradedPeerDependencies,\n      (spec, dep) => filteredUpgradedDependenciesAfterPeers[dep] || !filteredUpgradedDependencies[dep],\n    )\n  }\n  return {\n    violated,\n    filteredUpgradedDependencies: filteredUpgradedDependenciesAfterPeers,\n    upgradedPeerDependencies: filteredUpgradedPeerDependencies,\n  }\n}\n\nexport type UpgradePackageDefinitionsResult = [\n  upgradedDependencies: Index<VersionSpec>,\n  latestVersionResults: Index<VersionResult>,\n  newPeerDependencies?: Index<Index<VersionSpec>>,\n]\n\n/**\n * Returns a 3-tuple of upgradedDependencies, their latest versions and the resulting peer dependencies.\n *\n * @param currentDependencies\n * @param options\n * @returns\n */\nexport async function upgradePackageDefinitions(\n  currentDependencies: Index<VersionSpec>,\n  options: Options,\n): Promise<UpgradePackageDefinitionsResult> {\n  const latestVersionResults = await queryVersions(currentDependencies, options)\n\n  const latestVersions = keyValueBy(latestVersionResults, (dep, result) =>\n    result?.version &&\n    (!options.filterResults ||\n      options.filterResults(dep, {\n        currentVersion: currentDependencies[dep],\n        currentVersionSemver: parseRange(currentDependencies[dep]),\n        upgradedVersion: result.version,\n        upgradedVersionSemver: parse(result.version),\n      }))\n      ? {\n          [dep]: result.version,\n        }\n      : null,\n  )\n\n  const upgradedDependencies = upgradeDependencies(currentDependencies, latestVersions, options)\n\n  const filteredUpgradedDependencies = pickBy(upgradedDependencies, (v, dep) => {\n    return !options.jsonUpgraded || !options.minimal || !satisfies(latestVersions[dep], currentDependencies[dep])\n  })\n\n  const filteredLatestDependencies = pickBy(latestVersions, (spec, dep) => filteredUpgradedDependencies[dep])\n\n  let result: UpgradePackageDefinitionsResult = [\n    filteredUpgradedDependencies,\n    latestVersionResults,\n    options.peerDependencies,\n  ]\n\n  if (!options.peer || Object.keys(filteredLatestDependencies).length === 0) {\n    return result\n  }\n\n  if (options.peer && Object.keys(filteredLatestDependencies).length > 0) {\n    const upgradedPeerDependencies = await getPeerDependenciesFromRegistry(filteredLatestDependencies, options)\n\n    let checkPeerViolationResult: CheckIfInPeerViolationResult\n\n    if (\n      dequal(options.peerDependencies, {\n        ...options.peerDependencies,\n        ...upgradedPeerDependencies,\n      })\n    ) {\n      checkPeerViolationResult = checkIfInPeerViolation(\n        currentDependencies,\n        filteredUpgradedDependencies,\n        options.peerDependencies!,\n      )\n      if (!checkPeerViolationResult.violated) {\n        return result\n      }\n    } else {\n      checkPeerViolationResult = {\n        violated: false,\n        filteredUpgradedDependencies,\n        upgradedPeerDependencies,\n      }\n    }\n    let runCount = 0\n    do {\n      if (runCount++ > 6) {\n        throw new Error(`Stuck in a while loop. Please report an issue`)\n      }\n      const peerDependenciesAfterUpgrade = {\n        ...options.peerDependencies,\n        ...checkPeerViolationResult.upgradedPeerDependencies,\n      }\n      if (dequal(options.peerDependencies, peerDependenciesAfterUpgrade)) {\n        // We can't find anything to do, will not upgrade anything\n        return [{}, latestVersionResults, options.peerDependencies]\n      }\n      const [newUpgradedDependencies, newLatestVersions, newPeerDependencies] = await upgradePackageDefinitions(\n        { ...currentDependencies, ...checkPeerViolationResult.filteredUpgradedDependencies },\n        { ...options, peerDependencies: peerDependenciesAfterUpgrade, loglevel: 'silent' },\n      )\n      result = [\n        { ...checkPeerViolationResult.filteredUpgradedDependencies, ...newUpgradedDependencies },\n        { ...latestVersionResults, ...newLatestVersions },\n        newPeerDependencies,\n      ]\n      checkPeerViolationResult = checkIfInPeerViolation(currentDependencies, result[0], result[2]!)\n    } while (checkPeerViolationResult.violated)\n  }\n  return result\n}\n\nexport default upgradePackageDefinitions\n"
  },
  {
    "path": "src/lib/upgradeYamlCatalogDependencies.ts",
    "content": "import type { Document } from 'yaml'\nimport { CST, isCollection, isPair, isScalar, parseDocument } from 'yaml'\nimport { CatalogsConfig } from '../types/CatalogConfig'\nimport type { Options } from '../types/Options'\nimport programError from './programError'\n\ntype UpdateYamlCatalogDependenciesArgs = {\n  fileContent: string\n  upgrade: {\n    path: string[] // e.g., ['catalogs', 'my-catalog', 'my-dep'] or ['catalog', 'my-dep']\n    newValue: string // e.g., '^2.0.0'\n  }\n  options?: Options\n  filePath?: string\n}\n\n/** Throws a user-facing error for invalid YAML syntax. */\nfunction throwYamlSyntaxError(error: unknown, { options, filePath }: { options?: Options; filePath?: string }): never {\n  const details = error instanceof Error ? error.message : String(error)\n  const target = filePath ? ` in ${filePath}` : ''\n  const message = `Invalid YAML syntax${target}. Unable to read catalog dependencies.\\n${details}`\n\n  if (options) {\n    programError(options, message)\n  }\n\n  throw new Error(message)\n}\n\n/**\n * Change the scalar name and/or value of a collection item in a YAML document,\n * while keeping formatting consistent. Mutates the given document.\n *\n * Returns true when all requested updates were applied. Returns false when an\n * update could not be applied. The document may still be partially mutated when\n * false is returned (e.g. `newName` succeeds before `newValue` fails).\n */\nfunction changeDependencyIn(\n  document: Document,\n  path: string[],\n  { newName, newValue }: { newName?: string; newValue?: string },\n): boolean {\n  const parentPath = path.slice(0, -1)\n  const relevantItemKey = path.at(-1)\n\n  const parentNode = document.getIn(parentPath)\n\n  if (!parentNode || !isCollection(parentNode)) {\n    return false\n  }\n\n  const relevantNode = parentNode.items.find(\n    item => isPair(item) && isScalar(item.key) && item.key.value === relevantItemKey,\n  )\n\n  if (!relevantNode || !isPair(relevantNode)) {\n    return false\n  }\n\n  if (newName) {\n    /* the try..catch block above already throws if a key is an alias */\n    CST.setScalarValue(relevantNode.srcToken!.key!, newName)\n  }\n\n  if (newValue) {\n    // We only support scalar values when substituting. This explicitly avoids\n    // substituting aliases, since those can be resolved from a shared location,\n    // and replacing either the referrent anchor or the alias would be wrong in\n    // the general case. We leave this up to the user, e.g. via a Regex custom\n    // manager.\n    if (!CST.isScalar(relevantNode.srcToken?.value)) {\n      return false\n    }\n    CST.setScalarValue(relevantNode.srcToken.value, newValue)\n  }\n\n  return true\n}\n\n/**\n * Updates a dependency version in a PNPM/Yarn `catalog` or `catalogs` section.\n *\n * The function parses the YAML, validates it against `CatalogsConfig`, and\n * applies the change through CST tokens to preserve original formatting (such\n * as quotes, spacing, and comments) as much as possible.\n *\n * Returns the updated YAML string when the change succeeds. Returns the\n * original `fileContent` when the target dependency already has `newValue`.\n * Returns `null` when schema validation fails or when the target key/value\n * cannot be safely updated (for example, alias-based values). Throws on YAML\n * syntax errors and, when `options` is provided, reports them via\n * `programError`.\n */\nexport function updateYamlCatalogDependencies({\n  fileContent,\n  upgrade,\n  options,\n  filePath,\n}: UpdateYamlCatalogDependenciesArgs): string | null {\n  const { path } = upgrade\n\n  if (!(path.length > 1) && path[0] !== 'catalog' && path[0] !== 'catalogs') {\n    return null\n  }\n\n  const { newValue } = upgrade\n\n  let document: ReturnType<typeof parseDocument>\n  let parsedContents: CatalogsConfig\n\n  try {\n    // In order to preserve the original formatting as much as possible, we want\n    // manipulate the CST directly. Using the AST (the result of parseDocument)\n    // does not guarantee that formatting would be the same after\n    // stringification. However, the CST is more annoying to query for certain\n    // values. Thus, we use both an annotated AST and a JS representation; the\n    // former for manipulation, and the latter for querying/validation.\n    document = parseDocument(fileContent, { keepSourceTokens: true })\n  } catch (err) {\n    throwYamlSyntaxError(err, { options, filePath })\n  }\n\n  if (document.errors.length > 0) {\n    throwYamlSyntaxError(document.errors[0], { options, filePath })\n  }\n\n  try {\n    parsedContents = CatalogsConfig.parse(document.toJSON())\n  } catch {\n    return null\n  }\n\n  const oldVersion =\n    path[0] === 'catalog'\n      ? parsedContents.catalog?.[path[1]]\n      : parsedContents.catalogs?.[path[1]]\n        ? parsedContents.catalogs?.[path[1]][path[2]]\n        : undefined\n\n  if (oldVersion === newValue) {\n    return fileContent\n  }\n\n  const didModify = changeDependencyIn(document, path, {\n    newValue,\n    newName: upgrade.path.at(-1),\n  })\n\n  if (!didModify) {\n    // Case where we are explicitly unable to substitute the key/value, for\n    // example if the value was an alias.\n    return null\n  }\n\n  return CST.stringify(document.contents!.srcToken!)\n}\n"
  },
  {
    "path": "src/lib/utils/parseJson.ts",
    "content": "import { ParseError, ParseErrorCode, parse, stripComments } from 'jsonc-parser'\n\nconst stdoutColumns = process.stdout.columns || 80\n\n/**\n * Ensures the code line or a hint is always displayed for the code snippet.\n * If the line is empty, it outputs `<empty>`.\n * If the line is larger than a line of the terminal windows, it will cut it off. This also prevents too much\n * garbage data from being displayed.\n *\n * @param line - target line to check.\n * @returns either the hint or the actual line for the code snippet.\n */\nfunction ensureLineDisplay(line: string): string {\n  return `${line.length ? line.slice(0, Math.min(line.length, stdoutColumns)) : '<empty>'}\\n`\n}\n\n/**\n * Builds a marker line to point to the position of the found error.\n *\n * @param length - positions to the right of the error line.\n * @returns the marker line.\n */\nfunction getMarker(length: number): string {\n  return length > stdoutColumns ? '' : `${' '.repeat(length - 1)}^\\n`\n}\n\n/**\n * Builds a json code snippet to mark and contextualize the found error.\n * This snippet consists of 5 lines with the erroneous line in the middle.\n *\n * @param lines - all lines of the json file.\n * @param errorLine - erroneous line.\n * @param columnNumber - the error position inside the line.\n * @returns the entire code snippet.\n */\nfunction showSnippet(lines: string[], errorLine: number, columnNumber: number): string {\n  const len = lines.length\n  if (len === 0) return '<empty>'\n  if (len === 1) return `${ensureLineDisplay(lines[0])}${getMarker(columnNumber)}`\n  // Show an area of lines around the error line for a more detailed snippet.\n  const snippetEnd = Math.min(errorLine + 2, len)\n  let snippet = ''\n  for (let i = Math.max(errorLine - 2, 1); i <= snippetEnd; i++) {\n    // Lines in the output are counted starting from one, so choose the previous line\n    snippet += ensureLineDisplay(lines[i - 1])\n    if (i === errorLine) snippet += getMarker(columnNumber)\n  }\n  return `${snippet}\\n`\n}\n\n/**\n * Parses a json string, while also handling errors and comments.\n *\n * @param jsonString - target json string.\n * @returns the parsed json object.\n */\nexport default function parseJson(jsonString: string) {\n  jsonString = stripComments(jsonString)\n  try {\n    return JSON.parse(jsonString)\n  } catch {\n    const errors: ParseError[] = []\n    const json = parse(jsonString, errors)\n\n    // If no errors were found, just return the parsed json file\n    if (errors.length === 0) return json\n    let errorString = ''\n    const lines = jsonString.split('\\n')\n    for (const error of errors) {\n      const offset = error.offset\n      let lineNumber = 1\n      let columnNumber = 1\n      let currentOffset = 0\n      // Calculate line and column from the offset\n      for (const line of lines) {\n        if (currentOffset + line.length >= offset) {\n          columnNumber = offset - currentOffset + 1\n          break\n        }\n        currentOffset += line.length + 1 // +1 for the newline character\n        lineNumber++\n      }\n      // @ts-expect-error due to --isolatedModules forbidding to implement ambient constant enums.\n      errorString += `Error at line ${lineNumber}, column ${columnNumber}: ${ParseErrorCode[error.error]}\\n${showSnippet(lines, lineNumber, columnNumber)}\\n`\n    }\n    throw new SyntaxError(errorString)\n  }\n}\n"
  },
  {
    "path": "src/lib/version-util.ts",
    "content": "import propertyOf from 'lodash/propertyOf'\nimport parseGitHubUrl from 'parse-github-url'\nimport semver from 'semver'\nimport semverutils, { SemVer, parse, parseRange } from 'semver-utils'\nimport util from 'util'\nimport { DependencyGroup } from '../types/DependencyGroup'\nimport { Index } from '../types/IndexType'\nimport { Maybe } from '../types/Maybe'\nimport { Options } from '../types/Options'\nimport { UpgradeGroup } from '../types/UpgradeGroup'\nimport { VersionLevel } from '../types/VersionLevel'\nimport chalk from './chalk'\nimport { keyValueBy } from './keyValueBy'\nimport { sortBy } from './sortBy'\n\ntype VersionPart = keyof SemVer\n\nconst VERSION_BASE_PARTS = ['major', 'minor', 'patch'] as VersionPart[]\nconst VERSION_ADDED_PARTS = ['release', 'build'] as VersionPart[]\nconst VERSION_PARTS = [...VERSION_BASE_PARTS, ...VERSION_ADDED_PARTS] as VersionPart[]\nconst VERSION_PART_DELIM: SemVer = {\n  major: '',\n  minor: '.',\n  patch: '.',\n  release: '-',\n  build: '+',\n}\nexport const DEFAULT_WILDCARD = '^'\nexport const WILDCARDS = ['^', '~', '.*', '.x']\nconst WILDCARDS_PURE = ['^', '~', '^*', '*', 'x', 'x.x', 'x.x.x']\nconst WILDCARD_PURE_REGEX = new RegExp(`^(${WILDCARDS_PURE.join('|').replace(/\\^/g, '\\\\^').replace(/\\*/g, '\\\\*')})$`)\n\n/** Matches an npm alias version declaration. */\nconst NPM_ALIAS_REGEX = /^npm:(.*)@(.*)/\n\ninterface UpgradeOptions {\n  wildcard?: string\n  removeRange?: boolean\n}\n\n/**\n * @param version\n * @returns The number of parts in the version\n */\nexport function numParts(version: string) {\n  const [semver] = semverutils.parseRange(version)\n\n  if (!semver) {\n    throw new Error(\n      util.format(\n        'semverutils.parseRange returned null when trying to parse \"%s\". This is probably a problem with the \"semver-utils\" dependency. Please report an issue at https://github.com/raineorshine/npm-check-updates/issues.',\n        version,\n      ),\n    )\n  }\n\n  return VERSION_PARTS.reduce((count, part) => (semver[part] ? count + 1 : count), 0)\n}\n\n/**\n * Increases or decreases precision by the given amount, e.g. major+1 -> minor\n *\n * @param precision\n * @param n\n * @returns\n */\nexport function precisionAdd(precision: VersionPart, n: number) {\n  if (n === 0) return precision\n\n  const index = VERSION_BASE_PARTS.includes(precision)\n    ? VERSION_BASE_PARTS.indexOf(precision) + n\n    : VERSION_ADDED_PARTS.includes(precision)\n      ? VERSION_BASE_PARTS.length + n\n      : null\n\n  if (index === null || !VERSION_PARTS[index]) {\n    throw new Error(`Invalid precision: ${precision}`)\n  }\n\n  return VERSION_PARTS[index]\n}\n\n/**\n * Joins the major, minor, patch, release, and build parts (controlled by an\n * optional precision arg) of a semver object into a dot-delimited string.\n *\n * @param semver\n * @param [precision]\n * @returns\n */\nexport function stringify(semver: SemVer, precision?: VersionPart) {\n  // get a list of the parts up until (and including) the given precision\n  // or all of them, if no precision is specified\n  const parts = precision ? VERSION_PARTS.slice(0, VERSION_PARTS.indexOf(precision) + 1) : VERSION_PARTS\n\n  // pair each part with its delimiter and join together\n  return parts\n    .filter(part => (precision && VERSION_BASE_PARTS.includes(precision)) || semver[part])\n    .map(part => VERSION_PART_DELIM[part] + (semver[part] || '0'))\n    .join('')\n}\n\n/**\n * Gets how precise this version number is (major, minor, patch, release, or build)\n *\n * @param version\n * @returns\n */\nexport function getPrecision(version: string) {\n  const [semver] = semverutils.parseRange(version)\n  // expects VERSION_PARTS to be in correct order\n  return VERSION_PARTS.slice().reverse().find(propertyOf(semver))\n}\n\n/**\n * Sets the precision of a (loose) semver to the specified level: major, minor, etc.\n *\n * @param version\n * @param [precision]\n * @returns\n */\nexport function setPrecision(version: string, precision: VersionPart) {\n  const [semver] = semverutils.parseRange(version)\n  return stringify(semver, precision)\n}\n\n/**\n * Adds a given wildcard (^,~,.*,.x) to a version number. Adds ^ and ~ to the\n * beginning. Replaces everything after the major version number with .* or .x\n *\n * @param version\n * @param wildcard\n * @returns\n */\nexport function addWildCard(version: string, wildcard: string) {\n  return wildcard === '^' || wildcard === '~' ? wildcard + version : setPrecision(version, 'major') + wildcard\n}\n\n/**\n * Returns true if the given string is one of the wild cards.\n *\n * @param version\n * @returns\n */\nexport function isWildCard(version: string) {\n  return WILDCARD_PURE_REGEX.test(version)\n}\n\n/**\n * Returns true if the given digit is a wildcard for a part of a version.\n *\n * @param versionPart\n * @returns\n */\nexport function isWildPart(versionPartValue: Maybe<string>) {\n  return versionPartValue === '*' || versionPartValue === 'x'\n}\n\n/**\n * Determines the part of a version string that has changed when comparing two versions. Assumes that the two version strings are in the same format. Returns null if no parts have changed.\n *\n * @param from\n * @param to\n */\nexport function partChanged(from: string, to: string): UpgradeGroup {\n  if (from === to) return 'none'\n\n  // separate out leading ^ or ~\n  if (/^[~^]/.test(to) && to[0] === from[0]) {\n    to = to.slice(1)\n    from = from.slice(1)\n  }\n\n  // split into parts\n  const partsTo = to.split('.')\n  const partsFrom = from.split('.')\n\n  let i = partsTo.findIndex((partto, i) => partto !== partsFrom[i])\n  i = i >= 0 ? i : partsTo.length\n\n  // major = red (or any change before 1.0.0)\n  // minor = cyan\n  // patch = green\n  return partsTo[0] === '0' ? 'majorVersionZero' : i === 0 ? 'major' : i === 1 ? 'minor' : 'patch'\n}\n\n/**\n * Returns a list of group heading and a map of package names and versions.\n * Used with --format group and takes into account the custom --group function.\n */\nexport function getDependencyGroups(\n  newDependencies: Index<string>,\n  oldDependencies: Index<string>,\n  options: Options,\n): DependencyGroup[] {\n  const groups = keyValueBy<string, Index<string>>(newDependencies, (dep, to, accum) => {\n    const from = oldDependencies[dep]\n    const defaultGroup = partChanged(from, to)\n    const userDefinedUpgradeGroup =\n      options.groupFunction?.(dep, defaultGroup, parseRange(from), parseRange(to), parse(newDependencies[dep])) ??\n      defaultGroup\n    if (userDefinedUpgradeGroup === 'none') {\n      return accum\n    }\n    return {\n      ...accum,\n      [userDefinedUpgradeGroup]: {\n        ...accum[userDefinedUpgradeGroup],\n        [dep]: to,\n      },\n    }\n  })\n\n  // get the text for the default group headings\n  const headings = {\n    patch: chalk.green(chalk.bold('Patch') + '   Backwards-compatible bug fixes'),\n    minor: chalk.cyan(chalk.bold('Minor') + '   Backwards-compatible features'),\n    major: chalk.red(chalk.bold('Major') + '   Potentially breaking API changes'),\n    majorVersionZero: chalk.magenta(chalk.bold('Major version zero') + '   Anything may change'),\n  }\n\n  const groupOrder = Array.from(new Set(['patch', 'minor', 'major', 'majorVersionZero', ...Object.keys(groups).sort()]))\n\n  return groupOrder\n    .filter(groupName => {\n      return groupName in groups\n    })\n    .map(groupName => {\n      return {\n        groupName,\n        heading: groupName in headings ? headings[groupName as keyof typeof headings] : groupName,\n        packages: groups[groupName],\n      }\n    })\n}\n\n/**\n * Colorize the parts of a version string (to) that are different than\n * another (from). Assumes that the two version strings are in the same format.\n *\n * @param from\n * @param to\n * @returns\n */\nexport function colorizeDiff(from: string, to: string) {\n  let leadingWildcard = ''\n\n  // separate out leading ^ or ~\n  if (/^[~^]/.test(to) && to[0] === from[0]) {\n    leadingWildcard = to[0]\n    to = to.slice(1)\n    from = from.slice(1)\n  }\n\n  // split into parts\n  const partsToColor = to.split('.')\n  const partsToCompare = from.split('.')\n\n  let i = partsToColor.findIndex((part, i) => part !== partsToCompare[i])\n  i = i >= 0 ? i : partsToColor.length\n\n  // major = red (or any change before 1.0.0)\n  // minor = cyan\n  // patch = green\n  const color = i === 0 || partsToColor[0] === '0' ? 'red' : i === 1 ? 'cyan' : 'green'\n\n  // if we are colorizing only part of the word, add a dot in the middle\n  const middot = i > 0 && i < partsToColor.length ? '.' : ''\n\n  return leadingWildcard + partsToColor.slice(0, i).join('.') + middot + chalk[color](partsToColor.slice(i).join('.'))\n}\n\n/**\n * Extract prerelease tag, omitting build number\n * Example: 1.0.0-next.alpha.2 -> next.alpha\n *\n * @param version\n */\nconst getPre = (version: string) => {\n  const pre = semver.prerelease(version)\n  return pre && pre.slice(0, -1).join('.')\n}\n\n/**\n * Check if it is allowed to compare two versions based on their prerelease tag\n *\n * SemVer both states that different prerelease versions can't be compared\n * and at the same time compares them as part of the version via strcmp\n *\n * @param a\n * @param b\n * @returns True if two versions can be compared by the means of SemVer\n */\nexport function isComparable(a: string, b: string) {\n  const preA = getPre(a)\n  const preB = getPre(b)\n  return typeof preA !== 'string' || typeof preB !== 'string' || preA === preB\n}\n\n/** Comparator used to sort semver versions */\nexport function compareVersions(a: string, b: string) {\n  const isValid = semver.valid(a) && semver.valid(b)\n  const isGreater = isValid ? semver.gt(a, b) : a > b\n  return isGreater ? 1 : a === b ? 0 : -1\n}\n\n/**\n * Finds the greatest version at the given level (minor|patch).\n *\n * @param versions  Unsorted array of all available versions\n * @param current   Current version or range\n * @param level     major|minor\n * @returns         String representation of the suggested version.\n */\nexport function findGreatestByLevel(versions: string[], current: string, level: VersionLevel): string | null {\n  if (!semver.validRange(current)) {\n    return null\n  }\n\n  const cur = semver.minVersion(current)\n  const versionsSorted = [...versions].sort(compareVersions).filter(v => {\n    const parsed = semver.parse(v)\n    return (\n      parsed &&\n      (level === 'major' || parsed.major === cur?.major) &&\n      (level === 'major' || level === 'minor' || parsed.minor === cur?.minor)\n    )\n  })\n\n  return versionsSorted.at(-1) || null\n}\n\n/**\n * @param version\n * @returns True if the version is any kind of prerelease: alpha, beta, rc, pre\n */\nexport function isPre(version: string) {\n  return getPrecision(version) === 'release'\n}\n\n/** Checks if a string is a simple version in the format \"v1\". */\nconst isMissingMinorAndPatch = (s: string) => /^[vV]?\\d+$/.test(s)\n\n/** Checks if a version string is missing its match component, e.g. \"1.0\". */\nconst isMissingPatch = (s: string) => /^[vV]?\\d+\\.\\d+$/.test(s)\n\n/** Removes a leading 'v' or 'V' from a pseudo version.. */\nconst fixLeadingV = (s: string) => s.replace(/^[vV]/, '')\n\n/** Converts a pseudo version that is missing its minor and patch components into a valid semver version. NOOP for valid semver versions. */\nconst fixMissingMinorAndPatch = (s: string) => (isMissingMinorAndPatch(s) ? s + '.0.0' : s)\n\n/** Converts a pseudo version that is missing its patch component into a valid semver version. NOOP for valid semver versions. */\nconst fixMissingPatch = (s: string) => (isMissingPatch(s) ? s + '.0' : s)\n\n/** Converts a pseudo version into a valid semver version. NOOP for valid semver versions. */\nexport const fixPseudoVersion = (s: string) => fixMissingPatch(fixMissingMinorAndPatch(fixLeadingV(s)))\n\n/**\n * Returns 'v' if the string starts with a v; otherwise, returns empty string.\n *\n * @param str\n * @returns\n */\nexport function v(str: Maybe<string>) {\n  return str && (str[0] === 'v' || str[1] === 'v') ? 'v' : ''\n}\n\n/**\n * Constructs an npm alias from the name and version of the actual package.\n *\n * @param name Name of the actual package.\n * @param version Version of the actual package.\n * @returns    \"npm:package@x.y.z\"\n * @example    createNpmAlias('chalk', '2.0.0') -> 'npm:chalk@2.0.0'\n */\nexport const createNpmAlias = (name: string, version: string) => `npm:${name}@${version}`\n\n/**\n * Parses an npm alias into a [name, version] 2-tuple.\n *\n * @returns  [name, version] or null if the input is not an npm alias\n * @example  'npm:chalk@1.0.0' -> ['chalk', '1.0.0']\n */\nexport const parseNpmAlias = (alias: string) => {\n  const match = alias && alias.match && alias.match(NPM_ALIAS_REGEX)\n  return match && match.slice(1)\n}\n\n/**\n * Returns true if a version declaration is an npm alias.\n */\nexport const isNpmAlias = (declaration: string) => declaration && !!declaration.match(NPM_ALIAS_REGEX)\n\n/**\n * Replaces the version number embedded in an npm alias.\n */\nexport const upgradeNpmAlias = (declaration: string, upgraded: string) => {\n  const npmAlias = parseNpmAlias(declaration)\n  if (!npmAlias) return null\n  return createNpmAlias(npmAlias[0], upgraded)\n}\n\n/**\n * Returns true if a version declaration is a GitHub URL with a valid semver version.\n */\nexport const isGitHubUrl = (declaration: string | null) => {\n  if (!declaration) return false\n  let parsed = null\n  try {\n    parsed = parseGitHubUrl(declaration)\n  } catch {\n    // Strings like `npm:postman-request@2.88.1-postman.33` can throw errors instead of simply returning null\n    // In node 18.17+ due to url.parse regression: https://github.com/nodejs/node/issues/49330\n    // So if this throws, we can assume it's not a valid GitHub URL.\n  }\n  if (!parsed || !parsed.branch) return false\n\n  const version = decodeURIComponent(parsed.branch).replace(/^semver:/, '')\n  return !!semver.validRange(version)\n}\n\n/**\n * Returns the embedded tag in a GitHub URL.\n */\nexport const getGitHubUrlTag = (declaration: string | null) => {\n  if (!declaration) return null\n  const parsed = parseGitHubUrl(declaration)\n  if (!parsed || !parsed.branch) return null\n  const version = decodeURIComponent(parsed.branch).replace(/^semver:/, '')\n  return parsed && parsed.branch && semver.validRange(version) ? version : null\n}\n\n/**\n * Upgrade an existing dependency declaration to satisfy the latest version.\n *\n * @param declaration Current version declaration (e.g. \"1.2.x\")\n * @param latestVersion Latest version (e.g \"1.3.2\")\n * @param [options={}]\n * @returns The upgraded dependency declaration (e.g. \"1.3.x\")\n */\nexport function upgradeDependencyDeclaration(\n  declaration: string,\n  latestVersion: string | null,\n  options: UpgradeOptions = {},\n) {\n  options.wildcard = options.wildcard || DEFAULT_WILDCARD\n\n  if (!latestVersion) {\n    return declaration\n  }\n\n  // parse the latestVersion\n  // return original declaration if latestSemver is invalid\n  const [latestSemver] = semverutils.parseRange(latestVersion)\n  if (!latestSemver) {\n    return declaration\n  }\n\n  // return global wildcards immediately\n  if (options.removeRange) {\n    return latestVersion\n  } else if (isWildCard(declaration)) {\n    return declaration\n  }\n\n  // parse the declaration\n  // if multiple ranges, use the semver with the least number of parts\n  const parsedRange = sortBy(\n    semverutils.parseRange(declaration).filter(range => range.operator !== '||' && range.operator !== '-'),\n    s => numParts(stringify(s)),\n  ) as SemVer[]\n\n  const [declaredSemver] = parsedRange\n\n  /**\n   * Chooses version parts between the declared version and the latest.\n   * Base parts (major, minor, patch) are only included if they are in the original declaration.\n   * Added parts (release, build) are always included. They are only present if we are checking --greatest versions\n   * anyway.\n   */\n  function chooseVersion(part: VersionPart): string | null {\n    return (\n      (isWildPart(declaredSemver[part])\n        ? declaredSemver[part]\n        : VERSION_BASE_PARTS.includes(part) && declaredSemver[part]\n          ? latestSemver[part]\n          : VERSION_ADDED_PARTS.includes(part)\n            ? latestSemver[part]\n            : null) || null\n    )\n  }\n\n  // create a new semver object with major, minor, patch, build, and release parts\n  const newSemver = keyValueBy(VERSION_PARTS, (part: VersionPart) => ({\n    [part]: chooseVersion(part),\n  }))\n  const newSemverString = stringify(newSemver)\n  const version = v(declaredSemver.semver) + newSemverString\n\n  // determine the operator\n  // do not compact, because [undefined, '<'] must be differentiated from ['<']\n  const uniqueOperators = Array.from(new Set(parsedRange.map(range => range.operator)))\n  const operator = uniqueOperators[0] || ''\n\n  const hasWildCard = WILDCARDS.some(wildcard => newSemverString.includes(wildcard))\n  const isLessThanOrEqual = uniqueOperators[0] === '<' || uniqueOperators[0] === '<='\n  const isGreaterThan = uniqueOperators[0] === '>'\n  const isMixed = uniqueOperators.length > 1\n\n  // convert versions with </<= or mixed operators into the preferred wildcard\n  // only do so if the new version does not already contain a wildcard\n  return !hasWildCard && (isLessThanOrEqual || isMixed)\n    ? addWildCard(version, options.wildcard)\n    : // convert > to >= since there are likely no available versions > latest\n      // https://github.com/raineorshine/npm-check-updates/issues/957\n      (isGreaterThan ? '>=' : operator) + version\n}\n\n/** Reverts a valid semver version to a pseudo version. NOOP If the original version was a valid semver version. */\nconst revertPseudoVersion = (current: string, latest: string) => {\n  /** Reverts a valid semver version to a pseudo version with a leading 'v'. NOOP If the original version was a valid semver version. */\n  const leadingV = v(current)\n  let result = leadingV ? leadingV + latest : latest\n\n  /** Reverts a valid semver version to a pseudo version that is missing its minor and patch components. NOOP If the original version was a valid semver version. */\n  const missingMinorAndPatch = isMissingMinorAndPatch(current)\n  result = missingMinorAndPatch ? result.slice(0, result.length - '.0.0'.length) : result\n\n  /** Reverts a valid semver version to a pseudo version that is missing its patch components. NOOP If the original version was a valid semver version. */\n  const missingPatch = isMissingPatch(current)\n  result = missingPatch ? result.slice(0, result.length - '.0'.length) : result\n\n  return result\n}\n\n/**\n * Replaces the version number embedded in a GitHub URL.\n */\nexport const upgradeGitHubUrl = (declaration: string, upgraded: string) => {\n  // convert upgraded to a proper semver version if it is a pseudo version; otherwise, revertPseudoVersion will return an empty string\n  const upgradedNormalized = fixPseudoVersion(upgraded)\n  const parsedUrl = parseGitHubUrl(declaration)\n  if (!parsedUrl) return declaration\n  const tag = decodeURIComponent(parsedUrl.branch).replace(/^semver:/, '')\n  return declaration.replace(tag, upgradeDependencyDeclaration(tag, revertPseudoVersion(tag, upgradedNormalized)))\n}\n"
  },
  {
    "path": "src/lib/wrap.ts",
    "content": "/** Wraps a string by inserting newlines every n characters. Wraps on word break. Default: 92 chars. */\nconst wrap = (s: string, maxLineLength = 92) => {\n  const linesIn = s.split('\\n')\n  const linesOut: string[] = []\n  linesIn.forEach(lineIn => {\n    let i = 0\n    if (lineIn.length === 0) {\n      linesOut.push('')\n      return\n    }\n\n    while (i < lineIn.length) {\n      const lineFull = lineIn.slice(i, i + maxLineLength + 1)\n\n      // if the line is within the line length, push it as the last line and break\n      const lineTrimmed = lineFull.trimEnd()\n      if (lineTrimmed.length <= maxLineLength) {\n        linesOut.push(lineTrimmed)\n        break\n      }\n\n      // otherwise, wrap before the last word that exceeds the wrap length\n      // do not wrap in the middle of a word\n      // reverse the string and use match to find the first non-word character to wrap on\n      const wrapOffset =\n        lineFull\n          .split('')\n          .reverse()\n          .join('')\n          // add [^\\W] to not break in the middle of --registry\n          .match(/[ -][^\\W]/)?.index || 0\n      const line = lineFull.slice(0, lineFull.length - wrapOffset)\n\n      // make sure we do not end up in an infinite loop\n      if (line.length === 0) break\n\n      linesOut.push(line.trimEnd())\n      i += line.length\n    }\n    i = 0\n  })\n  return linesOut.join('\\n').trim()\n}\n\nexport default wrap\n"
  },
  {
    "path": "src/package-managers/README.md",
    "content": "# Package Managers\n\n## How to add a new package manager\n\nTo add support for another package manager, drop in a module with the following interface.\n\n```js\n{\n  *list: (npmOptions: {}) => Promise<{ name: version }>,\n  *latest: (pkgName: string) => Promise<String> version,\n  newest: (pkgName: string) => Promise<String> version,\n  greatest: (pkgName: string) => Promise<String> version,\n  minor: (pkgName: string, String currentVersion) => Promise<String> version,\n  patch: (pkgName: string, String currentVersion) => Promise<String> version,\n}\n```\n\n- `list` and `latest` are required.\n- Methods corresponding to other `--target` values are optional.\n- Methods are expected to reject with `'404 Not Found'` if the package is not found.\n"
  },
  {
    "path": "src/package-managers/bun.ts",
    "content": "import path from 'path'\nimport spawn from 'spawn-please'\nimport keyValueBy from '../lib/keyValueBy'\nimport { Index } from '../types/IndexType'\nimport { NpmOptions } from '../types/NpmOptions'\nimport { Options } from '../types/Options'\nimport { SpawnPleaseOptions } from '../types/SpawnPleaseOptions'\n\n/** Spawn bun. */\nasync function spawnBun(\n  args: string | string[],\n  npmOptions: NpmOptions = {},\n  spawnPleaseOptions: SpawnPleaseOptions = {},\n  spawnOptions: Index<any> = {},\n): Promise<{ stdout: string; stderr: string }> {\n  const fullArgs = [\n    ...(npmOptions.global ? ['--global'] : []),\n    ...(npmOptions.prefix ? [`--prefix=${npmOptions.prefix}`] : []),\n    ...(Array.isArray(args) ? args : [args]),\n  ]\n\n  return spawn('bun', fullArgs, spawnPleaseOptions, spawnOptions)\n}\n\n/** Returns the global directory of bun. */\nexport const defaultPrefix = async (options: Options): Promise<string | undefined> =>\n  options.global\n    ? options.prefix || process.env.BUN_INSTALL || path.dirname((await spawn('bun', ['pm', '-g', 'bin'])).stdout)\n    : undefined\n\n/**\n * (Bun) Fetches the list of all installed packages.\n */\nexport const list = async (options: Options = {}): Promise<Index<string | undefined>> => {\n  const { default: stripAnsi } = await import('strip-ansi')\n\n  // bun pm ls\n  const { stdout } = await spawnBun(\n    ['pm', 'ls'],\n    {\n      ...(options.global ? { global: true } : null),\n      ...(options.prefix ? { prefix: options.prefix } : null),\n    },\n    {\n      rejectOnError: false,\n    },\n    {\n      env: {\n        ...process.env,\n        // Disable color to ensure the output is parsed correctly.\n        // However, this may be ineffective in some environments (see stripAnsi below).\n        // https://bun.sh/docs/runtime/configuration#environment-variables\n        NO_COLOR: '1',\n      },\n      ...(options.cwd ? { cwd: options.cwd } : null),\n    },\n  )\n\n  // Parse the output of `bun pm ls` into an object { [name]: version }.\n  // When bun is spawned in the GitHub Actions environment, it outputs ANSI color. Unfortunately, it does not respect the `NO_COLOR` environment variable. Therefore, we have to manually strip ansi.\n  const lines = stripAnsi(stdout).split('\\n')\n  const dependencies = keyValueBy(lines, line => {\n    // The capturing group for the package name requires a + quantifier; otherwise, namespaced packages like @angular/cli will not be captured correctly.\n    const match = line.match(/.* (.+?)@(.+)/)\n    if (match) {\n      const [, name, version] = match\n      return { [name]: version }\n    }\n    return null\n  })\n\n  return dependencies\n}\n\nexport {\n  distTag,\n  getEngines,\n  getPeerDependencies,\n  greatest,\n  latest,\n  minor,\n  newest,\n  packageAuthorChanged,\n  patch,\n  semver,\n} from './npm'\n\nexport default spawnBun\n"
  },
  {
    "path": "src/package-managers/filters.ts",
    "content": "import semver from 'semver'\nimport parseCooldown from '../lib/parseCooldown'\nimport * as versionUtil from '../lib/version-util'\nimport type { CooldownFunction } from '../types/CooldownFunction'\nimport type { Index } from '../types/IndexType'\nimport type { Maybe } from '../types/Maybe'\nimport type { Options } from '../types/Options'\nimport type { Packument } from '../types/Packument'\nimport type { Version } from '../types/Version'\n\n/**\n * @param versionResult  Available version\n * @param options     Options\n * @returns         True if deprecated versions are allowed or the version is not deprecated\n */\nexport function allowDeprecatedOrIsNotDeprecated(versionResult: Partial<Packument>, options: Options): boolean {\n  return options.deprecated || !versionResult.deprecated\n}\n\n/**\n * @param versionResult  Available version\n * @param options     Options\n * @returns         True if pre-releases are allowed or the version is not a pre-release\n */\nexport function allowPreOrIsNotPre(versionResult: Partial<Packument>, options: Options): boolean {\n  if (options.pre) return true\n  return !versionResult.version || !versionUtil.isPre(versionResult.version)\n}\n\n/**\n * Returns true if the node engine requirement is satisfied or not specified for a given package version.\n *\n * @param versionResult     Version object returned by packument.\n * @param nodeEngineVersion The value of engines.node in the package file.\n * @returns                 True if the node engine requirement is satisfied or not specified.\n */\nexport function satisfiesNodeEngine(versionResult: Partial<Packument>, nodeEngineVersion: Maybe<string>): boolean {\n  if (!nodeEngineVersion) return true\n  const minVersion = semver.minVersion(nodeEngineVersion)?.version\n  if (!minVersion) return true\n  const versionNodeEngine: string | undefined = versionResult?.engines?.node\n  return !versionNodeEngine || semver.satisfies(minVersion, versionNodeEngine)\n}\n\n/**\n * Returns true if the peer dependencies requirement is satisfied or not specified for a given package version.\n *\n * @param versionResult     Version object returned by packument.\n * @param peerDependencies  The list of peer dependencies.\n * @returns                 True if the peer dependencies are satisfied or not specified.\n */\nexport function satisfiesPeerDependencies(versionResult: Partial<Packument>, peerDependencies: Index<Index<Version>>) {\n  if (!peerDependencies) return true\n  return Object.values(peerDependencies).every(\n    peers =>\n      peers[versionResult.name!] === undefined || semver.satisfies(versionResult.version!, peers[versionResult.name!]),\n  )\n}\n\n/**\n * Determines if a package version satisfies the specified cooldown period.\n *\n * @param versionResult - Partial packument object containing version and release time information.\n * @param cooldownDays - The cooldown period in days. If not specified or invalid, the function returns true.\n * @returns `true` if the version's release date is older than the cooldown period; otherwise, `false`.\n */\nexport function satisfiesCooldownPeriod(\n  versionResult: Partial<Packument>,\n  cooldownDaysOrPredicateFn: Maybe<number> | Maybe<CooldownFunction>,\n): boolean {\n  const version = versionResult.version\n  const versionTimeData = versionResult?.time?.[version!]\n\n  if (!cooldownDaysOrPredicateFn) return true\n  if (!versionTimeData) return false\n\n  const versionReleaseDate = new Date(versionTimeData)\n  const DAY_AS_MS = 86400000 // milliseconds in a day\n  const rawCooldown =\n    typeof cooldownDaysOrPredicateFn === 'function'\n      ? (cooldownDaysOrPredicateFn(versionResult.name!) ?? 0) // null → 0 days = no cooldown\n      : cooldownDaysOrPredicateFn\n  const cooldownDays = typeof rawCooldown === 'string' ? (parseCooldown(rawCooldown) ?? 0) : rawCooldown\n\n  return Date.now() - versionReleaseDate.getTime() >= cooldownDays * DAY_AS_MS\n}\n\n/** Returns a composite predicate that filters out deprecated, prerelease, and node engine incompatibilities from version objects returns by packument. */\nexport function filterPredicate(options: Options) {\n  const predicates: (((o: Partial<Packument>) => boolean) | null)[] = [\n    o => allowDeprecatedOrIsNotDeprecated(o, options),\n    o => allowPreOrIsNotPre(o, options),\n    options.enginesNode ? o => satisfiesNodeEngine(o, options.nodeEngineVersion) : null,\n    options.peerDependencies ? o => satisfiesPeerDependencies(o, options.peerDependencies!) : null,\n    options.cooldown ? o => satisfiesCooldownPeriod(o, options.cooldown as number | CooldownFunction) : null,\n  ]\n\n  return (o: Partial<Packument>) => predicates.every(predicate => (predicate ? predicate(o) : true))\n}\n"
  },
  {
    "path": "src/package-managers/gitTags.ts",
    "content": "/** Fetches package metadata from GitHub tags. */\nimport childProcess from 'node:child_process'\nimport { promisify } from 'node:util'\nimport parseGitHubUrl from 'parse-github-url'\nimport { valid } from 'semver'\nimport { print } from '../lib/logging'\nimport * as versionUtil from '../lib/version-util'\nimport { GetVersion } from '../types/GetVersion'\nimport { Index } from '../types/IndexType'\nimport { Options } from '../types/Options'\nimport { VersionLevel } from '../types/VersionLevel'\nimport { VersionResult } from '../types/VersionResult'\nimport { VersionSpec } from '../types/VersionSpec'\n\nconst execFile = promisify(childProcess.execFile)\n\n/**\n * Fetches and extracts all git tags from a git url.\n *\n * @param url - url to a github repository.\n * @returns the extracted git tags.\n */\nasync function getGitTags(url: string): Promise<Index<string>> {\n  const out = (await execFile('git', ['ls-remote', '--tags', url])).stdout\n  const tags: Index<string> = {}\n  for (const line of out.trim().split('\\n')) {\n    const splitted = line.split('\\t')\n    tags[splitted[1].replace(/^refs\\/tags\\/|\\^{}$/g, '')] = splitted[0]\n  }\n  return tags\n}\n\n/** Gets remote versions sorted. */\nasync function getSortedVersions(\n  name: string,\n  declaration: VersionSpec,\n  options?: Options,\n): Promise<string[] | undefined> {\n  // if present, github: is parsed as the protocol. This is not valid when passed into remote-git-tags.\n  declaration = declaration.replace(/^github:/, '')\n  const { auth, protocol, host, path } = parseGitHubUrl(declaration)!\n  let tags: Index<string>\n\n  try {\n    if (protocol !== null) {\n      tags = await getGitTags(\n        `${protocol ? protocol.replace('git+', '') : 'https:'}//${auth ? auth + '@' : ''}${host}/${path?.replace(/^:/, '')}`,\n      )\n    } else {\n      try {\n        tags = await getGitTags(`ssh://git@${host}/${path?.replace(/^:/, '')}`)\n      } catch {\n        tags = await getGitTags(`https://${auth ? auth + '@' : ''}${host}/${path}`)\n      }\n    }\n  } catch (e) {\n    // catch a variety of errors that occur on invalid or private repos\n    print(options ?? {}, `Invalid, private repo, or no tags for ${name}: ${declaration}`, 'verbose')\n    return\n  }\n\n  return (\n    Object.keys(tags)\n      .map(versionUtil.fixPseudoVersion)\n      // do not pass semver.valid reference directly since the mapping index will be interpreted as the loose option\n      // https://github.com/npm/node-semver#functions\n      .filter(tag => valid(tag))\n      .sort(versionUtil.compareVersions)\n  )\n}\n\n/** Return the highest non-prerelease numbered tag on a remote Git URL. */\nexport const latest: GetVersion = async (name: string, declaration: VersionSpec, options?: Options) => {\n  const versions = await getSortedVersions(name, declaration, options)\n  if (!versions) return { version: null }\n  const versionsFiltered = options?.pre ? versions : versions.filter(v => !versionUtil.isPre(v))\n  const latestVersion = versionsFiltered[versionsFiltered.length - 1]\n  return { version: latestVersion ? versionUtil.upgradeGitHubUrl(declaration, latestVersion) : null }\n}\n\n/** Return the highest numbered tag on a remote Git URL. */\nexport const greatest: GetVersion = async (name: string, declaration: VersionSpec, options?: Options) => {\n  const versions = await getSortedVersions(name, declaration, options)\n  if (!versions) return { version: null }\n  const greatestVersion = versions[versions.length - 1]\n  return { version: greatestVersion ? versionUtil.upgradeGitHubUrl(declaration, greatestVersion) : null }\n}\n\n/** Returns a function that returns the highest version at the given level. */\nexport const greatestLevel =\n  (level: VersionLevel) =>\n  async (name: string, declaration: VersionSpec, options: Options = {}): Promise<VersionResult> => {\n    const version = decodeURIComponent(parseGitHubUrl(declaration)!.branch).replace(/^semver:/, '')\n    const versions = await getSortedVersions(name, declaration, options)\n    if (!versions) return { version: null }\n\n    const greatestMinor = versionUtil.findGreatestByLevel(\n      versions.map(v => v.replace(/^v/, '')),\n      version,\n      level,\n    )\n\n    return { version: greatestMinor ? versionUtil.upgradeGitHubUrl(declaration, greatestMinor) : null }\n  }\n\nexport const minor = greatestLevel('minor')\nexport const patch = greatestLevel('patch')\n\n/** All git tags are exact versions, so --target semver should never upgrade git tags. */\n// https://github.com/raineorshine/npm-check-updates/pull/1368\nexport const semver: GetVersion = async (_name: string, _declaration: VersionSpec, _options?: Options) => {\n  return { version: null }\n}\n\n// use greatest for newest rather than leaving newest undefined\n// this allows a mix of npm and github urls to be used in a package file without causing an \"Unsupported target\" error\nexport const newest = greatest\n"
  },
  {
    "path": "src/package-managers/index.ts",
    "content": "import { Index } from '../types/IndexType'\nimport { PackageManager } from '../types/PackageManager'\nimport * as bun from './bun'\nimport * as gitTags from './gitTags'\nimport * as npm from './npm'\nimport * as pnpm from './pnpm'\nimport * as staticRegistry from './staticRegistry'\nimport * as yarn from './yarn'\n\nexport default {\n  npm,\n  pnpm,\n  yarn,\n  bun,\n  gitTags,\n  staticRegistry,\n} as Index<PackageManager>\n"
  },
  {
    "path": "src/package-managers/npm.ts",
    "content": "import camelCase from 'camelcase'\nimport memoize from 'fast-memoize'\nimport fs from 'fs'\nimport ini from 'ini'\nimport npmRegistryFetch from 'npm-registry-fetch'\nimport path from 'path'\nimport nodeSemver from 'semver'\nimport { parseRange } from 'semver-utils'\nimport untildify from 'untildify'\nimport pkg from '../../package.json'\nimport filterObject from '../lib/filterObject'\nimport { keyValueBy } from '../lib/keyValueBy'\nimport libnpmconfig from '../lib/libnpmconfig'\nimport { print, printSorted } from '../lib/logging'\nimport { sortBy } from '../lib/sortBy'\nimport spawnCommand from '../lib/spawnCommand'\nimport * as versionUtil from '../lib/version-util'\nimport type { CooldownFunction } from '../types/CooldownFunction'\nimport type { GetVersion } from '../types/GetVersion'\nimport type { Index } from '../types/IndexType'\nimport type { MockedVersions } from '../types/MockedVersions'\nimport type { NpmConfig } from '../types/NpmConfig'\nimport type { NpmOptions } from '../types/NpmOptions'\nimport type { Options } from '../types/Options'\nimport type { Packument } from '../types/Packument'\nimport type { SpawnOptions } from '../types/SpawnOptions'\nimport type { SpawnPleaseOptions } from '../types/SpawnPleaseOptions'\nimport type { Version } from '../types/Version'\nimport type { VersionResult } from '../types/VersionResult'\nimport type { VersionSpec } from '../types/VersionSpec'\nimport { filterPredicate, satisfiesCooldownPeriod, satisfiesNodeEngine } from './filters'\n\nconst EXPLICIT_RANGE_OPS = new Set(['-', '||', '&&', '<', '<=', '>', '>='])\n\n/** Returns true if the spec is an explicit version range (not ~ or ^). */\nconst isExplicitRange = (spec: VersionSpec) => {\n  const range = parseRange(spec)\n  return range.some(parsed => EXPLICIT_RANGE_OPS.has(parsed.operator || ''))\n}\n\n/** Returns true if the version is sa valid, exact version. */\nconst isExactVersion = (version: Version) =>\n  version && (!nodeSemver.validRange(version) || versionUtil.isWildCard(version))\n\n/** Fetches a packument or dist-tag from the npm registry. */\nconst fetchPartialPackument = async (\n  name: string,\n  fields: (keyof Packument)[],\n  tag: string | null,\n  opts: npmRegistryFetch.FetchOptions = {},\n  version?: Version,\n): Promise<Partial<Packument>> => {\n  const corgiDoc = 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*'\n  const fullDoc = 'application/json'\n\n  const registry = npmRegistryFetch.pickRegistry(name, opts)\n  const headers = {\n    'user-agent': opts.userAgent || `npm-check-updates/${pkg.version} node/${process.version}`,\n    'ncu-version': pkg.version,\n    'ncu-pkg-id': `registry:${name}`,\n    accept: opts.fullMetadata ? fullDoc : corgiDoc,\n    ...opts.headers,\n  }\n  const url = new URL(\n    // since the registry API expects /package or /package/version encoding\n    // scoped packages is needed as to not treat the package scope as the full\n    // package name and the actual package name as the version/dist-tag\n    encodeURIComponent(name),\n    // the WhatWG URL standard, when given a base URL to place the first\n    // parameter relative to, will find the dirname of the base, treating the\n    // last segment as a file name and not a directory name if it isn't\n    // terminated by a / and thus remove it before adding the first argument\n    // to the URL.\n    // this is undesirable for registries configured without a trailing slash\n    // in the npm config since, for example looking up the package @foo/bar\n    // will give the following results given these configured registry URL:s\n    //    https://example.com/npm  => https://example.com/%40foo%2fbar\n    //    https://example.com/npm/ => https://example.com/npm/%40foo%2fbar\n    // however, like npm itself does there should be leniency allowed in this.\n    registry.endsWith('/') ? registry : `${registry}/`,\n  )\n  if (version) {\n    url.pathname += `/${version}`\n  }\n  const fetchOptions = {\n    ...opts,\n    headers,\n    spec: name,\n  }\n\n  try {\n    if (opts.fullMetadata) {\n      return npmRegistryFetch.json(url.href, fetchOptions)\n    } else {\n      tag = tag || 'latest'\n      // typescript does not type async iterable stream correctly so we need to cast it\n      const stream = npmRegistryFetch.json.stream(url.href, '$*', fetchOptions) as unknown as IterableIterator<{\n        key: keyof Packument\n        value: Packument[keyof Packument]\n      }>\n\n      const partialPackument: Partial<Packument> = { name }\n\n      for await (const { key, value } of stream) {\n        if (fields.includes(key)) {\n          // TODO: Fix type\n          partialPackument[key] = value as any\n          if (Object.keys(partialPackument).length === fields.length + 1) {\n            break\n          }\n        }\n      }\n\n      return partialPackument\n    }\n  } catch (err: any) {\n    if (err.code !== 'E404' || opts.fullMetadata) {\n      throw err\n    }\n\n    // possible that corgis are not supported by this registry\n    return fetchPartialPackument(name, fields, tag, { ...opts, fullMetadata: true }, version)\n  }\n}\n\n/**\n * Decorates a tag-specific/version-specific packument object with the package name and `time` property from the full packument,\n * if the `time` information for the tag's version exists.\n *\n * @param tagPackument - A partial packument object representing a specific tag/version.\n * @param packument - The full packument object, potentially containing time metadata for versions.\n * @returns A new packument object that includes the `time` property if available for the tag's version and package name.\n */\nconst decorateTagPackumentWithTimeAndName = (\n  tagPackument: Partial<Packument>,\n  packument: Partial<Packument>,\n): Partial<Packument> => {\n  const version = tagPackument.version\n\n  return {\n    ...tagPackument,\n    name: packument.name,\n    ...(packument?.time?.[version!] ? { time: packument.time } : null),\n  }\n}\n\n/** Normalizes the keys of an npm config for pacote. */\nexport const normalizeNpmConfig = (\n  npmConfig: NpmConfig,\n  // config path used to determine relative cafile paths\n  configPath?: string,\n): NpmConfig => {\n  const npmConfigToPacoteMap = {\n    cafile: (capath: string): undefined | { ca: string[] } => {\n      // load-cafile, based on github.com/npm/cli/blob/40c1b0f/lib/config/load-cafile.js\n      if (!capath) return\n      // synchronous since it is loaded once on startup, and to avoid complexity in libnpmconfig\n      // https://github.com/raineorshine/npm-check-updates/issues/636?notification_referrer_id=MDE4Ok5vdGlmaWNhdGlvblRocmVhZDc0Njk2NjAzMjo3NTAyNzY%3D\n      const cadata = fs.readFileSync(path.resolve(configPath || '', untildify(capath)), 'utf8')\n      const delim = '-----END CERTIFICATE-----'\n      const output: string[] = cadata\n        .split(delim)\n        .filter(xs => !!xs.trim())\n        .map(xs => `${xs.trimStart()}${delim}`)\n      return { ca: output }\n    },\n    maxsockets: 'maxSockets',\n    'strict-ssl': 'strictSSL',\n  }\n\n  // all config variables are read in as strings, so we need to type coerce non-strings\n  // lowercased and hyphens removed for comparison purposes\n  const keyTypes: Index<'boolean' | 'number'> = {\n    all: 'boolean',\n    allowsameversion: 'boolean',\n    audit: 'boolean',\n    binlinks: 'boolean',\n    color: 'boolean',\n    commithooks: 'boolean',\n    description: 'boolean',\n    dev: 'boolean',\n    diffignoreallspace: 'boolean',\n    diffnameonly: 'boolean',\n    diffnoprefix: 'boolean',\n    difftext: 'boolean',\n    dryrun: 'boolean',\n    enginestrict: 'boolean',\n    force: 'boolean',\n    foregroundscripts: 'boolean',\n    formatpackagelock: 'boolean',\n    fund: 'boolean',\n    gittagversion: 'boolean',\n    global: 'boolean',\n    globalstyle: 'boolean',\n    ifpresent: 'boolean',\n    ignorescripts: 'boolean',\n    includestaged: 'boolean',\n    includeworkspaceroot: 'boolean',\n    installlinks: 'boolean',\n    json: 'boolean',\n    legacybundling: 'boolean',\n    legacypeerdeps: 'boolean',\n    link: 'boolean',\n    long: 'boolean',\n    offline: 'boolean',\n    omitlockfileregistryresolved: 'boolean',\n    packagelock: 'boolean',\n    packagelockonly: 'boolean',\n    parseable: 'boolean',\n    preferoffline: 'boolean',\n    preferonline: 'boolean',\n    progress: 'boolean',\n    readonly: 'boolean',\n    rebuildbundle: 'boolean',\n    save: 'boolean',\n    savebundle: 'boolean',\n    savedev: 'boolean',\n    saveexact: 'boolean',\n    saveoptional: 'boolean',\n    savepeer: 'boolean',\n    saveprod: 'boolean',\n    shrinkwrap: 'boolean',\n    signgitcommit: 'boolean',\n    signgittag: 'boolean',\n    strictpeerdeps: 'boolean',\n    strictssl: 'boolean',\n    timing: 'boolean',\n    unicode: 'boolean',\n    updatenotifier: 'boolean',\n    usage: 'boolean',\n    version: 'boolean',\n    versions: 'boolean',\n    workspacesupdate: 'boolean',\n    diffunified: 'number',\n    fetchretries: 'number',\n    fetchretryfactor: 'number',\n    fetchretrymaxtimeout: 'number',\n    fetchretrymintimeout: 'number',\n    fetchtimeout: 'number',\n    logsmax: 'number',\n    maxsockets: 'number',\n    searchlimit: 'number',\n    searchstaleness: 'number',\n    ssopollfrequency: 'number',\n    timeout: 'number',\n  }\n\n  /** Parses a string to a boolean. */\n  const stringToBoolean = (s: string): boolean => !!s && s !== 'false' && s !== '0'\n\n  /** Parses a string to a number. */\n  const stringToNumber = (s: string): number => parseInt(s) || 0\n\n  // needed until pacote supports full npm config compatibility\n  // See: https://github.com/zkat/pacote/issues/156\n  const config: NpmConfig = keyValueBy(npmConfig, (key: string, value: NpmConfig[keyof NpmConfig]) => {\n    // replace env ${VARS} in strings with the process.env value\n    const normalizedValue =\n      typeof value !== 'string'\n        ? value\n        : // parse stringified booleans\n          keyTypes[key.replace(/-/g, '').toLowerCase()] === 'boolean'\n          ? stringToBoolean(value)\n          : keyTypes[key.replace(/-/g, '').toLowerCase()] === 'number'\n            ? stringToNumber(value)\n            : value.replace(/\\${([^}]+)}/, (_, envVar) => process.env[envVar] as string)\n\n    // normalize the key for pacote\n    const { [key]: pacoteKey }: Index<NpmConfig[keyof NpmConfig]> = npmConfigToPacoteMap\n\n    return typeof pacoteKey === 'string'\n      ? // key is mapped to a string\n        { [pacoteKey]: normalizedValue }\n      : // key is mapped to a function\n        typeof pacoteKey === 'function'\n        ? { ...(pacoteKey(normalizedValue.toString()) as any) }\n        : // otherwise assign the camel-cased key\n          { [key.match(/^[a-z]/i) ? camelCase(key) : key]: normalizedValue }\n  })\n\n  return config\n}\n\n/** Finds and parses the npm config at the given path. If the path does not exist, returns null. If no path is provided, finds and merges the global and user npm configs using libnpmconfig and sets cache: false. */\nconst findNpmConfig = memoize((configPath?: string): NpmConfig | null => {\n  let config\n\n  if (configPath) {\n    try {\n      config = ini.parse(fs.readFileSync(configPath, 'utf-8'))\n    } catch (err: any) {\n      if (err.code === 'ENOENT') {\n        return null\n      } else {\n        throw err\n      }\n    }\n  } else {\n    // libnpmconfig incorrectly (?) ignores NPM_CONFIG_USERCONFIG because it is always overridden by the default builtin.userconfig\n    // set userconfig manually so that it is prioritized\n    const opts = libnpmconfig(null, {\n      userconfig: process.env.npm_config_userconfig || process.env.NPM_CONFIG_USERCONFIG,\n    })\n    config = {\n      ...opts.toJSON(),\n      cache: false,\n    }\n  }\n\n  return normalizeNpmConfig(config, configPath)\n})\n\n// get the base config that is used for all npm queries\n// this may be partially overwritten by .npmrc config files when using --deep\nconst npmConfig = findNpmConfig()\n\n/**\n * Parse JSON and throw an informative error on failure.\n *\n * @param result Data to be parsed\n * @param data\n * @returns\n */\nexport function parseJson<R>(result: string, data: { command?: string; packageName?: string }): R {\n  let json\n  try {\n    json = JSON.parse(result)\n  } catch (err) {\n    throw new Error(\n      `Expected JSON from \"${data.command}\".${\n        data.packageName ? ` There could be problems with the ${data.packageName} package.` : ''\n      } ${result ? 'Instead received: ' + result : 'Received empty response.'}`,\n    )\n  }\n  return json as R\n}\n\n/**\n * Check if package author changed between current and upgraded version.\n *\n * @param packageName Name of the package\n * @param currentVersion Current version declaration (may be range)\n * @param upgradedVersion Upgraded version declaration (may be range)\n * @param npmConfigLocal Additional npm config variables that are merged into the system npm config\n * @returns A promise that fulfills with boolean value.\n */\nexport async function packageAuthorChanged(\n  packageName: string,\n  currentVersion: VersionSpec,\n  upgradedVersion: VersionSpec,\n  options: Options = {},\n  npmConfigLocal?: NpmConfig,\n): Promise<boolean> {\n  const result = await fetchPartialPackument(packageName, ['versions'], null, {\n    ...npmConfigLocal,\n    ...npmConfig,\n    fullMetadata: true,\n    ...(options.registry ? { registry: options.registry, silent: true } : null),\n  })\n  if (result.versions) {\n    const pkgVersions = Object.keys(result.versions)\n    const current = nodeSemver.minSatisfying(pkgVersions, currentVersion)\n    const upgraded = nodeSemver.maxSatisfying(pkgVersions, upgradedVersion)\n    if (current && upgraded && result.versions[current]._npmUser && result.versions[upgraded]._npmUser) {\n      const currentAuthor = result.versions[current]._npmUser?.name\n      const latestAuthor = result.versions[upgraded]._npmUser?.name\n      return currentAuthor !== latestAuthor\n    }\n  }\n\n  return false\n}\n\n/** Returns true if an object is a Packument. */\nconst isPackument = (o: any): o is Partial<Packument> => !!(o && (o.name || o.engines || o.version || o.versions))\n\n/** Creates a function with the same signature as fetchUpgradedPackument that always returns the given versions. */\nexport const mockFetchUpgradedPackument =\n  (mockReturnedVersions: MockedVersions): typeof fetchUpgradedPackument =>\n  (name: string, fields: (keyof Packument)[], currentVersion: Version, options: Options) => {\n    // a partial Packument\n    const partialPackument =\n      typeof mockReturnedVersions === 'function'\n        ? mockReturnedVersions(options)?.[name]\n        : typeof mockReturnedVersions === 'string' || isPackument(mockReturnedVersions)\n          ? mockReturnedVersions\n          : mockReturnedVersions[name]\n\n    const version = isPackument(partialPackument) ? partialPackument.version : partialPackument\n\n    if (!version) {\n      throw new Error(\n        `fetchUpgradedPackument is mocked, but no mock version was supplied for ${name}. Make sure that all dependencies are mocked. `,\n      )\n    }\n\n    const time = (isPackument(partialPackument) && partialPackument.time?.[version]) || new Date().toISOString()\n    const packument: Packument = {\n      name,\n      'dist-tags': {\n        [options.distTag || 'latest']: version,\n      },\n      engines: { node: '' },\n      time: {\n        [version]: time,\n      },\n      version,\n      // overwritten below\n      versions: {},\n      ...(isPackument(partialPackument) ? partialPackument : null),\n    }\n\n    const { versions: _, ...packumentWithoutVersions } = packument\n\n    return Promise.resolve({\n      ...packument,\n      versions: {\n        ...((isPackument(partialPackument) && partialPackument.versions) || {\n          [version]: packumentWithoutVersions,\n        }),\n      },\n    })\n  }\n\n/** Merges the workspace, global, user, local, project, and cwd npm configs (in that order). */\n// Note that this is memoized on configs and options, but not on package name. This avoids duplicate messages when log level is verbose. findNpmConfig is memoized on config path, so it is not expensive to call multiple times.\nconst mergeNpmConfigs = memoize(\n  (\n    {\n      npmConfigLocal,\n      npmConfigUser,\n      npmConfigWorkspaceProject,\n    }: {\n      npmConfigLocal?: NpmConfig\n      npmConfigUser?: NpmConfig\n      npmConfigWorkspaceProject?: NpmConfig\n    },\n    options: Options,\n  ) => {\n    // merge project npm config with base config\n    const npmConfigProjectPath = options.packageFile ? path.join(options.packageFile, '../.npmrc') : null\n    const npmConfigProject = options.packageFile ? findNpmConfig(npmConfigProjectPath || undefined) : null\n    const npmConfigCWDPath = options.cwd ? path.join(options.cwd, '.npmrc') : null\n    const npmConfigCWD = options.cwd ? findNpmConfig(npmConfigCWDPath!) : null\n\n    if (npmConfigWorkspaceProject && Object.keys(npmConfigWorkspaceProject).length > 0) {\n      print(options, `\\nnpm config (workspace project):`, 'verbose')\n      const { cache: _, ...npmConfigWorkspaceProjectWithoutCache } = npmConfigWorkspaceProject\n      printSorted(options, npmConfigWorkspaceProjectWithoutCache, 'verbose')\n    }\n\n    if (npmConfigUser && Object.keys(npmConfigUser).length > 0) {\n      print(options, `\\nnpm config (user):`, 'verbose')\n      const { cache: _, ...npmConfigUserWithoutCache } = npmConfigUser\n      printSorted(options, npmConfigUserWithoutCache, 'verbose')\n    }\n\n    if (npmConfigLocal && Object.keys(npmConfigLocal).length > 0) {\n      print(options, `\\nnpm config (local override):`, 'verbose')\n      const { cache: _, ...npmConfigLocalWithoutCache } = npmConfigLocal\n      printSorted(options, npmConfigLocalWithoutCache, 'verbose')\n    }\n\n    if (npmConfigProject && Object.keys(npmConfigProject).length > 0) {\n      print(options, `\\nnpm config (project: ${npmConfigProjectPath}):`, 'verbose')\n      const { cache: _, ...npmConfigProjectWithoutCache } = npmConfigProject\n      printSorted(options, npmConfigProjectWithoutCache, 'verbose')\n    }\n\n    if (npmConfigCWD && Object.keys(npmConfigCWD).length > 0) {\n      print(options, `\\nnpm config (cwd: ${npmConfigCWDPath}):`, 'verbose')\n      // omit cache since it is added to every config\n      const { cache: _, ...npmConfigCWDWithoutCache } = npmConfigCWD\n      printSorted(options, npmConfigCWDWithoutCache, 'verbose')\n    }\n\n    const npmConfigMerged = {\n      ...npmConfigWorkspaceProject,\n      ...npmConfigUser,\n      ...npmConfigLocal,\n      ...npmConfigProject,\n      ...npmConfigCWD,\n      ...(options.registry ? { registry: options.registry, silent: true } : null),\n      ...(options.timeout ? { timeout: options.timeout } : null),\n    }\n\n    const isMerged = npmConfigWorkspaceProject || npmConfigLocal || npmConfigProject || npmConfigCWD\n    if (isMerged) {\n      print(options, `\\nmerged npm config:`, 'verbose')\n      // omit cache since it is added to every config\n      // @ts-expect-error -- though not typed, but the \"cache\" property does exist on the object and needs to be omitted\n      const { cache: _, ...npmConfigMergedWithoutCache } = npmConfigMerged\n      printSorted(options, npmConfigMergedWithoutCache, 'verbose')\n    }\n\n    return npmConfigMerged\n  },\n)\n\n/**\n * Returns an object of specified values retrieved by npm view.\n *\n * @param packageName   Name of the package\n * @param fields        Array of fields like versions, time, version\n * @param               currentVersion\n * @returns             dist-tags field return Index<Packument>, time field returns Index<Index<string>>>, versions field returns Index<Index<Packument>>\n */\nasync function fetchUpgradedPackument(\n  packageName: string,\n  fields: (keyof Packument)[],\n  currentVersion: Version,\n  options: Options,\n  retried = 0,\n  npmConfigLocal?: NpmConfig,\n  npmConfigWorkspaceProject?: NpmConfig,\n): Promise<Partial<Packument> | undefined> {\n  // See: /test/helpers/stubVersions\n  if (process.env.STUB_VERSIONS) {\n    const mockReturnedVersions = JSON.parse(process.env.STUB_VERSIONS)\n    return mockFetchUpgradedPackument(mockReturnedVersions)(packageName, fields, currentVersion, options)\n  }\n\n  if (isExactVersion(currentVersion)) {\n    return Promise.resolve({} as Index<Packument>)\n  }\n\n  // fields may already include time\n  const fieldsExtended =\n    options.format?.includes('time') && !fields.includes('time') ? ([...fields, 'time'] as (keyof Packument)[]) : fields\n  const fullMetadata = fieldsExtended.includes('time')\n\n  const npmConfigMerged = mergeNpmConfigs(\n    {\n      npmConfigUser: { ...npmConfig, fullMetadata },\n      npmConfigLocal,\n      npmConfigWorkspaceProject,\n    },\n    options,\n  )\n\n  let result: Partial<Packument> | undefined\n  try {\n    const tag = options.distTag || 'latest'\n    result = await fetchPartialPackument(\n      packageName,\n      Array.from(\n        new Set([\n          'dist-tags',\n          ...fields,\n          ...(!options.deprecated ? (['deprecated', 'versions'] as const) : []),\n          ...(options.enginesNode ? (['engines', 'versions'] as const) : []),\n        ]),\n      ),\n      fullMetadata ? null : tag,\n      npmConfigMerged,\n    )\n  } catch (err: any) {\n    if (options.retry && ++retried <= options.retry) {\n      return fetchUpgradedPackument(packageName, fieldsExtended, currentVersion, options, retried, npmConfigLocal)\n    }\n\n    throw err\n  }\n\n  return result\n}\n\n/** Memoize fetchUpgradedPackument for --deep and --workspaces performance. */\n// must be exported to stub\nexport const fetchUpgradedPackumentMemo = memoize(fetchUpgradedPackument, {\n  // serializer args are incorrectly typed as any[] instead of being generic, so we need to cast it\n  serializer: (([\n    packageName,\n    fields,\n    currentVersion,\n    options,\n    retried,\n    npmConfigLocal,\n    npmConfigWorkspaceProject,\n  ]: Parameters<typeof fetchUpgradedPackument>) => {\n    // packageFile varies by cwd in workspaces/deep mode, so we do not want to memoize on that\n    const { packageFile: _, ...optionsWithoutPackageFile } = options\n    return JSON.stringify([\n      packageName,\n      fields,\n      // currentVersion does not change the behavior of fetchUpgradedPackument unless it is an invalid/inexact version which causes it to short circuit\n      isExactVersion(currentVersion),\n      optionsWithoutPackageFile,\n      // make sure retries do not get memoized\n      retried,\n      npmConfigLocal,\n      npmConfigWorkspaceProject,\n    ])\n  }) as (args: any[]) => string,\n})\n\n/**\n * Spawns npm with --json. Handles different commands for Window and Linux/OSX.\n *\n * @param args\n * @param [npmOptions={}]\n * @param [spawnOptions={}]\n * @returns\n */\nasync function spawnNpm(\n  args: string | string[],\n  npmOptions: NpmOptions = {},\n  spawnPleaseOptions: SpawnPleaseOptions = {},\n  spawnOptions: Index<any> = {},\n): Promise<any> {\n  const fullArgs = [\n    ...(npmOptions.global ? [`--global`] : []),\n    ...(npmOptions.prefix ? [`--prefix=${npmOptions.prefix}`] : []),\n    '--json',\n    ...(Array.isArray(args) ? args : [args]),\n  ]\n  const { stdout } = await spawnCommand('npm', fullArgs, spawnPleaseOptions, spawnOptions)\n  return stdout\n}\n\n/**\n * Get platform-specific default prefix to pass on to npm.\n *\n * @param options\n * @param [options.global]\n * @param [options.prefix]\n * @returns\n */\nexport async function defaultPrefix(options: Options): Promise<string | undefined> {\n  if (options.prefix) {\n    return Promise.resolve(options.prefix)\n  }\n\n  let prefix: string | undefined\n\n  // catch spawn error which can occur on Windows\n  // https://github.com/raineorshine/npm-check-updates/issues/703\n  try {\n    const { stdout } = await spawnCommand('npm', ['config', 'get', 'prefix'])\n    prefix = stdout\n  } catch (e: any) {\n    const message = (e.message || e || '').toString()\n    print(\n      options,\n      'Error executing `npm config get prefix`. Caught and ignored. Unsolved: https://github.com/raineorshine/npm-check-updates/issues/703. ERROR: ' +\n        message,\n      'verbose',\n      'error',\n    )\n  }\n\n  // FIX: for ncu -g doesn't work on homebrew or windows #146\n  // https://github.com/raineorshine/npm-check-updates/issues/146\n  return options.global && prefix?.match('Cellar')\n    ? '/usr/local'\n    : // Workaround: get prefix on windows for global packages\n      // Only needed when using npm api directly\n      process.platform === 'win32' && options.global && !process.env.prefix\n      ? prefix\n        ? prefix.trim()\n        : `${process.env.AppData}\\\\npm`\n      : undefined\n}\n\n/**\n * Fetches the highest version number, regardless of tag or publish time.\n *\n * @param packageName\n * @param currentVersion\n * @param options\n * @returns\n */\nexport const greatest: GetVersion = async (\n  packageName,\n  currentVersion,\n  options = {},\n  npmConfig?: NpmConfig,\n  npmConfigProject?: NpmConfig,\n): Promise<VersionResult> => {\n  const fields: (keyof Packument)[] = ['versions']\n\n  if (options.cooldown) {\n    fields.push('time')\n  }\n\n  const packument = await fetchUpgradedPackumentMemo(\n    packageName,\n    fields,\n    currentVersion,\n    options,\n    0,\n    npmConfig,\n    npmConfigProject,\n  )\n\n  // known type based on 'versions'\n  const versions = packument?.versions\n\n  return {\n    version:\n      Object.values(versions || {})\n        .filter(tagPackument =>\n          filterPredicate(options)(decorateTagPackumentWithTimeAndName(tagPackument, packument as Partial<Packument>)),\n        )\n        .map(o => o.version)\n        .sort(versionUtil.compareVersions)\n        .at(-1) || null,\n  }\n}\n\n/**\n * Fetches the list of peer dependencies for a specific package version.\n *\n * @param packageName\n * @param version\n * @param spawnOptions\n * @returns Promised {packageName: version} collection\n */\nexport const getPeerDependencies = async (\n  packageName: string,\n  version: Version,\n  spawnOptions: SpawnOptions,\n): Promise<Index<Version>> => {\n  const args = ['view', `${packageName}@${version}`, 'peerDependencies']\n  const result = await spawnNpm(args, {}, { rejectOnError: false }, spawnOptions)\n  return result ? parseJson(result, { command: [...args, '--json'].join(' ') }) : {}\n}\n\n/**\n * Fetches the engines list from the registry for a specific package version.\n *\n * @param packageName\n * @param version\n * @returns Promised engines collection\n */\nexport const getEngines = async (\n  packageName: string,\n  version: Version,\n  options: Options = {},\n  npmConfigLocal?: NpmConfig,\n): Promise<Index<VersionSpec | undefined>> => {\n  const result = await fetchPartialPackument(\n    packageName,\n    [`engines`],\n    null,\n    {\n      ...npmConfigLocal,\n      ...npmConfig,\n      ...(options.registry ? { registry: options.registry, silent: true } : null),\n    },\n    version,\n  )\n  return result.engines || {}\n}\n\n/**\n * Fetches the list of all installed packages.\n *\n * @param [options]\n * @param [options.cwd]\n * @param [options.global]\n * @param [options.prefix]\n * @returns\n */\nexport const list = async (options: Options = {}): Promise<Index<string | undefined>> => {\n  const result = await spawnNpm(\n    ['ls', '--depth=0'],\n    {\n      ...(options.global ? { global: true } : null),\n      ...(options.prefix ? { prefix: options.prefix } : null),\n    },\n    {\n      rejectOnError: false,\n    },\n    {\n      ...(options.cwd ? { cwd: options.cwd } : null),\n    },\n  )\n  const dependencies = parseJson<{\n    dependencies: Index<{ version?: Version; required?: { version: Version } }>\n  }>(result, {\n    command: `npm${process.platform === 'win32' ? '.cmd' : ''} ls --json${options.global ? ' --global' : ''}`,\n  }).dependencies\n\n  return keyValueBy(dependencies, (name, info) => ({\n    // unmet peer dependencies have a different structure\n    [name]: info.version || info.required?.version,\n  }))\n}\n\n/**\n * Fetches the version of a package published to options.distTag.\n *\n * @param packageName\n * @param currentVersion\n * @param options\n * @returns\n */\nexport const distTag: GetVersion = async (\n  packageName,\n  currentVersion,\n  options: Options = {},\n  npmConfig?: NpmConfig,\n  npmConfigProject?: NpmConfig,\n) => {\n  const fields: (keyof Packument)[] = ['dist-tags']\n\n  if (options.cooldown) {\n    fields.push('time')\n  }\n\n  const packument = await fetchUpgradedPackumentMemo(\n    packageName,\n    fields,\n    currentVersion,\n    options,\n    0,\n    npmConfig,\n    npmConfigProject,\n  )\n  const version = packument?.['dist-tags']?.[options.distTag || 'latest']\n\n  // if the packument does not contain versions, we need to add a minimal versions property with the upgraded version\n  const tagPackument = packument?.versions\n    ? packument.versions?.[version!]\n    : {\n        name: packageName,\n        version,\n      }\n\n  const tagPackumentWithTime = decorateTagPackumentWithTimeAndName(tagPackument, packument as Partial<Packument>)\n\n  // latest should not be deprecated\n  // if latest exists and latest is not a prerelease version, return it\n  // if latest exists and latest is a prerelease version and --pre is specified, return it\n  // if latest exists and latest not satisfies min version of engines.node\n  // if latest exists and cooldown is specified and latest is within cooldown period, return it\n  if (tagPackument && filterPredicate(options)(tagPackumentWithTime)) {\n    return {\n      version: tagPackument.version,\n      ...(packument?.time?.[version!] ? { time: packument.time[version!] } : null),\n    }\n  }\n\n  // if version from dist-tag does not meet cooldown requirement skip finding other versions\n  if (options.cooldown) {\n    return {}\n  }\n\n  // If we use a custom dist-tag, we do not want to get other 'pre' versions, just the ones from this dist-tag\n  if (options.distTag && options.distTag !== 'latest') return {}\n\n  // if latest is a prerelease version and --pre is not specified\n  // or latest is deprecated\n  // find the next valid version\n  return greatest(packageName, currentVersion, options, npmConfig, npmConfigProject)\n}\n\n/**\n * Fetches the version published to the latest tag.\n *\n * @param packageName\n * @param currentVersion\n * @param options\n * @returns\n */\nexport const latest: GetVersion = async (\n  packageName: string,\n  currentVersion: Version,\n  options: Options = {},\n  npmConfig?: NpmConfig,\n  npmConfigProject?: NpmConfig,\n) => distTag(packageName, currentVersion, { ...options, distTag: 'latest' }, npmConfig, npmConfigProject)\n\n/**\n * Fetches the most recently published version, regardless of version number.\n *\n * @param packageName\n * @param currentVersion\n * @param options\n * @returns\n */\nexport const newest: GetVersion = async (\n  packageName,\n  currentVersion,\n  options = {},\n  npmConfig?: NpmConfig,\n  npmConfigProject?: NpmConfig,\n): Promise<VersionResult> => {\n  const result = await fetchUpgradedPackumentMemo(\n    packageName,\n    ['time', 'versions'],\n    currentVersion,\n    options,\n    0,\n    npmConfig,\n    npmConfigProject,\n  )\n\n  // Generate a map of versions that satisfy the node engine.\n  // result.versions is an object but is parsed as an array, so manually convert it to an object.\n  // Otherwise keyValueBy will pass the predicate arguments in the wrong order.\n  const versionsSatisfyingNodeEngine = keyValueBy(\n    result?.versions || {},\n    (version: Version, packument: Packument['versions'][string]) =>\n      satisfiesNodeEngine(packument, options.nodeEngineVersion) ? { [packument.version]: true } : null,\n  )\n\n  // filter out times that do not satisfy the node engine\n  // filter out prereleases if pre:false (same as allowPreOrIsNotPre)\n  const timesSatisfyingNodeEngine = filterObject(\n    (result?.time || {}) as Index<string>,\n    version => versionsSatisfyingNodeEngine[version] && (options.pre !== false || !versionUtil.isPre(version)),\n  )\n\n  // sort by timestamp (entry[1]) and map versions\n  const versionsSortedByTime = sortBy(Object.entries(timesSatisfyingNodeEngine), v => v[1]).map(([version]) => version)\n\n  if (options.cooldown) {\n    const versionsSatisfyingCooldownPeriod = versionsSortedByTime.filter(version =>\n      satisfiesCooldownPeriod(\n        decorateTagPackumentWithTimeAndName((result as Packument).versions[version], result as Packument),\n        options.cooldown as number | CooldownFunction,\n      ),\n    )\n\n    return { version: versionsSatisfyingCooldownPeriod.at(-1) }\n  }\n\n  return { version: versionsSortedByTime.at(-1) }\n}\n\n/**\n * Fetches the highest version with the same major version as currentVersion.\n *\n * @param packageName\n * @param currentVersion\n * @param options\n * @returns\n */\nexport const minor: GetVersion = async (\n  packageName,\n  currentVersion,\n  options = {},\n  npmConfig?: NpmConfig,\n  npmConfigProject?: NpmConfig,\n): Promise<VersionResult> => {\n  const fields: (keyof Packument)[] = ['versions']\n\n  if (options.cooldown) {\n    fields.push('time')\n  }\n\n  const packument = await fetchUpgradedPackumentMemo(\n    packageName,\n    fields,\n    currentVersion,\n    options,\n    0,\n    npmConfig,\n    npmConfigProject,\n  )\n\n  const versions = packument?.versions as Index<Packument>\n  const version = versionUtil.findGreatestByLevel(\n    Object.values(versions || {})\n      .filter(tagPackument =>\n        filterPredicate(options)(decorateTagPackumentWithTimeAndName(tagPackument, packument as Partial<Packument>)),\n      )\n      .map(o => o.version),\n    currentVersion,\n    'minor',\n  )\n  return { version }\n}\n\n/**\n * Fetches the highest version with the same minor and major version as currentVersion.\n *\n * @param packageName\n * @param currentVersion\n * @param options\n * @returns\n */\nexport const patch: GetVersion = async (\n  packageName,\n  currentVersion,\n  options = {},\n  npmConfig?: NpmConfig,\n  npmConfigProject?: NpmConfig,\n): Promise<VersionResult> => {\n  const fields: (keyof Packument)[] = ['versions']\n\n  if (options.cooldown) {\n    fields.push('time')\n  }\n\n  const packument = await fetchUpgradedPackumentMemo(\n    packageName,\n    fields,\n    currentVersion,\n    options,\n    0,\n    npmConfig,\n    npmConfigProject,\n  )\n\n  const versions = packument?.versions as Index<Packument>\n  const version = versionUtil.findGreatestByLevel(\n    Object.values(versions || {})\n      .filter(tagPackument =>\n        filterPredicate(options)(decorateTagPackumentWithTimeAndName(tagPackument, packument as Partial<Packument>)),\n      )\n      .map(o => o.version),\n    currentVersion,\n    'patch',\n  )\n  return { version }\n}\n\n/**\n * Fetches the highest version that satisfies the semver range specified in the package.json.\n *\n * @param packageName\n * @param currentVersion\n * @param options\n * @returns\n */\nexport const semver: GetVersion = async (\n  packageName,\n  currentVersion,\n  options = {},\n  npmConfig?: NpmConfig,\n  npmConfigProject?: NpmConfig,\n): Promise<VersionResult> => {\n  const fields: (keyof Packument)[] = ['versions']\n\n  if (options.cooldown) {\n    fields.push('time')\n  }\n\n  const packument = await fetchUpgradedPackumentMemo(\n    packageName,\n    fields,\n    currentVersion,\n    options,\n    0,\n    npmConfig,\n    npmConfigProject,\n  )\n\n  const versions = packument?.versions as Index<Packument>\n  // ignore explicit version ranges\n  if (isExplicitRange(currentVersion)) return { version: null }\n\n  const versionsFiltered = Object.values(versions || {})\n    .filter(tagPackument =>\n      filterPredicate(options)(decorateTagPackumentWithTimeAndName(tagPackument, packument as Partial<Packument>)),\n    )\n    .map(o => o.version)\n  // TODO: Upgrading within a prerelease does not seem to work.\n  // { includePrerelease: true } does not help.\n  const version = nodeSemver.maxSatisfying(versionsFiltered, currentVersion)\n  return { version }\n}\n\nexport default spawnNpm\n"
  },
  {
    "path": "src/package-managers/pnpm.ts",
    "content": "import memoize from 'fast-memoize'\nimport findUp from 'find-up'\nimport fs from 'fs/promises'\nimport ini from 'ini'\nimport path from 'path'\nimport keyValueBy from '../lib/keyValueBy'\nimport { print } from '../lib/logging'\nimport spawnCommand from '../lib/spawnCommand'\nimport { GetVersion } from '../types/GetVersion'\nimport { Index } from '../types/IndexType'\nimport { NpmConfig } from '../types/NpmConfig'\nimport { NpmOptions } from '../types/NpmOptions'\nimport { Options } from '../types/Options'\nimport { SpawnOptions } from '../types/SpawnOptions'\nimport { SpawnPleaseOptions } from '../types/SpawnPleaseOptions'\nimport { Version } from '../types/Version'\nimport * as npm from './npm'\n\n// return type of pnpm ls --json\ntype PnpmList = {\n  path: string\n  private: boolean\n  dependencies: Index<{\n    from: string\n    version: Version\n    resolved: string\n  }>\n}[]\n\n/** Reads the npmrc config file from the pnpm-workspace.yaml directory. */\nconst npmConfigFromPnpmWorkspace = memoize(async (options: Options): Promise<NpmConfig> => {\n  const pnpmWorkspacePath = await findUp('pnpm-workspace.yaml')\n  if (!pnpmWorkspacePath) return {}\n\n  const pnpmWorkspaceDir = path.dirname(pnpmWorkspacePath)\n  const pnpmWorkspaceConfigPath = path.join(pnpmWorkspaceDir, '.npmrc')\n\n  let pnpmWorkspaceConfig\n  try {\n    pnpmWorkspaceConfig = await fs.readFile(pnpmWorkspaceConfigPath, 'utf-8')\n  } catch (e) {\n    return {}\n  }\n\n  print(options, `\\nUsing pnpm workspace config at ${pnpmWorkspaceConfigPath}:`, 'verbose')\n\n  const config = npm.normalizeNpmConfig(ini.parse(pnpmWorkspaceConfig), pnpmWorkspaceDir)\n\n  print(options, config, 'verbose')\n\n  return config\n})\n\n/** Fetches the list of all installed packages. */\nexport const list = async (options: Options = {}): Promise<Index<string | undefined>> => {\n  // use npm for local ls for completeness\n  // this should never happen since list is only called in runGlobal -> getInstalledPackages\n  if (!options.global) return npm.list(options)\n\n  const { stdout } = await spawnCommand('pnpm', ['ls', '-g', '--json'])\n  const result = JSON.parse(stdout) as PnpmList\n  const list = keyValueBy(result[0].dependencies || {}, (name, { version }) => ({\n    [name]: version,\n  }))\n  return list\n}\n\n/** Wraps a GetVersion function and passes the npmrc located next to the pnpm-workspace.yaml if it exists. */\nconst withNpmWorkspaceConfig =\n  (getVersion: GetVersion): GetVersion =>\n  async (packageName, currentVersion, options = {}) =>\n    getVersion(packageName, currentVersion, options, {}, await npmConfigFromPnpmWorkspace(options))\n\nexport const distTag = withNpmWorkspaceConfig(npm.distTag)\nexport const greatest = withNpmWorkspaceConfig(npm.greatest)\nexport const latest = withNpmWorkspaceConfig(npm.latest)\nexport const minor = withNpmWorkspaceConfig(npm.minor)\nexport const newest = withNpmWorkspaceConfig(npm.newest)\nexport const patch = withNpmWorkspaceConfig(npm.patch)\nexport const semver = withNpmWorkspaceConfig(npm.semver)\n\n/**\n * Spawn pnpm.\n *\n * @param args\n * @param [npmOptions={}]\n * @param [spawnOptions={}]\n * @returns\n */\nasync function spawnPnpm(\n  args: string | string[],\n  npmOptions: NpmOptions = {},\n  spawnOptions?: SpawnOptions,\n  spawnPleaseOptions?: SpawnPleaseOptions,\n): Promise<string> {\n  const fullArgs = [\n    ...(npmOptions.global ? 'global' : []),\n    ...(Array.isArray(args) ? args : [args]),\n    ...(npmOptions.prefix ? `--prefix=${npmOptions.prefix}` : []),\n  ]\n\n  const { stdout } = await spawnCommand('pnpm', fullArgs, spawnPleaseOptions, spawnOptions)\n\n  return stdout\n}\n\nexport { defaultPrefix, getPeerDependencies, getEngines, packageAuthorChanged } from './npm'\n\nexport default spawnPnpm\n"
  },
  {
    "path": "src/package-managers/staticRegistry.ts",
    "content": "import memoize from 'fast-memoize'\nimport fs from 'fs/promises'\nimport programError from '../lib/programError'\nimport { GetVersion } from '../types/GetVersion'\nimport { Options } from '../types/Options'\nimport { StaticRegistry } from '../types/StaticRegistry'\nimport { Version } from '../types/Version'\n\n/** Returns true if a string is a url. */\nconst isUrl = (s: string) => (s && s.startsWith('http://')) || s.startsWith('https://')\n\n/**\n * Returns a registry object given a valid file path or url.\n *\n * @param path\n * @returns a registry object\n */\nconst readStaticRegistry = async (options: Options): Promise<StaticRegistry> => {\n  const path = options.registry!\n  let content: string\n\n  // url\n  if (isUrl(path)) {\n    const body = await fetch(path)\n    content = await body.text()\n  }\n  // local path\n  else {\n    try {\n      content = await fs.readFile(path, 'utf8')\n    } catch (err) {\n      programError(options, `\\nThe specified static registry file does not exist: ${options.registry}`)\n    }\n  }\n\n  return JSON.parse(content)\n}\n\nconst registryMemoized = memoize(readStaticRegistry)\n\n/**\n * Fetches the version in static registry.\n *\n * @param packageName\n * @param currentVersion\n * @param options\n * @returns A promise that fulfills to string value or null\n */\nexport const latest: GetVersion = async (packageName: string, currentVersion: Version, options?: Options) => {\n  const registry: StaticRegistry = await registryMemoized(options || {})\n  return { version: registry[packageName] || null }\n}\n"
  },
  {
    "path": "src/package-managers/yarn.ts",
    "content": "import memoize from 'fast-memoize'\nimport fs from 'fs/promises'\nimport yaml from 'js-yaml'\nimport jsonlines from 'jsonlines'\nimport curry from 'lodash/curry'\nimport os from 'os'\nimport path from 'path'\nimport exists from '../lib/exists'\nimport findLockfile from '../lib/findLockfile'\nimport { keyValueBy } from '../lib/keyValueBy'\nimport { print } from '../lib/logging'\nimport spawnCommand from '../lib/spawnCommand'\nimport { GetVersion } from '../types/GetVersion'\nimport { Index } from '../types/IndexType'\nimport { NpmConfig } from '../types/NpmConfig'\nimport { NpmOptions } from '../types/NpmOptions'\nimport { Options } from '../types/Options'\nimport { SpawnOptions } from '../types/SpawnOptions'\nimport { SpawnPleaseOptions } from '../types/SpawnPleaseOptions'\nimport { Version } from '../types/Version'\nimport { VersionSpec } from '../types/VersionSpec'\nimport * as npm from './npm'\n\ninterface ParsedDep {\n  version: string\n  from: string\n  required?: {\n    version: string\n  }\n}\n\nexport interface NpmScope {\n  npmAlwaysAuth?: boolean\n  npmAuthToken?: string\n  npmRegistryServer?: string\n}\n\ninterface YarnConfig {\n  npmScopes?: Index<NpmScope>\n}\n\n/** Safely interpolates a string as a template string. */\nconst interpolate = (s: string, data: Index<string | undefined>): string =>\n  s.replace(\n    /\\$\\{([^:-]+)(?:(:)?-([^}]*))?\\}/g,\n    (match, key, name, fallbackOnEmpty, fallback) => data[key] || (fallbackOnEmpty ? fallback : ''),\n  )\n\n/** Reads an auth token from a yarn config, interpolates it, and returns it as an npm config key-value pair. */\nexport const npmAuthTokenKeyValue = curry((npmConfig: Index<string | boolean>, dep: string, scopedConfig: NpmScope) => {\n  if (scopedConfig.npmAuthToken) {\n    // get registry server from this config or a previous config (assumes setNpmRegistry has already been called on all npm scopes)\n    const registryServer = scopedConfig.npmRegistryServer || (npmConfig[`@${dep}:registry`] as string | undefined)\n    // interpolate environment variable fallback\n    // https://yarnpkg.com/configuration/yarnrc\n    if (registryServer) {\n      let trimmedRegistryServer = registryServer.replace(/^https?:/, '')\n\n      if (trimmedRegistryServer.endsWith('/')) {\n        trimmedRegistryServer = trimmedRegistryServer.slice(0, -1)\n      }\n\n      return {\n        [`${trimmedRegistryServer}/:_authToken`]: interpolate(scopedConfig.npmAuthToken, process.env),\n      }\n    }\n  }\n\n  return null\n})\n\n/** Reads a registry from a yarn config. interpolates it, and returns it as an npm config key-value pair. */\nconst npmRegistryKeyValue = (dep: string, scopedConfig: NpmScope): null | Index<VersionSpec> =>\n  scopedConfig.npmRegistryServer\n    ? { [`@${dep}:registry`]: interpolate(scopedConfig.npmRegistryServer, process.env) }\n    : null\n\n/**\n * Returns the path to the local .yarnrc.yml, or undefined. This doesn't\n * actually check that the .yarnrc.yml file exists.\n *\n * Exported for test purposes only.\n *\n * @param readdirSync This is only a parameter so that it can be used in tests.\n */\nexport async function getPathToLookForYarnrc(\n  options: Options,\n  readdir: (_path: string) => Promise<string[]> = fs.readdir,\n): Promise<string | undefined> {\n  if (options.global) return undefined\n\n  const directoryPath = (await findLockfile(options, readdir))?.directoryPath\n  if (!directoryPath) return undefined\n\n  return path.join(directoryPath, '.yarnrc.yml')\n}\n\n// If private registry auth is specified in npmScopes in .yarnrc.yml, read them in and convert them to npm config variables.\n// Define as a memoized function to efficiently call existsSync and readFileSync only once, and only if yarn is being used.\n// https://github.com/raineorshine/npm-check-updates/issues/1036\nconst npmConfigFromYarn = memoize(async (options: Options): Promise<NpmConfig> => {\n  const yarnrcLocalPath = await getPathToLookForYarnrc(options)\n  const yarnrcUserPath = path.join(os.homedir(), '.yarnrc.yml')\n  const yarnrcLocalExists = typeof yarnrcLocalPath === 'string' && (await exists(yarnrcLocalPath))\n  const yarnrcUserExists = await exists(yarnrcUserPath)\n  const yarnrcLocal = yarnrcLocalExists ? await fs.readFile(yarnrcLocalPath, 'utf-8') : ''\n  const yarnrcUser = yarnrcUserExists ? await fs.readFile(yarnrcUserPath, 'utf-8') : ''\n  const yarnConfigLocal: YarnConfig = yaml.load(yarnrcLocal) as YarnConfig\n  const yarnConfigUser: YarnConfig = yaml.load(yarnrcUser) as YarnConfig\n\n  let npmConfig: Index<string | boolean> = {\n    ...keyValueBy(yarnConfigUser?.npmScopes || {}, npmRegistryKeyValue),\n    ...keyValueBy(yarnConfigLocal?.npmScopes || {}, npmRegistryKeyValue),\n  }\n\n  // npmAuthTokenKeyValue uses scoped npmRegistryServer, so must come after npmRegistryKeyValue\n  npmConfig = {\n    ...npmConfig,\n    ...keyValueBy(yarnConfigUser?.npmScopes || {}, npmAuthTokenKeyValue(npmConfig)),\n    ...keyValueBy(yarnConfigLocal?.npmScopes || {}, npmAuthTokenKeyValue(npmConfig)),\n  }\n\n  // set auth token after npm registry, since auth token syntax uses registry\n\n  if (yarnrcLocalExists) {\n    print(options, `\\nUsing local yarn config at ${yarnrcLocalPath}:`, 'verbose')\n    print(options, yarnConfigLocal, 'verbose')\n  }\n  if (yarnrcUserExists) {\n    print(options, `\\nUsing user yarn config at ${yarnrcUserPath}:`, 'verbose')\n    print(options, yarnConfigLocal, 'verbose')\n  }\n\n  if (Object.keys(npmConfig)) {\n    print(options, '\\nMerged yarn config in npm format:', 'verbose')\n    print(options, npmConfig, 'verbose')\n  }\n\n  return npmConfig\n})\n\n/**\n * Parse JSON lines and throw an informative error on failure.\n *\n * Note: although this is similar to the NPM parseJson() function we always return the\n * same concrete-type here, for now.\n *\n * @param result    Output from `yarn list --json` to be parsed\n */\nfunction parseJsonLines(result: string): Promise<{ dependencies: Index<ParsedDep> }> {\n  return new Promise((resolve, reject) => {\n    const dependencies: Index<ParsedDep> = {}\n\n    const parser = jsonlines.parse()\n\n    parser.on('data', d => {\n      // only parse info data\n      // ignore error info, e.g. \"Visit https://yarnpkg.com/en/docs/cli/list for documentation about this command.\"\n      if (d.type === 'info' && !d.data.match(/^Visit/)) {\n        // parse package name and version number from info data, e.g. \"nodemon@2.0.4\" has binaries\n        const [, pkgName, pkgVersion] = d.data.match(/\"(@?.*)@(.*)\"/) || []\n\n        dependencies[pkgName] = {\n          version: pkgVersion,\n          from: pkgName,\n        }\n      } else if (d.type === 'error') {\n        reject(new Error(d.data))\n      }\n    })\n\n    parser.on('end', () => {\n      resolve({ dependencies })\n    })\n\n    parser.on('error', reject)\n\n    parser.write(result)\n\n    parser.end()\n  })\n}\n\n/**\n * Extract first json line from multi line yarn output\n *\n * @param result    Output from yarn command to be parsed\n */\nfunction extractFirstJsonLine(result: string): Promise<string> {\n  return new Promise((resolve, reject) => {\n    const parser = jsonlines.parse()\n    let firstFound = false\n\n    parser.on('data', value => {\n      if (!firstFound) {\n        firstFound = true\n        resolve(JSON.stringify(value))\n      }\n    })\n    parser.on('error', reject)\n\n    parser.write(result)\n\n    parser.end()\n  })\n}\n\n/**\n * Spawn yarn requires a different command on Windows.\n *\n * @param args\n * @param [yarnOptions={}]\n * @param [spawnOptions={}]\n * @returns\n */\nasync function spawnYarn(\n  args: string | string[],\n  yarnOptions: NpmOptions = {},\n  spawnPleaseOptions: SpawnPleaseOptions = {},\n  spawnOptions: SpawnOptions = {},\n): Promise<string> {\n  const fullArgs = [\n    ...(yarnOptions.global ? ['global'] : []),\n    ...(yarnOptions.prefix ? [`--prefix=${yarnOptions.prefix}`] : []),\n    '--depth=0',\n    '--json',\n    '--no-progress',\n    // args must go after yarn options; otherwise, they are passed through to npm scripts\n    // https://github.com/raineorshine/npm-check-updates/issues/1362\n    ...(Array.isArray(args) ? args : [args]),\n  ]\n\n  const { stdout } = await spawnCommand('yarn', fullArgs, spawnPleaseOptions, spawnOptions)\n\n  return stdout\n}\n\n/**\n * Get platform-specific default prefix to pass on to yarn.\n *\n * @param options\n * @param [options.global]\n * @param [options.prefix]\n * @returns\n */\nexport async function defaultPrefix(options: Options): Promise<string | null> {\n  if (options.prefix) {\n    return Promise.resolve(options.prefix)\n  }\n\n  const { stdout: prefix } = await spawnCommand('yarn', ['global', 'dir'])\n    // yarn 2.0 does not support yarn global\n    // catch error to prevent process from crashing\n    // https://github.com/raineorshine/npm-check-updates/issues/873\n    .catch(() => ({\n      stdout: null,\n    }))\n\n  // FIX: for ncu -g doesn't work on homebrew or windows #146\n  // https://github.com/raineorshine/npm-check-updates/issues/146\n\n  return options.global && prefix && prefix.match('Cellar')\n    ? '/usr/local'\n    : // Workaround: get prefix on windows for global packages\n      // Only needed when using npm api directly\n      process.platform === 'win32' && options.global && !process.env.prefix\n      ? prefix\n        ? prefix.trim()\n        : `${process.env.LOCALAPPDATA}\\\\Yarn\\\\Data\\\\global`\n      : null\n}\n\n/**\n * Fetches the list of all installed packages.\n *\n * @param [options]\n * @param [options.cwd]\n * @param [options.global]\n * @param [options.prefix]\n * @returns\n */\nexport const list = async (options: Options = {}, spawnOptions?: SpawnOptions): Promise<Index<string | undefined>> => {\n  const jsonLines: string = await spawnYarn(\n    'list',\n    options as Index<string>,\n    {},\n    {\n      ...(options.cwd ? { cwd: options.cwd } : {}),\n      ...spawnOptions,\n    },\n  )\n  const json: { dependencies: Index<ParsedDep> } = await parseJsonLines(jsonLines)\n  const keyValues: Index<string | undefined> = keyValueBy<ParsedDep, string | undefined>(\n    json.dependencies,\n    (name, info): { [key: string]: string | undefined } => ({\n      // unmet peer dependencies have a different structure\n      [name]: info.version || info.required?.version,\n    }),\n  )\n  return keyValues\n}\n\n/** Wraps a GetVersion function and passes the yarn config. */\nconst withNpmConfigFromYarn =\n  (getVersion: GetVersion): GetVersion =>\n  async (packageName, currentVersion, options = {}) =>\n    getVersion(packageName, currentVersion, options, await npmConfigFromYarn(options))\n\nexport const distTag = withNpmConfigFromYarn(npm.distTag)\nexport const greatest = withNpmConfigFromYarn(npm.greatest)\nexport const latest = withNpmConfigFromYarn(npm.latest)\nexport const minor = withNpmConfigFromYarn(npm.minor)\nexport const newest = withNpmConfigFromYarn(npm.newest)\nexport const patch = withNpmConfigFromYarn(npm.patch)\nexport const semver = withNpmConfigFromYarn(npm.semver)\n\n/**\n * Fetches the list of peer dependencies for a specific package version.\n *\n * @param packageName\n * @param version\n * @param spawnOptions\n * @returns Promised {packageName: version} collection\n */\nexport const getPeerDependencies = async (\n  packageName: string,\n  version: Version,\n  spawnOptions: SpawnOptions,\n): Promise<Index<Version>> => {\n  const { stdout: yarnVersion } = await spawnCommand('yarn', ['--version'], { rejectOnError: false }, spawnOptions)\n  if (yarnVersion.startsWith('1')) {\n    const args = ['--json', 'info', `${packageName}@${version}`, 'peerDependencies']\n    const { stdout } = await spawnCommand('yarn', args, { rejectOnError: false }, spawnOptions)\n    return stdout ? npm.parseJson<{ data?: Index<Version> }>(stdout, { command: args.join(' ') }).data || {} : {}\n  } else {\n    const args = ['--json', 'npm', 'info', `${packageName}@${version}`, '--fields', 'peerDependencies']\n    const { stdout } = await spawnCommand('yarn', args, { rejectOnError: false }, spawnOptions)\n    if (!stdout) {\n      return {}\n    }\n    try {\n      return (\n        npm.parseJson<{ peerDependencies?: Index<Version> }>(stdout, { command: args.join(' ') }).peerDependencies || {}\n      )\n    } catch (parseError) {\n      /*\n      If package does not exist, yarn returns multiple json errors. As such, we want to extract just the first one, instead of crashing.\n      Example response:\n      {\"type\":\"error\",\"name\":35,\"displayName\":\"YN0035\",\"indent\":\"\",\"data\":\"Package not found\"}\n{\"type\":\"error\",\"name\":35,\"displayName\":\"YN0035\",\"indent\":\"\",\"data\":\"  \\u001b[96mResponse Code\\u001b[39m: \\u001b]8;;https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404\\u0007\\u001b[93m404\\u001b[39m (Not Found)\\u001b]8;;\\u0007\"}\n{\"type\":\"error\",\"name\":35,\"displayName\":\"YN0035\",\"indent\":\"\",\"data\":\"  \\u001b[96mRequest Method\\u001b[39m: GET\"}\n{\"type\":\"error\",\"name\":35,\"displayName\":\"YN0035\",\"indent\":\"\",\"data\":\"  \\u001b[96mRequest URL\\u001b[39m: \\u001b[95mhttps://registry.yarnpkg.com/fffffffffffff\\u001b[39m\"}\n       */\n      try {\n        const firstObj = await extractFirstJsonLine(stdout)\n        if (firstObj) {\n          return (\n            npm.parseJson<{ peerDependencies?: Index<Version> }>(firstObj, { command: args.join(' ') })\n              .peerDependencies || {}\n          )\n        }\n      } catch {}\n      throw parseError\n    }\n  }\n}\n\n/**\n * Fetches the engines list from the registry for a specific package version.\n *\n * @param packageName\n * @param version\n * @returns Promised engines collection\n */\nexport const getEngines = async (\n  packageName: string,\n  version: Version,\n  options: Options = {},\n): Promise<Index<VersionSpec | undefined>> =>\n  npm.getEngines(packageName, version, options, await npmConfigFromYarn(options))\n\n/**\n * Check if package author changed between current and upgraded version.\n *\n * @param packageName Name of the package\n * @param currentVersion Current version declaration (may be range)\n * @param upgradedVersion Upgraded version declaration (may be range)\n * @param npmConfigLocal Additional npm config variables that are merged into the system npm config\n * @returns A promise that fulfills with boolean value.\n */\nexport const packageAuthorChanged = async (\n  packageName: string,\n  currentVersion: VersionSpec,\n  upgradedVersion: VersionSpec,\n  options: Options = {},\n): Promise<boolean> =>\n  npm.packageAuthorChanged(packageName, currentVersion, upgradedVersion, options, await npmConfigFromYarn(options))\n\nexport default spawnYarn\n"
  },
  {
    "path": "src/scripts/build-options.ts",
    "content": "import fs from 'fs/promises'\nimport spawn from 'spawn-please'\nimport cliOptions, { renderExtendedHelp } from '../cli-options'\nimport { chalkInit } from '../lib/chalk'\nimport CLIOption from '../types/CLIOption'\n\nconst INJECT_HEADER =\n  '<!-- Do not edit this section by hand. It is auto-generated in build-options.ts. Run \"npm run build\" or \"npm run build:options\" to build. -->'\n\n/** Replaces markdown code ticks with <code>...</code> tag. */\nconst codeHtml = (code: string) => code.replace(/`(.+?)`/g, '<code>$1</code>')\n\n/** Replaces the \"Options\" and \"Advanced Options\" sections of the README with direct output from \"ncu --help\". */\nconst injectReadme = async () => {\n  const { default: stripAnsi } = await import('strip-ansi')\n  let readme = await fs.readFile('README.md', 'utf8')\n  const optionRows = cliOptions\n    .map(option => {\n      return `  <tr>\n    <td>${option.help ? `<a href=\"#${option.long.toLowerCase()}\">` : ''}${option.short ? `-${option.short}, ` : ''}${\n      option.cli !== false ? '--' : ''\n    }${option.long}${option.arg ? ` &lt;${option.arg}&gt;` : ''}${option.help ? '</a>' : ''}</td>\n    <td>${codeHtml(option.description)}${option.default ? ` (default: ${JSON.stringify(option.default)})` : ''}</td>\n  </tr>`\n    })\n    .join('\\n')\n\n  // inject options into README\n  const optionsStart = readme.indexOf('<!-- BEGIN Options -->') + '<!-- BEGIN Options -->'.length\n  const optionsEnd = readme.indexOf('<!-- END Options -->', optionsStart)\n  readme = `${readme.slice(0, optionsStart)}\n${INJECT_HEADER}\n\n<table>\n${optionRows}\n</table>\n\n${readme.slice(optionsEnd)}`\n\n  // Inject advanced options into README\n  // Even though chalkInit has a colorless option, we need stripAnsi to remove the ANSI characters frim the output of cli-table\n  await chalkInit()\n  const advancedOptionsStart =\n    readme.indexOf('<!-- BEGIN Advanced Options -->') + '<!-- BEGIN Advanced Options -->'.length\n  const advancedOptionsEnd = readme.indexOf('<!-- END Advanced Options -->', advancedOptionsStart)\n  readme = `${readme.slice(0, advancedOptionsStart)}\n${INJECT_HEADER}\n\n${cliOptions\n  .filter(option => option.help)\n  .map(\n    option => `## ${option.long}\n\n${stripAnsi(renderExtendedHelp(option, { markdown: true }))}\n`,\n  )\n  .join('\\n')}\n${readme.slice(advancedOptionsEnd)}`\n\n  return readme\n}\n\n/** Renders a single CLI option for a type definition file. */\nconst renderOption = (option: CLIOption<unknown>) => {\n  // deepPatternFix needs to be escaped; otherwise, it will break the block comment\n  const description = option.long === 'deep' ? option.description.replace('**/', '**\\\\/') : option.description\n\n  // pre must be internally typed as number and externally typed as boolean to maintain compatibility with the CLI option and the RunOption\n  const type = option.long === 'pre' ? 'boolean' : option.type\n\n  const defaults =\n    // do not render default empty arrays\n    option.default && (!Array.isArray(option.default) || option.default.length > 0)\n      ? `\\n   *\\n   * @default ${JSON.stringify(option.default)}\\n  `\n      : ''\n\n  // all options are optional\n  return `  /** ${description}${option.help ? ` Run \"ncu --help --${option.long}\" for details.` : ''}${defaults} */\n  ${option.long}?: ${type}\n`\n}\n\n/** Generate /src/types/RunOptions from cli-options so there is a single source of truth. */\nconst generateRunOptions = (options: CLIOption<unknown>[]) => {\n  const header = `/** This file is generated automatically from the options specified in /src/cli-options.ts. Do not edit manually. Run \"npm run build\" or \"npm run build:options\" to build. */\nimport { CooldownFunction } from './CooldownFunction'\nimport { FilterFunction } from './FilterFunction'\nimport { FilterResultsFunction } from './FilterResultsFunction'\nimport { GroupFunction } from './GroupFunction'\nimport { PackageFile } from './PackageFile'\nimport { TargetFunction } from './TargetFunction'\n\n/** Options that can be given on the CLI or passed to the ncu module to control all behavior. */\nexport interface RunOptions {\n`\n\n  const footer = '}\\n'\n\n  const optionsTypeCode = options.map(renderOption).join('\\n')\n\n  const output = `${header}${optionsTypeCode}${footer}`\n\n  return output\n}\n\n/** Generates a JSON schema for the ncurc file. */\nconst generateRunOptionsJsonSchema = async (): Promise<string> => {\n  // programmatic usage of typescript-json-schema does not work, at least not straightforwardly.\n  // Use the CLI which works out-of-the-box.\n  const { stdout } = await spawn('typescript-json-schema', ['tsconfig.json', 'RunOptions'])\n  return stdout\n}\n\n;(async () => {\n  await fs.writeFile('README.md', await injectReadme())\n  await fs.writeFile('src/types/RunOptions.ts', generateRunOptions(cliOptions))\n  await fs.writeFile('src/types/RunOptions.json', await generateRunOptionsJsonSchema())\n  await spawn('prettier', ['-w', 'src/types/RunOptions.json'])\n})()\n"
  },
  {
    "path": "src/scripts/install-hooks",
    "content": "#!/usr/bin/env bash\n\n# Use a simple bash script to install git hooks.\n# husky was slow and did not play nice with nvm.\n# lefthook suppressed the post-commit notification for some reason.\n\n# change the git hook directory from .git/hooks to .hooks\ngit config core.hooksPath .hooks\n"
  },
  {
    "path": "src/types/CLIOption.ts",
    "content": "import ExtendedHelp from './ExtendedHelp'\n\nexport interface CLIOption<T = any> {\n  arg?: string\n  choices?: T[]\n  /** If false, the option is only usable in the ncurc file, or when using npm-check-updates as a module, not on the command line. */\n  cli?: boolean\n  default?: T\n  deprecated?: boolean\n  description: string\n  help?: ExtendedHelp\n  /** Must be prepared to handle unknown input types since the user's ncurc.json may not match the schema. */\n  parse?: (s: unknown, p?: T) => T\n  long: string\n  short?: string\n  type: string\n}\n\nexport default CLIOption\n"
  },
  {
    "path": "src/types/Cacher.ts",
    "content": "import { Index } from './IndexType'\nimport { Version } from './Version'\n\nexport interface CacheData {\n  timestamp?: number\n  packages?: Record<string, string | undefined>\n  peers?: Record<string, Index<string> | undefined>\n}\n\nexport type Cacher = {\n  get(name: string, target: string): string | undefined\n  set(name: string, target: string, version: string): void\n  getPeers(name: string, version: Version): Index<string> | undefined\n  setPeers(name: string, version: Version, peers: Index<string>): void\n  save(): Promise<void>\n  log(peers?: boolean): void\n}\n"
  },
  {
    "path": "src/types/CatalogConfig.ts",
    "content": "import { z } from 'zod'\n\nexport const CatalogsConfig = z.object({\n  catalog: z.optional(z.record(z.string(), z.string())),\n  catalogs: z.optional(z.record(z.string(), z.record(z.string(), z.string()))).catch(undefined),\n})\n\nexport type CatalogsConfig = z.infer<typeof CatalogsConfig>\n"
  },
  {
    "path": "src/types/CooldownFunction.ts",
    "content": "/** A function that can be provided to the --cooldown option for custom cooldown predicate. */\nexport type CooldownFunction = (packageName: string) => number | string | null\n"
  },
  {
    "path": "src/types/DependencyGroup.ts",
    "content": "import { Index } from './IndexType'\n\nexport interface DependencyGroup {\n  heading: string\n  groupName: string\n  packages: Index<string>\n}\n"
  },
  {
    "path": "src/types/ExtendedHelp.ts",
    "content": "/** A function that renders extended help for an option. */\ntype ExtendedHelp = string | ((options: { markdown?: boolean }) => string)\n\nexport default ExtendedHelp\n"
  },
  {
    "path": "src/types/FilterFunction.ts",
    "content": "import { SemVer } from 'semver-utils'\n\n/** Supported function for the --filter and --reject options. */\nexport type FilterFunction = (packageName: string, versionRange: SemVer[]) => boolean\n"
  },
  {
    "path": "src/types/FilterPattern.ts",
    "content": "import { FilterFunction } from './FilterFunction'\n\n/** Supported patterns for the --filter and --reject options. */\nexport type FilterPattern = string | RegExp | readonly (string | RegExp)[] | FilterFunction\n"
  },
  {
    "path": "src/types/FilterResultsFunction.ts",
    "content": "import { SemVer } from 'semver-utils'\nimport { Version } from './Version'\nimport { VersionSpec } from './VersionSpec'\n\nexport type FilterResultsFunction = (\n  packageName: string,\n  versioningMetadata: {\n    currentVersion: VersionSpec\n    currentVersionSemver: SemVer[]\n    upgradedVersion: Version\n    upgradedVersionSemver: SemVer\n  },\n) => boolean\n"
  },
  {
    "path": "src/types/GetVersion.ts",
    "content": "import { NpmConfig } from './NpmConfig'\nimport { Options } from './Options'\nimport { Version } from './Version'\nimport { VersionResult } from './VersionResult'\n\n/** A function that gets a target version of a dependency. */\nexport type GetVersion = (\n  packageName: string,\n  currentVersion: Version,\n  options?: Options,\n  npmConfig?: NpmConfig,\n  npmConfigProject?: NpmConfig,\n) => Promise<VersionResult>\n"
  },
  {
    "path": "src/types/GroupFunction.ts",
    "content": "import { SemVer } from 'semver-utils'\nimport { UpgradeGroup } from './UpgradeGroup'\n\n/** Customize how packages are divided into groups when using `--format group`. Run \"ncu --help --groupFunction\" for details. */\nexport type GroupFunction = (\n  packageName: string,\n  defaultGroup: UpgradeGroup,\n  currentVersionSpec: SemVer[],\n  upgradedVersionSpec: SemVer[],\n  upgradedVersion: SemVer | null,\n) => UpgradeGroup | string\n"
  },
  {
    "path": "src/types/IgnoredUpgradeDueToEnginesNode.ts",
    "content": "import { Version } from './Version'\nimport { VersionSpec } from './VersionSpec'\n\n/** An object that represents an upgrade that was ignored due to mismatch of engines.node */\nexport interface IgnoredUpgradeDueToEnginesNode {\n  from: Version\n  to: Version\n  enginesNode: VersionSpec\n}\n"
  },
  {
    "path": "src/types/IgnoredUpgradeDueToPeerDeps.ts",
    "content": "import { Index } from './IndexType'\nimport { Version } from './Version'\n\n/** An object that represents an upgrade that was ignored due to peer dependencies, along with the reason. */\nexport interface IgnoredUpgradeDueToPeerDeps {\n  from: Version\n  to: Version\n  reason: Index<string>\n}\n"
  },
  {
    "path": "src/types/IndexType.ts",
    "content": "// This file cannot be named Index.ts as it conflicts with default directory import.\n\n/** A very generic object. */\nexport type Index<T = any> = Record<string, T>\n"
  },
  {
    "path": "src/types/Maybe.ts",
    "content": "/** A value that may be null or undefined. */\nexport type Maybe<T = any> = T | null | undefined\n"
  },
  {
    "path": "src/types/MockedVersions.ts",
    "content": "import { Index } from './IndexType'\nimport { Options } from './Options'\nimport { Packument } from './Packument'\nimport { Version } from './Version'\n\n/** Parameter type for stubVersions. */\nexport type MockedVersions =\n  | Version\n  | Partial<Packument>\n  | Index<Version>\n  | Index<Partial<Packument>>\n  | ((options: Options) => Index<Version> | Index<Partial<Packument>> | null)\n"
  },
  {
    "path": "src/types/NpmConfig.ts",
    "content": "import { Index } from './IndexType'\n\nexport type NpmConfig = Index<string | boolean | ((path: string) => string | { ca: string[] } | undefined)>\n"
  },
  {
    "path": "src/types/NpmOptions.ts",
    "content": "/** Options that can be provided to npm. */\nexport interface NpmOptions {\n  global?: boolean\n  prefix?: string\n  registry?: string\n}\n"
  },
  {
    "path": "src/types/Options.ts",
    "content": "import { Cacher } from './Cacher'\nimport { Index } from './IndexType'\nimport { RunOptions } from './RunOptions'\nimport { VersionSpec } from './VersionSpec'\n\n/** Internal, normalized options for all ncu behavior. Includes RunOptions that are specified in the CLI or passed to the ncu module, as well as meta information including CLI arguments, package information, and ncurc config. */\nexport type Options = RunOptions & {\n  args?: any[]\n  cacher?: Cacher\n  cli?: boolean\n  distTag?: string\n  json?: boolean\n  nodeEngineVersion?: VersionSpec\n  packageData?: string\n  peerDependencies?: Index<any>\n  rcConfigPath?: string\n  // A list of local workspace packages by name.\n  // This is used to ignore local workspace packages when fetching new versions.\n  workspacePackages?: string[]\n}\n"
  },
  {
    "path": "src/types/PackageFile.ts",
    "content": "import { Index } from './IndexType'\nimport { PackageFileRepository } from './PackageFileRepository'\nimport { VersionSpec } from './VersionSpec'\n\ntype NestedVersionSpecs = {\n  [name: string]: VersionSpec | NestedVersionSpecs\n}\n\n/** The relevant bits of a parsed package.json file. */\nexport interface PackageFile {\n  dependencies?: Index<VersionSpec>\n  devDependencies?: Index<VersionSpec>\n  // deno only\n  imports?: Index<VersionSpec>\n  engines?: Index<VersionSpec>\n  homepage?: string\n  name?: string\n  // https://nodejs.org/api/packages.html#packagemanager\n  packageManager?: string\n  optionalDependencies?: Index<VersionSpec>\n  overrides?: NestedVersionSpecs\n  peerDependencies?: Index<VersionSpec>\n  repository?: string | PackageFileRepository\n  scripts?: Index<string>\n  workspaces?: string[] | { packages: string[] }\n  version?: string\n}\n"
  },
  {
    "path": "src/types/PackageFileRepository.ts",
    "content": "/** Represents the repository field in package.json. */\nexport interface PackageFileRepository {\n  url: string\n  directory?: string\n}\n"
  },
  {
    "path": "src/types/PackageInfo.ts",
    "content": "import { PackageFile } from './PackageFile'\n\n/** Describes package data plus it's filepath */\nexport interface PackageInfo {\n  name?: string\n  pkg: PackageFile\n  pkgFile: string // the raw file string\n  filepath: string\n}\n"
  },
  {
    "path": "src/types/PackageManager.ts",
    "content": "import { GetVersion } from './GetVersion'\nimport { Index } from './IndexType'\nimport { NpmConfig } from './NpmConfig'\nimport { Options } from './Options'\nimport { SpawnOptions } from './SpawnOptions'\nimport { Version } from './Version'\nimport { VersionSpec } from './VersionSpec'\n\n/** The package manager API that ncu uses to fetch versions and meta information for packages. Includes npm and yarn, and others can be added as needed. */\nexport interface PackageManager {\n  defaultPrefix?: (options: Options) => Promise<string | undefined>\n  list?: (options: Options) => Promise<Index<Version>>\n  latest: GetVersion\n  minor?: GetVersion\n  newest?: GetVersion\n  patch?: GetVersion\n  greatest?: GetVersion\n  semver?: GetVersion\n  packageAuthorChanged?: (\n    packageName: string,\n    from: VersionSpec,\n    to: VersionSpec,\n    options?: Options,\n  ) => Promise<boolean>\n  getPeerDependencies?: (packageName: string, version: Version, spawnOptions: SpawnOptions) => Promise<Index<Version>>\n  getEngines?: (\n    packageName: string,\n    version: Version,\n    options: Options,\n    npmConfigLocal?: NpmConfig,\n  ) => Promise<Index<VersionSpec | undefined>>\n}\n"
  },
  {
    "path": "src/types/PackageManagerName.ts",
    "content": "/** A valid package manager. */\nexport type PackageManagerName = 'npm' | 'yarn' | 'pnpm' | 'deno' | 'bun' | 'staticRegistry'\n"
  },
  {
    "path": "src/types/Packument.ts",
    "content": "import { Index } from './IndexType'\nimport { Version } from './Version'\n\n/** A packument result object from npm-registry-fetch. */\nexport interface Packument {\n  name: string\n  deprecated?: boolean\n  'dist-tags': Index<Version>\n  engines: {\n    node: string\n  }\n  // fullMetadata only\n  // TODO: store only the time of the latest version?\n  time?: Index<string>\n  version: Version\n  versions: Index<\n    Omit<Packument, 'versions'> & {\n      _npmUser?: {\n        name: string\n      }\n    }\n  >\n}\n"
  },
  {
    "path": "src/types/RcOptions.ts",
    "content": "import { RunOptions } from './RunOptions'\n\n/** Options that would make no sense in a .ncurc file */\ntype Nonsensical = 'configFileName' | 'configFilePath' | 'cwd' | 'packageData' | 'stdin'\n\n/** Expected options that might be found in an .ncurc file. Since the config is external, this cannot be guaranteed */\nexport type RcOptions = Omit<RunOptions, Nonsensical> & {\n  $schema?: string\n  format?: string | string[] // Format is often set as a string, but needs to be an array\n}\n"
  },
  {
    "path": "src/types/RunOptions.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"definitions\": {\n    \"Index<string>\": {\n      \"description\": \"A very generic object.\",\n      \"type\": \"object\"\n    },\n    \"NestedVersionSpecs\": {\n      \"additionalProperties\": {\n        \"anyOf\": [\n          {\n            \"$ref\": \"#/definitions/NestedVersionSpecs\"\n          },\n          {\n            \"type\": \"string\"\n          }\n        ]\n      },\n      \"type\": \"object\"\n    },\n    \"PackageFile\": {\n      \"description\": \"The relevant bits of a parsed package.json file.\",\n      \"properties\": {\n        \"dependencies\": {\n          \"$ref\": \"#/definitions/Index<string>\",\n          \"description\": \"A very generic object.\"\n        },\n        \"devDependencies\": {\n          \"$ref\": \"#/definitions/Index<string>\",\n          \"description\": \"A very generic object.\"\n        },\n        \"engines\": {\n          \"$ref\": \"#/definitions/Index<string>\",\n          \"description\": \"A very generic object.\"\n        },\n        \"homepage\": {\n          \"type\": \"string\"\n        },\n        \"imports\": {\n          \"$ref\": \"#/definitions/Index<string>\",\n          \"description\": \"A very generic object.\"\n        },\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"optionalDependencies\": {\n          \"$ref\": \"#/definitions/Index<string>\",\n          \"description\": \"A very generic object.\"\n        },\n        \"overrides\": {\n          \"$ref\": \"#/definitions/NestedVersionSpecs\"\n        },\n        \"packageManager\": {\n          \"type\": \"string\"\n        },\n        \"peerDependencies\": {\n          \"$ref\": \"#/definitions/Index<string>\",\n          \"description\": \"A very generic object.\"\n        },\n        \"repository\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/PackageFileRepository\"\n            },\n            {\n              \"type\": \"string\"\n            }\n          ]\n        },\n        \"scripts\": {\n          \"$ref\": \"#/definitions/Index<string>\",\n          \"description\": \"A very generic object.\"\n        },\n        \"version\": {\n          \"type\": \"string\"\n        },\n        \"workspaces\": {\n          \"anyOf\": [\n            {\n              \"items\": {\n                \"type\": \"string\"\n              },\n              \"type\": \"array\"\n            },\n            {\n              \"properties\": {\n                \"packages\": {\n                  \"items\": {\n                    \"type\": \"string\"\n                  },\n                  \"type\": \"array\"\n                }\n              },\n              \"type\": \"object\"\n            }\n          ]\n        }\n      },\n      \"type\": \"object\"\n    },\n    \"PackageFileRepository\": {\n      \"description\": \"Represents the repository field in package.json.\",\n      \"properties\": {\n        \"directory\": {\n          \"type\": \"string\"\n        },\n        \"url\": {\n          \"type\": \"string\"\n        }\n      },\n      \"type\": \"object\"\n    },\n    \"RegExp\": {\n      \"properties\": {\n        \"dotAll\": {\n          \"type\": \"boolean\"\n        },\n        \"flags\": {\n          \"type\": \"string\"\n        },\n        \"global\": {\n          \"type\": \"boolean\"\n        },\n        \"hasIndices\": {\n          \"type\": \"boolean\"\n        },\n        \"ignoreCase\": {\n          \"type\": \"boolean\"\n        },\n        \"lastIndex\": {\n          \"type\": \"number\"\n        },\n        \"multiline\": {\n          \"type\": \"boolean\"\n        },\n        \"source\": {\n          \"type\": \"string\"\n        },\n        \"sticky\": {\n          \"type\": \"boolean\"\n        },\n        \"unicode\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"type\": \"object\"\n    }\n  },\n  \"description\": \"Options that can be given on the CLI or passed to the ncu module to control all behavior.\",\n  \"properties\": {\n    \"cache\": {\n      \"description\": \"Cache versions to a local cache file. Default `--cacheFile` is ~/.ncu-cache.json and default `--cacheExpiration` is 10 minutes.\",\n      \"type\": \"boolean\"\n    },\n    \"cacheClear\": {\n      \"description\": \"Clear the default cache, or the cache file specified by `--cacheFile`.\",\n      \"type\": \"boolean\"\n    },\n    \"cacheExpiration\": {\n      \"default\": 10,\n      \"description\": \"Cache expiration in minutes. Only works with `--cache`.\",\n      \"type\": \"number\"\n    },\n    \"cacheFile\": {\n      \"default\": \"~/.ncu-cache.json\",\n      \"description\": \"Filepath for the cache file. Only works with `--cache`.\",\n      \"type\": \"string\"\n    },\n    \"color\": {\n      \"description\": \"Force color in terminal.\",\n      \"type\": \"boolean\"\n    },\n    \"concurrency\": {\n      \"default\": 8,\n      \"description\": \"Max number of concurrent HTTP requests to registry.\",\n      \"type\": \"number\"\n    },\n    \"configFileName\": {\n      \"description\": \"Config file name. (default: .ncurc.{json,yml,js,cjs})\",\n      \"type\": \"string\"\n    },\n    \"configFilePath\": {\n      \"description\": \"Directory of .ncurc config file. (default: directory of `packageFile`)\",\n      \"type\": \"string\"\n    },\n    \"cooldown\": {\n      \"anyOf\": [\n        {\n          \"description\": \"A function that can be provided to the --cooldown option for custom cooldown predicate.\",\n          \"type\": \"object\"\n        },\n        {\n          \"type\": [\"string\", \"number\"]\n        }\n      ],\n      \"description\": \"Sets a minimum age for package versions to be considered for upgrade. Accepts a number (days) or a string with a unit: \\\"7d\\\" (days), \\\"12h\\\" (hours), \\\"30m\\\" (minutes). Reduces the risk of installing newly published, potentially compromised packages. Run \\\"ncu --help --cooldown\\\" for details.\"\n    },\n    \"cwd\": {\n      \"description\": \"Working directory in which npm will be executed.\",\n      \"type\": \"string\"\n    },\n    \"deep\": {\n      \"description\": \"Run recursively in current working directory. Alias of (`--packageFile '**\\\\/package.json'`).\",\n      \"type\": \"boolean\"\n    },\n    \"dep\": {\n      \"anyOf\": [\n        {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\"\n        },\n        {\n          \"type\": \"string\"\n        }\n      ],\n      \"default\": [\"prod\", \"dev\", \"optional\", \"packageManager\"],\n      \"description\": \"Check one or more sections of dependencies only: dev, optional, peer, prod, or packageManager (comma-delimited).\"\n    },\n    \"deprecated\": {\n      \"default\": true,\n      \"description\": \"Include deprecated packages. Use `--no-deprecated` to exclude deprecated packages (20–25% slower).\",\n      \"type\": \"boolean\"\n    },\n    \"doctor\": {\n      \"description\": \"Iteratively installs upgrades and runs tests to identify breaking upgrades. Requires `-u` to execute. Run \\\"ncu --help --doctor\\\" for details.\",\n      \"type\": \"boolean\"\n    },\n    \"doctorInstall\": {\n      \"description\": \"Specifies the install script to use in doctor mode. (default: `npm install` or the equivalent for your package manager)\",\n      \"type\": \"string\"\n    },\n    \"doctorTest\": {\n      \"description\": \"Specifies the test script to use in doctor mode. (default: `npm test`)\",\n      \"type\": \"string\"\n    },\n    \"enginesNode\": {\n      \"description\": \"Include only packages that satisfy engines.node as specified in the package file.\",\n      \"type\": \"boolean\"\n    },\n    \"errorLevel\": {\n      \"default\": 1,\n      \"description\": \"Set the error level. 1: exits with error code 0 if no errors occur. 2: exits with error code 0 if no packages need updating (useful for continuous integration).\",\n      \"type\": \"number\"\n    },\n    \"filter\": {\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/RegExp\"\n        },\n        {\n          \"description\": \"Supported function for the --filter and --reject options.\",\n          \"type\": \"object\"\n        },\n        {\n          \"items\": {\n            \"anyOf\": [\n              {\n                \"$ref\": \"#/definitions/RegExp\"\n              },\n              {\n                \"type\": \"string\"\n              }\n            ]\n          },\n          \"type\": \"array\"\n        },\n        {\n          \"type\": \"string\"\n        }\n      ],\n      \"description\": \"Include only package names matching the given string, wildcard, glob, comma-or-space-delimited list, /regex/, or predicate function. Run \\\"ncu --help --filter\\\" for details.\"\n    },\n    \"filterResults\": {\n      \"description\": \"Filters results based on a user provided predicate function after fetching new versions. Run \\\"ncu --help --filterResults\\\" for details.\",\n      \"type\": \"object\"\n    },\n    \"filterVersion\": {\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/RegExp\"\n        },\n        {\n          \"description\": \"Supported function for the --filter and --reject options.\",\n          \"type\": \"object\"\n        },\n        {\n          \"items\": {\n            \"anyOf\": [\n              {\n                \"$ref\": \"#/definitions/RegExp\"\n              },\n              {\n                \"type\": \"string\"\n              }\n            ]\n          },\n          \"type\": \"array\"\n        },\n        {\n          \"type\": \"string\"\n        }\n      ],\n      \"description\": \"Filter on package version using comma-or-space-delimited list, /regex/, or predicate function. Run \\\"ncu --help --filterVersion\\\" for details.\"\n    },\n    \"format\": {\n      \"description\": \"Modify the output formatting or show additional information. Specify one or more comma-delimited values: dep, group, ownerChanged, repo, time, lines, installedVersion. Run \\\"ncu --help --format\\\" for details.\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"type\": \"array\"\n    },\n    \"global\": {\n      \"description\": \"Check global packages instead of in the current project.\",\n      \"type\": \"boolean\"\n    },\n    \"groupFunction\": {\n      \"description\": \"Customize how packages are divided into groups when using `--format group`. Run \\\"ncu --help --groupFunction\\\" for details.\",\n      \"type\": \"object\"\n    },\n    \"install\": {\n      \"default\": \"prompt\",\n      \"description\": \"Control the auto-install behavior: always, never, prompt. Run \\\"ncu --help --install\\\" for details.\",\n      \"enum\": [\"always\", \"never\", \"prompt\"],\n      \"type\": \"string\"\n    },\n    \"interactive\": {\n      \"description\": \"Enable interactive prompts for each dependency; implies `-u` unless one of the json options are set.\",\n      \"type\": \"boolean\"\n    },\n    \"jsonAll\": {\n      \"description\": \"Output new package file instead of human-readable message.\",\n      \"type\": \"boolean\"\n    },\n    \"jsonDeps\": {\n      \"description\": \"Like `jsonAll` but only lists `dependencies`, `devDependencies`, `optionalDependencies`, etc of the new package data.\",\n      \"type\": \"boolean\"\n    },\n    \"jsonUpgraded\": {\n      \"description\": \"Output upgraded dependencies in json.\",\n      \"type\": \"boolean\"\n    },\n    \"loglevel\": {\n      \"default\": \"warn\",\n      \"description\": \"Amount to log: silent, error, minimal, warn, info, verbose, silly.\",\n      \"type\": \"string\"\n    },\n    \"mergeConfig\": {\n      \"description\": \"Merges nested configs with the root config file for `--deep` or `--packageFile` options. (default: false)\",\n      \"type\": \"boolean\"\n    },\n    \"minimal\": {\n      \"description\": \"Do not upgrade newer versions that are already satisfied by the version range according to semver.\",\n      \"type\": \"boolean\"\n    },\n    \"packageData\": {\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/PackageFile\"\n        },\n        {\n          \"type\": \"string\"\n        }\n      ],\n      \"description\": \"Package file data (you can also use stdin).\"\n    },\n    \"packageFile\": {\n      \"description\": \"Package file(s) location. (default: ./package.json)\",\n      \"type\": \"string\"\n    },\n    \"packageManager\": {\n      \"description\": \"npm, yarn, pnpm, deno, bun, staticRegistry (default: npm). Run \\\"ncu --help --packageManager\\\" for details.\",\n      \"enum\": [\"bun\", \"deno\", \"npm\", \"pnpm\", \"staticRegistry\", \"yarn\"],\n      \"type\": \"string\"\n    },\n    \"peer\": {\n      \"description\": \"Check peer dependencies of installed packages and filter updates to compatible versions. Run \\\"ncu --help --peer\\\" for details.\",\n      \"type\": \"boolean\"\n    },\n    \"pre\": {\n      \"description\": \"Include prerelease versions, e.g. -alpha.0, -beta.5, -rc.2. Automatically set to 1 when `--target` is newest or greatest, or when the current version is a prerelease. (default: 0)\",\n      \"type\": \"boolean\"\n    },\n    \"prefix\": {\n      \"description\": \"Current working directory of npm.\",\n      \"type\": \"string\"\n    },\n    \"registry\": {\n      \"description\": \"Specify the registry to use when looking up package versions.\",\n      \"type\": \"string\"\n    },\n    \"registryType\": {\n      \"description\": \"Specify whether --registry refers to a full npm registry or a simple JSON file or url: npm, json. (default: npm) Run \\\"ncu --help --registryType\\\" for details.\",\n      \"enum\": [\"json\", \"npm\"],\n      \"type\": \"string\"\n    },\n    \"reject\": {\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/RegExp\"\n        },\n        {\n          \"description\": \"Supported function for the --filter and --reject options.\",\n          \"type\": \"object\"\n        },\n        {\n          \"items\": {\n            \"anyOf\": [\n              {\n                \"$ref\": \"#/definitions/RegExp\"\n              },\n              {\n                \"type\": \"string\"\n              }\n            ]\n          },\n          \"type\": \"array\"\n        },\n        {\n          \"type\": \"string\"\n        }\n      ],\n      \"description\": \"Exclude packages matching the given string, wildcard, glob, comma-or-space-delimited list, /regex/, or predicate function. Run \\\"ncu --help --reject\\\" for details.\"\n    },\n    \"rejectVersion\": {\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/RegExp\"\n        },\n        {\n          \"description\": \"Supported function for the --filter and --reject options.\",\n          \"type\": \"object\"\n        },\n        {\n          \"items\": {\n            \"anyOf\": [\n              {\n                \"$ref\": \"#/definitions/RegExp\"\n              },\n              {\n                \"type\": \"string\"\n              }\n            ]\n          },\n          \"type\": \"array\"\n        },\n        {\n          \"type\": \"string\"\n        }\n      ],\n      \"description\": \"Exclude package.json versions using comma-or-space-delimited list, /regex/, or predicate function. Run \\\"ncu --help --rejectVersion\\\" for details.\"\n    },\n    \"removeRange\": {\n      \"description\": \"Remove version ranges from the final package version.\",\n      \"type\": \"boolean\"\n    },\n    \"retry\": {\n      \"default\": 3,\n      \"description\": \"Number of times to retry failed requests for package info.\",\n      \"type\": \"number\"\n    },\n    \"root\": {\n      \"default\": true,\n      \"description\": \"Runs updates on the root project in addition to specified workspaces. Only allowed with `--workspace` or `--workspaces`.\",\n      \"type\": \"boolean\"\n    },\n    \"silent\": {\n      \"description\": \"Don't output anything. Alias for `--loglevel` silent.\",\n      \"type\": \"boolean\"\n    },\n    \"stdin\": {\n      \"description\": \"Read package.json from stdin.\",\n      \"type\": \"string\"\n    },\n    \"target\": {\n      \"anyOf\": [\n        {\n          \"pattern\": \"^@.*$\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"A function that can be provided to the --target option for custom filtering.\",\n          \"type\": \"object\"\n        },\n        {\n          \"enum\": [\"greatest\", \"latest\", \"minor\", \"newest\", \"patch\", \"semver\"],\n          \"type\": \"string\"\n        }\n      ],\n      \"description\": \"Determines the version to upgrade to: latest, newest, greatest, minor, patch, semver, `@[tag]`, or [function]. (default: latest) Run \\\"ncu --help --target\\\" for details.\"\n    },\n    \"timeout\": {\n      \"description\": \"Global timeout in milliseconds. (default: no global timeout and 30 seconds per npm-registry-fetch)\",\n      \"type\": \"number\"\n    },\n    \"upgrade\": {\n      \"description\": \"Overwrite package file with upgraded versions instead of just outputting to console.\",\n      \"type\": \"boolean\"\n    },\n    \"verbose\": {\n      \"description\": \"Log additional information for debugging. Alias for `--loglevel` verbose.\",\n      \"type\": \"boolean\"\n    },\n    \"workspace\": {\n      \"description\": \"Run on one or more specified workspaces. Add `--no-root` to exclude the root project.\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"type\": \"array\"\n    },\n    \"workspaces\": {\n      \"description\": \"Run on all workspaces. Add `--no-root` to exclude the root project.\",\n      \"type\": \"boolean\"\n    }\n  },\n  \"type\": \"object\"\n}\n"
  },
  {
    "path": "src/types/RunOptions.ts",
    "content": "/** This file is generated automatically from the options specified in /src/cli-options.ts. Do not edit manually. Run \"npm run build\" or \"npm run build:options\" to build. */\nimport { CooldownFunction } from './CooldownFunction'\nimport { FilterFunction } from './FilterFunction'\nimport { FilterResultsFunction } from './FilterResultsFunction'\nimport { GroupFunction } from './GroupFunction'\nimport { PackageFile } from './PackageFile'\nimport { TargetFunction } from './TargetFunction'\n\n/** Options that can be given on the CLI or passed to the ncu module to control all behavior. */\nexport interface RunOptions {\n  /** Cache versions to a local cache file. Default `--cacheFile` is ~/.ncu-cache.json and default `--cacheExpiration` is 10 minutes. */\n  cache?: boolean\n\n  /** Clear the default cache, or the cache file specified by `--cacheFile`. */\n  cacheClear?: boolean\n\n  /** Cache expiration in minutes. Only works with `--cache`.\n   *\n   * @default 10\n   */\n  cacheExpiration?: number\n\n  /** Filepath for the cache file. Only works with `--cache`.\n   *\n   * @default \"~/.ncu-cache.json\"\n   */\n  cacheFile?: string\n\n  /** Force color in terminal. */\n  color?: boolean\n\n  /** Max number of concurrent HTTP requests to registry.\n   *\n   * @default 8\n   */\n  concurrency?: number\n\n  /** Config file name. (default: .ncurc.{json,yml,js,cjs}) */\n  configFileName?: string\n\n  /** Directory of .ncurc config file. (default: directory of `packageFile`) */\n  configFilePath?: string\n\n  /** Sets a minimum age for package versions to be considered for upgrade. Accepts a number (days) or a string with a unit: \"7d\" (days), \"12h\" (hours), \"30m\" (minutes). Reduces the risk of installing newly published, potentially compromised packages. Run \"ncu --help --cooldown\" for details. */\n  cooldown?: number | string | CooldownFunction\n\n  /** Working directory in which npm will be executed. */\n  cwd?: string\n\n  /** Run recursively in current working directory. Alias of (`--packageFile '**\\/package.json'`). */\n  deep?: boolean\n\n  /** Check one or more sections of dependencies only: dev, optional, peer, prod, or packageManager (comma-delimited).\n   *\n   * @default [\"prod\",\"dev\",\"optional\",\"packageManager\"]\n   */\n  dep?: string | readonly string[]\n\n  /** Include deprecated packages. Use `--no-deprecated` to exclude deprecated packages (20–25% slower).\n   *\n   * @default true\n   */\n  deprecated?: boolean\n\n  /** Iteratively installs upgrades and runs tests to identify breaking upgrades. Requires `-u` to execute. Run \"ncu --help --doctor\" for details. */\n  doctor?: boolean\n\n  /** Specifies the install script to use in doctor mode. (default: `npm install` or the equivalent for your package manager) */\n  doctorInstall?: string\n\n  /** Specifies the test script to use in doctor mode. (default: `npm test`) */\n  doctorTest?: string\n\n  /** Include only packages that satisfy engines.node as specified in the package file. */\n  enginesNode?: boolean\n\n  /** Set the error level. 1: exits with error code 0 if no errors occur. 2: exits with error code 0 if no packages need updating (useful for continuous integration).\n   *\n   * @default 1\n   */\n  errorLevel?: number\n\n  /** Include only package names matching the given string, wildcard, glob, comma-or-space-delimited list, /regex/, or predicate function. Run \"ncu --help --filter\" for details. */\n  filter?: string | RegExp | readonly (string | RegExp)[] | FilterFunction\n\n  /** Filters results based on a user provided predicate function after fetching new versions. Run \"ncu --help --filterResults\" for details. */\n  filterResults?: FilterResultsFunction\n\n  /** Filter on package version using comma-or-space-delimited list, /regex/, or predicate function. Run \"ncu --help --filterVersion\" for details. */\n  filterVersion?: string | RegExp | readonly (string | RegExp)[] | FilterFunction\n\n  /** Modify the output formatting or show additional information. Specify one or more comma-delimited values: dep, group, ownerChanged, repo, time, lines, installedVersion. Run \"ncu --help --format\" for details. */\n  format?: readonly string[]\n\n  /** Check global packages instead of in the current project. */\n  global?: boolean\n\n  /** Customize how packages are divided into groups when using `--format group`. Run \"ncu --help --groupFunction\" for details. */\n  groupFunction?: GroupFunction\n\n  /** Control the auto-install behavior: always, never, prompt. Run \"ncu --help --install\" for details.\n   *\n   * @default \"prompt\"\n   */\n  install?: 'always' | 'never' | 'prompt'\n\n  /** Enable interactive prompts for each dependency; implies `-u` unless one of the json options are set. */\n  interactive?: boolean\n\n  /** Output new package file instead of human-readable message. */\n  jsonAll?: boolean\n\n  /** Like `jsonAll` but only lists `dependencies`, `devDependencies`, `optionalDependencies`, etc of the new package data. */\n  jsonDeps?: boolean\n\n  /** Output upgraded dependencies in json. */\n  jsonUpgraded?: boolean\n\n  /** Amount to log: silent, error, minimal, warn, info, verbose, silly.\n   *\n   * @default \"warn\"\n   */\n  loglevel?: string\n\n  /** Merges nested configs with the root config file for `--deep` or `--packageFile` options. (default: false) */\n  mergeConfig?: boolean\n\n  /** Do not upgrade newer versions that are already satisfied by the version range according to semver. */\n  minimal?: boolean\n\n  /** Package file data (you can also use stdin). */\n  packageData?: string | PackageFile\n\n  /** Package file(s) location. (default: ./package.json) */\n  packageFile?: string\n\n  /** npm, yarn, pnpm, deno, bun, staticRegistry (default: npm). Run \"ncu --help --packageManager\" for details. */\n  packageManager?: 'npm' | 'yarn' | 'pnpm' | 'deno' | 'bun' | 'staticRegistry'\n\n  /** Check peer dependencies of installed packages and filter updates to compatible versions. Run \"ncu --help --peer\" for details. */\n  peer?: boolean\n\n  /** Include prerelease versions, e.g. -alpha.0, -beta.5, -rc.2. Automatically set to 1 when `--target` is newest or greatest, or when the current version is a prerelease. (default: 0) */\n  pre?: boolean\n\n  /** Current working directory of npm. */\n  prefix?: string\n\n  /** Specify the registry to use when looking up package versions. */\n  registry?: string\n\n  /** Specify whether --registry refers to a full npm registry or a simple JSON file or url: npm, json. (default: npm) Run \"ncu --help --registryType\" for details. */\n  registryType?: 'npm' | 'json'\n\n  /** Exclude packages matching the given string, wildcard, glob, comma-or-space-delimited list, /regex/, or predicate function. Run \"ncu --help --reject\" for details. */\n  reject?: string | RegExp | readonly (string | RegExp)[] | FilterFunction\n\n  /** Exclude package.json versions using comma-or-space-delimited list, /regex/, or predicate function. Run \"ncu --help --rejectVersion\" for details. */\n  rejectVersion?: string | RegExp | readonly (string | RegExp)[] | FilterFunction\n\n  /** Remove version ranges from the final package version. */\n  removeRange?: boolean\n\n  /** Number of times to retry failed requests for package info.\n   *\n   * @default 3\n   */\n  retry?: number\n\n  /** Runs updates on the root project in addition to specified workspaces. Only allowed with `--workspace` or `--workspaces`.\n   *\n   * @default true\n   */\n  root?: boolean\n\n  /** Don't output anything. Alias for `--loglevel` silent. */\n  silent?: boolean\n\n  /** Read package.json from stdin. */\n  stdin?: string\n\n  /** Determines the version to upgrade to: latest, newest, greatest, minor, patch, semver, `@[tag]`, or [function]. (default: latest) Run \"ncu --help --target\" for details. */\n  target?: 'latest' | 'newest' | 'greatest' | 'minor' | 'patch' | 'semver' | `@${string}` | TargetFunction\n\n  /** Global timeout in milliseconds. (default: no global timeout and 30 seconds per npm-registry-fetch) */\n  timeout?: number\n\n  /** Overwrite package file with upgraded versions instead of just outputting to console. */\n  upgrade?: boolean\n\n  /** Log additional information for debugging. Alias for `--loglevel` verbose. */\n  verbose?: boolean\n\n  /** Run on one or more specified workspaces. Add `--no-root` to exclude the root project. */\n  workspace?: readonly string[]\n\n  /** Run on all workspaces. Add `--no-root` to exclude the root project. */\n  workspaces?: boolean\n}\n"
  },
  {
    "path": "src/types/SpawnOptions.ts",
    "content": "import { Index } from './IndexType'\n\n/** Options to the spawn node built-in. */\nexport interface SpawnOptions {\n  cwd?: string\n  env?: Index<string>\n}\n"
  },
  {
    "path": "src/types/SpawnPleaseOptions.ts",
    "content": "export interface SpawnPleaseOptions {\n  rejectOnError?: boolean\n  stdin?: string\n  stdout?: (s: string) => void\n  stderr?: (s: string) => void\n}\n"
  },
  {
    "path": "src/types/StaticRegistry.ts",
    "content": "import { Version } from './Version'\n\nexport type StaticRegistry = {\n  [key: string]: Version\n}\n"
  },
  {
    "path": "src/types/Target.ts",
    "content": "import { TargetFunction } from './TargetFunction'\n\n/** Valid strings for the --target option. Indicates the desired version to upgrade to. */\nexport const supportedVersionTargets = ['latest', 'newest', 'greatest', 'minor', 'patch', 'semver'] as const\n\n/** A union of supported version target strings. */\nexport type TargetString = (typeof supportedVersionTargets)[number]\n\n/** Upgrading to specific distribution tags can be done by passing @-starting value to --target option. */\nexport type TargetDistTag = `@${string}`\n\n/** The type of the --target option. Specifies the range from which to select the version to upgrade to. */\nexport type Target = TargetString | TargetDistTag | TargetFunction\n"
  },
  {
    "path": "src/types/TargetFunction.ts",
    "content": "import { SemVer } from 'semver-utils'\n\n/** A function that can be provided to the --target option for custom filtering. */\nexport type TargetFunction = (packageName: string, versionRange: SemVer[]) => string\n"
  },
  {
    "path": "src/types/UpgradeGroup.ts",
    "content": "export type UpgradeGroup = 'major' | 'minor' | 'patch' | 'majorVersionZero' | 'none'\n"
  },
  {
    "path": "src/types/Version.ts",
    "content": "/** An exact version number or value. Includes SemVer and some nonstandard variants for convenience. */\nexport type Version = string\n"
  },
  {
    "path": "src/types/VersionLevel.ts",
    "content": "/** The three main parts of a SemVer version. */\nexport type VersionLevel = 'major' | 'minor' | 'patch'\n"
  },
  {
    "path": "src/types/VersionResult.ts",
    "content": "import { Version } from './Version'\n\n/** The result of fetching a version from the package manager, which may include an error. Used to pass errors back up the call chain for better reporting. */\nexport interface VersionResult {\n  version?: Version | null\n  error?: string\n  time?: string\n}\n"
  },
  {
    "path": "src/types/VersionSpec.ts",
    "content": "/** A version specification or range supported as a value in package.json dependencies. Includes SemVer, git urls, npm urls, etc. */\nexport type VersionSpec = string\n"
  },
  {
    "path": "src/types/libnpmconfig.d.ts",
    "content": "// add to tsconfig compilerOptions.paths\ndeclare module 'libnpmconfig'\n"
  },
  {
    "path": "src/types/prompts-ncu.d.ts",
    "content": "// add to tsconfig compilerOptions.paths\ndeclare module 'prompts-ncu'\n"
  },
  {
    "path": "tea.yaml",
    "content": "# https://tea.xyz/what-is-this-file\n---\nversion: 1.0.0\ncodeOwners:\n  - '0x333377f49cBD5396E27f750C9413b5D758c705C2'\nquorum: 1\n"
  },
  {
    "path": "test/bin.test.ts",
    "content": "import fs from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport spawn from 'spawn-please'\nimport { Index } from '../src/types/IndexType'\nimport { Version } from '../src/types/Version'\nimport chaiSetup from './helpers/chaiSetup'\nimport removeDir from './helpers/removeDir'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\nconst bin = path.join(__dirname, '../build/cli.js')\n\ndescribe('bin', async function () {\n  it('fetch latest version from registry (not stubbed)', async () => {\n    const { stdout } = await spawn('node', [bin, '--jsonUpgraded', '--stdin'], {\n      stdin: JSON.stringify({ dependencies: { 'ncu-test-v2': '1.0.0' } }),\n    })\n    const pkgData = JSON.parse(stdout)\n    pkgData.should.have.property('ncu-test-v2')\n  })\n\n  it('output only upgraded with --jsonUpgraded', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    const { stdout } = await spawn('node', [bin, '--jsonUpgraded', '--stdin'], {\n      stdin: JSON.stringify({ dependencies: { 'ncu-test-v2': '1.0.0' } }),\n    })\n    const pkgData = JSON.parse(stdout)\n    pkgData.should.have.property('ncu-test-v2')\n    stub.restore()\n  })\n\n  it('--loglevel verbose', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    const { stdout } = await spawn('node', [bin, '--loglevel', 'verbose'], {\n      stdin: JSON.stringify({ dependencies: { 'ncu-test-v2': '1.0.0' } }),\n    })\n    stdout.should.containIgnoreCase('Initializing')\n    stdout.should.containIgnoreCase('Running in local mode')\n    stdout.should.containIgnoreCase('Finding package file data')\n    stub.restore()\n  })\n\n  it('--verbose', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    const { stdout } = await spawn('node', [bin, '--verbose'], {\n      stdin: JSON.stringify({ dependencies: { 'ncu-test-v2': '1.0.0' } }),\n    })\n    stdout.should.containIgnoreCase('Initializing')\n    stdout.should.containIgnoreCase('Running in local mode')\n    stdout.should.containIgnoreCase('Finding package file data')\n    stub.restore()\n  })\n\n  it('accept stdin', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    const { stdout } = await spawn('node', [bin, '--stdin'], {\n      stdin: JSON.stringify({ dependencies: { express: '1' } }),\n    })\n    stdout.trim().should.startWith('express')\n    stub.restore()\n  })\n\n  it('reject out-of-date stdin with errorLevel 2', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    await spawn('node', [bin, '--stdin', '--errorLevel', '2'], {\n      stdin: JSON.stringify({ dependencies: { express: '1' } }),\n    }).should.eventually.be.rejectedWith('Dependencies not up-to-date')\n    stub.restore()\n  })\n\n  it('fall back to package.json search when receiving empty content on stdin', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    const { stdout } = await spawn('node', [bin, '--stdin'])\n    stdout\n      .toString()\n      .trim()\n      .should.match(/^Checking .+package.json/)\n    stub.restore()\n  })\n\n  it('use package.json in cwd by default', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    const { stdout } = await spawn('node', [bin, '--jsonUpgraded'], {}, { cwd: path.join(__dirname, 'test-data/ncu') })\n    const pkgData = JSON.parse(stdout)\n    pkgData.should.have.property('express')\n    stub.restore()\n  })\n\n  it('throw error if there is no package', async () => {\n    // run from tmp dir to avoid ncu analyzing the project's package.json\n    return spawn('node', [bin], {}, { cwd: os.tmpdir() }).should.eventually.be.rejectedWith('No package.json')\n  })\n\n  it('throw error if there is no package in --cwd', async () => {\n    return spawn('node', [bin, '--cwd', os.tmpdir()]).should.eventually.be.rejectedWith('No package.json')\n  })\n\n  it('throw error if --cwd does not exist', async () => {\n    return spawn('node', [bin, '--cwd', 'fnuoathufoawhtufonwauto']).should.eventually.be.rejectedWith(\n      'No such directory: fnuoathufoawhtufonwauto',\n    )\n  })\n\n  it('read --packageFile', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(pkgFile, JSON.stringify({ dependencies: { express: '1' } }), 'utf-8')\n    try {\n      const { stdout } = await spawn('node', [bin, '--jsonUpgraded', '--packageFile', pkgFile])\n      const pkgData = JSON.parse(stdout)\n      pkgData.should.have.property('express')\n    } finally {\n      await removeDir(tempDir)\n      stub.restore()\n    }\n  })\n\n  it('write to --packageFile', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(pkgFile, JSON.stringify({ dependencies: { express: '1' } }), 'utf-8')\n    try {\n      await spawn('node', [bin, '-u', '--packageFile', pkgFile])\n      const upgradedPkg = JSON.parse(await fs.readFile(pkgFile, 'utf-8'))\n      upgradedPkg.should.have.property('dependencies')\n      upgradedPkg.dependencies.should.have.property('express')\n      upgradedPkg.dependencies.express.should.not.equal('1')\n    } finally {\n      await removeDir(tempDir)\n      stub.restore()\n    }\n  })\n\n  it('write to --packageFile if errorLevel=2 and upgrades', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(pkgFile, JSON.stringify({ dependencies: { express: '1' } }), 'utf-8')\n\n    try {\n      await spawn('node', [bin, '-u', '--errorLevel', '2', '--packageFile', pkgFile]).should.eventually.be.rejectedWith(\n        'Dependencies not up-to-date',\n      )\n      const upgradedPkg = JSON.parse(await fs.readFile(pkgFile, 'utf-8'))\n      upgradedPkg.should.have.property('dependencies')\n      upgradedPkg.dependencies.should.have.property('express')\n      upgradedPkg.dependencies.express.should.not.equal('1')\n    } finally {\n      await removeDir(tempDir)\n      stub.restore()\n    }\n  })\n\n  it('write to --packageFile with jsonUpgraded flag', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(pkgFile, JSON.stringify({ dependencies: { express: '1' } }), 'utf-8')\n    try {\n      await spawn('node', [bin, '-u', '--jsonUpgraded', '--packageFile', pkgFile])\n      const upgradedPkg = JSON.parse(await fs.readFile(pkgFile, 'utf-8'))\n      upgradedPkg.should.have.property('dependencies')\n      upgradedPkg.dependencies.should.have.property('express')\n      upgradedPkg.dependencies.express.should.not.equal('1')\n    } finally {\n      await removeDir(tempDir)\n      stub.restore()\n    }\n  })\n\n  it('ignore stdin if --packageFile is specified', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(pkgFile, JSON.stringify({ dependencies: { express: '1' } }), 'utf-8')\n    try {\n      await spawn('node', [bin, '-u', '--stdin', '--packageFile', pkgFile], {\n        stdin: JSON.stringify({ dependencies: {} }),\n      })\n      const upgradedPkg = JSON.parse(await fs.readFile(pkgFile, 'utf-8'))\n      upgradedPkg.should.have.property('dependencies')\n      upgradedPkg.dependencies.should.have.property('express')\n      upgradedPkg.dependencies.express.should.not.equal('1')\n    } finally {\n      await removeDir(tempDir)\n      stub.restore()\n    }\n  })\n\n  it('suppress stdout when --silent is provided', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    const { stdout } = await spawn('node', [bin, '--silent'], {\n      stdin: JSON.stringify({ dependencies: { express: '1' } }),\n    })\n    stdout.trim().should.equal('')\n    stub.restore()\n  })\n\n  it('quote arguments with spaces in upgrade hint', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    const pkgData = {\n      dependencies: {\n        'ncu-test-v2': '^1.0.0',\n        'ncu-test-tag': '^1.0.0',\n      },\n    }\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(pkgFile, JSON.stringify(pkgData), 'utf-8')\n    try {\n      const { stdout } = await spawn('node', [bin, '--packageFile', pkgFile, '--filter', 'ncu-test-v2 ncu-test-tag'])\n      stdout.should.include('\"ncu-test-v2 ncu-test-tag\"')\n    } finally {\n      await removeDir(tempDir)\n      stub.restore()\n    }\n  })\n\n  it('ignore file: and link: protocols', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    const { default: stripAnsi } = await import('strip-ansi')\n    const dependencies = {\n      editor: 'file:../editor',\n      event: 'link:../link',\n      workspace: 'workspace:../workspace',\n    }\n    const { stdout } = await spawn('node', [bin, '--stdin'], { stdin: JSON.stringify({ dependencies }) })\n\n    stripAnsi(stdout)!.should.not.include('No package versions were returned.')\n    stub.restore()\n  })\n\n  it('combine boolean flags with arguments', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    const { stdout } = await spawn('node', [bin, '--stdin', '--jsonUpgraded', 'ncu-test-v2'], {\n      stdin: JSON.stringify({ dependencies: { 'ncu-test-v2': '1.0.0', 'ncu-test-tag': '0.1.0' } }),\n    })\n    const upgraded = JSON.parse(stdout) as Index<Version>\n    upgraded.should.deep.equal({\n      'ncu-test-v2': '99.9.9',\n    })\n    stub.restore()\n  })\n\n  it('combine short boolean options with long options', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    const promise = spawn('node', [bin, '-mp', 'foo'])\n    await promise.should.eventually.be.rejectedWith('Invalid package manager: foo')\n    stub.restore()\n  })\n\n  // TODO\n  // https://github.com/raineorshine/npm-check-updates/issues/1594\n  it.skip('upgrade duplicate dependencies with different versions', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(\n      pkgFile,\n      JSON.stringify({ dependencies: { 'ncu-test-v2': '1.0.0' }, devDependencies: { 'ncu-test-v2': '1.0.1' } }),\n      'utf-8',\n    )\n    try {\n      await spawn('node', [bin, '-u', '--packageFile', pkgFile])\n      const upgradedPkg = JSON.parse(await fs.readFile(pkgFile, 'utf-8'))\n      console.log(upgradedPkg)\n      upgradedPkg.dependencies.should.deep.equal({ 'ncu-test-v2': '99.9.9' })\n      upgradedPkg.devDependencies.should.deep.equal({ 'ncu-test-v2': '99.9.9' })\n    } finally {\n      await removeDir(tempDir)\n      stub.restore()\n    }\n  })\n\n  describe('embedded versions', () => {\n    it('strip url from GitHub url in \"to\" output', async () => {\n      // use dynamic import for ESM module\n      const { default: stripAnsi } = await import('strip-ansi')\n      const dependencies = {\n        'ncu-test-v2': 'https://github.com/raineorshine/ncu-test-v2.git#v1.0.0',\n      }\n      const { stdout } = await spawn('node', [bin, '--stdin'], { stdin: JSON.stringify({ dependencies }) })\n      stripAnsi(stdout)\n        .trim()\n        .should.equal('ncu-test-v2  https://github.com/raineorshine/ncu-test-v2.git#v1.0.0  →  v2.0.0')\n    })\n\n    it('strip prefix from npm alias in \"to\" output', async () => {\n      const stub = stubVersions('99.9.9', { spawn: true })\n      // use dynamic import for ESM module\n      const { default: stripAnsi } = await import('strip-ansi')\n      const dependencies = {\n        request: 'npm:ncu-test-v2@1.0.0',\n      }\n      const { stdout } = await spawn('node', [bin, '--stdin'], { stdin: JSON.stringify({ dependencies }) })\n      stripAnsi(stdout).trim().should.equal('request  npm:ncu-test-v2@1.0.0  →  99.9.9')\n      stub.restore()\n    })\n  })\n\n  describe('option-specific help', () => {\n    it('long option', async () => {\n      const { stdout } = await spawn('node', [bin, '--help', '--filter'])\n      stdout.trim().should.match(/^Usage:\\s+ncu --filter/)\n    })\n\n    it('long option without \"--\" prefix', async () => {\n      const { stdout } = await spawn('node', [bin, '--help', 'filter'])\n      stdout.trim().should.match(/^Usage:\\s+ncu --filter/)\n    })\n\n    it('short option', async () => {\n      const { stdout } = await spawn('node', [bin, '--help', '-f'])\n      stdout.trim().should.match(/^Usage:\\s+ncu --filter/)\n    })\n\n    it('short option without \"-\" prefix', async () => {\n      const { stdout } = await spawn('node', [bin, '--help', 'f'])\n      stdout.trim().should.match(/^Usage:\\s+ncu --filter/)\n    })\n\n    it('option with default', async () => {\n      const { stdout } = await spawn('node', [bin, '--help', '--concurrency'])\n      stdout.trim().should.containIgnoreCase('Default:')\n    })\n\n    it('option with extended help', async () => {\n      const { stdout } = await spawn('node', [bin, '--help', '--target'])\n      stdout.trim().should.containIgnoreCase('Upgrade to the highest version number')\n    })\n\n    it('-h', async () => {\n      const { stdout } = await spawn('node', [bin, '-h', '--filter'])\n      stdout.trim().should.match(/^Usage:\\s+ncu --filter/)\n    })\n\n    it('unknown option', async () => {\n      const { stdout } = await spawn('node', [bin, '--help', '--foo'])\n      stdout.trim().should.containIgnoreCase('Unknown option')\n    })\n\n    it('multiple options', async () => {\n      const { stdout } = await spawn('node', [bin, '--help', '--interactive', '--minimal'])\n      stdout.trim().should.containIgnoreCase('ncu --interactive')\n      stdout.trim().should.containIgnoreCase('ncu --minimal')\n    })\n\n    // version is a special case since it is not included in cli-options.ts\n    it('--version', async () => {\n      const { stdout } = await spawn('node', [bin, '-h', '--version'])\n      stdout.trim().should.match(/^Usage:\\s+ncu --version/)\n    })\n\n    it('-V', async () => {\n      const { stdout } = await spawn('node', [bin, '-h', '--version'])\n      stdout.trim().should.match(/^Usage:\\s+ncu --version/)\n    })\n\n    it('-v', async () => {\n      const { stdout } = await spawn('node', [bin, '-h', '--version'])\n      stdout.trim().should.match(/^Usage:\\s+ncu --version/)\n    })\n\n    describe('special --help help', () => {\n      it('--help --help', async () => {\n        const { stdout } = await spawn('node', [bin, '--help', '--help'])\n        stdout.trim().should.not.include('Usage')\n      })\n\n      it('--help help', async () => {\n        const { stdout } = await spawn('node', [bin, '--help', 'help'])\n        stdout.trim().should.not.include('Usage')\n      })\n\n      it('--help -h', async () => {\n        const { stdout } = await spawn('node', [bin, '--help', '-h'])\n        stdout.trim().should.not.include('Usage')\n      })\n\n      it('--help h', async () => {\n        const { stdout } = await spawn('node', [bin, '--help', 'h'])\n        stdout.trim().should.not.include('Usage')\n      })\n\n      it('-h --help', async () => {\n        const { stdout } = await spawn('node', [bin, '-h', '--help'])\n        stdout.trim().should.not.include('Usage')\n      })\n\n      it('-h help', async () => {\n        const { stdout } = await spawn('node', [bin, '-h', 'help'])\n        stdout.trim().should.not.include('Usage')\n      })\n\n      it('-h -h', async () => {\n        const { stdout } = await spawn('node', [bin, '-h', '-h'])\n        stdout.trim().should.not.include('Usage')\n      })\n\n      it('-h h', async () => {\n        const { stdout } = await spawn('node', [bin, '-h', 'h'])\n        stdout.trim().should.not.include('Usage')\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "test/bun/index.test.ts",
    "content": "import * as bun from '../../src/package-managers/bun'\nimport chaiSetup from '../helpers/chaiSetup'\nimport { testFail, testPass } from '../helpers/doctorHelpers'\nimport stubVersions from '../helpers/stubVersions'\n\nchaiSetup()\n\nconst mockNpmVersions = {\n  emitter20: '2.0.0',\n  'ncu-test-return-version': '2.0.0',\n  'ncu-test-tag': '1.1.0',\n  'ncu-test-v2': '2.0.0',\n}\n\ndescribe('bun', function () {\n  it('list', async () => {\n    const result = await bun.list({ cwd: __dirname })\n    result.should.have.property('ncu-test-v2')\n  })\n\n  it('latest', async () => {\n    const { version } = await bun.latest('ncu-test-v2', '1.0.0', { cwd: __dirname })\n    version!.should.equal('2.0.0')\n  })\n\n  describe('doctor', function () {\n    this.timeout(3 * 60 * 1000)\n\n    let stub: { restore: () => void }\n    before(() => (stub = stubVersions(mockNpmVersions, { spawn: true })))\n    after(() => stub.restore())\n\n    testPass({ packageManager: 'bun' })\n    testFail({ packageManager: 'bun' })\n  })\n})\n"
  },
  {
    "path": "test/bun/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"ncu-test-v2\": \"^1.0.0\"\n  }\n}\n"
  },
  {
    "path": "test/bun-install.sh",
    "content": "#!/usr/bin/env bash\n# Install bun if not installed.\n# Cannot be added to devDependencies as bun currently does not work on Linux.\nbun -v &>/dev/null\nBUN_EXISTS=\"$?\"\n\nif [ $BUN_EXISTS -ne 0 ]; then\n  npm install -g bun\nfi\n\n# Always return success, even if the install script fails.\n# Windows is expected to fail and the bun tests will be skipped.\nexit 0\n"
  },
  {
    "path": "test/cache.test.ts",
    "content": "import { expect } from 'chai'\nimport fs from 'fs/promises'\nimport ncu from '../src/'\nimport { CACHE_DELIMITER, resolvedDefaultCacheFile } from '../src/lib/cache'\nimport { CacheData } from '../src/types/Cacher'\nimport chaiSetup from './helpers/chaiSetup'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\ndescribe('cache', () => {\n  it('cache latest versions', async () => {\n    const stub = stubVersions({\n      'ncu-test-v2': '2.0.0',\n      'ncu-test-tag': '1.1.0',\n      'ncu-test-alpha': '1.0.0',\n    })\n    try {\n      const packageData = {\n        dependencies: {\n          'ncu-test-v2': '^1.0.0',\n          'ncu-test-tag': '1.0.0',\n          'ncu-test-alpha': '1.0.0',\n        },\n      }\n\n      await ncu({ packageData, cache: true, peer: true })\n\n      const cacheData: CacheData = await fs.readFile(resolvedDefaultCacheFile, 'utf-8').then(JSON.parse)\n\n      expect(cacheData.timestamp).lessThanOrEqual(Date.now())\n      expect(cacheData.packages).deep.eq({\n        [`ncu-test-v2${CACHE_DELIMITER}latest`]: '2.0.0',\n        [`ncu-test-tag${CACHE_DELIMITER}latest`]: '1.1.0',\n        [`ncu-test-alpha${CACHE_DELIMITER}latest`]: '1.0.0',\n      })\n      expect(cacheData.peers).deep.eq({\n        [`ncu-test-alpha${CACHE_DELIMITER}1.0.0`]: {},\n        [`ncu-test-tag${CACHE_DELIMITER}1.0.0`]: {},\n        [`ncu-test-tag${CACHE_DELIMITER}1.1.0`]: {},\n        [`ncu-test-v2${CACHE_DELIMITER}1.0.0`]: {},\n        [`ncu-test-v2${CACHE_DELIMITER}2.0.0`]: {},\n      })\n    } finally {\n      await fs.rm(resolvedDefaultCacheFile, { recursive: true, force: true })\n      stub.restore()\n    }\n  })\n\n  it('use different cache key for different target', async () => {\n    const stub = stubVersions(options =>\n      options.target === 'latest'\n        ? {\n            'ncu-test-v2': '2.0.0',\n            'ncu-test-tag': '1.1.0',\n            'ncu-test-alpha': '1.0.0',\n          }\n        : options.target === 'greatest'\n          ? {\n              'ncu-test-v2': '2.0.0',\n              'ncu-test-tag': '1.2.0-dev.0',\n              'ncu-test-alpha': '2.0.0-alpha.2',\n            }\n          : null,\n    )\n    try {\n      const packageData = {\n        dependencies: {\n          'ncu-test-v2': '^1.0.0',\n          'ncu-test-tag': '1.0.0',\n          'ncu-test-alpha': '1.0.0',\n        },\n      }\n\n      // first run caches latest\n      await ncu({ packageData, cache: true })\n\n      const cacheData1: CacheData = await fs.readFile(resolvedDefaultCacheFile, 'utf-8').then(JSON.parse)\n\n      expect(cacheData1.packages).deep.eq({\n        [`ncu-test-v2${CACHE_DELIMITER}latest`]: '2.0.0',\n        [`ncu-test-tag${CACHE_DELIMITER}latest`]: '1.1.0',\n        [`ncu-test-alpha${CACHE_DELIMITER}latest`]: '1.0.0',\n      })\n      expect(cacheData1.peers).deep.eq({})\n\n      // second run has a different target so should not use the cache\n      const result2 = await ncu({ packageData, cache: true, target: 'greatest' })\n      expect(result2).deep.eq({\n        'ncu-test-v2': '^2.0.0',\n        'ncu-test-tag': '1.2.0-dev.0',\n        'ncu-test-alpha': '2.0.0-alpha.2',\n      })\n\n      const cacheData2: CacheData = await fs.readFile(resolvedDefaultCacheFile, 'utf-8').then(JSON.parse)\n\n      expect(cacheData2.packages).deep.eq({\n        [`ncu-test-v2${CACHE_DELIMITER}latest`]: '2.0.0',\n        [`ncu-test-tag${CACHE_DELIMITER}latest`]: '1.1.0',\n        [`ncu-test-alpha${CACHE_DELIMITER}latest`]: '1.0.0',\n        [`ncu-test-v2${CACHE_DELIMITER}greatest`]: '2.0.0',\n        [`ncu-test-tag${CACHE_DELIMITER}greatest`]: '1.2.0-dev.0',\n        [`ncu-test-alpha${CACHE_DELIMITER}greatest`]: '2.0.0-alpha.2',\n      })\n    } finally {\n      await fs.rm(resolvedDefaultCacheFile, { recursive: true, force: true })\n      stub.restore()\n    }\n  })\n\n  it('clears the cache file', async () => {\n    const stub = stubVersions('99.9.9')\n    const packageData = {\n      dependencies: {\n        'ncu-test-v2': '^1.0.0',\n        'ncu-test-tag': '1.0.0',\n        'ncu-test-alpha': '1.0.0',\n      },\n    }\n\n    await ncu({ packageData, cache: true })\n\n    await ncu({ packageData, cacheClear: true })\n    let noCacheFile = false\n    try {\n      await fs.stat(resolvedDefaultCacheFile)\n    } catch (error) {\n      noCacheFile = true\n    }\n    expect(noCacheFile).eq(true)\n    stub.restore()\n  })\n})\n"
  },
  {
    "path": "test/cli-options.test.ts",
    "content": "import cliOptions from '../src/cli-options'\nimport chaiSetup from './helpers/chaiSetup'\n\nchaiSetup()\n\ndescribe('cli-options', () => {\n  it('require long and description properties', () => {\n    cliOptions.forEach(option => {\n      option.should.have.property('long')\n      option.should.have.property('description')\n    })\n  })\n})\n"
  },
  {
    "path": "test/cooldown.test.ts",
    "content": "import { expect } from 'chai'\nimport Sinon from 'sinon'\nimport ncu from '../src/'\nimport type { PackageFile } from '../src/types/PackageFile'\nimport type { Packument } from '../src/types/Packument'\nimport chaiSetup from './helpers/chaiSetup'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\nconst DAY = 24 * 60 * 60 * 1000\nconst NOW = Date.now()\n\ninterface CreateMockParams {\n  name: string\n  versions: Record<string, string>\n  distTags?: Record<string, string>\n}\n\n/**\n * Creates a mock package version object for testing purposes.\n *\n * @param params - The parameters for creating the mock version.\n * @param params.name - The name of the package.\n * @param params.versions - An object mapping version strings to their corresponding release dates.\n * @param params.distTags - An object representing distribution tags for the package.\n * @returns An object representing mocked package versions, including name, versions, time, and distTags.\n */\nconst createMockVersion = ({ name, versions, distTags }: CreateMockParams): Partial<Packument> => {\n  return {\n    name,\n    version: Object.keys(versions)[0],\n    versions: Object.fromEntries(Object.entries(versions).map(([version]) => [version, { version } as Packument])),\n    time: Object.fromEntries(Object.entries(versions).map(([version, date]) => [version, date])),\n    'dist-tags': distTags,\n  }\n}\n\ndescribe('cooldown', () => {\n  beforeEach(() => {\n    Sinon.restore()\n  })\n\n  describe('invalid cooldown values', () => {\n    it('throws error for negative cooldown', () => {\n      expect(\n        ncu({\n          cooldown: -1,\n        }),\n      ).to.be.rejectedWith(\n        'Cooldown must be a non-negative number (days), a string like \"7d\", \"12h\", or \"30m\", or a predicate function.',\n      )\n    })\n\n    it('throws error for unrecognized string cooldown', () => {\n      expect(\n        ncu({\n          cooldown: 'invalid',\n        }),\n      ).to.be.rejectedWith(\n        'Invalid cooldown value: \"invalid\". Use a number (days) or a string like \"7d\", \"12h\", or \"30m\".',\n      )\n    })\n  })\n\n  describe('cooldown string formats', () => {\n    it('upgrades package when cooldown is given in days (\"6d\")', async () => {\n      // Given: cooldown \"6d\" (6 days), version released 7 days ago — outside cooldown\n      const packageData: PackageFile = {\n        dependencies: { 'test-package': '1.0.0' },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: { '1.1.0': new Date(NOW - 7 * DAY).toISOString() },\n          distTags: { latest: '1.1.0' },\n        }),\n      )\n\n      const result = await ncu({\n        packageData,\n        cooldown: '6d',\n        target: 'latest',\n      })\n\n      expect(result).to.have.property('test-package', '1.1.0')\n\n      stub.restore()\n    })\n\n    it('skips upgrade when cooldown is given in days (\"6d\") and version is inside period', async () => {\n      // Given: cooldown \"6d\" (6 days), version released 5 days ago — inside cooldown\n      const packageData: PackageFile = {\n        dependencies: { 'test-package': '1.0.0' },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: { '1.1.0': new Date(NOW - 5 * DAY).toISOString() },\n          distTags: { latest: '1.1.0' },\n        }),\n      )\n\n      const result = await ncu({\n        packageData,\n        cooldown: '6d',\n        target: 'latest',\n      })\n\n      expect(result).to.not.have.property('test-package')\n\n      stub.restore()\n    })\n\n    it('upgrades package when cooldown is given in hours (\"12h\")', async () => {\n      // Given: cooldown \"12h\" (12 hours), version released 13 hours ago — outside cooldown\n      const HOUR = DAY / 24\n      const packageData: PackageFile = {\n        dependencies: { 'test-package': '1.0.0' },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: { '1.1.0': new Date(NOW - 13 * HOUR).toISOString() },\n          distTags: { latest: '1.1.0' },\n        }),\n      )\n\n      const result = await ncu({\n        packageData,\n        cooldown: '12h',\n        target: 'latest',\n      })\n\n      expect(result).to.have.property('test-package', '1.1.0')\n\n      stub.restore()\n    })\n\n    it('skips upgrade when cooldown is given in hours (\"12h\") and version is inside period', async () => {\n      // Given: cooldown \"12h\" (12 hours), version released 11 hours ago — inside cooldown\n      const HOUR = DAY / 24\n      const packageData: PackageFile = {\n        dependencies: { 'test-package': '1.0.0' },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: { '1.1.0': new Date(NOW - 11 * HOUR).toISOString() },\n          distTags: { latest: '1.1.0' },\n        }),\n      )\n\n      const result = await ncu({\n        packageData,\n        cooldown: '12h',\n        target: 'latest',\n      })\n\n      expect(result).to.not.have.property('test-package')\n\n      stub.restore()\n    })\n\n    it('upgrades package when cooldown is given in minutes (\"30m\")', async () => {\n      // Given: cooldown \"30m\" (30 minutes), version released 31 minutes ago — outside cooldown\n      const MINUTE = DAY / (24 * 60)\n      const packageData: PackageFile = {\n        dependencies: { 'test-package': '1.0.0' },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: { '1.1.0': new Date(NOW - 31 * MINUTE).toISOString() },\n          distTags: { latest: '1.1.0' },\n        }),\n      )\n\n      const result = await ncu({\n        packageData,\n        cooldown: '30m',\n        target: 'latest',\n      })\n\n      expect(result).to.have.property('test-package', '1.1.0')\n\n      stub.restore()\n    })\n\n    it('skips upgrade when cooldown is given in minutes (\"30m\") and version is inside period', async () => {\n      // Given: cooldown \"30m\" (30 minutes), version released 29 minutes ago — inside cooldown\n      const MINUTE = DAY / (24 * 60)\n      const packageData: PackageFile = {\n        dependencies: { 'test-package': '1.0.0' },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: { '1.1.0': new Date(NOW - 29 * MINUTE).toISOString() },\n          distTags: { latest: '1.1.0' },\n        }),\n      )\n\n      const result = await ncu({\n        packageData,\n        cooldown: '30m',\n        target: 'latest',\n      })\n\n      expect(result).to.not.have.property('test-package')\n\n      stub.restore()\n    })\n\n    it('\"6d\" string is equivalent to the number 6', async () => {\n      // Given: both cooldown forms should produce identical results\n      const packageData: PackageFile = {\n        dependencies: { 'test-package': '1.0.0' },\n      }\n      const mockData = createMockVersion({\n        name: 'test-package',\n        versions: { '1.1.0': new Date(NOW - 7 * DAY).toISOString() },\n        distTags: { latest: '1.1.0' },\n      })\n\n      const stub1 = stubVersions(mockData)\n      const resultNumber = await ncu({\n        packageData,\n        cooldown: 6,\n        target: 'latest',\n      })\n      stub1.restore()\n\n      const stub2 = stubVersions(mockData)\n      const resultString = await ncu({\n        packageData,\n        cooldown: '6d',\n        target: 'latest',\n      })\n      stub2.restore()\n\n      expect(resultNumber).to.deep.equal(resultString)\n    })\n  })\n\n  it('upgrades when cooldown is not set', async () => {\n    // Given: no cooldown set, test-package@1.0.0 installed, latest version 1.2.0 released 1 day ago\n    const packageData: PackageFile = {\n      dependencies: {\n        'test-package': '1.0.0',\n      },\n    }\n    const stub = stubVersions(\n      createMockVersion({\n        name: 'test-package',\n        versions: {\n          '1.2.0': new Date(NOW - DAY).toISOString(),\n        },\n        distTags: {\n          latest: '1.2.0',\n        },\n      }),\n    )\n\n    // When: running ncu without cooldown\n    const result = await ncu({ packageData })\n\n    // Then: package is upgraded to latest version (1.2.0)\n    expect(result).to.have.property('test-package', '1.2.0')\n\n    stub.restore()\n  })\n\n  it('upgrades package when cooldown is set to 0 (no cooldown)', async () => {\n    // Given: cooldown set to 0, test-package@1.0.0 installed, latest version 1.2.0 released 1 day ago\n    const cooldown = 0\n    const packageData: PackageFile = {\n      dependencies: {\n        'test-package': '1.0.0',\n      },\n    }\n    const stub = stubVersions(\n      createMockVersion({\n        name: 'test-package',\n        versions: {\n          '1.2.0': new Date(NOW - DAY).toISOString(),\n        },\n        distTags: {\n          latest: '1.2.0',\n        },\n      }),\n    )\n\n    // When ncu is run with a 0 day cooldown parameter\n    const result = await ncu({ packageData, cooldown })\n\n    // Then test-package should be upgraded to version 1.2.0 (latest) - as cooldown of 0 means no cooldown.\n    expect(result).to.have.property('test-package', '1.2.0')\n\n    stub.restore()\n  })\n\n  describe('when latest target', () => {\n    it('upgrades package when latest version was released outside cooldown period', async () => {\n      // Given: cooldown set to 10, test-package@1.0.0 installed, latest version 1.1.0 released 15 days ago (outside 10-day cooldown)\n      const cooldown = 10\n      const packageData: PackageFile = {\n        dependencies: {\n          'test-package': '1.0.0',\n        },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: {\n            '1.1.0': new Date(NOW - 15 * DAY).toISOString(),\n          },\n          distTags: {\n            latest: '1.1.0',\n          },\n        }),\n      )\n\n      // When ncu is run with the cooldown parameter and target is 'latest'\n      const result = await ncu({ packageData, cooldown, target: 'latest' })\n\n      // Then: package is upgraded to version 1.1.0\n      expect(result).to.have.property('test-package', '1.1.0')\n\n      stub.restore()\n    })\n\n    it('skips package upgrade completely when latest version is inside cooldown period', async () => {\n      // Given: cooldown set to 10, test-package@1.0.0 installed, latest version 1.1.0 released 5 days ago (within 10-day cooldown), older version 1.0.1 released 10 days ago\n      const cooldown = 10\n      const packageData: PackageFile = {\n        dependencies: {\n          'test-package': '1.0.0',\n        },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: {\n            '1.1.0': new Date(NOW - 5 * DAY).toISOString(),\n            '1.0.1': new Date(NOW - 10 * DAY).toISOString(),\n          },\n          distTags: {\n            latest: '1.1.0',\n          },\n        }),\n      )\n\n      // When ncu is run with the cooldown parameter and target is 'latest'\n      const result = await ncu({ packageData, cooldown, target: 'latest' })\n\n      // Then: package is not upgraded (latest version within cooldown, 1.0.1 is ignored as not latest)\n      expect(result).to.not.have.property('test-package')\n\n      stub.restore()\n    })\n  })\n\n  describe('when @TAG target', () => {\n    it('upgrades package when @next version was released outside cooldown period', async () => {\n      // Given: cooldown set to 10, test-package@1.0.0 installed, @next version 1.1.0-rc.1 released 15 days ago (outside 10-day cooldown boundary)\n      const cooldown = 10\n      const packageData: PackageFile = {\n        dependencies: {\n          'test-package': '1.0.0',\n        },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: {\n            '1.1.0-rc.1': new Date(NOW - 15 * DAY).toISOString(),\n          },\n          distTags: {\n            next: '1.1.0-rc.1',\n          },\n        }),\n      )\n\n      // When ncu is run with the cooldown parameter and target is '@next'\n      const result = await ncu({ packageData, cooldown, target: '@next' })\n\n      // Then: package is upgraded to @next version 1.1.0-rc.1\n      expect(result).to.have.property('test-package', '1.1.0-rc.1')\n\n      stub.restore()\n    })\n\n    it('skips package upgrade completely when @next version is inside cooldown period', async () => {\n      // Given: cooldown days is set to 10 days, test-package is installed in version 1.0.0, and the @next version - 1.1.0-rc.2 was released 5 days ago (inside cooldown period). Another version 1.1.0-rc.1 was released 10 days ago (outside cooldown period), but it is not marked as @next version.\n      const cooldown = 10\n      const packageData: PackageFile = {\n        dependencies: {\n          'test-package': '1.0.0',\n        },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: {\n            '1.1.0-rc.2': new Date(NOW - 5 * DAY).toISOString(),\n            '1.1.0-rc.1': new Date(NOW - 10 * DAY).toISOString(),\n          },\n          distTags: {\n            next: '1.1.0-rc.2',\n          },\n        }),\n      )\n\n      // When ncu is run with the cooldown parameter and target is '@next'\n      const result = await ncu({ packageData, cooldown, target: '@next' })\n\n      // Then: package is not upgraded (next version within cooldown, 1.1.0-rc.2 is ignored as not tagged as next)\n      expect(result).to.not.have.property('test-package')\n\n      stub.restore()\n    })\n  })\n\n  describe('when greatest target', () => {\n    it('upgrades package to greatest version older than cooldown period', async () => {\n      // Given: test-package@1.0.0 installed, version 1.2.0 released 5 days ago (within cooldown), version 1.1.0 released 15 days ago (outside 10-day cooldown)\n      const cooldown = 10\n      const packageData: PackageFile = {\n        dependencies: {\n          'test-package': '1.0.0',\n        },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: {\n            '1.2.0': new Date(NOW - 5 * DAY).toISOString(),\n            '1.1.0': new Date(NOW - 15 * DAY).toISOString(),\n          },\n          distTags: {\n            latest: '1.2.0',\n          },\n        }),\n      )\n\n      // When ncu is run with the cooldown parameter and target is 'greatest'\n      const result = await ncu({ packageData, cooldown, target: 'greatest' })\n\n      // Then: package is upgraded to version 1.1.0 (oldest version outside cooldown)\n      expect(result).to.have.property('test-package', '1.1.0')\n\n      stub.restore()\n    })\n\n    it('skips package upgrade if no versions are older than cooldown period', async () => {\n      // Given: test-package@1.0.0 installed, all versions (1.1.0, 1.2.0) released within 10-day cooldown (8 and 5 days ago)\n      const cooldown = 10\n      const packageData: PackageFile = {\n        dependencies: {\n          'test-package': '1.0.0',\n        },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: {\n            '1.2.0': new Date(NOW - 5 * DAY).toISOString(),\n            '1.1.0': new Date(NOW - 8 * DAY).toISOString(),\n          },\n          distTags: {\n            latest: '1.2.0',\n          },\n        }),\n      )\n\n      // When ncu is run with the cooldown parameter and target is 'greatest'\n      const result = await ncu({ packageData, cooldown, target: 'greatest' })\n\n      // Then test-package should not be upgraded (as no versions were released outside cooldown period)\n      expect(result).to.not.have.property('test-package')\n\n      stub.restore()\n    })\n  })\n\n  describe('when newest target', () => {\n    it('upgrades package to newest version older than cooldown period', async () => {\n      // Given: test-package@1.0.0 installed, version 1.2.0 released 5 days ago (within cooldown), version 1.1.0 released 15 days ago (outside 10-day cooldown)\n      const cooldown = 10\n      const packageData: PackageFile = {\n        dependencies: {\n          'test-package': '1.0.0',\n        },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: {\n            '1.2.0': new Date(NOW - 5 * DAY).toISOString(),\n            '1.1.0': new Date(NOW - 15 * DAY).toISOString(),\n          },\n        }),\n      )\n\n      // When ncu is run with the cooldown parameter and target is 'newest'\n      const result = await ncu({ packageData, cooldown, target: 'newest' })\n\n      // Then: package is upgraded to version 1.1.0 (newest version outside cooldown)\n      expect(result).to.have.property('test-package', '1.1.0')\n\n      stub.restore()\n    })\n  })\n\n  describe('when minor target', () => {\n    it('upgrades package to newest minor version older than cooldown period', async () => {\n      // Given: test-package@1.0.0 installed, version 1.2.0 released 5 days ago (within cooldown), version 1.1.0 released 15 days ago (outside 10-day cooldown)\n      const cooldown = 10\n      const packageData: PackageFile = {\n        dependencies: {\n          'test-package': '1.0.0',\n        },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: {\n            '1.2.0': new Date(NOW - 5 * DAY).toISOString(),\n            '1.1.0': new Date(NOW - 15 * DAY).toISOString(),\n          },\n        }),\n      )\n\n      // When ncu is run with the cooldown parameter and target is 'minor'\n      const result = await ncu({ packageData, cooldown, target: 'minor' })\n\n      // Then: package is upgraded to version 1.1.0 (newest minor version outside cooldown)\n      expect(result).to.have.property('test-package', '1.1.0')\n\n      stub.restore()\n    })\n  })\n\n  describe('when patch target', () => {\n    it('upgrades package to newest patch version older than cooldown period', async () => {\n      // Given: test-package@1.0.0 installed, version 1.0.2 released 5 days ago (within cooldown), version 1.0.1 released 15 days ago (outside 10-day cooldown)\n      const cooldown = 10\n      const packageData: PackageFile = {\n        dependencies: {\n          'test-package': '1.0.0',\n        },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: {\n            '1.0.2': new Date(NOW - 5 * DAY).toISOString(),\n            '1.0.1': new Date(NOW - 15 * DAY).toISOString(),\n          },\n        }),\n      )\n\n      // When ncu is run with the cooldown parameter and target is 'patch'\n      const result = await ncu({ packageData, cooldown, target: 'patch' })\n\n      // Then: package is upgraded to version 1.0.1 (newest patch version outside cooldown)\n      expect(result).to.have.property('test-package', '1.0.1')\n\n      stub.restore()\n    })\n  })\n\n  describe('when semver target', () => {\n    it('upgrades package to newest semver version older than cooldown period', async () => {\n      // Given: test-package@1.0.0 installed, version 1.1.0 released 5 days ago (within cooldown), version 1.0.1 released 15 days ago (outside 10-day cooldown)\n      const cooldown = 10\n      const packageData: PackageFile = {\n        dependencies: {\n          'test-package': '^1.0.0',\n        },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: {\n            '1.1.0': new Date(NOW - 5 * DAY).toISOString(),\n            '1.0.1': new Date(NOW - 15 * DAY).toISOString(),\n          },\n        }),\n      )\n\n      // When ncu is run with the cooldown parameter and target is 'semver'\n      const result = await ncu({ packageData, cooldown, target: 'semver' })\n\n      // Then: package is upgraded to version ^1.0.1 (newest semver version outside cooldown)\n      expect(result).to.have.property('test-package', '^1.0.1')\n      stub.restore()\n    })\n  })\n\n  it('skips package upgrade if no time data and cooldown is set', async () => {\n    // Given: cooldown days is set to 10 days, test-package is installed in version 1.0.0, and the latest version - 1.1.0 was released 5 days ago (inside cooldown period). Another version 1.0.1 was released 10 days ago (outside cooldown period), but it is not the latest version, so it should not be upgraded either.\n    const cooldown = 10\n    const packageData: PackageFile = {\n      dependencies: {\n        'test-package': '1.0.0',\n      },\n    }\n    const stub = stubVersions(\n      createMockVersion({\n        name: 'test-package',\n        versions: {\n          // @ts-expect-error -- testing missing time data\n          '1.1.0': undefined,\n          // @ts-expect-error -- testing missing time data\n          '1.0.1': undefined,\n        },\n        distTags: {\n          latest: '1.1.0',\n        },\n      }),\n    )\n\n    // When ncu is run with a 1 day cooldown parameter\n    const result = await ncu({ packageData, cooldown })\n\n    // Then test-package should not be upgraded\n    expect(result).to.not.have.property('test-package')\n\n    stub.restore()\n  })\n\n  it('upgrades package when version was released exactly at the cooldown boundary', async () => {\n    // Given: test-package@1.0.0 installed, latest version 1.1.0 released exactly 10 days ago (at cooldown boundary)\n    const cooldown = 10\n    const packageData: PackageFile = {\n      dependencies: {\n        'test-package': '1.0.0',\n      },\n    }\n    const stub = stubVersions(\n      createMockVersion({\n        name: 'test-package',\n        versions: {\n          '1.1.0': new Date(NOW - 10 * DAY).toISOString(),\n        },\n        distTags: {\n          latest: '1.1.0',\n        },\n      }),\n    )\n\n    // When ncu is run with the cooldown parameter and target is 'latest'\n    const result = await ncu({ packageData, cooldown, target: 'latest' })\n\n    // Then: test-package should be upgraded to version 1.1.0 (as 1.1.0 was released exactly at the cooldown boundary)\n    expect(result).to.have.property('test-package', '1.1.0')\n\n    stub.restore()\n  })\n\n  describe('cooldown predicate function', () => {\n    it('should skip cooldown check when predicate returns null', async () => {\n      // Given: cooldown set to 10, test-package@1.0.0 installed, latest version 1.1.0 released 5 days ago (within cooldown)\n      const cooldown = 10\n      const packageData: PackageFile = {\n        dependencies: {\n          'test-package': '1.0.0',\n        },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: {\n            '1.1.0': new Date(NOW - 5 * DAY).toISOString(),\n          },\n          distTags: {\n            latest: '1.1.0',\n          },\n        }),\n      )\n\n      // When: cooldown predicate returns null for test-package\n      const result = await ncu({\n        packageData,\n        cooldown: packageName => (packageName === 'test-package' ? null : cooldown),\n        target: 'latest',\n      })\n\n      // Then: test-package is upgraded to version 1.1.0 (cooldown check skipped)\n      expect(result).to.have.property('test-package', '1.1.0')\n\n      stub.restore()\n    })\n\n    it('should apply custom cooldown when predicate returns a number', async () => {\n      // Given: default cooldown set to 10, test-package and test-package-2 - both installed in version 1.0.0, and both has the latest version 1.1.0 released 5 days ago (within cooldown)\n      const cooldown = 10\n      const packageData: PackageFile = {\n        dependencies: {\n          'test-package': '1.0.0',\n          'test-package-2': '1.0.0',\n        },\n      }\n      const stub = stubVersions({\n        'test-package': createMockVersion({\n          name: 'test-package',\n          versions: {\n            '1.1.0': new Date(NOW - 5 * DAY).toISOString(),\n          },\n          distTags: {\n            latest: '1.1.0',\n          },\n        }),\n        'test-package-2': createMockVersion({\n          name: 'test-package-2',\n          versions: {\n            '1.1.0': new Date(NOW - 5 * DAY).toISOString(),\n          },\n          distTags: {\n            latest: '1.1.0',\n          },\n        }),\n      })\n\n      // When: cooldown predicate returns 5 for test-package (skipping cooldown), and 10 for the rest packages\n      const result = await ncu({\n        packageData,\n        cooldown: (packageName: string) => (packageName === 'test-package' ? 5 : cooldown),\n        target: 'latest',\n      })\n\n      // Then: test-package is upgraded to version 1.1.0 (as cooldown for this package was set to 5), but test-package-2 is not upgraded (as rest of the packages use default cooldown of 10)\n      expect(result).to.have.property('test-package', '1.1.0')\n      expect(result).to.not.have.property('test-package-2')\n\n      stub.restore()\n    })\n\n    it('should upgrade when predicate returns a sub-day (fractional) value and version is outside that period', async () => {\n      // Given: predicate returns 12/24 (= 12 hours), version released 13 hours ago — outside cooldown\n      const HOUR = DAY / 24\n      const packageData: PackageFile = {\n        dependencies: { 'test-package': '1.0.0' },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: { '1.1.0': new Date(NOW - 13 * HOUR).toISOString() },\n          distTags: { latest: '1.1.0' },\n        }),\n      )\n\n      // When: cooldown predicate returns 12/24 (fractional days = 12 hours)\n      const result = await ncu({\n        packageData,\n        cooldown: () => 12 / 24,\n        target: 'latest',\n      })\n\n      // Then: test-package is upgraded to 1.1.0\n      expect(result).to.have.property('test-package', '1.1.0')\n\n      stub.restore()\n    })\n\n    it('should skip upgrade when predicate returns a sub-day (fractional) value and version is inside that period', async () => {\n      // Given: predicate returns 12/24 (= 12 hours), version released 11 hours ago — inside cooldown\n      const HOUR = DAY / 24\n      const packageData: PackageFile = {\n        dependencies: { 'test-package': '1.0.0' },\n      }\n      const stub = stubVersions(\n        createMockVersion({\n          name: 'test-package',\n          versions: { '1.1.0': new Date(NOW - 11 * HOUR).toISOString() },\n          distTags: { latest: '1.1.0' },\n        }),\n      )\n\n      // When: cooldown predicate returns 12/24 (fractional days = 12 hours)\n      const result = await ncu({\n        packageData,\n        cooldown: () => 12 / 24,\n        target: 'latest',\n      })\n\n      // Then: test-package is not upgraded\n      expect(result).to.not.have.property('test-package')\n\n      stub.restore()\n    })\n\n    it('should accept a string (\"3m\") returned from the predicate for per-package unit suffixes', async () => {\n      // Given: predicate returns \"3m\" (3 minutes) for test-package and 10 (days) for test-package-2;\n      //        test-package released 4 minutes ago (outside 3m cooldown),\n      //        test-package-2 released 1 day ago (inside 10-day cooldown)\n      const MINUTE = DAY / (24 * 60)\n      const packageData: PackageFile = {\n        dependencies: {\n          'test-package': '1.0.0',\n          'test-package-2': '1.0.0',\n        },\n      }\n      const stub = stubVersions({\n        'test-package': createMockVersion({\n          name: 'test-package',\n          versions: { '1.1.0': new Date(NOW - 4 * MINUTE).toISOString() },\n          distTags: { latest: '1.1.0' },\n        }),\n        'test-package-2': createMockVersion({\n          name: 'test-package-2',\n          versions: { '1.1.0': new Date(NOW - DAY).toISOString() },\n          distTags: { latest: '1.1.0' },\n        }),\n      })\n\n      // When: predicate returns a string for one package and a number for another\n      const result = await ncu({\n        packageData,\n        cooldown: (packageName: string) => (packageName === 'test-package' ? '3m' : 10),\n        target: 'latest',\n      })\n\n      // Then: test-package is upgraded (4 min > 3 min cooldown), test-package-2 is not (1 day < 10 days)\n      expect(result).to.have.property('test-package', '1.1.0')\n      expect(result).to.not.have.property('test-package-2')\n\n      stub.restore()\n    })\n\n    it('should upgrade when predicate returns 0, disabling cooldown for that package', async () => {\n      // Given: predicate returns 0 for test-package (no cooldown) and 10 for others; version released 1 day ago\n      const packageData: PackageFile = {\n        dependencies: {\n          'test-package': '1.0.0',\n          'test-package-2': '1.0.0',\n        },\n      }\n      const stub = stubVersions({\n        'test-package': createMockVersion({\n          name: 'test-package',\n          versions: { '1.1.0': new Date(NOW - DAY).toISOString() },\n          distTags: { latest: '1.1.0' },\n        }),\n        'test-package-2': createMockVersion({\n          name: 'test-package-2',\n          versions: { '1.1.0': new Date(NOW - DAY).toISOString() },\n          distTags: { latest: '1.1.0' },\n        }),\n      })\n\n      // When: predicate returns 0 for test-package and 10 for everything else\n      const result = await ncu({\n        packageData,\n        cooldown: (packageName: string) => (packageName === 'test-package' ? 0 : 10),\n        target: 'latest',\n      })\n\n      // Then: test-package is upgraded (cooldown disabled for it), test-package-2 is not\n      expect(result).to.have.property('test-package', '1.1.0')\n      expect(result).to.not.have.property('test-package-2')\n\n      stub.restore()\n    })\n  })\n})\n"
  },
  {
    "path": "test/deep.test.ts",
    "content": "import { expect } from 'chai'\nimport fs from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport spawn from 'spawn-please'\nimport ncu from '../src/'\nimport mergeOptions from '../src/lib/mergeOptions'\nimport chaiSetup from './helpers/chaiSetup'\nimport removeDir from './helpers/removeDir'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\nconst bin = path.join(__dirname, '../build/cli.js')\n\n/** Creates a temp directory with nested package files for --deep testing. Returns the temp directory name (should be removed by caller).\n *\n * The file tree that is created is:\n * |- package.json\n * |- packages/\n * |  - sub1/\n * |    - package.json\n * |  - sub2/\n * |    - package.json\n */\nconst setupDeepTest = async () => {\n  const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n  await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n  const pkgData = JSON.stringify({\n    dependencies: {\n      express: '1',\n    },\n  })\n\n  // write root package file\n  await fs.writeFile(path.join(tempDir, 'package.json'), pkgData, 'utf-8')\n\n  // write sub-project package files\n  await fs.mkdir(path.join(tempDir, 'packages/sub1'), { recursive: true })\n  await fs.writeFile(path.join(tempDir, 'packages/sub1/package.json'), pkgData, 'utf-8')\n  await fs.mkdir(path.join(tempDir, 'packages/sub2'), { recursive: true })\n  await fs.writeFile(path.join(tempDir, 'packages/sub2/package.json'), pkgData, 'utf-8')\n\n  return tempDir\n}\n\ndescribe('--deep', function () {\n  this.timeout(60000)\n\n  let stub: { restore: () => void }\n  before(() => (stub = stubVersions('99.9.9', { spawn: true })))\n  after(() => stub.restore())\n\n  it('do not allow --packageFile and --deep together', async () => {\n    await ncu({ packageFile: './package.json', deep: true }).should.eventually.be.rejectedWith('Cannot specify both')\n  })\n\n  it('output json with --jsonAll', async () => {\n    const tempDir = await setupDeepTest()\n    try {\n      const { stdout } = await spawn('node', [bin, '--jsonAll', '--deep'], {}, { cwd: tempDir })\n      const deepJsonOut = JSON.parse(stdout)\n      deepJsonOut.should.have.property('package.json')\n      deepJsonOut.should.have.property('packages/sub1/package.json')\n      deepJsonOut.should.have.property('packages/sub2/package.json')\n      deepJsonOut['package.json'].dependencies.should.have.property('express')\n      deepJsonOut['packages/sub1/package.json'].dependencies.should.have.property('express')\n      deepJsonOut['packages/sub2/package.json'].dependencies.should.have.property('express')\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('ignore stdin if --packageFile glob is specified', async () => {\n    const tempDir = await setupDeepTest()\n    try {\n      await spawn(\n        'node',\n        [bin, '-u', '--packageFile', path.join(tempDir, '/**/package.json')],\n        { stdin: '{ \"dependencies\": {}}' },\n        {\n          cwd: tempDir,\n        },\n      )\n      const upgradedPkg = JSON.parse(await fs.readFile(path.join(tempDir, 'package.json'), 'utf-8'))\n      upgradedPkg.should.have.property('dependencies')\n      upgradedPkg.dependencies.should.have.property('express')\n      upgradedPkg.dependencies.express.should.not.equal('1')\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('update multiple packages', async () => {\n    const tempDir = await setupDeepTest()\n    try {\n      const { stdout } = await spawn(\n        'node',\n        [bin, '-u', '--jsonUpgraded', '--packageFile', path.join(tempDir, '**/package.json')],\n        { stdin: '{ \"dependencies\": {}}' },\n        { cwd: tempDir },\n      )\n\n      const upgradedPkg1 = JSON.parse(await fs.readFile(path.join(tempDir, 'packages/sub1/package.json'), 'utf-8'))\n      upgradedPkg1.should.have.property('dependencies')\n      upgradedPkg1.dependencies.should.have.property('express')\n      upgradedPkg1.dependencies.express.should.not.equal('1')\n\n      const upgradedPkg2 = JSON.parse(await fs.readFile(path.join(tempDir, 'packages/sub2/package.json'), 'utf-8'))\n      upgradedPkg2.should.have.property('dependencies')\n      upgradedPkg2.dependencies.should.have.property('express')\n      upgradedPkg2.dependencies.express.should.not.equal('1')\n\n      const json = JSON.parse(stdout)\n      // Make sure to fix windows paths with replace\n      json.should.have.property(path.join(tempDir, 'packages/sub1/package.json').replace(/\\\\/g, '/'))\n      json.should.have.property(path.join(tempDir, 'packages/sub2/package.json').replace(/\\\\/g, '/'))\n      json.should.have.property(path.join(tempDir, 'package.json').replace(/\\\\/g, '/'))\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('--deep --errorLevel 2 should exit with code 0 when there are no upgrades', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgData = JSON.stringify({\n      dependencies: {\n        'ncu-test-v2': '99.9.9',\n      },\n    })\n\n    // write root package file\n    await fs.writeFile(path.join(tempDir, 'package.json'), pkgData, 'utf-8')\n\n    try {\n      await spawn(\n        'node',\n        [bin, '--deep', '--errorLevel', '2'],\n        {},\n        {\n          cwd: tempDir,\n        },\n      )\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n})\n\ndescribe('--deep with nested ncurc files', function () {\n  const cwd = path.join(__dirname, 'test-data/deep-ncurc')\n\n  this.timeout(60000)\n\n  let stub: { restore: () => void }\n  before(() => (stub = stubVersions('99.9.9', { spawn: true })))\n  after(() => stub.restore())\n\n  it('use ncurc of nested packages', async () => {\n    const { stdout } = await spawn('node', [bin, '--jsonUpgraded', '--deep'], {}, { cwd })\n    const deepJsonOut = JSON.parse(stdout)\n\n    // root: reject: ['cute-animals']\n    deepJsonOut.should.have.property('package.json')\n    deepJsonOut['package.json'].should.not.have.property('cute-animals')\n    deepJsonOut['package.json'].should.have.property('fp-and-or')\n\n    // pkg1: reject: ['fp-ando-or']\n    deepJsonOut.should.have.property('pkg/sub1/package.json')\n    deepJsonOut['pkg/sub1/package.json'].should.have.property('cute-animals')\n    deepJsonOut['pkg/sub1/package.json'].should.not.have.property('fp-and-or')\n    deepJsonOut['pkg/sub1/package.json'].should.have.property('ncu-test-return-version')\n\n    // pkg2: reject: ['cute-animals']\n    deepJsonOut.should.have.property('pkg/sub2/package.json')\n    deepJsonOut['pkg/sub2/package.json'].should.not.have.property('cute-animals')\n    deepJsonOut['pkg/sub2/package.json'].should.have.property('fp-and-or')\n    deepJsonOut['pkg/sub2/package.json'].should.have.property('ncu-test-v2')\n\n    // pkg3: reject: ['cute-animals']\n    deepJsonOut.should.have.property('pkg/sub3/package.json')\n    deepJsonOut['pkg/sub3/package.json'].should.not.have.property('cute-animals')\n    deepJsonOut['pkg/sub3/package.json'].should.have.property('fp-and-or')\n    deepJsonOut['pkg/sub3/package.json'].should.have.property('ncu-test-v2')\n  })\n\n  it('use ncurc of nested packages with --mergeConfig option', async () => {\n    const { stdout } = await spawn('node', [bin, '--jsonUpgraded', '--deep', '--mergeConfig'], {}, { cwd })\n    const deepJsonOut = JSON.parse(stdout)\n\n    // root: reject: ['cute-animals']\n    deepJsonOut.should.have.property('package.json')\n    deepJsonOut['package.json'].should.not.have.property('cute-animals')\n    deepJsonOut['package.json'].should.have.property('fp-and-or')\n\n    // pkg1: reject: ['fp-ando-or', 'cute-animals']\n    deepJsonOut.should.have.property('pkg/sub1/package.json')\n    deepJsonOut['pkg/sub1/package.json'].should.not.have.property('cute-animals')\n    deepJsonOut['pkg/sub1/package.json'].should.not.have.property('fp-and-or')\n    deepJsonOut['pkg/sub1/package.json'].should.have.property('ncu-test-return-version')\n\n    // pkg2: reject: ['cute-animals']\n    deepJsonOut.should.have.property('pkg/sub2/package.json')\n    deepJsonOut['pkg/sub2/package.json'].should.not.have.property('cute-animals')\n    deepJsonOut['pkg/sub2/package.json'].should.have.property('fp-and-or')\n    deepJsonOut['pkg/sub2/package.json'].should.have.property('ncu-test-v2')\n\n    // pkg21: explicit reject: ['fp-ando-or'] and implicit reject ['cute-animals']\n    deepJsonOut.should.have.property('pkg/sub2/sub21/package.json')\n    deepJsonOut['pkg/sub2/sub21/package.json'].should.not.have.property('cute-animals')\n    deepJsonOut['pkg/sub2/sub21/package.json'].should.not.have.property('fp-and-or')\n    deepJsonOut['pkg/sub2/sub21/package.json'].should.have.property('ncu-test-return-version')\n\n    // pkg22: implicit reject: ['cute-animals']\n    deepJsonOut.should.have.property('pkg/sub2/sub22/package.json')\n    deepJsonOut['pkg/sub2/sub22/package.json'].should.not.have.property('cute-animals')\n    deepJsonOut['pkg/sub2/sub22/package.json'].should.have.property('fp-and-or')\n    deepJsonOut['pkg/sub2/sub22/package.json'].should.have.property('ncu-test-v2')\n\n    // pkg3: reject: ['cute-animals']\n    deepJsonOut.should.have.property('pkg/sub3/package.json')\n    deepJsonOut['pkg/sub3/package.json'].should.not.have.property('cute-animals')\n    deepJsonOut['pkg/sub3/package.json'].should.have.property('fp-and-or')\n    deepJsonOut['pkg/sub3/package.json'].should.have.property('ncu-test-v2')\n\n    // pkg31: explicit reject: ['fp-ando-or'] and implicit reject ['cute-animals']\n    deepJsonOut.should.have.property('pkg/sub3/sub31/package.json')\n    deepJsonOut['pkg/sub3/sub31/package.json'].should.not.have.property('cute-animals')\n    deepJsonOut['pkg/sub3/sub31/package.json'].should.not.have.property('fp-and-or')\n    deepJsonOut['pkg/sub3/sub31/package.json'].should.have.property('ncu-test-return-version')\n\n    // pkg32: implicit reject: ['cute-animals']\n    deepJsonOut.should.have.property('pkg/sub3/sub32/package.json')\n    deepJsonOut['pkg/sub3/sub32/package.json'].should.not.have.property('cute-animals')\n    deepJsonOut['pkg/sub3/sub32/package.json'].should.have.property('fp-and-or')\n    deepJsonOut['pkg/sub3/sub32/package.json'].should.have.property('ncu-test-v2')\n  })\n})\n\ndescribe('mergeOptions', function () {\n  it('merge options', () => {\n    /** Asserts that merging two options object deep equals the given result object. */\n    const eq = (\n      o1: Record<string, unknown> | null,\n      o2: Record<string, unknown> | null,\n      result: Record<string, unknown>,\n    ) => expect(mergeOptions(o1, o2)).to.deep.equal(result)\n\n    // trivial cases\n    eq(null, null, {})\n    eq({}, {}, {})\n\n    // standard merge not broken\n    eq({ a: 1 }, {}, { a: 1 })\n    eq({}, { a: 1 }, { a: 1 })\n    eq({ a: 1 }, { a: 2 }, { a: 2 })\n\n    // merge arrays (non standard behavior)\n    eq({ a: [1] }, { a: [2] }, { a: [1, 2] })\n    eq({ a: [1, 2] }, { a: [2, 3] }, { a: [1, 2, 3] })\n\n    // if property types different, then apply standard merge behavior\n    eq({ a: 1 }, { a: [2] }, { a: [2] })\n\n    // all together\n    eq(\n      { a: [1], b: true, c: 1, d1: 'd1' },\n      { a: [2], b: false, c: ['1'], d2: 'd2' },\n      { a: [1, 2], b: false, c: ['1'], d1: 'd1', d2: 'd2' },\n    )\n  })\n})\n"
  },
  {
    "path": "test/dep.test.ts",
    "content": "import fs from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport ncu from '../src/'\nimport chaiSetup from './helpers/chaiSetup'\nimport removeDir from './helpers/removeDir'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\nconst packageData = JSON.stringify({\n  dependencies: {\n    'ncu-test-v2': '0.1.0',\n  },\n  devDependencies: {\n    'ncu-test-tag': '0.1.0',\n  },\n  peerDependencies: {\n    'ncu-test-10': '0.1.0',\n  },\n})\n\ndescribe('--dep', () => {\n  it('do not upgrade peerDependencies by default', async () => {\n    const stub = stubVersions('99.9.9')\n\n    const upgraded = await ncu({ packageData })\n\n    upgraded!.should.have.property('ncu-test-v2')\n    upgraded!.should.have.property('ncu-test-tag')\n    upgraded!.should.not.have.property('ncu-test-10')\n\n    stub.restore()\n  })\n\n  it('only upgrade devDependencies with --dep dev', async () => {\n    const stub = stubVersions('99.9.9')\n\n    const upgraded = await ncu({ packageData, dep: 'dev' })\n\n    upgraded!.should.not.have.property('ncu-test-v2')\n    upgraded!.should.have.property('ncu-test-tag')\n    upgraded!.should.not.have.property('ncu-test-10')\n\n    stub.restore()\n  })\n\n  it('only upgrade devDependencies and peerDependencies with --dep dev,peer', async () => {\n    const stub = stubVersions('99.9.9')\n    const upgraded = await ncu({ packageData, dep: 'dev,peer' })\n\n    upgraded!.should.not.have.property('ncu-test-v2')\n    upgraded!.should.have.property('ncu-test-tag')\n    upgraded!.should.have.property('ncu-test-10')\n\n    stub.restore()\n  })\n\n  describe('section isolation', () => {\n    it('do not overwrite the same package in peerDependencies when upgrading devDependencies', async () => {\n      const stub = stubVersions('99.9.9')\n      const packageData = JSON.stringify({\n        dependencies: {\n          'ncu-test-v2': '0.1.0',\n        },\n        devDependencies: {\n          'ncu-test-tag': '0.1.0',\n        },\n        peerDependencies: {\n          'ncu-test-tag': '0.1.0',\n        },\n      })\n\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(pkgFile, packageData)\n\n      try {\n        await ncu({\n          packageFile: pkgFile,\n          jsonUpgraded: false,\n          upgrade: true,\n          dep: 'dev',\n        })\n        const pkgNew = JSON.parse(await fs.readFile(pkgFile, 'utf-8'))\n\n        pkgNew.should.deep.equal({\n          // unspecified dep sections are ignored\n          dependencies: {\n            'ncu-test-v2': '0.1.0',\n          },\n          // specified dep sections are upgraded\n          devDependencies: {\n            'ncu-test-tag': '99.9.9',\n          },\n          // unspecified dep sections are ignored, even if they have a package upgraded in another section\n          peerDependencies: {\n            'ncu-test-tag': '0.1.0',\n          },\n        })\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n\n    it('do not overwrite the same package in devDependencies when upgrading peerDependencies', async () => {\n      const stub = stubVersions('99.9.9')\n      const packageData = JSON.stringify({\n        dependencies: {\n          'ncu-test-v2': '0.1.0',\n        },\n        devDependencies: {\n          'ncu-test-tag': '0.1.0',\n        },\n        peerDependencies: {\n          'ncu-test-tag': '0.1.0',\n        },\n      })\n\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(pkgFile, packageData)\n\n      try {\n        await ncu({\n          packageFile: pkgFile,\n          jsonUpgraded: false,\n          upgrade: true,\n          dep: 'peer',\n        })\n        const pkgNew = JSON.parse(await fs.readFile(pkgFile, 'utf-8'))\n\n        pkgNew.should.deep.equal({\n          // unspecified dep sections are ignored\n          dependencies: {\n            'ncu-test-v2': '0.1.0',\n          },\n          // unspecified dep sections are ignored, even if they have a package upgraded in another section\n          devDependencies: {\n            'ncu-test-tag': '0.1.0',\n          },\n          // specified dep sections are upgraded\n          peerDependencies: {\n            'ncu-test-tag': '99.9.9',\n          },\n        })\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n\n    it('do not overwrite the same package in devDependencies when upgrading dependencies and peerDependencies', async () => {\n      const stub = stubVersions('99.9.9')\n      const packageData = JSON.stringify({\n        dependencies: {\n          'ncu-test-tag': '0.1.0',\n        },\n        devDependencies: {\n          'ncu-test-tag': '0.1.0',\n        },\n        peerDependencies: {\n          'ncu-test-tag': '0.1.0',\n        },\n      })\n\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(pkgFile, packageData)\n\n      try {\n        await ncu({\n          packageFile: pkgFile,\n          jsonUpgraded: false,\n          upgrade: true,\n          dep: 'prod,peer',\n        })\n        const pkgNew = JSON.parse(await fs.readFile(pkgFile, 'utf-8'))\n\n        pkgNew.should.deep.equal({\n          // specified dep sections are upgraded\n          dependencies: {\n            'ncu-test-tag': '99.9.9',\n          },\n          // unspecified dep sections are ignored, even if they have a package upgraded in another section\n          devDependencies: {\n            'ncu-test-tag': '0.1.0',\n          },\n          // specified dep sections are upgraded\n          peerDependencies: {\n            'ncu-test-tag': '99.9.9',\n          },\n        })\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n  })\n\n  describe('packageManager field', () => {\n    it('upgrade packageManager field by default', async () => {\n      const stub = stubVersions({\n        'ncu-test-tag': '1.0.0',\n        npm: '9.0.0',\n      })\n      const packageData = JSON.stringify(\n        {\n          packageManager: 'npm@6.0.0',\n          dependencies: {\n            'ncu-test-tag': '0.1.0',\n          },\n        },\n        null,\n        2,\n      )\n\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(pkgFile, packageData)\n\n      try {\n        await ncu({\n          packageFile: pkgFile,\n          jsonUpgraded: false,\n          upgrade: true,\n        })\n        const pkgDataNew = await fs.readFile(pkgFile, 'utf-8')\n        const pkgNew = JSON.parse(pkgDataNew)\n\n        pkgNew.should.deep.equal({\n          packageManager: 'npm@9.0.0',\n          dependencies: {\n            'ncu-test-tag': '1.0.0',\n          },\n        })\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n\n    it('do not upgrade packageManager field if missing from --dep', async () => {\n      const stub = stubVersions({\n        'ncu-test-tag': '1.0.0',\n        npm: '9.0.0',\n      })\n      const packageData = JSON.stringify(\n        {\n          packageManager: 'npm@6.0.0',\n          dependencies: {\n            'ncu-test-tag': '0.1.0',\n          },\n        },\n        null,\n        2,\n      )\n\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(pkgFile, packageData)\n\n      try {\n        await ncu({\n          packageFile: pkgFile,\n          jsonUpgraded: false,\n          upgrade: true,\n          dep: ['prod'],\n        })\n        const pkgDataNew = await fs.readFile(pkgFile, 'utf-8')\n        const pkgNew = JSON.parse(pkgDataNew)\n\n        pkgNew.should.deep.equal({\n          packageManager: 'npm@6.0.0',\n          dependencies: {\n            'ncu-test-tag': '1.0.0',\n          },\n        })\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n\n    it('do nothing if no packageManager field is present', async () => {\n      const stub = stubVersions({\n        'ncu-test-tag': '1.0.0',\n        npm: '9.0.0',\n      })\n      const packageData = JSON.stringify(\n        {\n          dependencies: {\n            'ncu-test-tag': '0.1.0',\n          },\n        },\n        null,\n        2,\n      )\n\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(pkgFile, packageData)\n\n      try {\n        await ncu({\n          packageFile: pkgFile,\n          jsonUpgraded: false,\n          upgrade: true,\n        })\n        const pkgDataNew = await fs.readFile(pkgFile, 'utf-8')\n        const pkgNew = JSON.parse(pkgDataNew)\n\n        pkgNew.should.deep.equal({\n          dependencies: {\n            'ncu-test-tag': '1.0.0',\n          },\n        })\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n\n    it('upgrade packageManager field if specified in --dep', async () => {\n      const stub = stubVersions({\n        'ncu-test-tag': '1.0.0',\n        npm: '9.0.0',\n      })\n      const packageData = JSON.stringify(\n        {\n          packageManager: 'npm@6.0.0',\n          dependencies: {\n            'ncu-test-tag': '0.1.0',\n          },\n        },\n        null,\n        2,\n      )\n\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(pkgFile, packageData)\n\n      try {\n        await ncu({\n          packageFile: pkgFile,\n          jsonUpgraded: false,\n          upgrade: true,\n          dep: ['prod', 'packageManager'],\n        })\n        const pkgDataNew = await fs.readFile(pkgFile, 'utf-8')\n        const pkgNew = JSON.parse(pkgDataNew)\n\n        pkgNew.should.deep.equal({\n          packageManager: 'npm@9.0.0',\n          dependencies: {\n            'ncu-test-tag': '1.0.0',\n          },\n        })\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n\n    it('do nothing if packageManager is up-to-date', async () => {\n      const stub = stubVersions({\n        'ncu-test-tag': '1.0.0',\n        npm: '9.0.0',\n      })\n      const packageData = JSON.stringify(\n        {\n          packageManager: 'npm@9.0.0',\n          dependencies: {\n            'ncu-test-tag': '0.1.0',\n          },\n        },\n        null,\n        2,\n      )\n\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(pkgFile, packageData)\n\n      try {\n        await ncu({\n          packageFile: pkgFile,\n          jsonUpgraded: false,\n          upgrade: true,\n        })\n        const pkgDataNew = await fs.readFile(pkgFile, 'utf-8')\n        const pkgNew = JSON.parse(pkgDataNew)\n\n        pkgNew.should.deep.equal({\n          packageManager: 'npm@9.0.0',\n          dependencies: {\n            'ncu-test-tag': '1.0.0',\n          },\n        })\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n  })\n})\n"
  },
  {
    "path": "test/determinePackageManager.test.ts",
    "content": "import determinePackageManager from '../src/lib/determinePackageManager'\nimport chaiSetup from './helpers/chaiSetup'\n\nchaiSetup()\n\nconst isWindows = process.platform === 'win32'\n\ndescribe('determinePackageManager', () => {\n  it('returns bun if bun.lockb exists in cwd', async () => {\n    /** Mock for filesystem calls. */\n    function readdirMock(path: string): Promise<string[]> {\n      switch (path) {\n        case '/home/test-repo':\n        case 'C:\\\\home\\\\test-repo':\n          return Promise.resolve(['bun.lockb'])\n      }\n\n      throw new Error(`Mock cannot handle path: ${path}.`)\n    }\n\n    const packageManager = await determinePackageManager(\n      {\n        cwd: isWindows ? 'C:\\\\home\\\\test-repo' : '/home/test-repo',\n      },\n      readdirMock,\n    )\n    packageManager.should.equal('bun')\n  })\n\n  it('returns bun if bun.lock exists in cwd', async () => {\n    /** Mock for filesystem calls. */\n    function readdirMock(path: string): Promise<string[]> {\n      switch (path) {\n        case '/home/test-repo':\n        case 'C:\\\\home\\\\test-repo':\n          return Promise.resolve(['bun.lock'])\n      }\n\n      throw new Error(`Mock cannot handle path: ${path}.`)\n    }\n\n    const packageManager = await determinePackageManager(\n      {\n        cwd: isWindows ? 'C:\\\\home\\\\test-repo' : '/home/test-repo',\n      },\n      readdirMock,\n    )\n    packageManager.should.equal('bun')\n  })\n\n  it('returns bun if bun.lockb exists in an ancestor directory', async () => {\n    /** Mock for filesystem calls. */\n    function readdirMock(path: string): Promise<string[]> {\n      switch (path) {\n        case '/home/test-repo/packages/package-a':\n        case 'C:\\\\home\\\\test-repo\\\\packages\\\\package-a':\n          return Promise.resolve(['index.ts'])\n        case '/home/test-repo/packages':\n        case 'C:\\\\home\\\\test-repo\\\\packages':\n          return Promise.resolve([])\n        case '/home/test-repo':\n        case 'C:\\\\home\\\\test-repo':\n          return Promise.resolve(['bun.lockb'])\n      }\n\n      throw new Error(`Mock cannot handle path: ${path}.`)\n    }\n\n    const packageManager = await determinePackageManager(\n      {\n        cwd: isWindows ? 'C:\\\\home\\\\test-repo\\\\packages\\\\package-a' : '/home/test-repo/packages/package-a',\n      },\n      readdirMock,\n    )\n    packageManager.should.equal('bun')\n  })\n\n  it('returns yarn if yarn.lock exists in cwd', async () => {\n    /** Mock for filesystem calls. */\n    function readdirMock(path: string): Promise<string[]> {\n      switch (path) {\n        case '/home/test-repo':\n        case 'C:\\\\home\\\\test-repo':\n          return Promise.resolve(['yarn.lock'])\n      }\n\n      throw new Error(`Mock cannot handle path: ${path}.`)\n    }\n\n    const packageManager = await determinePackageManager(\n      {\n        cwd: isWindows ? 'C:\\\\home\\\\test-repo' : '/home/test-repo',\n      },\n      readdirMock,\n    )\n    packageManager.should.equal('yarn')\n  })\n\n  it('returns yarn if yarn.lock exists in an ancestor directory', async () => {\n    /** Mock for filesystem calls. */\n    function readdirMock(path: string): Promise<string[]> {\n      switch (path) {\n        case '/home/test-repo/packages/package-a':\n        case 'C:\\\\home\\\\test-repo\\\\packages\\\\package-a':\n          return Promise.resolve(['index.ts'])\n        case '/home/test-repo/packages':\n        case 'C:\\\\home\\\\test-repo\\\\packages':\n          return Promise.resolve([])\n        case '/home/test-repo':\n        case 'C:\\\\home\\\\test-repo':\n          return Promise.resolve(['yarn.lock'])\n      }\n\n      throw new Error(`Mock cannot handle path: ${path}.`)\n    }\n\n    const packageManager = await determinePackageManager(\n      {\n        cwd: isWindows ? 'C:\\\\home\\\\test-repo\\\\packages\\\\package-a' : '/home/test-repo/packages/package-a',\n      },\n      readdirMock,\n    )\n    packageManager.should.equal('yarn')\n  })\n\n  it('returns npm if package-lock.json found before yarn.lock', async () => {\n    /** Mock for filesystem calls. */\n    function readdirMock(path: string): Promise<string[]> {\n      switch (path) {\n        case '/home/test-repo/packages/package-a':\n        case 'C:\\\\home\\\\test-repo\\\\packages\\\\package-a':\n          return Promise.resolve(['index.ts'])\n        case '/home/test-repo/packages':\n        case 'C:\\\\home\\\\test-repo\\\\packages':\n          return Promise.resolve(['package-lock.json'])\n        case '/home/test-repo':\n        case 'C:\\\\home\\\\test-repo':\n          return Promise.resolve(['yarn.lock'])\n      }\n\n      throw new Error(`Mock cannot handle path: ${path}.`)\n    }\n\n    const packageManager = await determinePackageManager(\n      {\n        cwd: isWindows ? 'C:\\\\home\\\\test-repo\\\\packages\\\\package-a' : '/home/test-repo/packages/package-a',\n      },\n      readdirMock,\n    )\n    packageManager.should.equal('npm')\n  })\n\n  it('does not loop infinitely if no lockfile found', async () => {\n    /** Mock for filesystem calls. */\n    function readdirMock(): Promise<string[]> {\n      return Promise.resolve([])\n    }\n\n    const packageManager = await determinePackageManager(\n      {\n        cwd: isWindows ? 'C:\\\\home\\\\test-repo\\\\packages\\\\package-a' : '/home/test-repo/packages/package-a',\n      },\n      readdirMock,\n    )\n    packageManager.should.equal('npm')\n  })\n\n  describe('global', () => {\n    it('detects bun', async () => {\n      const oldUserAgent = process.env.npm_config_user_agent\n      const oldExecpath = process.env.npm_execpath\n\n      process.env.npm_config_user_agent = 'bun/1.2.10'\n      process.env.npm_execpath = 'bun'\n      process.versions.bun = '1.2.10'\n\n      const packageManager = await determinePackageManager({\n        global: true,\n      })\n      packageManager.should.equal('bun')\n\n      process.env.npm_config_user_agent = oldUserAgent\n      process.env.npm_execpath = oldExecpath\n      process.versions.bun = ''\n    })\n\n    it('detects yarn', async () => {\n      const oldUserAgent = process.env.npm_config_user_agent\n      const oldExecpath = process.env.npm_execpath\n\n      process.env.npm_config_user_agent = 'yarn/1.2.10'\n      process.env.npm_execpath = 'yarn'\n\n      const packageManager = await determinePackageManager({\n        global: true,\n      })\n      packageManager.should.equal('yarn')\n\n      process.env.npm_config_user_agent = oldUserAgent\n      process.env.npm_execpath = oldExecpath\n    })\n\n    it('detects pnpm', async () => {\n      const oldUserAgent = process.env.npm_config_user_agent\n      const oldExecpath = process.env.npm_execpath\n\n      process.env.npm_config_user_agent = 'pnpm/1.2.10'\n      process.env.npm_execpath = 'pnpm'\n\n      const packageManager = await determinePackageManager({\n        global: true,\n      })\n      packageManager.should.equal('pnpm')\n\n      process.env.npm_config_user_agent = oldUserAgent\n      process.env.npm_execpath = oldExecpath\n    })\n\n    it('defaults to npm', async () => {\n      const oldUserAgent = process.env.npm_config_user_agent\n      const oldExecpath = process.env.npm_execpath\n\n      process.env.npm_config_user_agent = ''\n      process.env.npm_execpath = ''\n\n      const packageManager = await determinePackageManager({\n        global: true,\n      })\n      packageManager.should.equal('npm')\n\n      process.env.npm_config_user_agent = oldUserAgent\n      process.env.npm_execpath = oldExecpath\n    })\n  })\n})\n"
  },
  {
    "path": "test/doctor.test.ts",
    "content": "import fs from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport spawn from 'spawn-please'\nimport { cliOptionsMap } from '../src/cli-options'\nimport { chalkInit } from '../src/lib/chalk'\nimport chaiSetup from './helpers/chaiSetup'\nimport { testFail, testPass } from './helpers/doctorHelpers'\nimport removeDir from './helpers/removeDir'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\nconst bin = path.join(__dirname, '../build/cli.js')\nconst doctorTests = path.join(__dirname, 'test-data/doctor')\n\nconst mockNpmVersions = {\n  emitter20: '2.0.0',\n  'ncu-test-return-version': '2.0.0',\n  'ncu-test-tag': '1.1.0',\n  'ncu-test-v2': '2.0.0',\n}\n\n/** Run the ncu CLI. */\nconst ncu = async (\n  args: string[],\n  spawnPleaseOptions?: Parameters<typeof spawn>[2],\n  spawnOptions?: Parameters<typeof spawn>[3],\n) => {\n  const { stdout } = await spawn('node', [bin, ...args], spawnPleaseOptions, spawnOptions)\n  return stdout\n}\n\ndescribe('doctor', function () {\n  // 3 min timeout\n  this.timeout(3 * 60 * 1000)\n\n  let stub: { restore: () => void }\n  before(() => (stub = stubVersions(mockNpmVersions, { spawn: true })))\n  after(() => stub.restore())\n\n  describe('npm', () => {\n    it('print instructions when -u is not specified', async () => {\n      await chalkInit()\n      const { default: stripAnsi } = await import('strip-ansi')\n      const cwd = path.join(doctorTests, 'nopackagefile')\n      const output = await ncu(['--doctor'], {}, { cwd })\n      return stripAnsi(output).should.equal(\n        `Usage: ncu --doctor\\n\\n${stripAnsi(\n          (cliOptionsMap.doctor.help as (options: { markdown: boolean }) => string)({ markdown: false }),\n        )}\\n`,\n      )\n    })\n\n    it('throw an error if there is no package file', async () => {\n      const cwd = path.join(doctorTests, 'nopackagefile')\n      return ncu(['--doctor', '-u'], {}, { cwd }).should.eventually.be.rejectedWith('Missing or invalid package.json')\n    })\n\n    it('throw an error if there is no test script', async () => {\n      const cwd = path.join(doctorTests, 'notestscript')\n      return ncu(['--doctor', '-u'], {}, { cwd }).should.eventually.be.rejectedWith('No npm \"test\" script')\n    })\n\n    it('throw an error if --packageData or --packageFile are supplied', async () => {\n      return Promise.all([\n        ncu(['--doctor', '-u', '--packageFile', 'package.json']).should.eventually.be.rejectedWith(\n          '--packageData and --packageFile are not allowed with --doctor',\n        ),\n        ncu(['--doctor', '-u', '--packageData', '{}']).should.eventually.be.rejectedWith(\n          '--packageData and --packageFile are not allowed with --doctor',\n        ),\n      ])\n    })\n\n    testPass({ packageManager: 'npm' })\n    testFail({ packageManager: 'npm' })\n\n    it('pass through options', async function () {\n      // use dynamic import for ESM module\n      const { default: stripAnsi } = await import('strip-ansi')\n      const cwd = path.join(doctorTests, 'options')\n      const pkgPath = path.join(cwd, 'package.json')\n      const lockfilePath = path.join(cwd, 'package-lock.json')\n      const nodeModulesPath = path.join(cwd, 'node_modules')\n      const pkgOriginal = await fs.readFile(path.join(cwd, 'package.json'), 'utf-8')\n      let stdout = ''\n      let stderr = ''\n\n      try {\n        // check only ncu-test-v2 (excluding ncu-return-version)\n        await ncu(\n          ['--doctor', '-u', '--filter', 'ncu-test-v2'],\n          {\n            stdout: function (data: string) {\n              stdout += data\n            },\n            stderr: function (data: string) {\n              stderr += data\n            },\n          },\n          { cwd },\n        )\n      } catch (e) {}\n\n      const pkgUpgraded = await fs.readFile(pkgPath, 'utf-8')\n\n      // cleanup before assertions in case they fail\n      await fs.writeFile(pkgPath, pkgOriginal)\n      await fs.rm(lockfilePath, { recursive: true, force: true })\n      await fs.rm(nodeModulesPath, { recursive: true, force: true })\n\n      // stderr should be empty or equal to the test script output (output varies by platform/node version)\n      stderr = stripAnsi(stderr).trim()\n      if (stderr !== '') {\n        stderr.should.equal(`> test\n> node test.js\n\n\n\n> test\n> node test.js`)\n      }\n\n      // stdout should include normal output\n      stripAnsi(stdout).should.containIgnoreCase('Tests pass')\n      stripAnsi(stdout).should.containIgnoreCase('ncu-test-v2  ~1.0.0  →  ~2.0.0')\n\n      // package file should include upgrades\n      pkgUpgraded.should.containIgnoreCase('\"ncu-test-v2\": \"~2.0.0\"')\n    })\n\n    it('custom install script with --doctorInstall', async function () {\n      // use dynamic import for ESM module\n      const { default: stripAnsi } = await import('strip-ansi')\n      const cwd = path.join(doctorTests, 'custominstall')\n      const pkgPath = path.join(cwd, 'package.json')\n      const lockfilePath = path.join(cwd, 'package-lock.json')\n      const nodeModulesPath = path.join(cwd, 'node_modules')\n      const pkgOriginal = await fs.readFile(path.join(cwd, 'package.json'), 'utf-8')\n      const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'\n      let stdout = ''\n      let stderr = ''\n\n      try {\n        await ncu(\n          ['--doctor', '-u', '--doctorInstall', npmCmd + ' run myinstall'],\n          {\n            stdout: function (data: string) {\n              stdout += data\n            },\n            stderr: function (data: string) {\n              stderr += data\n            },\n          },\n          { cwd },\n        )\n      } catch (e) {}\n\n      const pkgUpgraded = await fs.readFile(pkgPath, 'utf-8')\n\n      // cleanup before assertions in case they fail\n      await fs.writeFile(pkgPath, pkgOriginal)\n      await fs.rm(lockfilePath, { recursive: true, force: true })\n      await fs.rm(nodeModulesPath, { recursive: true, force: true })\n\n      // stderr should be empty or equal to the test script output (output varies by platform/node version)\n      stderr = stripAnsi(stderr).trim()\n      if (stderr !== '') {\n        stripAnsi(stderr).should.equal(`> test\n> echo 'Test Success'\n\n\n\n> test\n> echo 'Test Success'`)\n      }\n\n      // stdout should include normal output\n      stripAnsi(stdout).should.containIgnoreCase('Tests pass')\n\n      // package file should include upgrades\n      pkgUpgraded.should.containIgnoreCase('\"ncu-test-v2\": \"~2.0.0\"')\n    })\n\n    it('custom test script with --doctorTest', async function () {\n      // use dynamic import for ESM module\n      const { default: stripAnsi } = await import('strip-ansi')\n      const cwd = path.join(doctorTests, 'customtest')\n      const pkgPath = path.join(cwd, 'package.json')\n      const lockfilePath = path.join(cwd, 'package-lock.json')\n      const nodeModulesPath = path.join(cwd, 'node_modules')\n      const pkgOriginal = await fs.readFile(path.join(cwd, 'package.json'), 'utf-8')\n      const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'\n      let stdout = ''\n      let stderr = ''\n\n      try {\n        await ncu(\n          ['--doctor', '-u', '--doctorTest', npmCmd + ' run mytest'],\n          {\n            stdout: function (data: string) {\n              stdout += data\n            },\n            stderr: function (data: string) {\n              stderr += data\n            },\n          },\n          { cwd },\n        )\n      } catch (e) {}\n\n      const pkgUpgraded = await fs.readFile(pkgPath, 'utf-8')\n\n      // cleanup before assertions in case they fail\n      await fs.writeFile(pkgPath, pkgOriginal)\n      await fs.rm(lockfilePath, { recursive: true, force: true })\n      await fs.rm(nodeModulesPath, { recursive: true, force: true })\n\n      // stderr should be empty or equal to the test script output (output varies by platform/node version)\n      stderr = stripAnsi(stderr).trim()\n      if (stderr !== '') {\n        stderr.should.equal(`> mytest\n> echo Success\n\n\n\n> mytest\n> echo Success`)\n      }\n\n      // stdout should include normal output\n      stripAnsi(stdout).should.containIgnoreCase('Tests pass')\n\n      // package file should include upgrades\n      pkgUpgraded.should.containIgnoreCase('\"ncu-test-v2\": \"~2.0.0\"')\n    })\n\n    it('custom test script with --doctorTest command that includes spaced words wrapped in quotes', async function () {\n      // use dynamic import for ESM module\n      const { default: stripAnsi } = await import('strip-ansi')\n      const cwd = path.join(doctorTests, 'customtest2')\n      const pkgPath = path.join(cwd, 'package.json')\n      const lockfilePath = path.join(cwd, 'package-lock.json')\n      const nodeModulesPath = path.join(cwd, 'node_modules')\n      const echoPath = path.join(cwd, 'echo.js')\n      const pkgOriginal = await fs.readFile(path.join(cwd, 'package.json'), 'utf-8')\n      let stdout = ''\n      let stderr = ''\n\n      try {\n        await ncu(\n          ['--doctor', '-u', '--doctorTest', `node ${echoPath} '123 456'`],\n          {\n            stdout: function (data: string) {\n              stdout += data\n            },\n            stderr: function (data: string) {\n              stderr += data\n            },\n          },\n          { cwd },\n        )\n      } catch (e) {}\n\n      const pkgUpgraded = await fs.readFile(pkgPath, 'utf-8')\n\n      // cleanup before assertions in case they fail\n      await fs.writeFile(pkgPath, pkgOriginal)\n      await fs.rm(lockfilePath, { recursive: true, force: true })\n      await fs.rm(nodeModulesPath, { recursive: true, force: true })\n\n      // stderr should be empty\n      stderr.should.equal('')\n\n      // stdout should include expected output\n      stripAnsi(stdout).should.contain(\"'123 456'\")\n\n      // package file should include upgrades\n      pkgUpgraded.should.containIgnoreCase('\"ncu-test-v2\": \"~2.0.0\"')\n    })\n\n    it('handle failed prepare script', async () => {\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgPath = path.join(tempDir, 'package.json')\n      await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n\n      // package.json\n      await fs.writeFile(\n        pkgPath,\n        JSON.stringify({\n          scripts: {\n            prepare: 'node prepare.js',\n            test: 'echo \"No tests\"',\n          },\n          dependencies: {\n            'ncu-test-v2': '1.0.0',\n            'ncu-test-tag': '1.0.0',\n          },\n        }),\n        'utf-8',\n      )\n\n      // prepare.js\n      // A script that fails if ncu-test-v2 is not at 1.0.0.\n      // This is an arbitrary fail condition used to test that doctor mode still works when the npm prepare script fails.\n      await fs.writeFile(\n        path.join(tempDir, 'prepare.js'),\n        `const ncuTestPkg = require('./node_modules/ncu-test-v2/package.json')\n\nif (ncuTestPkg.version === '1.0.0') {\n  console.log('done')\n  process.exit(0)\n}\nelse {\n  console.error('failed')\n  process.exit(1)\n}`,\n        'utf-8',\n      )\n\n      let stdout = ''\n      let stderr = ''\n      let pkgUpgraded\n\n      try {\n        // explicitly set packageManager to avoid auto yarn detection\n        await spawn('npm', ['install'], {}, { cwd: tempDir })\n\n        await ncu(\n          ['--doctor', '-u', '-p', 'npm'],\n          {\n            stdout: function (data: string) {\n              stdout += data\n            },\n            stderr: function (data: string) {\n              stderr += data\n            },\n          },\n          { cwd: tempDir },\n        )\n\n        pkgUpgraded = JSON.parse(await fs.readFile(pkgPath, 'utf-8'))\n      } finally {\n        await removeDir(tempDir)\n      }\n\n      // stdout should include successful upgrades\n      stdout.should.containIgnoreCase('ncu-test-tag 1.0.0 →')\n      stdout.should.not.containIgnoreCase('ncu-test-v2 1.0.0 →')\n\n      // stderr should include failed prepare script\n      stderr.should.containIgnoreCase('failed')\n      stderr.should.containIgnoreCase('ncu-test-v2 1.0.0 →')\n      stderr.should.not.containIgnoreCase('ncu-test-tag 1.0.0 →')\n\n      // package file should only include successful upgrades\n      pkgUpgraded.dependencies.should.deep.equal({\n        'ncu-test-v2': '1.0.0',\n        'ncu-test-tag': '1.1.0',\n      })\n    })\n  })\n\n  describe('yarn', () => {\n    testPass({ packageManager: 'yarn' })\n    testFail({ packageManager: 'yarn' })\n  })\n})\n"
  },
  {
    "path": "test/e2e/cjs/index.js",
    "content": "/** NOTE: This script is copied into a temp directory by the e2e test and dependencies are installed from the local verdaccio registry. */\nconst ncu = require('npm-check-updates')\nconst assert = require('assert')\n\nconst registry = process.env.REGISTRY || 'http://localhost:4873'\n\n// must exit with error code on unhandledRejection; otherwise, script will exit with 0 if an assertion fails in the async block\nprocess.on('unhandledRejection', (reason, p) => {\n  process.exit(1)\n})\n\n// test\n;(async () => {\n  const upgraded = await ncu.run({\n    // --pre 1 to ensure that an upgrade is always suggested even if npm-check-updates is on a prerelease version\n    pre: true,\n    packageData: JSON.stringify({\n      dependencies: {\n        'npm-check-updates': '1.0.0',\n      },\n    }),\n    registry,\n  })\n\n  console.info(upgraded)\n\n  assert.notStrictEqual(upgraded['npm-check-updates'], '1.0.0', 'npm-check-updates should be upgraded')\n})()\n"
  },
  {
    "path": "test/e2e/cjs/package.json",
    "content": "{}\n"
  },
  {
    "path": "test/e2e/esm/index.js",
    "content": "/** NOTE: This script is copied into a temp directory by the e2e test and dependencies are installed from the local verdaccio registry. */\nimport assert from 'assert'\nimport ncu from 'npm-check-updates'\n\nconst registry = process.env.REGISTRY || 'http://localhost:4873'\n\n// must exit with error code on unhandledRejection; otherwise, script will exit with 0 if an assertion fails in the async block\nprocess.on('unhandledRejection', (reason, p) => {\n  process.exit(1)\n})\n\n// test\n;(async () => {\n  const upgraded = await ncu.run({\n    // --pre 1 to ensure that an upgrade is always suggested even if npm-check-updates is on a prerelease version\n    pre: true,\n    packageData: JSON.stringify({\n      dependencies: {\n        'npm-check-updates': '1.0.0',\n      },\n    }),\n    registry,\n  })\n\n  console.info(upgraded)\n\n  assert.notStrictEqual(upgraded['npm-check-updates'], '1.0.0', 'npm-check-updates should be upgraded')\n})()\n"
  },
  {
    "path": "test/e2e/esm/package.json",
    "content": "{\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "test/e2e.sh",
    "content": "#!/bin/bash\n\n# These can be set once the fnm permission errors are figured out\n# See: https://github.com/Schniz/fnm\n# set -e\n# set -o pipefail\n\ncwd=$(pwd)\ne2e_dir=$(dirname \"$(readlink -f \"$0\")\")\ntemp_dir=$(mktemp -d)\nregistry_port=4873\nregistry_local=\"http://localhost:$registry_port\"\nregistry_log=$temp_dir/verdaccio.log\nverdaccio_config=$temp_dir/verdaccio-config.yaml\n\n# cleanup on exit\ncleanup() {\n\n  exit_status=$?\n\n  # shut down verdaccio\n  verdaccio_pid=$(lsof -t -i :$registry_port)\n  if [ -n \"$verdaccio_pid\" ]; then\n    echo Shutting down verdaccio\n    kill -9 $verdaccio_pid\n    wait $verdaccio_pid 2>/dev/null\n  fi\n\n  # delete authToken\n  # WARNING: The original authToken cannot be restored because it is protected and cannot be read with 'npm config get'.\n  npm config delete \"//localhost:$registry_port/:_authToken\"\n\n  # remove temp directory\n  rm -rf $temp_dir\n\n  # return to working directory\n  cd $cwd\n\n  if [ $exit_status -ne 0 ]; then\n    echo Error\n  else\n    echo Done\n  fi\n}\n\ntrap cleanup EXIT\n\n# create verdaccio config\n#   - store packages in temp directory so they are deleted on exit\n#   - allow anyone to publish to avoid npm login\necho \"\nstorage: $temp_dir/storage\npackages:\n  npm-check-updates:\n    access: \\$all\n    publish: \\$all\n  '**':\n    access: \\$all\n    proxy: npmjs\nuplinks:\n  npmjs:\n    url: https://registry.npmjs.org/\n\" >$verdaccio_config\n\n# start verdaccio and wait for it to boot\necho Starting local registry\nnohup verdaccio -l $registry_port -c $verdaccio_config &>$registry_log &\ngrep -q 'http address' <(tail -f $registry_log)\n\n# set dummy authToken which is required to publish\n# https://github.com/verdaccio/verdaccio/issues/212#issuecomment-308578500\nnpm config set \"//localhost:$registry_port/:_authToken=e2e_dummy\"\n\n# publish to local registry\necho Publishing to local registry\nnpm publish --registry $registry_local\n\n# Test: ncu -v\necho ncu -v\nnpx --registry $registry_local npm-check-updates -v\n\n# Test: cli\n# Create a package.json file with a dependency on npm-check-updates since it is already published to the local registry\necho Test: cli\necho '{\n  \"dependencies\": {\n    \"npm-check-updates\": \"1.0.0\"\n  }\n}' >$temp_dir/package.json\n\n# --configFilePath to avoid reading the repo .ncurc\n# --cwd to point to the temp package file\n# --pre 1 to ensure that an upgrade is always suggested even if npm-check-updates is on a prerelease version\nnpx --registry $registry_local npm-check-updates --configFilePath $temp_dir --cwd $temp_dir --pre 1 --registry $registry_local\n\nrm $temp_dir/package.json\ncp -r $e2e_dir/e2e $temp_dir\n\n# Test: cjs\necho Test: cjs\ncd $temp_dir/e2e/cjs\n\necho Installing\nnpm i npm-check-updates@latest --registry $registry_local\n\necho Running test\nREGISTRY=$registry_local node $temp_dir/e2e/cjs/index.js || { exit 1; }\n\n# Test: esm\necho Test: esm\ncd $temp_dir/e2e/esm\n\necho Installing\nnpm i npm-check-updates@latest --registry $registry_local\n\necho Running test\nREGISTRY=$registry_local node $temp_dir/e2e/esm/index.js || { exit 1; }\n"
  },
  {
    "path": "test/enginesNode.test.ts",
    "content": "import ncu from '../src/'\nimport { Index } from '../src/types/IndexType'\nimport { VersionSpec } from '../src/types/VersionSpec'\nimport chaiSetup from './helpers/chaiSetup'\n\nchaiSetup()\n\ndescribe('enginesNode', () => {\n  it(\"update packages that satisfy the project's engines.node\", async () => {\n    const upgraded = await ncu({\n      enginesNode: true,\n      packageData: {\n        dependencies: {\n          del: '3.0.0',\n        },\n        engines: {\n          node: '>=6',\n        },\n      },\n    })\n\n    upgraded!.should.eql({\n      del: '4.1.1',\n    })\n  })\n\n  it('do not update packages with incompatible engines.node', async () => {\n    const upgraded = await ncu({\n      enginesNode: true,\n      packageData: {\n        dependencies: {\n          del: '3.0.0',\n        },\n        engines: {\n          node: '>=1',\n        },\n      },\n    })\n\n    upgraded!.should.eql({})\n  })\n\n  it('update packages that do not have engines.node', async () => {\n    const upgraded = (await ncu({\n      enginesNode: true,\n      packageData: {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n        },\n        engines: {\n          node: '>=6',\n        },\n      },\n    })) as Index<VersionSpec>\n\n    upgraded!.should.eql({\n      'ncu-test-v2': '2.0.0',\n    })\n  })\n})\n"
  },
  {
    "path": "test/filter.test.ts",
    "content": "import fs from 'fs/promises'\nimport path from 'path'\nimport spawn from 'spawn-please'\nimport ncu from '../src'\nimport { Index } from '../src/types/IndexType'\nimport chaiSetup from './helpers/chaiSetup'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\nconst bin = path.join(__dirname, '../build/cli.js')\n\ndescribe('filter', () => {\n  describe('module', () => {\n    let stub: { restore: () => void }\n    before(() => (stub = stubVersions('99.9.9')))\n    after(() => stub.restore())\n\n    it('filter by package name with one arg', async () => {\n      const upgraded = (await ncu({\n        packageData: await fs.readFile(path.join(__dirname, 'test-data/ncu/package2.json'), 'utf-8'),\n        filter: ['lodash.map'],\n      })) as Index<string>\n      upgraded.should.have.property('lodash.map')\n      upgraded.should.not.have.property('lodash.filter')\n    })\n\n    it('filter by package name with multiple args', async () => {\n      const upgraded = (await ncu({\n        packageData: await fs.readFile(path.join(__dirname, 'test-data/ncu/package2.json'), 'utf-8'),\n        filter: ['lodash.map', 'lodash.filter'],\n      })) as Index<string>\n      upgraded.should.have.property('lodash.map')\n      upgraded.should.have.property('lodash.filter')\n    })\n\n    it('filter with wildcard', async () => {\n      const upgraded = (await ncu({\n        packageData: {\n          dependencies: {\n            lodash: '2.0.0',\n            'lodash.map': '2.0.0',\n            'lodash.filter': '2.0.0',\n          },\n        },\n        filter: ['lodash.*'],\n      })) as Index<string>\n      upgraded.should.have.property('lodash.map')\n      upgraded.should.have.property('lodash.filter')\n    })\n\n    it('filter with wildcard for scoped package', async () => {\n      const pkg = {\n        dependencies: {\n          vite: '1.0.0',\n          '@vitejs/plugin-react': '1.0.0',\n          '@vitejs/plugin-vue': '1.0.0',\n        },\n      }\n\n      {\n        const upgraded = await ncu({ packageData: pkg, filter: ['vite'] })\n        upgraded!.should.have.property('vite')\n        upgraded!.should.not.have.property('@vitejs/plugin-react')\n        upgraded!.should.not.have.property('@vitejs/plugin-vue')\n      }\n\n      {\n        const upgraded = await ncu({ packageData: pkg, filter: ['@vite*'] })\n        upgraded!.should.not.have.property('vite')\n        upgraded!.should.have.property('@vitejs/plugin-react')\n        upgraded!.should.have.property('@vitejs/plugin-vue')\n      }\n\n      {\n        const upgraded = await ncu({ packageData: pkg, filter: ['*vite*'] })\n        upgraded!.should.have.property('vite')\n        upgraded!.should.have.property('@vitejs/plugin-react')\n        upgraded!.should.have.property('@vitejs/plugin-vue')\n      }\n\n      {\n        const upgraded = await ncu({ packageData: pkg, filter: ['*vite*/*react*'] })\n        upgraded!.should.not.have.property('vite')\n        upgraded!.should.have.property('@vitejs/plugin-react')\n        upgraded!.should.not.have.property('@vitejs/plugin-vue')\n      }\n\n      {\n        const upgraded = await ncu({ packageData: pkg, filter: ['*vite*vue*'] })\n        upgraded!.should.not.have.property('vite')\n        upgraded!.should.not.have.property('@vitejs/plugin-react')\n        upgraded!.should.have.property('@vitejs/plugin-vue')\n      }\n    })\n\n    it('filter with negated wildcard', async () => {\n      const upgraded = (await ncu({\n        packageData: {\n          dependencies: {\n            lodash: '2.0.0',\n            'lodash.map': '2.0.0',\n            'lodash.filter': '2.0.0',\n          },\n        },\n        filter: ['!lodash.*'],\n      })) as Index<string>\n      upgraded.should.have.property('lodash')\n    })\n\n    it('filter with regex string', async () => {\n      const upgraded = (await ncu({\n        packageData: {\n          dependencies: {\n            lodash: '2.0.0',\n            'lodash.map': '2.0.0',\n            'lodash.filter': '2.0.0',\n          },\n        },\n        filter: '/lodash\\\\..*/',\n      })) as Index<string>\n      upgraded.should.have.property('lodash.map')\n      upgraded.should.have.property('lodash.filter')\n    })\n\n    it('filter with array of strings', async () => {\n      const upgraded = (await ncu({\n        packageData: {\n          dependencies: {\n            lodash: '2.0.0',\n            'lodash.map': '2.0.0',\n            'lodash.filter': '2.0.0',\n          },\n        },\n        filter: ['lodash.map', 'lodash.filter'],\n      })) as Index<string>\n      upgraded.should.have.property('lodash.map')\n      upgraded.should.have.property('lodash.filter')\n    })\n\n    it('filter with array of regex', async () => {\n      const upgraded = (await ncu({\n        packageData: {\n          dependencies: {\n            'fp-and-or': '0.1.0',\n            lodash: '2.0.0',\n            'lodash.map': '2.0.0',\n            'lodash.filter': '2.0.0',\n          },\n        },\n        filter: [/lodash\\..*/, /fp.*/],\n      })) as Index<string>\n      upgraded.should.have.property('lodash.map')\n      upgraded.should.have.property('lodash.filter')\n      upgraded.should.have.property('fp-and-or')\n    })\n\n    it('filter with array of mixed strings and regex', async () => {\n      const upgraded = (await ncu({\n        packageData: {\n          dependencies: {\n            'fp-and-or': '0.1.0',\n            lodash: '2.0.0',\n            'lodash.map': '2.0.0',\n            'lodash.filter': '2.0.0',\n          },\n        },\n        filter: ['fp-and-or', /lodash\\..*/],\n      })) as Index<string>\n      upgraded.should.deep.equal({\n        'fp-and-or': '99.9.9',\n        'lodash.map': '99.9.9',\n        'lodash.filter': '99.9.9',\n      })\n    })\n\n    it('filter with array of regex strings', async () => {\n      const upgraded = (await ncu({\n        packageData: {\n          dependencies: {\n            'fp-and-or': '0.1.0',\n            lodash: '2.0.0',\n            'lodash.map': '2.0.0',\n            'lodash.filter': '2.0.0',\n          },\n        },\n        filter: ['/lodash\\\\..*/', '/fp.*/'],\n      })) as Index<string>\n      upgraded.should.have.property('lodash.map')\n      upgraded.should.have.property('lodash.filter')\n      upgraded.should.have.property('fp-and-or')\n    })\n\n    it('trim and ignore empty array', async () => {\n      const upgraded = (await ncu({\n        packageData: await fs.readFile(path.join(__dirname, 'test-data/ncu/package2.json'), 'utf-8'),\n        filter: [],\n      })) as Index<string>\n      upgraded.should.have.property('lodash.map')\n      upgraded.should.have.property('lodash.filter')\n    })\n\n    it('empty string is invalid', async () => {\n      const promise = ncu({\n        packageData: await fs.readFile(path.join(__dirname, 'test-data/ncu/package2.json'), 'utf-8'),\n        filter: ',test',\n      })\n      promise.should.eventually.be.rejectedWith('Invalid filter: Expected pattern to be a non-empty string')\n    })\n  })\n\n  describe('cli', () => {\n    let stub: { restore: () => void }\n    before(() => (stub = stubVersions('99.9.9', { spawn: true })))\n    after(() => stub.restore())\n\n    it('filter by package name with --filter', async () => {\n      const { stdout } = await spawn('node', [bin, '--jsonUpgraded', '--stdin', '--filter', 'express'], {\n        stdin: '{ \"dependencies\": { \"express\": \"1\", \"chalk\": \"0.1.0\" } }',\n      })\n      const pkgData = JSON.parse(stdout)\n      pkgData.should.have.property('express')\n      pkgData.should.not.have.property('chalk')\n    })\n\n    it('filter by package name with -f', async () => {\n      const { stdout } = await spawn('node', [bin, '--jsonUpgraded', '--stdin', '-f', 'express'], {\n        stdin: '{ \"dependencies\": { \"express\": \"1\", \"chalk\": \"0.1.0\" } }',\n      })\n      const pkgData = JSON.parse(stdout)\n      pkgData.should.have.property('express')\n      pkgData.should.not.have.property('chalk')\n    })\n\n    it('do not allow non-matching --filter and arguments', async () => {\n      const pkgData = {\n        dependencies: {\n          'lodash.map': '2.0.0',\n          'lodash.filter': '2.0.0',\n        },\n      }\n\n      await spawn('node', [bin, '--jsonUpgraded', '--filter', 'lodash.map', 'lodash.filter'], {\n        stdin: JSON.stringify(pkgData),\n      }).should.eventually.be.rejected\n    })\n\n    it('allow matching --filter and arguments', async () => {\n      const pkgData = {\n        dependencies: {\n          'lodash.map': '2.0.0',\n          'lodash.filter': '2.0.0',\n        },\n      }\n\n      const { stdout } = await spawn(\n        'node',\n        [bin, '--jsonUpgraded', '--stdin', '--filter', 'lodash.map lodash.filter', 'lodash.map', 'lodash.filter'],\n        { stdin: JSON.stringify(pkgData) },\n      )\n      const upgraded = JSON.parse(stdout)\n      upgraded.should.have.property('lodash.map')\n      upgraded.should.have.property('lodash.filter')\n    })\n\n    it('trim and ignore empty args', async () => {\n      const pkgData = {\n        dependencies: {\n          'lodash.map': '2.0.0',\n          'lodash.filter': '2.0.0',\n        },\n      }\n\n      const { stdout } = await spawn(\n        'node',\n        [bin, '--jsonUpgraded', '--stdin', '--filter', 'lodash.map lodash.filter', ' '],\n        { stdin: JSON.stringify(pkgData) },\n      )\n      const upgraded = JSON.parse(stdout)\n      upgraded.should.have.property('lodash.map')\n      upgraded.should.have.property('lodash.filter')\n    })\n\n    it('allow multiple --filter options', async () => {\n      const pkgData = {\n        dependencies: {\n          'ncu-test-v2': '^1.0.0',\n          'ncu-test-tag': '^1.0.0',\n        },\n      }\n\n      const { stdout } = await spawn(\n        'node',\n        [bin, '--jsonUpgraded', '--stdin', '--filter', 'ncu-test-v2', '--filter', 'ncu-test-tag'],\n        { stdin: JSON.stringify(pkgData) },\n      )\n      const upgraded = JSON.parse(stdout)\n      upgraded.should.have.property('ncu-test-v2')\n      upgraded.should.have.property('ncu-test-tag')\n    })\n  })\n})\n\ndescribe('reject', () => {\n  describe('cli', () => {\n    let stub: { restore: () => void }\n    before(() => (stub = stubVersions('99.9.9', { spawn: true })))\n    after(() => stub.restore())\n\n    it('reject by package name with --reject', async () => {\n      const { stdout } = await spawn('node', [bin, '--jsonUpgraded', '--stdin', '--reject', 'chalk'], {\n        stdin: '{ \"dependencies\": { \"express\": \"1\", \"chalk\": \"0.1.0\" } }',\n      })\n      const pkgData = JSON.parse(stdout)\n      pkgData.should.have.property('express')\n      pkgData.should.not.have.property('chalk')\n    })\n\n    it('reject by package name with -x', async () => {\n      const { stdout } = await spawn('node', [bin, '--jsonUpgraded', '--stdin', '-x', 'chalk'], {\n        stdin: '{ \"dependencies\": { \"express\": \"1\", \"chalk\": \"0.1.0\" } }',\n      })\n      const pkgData = JSON.parse(stdout)\n      pkgData.should.have.property('express')\n      pkgData.should.not.have.property('chalk')\n    })\n\n    it('reject with empty string should not reject anything', async () => {\n      const { stdout } = await spawn('node', [bin, '--jsonUpgraded', '--reject', '\"\"', '--stdin', '-x', 'chalk'], {\n        stdin: '{ \"dependencies\": { \"ncu-test-v2\": \"1.0.0\", \"ncu-test-tag\": \"1.0.0\" } }',\n      })\n      const pkgData = JSON.parse(stdout)\n      pkgData.should.have.property('ncu-test-v2')\n      pkgData.should.have.property('ncu-test-tag')\n    })\n\n    it('allow multiple --reject options', async () => {\n      const pkgData = {\n        dependencies: {\n          'ncu-test-v2': '^1.0.0',\n          'ncu-test-tag': '^1.0.0',\n        },\n      }\n\n      const { stdout } = await spawn(\n        'node',\n        [bin, '--jsonUpgraded', '--stdin', '--reject', 'ncu-test-v2', '--reject', 'ncu-test-tag'],\n        { stdin: JSON.stringify(pkgData) },\n      )\n      const upgraded = JSON.parse(stdout)\n      upgraded.should.not.have.property('ncu-test-v2')\n      upgraded.should.not.have.property('ncu-test-tag')\n    })\n  })\n})\n"
  },
  {
    "path": "test/filterResults.test.ts",
    "content": "import { expect } from 'chai'\nimport fs from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport ncu from '../src/'\nimport chaiSetup from './helpers/chaiSetup'\nimport removeDir from './helpers/removeDir'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\ndescribe('filterResults', () => {\n  it('should return only major versions updated', async () => {\n    const dependencies = { 'ncu-test-v2': '2.0.0', 'ncu-test-return-version': '1.0.0', 'ncu-test-tag': '1.0.0' }\n    const stub = stubVersions(\n      {\n        'ncu-test-v2': '3.0.0',\n        'ncu-test-tag': '2.1.0',\n        'ncu-test-return-version': '1.2.0',\n      },\n      { spawn: true },\n    )\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(\n      pkgFile,\n      JSON.stringify({\n        dependencies,\n      }),\n      'utf-8',\n    )\n\n    try {\n      const upgraded = await ncu({\n        packageFile: pkgFile,\n        filterResults: (\n          packageName,\n          // eslint-disable-next-line @typescript-eslint/no-unused-vars\n          { currentVersion, currentVersionSemver, upgradedVersion, upgradedVersionSemver },\n        ) => {\n          const currentMajorVersion = currentVersionSemver?.[0]?.major\n          const upgradedMajorVersion = upgradedVersionSemver?.major\n          if (currentMajorVersion && upgradedMajorVersion) {\n            return currentMajorVersion < upgradedMajorVersion\n          }\n          return true\n        },\n      })\n      expect(upgraded).to.have.property('ncu-test-tag', '2.1.0')\n      expect(upgraded).to.have.property('ncu-test-v2', '3.0.0')\n      expect(upgraded).to.not.have.property('ncu-test-return-version')\n    } finally {\n      await removeDir(tempDir)\n      stub.restore()\n    }\n  })\n})\n"
  },
  {
    "path": "test/filterVersion.test.ts",
    "content": "import path from 'path'\nimport spawn from 'spawn-please'\nimport ncu from '../src'\nimport chaiSetup from './helpers/chaiSetup'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\nconst bin = path.join(__dirname, '../build/cli.js')\n\ndescribe('filterVersion', () => {\n  describe('module', () => {\n    let stub: { restore: () => void }\n    before(() => {\n      stub = stubVersions({\n        'ncu-test-v2': '2.0.0',\n        'ncu-test-return-version': '2.0.0',\n      })\n    })\n    after(() => {\n      stub.restore()\n    })\n\n    it('filter by package version with string', async () => {\n      const pkg = {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n          'ncu-test-return-version': '1.0.1',\n        },\n      }\n\n      const upgraded = await ncu({\n        packageData: pkg,\n        filterVersion: '1.0.0',\n      })\n\n      upgraded!.should.have.property('ncu-test-v2')\n      upgraded!.should.not.have.property('ncu-test-return-version')\n\n      stub.restore()\n    })\n\n    it('filter by package version with space-delimited list of strings', async () => {\n      const pkg = {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n          'ncu-test-return-version': '1.0.1',\n          'fp-and-or': '0.1.0',\n        },\n      }\n\n      const upgraded = await ncu({\n        packageData: pkg,\n        filterVersion: '1.0.0 0.1.0',\n      })\n\n      upgraded!.should.have.property('ncu-test-v2')\n      upgraded!.should.not.have.property('ncu-test-return-version')\n      upgraded!.should.have.property('fp-and-or')\n    })\n\n    it('filter by package version with comma-delimited list of strings', async () => {\n      const pkg = {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n          'ncu-test-return-version': '1.0.1',\n          'fp-and-or': '0.1.0',\n        },\n      }\n\n      const upgraded = await ncu({\n        packageData: pkg,\n        filterVersion: '1.0.0,0.1.0',\n      })\n\n      upgraded!.should.have.property('ncu-test-v2')\n      upgraded!.should.not.have.property('ncu-test-return-version')\n      upgraded!.should.have.property('fp-and-or')\n    })\n\n    it('filter by package version with RegExp', async () => {\n      const pkg = {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n          'ncu-test-return-version': '1.0.1',\n          'fp-and-or': '0.1.0',\n        },\n      }\n\n      const upgraded = await ncu({\n        packageData: pkg,\n        filterVersion: /^1/,\n      })\n\n      upgraded!.should.have.property('ncu-test-v2')\n      upgraded!.should.have.property('ncu-test-return-version')\n      upgraded!.should.not.have.property('fp-and-or')\n    })\n\n    it('filter by package version with RegExp string', async () => {\n      const pkg = {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n          'ncu-test-return-version': '1.0.1',\n          'fp-and-or': '0.1.0',\n        },\n      }\n\n      const upgraded = await ncu({\n        packageData: pkg,\n        filterVersion: '/^1/',\n      })\n\n      upgraded!.should.have.property('ncu-test-v2')\n      upgraded!.should.have.property('ncu-test-return-version')\n      upgraded!.should.not.have.property('fp-and-or')\n    })\n  })\n\n  describe('cli', () => {\n    it('allow multiple --filterVersion options', async () => {\n      const stub = stubVersions('99.9.9', { spawn: true })\n      const pkgData = {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n          'ncu-test-10': '1.0.9',\n        },\n      }\n\n      const { stdout } = await spawn(\n        'node',\n        [bin, '--jsonUpgraded', '--verbose', '--stdin', '--filterVersion', '1.0.0', '--filterVersion', '1.0.9'],\n        { stdin: JSON.stringify(pkgData) },\n      )\n      const upgraded = JSON.parse(stdout)\n      upgraded.should.have.property('ncu-test-v2')\n      upgraded.should.have.property('ncu-test-10')\n      stub.restore()\n    })\n  })\n})\n\ndescribe('rejectVersion', () => {\n  describe('cli', () => {\n    it('allow multiple --rejectVersion options', async () => {\n      const stub = stubVersions('99.9.9', { spawn: true })\n      const pkgData = {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n          'ncu-test-10': '1.0.9',\n        },\n      }\n\n      const { stdout } = await spawn(\n        'node',\n        [bin, '--jsonUpgraded', '--verbose', '--stdin', '--rejectVersion', '1.0.0', '--rejectVersion', '1.0.9'],\n        { stdin: JSON.stringify(pkgData) },\n      )\n      const upgraded = JSON.parse(stdout)\n      upgraded.should.not.have.property('ncu-test-v2')\n      upgraded.should.not.have.property('ncu-test-10')\n      stub.restore()\n    })\n  })\n})\n"
  },
  {
    "path": "test/format.test.ts",
    "content": "import { expect } from 'chai'\nimport fs from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport spawn from 'spawn-please'\nimport chaiSetup from './helpers/chaiSetup'\nimport removeDir from './helpers/removeDir'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\nconst bin = path.join(__dirname, '../build/cli.js')\n\ndescribe('format', () => {\n  it('--format dep', async () => {\n    const stub = stubVersions(\n      {\n        'ncu-test-v2': '2.0.0',\n        'ncu-test-tag': '2.0.0',\n        'ncu-test-peer-update': '2.0.0',\n        'ncu-test-return-version': '2.0.0',\n      },\n      { spawn: true },\n    )\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(\n      pkgFile,\n      JSON.stringify({\n        dependencies: {\n          'ncu-test-v2': '^1.0.0',\n        },\n        devDependencies: {\n          'ncu-test-tag': '^1.0.0',\n        },\n        peerDependencies: {\n          'ncu-test-peer-update': '^1.0.0',\n        },\n        optionalDependencies: {\n          'ncu-test-return-version': '^1.0.0',\n        },\n      }),\n      'utf-8',\n    )\n    try {\n      const { stdout } = await spawn(\n        'node',\n        // -u was added to avoid accidentally matching dev, peer, optional from \"Run ncu --dep prod,dev,peer,optional --format dep -u to upgrade package.json\"\n        [bin, '--dep', 'prod,dev,peer,optional', '--format', 'dep', '-u'],\n        {},\n        { cwd: tempDir },\n      )\n\n      stdout.should.include('dev')\n      stdout.should.include('peer')\n      stdout.should.include('optional')\n    } finally {\n      await removeDir(tempDir)\n      stub.restore()\n    }\n  })\n\n  describe('diff', () => {\n    it('basic', async () => {\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(\n        pkgFile,\n        JSON.stringify({\n          dependencies: {\n            'ncu-test-v2': '^1.0.0',\n          },\n        }),\n        'utf-8',\n      )\n      try {\n        const { stdout } = await spawn('node', [bin, '--format', 'diff'], {}, { cwd: tempDir })\n        stdout.should.include('https://npmdiff.dev/ncu-test-v2/1.0.0/2.0.0')\n      } finally {\n        await removeDir(tempDir)\n      }\n    })\n\n    // https://github.com/raineorshine/npm-check-updates/pull/1603/changes/BASE..4ab36b01b5f90e8d2563361a3b18ed2b3f9d2280#r2865386584\n    it('encodeURIComponent', async () => {\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(\n        pkgFile,\n        JSON.stringify({\n          dependencies: {\n            '@types/jsonlines': '0.1.0',\n          },\n        }),\n        'utf-8',\n      )\n      try {\n        const { stdout } = await spawn('node', [bin, '--format', 'diff'], {}, { cwd: tempDir })\n        // purposefully omit 'to' version since this is a live package\n        stdout.should.include('https://npmdiff.dev/%40types%2Fjsonlines/0.1.0/')\n      } finally {\n        await removeDir(tempDir)\n      }\n    })\n  })\n\n  // do not stubVersions here, because we need to test if if time is parsed correctly from npm-registry-fetch\n  it('--format time', async () => {\n    const timestamp = '2020-04-27T21:48:11.660Z'\n    const packageData = {\n      dependencies: {\n        'ncu-test-v2': '^1.0.0',\n      },\n    }\n    const { stdout } = await spawn('node', [bin, '--format', 'time', '--stdin'], { stdin: JSON.stringify(packageData) })\n    expect(stdout).contains(timestamp)\n  })\n\n  it('--format repo', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(\n      pkgFile,\n      JSON.stringify({\n        dependencies: {\n          'modern-diacritics': '^1.0.0',\n        },\n      }),\n      'utf-8',\n    )\n    try {\n      await spawn('npm', ['install'], {}, { cwd: tempDir })\n      const { stdout } = await spawn('node', [bin, '--format', 'repo'], {}, { cwd: tempDir })\n      stdout.should.include('https://github.com/Mitsunee/modern-diacritics')\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('--format homepage', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(\n      pkgFile,\n      JSON.stringify({\n        dependencies: {\n          'hosted-git-info': '^5.0.0',\n        },\n      }),\n      'utf-8',\n    )\n    try {\n      await spawn('npm', ['install'], {}, { cwd: tempDir })\n      const { stdout } = await spawn('node', [bin, '--format', 'homepage'], {}, { cwd: tempDir })\n      stdout.should.include('https://github.com/npm/hosted-git-info')\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('--format lines', async () => {\n    const stub = stubVersions(\n      {\n        'ncu-test-v2': '2.0.0',\n        'ncu-test-tag': '1.1.0',\n      },\n      { spawn: true },\n    )\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(\n      pkgFile,\n      JSON.stringify({\n        dependencies: {\n          'ncu-test-v2': '^1.0.0',\n          'ncu-test-tag': '^1.0.0',\n        },\n      }),\n      'utf-8',\n    )\n    try {\n      const { stdout } = await spawn('node', [bin, '--format', 'lines'], {}, { cwd: tempDir })\n      stdout.should.equals('ncu-test-v2@^2.0.0\\nncu-test-tag@^1.1.0\\n')\n    } finally {\n      await removeDir(tempDir)\n      stub.restore()\n    }\n  })\n\n  it('disallow --format lines with --jsonUpgraded', async () => {\n    const stub = stubVersions(\n      {\n        'ncu-test-v2': '2.0.0',\n        'ncu-test-tag': '1.1.0',\n      },\n      { spawn: true },\n    )\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(\n      pkgFile,\n      JSON.stringify({\n        dependencies: {\n          'ncu-test-v2': '^1.0.0',\n          'ncu-test-tag': '^1.0.0',\n        },\n      }),\n      'utf-8',\n    )\n    try {\n      await spawn(\n        'node',\n        [bin, '--format', 'lines', '--jsonUpgraded'],\n        {},\n        {\n          cwd: tempDir,\n        },\n      ).should.eventually.be.rejectedWith('Cannot specify both --format lines and --jsonUpgraded.')\n    } finally {\n      await removeDir(tempDir)\n      stub.restore()\n    }\n  })\n\n  it('disallow --format lines with --jsonAll', async () => {\n    const stub = stubVersions(\n      {\n        'ncu-test-v2': '2.0.0',\n        'ncu-test-tag': '1.1.0',\n      },\n      { spawn: true },\n    )\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(\n      pkgFile,\n      JSON.stringify({\n        dependencies: {\n          'ncu-test-v2': '^1.0.0',\n          'ncu-test-tag': '^1.0.0',\n        },\n      }),\n      'utf-8',\n    )\n    try {\n      await spawn(\n        'node',\n        [bin, '--format', 'lines', '--jsonAll'],\n        {},\n        {\n          cwd: tempDir,\n        },\n      ).should.eventually.be.rejectedWith('Cannot specify both --format lines and --jsonAll.')\n    } finally {\n      await removeDir(tempDir)\n      stub.restore()\n    }\n  })\n\n  it('disallow --format lines with other format options', async () => {\n    const stub = stubVersions(\n      {\n        'ncu-test-v2': '2.0.0',\n        'ncu-test-tag': '1.1.0',\n      },\n      { spawn: true },\n    )\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(\n      pkgFile,\n      JSON.stringify({\n        dependencies: {\n          'ncu-test-v2': '^1.0.0',\n          'ncu-test-tag': '^1.0.0',\n        },\n      }),\n      'utf-8',\n    )\n    try {\n      await spawn(\n        'node',\n        [bin, '--format', 'lines,group'],\n        {},\n        {\n          cwd: tempDir,\n        },\n      ).should.eventually.be.rejectedWith('Cannot use --format lines with other formatting options.')\n    } finally {\n      await removeDir(tempDir)\n      stub.restore()\n    }\n  })\n})\n"
  },
  {
    "path": "test/getAllPackages.test.ts",
    "content": "import path from 'path'\nimport getAllPackages from '../src/lib/getAllPackages'\nimport { Options } from '../src/types/Options'\nimport { PackageInfo } from '../src/types/PackageInfo'\nimport chaiSetup from './helpers/chaiSetup'\n\nchaiSetup()\n\n/** forces path to a posix version (windows-style) */\nfunction asPosixPath(filepath: string): string {\n  return filepath.split(path.sep).join(path.posix.sep)\n}\n\n/** given a dirPath removes it from a tuple of strings  */\nasync function stripDir(dirPath: string, paths: [string[], string[]]): Promise<[string[], string[]]> {\n  const [pkgs, workspacePackages]: [string[], string[]] = paths\n  return [\n    pkgs.map((path: string): string => asPosixPath(path).replace(dirPath, '')),\n    workspacePackages.map((path: string): string => asPosixPath(path).replace(dirPath, '')),\n  ]\n}\n\n/** convenience function to call getAllPackages for a given test-path  */\nasync function getAllPackagesForTest(testPath: string, options: Options): Promise<[string[], string[]]> {\n  const testCwd = path.join(__dirname, testPath).replace(/\\\\/g, '/')\n  const optionsWithTestCwd: Options = { cwd: testCwd, ...options }\n  const [pkgInfos, workspacePackageNames]: [PackageInfo[], string[]] = await getAllPackages(optionsWithTestCwd)\n  const packagePaths: string[] = pkgInfos.map((packageInfo: PackageInfo) => packageInfo.filepath)\n  const [pkgs, workspacePackages]: [string[], string[]] = await stripDir(testCwd, [packagePaths, workspacePackageNames])\n  return [pkgs, workspacePackages]\n}\n\ndescribe('getAllPackages', () => {\n  it('returns default package without cwd', async () => {\n    const [pkgInfos, workspacePackageNames]: [PackageInfo[], string[]] = await getAllPackages({})\n    const packagePaths: string[] = pkgInfos.map((packageInfo: PackageInfo) => packageInfo.filepath)\n    packagePaths.should.deep.equal(['package.json'])\n    // allPackageInfos[0].name.should.deep.equal(undefined)\n    workspacePackageNames.should.deep.equal([])\n  })\n\n  describe('basic npm package', () => {\n    it('handles tradition flat npm project ', async () => {\n      const [pkgs, workspacePackages]: [string[], string[]] = await getAllPackagesForTest('test-data/basic/', {})\n      pkgs.should.deep.equal(['package.json'])\n      workspacePackages.should.deep.equal([])\n    })\n\n    it('errors in non-workspace project with --workspaces option', async () => {\n      await getAllPackagesForTest('test-data/basic/', {\n        workspaces: true,\n      }).should.be.rejectedWith('workspaces property missing from package.json. --workspaces')\n    })\n\n    it('errors in non-workspace project with --workspace=<name> option', async () => {\n      await getAllPackagesForTest('test-data/basic/', {\n        workspace: ['basic-sub-package'],\n      }).should.be.rejectedWith('workspaces property missing from package.json. --workspace')\n    })\n  })\n\n  describe('basic workspace project', () => {\n    it('handles simple workspace without --workspaces option', async () => {\n      const [pkgs, workspacePackages]: [string[], string[]] = await getAllPackagesForTest(\n        'test-data/workspace-basic/',\n        {},\n      )\n      pkgs.should.deep.equal(['package.json'])\n      workspacePackages.should.deep.equal([])\n    })\n\n    it('handles simple workspace with --workspaces option', async () => {\n      const [pkgs, workspacePackages]: [string[], string[]] = await getAllPackagesForTest(\n        'test-data/workspace-basic/',\n        { workspaces: true },\n      )\n\n      // without --root should just return the sub-package\n      pkgs.should.deep.equal(['pkg/sub/package.json'])\n      workspacePackages.should.deep.equal(['basic-sub-package'])\n    })\n\n    it('handles simple workspace with --workspaces and --root option', async () => {\n      const [pkgs, workspacePackages]: [string[], string[]] = await getAllPackagesForTest(\n        'test-data/workspace-basic/',\n        { root: true, workspaces: true },\n      )\n\n      // with --root should return root package and the sub-package\n      pkgs.should.deep.equal(['package.json', 'pkg/sub/package.json'])\n      workspacePackages.should.deep.equal(['basic-sub-package'])\n    })\n\n    it('handles simple workspace with --workspaces=false', async () => {\n      const [pkgs, workspacePackages]: [string[], string[]] = await getAllPackagesForTest(\n        'test-data/workspace-basic/',\n        { workspaces: false },\n      )\n\n      // with workspaces=false should return just the root package, no sub-packages,\n      // when inside a workspace project\n      pkgs.should.deep.equal(['package.json'])\n      workspacePackages.should.deep.equal([])\n    })\n\n    describe('--workspace=\"<string>\"', () => {\n      it('handles simple workspace with --workspace=\"basic-sub-package\"', async () => {\n        const [pkgs, workspacePackages]: [string[], string[]] = await getAllPackagesForTest(\n          'test-data/workspace-basic/',\n          { workspace: ['basic-sub-package'] },\n        )\n\n        // should only return the sub-package\n        pkgs.should.deep.equal(['pkg/sub/package.json'])\n        workspacePackages.should.deep.equal(['basic-sub-package'])\n      })\n\n      it('handles simple workspace with --workspaces and --workspace=\"basic-sub-package\"', async () => {\n        const [pkgs, workspacePackages]: [string[], string[]] = await getAllPackagesForTest(\n          'test-data/workspace-basic/',\n          { workspaces: true, workspace: ['basic-sub-package'] },\n        )\n\n        pkgs.should.deep.equal(['pkg/sub/package.json'])\n        workspacePackages.should.deep.equal(['basic-sub-package'])\n      })\n\n      it('handles simple workspace with --workspaces, --workspace=\"basic-sub-package\", and --root option', async () => {\n        const [pkgs, workspacePackages]: [string[], string[]] = await getAllPackagesForTest(\n          'test-data/workspace-basic/',\n          { root: true, workspaces: true, workspace: ['basic-sub-package'] },\n        )\n\n        // with --root should return root package and the sub-package\n        pkgs.should.deep.equal(['package.json', 'pkg/sub/package.json'])\n        workspacePackages.should.deep.equal(['basic-sub-package'])\n      })\n\n      it('handles simple workspace with --workspaces and --workspace=<empty>', async () => {\n        const [pkgs, workspacePackages]: [string[], string[]] = await getAllPackagesForTest(\n          'test-data/workspace-basic/',\n          { workspaces: true, workspace: [] },\n        )\n\n        pkgs.should.deep.equal(['pkg/sub/package.json'])\n        workspacePackages.should.deep.equal(['basic-sub-package'])\n      })\n\n      it('handles simple workspace with --workspaces=false and --workspace=\"basic-sub-package\"', async () => {\n        const [pkgs, workspacePackages]: [string[], string[]] = await getAllPackagesForTest(\n          'test-data/workspace-basic/',\n          { workspaces: false, workspace: ['basic-sub-package'] },\n        )\n\n        pkgs.should.deep.equal(['pkg/sub/package.json'])\n        workspacePackages.should.deep.equal(['basic-sub-package'])\n      })\n    })\n  })\n\n  describe('empty workspace project', () => {\n    describe('package.workspaces is empty array', () => {\n      it('should return empty data for empty workspaces', async () => {\n        const [pkgs, workspacePackages]: [string[], string[]] = await getAllPackagesForTest(\n          'test-data/workspace-workspace-param-is-array/',\n          { workspaces: true },\n        )\n\n        pkgs.should.deep.equal([])\n        workspacePackages.should.deep.equal([])\n      })\n    })\n\n    describe('package.workspaces is object and package.workspaces.packages is empty array', () => {\n      it('should return empty data for empty workspaces', async () => {\n        const [pkgs, workspacePackages]: [string[], string[]] = await getAllPackagesForTest(\n          'test-data/workspace-no-sub-packages/',\n          { workspaces: true },\n        )\n\n        pkgs.should.deep.equal([])\n        workspacePackages.should.deep.equal([])\n      })\n    })\n  })\n\n  describe('sub-package-names', () => {\n    // TODO\n    it.skip('--workspaces should return all packages not just ones that dir-names-match', async () => {\n      const [pkgs, workspacePackages]: [string[], string[]] = await getAllPackagesForTest(\n        'test-data/workspace-sub-package-names/',\n        { workspaces: true },\n      )\n\n      pkgs.should.deep.equal(['pkg/dirname-matches-name/package.json', 'pkg/dirname-will-become-name/package.json'])\n      workspacePackages.should.deep.equal([\n        'dirname-matches-name',\n        'dirname-will-become-name', // should use the directory name\n        'dirname-does-not-match-name', // TODO: this should be returned too\n      ])\n    })\n\n    // TODO\n    it.skip('--workspace should return all named packages not just ones that dir-names-match', async () => {\n      const [pkgs, workspacePackages]: [string[], string[]] = await getAllPackagesForTest(\n        'test-data/workspace-sub-package-names/',\n        {\n          workspaces: false,\n          workspace: [\n            'dirname-matches-name',\n            'dirname-will-become-name',\n            // 'dirname-does-not-match-name',  TODO: this should be returned too\n          ],\n        },\n      )\n\n      pkgs.should.deep.equal(['pkg/dirname-matches-name/package.json', 'pkg/dirname-will-become-name/package.json'])\n      workspacePackages.should.deep.equal([\n        'dirname-matches-name',\n        'dirname-will-become-name',\n        'dirname-does-not-match-name', // TODO: this should be returned too\n      ])\n    })\n  })\n})\n"
  },
  {
    "path": "test/getCurrentDependencies.test.ts",
    "content": "import { SemVer } from 'semver-utils'\nimport getCurrentDependencies from '../src/lib/getCurrentDependencies'\nimport { PackageFile } from '../src/types/PackageFile'\nimport chaiSetup from './helpers/chaiSetup'\n\nchaiSetup()\n\ndescribe('getCurrentDependencies', () => {\n  let deps: PackageFile\n  beforeEach(() => {\n    deps = {\n      dependencies: {\n        bluebird: '^1.0.0',\n        mocha: '1.2',\n      },\n      devDependencies: {\n        lodash: '^3.9.3',\n        moment: '^1.0.0',\n      },\n      peerDependencies: {\n        'ncu-test-v2': '0.1.0',\n      },\n      optionalDependencies: {\n        chalk: '^1.1.0',\n      },\n    }\n  })\n\n  it('return an empty object for an empty package.json and handle default options', () => {\n    getCurrentDependencies().should.eql({})\n    getCurrentDependencies({}).should.eql({})\n    getCurrentDependencies({}, {}).should.eql({})\n  })\n\n  it('get dependencies, devDependencies, and optionalDependencies by default', () => {\n    getCurrentDependencies(deps).should.eql({\n      bluebird: '^1.0.0',\n      mocha: '1.2',\n      lodash: '^3.9.3',\n      chalk: '^1.1.0',\n      moment: '^1.0.0',\n    })\n  })\n\n  describe('dep', () => {\n    it('only get dependencies with --dep prod', () => {\n      getCurrentDependencies(deps, { dep: 'prod' }).should.eql({\n        bluebird: '^1.0.0',\n        mocha: '1.2',\n      })\n    })\n\n    it('only get devDependencies with --dep dev', () => {\n      getCurrentDependencies(deps, { dep: 'dev' }).should.eql({\n        lodash: '^3.9.3',\n        moment: '^1.0.0',\n      })\n    })\n\n    it('only get optionalDependencies with --dep optional', () => {\n      getCurrentDependencies(deps, { dep: 'optional' }).should.eql({\n        chalk: '^1.1.0',\n      })\n    })\n\n    it('only get peerDependencies with --dep peer', () => {\n      getCurrentDependencies(deps, { dep: 'peer' }).should.eql({\n        'ncu-test-v2': '0.1.0',\n      })\n    })\n\n    it('only get devDependencies and peerDependencies with --dep dev,peer', () => {\n      getCurrentDependencies(deps, { dep: 'dev,peer' }).should.eql({\n        lodash: '^3.9.3',\n        moment: '^1.0.0',\n        'ncu-test-v2': '0.1.0',\n      })\n    })\n  })\n\n  describe('filter', () => {\n    it('filter dependencies by package name', () => {\n      getCurrentDependencies(deps, { filter: 'mocha' }).should.eql({\n        mocha: '1.2',\n      })\n    })\n\n    it('filter dependencies by @org/package name', () => {\n      const deps = {\n        dependencies: {\n          '@ngrx/store': '4.0.0',\n          mocha: '1.0.0',\n        },\n      }\n\n      getCurrentDependencies(deps, { filter: '@ngrx/store' }).should.eql({\n        '@ngrx/store': '4.0.0',\n      })\n    })\n\n    it('do not filter out dependencies with a partial package name', () => {\n      getCurrentDependencies(deps, { filter: 'o' }).should.eql({})\n    })\n\n    it('filter dependencies by multiple packages', () => {\n      getCurrentDependencies(deps, { filter: 'mocha lodash' }).should.eql({\n        mocha: '1.2',\n        lodash: '^3.9.3',\n      })\n      getCurrentDependencies(deps, { filter: 'mocha,lodash' }).should.eql({\n        mocha: '1.2',\n        lodash: '^3.9.3',\n      })\n      getCurrentDependencies(deps, { filter: ['mocha', 'lodash'] }).should.eql({\n        mocha: '1.2',\n        lodash: '^3.9.3',\n      })\n    })\n\n    it('filter dependencies by regex', () => {\n      getCurrentDependencies(deps, { filter: /o/ }).should.eql({\n        lodash: '^3.9.3',\n        mocha: '1.2',\n        moment: '^1.0.0',\n      })\n      getCurrentDependencies(deps, { filter: '/o/' }).should.eql({\n        lodash: '^3.9.3',\n        mocha: '1.2',\n        moment: '^1.0.0',\n      })\n    })\n\n    it.skip('should filter org dependencies by regex', () => {\n      getCurrentDependencies(deps, { filter: /store/ }).should.eql({\n        '@ngrx/store': '4.0.0',\n      })\n    })\n\n    it('filter dependencies by name with a filter function', () => {\n      getCurrentDependencies(deps, { filter: (s: string) => s.startsWith('m') }).should.eql({\n        mocha: '1.2',\n        moment: '^1.0.0',\n      })\n    })\n\n    it('filter dependencies by version spec with a filter function', () => {\n      getCurrentDependencies(deps, {\n        filter: (name: string, versionSpec: SemVer[]) => versionSpec[0].major === '1',\n      }).should.eql({\n        mocha: '1.2',\n        moment: '^1.0.0',\n        chalk: '^1.1.0',\n        bluebird: '^1.0.0',\n      })\n    })\n  })\n\n  describe('filterVersion', () => {\n    it('filter dependency versions by pinned version', () => {\n      getCurrentDependencies(deps, { filterVersion: '1.2' }).should.eql({\n        mocha: '1.2',\n      })\n    })\n\n    it('filter dependency versions by caret version', () => {\n      getCurrentDependencies(deps, { filterVersion: '^1.0.0' }).should.eql({\n        moment: '^1.0.0',\n        bluebird: '^1.0.0',\n      })\n    })\n\n    it('filter dependencies by multiple versions (comma-or-space-delimited)', () => {\n      getCurrentDependencies(deps, { filterVersion: '^1.0.0,^1.1.0' }).should.eql({\n        chalk: '^1.1.0',\n        moment: '^1.0.0',\n        bluebird: '^1.0.0',\n      })\n      getCurrentDependencies(deps, { filterVersion: '^1.0.0 ^1.1.0' }).should.eql({\n        chalk: '^1.1.0',\n        moment: '^1.0.0',\n        bluebird: '^1.0.0',\n      })\n    })\n\n    it('filter dependency versions by regex', () => {\n      getCurrentDependencies(deps, { filterVersion: '/^\\\\^1/' }).should.eql({\n        chalk: '^1.1.0',\n        moment: '^1.0.0',\n        bluebird: '^1.0.0',\n      })\n      getCurrentDependencies(deps, { filterVersion: /^\\^1/ }).should.eql({\n        chalk: '^1.1.0',\n        moment: '^1.0.0',\n        bluebird: '^1.0.0',\n      })\n    })\n\n    it('filter dependencies by version spec with a filterVersion function', () => {\n      getCurrentDependencies(deps, {\n        filterVersion: (name: string, versionSpec: SemVer[]) => versionSpec[0].major === '1',\n      }).should.eql({\n        mocha: '1.2',\n        moment: '^1.0.0',\n        chalk: '^1.1.0',\n        bluebird: '^1.0.0',\n      })\n    })\n  })\n\n  describe('reject', () => {\n    it('reject dependencies by package name', () => {\n      getCurrentDependencies(deps, { reject: 'chalk' }).should.eql({\n        mocha: '1.2',\n        lodash: '^3.9.3',\n        bluebird: '^1.0.0',\n        moment: '^1.0.0',\n      })\n    })\n\n    it('do not reject dependencies with a partial package name', () => {\n      getCurrentDependencies(deps, { reject: 'o' }).should.eql({\n        mocha: '1.2',\n        lodash: '^3.9.3',\n        chalk: '^1.1.0',\n        bluebird: '^1.0.0',\n        moment: '^1.0.0',\n      })\n    })\n\n    it('reject dependencies by multiple packages', () => {\n      getCurrentDependencies(deps, { reject: 'mocha lodash' }).should.eql({\n        chalk: '^1.1.0',\n        bluebird: '^1.0.0',\n        moment: '^1.0.0',\n      })\n      getCurrentDependencies(deps, { reject: 'mocha,lodash' }).should.eql({\n        chalk: '^1.1.0',\n        bluebird: '^1.0.0',\n        moment: '^1.0.0',\n      })\n      getCurrentDependencies(deps, { reject: ['mocha', 'lodash'] }).should.eql({\n        chalk: '^1.1.0',\n        bluebird: '^1.0.0',\n        moment: '^1.0.0',\n      })\n    })\n\n    it('reject dependencies by regex', () => {\n      getCurrentDependencies(deps, { reject: /o/ }).should.eql({\n        chalk: '^1.1.0',\n        bluebird: '^1.0.0',\n      })\n      getCurrentDependencies(deps, { reject: '/o/' }).should.eql({\n        chalk: '^1.1.0',\n        bluebird: '^1.0.0',\n      })\n    })\n\n    it('reject dependencies by function', () => {\n      getCurrentDependencies(deps, { reject: (s: string) => s.startsWith('m') }).should.eql({\n        lodash: '^3.9.3',\n        chalk: '^1.1.0',\n        bluebird: '^1.0.0',\n      })\n    })\n\n    it('filter and reject', () => {\n      getCurrentDependencies(deps, { filter: 'mocha chalk', reject: 'chalk' }).should.eql({\n        mocha: '1.2',\n      })\n    })\n  })\n\n  describe('rejectVersion', () => {\n    it('reject dependency versions by pinned version', () => {\n      getCurrentDependencies(deps, { rejectVersion: '1.2' }).should.eql({\n        lodash: '^3.9.3',\n        moment: '^1.0.0',\n        chalk: '^1.1.0',\n        bluebird: '^1.0.0',\n      })\n    })\n\n    it('reject dependency versions by caret version', () => {\n      getCurrentDependencies(deps, { rejectVersion: '^1.0.0' }).should.eql({\n        mocha: '1.2',\n        lodash: '^3.9.3',\n        chalk: '^1.1.0',\n      })\n    })\n\n    it('reject dependencies by multiple versions (comma-or-space-delimited)', () => {\n      getCurrentDependencies(deps, { rejectVersion: '^1.0.0,^1.1.0' }).should.eql({\n        mocha: '1.2',\n        lodash: '^3.9.3',\n      })\n      getCurrentDependencies(deps, { rejectVersion: '^1.0.0 ^1.1.0' }).should.eql({\n        mocha: '1.2',\n        lodash: '^3.9.3',\n      })\n    })\n\n    it('reject dependency versions by regex', () => {\n      getCurrentDependencies(deps, { rejectVersion: '/^\\\\^1/' }).should.eql({\n        mocha: '1.2',\n        lodash: '^3.9.3',\n      })\n      getCurrentDependencies(deps, { rejectVersion: /^\\^1/ }).should.eql({\n        mocha: '1.2',\n        lodash: '^3.9.3',\n      })\n    })\n\n    it('reject dependency versions by function', () => {\n      getCurrentDependencies(deps, { rejectVersion: (s: string) => s.startsWith('^3') }).should.eql({\n        mocha: '1.2',\n        moment: '^1.0.0',\n        chalk: '^1.1.0',\n        bluebird: '^1.0.0',\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "test/getEnginesNodeFromRegistry.test.ts",
    "content": "import { chalkInit } from '../src/lib/chalk'\nimport getEnginesNodeFromRegistry from '../src/lib/getEnginesNodeFromRegistry'\nimport chaiSetup from './helpers/chaiSetup'\n\nchaiSetup()\n\ndescribe('getEnginesNodeFromRegistry', function () {\n  it('single package', async () => {\n    await chalkInit()\n    const data = await getEnginesNodeFromRegistry({ del: '2.0.0' }, {})\n    data.should.deep.equal({\n      del: '>=0.10.0',\n    })\n  })\n\n  it('single package empty', async () => {\n    await chalkInit()\n    const data = await getEnginesNodeFromRegistry({ 'ncu-test-return-version': '1.0.0' }, {})\n    data.should.deep.equal({ 'ncu-test-return-version': undefined })\n  })\n\n  it('multiple packages', async () => {\n    await chalkInit()\n    const data = await getEnginesNodeFromRegistry(\n      {\n        'ncu-test-return-version': '1.0.0',\n        'ncu-test-peer': '1.0.0',\n        del: '2.0.0',\n      },\n      {},\n    )\n    data.should.deep.equal({\n      'ncu-test-return-version': undefined,\n      'ncu-test-peer': undefined,\n      del: '>=0.10.0',\n    })\n  })\n})\n"
  },
  {
    "path": "test/getIgnoredUpgradesDueToEnginesNode.test.ts",
    "content": "import getIgnoredUpgradesDueToEnginesNode from '../src/lib/getIgnoredUpgradesDueToEnginesNode'\nimport chaiSetup from './helpers/chaiSetup'\n\nconst MOCK_ESLINT_VERSION = '999.0.0'\nconst MOCK_DEL_VERSION = '999.0.1'\n\nchaiSetup()\n\n/* This test needs to be rewritten because it is run against live data that affects the outcome of the test. The eslint and del versions were mocked in order to prevent this, but now the latest del.enginesNode is >=18 which fails the test. This data should either be mocked or the target packages should be entirely replaced by packages under our control. */\ndescribe.skip('getIgnoredUpgradesDueToEnginesNode', function () {\n  it('ncu-test-peer-update', async () => {\n    const data = await getIgnoredUpgradesDueToEnginesNode(\n      {\n        'ncu-test-return-version': '1.0.0',\n        'ncu-test-peer': '^1.0.0',\n        del: '2.2.2',\n        '@typescript-eslint/eslint-plugin': '^7.18.0',\n      },\n      {\n        'ncu-test-return-version': '2.0.0',\n        'ncu-test-peer': '^1.1.0',\n        del: '2.2.2',\n        '@typescript-eslint/eslint-plugin': '^8.1.0',\n      },\n      {\n        enginesNode: true,\n        nodeEngineVersion: `^0.10.0`,\n      },\n    )\n\n    // override 'to' fields with mock versions since this is live npm data that will change\n    data['@typescript-eslint/eslint-plugin'].to = MOCK_ESLINT_VERSION\n    data.del.to = MOCK_DEL_VERSION\n\n    data.should.deep.equal({\n      '@typescript-eslint/eslint-plugin': {\n        enginesNode: '^18.18.0 || ^20.9.0 || >=21.1.0',\n        from: '^7.18.0',\n        to: MOCK_ESLINT_VERSION,\n      },\n      del: {\n        enginesNode: '>=14.16',\n        from: '2.2.2',\n        to: MOCK_DEL_VERSION,\n      },\n    })\n  })\n})\n"
  },
  {
    "path": "test/getIgnoredUpgradesDueToPeerDeps.test.ts",
    "content": "import getIgnoredUpgradesDueToPeerDeps from '../src/lib/getIgnoredUpgradesDueToPeerDeps'\nimport { Packument } from '../src/types/Packument'\nimport chaiSetup from './helpers/chaiSetup'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\ndescribe('getIgnoredUpgradesDueToPeerDeps', function () {\n  it('ncu-test-peer-update', async () => {\n    const data = await getIgnoredUpgradesDueToPeerDeps(\n      {\n        'ncu-test-return-version': '1.0.0',\n        'ncu-test-peer': '1.0.0',\n      },\n      {\n        'ncu-test-return-version': '1.1.0',\n        'ncu-test-peer': '1.1.0',\n      },\n      {\n        'ncu-test-peer': {\n          'ncu-test-return-version': '1.1.x',\n        },\n      },\n      {},\n    )\n    data.should.deep.equal({\n      'ncu-test-return-version': {\n        from: '1.0.0',\n        to: '2.0.0',\n        reason: {\n          'ncu-test-peer': '1.1.x',\n        },\n      },\n    })\n  })\n  it('ignored peer after upgrade', async () => {\n    const stub = stubVersions({\n      '@vitest/ui': {\n        version: '1.6.0',\n        versions: {\n          '1.3.1': {\n            version: '1.3.1',\n          } as Packument,\n          '1.6.0': {\n            version: '1.6.0',\n          } as Packument,\n        },\n      },\n      vitest: {\n        version: '1.6.0',\n        versions: {\n          '1.3.1': {\n            version: '1.3.1',\n          } as Packument,\n          '1.6.0': {\n            version: '1.6.0',\n          } as Packument,\n        },\n      },\n      eslint: {\n        version: '9.0.0',\n        versions: {\n          '8.57.0': {\n            version: '8.57.0',\n          } as Packument,\n          '9.0.0': {\n            version: '9.0.0',\n          } as Packument,\n        },\n      },\n      'eslint-plugin-import': {\n        version: '2.29.1',\n        versions: {\n          '2.29.1': {\n            version: '2.29.1',\n          } as Packument,\n        },\n      },\n      'eslint-plugin-unused-imports': {\n        version: '4.0.0',\n        versions: {\n          '4.0.0': {\n            version: '4.0.0',\n          } as Packument,\n          '3.0.0': {\n            version: '3.0.0',\n          } as Packument,\n        },\n      },\n    })\n    const data = await getIgnoredUpgradesDueToPeerDeps(\n      {\n        '@vitest/ui': '1.3.1',\n        vitest: '1.3.1',\n        eslint: '8.57.0',\n        'eslint-plugin-import': '2.29.1',\n        'eslint-plugin-unused-imports': '3.0.0',\n      },\n      {\n        '@vitest/ui': '1.6.0',\n        vitest: '1.6.0',\n      },\n      {\n        '@vitest/ui': {\n          vitest: '1.6.0',\n        },\n        vitest: {\n          jsdom: '*',\n          'happy-dom': '*',\n          '@vitest/ui': '1.6.0',\n          '@types/node': '^18.0.0 || >=20.0.0',\n          '@vitest/browser': '1.6.0',\n          '@edge-runtime/vm': '*',\n        },\n        eslint: {},\n        'eslint-plugin-import': {\n          eslint: '^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8',\n        },\n        'eslint-plugin-unused-imports': {\n          '@typescript-eslint/eslint-plugin': '^6.0.0',\n          eslint: '^8.0.0',\n        },\n      },\n      {\n        target: packageName => {\n          return packageName === 'eslint-plugin-unused-imports' ? 'greatest' : 'minor'\n        },\n      },\n    )\n    data.should.deep.equal({\n      'eslint-plugin-unused-imports': {\n        from: '3.0.0',\n        reason: {\n          'eslint-plugin-unused-imports': 'eslint 9',\n        },\n        to: '4.0.0',\n      },\n    })\n    stub.restore()\n  })\n})\n"
  },
  {
    "path": "test/getInstalledPackages.test.ts",
    "content": "import getInstalledPackages from '../src/lib/getInstalledPackages'\n\n// test getInstalledPackages since we cannot test runGlobal without additional code for mocking\ndescribe('getInstalledPackages', () => {\n  it('execute npm ls', async () => {\n    await getInstalledPackages()\n  })\n})\n"
  },
  {
    "path": "test/getPeerDependenciesFromRegistry.test.ts",
    "content": "import { chalkInit } from '../src/lib/chalk'\nimport getPeerDependenciesFromRegistry from '../src/lib/getPeerDependenciesFromRegistry'\nimport chaiSetup from './helpers/chaiSetup'\n\nchaiSetup()\n\ndescribe('getPeerDependenciesFromRegistry', function () {\n  it('single package', async () => {\n    await chalkInit()\n    const data = await getPeerDependenciesFromRegistry({ 'ncu-test-peer': '1.0' }, {})\n    data.should.deep.equal({\n      'ncu-test-peer': {\n        'ncu-test-return-version': '1.x',\n      },\n    })\n  })\n\n  it('single package empty', async () => {\n    await chalkInit()\n    const data = await getPeerDependenciesFromRegistry({ 'ncu-test-return-version': '1.0' }, {})\n    data.should.deep.equal({ 'ncu-test-return-version': {} })\n  })\n\n  it('multiple packages', async () => {\n    await chalkInit()\n    const data = await getPeerDependenciesFromRegistry(\n      {\n        'ncu-test-return-version': '1.0.0',\n        'ncu-test-peer': '1.0.0',\n      },\n      {},\n    )\n    data.should.deep.equal({\n      'ncu-test-return-version': {},\n      'ncu-test-peer': {\n        'ncu-test-return-version': '1.x',\n      },\n    })\n  })\n})\n"
  },
  {
    "path": "test/getPreferredWildcard.test.ts",
    "content": "import getPreferredWildcard from '../src/lib/getPreferredWildcard'\nimport chaiSetup from './helpers/chaiSetup'\n\nconst should = chaiSetup()\n\ndescribe('getPreferredWildcard', () => {\n  it('identify ^ when it is preferred', () => {\n    const deps = {\n      async: '^0.9.0',\n      bluebird: '^2.9.27',\n      cint: '^8.2.1',\n      commander: '~2.8.1',\n      lodash: '^3.2.0',\n    }\n    getPreferredWildcard(deps)!.should.equal('^')\n  })\n\n  it('identify ~ when it is preferred', () => {\n    const deps = {\n      async: '~0.9.0',\n      bluebird: '~2.9.27',\n      cint: '^8.2.1',\n      commander: '~2.8.1',\n      lodash: '^3.2.0',\n    }\n    getPreferredWildcard(deps)!.should.equal('~')\n  })\n\n  it('identify .x when it is preferred', () => {\n    const deps = {\n      async: '0.9.x',\n      bluebird: '2.9.x',\n      cint: '^8.2.1',\n      commander: '~2.8.1',\n      lodash: '3.x',\n    }\n    getPreferredWildcard(deps)!.should.equal('.x')\n  })\n\n  it('identify .* when it is preferred', () => {\n    const deps = {\n      async: '0.9.*',\n      bluebird: '2.9.*',\n      cint: '^8.2.1',\n      commander: '~2.8.1',\n      lodash: '3.*',\n    }\n    getPreferredWildcard(deps)!.should.equal('.*')\n  })\n\n  it('do not allow wildcards to be outnumbered by non-wildcards', () => {\n    const deps = {\n      gulp: '^4.0.0',\n      typescript: '3.3.0',\n      webpack: '4.30.0',\n    }\n    getPreferredWildcard(deps)!.should.equal('^')\n  })\n\n  it('use the first wildcard if there is a tie', () => {\n    const deps = {\n      async: '0.9.x',\n      commander: '2.8.*',\n    }\n    getPreferredWildcard(deps)!.should.equal('.x')\n  })\n\n  it('return null when it cannot be determined from other dependencies', () => {\n    const deps = {\n      async: '0.9.0',\n      commander: '2.8.1',\n      lodash: '3.2.0',\n    }\n    should.equal(getPreferredWildcard(deps), null)\n    should.equal(getPreferredWildcard({}), null)\n  })\n})\n"
  },
  {
    "path": "test/getRepoUrl.test.ts",
    "content": "import getRepoUrl from '../src/lib/getRepoUrl'\nimport chaiSetup from './helpers/chaiSetup'\n\nconst should = chaiSetup()\n\ndescribe('getRepoUrl', () => {\n  it('return null if package is not installed', async () => {\n    should.equal(await getRepoUrl('not-installed/package'), null)\n  })\n  it('return null repository field is undefined', async () => {\n    should.equal(await getRepoUrl('package-name', {}), null)\n  })\n  it('return null repository field is unknown type', async () => {\n    should.equal(await getRepoUrl('package-name', { repository: true as any /* allow to compile */ }), null)\n  })\n  it('return url directly from repository field if valid https url', async () => {\n    const url = await getRepoUrl('package-name', { repository: 'https://github.com/user/repo' })\n    url!.should.equal('https://github.com/user/repo')\n  })\n  it('return url directly from repository field if valid http url', async () => {\n    const url = await getRepoUrl('package-name', { repository: 'http://anything.com/user/repo' })\n    url!.should.equal('http://anything.com/user/repo')\n  })\n  it('return url constructed from github shortcut syntax string', async () => {\n    const url = await getRepoUrl('package-name', { repository: 'user/repo' })\n    url!.should.equal('https://github.com/user/repo')\n  })\n  it('return url constructed from repository specific shortcut syntax string', async () => {\n    const url = await getRepoUrl('package-name', { repository: 'github:user/repo' })\n    url!.should.equal('https://github.com/user/repo')\n  })\n  it('return url directly from url field if not a known git host', async () => {\n    const url = await getRepoUrl('package-name', { repository: { url: 'https://any.website.com/some/path' } })\n    url!.should.equal('https://any.website.com/some/path')\n  })\n  it('return url constructed from git-https protocol', async () => {\n    const url = await getRepoUrl('package-name', { repository: { url: 'git+https://github.com/user/repo.git' } })\n    url!.should.equal('https://github.com/user/repo')\n  })\n  it('return url constructed from git protocol', async () => {\n    const url = await getRepoUrl('package-name', { repository: { url: 'git://github.com/user/repo.git' } })\n    url!.should.equal('https://github.com/user/repo')\n  })\n  it('return url constructed from http protocol', async () => {\n    const url = await getRepoUrl('package-name', { repository: { url: 'http://github.com/user/repo.git' } })\n    url!.should.equal('https://github.com/user/repo')\n  })\n  it('return url with directory path', async () => {\n    const url = await getRepoUrl('package-name', {\n      repository: { url: 'http://github.com/user/repo.git', directory: 'packages/specific-package' },\n    })\n    url!.should.equal('https://github.com/user/repo/tree/HEAD/packages/specific-package')\n  })\n})\n"
  },
  {
    "path": "test/github-urls.test.ts",
    "content": "import ncu from '../src'\nimport chaiSetup from './helpers/chaiSetup'\n\nchaiSetup()\n\ndescribe('github urls', () => {\n  it('upgrade github https urls', async () => {\n    const upgrades = await ncu({\n      packageData: {\n        dependencies: {\n          'ncu-test-v2': 'https://github.com/raineorshine/ncu-test-v2#1.0.0',\n        },\n      },\n    })\n    upgrades!.should.deep.equal({\n      'ncu-test-v2': 'https://github.com/raineorshine/ncu-test-v2#2.0.0',\n    })\n  })\n\n  it('upgrade short github urls', async () => {\n    const upgrades = await ncu({\n      packageData: {\n        dependencies: {\n          'ncu-test-v2': 'github:raineorshine/ncu-test-v2#1.0.0',\n        },\n      },\n    })\n    upgrades!.should.deep.equal({\n      'ncu-test-v2': 'github:raineorshine/ncu-test-v2#2.0.0',\n    })\n  })\n\n  it('upgrade shortest github urls', async () => {\n    const upgrades = await ncu({\n      packageData: {\n        dependencies: {\n          'ncu-test-v2': 'raineorshine/ncu-test-v2#1.0.0',\n        },\n      },\n    })\n    upgrades!.should.deep.equal({\n      'ncu-test-v2': 'raineorshine/ncu-test-v2#2.0.0',\n    })\n  })\n\n  it('upgrade github http urls with semver', async () => {\n    const upgrades = await ncu({\n      packageData: {\n        dependencies: {\n          'ncu-test-v2': 'https://github.com/raineorshine/ncu-test-v2#semver:^1.0.0',\n        },\n      },\n    })\n    upgrades!.should.deep.equal({\n      'ncu-test-v2': 'https://github.com/raineorshine/ncu-test-v2#semver:^2.0.0',\n    })\n  })\n\n  // does not work in GitHub actions for some reason\n  it.skip('upgrade github git+ssh urls with semver', async () => {\n    const upgrades = await ncu({\n      packageData: {\n        dependencies: {\n          'ncu-test-v2': 'git+ssh://git@github.com/raineorshine/ncu-test-v2.git#semver:^1.0.0',\n        },\n      },\n    })\n    upgrades!.should.deep.equal({\n      'ncu-test-v2': 'git+ssh://git@github.com/raineorshine/ncu-test-v2.git#semver:^2.0.0',\n    })\n  })\n})\n"
  },
  {
    "path": "test/global.test.ts",
    "content": "import { expect } from 'chai'\nimport path from 'path'\nimport spawn from 'spawn-please'\nimport chaiSetup from './helpers/chaiSetup'\n\nchaiSetup()\n\nconst bin = path.join(__dirname, '../build/cli.js')\n\ndescribe('global', () => {\n  // TODO: Hangs on Windows\n  const itSkipWindows = process.platform === 'win32' ? it.skip : it\n  itSkipWindows('global should run', async () => {\n    const { stdout } = await spawn('node', [bin, '--jsonUpgraded', '--global', 'npm'])\n    expect(JSON.parse(stdout))\n  })\n})\n"
  },
  {
    "path": "test/group.test.ts",
    "content": "import fs from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport spawn from 'spawn-please'\nimport { GroupFunction } from '../src/types/GroupFunction'\nimport chaiSetup from './helpers/chaiSetup'\nimport removeDir from './helpers/removeDir'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\nconst bin = path.join(__dirname, '../build/cli.js')\n\n/**\n * Sets up and tears down the temporary directories required to run each test\n */\nasync function groupTestScaffold(\n  dependencies: Record<string, string>,\n  groupFn: GroupFunction,\n  expectedOutput: string,\n): Promise<void> {\n  const stub = stubVersions(\n    {\n      'ncu-test-v2': '2.0.0',\n      'ncu-test-tag': '1.1.0',\n      'ncu-test-return-version': '2.0.0',\n    },\n    { spawn: true },\n  )\n\n  // use dynamic import for ESM module\n  const { default: stripAnsi } = await import('strip-ansi')\n  const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n  const pkgFile = path.join(tempDir, 'package.json')\n  await fs.writeFile(\n    pkgFile,\n    JSON.stringify({\n      dependencies,\n    }),\n    'utf-8',\n  )\n  const configFile = path.join(tempDir, '.ncurc.js')\n  await fs.writeFile(configFile, `module.exports = { groupFunction: ${groupFn.toString()} }`, 'utf-8')\n  try {\n    const { stdout } = await spawn(\n      'node',\n      [bin, '--format', 'group', '--configFilePath', tempDir],\n      {},\n      {\n        cwd: tempDir,\n      },\n    )\n    stripAnsi(stdout).should.containIgnoreCase(expectedOutput)\n  } finally {\n    await removeDir(tempDir)\n    stub.restore()\n  }\n}\n\ndescribe('--format group', () => {\n  it('group upgrades by type', async () => {\n    await groupTestScaffold(\n      { 'ncu-test-v2': '1.0.0', 'ncu-test-return-version': '1.0.0', 'ncu-test-tag': '1.0.0' },\n      (packageName, defaultGroup) => defaultGroup,\n      `Minor   Backwards-compatible features\n ncu-test-tag  1.0.0  →  1.1.0\n\nMajor   Potentially breaking API changes\n ncu-test-return-version  1.0.0  →  2.0.0\n ncu-test-v2              1.0.0  →  2.0.0`,\n    )\n  })\n\n  it('preserve version ranges', async () => {\n    await groupTestScaffold(\n      { 'ncu-test-v2': '^1.0.0' },\n      (packageName, defaultGroup) => defaultGroup,\n      `Major   Potentially breaking API changes\n ncu-test-v2  ^1.0.0  →  ^2.0.0`,\n    )\n  })\n\n  it('moves package to major group', async () => {\n    await groupTestScaffold(\n      { 'ncu-test-v2': '1.0.0', 'ncu-test-return-version': '1.0.0', 'ncu-test-tag': '1.0.0' },\n      (packageName, defaultGroup) => (packageName === 'ncu-test-tag' ? 'major' : defaultGroup),\n      `Major   Potentially breaking API changes\n ncu-test-return-version  1.0.0  →  2.0.0\n ncu-test-tag             1.0.0  →  1.1.0\n ncu-test-v2              1.0.0  →  2.0.0`,\n    )\n  })\n\n  it('moves package to minor group', async () => {\n    await groupTestScaffold(\n      { 'ncu-test-v2': '1.0.0', 'ncu-test-return-version': '1.0.0', 'ncu-test-tag': '1.0.0' },\n      (packageName, defaultGroup) => (packageName === 'ncu-test-v2' ? 'minor' : defaultGroup),\n      `Minor   Backwards-compatible features\n ncu-test-tag  1.0.0  →  1.1.0\n ncu-test-v2   1.0.0  →  2.0.0\n\nMajor   Potentially breaking API changes\n ncu-test-return-version  1.0.0  →  2.0.0`,\n    )\n  })\n\n  it('moves package to patch group', async () => {\n    await groupTestScaffold(\n      { 'ncu-test-v2': '1.0.0', 'ncu-test-return-version': '1.0.0', 'ncu-test-tag': '1.0.0' },\n      (packageName, defaultGroup) => (packageName === 'ncu-test-v2' ? 'patch' : defaultGroup),\n      `Patch   Backwards-compatible bug fixes\n ncu-test-v2  1.0.0  →  2.0.0\n\nMinor   Backwards-compatible features\n ncu-test-tag  1.0.0  →  1.1.0\n\nMajor   Potentially breaking API changes\n ncu-test-return-version  1.0.0  →  2.0.0`,\n    )\n  })\n\n  it('moves package to majorVersionZero group', async () => {\n    await groupTestScaffold(\n      { 'ncu-test-v2': '1.0.0', 'ncu-test-return-version': '1.0.0', 'ncu-test-tag': '1.0.0' },\n      (packageName, defaultGroup) => (packageName === 'ncu-test-v2' ? 'majorVersionZero' : defaultGroup),\n      `Minor   Backwards-compatible features\n ncu-test-tag  1.0.0  →  1.1.0\n\nMajor   Potentially breaking API changes\n ncu-test-return-version  1.0.0  →  2.0.0\n\nMajor version zero   Anything may change\n ncu-test-v2  1.0.0  →  2.0.0`,\n    )\n  })\n\n  it('creates custom groups', async () => {\n    await groupTestScaffold(\n      { 'ncu-test-v2': '1.0.0', 'ncu-test-return-version': '1.0.0', 'ncu-test-tag': '1.0.0' },\n      (packageName, defaultGroup, currentVersionSpec, upgradedVersionSpec, upgradedVersion) =>\n        `Custom Group for ${packageName} ${JSON.stringify(currentVersionSpec)} ${JSON.stringify(\n          upgradedVersionSpec,\n        )} ${JSON.stringify(upgradedVersion)}`,\n      `Custom Group for ncu-test-return-version [{\"semver\":\"1.0.0\",\"major\":\"1\",\"minor\":\"0\",\"patch\":\"0\"}] [{\"semver\":\"2.0.0\",\"major\":\"2\",\"minor\":\"0\",\"patch\":\"0\"}] {\"semver\":\"2.0.0\",\"version\":\"2.0.0\",\"major\":\"2\",\"minor\":\"0\",\"patch\":\"0\"}\n ncu-test-return-version  1.0.0  →  2.0.0\n\nCustom Group for ncu-test-tag [{\"semver\":\"1.0.0\",\"major\":\"1\",\"minor\":\"0\",\"patch\":\"0\"}] [{\"semver\":\"1.1.0\",\"major\":\"1\",\"minor\":\"1\",\"patch\":\"0\"}] {\"semver\":\"1.1.0\",\"version\":\"1.1.0\",\"major\":\"1\",\"minor\":\"1\",\"patch\":\"0\"}\n ncu-test-tag  1.0.0  →  1.1.0\n\nCustom Group for ncu-test-v2 [{\"semver\":\"1.0.0\",\"major\":\"1\",\"minor\":\"0\",\"patch\":\"0\"}] [{\"semver\":\"2.0.0\",\"major\":\"2\",\"minor\":\"0\",\"patch\":\"0\"}] {\"semver\":\"2.0.0\",\"version\":\"2.0.0\",\"major\":\"2\",\"minor\":\"0\",\"patch\":\"0\"}\n ncu-test-v2  1.0.0  →  2.0.0`,\n    )\n  })\n})\n"
  },
  {
    "path": "test/helpers/chaiSetup.ts",
    "content": "import chai from 'chai'\nimport chaiAsPromised from 'chai-as-promised'\nimport chaiString from 'chai-string'\n\n/** Global chai setup. */\nconst chaiSetup = () => {\n  const should = chai.should()\n  chai.use(chaiAsPromised)\n  chai.use(chaiString)\n\n  // do not truncate strings in error messages\n  chai.config.truncateThreshold = 0\n\n  process.env.NCU_TESTS = 'true'\n\n  return should\n}\n\nexport default chaiSetup\n"
  },
  {
    "path": "test/helpers/doctorHelpers.ts",
    "content": "import fs from 'fs/promises'\nimport path from 'path'\nimport spawn from 'spawn-please'\nimport { PackageManagerName } from '../../src/types/PackageManagerName'\n\nconst bin = path.join(__dirname, '../../build/cli.js')\nconst doctorTests = path.join(__dirname, '../test-data/doctor')\n\n/** Run the ncu CLI. */\nconst ncu = async (\n  args: string[],\n  spawnPleaseOptions?: Parameters<typeof spawn>[2],\n  spawnOptions?: Parameters<typeof spawn>[3],\n) => {\n  const { stdout } = await spawn('node', [bin, ...args], spawnPleaseOptions, spawnOptions)\n  return stdout\n}\n\n/** Assertions for npm or yarn when tests pass. */\nexport const testPass = ({ packageManager }: { packageManager: PackageManagerName }) => {\n  it('upgrade dependencies when tests pass', async function () {\n    // use dynamic import for ESM module\n    const { default: stripAnsi } = await import('strip-ansi')\n    const cwd = path.join(doctorTests, 'pass')\n    const pkgPath = path.join(cwd, 'package.json')\n    const nodeModulesPath = path.join(cwd, 'node_modules')\n    const lockfilePath = path.join(\n      cwd,\n      packageManager === 'yarn'\n        ? 'yarn.lock'\n        : packageManager === 'pnpm'\n          ? 'pnpm-lock.yaml'\n          : packageManager === 'bun'\n            ? 'bun.lockb'\n            : 'package-lock.json',\n    )\n    const pkgOriginal = await fs.readFile(path.join(cwd, 'package.json'), 'utf-8')\n    let stdout = ''\n    let stderr = ''\n\n    // touch yarn.lock\n    // yarn.lock is necessary otherwise yarn sees the package.json in the npm-check-updates directory and throws an error.\n    if (packageManager === 'yarn' || packageManager === 'bun') {\n      await fs.writeFile(lockfilePath, '')\n    }\n\n    try {\n      // explicitly set packageManager to avoid auto yarn detection\n      await ncu(\n        ['--doctor', '-u', '-p', packageManager],\n        {\n          stdout: function (data: string) {\n            stdout += data\n          },\n          stderr: function (data: string) {\n            stderr += data\n          },\n        },\n        { cwd },\n      )\n    } catch (e) {}\n\n    const pkgUpgraded = await fs.readFile(pkgPath, 'utf-8')\n\n    // cleanup before assertions in case they fail\n    await fs.writeFile(pkgPath, pkgOriginal)\n    await fs.rm(nodeModulesPath, { recursive: true, force: true })\n    await fs.rm(lockfilePath, { recursive: true, force: true })\n\n    // delete yarn cache\n    if (packageManager === 'yarn') {\n      await fs.rm(path.join(cwd, '.yarn'), { recursive: true, force: true })\n      await fs.rm(path.join(cwd, '.pnp.js'), { recursive: true, force: true })\n    }\n\n    // bun prints the run header to stderr instead of stdout\n    if (packageManager === 'bun') {\n      stripAnsi(stderr).should.equal('$ echo Success\\n\\n$ echo Success\\n\\n')\n    } else {\n      stderr = stripAnsi(stderr).trim()\n      if (stderr !== '') {\n        stderr.should.equal(`> test\n> echo Success\n\n\n\n> test\n> echo Success`)\n      }\n    }\n\n    // stdout should include normal output\n    stripAnsi(stdout).should.containIgnoreCase('Tests pass')\n    stripAnsi(stdout).should.containIgnoreCase('ncu-test-v2  ~1.0.0  →  ~2.0.0')\n\n    // package file should include upgrades\n    pkgUpgraded.should.containIgnoreCase('\"ncu-test-v2\": \"~2.0.0\"')\n  })\n}\n\n/** Assertions for npm or yarn when tests fail. */\nexport const testFail = ({ packageManager }: { packageManager: PackageManagerName }) => {\n  it('identify broken upgrade', async function () {\n    const cwd = path.join(doctorTests, 'fail')\n    const pkgPath = path.join(cwd, 'package.json')\n    const nodeModulesPath = path.join(cwd, 'node_modules')\n    const lockfilePath = path.join(\n      cwd,\n      packageManager === 'yarn'\n        ? 'yarn.lock'\n        : packageManager === 'pnpm'\n          ? 'pnpm-lock.yaml'\n          : packageManager === 'bun'\n            ? 'bun.lockb'\n            : 'package-lock.json',\n    )\n    const pkgOriginal = await fs.readFile(path.join(cwd, 'package.json'), 'utf-8')\n    let stdout = ''\n    let stderr = ''\n    let pkgUpgraded\n\n    // touch yarn.lock (see fail/README)\n    if (packageManager === 'yarn') {\n      await fs.writeFile(lockfilePath, '')\n    }\n\n    try {\n      // explicitly set packageManager to avoid auto yarn detection\n      await ncu(\n        ['--doctor', '-u', '-p', packageManager],\n        {\n          stdout: function (data: string) {\n            stdout += data\n          },\n          stderr: function (data: string) {\n            stderr += data\n          },\n        },\n        { cwd },\n      )\n    } finally {\n      pkgUpgraded = await fs.readFile(pkgPath, 'utf-8')\n      await fs.writeFile(pkgPath, pkgOriginal)\n      await fs.rm(nodeModulesPath, { recursive: true, force: true })\n      await fs.rm(lockfilePath, { recursive: true, force: true })\n\n      // delete yarn cache\n      if (packageManager === 'yarn') {\n        await fs.rm(path.join(cwd, '.yarn'), { recursive: true, force: true })\n        await fs.rm(path.join(cwd, '.pnp.js'), { recursive: true, force: true })\n      }\n    }\n\n    // stdout should include successful upgrades\n    stdout.should.containIgnoreCase('ncu-test-v2 ~1.0.0 →')\n    stdout.should.not.include('ncu-test-return-version ~1.0.0 →')\n    stdout.should.containIgnoreCase('emitter20 1.0.0 →')\n\n    // stderr should include first failing upgrade\n    stderr.should.containIgnoreCase('Breaks with v2.x')\n    stderr.should.not.include('ncu-test-v2 ~1.0.0 →')\n    stderr.should.containIgnoreCase('ncu-test-return-version ~1.0.0 →')\n    stderr.should.not.include('emitter20 1.0.0 →')\n\n    // package file should only include successful upgrades\n    pkgUpgraded.should.containIgnoreCase('\"ncu-test-v2\": \"~2.0.0\"')\n    pkgUpgraded.should.containIgnoreCase('\"ncu-test-return-version\": \"~1.0.0\"')\n    pkgUpgraded.should.not.include('\"emitter20\": \"1.0.0\"')\n  })\n}\n"
  },
  {
    "path": "test/helpers/removeDir.ts",
    "content": "import fs from 'fs/promises'\n\n/**\n * Helper function to remove a directory while avoiding errors like:\n * Error: EBUSY: resource busy or locked, rmdir 'C:\\Users\\alice\\AppData\\Local\\Temp\\npm-check-updates-yc1wT3'\n *\n * On Windows, spawned child processes may hold locks on directories briefly after exiting.\n * This function retries the removal with exponential backoff to handle transient locks.\n */\nasync function removeDir(dirPath: string, maxRetries = 10, delayMs = 100) {\n  for (let attempt = 0; attempt < maxRetries; attempt++) {\n    try {\n      await fs.rm(dirPath, { recursive: true, force: true })\n      return\n    } catch (err: unknown) {\n      const isEBUSY = err instanceof Error && 'code' in err && err.code === 'EBUSY'\n      if (!isEBUSY || attempt === maxRetries - 1) {\n        throw err\n      }\n      // Wait before retrying, with exponential backoff\n      await new Promise(resolve => setTimeout(resolve, delayMs * Math.pow(2, attempt)))\n    }\n  }\n}\n\nexport default removeDir\n"
  },
  {
    "path": "test/helpers/stubVersions.ts",
    "content": "import sinon from 'sinon'\nimport * as npmPackageManager from '../../src/package-managers/npm'\nimport { MockedVersions } from '../../src/types/MockedVersions'\n\n/** Stubs the npmView function from package-managers/npm. Returns the stub object. Call stub.restore() after assertions to restore the original function. Set spawn:true to stub ncu spawned as a child process. */\nconst stubVersions = (mockReturnedVersions: MockedVersions, { spawn }: { spawn?: boolean } = {}) => {\n  // stub child process\n  // the only way to stub functionality in spawned child processes is to pass data through process.env and stub internally\n  if (spawn) {\n    process.env.STUB_VERSIONS = JSON.stringify(mockReturnedVersions)\n    return {\n      restore: () => {\n        process.env.STUB_VERSIONS = ''\n      },\n    }\n  }\n  // stub module\n  else {\n    return sinon\n      .stub(npmPackageManager, 'fetchUpgradedPackumentMemo')\n      .callsFake(npmPackageManager.mockFetchUpgradedPackument(mockReturnedVersions))\n  }\n}\n\nexport default stubVersions\n"
  },
  {
    "path": "test/index.test.ts",
    "content": "import fs from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport ncu from '../src/'\nimport chaiSetup from './helpers/chaiSetup'\nimport removeDir from './helpers/removeDir'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\ndescribe('run', function () {\n  it('return jsonUpgraded by default', async () => {\n    const stub = stubVersions('99.9.9')\n\n    const output = await ncu({\n      packageData: await fs.readFile(path.join(__dirname, 'test-data/ncu/package.json'), 'utf-8'),\n    })\n    output!.should.deep.equal({\n      express: '^99.9.9',\n    })\n\n    stub.restore()\n  })\n\n  it('pass object as packageData', async () => {\n    const stub = stubVersions('99.9.9')\n\n    const output = await ncu({\n      packageData: {\n        dependencies: {\n          MOCK_PACKAGE: '1.0.0',\n        },\n      },\n    })\n    output!.should.have.property('MOCK_PACKAGE')\n\n    stub.restore()\n  })\n\n  it('do not suggest upgrades to versions within the specified version range if jsonUpgraded is true and minimal is true', async () => {\n    const stub = stubVersions('2.1.1')\n\n    const upgraded = await ncu({\n      packageData: { dependencies: { MOCK_PACKAGE: '^2.1.0' } },\n      jsonUpgraded: true,\n      minimal: true,\n    })\n\n    upgraded!.should.not.have.property('MOCK_PACKAGE')\n\n    stub.restore()\n  })\n\n  it('write to --packageFile and output jsonUpgraded', async () => {\n    const stub = stubVersions('99.9.9')\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(pkgFile, '{ \"dependencies\": { \"express\": \"1\" } }', 'utf-8')\n\n    try {\n      const result = await ncu({\n        packageFile: pkgFile,\n        jsonUpgraded: true,\n        upgrade: true,\n      })\n      result!.should.have.property('express')\n\n      const upgradedPkg = JSON.parse(await fs.readFile(pkgFile, 'utf-8'))\n      upgradedPkg.should.have.property('dependencies')\n      upgradedPkg.dependencies.should.have.property('express')\n    } finally {\n      await removeDir(tempDir)\n      stub.restore()\n    }\n  })\n\n  it('exclude -alpha, -beta, -rc', () => {\n    return ncu({\n      jsonAll: true,\n      packageData: {\n        dependencies: {\n          'ncu-mock-pre': '1.0.0',\n        },\n      },\n    }).then(data => {\n      return data!.should.eql({\n        dependencies: {\n          'ncu-mock-pre': '1.0.0',\n        },\n      })\n    })\n  })\n\n  it('upgrade prereleases to newer prereleases', () => {\n    return ncu({\n      packageData: {\n        dependencies: {\n          'ncu-test-alpha-latest': '1.0.0-alpha.1',\n        },\n      },\n    }).then(data => {\n      return data!.should.eql({\n        'ncu-test-alpha-latest': '1.0.0-alpha.2',\n      })\n    })\n  })\n\n  it('do not upgrade prereleases to newer prereleases with --pre 0', () => {\n    return ncu({\n      pre: false,\n      packageData: {\n        dependencies: {\n          'ncu-test-alpha-latest': '1.0.0-alpha.1',\n        },\n      },\n    }).then(data => {\n      return data!.should.eql({})\n    })\n  })\n\n  it('include -alpha, -beta, -rc with --pre option', () => {\n    return ncu({\n      jsonAll: true,\n      packageData: {\n        dependencies: {\n          'ncu-mock-pre': '1.0.0',\n        },\n      },\n      pre: true,\n    }).then(data => {\n      return data!.should.eql({\n        dependencies: {\n          'ncu-mock-pre': '2.0.0-alpha.0',\n        },\n      })\n    })\n  })\n\n  describe('deprecated', () => {\n    it('deprecated included by default', async () => {\n      const upgrades = await ncu({\n        packageData: {\n          dependencies: {\n            'ncu-test-deprecated': '1.0.0',\n          },\n        },\n      })\n      upgrades!.should.deep.equal({\n        'ncu-test-deprecated': '2.0.0',\n      })\n    })\n\n    it('deprecated included with --deprecated', async () => {\n      const upgrades = await ncu({\n        deprecated: true,\n        packageData: {\n          dependencies: {\n            'ncu-test-deprecated': '1.0.0',\n          },\n        },\n      })\n      upgrades!.should.deep.equal({\n        'ncu-test-deprecated': '2.0.0',\n      })\n    })\n\n    it('deprecated excluded with --no-deprecated', async () => {\n      const upgrades = await ncu({\n        deprecated: false,\n        packageData: {\n          dependencies: {\n            'ncu-test-deprecated': '1.0.0',\n          },\n        },\n      })\n      upgrades!.should.deep.equal({})\n    })\n  })\n\n  it('ignore non-string versions (sometimes used as comments)', async () => {\n    const upgrades = await ncu({\n      packageData: {\n        dependencies: {\n          '//': 'This is a comment',\n        },\n      },\n    })\n    upgrades!.should.deep.equal({})\n  })\n\n  it('update devDependency when duplicate dependency is up-to-date', async () => {\n    const stub = stubVersions('2.0.0')\n    const upgrades = await ncu({\n      packageData: {\n        dependencies: {\n          'ncu-test-v2': '^2.0.0',\n        },\n        devDependencies: {\n          'ncu-test-v2': '^1.0.0',\n        },\n      },\n    })\n    upgrades!.should.deep.equal({\n      'ncu-test-v2': '^2.0.0',\n    })\n    stub.restore()\n  })\n\n  it('update dependency when duplicate devDependency is up-to-date', async () => {\n    const stub = stubVersions('2.0.0')\n    const upgrades = await ncu({\n      packageData: {\n        dependencies: {\n          'ncu-test-v2': '^1.0.0',\n        },\n        devDependencies: {\n          'ncu-test-v2': '^2.0.0',\n        },\n      },\n    })\n    upgrades!.should.deep.equal({\n      'ncu-test-v2': '^2.0.0',\n    })\n    stub.restore()\n  })\n\n  // https://github.com/raineorshine/npm-check-updates/issues/1129\n  it('ignore invalid semver version', async () => {\n    const upgrades = await ncu({\n      // needed to cause the npm package handler to use greatest or newest and compare all published versions\n      target: 'minor',\n      packageData: {\n        dependencies: {\n          // grunt-contrib-requirejs contains 0.4.0rc7 which is not valid semver\n          'grunt-contrib-requirejs': '0.3.0',\n        },\n      },\n    })\n    upgrades!.should.haveOwnProperty('grunt-contrib-requirejs')\n  })\n\n  it('ignore file: and link: protocols', async () => {\n    const output = await ncu({\n      packageData: {\n        dependencies: {\n          editor: 'file:../editor',\n          event: 'link:../link',\n        },\n      },\n    })\n    output!.should.deep.equal({})\n  })\n\n  describe('overrides', () => {\n    it('upgrade overrides', async () => {\n      const stub = stubVersions('99.9.9')\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const packageFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(\n        packageFile,\n        JSON.stringify(\n          {\n            dependencies: {\n              'ncu-test-v2': '^1.0.0',\n            },\n            overrides: {\n              'ncu-test-v2': '^1.0.0',\n            },\n          },\n          null,\n          2,\n        ),\n        'utf-8',\n      )\n\n      try {\n        await ncu({ packageFile, upgrade: true })\n\n        const upgradedPkg = JSON.parse(await fs.readFile(packageFile, 'utf-8'))\n        upgradedPkg.should.deep.equal({\n          dependencies: {\n            'ncu-test-v2': '^99.9.9',\n          },\n          overrides: {\n            'ncu-test-v2': '^99.9.9',\n          },\n        })\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n\n    it('upgrade self override', async () => {\n      const stub = stubVersions('99.9.9')\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const packageFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(\n        packageFile,\n        JSON.stringify(\n          {\n            dependencies: {\n              'ncu-test-v2': '^1.0.0',\n            },\n            overrides: {\n              'ncu-test-v2': {\n                '.': '^1.0.0',\n                'ncu-test-tag': '^1.0.0',\n              },\n            },\n          },\n          null,\n          2,\n        ),\n        'utf-8',\n      )\n\n      try {\n        await ncu({ packageFile, upgrade: true })\n\n        const pkgDataNew = await fs.readFile(packageFile, 'utf-8')\n        const upgradedPkg = JSON.parse(pkgDataNew)\n        upgradedPkg.should.deep.equal({\n          dependencies: {\n            'ncu-test-v2': '^99.9.9',\n          },\n          overrides: {\n            'ncu-test-v2': {\n              '.': '^99.9.9',\n              'ncu-test-tag': '^1.0.0',\n            },\n          },\n        })\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n\n    it('upgrade child override', async () => {\n      const stub = stubVersions('99.9.9')\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const packageFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(\n        packageFile,\n        JSON.stringify(\n          {\n            dependencies: {\n              'ncu-test-v2': '^1.0.0',\n            },\n            overrides: {\n              'ncu-test-tag': {\n                'ncu-test-v2': '^1.0.0',\n              },\n            },\n          },\n          null,\n          2,\n        ),\n        'utf-8',\n      )\n\n      try {\n        await ncu({ packageFile, upgrade: true })\n\n        const pkgDataNew = await fs.readFile(packageFile, 'utf-8')\n        const upgradedPkg = JSON.parse(pkgDataNew)\n        upgradedPkg.should.deep.equal({\n          dependencies: {\n            'ncu-test-v2': '^99.9.9',\n          },\n          overrides: {\n            'ncu-test-tag': {\n              'ncu-test-v2': '^99.9.9',\n            },\n          },\n        })\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n\n    it('upgrade nested override', async () => {\n      const stub = stubVersions('99.9.9')\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const packageFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(\n        packageFile,\n        JSON.stringify(\n          {\n            dependencies: {\n              'ncu-test-v2': '^1.0.0',\n            },\n            overrides: {\n              foo: {\n                bar: {\n                  'ncu-test-v2': '^1.0.0',\n                },\n              },\n            },\n          },\n          null,\n          2,\n        ),\n        'utf-8',\n      )\n\n      try {\n        await ncu({ packageFile, upgrade: true })\n\n        const pkgDataNew = await fs.readFile(packageFile, 'utf-8')\n        const upgradedPkg = JSON.parse(pkgDataNew)\n        upgradedPkg.should.deep.equal({\n          dependencies: {\n            'ncu-test-v2': '^99.9.9',\n          },\n          overrides: {\n            foo: {\n              bar: {\n                'ncu-test-v2': '^99.9.9',\n              },\n            },\n          },\n        })\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n  })\n})\n"
  },
  {
    "path": "test/install.test.ts",
    "content": "/* eslint-disable @typescript-eslint/no-unused-expressions */\n// eslint doesn't like .to.be.false syntax\nimport { expect } from 'chai'\nimport fs from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport spawn from 'spawn-please'\nimport exists from '../src/lib/exists'\nimport chaiSetup from './helpers/chaiSetup'\nimport removeDir from './helpers/removeDir'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\nconst bin = path.join(__dirname, '../build/cli.js')\n\ndescribe('install', () => {\n  describe('non-interactive', () => {\n    it('print install hint without --install', async () => {\n      const { default: stripAnsi } = await import('strip-ansi')\n      const pkgData = {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n        },\n      }\n\n      const stub = stubVersions('2.0.0', { spawn: true })\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(pkgFile, JSON.stringify(pkgData), 'utf-8')\n\n      try {\n        const { stdout } = await spawn('node', [bin, '-u', '--packageFile', pkgFile])\n        stripAnsi(stdout).should.match(/Run (npm|yarn) install to install new versions/)\n        expect(await exists(path.join(tempDir, 'package-lock.json'))).to.be.false\n        expect(await exists(path.join(tempDir, 'yarn.lock'))).to.be.false\n        expect(await exists(path.join(tempDir, 'node_modules'))).to.be.false\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n\n    it('install packages and do not print install hint with --install always', async () => {\n      const { default: stripAnsi } = await import('strip-ansi')\n      const pkgData = {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n        },\n      }\n\n      const stub = stubVersions('2.0.0', { spawn: true })\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(pkgFile, JSON.stringify(pkgData), 'utf-8')\n\n      try {\n        const { stdout } = await spawn('node', [bin, '-u', '--packageFile', pkgFile, '--install', 'always'])\n        stripAnsi(stdout).should.not.match(/Run (npm|yarn) install to install new versions/)\n        expect(await exists(path.join(tempDir, 'package-lock.json'))).to.be.true\n        expect(await exists(path.join(tempDir, 'node_modules'))).to.be.true\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n\n    it('do not print install hint with --install never', async () => {\n      const { default: stripAnsi } = await import('strip-ansi')\n      const pkgData = {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n        },\n      }\n\n      const stub = stubVersions('2.0.0', { spawn: true })\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(pkgFile, JSON.stringify(pkgData), 'utf-8')\n\n      try {\n        const { stdout } = await spawn('node', [bin, '-u', '--packageFile', pkgFile, '--install', 'never'])\n        stripAnsi(stdout).should.not.match(/Run (npm|yarn) install to install new versions/)\n        expect(await exists(path.join(tempDir, 'package-lock.json'))).to.be.false\n        expect(await exists(path.join(tempDir, 'yarn.lock'))).to.be.false\n        expect(await exists(path.join(tempDir, 'node_modules'))).to.be.false\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n  })\n\n  describe('interactive', () => {\n    it('install when responding yes to prompt without --install', async () => {\n      const pkgData = {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n        },\n      }\n\n      const stub = stubVersions('2.0.0', { spawn: true })\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(pkgFile, JSON.stringify(pkgData), 'utf-8')\n\n      try {\n        await spawn(\n          'node',\n          [bin, '-iu', '--packageFile', pkgFile],\n          {},\n          {\n            env: {\n              ...process.env,\n              INJECT_PROMPTS: JSON.stringify([['ncu-test-v2'], true]),\n            },\n          },\n        )\n        expect(await exists(path.join(tempDir, 'package-lock.json'))).to.be.true\n        expect(await exists(path.join(tempDir, 'node_modules'))).to.be.true\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n\n    it('do not install when responding no to prompt without --install', async () => {\n      const pkgData = {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n        },\n      }\n\n      const stub = stubVersions('2.0.0', { spawn: true })\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(pkgFile, JSON.stringify(pkgData), 'utf-8')\n\n      try {\n        await spawn(\n          'node',\n          [bin, '-iu', '--packageFile', pkgFile],\n          {},\n          {\n            env: {\n              ...process.env,\n              INJECT_PROMPTS: JSON.stringify([['ncu-test-v2'], false]),\n            },\n          },\n        )\n        expect(await exists(path.join(tempDir, 'package-lock.json'))).to.be.false\n        expect(await exists(path.join(tempDir, 'node_modules'))).to.be.false\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n\n    it('install with --install always', async () => {\n      const pkgData = {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n        },\n      }\n\n      const stub = stubVersions('2.0.0', { spawn: true })\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(pkgFile, JSON.stringify(pkgData), 'utf-8')\n\n      try {\n        await spawn(\n          'node',\n          [bin, '-iu', '--packageFile', pkgFile, '--install', 'always'],\n          {},\n          {\n            env: {\n              ...process.env,\n              // NOTE: We can inject values, but we cannot test if the prompt was actually shown or not.\n              // i.e. Testing that the prompt is not shown with --install always must be done manually.\n              INJECT_PROMPTS: JSON.stringify([['ncu-test-v2']]),\n            },\n          },\n        )\n        expect(await exists(path.join(tempDir, 'package-lock.json'))).to.be.true\n        expect(await exists(path.join(tempDir, 'node_modules'))).to.be.true\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n\n    it('do not install with --install never', async () => {\n      const pkgData = {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n        },\n      }\n\n      const stub = stubVersions('2.0.0', { spawn: true })\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(pkgFile, JSON.stringify(pkgData), 'utf-8')\n\n      try {\n        await spawn(\n          'node',\n          [bin, '-iu', '--packageFile', pkgFile, '--install', 'never'],\n          {},\n          {\n            env: {\n              ...process.env,\n              // NOTE: We can inject values, but we cannot test if the prompt was actually shown or not.\n              // i.e. Testing that the prompt is not shown with --install never must be done manually.\n              INJECT_PROMPTS: JSON.stringify([['ncu-test-v2']]),\n            },\n          },\n        )\n        expect(await exists(path.join(tempDir, 'package-lock.json'))).to.be.false\n        expect(await exists(path.join(tempDir, 'node_modules'))).to.be.false\n      } finally {\n        await removeDir(tempDir)\n        stub.restore()\n      }\n    })\n  })\n})\n"
  },
  {
    "path": "test/interactive.test.ts",
    "content": "import fs from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport spawn from 'spawn-please'\nimport chaiSetup from './helpers/chaiSetup'\nimport removeDir from './helpers/removeDir'\nimport stubVersions from './helpers/stubVersions'\n\nconst should = chaiSetup()\n\nconst bin = path.join(__dirname, '../build/cli.js')\n\ndescribe('--interactive', () => {\n  let stub: { restore: () => void }\n  before(() => {\n    stub = stubVersions(\n      {\n        'ncu-test-v2': '2.0.0',\n        'ncu-test-tag': '1.1.0',\n        'ncu-test-return-version': '2.0.0',\n        // this must be a real version for --format repo to work\n        'modern-diacritics': '2.0.0',\n      },\n      { spawn: true },\n    )\n  })\n  after(() => {\n    stub.restore()\n  })\n\n  it('prompt for each upgraded dependency', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(\n      pkgFile,\n      JSON.stringify({\n        dependencies: { 'ncu-test-v2': '1.0.0', 'ncu-test-return-version': '1.0.0', 'ncu-test-tag': '1.0.0' },\n      }),\n      'utf-8',\n    )\n    try {\n      const { stdout } = await spawn(\n        'node',\n        [bin, '--interactive'],\n        {},\n        {\n          cwd: tempDir,\n          env: {\n            ...process.env,\n            INJECT_PROMPTS: JSON.stringify([['ncu-test-v2', 'ncu-test-return-version'], true]),\n          },\n        },\n      )\n\n      should.equal(/^Upgrading/m.test(stdout), true)\n\n      // do not show install hint when choosing auto-install\n      should.equal(/^Run npm install to install new versions.$/m.test(stdout), false)\n\n      const upgradedPkg = JSON.parse(await fs.readFile(pkgFile, 'utf-8'))\n      upgradedPkg.dependencies.should.deep.equal({\n        // upgraded\n        'ncu-test-v2': '2.0.0',\n        'ncu-test-return-version': '2.0.0',\n        // no upgraded\n        'ncu-test-tag': '1.0.0',\n      })\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('with --format group', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(\n      pkgFile,\n      JSON.stringify({\n        dependencies: { 'ncu-test-v2': '1.0.0', 'ncu-test-return-version': '1.0.0', 'ncu-test-tag': '1.0.0' },\n      }),\n      'utf-8',\n    )\n    try {\n      await spawn(\n        'node',\n        [bin, '--interactive', '--format', 'group'],\n        {},\n        {\n          cwd: tempDir,\n          env: {\n            ...process.env,\n            INJECT_PROMPTS: JSON.stringify([['ncu-test-v2', 'ncu-test-return-version'], true]),\n          },\n        },\n      )\n\n      const upgradedPkg = JSON.parse(await fs.readFile(pkgFile, 'utf-8'))\n      upgradedPkg.dependencies.should.deep.equal({\n        // upgraded\n        'ncu-test-v2': '2.0.0',\n        'ncu-test-return-version': '2.0.0',\n        // no upgraded\n        'ncu-test-tag': '1.0.0',\n      })\n\n      // prompts does not print during injection, so we cannot assert the output in interactive mode\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('with --format group and custom group function', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(\n      pkgFile,\n      JSON.stringify({\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n          'ncu-test-return-version': '1.0.0',\n          'ncu-test-tag': '1.0.0',\n        },\n      }),\n      'utf-8',\n    )\n    const configFile = path.join(tempDir, '.ncurc.js')\n    await fs.writeFile(configFile, `module.exports = { groupFunction: () => 'minor' }`, 'utf-8')\n    try {\n      await spawn(\n        'node',\n        [bin, '--interactive', '--format', 'group', '--configFilePath', tempDir],\n        {},\n        {\n          cwd: tempDir,\n          env: {\n            ...process.env,\n            INJECT_PROMPTS: JSON.stringify([['ncu-test-v2', 'ncu-test-return-version'], true]),\n          },\n        },\n      )\n\n      const upgradedPkg = JSON.parse(await fs.readFile(pkgFile, 'utf-8'))\n      upgradedPkg.dependencies.should.deep.equal({\n        // upgraded\n        'ncu-test-v2': '2.0.0',\n        'ncu-test-return-version': '2.0.0',\n        // no upgraded\n        'ncu-test-tag': '1.0.0',\n      })\n\n      // prompts does not print during injection, so we cannot assert the output in interactive mode\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('with --format repo', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(\n      pkgFile,\n      JSON.stringify({\n        dependencies: {\n          'modern-diacritics': '^1.0.0',\n        },\n      }),\n      'utf-8',\n    )\n    try {\n      await spawn('npm', ['install'], {}, { cwd: tempDir })\n      const { stdout } = await spawn(\n        'node',\n        [bin, '--interactive', '--format', 'repo'],\n        {},\n        {\n          cwd: tempDir,\n          env: {\n            ...process.env,\n            INJECT_PROMPTS: JSON.stringify([['modern-diacritics'], true]),\n          },\n        },\n      )\n\n      stdout.should.include('https://github.com/Mitsunee/modern-diacritics')\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n})\n"
  },
  {
    "path": "test/isUpgradeable.test.ts",
    "content": "import isUpgradeable from '../src/lib/isUpgradeable'\nimport chaiSetup from './helpers/chaiSetup'\n\nchaiSetup()\n\ndescribe('isUpgradeable', () => {\n  it('do not upgrade pure wildcards', () => {\n    isUpgradeable('*', '0.5.1').should.equal(false)\n  })\n\n  it('upgrade versions that do not satisfy latest versions', () => {\n    isUpgradeable('0.1.x', '0.5.1').should.equal(true)\n  })\n\n  it('do not upgrade invalid versions', () => {\n    isUpgradeable('https://github.com/strongloop/express', '4.11.2').should.equal(false)\n  })\n\n  it('do not upgrade versions beyond the latest', () => {\n    isUpgradeable('5.0.0', '4.11.2').should.equal(false)\n  })\n\n  it('handle comparison constraints', () => {\n    isUpgradeable('>1.0', '0.5.1').should.equal(false)\n    isUpgradeable('<3.0 >0.1', '0.5.1').should.equal(false)\n    isUpgradeable('>0.1.x', '0.5.1').should.equal(true)\n    isUpgradeable('<7.0.0', '7.2.0').should.equal(true)\n    isUpgradeable('<7.0', '7.2.0').should.equal(true)\n    isUpgradeable('<7', '7.2.0').should.equal(true)\n  })\n\n  it('upgrade simple versions', () => {\n    isUpgradeable('v1', 'v2').should.equal(true)\n  })\n})\n"
  },
  {
    "path": "test/package-managers/deno/index.test.ts",
    "content": "import fs from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\nimport spawn from 'spawn-please'\nimport parseJson from '../../../src/lib/utils/parseJson'\nimport chaiSetup from '../../helpers/chaiSetup'\nimport removeDir from '../../helpers/removeDir'\n\nchaiSetup()\n\nconst bin = path.join(__dirname, '../../../build/cli.js')\n\ndescribe('deno', async function () {\n  it('handle import map', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'deno.json')\n    const pkg = {\n      imports: {\n        'ncu-test-v2': 'npm:ncu-test-v2@1.0.0',\n      },\n    }\n    await fs.writeFile(pkgFile, JSON.stringify(pkg))\n    try {\n      const { stdout } = await spawn('node', [\n        bin,\n        '--jsonUpgraded',\n        '--packageManager',\n        'deno',\n        '--packageFile',\n        pkgFile,\n      ])\n      const pkg = parseJson(stdout)\n      pkg.should.have.property('ncu-test-v2')\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('auto detect deno.json', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'deno.json')\n    const pkg = {\n      imports: {\n        'ncu-test-v2': 'npm:ncu-test-v2@1.0.0',\n      },\n    }\n    await fs.writeFile(pkgFile, JSON.stringify(pkg))\n    try {\n      const { stdout } = await spawn('node', [bin, '--jsonUpgraded'], undefined, {\n        cwd: tempDir,\n      })\n      const pkg = parseJson(stdout)\n      pkg.should.have.property('ncu-test-v2')\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('rewrite deno.json', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'deno.json')\n    const pkg = {\n      imports: {\n        'ncu-test-v2': 'npm:ncu-test-v2@1.0.0',\n      },\n    }\n    await fs.writeFile(pkgFile, JSON.stringify(pkg))\n    try {\n      await spawn('node', [bin, '-u'], undefined, { cwd: tempDir })\n      const pkgDataNew = await fs.readFile(pkgFile, 'utf-8')\n      const pkg = parseJson(pkgDataNew)\n      pkg.should.deep.equal({\n        imports: {\n          'ncu-test-v2': 'npm:ncu-test-v2@2.0.0',\n        },\n      })\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('auto detect deno.jsonc', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'deno.jsonc')\n    const pkgString = `{\n  \"imports\": {\n    // this comment should be ignored in a jsonc file\n    \"ncu-test-v2\": \"npm:ncu-test-v2@1.0.0\"\n  }\n}`\n    await fs.writeFile(pkgFile, pkgString)\n    try {\n      const { stdout } = await spawn('node', [bin, '--jsonUpgraded'], undefined, {\n        cwd: tempDir,\n      })\n      const pkg = parseJson(stdout)\n      pkg.should.have.property('ncu-test-v2')\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('rewrite deno.jsonc', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const pkgFile = path.join(tempDir, 'deno.jsonc')\n    const pkg = {\n      imports: {\n        'ncu-test-v2': 'npm:ncu-test-v2@1.0.0',\n      },\n    }\n    await fs.writeFile(pkgFile, JSON.stringify(pkg))\n    try {\n      await spawn('node', [bin, '-u'], undefined, { cwd: tempDir })\n      const pkgDataNew = await fs.readFile(pkgFile, 'utf-8')\n      const pkg = parseJson(pkgDataNew)\n      pkg.should.deep.equal({\n        imports: {\n          'ncu-test-v2': 'npm:ncu-test-v2@2.0.0',\n        },\n      })\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n})\n"
  },
  {
    "path": "test/package-managers/npm/index.test.ts",
    "content": "import * as npm from '../../../src/package-managers/npm'\nimport chaiSetup from '../../helpers/chaiSetup'\n\nchaiSetup()\n\ndescribe('npm', function () {\n  it('list', async () => {\n    const versionObject = await npm.list({ cwd: __dirname })\n    versionObject.should.have.property('express')\n  })\n\n  it('latest', async () => {\n    const { version } = await npm.latest('express', '', { cwd: __dirname })\n    parseInt(version!, 10).should.be.above(1)\n  })\n\n  it('greatest', async () => {\n    const { version } = await npm.greatest('ncu-test-greatest-not-newest', '', { pre: true, cwd: __dirname })\n    version!.should.equal('2.0.0-beta')\n  })\n\n  it('ownerChanged', async () => {\n    await npm.packageAuthorChanged('mocha', '^7.1.0', '8.0.1').should.eventually.equal(true)\n    await npm.packageAuthorChanged('htmlparser2', '^3.10.1', '^4.0.0').should.eventually.equal(false)\n    await npm.packageAuthorChanged('ncu-test-v2', '^1.0.0', '2.2.0').should.eventually.equal(false)\n  })\n\n  it('getPeerDependencies', async () => {\n    const spawnOptions = { cwd: __dirname }\n    await npm.getPeerDependencies('ncu-test-return-version', '1.0.0', spawnOptions).should.eventually.deep.equal({})\n    await npm.getPeerDependencies('ncu-test-peer', '1.0.0', spawnOptions).should.eventually.deep.equal({\n      'ncu-test-return-version': '1.x',\n    })\n  })\n\n  it('getEngines', async () => {\n    await npm.getEngines('del', '2.0.0').should.eventually.deep.equal({ node: '>=0.10.0' })\n    await npm.getEngines('ncu-test-return-version', '1.0.0').should.eventually.deep.equal({})\n    await npm\n      .getEngines('ncu-test-return-version', '1.0')\n      .should.eventually.be.rejectedWith('404 Not Found - GET https://registry.npmjs.org/ncu-test-return-version/1.0')\n  })\n})\n"
  },
  {
    "path": "test/package-managers/npm/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"express\": \"^1.0.0\"\n  }\n}\n"
  },
  {
    "path": "test/package-managers/yarn/default/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"packageManager\": \"yarn@1.22.22\",\n  \"dependencies\": {\n    \"chalk\": \"^3.0.0\"\n  }\n}\n"
  },
  {
    "path": "test/package-managers/yarn/index.test.ts",
    "content": "import path from 'path'\nimport * as yarn from '../../../src/package-managers/yarn'\nimport { getPathToLookForYarnrc } from '../../../src/package-managers/yarn'\nimport chaiSetup from '../../helpers/chaiSetup'\n\nconst should = chaiSetup()\n\nconst isWindows = process.platform === 'win32'\n\n// append the local node_modules bin directory to process.env.PATH so local yarn is used during tests\nconst localBin = path.resolve(__dirname.replace('build/', ''), '../../../node_modules/.bin')\nconst localYarnSpawnOptions = {\n  env: {\n    ...process.env,\n    PATH: `${process.env.PATH}:${localBin}`,\n  },\n}\n\nconst filteredPath = (process.env.PATH || '')\n  .split(path.delimiter)\n  .filter(p => !p.includes(path.join('node_modules', '.bin'))) // Avoid running yarn form the node module bin\n  .join(path.delimiter)\nconst cleanEnv = {\n  ...process.env,\n  PATH: filteredPath,\n}\n\ndescribe('yarn', function () {\n  it('list', async () => {\n    const testDir = path.join(__dirname, 'default')\n    const { version } = await yarn.latest('chalk', '', { cwd: testDir })\n    parseInt(version!, 10).should.be.above(3)\n  })\n\n  it('latest', async () => {\n    const testDir = path.join(__dirname, 'default')\n    const { version } = await yarn.latest('chalk', '', { cwd: testDir })\n    parseInt(version!, 10).should.be.above(3)\n  })\n\n  it('greatest', async () => {\n    const { version } = await yarn.greatest('ncu-test-greatest-not-newest', '', { pre: true, cwd: __dirname })\n    version!.should.equal('2.0.0-beta')\n  })\n\n  it('avoids deprecated', async () => {\n    const testDir = path.join(__dirname, 'default')\n    const { version } = await yarn.minor('popper.js', '1.15.0', { cwd: testDir, pre: true })\n    version!.should.equal('1.16.1-lts')\n  })\n\n  it('\"No lockfile\" error should be thrown on list command when there is no lockfile', async () => {\n    const testDir = path.join(__dirname, 'nolockfile')\n    const lockFileErrorMessage = 'No lockfile in this directory. Run `yarn install` to generate one.'\n    await yarn.list({ cwd: testDir }, localYarnSpawnOptions).should.eventually.be.rejectedWith(lockFileErrorMessage)\n  })\n\n  it('getPeerDependencies v1', async () => {\n    const testDir = path.join(__dirname, 'default')\n    const spawnOptions = { cwd: testDir, env: cleanEnv }\n    await yarn.getPeerDependencies('ncu-test-return-version', '1.0.0', spawnOptions).should.eventually.deep.equal({})\n    await yarn.getPeerDependencies('ncu-test-peer', '1.0.0', spawnOptions).should.eventually.deep.equal({\n      'ncu-test-return-version': '1.x',\n    })\n    await yarn.getPeerDependencies('fffffffffffff', '1.0.0', spawnOptions).should.eventually.deep.equal({})\n  })\n\n  it('getPeerDependencies v4', async () => {\n    const testDir = path.join(__dirname, 'v4')\n    const spawnOptions = { cwd: testDir, env: cleanEnv }\n    await yarn.getPeerDependencies('ncu-test-return-version', '1.0.0', spawnOptions).should.eventually.deep.equal({})\n    await yarn.getPeerDependencies('ncu-test-peer', '1.0.0', spawnOptions).should.eventually.deep.equal({\n      'ncu-test-return-version': '1.x',\n    })\n    await yarn.getPeerDependencies('fffffffffffff', '1.0.0', spawnOptions).should.eventually.deep.equal({})\n  })\n\n  describe('npmAuthTokenKeyValue', () => {\n    it('npmRegistryServer with trailing slash', () => {\n      const authToken = yarn.npmAuthTokenKeyValue({}, 'fortawesome', {\n        npmAlwaysAuth: true,\n        npmAuthToken: 'MY-AUTH-TOKEN',\n        npmRegistryServer: 'https://npm.fontawesome.com/',\n      })\n\n      authToken!.should.deep.equal({\n        '//npm.fontawesome.com/:_authToken': 'MY-AUTH-TOKEN',\n      })\n    })\n\n    it('npmRegistryServer without trailing slash', () => {\n      const authToken = yarn.npmAuthTokenKeyValue({}, 'fortawesome', {\n        npmAlwaysAuth: true,\n        npmAuthToken: 'MY-AUTH-TOKEN',\n        npmRegistryServer: 'https://npm.fontawesome.com',\n      })\n\n      authToken!.should.deep.equal({\n        '//npm.fontawesome.com/:_authToken': 'MY-AUTH-TOKEN',\n      })\n    })\n\n    it('returns null when no npmAlwaysAuth', () => {\n      const authToken = yarn.npmAuthTokenKeyValue({}, 'fortawesome', {\n        npmAlwaysAuth: true,\n        // undefined: npmAuthToken: 'MY-AUTH-TOKEN',\n        npmRegistryServer: 'https://npm.fontawesome.com/',\n      })\n\n      should.equal(authToken, null)\n    })\n\n    it('returns null when no registry server', () => {\n      const authToken = yarn.npmAuthTokenKeyValue({}, 'fortawesome', {\n        npmAlwaysAuth: true,\n        npmAuthToken: 'MY-AUTH-TOKEN',\n        // undefined: npmRegistryServer: 'https://npm.fontawesome.com/',\n      })\n\n      should.equal(authToken, null)\n    })\n  })\n\n  describe('getPathToLookForLocalYarnrc', () => {\n    it('returns the correct path when using Yarn workspaces', async () => {\n      /** Mock for filesystem calls. */\n      function readdirMock(path: string): Promise<string[]> {\n        switch (path) {\n          case '/home/test-repo/packages/package-a':\n          case 'C:\\\\home\\\\test-repo\\\\packages\\\\package-a':\n            return Promise.resolve(['index.ts'])\n          case '/home/test-repo/packages':\n          case 'C:\\\\home\\\\test-repo\\\\packages':\n            return Promise.resolve([])\n          case '/home/test-repo':\n          case 'C:\\\\home\\\\test-repo':\n            return Promise.resolve(['yarn.lock'])\n        }\n\n        throw new Error(`Mock cannot handle path: ${path}.`)\n      }\n\n      const yarnrcPath = await getPathToLookForYarnrc(\n        {\n          cwd: isWindows ? 'C:\\\\home\\\\test-repo\\\\packages\\\\package-a' : '/home/test-repo/packages/package-a',\n        },\n        readdirMock,\n      )\n\n      should.exist(yarnrcPath)\n      yarnrcPath!.should.equal(isWindows ? 'C:\\\\home\\\\test-repo\\\\.yarnrc.yml' : '/home/test-repo/.yarnrc.yml')\n    })\n  })\n})\n"
  },
  {
    "path": "test/package-managers/yarn/nolockfile/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"chalk\": \"^3.0.0\"\n  }\n}\n"
  },
  {
    "path": "test/package-managers/yarn/v4/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"packageManager\": \"yarn@4.9.2\",\n  \"dependencies\": {\n    \"chalk\": \"^3.0.0\"\n  }\n}\n"
  },
  {
    "path": "test/parseJson.test.ts",
    "content": "import parseJson from '../src/lib/utils/parseJson'\nimport chaiSetup from './helpers/chaiSetup'\n\nchaiSetup()\n\ndescribe('parseJson', async function () {\n  it('handles valid json strings', async () => {\n    parseJson('{}').should.deep.equal({})\n    parseJson('{\"name\": \"John\", \"age\": 30, \"city\": \"New York\"}').should.deep.equal({\n      name: 'John',\n      age: 30,\n      city: 'New York',\n    })\n  })\n\n  it('handles valid jsonc strings', () => {\n    const string1 = `\n  {\n    \"name\": \"John\",\n    // Now the age\n    \"age\": 30\n}\n`\n    const string2 = `\n      {\n        \"a\": \"b\",\n        /**\n         *  Here could be some very important comment, but there is none.\n         */\n        \"c\": [\"d\", \"e\", \"f\"]\n    }\n    `\n    parseJson(string1).should.deep.equal({ name: 'John', age: 30 })\n    parseJson(string2).should.deep.equal({ a: 'b', c: ['d', 'e', 'f'] })\n  })\n\n  it('shows descriptive and helpful error messages', () => {\n    ;(() => parseJson('{\"name\": \"John\", \"age\": 30, \"city\": \"New York\"')).should.throw(SyntaxError)\n    ;(() => parseJson('{\"name\": \"John\", \"age\": 30, \"city\": \"New York\"')).should.throw(\n      SyntaxError,\n      'Error at line 1, column 47: CloseBraceExpected\\n{\"name\": \"John\", \"age\": 30, \"city\": \"New York\"\\n                                              ^\\n\\n',\n    )\n  })\n\n  it('shows a snippet of code surrounded by 4 surrounding lines', () => {\n    const string = `{\n      \"test\": {\n          \"a\": test\n        }\n  }`\n    ;(() => parseJson(string)).should.throw(\n      SyntaxError,\n      `Error at line 3, column 16: InvalidSymbol\n{\n      \"test\": {\n          \"a\": test\n               ^\n        }\n  }\n\n\nError at line 4, column 9: ValueExpected\n      \"test\": {\n          \"a\": test\n        }\n        ^\n  }\n\n`,\n    )\n  })\n\n  it('show an empty line hint', () => {\n    // This string misses the last bracket, but since the line is '', it would show nothing.\n    const string = `{\n      \"test\": {\n          \"a\": 5\n        }\n`\n    ;(() => parseJson(string)).should.throw(\n      SyntaxError,\n      `Error at line 5, column 1: CloseBraceExpected\n          \"a\": 5\n        }\n<empty>\n^`,\n    )\n  })\n})\n"
  },
  {
    "path": "test/peer.test.ts",
    "content": "import path from 'path'\nimport ncu from '../src/'\nimport { Packument } from '../src/types/Packument'\nimport chaiSetup from './helpers/chaiSetup'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\ndescribe('peer dependencies', function () {\n  it('peer dependencies are ignored by default', async () => {\n    const upgrades = await ncu({\n      packageData: {\n        dependencies: {\n          'ncu-test-peer': '1.0.0',\n          'ncu-test-return-version': '1.0.0',\n        },\n      },\n    })\n    upgrades!.should.deep.equal({\n      'ncu-test-return-version': '2.0.0',\n    })\n  })\n\n  it('peer dependencies are checked when using option peer', async () => {\n    const upgrades = await ncu({\n      peer: true,\n      packageData: {\n        dependencies: {\n          'ncu-test-peer': '1.0.0',\n          'ncu-test-return-version': '1.0.0',\n        },\n      },\n    })\n    upgrades!.should.deep.equal({\n      'ncu-test-return-version': '1.1.0',\n    })\n  })\n\n  it('peer dependencies are checked iteratively when using option peer', async () => {\n    const upgrades = await ncu({\n      peer: true,\n      packageData: {\n        dependencies: {\n          'ncu-test-peer-update': '1.0.0',\n          'ncu-test-return-version': '1.0.0',\n        },\n      },\n    })\n    upgrades!.should.deep.equal({\n      'ncu-test-return-version': '1.1.0',\n      'ncu-test-peer-update': '1.1.0',\n    })\n  })\n\n  it('circular peer dependencies are ignored', async () => {\n    const upgrades = await ncu({\n      peer: true,\n      packageData: {\n        dependencies: {\n          '@vitest/ui': '^1.3.1',\n          vitest: '^1.3.1',\n        },\n      },\n    })\n    upgrades!.should.contain.keys('@vitest/ui', 'vitest')\n  })\n\n  // https://github.com/raineorshine/npm-check-updates/issues/1437\n  it('git urls are ignored', async () => {\n    const upgrades = await ncu({\n      peer: true,\n      packageData: {\n        dependencies: {\n          '@libraries/project-4-utils': 'git+gitlab.com/projects/libraries/project-4-utils.git',\n        },\n      },\n    })\n    upgrades!.should.deep.equal({})\n  })\n\n  it('ignores if post upgrade peers are unmet', async () => {\n    const stub = stubVersions({\n      '@vitest/ui': {\n        version: '1.6.0',\n        versions: {\n          '1.3.1': {\n            version: '1.3.1',\n          } as Packument,\n          '1.6.0': {\n            version: '1.6.0',\n          } as Packument,\n        },\n      },\n      vitest: {\n        version: '1.6.0',\n        versions: {\n          '1.3.1': {\n            version: '1.3.1',\n          } as Packument,\n          '1.6.0': {\n            version: '1.6.0',\n          } as Packument,\n        },\n      },\n      eslint: {\n        version: '9.0.0',\n        versions: {\n          '8.57.0': {\n            version: '8.57.0',\n          } as Packument,\n          '9.0.0': {\n            version: '9.0.0',\n          } as Packument,\n        },\n      },\n      'eslint-plugin-import': {\n        version: '2.29.1',\n        versions: {\n          '2.29.1': {\n            version: '2.29.1',\n          } as Packument,\n        },\n      },\n      'eslint-plugin-unused-imports': {\n        version: '4.0.0',\n        versions: {\n          '4.0.0': {\n            version: '4.0.0',\n          } as Packument,\n          '3.0.0': {\n            version: '3.0.0',\n          } as Packument,\n        },\n      },\n    })\n    const cwd = path.join(__dirname, 'test-data/peer-post-upgrade/')\n    const upgrades = await ncu({\n      cwd,\n      peer: true,\n      target: packageName => {\n        return packageName === 'eslint-plugin-unused-imports' ? 'greatest' : 'minor'\n      },\n    })\n    upgrades!.should.have.all.keys('@vitest/ui', 'vitest')\n    stub.restore()\n  })\n\n  it('ignores if post upgrade peers are unmet - no upgrades', async () => {\n    const stub = stubVersions({\n      eslint: {\n        version: '9.0.0',\n        versions: {\n          '8.57.0': {\n            version: '8.57.0',\n          } as Packument,\n          '9.0.0': {\n            version: '9.0.0',\n          } as Packument,\n        },\n      },\n      'eslint-plugin-import': {\n        version: '2.29.1',\n        versions: {\n          '2.29.1': {\n            version: '2.29.1',\n          } as Packument,\n        },\n      },\n      'eslint-plugin-unused-imports': {\n        version: '4.0.0',\n        versions: {\n          '4.0.0': {\n            version: '4.0.0',\n          } as Packument,\n          '3.0.0': {\n            version: '3.0.0',\n          } as Packument,\n        },\n      },\n    })\n    const cwd = path.join(__dirname, 'test-data/peer-post-upgrade-no-upgrades/')\n    const upgrades = await ncu({\n      cwd,\n      peer: true,\n      target: packageName => {\n        return packageName === 'eslint-plugin-unused-imports' ? 'greatest' : 'minor'\n      },\n    })\n    upgrades!.should.deep.equal({})\n    stub.restore()\n  })\n})\n"
  },
  {
    "path": "test/queryVersions.test.ts",
    "content": "import queryVersions from '../src/lib/queryVersions'\nimport chaiSetup from './helpers/chaiSetup'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\ndescribe('queryVersions', function () {\n  it('valid single package', async () => {\n    const stub = stubVersions('99.9.9')\n    const latestVersions = await queryVersions({ async: '1.5.1' }, { loglevel: 'silent' })\n    latestVersions.should.have.property('async')\n    stub.restore()\n  })\n\n  it('valid packages', async () => {\n    const stub = stubVersions('99.9.9')\n    const latestVersions = await queryVersions({ async: '1.5.1', npm: '3.10.3' }, { loglevel: 'silent' })\n    latestVersions.should.have.property('async')\n    latestVersions.should.have.property('npm')\n    stub.restore()\n  })\n\n  it('unavailable packages should be ignored', async () => {\n    const result = await queryVersions({ abchdefntofknacuifnt: '1.2.3' }, { loglevel: 'silent' })\n    result.should.deep.equal({})\n  })\n\n  it('error while querying version should be handled', async () => {\n    const stub = stubVersions(() => {\n      throw new Error(`Package inaccessible`)\n    })\n\n    const result = await queryVersions({ async: '1.5.1' }, { loglevel: 'silent' })\n    result.should.deep.equal({\n      async: {\n        error: 'Error: Package inaccessible',\n        version: null,\n      },\n    })\n\n    stub.restore()\n  })\n\n  it('local file urls should be ignored', async () => {\n    const result = await queryVersions(\n      { 'eslint-plugin-internal': 'file:devtools/eslint-rules' },\n      { loglevel: 'silent' },\n    )\n    result.should.deep.equal({})\n  })\n\n  it('set the target explicitly to latest', async () => {\n    const stub = stubVersions('99.9.9')\n    const result = await queryVersions({ async: '1.5.1' }, { target: 'latest', loglevel: 'silent' })\n    result.should.have.property('async')\n    stub.restore()\n  })\n\n  it('set the target to greatest', async () => {\n    const stub = stubVersions('99.9.9')\n    const result = await queryVersions({ async: '1.5.1' }, { target: 'greatest', loglevel: 'silent' })\n    result.should.have.property('async')\n    stub.restore()\n  })\n\n  it('return an error for an unsupported target', () => {\n    const a = queryVersions({ async: '1.5.1' }, { target: 'foo', loglevel: 'silent' } as any)\n    return a.should.be.rejected\n  })\n\n  it('npm aliases should upgrade the installed package', async () => {\n    const result = await queryVersions(\n      {\n        request: 'npm:ncu-test-v2@1.0.0',\n      },\n      { loglevel: 'silent' },\n    )\n    result.should.deep.equal({\n      request: {\n        version: 'npm:ncu-test-v2@2.0.0',\n      },\n    })\n  })\n\n  describe('github urls', () => {\n    it('github urls should upgrade the embedded version tag', async () => {\n      const upgrades = await queryVersions(\n        {\n          'ncu-test-v2': 'https://github.com/raineorshine/ncu-test-v2#v1.0.0',\n        },\n        { loglevel: 'silent' },\n      )\n\n      upgrades.should.deep.equal({\n        'ncu-test-v2': {\n          version: 'https://github.com/raineorshine/ncu-test-v2#v2.0.0',\n        },\n      })\n    })\n\n    it('short github urls should be ignored', async () => {\n      const upgrades = await queryVersions(\n        {\n          'ncu-test-v2': 'raineorshine/ncu-test-v2',\n        },\n        { loglevel: 'silent' },\n      )\n\n      upgrades.should.deep.equal({})\n    })\n\n    it('git+https urls should upgrade the embedded version tag', async () => {\n      const upgrades = await queryVersions(\n        {\n          'ncu-test-v2': 'git+https://github.com/raineorshine/ncu-test-v2#v1.0.0',\n        },\n        { loglevel: 'silent' },\n      )\n\n      upgrades.should.deep.equal({\n        'ncu-test-v2': {\n          version: 'git+https://github.com/raineorshine/ncu-test-v2#v2.0.0',\n        },\n      })\n    })\n\n    it('ignore tags that are not valid versions', async () => {\n      // this repo has tag \"1.0\" which is not a valid version\n      const upgrades1 = await queryVersions(\n        {\n          'ncu-test-invalid-tag': 'raineorshine/ncu-test-invalid-tag.git#v3.0.0',\n        },\n        { loglevel: 'silent' },\n      )\n\n      upgrades1.should.deep.equal({\n        'ncu-test-invalid-tag': {\n          version: 'raineorshine/ncu-test-invalid-tag.git#v3.0.5',\n        },\n      })\n\n      // this repo has tag \"v0.1.3a\" which is not a valid version\n      const upgrades2 = await queryVersions(\n        {\n          'angular-toasty': 'git+https://github.com/raineorshine/ncu-test-v0.1.3a.git#1.0.0',\n        },\n        { loglevel: 'silent' },\n      )\n\n      upgrades2.should.deep.equal({\n        'angular-toasty': {\n          version: 'git+https://github.com/raineorshine/ncu-test-v0.1.3a.git#1.0.7',\n        },\n      })\n    })\n\n    it('support simple, non-semver tags in the format \"v1\"', async () => {\n      const upgrades = await queryVersions(\n        {\n          // this repo has tag \"1.0\" which is not valid semver\n          'ncu-test-invalid-tag': 'git+https://github.com/raineorshine/ncu-test-simple-tag#v1',\n        },\n        { loglevel: 'silent' },\n      )\n\n      upgrades.should.deep.equal({\n        'ncu-test-invalid-tag': {\n          version: 'git+https://github.com/raineorshine/ncu-test-simple-tag#v3',\n        },\n      })\n    })\n\n    it('ignore repos with no tags', async () => {\n      const upgrades = await queryVersions(\n        {\n          // this repo has tag \"1.0\" which is not valid semver\n          'ncu-test-invalid-tag': 'git+https://github.com/raineorshine/ncu-test-no-tags#v1',\n        },\n        { loglevel: 'silent' },\n      )\n      upgrades.should.deep.equal({})\n    })\n\n    it('valid but nonexistent github urls with tags should be ignored', async () => {\n      const upgrades = await queryVersions(\n        {\n          'ncu-test-alpha': 'git+https://username:dh9dnas0nndnjnjasd4@bitbucket.org/somename/common.git#v283',\n          // disable since fetching from a private repo prompts the user for credentials\n          // 'ncu-test-private': 'https://github.com/ncu-test/ncu-test-private#v999.9.9',\n          'ncu-return-version': 'git+https://raineorshine@github.com/ncu-return-version#v999.9.9',\n          'ncu-test-v2': '^1.0.0',\n        },\n        { loglevel: 'silent' },\n      )\n\n      upgrades.should.deep.equal({\n        'ncu-test-v2': {\n          version: '2.0.0',\n        },\n      })\n    })\n\n    it('github urls should upgrade the embedded semver version range', async () => {\n      const upgrades = await queryVersions(\n        {\n          'ncu-test-v2': 'https://github.com/raineorshine/ncu-test-v2#semver:^1.0.0',\n        },\n        { loglevel: 'silent' },\n      )\n\n      upgrades.should.deep.equal({\n        'ncu-test-v2': {\n          version: 'https://github.com/raineorshine/ncu-test-v2#semver:^2.0.0',\n        },\n      })\n    })\n\n    it('github urls should support --target greatest', async () => {\n      const upgrades = await queryVersions(\n        {\n          'ncu-test-greatest-not-newest': 'https://github.com/raineorshine/ncu-test-greatest-not-newest#semver:^1.0.0',\n        },\n        { loglevel: 'silent', target: 'newest' },\n      )\n\n      upgrades.should.deep.equal({\n        'ncu-test-greatest-not-newest': {\n          version: 'https://github.com/raineorshine/ncu-test-greatest-not-newest#semver:^2.0.0-beta',\n        },\n      })\n    })\n\n    it('github urls should support --target newest', async () => {\n      const upgrades = await queryVersions(\n        {\n          'ncu-test-greatest-not-newest': 'https://github.com/raineorshine/ncu-test-greatest-not-newest#semver:^1.0.0',\n        },\n        { loglevel: 'silent', target: 'newest' },\n      )\n\n      upgrades.should.deep.equal({\n        'ncu-test-greatest-not-newest': {\n          version: 'https://github.com/raineorshine/ncu-test-greatest-not-newest#semver:^2.0.0-beta',\n        },\n      })\n    })\n\n    it('github urls should support --target minor', async () => {\n      const upgrades = await queryVersions(\n        {\n          'ncu-test-return-version': 'https://github.com/raineorshine/ncu-test-return-version#semver:^0.1.0',\n        },\n        { loglevel: 'silent', target: 'minor' },\n      )\n\n      upgrades.should.deep.equal({\n        'ncu-test-return-version': {\n          version: 'https://github.com/raineorshine/ncu-test-return-version#semver:^0.2.0',\n        },\n      })\n    })\n\n    it('github urls should support --target patch', async () => {\n      const upgrades = await queryVersions(\n        {\n          'ncu-test-return-version': 'https://github.com/raineorshine/ncu-test-return-version#semver:^1.0.0',\n        },\n        { loglevel: 'silent', target: 'patch' },\n      )\n\n      upgrades.should.deep.equal({\n        'ncu-test-return-version': {\n          version: 'https://github.com/raineorshine/ncu-test-return-version#semver:^1.0.1',\n        },\n      })\n    })\n\n    it('github urls should not upgrade embedded semver version ranges to prereleases by default', async () => {\n      const upgrades = await queryVersions(\n        {\n          'ncu-test-greatest-not-newest': 'https://github.com/raineorshine/ncu-test-greatest-not-newest#semver:^1.0.0',\n        },\n        { loglevel: 'silent' },\n      )\n\n      upgrades.should.deep.equal({\n        'ncu-test-greatest-not-newest': {\n          version: 'https://github.com/raineorshine/ncu-test-greatest-not-newest#semver:^1.0.1',\n        },\n      })\n    })\n\n    it('github urls should upgrade embedded semver version ranges to prereleases with --target greatest and newest', async () => {\n      const upgradesNewest = await queryVersions(\n        {\n          'ncu-test-greatest-not-newest': 'https://github.com/raineorshine/ncu-test-greatest-not-newest#semver:^1.0.0',\n        },\n        { loglevel: 'silent', target: 'newest' },\n      )\n\n      upgradesNewest.should.deep.equal({\n        'ncu-test-greatest-not-newest': {\n          version: 'https://github.com/raineorshine/ncu-test-greatest-not-newest#semver:^2.0.0-beta',\n        },\n      })\n\n      const upgradesGreatest = await queryVersions(\n        {\n          'ncu-test-greatest-not-newest': 'https://github.com/raineorshine/ncu-test-greatest-not-newest#semver:^1.0.0',\n        },\n        { loglevel: 'silent', target: 'greatest' },\n      )\n\n      upgradesGreatest.should.deep.equal({\n        'ncu-test-greatest-not-newest': {\n          version: 'https://github.com/raineorshine/ncu-test-greatest-not-newest#semver:^2.0.0-beta',\n        },\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "test/rc-config.test.ts",
    "content": "import fs from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport spawn from 'spawn-please'\nimport chaiSetup from './helpers/chaiSetup'\nimport removeDir from './helpers/removeDir'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\nconst bin = path.join(__dirname, '../build/cli.js')\n\ndescribe('rc-config', () => {\n  // before/after must be placed within the describe block; otherwise, they will apply to tests in other files\n  let stub: { restore: () => void }\n  before(() => (stub = stubVersions('99.9.9', { spawn: true })))\n  after(() => stub.restore())\n\n  it('print rcConfigPath when there is a non-empty rc config file', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const tempConfigFile = path.join(tempDir, '.ncurc.json')\n    await fs.writeFile(tempConfigFile, JSON.stringify({ filter: 'ncu-test-v2' }), 'utf-8')\n    try {\n      const { stdout } = await spawn('node', [bin, '--stdin', '--configFilePath', tempDir], {\n        stdin: JSON.stringify({ dependencies: { 'ncu-test-v2': '1.0.0', 'ncu-test-tag': '0.1.0' } }),\n      })\n      stdout.should.containIgnoreCase(`Using config file ${tempConfigFile}`)\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('do not print rcConfigPath when there is no rc config file', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    try {\n      const { stdout } = await spawn('node', [bin, '--stdin', '--cwd', tempDir], {\n        stdin: JSON.stringify({ dependencies: { 'ncu-test-v2': '1.0.0' } }),\n      })\n      stdout.should.not.include('Using config file')\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('do not print rcConfigPath when there is an empty rc config file', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const tempConfigFile = path.join(tempDir, '.ncurc.json')\n    await fs.writeFile(tempConfigFile, '{}', 'utf-8')\n    try {\n      const { stdout } = await spawn('node', [bin, '--stdin', '--configFilePath', tempDir], {\n        stdin: JSON.stringify({ dependencies: { 'ncu-test-v2': '1', 'ncu-test-tag': '0.1.0' } }),\n      })\n      stdout.should.not.include('Using config file')\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('error on missing --configFileName', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const configFileName = '.ncurc_missing.json'\n    try {\n      const result = spawn('node', [bin, '--stdin', '--configFilePath', tempDir, '--configFileName', configFileName], {\n        stdin: JSON.stringify({ dependencies: { 'ncu-test-v2': '1', 'ncu-test-tag': '0.1.0' } }),\n      })\n      await result.should.eventually.be.rejectedWith(`Config file ${configFileName} not found in ${tempDir}`)\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('read --configFilePath', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const tempConfigFile = path.join(tempDir, '.ncurc.json')\n    await fs.writeFile(tempConfigFile, JSON.stringify({ jsonUpgraded: true, filter: 'ncu-test-v2' }), 'utf-8')\n    try {\n      const { stdout } = await spawn('node', [bin, '--stdin', '--configFilePath', tempDir], {\n        stdin: JSON.stringify({ dependencies: { 'ncu-test-v2': '1', 'ncu-test-tag': '0.1.0' } }),\n      })\n      const pkgData = JSON.parse(stdout)\n      pkgData.should.have.property('ncu-test-v2')\n      pkgData.should.not.have.property('ncu-test-tag')\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('read --configFileName', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const tempConfigFileName = '.rctemp.json'\n    const tempConfigFile = path.join(tempDir, tempConfigFileName)\n    await fs.writeFile(tempConfigFile, JSON.stringify({ jsonUpgraded: true, filter: 'ncu-test-v2' }), 'utf-8')\n    try {\n      const { stdout } = await spawn(\n        'node',\n        [bin, '--stdin', '--configFilePath', tempDir, '--configFileName', tempConfigFileName],\n        { stdin: JSON.stringify({ dependencies: { 'ncu-test-v2': '1', 'ncu-test-tag': '0.1.0' } }) },\n      )\n      const pkgData = JSON.parse(stdout)\n      pkgData.should.have.property('ncu-test-v2')\n      pkgData.should.not.have.property('ncu-test-tag')\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('override config with arguments', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const tempConfigFile = path.join(tempDir, '.ncurc.json')\n    await fs.writeFile(tempConfigFile, JSON.stringify({ jsonUpgraded: true, filter: 'ncu-test-v2' }), 'utf-8')\n    try {\n      const { stdout } = await spawn(\n        'node',\n        [bin, '--stdin', '--configFilePath', tempDir, '--filter', 'ncu-test-tag'],\n        { stdin: JSON.stringify({ dependencies: { 'ncu-test-v2': '1', 'ncu-test-tag': '0.1.0' } }) },\n      )\n      const pkgData = JSON.parse(stdout)\n      pkgData.should.have.property('ncu-test-tag')\n      pkgData.should.not.have.property('ncu-test-v2')\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('override true in config with false in the cli', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const tempConfigFile = path.join(tempDir, '.ncurc.json')\n    await fs.writeFile(tempConfigFile, JSON.stringify({ jsonUpgraded: true }), 'utf-8')\n    try {\n      const { stdout } = await spawn('node', [bin, '--stdin', '--configFilePath', tempDir, '--no-jsonUpgraded'], {\n        stdin: JSON.stringify({ dependencies: { 'ncu-test-v2': '1', 'ncu-test-tag': '0.1.0' } }),\n      })\n      // if the output contains \"Using config file\", then we know that jsonUpgraded was overridden\n      stdout.should.include('Using config file')\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('handle boolean arguments', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const tempConfigFile = path.join(tempDir, '.ncurc.json')\n    // if boolean arguments are not handled as a special case, ncu will incorrectly pass \"--deep false\" to commander, which will interpret it as two args, i.e. --deep and --filter false\n    await fs.writeFile(tempConfigFile, JSON.stringify({ jsonUpgraded: true, deep: false }), 'utf-8')\n    try {\n      const { stdout } = await spawn('node', [bin, '--stdin', '--configFilePath', tempDir], {\n        stdin: JSON.stringify({ dependencies: { 'ncu-test-tag': '0.1.0' } }),\n      })\n      const pkgData = JSON.parse(stdout)\n      pkgData.should.have.property('ncu-test-tag')\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('auto detect .ncurc.json', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const configFile = path.join(tempDir, '.ncurc.json')\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(configFile, JSON.stringify({ filter: 'ncu-test-v2' }), 'utf-8')\n    await fs.writeFile(\n      pkgFile,\n      JSON.stringify({ dependencies: { 'ncu-test-v2': '1.0.0', 'ncu-test-tag': '0.1.0' } }),\n      'utf-8',\n    )\n    try {\n      // awkwardly, we have to set mergeConfig to enable autodetecting the rcconfig because otherwise it is explicitly disabled for tests\n      const { stdout } = await spawn('node', [bin, '--mergeConfig'], {}, { cwd: tempDir })\n      const firstLine = stdout.split('\\n')[0]\n      // On OSX tempDir is /var/folders/cb/12345, but npm-check-updates receives /private/var/folders/cb/12345.\n      // Apparently OSX symlinks /tmp to /private/tmp for historical reasons.\n      // Therefore, ignore any directories prepended to the config file path.\n      firstLine.should.contains('Using config file')\n      firstLine.should.contains(configFile)\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('auto detect .ncurc.cjs', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const configFile = path.join(tempDir, '.ncurc.cjs')\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(configFile, 'module.exports = { \"filter\": \"ncu-test-v2\" }', 'utf-8')\n    await fs.writeFile(\n      pkgFile,\n      JSON.stringify({ dependencies: { 'ncu-test-v2': '1.0.0', 'ncu-test-tag': '0.1.0' } }),\n      'utf-8',\n    )\n    try {\n      // awkwardly, we have to set mergeConfig to enable autodetecting the rcconfig because otherwise it is explicitly disabled for tests\n      const { stdout } = await spawn('node', [bin, '--mergeConfig'], {}, { cwd: tempDir })\n      const firstLine = stdout.split('\\n')[0]\n      // On OSX tempDir is /var/folders/cb/12345, but npm-check-updates receives /private/var/folders/cb/12345.\n      // Apparently OSX symlinks /tmp to /private/tmp for historical reasons.\n      // Therefore, ignore any directories prepended to the config file path.\n      firstLine.should.contains('Using config file')\n      firstLine.should.contains(configFile)\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  it('should not crash if because of $schema property', async () => {\n    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n    const configFile = path.join(tempDir, '.ncurc.json')\n    const pkgFile = path.join(tempDir, 'package.json')\n    await fs.writeFile(configFile, JSON.stringify({ $schema: 'schema url' }), 'utf-8')\n    await fs.writeFile(pkgFile, JSON.stringify({ dependencies: { axios: '1.0.0' } }), 'utf-8')\n\n    try {\n      // awkwardly, we have to set mergeConfig to enable autodetecting the rcconfig because otherwise it is explicitly disabled for tests\n      await spawn('node', [bin, '--mergeConfig'], {}, { cwd: tempDir })\n    } finally {\n      await removeDir(tempDir)\n    }\n  })\n\n  describe('config functions', () => {\n    it('filter function', async () => {\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const configFile = path.join(tempDir, '.ncurc.js')\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(\n        configFile,\n        `module.exports = {\n        filter: name => name.endsWith('tag')\n       }`,\n        'utf-8',\n      )\n      await fs.writeFile(\n        pkgFile,\n        JSON.stringify({ dependencies: { 'ncu-test-v2': '1.0.0', 'ncu-test-tag': '0.1.0' } }),\n        'utf-8',\n      )\n      try {\n        // awkwardly, we have to set mergeConfig to enable autodetecting the rcconfig because otherwise it is explicitly disabled for tests\n        const { stdout } = await spawn('node', [bin, '--mergeConfig', '--jsonUpgraded'], {}, { cwd: tempDir })\n        const pkgData = JSON.parse(stdout)\n        pkgData.should.not.have.property('ncu-test-v2')\n        pkgData.should.have.property('ncu-test-tag')\n      } finally {\n        await removeDir(tempDir)\n      }\n    })\n\n    it('filterVersion function', async () => {\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const configFile = path.join(tempDir, '.ncurc.js')\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(\n        configFile,\n        `module.exports = {\n        filterVersion: version => version === '1.0.0'\n       }`,\n        'utf-8',\n      )\n      await fs.writeFile(\n        pkgFile,\n        JSON.stringify({ dependencies: { 'ncu-test-v2': '1.0.0', 'ncu-test-tag': '0.1.0' } }),\n        'utf-8',\n      )\n      try {\n        // awkwardly, we have to set mergeConfig to enable autodetecting the rcconfig because otherwise it is explicitly disabled for tests\n        const { stdout } = await spawn('node', [bin, '--mergeConfig', '--jsonUpgraded'], {}, { cwd: tempDir })\n        const pkgData = JSON.parse(stdout)\n        pkgData.should.have.property('ncu-test-v2')\n        pkgData.should.not.have.property('ncu-test-tag')\n      } finally {\n        await removeDir(tempDir)\n      }\n    })\n\n    it('filterResults function', async () => {\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const configFile = path.join(tempDir, '.ncurc.js')\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(\n        configFile,\n        `module.exports = {\n        filterResults: (name, { upgradedVersion }) => upgradedVersion === '99.9.9'\n       }`,\n        'utf-8',\n      )\n      await fs.writeFile(\n        pkgFile,\n        JSON.stringify({ dependencies: { 'ncu-test-v2': '1.0.0', 'ncu-test-tag': '0.1.0' } }),\n        'utf-8',\n      )\n      try {\n        // awkwardly, we have to set mergeConfig to enable autodetecting the rcconfig because otherwise it is explicitly disabled for tests\n        const { stdout } = await spawn('node', [bin, '--mergeConfig', '--jsonUpgraded'], {}, { cwd: tempDir })\n        const pkgData = JSON.parse(stdout)\n        pkgData.should.have.property('ncu-test-v2')\n        pkgData.should.have.property('ncu-test-tag')\n      } finally {\n        await removeDir(tempDir)\n      }\n    })\n\n    it('reject function', async () => {\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const configFile = path.join(tempDir, '.ncurc.js')\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(\n        configFile,\n        `module.exports = {\n        reject: name => name.endsWith('tag')\n       }`,\n        'utf-8',\n      )\n      await fs.writeFile(\n        pkgFile,\n        JSON.stringify({ dependencies: { 'ncu-test-v2': '1.0.0', 'ncu-test-tag': '0.1.0' } }),\n        'utf-8',\n      )\n      try {\n        // awkwardly, we have to set mergeConfig to enable autodetecting the rcconfig because otherwise it is explicitly disabled for tests\n        const { stdout } = await spawn('node', [bin, '--mergeConfig', '--jsonUpgraded'], {}, { cwd: tempDir })\n        const pkgData = JSON.parse(stdout)\n        pkgData.should.have.property('ncu-test-v2')\n        pkgData.should.not.have.property('ncu-test-tag')\n      } finally {\n        await removeDir(tempDir)\n      }\n    })\n\n    it('rejectVersion function', async () => {\n      const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n      const configFile = path.join(tempDir, '.ncurc.js')\n      const pkgFile = path.join(tempDir, 'package.json')\n      await fs.writeFile(\n        configFile,\n        `module.exports = {\n        rejectVersion: version => version === '1.0.0'\n       }`,\n        'utf-8',\n      )\n      await fs.writeFile(\n        pkgFile,\n        JSON.stringify({ dependencies: { 'ncu-test-v2': '1.0.0', 'ncu-test-tag': '0.1.0' } }),\n        'utf-8',\n      )\n      try {\n        // awkwardly, we have to set mergeConfig to enable autodetecting the rcconfig because otherwise it is explicitly disabled for tests\n        const { stdout } = await spawn('node', [bin, '--mergeConfig', '--jsonUpgraded'], {}, { cwd: tempDir })\n        const pkgData = JSON.parse(stdout)\n        pkgData.should.not.have.property('ncu-test-v2')\n        pkgData.should.have.property('ncu-test-tag')\n      } finally {\n        await removeDir(tempDir)\n      }\n    })\n  })\n})\n"
  },
  {
    "path": "test/registryType.test.ts",
    "content": "import ncu from '../src/index'\nimport chaiSetup from './helpers/chaiSetup'\n\nchaiSetup()\n\ndescribe('staticRegistry', function () {\n  it('upgrade to the version specified in the static registry file', async () => {\n    const output = await ncu({\n      packageData: {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n        },\n      },\n      registryType: 'json',\n      registry: './test/test-data/registry.json',\n    })\n\n    output!.should.deep.equal({\n      'ncu-test-v2': '99.9.9',\n    })\n  })\n\n  it('ignore dependencies that are not in the static registry', async () => {\n    const output = await ncu({\n      packageData: {\n        dependencies: {\n          'ncu-test-tag': '1.0.0',\n        },\n      },\n      registryType: 'json',\n      registry: './test/test-data/registry.json',\n    })\n\n    output!.should.deep.equal({})\n  })\n\n  it('fetch static registry from a url', async () => {\n    const output = await ncu({\n      packageData: {\n        dependencies: {\n          'ncu-test-tag': '1.0.0',\n        },\n      },\n      registryType: 'json',\n      registry:\n        // https://gist.github.com/raineorshine/0802d7388c69193bed49c5ee6ab611b9\n        'https://gist.githubusercontent.com/raineorshine/0802d7388c69193bed49c5ee6ab611b9/raw/6f22bfdf19b7596089e56e0b14cd66d077f049d5/staticRegistry.json',\n    })\n\n    output!.should.deep.equal({})\n  })\n\n  it('infer registryType json when --registry file path ends in .json', async () => {\n    const output = await ncu({\n      packageData: {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n        },\n      },\n      registry: './test/test-data/registry.json',\n    })\n\n    output!.should.deep.equal({\n      'ncu-test-v2': '99.9.9',\n    })\n  })\n\n  it('infer registryType json when --registry url ends in .json', async () => {\n    const output = await ncu({\n      packageData: {\n        dependencies: {\n          'ncu-test-v2': '1.0.0',\n        },\n      },\n      registry:\n        // https://gist.github.com/raineorshine/0802d7388c69193bed49c5ee6ab611b9\n        'https://gist.githubusercontent.com/raineorshine/0802d7388c69193bed49c5ee6ab611b9/raw/6f22bfdf19b7596089e56e0b14cd66d077f049d5/staticRegistry.json',\n    })\n\n    output!.should.deep.equal({\n      'ncu-test-v2': '99.9.9',\n    })\n  })\n})\n"
  },
  {
    "path": "test/rejectVersion.ts",
    "content": "import ncu from '../src'\nimport chaiSetup from './helpers/chaiSetup'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\ndescribe('rejectVersion', () => {\n  it('reject by package version with string', async () => {\n    const stub = stubVersions({\n      'ncu-test-v2': '2.0.0',\n      'ncu-test-return-version': '2.0.0',\n    })\n\n    const pkg = {\n      dependencies: {\n        'ncu-test-v2': '1.0.0',\n        'ncu-test-return-version': '1.0.1',\n      },\n    }\n\n    const upgraded = await ncu({\n      packageData: pkg,\n      rejectVersion: '1.0.0',\n    })\n\n    upgraded!.should.not.have.property('ncu-test-v2')\n    upgraded!.should.have.property('ncu-test-return-version')\n\n    stub.restore()\n  })\n\n  it('reject by package version with space-delimited list of strings', async () => {\n    const stub = stubVersions({\n      'ncu-test-v2': '2.0.0',\n      'ncu-test-return-version': '2.0.0',\n      'fp-and-or': '0.1.3',\n    })\n\n    const pkg = {\n      dependencies: {\n        'ncu-test-v2': '1.0.0',\n        'ncu-test-return-version': '1.0.1',\n        'fp-and-or': '0.1.0',\n      },\n    }\n\n    const upgraded = await ncu({\n      packageData: pkg,\n      rejectVersion: '1.0.0 0.1.0',\n    })\n\n    upgraded!.should.not.have.property('ncu-test-v2')\n    upgraded!.should.have.property('ncu-test-return-version')\n    upgraded!.should.not.have.property('fp-and-or')\n\n    stub.restore()\n  })\n\n  it('reject by package version with comma-delimited list of strings', async () => {\n    const stub = stubVersions({\n      'ncu-test-v2': '2.0.0',\n      'ncu-test-return-version': '2.0.0',\n      'fp-and-or': '0.1.3',\n    })\n\n    const pkg = {\n      dependencies: {\n        'ncu-test-v2': '1.0.0',\n        'ncu-test-return-version': '1.0.1',\n        'fp-and-or': '0.1.0',\n      },\n    }\n\n    const upgraded = await ncu({\n      packageData: pkg,\n      rejectVersion: '1.0.0,0.1.0',\n    })\n\n    upgraded!.should.not.have.property('ncu-test-v2')\n    upgraded!.should.have.property('ncu-test-return-version')\n    upgraded!.should.not.have.property('fp-and-or')\n\n    stub.restore()\n  })\n\n  it('reject by package version with RegExp', async () => {\n    const stub = stubVersions({\n      'ncu-test-v2': '2.0.0',\n      'ncu-test-return-version': '2.0.0',\n      'fp-and-or': '0.1.3',\n    })\n\n    const pkg = {\n      dependencies: {\n        'ncu-test-v2': '1.0.0',\n        'ncu-test-return-version': '1.0.1',\n        'fp-and-or': '0.1.0',\n      },\n    }\n\n    const upgraded = await ncu({\n      packageData: pkg,\n      rejectVersion: /^1/,\n    })\n\n    upgraded!.should.not.have.property('ncu-test-v2')\n    upgraded!.should.not.have.property('ncu-test-return-version')\n    upgraded!.should.have.property('fp-and-or')\n\n    stub.restore()\n  })\n\n  it('reject by package version with RegExp string', async () => {\n    const stub = stubVersions({\n      'ncu-test-v2': '2.0.0',\n      'ncu-test-return-version': '2.0.0',\n      'fp-and-or': '0.1.3',\n    })\n\n    const pkg = {\n      dependencies: {\n        'ncu-test-v2': '1.0.0',\n        'ncu-test-return-version': '1.0.1',\n        'fp-and-or': '0.1.0',\n      },\n    }\n\n    const upgraded = await ncu({\n      packageData: pkg,\n      rejectVersion: '/^1/',\n    })\n\n    upgraded!.should.not.have.property('ncu-test-v2')\n    upgraded!.should.not.have.property('ncu-test-return-version')\n    upgraded!.should.have.property('fp-and-or')\n\n    stub.restore()\n  })\n})\n"
  },
  {
    "path": "test/target.test.ts",
    "content": "import ncu from '../src/'\nimport { FilterFunction } from '../src/types/FilterFunction'\nimport { Index } from '../src/types/IndexType'\nimport { TargetFunction } from '../src/types/TargetFunction'\nimport { Version } from '../src/types/Version'\nimport chaiSetup from './helpers/chaiSetup'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\n// TODO: Mock based on real output of viewMany\ndescribe('target', () => {\n  describe('minor', () => {\n    it('do not update major versions with --target minor', async () => {\n      const pkgData = await ncu({ target: 'minor', packageData: { dependencies: { chalk: '3.0.0' } } })\n      pkgData!.should.not.have.property('chalk')\n    })\n\n    it('update minor versions with --target minor', async () => {\n      const pkgData = (await ncu({\n        target: 'minor',\n        packageData: { dependencies: { chalk: '2.3.0' } },\n      })) as Index<Version>\n      pkgData!.should.have.property('chalk')\n      pkgData.chalk.should.equal('2.4.2')\n    })\n\n    it('update patch versions with --target minor', async () => {\n      const pkgData = (await ncu({\n        target: 'minor',\n        packageData: { dependencies: { chalk: '2.4.0' } },\n      })) as Index<Version>\n      pkgData!.should.have.property('chalk')\n      pkgData.chalk.should.equal('2.4.2')\n    })\n  })\n\n  describe('patch', () => {\n    it('do not update major versions with --target patch', async () => {\n      const pkgData = await ncu({ target: 'patch', packageData: { dependencies: { chalk: '3.0.0' } } })\n      pkgData!.should.not.have.property('chalk')\n    })\n\n    it('do not update minor versions with --target patch', async () => {\n      const pkgData = await ncu({ target: 'patch', packageData: { dependencies: { chalk: '2.3.2' } } })\n      pkgData!.should.not.have.property('chalk')\n    })\n\n    it('update patch versions with --target patch', async () => {\n      const pkgData = (await ncu({\n        target: 'patch',\n        packageData: { dependencies: { chalk: '2.4.1' } },\n      })) as Index<Version>\n      pkgData!.should.have.property('chalk')\n      pkgData.chalk.should.equal('2.4.2')\n    })\n\n    it('skip non-semver versions with --target patch', async () => {\n      const pkgData = await ncu({ target: 'patch', packageData: { dependencies: { test: 'github:a/b' } } })\n      pkgData!.should.not.have.property('test')\n    })\n  })\n\n  describe('newest', () => {\n    it('do not require --pre with --target newest', () => {\n      return ncu({\n        jsonAll: true,\n        packageData: {\n          dependencies: {\n            'ncu-mock-pre': '1.0.0',\n          },\n        },\n        target: 'newest',\n      }).then(data => {\n        return data!.should.eql({\n          dependencies: {\n            'ncu-mock-pre': '2.0.0-alpha.0',\n          },\n        })\n      })\n    })\n\n    it('allow --pre 0 with --target newest to exclude prereleases', () => {\n      return ncu({\n        jsonAll: true,\n        packageData: {\n          dependencies: {\n            'ncu-mock-pre': '1.0.0',\n          },\n        },\n        target: 'newest',\n        pre: false,\n      }).then(data => {\n        return data!.should.eql({\n          dependencies: {\n            'ncu-mock-pre': '1.0.0',\n          },\n        })\n      })\n    })\n\n    it('work with --target newest with any invalid or wildcard range', () => {\n      return Promise.all([\n        ncu({\n          jsonAll: true,\n          target: 'newest',\n          packageData: {\n            dependencies: {\n              del: '',\n            },\n          },\n        }),\n        ncu({\n          jsonAll: true,\n          target: 'newest',\n          packageData: {\n            dependencies: {\n              del: 'invalid range',\n            },\n          },\n        }),\n        ncu({\n          jsonAll: true,\n          target: 'newest',\n          packageData: {\n            dependencies: {\n              del: '*',\n            },\n          },\n        }),\n        ncu({\n          jsonAll: true,\n          target: 'newest',\n          packageData: {\n            dependencies: {\n              del: '~',\n            },\n          },\n        }),\n      ])\n    })\n  })\n\n  describe('greatest', () => {\n    it('do not require --pre with --target greatest', () => {\n      return ncu({\n        jsonAll: true,\n        packageData: {\n          dependencies: {\n            'ncu-mock-pre': '1.0.0',\n          },\n        },\n        target: 'greatest',\n      }).then(data => {\n        return data!.should.eql({\n          dependencies: {\n            'ncu-mock-pre': '2.0.0-alpha.0',\n          },\n        })\n      })\n    })\n  })\n\n  describe('semver', () => {\n    describe('^', () => {\n      it('highest minor for post-1.0 version', async () => {\n        const data = await ncu({\n          jsonAll: true,\n          packageData: {\n            dependencies: {\n              'ncu-test-semver': '^1.0.0',\n            },\n          },\n          target: 'semver',\n        })\n\n        data!.should.eql({\n          dependencies: {\n            'ncu-test-semver': '^1.2.0',\n          },\n        })\n      })\n\n      it('highest patch for pre-1.0 version', async () => {\n        const data = await ncu({\n          jsonAll: true,\n          packageData: {\n            dependencies: {\n              'ncu-test-pre1': '^0.1.0',\n            },\n          },\n          target: 'semver',\n        })\n\n        data!.should.eql({\n          dependencies: {\n            'ncu-test-pre1': '^0.1.2',\n          },\n        })\n      })\n\n      // TODO: Why doesn't this work?\n      it.skip('alpha', async () => {\n        const data = await ncu({\n          jsonAll: true,\n          packageData: {\n            dependencies: {\n              'ncu-test-alpha': '^1.0.0-alpha.1',\n            },\n          },\n          pre: true,\n          target: 'semver',\n        })\n\n        data!.should.eql({\n          dependencies: {\n            'ncu-test-alpha': '^1.0.0-alpha.2',\n          },\n        })\n      })\n    })\n\n    describe('~', () => {\n      it('highest patch for post-1.0 version', async () => {\n        const data = await ncu({\n          jsonAll: true,\n          packageData: {\n            dependencies: {\n              'ncu-test-semver': '~1.0.0',\n            },\n          },\n          target: 'semver',\n        })\n\n        data!.should.eql({\n          dependencies: {\n            'ncu-test-semver': '~1.0.1',\n          },\n        })\n      })\n\n      it('highest patch for pre-1.0 version', async () => {\n        const data = await ncu({\n          jsonAll: true,\n          packageData: {\n            dependencies: {\n              'ncu-test-pre1': '~0.1.0',\n            },\n          },\n          target: 'semver',\n        })\n\n        data!.should.eql({\n          dependencies: {\n            'ncu-test-pre1': '~0.1.2',\n          },\n        })\n      })\n    })\n\n    describe('exact version', () => {\n      it('ignore exact version range', async () => {\n        const data = await ncu({\n          jsonAll: true,\n          packageData: {\n            dependencies: {\n              'ncu-test-semver': '1.0.0',\n            },\n          },\n          target: 'semver',\n        })\n\n        data!.should.eql({\n          dependencies: {\n            'ncu-test-semver': '1.0.0',\n          },\n        })\n      })\n    })\n\n    describe('explicit ranges', () => {\n      it('ignore inclusive range', async () => {\n        const data = await ncu({\n          jsonAll: true,\n          packageData: {\n            dependencies: {\n              'ncu-test-semver': '1.0.0 - 1.3.0',\n            },\n          },\n          target: 'semver',\n        })\n\n        data!.should.eql({\n          dependencies: {\n            'ncu-test-semver': '1.0.0 - 1.3.0',\n          },\n        })\n      })\n\n      it('ignore >', async () => {\n        const data = await ncu({\n          jsonAll: true,\n          packageData: {\n            dependencies: {\n              'ncu-test-semver': '>1',\n            },\n          },\n          target: 'semver',\n        })\n\n        data!.should.eql({\n          dependencies: {\n            'ncu-test-semver': '>1',\n          },\n        })\n      })\n\n      it('ignore >=', async () => {\n        const data = await ncu({\n          jsonAll: true,\n          packageData: {\n            dependencies: {\n              'ncu-test-semver': '>=1',\n            },\n          },\n          target: 'semver',\n        })\n\n        data!.should.eql({\n          dependencies: {\n            'ncu-test-semver': '>=1',\n          },\n        })\n      })\n\n      it('ignore <', async () => {\n        const data = await ncu({\n          jsonAll: true,\n          packageData: {\n            dependencies: {\n              'ncu-test-semver': '<2',\n            },\n          },\n          target: 'semver',\n        })\n\n        data!.should.eql({\n          dependencies: {\n            'ncu-test-semver': '<2',\n          },\n        })\n      })\n\n      it('ignore <=', async () => {\n        const data = await ncu({\n          jsonAll: true,\n          packageData: {\n            dependencies: {\n              'ncu-test-semver': '<=2',\n            },\n          },\n          target: 'semver',\n        })\n\n        data!.should.eql({\n          dependencies: {\n            'ncu-test-semver': '<=2',\n          },\n        })\n      })\n\n      it('ignore ||', async () => {\n        const data = await ncu({\n          jsonAll: true,\n          packageData: {\n            dependencies: {\n              'ncu-test-semver': '1 || 2',\n            },\n          },\n          target: 'semver',\n        })\n\n        data!.should.eql({\n          dependencies: {\n            'ncu-test-semver': '1 || 2',\n          },\n        })\n      })\n    })\n  })\n\n  describe('custom', () => {\n    it('custom target function to mimic semver', async () => {\n      // eslint-disable-next-line jsdoc/require-jsdoc\n      const target: TargetFunction = (name, [{ operator }]) =>\n        operator === '^' ? 'minor' : operator === '~' ? 'patch' : 'latest'\n      const pkgData = (await ncu({\n        target,\n        packageData: {\n          dependencies: {\n            'eslint-plugin-jsdoc': '~36.1.0',\n            jsonlines: '0.1.0',\n            juggernaut: '1.0.0',\n            mocha: '^8.3.2',\n          },\n        },\n      })) as Index<Version>\n      pkgData!.should.have.property('eslint-plugin-jsdoc')\n      pkgData['eslint-plugin-jsdoc'].should.equal('~36.1.1')\n      pkgData!.should.have.property('jsonlines')\n      pkgData.jsonlines.should.equal('0.1.1')\n      pkgData!.should.have.property('juggernaut')\n      pkgData.juggernaut.should.equal('2.1.1')\n      pkgData!.should.have.property('mocha')\n      pkgData.mocha.should.equal('^8.4.0')\n    })\n\n    it('custom target and filter function to mimic semver', async () => {\n      // eslint-disable-next-line jsdoc/require-jsdoc\n      const target: TargetFunction = (name, [{ operator }]) =>\n        operator === '^' ? 'minor' : operator === '~' ? 'patch' : 'latest'\n      // eslint-disable-next-line jsdoc/require-jsdoc\n      const filter: FilterFunction = (_, [{ major, operator }]) =>\n        !(major === '0' || major === undefined || operator === undefined)\n      const pkgData = (await ncu({\n        filter,\n        target,\n        packageData: {\n          dependencies: {\n            'eslint-plugin-jsdoc': '~36.1.0',\n            jsonlines: '0.1.0',\n            juggernaut: '1.0.0',\n            mocha: '^8.3.2',\n          },\n        },\n      })) as Index<Version>\n      pkgData!.should.have.property('eslint-plugin-jsdoc')\n      pkgData['eslint-plugin-jsdoc'].should.equal('~36.1.1')\n      pkgData!.should.not.have.property('jsonlines')\n      pkgData!.should.not.have.property('juggernaut')\n      pkgData!.should.have.property('mocha')\n      pkgData.mocha.should.equal('^8.4.0')\n    })\n  })\n}) // end 'target'\n\ndescribe('tags', () => {\n  it('upgrade nonprerelease version to specific tag', async () => {\n    const upgraded = (await ncu({\n      target: '@next',\n      packageData: {\n        dependencies: {\n          'ncu-test-tag': '0.1.0',\n        },\n      },\n    })) as Index<Version>\n\n    upgraded['ncu-test-tag'].should.equal('1.0.0-1')\n  })\n\n  it('upgrade prerelease version without preid to nonprerelease', async () => {\n    const upgraded = (await ncu({\n      target: 'latest',\n      packageData: {\n        dependencies: {\n          'ncu-test-tag': '1.0.0-1',\n        },\n      },\n    })) as Index<Version>\n\n    upgraded['ncu-test-tag'].should.equal('1.1.0')\n  })\n\n  it('upgrade prerelease version with preid to higher version on a specific tag', async () => {\n    const upgraded = (await ncu({\n      target: '@beta',\n      packageData: {\n        dependencies: {\n          'ncu-test-tag': '1.0.0-task-42.0',\n        },\n      },\n    })) as Index<Version>\n\n    upgraded['ncu-test-tag'].should.equal('1.0.1-beta.0')\n  })\n\n  // can't detect which prerelease is higher, so just allow switching\n  it('upgrade from prerelease without preid to prerelease with preid at a specific tag if major.minor.patch is the same', async () => {\n    const upgraded = (await ncu({\n      target: '@task-42',\n      packageData: {\n        dependencies: {\n          'ncu-test-tag': '1.0.0-beta.0',\n        },\n      },\n    })) as Index<Version>\n\n    upgraded['ncu-test-tag'].should.equal('1.0.0-task-42.0')\n  })\n\n  // need to test reverse order too, because by base semver logic preid are sorted alphabetically\n  it('upgrade from prerelease with preid to prerelease without preid at a specific tag if major.minor.patch is the same', async () => {\n    const upgraded = (await ncu({\n      target: '@next',\n      packageData: {\n        dependencies: {\n          'ncu-test-tag': '1.0.0-task-42.0',\n        },\n      },\n    })) as Index<Version>\n\n    upgraded['ncu-test-tag'].should.equal('1.0.0-1')\n  })\n\n  // comparing semver between different dist-tags is incorrect, both versions could be released from the same latest\n  // so instead of looking at numbers, we should focus on intention of the user upgrading to specific dist-tag\n  it('downgrade to tag with a non-matching preid and lower patch', async () => {\n    const upgraded = (await ncu({\n      target: '@task-42',\n      packageData: {\n        dependencies: {\n          'ncu-test-tag': '1.0.1-beta.0',\n        },\n      },\n    })) as Index<Version>\n\n    upgraded['ncu-test-tag'].should.equal('1.0.0-task-42.0')\n  })\n\n  // same as previous, doesn't matter if it's patch, minor or major, comparing different dist-tags is incorrect\n  it('downgrade to tag with a non-matching preid and lower minor', async () => {\n    const upgraded = (await ncu({\n      target: '@next',\n      packageData: {\n        dependencies: {\n          'ncu-test-tag': '1.2.0-dev.0',\n        },\n      },\n    })) as Index<Version>\n\n    upgraded['ncu-test-tag'].should.equal('1.0.0-1')\n  })\n\n  it('do not downgrade nonprerelease version to lower version with specific tag', async () => {\n    const stub = stubVersions('1.0.0-1')\n\n    const upgraded = await ncu({\n      target: '@next',\n      packageData: {\n        dependencies: {\n          'ncu-test-tag': '1.1.0',\n        },\n      },\n    })\n\n    upgraded!.should.not.have.property('ncu-test-tag')\n\n    stub.restore()\n  })\n\n  it('do not downgrade to latest with lower version by default', async () => {\n    const stub = stubVersions('1.1.0')\n\n    const upgraded = await ncu({\n      packageData: {\n        dependencies: {\n          'ncu-test-tag': '^1.1.1-beta.0',\n        },\n      },\n    })\n\n    upgraded!.should.not.have.property('ncu-test-tag')\n\n    stub.restore()\n  })\n\n  it('do not downgrade to latest with lower version with --target latest', async () => {\n    const stub = stubVersions('1.1.0')\n\n    const upgraded = await ncu({\n      target: 'latest',\n      packageData: {\n        dependencies: {\n          'ncu-test-tag': '^1.1.1-beta.0',\n        },\n      },\n    })\n\n    upgraded!.should.not.have.property('ncu-test-tag')\n\n    stub.restore()\n  })\n\n  it('downgrade to latest with lower version with explicit --target @latest', async () => {\n    const stub = stubVersions('1.1.0')\n\n    const upgraded = (await ncu({\n      target: '@latest',\n      packageData: {\n        dependencies: {\n          'ncu-test-tag': '^1.1.1-beta.0',\n        },\n      },\n    })) as Index<Version>\n\n    upgraded['ncu-test-tag'].should.equal('^1.1.0')\n\n    stub.restore()\n  })\n\n  it('downgrade to latest with lower version with target function returning @latest', async () => {\n    const stub = stubVersions('1.1.0')\n\n    const upgraded = (await ncu({\n      target: () => '@latest',\n      packageData: {\n        dependencies: {\n          'ncu-test-tag': '^1.1.1-beta.0',\n        },\n      },\n    })) as Index<Version>\n\n    upgraded['ncu-test-tag'].should.equal('^1.1.0')\n\n    stub.restore()\n  })\n}) // end 'tags'\n"
  },
  {
    "path": "test/test-data/basic/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "test/test-data/deep-ncurc/.ncurc.js",
    "content": "module.exports = {\n  reject: ['cute-animals'],\n}\n"
  },
  {
    "path": "test/test-data/deep-ncurc/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"cute-animals\": \"^0.1.0\",\n    \"fp-and-or\": \"^0.1.0\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/deep-ncurc/pkg/sub1/.ncurc.js",
    "content": "module.exports = {\n  reject: ['fp-and-or'],\n}\n"
  },
  {
    "path": "test/test-data/deep-ncurc/pkg/sub1/package.json",
    "content": "{\n  \"dependencies\": {\n    \"cute-animals\": \"^0.1.0\",\n    \"fp-and-or\": \"^0.1.0\",\n    \"ncu-test-return-version\": \"^0.1.0\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/deep-ncurc/pkg/sub2/.ncurc.js",
    "content": "module.exports = {\n  reject: ['cute-animals'],\n}\n"
  },
  {
    "path": "test/test-data/deep-ncurc/pkg/sub2/package.json",
    "content": "{\n  \"dependencies\": {\n    \"cute-animals\": \"^0.1.0\",\n    \"fp-and-or\": \"^0.1.0\",\n    \"ncu-test-v2\": \"^0.1.0\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/deep-ncurc/pkg/sub2/sub21/.ncurc.js",
    "content": "module.exports = {\n  reject: ['fp-and-or'],\n}\n"
  },
  {
    "path": "test/test-data/deep-ncurc/pkg/sub2/sub21/package.json",
    "content": "{\n  \"dependencies\": {\n    \"cute-animals\": \"^0.1.0\",\n    \"fp-and-or\": \"^0.1.0\",\n    \"ncu-test-return-version\": \"^0.1.0\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/deep-ncurc/pkg/sub2/sub22/package.json",
    "content": "{\n  \"dependencies\": {\n    \"cute-animals\": \"^0.1.0\",\n    \"fp-and-or\": \"^0.1.0\",\n    \"ncu-test-v2\": \"^0.1.0\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/deep-ncurc/pkg/sub3/package.json",
    "content": "{\n  \"dependencies\": {\n    \"cute-animals\": \"^0.1.0\",\n    \"fp-and-or\": \"^0.1.0\",\n    \"ncu-test-v2\": \"^0.1.0\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/deep-ncurc/pkg/sub3/sub31/.ncurc.js",
    "content": "module.exports = {\n  reject: ['fp-and-or'],\n}\n"
  },
  {
    "path": "test/test-data/deep-ncurc/pkg/sub3/sub31/package.json",
    "content": "{\n  \"dependencies\": {\n    \"cute-animals\": \"^0.1.0\",\n    \"fp-and-or\": \"^0.1.0\",\n    \"ncu-test-return-version\": \"^0.1.0\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/deep-ncurc/pkg/sub3/sub32/package.json",
    "content": "{\n  \"dependencies\": {\n    \"cute-animals\": \"^0.1.0\",\n    \"fp-and-or\": \"^0.1.0\",\n    \"ncu-test-v2\": \"^0.1.0\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/doctor/custominstall/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"ncu-test-v2\": \"~1.0.0\"\n  },\n  \"scripts\": {\n    \"myinstall\": \"echo 'Install Success'\",\n    \"test\": \"echo 'Test Success'\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/doctor/customtest/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"ncu-test-v2\": \"~1.0.0\"\n  },\n  \"scripts\": {\n    \"mytest\": \"echo Success\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/doctor/customtest2/echo.js",
    "content": "console.log(process.argv)\n"
  },
  {
    "path": "test/test-data/doctor/customtest2/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"ncu-test-v2\": \"~1.0.0\"\n  },\n  \"scripts\": {\n    \"mytest\": \"echo Success\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/doctor/fail/README.md",
    "content": "- yarn.lock is necessary otherwise yarn sees the package.json in the npm-check-updates directory and throws an error.\n- license must be specified in the package.json otherwise yarn will print a warning to stderr, which doctor thinks is a failed test.\n"
  },
  {
    "path": "test/test-data/doctor/fail/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"emitter20\": \"1.0.0\",\n    \"ncu-test-return-version\": \"~1.0.0\",\n    \"ncu-test-v2\": \"~1.0.0\"\n  },\n  \"scripts\": {\n    \"test\": \"node test.js\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/doctor/fail/test.js",
    "content": "const returnVersion = require('ncu-test-return-version')\nconst v = returnVersion()\n\n// pass on < 2\nif (v.startsWith('0')) {\n  console.log('Works with v0.x :)')\n} else if (v.startsWith('1')) {\n  console.log('Works with v1.x :)')\n}\n// break on v2.x\nelse if (v.startsWith('2')) {\n  throw new Error('Breaks with v2.x :(')\n}\n"
  },
  {
    "path": "test/test-data/doctor/nolockfile/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"express\": \"^1.0.0\"\n  },\n  \"scripts\": {\n    \"test\": \"echo 'No test'\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/doctor/nopackagefile/nil",
    "content": ""
  },
  {
    "path": "test/test-data/doctor/notestscript/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"express\": \"^1.0.0\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/doctor/options/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"ncu-test-v2\": \"~1.0.0\",\n    \"ncu-test-return-version\": \"~1.0.0\"\n  },\n  \"scripts\": {\n    \"test\": \"node test.js\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/doctor/options/test.js",
    "content": "const returnVersion = require('ncu-test-return-version')\nconst v = returnVersion()\n\n// pass on < 2\nif (v.startsWith('0')) {\n  console.log('Works with v0.x :)')\n} else if (v.startsWith('1')) {\n  console.log('Works with v1.x :)')\n}\n// break on v2.x\nelse if (v.startsWith('2')) {\n  throw new Error('Breaks with v2.x :(')\n}\n"
  },
  {
    "path": "test/test-data/doctor/pass/README.md",
    "content": "- yarn.lock is necessary otherwise yarn sees the package.json in the npm-check-updates directory and throws an error.\n- license must be specified in the package.json otherwise yarn will print a warning to stderr, which doctor thinks is a failed test.\n"
  },
  {
    "path": "test/test-data/doctor/pass/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"ncu-test-v2\": \"~1.0.0\"\n  },\n  \"scripts\": {\n    \"test\": \"echo Success\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/ncu/package-large.json",
    "content": "{\n  \"dependencies\": {\n    \"@angular-devkit/architect\": \"^0.800.0-beta.15\",\n    \"@angular-devkit/build-optimizer\": \"^0.800.0-beta.15\",\n    \"@angular-devkit/core\": \"^8.0.0-beta.15\",\n    \"@angular-devkit/schematics\": \"^8.0.0-beta.15\",\n    \"@angular/bazel\": \"file:./tools/npm/@angular_bazel\",\n    \"@angular/cli\": \"^8.0.0-beta.15\",\n    \"@bazel/bazel\": \"0.27.0\",\n    \"@bazel/buildifier\": \"^0.26.0\",\n    \"@bazel/hide-bazel-files\": \"0.32.2\",\n    \"@bazel/ibazel\": \"~0.9.0\",\n    \"@bazel/jasmine\": \"0.32.2\",\n    \"@bazel/karma\": \"0.32.2\",\n    \"@bazel/typescript\": \"0.32.2\",\n    \"@microsoft/api-extractor\": \"^7.0.21\",\n    \"@schematics/angular\": \"^8.0.0-beta.15\",\n    \"@types/angular\": \"^1.6.47\",\n    \"@types/base64-js\": \"1.2.5\",\n    \"@types/bluebird\": \"^3.5.27\",\n    \"@types/chai\": \"^4.1.2\",\n    \"@types/chokidar\": \"^1.7.5\",\n    \"@types/convert-source-map\": \"^1.5.1\",\n    \"@types/diff\": \"^3.5.1\",\n    \"@types/fs-extra\": \"4.0.2\",\n    \"@types/hammerjs\": \"2.0.35\",\n    \"@types/inquirer\": \"^0.0.44\",\n    \"@types/jasmine\": \"^2.8.8\",\n    \"@types/jasminewd2\": \"^2.0.6\",\n    \"@types/minimist\": \"^1.2.0\",\n    \"@types/minimist\": \"^1.2.0\",\n    \"@types/node\": \"^10.9.4\",\n    \"@types/selenium-webdriver\": \"3.0.7\",\n    \"@types/shelljs\": \"^0.7.8\",\n    \"@types/source-map\": \"^0.5.1\",\n    \"@types/systemjs\": \"0.19.32\",\n    \"@types/yargs\": \"^11.1.1\",\n    \"@webcomponents/custom-elements\": \"^1.0.4\",\n    \"angular\": \"npm:angular@1.7\",\n    \"angular-1.5\": \"npm:angular@1.5\",\n    \"angular-1.6\": \"npm:angular@1.6\",\n    \"angular-mocks\": \"npm:angular-mocks@1.7\",\n    \"angular-mocks-1.5\": \"npm:angular-mocks@1.5\",\n    \"angular-mocks-1.6\": \"npm:angular-mocks@1.6\",\n    \"art\": \"^0.10.1\",\n    \"autoprefixer\": \"^1.0.0\",\n    \"babel-cli\": \"^6.6.5\",\n    \"babel-code-frame\": \"^6.26.0\",\n    \"babel-core\": \"^1.0.0\",\n    \"babel-eslint\": \"^1.0.0\",\n    \"babel-helper-vue-jsx-merge-props\": \"^1.0.0\",\n    \"babel-jest\": \"^1.0.0\",\n    \"babel-loader\": \"^1.0.0\",\n    \"babel-plugin-check-es2015-constants\": \"^6.5.0\",\n    \"babel-plugin-dynamic-import-node\": \"^1.0.0\",\n    \"babel-plugin-external-helpers\": \"^6.22.0\",\n    \"babel-plugin-syntax-dynamic-import\": \"^6.18.0\",\n    \"babel-plugin-syntax-jsx\": \"^1.0.0\",\n    \"babel-plugin-syntax-trailing-function-commas\": \"^6.5.0\",\n    \"babel-plugin-transform-async-to-generator\": \"^6.22.0\",\n    \"babel-plugin-transform-class-properties\": \"^6.11.5\",\n    \"babel-plugin-transform-es2015-arrow-functions\": \"^6.5.2\",\n    \"babel-plugin-transform-es2015-block-scoped-functions\": \"^6.5.0\",\n    \"babel-plugin-transform-es2015-block-scoping\": \"^6.23.0\",\n    \"babel-plugin-transform-es2015-classes\": \"^6.5.2\",\n    \"babel-plugin-transform-es2015-computed-properties\": \"^6.5.2\",\n    \"babel-plugin-transform-es2015-destructuring\": \"^6.5.0\",\n    \"babel-plugin-transform-es2015-for-of\": \"^6.5.2\",\n    \"babel-plugin-transform-es2015-literals\": \"^6.5.0\",\n    \"babel-plugin-transform-es2015-modules-commonjs\": \"^1.0.0\",\n    \"babel-plugin-transform-es2015-modules-commonjs\": \"^6.5.2\",\n    \"babel-plugin-transform-es2015-object-super\": \"^6.5.0\",\n    \"babel-plugin-transform-es2015-parameters\": \"^6.5.0\",\n    \"babel-plugin-transform-es2015-shorthand-properties\": \"^6.5.0\",\n    \"babel-plugin-transform-es2015-spread\": \"^6.5.2\",\n    \"babel-plugin-transform-es2015-template-literals\": \"^6.5.2\",\n    \"babel-plugin-transform-object-rest-spread\": \"^6.6.5\",\n    \"babel-plugin-transform-react-jsx-source\": \"^6.8.0\",\n    \"babel-plugin-transform-regenerator\": \"^6.26.0\",\n    \"babel-plugin-transform-runtime\": \"^1.0.0\",\n    \"babel-plugin-transform-vue-jsx\": \"^1.0.0\",\n    \"babel-preset-env\": \"^1.0.0\",\n    \"babel-preset-react\": \"^6.5.0\",\n    \"babel-preset-stage-2\": \"^1.0.0\",\n    \"babel-traverse\": \"^6.9.0\",\n    \"babylon\": \"6.18.0\",\n    \"base64-js\": \"1.2.1\",\n    \"bluebird\": \"^3.5.5\",\n    \"brotli\": \"^1.3.2\",\n    \"browserstacktunnel-wrapper\": \"2.0.1\",\n    \"canonical-path\": \"1.0.0\",\n    \"chai\": \"^4.1.2\",\n    \"chalk\": \"^1.0.0\",\n    \"check-side-effects\": \"0.0.21\",\n    \"chokidar\": \"^2.1.1\",\n    \"clang-format\": \"1.0.41\",\n    \"cldr\": \"4.10.0\",\n    \"cldr-data-downloader\": \"0.3.2\",\n    \"cldrjs\": \"0.5.0\",\n    \"cli-table\": \"^0.3.1\",\n    \"coffee-script\": \"^1.8.0\",\n    \"conventional-changelog\": \"^2.0.3\",\n    \"convert-source-map\": \"^1.5.1\",\n    \"copy-webpack-plugin\": \"^1.0.0\",\n    \"core-js\": \"^2.2.1\",\n    \"cors\": \"2.8.4\",\n    \"coveralls\": \"^2.11.6\",\n    \"create-react-class\": \"^15.6.3\",\n    \"cross-env\": \"^5.1.1\",\n    \"css-loader\": \"^0.28.0\",\n    \"danger\": \"^3.0.4\",\n    \"dependency-graph\": \"^0.7.2\",\n    \"diff\": \"^3.5.0\",\n    \"domino\": \"2.1.2\",\n    \"entities\": \"1.1.1\",\n    \"error-stack-parser\": \"^2.0.2\",\n    \"eslint\": \"^1.0.0\",\n    \"eslint-config-airbnb-base\": \"^1.0.0\",\n    \"eslint-config-fbjs\": \"^1.1.1\",\n    \"eslint-friendly-formatter\": \"^1.0.0\",\n    \"eslint-import-resolver-webpack\": \"^0.8.3\",\n    \"eslint-loader\": \"^1.0.0\",\n    \"eslint-plugin-babel\": \"^3.3.0\",\n    \"eslint-plugin-flowtype\": \"^2.25.0\",\n    \"eslint-plugin-import\": \"^1.0.0\",\n    \"eslint-plugin-jest\": \"^21.6.1\",\n    \"eslint-plugin-no-for-of-loops\": \"^1.0.0\",\n    \"eslint-plugin-react\": \"^6.7.1\",\n    \"eslint-plugin-react-internal\": \"link:./scripts/eslint-rules/\",\n    \"eslint-plugin-vue\": \"^1.0.0\",\n    \"extract-text-webpack-plugin\": \"^1.0.0\",\n    \"fbjs-scripts\": \"^0.8.3\",\n    \"file-loader\": \"^1.0.0\",\n    \"filesize\": \"^3.5.6\",\n    \"firebase-tools\": \"5.1.1\",\n    \"firefox-profile\": \"1.0.3\",\n    \"flow-bin\": \"^0.72.0\",\n    \"friendly-errors-webpack-plugin\": \"^1.0.0\",\n    \"fs-extra\": \"4.0.2\",\n    \"glob\": \"7.1.2\",\n    \"glob-stream\": \"^6.1.0\",\n    \"google-closure-compiler\": \"20190301.0.0\",\n    \"gulp\": \"3.9.1\",\n    \"gulp-clang-format\": \"1.0.23\",\n    \"gulp-connect\": \"5.0.0\",\n    \"gulp-conventional-changelog\": \"^2.0.3\",\n    \"gulp-filter\": \"^5.1.0\",\n    \"gulp-git\": \"^2.7.0\",\n    \"gulp-tslint\": \"8.1.2\",\n    \"gzip-size\": \"^3.0.0\",\n    \"hammerjs\": \"2.0.8\",\n    \"html-webpack-plugin\": \"^1.0.0\",\n    \"husky\": \"^0.14.3\",\n    \"incremental-dom\": \"0.4.1\",\n    \"jasmine\": \"^3.1.0\",\n    \"jasmine-check\": \"^1.0.0-rc.0\",\n    \"jasmine-core\": \"^3.1.0\",\n    \"jest\": \"^1.0.0\",\n    \"jest-diff\": \"^23.0.1\",\n    \"jest-serializer-vue\": \"^0.3.0\",\n    \"jest-snapshot-serializer-raw\": \"^1.1.0\",\n    \"jpm\": \"1.3.1\",\n    \"jquery\": \"3.0.0\",\n    \"karma\": \"^3.1.4\",\n    \"karma-browserstack-launcher\": \"^1.3.0\",\n    \"karma-chrome-launcher\": \"^2.2.0\",\n    \"karma-jasmine\": \"^1.1.2\",\n    \"karma-sauce-launcher\": \"^2.0.2\",\n    \"karma-sourcemap-loader\": \"^0.3.7\",\n    \"madge\": \"0.5.0\",\n    \"magic-string\": \"^0.25.0\",\n    \"materialize-css\": \"1.0.0\",\n    \"minimatch\": \"^3.0.4\",\n    \"minimist\": \"^1.2.0\",\n    \"mkdirp\": \"^0.5.1\",\n    \"mutation-observer\": \"^1.0.3\",\n    \"ncp\": \"^2.0.0\",\n    \"node-notifier\": \"^1.0.0\",\n    \"node-uuid\": \"1.4.8\",\n    \"nodejs-websocket\": \"^1.7.2\",\n    \"object-assign\": \"^4.1.1\",\n    \"optimize-css-assets-webpack-plugin\": \"^1.0.0\",\n    \"ora\": \"^1.0.0\",\n    \"portfinder\": \"^1.0.0\",\n    \"postcss-import\": \"^1.0.0\",\n    \"postcss-loader\": \"^1.0.0\",\n    \"postcss-url\": \"^1.0.0\",\n    \"prettier\": \"1.13.7\",\n    \"prop-types\": \"^15.6.2\",\n    \"protractor\": \"^5.4.2\",\n    \"random-seed\": \"^0.3.0\",\n    \"react-lifecycles-compat\": \"^3.0.2\",\n    \"reflect-metadata\": \"^0.1.3\",\n    \"rewire\": \"2.5.2\",\n    \"rimraf\": \"^1.0.0\",\n    \"rollup\": \"^0.52.1\",\n    \"rollup-plugin-amd\": \"^3.0.0\",\n    \"rollup-plugin-babel\": \"^3.0.1\",\n    \"rollup-plugin-commonjs\": \"^8.2.6\",\n    \"rollup-plugin-json\": \"^4.0.0\",\n    \"rollup-plugin-node-resolve\": \"^2.1.1\",\n    \"rollup-plugin-prettier\": \"^0.3.0\",\n    \"rollup-plugin-replace\": \"^2.0.0\",\n    \"rollup-plugin-sourcemaps\": \"^0.4.2\",\n    \"rollup-plugin-strip-banner\": \"^0.2.0\",\n    \"rxjs\": \"^6.4.0\",\n    \"sauce-connect\": \"https://saucelabs.com/downloads/sc-4.5.1-linux.tar.gz\",\n    \"selenium-webdriver\": \"3.5.0\",\n    \"semver\": \"5.4.1\",\n    \"semver\": \"^1.0.0\",\n    \"semver\": \"^5.5.0\",\n    \"shelljs\": \"^0.7.6\",\n    \"shelljs\": \"^0.8.1\",\n    \"source-map\": \"^0.6.1\",\n    \"source-map-support\": \"0.5.9\",\n    \"systemjs\": \"0.18.10\",\n    \"targz\": \"^1.0.1\",\n    \"through2\": \"^2.0.0\",\n    \"tmp\": \"~0.0.28\",\n    \"tsickle\": \"0.34.3\",\n    \"tslib\": \"^1.9.0\",\n    \"tslint\": \"5.7.0\",\n    \"tslint-eslint-rules\": \"4.1.1\",\n    \"tslint-no-toplevel-property-access\": \"0.0.2\",\n    \"tsutils\": \"2.27.2\",\n    \"typescript\": \"~1.8.10\",\n    \"uglifyjs-webpack-plugin\": \"^1.0.0\",\n    \"universal-analytics\": \"0.4.15\",\n    \"url-loader\": \"^0.5.8\",\n    \"vlq\": \"0.2.2\",\n    \"vrsource-tslint-rules\": \"5.1.1\",\n    \"vue-jest\": \"^1.0.0\",\n    \"vue-loader\": \"^1.0.0\",\n    \"vue-style-loader\": \"^1.0.0\",\n    \"vue-template-compiler\": \"^1.0.0\",\n    \"webpack\": \"1.12.9\",\n    \"webpack-bundle-analyzer\": \"^1.0.0\",\n    \"webpack-dev-server\": \"^1.0.0\",\n    \"webpack-merge\": \"^1.0.0\",\n    \"xhr2\": \"0.1.4\",\n    \"yargs\": \"13.1.0\",\n    \"zone.js\": \"^0.9.1\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/ncu/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"express\": \"^1.0.0\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/ncu/package2.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"lodash.map\": \"2.0.0\",\n    \"lodash.filter\": \"2.0.0\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/peer-post-upgrade/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@vitest/ui\": \"1.3.1\",\n    \"vitest\": \"1.3.1\",\n    \"eslint\": \"8.57.0\",\n    \"eslint-plugin-import\": \"2.29.1\",\n    \"eslint-plugin-unused-imports\": \"3.0.0\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/peer-post-upgrade-no-upgrades/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"eslint\": \"8.57.0\",\n    \"eslint-plugin-import\": \"2.29.1\",\n    \"eslint-plugin-unused-imports\": \"3.0.0\"\n  }\n}\n"
  },
  {
    "path": "test/test-data/registry.json",
    "content": "{\n  \"ncu-test-v2\": \"99.9.9\"\n}\n"
  },
  {
    "path": "test/test-data/workspace-basic/package.json",
    "content": "{\n  \"name\": \"basic-test-workspaces\",\n  \"license\": \"MIT\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"dependencies\": {},\n  \"workspaces\": {\n    \"packages\": [\n      \"pkg/sub\"\n    ]\n  }\n}\n"
  },
  {
    "path": "test/test-data/workspace-basic/pkg/sub/package.json",
    "content": "{\n  \"name\": \"basic-sub-package\",\n  \"license\": \"MIT\",\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "test/test-data/workspace-no-sub-packages/package.json",
    "content": "{\n  \"name\": \"test-workspace-no-sub-packages\",\n  \"license\": \"MIT\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"dependencies\": {},\n  \"workspaces\": {\n    \"packages\": []\n  }\n}\n"
  },
  {
    "path": "test/test-data/workspace-sub-package-names/package.json",
    "content": "{\n  \"name\": \"basic-test-workspaces\",\n  \"license\": \"MIT\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"dependencies\": {},\n  \"workspaces\": {\n    \"packages\": [\n      \"pkg/a-different-name-to-dirname\",\n      \"pkg/dirname-matches-name\",\n      \"pkg/dirname-will-become-name\"\n    ]\n  }\n}\n"
  },
  {
    "path": "test/test-data/workspace-sub-package-names/pkg/dirname-does-not-match-name/package.json",
    "content": "{\n  \"name\": \"a-different-name-to-dirname\",\n  \"license\": \"MIT\",\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "test/test-data/workspace-sub-package-names/pkg/dirname-matches-name/package.json",
    "content": "{\n  \"name\": \"dirname-matches-name\",\n  \"license\": \"MIT\",\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "test/test-data/workspace-sub-package-names/pkg/dirname-will-become-name/package.json",
    "content": "{\n  \"license\": \"MIT\",\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "test/test-data/workspace-sub-package-names/pkg/unlisted/package.json",
    "content": "{\n  \"name\": \"this-should-never-be-listed\",\n  \"license\": \"MIT\",\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "test/test-data/workspace-workspace-param-is-array/package.json",
    "content": "{\n  \"name\": \"test-workspace-no-sub-packages\",\n  \"license\": \"MIT\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"dependencies\": {},\n  \"workspaces\": []\n}\n"
  },
  {
    "path": "test/timeout.test.ts",
    "content": "import fs from 'fs/promises'\nimport path from 'path'\nimport spawn from 'spawn-please'\nimport ncu from '../src/'\nimport chaiSetup from './helpers/chaiSetup'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\nconst bin = path.join(__dirname, '../build/cli.js')\n\ndescribe('timeout', function () {\n  it('throw an exception instead of printing to the console when timeout is exceeded', async () => {\n    const pkgPath = path.join(__dirname, './test-data/ncu/package-large.json')\n    return ncu({\n      packageData: await fs.readFile(pkgPath, 'utf-8'),\n      timeout: 1,\n    }).should.eventually.be.rejectedWith(/Exceeded global timeout of 1ms|Idle timeout reached/)\n  })\n\n  it('exit with error when timeout is exceeded', async () => {\n    return spawn('node', [bin, '--timeout', '1'], {\n      stdin: '{ \"dependencies\": { \"express\": \"1\" } }',\n    }).should.eventually.be.rejectedWith(/Exceeded global timeout of 1ms|Idle timeout reached/)\n  })\n\n  it('completes successfully with timeout', async () => {\n    const stub = stubVersions('99.9.9', { spawn: true })\n    await spawn('node', [bin, '--timeout', '100000'], { stdin: '{ \"dependencies\": { \"express\": \"1\" } }' })\n    stub.restore()\n  })\n})\n"
  },
  {
    "path": "test/upgradeDependencies.test.ts",
    "content": "import upgradeDependencies from '../src/lib/upgradeDependencies'\nimport chaiSetup from './helpers/chaiSetup'\n\nchaiSetup()\n\ndescribe('upgradeDependencies', () => {\n  it('upgrade simple, non-semver versions', () => {\n    upgradeDependencies({ foo: '1' }, { foo: '2' }).should.eql({ foo: '2' })\n    upgradeDependencies({ foo: '1.0' }, { foo: '1.1' }).should.eql({ foo: '1.1' })\n    upgradeDependencies({ 'ncu-test-simple-tag': 'v1' }, { 'ncu-test-simple-tag': 'v3' }).should.eql({\n      'ncu-test-simple-tag': 'v3',\n    })\n  })\n\n  it('upgrade github dependencies', () => {\n    upgradeDependencies({ foo: 'github:foo/bar#v1' }, { foo: 'github:foo/bar#v2' }).should.eql({\n      foo: 'github:foo/bar#v2',\n    })\n    upgradeDependencies({ foo: 'github:foo/bar#v1.0' }, { foo: 'github:foo/bar#v2.0' }).should.eql({\n      foo: 'github:foo/bar#v2.0',\n    })\n    upgradeDependencies({ foo: 'github:foo/bar#v1.0.0' }, { foo: 'github:foo/bar#v2.0.0' }).should.eql({\n      foo: 'github:foo/bar#v2.0.0',\n    })\n  })\n\n  it('upgrade latest versions that already satisfy the specified version', () => {\n    upgradeDependencies({ mongodb: '^1.0.0' }, { mongodb: '1.4.30' }).should.eql({\n      mongodb: '^1.4.30',\n    })\n  })\n\n  it('do not downgrade', () => {\n    upgradeDependencies({ mongodb: '^2.0.7' }, { mongodb: '1.4.30' }).should.eql({})\n  })\n\n  it('allow to update to latest via @latest tag', () => {\n    upgradeDependencies({ mongodb: '^1.5.0-alpha.1' }, { mongodb: '1.4.30' }, { target: '@latest' }).should.eql({\n      mongodb: '^1.4.30',\n    })\n  })\n\n  it('use the preferred wildcard when converting <, closed, or mixed ranges', () => {\n    upgradeDependencies({ a: '1.*', mongodb: '<1.0' }, { mongodb: '3.0.0' }).should.eql({ mongodb: '3.*' })\n    upgradeDependencies({ a: '1.x', mongodb: '<1.0' }, { mongodb: '3.0.0' }).should.eql({ mongodb: '3.x' })\n    upgradeDependencies({ a: '~1', mongodb: '<1.0' }, { mongodb: '3.0.0' }).should.eql({ mongodb: '~3.0' })\n    upgradeDependencies({ a: '^1', mongodb: '<1.0' }, { mongodb: '3.0.0' }).should.eql({ mongodb: '^3.0' })\n\n    upgradeDependencies({ a: '1.*', mongodb: '1.0 < 2.0' }, { mongodb: '3.0.0' }).should.eql({ mongodb: '3.*' })\n    upgradeDependencies({ mongodb: '1.0 < 2.*' }, { mongodb: '3.0.0' }).should.eql({ mongodb: '3.*' })\n  })\n\n  it('convert closed ranges to caret (^) when preferred wildcard is unknown', () => {\n    upgradeDependencies({ mongodb: '1.0 < 2.0' }, { mongodb: '3.0.0' }).should.eql({ mongodb: '^3.0' })\n  })\n\n  it('ignore packages with empty values', () => {\n    upgradeDependencies({ mongodb: null }, { mongodb: '1.4.30' }).should.eql({})\n    upgradeDependencies({ mongodb: '' }, { mongodb: '1.4.30' }).should.eql({})\n  })\n})\n"
  },
  {
    "path": "test/upgradeYamlCatalogDependencies.test.ts",
    "content": "import { updateYamlCatalogDependencies } from '../src/lib/upgradeYamlCatalogDependencies'\nimport chaiSetup from './helpers/chaiSetup'\n\nconst should = chaiSetup()\n\ndescribe('updateYamlCatalogDependencies', () => {\n  it('updates a catalog dependency while preserving quotes', () => {\n    const yaml = `nodeLinker: node-modules\n\ncatalog:\n  react: '18.3.1'\n`\n\n    const updated = updateYamlCatalogDependencies({\n      fileContent: yaml,\n      upgrade: { path: ['catalog', 'react'], newValue: '19.0.0' },\n    })\n\n    should.exist(updated)\n    updated!.should.equal(`nodeLinker: node-modules\n\ncatalog:\n  react: '19.0.0'\n`)\n  })\n\n  it('updates a named catalog dependency', () => {\n    const yaml = `nodeLinker: node-modules\n\ncatalogs:\n  react17:\n    react: 17.0.0\n`\n\n    const updated = updateYamlCatalogDependencies({\n      fileContent: yaml,\n      upgrade: { path: ['catalogs', 'react17', 'react'], newValue: '19.0.0' },\n    })\n\n    should.exist(updated)\n    updated!.should.equal(`nodeLinker: node-modules\n\ncatalogs:\n  react17:\n    react: 19.0.0\n`)\n  })\n\n  it('returns the original content if the version already matches', () => {\n    const yaml = `catalog:\n  react: 19.0.0\n`\n\n    const updated = updateYamlCatalogDependencies({\n      fileContent: yaml,\n      upgrade: { path: ['catalog', 'react'], newValue: '19.0.0' },\n    })\n\n    should.exist(updated)\n    updated!.should.equal(yaml)\n  })\n\n  it('returns null when the dependency is not present in the target catalog', () => {\n    const yaml = `catalogs:\n  react18:\n    react: 18.3.1\n`\n\n    const updated = updateYamlCatalogDependencies({\n      fileContent: yaml,\n      upgrade: { path: ['catalogs', 'react17', 'react'], newValue: '19.0.0' },\n    })\n\n    should.equal(updated, null)\n  })\n\n  it('returns null when the dependency value is an alias', () => {\n    const yaml = `__deps:\n  react: &react 18.3.1\n\ncatalog:\n  react: *react\n`\n\n    const updated = updateYamlCatalogDependencies({\n      fileContent: yaml,\n      upgrade: { path: ['catalog', 'react'], newValue: '19.0.0' },\n    })\n\n    should.equal(updated, null)\n  })\n\n  it('preserves inline comments and spacing', () => {\n    const yaml = `catalog:\n  react:    18.3.1 # keep this\n  react-dom: 18.3.1\n`\n\n    const updated = updateYamlCatalogDependencies({\n      fileContent: yaml,\n      upgrade: { path: ['catalog', 'react'], newValue: '19.0.0' },\n    })\n\n    should.exist(updated)\n    updated!.should.equal(`catalog:\n  react:    19.0.0 # keep this\n  react-dom: 18.3.1\n`)\n  })\n\n  it('throws on invalid yaml syntax', () => {\n    const yaml = `catalog:\n  react: [18.3.1\n`\n\n    ;(() =>\n      updateYamlCatalogDependencies({\n        fileContent: yaml,\n        upgrade: { path: ['catalog', 'react'], newValue: '19.0.0' },\n      })).should.throw('Invalid YAML syntax')\n  })\n})\n"
  },
  {
    "path": "test/version-util.test.ts",
    "content": "import chalk, { chalkInit } from '../src/lib/chalk'\nimport * as versionUtil from '../src/lib/version-util'\nimport chaiSetup from './helpers/chaiSetup'\n\nconst should = chaiSetup()\n\ndescribe('version-util', () => {\n  describe('upgradeDependencyDeclaration', () => {\n    it('numeric upgrades', () => {\n      versionUtil.upgradeDependencyDeclaration('0', '1.0.0').should.equal('1')\n      versionUtil.upgradeDependencyDeclaration('1', '10.0.0').should.equal('10')\n\n      versionUtil.upgradeDependencyDeclaration('0.1', '1.0.0').should.equal('1.0')\n      versionUtil.upgradeDependencyDeclaration('1.0', '1.1.0').should.equal('1.1')\n\n      versionUtil.upgradeDependencyDeclaration('1.0.0', '1.0.1').should.equal('1.0.1')\n      versionUtil.upgradeDependencyDeclaration('1.0.1', '1.1.0').should.equal('1.1.0')\n      versionUtil.upgradeDependencyDeclaration('2.0.1', '2.0.11').should.equal('2.0.11')\n    })\n\n    it('wildcard upgrades', () => {\n      versionUtil.upgradeDependencyDeclaration('1.x', '1.1.0').should.equal('1.x')\n      versionUtil.upgradeDependencyDeclaration('1.x.1', '1.1.2').should.equal('1.x.2')\n      versionUtil.upgradeDependencyDeclaration('1.0.x', '1.1.1').should.equal('1.1.x')\n      versionUtil.upgradeDependencyDeclaration('1.0.x', '1.1.0').should.equal('1.1.x')\n      versionUtil.upgradeDependencyDeclaration('1.0.x', '2.0.0').should.equal('2.0.x')\n\n      versionUtil.upgradeDependencyDeclaration('*', '1.0.0').should.equal('*')\n      versionUtil.upgradeDependencyDeclaration('1.*', '2.0.1').should.equal('2.*')\n\n      versionUtil.upgradeDependencyDeclaration('^*', '1.0.0').should.equal('^*')\n\n      versionUtil.upgradeDependencyDeclaration('x', '1.0.0').should.equal('x')\n      versionUtil.upgradeDependencyDeclaration('x.x', '1.0.0').should.equal('x.x')\n      versionUtil.upgradeDependencyDeclaration('x.x.x', '1.0.0').should.equal('x.x.x')\n    })\n\n    it('convert < to ^', () => {\n      versionUtil.upgradeDependencyDeclaration('<1', '2.1.0').should.equal('^2')\n      versionUtil.upgradeDependencyDeclaration('<1.0', '1.1.0').should.equal('^1.1')\n    })\n\n    it('preserve >=', () => {\n      versionUtil.upgradeDependencyDeclaration('>=1.0', '2.0.0').should.equal('>=2.0')\n    })\n\n    it('convert > to >=', () => {\n      versionUtil.upgradeDependencyDeclaration('>1.0.0', '2.0.0').should.equal('>=2.0.0')\n    })\n\n    it('preserve ^ and ~', () => {\n      versionUtil.upgradeDependencyDeclaration('^1.2.3', '1.2.4').should.equal('^1.2.4')\n      versionUtil.upgradeDependencyDeclaration('~1.2.3', '1.2.4').should.equal('~1.2.4')\n    })\n\n    it('preserve prerelease versions', () => {\n      versionUtil.upgradeDependencyDeclaration('^0.15.7', '0.16.0-beta.3').should.equal('^0.16.0-beta.3')\n    })\n\n    it('replace multiple ranges with ^', () => {\n      versionUtil.upgradeDependencyDeclaration('>1.0 >2.0 < 3.0', '3.1.0').should.equal('^3.1')\n    })\n\n    it('handle ||', () => {\n      versionUtil.upgradeDependencyDeclaration('~1.0 || ~1.2', '3.1.0').should.equal('~3.1')\n    })\n\n    it('hyphen (-) range', () => {\n      versionUtil.upgradeDependencyDeclaration('1.0 - 2.0', '3.1.0').should.equal('3.1')\n    })\n\n    it('use the range with the fewest parts if there are multiple ranges', () => {\n      versionUtil.upgradeDependencyDeclaration('1.1 || 1.2.0', '3.1.0').should.equal('3.1')\n      versionUtil.upgradeDependencyDeclaration('1.2.0 || 1.1', '3.1.0').should.equal('3.1')\n    })\n\n    it('preserve wildcards in comparisons', () => {\n      versionUtil.upgradeDependencyDeclaration('1.x < 1.2.0', '3.1.0').should.equal('3.x')\n    })\n\n    it('use the first operator if a comparison has mixed operators', () => {\n      versionUtil.upgradeDependencyDeclaration('1.x < 1.*', '3.1.0').should.equal('3.x')\n    })\n\n    it(\"maintain 'unclean' semantic versions\", () => {\n      versionUtil.upgradeDependencyDeclaration('v1.0', '1.1').should.equal('v1.1')\n      versionUtil.upgradeDependencyDeclaration('=v1.0', '1.1').should.equal('=v1.1')\n      versionUtil.upgradeDependencyDeclaration(' =v1.0', '1.1').should.equal('=v1.1')\n    })\n\n    it(\"maintain 'unclean' semantic versions\", () => {\n      versionUtil.upgradeDependencyDeclaration('v1.0', '1.1').should.equal('v1.1')\n      versionUtil.upgradeDependencyDeclaration('=v1.0', '1.1').should.equal('=v1.1')\n      versionUtil.upgradeDependencyDeclaration(' =v1.0', '1.1').should.equal('=v1.1')\n    })\n\n    it('maintain existing version if new version is unknown', () => {\n      versionUtil.upgradeDependencyDeclaration('1.0', '').should.equal('1.0')\n      versionUtil.upgradeDependencyDeclaration('1.0', null).should.equal('1.0')\n    })\n\n    it('remove semver range if removeRange option is specified', () => {\n      versionUtil.upgradeDependencyDeclaration('^1.0.0', '1.0.1', { removeRange: true }).should.equal('1.0.1')\n      versionUtil.upgradeDependencyDeclaration('2.2.*', '3.1.1', { removeRange: true }).should.equal('3.1.1')\n    })\n  })\n\n  describe('numParts', () => {\n    it('count the number of parts in a version', () => {\n      versionUtil.numParts('1').should.equal(1)\n      versionUtil.numParts('1.2').should.equal(2)\n      versionUtil.numParts('1.2.3').should.equal(3)\n      versionUtil.numParts('1.2.3-alpha.1').should.equal(4)\n      versionUtil.numParts('1.2.3+build12345').should.equal(4)\n    })\n  })\n\n  describe('getPrecision', () => {\n    it('detect versions as precise as \"major\"', () => {\n      versionUtil.getPrecision('1')!.should.equal('major')\n    })\n\n    it('detect versions as precise as \"minor\"', () => {\n      versionUtil.getPrecision('1.2')!.should.equal('minor')\n    })\n\n    it('detect versions as precise as \"patch\"', () => {\n      versionUtil.getPrecision('1.2.3')!.should.equal('patch')\n    })\n\n    it('detect versions as precise as \"release\"', () => {\n      versionUtil.getPrecision('1.2.3-alpha.1')!.should.equal('release')\n      versionUtil.getPrecision('1.2.3-beta.1')!.should.equal('release')\n      versionUtil.getPrecision('1.2.3-rc.1')!.should.equal('release')\n      versionUtil.getPrecision('1.2.3-alpha')!.should.equal('release')\n      versionUtil.getPrecision('1.2.3-beta')!.should.equal('release')\n      versionUtil.getPrecision('1.2.3-rc')!.should.equal('release')\n    })\n\n    it('detect versions as precise as \"build\"', () => {\n      versionUtil.getPrecision('1.2.3+build12345')!.should.equal('build')\n    })\n  })\n\n  describe('stringify', () => {\n    it('build a version string of the given parts', () => {\n      versionUtil.stringify({ major: '1' }).should.equal('1')\n\n      versionUtil\n        .stringify({\n          major: '1',\n          minor: '2',\n        })\n        .should.equal('1.2')\n\n      versionUtil\n        .stringify({\n          major: '1',\n          minor: '2',\n          patch: '3',\n        })\n        .should.equal('1.2.3')\n\n      versionUtil\n        .stringify({\n          major: '1',\n          minor: '2',\n          patch: '3',\n          release: 'alpha.1',\n        })\n        .should.equal('1.2.3-alpha.1')\n\n      versionUtil\n        .stringify({\n          major: '1',\n          minor: '2',\n          patch: '3',\n          build: 'build12345',\n        })\n        .should.equal('1.2.3+build12345')\n    })\n\n    it('pad the version with an optional precision argument', () => {\n      versionUtil.stringify({ major: '1' }, 'minor').should.equal('1.0')\n      versionUtil.stringify({ major: '1' }, 'patch').should.equal('1.0.0')\n    })\n\n    it('truncate the version when a precision is provided', () => {\n      versionUtil\n        .stringify(\n          {\n            major: '1',\n            minor: '2',\n            patch: '3',\n            build: 'build12345',\n          },\n          'patch',\n        )\n        .should.equal('1.2.3')\n      versionUtil\n        .stringify(\n          {\n            major: '1',\n            minor: '2',\n            patch: '3',\n            build: 'build12345',\n          },\n          'minor',\n        )\n        .should.equal('1.2')\n      versionUtil\n        .stringify(\n          {\n            major: '1',\n            minor: '2',\n            patch: '3',\n            build: 'build12345',\n          },\n          'major',\n        )\n        .should.equal('1')\n    })\n  })\n\n  describe('setPrecision', () => {\n    it('set the precision of a version at \"major\"', () => {\n      versionUtil.setPrecision('1.2.3-alpha.1', 'major').should.equal('1')\n    })\n\n    it('set the precision of a version at \"minor\"', () => {\n      versionUtil.setPrecision('1.2.3-alpha.1', 'minor').should.equal('1.2')\n    })\n\n    it('add 0 to minor if needed', () => {\n      versionUtil.setPrecision('1', 'minor').should.equal('1.0')\n    })\n\n    it('set the precision of a version at \"patch\"', () => {\n      versionUtil.setPrecision('1.2.3-alpha.1', 'patch').should.equal('1.2.3')\n    })\n\n    it('add 0 to patch if needed', () => {\n      versionUtil.setPrecision('1', 'patch').should.equal('1.0.0')\n    })\n\n    it('set the precision of a version at \"release\"', () => {\n      versionUtil.setPrecision('1.2.3-alpha.1', 'release').should.equal('1.2.3-alpha.1')\n    })\n\n    it('set the precision of a version at \"build\"', () => {\n      versionUtil.setPrecision('1.2.3+build12345', 'build').should.equal('1.2.3+build12345')\n    })\n  })\n\n  describe('isComparable', () => {\n    it('a version without a preid is comparable to any version', () => {\n      versionUtil.isComparable('2.0.1', '0.0.1').should.equal(true)\n      versionUtil.isComparable('1.2.3-1', '1.2.3').should.equal(true)\n      versionUtil.isComparable('1.3.3', '1.2.3-2').should.equal(true)\n      versionUtil.isComparable('2.0.1-1', '0.0.1-2').should.equal(true)\n      versionUtil.isComparable('1.2.3-alpha.1', '1.2.3').should.equal(true)\n      versionUtil.isComparable('1.3.3', '1.2.3-alpha.2').should.equal(true)\n      versionUtil.isComparable('1.2.3-.dev.1', '1.2.3').should.equal(true)\n      versionUtil.isComparable('1.2.3', '1.2.3-next.dev.1').should.equal(true)\n      versionUtil.isComparable('1.2.3-next.dev.5', '1.2.3-next.dev.1').should.equal(true)\n    })\n\n    it('versions with non-matching preids are not comparable', () => {\n      versionUtil.isComparable('1.2.3-1', '1.2.3-dev.1').should.equal(false)\n      versionUtil.isComparable('1.3.3-next.1', '1.2.3-dev.2').should.equal(false)\n      versionUtil.isComparable('2.0.1-next.1', '0.0.1-task-42.2').should.equal(false)\n      versionUtil.isComparable('1.2.3-next.0', '1.2.3-next.dev.0').should.equal(false)\n      versionUtil.isComparable('1.2.3-alpha.0', '1.2.3-next.dev.0').should.equal(false)\n    })\n\n    it('versions with matching preids are comparable', () => {\n      versionUtil.isComparable('1.3.3-next.1', '1.2.3-next.2').should.equal(true)\n    })\n  })\n\n  describe('precisionAdd', () => {\n    it('handle precision increase/decrease of base precisions', () => {\n      versionUtil.precisionAdd('major', 0).should.equal('major')\n      versionUtil.precisionAdd('major', 1).should.equal('minor')\n      versionUtil.precisionAdd('major', 2).should.equal('patch')\n      versionUtil.precisionAdd('minor', -1).should.equal('major')\n      versionUtil.precisionAdd('minor', 0).should.equal('minor')\n      versionUtil.precisionAdd('minor', 1).should.equal('patch')\n      versionUtil.precisionAdd('patch', -2).should.equal('major')\n      versionUtil.precisionAdd('patch', -1).should.equal('minor')\n      versionUtil.precisionAdd('patch', 0).should.equal('patch')\n    })\n\n    it('handle precision decrease of added precisions (release, build)', () => {\n      versionUtil.precisionAdd('build', -1).should.equal('patch')\n      versionUtil.precisionAdd('build', -2).should.equal('minor')\n      versionUtil.precisionAdd('build', -3).should.equal('major')\n      versionUtil.precisionAdd('release', -1).should.equal('patch')\n      versionUtil.precisionAdd('release', -2).should.equal('minor')\n      versionUtil.precisionAdd('release', -3).should.equal('major')\n    })\n  })\n\n  describe('addWildCard', () => {\n    it('add ~', () => {\n      versionUtil.addWildCard('1', '~').should.equal('~1')\n      versionUtil.addWildCard('1.2', '~').should.equal('~1.2')\n      versionUtil.addWildCard('1.2.3', '~').should.equal('~1.2.3')\n      versionUtil.addWildCard('1.2.3-alpha.1', '~').should.equal('~1.2.3-alpha.1')\n      versionUtil.addWildCard('1.2.3+build12345', '~').should.equal('~1.2.3+build12345')\n    })\n    it('add ^', () => {\n      versionUtil.addWildCard('1', '^').should.equal('^1')\n      versionUtil.addWildCard('1.2', '^').should.equal('^1.2')\n      versionUtil.addWildCard('1.2.3', '^').should.equal('^1.2.3')\n      versionUtil.addWildCard('1.2.3-alpha.1', '^').should.equal('^1.2.3-alpha.1')\n      versionUtil.addWildCard('1.2.3+build12345', '^').should.equal('^1.2.3+build12345')\n    })\n    it('add .*', () => {\n      versionUtil.addWildCard('1', '.*').should.equal('1.*')\n      versionUtil.addWildCard('1.2', '.*').should.equal('1.*')\n      versionUtil.addWildCard('1.2.3', '.*').should.equal('1.*')\n      versionUtil.addWildCard('1.2.3-alpha.1', '.*').should.equal('1.*')\n      versionUtil.addWildCard('1.2.3+build12345', '.*').should.equal('1.*')\n    })\n    it('add .x', () => {\n      versionUtil.addWildCard('1', '.x').should.equal('1.x')\n      versionUtil.addWildCard('1.2', '.x').should.equal('1.x')\n      versionUtil.addWildCard('1.2.3', '.x').should.equal('1.x')\n      versionUtil.addWildCard('1.2.3-alpha.1', '.x').should.equal('1.x')\n      versionUtil.addWildCard('1.2.3+build12345', '.x').should.equal('1.x')\n    })\n  })\n\n  describe('isWildCard', () => {\n    it('return true for ~', () => {\n      versionUtil.isWildCard('~').should.equal(true)\n    })\n    it('return true for ^', () => {\n      versionUtil.isWildCard('^').should.equal(true)\n    })\n    it('return true for ^*', () => {\n      versionUtil.isWildCard('^*').should.equal(true)\n    })\n    it('return true for *', () => {\n      versionUtil.isWildCard('*').should.equal(true)\n    })\n    it('return true for x', () => {\n      versionUtil.isWildCard('x').should.equal(true)\n    })\n    it('return true for x.x', () => {\n      versionUtil.isWildCard('x.x').should.equal(true)\n    })\n    it('return true for x.x.x', () => {\n      versionUtil.isWildCard('x.x.x').should.equal(true)\n    })\n    it('return false for strings that more than a wildcard', () => {\n      versionUtil.isWildCard('^0.15.0').should.equal(false)\n      versionUtil.isWildCard('1.*').should.equal(false)\n    })\n  })\n\n  describe('isWildPart', () => {\n    it('return true for *', () => {\n      versionUtil.isWildPart('*').should.equal(true)\n    })\n    it('return true for x', () => {\n      versionUtil.isWildPart('x').should.equal(true)\n    })\n    it('return false for anything other than * or x', () => {\n      versionUtil.isWildPart('^').should.equal(false)\n      versionUtil.isWildPart('~').should.equal(false)\n      versionUtil.isWildPart('1.*').should.equal(false)\n      versionUtil.isWildPart('1.x').should.equal(false)\n      versionUtil.isWildPart('^0.15.0').should.equal(false)\n    })\n  })\n\n  describe('partChanged', () => {\n    it('nothing changed', () => {\n      versionUtil.partChanged('1.0.0', '1.0.0')!.should.equal('none')\n    })\n    it('patch changed', () => {\n      versionUtil.partChanged('1.0.0', '1.0.1')!.should.equal('patch')\n      versionUtil.partChanged('1.0.10', '1.0.11')!.should.equal('patch')\n    })\n    it('minor changed', () => {\n      versionUtil.partChanged('1.0.0', '1.1.0')!.should.equal('minor')\n    })\n    it('major changed', () => {\n      versionUtil.partChanged('1.0.0', '2.0.0')!.should.equal('major')\n      versionUtil.partChanged('^1.0.0', '^2.0.0')!.should.equal('major')\n      versionUtil.partChanged('~1.0.0', '~2.0.0')!.should.equal('major')\n    })\n    it('major version zero changed', () => {\n      versionUtil.partChanged('0.1.0', '0.2.0')!.should.equal('majorVersionZero')\n      versionUtil.partChanged('0.1.0', '0.1.1')!.should.equal('majorVersionZero')\n      versionUtil.partChanged('~0.1.0', '~0.1.1')!.should.equal('majorVersionZero')\n    })\n  })\n\n  describe('colorizeDiff', async () => {\n    await chalkInit()\n    it('do not colorize unchanged versions', () => {\n      versionUtil.colorizeDiff('1.0.0', '1.0.0').should.equal('1.0.0')\n    })\n    it('colorize changed patch versions', () => {\n      versionUtil.colorizeDiff('1.0.0', '1.0.1').should.equal(`1.0.${chalk.green('1')}`)\n    })\n    it('colorize changed minor versions', () => {\n      versionUtil.colorizeDiff('1.0.0', '1.1.0').should.equal(`1.${chalk.cyan('1.0')}`)\n    })\n    it('colorize changed major versions', () => {\n      versionUtil.colorizeDiff('1.0.0', '2.0.0').should.equal(chalk.red('2.0.0'))\n    })\n    it('colorize whole parts', () => {\n      versionUtil.colorizeDiff('1.0.10', '1.0.11').should.equal(`1.0.${chalk.green('11')}`)\n    })\n    it('do not include the leading ^ or ~ if the same', () => {\n      versionUtil.colorizeDiff('^1.0.0', '^2.0.0').should.equal(`^${chalk.red('2.0.0')}`)\n      versionUtil.colorizeDiff('~1.0.0', '~2.0.0').should.equal(`~${chalk.red('2.0.0')}`)\n    })\n    it('colorize changed versions before 1.0.0 as breaking', () => {\n      versionUtil.colorizeDiff('0.1.0', '0.2.0').should.equal(`0.${chalk.red('2.0')}`)\n      versionUtil.colorizeDiff('0.1.0', '0.1.1').should.equal(`0.1.${chalk.red('1')}`)\n      versionUtil.colorizeDiff('~0.1.0', '~0.1.1').should.equal(`~0.1.${chalk.red('1')}`)\n    })\n  })\n\n  describe('findGreatestByLevel', () => {\n    it('find the greatest version at the given semantic versioning level', () => {\n      const versions = ['0.1.0', '1.0.0', '1.0.1', '1.1.0', '2.0.1']\n\n      versionUtil.findGreatestByLevel(versions, '1.0.0', 'major')!.should.equal('2.0.1')\n      versionUtil.findGreatestByLevel(versions, '2.0.0', 'major')!.should.equal('2.0.1')\n      versionUtil.findGreatestByLevel(versions, '1.0.0', 'minor')!.should.equal('1.1.0')\n      versionUtil.findGreatestByLevel(versions, '1.1.0', 'minor')!.should.equal('1.1.0')\n      versionUtil.findGreatestByLevel(versions, '1.0.0', 'patch')!.should.equal('1.0.1')\n      versionUtil.findGreatestByLevel(versions, '1.0.1', 'patch')!.should.equal('1.0.1')\n    })\n\n    it('find the greatest prerelease version', () => {\n      const versions = ['0.1.0', '0.2.0-alpha.0', '0.2.0-alpha.1']\n      versionUtil.findGreatestByLevel(versions, '0.1.0', 'major')!.should.equal('0.2.0-alpha.1')\n      versionUtil.findGreatestByLevel(versions, '0.1.0', 'minor')!.should.equal('0.2.0-alpha.1')\n      versionUtil.findGreatestByLevel(versions, '0.1.0', 'patch')!.should.equal('0.1.0')\n    })\n\n    it('handle wildcards', () => {\n      const versions = ['1.0.1', '1.0.2']\n\n      should.equal(versionUtil.findGreatestByLevel(versions, '^1.0.1', 'minor'), '1.0.2')\n      should.equal(versionUtil.findGreatestByLevel(versions, '1.*', 'minor'), '1.0.2')\n      should.equal(versionUtil.findGreatestByLevel(versions, '1.1', 'minor'), '1.0.2')\n      should.equal(versionUtil.findGreatestByLevel(versions, '1.x', 'minor'), '1.0.2')\n      should.equal(versionUtil.findGreatestByLevel(versions, '>1.1', 'minor'), '1.0.2')\n    })\n\n    it('sort version list', () => {\n      const versions = ['0.1.0', '0.3.0', '0.2.0']\n      versionUtil.findGreatestByLevel(versions, '0.1.0', 'minor')!.should.equal('0.3.0')\n    })\n  })\n\n  describe('isPre', () => {\n    it('return false for non-prerelease versions', () => {\n      versionUtil.isPre('1.0.0').should.equal(false)\n    })\n\n    it('return true for prerelease versions', () => {\n      versionUtil.isPre('1.0.0-alpha').should.equal(true)\n      versionUtil.isPre('1.0.0-beta').should.equal(true)\n      versionUtil.isPre('1.0.0-rc').should.equal(true)\n    })\n  })\n\n  describe('npm aliases', () => {\n    describe('createNpmAlias', () => {\n      it('create an npm alias from a name and version', () => {\n        versionUtil.createNpmAlias('chalk', '1.0.0').should.equal('npm:chalk@1.0.0')\n      })\n    })\n\n    describe('parseNpmAlias', () => {\n      it('parse an npm alias into [name, version]', () => {\n        versionUtil.parseNpmAlias('npm:chalk@1.0.0')!.should.eql(['chalk', '1.0.0'])\n      })\n\n      it('return null if given a non-alias', () => {\n        should.equal(versionUtil.parseNpmAlias('1.0.0'), null)\n      })\n    })\n\n    describe('isNpmAlias', () => {\n      it('return true if an npm alias', () => {\n        should.equal(versionUtil.isNpmAlias('npm:chalk@1.0.0'), true)\n        should.equal(versionUtil.isNpmAlias('npm:chalk@^1.0.0'), true)\n        should.equal(versionUtil.isNpmAlias('npm:postman-request@2.88.1-postman.33'), true)\n      })\n\n      it('return false if not an npm alias', () => {\n        should.equal(versionUtil.isNpmAlias('1.0.0'), false)\n        should.equal(versionUtil.isNpmAlias('npm:chalk'), false)\n      })\n    })\n\n    describe('upgradeNpmAlias', () => {\n      it('replace embedded version', () => {\n        versionUtil.upgradeNpmAlias('npm:chalk@^1.0.0', '2.0.0')!.should.equal('npm:chalk@2.0.0')\n        versionUtil\n          .upgradeNpmAlias('npm:postman-request@2.88.1-postman.32', '2.88.1-postman.33')!\n          .should.equal('npm:postman-request@2.88.1-postman.33')\n      })\n    })\n  })\n\n  describe('github urls', () => {\n    describe('isGitHubUrl', () => {\n      it('return true if a declaration is a GitHub url with a semver tag and false otherwise', () => {\n        should.equal(versionUtil.isGitHubUrl(null), false)\n        should.equal(versionUtil.isGitHubUrl('https://github.com/raineorshine/ncu-test-v2'), false)\n        should.equal(versionUtil.isGitHubUrl('https://github.com/raineorshine/ncu-test-v2#1.0.0'), true)\n        should.equal(versionUtil.isGitHubUrl('https://github.com/raineorshine/ncu-test-v2#v1.0.0'), true)\n      })\n    })\n\n    describe('getGitHubUrlTag', () => {\n      it('return an embedded tag in a GitHub URL, or null if not valid', () => {\n        should.equal(versionUtil.getGitHubUrlTag(null), null)\n        should.equal(versionUtil.getGitHubUrlTag('https://github.com/raineorshine/ncu-test-v2'), null)\n        should.equal(versionUtil.getGitHubUrlTag('https://github.com/raineorshine/ncu-test-v2#1.0.0'), '1.0.0')\n        should.equal(versionUtil.getGitHubUrlTag('https://github.com/raineorshine/ncu-test-v2#v1.0.0'), 'v1.0.0')\n      })\n    })\n\n    describe('upgradeGitHubUrl', () => {\n      it('replace embedded version', () => {\n        versionUtil\n          .upgradeGitHubUrl('https://github.com/raineorshine/ncu-test-v2#v1.0.0', 'v2.0.0')\n          .should.equal('https://github.com/raineorshine/ncu-test-v2#v2.0.0')\n        versionUtil\n          .upgradeGitHubUrl('https://github.com/raineorshine/ncu-test-v2#1.0.0', '2.0.0')\n          .should.equal('https://github.com/raineorshine/ncu-test-v2#2.0.0')\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "test/workspaces.test.ts",
    "content": "/* eslint-disable @typescript-eslint/no-unused-expressions */\n// eslint doesn't like .should.be.empty syntax\nimport fs from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport spawn from 'spawn-please'\nimport ncu from '../src/'\nimport chaiSetup from './helpers/chaiSetup'\nimport removeDir from './helpers/removeDir'\nimport stubVersions from './helpers/stubVersions'\n\nchaiSetup()\n\nconst bin = path.join(__dirname, '../build/cli.js')\n\n/** Creates a temp directory with nested package files for --workspaces testing. Returns the temp directory name (should be removed by caller).\n *\n * The file tree that is created is:\n * |- package.json\n * |- packages/\n * |  - a/\n * |    - package.json\n * |  - b/\n * |    - package.json\n */\nconst setup = async (\n  workspaces: string[] | { packages: string[] } = ['packages/**'],\n  {\n    pnpm,\n  }: {\n    // add workspaces to a pnpm-workspace.yaml file instead of the package file\n    pnpm?: boolean\n  } = {},\n) => {\n  const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n\n  const pkgDataRoot = JSON.stringify({\n    dependencies: {\n      'ncu-test-v2': '1.0.0',\n    },\n    ...(!pnpm ? { workspaces } : null),\n  })\n\n  const pkgDataA = JSON.stringify({\n    dependencies: {\n      'ncu-test-tag': '1.0.0',\n    },\n  })\n\n  const pkgDataB = JSON.stringify({\n    dependencies: {\n      'ncu-test-return-version': '1.0.0',\n    },\n  })\n\n  // write root package file\n  await fs.writeFile(path.join(tempDir, 'package.json'), pkgDataRoot, 'utf-8')\n  if (pnpm) {\n    await fs.writeFile(\n      path.join(tempDir, 'pnpm-workspace.yaml'),\n      `packages:\\n${((workspaces as { packages: string[] }).packages || workspaces)\n        .map(glob => `  - '${glob}'`)\n        .join('\\n')}`,\n      'utf-8',\n    )\n  }\n\n  // write workspace package files\n  await fs.mkdir(path.join(tempDir, 'packages/a'), { recursive: true })\n  await fs.writeFile(path.join(tempDir, 'packages/a/package.json'), pkgDataA, 'utf-8')\n  await fs.mkdir(path.join(tempDir, 'packages/b'), { recursive: true })\n  await fs.writeFile(path.join(tempDir, 'packages/b/package.json'), pkgDataB, 'utf-8')\n\n  return tempDir\n}\n\n/** Sets up a workspace with a dependency to a symlinked workspace package. */\nconst setupSymlinkedPackages = async (\n  workspaces: string[] | { packages: string[] } = ['packages/**'],\n  // applies a custom package name to /packages/bar\n  customName?: string,\n) => {\n  const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n  await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n\n  const pkgDataRoot = JSON.stringify({ workspaces })\n\n  const pkgDataFoo = JSON.stringify({\n    dependencies: {\n      [customName || 'bar']: '0.4.2',\n      'ncu-test-v2': '1.0.0',\n    },\n  })\n\n  const pkgDataBar = JSON.stringify({\n    ...(customName ? { name: customName } : null),\n    dependencies: {\n      'ncu-test-v2': '1.1.0',\n    },\n  })\n\n  // write root package file\n  await fs.writeFile(path.join(tempDir, 'package.json'), pkgDataRoot, 'utf-8')\n\n  // write workspace package files\n  await fs.mkdir(path.join(tempDir, 'packages/foo'), { recursive: true })\n  await fs.writeFile(path.join(tempDir, 'packages/foo/package.json'), pkgDataFoo, 'utf-8')\n  await fs.mkdir(path.join(tempDir, 'packages/bar'), { recursive: true })\n  await fs.writeFile(path.join(tempDir, 'packages/bar/package.json'), pkgDataBar, 'utf-8')\n\n  return tempDir\n}\n\nlet stub: { restore: () => void }\n\ndescribe('workspaces', () => {\n  describe('stubbed', () => {\n    before(() => {\n      stub = stubVersions(\n        {\n          'ncu-test-v2': '2.0.0',\n          'ncu-test-tag': '1.1.0',\n          'ncu-test-return-version': '2.0.0',\n        },\n        { spawn: true },\n      )\n    })\n    after(() => {\n      stub.restore()\n    })\n\n    describe('--workspaces', function () {\n      this.timeout(60000)\n\n      it('do not allow --workspaces and --deep together', async () => {\n        await ncu({ workspaces: true, deep: true }).should.eventually.be.rejectedWith('Cannot specify both')\n      })\n\n      it('update workspaces with --workspaces', async () => {\n        const tempDir = await setup(['packages/a'])\n        try {\n          const { stdout } = await spawn('node', [bin, '--jsonAll', '--workspaces'], {}, { cwd: tempDir })\n          const output = JSON.parse(stdout)\n          output.should.have.property('packages/a/package.json')\n          output.should.not.have.property('packages/b/package.json')\n          output['packages/a/package.json'].dependencies.should.have.property('ncu-test-tag')\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      it('update workspaces glob', async () => {\n        const tempDir = await setup()\n        try {\n          const { stdout } = await spawn('node', [bin, '--jsonAll', '--workspaces'], {}, { cwd: tempDir })\n          const output = JSON.parse(stdout)\n          output.should.have.property('packages/a/package.json')\n          output.should.have.property('packages/b/package.json')\n          output['packages/a/package.json'].dependencies.should.have.property('ncu-test-tag')\n          output['packages/b/package.json'].dependencies.should.have.property('ncu-test-return-version')\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      it('update workspaces with -w', async () => {\n        const tempDir = await setup()\n        try {\n          const { stdout } = await spawn('node', [bin, '--jsonAll', '-w'], {}, { cwd: tempDir })\n          const output = JSON.parse(stdout)\n          output.should.have.property('packages/a/package.json')\n          output.should.have.property('packages/b/package.json')\n          output['packages/a/package.json'].dependencies.should.have.property('ncu-test-tag')\n          output['packages/b/package.json'].dependencies.should.have.property('ncu-test-return-version')\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      it('do not update non-workspace subpackages', async () => {\n        const tempDir = await setup()\n        await fs.mkdir(path.join(tempDir, 'other'), { recursive: true })\n        await fs.writeFile(\n          path.join(tempDir, 'other/package.json'),\n          JSON.stringify({\n            dependencies: {\n              'ncu-test-return-version': '1.0.0',\n            },\n          }),\n          'utf-8',\n        )\n\n        try {\n          const { stdout } = await spawn('node', [bin, '--jsonAll', '--workspaces'], {}, { cwd: tempDir })\n          const output = JSON.parse(stdout)\n          output.should.have.property('packages/a/package.json')\n          output.should.have.property('packages/b/package.json')\n          output.should.not.have.property('other/package.json')\n          output['packages/a/package.json'].dependencies.should.have.property('ncu-test-tag')\n          output['packages/b/package.json'].dependencies.should.have.property('ncu-test-return-version')\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      // support for object type with packages property\n      // https://classic.yarnpkg.com/blog/2018/02/15/nohoist/\n      it('update workspaces/packages', async () => {\n        const tempDir = await setup({ packages: ['packages/**'] })\n        try {\n          const { stdout } = await spawn('node', [bin, '--jsonAll', '--workspaces'], {}, { cwd: tempDir })\n          const output = JSON.parse(stdout)\n          output.should.have.property('packages/a/package.json')\n          output.should.have.property('packages/b/package.json')\n          output['packages/a/package.json'].dependencies.should.have.property('ncu-test-tag')\n          output['packages/b/package.json'].dependencies.should.have.property('ncu-test-return-version')\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      // https://github.com/raineorshine/npm-check-updates/issues/1217\n      it('ignore local workspace packages', async () => {\n        const tempDir = await setupSymlinkedPackages()\n        try {\n          const { stdout } = await spawn('node', [bin, '--jsonUpgraded', '--workspaces'], {}, { cwd: tempDir })\n          const upgrades = JSON.parse(stdout)\n          upgrades.should.deep.equal({\n            'package.json': {},\n            'packages/foo/package.json': {\n              'ncu-test-v2': '2.0.0',\n            },\n            'packages/bar/package.json': {\n              'ncu-test-v2': '2.0.0',\n            },\n          })\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      it('ignore local workspace packages with different names than their folders', async () => {\n        const tempDir = await setupSymlinkedPackages(['packages/**'], 'chalk')\n        try {\n          const { stdout } = await spawn('node', [bin, '--jsonUpgraded', '--workspaces'], {}, { cwd: tempDir })\n          const upgrades = JSON.parse(stdout)\n          upgrades.should.deep.equal({\n            'package.json': {},\n            'packages/foo/package.json': {\n              'ncu-test-v2': '2.0.0',\n            },\n            'packages/bar/package.json': {\n              'ncu-test-v2': '2.0.0',\n            },\n          })\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n    })\n\n    describe('--workspace', function () {\n      this.timeout(60000)\n\n      it('do not allow --workspace and --deep together', async () => {\n        await ncu({ workspace: ['a'], deep: true }).should.eventually.be.rejectedWith('Cannot specify both')\n      })\n\n      it('do not allow --workspace and --workspaces together', async () => {\n        await ncu({ workspace: ['a'], deep: true }).should.eventually.be.rejectedWith('Cannot specify both')\n      })\n\n      it('update single workspace with --workspace', async () => {\n        const tempDir = await setup()\n        try {\n          const { stdout } = await spawn('node', [bin, '--jsonAll', '--workspace', 'a'], {}, { cwd: tempDir })\n          const output = JSON.parse(stdout)\n          output.should.have.property('packages/a/package.json')\n          output.should.not.have.property('packages/b/package.json')\n          output['packages/a/package.json'].dependencies.should.have.property('ncu-test-tag')\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      it('update more than one workspace', async () => {\n        const tempDir = await setup()\n        try {\n          const { stdout } = await spawn(\n            'node',\n            [bin, '--jsonAll', '--workspace', 'a', '--workspace', 'b'],\n            {},\n            {\n              cwd: tempDir,\n            },\n          )\n          const output = JSON.parse(stdout)\n          output.should.have.property('packages/a/package.json')\n          output.should.have.property('packages/b/package.json')\n          output['packages/a/package.json'].dependencies.should.have.property('ncu-test-tag')\n          output['packages/b/package.json'].dependencies.should.have.property('ncu-test-return-version')\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      it('update single workspace with --cwd and --workspace', async () => {\n        const tempDir = await setup()\n        try {\n          // when npm-check-updates is executed in a workspace directory but uses --cwd to point up to the root, make sure that the root package.json is checked for the workspaces property\n          const { stdout } = await spawn(\n            'node',\n            [bin, '--jsonAll', '--workspace', 'a', '--cwd', '../../'],\n            {},\n            {\n              cwd: path.join(tempDir, 'packages', 'a'),\n            },\n          )\n          const output = JSON.parse(stdout)\n          output.should.have.property('packages/a/package.json')\n          output.should.not.have.property('packages/b/package.json')\n          output['packages/a/package.json'].dependencies.should.have.property('ncu-test-tag')\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      // https://github.com/raineorshine/npm-check-updates/issues/1304\n      it('update namespaced workspace', async () => {\n        const tempDir = await setupSymlinkedPackages(['packages/**'], '@ncu/bar')\n        try {\n          const { stdout } = await spawn(\n            'node',\n            [bin, '--jsonUpgraded', '--workspace', '@ncu/bar'],\n            {},\n            {\n              cwd: tempDir,\n            },\n          )\n          const upgrades = JSON.parse(stdout)\n          upgrades.should.deep.equal({\n            'package.json': {},\n            'packages/bar/package.json': {\n              'ncu-test-v2': '2.0.0',\n            },\n          })\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n    })\n\n    describe('--root/--no-root', function () {\n      this.timeout(60000)\n\n      it('update root project by default', async () => {\n        const tempDir = await setup()\n        try {\n          const { stdout } = await spawn('node', [bin, '--jsonAll', '--workspaces', '--root'], {}, { cwd: tempDir })\n          const output = JSON.parse(stdout)\n          output.should.have.property('package.json')\n          output.should.have.property('packages/a/package.json')\n          output.should.have.property('packages/b/package.json')\n          output['package.json'].dependencies.should.have.property('ncu-test-v2')\n          output['packages/a/package.json'].dependencies.should.have.property('ncu-test-tag')\n          output['packages/b/package.json'].dependencies.should.have.property('ncu-test-return-version')\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      it('do not update the root project with --no-root', async () => {\n        const tempDir = await setup()\n        try {\n          const { stdout } = await spawn('node', [bin, '--jsonAll', '--workspaces', '--no-root'], {}, { cwd: tempDir })\n          const output = JSON.parse(stdout)\n          output.should.not.have.property('package.json')\n          output.should.have.property('packages/a/package.json')\n          output.should.have.property('packages/b/package.json')\n          output['packages/a/package.json'].dependencies.should.have.property('ncu-test-tag')\n          output['packages/b/package.json'].dependencies.should.have.property('ncu-test-return-version')\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      it('update root project and workspaces if errorLevel=2', async () => {\n        const tempDir = await setup()\n        try {\n          await spawn(\n            'node',\n            [bin, '--upgrade', '--workspaces', '--errorLevel', '2'],\n            {},\n            {\n              cwd: tempDir,\n            },\n          ).should.eventually.be.rejectedWith('Dependencies not up-to-date')\n          const upgradedPkg = JSON.parse(await fs.readFile(path.join(tempDir, 'package.json'), 'utf-8'))\n          upgradedPkg.should.have.property('dependencies')\n          upgradedPkg.dependencies.should.have.property('ncu-test-v2')\n          upgradedPkg.dependencies['ncu-test-v2'].should.not.equal('1.0.0')\n          const upgradedPkgA = JSON.parse(await fs.readFile(path.join(tempDir, 'packages/a/package.json'), 'utf-8'))\n          upgradedPkgA.should.have.property('dependencies')\n          upgradedPkgA.dependencies.should.have.property('ncu-test-tag')\n          upgradedPkgA.dependencies['ncu-test-tag'].should.not.equal('1.0.0')\n          const upgradedPkgB = JSON.parse(await fs.readFile(path.join(tempDir, 'packages/b/package.json'), 'utf-8'))\n          upgradedPkgB.should.have.property('dependencies')\n          upgradedPkgB.dependencies.should.have.property('ncu-test-return-version')\n          upgradedPkgB.dependencies['ncu-test-return-version'].should.not.equal('1.0.0')\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      it('do not update non-workspace subpackages', async () => {\n        const tempDir = await setup()\n        await fs.mkdir(path.join(tempDir, 'other'), { recursive: true })\n        await fs.writeFile(\n          path.join(tempDir, 'other/package.json'),\n          JSON.stringify({\n            dependencies: {\n              'ncu-test-return-version': '1.0.0',\n            },\n          }),\n          'utf-8',\n        )\n\n        try {\n          const { stdout } = await spawn('node', [bin, '--jsonAll', '--workspaces'], {}, { cwd: tempDir })\n          const output = JSON.parse(stdout)\n          output.should.have.property('package.json')\n          output.should.have.property('packages/a/package.json')\n          output.should.have.property('packages/b/package.json')\n          output.should.not.have.property('other/package.json')\n          output['package.json'].dependencies.should.have.property('ncu-test-v2')\n          output['packages/a/package.json'].dependencies.should.have.property('ncu-test-tag')\n          output['packages/b/package.json'].dependencies.should.have.property('ncu-test-return-version')\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n    })\n\n    describe('--workspace should include --root by default', function () {\n      this.timeout(60000)\n\n      it('update root project and single workspace', async () => {\n        const tempDir = await setup()\n        try {\n          const { stdout } = await spawn('node', [bin, '--jsonAll', '--workspace', 'a'], {}, { cwd: tempDir })\n          const output = JSON.parse(stdout)\n          output.should.have.property('package.json')\n          output.should.have.property('packages/a/package.json')\n          output.should.not.have.property('packages/b/package.json')\n          output['package.json'].dependencies.should.have.property('ncu-test-v2')\n          output['packages/a/package.json'].dependencies.should.have.property('ncu-test-tag')\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      it('update more than one workspace', async () => {\n        const tempDir = await setup()\n        try {\n          const { stdout } = await spawn(\n            'node',\n            [bin, '--jsonAll', '--workspace', 'a', '--workspace', 'b'],\n            {},\n            {\n              cwd: tempDir,\n            },\n          )\n          const output = JSON.parse(stdout)\n          output.should.have.property('package.json')\n          output.should.have.property('packages/a/package.json')\n          output.should.have.property('packages/b/package.json')\n          output['package.json'].dependencies.should.have.property('ncu-test-v2')\n          output['packages/a/package.json'].dependencies.should.have.property('ncu-test-tag')\n          output['packages/b/package.json'].dependencies.should.have.property('ncu-test-return-version')\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n    })\n\n    describe('pnpm', () => {\n      it('read packages from pnpm-workspace.yaml', async () => {\n        const tempDir = await setup(['packages/**'], { pnpm: true })\n        try {\n          const { stdout } = await spawn('node', [bin, '--jsonAll', '--workspaces'], {}, { cwd: tempDir })\n          const output = JSON.parse(stdout)\n          output.should.have.property('packages/a/package.json')\n          output.should.have.property('packages/b/package.json')\n          output['packages/a/package.json'].dependencies.should.have.property('ncu-test-tag')\n          output['packages/b/package.json'].dependencies.should.have.property('ncu-test-return-version')\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      it('update pnpm catalog dependencies from pnpm-workspace.yaml (named catalogs)', async () => {\n        const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n        try {\n          const pkgDataRoot = JSON.stringify({\n            dependencies: {\n              'ncu-test-v2': '1.0.0',\n            },\n          })\n\n          const pnpmWorkspaceData = `packages:\n  - 'packages/**'\n\ncatalogs:\n  default:\n    ncu-test-v2: '1.0.0'\n  test:\n    ncu-test-tag: '1.0.0'\n`\n\n          // write root package file and pnpm-workspace.yaml\n          await fs.writeFile(path.join(tempDir, 'package.json'), pkgDataRoot, 'utf-8')\n          await fs.writeFile(path.join(tempDir, 'pnpm-workspace.yaml'), pnpmWorkspaceData, 'utf-8')\n          await fs.writeFile(path.join(tempDir, 'pnpm-lock.yaml'), '', 'utf-8')\n\n          // create workspace package\n          await fs.mkdir(path.join(tempDir, 'packages/a'), { recursive: true })\n          await fs.writeFile(\n            path.join(tempDir, 'packages/a/package.json'),\n            JSON.stringify({\n              dependencies: {\n                'ncu-test-tag': 'catalog:test',\n              },\n            }),\n            'utf-8',\n          )\n\n          const { stdout, stderr } = await spawn(\n            'node',\n            [bin, '--jsonAll', '--workspaces'],\n            { rejectOnError: false },\n            { cwd: tempDir },\n          )\n\n          // Assert no errors and valid output\n          stderr.should.be.empty\n          stdout.should.not.be.empty\n\n          const output = JSON.parse(stdout)\n\n          // Should include catalog updates\n          output.should.deep.equal({\n            'pnpm-workspace.yaml': {\n              packages: ['packages/**'],\n              catalogs: { default: { 'ncu-test-v2': '2.0.0' }, test: { 'ncu-test-tag': '1.1.0' } },\n            },\n            'package.json': { dependencies: { 'ncu-test-v2': '2.0.0' } },\n            'packages/a/package.json': { dependencies: { 'ncu-test-tag': 'catalog:test' } },\n          })\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      it('update pnpm catalog dependencies from pnpm-workspace.yaml (singular catalog)', async () => {\n        const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n        try {\n          const pkgDataRoot = JSON.stringify({\n            dependencies: {\n              'ncu-test-v2': '1.0.0',\n            },\n          })\n\n          const pnpmWorkspaceData = `packages:\n  - 'packages/**'\n\ncatalog:\n  ncu-test-v2: '1.0.0'\n  ncu-test-tag: '1.0.0'\n`\n\n          // write root package file and pnpm-workspace.yaml\n          await fs.writeFile(path.join(tempDir, 'package.json'), pkgDataRoot, 'utf-8')\n          await fs.writeFile(path.join(tempDir, 'pnpm-workspace.yaml'), pnpmWorkspaceData, 'utf-8')\n          await fs.writeFile(path.join(tempDir, 'pnpm-lock.yaml'), '', 'utf-8')\n\n          // create workspace package\n          await fs.mkdir(path.join(tempDir, 'packages/a'), { recursive: true })\n          await fs.writeFile(\n            path.join(tempDir, 'packages/a/package.json'),\n            JSON.stringify({\n              dependencies: {\n                'ncu-test-tag': 'catalog:',\n              },\n            }),\n            'utf-8',\n          )\n\n          const { stdout, stderr } = await spawn(\n            'node',\n            [bin, '--jsonAll', '--workspaces'],\n            { rejectOnError: false },\n            { cwd: tempDir },\n          )\n\n          // Assert no errors and valid output\n          stderr.should.be.empty\n          stdout.should.not.be.empty\n\n          const output = JSON.parse(stdout)\n\n          // Should include catalog updates\n          output.should.deep.equal({\n            'pnpm-workspace.yaml': {\n              packages: ['packages/**'],\n              catalog: { 'ncu-test-v2': '2.0.0', 'ncu-test-tag': '1.1.0' },\n            },\n            'package.json': { dependencies: { 'ncu-test-v2': '2.0.0' } },\n            'packages/a/package.json': { dependencies: { 'ncu-test-tag': 'catalog:' } },\n          })\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      it('update pnpm catalog dependencies from pnpm-workspace.yaml', async () => {\n        const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n        try {\n          const pkgDataRoot = JSON.stringify({\n            dependencies: {\n              'ncu-test-v2': '1.0.0',\n            },\n          })\n\n          const pnpmWorkspaceData = `packages:\n  - 'packages/**'\n\ncatalog:\n  ncu-test-tag: '1.0.0'\n  ncu-test-v2: '1.0.0'\n\ncatalogs:\n  test:\n    ncu-test-tag: '1.0.0'\n`\n\n          // write root package file and pnpm-workspace.yaml\n          await fs.writeFile(path.join(tempDir, 'package.json'), pkgDataRoot, 'utf-8')\n          await fs.writeFile(path.join(tempDir, 'pnpm-workspace.yaml'), pnpmWorkspaceData, 'utf-8')\n          await fs.writeFile(path.join(tempDir, 'pnpm-lock.yaml'), '', 'utf-8')\n\n          // create workspace package\n          await fs.mkdir(path.join(tempDir, 'packages/a'), { recursive: true })\n          await fs.writeFile(\n            path.join(tempDir, 'packages/a/package.json'),\n            JSON.stringify({\n              dependencies: {\n                'ncu-test-tag': 'catalog:',\n              },\n            }),\n            'utf-8',\n          )\n\n          await spawn('node', [bin, '-u', '--workspaces'], { rejectOnError: false }, { cwd: tempDir })\n\n          const updatedConfig = await fs.readFile(path.join(tempDir, 'pnpm-workspace.yaml'), 'utf-8')\n          updatedConfig.should.be.equal(`packages:\n  - 'packages/**'\n\ncatalog:\n  ncu-test-tag: '1.1.0'\n  ncu-test-v2: '2.0.0'\n\ncatalogs:\n  test:\n    ncu-test-tag: '1.1.0'\n`)\n        } finally {\n          await fs.rm(tempDir, { recursive: true, force: true })\n        }\n      })\n\n      it('update pnpm catalog with --workspace flag (specific workspace)', async () => {\n        const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n        try {\n          const pkgDataRoot = JSON.stringify({\n            workspaces: ['packages/*'],\n          })\n\n          const pnpmWorkspaceData = `packages:\n  - 'packages/*'\n\ncatalog:\n  ncu-test-v2: '1.0.0'\n`\n\n          // write root package file and pnpm-workspace.yaml\n          await fs.writeFile(path.join(tempDir, 'package.json'), pkgDataRoot, 'utf-8')\n          await fs.writeFile(path.join(tempDir, 'pnpm-workspace.yaml'), pnpmWorkspaceData, 'utf-8')\n          await fs.writeFile(path.join(tempDir, 'pnpm-lock.yaml'), '', 'utf-8')\n\n          // create workspace packages\n          await fs.mkdir(path.join(tempDir, 'packages/a'), { recursive: true })\n          await fs.writeFile(\n            path.join(tempDir, 'packages/a/package.json'),\n            JSON.stringify({\n              name: 'a',\n              dependencies: {\n                'ncu-test-v2': 'catalog:',\n              },\n            }),\n            'utf-8',\n          )\n\n          await fs.mkdir(path.join(tempDir, 'packages/b'), { recursive: true })\n          await fs.writeFile(\n            path.join(tempDir, 'packages/b/package.json'),\n            JSON.stringify({\n              name: 'b',\n              dependencies: {\n                'ncu-test-tag': '1.0.0',\n              },\n            }),\n            'utf-8',\n          )\n\n          const { stdout, stderr } = await spawn(\n            'node',\n            [bin, '--jsonAll', '--workspace', 'a'],\n            { rejectOnError: false },\n            { cwd: tempDir },\n          )\n\n          // Assert no errors and valid output\n          stderr.should.be.empty\n          stdout.should.not.be.empty\n\n          const output = JSON.parse(stdout)\n\n          // Should include catalog updates even when using --workspace (not --workspaces)\n          output.should.have.property('pnpm-workspace.yaml')\n          output['pnpm-workspace.yaml'].should.deep.equal({\n            packages: ['packages/*'],\n            catalog: { 'ncu-test-v2': '2.0.0' },\n          })\n          output.should.have.property('packages/a/package.json')\n          output['packages/a/package.json'].should.deep.equal({\n            name: 'a',\n            dependencies: { 'ncu-test-v2': 'catalog:' },\n          })\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n    })\n\n    describe('yarn', () => {\n      it('update yarn catalog dependencies from yarnrc.yml (named catalogs)', async () => {\n        const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n        try {\n          const pkgDataRoot = JSON.stringify({\n            workspaces: ['packages/**'],\n            dependencies: {\n              'ncu-test-v2': '1.0.0',\n            },\n          })\n\n          const yarnConfig = `\ncatalogs:\n  default:\n    ncu-test-v2: '1.0.0'\n  test:\n    ncu-test-tag: '1.0.0'\n`\n\n          // write root package file and .yarnrc.yml\n          await fs.writeFile(path.join(tempDir, 'package.json'), pkgDataRoot, 'utf-8')\n          await fs.writeFile(path.join(tempDir, '.yarnrc.yml'), yarnConfig, 'utf-8')\n          await fs.writeFile(path.join(tempDir, 'yarn.lock'), '', 'utf-8')\n\n          // create workspace package\n          await fs.mkdir(path.join(tempDir, 'packages/a'), { recursive: true })\n          await fs.writeFile(\n            path.join(tempDir, 'packages/a/package.json'),\n            JSON.stringify({\n              dependencies: {\n                'ncu-test-tag': 'catalog:test',\n              },\n            }),\n            'utf-8',\n          )\n\n          const { stdout, stderr } = await spawn(\n            'node',\n            [bin, '--jsonAll', '--workspaces'],\n            { rejectOnError: false },\n            { cwd: tempDir },\n          )\n\n          // Assert no errors and valid output\n          stderr.should.be.empty\n          stdout.should.not.be.empty\n\n          const output = JSON.parse(stdout)\n\n          // Should include catalog updates\n          output.should.deep.equal({\n            '.yarnrc.yml': {\n              catalogs: { default: { 'ncu-test-v2': '2.0.0' }, test: { 'ncu-test-tag': '1.1.0' } },\n            },\n            'package.json': { workspaces: ['packages/**'], dependencies: { 'ncu-test-v2': '2.0.0' } },\n            'packages/a/package.json': { dependencies: { 'ncu-test-tag': 'catalog:test' } },\n          })\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      it('update yarn catalog dependencies from .yarnrc.yml (singular catalog)', async () => {\n        const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n        try {\n          const pkgDataRoot = JSON.stringify({\n            workspaces: ['packages/**'],\n            dependencies: {\n              'ncu-test-v2': '1.0.0',\n            },\n          })\n\n          const yarnConfig = `\ncatalog:\n  ncu-test-v2: '1.0.0'\n  ncu-test-tag: '1.0.0'\n`\n\n          // write root package file and pnpm-workspace.yaml\n          await fs.writeFile(path.join(tempDir, 'package.json'), pkgDataRoot, 'utf-8')\n          await fs.writeFile(path.join(tempDir, '.yarnrc.yml'), yarnConfig, 'utf-8')\n          await fs.writeFile(path.join(tempDir, 'yarn.lock'), '', 'utf-8')\n\n          // create workspace package\n          await fs.mkdir(path.join(tempDir, 'packages/a'), { recursive: true })\n          await fs.writeFile(\n            path.join(tempDir, 'packages/a/package.json'),\n            JSON.stringify({\n              dependencies: {\n                'ncu-test-tag': 'catalog:',\n              },\n            }),\n            'utf-8',\n          )\n\n          const { stdout, stderr } = await spawn(\n            'node',\n            [bin, '--jsonAll', '--workspaces'],\n            { rejectOnError: false },\n            { cwd: tempDir },\n          )\n\n          // Assert no errors and valid output\n          stderr.should.be.empty\n          stdout.should.not.be.empty\n\n          const output = JSON.parse(stdout)\n\n          // Should include catalog updates\n          output.should.deep.equal({\n            '.yarnrc.yml': {\n              catalog: { 'ncu-test-v2': '2.0.0', 'ncu-test-tag': '1.1.0' },\n            },\n            'package.json': { workspaces: ['packages/**'], dependencies: { 'ncu-test-v2': '2.0.0' } },\n            'packages/a/package.json': { dependencies: { 'ncu-test-tag': 'catalog:' } },\n          })\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n      it('update yarn catalog dependencies from .yarnrc.yml', async () => {\n        const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n        try {\n          const pkgDataRoot = JSON.stringify({\n            workspaces: ['packages/**'],\n            dependencies: {\n              'ncu-test-v2': '1.0.0',\n            },\n          })\n\n          const yarnConfig = `\ncatalog:\n  ncu-test-v2: '1.0.0'\n  ncu-test-tag: '1.0.0'\n\ncatalogs:\n  test:\n    ncu-test-tag: '1.0.0'\n`\n\n          // write root package file and pnpm-workspace.yaml\n          await fs.writeFile(path.join(tempDir, 'package.json'), pkgDataRoot, 'utf-8')\n          await fs.writeFile(path.join(tempDir, '.yarnrc.yml'), yarnConfig, 'utf-8')\n          await fs.writeFile(path.join(tempDir, 'yarn.lock'), '', 'utf-8')\n\n          // create workspace package\n          await fs.mkdir(path.join(tempDir, 'packages/a'), { recursive: true })\n          await fs.writeFile(\n            path.join(tempDir, 'packages/a/package.json'),\n            JSON.stringify({\n              dependencies: {\n                'ncu-test-tag': 'catalog:',\n              },\n            }),\n            'utf-8',\n          )\n\n          const { stdout } = await spawn(\n            'node',\n            [bin, '-u', '--workspaces'],\n            { rejectOnError: false },\n            { cwd: tempDir },\n          )\n\n          stdout.should.not.be.empty\n\n          const updatedConfig = await fs.readFile(path.join(tempDir, '.yarnrc.yml'), 'utf-8')\n          updatedConfig.should.equal(`catalog:\n  ncu-test-v2: '2.0.0'\n  ncu-test-tag: '1.1.0'\n\ncatalogs:\n  test:\n    ncu-test-tag: '1.1.0'\n`)\n        } finally {\n          await fs.rm(tempDir, { recursive: true, force: true })\n        }\n      })\n    })\n\n    describe('bun', () => {\n      it('update bun catalog dependencies from package.json (top-level)', async () => {\n        const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n        try {\n          const pkgDataRoot = JSON.stringify({\n            workspaces: ['packages/**'],\n            catalog: {\n              'ncu-test-v2': '1.0.0',\n            },\n            catalogs: {\n              test: {\n                'ncu-test-tag': '1.0.0',\n              },\n            },\n          })\n\n          // write root package.json and bun.lock\n          await fs.writeFile(path.join(tempDir, 'package.json'), pkgDataRoot, 'utf-8')\n          await fs.writeFile(path.join(tempDir, 'bun.lock'), '{}', 'utf-8')\n\n          // create workspace package\n          await fs.mkdir(path.join(tempDir, 'packages/a'), { recursive: true })\n          await fs.writeFile(\n            path.join(tempDir, 'packages/a/package.json'),\n            JSON.stringify({ dependencies: { 'ncu-test-tag': 'catalog:test' } }),\n            'utf-8',\n          )\n\n          const { stdout, stderr } = await spawn(\n            'node',\n            [bin, '--jsonAll', '--workspaces', '--root'],\n            { rejectOnError: false },\n            { cwd: tempDir },\n          )\n\n          // Assert no errors and valid output\n          stderr.should.be.empty\n          stdout.should.not.be.empty\n\n          const output = JSON.parse(stdout)\n\n          // Should include catalog updates in package.json\n          output.should.deep.equal({\n            'package.json': {\n              workspaces: ['packages/**'],\n              catalog: { 'ncu-test-v2': '2.0.0' },\n              catalogs: { test: { 'ncu-test-tag': '1.1.0' } },\n            },\n            'packages/a/package.json': { dependencies: { 'ncu-test-tag': 'catalog:test' } },\n          })\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n\n      it('update bun catalog dependencies from package.json (workspaces object)', async () => {\n        const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))\n        try {\n          const pkgDataRoot = JSON.stringify({\n            workspaces: {\n              packages: ['packages/**'],\n              catalog: {\n                'ncu-test-v2': '1.0.0',\n              },\n              catalogs: {\n                test: {\n                  'ncu-test-tag': '1.0.0',\n                },\n              },\n            },\n          })\n\n          // write root package.json and bun.lock\n          await fs.writeFile(path.join(tempDir, 'package.json'), pkgDataRoot, 'utf-8')\n          await fs.writeFile(path.join(tempDir, 'bun.lock'), '{}', 'utf-8')\n\n          // create workspace package\n          await fs.mkdir(path.join(tempDir, 'packages/a'), { recursive: true })\n          await fs.writeFile(\n            path.join(tempDir, 'packages/a/package.json'),\n            JSON.stringify({\n              dependencies: {\n                'ncu-test-tag': 'catalog:test',\n              },\n            }),\n            'utf-8',\n          )\n\n          const { stdout, stderr } = await spawn(\n            'node',\n            [bin, '--jsonAll', '--workspaces', '--root'],\n            { rejectOnError: false },\n            { cwd: tempDir },\n          )\n\n          // Assert no errors and valid output\n          stderr.should.be.empty\n          stdout.should.not.be.empty\n\n          const output = JSON.parse(stdout)\n\n          // Should include catalog updates in package.json\n          output.should.deep.equal({\n            'package.json': {\n              workspaces: {\n                packages: ['packages/**'],\n                catalog: { 'ncu-test-v2': '2.0.0' },\n                catalogs: { test: { 'ncu-test-tag': '1.1.0' } },\n              },\n            },\n            'packages/a/package.json': { dependencies: { 'ncu-test-tag': 'catalog:test' } },\n          })\n        } finally {\n          await removeDir(tempDir)\n        }\n      })\n    })\n  })\n\n  // cannot be stubbed because npm config printing occurs in viewMany\n  describe('not stubbed', () => {\n    // This test fails on Node v20.3.1 on GitHub Actions (only).\n    // The stdout fails to match the expected value: \"npm config (workspace project):\\n{ncutest: 'root' }\"\n    // Strangely, it matches up to the single quote: \"npm config (workspace project):\\n{ncutest: \"\n    it.skip('merge local npm config with pnpm workspace npm config', async () => {\n      const tempDir = await setup(['packages/**'], { pnpm: true })\n      try {\n        await fs.writeFile(path.join(tempDir, '.npmrc'), 'ncutest=root')\n        await fs.writeFile(path.join(tempDir, 'packages/a/.npmrc'), 'ncutest=a')\n        const { stdout } = await spawn(\n          'node',\n          [bin, '--verbose', '--packageManager', 'pnpm'],\n          {},\n          {\n            cwd: path.join(tempDir, 'packages/a'),\n          },\n        )\n        stdout.should.include(`npm config (workspace project):\\n{ ncutest: 'root' }`)\n        stdout.should.include(`Using merged npm config:\\n{\\n  ncutest: 'a',`)\n      } finally {\n        await removeDir(tempDir)\n      }\n    })\n  })\n})\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"include\": [\"src\", \"test\", \"vite.config.mts\"],\n  \"exclude\": [\"test/deep\", \"test/doctor\"],\n  \"compilerOptions\": {\n    \"allowJs\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"declaration\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"isolatedModules\": true,\n    \"lib\": [\"es2022\"],\n    \"module\": \"nodenext\",\n    \"moduleResolution\": \"nodenext\",\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitAny\": true,\n    \"paths\": {\n      \"libnpmconfig\": [\"./src/types/libnpmconfig\"],\n      \"prompts-ncu\": [\"./src/types/prompts-ncu\"]\n    },\n    \"resolveJsonModule\": true,\n    \"outDir\": \"./build\",\n    \"skipLibCheck\": true,\n    \"sourceMap\": true,\n    \"strict\": true,\n    \"target\": \"es2021\"\n  }\n}\n"
  },
  {
    "path": "vite.config.mts",
    "content": "import { nodeExternals } from 'rollup-plugin-node-externals'\nimport { defineConfig } from 'vite'\nimport { analyzer } from 'vite-bundle-analyzer'\nimport dts from 'vite-plugin-dts'\n\nexport default defineConfig(({ mode }) => ({\n  plugins: [\n    dts({\n      entryRoot: 'src',\n      rollupTypes: true,\n      include: ['src'],\n    }),\n    nodeExternals(),\n    ...(process.env.ANALYZER ? [analyzer()] : []),\n  ],\n  ssr: {\n    // bundle and treeshake everything\n    noExternal: true,\n  },\n  build: {\n    ssr: true,\n    lib: {\n      entry: ['src/index.ts', 'src/bin/cli.ts'],\n      formats: ['cjs'],\n    },\n    target: 'node18',\n    outDir: 'build',\n    sourcemap: true,\n    minify: mode === 'production' && 'esbuild',\n  },\n}))\n"
  }
]