[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = tab\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.yml]\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto eol=lf\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve np\n---\n\n<!--- Provide a short summary of the issue in the title above -->\n\n## Description\n\n<!-- Write a clear and concise description of what the issue is -->\n\n## Steps to reproduce\n\n<!--- Let us how how we can reproduce the issue on our end -->\n\n1.\n2.\n3.\n\n## Expected behavior\n\n<!--- Tell us what you expected to happen -->\n\n## Environment\n\n<!-- Mention which versions of np, Node.js, npm and Git you're using, as well as your OS and version -->\n\nnp - x.x.x\nNode.js - x.x.x\nnpm - x.x.x\nGit - x.x.x\nOS - XXX x.x.x\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea\n---\n\n<!--- Provide a short summary of the request in the title above -->\n\n## Description\n\n<!-- Write a clear and concise description of the feature -->\n\n**Is the feature request related to a problem?**\n\n<!-- Describe what the problem is (e.g. I'm always frustrated when […]) -->\n\n## Possible implementation\n\n<!-- If possible, let us know how you think the feature should be implemented -->\n\n## Alternatives \n\n<!-- Include any alternative solutions and features you've considered -->\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "<!--\n\nThanks for submitting a pull request 🙌\n\n**Note:** Please don't create a pull request which has significant changes (i.e. adds new functionality or modifies existing one in a non-trivial way) without creating an issue first.\n\nTry to limit the scope of your pull request and provide a general description of the changes. If this fixes an open issue, link to it in the following way: `Fixes #321`. New features and bug fixes should come with tests.\n\n-->\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "name: CI\non:\n  - push\n  - pull_request\njobs:\n  test:\n    name: Node.js ${{ matrix.node-version }}\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        node-version:\n          - 24\n          - 20\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node-version }}\n      - run: git config --global user.name \"Github Actions\"\n      - run: git config --global user.email \"actions@users.noreply.github.com\"\n      - run: npm install\n      - run: npm test\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\nyarn.lock\n"
  },
  {
    "path": ".npmrc",
    "content": "package-lock=false\n"
  },
  {
    "path": "license",
    "content": "MIT License\n\nCopyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "package.json",
    "content": "{\n\t\"name\": \"np\",\n\t\"version\": \"11.0.2\",\n\t\"description\": \"A better `npm publish`\",\n\t\"license\": \"MIT\",\n\t\"repository\": \"sindresorhus/np\",\n\t\"funding\": \"https://github.com/sindresorhus/np?sponsor=1\",\n\t\"type\": \"module\",\n\t\"bin\": \"./source/cli.js\",\n\t\"engines\": {\n\t\t\"node\": \">=20\",\n\t\t\"npm\": \">=9\",\n\t\t\"git\": \">=2.11.0\",\n\t\t\"yarn\": \">=1.7.0\",\n\t\t\"pnpm\": \">=8\",\n\t\t\"bun\": \">=1\"\n\t},\n\t\"scripts\": {\n\t\t\"test\": \"xo && ava\"\n\t},\n\t\"files\": [\n\t\t\"source\"\n\t],\n\t\"keywords\": [\n\t\t\"cli-app\",\n\t\t\"cli\",\n\t\t\"npm\",\n\t\t\"publish\",\n\t\t\"git\",\n\t\t\"push\",\n\t\t\"version\",\n\t\t\"bump\",\n\t\t\"commit\"\n\t],\n\t\"dependencies\": {\n\t\t\"chalk\": \"^5.6.2\",\n\t\t\"chalk-template\": \"^1.1.2\",\n\t\t\"clipboardy\": \"^5.0.2\",\n\t\t\"cosmiconfig\": \"^9.0.0\",\n\t\t\"del\": \"^8.0.1\",\n\t\t\"escape-goat\": \"^4.0.0\",\n\t\t\"escape-string-regexp\": \"^5.0.0\",\n\t\t\"execa\": \"^9.6.1\",\n\t\t\"exit-hook\": \"^5.0.1\",\n\t\t\"github-url-from-git\": \"^1.5.0\",\n\t\t\"hosted-git-info\": \"^9.0.2\",\n\t\t\"ignore-walk\": \"^8.0.0\",\n\t\t\"import-local\": \"^3.2.0\",\n\t\t\"inquirer\": \"^13.2.0\",\n\t\t\"is-installed-globally\": \"^1.0.0\",\n\t\t\"is-interactive\": \"^2.0.0\",\n\t\t\"is-scoped\": \"^3.0.0\",\n\t\t\"issue-regex\": \"^4.3.0\",\n\t\t\"listr\": \"^0.14.3\",\n\t\t\"listr-input\": \"^0.2.1\",\n\t\t\"log-symbols\": \"^7.0.1\",\n\t\t\"meow\": \"^14.0.0\",\n\t\t\"new-github-release-url\": \"^2.0.0\",\n\t\t\"npm-name\": \"^8.0.0\",\n\t\t\"onetime\": \"^7.0.0\",\n\t\t\"open\": \"^11.0.0\",\n\t\t\"p-memoize\": \"^8.0.0\",\n\t\t\"package-directory\": \"^8.0.0\",\n\t\t\"path-exists\": \"^5.0.0\",\n\t\t\"read-package-up\": \"^12.0.0\",\n\t\t\"read-pkg\": \"^10.0.0\",\n\t\t\"rxjs\": \"^7.8.2\",\n\t\t\"semver\": \"^7.7.3\",\n\t\t\"symbol-observable\": \"^4.0.0\",\n\t\t\"terminal-link\": \"^5.0.0\",\n\t\t\"update-notifier\": \"^7.3.1\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@sindresorhus/is\": \"^7.2.0\",\n\t\t\"@types/semver\": \"^7.7.1\",\n\t\t\"ava\": \"^6.4.1\",\n\t\t\"common-tags\": \"^1.8.2\",\n\t\t\"esmock\": \"^2.7.3\",\n\t\t\"fs-extra\": \"^11.3.3\",\n\t\t\"map-obj\": \"^6.0.0\",\n\t\t\"sinon\": \"^21.0.1\",\n\t\t\"strip-ansi\": \"^7.1.2\",\n\t\t\"tempy\": \"^3.1.0\",\n\t\t\"write-package\": \"^7.2.0\",\n\t\t\"xo\": \"^1.2.3\"\n\t},\n\t\"ava\": {\n\t\t\"files\": [\n\t\t\t\"!test/fixtures\",\n\t\t\t\"!test/_helpers\"\n\t\t],\n\t\t\"environmentVariables\": {\n\t\t\t\"FORCE_HYPERLINK\": \"1\",\n\t\t\t\"HOME\": \"/tmp\",\n\t\t\t\"NODE_ENV\": \"test\",\n\t\t\t\"GIT_AUTHOR_NAME\": \"test\",\n\t\t\t\"GIT_COMMITTER_NAME\": \"test\",\n\t\t\t\"GIT_AUTHOR_EMAIL\": \"test@example.com\",\n\t\t\t\"GIT_COMMITTER_EMAIL\": \"test@example.com\"\n\t\t},\n\t\t\"nodeArguments\": [\n\t\t\t\"--loader=esmock\",\n\t\t\t\"--no-warnings=ExperimentalWarning\"\n\t\t]\n\t},\n\t\"xo\": {\n\t\t\"ignores\": [\n\t\t\t\"test/fixtures\"\n\t\t]\n\t}\n}\n"
  },
  {
    "path": "readme.md",
    "content": "# np [![XO code style](https://shields.io/badge/code_style-5ed9c7?logo=xo&labelColor=gray&logoSize=auto&logoWidth=20)](https://github.com/xojs/xo)\n\n> A better `npm publish`\n\n<img src=\"media/screenshot.gif\" width=\"688\">\n\n## Why\n\n- [Interactive UI](#interactive-ui)\n- Ensures you are publishing from your release branch (`main` and `master` by default)\n- Ensures the working directory is clean and that there are no unpulled changes\n- Reinstalls dependencies to ensure your project works with the latest dependency tree\n- Ensures your Node.js and npm versions are supported by the project and its dependencies\n- Runs the tests\n- Bumps the version in package.json and npm-shrinkwrap.json (if present) and creates a git tag\n- Prevents [accidental publishing](https://github.com/npm/npm/issues/13248) of pre-release versions under the `latest` [dist-tag](https://docs.npmjs.com/cli/dist-tag)\n- Publishes the new version to npm, optionally under a dist-tag\n- Rolls back the project to its previous state in case publishing fails\n- Pushes commits and tags (newly & previously created) to GitHub/GitLab\n- Supports [two-factor authentication](https://docs.npmjs.com/getting-started/using-two-factor-authentication)\n- Enables two-factor authentication on new repositories\n  <br>\n  <sub>(does not apply to external registries)</sub>\n- Opens a prefilled GitHub Releases draft after publish\n- Warns about the possibility of extraneous files being published\n- See exactly what will be executed with [preview mode](https://github.com/sindresorhus/np/issues/391), without pushing or publishing anything remotely\n- Supports [GitHub Packages](https://github.com/features/packages)\n- Supports npm 9+, Yarn (Classic and Berry), pnpm 8+, and Bun\n\n### Why not\n\n- Monorepos are not supported.\n- CI is [not an ideal environment](https://github.com/sindresorhus/np/issues/619#issuecomment-994493179) for `np`. It's meant to be used locally as an interactive tool.\n\n## Prerequisite\n\n- Node.js 20 or later\n- npm 9 or later\n- Git 2.11 or later\n\n## Install\n\n```sh\nnpm install --global np\n```\n\n## Usage\n\n```\n$ np --help\n\n  Usage\n    $ np <version>\n\n    Version can be:\n      patch | minor | major | prepatch | preminor | premajor | prerelease | 1.2.3\n\n  Options\n    --any-branch            Allow publishing from any branch\n    --branch                Name of the release branch (default: main | master)\n    --no-cleanup            Skips cleanup of node_modules\n    --no-tests              Skips tests\n    --yolo                  Skips cleanup and testing\n    --no-publish            Skips publishing\n    --preview               Show tasks without actually executing them\n    --tag                   Publish under a given dist-tag\n    --contents              Subdirectory to publish\n    --no-release-draft      Skips opening a GitHub release draft\n    --release-draft-only    Only opens a GitHub release draft for the latest published version\n    --no-release-notes      Skips generating release notes when opening a GitHub release draft\n    --test-script           Name of npm run script to run tests before publishing (default: test)\n    --no-2fa                Don't enable 2FA on new packages (not recommended)\n    --message               Version bump commit message, '%s' will be replaced with version (default: '%s' with npm and 'v%s' with yarn)\n    --package-manager       Use a specific package manager (default: 'packageManager' field in package.json)\n    --provenance            Publish with npm provenance statements (CI-only)\n    --remote                Git remote to push to (default: origin)\n\n  Examples\n    $ np\n    $ np patch\n    $ np 1.0.2\n    $ np 1.0.2-beta.3 --tag=beta\n    $ np 1.0.2-beta.3 --tag=beta --contents=dist\n```\n\n## Interactive UI\n\nRun `np` without arguments to launch the interactive UI that guides you through publishing a new version.\n\n<img src=\"media/screenshot-ui.png\" width=\"1290\">\n\n## Config\n\n`np` can be configured both globally and locally. When using the global `np` binary, you can configure any of the CLI flags in either a `.np-config.js` (as CJS), `.np-config.cjs`, `.np-config.mjs`, or `.np-config.json` file in the home directory. When using the local `np` binary, for example, in a `npm run` script, you can configure `np` by setting the flags in either a top-level `np` field in `package.json` or in one of the aforementioned file types in the project directory. If it exists, the local installation will always take precedence. This ensures any local config matches the version of `np` it was designed for.\n\nCurrently, these are the flags you can configure:\n\n- `anyBranch` - Allow publishing from any branch (`false` by default).\n- `branch` - Name of the release branch (`main` or `master` by default).\n- `cleanup` - Cleanup `node_modules` (`true` by default).\n- `tests` - Run `npm test` (`true` by default).\n- `yolo` - Skip cleanup and testing (`false` by default).\n- `publish` - Publish (`true` by default).\n- `preview` - Show tasks without actually executing them (`false` by default).\n- `tag` - Publish under a given dist-tag (`latest` by default).\n- `contents` - Subdirectory to publish (`.` by default).\n- `releaseDraft` - Open a GitHub release draft after releasing (`true` by default).\n- `releaseNotes` - Auto-generate release notes when opening a GitHub release draft (`true` by default).\n- `testScript` - Name of npm run script to run tests before publishing (`test` by default).\n- `2fa` - Enable 2FA on new packages (`true` by default) (setting this to `false` is not recommended).\n- `message` - The commit message used for the version bump. Any `%s` in the string will be replaced with the new version. By default, npm uses `%s` and Yarn uses `v%s`.\n- `packageManager` - Set the package manager to be used. Defaults to the [packageManager field in package.json](https://nodejs.org/dist/latest-v16.x/docs/api/all.html#all_packages_packagemanager), so only use if you can't update package.json for some reason.\n- `provenance` - Publish with [npm provenance statements](https://docs.npmjs.com/generating-provenance-statements) (`false` by default). Requires npm 9.5.0+ and a supported CI environment (GitHub Actions or GitLab CI/CD).\n- `remote` - Git remote to push tags and commits to. Useful when publishing from a fork where `origin` is your fork and `upstream` is the main repository.\n\nFor example, this configures `np` to use `unit-test` as a test script, and to use `dist` as the subdirectory to publish:\n\n`package.json`\n```json\n{\n\t\"name\": \"superb-package\",\n\t\"np\": {\n\t\t\"testScript\": \"unit-test\",\n\t\t\"contents\": \"dist\"\n\t}\n}\n```\n\n`.np-config.json`\n```json\n{\n\t\"testScript\": \"unit-test\",\n\t\"contents\": \"dist\"\n}\n```\n\n`.np-config.js` or `.np-config.cjs`\n```js\nmodule.exports = {\n\ttestScript: 'unit-test',\n\tcontents: 'dist'\n};\n```\n\n`.np-config.mjs`\n```js\nexport default {\n\ttestScript: 'unit-test',\n\tcontents: 'dist'\n};\n```\n\n_**Note:** The global config only applies when using the global `np` binary, and is never inherited when using a local binary._\n\n## Tips\n\n### npm hooks\n\nYou can use any of the test/version/publish related [npm lifecycle hooks](https://docs.npmjs.com/misc/scripts) in your package.json to add extra behavior.\n\nFor example, here we build the documentation before tagging the release:\n\n```json\n{\n\t\"name\": \"my-awesome-package\",\n\t\"scripts\": {\n\t\t\"version\": \"./build-docs && git add docs\"\n\t}\n}\n```\n\n### Release script\n\nYou can also add `np` to a custom script in `package.json`. This can be useful if you want all maintainers of a package to release the same way (Not forgetting to push Git tags, for example). However, you can't use `publish` as name of your script because it's an [npm defined lifecycle hook](https://docs.npmjs.com/misc/scripts).\n\n```json\n{\n\t\"name\": \"my-awesome-package\",\n\t\"scripts\": {\n\t\t\"release\": \"np\"\n\t},\n\t\"devDependencies\": {\n\t\t\"np\": \"*\"\n\t}\n}\n```\n\n### User-defined tests\n\nIf you want to run a user-defined test script before publishing instead of the normal `npm test` or `yarn test`, you can use `--test-script` flag or the `testScript` config. This can be useful when your normal test script is running with a `--watch` flag or in case you want to run some specific tests (maybe on the packaged files) before publishing.\n\nFor example, `np --test-script=publish-test` would run the `publish-test` script instead of the default `test`.\n\n```json\n{\n\t\"name\": \"my-awesome-package\",\n\t\"scripts\": {\n\t\t\"test\": \"ava --watch\",\n\t\t\"publish-test\": \"ava\"\n\t},\n\t\"devDependencies\": {\n\t\t\"np\": \"*\"\n\t}\n}\n```\n\n### Signed Git tag\n\nSet the [`sign-git-tag`](https://docs.npmjs.com/misc/config#sign-git-tag) npm config to have the Git tag signed:\n\n```\n$ npm config set sign-git-tag true\n```\n\nOr set the [`version-sign-git-tag`](https://yarnpkg.com/lang/en/docs/cli/version/#toc-git-tags) Yarn config:\n\n```\n$ yarn config set version-sign-git-tag true\n```\n\n### Private packages\n\n<img src=\"media/private-packages.png\" width=\"260\" align=\"right\">\n\nYou can use `np` for packages that aren't publicly published to npm (perhaps installed from a private git repo).\n\nSet `\"private\": true` in your `package.json` and the publishing step will be skipped. All other steps\nincluding versioning and pushing tags will still be completed.\n\n### Public scoped packages\n\nTo publish [scoped packages](https://docs.npmjs.com/misc/scope#publishing-public-scoped-packages-to-the-public-npm-registry) to the public registry, you need to set the access level to `public`. You can do that by adding the following to your `package.json`:\n\n```json\n\"publishConfig\": {\n\t\"access\": \"public\"\n}\n```\n\nIf publishing a scoped package for the first time, `np` will prompt you to ask if you want to publish it publicly.\n\n**Note:** When publishing a scoped package, the first ever version you publish has to be done interactively using `np`. If not, you cannot use `np` to publish future versions of the package.\n\n### Private Org-scoped packages\n\nTo publish a [private Org-scoped package](https://docs.npmjs.com/creating-and-publishing-an-org-scoped-package#publishing-a-private-org-scoped-package), you need to set the access level to `restricted`. You can do that by adding the following to your `package.json`:\n\n```json\n\"publishConfig\": {\n\t\"access\": \"restricted\"\n}\n```\n\n### Publish to a custom registry\n\nSet the [`registry` option](https://docs.npmjs.com/misc/config#registry) in package.json to the URL of your registry:\n\n```json\n\"publishConfig\": {\n\t\"registry\": \"https://my-internal-registry.local\"\n}\n```\n\n### Package managers\n\nIf a package manager is not set in package.json, via configuration (`packageManager`), or via the CLI (`--package-manager`), `np` will attempt to infer the best package manager to use by looking for lockfiles. But it's recommended to set the [`packageManager` field](https://nodejs.org/api/packages.html#packagemanager) in your package.json to be consistent with other tools. See also the [corepack docs](https://nodejs.org/api/corepack.html).\n\n### Publish with a CI\n\nIf you use a Continuous Integration server to publish your tagged commits, use the `--no-publish` flag to skip the publishing step of `np`.\n\n### Publish to gh-pages\n\nTo publish to `gh-pages` (or any other branch that serves your static assets), install [`branchsite`](https://github.com/enriquecaballero/branchsite), an `np`-like CLI tool aimed to complement `np`, and create an [npm \"post\" hook](https://docs.npmjs.com/misc/scripts) that runs after `np`.\n\n```sh\nnpm install --save-dev branchsite\n```\n\n```json\n\"scripts\": {\n\t\"deploy\": \"np\",\n\t\"postdeploy\": \"bs\"\n}\n```\n\n### Initial version\n\nFor new packages, start the `version` field in package.json at `0.0.0` and let `np` bump it to `1.0.0` or `0.1.0` when publishing.\n\n### Release an update to an old major version\n\nTo release a minor/patch version for an old major version, create a branch from the major version's git tag and run `np`:\n\n```console\n$ git checkout -b fix-old-bug v1.0.0 # Where 1.0.0 is the previous major version\n# Create some commits…\n$ git push --set-upstream origin HEAD\n$ np patch --any-branch --tag=v1\n```\n\n### The prerequisite step runs forever on macOS\n\nIf you're using macOS Sierra 10.12.2 or later, your SSH key passphrase is no longer stored into the keychain by default. This may cause the `prerequisite` step to run forever because it prompts for your passphrase in the background. To fix this, add the following lines to your `~/.ssh/config` and run a simple Git command like `git fetch`.\n\n```\nHost *\n AddKeysToAgent yes\n UseKeychain yes\n```\n\nIf you're running into other issues when using SSH, please consult [GitHub's support article](https://help.github.com/articles/connecting-to-github-with-ssh/).\n\n### Ignore strategy\n\nThe [ignore strategy](https://docs.npmjs.com/files/package.json#files), either maintained in the `files`-property in `package.json` or in `.npmignore`, is meant to help reduce the package size. To avoid broken packages caused by essential files being accidentally ignored, `np` prints out all the new and unpublished files added to Git. Test files and other [common files](https://docs.npmjs.com/files/package.json#files) that are never published are not considered. `np` assumes either a standard directory layout or a customized layout represented in the `directories` property in `package.json`.\n\n## FAQ\n\n### I get an error when publishing my package through Yarn\n\nIf you get an error like this…\n\n```shell\n❯ Prerequisite check\n✔ Ping npm registry\n✔ Check npm version\n✔ Check yarn version\n✖ Verify user is authenticated\n\nnpm ERR! code E403\nnpm ERR! 403 Forbidden - GET https://registry.yarnpkg.com/-/package/my-awesome-package/collaborators?format=cli - Forbidden\n```\n\n…please check whether the command `npm access list collaborators my-awesome-package` succeeds. If it doesn't, Yarn has overwritten your registry URL. To fix this, add the correct registry URL to `package.json`:\n\n```json\n\"publishConfig\": {\n\t\"registry\": \"https://registry.npmjs.org\"\n}\n```\n\n### np hangs during the \"Publishing package\" step\n\nIf `np` hangs indefinitely during publishing, common causes include:\n\n**Lifecycle scripts that don't exit**\n\nnpm automatically runs lifecycle hooks like `prepublish`, `publish`, and `postpublish` during publishing. If these scripts don't exit (e.g., running in watch mode), `np` will hang.\n\n```json\n{\n\t\"scripts\": {\n\t\t\"test\": \"vitest\",\n\t\t\"publish\": \"npm run test && np\"\n\t}\n}\n```\n\n**Solution**: Don't name your scripts `publish`, `prepublish`, or `postpublish` (these are reserved npm lifecycle hooks). Use names like `release` instead:\n\n```json\n{\n\t\"scripts\": {\n\t\t\"test\": \"vitest run\",\n\t\t\"release\": \"np\"\n\t}\n}\n```\n\n**Tests running in watch mode**\n\nIf your test script runs in watch mode, it won't exit after running tests.\n\n**Solution**: Ensure your test command exits after running:\n\n```json\n{\n\t\"scripts\": {\n\t\t\"test\": \"vitest run\",\n\t\t\"test:dev\": \"vitest\"\n\t}\n}\n```\n\n**Registry configuration issues**\n\nA missing trailing slash in `.npmrc` registry configuration can cause hangs.\n\n**Solution**: Ensure registry URLs have a trailing slash:\n\n```npmrc\n@ORG:registry=https://npm.pkg.github.com/\n```\n\n## Maintainers\n\n- [Sindre Sorhus](https://github.com/sindresorhus)\n- [Tommy Mitchell](https://github.com/tommy-mitchell)\n"
  },
  {
    "path": "source/.npmignore",
    "content": "*.d.ts\n"
  },
  {
    "path": "source/cli-implementation.js",
    "content": "#!/usr/bin/env node\nimport process from 'node:process';\nimport logSymbols from 'log-symbols';\nimport meow from 'meow';\nimport updateNotifier from 'update-notifier';\nimport isInteractive from 'is-interactive';\nimport {gracefulExit} from 'exit-hook';\nimport {getPackageManagerConfig} from './package-manager/index.js';\nimport config from './config.js';\nimport * as util from './util.js';\nimport * as git from './git-util.js';\nimport * as npm from './npm/util.js';\nimport {getOidcProvider} from './npm/oidc.js';\nimport {SEMVER_INCREMENTS} from './version.js';\nimport ui from './ui.js';\nimport np from './index.js';\n\n/** @typedef {typeof cli} CLI */\n\nconst cli = meow(`\n\tUsage\n\t  $ np <version>\n\n\t  Version can be:\n\t    ${SEMVER_INCREMENTS.join(' | ')} | 1.2.3\n\n\tOptions\n\t  --any-branch           Allow publishing from any branch\n\t  --branch               Name of the release branch (default: main | master)\n\t  --no-cleanup           Skips cleanup of node_modules\n\t  --no-tests             Skips tests\n\t  --yolo                 Skips cleanup and testing\n\t  --no-publish           Skips publishing\n\t  --preview              Show tasks without actually executing them\n\t  --tag                  Publish under a given dist-tag\n\t  --contents             Subdirectory to publish\n\t  --no-release-draft     Skips opening a GitHub release draft\n\t  --release-draft-only   Only opens a GitHub release draft for the latest published version\n\t  --no-release-notes     Skips generating release notes when opening a GitHub release draft\n\t  --test-script          Name of npm run script to run tests before publishing (default: test)\n\t  --no-2fa               Don't enable 2FA on new packages (not recommended)\n\t  --message              Version bump commit message, '%s' will be replaced with version (default: '%s' with npm and 'v%s' with yarn)\n\t  --package-manager      Use a specific package manager (default: 'packageManager' field in package.json)\n\t  --provenance           Publish with npm provenance statements (CI-only)\n\t  --remote               Git remote to push to (default: origin)\n\n\tExamples\n\t  $ np\n\t  $ np patch\n\t  $ np 1.0.2\n\t  $ np 1.0.2-beta.3 --tag=beta\n\t  $ np 1.0.2-beta.3 --tag=beta --contents=dist\n`, {\n\timportMeta: import.meta,\n\tbooleanDefault: undefined,\n\t// Don't use `default` for flags - we apply defaults later so config can override them\n\tflags: {\n\t\tanyBranch: {\n\t\t\ttype: 'boolean',\n\t\t},\n\t\tbranch: {\n\t\t\ttype: 'string',\n\t\t},\n\t\tcleanup: {\n\t\t\ttype: 'boolean',\n\t\t},\n\t\ttests: {\n\t\t\ttype: 'boolean',\n\t\t},\n\t\tyolo: {\n\t\t\ttype: 'boolean',\n\t\t},\n\t\tpublish: {\n\t\t\ttype: 'boolean',\n\t\t},\n\t\treleaseDraft: {\n\t\t\ttype: 'boolean',\n\t\t},\n\t\treleaseDraftOnly: {\n\t\t\ttype: 'boolean',\n\t\t},\n\t\treleaseNotes: {\n\t\t\ttype: 'boolean',\n\t\t},\n\t\ttag: {\n\t\t\ttype: 'string',\n\t\t},\n\t\tpackageManager: {\n\t\t\ttype: 'string',\n\t\t},\n\t\tcontents: {\n\t\t\ttype: 'string',\n\t\t},\n\t\tpreview: {\n\t\t\ttype: 'boolean',\n\t\t},\n\t\ttestScript: {\n\t\t\ttype: 'string',\n\t\t},\n\t\t'2fa': {\n\t\t\ttype: 'boolean',\n\t\t},\n\t\tmessage: {\n\t\t\ttype: 'string',\n\t\t},\n\t\tprovenance: {\n\t\t\ttype: 'boolean',\n\t\t},\n\t\tremote: {\n\t\t\ttype: 'string',\n\t\t},\n\t},\n});\n\nupdateNotifier({pkg: cli.pkg}).notify();\n\n/** @typedef {Awaited<ReturnType<typeof getOptions>>['options']} Options */\n\nasync function getOptions() {\n\t// Load config from cwd first to get `contents` option before reading package\n\tconst initialConfig = await config(process.cwd());\n\tconst contents = cli.flags.contents ?? initialConfig?.contents;\n\n\tconst {package_, rootDirectory} = await util.readPackage(contents);\n\n\tconst localConfig = await config(rootDirectory);\n\n\t// Filter out undefined CLI flags (not provided by user)\n\tconst explicitCliFlags = Object.fromEntries(Object.entries(cli.flags).filter(([, value]) => value !== undefined));\n\n\t// Merge: local config → explicit CLI flags → defaults\n\tconst flags = {\n\t\tcleanup: true,\n\t\ttests: true,\n\t\tpublish: true,\n\t\treleaseDraft: true,\n\t\treleaseNotes: true,\n\t\t'2fa': true,\n\t\t...localConfig,\n\t\t...explicitCliFlags,\n\t};\n\n\t// Workaround for unintended auto-casing behavior from `meow`.\n\tif ('2Fa' in flags) {\n\t\tflags['2fa'] = flags['2Fa'];\n\t}\n\n\tif (flags.packageManager) {\n\t\tpackage_.packageManager = flags.packageManager;\n\t}\n\n\tconst packageManager = getPackageManagerConfig(rootDirectory, package_);\n\n\tif (packageManager.throwOnExternalRegistry && npm.isExternalRegistry(package_)) {\n\t\tthrow new Error(`External registry is not yet supported with ${packageManager.id}.`);\n\t}\n\n\tconst runPublish = !flags.releaseDraftOnly && flags.publish && !package_.private;\n\n\tconst availability = runPublish\n\t\t? await npm.isPackageNameAvailable(package_)\n\t\t: {\n\t\t\tisAvailable: false,\n\t\t\tisUnknown: false,\n\t\t};\n\n\t// Use current (latest) version when 'releaseDraftOnly', otherwise try to use the first argument.\n\tconst version = flags.releaseDraftOnly ? package_.version : cli.input.at(0);\n\n\tconst branch = flags.branch ?? await git.defaultBranch();\n\n\tconst options = await ui({\n\t\t...flags,\n\t\tpackageManager,\n\t\trunPublish,\n\t\tavailability,\n\t\tversion,\n\t\tbranch,\n\t}, {package_, rootDirectory});\n\n\treturn {\n\t\toptions: {...options, packageManager},\n\t\trootDirectory,\n\t\tpackage_,\n\t};\n}\n\ntry {\n\tconst {options, rootDirectory, package_} = await getOptions();\n\n\tif (!options.confirm) {\n\t\tgracefulExit();\n\t}\n\n\t// Check authentication early, before Listr starts (so login can be interactive)\n\tif (options.runPublish) {\n\t\t// Skip auth check if OIDC is available (will be handled by npm publish itself)\n\t\tif (getOidcProvider()) {\n\t\t\tconsole.log('OIDC authentication detected - skipping auth check');\n\t\t} else {\n\t\t\tconst externalRegistry = npm.isExternalRegistry(package_)\n\t\t\t\t? package_.publishConfig.registry\n\t\t\t\t: false;\n\n\t\t\ttry {\n\t\t\t\tawait npm.username({externalRegistry});\n\t\t\t} catch (error) {\n\t\t\t\tif (error.isNotLoggedIn && isInteractive()) {\n\t\t\t\t\tconsole.log('\\nYou must be logged in to publish. Running `npm login`...\\n');\n\t\t\t\t\tawait npm.login({externalRegistry});\n\t\t\t\t} else {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tconsole.log(); // Prints a newline for readability\n\tconst newPackage = await np(options.version.toString(), options, {package_, rootDirectory});\n\n\tif (options.preview || options.releaseDraftOnly) {\n\t\tgracefulExit();\n\t}\n\n\tconsole.log(`\\n ${newPackage.name} ${newPackage.version} published 🎉`);\n} catch (error) {\n\tif (error.name === 'ExitPromptError') {\n\t\tprocess.exit(0);\n\t}\n\n\tconsole.error(`\\n${logSymbols.error} ${error?.stack ?? error}`);\n\tgracefulExit(1);\n}\n"
  },
  {
    "path": "source/cli.js",
    "content": "#!/usr/bin/env node\nimport {debuglog} from 'node:util';\nimport importLocal from 'import-local';\nimport isInstalledGlobally from 'is-installed-globally';\n\nconst log = debuglog('np');\n\n// Prefer the local installation\nif (!importLocal(import.meta.url)) {\n\tif (isInstalledGlobally) {\n\t\tlog('Using global install of np.');\n\t}\n\n\tawait import('./cli-implementation.js');\n}\n"
  },
  {
    "path": "source/config.js",
    "content": "import os from 'node:os';\nimport isInstalledGlobally from 'is-installed-globally';\nimport {cosmiconfig} from 'cosmiconfig';\n\nexport default async function getConfig(rootDirectory) {\n\tconst searchPlaces = [\n\t\t'.np-config.json',\n\t\t'.np-config.js',\n\t\t'.np-config.cjs',\n\t\t'.np-config.mjs',\n\t\t'package.json',\n\t];\n\n\tconst explorer = cosmiconfig('np', {\n\t\tsearchPlaces,\n\t\tstopDir: rootDirectory,\n\t});\n\n\t// Always read project config\n\tconst {config: projectConfig} = (await explorer.search(rootDirectory)) ?? {};\n\n\t// When globally installed, also read global config and merge (project wins)\n\tif (isInstalledGlobally) {\n\t\tconst globalExplorer = cosmiconfig('np', {\n\t\t\tsearchPlaces: searchPlaces.filter(place => place !== 'package.json'),\n\t\t\tstopDir: os.homedir(),\n\t\t});\n\n\t\tconst {config: globalConfig} = (await globalExplorer.search(os.homedir())) ?? {};\n\n\t\treturn {\n\t\t\t...globalConfig,\n\t\t\t...projectConfig,\n\t\t};\n\t}\n\n\treturn projectConfig;\n}\n"
  },
  {
    "path": "source/git-tasks.js",
    "content": "import Listr from 'listr';\nimport * as git from './git-util.js';\n\nconst gitTasks = options => {\n\tconst tasks = [\n\t\t{\n\t\t\ttitle: 'Check current branch',\n\t\t\ttask: () => git.verifyCurrentBranchIsReleaseBranch(options.branch),\n\t\t},\n\t\t{\n\t\t\ttitle: 'Check local working tree',\n\t\t\ttask: () => git.verifyWorkingTreeIsClean(),\n\t\t},\n\t\t{\n\t\t\ttitle: 'Check remote history',\n\t\t\ttask: () => git.verifyRemoteHistoryIsClean(),\n\t\t},\n\t];\n\n\tif (options.anyBranch) {\n\t\ttasks.shift();\n\t}\n\n\treturn new Listr(tasks);\n};\n\nexport default gitTasks;\n"
  },
  {
    "path": "source/git-util.js",
    "content": "import path from 'node:path';\nimport {execa} from 'execa';\nimport escapeStringRegexp from 'escape-string-regexp';\nimport ignoreWalker from 'ignore-walk';\nimport semver from 'semver';\nimport * as util from './util.js';\n\nconst gitNetworkTimeout = 120_000; // 2 minutes for remote git operations\n\nexport const latestTag = async () => {\n\tconst {stdout} = await execa('git', ['describe', '--abbrev=0', '--tags']);\n\treturn stdout;\n};\n\nexport const root = async () => {\n\tconst {stdout} = await execa('git', ['rev-parse', '--show-toplevel']);\n\treturn stdout;\n};\n\nexport const newFilesSinceLastRelease = async rootDirectory => {\n\ttry {\n\t\tconst {stdout} = await execa('git', ['diff', '--name-only', '--diff-filter=A', await latestTag(), 'HEAD']);\n\t\tif (stdout.trim().length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst result = stdout.trim().split('\\n').map(row => row.trim());\n\t\treturn result;\n\t} catch {\n\t\t// Get all files under version control\n\t\treturn ignoreWalker({\n\t\t\tpath: rootDirectory,\n\t\t\tignoreFiles: ['.gitignore'],\n\t\t});\n\t}\n};\n\nexport const readFileFromLastRelease = async file => {\n\tconst rootPath = await root();\n\tconst filePathFromRoot = path.relative(rootPath, path.resolve(rootPath, file));\n\tconst {stdout: oldFile} = await execa('git', ['show', `${await latestTag()}:${filePathFromRoot}`]);\n\treturn oldFile;\n};\n\n/** Returns an array of all tags. */\nconst tagList = async () => {\n\tconst {stdout} = await execa('git', ['tag']);\n\treturn stdout ? stdout.split('\\n') : [];\n};\n\n/** Returns an array of tags sorted by semver in ascending order. Non-semver tags are excluded. */\nconst tagListSortedBySemver = async () => {\n\tconst tags = await tagList();\n\treturn tags\n\t\t.filter(tag => semver.valid(tag))\n\t\t.sort((a, b) => semver.compare(a, b));\n};\n\nconst firstCommit = async () => {\n\tconst {stdout} = await execa('git', ['rev-list', '--max-parents=0', 'HEAD']);\n\t// Repository may have multiple initial commits (e.g., from merging unrelated histories).\n\t// Return just the first one.\n\treturn stdout.split('\\n')[0];\n};\n\nexport const previousTagOrFirstCommit = async () => {\n\tconst tags = await tagListSortedBySemver();\n\n\tif (tags.length === 0) {\n\t\treturn;\n\t}\n\n\tif (tags.length === 1) {\n\t\treturn firstCommit();\n\t}\n\n\ttry {\n\t\t// Return the tag before the latest one (sorted by semver).\n\t\tconst latest = await latestTag();\n\t\tconst index = tags.indexOf(latest);\n\n\t\tif (index === -1 || index === 0) {\n\t\t\treturn firstCommit();\n\t\t}\n\n\t\treturn tags[index - 1];\n\t} catch {\n\t\t// Fallback to the first commit.\n\t\treturn firstCommit();\n\t}\n};\n\nexport const latestTagOrFirstCommit = async () => {\n\tlet latest;\n\ttry {\n\t\t// In case a previous tag exists, we use it to compare the current repo status to.\n\t\tlatest = await latestTag();\n\t} catch {\n\t\t// Otherwise, we fallback to using the first commit for comparison.\n\t\tlatest = await firstCommit();\n\t}\n\n\treturn latest;\n};\n\nexport const hasUpstream = async () => {\n\tconst escapedCurrentBranch = escapeStringRegexp(await getCurrentBranch());\n\tconst {stdout} = await execa('git', ['status', '--short', '--branch', '--porcelain']);\n\n\treturn new RegExp(String.raw`^## ${escapedCurrentBranch}\\.\\.\\..+\\/${escapedCurrentBranch}`).test(stdout);\n};\n\nexport const getCurrentBranch = async () => {\n\tconst {stdout} = await execa('git', ['symbolic-ref', '--short', 'HEAD']);\n\treturn stdout;\n};\n\nexport const verifyCurrentBranchIsReleaseBranch = async releaseBranch => {\n\tconst currentBranch = await getCurrentBranch();\n\tif (currentBranch !== releaseBranch) {\n\t\tthrow new Error(`Not on \\`${releaseBranch}\\` branch. Use --any-branch to publish anyway, or set a different release branch using --branch.`);\n\t}\n};\n\nexport const isHeadDetached = async () => {\n\ttry {\n\t\t// Command will fail with code 1 if the HEAD is detached.\n\t\tawait execa('git', ['symbolic-ref', '--quiet', 'HEAD']);\n\t\treturn false;\n\t} catch {\n\t\treturn true;\n\t}\n};\n\nconst isWorkingTreeClean = async () => {\n\ttry {\n\t\tconst {stdout: status} = await execa('git', ['status', '--porcelain']);\n\t\tif (status !== '') {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n};\n\nexport const verifyWorkingTreeIsClean = async () => {\n\tif (!(await isWorkingTreeClean())) {\n\t\tthrow new Error('Unclean working tree. Commit or stash changes first.');\n\t}\n};\n\nconst hasRemote = async () => {\n\ttry {\n\t\tawait execa('git', ['rev-parse', '@{u}']);\n\t} catch { // Has no remote if command fails\n\t\treturn false;\n\t}\n\n\treturn true;\n};\n\nconst hasUnfetchedChangesFromRemote = async () => {\n\t// Inherit stdin to allow SSH password prompts for password-protected keys\n\tconst {stdout: possibleNewChanges} = await execa('git', ['fetch', '--dry-run'], {stdin: 'inherit', timeout: gitNetworkTimeout});\n\n\t// There are unfetched changes if output is not empty.\n\treturn Boolean(possibleNewChanges);\n};\n\nconst isRemoteHistoryClean = async () => {\n\tconst {stdout: history} = await execa('git', ['rev-list', '--count', '--left-only', '@{u}...HEAD']);\n\n\t// Remote history is clean if there are 0 revisions.\n\treturn history === '0';\n};\n\nexport const verifyRemoteHistoryIsClean = async () => {\n\tif (!(await hasRemote())) {\n\t\treturn;\n\t}\n\n\tif (await hasUnfetchedChangesFromRemote()) {\n\t\tthrow new Error('Remote history differs. Please run `git fetch` and pull changes.');\n\t}\n\n\tif (!(await isRemoteHistoryClean())) {\n\t\tthrow new Error('Remote history differs. Please pull changes.');\n\t}\n};\n\nexport const verifyRemoteIsValid = async remote => {\n\ttry {\n\t\t// Inherit stdin to allow SSH password prompts for password-protected keys\n\t\tawait execa('git', ['ls-remote', remote ?? 'origin', 'HEAD'], {stdin: 'inherit', timeout: gitNetworkTimeout});\n\t} catch (error) {\n\t\tthrow new Error(error.stderr.replace('fatal:', 'Git fatal error:'));\n\t}\n};\n\nexport const fetch = async () => {\n\t// Inherit stdin to allow SSH password prompts for password-protected keys\n\tawait execa('git', ['fetch'], {stdin: 'inherit', timeout: gitNetworkTimeout});\n};\n\nconst hasLocalBranch = async branch => {\n\ttry {\n\t\tawait execa('git', ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`]);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n};\n\nexport const defaultBranch = async () => {\n\tfor (const branch of ['main', 'master', 'gh-pages']) {\n\t\t// eslint-disable-next-line no-await-in-loop\n\t\tif (await hasLocalBranch(branch)) {\n\t\t\treturn branch;\n\t\t}\n\t}\n\n\tthrow new Error('Could not infer the default Git branch. Please specify one with the --branch flag or with a np config.');\n};\n\nconst tagExistsOnRemote = async tagName => {\n\ttry {\n\t\tconst {stdout: revInfo} = await execa('git', ['rev-parse', '--quiet', '--verify', `refs/tags/${tagName}`]);\n\n\t\tif (revInfo) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t} catch (error) {\n\t\t// Command fails with code 1 and no output if the tag does not exist, even though `--quiet` is provided\n\t\t// https://github.com/sindresorhus/np/pull/73#discussion_r72385685\n\t\tif (error.stdout === '' && error.stderr === '') {\n\t\t\treturn false;\n\t\t}\n\n\t\tthrow error;\n\t}\n};\n\nexport const verifyTagDoesNotExistOnRemote = async tagName => {\n\tif (await tagExistsOnRemote(tagName)) {\n\t\tthrow new Error(`Git tag \\`${tagName}\\` already exists.`);\n\t}\n};\n\nexport const commitLogFromRevision = async revision => {\n\tconst {stdout} = await execa('git', ['log', '--format=%s %h', `${revision}..HEAD`]);\n\treturn stdout;\n};\n\nconst push = async (remote, tagArgument = '--follow-tags') => {\n\t// Inherit stdin to allow SSH password prompts for password-protected keys\n\tawait execa('git', ['push', ...(remote ? [remote] : []), tagArgument], {stdin: 'inherit', timeout: gitNetworkTimeout});\n};\n\nexport const pushGraceful = async (remoteIsOnGitHub, remote) => {\n\ttry {\n\t\tawait push(remote);\n\t} catch (error) {\n\t\tif (remoteIsOnGitHub && error.stderr && error.stderr.includes('GH006')) {\n\t\t\t// Try to push tags only, when commits can't be pushed due to branch protection\n\t\t\tawait push(remote, '--tags');\n\t\t\treturn {pushed: 'tags', reason: 'Branch protection: np can`t push the commits. Push them manually.'};\n\t\t}\n\n\t\tthrow error;\n\t}\n};\n\nexport const deleteTag = async tagName => {\n\tawait execa('git', ['tag', '--delete', tagName]);\n};\n\nexport const removeLastCommit = async () => {\n\tawait execa('git', ['reset', '--hard', 'HEAD~1']);\n};\n\nconst gitVersion = async () => {\n\tconst {stdout} = await execa('git', ['version']);\n\tconst match = /git version (?<version>\\d+\\.\\d+\\.\\d+).*/.exec(stdout);\n\treturn match && match.groups.version;\n};\n\nexport const verifyRecentGitVersion = async () => {\n\tconst installedVersion = await gitVersion();\n\tutil.validateEngineVersionSatisfies('git', installedVersion);\n};\n\nexport const verifyUserConfigIsSet = async () => {\n\tconst [nameResult, emailResult] = await Promise.allSettled([\n\t\texeca('git', ['config', 'user.name']),\n\t\texeca('git', ['config', 'user.email']),\n\t]);\n\n\tif (nameResult.status !== 'fulfilled' || !nameResult.value.stdout || emailResult.status !== 'fulfilled' || !emailResult.value.stdout) {\n\t\tthrow new Error([\n\t\t\t'Git user configuration is not set.',\n\t\t\t'',\n\t\t\t'Please set your git user name and email:',\n\t\t\t'  git config --global user.name \"Your Name\"',\n\t\t\t'  git config --global user.email \"you@example.com\"',\n\t\t].join('\\n'));\n\t}\n};\n"
  },
  {
    "path": "source/index.js",
    "content": "import {execa} from 'execa';\nimport {deleteAsync} from 'del';\n// NOTE: We intentionally use the original `listr` package instead of `listr2`.\n// listr2's DefaultRenderer uses log-update which has known issues with terminal scrolling\n// that cause it to overwrite content printed before listr2 started (like our inquirer prompts).\n// See: https://github.com/cenk1cenk2/listr2/issues/296\nimport Listr from 'listr';\nimport {\n\tmerge,\n\tcatchError,\n\tfilter,\n\tfinalize,\n\tfrom,\n\tmergeMap,\n\tthrowError,\n} from 'rxjs';\nimport hostedGitInfo from 'hosted-git-info';\nimport onetime from 'onetime';\nimport {asyncExitHook} from 'exit-hook';\nimport logSymbols from 'log-symbols';\nimport prerequisiteTasks from './prerequisite-tasks.js';\nimport gitTasks from './git-tasks.js';\nimport {getPackagePublishArguments, runPublish} from './npm/publish.js';\nimport enable2fa, {getEnable2faArguments} from './npm/enable-2fa.js';\nimport handleNpmError from './npm/handle-npm-error.js';\nimport {getOidcProvider} from './npm/oidc.js';\nimport releaseTaskHelper from './release-task-helper.js';\nimport {findLockfile, printCommand} from './package-manager/index.js';\nimport * as util from './util.js';\nimport * as git from './git-util.js';\nimport * as npm from './npm/util.js';\n\n/** @type {(cmd: string, args: string[], options?: import('execa').Options) => any} */\nconst exec = (command, arguments_, options) => {\n\t// Use `Observable` support if merged https://github.com/sindresorhus/execa/pull/26\n\tconst subProcess = execa(command, arguments_, options);\n\n\treturn merge(subProcess.stdout, subProcess.stderr, subProcess).pipe(\n\t\tfilter(Boolean),\n\t\tcatchError(error => {\n\t\t\t// Include stderr in error message for better diagnostics\n\t\t\tif (error.stderr) {\n\t\t\t\terror.message = `${error.shortMessage}\\n${error.stderr}`;\n\t\t\t}\n\n\t\t\tthrow error;\n\t\t}),\n\t);\n};\n\n/**\n@param {string} input\n@param {import('./cli-implementation.js').Options} options\n@param {{package_: import('read-pkg').NormalizedPackageJson; rootDirectory: string}} context\n*/\nconst np = async (input = 'patch', {packageManager, ...options}, {package_, rootDirectory}) => {\n\t// TODO: Remove sometime far in the future\n\tif (options.skipCleanup) {\n\t\toptions.cleanup = false;\n\t}\n\n\tconst runTests = options.tests && !options.yolo;\n\tconst runCleanup = options.cleanup && !options.yolo;\n\tconst lockfile = findLockfile(rootDirectory, packageManager);\n\tconst isOnGitHub = options.repoUrl && hostedGitInfo.fromUrl(options.repoUrl)?.type === 'github';\n\tconst testScript = options.testScript || 'test';\n\n\tif (options.releaseDraftOnly) {\n\t\tawait releaseTaskHelper(options, package_, packageManager);\n\t\treturn package_;\n\t}\n\n\tlet publishStatus = 'UNKNOWN';\n\tlet pushedObjects;\n\n\tconst rollback = onetime(async () => {\n\t\tconsole.log('\\nPublish failed. Rolling back to the previous state…');\n\n\t\tconst tagVersionPrefix = await util.getTagVersionPrefix(packageManager);\n\n\t\tconst latestTag = await git.latestTag();\n\t\tconst versionInLatestTag = latestTag.slice(tagVersionPrefix.length);\n\n\t\tasync function getPackageVersion() {\n\t\t\tconst package_ = await util.readPackage(rootDirectory);\n\t\t\treturn package_.version;\n\t\t}\n\n\t\ttry {\n\t\t\t// Verify that the package's version has been bumped before deleting the last tag and commit.\n\t\t\tif (versionInLatestTag === await getPackageVersion() && versionInLatestTag !== package_.version) {\n\t\t\t\tawait git.deleteTag(latestTag);\n\t\t\t\tawait git.removeLastCommit();\n\t\t\t}\n\n\t\t\tconsole.log('Successfully rolled back the project to its previous state.');\n\t\t} catch (error) {\n\t\t\tconsole.log(`Couldn't roll back because of the following error:\\n${error}`);\n\t\t}\n\t});\n\n\tasyncExitHook(async () => {\n\t\tif (options.preview || publishStatus === 'SUCCESS') {\n\t\t\treturn;\n\t\t}\n\n\t\tif (publishStatus === 'FAILED') {\n\t\t\tawait rollback();\n\t\t} else {\n\t\t\tconsole.log('\\nAborted!');\n\t\t}\n\t}, {wait: 2000});\n\n\t// Don't enable 2FA when using OIDC (Trusted Publishing) as it's already managed\n\tconst shouldEnable2FA = options['2fa'] && options.availability.isAvailable && !options.availability.isUnknown && !package_.private && !npm.isExternalRegistry(package_) && !getOidcProvider();\n\n\t// To prevent the process from hanging due to watch mode (e.g. when running `vitest`)\n\tconst ciEnvOptions = {env: {CI: 'true'}};\n\n\t/** @param {typeof options} _options */\n\tfunction getPublishCommand(_options) {\n\t\tconst publishCommand = packageManager.publishCommand || (arguments_ => [packageManager.cli, arguments_]);\n\t\tconst arguments_ = getPackagePublishArguments(_options);\n\t\treturn publishCommand(arguments_);\n\t}\n\n\tconst tasks = new Listr([\n\t\t{\n\t\t\ttitle: 'Prerequisite check',\n\t\t\tenabled: () => options.runPublish,\n\t\t\ttask: () => prerequisiteTasks(input, package_, options, {packageManager, rootDirectory}),\n\t\t},\n\t\t{\n\t\t\ttitle: 'Git',\n\t\t\ttask: () => gitTasks(options),\n\t\t},\n\t\t{\n\t\t\ttitle: 'Cleanup',\n\t\t\tenabled: () => runCleanup && !lockfile,\n\t\t\ttask: () => deleteAsync('node_modules'),\n\t\t},\n\t\t{\n\t\t\ttitle: `Installing dependencies using ${packageManager.id}`,\n\t\t\tenabled: () => runCleanup,\n\t\t\ttask: () => new Listr([\n\t\t\t\t{\n\t\t\t\t\ttitle: 'Running install command',\n\t\t\t\t\ttask() {\n\t\t\t\t\t\tconst installCommand = lockfile ? packageManager.installCommand : packageManager.installCommandNoLockfile;\n\t\t\t\t\t\treturn exec(...installCommand);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttitle: 'Checking working tree is still clean', // If lockfile was out of date and tracked by git, this will fail\n\t\t\t\t\ttask: () => git.verifyWorkingTreeIsClean(),\n\t\t\t\t},\n\t\t\t]),\n\t\t},\n\t\t{\n\t\t\ttitle: 'Running tests',\n\t\t\tenabled: () => runTests,\n\t\t\ttask: () => exec(packageManager.cli, ['run', testScript], ciEnvOptions),\n\t\t},\n\t\t{\n\t\t\ttitle: 'Bumping version',\n\t\t\tskip() {\n\t\t\t\tif (options.preview) {\n\t\t\t\t\tconst [cli, arguments_] = packageManager.versionCommand(input);\n\n\t\t\t\t\tif (options.message) {\n\t\t\t\t\t\targuments_.push('--message', options.message.replaceAll('%s', input));\n\t\t\t\t\t}\n\n\t\t\t\t\treturn `[Preview] Command not executed: ${printCommand([cli, arguments_])}`;\n\t\t\t\t}\n\t\t\t},\n\t\t\ttask() {\n\t\t\t\tconst [cli, arguments_] = packageManager.versionCommand(input);\n\n\t\t\t\tif (options.message) {\n\t\t\t\t\targuments_.push('--message', options.message);\n\t\t\t\t}\n\n\t\t\t\t// Inherit stdin to allow GPG password prompts for commit signing\n\t\t\t\treturn exec(cli, arguments_, {stdin: 'inherit'});\n\t\t\t},\n\t\t},\n\t\t...options.runPublish\n\t\t\t? [\n\t\t\t\t{\n\t\t\t\t\ttitle: 'Publishing package',\n\t\t\t\t\tskip() {\n\t\t\t\t\t\tif (options.preview) {\n\t\t\t\t\t\t\tconst command = getPublishCommand(options);\n\t\t\t\t\t\t\treturn `[Preview] Command not executed: ${printCommand(command)}.`;\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t/** @type {(context, task) => Listr.ListrTaskResult<any>} */\n\t\t\t\t\ttask(context, task) {\n\t\t\t\t\t\tlet hasError = false;\n\n\t\t\t\t\t\treturn runPublish(getPublishCommand(options), {cwd: rootDirectory})\n\t\t\t\t\t\t\t.pipe(catchError(error => handleNpmError(error, task, otp => {\n\t\t\t\t\t\t\t\tcontext.otp = otp;\n\n\t\t\t\t\t\t\t\treturn runPublish(getPublishCommand({...options, otp}), {cwd: rootDirectory});\n\t\t\t\t\t\t\t})))\n\t\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\t\t// Note: Cannot use `async` here as the `await` will not finish before the error propagates.\n\t\t\t\t\t\t\t\tcatchError(error => {\n\t\t\t\t\t\t\t\t\thasError = true;\n\t\t\t\t\t\t\t\t\treturn from(rollback()).pipe(\n\t\t\t\t\t\t\t\t\t\tmergeMap(() => throwError(() => new Error(`Error publishing package:\\n${error.message}\\n\\nThe project was rolled back to its previous state.`))),\n\t\t\t\t\t\t\t\t\t\tcatchError(() => throwError(() => new Error(`Error publishing package:\\n${error.message}\\n\\nThe project was rolled back to its previous state.`))),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\tfinalize(() => {\n\t\t\t\t\t\t\t\t\tpublishStatus = hasError ? 'FAILED' : 'SUCCESS';\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t...shouldEnable2FA\n\t\t\t\t\t? [{\n\t\t\t\t\t\ttitle: 'Enabling two-factor authentication',\n\t\t\t\t\t\tasync skip() {\n\t\t\t\t\t\t\tif (options.preview) {\n\t\t\t\t\t\t\t\tconst arguments_ = await getEnable2faArguments(package_.name, options);\n\t\t\t\t\t\t\t\treturn `[Preview] Command not executed: npm ${arguments_.join(' ')}.`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\ttask: (context, task) => enable2fa(task, package_.name, {otp: context.otp}),\n\t\t\t\t\t}]\n\t\t\t\t\t: [],\n\t\t\t]\n\t\t\t: [],\n\t\t{\n\t\t\ttitle: 'Pushing tags',\n\t\t\tasync skip() {\n\t\t\t\tif (!options.remote && !(await git.hasUpstream())) {\n\t\t\t\t\treturn 'Upstream branch not found; not pushing.';\n\t\t\t\t}\n\n\t\t\t\tif (options.preview) {\n\t\t\t\t\tconst remote = options.remote ? `${options.remote} ` : '';\n\t\t\t\t\treturn `[Preview] Command not executed: git push ${remote}--follow-tags.`;\n\t\t\t\t}\n\n\t\t\t\tif (publishStatus === 'FAILED' && options.runPublish) {\n\t\t\t\t\treturn 'Couldn\\'t publish package to npm; not pushing.';\n\t\t\t\t}\n\t\t\t},\n\t\t\tasync task() {\n\t\t\t\tpushedObjects = await git.pushGraceful(isOnGitHub, options.remote);\n\t\t\t},\n\t\t},\n\t\t...options.releaseDraft\n\t\t\t? [{\n\t\t\t\ttitle: 'Creating release draft on GitHub',\n\t\t\t\tenabled: () => isOnGitHub === true,\n\t\t\t\tskip() {\n\t\t\t\t\tif (options.preview) {\n\t\t\t\t\t\treturn '[Preview] GitHub Releases draft will not be opened in preview mode.';\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\ttask: () => releaseTaskHelper(options, package_, packageManager),\n\t\t\t}]\n\t\t\t: [],\n\t], {\n\t\tshowSubtasks: false,\n\t\trenderer: options.renderer ?? 'default',\n\t\tclearOutput: !options.preview && !options.releaseDraftOnly,\n\t});\n\n\tif (!options.runPublish) {\n\t\tpublishStatus = 'SUCCESS';\n\t}\n\n\tawait tasks.run();\n\n\tif (pushedObjects) {\n\t\tconsole.error(`\\n${logSymbols.error} ${pushedObjects.reason}`);\n\t}\n\n\tconst {package_: newPackage} = await util.readPackage();\n\treturn newPackage;\n};\n\nexport default np;\n"
  },
  {
    "path": "source/npm/enable-2fa.js",
    "content": "import {execa} from 'execa';\nimport {from, catchError} from 'rxjs';\nimport Version from '../version.js';\nimport handleNpmError from './handle-npm-error.js';\nimport {npmNetworkTimeout, version as npmVersionCheck} from './util.js';\n\nexport const getEnable2faArguments = async (packageName, options) => {\n\tconst npmVersion = await npmVersionCheck();\n\tconst arguments_ = new Version(npmVersion).satisfies('>=9.0.0')\n\t\t? ['access', 'set', 'mfa=publish', packageName]\n\t\t: ['access', '2fa-required', packageName];\n\n\tif (options && options.otp) {\n\t\targuments_.push('--otp', options.otp);\n\t}\n\n\treturn arguments_;\n};\n\nconst enable2fa = async (packageName, options) => execa('npm', await getEnable2faArguments(packageName, options), {timeout: npmNetworkTimeout});\n\nconst tryEnable2fa = (task, packageName, options) => from(enable2fa(packageName, options)).pipe(catchError(error => handleNpmError(error, task, otp => enable2fa(packageName, {otp}))));\n\nexport default tryEnable2fa;\n"
  },
  {
    "path": "source/npm/handle-npm-error.js",
    "content": "import listrInput from 'listr-input';\nimport chalk from 'chalk';\nimport {throwError, catchError} from 'rxjs';\n\nconst handleNpmError = (error, task, message, executor) => {\n\tif (typeof message === 'function') {\n\t\texecutor = message;\n\t\tmessage = undefined;\n\t}\n\n\t// `one-time pass` is for npm and `Two factor authentication` is for Yarn.\n\tif (\n\t\terror.stderr.includes('one-time pass') // Npm\n\t\t|| error.stdout.includes('Two factor authentication') // Yarn v1\n\t\t|| error.stdout.includes('One-time password:') // Yarn berry\n\t) {\n\t\tconst {title} = task;\n\t\ttask.title = `${title} ${chalk.yellow('(waiting for input…)')}`;\n\n\t\treturn listrInput('Enter OTP:', {\n\t\t\tdone(otp) {\n\t\t\t\ttask.title = title;\n\t\t\t\treturn executor(otp);\n\t\t\t},\n\t\t\tautoSubmit: value => value.length === 6,\n\t\t}).pipe(catchError(error => handleNpmError(error, task, 'OTP was incorrect, try again:', executor)));\n\t}\n\n\t// Attempting to privately publish a scoped package without the correct npm plan\n\t// https://stackoverflow.com/a/44862841/10292952\n\tif (\n\t\terror.code === 402\n\t\t|| error.stderr.includes('npm ERR! 402 Payment Required') // Npm/pnpm\n\t\t|| error.stdout.includes('Response Code: 402 (Payment Required)') // Yarn Berry\n\t) {\n\t\tthrow new Error('You cannot publish a scoped package privately without a paid plan. Did you mean to publish publicly?');\n\t}\n\n\treturn throwError(() => error);\n};\n\nexport default handleNpmError;\n"
  },
  {
    "path": "source/npm/oidc.js",
    "content": "import process from 'node:process';\n\nconst oidcProviders = [\n\t{\n\t\tid: 'github',\n\t\tname: 'GitHub Actions',\n\t\t// See https://github.com/npm/cli/blob/7da8fdd3625dd5541af57052c90fe1eabb41eb96/lib/utils/oidc.js#L49-L67\n\t\t// See https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-cloud-providers#adding-permissions-settings\n\t\tvalidate: () =>\n\t\t\tprocess.env.GITHUB_ACTIONS\n\t\t\t&& process.env.ACTIONS_ID_TOKEN_REQUEST_URL\n\t\t\t&& process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN,\n\t},\n\t{\n\t\tid: 'gitlab',\n\t\tname: 'GitLab CI',\n\t\t// See https://github.com/npm/cli/blob/7da8fdd3625dd5541af57052c90fe1eabb41eb96/lib/utils/oidc.js#L37-L47\n\t\tvalidate: () => process.env.GITLAB_CI && process.env.NPM_ID_TOKEN,\n\t},\n];\n\nexport const getOidcProvider = () => {\n\tfor (const provider of oidcProviders) {\n\t\tif (provider.validate()) {\n\t\t\treturn provider.id;\n\t\t}\n\t}\n};\n"
  },
  {
    "path": "source/npm/publish.js",
    "content": "import {execa} from 'execa';\nimport {merge, filter, catchError} from 'rxjs';\nimport open from 'open';\n\nexport const getPackagePublishArguments = options => {\n\tconst arguments_ = ['publish'];\n\n\tif (options.tag) {\n\t\targuments_.push('--tag', options.tag);\n\t}\n\n\tif (options.otp) {\n\t\targuments_.push('--otp', options.otp);\n\t}\n\n\tif (options.publishScoped) {\n\t\targuments_.push('--access', 'public');\n\t}\n\n\tif (options.provenance) {\n\t\targuments_.push('--provenance');\n\t}\n\n\treturn arguments_;\n};\n\n// 3 minutes timeout for publish operations (like git network operations)\n// Publishing can take time for large packages or slow connections\nconst publishTimeout = 180_000;\n\nexport function runPublish(arguments_, options = {}) {\n\tconst execaOptions = {\n\t\tstdin: 'pipe',\n\t\t// Timeout to prevent infinite hangs (e.g., from lifecycle scripts in watch mode)\n\t\ttimeout: publishTimeout,\n\t};\n\n\t// `npm` 8.5+ has a bug where `npm publish <folder>` publishes from cwd instead of <folder>.\n\t// We work around this by changing cwd to the target directory.\n\t// https://github.com/npm/cli/issues/5136\n\tif (options.cwd) {\n\t\texecaOptions.cwd = options.cwd;\n\t}\n\n\tconst subprocess = execa(...arguments_, execaOptions);\n\n\tlet outputBuffer = '';\n\n\tconst handleAuthPrompt = data => {\n\t\toutputBuffer += data.toString();\n\n\t\t// Detect npm's browser authentication prompt\n\t\t// Example: \"Authenticate your account at:\\nhttps://www.npmjs.com/auth/cli/xyz\"\n\t\tif (outputBuffer.includes('Authenticate your account at:')) {\n\t\t\tconst urlMatch = outputBuffer.match(/https:\\/\\/www\\.npmjs\\.com\\/auth\\/cli\\/\\S+/);\n\t\t\tif (urlMatch) {\n\t\t\t\tconst authUrl = urlMatch[0];\n\t\t\t\t// Auto-open browser for authentication (ignore errors if browser fails to open)\n\t\t\t\t(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait open(authUrl);\n\t\t\t\t\t} catch {}\n\t\t\t\t})();\n\n\t\t\t\t// Automatically send ENTER to continue (skip \"Press ENTER\" prompt)\n\t\t\t\tsubprocess.stdin?.write('\\n');\n\t\t\t\t// Clear buffer after handling to prevent repeated triggers\n\t\t\t\toutputBuffer = '';\n\t\t\t}\n\t\t}\n\n\t\t// Prevent buffer from growing indefinitely\n\t\tif (outputBuffer.length > 10_000) {\n\t\t\toutputBuffer = outputBuffer.slice(-5000);\n\t\t}\n\t};\n\n\t// Monitor both stdout and stderr for the authentication prompt\n\tsubprocess.stdout?.on('data', handleAuthPrompt);\n\tsubprocess.stderr?.on('data', handleAuthPrompt);\n\n\treturn merge(subprocess.stdout, subprocess.stderr, subprocess).pipe(\n\t\tfilter(Boolean),\n\t\tcatchError(error => {\n\t\t\t// Include stderr in error message for better diagnostics\n\t\t\tif (error.stderr) {\n\t\t\t\terror.message = `${error.shortMessage}\\n${error.stderr}`;\n\t\t\t}\n\n\t\t\tthrow error;\n\t\t}),\n\t);\n}\n"
  },
  {
    "path": "source/npm/util.js",
    "content": "import path from 'node:path';\nimport {pathExists} from 'path-exists';\nimport {execa} from 'execa';\nimport npmName from 'npm-name';\nimport chalk from 'chalk-template';\nimport * as util from '../util.js';\n\nexport const version = async () => {\n\tconst {stdout} = await execa('npm', ['--version']);\n\treturn stdout;\n};\n\nexport const npmNetworkTimeout = 15_000; // 15 seconds for npm registry calls\n\nconst throwIfNpmTimeout = error => {\n\tif (error.timedOut) {\n\t\terror.message = 'Connection to npm registry timed out';\n\t\tthrow error;\n\t}\n};\n\nexport const checkConnection = async () => {\n\ttry {\n\t\tawait execa('npm', ['ping'], {timeout: npmNetworkTimeout});\n\t\treturn true;\n\t} catch (error) {\n\t\tthrowIfNpmTimeout(error);\n\t\tthrow new Error('Connection to npm registry failed');\n\t}\n};\n\nexport const username = async ({externalRegistry}) => {\n\tconst arguments_ = ['whoami'];\n\n\tif (externalRegistry) {\n\t\targuments_.push('--registry', externalRegistry);\n\t}\n\n\ttry {\n\t\tconst {stdout} = await execa('npm', arguments_, {timeout: npmNetworkTimeout});\n\t\treturn stdout;\n\t} catch (error) {\n\t\tthrowIfNpmTimeout(error);\n\t\tconst isNotLoggedIn = /ENEEDAUTH|E401/.test(error.stderr);\n\t\tconst message = isNotLoggedIn\n\t\t\t? 'You must be logged in. Use `npm login` and try again.'\n\t\t\t: 'Authentication error. Use `npm whoami` to troubleshoot.';\n\t\tconst authError = new Error(message);\n\t\tauthError.isNotLoggedIn = isNotLoggedIn;\n\t\tthrow authError;\n\t}\n};\n\nexport const login = async ({externalRegistry}) => {\n\tconst arguments_ = ['login'];\n\n\tif (externalRegistry) {\n\t\targuments_.push('--registry', externalRegistry);\n\t}\n\n\ttry {\n\t\tawait execa('npm', arguments_, {\n\t\t\tstdin: 'inherit',\n\t\t\tstdout: 'inherit',\n\t\t\tstderr: 'pipe',\n\t\t});\n\t} catch (error) {\n\t\t// User canceled the login prompt\n\t\tif (error.stderr?.includes('canceled')) {\n\t\t\tconst cancelError = new Error('Login canceled');\n\t\t\tcancelError.name = 'ExitPromptError';\n\t\t\tthrow cancelError;\n\t\t}\n\n\t\tthrow error;\n\t}\n};\n\nconst NPM_DEFAULT_REGISTRIES = new Set([\n\t// https://docs.npmjs.com/cli/v10/using-npm/registry\n\t'https://registry.npmjs.org',\n\t// https://docs.npmjs.com/cli/v10/commands/npm-profile#registry\n\t'https://registry.npmjs.org/',\n]);\nexport const isExternalRegistry = package_ => {\n\tconst registry = package_.publishConfig?.registry;\n\tif (typeof registry !== 'string') {\n\t\treturn false;\n\t}\n\n\tconst normalizedRegistry = registry.trim();\n\tconst httpsVariant = normalizedRegistry.replace(/^http:\\/\\//, 'https://');\n\n\treturn !NPM_DEFAULT_REGISTRIES.has(normalizedRegistry)\n\t\t&& !NPM_DEFAULT_REGISTRIES.has(httpsVariant);\n};\n\nexport const collaborators = async package_ => {\n\tconst packageName = package_.name;\n\tutil.assert(typeof packageName === 'string', 'Package name is required');\n\n\tconst arguments_ = ['access', 'list', 'collaborators', packageName, '--json'];\n\n\tif (package_.publishConfig?.registry) {\n\t\targuments_.push('--registry', package_.publishConfig.registry);\n\t}\n\n\ttry {\n\t\tconst {stdout} = await execa('npm', arguments_, {timeout: npmNetworkTimeout});\n\t\treturn stdout;\n\t} catch (error) {\n\t\tthrowIfNpmTimeout(error);\n\n\t\t// Ignore non-existing package error\n\t\tif (error.stderr.includes('code E404')) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// External registries often don't support this endpoint, so ignore errors.\n\t\t// The whoami check is sufficient for verifying authentication.\n\t\t// See: https://github.com/sindresorhus/np/issues/420\n\t\tif (isExternalRegistry(package_)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthrow error;\n\t}\n};\n\nexport const prereleaseTags = async packageName => {\n\tutil.assert(typeof packageName === 'string', 'Package name is required');\n\n\tlet tags = [];\n\ttry {\n\t\tconst {stdout} = await execa('npm', ['view', '--json', packageName, 'dist-tags'], {timeout: npmNetworkTimeout});\n\t\ttags = Object.keys(JSON.parse(stdout))\n\t\t\t.filter(tag => tag !== 'latest');\n\t} catch (error) {\n\t\tthrowIfNpmTimeout(error);\n\t\t// HACK: NPM is mixing JSON with plain text errors. Luckily, the error\n\t\t// always starts with 'npm ERR!' (npm <10) or 'npm error' (npm >=10)\n\t\t// so as a solution, until npm/cli#2740 is fixed, we can remove anything\n\t\t// starting with 'npm ERR!' or 'npm error'\n\t\t/** @type {string} */\n\t\tconst errorMessage = error.stderr;\n\t\tconst errorJSON = errorMessage\n\t\t\t.split('\\n')\n\t\t\t.filter(line => !line.startsWith('npm ERR!') && !line.startsWith('npm error'))\n\t\t\t.join('\\n');\n\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(errorJSON);\n\t\t\t// Only handle E404 errors gracefully; throw all other errors\n\t\t\tif (parsed?.error?.code !== 'E404') {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t} catch {\n\t\t\t// If JSON parsing fails, we can't determine the error type, so throw the original error\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tif (tags.length === 0) {\n\t\ttags.push('next');\n\t}\n\n\treturn tags;\n};\n\nexport const isPackageNameAvailable = async package_ => {\n\tconst arguments_ = [package_.name];\n\tconst availability = {\n\t\tisAvailable: false,\n\t\tisUnknown: false,\n\t};\n\n\tif (isExternalRegistry(package_)) {\n\t\targuments_.push({\n\t\t\tregistryUrl: package_.publishConfig.registry,\n\t\t});\n\t}\n\n\ttry {\n\t\tavailability.isAvailable = await npmName(...arguments_) || false;\n\t} catch {\n\t\tavailability.isUnknown = true;\n\t}\n\n\treturn availability;\n};\n\nexport const verifyRecentNpmVersion = async () => {\n\tconst npmVersion = await version();\n\tutil.validateEngineVersionSatisfies('npm', npmVersion);\n};\n\nexport const checkIgnoreStrategy = async ({files}, rootDirectory) => {\n\tconst npmignoreExistsInPackageRootDirectory = await pathExists(path.resolve(rootDirectory, '.npmignore'));\n\n\tif (!files && !npmignoreExistsInPackageRootDirectory) {\n\t\tconsole.log(chalk`\n\t\t\\n{bold.yellow Warning:} No {bold.cyan files} field specified in {bold.magenta package.json} nor is a {bold.magenta .npmignore} file present. Having one of those will prevent you from accidentally publishing development-specific files along with your package's source code to npm.\n\t\t`);\n\t}\n};\n\nexport const getFilesToBePacked = async rootDirectory => {\n\tconst {stdout} = await execa('npm', [\n\t\t'pack',\n\t\t'--dry-run',\n\t\t'--json',\n\t\t'--silent',\n\t\t// TODO: Remove this once [npm/cli#7354](https://github.com/npm/cli/issues/7354) is resolved.\n\t\t'--foreground-scripts=false',\n\t], {cwd: rootDirectory});\n\n\ttry {\n\t\t// HACK: NPM lifecycle scripts can output text even with --silent and --foreground-scripts=false.\n\t\t// For example, Husky's prepare script outputs \"> package@version prepare\" and \"> husky install\".\n\t\t// We extract only the JSON portion by finding the first '[' character.\n\t\t// Related: https://github.com/sindresorhus/np/issues/742\n\t\tconst {files} = JSON.parse(stdout.slice(Math.max(0, stdout.indexOf('[')))).at(0);\n\t\treturn files.map(file => file.path);\n\t} catch (error) {\n\t\tthrow new Error('Failed to parse output of npm pack', {cause: error});\n\t}\n};\n\nconst isValidEntryPoint = value => typeof value === 'string' && !value.includes('*');\n\nconst getExportsFiles = exports => {\n\tconst files = [];\n\n\tconst extract = value => {\n\t\tif (isValidEntryPoint(value)) {\n\t\t\tfiles.push(value);\n\t\t} else if (typeof value === 'object' && value !== null) {\n\t\t\tfor (const subvalue of Object.values(value)) {\n\t\t\t\textract(subvalue);\n\t\t\t}\n\t\t}\n\t};\n\n\textract(exports);\n\treturn files;\n};\n\nexport const getPackageEntryPoints = package_ => {\n\tconst entryPoints = [];\n\n\tif (isValidEntryPoint(package_.main)) {\n\t\tentryPoints.push({field: 'main', path: package_.main});\n\t}\n\n\tif (typeof package_.bin === 'string') {\n\t\tif (isValidEntryPoint(package_.bin)) {\n\t\t\tentryPoints.push({field: 'bin', path: package_.bin});\n\t\t}\n\t} else if (typeof package_.bin === 'object' && package_.bin !== null) {\n\t\tfor (const [name, binPath] of Object.entries(package_.bin)) {\n\t\t\tif (isValidEntryPoint(binPath)) {\n\t\t\t\tentryPoints.push({field: `bin.${name}`, path: binPath});\n\t\t\t}\n\t\t}\n\t}\n\n\tif (package_.exports) {\n\t\tfor (const file of getExportsFiles(package_.exports)) {\n\t\t\tentryPoints.push({field: 'exports', path: file});\n\t\t}\n\t}\n\n\treturn entryPoints;\n};\n\nexport const verifyPackageEntryPoints = async (package_, rootDirectory) => {\n\tconst packedFiles = new Set(await getFilesToBePacked(rootDirectory));\n\tconst entryPoints = getPackageEntryPoints(package_);\n\n\tconst seenPaths = new Set();\n\tconst missingEntryPoints = [];\n\n\tfor (const entryPoint of entryPoints) {\n\t\tconst normalizedPath = entryPoint.path.replace(/^\\.\\//, '');\n\n\t\tif (seenPaths.has(normalizedPath)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tseenPaths.add(normalizedPath);\n\n\t\tif (!packedFiles.has(normalizedPath)) {\n\t\t\tmissingEntryPoints.push(entryPoint);\n\t\t}\n\t}\n\n\tif (missingEntryPoints.length > 0) {\n\t\tconst missing = missingEntryPoints.map(({field, path: entryPath}) => `  \"${field}\": ${entryPath}`).join('\\n');\n\t\tthrow new Error(`Missing entry points in published files:\\n${missing}\\n\\nEnsure these files exist and are included in the \"files\" field.`);\n\t}\n};\n\nexport const getPublishedPackageEngines = async package_ => {\n\tconst arguments_ = ['view', '--json', package_.name, 'engines'];\n\n\tif (package_.publishConfig?.registry) {\n\t\targuments_.push('--registry', package_.publishConfig.registry);\n\t}\n\n\ttry {\n\t\tconst {stdout} = await execa('npm', arguments_, {timeout: npmNetworkTimeout});\n\n\t\t// Handle empty response (package exists but has no engines field)\n\t\tif (stdout.trim() === '') {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn JSON.parse(stdout);\n\t} catch (error) {\n\t\tthrowIfNpmTimeout(error);\n\n\t\t// Package doesn't exist yet (first publish)\n\t\tif (error.stderr?.includes('E404')) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// External registries often don't support this endpoint, so ignore errors.\n\t\t// See: https://github.com/sindresorhus/np/issues/420\n\t\tif (isExternalRegistry(package_)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tthrow error;\n\t}\n};\n"
  },
  {
    "path": "source/package-manager/configs.js",
    "content": "/** @type {import('./types.d.ts').PackageManagerConfig} */\nexport const npmConfig = {\n\tcli: 'npm',\n\tid: 'npm',\n\tinstallCommand: ['npm', ['ci', '--engine-strict']],\n\tinstallCommandNoLockfile: ['npm', ['install', '--no-package-lock', '--no-production', '--engine-strict']],\n\tversionCommand: version => ['npm', ['version', version]],\n\tgetRegistryCommand: ['npm', ['config', 'get', 'registry', '--workspaces=false']],\n\ttagVersionPrefixCommand: ['npm', ['config', 'get', 'tag-version-prefix', '--workspaces=false']],\n\tlockfiles: ['package-lock.json', 'npm-shrinkwrap.json'],\n};\n\n/** @type {import('./types.d.ts').PackageManagerConfig} */\nexport const pnpmConfig = {\n\tcli: 'pnpm',\n\tid: 'pnpm',\n\tinstallCommand: ['pnpm', ['install']],\n\tinstallCommandNoLockfile: ['pnpm', ['install']],\n\tversionCommand: version => ['pnpm', ['version', version]],\n\t// By default, pnpm config returns `undefined` instead of `v` for tag-version-prefix, so for consistent default behavior, use npm.\n\ttagVersionPrefixCommand: ['npm', ['config', 'get', 'tag-version-prefix', '--workspaces=false']],\n\t// Disable duplicated pnpm Git checks\n\tpublishCommand: arguments_ => ['pnpm', [...arguments_, '--no-git-checks']],\n\tgetRegistryCommand: ['pnpm', ['config', 'get', 'registry']],\n\tlockfiles: ['pnpm-lock.yaml'],\n};\n\n/** @type {import('./types.d.ts').PackageManagerConfig} */\nexport const yarnConfig = {\n\tcli: 'yarn',\n\tid: 'yarn',\n\tinstallCommand: ['yarn', ['install', '--frozen-lockfile', '--production=false']],\n\tinstallCommandNoLockfile: ['yarn', ['install', '--production=false']],\n\tgetRegistryCommand: ['yarn', ['config', 'get', 'registry']],\n\ttagVersionPrefixCommand: ['yarn', ['config', 'get', 'version-tag-prefix']],\n\tversionCommand: version => ['yarn', ['version', '--new-version', version]],\n\tlockfiles: ['yarn.lock'],\n};\n\n/** @type {import('./types.d.ts').PackageManagerConfig} */\nexport const yarnBerryConfig = {\n\tcli: 'yarn',\n\tid: 'yarn-berry',\n\tinstallCommand: ['yarn', ['install', '--immutable']],\n\tinstallCommandNoLockfile: ['yarn', ['install']],\n\t// Yarn berry doesn't support git committing/tagging, so we use npm instead\n\tversionCommand: version => ['npm', ['version', version]],\n\ttagVersionPrefixCommand: ['yarn', ['config', 'get', 'version-tag-prefix']],\n\t// Yarn berry offloads publishing to npm, e.g. `yarn npm publish x.y.z`\n\tpublishCommand: arguments_ => ['yarn', ['npm', ...arguments_]],\n\tgetRegistryCommand: ['yarn', ['config', 'get', 'npmRegistryServer']],\n\tthrowOnExternalRegistry: true,\n\tlockfiles: ['yarn.lock'],\n};\n\n/** @type {import('./types.d.ts').PackageManagerConfig} */\nexport const bunConfig = {\n\tcli: 'bun',\n\tid: 'bun',\n\tinstallCommand: ['bun', ['install', '--frozen-lockfile']],\n\tinstallCommandNoLockfile: ['bun', ['install', '--no-save']],\n\tversionCommand: version => ['npm', ['version', version]],\n\t// Bun doesn't support publishing, so we use npm instead. See https://github.com/oven-sh/bun/issues/5050\n\tpublishCommand: arguments_ => ['npm', arguments_],\n\t// TODO: Bun doesn't support config get registry, this should be added in the future. See https://github.com/oven-sh/bun/issues/7140\n\tgetRegistryCommand: ['npm', ['config', 'get', 'registry', '--workspaces=false']],\n\ttagVersionPrefixCommand: ['npm', ['config', 'get', 'tag-version-prefix', '--workspaces=false']],\n\tlockfiles: ['bun.lockb', 'bun.lock'],\n};\n"
  },
  {
    "path": "source/package-manager/index.js",
    "content": "import fs from 'node:fs';\nimport path from 'node:path';\nimport semver from 'semver';\nimport * as configs from './configs.js';\n\n/**\n@param {string} rootDirectory\n@param {import('./types.d.ts').PackageManagerConfig} config\n*/\nexport function findLockfile(rootDirectory, config) {\n\treturn config.lockfiles\n\t\t.map(filename => path.resolve(rootDirectory || '.', filename))\n\t\t.find(filepath => fs.existsSync(filepath));\n}\n\n/**\n@param {string} rootDirectory\n@param {import('read-pkg').NormalizedPackageJson} package_\n*/\nexport function getPackageManagerConfig(rootDirectory, package_) {\n\tconst config = configFromPackageManagerField(package_);\n\treturn config || configFromLockfile(rootDirectory) || configs.npmConfig;\n}\n\n/** @param {import('read-pkg').NormalizedPackageJson} package_ */\nfunction configFromPackageManagerField(package_) {\n\tif (typeof package_.packageManager !== 'string') {\n\t\treturn undefined;\n\t}\n\n\tconst [packageManager, version] = package_.packageManager.split('@');\n\n\tif (packageManager === 'yarn' && version && semver.gte(version, '2.0.0')) {\n\t\treturn configs.yarnBerryConfig;\n\t}\n\n\tif (packageManager === 'npm') {\n\t\treturn configs.npmConfig;\n\t}\n\n\tif (packageManager === 'pnpm') {\n\t\treturn configs.pnpmConfig;\n\t}\n\n\tif (packageManager === 'yarn') {\n\t\treturn configs.yarnConfig;\n\t}\n\n\tif (packageManager === 'bun') {\n\t\treturn configs.bunConfig;\n\t}\n\n\tthrow new Error(`Invalid package manager: ${package_.packageManager}`);\n}\n\n/** @param {string} rootDirectory */\nfunction configFromLockfile(rootDirectory, options = [configs.npmConfig, configs.pnpmConfig, configs.yarnConfig]) {\n\tconst foundConfig = options.find(config => findLockfile(rootDirectory, config));\n\n\t// If yarn.lock is found, check if it's Yarn Berry by looking for .yarnrc.yml\n\tif (foundConfig === configs.yarnConfig) {\n\t\tconst yarnrcYmlPath = path.resolve(rootDirectory || '.', '.yarnrc.yml');\n\t\tif (fs.existsSync(yarnrcYmlPath)) {\n\t\t\treturn configs.yarnBerryConfig;\n\t\t}\n\t}\n\n\treturn foundConfig;\n}\n\n/** @param {import('./types.d.ts').Command} command */\nexport function printCommand([cli, arguments_]) {\n\treturn `${cli} ${arguments_.join(' ')}`;\n}\n"
  },
  {
    "path": "source/package-manager/types.d.ts",
    "content": "export type PackageManager = 'npm' | 'yarn' | 'pnpm';\n\n/**\nCLI and arguments, which can be passed to `execa`.\n*/\nexport type Command = [cli: string, args: string[]];\n\nexport type PackageManagerConfig = {\n\t/**\n \tThe main CLI, e.g. the `npm` in `npm install`, `npm test`, etc.\n  \t*/\n\tcli: PackageManager;\n\n\t/**\n \tHow the package manager should be referred to in user-facing messages (since there are two different configs for some, e.g. yarn and yarn-berry).\n  \t*/\n\tid: string;\n\n\t/**\n \tHow to install packages when there is a lockfile, e.g. `[\"npm\", [\"install\"]]`.\n  \t*/\n\tinstallCommand: Command;\n\n\t/**\n\tHow to install packages when there is no lockfile, e.g. `[\"npm\", [\"install\"]]`.\n\t*/\n\tinstallCommandNoLockfile: Command;\n\n\t/**\n \tGiven a version string, return a version command e.g. `version => [\"npm\", [\"version\", version]]`.\n  \t*/\n\tversionCommand: (version: string) => [cli: string, args: string[]];\n\n\t/**\n \tModify the actual publish command. Defaults to `args => [config.cli, args]`.\n  \t*/\n\tpublishCommand?: (arguments_: string[]) => Command;\n\n\t/**\n \tCLI command which is expected to output the npm registry to use, e.g. `['npm', ['config', 'get', 'registry']]`.\n  \t*/\n\tgetRegistryCommand: Command;\n\n\t/**\n \tCLI command expected to output the version tag prefix (often \"v\"). e,g. `['npm', ['config', 'get', 'tag-version-prefix']]`.\n  \t*/\n\ttagVersionPrefixCommand: Command;\n\n\t/**\n \tSet to true if the package manager doesn't support external registries. `np` will throw if one is detected and this is set.\n  \t*/\n\tthrowOnExternalRegistry?: boolean;\n\n\t/**\n \tList of lockfile names expected for this package manager, relative to CWD. e.g. `['package-lock.json', 'npm-shrinkwrap.json']`.\n  \t*/\n\tlockfiles: string[];\n};\n"
  },
  {
    "path": "source/prerequisite-tasks.js",
    "content": "import process from 'node:process';\nimport Listr from 'listr';\nimport {execa} from 'execa';\nimport semver from 'semver';\nimport Version from './version.js';\nimport * as util from './util.js';\nimport * as git from './git-util.js';\nimport * as npm from './npm/util.js';\nimport {getOidcProvider} from './npm/oidc.js';\n\nconst prerequisiteTasks = (input, package_, options, {packageManager, rootDirectory}) => {\n\tconst isExternalRegistry = npm.isExternalRegistry(package_);\n\tlet newVersion;\n\n\tconst tasks = [\n\t\t{\n\t\t\ttitle: 'Ping npm registry',\n\t\t\tenabled: () => !package_.private && !isExternalRegistry,\n\t\t\ttask: async () => npm.checkConnection(),\n\t\t},\n\t\t{\n\t\t\ttitle: `Check ${packageManager.cli} version`,\n\t\t\tasync task() {\n\t\t\t\tconst {stdout: version} = await execa(packageManager.cli, ['--version']);\n\t\t\t\tutil.validateEngineVersionSatisfies(packageManager.cli, version);\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttitle: 'Verify user is authenticated',\n\t\t\tenabled: () => process.env.NODE_ENV !== 'test' && !package_.private,\n\t\t\tskip() {\n\t\t\t\tif (getOidcProvider()) {\n\t\t\t\t\treturn 'Environment support for OIDC authentication detected - Skipping whoami check';\n\t\t\t\t}\n\t\t\t},\n\t\t\tasync task() {\n\t\t\t\tconst externalRegistry = package_.publishConfig?.registry;\n\t\t\t\tconst username = await npm.username({externalRegistry});\n\n\t\t\t\tconst collaborators = await npm.collaborators(package_);\n\t\t\t\tif (!collaborators) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst json = JSON.parse(collaborators);\n\t\t\t\tconst permissions = json[username];\n\t\t\t\tif (!permissions || !permissions.includes('write')) {\n\t\t\t\t\tthrow new Error('You do not have write permissions required to publish this package.');\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttitle: 'Check git version',\n\t\t\ttask: async () => git.verifyRecentGitVersion(),\n\t\t},\n\t\t{\n\t\t\ttitle: 'Check git user configuration',\n\t\t\ttask: async () => git.verifyUserConfigIsSet(),\n\t\t},\n\t\t{\n\t\t\ttitle: 'Check git remote',\n\t\t\ttask: async () => git.verifyRemoteIsValid(options.remote),\n\t\t},\n\t\t{\n\t\t\ttitle: 'Validate version',\n\t\t\ttask() {\n\t\t\t\tnewVersion = input instanceof Version\n\t\t\t\t\t? input\n\t\t\t\t\t: new Version(package_.version).setFrom(input);\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttitle: 'Check for pre-release version',\n\t\t\ttask() {\n\t\t\t\tif (!package_.private && newVersion.isPrerelease() && !options.tag) {\n\t\t\t\t\tthrow new Error('You must specify a dist-tag using --tag when publishing a pre-release version. This prevents accidentally tagging unstable versions as \"latest\". https://docs.npmjs.com/cli/dist-tag');\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttitle: 'Check for Node.js engine support drop',\n\t\t\tenabled: () => !options.yolo && !package_.private,\n\t\t\tasync task() {\n\t\t\t\tconst publishedEngines = await npm.getPublishedPackageEngines(package_);\n\n\t\t\t\t// Skip if this is the first publish or if published package has no engines.node\n\t\t\t\tif (!publishedEngines?.node) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst localNodeEngine = package_.engines?.node;\n\n\t\t\t\t// Skip if local package has no engines.node (we can't compare)\n\t\t\t\tif (!localNodeEngine) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst publishedMinimum = util.getMinimumNodeVersion(publishedEngines.node);\n\t\t\t\tconst localMinimum = util.getMinimumNodeVersion(localNodeEngine);\n\n\t\t\t\t// Skip if we couldn't parse either version\n\t\t\t\tif (!publishedMinimum || !localMinimum) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Check if the minimum Node.js version has increased\n\t\t\t\tif (semver.gt(localMinimum, publishedMinimum)) {\n\t\t\t\t\tconst diff = semver.diff(package_.version, newVersion.toString());\n\n\t\t\t\t\t// Only major and premajor releases are allowed to drop Node.js support\n\t\t\t\t\t// For pre-1.0.0 packages, minor bumps are considered breaking changes per semver\n\t\t\t\t\tif (diff !== 'major' && diff !== 'premajor' && semver.major(package_.version) >= 1) {\n\t\t\t\t\t\tthrow new Error(`Raising minimum Node.js version from ${publishedMinimum} to ${localMinimum} requires a major version bump. The current release is a ${diff} bump.`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttitle: 'Verify package entry points',\n\t\t\tenabled: () => !options.yolo,\n\t\t\tasync task() {\n\t\t\t\tawait npm.verifyPackageEntryPoints(package_, rootDirectory);\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttitle: 'Check git tag existence',\n\t\t\tasync task() {\n\t\t\t\tawait git.fetch();\n\n\t\t\t\tconst tagPrefix = await util.getTagVersionPrefix(packageManager);\n\n\t\t\t\tawait git.verifyTagDoesNotExistOnRemote(`${tagPrefix}${newVersion}`);\n\t\t\t},\n\t\t},\n\t];\n\n\treturn new Listr(tasks);\n};\n\nexport default prerequisiteTasks;\n"
  },
  {
    "path": "source/release-task-helper.js",
    "content": "import open from 'open';\nimport newGithubReleaseUrl from 'new-github-release-url';\nimport clipboard from 'clipboardy';\nimport {getTagVersionPrefix, getPreReleasePrefix} from './util.js';\nimport Version from './version.js';\n\n// GitHub has a URL limit of ~8195 characters. We use a conservative limit to be safe.\nconst URL_LENGTH_LIMIT = 7900;\nconst CLIPBOARD_PLACEHOLDER = '<!-- Paste release notes from clipboard -->';\n\nconst releaseTaskHelper = async (options, package_, packageManager) => {\n\tif (!options.repoUrl) {\n\t\tthrow new Error('Missing `repository` field in package.json. This is required for creating GitHub releases.');\n\t}\n\n\tconst newVersion = options.releaseDraftOnly\n\t\t? new Version(package_.version)\n\t\t: new Version(package_.version).setFrom(options.version.toString(), {prereleasePrefix: await getPreReleasePrefix(packageManager)});\n\n\tconst tag = await getTagVersionPrefix(packageManager) + newVersion.toString();\n\n\tconst releaseNotes = options.releaseNotes ? options.generateReleaseNotes(tag) : '';\n\n\t// Try to generate URL with full release notes\n\tlet url = newGithubReleaseUrl({\n\t\trepoUrl: options.repoUrl,\n\t\ttag,\n\t\tbody: releaseNotes,\n\t\tisPrerelease: newVersion.isPrerelease(),\n\t});\n\n\t// If the URL is too long, copy release notes to clipboard and use a placeholder\n\tif (url.length > URL_LENGTH_LIMIT) {\n\t\tawait clipboard.write(releaseNotes);\n\t\turl = newGithubReleaseUrl({\n\t\t\trepoUrl: options.repoUrl,\n\t\t\ttag,\n\t\t\tbody: CLIPBOARD_PLACEHOLDER,\n\t\t\tisPrerelease: newVersion.isPrerelease(),\n\t\t});\n\t\tconsole.log('\\nRelease notes are too long for URL. Copied to clipboard instead.');\n\t}\n\n\tawait open(url);\n};\n\nexport default releaseTaskHelper;\n"
  },
  {
    "path": "source/ui.js",
    "content": "import inquirer from 'inquirer';\nimport chalk from 'chalk';\nimport githubUrlFromGit from 'github-url-from-git';\nimport hostedGitInfo from 'hosted-git-info';\nimport {htmlEscape} from 'escape-goat';\nimport isScoped from 'is-scoped';\nimport isInteractive from 'is-interactive';\nimport {execa} from 'execa';\nimport semver from 'semver';\nimport Version, {SEMVER_INCREMENTS} from './version.js';\nimport * as util from './util.js';\nimport * as git from './git-util.js';\nimport * as npm from './npm/util.js';\n\nconst PRERELEASE_INCREMENTS = new Set([\n\t'prepatch',\n\t'preminor',\n\t'premajor',\n\t'prerelease',\n]);\n\nconst printCommitLog = async (repoUrl, registryUrl, fromLatestTag, releaseBranch) => {\n\tconst revision = fromLatestTag ? await git.latestTagOrFirstCommit() : await git.previousTagOrFirstCommit();\n\tif (!revision) {\n\t\tthrow new Error('The package has not been published yet.');\n\t}\n\n\tconst log = await git.commitLogFromRevision(revision);\n\n\tif (!log) {\n\t\treturn {\n\t\t\thasCommits: false,\n\t\t\thasUnreleasedCommits: false,\n\t\t\tgenerateReleaseNotes() {},\n\t\t};\n\t}\n\n\tlet hasUnreleasedCommits = false;\n\tlet commitRangeText = `${revision}...${releaseBranch}`;\n\n\tlet commits = log.split('\\n')\n\t\t.map(commit => {\n\t\t\tconst splitIndex = commit.lastIndexOf(' ');\n\t\t\treturn {\n\t\t\t\tmessage: commit.slice(0, splitIndex),\n\t\t\t\tid: commit.slice(splitIndex + 1),\n\t\t\t};\n\t\t});\n\n\tif (!fromLatestTag) {\n\t\tconst latestTag = await git.latestTag();\n\n\t\t// Version bump commit created by np, following the semver specification.\n\t\tconst versionBumpCommitName = latestTag.match(/v\\d+\\.\\d+\\.\\d+/) && latestTag.slice(1); // Name v1.0.1 becomes 1.0.1\n\t\tconst versionBumpCommitIndex = commits.findIndex(commit => commit.message === versionBumpCommitName);\n\n\t\tif (versionBumpCommitIndex > 0) {\n\t\t\tcommitRangeText = `${revision}...${latestTag}`;\n\t\t\thasUnreleasedCommits = true;\n\t\t}\n\n\t\tif (await git.isHeadDetached()) {\n\t\t\tcommitRangeText = `${revision}...${latestTag}`;\n\t\t}\n\n\t\t// Get rid of unreleased commits and of the version bump commit.\n\t\tcommits = commits.slice(versionBumpCommitIndex + 1);\n\t}\n\n\tconst history = commits.map(commit => {\n\t\tconst commitMessage = util.linkifyIssues(repoUrl, commit.message);\n\t\tconst commitId = util.linkifyCommit(repoUrl, commit.id);\n\t\treturn `- ${commitMessage}  ${commitId}`;\n\t}).join('\\n');\n\n\tconst generateReleaseNotes = nextTag => commits.map(commit =>\n\t\t`- ${htmlEscape(commit.message)}  ${commit.id}`).join('\\n') + `\\n\\n---\\n\\n${repoUrl}/compare/${revision}...${nextTag}`;\n\n\tconst commitRange = util.linkifyCommitRange(repoUrl, commitRangeText);\n\tconsole.log(`${chalk.bold('Commits:')}\\n${history}\\n\\n${chalk.bold('Commit Range:')}\\n${commitRange}\\n\\n${chalk.bold('Registry:')}\\n${registryUrl}\\n`);\n\n\treturn {\n\t\thasCommits: true,\n\t\thasUnreleasedCommits,\n\t\tgenerateReleaseNotes,\n\t};\n};\n\nconst checkNewFilesAndDependencies = async (package_, rootDirectory) => {\n\tconst newFiles = await util.getNewFiles(rootDirectory);\n\tconst newDependencies = await util.getNewDependencies(package_, rootDirectory);\n\n\tconst noNewFirstTimeFiles = !newFiles.firstTime || newFiles.firstTime.length === 0;\n\tconst noNewDependencies = !newDependencies || newDependencies.length === 0;\n\n\t// Only prompt for first-time files and new dependencies (things that WILL be published)\n\tif (noNewFirstTimeFiles && noNewDependencies) {\n\t\treturn {\n\t\t\tconfirmed: true,\n\t\t\tunpublishedFiles: newFiles.unpublished || [],\n\t\t};\n\t}\n\n\tconst messages = [];\n\tif (newFiles.firstTime.length > 0) {\n\t\tmessages.push(`The following new files will be published for the first time:\\n${util.joinList(newFiles.firstTime)}\\n\\nPlease make sure only the intended files are listed.`);\n\t}\n\n\tif (newDependencies.length > 0) {\n\t\tmessages.push(`The following new dependencies will be part of your published package:\\n${util.joinList(newDependencies)}\\n\\nPlease make sure these new dependencies are intentional.`);\n\t}\n\n\tif (!isInteractive()) {\n\t\tconsole.log(messages.join('\\n'));\n\t\treturn {\n\t\t\tconfirmed: true,\n\t\t\tunpublishedFiles: newFiles.unpublished || [],\n\t\t};\n\t}\n\n\tconst answers = await inquirer.prompt([{\n\t\ttype: 'confirm',\n\t\tname: 'confirm',\n\t\tmessage: `${messages.join('\\n')}\\nContinue?`,\n\t\tdefault: false,\n\t}]);\n\n\treturn {\n\t\tconfirmed: answers.confirm,\n\t\tunpublishedFiles: newFiles.unpublished || [],\n\t};\n};\n\nconst displayUnpublishedFilesWarning = unpublishedFiles => {\n\tif (!unpublishedFiles || unpublishedFiles.length === 0) {\n\t\treturn;\n\t}\n\n\tconsole.log([\n\t\t'',\n\t\tchalk.yellow('⚠ WARNING: The following new files will NOT be published:'),\n\t\tchalk.dim(util.groupFilesInFolders(unpublishedFiles)),\n\t\t'',\n\t\tchalk.yellow('These files are excluded by your package.json \"files\" field.'),\n\t\tchalk.yellow('If you intended to publish them, add them to the \"files\" field.'),\n\t\t'',\n\t].join('\\n'));\n};\n\n/**\n@param {import('./cli-implementation.js').CLI['flags']} options\n@param {{package_: import('read-pkg').NormalizedPackageJson; rootDirectory: string}} context\n*/\nconst ui = async ({packageManager, ...options}, {package_, rootDirectory}) => { // eslint-disable-line complexity\n\tconst oldVersion = package_.version;\n\tconst extraBaseUrls = ['gitlab.com'];\n\tconst repoUrl = package_.repository && (() => {\n\t\t// Try to parse with hosted-git-info first to handle shorthand URLs like \"github:foo/bar\"\n\t\tconst gitInfo = hostedGitInfo.fromUrl(package_.repository.url);\n\t\tif (gitInfo?.browse) {\n\t\t\treturn gitInfo.browse({noCommittish: true});\n\t\t}\n\n\t\t// Fall back to github-url-from-git for GitLab and other known hosts\n\t\tconst githubUrl = githubUrlFromGit(package_.repository.url, {extraBaseUrls});\n\t\tif (githubUrl) {\n\t\t\treturn githubUrl;\n\t\t}\n\n\t\t// Final fallback: parse any git URL format (handles GitHub Enterprise and other hosts)\n\t\treturn util.parseGitUrl(package_.repository.url);\n\t})();\n\n\tconst {stdout: registryUrl} = await execa(...packageManager.getRegistryCommand);\n\tconst releaseBranch = options.branch;\n\n\tlet unpublishedFiles;\n\tif (options.runPublish) {\n\t\tawait npm.checkIgnoreStrategy(package_, rootDirectory);\n\n\t\tconst {confirmed, unpublishedFiles: files} = await checkNewFilesAndDependencies(package_, rootDirectory);\n\t\tunpublishedFiles = files;\n\t\tif (!confirmed) {\n\t\t\treturn {\n\t\t\t\t...options,\n\t\t\t\tconfirm: confirmed,\n\t\t\t};\n\t\t}\n\t}\n\n\tif (options.releaseDraftOnly) {\n\t\tconsole.log(`\\nCreate a release draft on GitHub for ${chalk.bold.magenta(package_.name)} ${chalk.dim(`(current: ${oldVersion})`)}\\n`);\n\t} else {\n\t\tconst versionText = options.version\n\t\t\t? chalk.dim(`(current: ${oldVersion}, next: ${new Version(oldVersion).setFrom(options.version, {prereleasePrefix: await util.getPreReleasePrefix(packageManager)}).format()})`)\n\t\t\t: chalk.dim(`(current: ${oldVersion})`);\n\n\t\tconsole.log(`\\nPublish a new version of ${chalk.bold.magenta(package_.name)} ${versionText}\\n`);\n\t}\n\n\tconst useLatestTag = !options.releaseDraftOnly;\n\tconst {hasCommits, hasUnreleasedCommits, generateReleaseNotes} = await printCommitLog(repoUrl, registryUrl, useLatestTag, releaseBranch);\n\n\t// Display unpublished files warning after commit log\n\tdisplayUnpublishedFilesWarning(unpublishedFiles);\n\n\tif (hasUnreleasedCommits && options.releaseDraftOnly) {\n\t\tconst answers = await inquirer.prompt({\n\t\t\tconfirm: {\n\t\t\t\ttype: 'confirm',\n\t\t\t\tmessage: 'Unreleased commits found. They won\\'t be included in the release draft. Continue?',\n\t\t\t\tdefault: false,\n\t\t\t},\n\t\t});\n\n\t\tif (!answers.confirm) {\n\t\t\treturn {\n\t\t\t\t...options,\n\t\t\t\t...answers,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Non-interactive mode - return before prompting\n\t// But if it's a prerelease without a tag, we need to prompt for the tag\n\tif (options.version) {\n\t\tconst prereleasePrefix = await util.getPreReleasePrefix(packageManager);\n\t\tconst versionObject = new Version(oldVersion).setFrom(options.version, {prereleasePrefix});\n\t\tconst needsTag = options.runPublish && versionObject.isPrerelease() && !options.tag;\n\n\t\tif (!needsTag) {\n\t\t\treturn {\n\t\t\t\t...options,\n\t\t\t\tconfirm: true,\n\t\t\t\trepoUrl,\n\t\t\t\tgenerateReleaseNotes,\n\t\t\t};\n\t\t}\n\n\t\t// Prompt for tag only\n\t\tconst answers = await inquirer.prompt({\n\t\t\ttag: {\n\t\t\t\ttype: 'select',\n\t\t\t\tmessage: 'How should this pre-release version be tagged in npm?',\n\t\t\t\tasync choices() {\n\t\t\t\t\tconst existingPrereleaseTags = await npm.prereleaseTags(package_.name);\n\n\t\t\t\t\treturn [\n\t\t\t\t\t\t...existingPrereleaseTags,\n\t\t\t\t\t\tnew inquirer.Separator(),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: 'Other (specify)',\n\t\t\t\t\t\t\tvalue: undefined,\n\t\t\t\t\t\t},\n\t\t\t\t\t];\n\t\t\t\t},\n\t\t\t},\n\t\t\tcustomTag: {\n\t\t\t\ttype: 'input',\n\t\t\t\tmessage: 'Tag',\n\t\t\t\twhen: answers => answers.tag === undefined,\n\t\t\t\tvalidate(input) {\n\t\t\t\t\tif (input.length === 0) {\n\t\t\t\t\t\treturn 'Please specify a tag, for example, `next`.';\n\t\t\t\t\t}\n\n\t\t\t\t\tif (input.toLowerCase() === 'latest') {\n\t\t\t\t\t\treturn 'It\\'s not possible to publish pre-releases under the `latest` tag. Please specify something else, for example, `next`.';\n\t\t\t\t\t}\n\n\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t},\n\t\t});\n\n\t\treturn {\n\t\t\t...options,\n\t\t\ttag: answers.tag || answers.customTag || options.tag,\n\t\t\tconfirm: true,\n\t\t\trepoUrl,\n\t\t\tgenerateReleaseNotes,\n\t\t};\n\t}\n\n\tif (!hasCommits) {\n\t\tconst answers = await inquirer.prompt({\n\t\t\tconfirm: {\n\t\t\t\ttype: 'confirm',\n\t\t\t\tmessage: 'No commits found since previous release, continue?',\n\t\t\t\tdefault: false,\n\t\t\t},\n\t\t});\n\n\t\tif (!answers.confirm) {\n\t\t\treturn {\n\t\t\t\t...options,\n\t\t\t\t...answers,\n\t\t\t};\n\t\t}\n\t}\n\n\tif (options.availability.isUnknown) {\n\t\tif (!isScoped(package_.name)) {\n\t\t\tthrow new Error('Unknown availability, but package is not scoped. This shouldn\\'t happen');\n\t\t}\n\n\t\tconst answers = await inquirer.prompt({\n\t\t\tconfirm: {\n\t\t\t\ttype: 'confirm',\n\t\t\t\twhen: isScoped(package_.name) && options.runPublish,\n\t\t\t\tmessage: `Failed to check availability of scoped repo name ${chalk.bold.magenta(package_.name)}. Do you want to try and publish it anyway?`,\n\t\t\t\tdefault: false,\n\t\t\t},\n\t\t});\n\n\t\tif (!answers.confirm) {\n\t\t\treturn {\n\t\t\t\t...options,\n\t\t\t\t...answers,\n\t\t\t};\n\t\t}\n\t}\n\n\tconst needsPrereleaseTag = answers => {\n\t\tif (!options.runPublish || options.tag) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Check if version is a prerelease increment\n\t\tif (answers.version) {\n\t\t\treturn PRERELEASE_INCREMENTS.has(answers.version);\n\t\t}\n\n\t\t// Check if custom version is a prerelease\n\t\treturn answers.customVersion?.isPrerelease();\n\t};\n\n\tconst alreadyPublicScoped = packageManager.id === 'yarn-berry' && options.runPublish && await util.getNpmPackageAccess(package_) === 'public';\n\n\t// Note that inquirer question.when is a bit confusing. Only `false` will cause the question to be skipped.\n\t// Any other value like `true` and `undefined` means ask the question.\n\t// so we make sure to always return an explicit boolean here to make it less confusing\n\t// see https://github.com/SBoudrias/Inquirer.js/pull/1340\n\tconst needToAskForPublish = (() => {\n\t\tif (alreadyPublicScoped || !isScoped(package_.name) || !options.availability.isAvailable || options.availability.isUnknown || !options.runPublish) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Only ask if access is not explicitly set and not using an external registry\n\t\treturn !package_.publishConfig?.access && !npm.isExternalRegistry(package_);\n\t})();\n\n\t// Extract prerelease identifier from current version if it exists, otherwise use npm config\n\tconst currentPrerelease = semver.prerelease(oldVersion);\n\t// Only use the prefix if it's a string (not a number like in '1.0.0-0')\n\tconst currentPrereleasePrefix = typeof currentPrerelease?.[0] === 'string' ? currentPrerelease[0] : undefined;\n\tconst configPrereleasePrefix = await util.getPreReleasePrefix(packageManager);\n\tconst defaultPrereleasePrefix = currentPrereleasePrefix ?? configPrereleasePrefix;\n\n\tconst answers = await inquirer.prompt({\n\t\tversion: {\n\t\t\ttype: 'select',\n\t\t\tmessage: 'Select SemVer increment or specify new version',\n\t\t\tpageSize: SEMVER_INCREMENTS.length + 2,\n\t\t\tdefault: 0,\n\t\t\tchoices: [\n\t\t\t\t...SEMVER_INCREMENTS.map(increment => ({\n\t\t\t\t\tname: `${increment} \t${new Version(oldVersion, increment, {prereleasePrefix: defaultPrereleasePrefix}).format()}`,\n\t\t\t\t\tvalue: increment,\n\t\t\t\t})),\n\t\t\t\tnew inquirer.Separator(),\n\t\t\t\t{\n\t\t\t\t\tname: 'Other (specify)',\n\t\t\t\t\tvalue: undefined,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\tcustomVersion: {\n\t\t\ttype: 'input',\n\t\t\tmessage: 'Version',\n\t\t\twhen: answers => answers.version === undefined,\n\t\t\tfilter(input) {\n\t\t\t\tif (SEMVER_INCREMENTS.includes(input)) {\n\t\t\t\t\tthrow new Error('Custom version should not be a SemVer increment.');\n\t\t\t\t}\n\n\t\t\t\tconst version = new Version(oldVersion);\n\n\t\t\t\ttry {\n\t\t\t\t\t// Version error handling does validation\n\t\t\t\t\tversion.setFrom(input);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error.message.includes('valid SemVer version')) {\n\t\t\t\t\t\tthrow new Error(`Custom version ${input} should be a valid SemVer version.`);\n\t\t\t\t\t}\n\n\t\t\t\t\terror.message = error.message.replace('New', 'Custom');\n\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\n\t\t\t\treturn version;\n\t\t\t},\n\t\t},\n\t\tprereleasePrefix: {\n\t\t\ttype: 'input',\n\t\t\tmessage: 'Prerelease identifier',\n\t\t\t// Use || not ?? to treat empty string as falsy (show 'rc' instead of empty default)\n\t\t\tdefault: defaultPrereleasePrefix || 'rc',\n\t\t\twhen(answers) {\n\t\t\t\t// Only ask when a prerelease increment was selected from the menu\n\t\t\t\tif (!answers.version) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\treturn PRERELEASE_INCREMENTS.has(answers.version);\n\t\t\t},\n\t\t},\n\t\ttag: {\n\t\t\ttype: 'select',\n\t\t\tmessage: 'How should this pre-release version be tagged in npm?',\n\t\t\twhen: answers => needsPrereleaseTag(answers),\n\t\t\tasync choices() {\n\t\t\t\tconst existingPrereleaseTags = await npm.prereleaseTags(package_.name);\n\n\t\t\t\treturn [\n\t\t\t\t\t...existingPrereleaseTags,\n\t\t\t\t\tnew inquirer.Separator(),\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'Other (specify)',\n\t\t\t\t\t\tvalue: undefined,\n\t\t\t\t\t},\n\t\t\t\t];\n\t\t\t},\n\t\t},\n\t\tcustomTag: {\n\t\t\ttype: 'input',\n\t\t\tmessage: 'Tag',\n\t\t\twhen: answers => answers.tag === undefined && needsPrereleaseTag(answers),\n\t\t\tvalidate(input) {\n\t\t\t\tif (input.length === 0) {\n\t\t\t\t\treturn 'Please specify a tag, for example, `next`.';\n\t\t\t\t}\n\n\t\t\t\tif (input.toLowerCase() === 'latest') {\n\t\t\t\t\treturn 'It\\'s not possible to publish pre-releases under the `latest` tag. Please specify something else, for example, `next`.';\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t},\n\t\t},\n\t\tpublishScoped: {\n\t\t\ttype: 'confirm',\n\t\t\twhen: needToAskForPublish,\n\t\t\tmessage: `This scoped repo ${chalk.bold.magenta(package_.name)} hasn't been published. Do you want to publish it publicly?`,\n\t\t\tdefault: false,\n\t\t},\n\t});\n\n\t// Create Version object with custom prerelease prefix if provided\n\tlet version;\n\tif (answers.version) {\n\t\t// Use || not ?? to treat empty string as falsy (fall back to default/rc)\n\t\tconst prereleasePrefix = answers.prereleasePrefix || defaultPrereleasePrefix;\n\t\tversion = new Version(oldVersion, answers.version, {prereleasePrefix});\n\t} else if (answers.customVersion) {\n\t\tversion = answers.customVersion;\n\t} else {\n\t\tversion = options.version;\n\t}\n\n\treturn {\n\t\t...options,\n\t\tversion,\n\t\ttag: answers.tag || answers.customTag || options.tag,\n\t\tpublishScoped: alreadyPublicScoped || answers.publishScoped,\n\t\tconfirm: true,\n\t\trepoUrl,\n\t\tgenerateReleaseNotes,\n\t};\n};\n\nexport default ui;\n"
  },
  {
    "path": "source/util.js",
    "content": "import process from 'node:process';\nimport {fileURLToPath} from 'node:url';\nimport path from 'node:path';\nimport {readPackageUp} from 'read-package-up';\nimport {parsePackage} from 'read-pkg';\nimport issueRegex from 'issue-regex';\nimport terminalLink from 'terminal-link';\nimport {execa} from 'execa';\nimport pMemoize from 'p-memoize';\nimport chalk from 'chalk';\nimport semver from 'semver';\nimport Version from './version.js';\nimport * as git from './git-util.js';\nimport * as npm from './npm/util.js';\n\nexport const assert = (condition, message) => {\n\tif (!condition) {\n\t\tthrow new Error(message);\n\t}\n};\n\nexport const readPackage = async (packagePath = process.cwd()) => {\n\tconst packageResult = await readPackageUp({cwd: packagePath});\n\n\tif (!packageResult) {\n\t\tthrow new Error('No `package.json` found. Make sure the current directory is a valid package.');\n\t}\n\n\treturn {package_: packageResult.packageJson, rootDirectory: path.dirname(packageResult.path)};\n};\n\nconst _npRootDirectory = fileURLToPath(new URL('..', import.meta.url));\n\n// Re-define `npRootDirectory` for trailing slash consistency.\nexport const {package_: npPackage, rootDirectory: npRootDirectory} = await readPackage(_npRootDirectory);\n\nexport const linkifyIssues = (url, message) => {\n\tif (!url) {\n\t\treturn message;\n\t}\n\n\treturn message.replace(issueRegex(), issue => {\n\t\tconst issuePart = issue.replace('#', '/issues/');\n\n\t\tif (issue.startsWith('#')) {\n\t\t\treturn terminalLink(issue, `${url}${issuePart}`);\n\t\t}\n\n\t\treturn terminalLink(issue, `https://github.com/${issuePart}`);\n\t});\n};\n\nexport const linkifyCommit = (url, commit) => {\n\tif (!url) {\n\t\treturn commit;\n\t}\n\n\treturn terminalLink(commit, `${url}/commit/${commit}`);\n};\n\nexport const linkifyCommitRange = (url, commitRange) => {\n\tif (!url) {\n\t\treturn commitRange;\n\t}\n\n\treturn terminalLink(commitRange, `${url}/compare/${commitRange}`);\n};\n\n/*\nGit URL patterns for parsing various formats.\n\nPatterns use greedy matching + cleanRepo logic to handle edge cases like:\n- URLs with double .git suffix (repo.git.git)\n- Repos with .git in their name (my.git.git where repo is my.git)\n\nUsing [^\\s/?#] to exclude whitespace, query params (?), and fragments (#)\nQuery params and fragments are stripped before matching.\n*/\nconst GIT_URL_PATTERNS = [\n\t/// https://host/owner/repo.git or https://host/owner/repo\n\t// Case-insensitive protocol matching via /i flag\n\t{\n\t\tregex: /^https?:\\/\\/([^\\s/?#]+)\\/([^\\s/?#]+)\\/([^\\s/?#]+)(\\.git)?$/i,\n\t\ttransform: (host, owner, repo) => `https://${host}/${owner}/${repo}`,\n\t},\n\t/// git@host:owner/repo.git (common SSH format)\n\t// Using [^\\s:?#] and [^\\s/?#] creates clear boundaries\n\t{\n\t\tregex: /^git@([^\\s:?#]+):([^\\s/?#]+)\\/([^\\s/?#]+)(\\.git)?$/,\n\t\ttransform: (host, owner, repo) => `https://${host}/${owner}/${repo}`,\n\t},\n\t/// git+https://host/owner/repo.git\n\t{\n\t\tregex: /^git\\+https:\\/\\/([^\\s/?#]+)\\/([^\\s/?#]+)\\/([^\\s/?#]+)(\\.git)?$/i,\n\t\ttransform: (host, owner, repo) => `https://${host}/${owner}/${repo}`,\n\t},\n\t/// ssh://git@host/owner/repo.git\n\t{\n\t\tregex: /^ssh:\\/\\/git@([^\\s/?#]+)\\/([^\\s/?#]+)\\/([^\\s/?#]+)(\\.git)?$/i,\n\t\ttransform: (host, owner, repo) => `https://${host}/${owner}/${repo}`,\n\t},\n];\n\nconst ALPHANUMERIC_REGEX = /[a-z\\d]/i;\nconst isValidGitPathComponent = value => Boolean(value) && ALPHANUMERIC_REGEX.test(value);\n\n/**\nParse a git URL to extract the HTTPS browse URL.\n\nHandles various git URL formats including GitHub Enterprise.\n\nThis function uses carefully crafted regex patterns that avoid ReDoS vulnerabilities:\n- All patterns are anchored with ^ and $ to prevent partial matches\n- Character classes use negated sets [^...] which are linear-time\n- No nested quantifiers or overlapping alternatives\n- Greedy quantifiers with explicit bounds prevent exponential backtracking\n\n@param {string} url - The git URL to parse.\n@returns {string | undefined} - The HTTPS browse URL or undefined if parsing fails.\n\n@example\n```\nparseGitUrl('git@github.com:owner/repo.git');\n//=> 'https://github.com/owner/repo'\n\nparseGitUrl('https://github.com/owner/repo.git');\n//=> 'https://github.com/owner/repo'\n\nparseGitUrl('github:owner/repo');\n//=> undefined (use hosted-git-info for this)\n```\n*/\nexport const parseGitUrl = url => {\n\tif (typeof url !== 'string' || url.length === 0) {\n\t\treturn;\n\t}\n\n\tconst cleanUrl = url.split(/[?#]/, 1)[0];\n\tif (cleanUrl.length === 0) {\n\t\treturn;\n\t}\n\n\tfor (const {regex, transform} of GIT_URL_PATTERNS) {\n\t\tconst match = cleanUrl.match(regex);\n\t\tif (match) {\n\t\t\tconst [, host, owner, repo] = match;\n\n\t\t\t// Remove .git suffix if present in the captured repo name\n\t\t\tconst cleanRepo = repo.endsWith('.git') ? repo.slice(0, -4) : repo;\n\n\t\t\t// Validate that none of the components are empty\n\t\t\tif (!host) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Validate that owner and repo contain at least one alphanumeric character\n\t\t\t// This prevents pathological inputs like all dots or special chars\n\t\t\tif (!isValidGitPathComponent(owner) || !isValidGitPathComponent(cleanRepo)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\treturn transform(host, owner, cleanRepo);\n\t\t}\n\t}\n};\n\n/** @type {(config: import('./package-manager/types.js').PackageManagerConfig) => Promise<string>} */\nexport const getTagVersionPrefix = pMemoize(async config => {\n\tassert(config && Object.hasOwn(config, 'tagVersionPrefixCommand'), 'Config is missing key `tagVersionPrefixCommand`');\n\n\ttry {\n\t\tconst {stdout} = await execa(...config.tagVersionPrefixCommand);\n\n\t\treturn stdout;\n\t} catch {\n\t\treturn 'v';\n\t}\n});\n\nexport const joinList = list => chalk.reset(list.map(item => `- ${item}`).join('\\n'));\n\nexport const groupFilesInFolders = (files, groupingMinimumDepth = 1, groupingThresholdCount = 5) => {\n\tconst groups = {};\n\tfor (const file of files) {\n\t\tconst groupKey = path.join(...file.split(path.sep).slice(0, groupingMinimumDepth));\n\t\tgroups[groupKey] = [...groups[groupKey] ?? [], file];\n\t}\n\n\tconst lines = [];\n\tfor (const [folder, filesInFolder] of Object.entries(groups)) {\n\t\tif (filesInFolder.length > groupingThresholdCount) {\n\t\t\tlines.push(`- ${folder}/* ${chalk.bold.white(`(${filesInFolder.length} files)`)}`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (const file of filesInFolder) {\n\t\t\tlines.push(`- ${file}`);\n\t\t}\n\t}\n\n\treturn chalk.reset(lines.join('\\n'));\n};\n\nexport const getNewFiles = async rootDirectory => {\n\tconst listNewFiles = await git.newFilesSinceLastRelease(rootDirectory);\n\tconst listPackageFiles = await npm.getFilesToBePacked(rootDirectory);\n\n\treturn {\n\t\tunpublished: listNewFiles.filter(file => !listPackageFiles.includes(file) && !file.startsWith('.git')),\n\t\tfirstTime: listNewFiles.filter(file => listPackageFiles.includes(file)),\n\t};\n};\n\nexport const getNewDependencies = async (newPackage, rootDirectory) => {\n\tlet oldPackageFile;\n\n\ttry {\n\t\toldPackageFile = await git.readFileFromLastRelease(path.resolve(rootDirectory, 'package.json'));\n\t} catch {\n\t\t// Handle first time publish\n\t\treturn Object.keys(newPackage.dependencies ?? {});\n\t}\n\n\tconst oldPackage = parsePackage(oldPackageFile);\n\n\tconst newDependencies = [];\n\n\tfor (const dependency of Object.keys(newPackage.dependencies ?? {})) {\n\t\tif (!oldPackage.dependencies?.[dependency]) {\n\t\t\tnewDependencies.push(dependency);\n\t\t}\n\t}\n\n\treturn newDependencies;\n};\n\n/** @type {(config: import('./package-manager/types.js').PackageManagerConfig) => Promise<string>} */\nexport const getPreReleasePrefix = pMemoize(async config => {\n\tassert(config && Object.hasOwn(config, 'cli'), 'Config is missing key `cli`');\n\n\ttry {\n\t\tconst {stdout} = await execa(config.cli, ['config', 'get', 'preid']);\n\n\t\treturn stdout === 'undefined' ? '' : stdout;\n\t} catch {\n\t\treturn '';\n\t}\n});\n\nexport const validateEngineVersionSatisfies = (engine, version) => {\n\tconst engineRange = npPackage.engines[engine];\n\tif (!new Version(version).satisfies(engineRange)) {\n\t\tthrow new Error(`\\`np\\` requires ${engine} ${engineRange}`);\n\t}\n};\n\nexport async function getNpmPackageAccess(package_) {\n\tconst arguments_ = ['access', 'get', 'status', package_.name, '--json'];\n\n\tif (package_.publishConfig?.registry) {\n\t\targuments_.push('--registry', package_.publishConfig.registry);\n\t}\n\n\ttry {\n\t\tconst {stdout} = await execa('npm', arguments_, {timeout: npm.npmNetworkTimeout});\n\t\treturn JSON.parse(stdout)[package_.name]; // Note: returns \"private\" for non-existent packages\n\t} catch (error) {\n\t\tif (error.timedOut) {\n\t\t\terror.message = 'Connection to npm registry timed out';\n\t\t}\n\n\t\tthrow error;\n\t}\n}\n\nexport const getMinimumNodeVersion = range => {\n\tif (!range || typeof range !== 'string') {\n\t\treturn undefined;\n\t}\n\n\ttry {\n\t\tconst minVersion = semver.minVersion(range);\n\t\treturn minVersion?.version;\n\t} catch {\n\t\treturn undefined;\n\t}\n};\n"
  },
  {
    "path": "source/version.js",
    "content": "import semver from 'semver';\nimport {template as chalk} from 'chalk-template';\n\n/** @type {string[]} Allowed `SemVer` release types. */\nexport const SEMVER_INCREMENTS = ['patch', 'minor', 'major', 'prepatch', 'preminor', 'premajor', 'prerelease'];\nexport const SEMVER_INCREMENTS_LIST = SEMVER_INCREMENTS.join(', ');\nconst SEMVER_INCREMENTS_LIST_LAST_OR = `${SEMVER_INCREMENTS.slice(0, -1).join(', ')}, or ${SEMVER_INCREMENTS.slice(-1)}`;\n\n/** @typedef {semver.SemVer} SemVerInstance */\n/** @typedef {semver.ReleaseType} SemVerIncrement */\n/** @typedef {import('chalk').ColorName | import('chalk').ModifierName} ColorName */\n\n/** @param {string} input @returns {input is SemVerIncrement} */\nconst isSemVersionIncrement = input => SEMVER_INCREMENTS.includes(input);\n\n/** @param {string} input */\nconst isInvalidSemVersion = input => Boolean(!semver.valid(input));\n\n/**\nFormats the first difference between two versions to the given `diffColor`. Useful for `prerelease` diffs.\n\n@param {string[]} current @param {string[]} previous @param {ColorName} diffColor\n*/\nconst formatFirstDifference = (current, previous, diffColor) => {\n\tconst firstDifferenceIndex = current.findIndex((part, i) => previous.at(i) !== part);\n\tcurrent[firstDifferenceIndex] = `{${diffColor} ${current.at(firstDifferenceIndex)}}`;\n\treturn current.join('.');\n};\n\nexport default class Version {\n\t/** @type {SemVerInstance} */\n\t#version;\n\t/** @type {SemVerIncrement | undefined} */\n\t#diff = undefined;\n\t/** @type {string | undefined} */\n\t#prereleasePrefix = undefined;\n\n\ttoString() {\n\t\treturn this.#version.version;\n\t}\n\n\t/**\n\tSets `this.#version` to the given version.\n\n\t@param {string} version\n\t@throws If `version` is an invalid `SemVer` version.\n\t*/\n\t#trySetVersion(version) {\n\t\tthis.#version = semver.parse(version);\n\n\t\tif (this.#version === null) {\n\t\t\tthrow new Error(`Version ${version} should be a valid SemVer version.`);\n\t\t}\n\t}\n\n\t/**\n\t@param {string} version - A valid `SemVer` version.\n\t@param {SemVerIncrement} [increment] - Optionally increment `version`.\n\t@param {object} [options]\n\t@param {string} [options.prereleasePrefix] - A prefix to use for `prerelease` versions.\n\t*/\n\tconstructor(version, increment, {prereleasePrefix} = {}) {\n\t\tthis.#prereleasePrefix = prereleasePrefix;\n\t\tthis.#trySetVersion(version);\n\n\t\tif (increment) {\n\t\t\tif (!isSemVersionIncrement(increment)) {\n\t\t\t\tthrow new Error(`Increment ${increment} should be one of ${SEMVER_INCREMENTS_LIST_LAST_OR}.`);\n\t\t\t}\n\n\t\t\tthis.setFrom(increment);\n\t\t}\n\t}\n\n\t/**\n\tSets a new version based on `input`. If `input` is a valid `SemVer` increment, the current version will be incremented by that amount. If `input` is a valid `SemVer` version, the current version will be set to `input` if it is greater than the current version.\n\n\t@param {string | SemVerIncrement} input - A new valid `SemVer` version or a `SemVer` increment to increase the current version by.\n\t@param {object} [options]\n\t@param {string} [options.prereleasePrefix] - A prefix to use for `prerelease` versions.\n\t@throws If `input` is not a valid `SemVer` version or increment, or if `input` is a valid `SemVer` version but is not greater than the current version.\n\t*/\n\tsetFrom(input, {prereleasePrefix = ''} = {}) {\n\t\tthis.#prereleasePrefix ??= prereleasePrefix;\n\t\tconst previousVersion = this.toString();\n\n\t\tif (isSemVersionIncrement(input)) {\n\t\t\tthis.#version.inc(input, this.#prereleasePrefix);\n\t\t} else {\n\t\t\tif (isInvalidSemVersion(input)) {\n\t\t\t\tthrow new Error(`New version ${input} should either be one of ${SEMVER_INCREMENTS_LIST}, or a valid SemVer version.`);\n\t\t\t}\n\n\t\t\tif (this.#isGreaterThanOrEqualTo(input)) {\n\t\t\t\tthrow new Error(`New version ${input} should be higher than current version ${this.toString()}.`);\n\t\t\t}\n\n\t\t\tthis.#trySetVersion(input);\n\t\t}\n\n\t\t// Set `this.#diff` to format version diffs\n\t\tthis.#diff = semver.diff(previousVersion, this.#version);\n\t\treturn this;\n\t}\n\n\t/**\n\tFormats the current version with `options.color`, pretty-printing the version's diff with `options.diffColor` if possible.\n\n\tIf the current version has never been changed, providing `options.previousVersion` will allow pretty-printing the diff. It must be provided to format diffs between `prerelease` versions.\n\n\t@param {object} options\n\t@param {ColorName} [options.color = 'dim']\n\t@param {ColorName} [options.diffColor = 'cyan']\n\t@param {string} [options.prereleasePrefix]\n\t@returns {string} A color-formatted version string.\n\t*/\n\tformat({color = 'dim', diffColor = 'cyan', previousVersion} = {}) {\n\t\tif (typeof previousVersion === 'string') {\n\t\t\tconst previousSemver = semver.parse(previousVersion);\n\n\t\t\tif (previousSemver === null) {\n\t\t\t\tthrow new Error(`Previous version ${previousVersion} should be a valid SemVer version.`);\n\t\t\t}\n\n\t\t\tpreviousVersion = previousSemver;\n\t\t}\n\n\t\tif (!this.#diff) {\n\t\t\tif (!previousVersion) {\n\t\t\t\treturn chalk(`{${color} ${this.toString()}}`);\n\t\t\t}\n\n\t\t\tthis.#diff = semver.diff(previousVersion, this.#version);\n\t\t}\n\n\t\tconst {major, minor, patch, prerelease} = this.#version;\n\t\tconst previousPrerelease = semver.prerelease(previousVersion);\n\n\t\tif (prerelease && previousPrerelease) {\n\t\t\tconst prereleaseDiff = formatFirstDifference(prerelease, previousPrerelease, diffColor);\n\t\t\treturn chalk(`{${color} ${major}.${minor}.${patch}-${prereleaseDiff}}`);\n\t\t}\n\n\t\t/* eslint-disable unicorn/no-nested-ternary */\n\t\treturn (\n\t\t\tthis.#diff === 'major'\n\t\t\t\t? chalk(`{${color} {${diffColor} ${major}}.${minor}.${patch}}`)\n\t\t\t\t: this.#diff === 'minor'\n\t\t\t\t\t? chalk(`{${color} ${major}.{${diffColor} ${minor}}.${patch}}`)\n\t\t\t\t\t: this.#diff === 'patch'\n\t\t\t\t\t\t? chalk(`{${color} ${major}.${minor}.{${diffColor} ${patch}}}`)\n\t\t\t\t\t\t: this.#diff === 'premajor'\n\t\t\t\t\t\t\t? chalk(`{${color} {${diffColor} ${major}}.${minor}.${patch}-{${diffColor} ${prerelease.join('.')}}}`)\n\t\t\t\t\t\t\t: this.#diff === 'preminor'\n\t\t\t\t\t\t\t\t? chalk(`{${color} ${major}.{${diffColor} ${minor}}.${patch}-{${diffColor} ${prerelease.join('.')}}}`)\n\t\t\t\t\t\t\t\t: this.#diff === 'prepatch'\n\t\t\t\t\t\t\t\t\t? chalk(`{${color} ${major}.${minor}.{${diffColor} ${patch}}-{${diffColor} ${prerelease.join('.')}}}`)\n\t\t\t\t\t\t\t\t\t: this.#diff === 'prerelease' ? chalk(`{${color} ${major}.${minor}.${patch}-{${diffColor} ${prerelease.join('.')}}}`) : ''\n\t\t);\n\t\t/* eslint-enable unicorn/no-nested-ternary */\n\t}\n\n\t/**\n\tIf the current version satisifes the given `SemVer` range.\n\n\t@param {string} range\n\t@throws If `range` is invalid.\n\t*/\n\tsatisfies(range) {\n\t\tif (!semver.validRange(range)) {\n\t\t\tthrow new Error(`Range ${range} is not a valid SemVer range.`);\n\t\t}\n\n\t\treturn semver.satisfies(this.#version, range, {\n\t\t\tincludePrerelease: true,\n\t\t});\n\t}\n\n\t/**\n\tIf the current version has any `prerelease` components.\n\t*/\n\tisPrerelease() {\n\t\treturn Boolean(semver.prerelease(this.#version));\n\t}\n\n\t/**\n\tIf the current version is the same as or higher than the given version.\n\n\t@param {string} otherVersion\n\t*/\n\t#isGreaterThanOrEqualTo(otherVersion) {\n\t\treturn semver.gte(this.#version, otherVersion);\n\t}\n}\n"
  },
  {
    "path": "test/_helpers/integration-test.d.ts",
    "content": "import type {Macro, ExecutionContext} from 'ava';\nimport type {Execa$} from 'execa';\n\ntype Context = {\n\tfirstCommitMessage: string;\n\tgetCommitMessage: (sha: string) => Promise<string>;\n\tcreateFile: (file: string, content?: string) => Promise<void>;\n\tcommitNewFile: () => Promise<{\n\t\tsha: string;\n\t\tcommitMessage: string;\n\t}>;\n};\n\ntype CommandsFunctionParameters = [{\n\tt: ExecutionContext<Context>;\n\t$$: Execa$<string>;\n\ttemporaryDirectory: string;\n}];\n\ntype AssertionsFunctionParameters<MockType> = [{\n\tt: ExecutionContext<Context>;\n\ttestedModule: MockType;\n\t$$: Execa$<string>;\n\ttemporaryDirectory: string;\n}];\n\nexport type CreateFixtureMacro<MockType> = Macro<[\n\tcommands: (...arguments_: CommandsFunctionParameters) => Promise<void>,\n\tassertions: (...arguments_: AssertionsFunctionParameters<MockType>) => Promise<void>,\n], Context>;\n\nexport function _createFixture<MockType>(source: string): CreateFixtureMacro<MockType>;\n"
  },
  {
    "path": "test/_helpers/integration-test.js",
    "content": "/* eslint-disable ava/no-ignored-test-files */\nimport crypto from 'node:crypto';\nimport path from 'node:path';\nimport fs from 'fs-extra';\nimport test from 'ava';\nimport esmock from 'esmock';\nimport {$, execa} from 'execa';\nimport {temporaryDirectoryTask} from 'tempy';\n\nconst createEmptyGitRepo = async ($$, temporaryDirectory) => {\n\tconst firstCommitMessage = '\"init1\"';\n\n\tawait $$`git init`;\n\n\t// `git tag` needs an initial commit\n\tawait fs.createFile(path.resolve(temporaryDirectory, 'temp'));\n\tawait $$`git add temp`;\n\tawait $$`git commit -m ${firstCommitMessage}`;\n\tawait $$`git rm temp`;\n\tawait $$`git commit -m \"init2\"`;\n\n\treturn firstCommitMessage;\n};\n\nexport const createIntegrationTest = async (t, assertions) => {\n\tawait temporaryDirectoryTask(async temporaryDirectory => {\n\t\tconst $$ = $({cwd: temporaryDirectory});\n\n\t\tt.context.firstCommitMessage = await createEmptyGitRepo($$, temporaryDirectory);\n\n\t\t// From https://stackoverflow.com/a/3357357/10292952\n\t\tt.context.getCommitMessage = async sha => {\n\t\t\tconst {stdout: commitMessage} = await $$`git log --format=%B -n 1 ${sha}`;\n\t\t\treturn commitMessage.trim();\n\t\t};\n\n\t\tt.context.createFile = async (file, content = '') => fs.outputFile(path.resolve(temporaryDirectory, file), content);\n\n\t\tt.context.commitNewFile = async () => {\n\t\t\tawait t.context.createFile(`new-${crypto.randomUUID()}`);\n\t\t\tawait $$`git add .`;\n\t\t\tawait $$`git commit -m \"added\"`;\n\n\t\t\tconst {stdout: lastCommitSha} = await $$`git rev-parse --short HEAD`;\n\n\t\t\treturn {\n\t\t\t\tsha: lastCommitSha,\n\t\t\t\tcommitMessage: await t.context.getCommitMessage(lastCommitSha),\n\t\t\t};\n\t\t};\n\n\t\tawait assertions({$$, temporaryDirectory});\n\t});\n};\n\nexport const _createFixture = source => test.macro(async (t, commands, assertions) => {\n\tawait createIntegrationTest(t, async ({$$, temporaryDirectory}) => {\n\t\tconst testedModule = await esmock(source, {}, {\n\t\t\t'node:process': {cwd: () => temporaryDirectory},\n\t\t\texeca: {execa: async (command, commandArguments, options = {}) => execa(command, commandArguments, {...options, cwd: temporaryDirectory})},\n\t\t});\n\n\t\tawait commands({t, $$, temporaryDirectory});\n\t\tawait assertions({\n\t\t\tt, testedModule, $$, temporaryDirectory,\n\t\t});\n\t});\n});\n"
  },
  {
    "path": "test/_helpers/listr-renderer.js",
    "content": "let tasks;\n\nexport class SilentRenderer {\n\tconstructor(_tasks) {\n\t\ttasks = _tasks;\n\t}\n\n\tstatic get tasks() {\n\t\treturn tasks;\n\t}\n\n\tstatic get nonTTY() {\n\t\treturn true;\n\t}\n\n\tstatic clearTasks() {\n\t\ttasks = [];\n\t}\n\n\trender() {}\n\n\tend() {}\n}\n"
  },
  {
    "path": "test/_helpers/listr.js",
    "content": "import {SilentRenderer} from './listr-renderer.js';\n\nexport const run = async listr => {\n\tlistr.setRenderer(SilentRenderer);\n\tawait listr.run();\n};\n\nexport const assertTaskFailed = (t, taskTitle) => {\n\tconst task = SilentRenderer.tasks.find(task => task.title === taskTitle);\n\tt.true(task.hasFailed(), `Task '${taskTitle}' did not fail!`);\n};\n\nexport const assertTaskDisabled = (t, taskTitle) => {\n\tconst task = SilentRenderer.tasks.find(task => task.title === taskTitle);\n\tt.true(!task.isEnabled(), `Task '${taskTitle}' was enabled!`);\n};\n\nexport const assertTaskDoesntExist = (t, taskTitle) => {\n\tt.true(SilentRenderer.tasks.every(task => task.title !== taskTitle), `Task '${taskTitle}' exists!`);\n};\n\nexport const assertTaskSkipped = (t, taskTitle) => {\n\tconst task = SilentRenderer.tasks.find(task => task.title === taskTitle);\n\tt.true(task.isSkipped(), `Task '${taskTitle}' was not skipped!`);\n};\n"
  },
  {
    "path": "test/_helpers/mock-inquirer.js",
    "content": "import esmock from 'esmock';\nimport is from '@sindresorhus/is';\nimport stripAnsi from 'strip-ansi';\nimport mapObject from 'map-obj';\n\n/** @typedef {import('ava').ExecutionContext<Record<string, never>>} ExecutionContext */\n/** @typedef {string | boolean} ShortAnswer */\n/** @typedef {Record<'input' | 'error', string> | Record<'choice', string> | Record<'confirm', boolean>} LongAnswer */\n/** @typedef {ShortAnswer | LongAnswer} Answer */\n/** @typedef {Record<string, Answer>} Answers  */\n/** @typedef {import('inquirer').DistinctQuestion & {name?: never}} Prompt */\n\n/**\nMocks `inquirer.prompt` and answers each prompt in the program with the provided `inputAnswers`.\n\nThis only handles prompts of type `input`, `list`, and `confirm`. If other prompt types are added, they must be implemented here.\n\nLogs for debugging are outputted on test failure.\n\n@see https://gist.github.com/yyx990803/f61f347b6892078c40a9e8e77b9bd984\n\n@param {object} o Test input and actual prompts\n@param {ExecutionContext} o.t\n@param {Answers} o.inputAnswers Test input\n@param {Record<string, Prompt> | Prompt[]} o.prompts Actual prompts\n*/\nconst mockPrompt = async ({t, inputAnswers, prompts}) => {\n\tconst answers = {};\n\n\t// Ensure `prompts` is an object\n\tif (Array.isArray(prompts)) {\n\t\tconst promptsObject = {};\n\n\t\tfor (const prompt of prompts) {\n\t\t\tpromptsObject[prompt.name] = prompt;\n\t\t}\n\n\t\tprompts = promptsObject;\n\t}\n\n\tt.log('prompts:', Object.keys(prompts));\n\n\t/* eslint-disable no-await-in-loop */\n\tfor (const [name, prompt] of Object.entries(prompts)) {\n\t\tif (prompt.when !== undefined) {\n\t\t\tif (is.boolean(prompt.when) && !prompt.when) {\n\t\t\t\tt.log(`skipping prompt '${name}'`);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (is.function(prompt.when) && !prompt.when(answers)) {\n\t\t\t\tt.log(`skipping prompt '${name}'`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tt.log(`getting input for prompt '${name}'`);\n\n\t\tconst setValue = value => {\n\t\t\tif (prompt.validate) {\n\t\t\t\tconst result = prompt.validate(value);\n\n\t\t\t\tif (result !== true) {\n\t\t\t\t\tif (is.string(result)) {\n\t\t\t\t\t\tthrow new Error(result);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (result === false) {\n\t\t\t\t\t\tthrow new Error('You must provide a valid value');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (is.string(value)) {\n\t\t\t\tt.log(`filtering value '${value}' for prompt '${name}'`);\n\t\t\t} else {\n\t\t\t\tt.log(`filtering value for prompt '${name}':`, value);\n\t\t\t}\n\n\t\t\tanswers[name] = prompt.filter\n\t\t\t\t? prompt.filter(value) // eslint-disable-line unicorn/no-array-callback-reference\n\t\t\t\t: value;\n\n\t\t\tt.log(`got value '${answers[name]}' for prompt '${name}'`);\n\t\t};\n\n\t\t/** @param {Answer} input */\n\t\tconst chooseValue = async input => {\n\t\t\tt.is(prompt.type, 'select');\n\t\t\tlet choices;\n\n\t\t\tif (is.asyncFunction(prompt.choices)) {\n\t\t\t\tchoices = await prompt.choices(answers);\n\t\t\t} else if (is.function(prompt.choices)) {\n\t\t\t\tchoices = prompt.choices(answers);\n\t\t\t} else {\n\t\t\t\tchoices = prompt.choices;\n\t\t\t}\n\n\t\t\tt.log(`choices for prompt '${name}':`, choices);\n\n\t\t\tconst value = choices.find(choice => {\n\t\t\t\tif (is.object(choice)) {\n\t\t\t\t\treturn choice.name && stripAnsi(choice.name).startsWith(input.choice ?? input);\n\t\t\t\t}\n\n\t\t\t\tif (is.string(choice)) {\n\t\t\t\t\treturn stripAnsi(choice).startsWith(input.choice ?? input);\n\t\t\t\t}\n\n\t\t\t\treturn false;\n\t\t\t});\n\n\t\t\t// `value.value` could exist but literally be `undefined`\n\t\t\tsetValue(Object.hasOwn(value, 'value') ? value.value : value);\n\t\t};\n\n\t\tconst input = inputAnswers[name];\n\n\t\tif (is.undefined(input)) {\n\t\t\tt.fail(`Expected input for prompt '${name}'.`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (is.string(input)) {\n\t\t\tt.log(`found input for prompt '${name}': '${input}'`);\n\t\t} else {\n\t\t\tt.log(`found input for prompt '${name}':`, input);\n\t\t}\n\n\t\t/** @param {Answer} input */\n\t\tconst handleInput = async input => {\n\t\t\tif (is.string(input)) {\n\t\t\t\tif (['input'].includes(prompt.type)) {\n\t\t\t\t\tsetValue(input);\n\t\t\t\t} else if (['select'].includes(prompt.type)) {\n\t\t\t\t\treturn chooseValue(input);\n\t\t\t\t} else {\n\t\t\t\t\tt.fail('Incorrect input type');\n\t\t\t\t}\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (input.input !== undefined) {\n\t\t\t\tt.is(prompt.type, 'input');\n\t\t\t\tsetValue(input.input);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (input.choice !== undefined) {\n\t\t\t\tawait chooseValue(input);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (is.boolean(input.confirm) || is.boolean(input)) {\n\t\t\t\tt.is(prompt.type, 'confirm');\n\t\t\t\tsetValue(input.confirm ?? input);\n\t\t\t}\n\t\t};\n\n\t\t// Multiple inputs for the given prompt\n\t\tif (is.array(input)) {\n\t\t\tfor (const attempt of input) {\n\t\t\t\tif (attempt.error) {\n\t\t\t\t\tawait t.throwsAsync(\n\t\t\t\t\t\thandleInput(attempt),\n\t\t\t\t\t\t{message: attempt.error},\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tawait handleInput(attempt);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tawait handleInput(input);\n\t}\n\t/* eslint-enable no-await-in-loop */\n\n\treturn answers;\n};\n\n/**\nFixes relative module paths for use with `esmock`. Allows specifiying the same relative location in test files as in source files.\n@param {import('esmock').MockMap} mocks\n*/\nconst fixRelativeMocks = mocks => mapObject(mocks, (key, value) => [key.replace('./', '../../source/'), value]);\n\n/**\nMocks `inquirer` for testing `source/ui.js`.\n\n@param {object} o Test input and optional global mocks\n@param {ExecutionContext} o.t\n@param {Answers} o.answers Test input\n@param {import('esmock').MockMap} [o.mocks] Optional global mocks\n*/\nexport const mockInquirer = async ({t, answers, mocks = {}}) => {\n\t/** @type {string[]} */\n\tconst logs = [];\n\n\t/** @type {import('../../source/ui.js')} */\n\tconst ui = await esmock('../../source/ui.js', import.meta.url, {\n\t\tinquirer: {\n\t\t\tasync prompt(prompts) {\n\t\t\t\tlet uiAnswers = {};\n\n\t\t\t\tconst assertions = await t.try(async tt => {\n\t\t\t\t\tuiAnswers = await mockPrompt({t: tt, inputAnswers: answers, prompts});\n\t\t\t\t});\n\n\t\t\t\tassertions.commit({retainLogs: !assertions.passed});\n\t\t\t\treturn uiAnswers;\n\t\t\t},\n\t\t},\n\t}, {\n\t\t...fixRelativeMocks(mocks),\n\t\timport: {\n\t\t\tconsole: {log: (...arguments_) => logs.push(...arguments_)},\n\t\t},\n\t});\n\n\treturn {ui, logs};\n};\n"
  },
  {
    "path": "test/_helpers/stub-execa.d.ts",
    "content": "import type {Macro, ExecutionContext} from 'ava';\nimport type {ExecaReturnValue} from 'execa';\n\ntype AssertionsFunctionParameters<MockType> = [{\n\tt: ExecutionContext;\n\ttestedModule: MockType;\n}];\n\nexport type CreateFixtureMacro<MockType> = Macro<[\n\tcommands: ExecaReturnValue[],\n\tassertions: (...arguments_: AssertionsFunctionParameters<MockType>) => Promise<void>,\n]>;\n\nexport function _createFixture<MockType>(source: string, importMeta: string): CreateFixtureMacro<MockType>;\n"
  },
  {
    "path": "test/_helpers/stub-execa.js",
    "content": "/* eslint-disable ava/no-ignored-test-files */\nimport test from 'ava';\nimport esmock from 'esmock';\nimport sinon from 'sinon';\nimport {execa} from 'execa';\n\n// Default stubs for common commands that should pass by default\nconst defaultCommands = [\n\t{command: 'npm --version', stdout: '10.0.0'},\n\t{command: 'npm ping', stdout: ''},\n\t{command: 'npm view --json test engines', stdout: ''},\n\t{command: 'git version', stdout: 'git version 2.40.0'},\n\t{command: 'git ls-remote origin HEAD', stdout: 'abc123\\tHEAD'},\n\t{command: 'git fetch', stdout: ''},\n\t{command: 'git config --get tag.gpgSign', stdout: ''},\n];\n\n/**\nStubs `execa` to return a specific result when called with the given commands.\n\nA command passes if its exit code is 0, or if there's no exit code and no stderr.\n\nResolves or throws the given result.\n\n@param {import('execa').ExecaReturnValue[]} commands\n*/\nconst makeExecaStub = commands => {\n\tconst normalizedCommands = [...defaultCommands, ...commands].map(result => {\n\t\tconst [command, ...commandArguments] = result.command.split(' ');\n\t\treturn {\n\t\t\t...result,\n\t\t\tcommand,\n\t\t\tcommandArguments,\n\t\t};\n\t});\n\n\treturn sinon.stub().callsFake((command, commandArguments = [], options) => {\n\t\tfor (let index = normalizedCommands.length - 1; index >= 0; index--) {\n\t\t\tconst result = normalizedCommands[index];\n\n\t\t\tif (result.command !== command) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!areArgumentsEqual(result.commandArguments, commandArguments)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!matchesOptions(result.options, options)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst passes = result.exitCode === 0 || (!result.exitCode && !result.stderr);\n\n\t\t\tif (passes) {\n\t\t\t\treturn Promise.resolve(result);\n\t\t\t}\n\n\t\t\treturn Promise.reject(Object.assign(new Error(), result)); // eslint-disable-line unicorn/error-message\n\t\t}\n\t});\n};\n\nconst areArgumentsEqual = (left, right) => left.length === right.length && left.every((value, index) => value === right[index]);\n\nconst matchesOptions = (expectedOptions, actualOptions) => {\n\tif (!expectedOptions) {\n\t\treturn true;\n\t}\n\n\tif (!actualOptions) {\n\t\treturn false;\n\t}\n\n\treturn Object.entries(expectedOptions).every(([key, value]) => Object.is(actualOptions[key], value));\n};\n\nconst stubExeca = commands => {\n\tconst execaStub = makeExecaStub(commands);\n\n\treturn {\n\t\texeca: {\n\t\t\tasync execa(...arguments_) {\n\t\t\t\t// Only call real execa if stub doesn't have a match\n\t\t\t\tconst result = execaStub(...arguments_);\n\t\t\t\tif (result === undefined) {\n\t\t\t\t\treturn execa(...arguments_);\n\t\t\t\t}\n\n\t\t\t\treturn result;\n\t\t\t},\n\t\t},\n\t};\n};\n\nexport const _createFixture = (source, importMeta) => test.macro(async (t, commands, assertions) => {\n\tconst testedModule = await esmock(source, importMeta, {}, stubExeca(commands));\n\tawait assertions({t, testedModule});\n});\n"
  },
  {
    "path": "test/_helpers/util.js",
    "content": "import {fileURLToPath} from 'node:url';\nimport path from 'node:path';\n\nexport const runIfExists = async (function_, ...arguments_) => {\n\tif (typeof function_ === 'function') {\n\t\tawait function_(...arguments_);\n\t}\n};\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nexport const getFixture = fixture => path.resolve(__dirname, '..', 'fixtures', ...fixture.split('/'));\n"
  },
  {
    "path": "test/_helpers/verify-cli.d.ts",
    "content": "import type {Macro, ExecutionContext} from 'ava';\n\ntype VerifyCliMacro = Macro<[\n\tbinPath: string,\n\targs: string | string[],\n\texpectedLines: string[],\n], Record<string, never>>;\n\nexport const cliPasses: VerifyCliMacro;\nexport const cliFails: VerifyCliMacro;\n"
  },
  {
    "path": "test/_helpers/verify-cli.js",
    "content": "/* eslint-disable ava/no-ignored-test-files */\nimport test from 'ava';\nimport {execa} from 'execa';\n\nconst trim = stdout => stdout.split('\\n').map(line => line.trim());\n\nconst _verifyCli = shouldPass => test.macro(async (t, binaryPath, arguments_, expectedLines) => {\n\tconst {exitCode, stdout} = await execa(binaryPath, [arguments_].flat(), {reject: false});\n\tconst receivedLines = trim(stdout);\n\n\tt.deepEqual(receivedLines, expectedLines, 'CLI output different than expectations!');\n\tt.is(exitCode, shouldPass ? 0 : 1, 'CLI exited with the wrong exit code!');\n});\n\nexport const cliPasses = _verifyCli(true);\nexport const cliFails = _verifyCli(false);\n"
  },
  {
    "path": "test/cli.js",
    "content": "import path from 'node:path';\nimport test from 'ava';\nimport {npPackage, npRootDirectory as rootDirectory} from '../source/util.js';\nimport {cliPasses} from './_helpers/verify-cli.js';\n\nconst cli = path.resolve(rootDirectory, 'source', 'cli-implementation.js');\n\ntest('flags: --help', cliPasses, cli, '--help', [\n\t'',\n\t'A better `npm publish`',\n\t'',\n\t'Usage',\n\t'$ np <version>',\n\t'',\n\t'Version can be:',\n\t'patch | minor | major | prepatch | preminor | premajor | prerelease | 1.2.3',\n\t'',\n\t'Options',\n\t'--any-branch           Allow publishing from any branch',\n\t'--branch               Name of the release branch (default: main | master)',\n\t'--no-cleanup           Skips cleanup of node_modules',\n\t'--no-tests             Skips tests',\n\t'--yolo                 Skips cleanup and testing',\n\t'--no-publish           Skips publishing',\n\t'--preview              Show tasks without actually executing them',\n\t'--tag                  Publish under a given dist-tag',\n\t'--contents             Subdirectory to publish',\n\t'--no-release-draft     Skips opening a GitHub release draft',\n\t'--release-draft-only   Only opens a GitHub release draft for the latest published version',\n\t'--no-release-notes     Skips generating release notes when opening a GitHub release draft',\n\t'--test-script          Name of npm run script to run tests before publishing (default: test)',\n\t'--no-2fa               Don\\'t enable 2FA on new packages (not recommended)',\n\t'--message              Version bump commit message, \\'%s\\' will be replaced with version (default: \\'%s\\' with npm and \\'v%s\\' with yarn)',\n\t'--package-manager      Use a specific package manager (default: \\'packageManager\\' field in package.json)',\n\t'--provenance           Publish with npm provenance statements (CI-only)',\n\t'--remote               Git remote to push to (default: origin)',\n\t'',\n\t'Examples',\n\t'$ np',\n\t'$ np patch',\n\t'$ np 1.0.2',\n\t'$ np 1.0.2-beta.3 --tag=beta',\n\t'$ np 1.0.2-beta.3 --tag=beta --contents=dist',\n\t'',\n]);\n\ntest('flags: --version', cliPasses, cli, '--version', [npPackage.version]);\n"
  },
  {
    "path": "test/config.js",
    "content": "import path from 'node:path';\nimport test from 'ava';\nimport esmock from 'esmock';\nimport {readPackage} from '../source/util.js';\n\nconst testedModulePath = '../source/config.js';\n\nconst getFixture = fixture => path.resolve('test', 'fixtures', 'config', fixture);\n\nconst getConfigWhenGlobalBinaryIsUsed = async pathPackageDirectory => {\n\tconst getConfig = await esmock(testedModulePath, {\n\t\t'is-installed-globally': true,\n\t});\n\treturn getConfig(pathPackageDirectory);\n};\n\nconst getConfigWhenLocalBinaryIsUsed = async pathPackageDirectory => {\n\tconst getConfig = await esmock(testedModulePath, {\n\t\t'is-installed-globally': false,\n\t});\n\treturn getConfig(pathPackageDirectory);\n};\n\nconst useGlobalBinary = test.macro(async (t, packageDirectory, source) => {\n\tconst config = await getConfigWhenGlobalBinaryIsUsed(getFixture(packageDirectory));\n\tt.deepEqual(config, {source});\n});\n\nconst useLocalBinary = test.macro(async (t, packageDirectory, source) => {\n\tconst config = await getConfigWhenLocalBinaryIsUsed(getFixture(packageDirectory));\n\tt.deepEqual(config, {source});\n});\n\ntest(\n\t'returns config from package directory when global binary is used and `package.json` exists in package directory',\n\tuseGlobalBinary,\n\t'pkg-dir',\n\t'package.json',\n);\n\ntest(\n\t'returns config from package directory when global binary is used and `.np-config.json` exists in package directory',\n\tuseGlobalBinary,\n\t'local1',\n\t'packagedir/.np-config.json',\n);\n\ntest(\n\t'returns config from package directory when global binary is used and `.np-config.js` as CJS exists in package directory',\n\tuseGlobalBinary,\n\t'local2',\n\t'packagedir/.np-config.js',\n);\n\ntest(\n\t'returns config from package directory when global binary is used and `.np-config.cjs` exists in package directory',\n\tuseGlobalBinary,\n\t'local3',\n\t'packagedir/.np-config.cjs',\n);\n\ntest(\n\t'returns config from package directory when global binary is used and `.np-config.js` as ESM exists in package directory',\n\tuseGlobalBinary,\n\t'local4',\n\t'packagedir/.np-config.js',\n);\n\ntest(\n\t'returns config from package directory when global binary is used and `.np-config.mjs` exists in package directory',\n\tuseGlobalBinary,\n\t'local5',\n\t'packagedir/.np-config.mjs',\n);\n\ntest('global binary merges global and project config with project taking precedence', async t => {\n\tconst fixtureDirectory = getFixture('pkg-dir');\n\n\t// Create a temporary home directory with global config\n\tconst temporaryHome = getFixture('homedir1');\n\n\tconst getConfig = await esmock(testedModulePath, {\n\t\t'is-installed-globally': true,\n\t\t'node:os': {homedir: () => temporaryHome},\n\t});\n\n\tconst config = await getConfig(fixtureDirectory);\n\n\t// Should have project config\n\tt.is(config.source, 'package.json');\n});\n\ntest(\n\t'returns config from package directory when local binary is used and `package.json` exists in package directory',\n\tuseLocalBinary,\n\t'pkg-dir',\n\t'package.json',\n);\n\ntest(\n\t'returns config from package directory when local binary is used and `.np-config.json` exists in package directory',\n\tuseLocalBinary,\n\t'local1',\n\t'packagedir/.np-config.json',\n);\n\ntest(\n\t'returns config from package directory when local binary is used and `.np-config.js` as CJS exists in package directory',\n\tuseLocalBinary,\n\t'local2',\n\t'packagedir/.np-config.js',\n);\n\ntest(\n\t'returns config from package directory when local binary is used and `.np-config.cjs` exists in package directory',\n\tuseLocalBinary,\n\t'local3',\n\t'packagedir/.np-config.cjs',\n);\n\ntest(\n\t'returns config from package directory when local binary is used and `.np-config.js` as ESM exists in package directory',\n\tuseLocalBinary,\n\t'local4',\n\t'packagedir/.np-config.js',\n);\n\ntest(\n\t'returns config from package directory when local binary is used and `.np-config.mjs` exists in package directory',\n\tuseLocalBinary,\n\t'local5',\n\t'packagedir/.np-config.mjs',\n);\n\ntest('`contents` option in config allows reading package from subdirectory', async t => {\n\tconst fixtureDirectory = getFixture('contents-option');\n\n\t// Load config from fixture directory (simulates loading from process.cwd())\n\tconst getConfig = await esmock(testedModulePath, {\n\t\t'is-installed-globally': false,\n\t});\n\n\tconst config = await getConfig(fixtureDirectory);\n\n\t// Config should have contents option\n\tt.is(config.contents, 'dist');\n\n\t// Using contents from config should read package from subdirectory\n\tconst contentsPath = path.join(fixtureDirectory, config.contents);\n\tconst {package_, rootDirectory} = await readPackage(contentsPath);\n\n\tt.is(package_.name, 'from-dist');\n\tt.is(rootDirectory, contentsPath);\n});\n\ntest('config values override defaults', async t => {\n\tconst fixtureDirectory = getFixture('flag-precedence');\n\n\tconst getConfig = await esmock(testedModulePath, {\n\t\t'is-installed-globally': false,\n\t});\n\n\tconst config = await getConfig(fixtureDirectory);\n\n\t// Config should override default values\n\tt.is(config.tests, false);\n\tt.is(config.cleanup, false);\n\tt.is(config.publish, false);\n});\n"
  },
  {
    "path": "test/fixtures/config/contents-option/.np-config.json",
    "content": "{\n\t\"contents\": \"dist\"\n}\n"
  },
  {
    "path": "test/fixtures/config/contents-option/dist/package.json",
    "content": "{\n\t\"name\": \"from-dist\",\n\t\"version\": \"1.0.0\"\n}\n"
  },
  {
    "path": "test/fixtures/config/flag-precedence/.np-config.json",
    "content": "{\n\t\"tests\": false,\n\t\"cleanup\": false,\n\t\"publish\": false\n}\n"
  },
  {
    "path": "test/fixtures/config/flag-precedence/package.json",
    "content": "{\n\t\"name\": \"test-flag-precedence\",\n\t\"version\": \"1.0.0\"\n}\n"
  },
  {
    "path": "test/fixtures/config/homedir1/.np-config.json",
    "content": "{\n\t\"source\": \"homedir/.np-config.json\"\n}\n"
  },
  {
    "path": "test/fixtures/config/homedir2/.np-config.js",
    "content": "module.exports = {\n\tsource: 'homedir/.np-config.js'\n};\n"
  },
  {
    "path": "test/fixtures/config/homedir3/.np-config.cjs",
    "content": "module.exports = {\n\tsource: 'homedir/.np-config.cjs'\n};\n"
  },
  {
    "path": "test/fixtures/config/homedir4/.np-config.js",
    "content": "export default {\n\tsource: 'homedir/.np-config.js'\n};\n"
  },
  {
    "path": "test/fixtures/config/homedir5/.np-config.mjs",
    "content": "export default {\n\tsource: 'homedir/.np-config.mjs'\n};\n"
  },
  {
    "path": "test/fixtures/config/local1/.np-config.json",
    "content": "{\n\t\"source\": \"packagedir/.np-config.json\"\n}\n"
  },
  {
    "path": "test/fixtures/config/local2/.np-config.js",
    "content": "module.exports = {\n\tsource: 'packagedir/.np-config.js'\n};\n"
  },
  {
    "path": "test/fixtures/config/local3/.np-config.cjs",
    "content": "module.exports = {\n\tsource: 'packagedir/.np-config.cjs'\n};\n"
  },
  {
    "path": "test/fixtures/config/local4/.np-config.js",
    "content": "export default {\n\tsource: 'packagedir/.np-config.js'\n};\n"
  },
  {
    "path": "test/fixtures/config/local4/package.json",
    "content": "{\n\t\"name\": \"use-type-module-for-config-fixtures\",\n\t\"type\": \"module\"\n}\n"
  },
  {
    "path": "test/fixtures/config/local5/.np-config.mjs",
    "content": "export default {\n\tsource: 'packagedir/.np-config.mjs'\n};\n"
  },
  {
    "path": "test/fixtures/config/package.json",
    "content": "{\n\t\"name\": \"override-type-module-for-config-fixtures\"\n}\n"
  },
  {
    "path": "test/fixtures/config/pkg-dir/package.json",
    "content": "{\n\t\"name\": \"test-fixtures\",\n\t\"np\": {\n\t\t\"source\": \"package.json\"\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/files/dot-github/.github/pull_request_template.md",
    "content": "<!--\n\nThanks for submitting a pull request 🙌\n\n**Note:** Please don't create a pull request which has significant changes (i.e. adds new functionality or modifies existing one in a non-trivial way) without creating an issue first.\n\nTry to limit the scope of your pull request and provide a general description of the changes. If this fixes an open issue, link to it in the following way: `Fixes #321`. New features and bug fixes should come with tests.\n\n-->\n"
  },
  {
    "path": "test/fixtures/files/dot-github/index.js",
    "content": "console.log('foo');\n"
  },
  {
    "path": "test/fixtures/files/dot-github/package.json",
    "content": "{\n\t\"name\": \"foo\",\n\t\"version\": \"0.0.0\",\n\t\"files\": [\"index.js\"]\n}\n"
  },
  {
    "path": "test/fixtures/files/files-and-npmignore/package.json",
    "content": "{\n\t\"name\": \"foo\",\n\t\"version\": \"0.0.0\",\n\t\"files\": [\"source\"]\n}\n"
  },
  {
    "path": "test/fixtures/files/files-and-npmignore/readme.md",
    "content": "# Foo\n"
  },
  {
    "path": "test/fixtures/files/files-and-npmignore/source/.npmignore",
    "content": "index.test-d.ts\n"
  },
  {
    "path": "test/fixtures/files/files-and-npmignore/source/bar.js",
    "content": "console.log('bar');\n"
  },
  {
    "path": "test/fixtures/files/files-and-npmignore/source/foo.js",
    "content": "console.log('foo');\n"
  },
  {
    "path": "test/fixtures/files/files-and-npmignore/source/index.d.ts",
    "content": "export function foo(): string;\nexport function bar(): string;\n"
  },
  {
    "path": "test/fixtures/files/files-and-npmignore/source/index.test-d.ts",
    "content": "import {expectType} from 'tsd';\nimport {foo, bar} from './index.js';\n\nexpectType<string>(foo());\nexpectType<string>(bar());\n"
  },
  {
    "path": "test/fixtures/files/files-slash/index.js",
    "content": "console.log('foo');\n"
  },
  {
    "path": "test/fixtures/files/files-slash/package.json",
    "content": "{\n\t\"name\": \"foo\",\n\t\"version\": \"0.0.0\",\n\t\"files\": [\"/index.js\"]\n}\n"
  },
  {
    "path": "test/fixtures/files/gitignore/.gitignore",
    "content": "dist\n"
  },
  {
    "path": "test/fixtures/files/gitignore/index.d.ts",
    "content": "export default function foo(): string;\n"
  },
  {
    "path": "test/fixtures/files/gitignore/index.js",
    "content": "export default function foo() {\n\treturn 'bar';\n}\n"
  },
  {
    "path": "test/fixtures/files/gitignore/index.test-d.ts",
    "content": "import {expectType} from 'tsd';\nimport foo from './index.js';\n\nexpectType<string>(foo());\n"
  },
  {
    "path": "test/fixtures/files/gitignore/package.json",
    "content": "{\n\t\"name\": \"foo\",\n\t\"version\": \"0.0.0\",\n\t\"files\": [\"dist\"]\n}\n"
  },
  {
    "path": "test/fixtures/files/gitignore/readme.md",
    "content": "# Foo\n"
  },
  {
    "path": "test/fixtures/files/has-readme-and-license/index.js",
    "content": "console.log('foo');\n"
  },
  {
    "path": "test/fixtures/files/has-readme-and-license/license.md",
    "content": "MIT\n"
  },
  {
    "path": "test/fixtures/files/has-readme-and-license/package.json",
    "content": "{\n\t\"name\": \"foo\",\n\t\"version\": \"0.0.0\",\n\t\"files\": [\"index.js\"]\n}\n"
  },
  {
    "path": "test/fixtures/files/has-readme-and-license/readme.md",
    "content": "# Foo\n"
  },
  {
    "path": "test/fixtures/files/main/bar.js",
    "content": "console.log('bar');\n"
  },
  {
    "path": "test/fixtures/files/main/foo.js",
    "content": "console.log('foo');\n"
  },
  {
    "path": "test/fixtures/files/main/package.json",
    "content": "{\n\t\"name\": \"foo\",\n\t\"version\": \"0.0.0\",\n\t\"main\": \"foo.js\",\n\t\"files\": [\"bar.js\"]\n}\n"
  },
  {
    "path": "test/fixtures/files/missing-bin/index.js",
    "content": "export default 'foo';\n"
  },
  {
    "path": "test/fixtures/files/missing-bin/package.json",
    "content": "{\n\t\"name\": \"missing-bin\",\n\t\"version\": \"0.0.0\",\n\t\"bin\": \"./cli.js\",\n\t\"files\": [\"index.js\"]\n}\n"
  },
  {
    "path": "test/fixtures/files/missing-main/package.json",
    "content": "{\n\t\"name\": \"missing-main\",\n\t\"version\": \"0.0.0\",\n\t\"main\": \"dist/index.js\",\n\t\"files\": [\"source\"]\n}\n"
  },
  {
    "path": "test/fixtures/files/missing-main/source/index.js",
    "content": "export default 'foo';\n"
  },
  {
    "path": "test/fixtures/files/npmignore/.npmignore",
    "content": "index.test-d.ts\n"
  },
  {
    "path": "test/fixtures/files/npmignore/index.d.ts",
    "content": "export default function foo(): string;\n"
  },
  {
    "path": "test/fixtures/files/npmignore/index.js",
    "content": "export default function foo() {\n\treturn 'bar';\n}\n"
  },
  {
    "path": "test/fixtures/files/npmignore/index.test-d.ts",
    "content": "import {expectType} from 'tsd';\nimport foo from './index.js';\n\nexpectType<string>(foo());\n"
  },
  {
    "path": "test/fixtures/files/npmignore/package.json",
    "content": "{\n\t\"name\": \"foo\",\n\t\"version\": \"0.0.0\"\n}\n"
  },
  {
    "path": "test/fixtures/files/npmignore/readme.md",
    "content": "# Foo\n"
  },
  {
    "path": "test/fixtures/files/npmignore-and-gitignore/.gitignore",
    "content": "dist\n"
  },
  {
    "path": "test/fixtures/files/npmignore-and-gitignore/.npmignore",
    "content": "script/\nsource/\n"
  },
  {
    "path": "test/fixtures/files/npmignore-and-gitignore/package.json",
    "content": "{\n\t\"name\": \"foo\",\n\t\"version\": \"0.0.0\"\n}\n"
  },
  {
    "path": "test/fixtures/files/npmignore-and-gitignore/readme.md",
    "content": "# Foo\n"
  },
  {
    "path": "test/fixtures/files/npmignore-and-gitignore/script/build.js",
    "content": "// ... yada yada yada\n"
  },
  {
    "path": "test/fixtures/files/npmignore-and-gitignore/source/index.ts",
    "content": "export default function foo() {\n\treturn 'bar';\n}\n"
  },
  {
    "path": "test/fixtures/files/one-file/index.js",
    "content": "console.log('foo');\n"
  },
  {
    "path": "test/fixtures/files/one-file/package.json",
    "content": "{\n\t\"name\": \"foo\",\n\t\"version\": \"0.0.0\",\n\t\"files\": [\"index.js\"]\n}\n"
  },
  {
    "path": "test/fixtures/files/prepare-script/index.js",
    "content": "export default function foo() {\n\treturn 'bar';\n}\n"
  },
  {
    "path": "test/fixtures/files/prepare-script/package.json",
    "content": "{\n\t\"name\": \"foo\",\n\t\"version\": \"0.0.0\",\n\t\"files\": [\"index.js\"],\n\t\"scripts\": {\n\t\t\"prepare\": \"echo '> foo@0.0.0 prepare' && echo '> test prepare script'\"\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/files/source-and-dist-dir/dist/index.js",
    "content": "console.log('foo');\nconsole.log('bar');\n"
  },
  {
    "path": "test/fixtures/files/source-and-dist-dir/package.json",
    "content": "{\n\t\"name\": \"foo\",\n\t\"version\": \"0.0.0\",\n\t\"files\": [\"source\"]\n}\n"
  },
  {
    "path": "test/fixtures/files/source-and-dist-dir/source/bar.js",
    "content": "console.log('bar');\n"
  },
  {
    "path": "test/fixtures/files/source-and-dist-dir/source/foo.js",
    "content": "console.log('foo');\n"
  },
  {
    "path": "test/fixtures/files/source-dir/package.json",
    "content": "{\n\t\"name\": \"foo\",\n\t\"version\": \"0.0.0\",\n\t\"files\": [\"source\"]\n}\n"
  },
  {
    "path": "test/fixtures/files/source-dir/source/bar.js",
    "content": "console.log('bar');\n"
  },
  {
    "path": "test/fixtures/files/source-dir/source/foo.js",
    "content": "console.log('foo');\n"
  },
  {
    "path": "test/fixtures/readme.md",
    "content": "The directory is for the resources\nin the script npmignore.js\n"
  },
  {
    "path": "test/git-util/commit-log-from-revision.js",
    "content": "import test from 'ava';\nimport {stripIndent} from 'common-tags';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js');\n\ntest('returns single commit', createFixture, async () => {\n\t//\n}, async ({t, testedModule: {commitLogFromRevision}, $$}) => {\n\tawait $$`git tag v0.0.0`;\n\tconst {sha, commitMessage} = await t.context.commitNewFile();\n\n\tt.is(await commitLogFromRevision('v0.0.0'), `${commitMessage} ${sha}`);\n});\n\ntest('returns multiple commits, from newest to oldest', createFixture, async () => {\n\t//\n}, async ({t, testedModule: {commitLogFromRevision}, $$}) => {\n\tawait $$`git tag v0.0.0`;\n\tconst commit1 = await t.context.commitNewFile();\n\tconst commit2 = await t.context.commitNewFile();\n\tconst commit3 = await t.context.commitNewFile();\n\n\tconst commitLog = stripIndent`\n\t\t${commit3.commitMessage} ${commit3.sha}\n\t\t${commit2.commitMessage} ${commit2.sha}\n\t\t${commit1.commitMessage} ${commit1.sha}\n\t`;\n\n\tt.is(await commitLogFromRevision('v0.0.0'), commitLog);\n});\n"
  },
  {
    "path": "test/git-util/default-branch.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js');\n\ntest('main', createFixture, async ({$$}) => {\n\tawait $$`git checkout -B main`;\n}, async ({t, testedModule: {defaultBranch}}) => {\n\tt.is(await defaultBranch(), 'main');\n});\n\ntest('master', createFixture, async ({$$}) => {\n\tawait $$`git checkout -B master`;\n\tawait $$`git update-ref -d refs/heads/main`;\n}, async ({t, testedModule: {defaultBranch}}) => {\n\tt.is(await defaultBranch(), 'master');\n});\n\ntest('gh-pages', createFixture, async ({$$}) => {\n\tawait $$`git checkout -B gh-pages`;\n\tawait $$`git update-ref -d refs/heads/main`;\n\tawait $$`git update-ref -d refs/heads/master`;\n}, async ({t, testedModule: {defaultBranch}}) => {\n\tt.is(await defaultBranch(), 'gh-pages');\n});\n\ntest('fails', createFixture, async ({$$}) => {\n\tawait $$`git checkout -B unicorn`;\n\tawait $$`git update-ref -d refs/heads/main`;\n\tawait $$`git update-ref -d refs/heads/master`;\n}, async ({t, testedModule: {defaultBranch}}) => {\n\tawait t.throwsAsync(\n\t\tdefaultBranch(),\n\t\t{message: 'Could not infer the default Git branch. Please specify one with the --branch flag or with a np config.'},\n\t);\n});\n"
  },
  {
    "path": "test/git-util/delete-tag.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js');\n\ntest('deletes given tag', createFixture, async ({$$}) => {\n\tawait $$`git tag v0.0.0`;\n\tawait $$`git tag v1.0.0`;\n}, async ({t, testedModule: {deleteTag}, $$}) => {\n\tawait deleteTag('v1.0.0');\n\tconst {stdout: tags} = await $$`git tag`;\n\n\tt.is(tags, 'v0.0.0');\n});\n\ntest('deletes given tag from a large list', createFixture, async ({$$}) => {\n\tawait $$`git tag v0.0.0`;\n\tawait $$`git tag v1.0.0`;\n\tawait $$`git tag v2.0.0`;\n\tawait $$`git tag v3.0.0`;\n\tawait $$`git tag v4.0.0`;\n}, async ({t, testedModule: {deleteTag}, $$}) => {\n\tawait deleteTag('v2.0.0');\n\tconst {stdout: tags} = await $$`git tag`;\n\n\tt.deepEqual(\n\t\ttags.split('\\n'),\n\t\t['v0.0.0', 'v1.0.0', 'v3.0.0', 'v4.0.0'],\n\t);\n});\n\ntest('throws if tag not found', createFixture, async () => {\n\t//\n}, async ({t, testedModule: {deleteTag}}) => {\n\tawait t.throwsAsync(\n\t\tdeleteTag('v1.0.0'),\n\t\t{message: /error: tag 'v1\\.0\\.0' not found\\./},\n\t);\n});\n"
  },
  {
    "path": "test/git-util/get-current-branch.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js');\n\ntest('returns current branch', createFixture, async ({$$}) => {\n\tawait $$`git switch -c unicorn`;\n}, async ({t, testedModule: {getCurrentBranch}}) => {\n\tconst currentBranch = await getCurrentBranch();\n\tt.is(currentBranch, 'unicorn');\n});\n"
  },
  {
    "path": "test/git-util/has-upstream.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js');\n\ntest('no upstream', createFixture, async () => {\n\t//\n}, async ({t, testedModule: {hasUpstream}}) => {\n\tt.false(await hasUpstream());\n});\n"
  },
  {
    "path": "test/git-util/is-head-detached.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js');\n\ntest('not detached', createFixture, async () => {\n\t//\n}, async ({t, testedModule: {isHeadDetached}}) => {\n\tt.false(await isHeadDetached());\n});\n\ntest('detached', createFixture, async ({$$}) => {\n\tconst {stdout: firstCommitSha} = await $$`git rev-list --max-parents=0 HEAD`;\n\tawait $$`git checkout ${firstCommitSha}`;\n}, async ({t, testedModule: {isHeadDetached}}) => {\n\tt.true(await isHeadDetached());\n});\n"
  },
  {
    "path": "test/git-util/latest-tag-or-first-commit.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js');\n\n// From https://stackoverflow.com/a/3357357/10292952\nconst getCommitMessage = async ($$, sha) => $$`git log --format=%B -n 1 ${sha}`;\n\ntest('one tag', createFixture, async ({$$}) => {\n\tawait $$`git tag v0.0.0`;\n}, async ({t, testedModule: {latestTagOrFirstCommit}}) => {\n\tconst result = await latestTagOrFirstCommit();\n\tt.is(result, 'v0.0.0');\n});\n\ntest('two tags', createFixture, async ({t, $$}) => {\n\tawait $$`git tag v0.0.0`;\n\tawait t.context.commitNewFile();\n\tawait $$`git tag v1.0.0`;\n}, async ({t, testedModule: {latestTagOrFirstCommit}}) => {\n\tconst result = await latestTagOrFirstCommit();\n\tt.is(result, 'v1.0.0');\n});\n\ntest('no tags (fallback)', createFixture, async () => {\n\t//\n}, async ({t, testedModule: {latestTagOrFirstCommit}, $$}) => {\n\tconst result = await latestTagOrFirstCommit();\n\tconst {stdout: firstCommitMessage} = await getCommitMessage($$, result);\n\n\tt.is(firstCommitMessage.trim(), '\"init1\"');\n});\n"
  },
  {
    "path": "test/git-util/latest-tag.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js');\n\ntest('returns latest tag', createFixture, async ({$$}) => {\n\tawait $$`git tag v0.0.0`;\n}, async ({t, testedModule: {latestTag}}) => {\n\tt.is(await latestTag(), 'v0.0.0');\n});\n\ntest('returns latest tag - multiple set', createFixture, async ({t, $$}) => {\n\tawait $$`git tag v0.0.0`;\n\t/* eslint-disable no-await-in-loop */\n\tfor (const major of [1, 2, 3, 4]) {\n\t\tawait t.context.commitNewFile();\n\t\tawait $$`git tag v${major}.0.0`;\n\t}\n\t/* eslint-enable no-await-in-loop */\n}, async ({t, testedModule: {latestTag}}) => {\n\tt.is(await latestTag(), 'v4.0.0');\n});\n"
  },
  {
    "path": "test/git-util/multiple-initial-commits.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js');\n\ntest('repository with multiple initial commits', createFixture, async ({t, $$}) => {\n\t// Get the current branch name before creating orphan branch\n\tconst {stdout: initialBranch} = await $$`git branch --show-current`;\n\n\t// Create a second orphan branch to simulate multiple initial commits\n\tawait $$`git checkout --orphan other-branch`;\n\tawait t.context.createFile('other-file');\n\tawait $$`git add other-file`;\n\tawait $$`git commit -m orphan`;\n\n\t// Merge the orphan branch into the initial branch, creating multiple root commits\n\tawait $$`git checkout ${initialBranch}`;\n\tawait $$`git merge --allow-unrelated-histories other-branch -m merge`;\n}, async ({t, testedModule: {latestTagOrFirstCommit, commitLogFromRevision}}) => {\n\t// This should not throw an error even with multiple initial commits\n\tconst result = await latestTagOrFirstCommit();\n\n\t// Verify result is a valid commit hash (single line)\n\tt.false(result.includes('\\n'), 'Result should be a single commit hash');\n\tt.is(result.length, 40, 'Result should be a 40-character SHA-1 hash');\n\n\t// This was the operation that failed in the original issue\n\tawait t.notThrowsAsync(async () => {\n\t\tawait commitLogFromRevision(result);\n\t}, 'commitLogFromRevision should work with the returned first commit');\n});\n"
  },
  {
    "path": "test/git-util/new-files-since-last-release.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js');\n\ntest('returns files added since latest tag', createFixture, async ({t, $$}) => {\n\tawait $$`git tag v0.0.0`;\n\tawait t.context.createFile('new');\n\tawait t.context.createFile('index.js');\n\tawait $$`git add .`;\n\tawait $$`git commit -m \"added\"`;\n}, async ({t, testedModule: {newFilesSinceLastRelease}, temporaryDirectory}) => {\n\tconst newFiles = await newFilesSinceLastRelease(temporaryDirectory);\n\tt.deepEqual(\n\t\tnewFiles.sort(),\n\t\t['new', 'index.js'].sort(),\n\t);\n});\n\ntest('no files', createFixture, async ({$$}) => {\n\tawait $$`git tag v0.0.0`;\n}, async ({t, testedModule: {newFilesSinceLastRelease}, temporaryDirectory}) => {\n\tconst newFiles = await newFilesSinceLastRelease(temporaryDirectory);\n\tt.deepEqual(newFiles, []);\n});\n\ntest('uses ignoreWalker', createFixture, async ({t}) => {\n\tawait t.context.createFile('index.js');\n\tawait t.context.createFile('package.json');\n\tawait t.context.createFile('package-lock.json');\n\tawait t.context.createFile('.gitignore', 'package-lock.json\\n.git'); // ignoreWalker doesn't ignore `.git`: npm/ignore-walk#2\n}, async ({t, testedModule: {newFilesSinceLastRelease}, temporaryDirectory}) => {\n\tconst newFiles = await newFilesSinceLastRelease(temporaryDirectory);\n\tt.deepEqual(\n\t\tnewFiles.sort(),\n\t\t['index.js', 'package.json', '.gitignore'].sort(),\n\t);\n});\n"
  },
  {
    "path": "test/git-util/previous-tag-or-first-commit.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js');\n\ntest('no tags', createFixture, () => {\n\t//\n}, async ({t, testedModule: {previousTagOrFirstCommit}}) => {\n\tconst result = await previousTagOrFirstCommit();\n\tt.is(result, undefined);\n});\n\ntest('one tag - fallback to first commit', createFixture, async ({$$}) => {\n\tawait $$`git tag v0.0.0`;\n}, async ({t, testedModule: {previousTagOrFirstCommit}}) => {\n\tconst result = await previousTagOrFirstCommit();\n\tconst commitMessage = await t.context.getCommitMessage(result);\n\n\tt.is(commitMessage, t.context.firstCommitMessage);\n});\n\ntest('two tags', createFixture, async ({t, $$}) => {\n\tawait $$`git tag v0.0.0`;\n\tawait t.context.commitNewFile();\n\tawait $$`git tag v1.0.0`;\n}, async ({t, testedModule: {previousTagOrFirstCommit}}) => {\n\tconst result = await previousTagOrFirstCommit();\n\tt.is(result, 'v0.0.0');\n});\n\ntest('multiple tags', createFixture, async ({t, $$}) => {\n\tawait $$`git tag v0.0.0`;\n\t/* eslint-disable no-await-in-loop */\n\tfor (const major of [1, 2, 3, 4]) {\n\t\tawait t.context.commitNewFile();\n\t\tawait $$`git tag v${major}.0.0`;\n\t}\n\t/* eslint-enable no-await-in-loop */\n}, async ({t, testedModule: {previousTagOrFirstCommit}}) => {\n\tconst result = await previousTagOrFirstCommit();\n\tt.is(result, 'v3.0.0');\n});\n\ntest('tags created out of order - should sort by semver not creation date', createFixture, async ({t, $$}) => {\n\t// Create tags out of semver order (simulating a hotfix scenario)\n\tawait $$`git tag v1.0.0`;\n\tawait t.context.commitNewFile();\n\tawait $$`git tag v1.2.0`;\n\tawait t.context.commitNewFile();\n\tawait $$`git tag v1.2.1`;\n\tawait t.context.commitNewFile();\n\t// Create a hotfix tag for an older version (created after v1.2.1 but semver is lower)\n\tawait $$`git tag v1.0.1`;\n\tawait t.context.commitNewFile();\n\tawait $$`git tag v1.2.2`;\n}, async ({t, testedModule: {previousTagOrFirstCommit}}) => {\n\t// Should return v1.2.1 (semver previous), not v1.0.1 (creation date previous)\n\tconst result = await previousTagOrFirstCommit();\n\tt.is(result, 'v1.2.1');\n});\n\ntest.todo('test fallback case');\n"
  },
  {
    "path": "test/git-util/push-graceful.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js', import.meta.url);\n\ntest('succeeds', createFixture, [{\n\tcommand: 'git push --follow-tags',\n\texitCode: 0,\n}], async ({t, testedModule: {pushGraceful}}) => {\n\tawait t.notThrowsAsync(pushGraceful());\n});\n\ntest('fails w/ remote on GitHub and bad branch permission', createFixture, [\n\t{\n\t\tcommand: 'git push --follow-tags',\n\t\tstderr: 'GH006',\n\t},\n\t{\n\t\tcommand: 'git push --tags',\n\t\texitCode: 0,\n\t},\n], async ({t, testedModule: {pushGraceful}}) => {\n\tconst {pushed, reason} = await pushGraceful(true);\n\n\tt.is(pushed, 'tags');\n\tt.is(reason, 'Branch protection: np can`t push the commits. Push them manually.');\n});\n\ntest('throws', createFixture, [{\n\tcommand: 'git push --follow-tags',\n\texitCode: 1,\n}], async ({t, testedModule: {pushGraceful}}) => {\n\tawait t.throwsAsync(pushGraceful(false));\n});\n\ntest('pushes to custom remote', createFixture, [{\n\tcommand: 'git push upstream --follow-tags',\n\texitCode: 0,\n}], async ({t, testedModule: {pushGraceful}}) => {\n\tawait t.notThrowsAsync(pushGraceful(false, 'upstream'));\n});\n\ntest('throws with custom remote', createFixture, [{\n\tcommand: 'git push upstream --follow-tags',\n\texitCode: 1,\n}], async ({t, testedModule: {pushGraceful}}) => {\n\tawait t.throwsAsync(pushGraceful(false, 'upstream'));\n});\n\ntest('pushes tags only to custom remote on branch protection error', createFixture, [\n\t{\n\t\tcommand: 'git push upstream --follow-tags',\n\t\tstderr: 'GH006',\n\t},\n\t{\n\t\tcommand: 'git push upstream --tags',\n\t\texitCode: 0,\n\t},\n], async ({t, testedModule: {pushGraceful}}) => {\n\tconst {pushed, reason} = await pushGraceful(true, 'upstream');\n\n\tt.is(pushed, 'tags');\n\tt.is(reason, 'Branch protection: np can`t push the commits. Push them manually.');\n});\n\n"
  },
  {
    "path": "test/git-util/read-file-from-last-release.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js');\n\ntest('returns content of a given file', createFixture, async ({t, $$}) => {\n\tawait t.context.createFile('unicorn.txt', 'unicorn-1');\n\tawait $$`git add .`;\n\tawait $$`git commit -m \"added\"`;\n\tawait $$`git tag v0.0.0`;\n\tawait t.context.createFile('unicorn.txt', 'unicorn-2');\n\tawait $$`git add .`;\n\tawait $$`git commit -m \"added\"`;\n}, async ({t, testedModule: {readFileFromLastRelease}}) => {\n\tconst file = await readFileFromLastRelease('unicorn.txt');\n\tt.is(file, 'unicorn-1');\n});\n\ntest('fails if file not in previous release', createFixture, async ({t, $$}) => {\n\tawait $$`git tag v0.0.0`;\n\tawait t.context.createFile('unicorn.txt', 'unicorn');\n\tawait $$`git add .`;\n\tawait $$`git commit -m \"added\"`;\n}, async ({t, testedModule: {readFileFromLastRelease}}) => {\n\tawait t.throwsAsync(\n\t\treadFileFromLastRelease('unicorn.txt'),\n\t\t{message: /fatal: path '[^']*' exists on disk, but not in 'v0\\.0\\.0'/},\n\t);\n});\n\ntest('no previous release', createFixture, async ({t, $$}) => {\n\tawait t.context.createFile('unicorn.txt', 'unicorn');\n\tawait $$`git add .`;\n\tawait $$`git commit -m \"added\"`;\n}, async ({t, testedModule: {readFileFromLastRelease}}) => {\n\tawait t.throwsAsync(\n\t\treadFileFromLastRelease('unicorn.txt'),\n\t\t{message: /fatal: No names found, cannot describe anything./},\n\t);\n});\n\n// These errors could probably be handled in 'readFileFromLastRelease'\n"
  },
  {
    "path": "test/git-util/remove-last-commit.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js');\n\ntest('removes latest commit', createFixture, async ({t, $$}) => {\n\tawait t.context.createFile('index.js');\n\tawait $$`git add -A`;\n\tawait $$`git commit -m \"added\"`;\n}, async ({t, testedModule: {removeLastCommit}, $$}) => {\n\tconst {stdout: commitsBefore} = await $$`git log --pretty=\"%s\"`;\n\tt.true(commitsBefore.includes('\"added\"'));\n\n\tawait removeLastCommit();\n\n\tconst {stdout: commitsAfter} = await $$`git log --pretty=\"%s\"`;\n\tt.false(commitsAfter.includes('\"added\"'));\n});\n"
  },
  {
    "path": "test/git-util/root.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\nimport {npRootDirectory} from '../../source/util.js';\nimport {root} from '../../source/git-util.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js');\n\ntest('returns np root dir', async t => {\n\tt.is(await root(), npRootDirectory);\n});\n\ntest('returns root dir of temp dir', createFixture, () => {\n\t//\n}, async ({t, testedModule: git, temporaryDirectory}) => {\n\tt.is(await git.root(), temporaryDirectory);\n});\n"
  },
  {
    "path": "test/git-util/verify-current-branch-is-release-branch.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js');\n\ntest('on release branch', createFixture, async ({$$}) => {\n\tawait $$`git switch -c unicorn`;\n}, async ({t, testedModule: {verifyCurrentBranchIsReleaseBranch}}) => {\n\tawait t.notThrowsAsync(verifyCurrentBranchIsReleaseBranch('unicorn'));\n});\n\ntest('not on release branch', createFixture, async ({$$}) => {\n\tawait $$`git switch -c unicorn`;\n}, async ({t, testedModule: {verifyCurrentBranchIsReleaseBranch}}) => {\n\tawait t.throwsAsync(\n\t\tverifyCurrentBranchIsReleaseBranch('main'),\n\t\t{message: 'Not on `main` branch. Use --any-branch to publish anyway, or set a different release branch using --branch.'},\n\t);\n});\n"
  },
  {
    "path": "test/git-util/verify-recent-git-version.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js', import.meta.url);\n\ntest('satisfied', createFixture, [{\n\tcommand: 'git version',\n\tstdout: 'git version 2.12.0', // One higher than minimum\n}], async ({t, testedModule: {verifyRecentGitVersion}}) => {\n\tawait t.notThrowsAsync(verifyRecentGitVersion());\n});\n\ntest('not satisfied', createFixture, [{\n\tcommand: 'git version',\n\tstdout: 'git version 2.10.0', // One lower than minimum\n}], async ({t, testedModule: {verifyRecentGitVersion}}) => {\n\tawait t.throwsAsync(\n\t\tverifyRecentGitVersion(),\n\t\t{message: '`np` requires git >=2.11.0'},\n\t);\n});\n"
  },
  {
    "path": "test/git-util/verify-remote-history-is-clean.js",
    "content": "import test from 'ava';\nimport {_createFixture as _createStubFixture} from '../_helpers/stub-execa.js';\nimport {_createFixture as _createIntegrationFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createStubFixture<import('../../source/git-util.js')>>} */\nconst createStubFixture = _createStubFixture('../../source/git-util.js', import.meta.url);\n\n/** @type {ReturnType<typeof _createIntegrationFixture<import('../../source/git-util.js')>>} */\nconst createIntegrationFixture = _createIntegrationFixture('../../source/git-util.js');\n\ntest('unfetched changes', createStubFixture, [\n\t{\n\t\tcommand: 'git rev-parse @{u}',\n\t\texitCode: 0,\n\t},\n\t{\n\t\tcommand: 'git fetch --dry-run',\n\t\tstdout: 'From https://github.com/sindresorhus/np', // Has unfetched changes\n\t},\n], async ({t, testedModule: {verifyRemoteHistoryIsClean}}) => {\n\tawait t.throwsAsync(\n\t\tverifyRemoteHistoryIsClean(),\n\t\t{message: 'Remote history differs. Please run `git fetch` and pull changes.'},\n\t);\n});\n\ntest('unclean remote history', createStubFixture, [\n\t{\n\t\tcommand: 'git rev-parse @{u}',\n\t\texitCode: 0,\n\t},\n\t{\n\t\tcommand: 'git fetch --dry-run',\n\t\texitCode: 0,\n\t},\n\t{\n\t\tcommand: 'git rev-list --count --left-only @{u}...HEAD',\n\t\tstdout: '1', // Has unpulled changes\n\t},\n], async ({t, testedModule: {verifyRemoteHistoryIsClean}}) => {\n\tawait t.throwsAsync(\n\t\tverifyRemoteHistoryIsClean(),\n\t\t{message: 'Remote history differs. Please pull changes.'},\n\t);\n});\n\ntest('clean fetched remote history', createStubFixture, [\n\t{\n\t\tcommand: 'git rev-parse @{u}',\n\t\texitCode: 0,\n\t},\n\t{\n\t\tcommand: 'git fetch --dry-run',\n\t\texitCode: 0,\n\t},\n\t{\n\t\tcommand: 'git rev-list --count --left-only @{u}...HEAD',\n\t\tstdout: '0', // No changes\n\t},\n], async ({t, testedModule: {verifyRemoteHistoryIsClean}}) => {\n\tawait t.notThrowsAsync(verifyRemoteHistoryIsClean());\n});\n\ntest('no remote', createIntegrationFixture, async () => {\n\t//\n}, async ({t, testedModule: {verifyRemoteHistoryIsClean}}) => {\n\tawait t.notThrowsAsync(verifyRemoteHistoryIsClean());\n});\n"
  },
  {
    "path": "test/git-util/verify-remote-is-valid.js",
    "content": "import test from 'ava';\nimport {_createFixture as _createStubFixture} from '../_helpers/stub-execa.js';\nimport {_createFixture as _createIntegrationFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createStubFixture<import('../../source/git-util.js')>>} */\nconst createStubFixture = _createStubFixture('../../source/git-util.js', import.meta.url);\n\n/** @type {ReturnType<typeof _createIntegrationFixture<import('../../source/git-util.js')>>} */\nconst createIntegrationFixture = _createIntegrationFixture('../../source/git-util.js');\n\ntest('has remote', createStubFixture, [{\n\tcommand: 'git ls-remote origin HEAD',\n\texitCode: 0,\n}], async ({t, testedModule: {verifyRemoteIsValid}}) => {\n\tawait t.notThrowsAsync(verifyRemoteIsValid());\n});\n\ntest('no remote', createIntegrationFixture, async () => {\n\t//\n}, async ({t, testedModule: {verifyRemoteIsValid}}) => {\n\tawait t.throwsAsync(\n\t\tverifyRemoteIsValid(),\n\t\t{message: /^Git fatal error:/m},\n\t);\n});\n\ntest('has custom remote', createStubFixture, [{\n\tcommand: 'git ls-remote upstream HEAD',\n\texitCode: 0,\n}], async ({t, testedModule: {verifyRemoteIsValid}}) => {\n\tawait t.notThrowsAsync(verifyRemoteIsValid('upstream'));\n});\n"
  },
  {
    "path": "test/git-util/verify-tag-does-not-exist-on-remote.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js', import.meta.url);\n\ntest('exists', createFixture, [{\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v0.0.0',\n\tstdout: '123456789', // Some hash\n}], async ({t, testedModule: {verifyTagDoesNotExistOnRemote}}) => {\n\tawait t.throwsAsync(\n\t\tverifyTagDoesNotExistOnRemote('v0.0.0'),\n\t\t{message: 'Git tag `v0.0.0` already exists.'},\n\t);\n});\n\ntest('does not exist', createFixture, [{\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v0.0.0',\n\texitCode: 1,\n\tstderr: '',\n\tstdout: '',\n}], async ({t, testedModule: {verifyTagDoesNotExistOnRemote}}) => {\n\tawait t.notThrowsAsync(verifyTagDoesNotExistOnRemote('v0.0.0'));\n});\n"
  },
  {
    "path": "test/git-util/verify-working-tree-is-clean.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */\nconst createFixture = _createFixture('../../source/git-util.js');\n\ntest('clean', createFixture, async ({t, $$}) => {\n\tawait t.context.createFile('index.js');\n\tawait $$`git add .`;\n\tawait $$`git commit -m \"added\"`;\n}, async ({t, testedModule: {verifyWorkingTreeIsClean}}) => {\n\tawait t.notThrowsAsync(verifyWorkingTreeIsClean());\n});\n\ntest('not clean', createFixture, async ({t}) => {\n\tawait t.context.createFile('index.js');\n}, async ({t, testedModule: {verifyWorkingTreeIsClean}}) => {\n\tawait t.throwsAsync(\n\t\tverifyWorkingTreeIsClean(),\n\t\t{message: 'Unclean working tree. Commit or stash changes first.'},\n\t);\n});\n"
  },
  {
    "path": "test/index.js",
    "content": "import path from 'node:path';\nimport process from 'node:process';\nimport test from 'ava';\nimport sinon from 'sinon';\nimport esmock from 'esmock';\nimport {of, throwError} from 'rxjs';\nimport {npmConfig as packageManager} from '../source/package-manager/configs.js';\nimport * as util from '../source/util.js';\n\nconst defaultOptions = {\n\tcleanup: true,\n\ttests: true,\n\tpublish: true,\n\tpackageManager,\n\trunPublish: true,\n\tavailability: {\n\t\tisAvailable: false,\n\t\tisUnknown: false,\n\t},\n\trenderer: 'silent',\n};\n\nconst npPackageResult = await util.readPackage();\n\nconst getNpMock = async () => esmock('../source/index.js', {}, {\n\texeca: {execa: sinon.stub().resolves({stdout: '10.0.0', stderr: ''})},\n\t'../source/git-util.js': {\n\t\thasUpstream: sinon.stub().returns(true),\n\t\tpushGraceful: sinon.stub(),\n\t\tverifyWorkingTreeIsClean: sinon.stub(),\n\t\tverifyCurrentBranchIsReleaseBranch: sinon.stub(),\n\t\tverifyRemoteHistoryIsClean: sinon.stub(),\n\t\tverifyRemoteIsValid: sinon.stub(),\n\t\tverifyRecentGitVersion: sinon.stub(),\n\t\tfetch: sinon.stub(),\n\t\tverifyTagDoesNotExistOnRemote: sinon.stub(),\n\t},\n\t'../source/npm/util.js': {\n\t\t...await import('../source/npm/util.js'),\n\t\tcheckConnection: sinon.stub().resolves(),\n\t},\n});\n\nconst npFails = test.macro(async (t, inputs, message) => {\n\tconst npMock = await getNpMock();\n\tawait t.throwsAsync(\n\t\tPromise.all(inputs.map(input => npMock(input, defaultOptions, npPackageResult))),\n\t\t{message},\n\t);\n});\n\ntest('version is invalid', npFails, ['foo', '4.x.3'], /New version (?:foo|4\\.x\\.3) should either be one of patch, minor, major, prepatch, preminor, premajor, prerelease, or a valid SemVer version\\./);\n\ntest('version is pre-release', npFails, ['premajor', 'preminor', 'prepatch', 'prerelease', '100.0.0-0', '100.0.0-beta'], 'You must specify a dist-tag using --tag when publishing a pre-release version. This prevents accidentally tagging unstable versions as \"latest\". https://docs.npmjs.com/cli/dist-tag');\n\ntest('errors on too low version', npFails, ['1.0.0', '1.0.0-beta'], /New version 1\\.0\\.0(?:-beta)? should be higher than current version \\d+\\.\\d+\\.\\d+/);\n\nconst fakeExecaReturn = () => Object.assign(\n\tPromise.resolve({pipe: sinon.stub()}),\n\t{stdout: '', stderr: ''},\n);\n\nconst fakeObservableReturn = () => of('');\n\nconst fakeObservableReject = error => throwError(() => Object.assign(new Error(error), {stdout: '', stderr: error}));\n\ntest('skip enabling 2FA if the package exists', async t => {\n\tconst enable2faStub = sinon.stub();\n\n\t/** @type {typeof np} */\n\tconst npMock = await esmock('../source/index.js', {\n\t\tdel: {deleteAsync: sinon.stub()},\n\t\texeca: {execa: sinon.stub().returns(fakeExecaReturn())},\n\t\t'../source/prerequisite-tasks.js': sinon.stub(),\n\t\t'../source/git-tasks.js': sinon.stub(),\n\t\t'../source/git-util.js': {\n\t\t\thasUpstream: sinon.stub().returns(true),\n\t\t\tpushGraceful: sinon.stub(),\n\t\t\tverifyWorkingTreeIsClean: sinon.stub(),\n\t\t},\n\t\t'../source/npm/enable-2fa.js': enable2faStub,\n\t\t'../source/npm/publish.js': {\n\t\t\tgetPackagePublishArguments: sinon.stub().returns([]),\n\t\t\trunPublish: sinon.stub().returns(fakeObservableReturn()),\n\t\t},\n\t});\n\n\tawait t.notThrowsAsync(npMock('1.0.0', {\n\t\t...defaultOptions,\n\t\tavailability: {\n\t\t\tisAvailable: false,\n\t\t\tisUnknown: false,\n\t\t},\n\t}, npPackageResult));\n\n\tt.true(enable2faStub.notCalled);\n});\n\ntest('skip enabling 2FA if the `2fa` option is false', async t => {\n\tconst enable2faStub = sinon.stub();\n\n\t/** @type {typeof np} */\n\tconst npMock = await esmock('../source/index.js', {\n\t\tdel: {deleteAsync: sinon.stub()},\n\t\texeca: {execa: sinon.stub().returns(fakeExecaReturn())},\n\t\t'../source/prerequisite-tasks.js': sinon.stub(),\n\t\t'../source/git-tasks.js': sinon.stub(),\n\t\t'../source/git-util.js': {\n\t\t\thasUpstream: sinon.stub().returns(true),\n\t\t\tpushGraceful: sinon.stub(),\n\t\t\tverifyWorkingTreeIsClean: sinon.stub(),\n\t\t},\n\t\t'../source/npm/enable-2fa.js': enable2faStub,\n\t\t'../source/npm/publish.js': {\n\t\t\tgetPackagePublishArguments: sinon.stub().returns([]),\n\t\t\trunPublish: sinon.stub().returns(fakeObservableReturn()),\n\t\t},\n\t});\n\n\tawait t.notThrowsAsync(npMock('1.0.0', {\n\t\t...defaultOptions,\n\t\tavailability: {\n\t\t\tisAvailable: true,\n\t\t\tisUnknown: false,\n\t\t},\n\t\t'2fa': false,\n\t}, npPackageResult));\n\n\tt.true(enable2faStub.notCalled);\n});\n\ntest('skip enabling 2FA in trusted publishing (OIDC) contexts', async t => {\n\tconst enable2faStub = sinon.stub();\n\n\t/** @type {typeof np} */\n\tconst npMock = await esmock('../source/index.js', {\n\t\tdel: {deleteAsync: sinon.stub()},\n\t\texeca: {execa: sinon.stub().returns(fakeExecaReturn())},\n\t\t'../source/prerequisite-tasks.js': sinon.stub(),\n\t\t'../source/git-tasks.js': sinon.stub(),\n\t\t'../source/git-util.js': {\n\t\t\thasUpstream: sinon.stub().returns(true),\n\t\t\tpushGraceful: sinon.stub(),\n\t\t\tverifyWorkingTreeIsClean: sinon.stub(),\n\t\t},\n\t\t'../source/npm/enable-2fa.js': enable2faStub,\n\t\t'../source/npm/publish.js': {\n\t\t\tgetPackagePublishArguments: sinon.stub().returns([]),\n\t\t\trunPublish: sinon.stub().returns(fakeObservableReturn()),\n\t\t},\n\t\t'../source/npm/oidc.js': {\n\t\t\tgetOidcProvider: () => 'github',\n\t\t},\n\t});\n\n\tawait t.notThrowsAsync(npMock('1.0.0', {\n\t\t...defaultOptions,\n\t\tavailability: {\n\t\t\tisAvailable: true,\n\t\t\tisUnknown: false,\n\t\t},\n\t\t'2fa': true,\n\t}, npPackageResult));\n\n\tt.true(enable2faStub.notCalled);\n});\n\ntest('rollback is called when publish fails', async t => {\n\tconst deleteTagStub = sinon.stub().resolves();\n\tconst removeLastCommitStub = sinon.stub().resolves();\n\n\t/** @type {typeof np} */\n\tconst npMock = await esmock('../source/index.js', {\n\t\tdel: {deleteAsync: sinon.stub()},\n\t\texeca: {execa: sinon.stub().returns(fakeExecaReturn())},\n\t\t'../source/prerequisite-tasks.js': sinon.stub(),\n\t\t'../source/git-tasks.js': sinon.stub(),\n\t\t'../source/git-util.js': {\n\t\t\thasUpstream: sinon.stub().returns(true),\n\t\t\tpushGraceful: sinon.stub(),\n\t\t\tverifyWorkingTreeIsClean: sinon.stub(),\n\t\t\tlatestTag: sinon.stub().resolves('v1.0.0'),\n\t\t\tdeleteTag: deleteTagStub,\n\t\t\tremoveLastCommit: removeLastCommitStub,\n\t\t},\n\t\t'../source/npm/enable-2fa.js': sinon.stub(),\n\t\t'../source/npm/publish.js': {\n\t\t\tgetPackagePublishArguments: sinon.stub().returns([]),\n\t\t\trunPublish: sinon.stub().returns(fakeObservableReject('npm ERR! publish failed')),\n\t\t},\n\t\t'../source/util.js': {\n\t\t\t...util,\n\t\t\treadPackage: sinon.stub().resolves({version: '1.0.0'}),\n\t\t\tgetTagVersionPrefix: sinon.stub().resolves('v'),\n\t\t},\n\t});\n\n\tawait t.throwsAsync(\n\t\tnpMock('1.0.0', {\n\t\t\t...defaultOptions,\n\t\t}, {package_: {version: '0.9.0'}, rootDirectory: process.cwd()}),\n\t\t{message: /Error publishing package/},\n\t);\n\n\tt.true(deleteTagStub.calledOnce, 'deleteTag should be called once');\n\tt.true(removeLastCommitStub.calledOnce, 'removeLastCommit should be called once');\n});\n\ntest('publish uses rootDirectory from context as cwd', async t => {\n\tconst contentsDirectory = path.resolve('dist');\n\tlet publishCwd;\n\n\t/** @type {typeof np} */\n\tconst npMock = await esmock('../source/index.js', {\n\t\tdel: {deleteAsync: sinon.stub()},\n\t\texeca: {execa: sinon.stub().returns(fakeExecaReturn())},\n\t\t'../source/prerequisite-tasks.js': sinon.stub(),\n\t\t'../source/git-tasks.js': sinon.stub(),\n\t\t'../source/git-util.js': {\n\t\t\thasUpstream: sinon.stub().returns(true),\n\t\t\tpushGraceful: sinon.stub(),\n\t\t\tverifyWorkingTreeIsClean: sinon.stub(),\n\t\t},\n\t\t'../source/npm/enable-2fa.js': sinon.stub(),\n\t\t'../source/npm/publish.js': {\n\t\t\tgetPackagePublishArguments: sinon.stub().returns([]),\n\t\t\trunPublish: sinon.stub().callsFake((_arguments, options) => {\n\t\t\t\tpublishCwd = options?.cwd;\n\t\t\t\treturn fakeObservableReturn();\n\t\t\t}),\n\t\t},\n\t});\n\n\tawait npMock('1.0.0', defaultOptions, {package_: npPackageResult.package_, rootDirectory: contentsDirectory});\n\n\tt.is(publishCwd, contentsDirectory, 'publish should use rootDirectory from context as cwd');\n});\n"
  },
  {
    "path": "test/npm/enable-2fa.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/npm/enable-2fa.js')>>} */\nconst createFixture = _createFixture('../../source/npm/enable-2fa.js', import.meta.url);\n\nconst npmVersionFixtures = [\n\t{version: '8.0.0', accessArgs: ['access', '2fa-required']},\n\t{version: '9.0.0', accessArgs: ['access', 'set', 'mfa=publish']},\n];\n\nfor (const {version, accessArgs} of npmVersionFixtures) {\n\tconst npmVersionCommand = [{\n\t\tcommand: 'npm --version',\n\t\tstdout: version,\n\t}];\n\n\ttest(`npm v${version} - no options`, createFixture, npmVersionCommand, async ({t, testedModule: {getEnable2faArguments}}) => {\n\t\tt.deepEqual(\n\t\t\tawait getEnable2faArguments('np'),\n\t\t\t[...accessArgs, 'np'],\n\t\t);\n\t});\n\n\ttest(`npm v${version} - options, no otp`, createFixture, npmVersionCommand, async ({t, testedModule: {getEnable2faArguments}}) => {\n\t\tt.deepEqual(\n\t\t\tawait getEnable2faArguments('np', {confirm: true}),\n\t\t\t[...accessArgs, 'np'],\n\t\t);\n\t});\n\n\ttest(`npm v${version} - options, with otp`, createFixture, npmVersionCommand, async ({t, testedModule: {getEnable2faArguments}}) => {\n\t\tt.deepEqual(\n\t\t\tawait getEnable2faArguments('np', {otp: '123456'}),\n\t\t\t[...accessArgs, 'np', '--otp', '123456'],\n\t\t);\n\t});\n}\n\n"
  },
  {
    "path": "test/npm/handle-npm-error.js",
    "content": "import test from 'ava';\nimport handleNpmError from '../../source/npm/handle-npm-error.js';\n\nconst makeError = ({code, stdout, stderr}) => ({\n\tcode,\n\tstdout: stdout ?? '',\n\tstderr: stderr ?? '',\n});\n\ntest('error code 402 - privately publish scoped package', t => {\n\tt.throws(\n\t\t() => handleNpmError(makeError({code: 402})),\n\t\t{message: 'You cannot publish a scoped package privately without a paid plan. Did you mean to publish publicly?'},\n\t);\n\n\tt.throws(\n\t\t() => handleNpmError(makeError({stderr: 'npm ERR! 402 Payment Required'})),\n\t\t{message: 'You cannot publish a scoped package privately without a paid plan. Did you mean to publish publicly?'},\n\t);\n});\n"
  },
  {
    "path": "test/npm/oidc.js",
    "content": "import test from 'ava';\nimport esmock from 'esmock';\n\ntest('detects GitHub Actions', async t => {\n\tconst {getOidcProvider} = await esmock('../../source/npm/oidc.js', {\n\t\t'node:process': {\n\t\t\tenv: {\n\t\t\t\tGITHUB_ACTIONS: 'true',\n\t\t\t\tACTIONS_ID_TOKEN_REQUEST_URL: 'https://example.com',\n\t\t\t\tACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',\n\t\t\t},\n\t\t},\n\t});\n\n\tt.is(getOidcProvider(), 'github');\n});\n\ntest('detects GitLab CI', async t => {\n\tconst {getOidcProvider} = await esmock('../../source/npm/oidc.js', {\n\t\t'node:process': {\n\t\t\tenv: {\n\t\t\t\tGITLAB_CI: 'true',\n\t\t\t\tNPM_ID_TOKEN: 'token',\n\t\t\t},\n\t\t},\n\t});\n\n\tt.is(getOidcProvider(), 'gitlab');\n});\n\ntest('detects no OIDC', async t => {\n\tconst {getOidcProvider} = await esmock('../../source/npm/oidc.js', {\n\t\t'node:process': {\n\t\t\tenv: {},\n\t\t},\n\t});\n\n\tt.is(getOidcProvider(), undefined);\n});\n"
  },
  {
    "path": "test/npm/publish.js",
    "content": "import test from 'ava';\nimport {firstValueFrom} from 'rxjs';\nimport {getPackagePublishArguments, runPublish} from '../../source/npm/publish.js';\n\ntest('no options set', t => {\n\tt.deepEqual(\n\t\tgetPackagePublishArguments({}),\n\t\t['publish'],\n\t);\n});\n\ntest('options.tag', t => {\n\tt.deepEqual(\n\t\tgetPackagePublishArguments({tag: 'beta'}),\n\t\t['publish', '--tag', 'beta'],\n\t);\n});\n\ntest('options.otp', t => {\n\tt.deepEqual(\n\t\tgetPackagePublishArguments({otp: '123456'}),\n\t\t['publish', '--otp', '123456'],\n\t);\n});\n\ntest('options.publishScoped', t => {\n\tt.deepEqual(\n\t\tgetPackagePublishArguments({publishScoped: true}),\n\t\t['publish', '--access', 'public'],\n\t);\n});\n\ntest('options.provenance', t => {\n\tt.deepEqual(\n\t\tgetPackagePublishArguments({provenance: true}),\n\t\t['publish', '--provenance'],\n\t);\n});\n\ntest('runPublish uses cwd option when provided', async t => {\n\tconst observable = runPublish(['echo', ['test']], {cwd: '/tmp'});\n\t// Should complete successfully\n\tawait t.notThrowsAsync(firstValueFrom(observable));\n});\n\ntest('runPublish returns an Observable that completes successfully', async t => {\n\tconst observable = runPublish(['echo', ['test']]);\n\tt.not(observable, undefined);\n\t// Process should complete successfully with our default options\n\tawait t.notThrowsAsync(firstValueFrom(observable));\n});\n"
  },
  {
    "path": "test/npm/util/check-connection.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../../source/npm/util.js')>>} */\nconst createFixture = _createFixture('../../../source/npm/util.js', import.meta.url);\n\ntest('success', createFixture, [{\n\tcommand: 'npm ping',\n\texitCode: 0,\n\toptions: {timeout: 15_000},\n}], async ({t, testedModule: npm}) => {\n\tt.true(await npm.checkConnection());\n});\n\ntest('fail', createFixture, [{\n\tcommand: 'npm ping',\n\texitCode: 1,\n}], async ({t, testedModule: npm}) => {\n\tawait t.throwsAsync(\n\t\tnpm.checkConnection(),\n\t\t{message: 'Connection to npm registry failed'},\n\t);\n});\n\ntest('timeout', createFixture, [{\n\tcommand: 'npm ping',\n\texitCode: 1,\n\ttimedOut: true,\n}], async ({t, testedModule: npm}) => {\n\tawait t.throwsAsync(\n\t\tnpm.checkConnection(),\n\t\t{message: 'Connection to npm registry timed out'},\n\t);\n});\n"
  },
  {
    "path": "test/npm/util/check-ignore-strategy.js",
    "content": "import path from 'node:path';\nimport test from 'ava';\nimport esmock from 'esmock';\nimport stripAnsi from 'strip-ansi';\nimport {oneLine} from 'common-tags';\n\nconst checkIgnoreStrategy = test.macro(async (t, {fixture = '', files, expected = ''} = {}) => {\n\tlet output = '';\n\n\t/** @type {import('../../../source/npm/util.js')} */\n\tconst {checkIgnoreStrategy} = await esmock('../../../source/npm/util.js', {\n\t\timport: {console: {log: (...arguments_) => output = arguments_.join('')}}, // eslint-disable-line no-return-assign\n\t});\n\n\tconst fixtureDirectory = path.resolve('test/fixtures/files', fixture);\n\tconst package_ = files ? {files} : {};\n\n\tawait checkIgnoreStrategy(package_, fixtureDirectory);\n\n\toutput = stripAnsi(output).trim();\n\tt.is(output, expected);\n});\n\nconst ignoreStrategyMessage = oneLine`\n\tWarning: No files field specified in package.json nor is a .npmignore file present.\n\tHaving one of those will prevent you from accidentally publishing development-specific files along with your package's source code to npm.\n`;\n\ntest('no files, no .npmignore', checkIgnoreStrategy, {fixture: 'main', expected: ignoreStrategyMessage});\n\ntest('no files w/ .npmignore', checkIgnoreStrategy, {fixture: 'npmignore', expected: ''});\n\ntest('files, no .npmignore', checkIgnoreStrategy, {fixture: 'main', files: ['index.js'], expected: ''});\n\ntest('files w/ .npmignore', checkIgnoreStrategy, {fixture: 'npmignore', files: ['index.js'], expected: ''});\n"
  },
  {
    "path": "test/npm/util/collaborators.js",
    "content": "import test from 'ava';\nimport {stripIndent} from 'common-tags';\nimport {_createFixture} from '../../_helpers/stub-execa.js';\nimport * as npm from '../../../source/npm/util.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../../source/npm/util.js')>>} */\nconst createFixture = _createFixture('../../../source/npm/util.js', import.meta.url);\n\ntest('package.name not a string', async t => {\n\tawait t.throwsAsync(\n\t\tnpm.collaborators({name: 1}),\n\t\t{message: 'Package name is required'},\n\t);\n});\n\nconst accessCommand = (name = 'np') => `npm access list collaborators ${name} --json`;\n\nconst collaboratorsStdout = stripIndent`\n\t{\n\t\t\"sindresorhus\": \"read-write\",\n\t\t\"samverschueren\": \"read-write\",\n\t\t\"itaisteinherz\": \"read-write\"\n\t}\n`;\n\ntest('main', createFixture, [{\n\tcommand: accessCommand(),\n\tstdout: collaboratorsStdout,\n}], async ({t, testedModule: {collaborators}}) => {\n\tt.deepEqual(\n\t\tawait collaborators({name: 'np'}),\n\t\tcollaboratorsStdout,\n\t);\n});\n\n// TODO: this is timing out, seemingly the command isn't matching for Sinon\n// eslint-disable-next-line ava/no-skip-test\ntest.skip('external registry', createFixture, [{\n\tcommand: `${accessCommand()} --registry http://my-internal-registry.local`,\n\tstdout: collaboratorsStdout,\n}], async ({t, testedModule: {collaborators}}) => {\n\tconst output = await collaborators({\n\t\tname: 'np',\n\t\tpublishConfig: {\n\t\t\tregistry: 'http://my-internal-registry.local',\n\t\t},\n\t});\n\n\tt.deepEqual(\n\t\tJSON.parse(output),\n\t\tJSON.parse(collaboratorsStdout),\n\t);\n});\n\ntest('non-existent', createFixture, [{\n\tcommand: accessCommand('non-existent'),\n\tstderr: 'npm ERR! code E404\\nnpm ERR! 404 Not Found',\n}], async ({t, testedModule: {collaborators}}) => {\n\tt.is(\n\t\tawait collaborators({name: 'non-existent'}),\n\t\tfalse,\n\t);\n});\n\ntest('error on default registry', createFixture, [{\n\tcommand: accessCommand('@private/pkg'),\n\tstderr: 'npm ERR! code E403\\nnpm ERR! 403 403 Forbidden',\n}], async ({t, testedModule: {collaborators}}) => {\n\tconst {stderr} = await t.throwsAsync(collaborators({name: '@private/pkg'}));\n\tt.is(stderr, 'npm ERR! code E403\\nnpm ERR! 403 403 Forbidden');\n});\n\ntest('error on external registry - returns false', createFixture, [{\n\tcommand: `${accessCommand('@private/pkg')} --registry http://my-internal-registry.local`,\n\tstderr: 'npm ERR! code E403\\nnpm ERR! 403 403 Forbidden',\n}], async ({t, testedModule: {collaborators}}) => {\n\t// Errors should return false instead of throwing, since external registries\n\t// often don't support the collaborators endpoint.\n\t// See: https://github.com/sindresorhus/np/issues/420\n\tt.is(await collaborators({\n\t\tname: '@private/pkg',\n\t\tpublishConfig: {\n\t\t\tregistry: 'http://my-internal-registry.local',\n\t\t},\n\t}), false);\n});\n"
  },
  {
    "path": "test/npm/util/entry-points.js",
    "content": "import path from 'node:path';\nimport test from 'ava';\nimport * as npm from '../../../source/npm/util.js';\n\nconst getFixture = name => path.resolve('test', 'fixtures', 'files', name);\n\ntest('getPackageEntryPoints - main', t => {\n\tt.deepEqual(\n\t\tnpm.getPackageEntryPoints({main: 'index.js'}),\n\t\t[{field: 'main', path: 'index.js'}],\n\t);\n\n\tt.deepEqual(\n\t\tnpm.getPackageEntryPoints({main: './dist/index.js'}),\n\t\t[{field: 'main', path: './dist/index.js'}],\n\t);\n});\n\ntest('getPackageEntryPoints - bin as string', t => {\n\tt.deepEqual(\n\t\tnpm.getPackageEntryPoints({name: 'my-cli', bin: './cli.js'}),\n\t\t[{field: 'bin', path: './cli.js'}],\n\t);\n});\n\ntest('getPackageEntryPoints - bin as object', t => {\n\tt.deepEqual(\n\t\tnpm.getPackageEntryPoints({bin: {foo: './bin/foo.js', bar: './bin/bar.js'}}),\n\t\t[\n\t\t\t{field: 'bin.foo', path: './bin/foo.js'},\n\t\t\t{field: 'bin.bar', path: './bin/bar.js'},\n\t\t],\n\t);\n});\n\ntest('getPackageEntryPoints - exports as string', t => {\n\tt.deepEqual(\n\t\tnpm.getPackageEntryPoints({exports: './index.js'}),\n\t\t[{field: 'exports', path: './index.js'}],\n\t);\n});\n\ntest('getPackageEntryPoints - exports with subpaths', t => {\n\tconst entryPoints = npm.getPackageEntryPoints({\n\t\texports: {\n\t\t\t'.': './index.js',\n\t\t\t'./foo': './foo.js',\n\t\t},\n\t});\n\n\tt.deepEqual(entryPoints, [\n\t\t{field: 'exports', path: './index.js'},\n\t\t{field: 'exports', path: './foo.js'},\n\t]);\n});\n\ntest('getPackageEntryPoints - exports with conditions', t => {\n\tconst entryPoints = npm.getPackageEntryPoints({\n\t\texports: {\n\t\t\t'.': {\n\t\t\t\timport: './index.mjs',\n\t\t\t\trequire: './index.cjs',\n\t\t\t},\n\t\t},\n\t});\n\n\tt.deepEqual(entryPoints, [\n\t\t{field: 'exports', path: './index.mjs'},\n\t\t{field: 'exports', path: './index.cjs'},\n\t]);\n});\n\ntest('getPackageEntryPoints - exports with nested conditions', t => {\n\tconst entryPoints = npm.getPackageEntryPoints({\n\t\texports: {\n\t\t\t'.': {\n\t\t\t\tnode: {\n\t\t\t\t\timport: './index.node.mjs',\n\t\t\t\t\trequire: './index.node.cjs',\n\t\t\t\t},\n\t\t\t\tdefault: './index.js',\n\t\t\t},\n\t\t},\n\t});\n\n\tt.deepEqual(entryPoints, [\n\t\t{field: 'exports', path: './index.node.mjs'},\n\t\t{field: 'exports', path: './index.node.cjs'},\n\t\t{field: 'exports', path: './index.js'},\n\t]);\n});\n\ntest('getPackageEntryPoints - combined main, bin, and exports', t => {\n\tconst entryPoints = npm.getPackageEntryPoints({\n\t\tmain: './index.js',\n\t\tbin: './cli.js',\n\t\texports: {\n\t\t\t'.': './index.js',\n\t\t\t'./cli': './cli.js',\n\t\t},\n\t});\n\n\tt.deepEqual(entryPoints, [\n\t\t{field: 'main', path: './index.js'},\n\t\t{field: 'bin', path: './cli.js'},\n\t\t{field: 'exports', path: './index.js'},\n\t\t{field: 'exports', path: './cli.js'},\n\t]);\n});\n\ntest('getPackageEntryPoints - empty package', t => {\n\tt.deepEqual(npm.getPackageEntryPoints({}), []);\n\tt.deepEqual(npm.getPackageEntryPoints({name: 'foo', version: '1.0.0'}), []);\n});\n\ntest('getPackageEntryPoints - exports with wildcard patterns are skipped', t => {\n\tconst entryPoints = npm.getPackageEntryPoints({\n\t\texports: {\n\t\t\t'.': './index.js',\n\t\t\t'./features/*.js': './src/features/*.js',\n\t\t\t'./utils/*': './src/utils/*',\n\t\t},\n\t});\n\n\t// Only non-wildcard exports should be included\n\tt.deepEqual(entryPoints, [\n\t\t{field: 'exports', path: './index.js'},\n\t]);\n});\n\ntest('getPackageEntryPoints - exports with null values are skipped', t => {\n\tconst entryPoints = npm.getPackageEntryPoints({\n\t\texports: {\n\t\t\t'.': './index.js',\n\t\t\t'./internal/*': null,\n\t\t},\n\t});\n\n\tt.deepEqual(entryPoints, [\n\t\t{field: 'exports', path: './index.js'},\n\t]);\n});\n\ntest('getPackageEntryPoints - invalid main values are skipped', t => {\n\tt.deepEqual(npm.getPackageEntryPoints({main: null}), []);\n\tt.deepEqual(npm.getPackageEntryPoints({main: 123}), []);\n\tt.deepEqual(npm.getPackageEntryPoints({main: {}}), []);\n});\n\ntest('getPackageEntryPoints - invalid bin values are skipped', t => {\n\tt.deepEqual(npm.getPackageEntryPoints({bin: null}), []);\n\tt.deepEqual(npm.getPackageEntryPoints({bin: 123}), []);\n\tt.deepEqual(npm.getPackageEntryPoints({bin: {foo: null, bar: './bar.js'}}), [\n\t\t{field: 'bin.bar', path: './bar.js'},\n\t]);\n});\n\ntest('getPackageEntryPoints - duplicate paths from main and exports', t => {\n\tconst entryPoints = npm.getPackageEntryPoints({\n\t\tmain: './index.js',\n\t\texports: './index.js',\n\t});\n\n\t// Both are returned (deduplication happens in verifyPackageEntryPoints)\n\tt.deepEqual(entryPoints, [\n\t\t{field: 'main', path: './index.js'},\n\t\t{field: 'exports', path: './index.js'},\n\t]);\n});\n\ntest('verifyPackageEntryPoints - missing main', async t => {\n\tconst fixtureDirectory = getFixture('missing-main');\n\n\tawait t.throwsAsync(\n\t\tnpm.verifyPackageEntryPoints({main: 'dist/index.js'}, fixtureDirectory),\n\t\t{message: /Missing entry points.*\"main\": dist\\/index\\.js/s},\n\t);\n});\n\ntest('verifyPackageEntryPoints - missing bin', async t => {\n\tconst fixtureDirectory = getFixture('missing-bin');\n\n\tawait t.throwsAsync(\n\t\tnpm.verifyPackageEntryPoints({bin: './cli.js'}, fixtureDirectory),\n\t\t{message: /Missing entry points.*\"bin\": \\.\\/cli\\.js/s},\n\t);\n});\n\ntest('verifyPackageEntryPoints - valid entry points', async t => {\n\tconst fixtureDirectory = getFixture('one-file');\n\n\tawait t.notThrowsAsync(npm.verifyPackageEntryPoints({main: 'index.js'}, fixtureDirectory));\n});\n"
  },
  {
    "path": "test/npm/util/is-external-registry.js",
    "content": "import test from 'ava';\nimport * as npm from '../../../source/npm/util.js';\n\ntest('main', t => {\n\tt.true(npm.isExternalRegistry({publishConfig: {registry: 'https://my-internal-registry.local'}}));\n\n\tt.false(npm.isExternalRegistry({name: 'foo'}));\n\tt.false(npm.isExternalRegistry({publishConfig: {registry: true}}));\n\tt.false(npm.isExternalRegistry({publishConfig: 'not an object'}));\n\tt.false(npm.isExternalRegistry({publishConfig: {registry: 'https://registry.npmjs.org'}}));\n\tt.false(npm.isExternalRegistry({publishConfig: {registry: 'https://registry.npmjs.org/'}}));\n\n\t// Test normalization: whitespace trimming\n\tt.false(npm.isExternalRegistry({publishConfig: {registry: ' https://registry.npmjs.org '}}));\n\tt.false(npm.isExternalRegistry({publishConfig: {registry: '\thttps://registry.npmjs.org/\t'}}));\n\n\t// Test normalization: http variant\n\tt.false(npm.isExternalRegistry({publishConfig: {registry: 'http://registry.npmjs.org'}}));\n\tt.false(npm.isExternalRegistry({publishConfig: {registry: 'http://registry.npmjs.org/'}}));\n});\n"
  },
  {
    "path": "test/npm/util/is-package-name-available.js",
    "content": "import test from 'ava';\nimport esmock from 'esmock';\nimport sinon from 'sinon';\n\nconst externalRegistry = 'http://my-internal-registry.local';\n\nconst createFixture = test.macro(async (t, {name = 'foo', npmNameStub, expected, isExternalRegistry = false}) => {\n\t/** @type {import('../../../source/npm/util.js')} */\n\tconst npm = await esmock('../../../source/npm/util.js', {\n\t\t'npm-name': npmNameStub,\n\t});\n\n\tconst package_ = isExternalRegistry\n\t\t? {name, publishConfig: {registry: externalRegistry}}\n\t\t: {name};\n\n\tconst availability = await npm.isPackageNameAvailable(package_);\n\tt.like(availability, expected);\n});\n\ntest('available', createFixture, {\n\tnpmNameStub: sinon.stub().resolves(true),\n\texpected: {isAvailable: true, isUnknown: false},\n});\n\ntest('unavailable', createFixture, {\n\tnpmNameStub: sinon.stub().resolves(false),\n\texpected: {isAvailable: false, isUnknown: false},\n});\n\ntest('bad package name', createFixture, {\n\tname: '_foo',\n\tnpmNameStub: sinon.stub().rejects('Invalid package name: _foo\\n- name cannot start with an underscore'),\n\texpected: {isAvailable: false, isUnknown: true},\n});\n\ntest('external registry', createFixture, {\n\tname: 'external-foo',\n\tisExternalRegistry: true,\n\tnpmNameStub: async (name, {registryUrl}) => name === 'external-foo' && registryUrl === externalRegistry,\n\texpected: {isAvailable: true, isUnknown: false},\n});\n"
  },
  {
    "path": "test/npm/util/login.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../../source/npm/util.js')>>} */\nconst createFixture = _createFixture('../../../source/npm/util.js', import.meta.url);\n\ntest('main', createFixture, [{\n\tcommand: 'npm login',\n\tstdout: '',\n}], async ({t, testedModule: npm}) => {\n\tawait t.notThrowsAsync(npm.login({}));\n});\n\ntest('--registry flag', createFixture, [{\n\tcommand: 'npm login --registry http://my.io',\n\tstdout: '',\n}], async ({t, testedModule: npm}) => {\n\tawait t.notThrowsAsync(npm.login({externalRegistry: 'http://my.io'}));\n});\n\ntest('fails if login fails', createFixture, [{\n\tcommand: 'npm login',\n\texitCode: 1,\n\tstderr: 'npm ERR! Login failed',\n}], async ({t, testedModule: npm}) => {\n\tawait t.throwsAsync(npm.login({}));\n});\n"
  },
  {
    "path": "test/npm/util/packed-files.js",
    "content": "import path from 'node:path';\nimport test from 'ava';\nimport {getFilesToBePacked} from '../../../source/npm/util.js';\nimport {runIfExists} from '../../_helpers/util.js';\n\nconst getFixture = name => path.resolve('test', 'fixtures', 'files', name);\n\nconst verifyPackedFiles = test.macro(async (t, fixture, expectedFiles, {before, after} = {}) => {\n\tconst fixtureDirectory = getFixture(fixture);\n\n\tawait runIfExists(before, fixtureDirectory);\n\tt.teardown(async () => runIfExists(after, fixtureDirectory));\n\n\tconst files = await getFilesToBePacked(fixtureDirectory);\n\tt.deepEqual(files.sort(), [...expectedFiles, 'package.json'].sort(), 'Files different from expectations!');\n});\n\ntest('package.json files field - one file', verifyPackedFiles, 'one-file', [\n\t'index.js',\n]);\n\ntest('package.json files field - source dir', verifyPackedFiles, 'source-dir', [\n\t'source/foo.js',\n\t'source/bar.js',\n]);\n\ntest('package.json files field - source and dist dirs', verifyPackedFiles, 'source-and-dist-dir', [\n\t'source/foo.js',\n\t'source/bar.js',\n]);\n\ntest('package.json files field - leading slash', verifyPackedFiles, 'files-slash', [\n\t'index.js',\n]);\n\ntest('package.json files field - has readme and license', verifyPackedFiles, 'has-readme-and-license', [\n\t'readme.md',\n\t'license.md',\n\t'index.js',\n]);\n\ntest('npmignore', verifyPackedFiles, 'npmignore', [\n\t'readme.md',\n\t'index.js',\n\t'index.d.ts',\n]);\n\ntest('package.json files field and npmignore', verifyPackedFiles, 'files-and-npmignore', [\n\t'readme.md',\n\t'source/foo.js',\n\t'source/bar.js',\n\t'source/index.d.ts',\n]);\n\ntest('package.json files field and gitignore', verifyPackedFiles, 'gitignore', [\n\t'readme.md',\n\t'dist/index.js',\n]);\n\ntest('npmignore and gitignore', verifyPackedFiles, 'npmignore-and-gitignore', [\n\t'readme.md',\n\t'dist/index.js',\n]);\n\ntest('package.json main field not in files field', verifyPackedFiles, 'main', [\n\t'foo.js',\n\t'bar.js',\n]);\n\ntest('doesn\\'t show files in .github', verifyPackedFiles, 'dot-github', [\n\t'index.js',\n]);\n\ntest('handles prepare script output (e.g., Husky)', verifyPackedFiles, 'prepare-script', [\n\t'index.js',\n]);\n"
  },
  {
    "path": "test/npm/util/prerelease-tags.js",
    "content": "import test from 'ava';\nimport {stripIndent} from 'common-tags';\nimport {_createFixture} from '../../_helpers/stub-execa.js';\nimport * as npm from '../../../source/npm/util.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../../source/npm/util.js')>>} */\nconst createFixture = _createFixture('../../../source/npm/util.js', import.meta.url);\n\ntest('packageName not a string', async t => {\n\tawait t.throwsAsync(\n\t\tnpm.prereleaseTags(1),\n\t\t{message: 'Package name is required'},\n\t);\n});\n\ntest('tags: latest', createFixture, [{\n\tcommand: 'npm view --json foo dist-tags',\n\tstdout: JSON.stringify({\n\t\tlatest: '1.0.0',\n\t}),\n}], async ({t, testedModule: {prereleaseTags}}) => {\n\tt.deepEqual(\n\t\tawait prereleaseTags('foo'),\n\t\t['next'],\n\t);\n});\n\ntest('tags: latest, beta', createFixture, [{\n\tcommand: 'npm view --json foo dist-tags',\n\tstdout: JSON.stringify({\n\t\tlatest: '1.0.0',\n\t\tbeta: '2.0.0-beta',\n\t}),\n}], async ({t, testedModule: {prereleaseTags}}) => {\n\tt.deepEqual(\n\t\tawait prereleaseTags('foo'),\n\t\t['beta'],\n\t);\n});\n\ntest('non-existent (code 404) - should not throw', createFixture, [{\n\tcommand: 'npm view --json non-existent dist-tags',\n\tstderr: stripIndent`\n\t\tnpm ERR! code E404\n\t\tnpm ERR! 404 Not Found - GET https://registry.npmjs.org/non-existent - Not found\n\t\tnpm ERR! 404\n\t\tnpm ERR! 404  'non-existent@*' is not in this registry.\n\t\tnpm ERR! 404\n\t\tnpm ERR! 404 Note that you can also install from a\n\t\tnpm ERR! 404 tarball, folder, http url, or git url.\n\t\t{\n\t\t\t\"error\": {\n\t\t\t\t\"code\": \"E404\",\n\t\t\t\t\"summary\": \"Not Found - GET https://registry.npmjs.org/non-existent - Not found\",\n\t\t\t\t\"detail\": \"'non-existent@*' is not in this registry. Note that you can also install from a tarball, folder, http url, or git url.\"\n\t\t\t}\n\t\t}\n\t\tnpm ERR! A complete log of this run can be found in:\n\t\tnpm ERR!     ~/.npm/_logs/...-debug.log\n\t`,\n}], async ({t, testedModule: {prereleaseTags}}) => {\n\tt.deepEqual(\n\t\tawait prereleaseTags('non-existent'),\n\t\t['next'],\n\t);\n});\n\ntest('non-existent with modern npm format (npm >=10) - should not throw', createFixture, [{\n\tcommand: 'npm view --json non-existent dist-tags',\n\tstderr: stripIndent`\n\t\tnpm error code E404\n\t\tnpm error 404 Not Found - GET https://registry.npmjs.org/non-existent - Not found\n\t\tnpm error 404\n\t\tnpm error 404  The requested resource 'non-existent@*' could not be found or you do not have permission to access it.\n\t\tnpm error 404\n\t\tnpm error 404 Note that you can also install from a\n\t\tnpm error 404 tarball, folder, http url, or git url.\n\t\t{\n\t\t\t\"error\": {\n\t\t\t\t\"code\": \"E404\",\n\t\t\t\t\"summary\": \"Not Found - GET https://registry.npmjs.org/non-existent - Not found\",\n\t\t\t\t\"detail\": \"The requested resource 'non-existent@*' could not be found.\"\n\t\t\t}\n\t\t}\n\t\tnpm error A complete log of this run can be found in: ~/.npm/_logs/...-debug.log\n\t`,\n}], async ({t, testedModule: {prereleaseTags}}) => {\n\tt.deepEqual(\n\t\tawait prereleaseTags('non-existent'),\n\t\t['next'],\n\t);\n});\n\ntest('bad permission (code 403) - should throw', createFixture, [{\n\tcommand: 'npm view --json @private/pkg dist-tags',\n\tstderr: stripIndent`\n\t\tnpm ERR! code E403\n\t\tnpm ERR! 403 403 Forbidden - GET https://registry.npmjs.org/@private%2fpkg - Forbidden\n\t\tnpm ERR! 403 In most cases, you or one of your dependencies are requesting\n\t\tnpm ERR! 403 a package version that is forbidden by your security policy, or\n\t\tnpm ERR! 403 on a server you do not have access to.\n\t\t{\n\t\t\t\"error\": {\n\t\t\t\t\"code\": \"E403\",\n\t\t\t\t\"summary\": \"403 Forbidden - GET https://registry.npmjs.org/@private%2fpkg - Forbidden\",\n\t\t\t\t\"detail\": \"In most cases, you or one of your dependencies are requesting a package version that is forbidden by your security policy, or on a server you do not have access to.\"\n\t\t\t}\n\t\t}\n\t\tnpm ERR! A complete log of this run can be found in:\n\t\tnpm ERR!     ~/.npm/_logs/...-debug.log\n\t`,\n}], async ({t, testedModule: {prereleaseTags}}) => {\n\tconst error = await t.throwsAsync(prereleaseTags('@private/pkg'));\n\tt.true(error.stderr?.includes('E403'));\n});\n"
  },
  {
    "path": "test/npm/util/username.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../../source/npm/util.js')>>} */\nconst createFixture = _createFixture('../../../source/npm/util.js', import.meta.url);\n\ntest('main', createFixture, [{\n\tcommand: 'npm whoami',\n\tstdout: 'sindresorhus',\n}], async ({t, testedModule: npm}) => {\n\tt.is(await npm.username({}), 'sindresorhus');\n});\n\ntest('--registry flag', createFixture, [{\n\tcommand: 'npm whoami --registry http://my.io',\n\tstdout: 'sindresorhus',\n}], async ({t, testedModule: npm}) => {\n\tt.is(await npm.username({externalRegistry: 'http://my.io'}), 'sindresorhus');\n});\n\ntest('fails if not logged in - ENEEDAUTH', createFixture, [{\n\tcommand: 'npm whoami',\n\tstderr: 'npm ERR! code ENEEDAUTH',\n}], async ({t, testedModule: npm}) => {\n\tconst error = await t.throwsAsync(\n\t\tnpm.username({}),\n\t\t{message: 'You must be logged in. Use `npm login` and try again.'},\n\t);\n\tt.true(error.isNotLoggedIn);\n});\n\ntest('fails if not logged in - E401', createFixture, [{\n\tcommand: 'npm whoami',\n\tstderr: 'npm error code E401\\nnpm error 401 Unauthorized',\n}], async ({t, testedModule: npm}) => {\n\tconst error = await t.throwsAsync(\n\t\tnpm.username({}),\n\t\t{message: 'You must be logged in. Use `npm login` and try again.'},\n\t);\n\tt.true(error.isNotLoggedIn);\n});\n\ntest('fails with authentication error', createFixture, [{\n\tcommand: 'npm whoami',\n\tstderr: 'npm ERR! OTP required for authentication',\n}], async ({t, testedModule: npm}) => {\n\tconst error = await t.throwsAsync(\n\t\tnpm.username({}),\n\t\t{message: 'Authentication error. Use `npm whoami` to troubleshoot.'},\n\t);\n\tt.false(error.isNotLoggedIn);\n});\n"
  },
  {
    "path": "test/npm/util/verify-recent-npm-version.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../../source/npm/util.js')>>} */\nconst createFixture = _createFixture('../../../source/npm/util.js', import.meta.url);\n\ntest('satisfied', createFixture, [{\n\tcommand: 'npm --version',\n\tstdout: '99.20.0', // Higher than minimum\n}], async ({t, testedModule: npm}) => {\n\tawait t.notThrowsAsync(npm.verifyRecentNpmVersion());\n});\n\ntest('not satisfied', createFixture, [{\n\tcommand: 'npm --version',\n\tstdout: '5.18.0', // Lower than minimum\n}], async ({t, testedModule: npm}) => {\n\tawait t.throwsAsync(\n\t\tnpm.verifyRecentNpmVersion(),\n\t\t{message: /`np` requires npm >=/},\n\t);\n});\n"
  },
  {
    "path": "test/package-manager.js",
    "content": "import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport test from 'ava';\nimport {getPackageManagerConfig} from '../source/package-manager/index.js';\nimport {\n\tnpmConfig,\n\tyarnConfig,\n\tyarnBerryConfig,\n\tpnpmConfig,\n} from '../source/package-manager/configs.js';\n\ntest('detects npm from package-lock.json', t => {\n\tconst temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'np-test-'));\n\tfs.writeFileSync(path.join(temporaryDirectory, 'package-lock.json'), '');\n\n\tconst config = getPackageManagerConfig(temporaryDirectory, {name: 'test', version: '1.0.0'});\n\n\tt.is(config, npmConfig);\n\tfs.rmSync(temporaryDirectory, {recursive: true});\n});\n\ntest('detects pnpm from pnpm-lock.yaml', t => {\n\tconst temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'np-test-'));\n\tfs.writeFileSync(path.join(temporaryDirectory, 'pnpm-lock.yaml'), '');\n\n\tconst config = getPackageManagerConfig(temporaryDirectory, {name: 'test', version: '1.0.0'});\n\n\tt.is(config, pnpmConfig);\n\tfs.rmSync(temporaryDirectory, {recursive: true});\n});\n\ntest('detects Yarn Classic from yarn.lock without .yarnrc.yml', t => {\n\tconst temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'np-test-'));\n\tfs.writeFileSync(path.join(temporaryDirectory, 'yarn.lock'), '');\n\n\tconst config = getPackageManagerConfig(temporaryDirectory, {name: 'test', version: '1.0.0'});\n\n\tt.is(config, yarnConfig);\n\tfs.rmSync(temporaryDirectory, {recursive: true});\n});\n\ntest('detects Yarn Berry from yarn.lock with .yarnrc.yml', t => {\n\tconst temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'np-test-'));\n\tfs.writeFileSync(path.join(temporaryDirectory, 'yarn.lock'), '');\n\tfs.writeFileSync(path.join(temporaryDirectory, '.yarnrc.yml'), 'nodeLinker: node-modules\\n');\n\n\tconst config = getPackageManagerConfig(temporaryDirectory, {name: 'test', version: '1.0.0'});\n\n\tt.is(config, yarnBerryConfig);\n\tfs.rmSync(temporaryDirectory, {recursive: true});\n});\n\ntest('detects Yarn Berry from packageManager field', t => {\n\tconst temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'np-test-'));\n\n\tconst config = getPackageManagerConfig(temporaryDirectory, {\n\t\tname: 'test',\n\t\tversion: '1.0.0',\n\t\tpackageManager: 'yarn@3.0.0',\n\t});\n\n\tt.is(config, yarnBerryConfig);\n\tfs.rmSync(temporaryDirectory, {recursive: true});\n});\n\ntest('detects Yarn Classic from packageManager field', t => {\n\tconst temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'np-test-'));\n\n\tconst config = getPackageManagerConfig(temporaryDirectory, {\n\t\tname: 'test',\n\t\tversion: '1.0.0',\n\t\tpackageManager: 'yarn@1.22.0',\n\t});\n\n\tt.is(config, yarnConfig);\n\tfs.rmSync(temporaryDirectory, {recursive: true});\n});\n"
  },
  {
    "path": "test/release-task-helper.js",
    "content": "import test from 'ava';\nimport sinon from 'sinon';\nimport esmock from 'esmock';\n\nconst verifyRelease = test.macro(async (t, {oldVersion, newVersion, prefixes = {}, like}) => {\n\tconst repoUrl = 'https://github.com/sindresorhus/np';\n\n\t/** @type {import('../source/release-task-helper.js')} */\n\tconst {default: releaseTaskHelper} = await esmock('../source/release-task-helper.js', import.meta.url, {\n\t\topen: sinon.stub(),\n\t\tclipboardy: {\n\t\t\twrite: sinon.stub(),\n\t\t},\n\t\t'../source/util.js': {\n\t\t\tgetTagVersionPrefix: async () => prefixes.tag ?? 'v',\n\t\t\tgetPreReleasePrefix: async () => prefixes.preRelease ?? '',\n\t\t},\n\t\t'new-github-release-url': options_ => t.like(options_, {repoUrl, ...like}),\n\t});\n\n\tawait releaseTaskHelper(\n\t\t{\n\t\t\tversion: newVersion,\n\t\t\trepoUrl,\n\t\t\treleaseNotes: true,\n\t\t\tgenerateReleaseNotes: sinon.stub(),\n\t\t},\n\t\t{version: oldVersion},\n\t);\n});\n\n// TODO: test `body`\n\ntest('main', verifyRelease, {\n\toldVersion: '1.0.0',\n\tnewVersion: '1.1.0',\n\tlike: {\n\t\ttag: 'v1.1.0',\n\t\tisPrerelease: false,\n\t},\n});\n\ntest('handles increment as new version', verifyRelease, {\n\toldVersion: '1.0.0',\n\tnewVersion: 'minor',\n\tlike: {\n\t\ttag: 'v1.1.0',\n\t\tisPrerelease: false,\n\t},\n});\n\ntest('uses resolved prefix', verifyRelease, {\n\toldVersion: '1.0.0',\n\tnewVersion: '1.1.0',\n\tprefixes: {tag: 'ver'},\n\tlike: {\n\t\ttag: 'ver1.1.0',\n\t},\n});\n\ntest('prerelease', verifyRelease, {\n\toldVersion: '1.0.0',\n\tnewVersion: 'prerelease',\n\tprefixes: {preRelease: 'beta'},\n\tlike: {\n\t\ttag: 'v1.0.1-beta.0',\n\t\tisPrerelease: true,\n\t},\n});\n\ntest('uses clipboard when URL is too long', async t => {\n\tconst repoUrl = 'https://github.com/sindresorhus/np';\n\tconst clipboardStub = sinon.stub();\n\tconst openStub = sinon.stub();\n\tconst urlCalls = [];\n\n\t// Generate a very long release notes string that will exceed the URL limit\n\tconst longReleaseNotes = 'x'.repeat(8000);\n\n\tconst {default: releaseTaskHelper} = await esmock('../source/release-task-helper.js', import.meta.url, {\n\t\topen: openStub,\n\t\tclipboardy: {\n\t\t\twrite: clipboardStub,\n\t\t},\n\t\t'../source/util.js': {\n\t\t\tasync getTagVersionPrefix() {\n\t\t\t\treturn 'v';\n\t\t\t},\n\t\t\tasync getPreReleasePrefix() {\n\t\t\t\treturn '';\n\t\t\t},\n\t\t},\n\t\t'new-github-release-url'(options_) {\n\t\t\turlCalls.push(options_);\n\t\t\t// Generate a realistic URL\n\t\t\tconst baseUrl = `${options_.repoUrl}/releases/new`;\n\t\t\tconst parameters = new URLSearchParams({\n\t\t\t\ttag: options_.tag,\n\t\t\t\tbody: options_.body,\n\t\t\t\tprerelease: options_.isPrerelease ? '1' : '0',\n\t\t\t});\n\t\t\treturn `${baseUrl}?${parameters.toString()}`;\n\t\t},\n\t});\n\n\tawait releaseTaskHelper(\n\t\t{\n\t\t\tversion: '1.1.0',\n\t\t\trepoUrl,\n\t\t\treleaseNotes: true,\n\t\t\tgenerateReleaseNotes: () => longReleaseNotes,\n\t\t},\n\t\t{version: '1.0.0'},\n\t);\n\n\t// Should be called twice: once with long notes, once with placeholder\n\tt.is(urlCalls.length, 2);\n\tt.is(urlCalls[0].body, longReleaseNotes);\n\tt.is(urlCalls[1].body, '<!-- Paste release notes from clipboard -->');\n\tt.true(clipboardStub.calledOnce);\n\tt.true(clipboardStub.calledWith(longReleaseNotes));\n\tt.true(openStub.calledOnce);\n});\n\ntest('does not use clipboard when URL is short enough', async t => {\n\tconst repoUrl = 'https://github.com/sindresorhus/np';\n\tconst clipboardStub = sinon.stub();\n\tconst openStub = sinon.stub();\n\tconst urlCalls = [];\n\n\tconst shortReleaseNotes = 'Short release notes';\n\n\tconst {default: releaseTaskHelper} = await esmock('../source/release-task-helper.js', import.meta.url, {\n\t\topen: openStub,\n\t\tclipboardy: {\n\t\t\twrite: clipboardStub,\n\t\t},\n\t\t'../source/util.js': {\n\t\t\tasync getTagVersionPrefix() {\n\t\t\t\treturn 'v';\n\t\t\t},\n\t\t\tasync getPreReleasePrefix() {\n\t\t\t\treturn '';\n\t\t\t},\n\t\t},\n\t\t'new-github-release-url'(options_) {\n\t\t\turlCalls.push(options_);\n\t\t\t// Generate a realistic URL\n\t\t\tconst baseUrl = `${options_.repoUrl}/releases/new`;\n\t\t\tconst parameters = new URLSearchParams({\n\t\t\t\ttag: options_.tag,\n\t\t\t\tbody: options_.body,\n\t\t\t\tprerelease: options_.isPrerelease ? '1' : '0',\n\t\t\t});\n\t\t\treturn `${baseUrl}?${parameters.toString()}`;\n\t\t},\n\t});\n\n\tawait releaseTaskHelper(\n\t\t{\n\t\t\tversion: '1.1.0',\n\t\t\trepoUrl,\n\t\t\treleaseNotes: true,\n\t\t\tgenerateReleaseNotes: () => shortReleaseNotes,\n\t\t},\n\t\t{version: '1.0.0'},\n\t);\n\n\t// Should be called only once with the short notes\n\tt.is(urlCalls.length, 1);\n\tt.is(urlCalls[0].body, shortReleaseNotes);\n\tt.false(clipboardStub.called);\n\tt.true(openStub.calledOnce);\n});\n"
  },
  {
    "path": "test/tasks/git-tasks.js",
    "content": "import test from 'ava';\nimport {SilentRenderer} from '../_helpers/listr-renderer.js';\nimport {_createFixture} from '../_helpers/stub-execa.js';\nimport {run, assertTaskFailed, assertTaskDoesntExist} from '../_helpers/listr.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/git-tasks.js')>>} */\nconst createFixture = _createFixture('../../source/git-tasks.js', import.meta.url);\n\ntest.afterEach(() => {\n\tSilentRenderer.clearTasks();\n});\n\ntest.serial('should fail when release branch is not specified, current branch is not the release branch, and publishing from any branch not permitted', createFixture, [{\n\tcommand: 'git symbolic-ref --short HEAD',\n\tstdout: 'feature',\n}], async ({t, testedModule: gitTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(gitTasks({branch: 'master'})),\n\t\t{message: 'Not on `master` branch. Use --any-branch to publish anyway, or set a different release branch using --branch.'},\n\t);\n\n\tassertTaskFailed(t, 'Check current branch');\n});\n\ntest.serial('should fail when current branch is not the specified release branch and publishing from any branch not permitted', createFixture, [{\n\tcommand: 'git symbolic-ref --short HEAD',\n\tstdout: 'feature',\n}], async ({t, testedModule: gitTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(gitTasks({branch: 'release'})),\n\t\t{message: 'Not on `release` branch. Use --any-branch to publish anyway, or set a different release branch using --branch.'},\n\t);\n\n\tassertTaskFailed(t, 'Check current branch');\n});\n\ntest.serial('should not fail when current branch not master and publishing from any branch permitted', createFixture, [\n\t{\n\t\tcommand: 'git symbolic-ref --short HEAD',\n\t\tstdout: 'feature',\n\t},\n\t{\n\t\tcommand: 'git status --porcelain',\n\t\tstdout: '',\n\t},\n\t{\n\t\tcommand: 'git rev-parse @{u}',\n\t\texitCode: 0,\n\t},\n\t{\n\t\tcommand: 'git fetch --dry-run',\n\t\texitCode: 0,\n\t},\n\t{\n\t\tcommand: 'git rev-list --count --left-only @{u}...HEAD',\n\t\tstdout: '0',\n\t},\n], async ({t, testedModule: gitTasks}) => {\n\tawait t.notThrowsAsync(run(gitTasks({anyBranch: true})));\n\n\tassertTaskDoesntExist(t, 'Check current branch');\n});\n\ntest.serial('should fail when local working tree modified', createFixture, [\n\t{\n\t\tcommand: 'git symbolic-ref --short HEAD',\n\t\tstdout: 'master',\n\t},\n\t{\n\t\tcommand: 'git status --porcelain',\n\t\tstdout: 'M source/git-tasks.js',\n\t},\n], async ({t, testedModule: gitTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(gitTasks({branch: 'master'})),\n\t\t{message: 'Unclean working tree. Commit or stash changes first.'},\n\t);\n\n\tassertTaskFailed(t, 'Check local working tree');\n});\n\ntest.serial('should not fail when no remote set up', createFixture, [\n\t{\n\t\tcommand: 'git symbolic-ref --short HEAD',\n\t\tstdout: 'master',\n\t},\n\t{\n\t\tcommand: 'git status --porcelain',\n\t\tstdout: '',\n\t},\n\t{\n\t\tcommand: 'git rev-parse @{u}',\n\t\tstderr: 'fatal: no upstream configured for branch \\'master\\'',\n\t},\n], async ({t, testedModule: gitTasks}) => {\n\tawait t.notThrowsAsync(run(gitTasks({branch: 'master'})));\n});\n\ntest.serial('should fail when remote history differs and changes are fetched', createFixture, [\n\t{\n\t\tcommand: 'git symbolic-ref --short HEAD',\n\t\tstdout: 'master',\n\t},\n\t{\n\t\tcommand: 'git status --porcelain',\n\t\tstdout: '',\n\t},\n\t{\n\t\tcommand: 'git rev-parse @{u}',\n\t\texitCode: 0,\n\t},\n\t{\n\t\tcommand: 'git fetch --dry-run',\n\t\texitCode: 0,\n\t},\n\t{\n\t\tcommand: 'git rev-list --count --left-only @{u}...HEAD',\n\t\tstdout: '1', // Has unpulled changes\n\t},\n], async ({t, testedModule: gitTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(gitTasks({branch: 'master'})),\n\t\t{message: 'Remote history differs. Please pull changes.'},\n\t);\n\n\tassertTaskFailed(t, 'Check remote history');\n});\n\ntest.serial('should fail when remote has unfetched changes', createFixture, [\n\t{\n\t\tcommand: 'git symbolic-ref --short HEAD',\n\t\tstdout: 'master',\n\t},\n\t{\n\t\tcommand: 'git status --porcelain',\n\t\tstdout: '',\n\t},\n\t{\n\t\tcommand: 'git rev-parse @{u}',\n\t\texitCode: 0,\n\t},\n\t{\n\t\tcommand: 'git fetch --dry-run',\n\t\tstdout: 'From https://github.com/sindresorhus/np', // Has unfetched changes\n\t},\n], async ({t, testedModule: gitTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(gitTasks({branch: 'master'})),\n\t\t{message: 'Remote history differs. Please run `git fetch` and pull changes.'},\n\t);\n\n\tassertTaskFailed(t, 'Check remote history');\n});\n\ntest.serial('checks should pass when publishing from master, working tree is clean and remote history not different', createFixture, [\n\t{\n\t\tcommand: 'git symbolic-ref --short HEAD',\n\t\tstdout: 'master',\n\t},\n\t{\n\t\tcommand: 'git status --porcelain',\n\t\tstdout: '',\n\t},\n\t{\n\t\tcommand: 'git rev-parse @{u}',\n\t\texitCode: 0,\n\t},\n\t{\n\t\tcommand: 'git fetch --dry-run',\n\t\texitCode: 0,\n\t},\n\t{\n\t\tcommand: 'git rev-list --count --left-only @{u}...HEAD',\n\t\tstdout: '0',\n\t},\n], async ({t, testedModule: gitTasks}) => {\n\tawait t.notThrowsAsync(run(gitTasks({branch: 'master'})));\n});\n"
  },
  {
    "path": "test/tasks/prerequisite-tasks.js",
    "content": "import process from 'node:process';\nimport test from 'ava';\nimport {npmConfig, yarnConfig} from '../../source/package-manager/configs.js';\nimport {npPackage} from '../../source/util.js';\nimport {SilentRenderer} from '../_helpers/listr-renderer.js';\nimport {_createFixture} from '../_helpers/stub-execa.js';\nimport {\n\trun,\n\tassertTaskFailed,\n\tassertTaskDisabled,\n\tassertTaskSkipped,\n} from '../_helpers/listr.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/prerequisite-tasks.js')>>} */\nconst createFixture = _createFixture('../../source/prerequisite-tasks.js', import.meta.url);\n\ntest.beforeEach(() => {\n\tprocess.env.NODE_ENV = 'test';\n});\n\ntest.afterEach(() => {\n\tSilentRenderer.clearTasks();\n\tprocess.env.NODE_ENV = 'test';\n});\n\ntest.serial('public-package published on npm registry: should fail when npm registry not pingable', createFixture, [{\n\tcommand: 'npm ping',\n\texitCode: 1,\n\texitCodeName: 'EPERM',\n\tstdout: '',\n\tstderr: 'failed',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('1.0.0', {name: 'test'}, {}, {packageManager: npmConfig})),\n\t\t{message: 'Connection to npm registry failed'},\n\t);\n\n\tassertTaskFailed(t, 'Ping npm registry');\n});\n\ntest.serial('private package: should disable task pinging npm registry', createFixture, [{\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v2.0.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.notThrowsAsync(run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0', private: true}, {}, {packageManager: npmConfig})));\n\n\tassertTaskDisabled(t, 'Ping npm registry');\n});\n\ntest.serial('external registry: should disable task pinging npm registry', createFixture, [{\n\tcommand: 'npm view --json test engines --registry http://my.io',\n\tstdout: '',\n}, {\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v2.0.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.notThrowsAsync(run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0', publishConfig: {registry: 'http://my.io'}}, {}, {packageManager: npmConfig})));\n\n\tassertTaskDisabled(t, 'Ping npm registry');\n});\n\ntest.serial('should fail when npm version does not match range in `package.json`', createFixture, [\n\t{\n\t\tcommand: 'npm --version',\n\t\tstdout: '6.0.0',\n\t},\n\t{\n\t\tcommand: 'git rev-parse --quiet --verify refs/tags/v2.0.0',\n\t\tstdout: '',\n\t},\n], async ({t, testedModule: prerequisiteTasks}) => {\n\tconst depRange = npPackage.engines.npm;\n\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {}, {packageManager: npmConfig})),\n\t\t{message: `\\`np\\` requires npm ${depRange}`},\n\t);\n\n\tassertTaskFailed(t, 'Check npm version');\n});\n\ntest.serial('should fail when yarn version does not match range in `package.json`', createFixture, [\n\t{\n\t\tcommand: 'yarn --version',\n\t\tstdout: '1.0.0',\n\t},\n\t{\n\t\tcommand: 'git rev-parse --quiet --verify refs/tags/v2.0.0',\n\t\tstdout: '',\n\t},\n], async ({t, testedModule: prerequisiteTasks}) => {\n\tconst depRange = npPackage.engines.yarn;\n\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {}, {packageManager: yarnConfig})),\n\t\t{message: `\\`np\\` requires yarn ${depRange}`},\n\t);\n\n\tassertTaskFailed(t, 'Check yarn version');\n});\n\ntest.serial('should fail when user is not authenticated at npm registry', createFixture, [\n\t{\n\t\tcommand: 'npm whoami',\n\t\tstdout: 'sindresorhus',\n\t},\n\t{\n\t\tcommand: 'npm access list collaborators test --json',\n\t\tstdout: '{\"sindresorhus\": \"read\"}',\n\t},\n], async ({t, testedModule: prerequisiteTasks}) => {\n\tprocess.env.NODE_ENV = 'P';\n\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {}, {packageManager: npmConfig})),\n\t\t{message: 'You do not have write permissions required to publish this package.'},\n\t);\n\n\tprocess.env.NODE_ENV = 'test';\n\n\tassertTaskFailed(t, 'Verify user is authenticated');\n});\n\ntest.serial('should fail when user is not authenticated at external registry', createFixture, [\n\t{\n\t\tcommand: 'npm whoami --registry http://my.io',\n\t\tstdout: 'sindresorhus',\n\t},\n\t{\n\t\tcommand: 'npm access list collaborators test --json --registry http://my.io',\n\t\tstdout: '{\"sindresorhus\": \"read\"}',\n\t},\n], async ({t, testedModule: prerequisiteTasks}) => {\n\tprocess.env.NODE_ENV = 'P';\n\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0', publishConfig: {registry: 'http://my.io'}}, {}, {packageManager: npmConfig})),\n\t\t{message: 'You do not have write permissions required to publish this package.'},\n\t);\n\n\tprocess.env.NODE_ENV = 'test';\n\n\tassertTaskFailed(t, 'Verify user is authenticated');\n});\n\ntest.serial('should use publishConfig.registry even when set to official npm registry', createFixture, [\n\t{\n\t\tcommand: 'npm whoami --registry https://registry.npmjs.org/',\n\t\tstdout: 'sindresorhus',\n\t},\n\t{\n\t\tcommand: 'npm access list collaborators test --json --registry https://registry.npmjs.org/',\n\t\tstdout: '{\"sindresorhus\": \"read\"}',\n\t},\n], async ({t, testedModule: prerequisiteTasks}) => {\n\tprocess.env.NODE_ENV = 'P';\n\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0', publishConfig: {registry: 'https://registry.npmjs.org/'}}, {}, {packageManager: npmConfig})),\n\t\t{message: 'You do not have write permissions required to publish this package.'},\n\t);\n\n\tprocess.env.NODE_ENV = 'test';\n\n\tassertTaskFailed(t, 'Verify user is authenticated');\n});\n\ntest.serial.todo('should not fail if no collaborators'); // Verify user is authenticated\n\ntest.serial('private package: should disable task `verify user is authenticated`', createFixture, [{\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v2.0.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tprocess.env.NODE_ENV = 'P';\n\n\tawait t.notThrowsAsync(run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0', private: true}, {}, {packageManager: npmConfig})));\n\n\tprocess.env.NODE_ENV = 'test';\n\n\tassertTaskDisabled(t, 'Verify user is authenticated');\n});\n\ntest.serial('should fail when git version does not match range in `package.json`', createFixture, [{\n\tcommand: 'git version',\n\tstdout: 'git version 1.0.0',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tconst depRange = npPackage.engines.git;\n\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {}, {packageManager: npmConfig})),\n\t\t{message: `\\`np\\` requires git ${depRange}`},\n\t);\n\n\tassertTaskFailed(t, 'Check git version');\n});\n\ntest.serial('should fail when git user.name is not set', createFixture, [{\n\tcommand: 'git config user.name',\n\texitCode: 1,\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {}, {packageManager: npmConfig})),\n\t\t{message: /Git user configuration is not set/},\n\t);\n\n\tassertTaskFailed(t, 'Check git user configuration');\n});\n\ntest.serial('should fail when git user.email is not set', createFixture, [{\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\texitCode: 1,\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {}, {packageManager: npmConfig})),\n\t\t{message: /Git user configuration is not set/},\n\t);\n\n\tassertTaskFailed(t, 'Check git user configuration');\n});\n\ntest.serial('should fail when git remote does not exist', createFixture, [{\n\tcommand: 'git ls-remote origin HEAD',\n\texitCode: 1,\n\texitCodeName: 'EPERM',\n\tstderr: 'not found',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {}, {packageManager: npmConfig})),\n\t\t{message: 'not found'},\n\t);\n\n\tassertTaskFailed(t, 'Check git remote');\n});\n\ntest.serial('should fail when version is invalid', createFixture, [], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('DDD', {name: 'test', version: '1.0.0'}, {}, {packageManager: npmConfig})),\n\t\t{message: 'New version DDD should either be one of patch, minor, major, prepatch, preminor, premajor, prerelease, or a valid SemVer version.'},\n\t);\n\n\tassertTaskFailed(t, 'Validate version');\n});\n\ntest.serial('should fail when version is lower than latest version', createFixture, [], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('0.1.0', {name: 'test', version: '1.0.0'}, {}, {packageManager: npmConfig})),\n\t\t{message: 'New version 0.1.0 should be higher than current version 1.0.0.'},\n\t);\n\n\tassertTaskFailed(t, 'Validate version');\n});\n\ntest.serial('should fail when prerelease version of public package without dist tag given', createFixture, [], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('2.0.0-1', {name: 'test', version: '1.0.0'}, {}, {packageManager: npmConfig})),\n\t\t{message: 'You must specify a dist-tag using --tag when publishing a pre-release version. This prevents accidentally tagging unstable versions as \"latest\". https://docs.npmjs.com/cli/dist-tag'},\n\t);\n\n\tassertTaskFailed(t, 'Check for pre-release version');\n});\n\ntest.serial('should not fail when prerelease version of public package with dist tag given', createFixture, [{\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v2.0.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.notThrowsAsync(run(prerequisiteTasks('2.0.0-1', {name: 'test', version: '1.0.0'}, {tag: 'pre'}, {packageManager: npmConfig})));\n});\n\ntest.serial('should not fail when prerelease version of private package without dist tag given', createFixture, [{\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v2.0.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.notThrowsAsync(run(prerequisiteTasks('2.0.0-1', {name: 'test', version: '1.0.0', private: true}, {}, {packageManager: npmConfig})));\n});\n\ntest.serial('should fail when git tag already exists', createFixture, [{\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v2.0.0',\n\tstdout: 'vvb',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {}, {packageManager: npmConfig})),\n\t\t{message: 'Git tag `v2.0.0` already exists.'},\n\t);\n\n\tassertTaskFailed(t, 'Check git tag existence');\n});\n\ntest.serial('checks should pass', createFixture, [{\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v2.0.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.notThrowsAsync(run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {}, {packageManager: npmConfig})));\n});\n\ntest.serial('should skip authentication check when OIDC is detected', createFixture, [{\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v2.0.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tprocess.env.NODE_ENV = 'P';\n\tprocess.env.GITHUB_ACTIONS = 'true';\n\tprocess.env.ACTIONS_ID_TOKEN_REQUEST_URL = 'url';\n\tprocess.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'token';\n\n\tt.teardown(() => {\n\t\tdelete process.env.GITHUB_ACTIONS;\n\t\tdelete process.env.ACTIONS_ID_TOKEN_REQUEST_URL;\n\t\tdelete process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;\n\t\tprocess.env.NODE_ENV = 'test';\n\t});\n\n\tawait t.notThrowsAsync(run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {}, {packageManager: npmConfig})));\n\n\tassertTaskSkipped(t, 'Verify user is authenticated');\n});\n\ntest.serial('should fail when dropping Node.js support in a minor release', createFixture, [{\n\tcommand: 'npm view --json test engines',\n\tstdout: JSON.stringify({node: '>=16'}),\n}, {\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v1.1.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('1.1.0', {name: 'test', version: '1.0.0', engines: {node: '>=18'}}, {}, {packageManager: npmConfig})),\n\t\t{message: 'Raising minimum Node.js version from 16.0.0 to 18.0.0 requires a major version bump. The current release is a minor bump.'},\n\t);\n\n\tassertTaskFailed(t, 'Check for Node.js engine support drop');\n});\n\ntest.serial('should fail when dropping Node.js support in a patch release', createFixture, [{\n\tcommand: 'npm view --json test engines',\n\tstdout: JSON.stringify({node: '>=16'}),\n}, {\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v1.0.1',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('1.0.1', {name: 'test', version: '1.0.0', engines: {node: '>=18'}}, {}, {packageManager: npmConfig})),\n\t\t{message: 'Raising minimum Node.js version from 16.0.0 to 18.0.0 requires a major version bump. The current release is a patch bump.'},\n\t);\n\n\tassertTaskFailed(t, 'Check for Node.js engine support drop');\n});\n\ntest.serial('should not fail when dropping Node.js support in a major release', createFixture, [{\n\tcommand: 'npm view --json test engines',\n\tstdout: JSON.stringify({node: '>=16'}),\n}, {\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v2.0.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.notThrowsAsync(run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0', engines: {node: '>=18'}}, {}, {packageManager: npmConfig})));\n});\n\ntest.serial('should not fail when dropping Node.js support in a premajor release', createFixture, [{\n\tcommand: 'npm view --json test engines',\n\tstdout: JSON.stringify({node: '>=16'}),\n}, {\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v2.0.0-0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.notThrowsAsync(run(prerequisiteTasks('2.0.0-0', {name: 'test', version: '1.0.0', engines: {node: '>=18'}}, {tag: 'next'}, {packageManager: npmConfig})));\n});\n\ntest.serial('should not fail when dropping Node.js support in a pre-1.0.0 minor release', createFixture, [{\n\tcommand: 'npm view --json test engines',\n\tstdout: JSON.stringify({node: '>=16'}),\n}, {\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v0.2.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.notThrowsAsync(run(prerequisiteTasks('0.2.0', {name: 'test', version: '0.1.0', engines: {node: '>=18'}}, {}, {packageManager: npmConfig})));\n});\n\ntest.serial('should not fail when engines.node was not previously set', createFixture, [{\n\tcommand: 'npm view --json test engines',\n\tstdout: JSON.stringify({}),\n}, {\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v1.1.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.notThrowsAsync(run(prerequisiteTasks('1.1.0', {name: 'test', version: '1.0.0', engines: {node: '>=18'}}, {}, {packageManager: npmConfig})));\n});\n\ntest.serial('should not fail when first publishing a package', createFixture, [{\n\tcommand: 'npm view --json test engines',\n\texitCode: 1,\n\tstderr: 'E404 Not Found',\n}, {\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v1.0.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.notThrowsAsync(run(prerequisiteTasks('1.0.0', {name: 'test', version: '0.0.0', engines: {node: '>=18'}}, {}, {packageManager: npmConfig})));\n});\n\ntest.serial('should not fail when local package has no engines.node', createFixture, [{\n\tcommand: 'npm view --json test engines',\n\tstdout: JSON.stringify({node: '>=16'}),\n}, {\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v1.1.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.notThrowsAsync(run(prerequisiteTasks('1.1.0', {name: 'test', version: '1.0.0'}, {}, {packageManager: npmConfig})));\n});\n\ntest.serial('private package: should disable task checking for Node.js engine support drop', createFixture, [{\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v1.1.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tconst package_ = {\n\t\tname: 'test',\n\t\tversion: '1.0.0',\n\t\tprivate: true,\n\t\tengines: {node: '>=18'},\n\t};\n\n\tawait t.notThrowsAsync(run(prerequisiteTasks('1.1.0', package_, {}, {packageManager: npmConfig})));\n\n\tassertTaskDisabled(t, 'Check for Node.js engine support drop');\n});\n\ntest.serial('should not fail when Node.js minimum version stays the same', createFixture, [{\n\tcommand: 'npm view --json test engines',\n\tstdout: JSON.stringify({node: '>=18'}),\n}, {\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v1.1.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.notThrowsAsync(run(prerequisiteTasks('1.1.0', {name: 'test', version: '1.0.0', engines: {node: '>=18'}}, {}, {packageManager: npmConfig})));\n});\n\ntest.serial('should not fail when Node.js minimum version is lowered', createFixture, [{\n\tcommand: 'npm view --json test engines',\n\tstdout: JSON.stringify({node: '>=18'}),\n}, {\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v1.1.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.notThrowsAsync(run(prerequisiteTasks('1.1.0', {name: 'test', version: '1.0.0', engines: {node: '>=16'}}, {}, {packageManager: npmConfig})));\n});\n\ntest.serial('should fail when dropping Node.js support in a preminor release', createFixture, [{\n\tcommand: 'npm view --json test engines',\n\tstdout: JSON.stringify({node: '>=16'}),\n}, {\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v1.1.0-0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('1.1.0-0', {name: 'test', version: '1.0.0', engines: {node: '>=18'}}, {tag: 'next'}, {packageManager: npmConfig})),\n\t\t{message: 'Raising minimum Node.js version from 16.0.0 to 18.0.0 requires a major version bump. The current release is a preminor bump.'},\n\t);\n\n\tassertTaskFailed(t, 'Check for Node.js engine support drop');\n});\n\ntest.serial('should fail when dropping Node.js support in a prepatch release', createFixture, [{\n\tcommand: 'npm view --json test engines',\n\tstdout: JSON.stringify({node: '>=16'}),\n}, {\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v1.0.1-0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('1.0.1-0', {name: 'test', version: '1.0.0', engines: {node: '>=18'}}, {tag: 'next'}, {packageManager: npmConfig})),\n\t\t{message: 'Raising minimum Node.js version from 16.0.0 to 18.0.0 requires a major version bump. The current release is a prepatch bump.'},\n\t);\n\n\tassertTaskFailed(t, 'Check for Node.js engine support drop');\n});\n\ntest.serial('should fail when dropping Node.js support in a prerelease release', createFixture, [{\n\tcommand: 'npm view --json test engines',\n\tstdout: JSON.stringify({node: '>=16'}),\n}, {\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v1.0.0-1',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tawait t.throwsAsync(\n\t\trun(prerequisiteTasks('1.0.0-1', {name: 'test', version: '1.0.0-0', engines: {node: '>=18'}}, {tag: 'next'}, {packageManager: npmConfig})),\n\t\t{message: 'Raising minimum Node.js version from 16.0.0 to 18.0.0 requires a major version bump. The current release is a prerelease bump.'},\n\t);\n\n\tassertTaskFailed(t, 'Check for Node.js engine support drop');\n});\n\ntest.serial('yolo mode: should disable task checking for Node.js engine support drop', createFixture, [{\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v1.1.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tconst package_ = {\n\t\tname: 'test',\n\t\tversion: '1.0.0',\n\t\tengines: {node: '>=18'},\n\t};\n\n\t// Should not throw even though we're dropping Node.js support in a minor release,\n\t// because yolo mode skips this check\n\tawait t.notThrowsAsync(run(prerequisiteTasks('1.1.0', package_, {yolo: true}, {packageManager: npmConfig})));\n\n\tassertTaskDisabled(t, 'Check for Node.js engine support drop');\n});\n\ntest.serial('external registry: should skip engine check when registry returns error', createFixture, [{\n\tcommand: 'npm view --json test engines --registry http://my.io',\n\texitCode: 1,\n\tstderr: 'E405 Method Not Allowed',\n}, {\n\tcommand: 'git config user.name',\n\tstdout: 'Test User',\n}, {\n\tcommand: 'git config user.email',\n\tstdout: 'test@example.com',\n}, {\n\tcommand: 'git rev-parse --quiet --verify refs/tags/v1.1.0',\n\tstdout: '',\n}], async ({t, testedModule: prerequisiteTasks}) => {\n\tconst package_ = {\n\t\tname: 'test',\n\t\tversion: '1.0.0',\n\t\tengines: {node: '>=18'},\n\t\tpublishConfig: {registry: 'http://my.io'},\n\t};\n\n\t// Should not throw even though we're dropping Node.js support in a minor release,\n\t// because the external registry doesn't support the npm view endpoint\n\tawait t.notThrowsAsync(run(prerequisiteTasks('1.1.0', package_, {}, {packageManager: npmConfig})));\n});\n"
  },
  {
    "path": "test/ui/new-files-dependencies.d.ts",
    "content": "import type {Macro, ExecutionContext} from 'ava';\nimport type {PackageJson} from 'read-pkg';\n\ntype Context = {\n\tcreateFile: (file: string, content?: string) => Promise<void>;\n};\n\ntype CommandsFunctionParameters = [{\n\tt: ExecutionContext<Context>;\n\t$$: Execa$<string>;\n\ttemporaryDir: string;\n}];\n\ntype ListItem = `- ${string}`;\n\ntype Expected = {\n\tunpublished: ListItem[];\n\tfirstTime: ListItem[];\n\tdependencies: ListItem[];\n};\n\ntype AssertionsFunctionParameters = [{\n\tt: ExecutionContext<Context>;\n\t$$: Execa$<string>;\n\ttemporaryDir: string;\n\tlogs: string[];\n}];\n\nexport type CreateFixtureMacro = Macro<[\n\tpackage_: PackageJson,\n\tcommands: (...arguments_: CommandsFunctionParameters) => Promise<void>,\n\texpected: Expected,\n\tassertions: (...arguments_: AssertionsFunctionParameters) => Promise<void>,\n], Context>;\n"
  },
  {
    "path": "test/ui/new-files-dependencies.js",
    "content": "import test from 'ava';\nimport sinon from 'sinon';\nimport {execa} from 'execa';\nimport {removePackageDependencies, updatePackage} from 'write-package';\nimport stripAnsi from 'strip-ansi';\nimport {readPackage} from 'read-pkg';\nimport {npmConfig as packageManager} from '../../source/package-manager/configs.js';\nimport {createIntegrationTest} from '../_helpers/integration-test.js';\nimport {mockInquirer} from '../_helpers/mock-inquirer.js';\n\n/** @param {string} message */\nconst checkLines = message => (\n\t/** @param {import('ava').ExecutionContext} t @param {string[]} logs @param {string[]} expectedLines */\n\t(t, logs, expectedLines) => {\n\t\tconst lineAfterMessage = logs.indexOf(message) + 1;\n\t\tconst endOfList = logs.findIndex((log, ind) => ind > lineAfterMessage && !log.startsWith('-'));\n\n\t\tt.deepEqual(logs.slice(lineAfterMessage, endOfList), expectedLines);\n\t}\n);\n\nconst checkNewUnpublished = checkLines('⚠ WARNING: The following new files will NOT be published:');\nconst checkFirstTimeFiles = checkLines('The following new files will be published for the first time:');\nconst checkNewDependencies = checkLines('The following new dependencies will be part of your published package:');\n\n/** @type {import('./new-files-dependencies.d.ts').CreateFixtureMacro} */\nconst createFixture = test.macro(async (t, package_, commands, expected) => {\n\tawait createIntegrationTest(t, async ({$$, temporaryDirectory}) => {\n\t\tpackage_ = {\n\t\t\tname: '@np/foo',\n\t\t\tversion: '0.0.0',\n\t\t\tdependencies: {},\n\t\t\t...package_,\n\t\t};\n\n\t\tawait updatePackage(temporaryDirectory, package_);\n\n\t\tawait $$`git add .`;\n\t\tawait $$`git commit -m \"added\"`;\n\t\tawait $$`git tag v0.0.0`;\n\n\t\tawait commands({t, $$, temporaryDirectory});\n\t\tpackage_ = await readPackage({cwd: temporaryDirectory});\n\n\t\tconst {ui, logs: logsArray} = await mockInquirer({\n\t\t\tt, answers: {confirm: {confirm: false}}, mocks: {\n\t\t\t\t'./npm/util.js': {\n\t\t\t\t\tcheckIgnoreStrategy: sinon.stub().resolves(),\n\t\t\t\t},\n\t\t\t\t'node:process': {cwd: () => temporaryDirectory},\n\t\t\t\texeca: {execa: async (...arguments_) => execa(...arguments_, {cwd: temporaryDirectory})},\n\t\t\t\t'is-interactive': () => false,\n\t\t\t},\n\t\t});\n\n\t\tawait ui({runPublish: true, version: 'major', packageManager}, {package_, rootDirectory: temporaryDirectory});\n\t\tconst logs = logsArray.join('').split('\\n').map(log => stripAnsi(log));\n\n\t\tconst {unpublished, firstTime, dependencies} = expected;\n\n\t\tconst assertions = await t.try(tt => {\n\t\t\tif (unpublished) {\n\t\t\t\tcheckNewUnpublished(tt, logs, unpublished);\n\t\t\t}\n\n\t\t\tif (firstTime) {\n\t\t\t\tcheckFirstTimeFiles(tt, logs, firstTime);\n\t\t\t}\n\n\t\t\tif (dependencies) {\n\t\t\t\tcheckNewDependencies(tt, logs, dependencies);\n\t\t\t}\n\t\t});\n\n\t\tif (!assertions.passed) {\n\t\t\tt.log('logs:', logs);\n\t\t\tt.log('package:', package_);\n\t\t\tt.log('expected:', expected);\n\t\t}\n\n\t\tassertions.commit();\n\t});\n});\n\ntest('unpublished', createFixture, {files: ['*.js']}, async ({t, $$}) => {\n\tawait t.context.createFile('new');\n\tawait $$`git add .`;\n\tawait $$`git commit -m \"added\"`;\n}, {unpublished: ['- new']});\n\ntest('unpublished and first time', createFixture, {files: ['*.js']}, async ({t, $$}) => {\n\tawait t.context.createFile('new');\n\tawait t.context.createFile('index.js');\n\tawait $$`git add .`;\n\tawait $$`git commit -m \"added\"`;\n}, {unpublished: ['- new'], firstTime: ['- index.js']});\n\ntest('unpublished and dependencies', createFixture, {files: ['*.js']}, async ({t, $$, temporaryDirectory}) => {\n\tawait t.context.createFile('new');\n\tawait $$`git add .`;\n\tawait $$`git commit -m \"added\"`;\n\tawait updatePackage(temporaryDirectory, {dependencies: {'cat-names': '^3.1.0'}});\n}, {unpublished: ['- new'], dependencies: ['- cat-names']});\n\ntest('first time', createFixture, {}, async ({t, $$}) => {\n\tawait t.context.createFile('new');\n\tawait $$`git add .`;\n\tawait $$`git commit -m \"added\"`;\n}, {firstTime: ['- new']});\n\ntest('first time and dependencies', createFixture, {}, async ({t, $$, temporaryDirectory}) => {\n\tawait t.context.createFile('new');\n\tawait $$`git add .`;\n\tawait $$`git commit -m \"added\"`;\n\tawait updatePackage(temporaryDirectory, {dependencies: {'cat-names': '^3.1.0'}});\n}, {firstTime: ['- new'], dependencies: ['- cat-names']});\n\ntest('dependencies', createFixture, {dependencies: {'dog-names': '^2.1.0'}}, async ({temporaryDirectory}) => {\n\tawait removePackageDependencies(temporaryDirectory, ['dog-names']);\n\tawait updatePackage(temporaryDirectory, {dependencies: {'cat-names': '^3.1.0'}});\n}, {dependencies: ['- cat-names']});\n\ntest('unpublished and first time and dependencies', createFixture, {files: ['*.js']}, async ({t, $$, temporaryDirectory}) => {\n\tawait t.context.createFile('new');\n\tawait t.context.createFile('index.js');\n\tawait $$`git add .`;\n\tawait $$`git commit -m \"added\"`;\n\tawait updatePackage(temporaryDirectory, {dependencies: {'cat-names': '^3.1.0'}});\n}, {unpublished: ['- new'], firstTime: ['- index.js'], dependencies: ['- cat-names']});\n"
  },
  {
    "path": "test/ui/prompts/tags.js",
    "content": "import test from 'ava';\nimport sinon from 'sinon';\nimport {npmConfig as packageManager} from '../../../source/package-manager/configs.js';\nimport {npPackage} from '../../../source/util.js';\nimport {mockInquirer} from '../../_helpers/mock-inquirer.js';\n\nconst testUi = test.macro(async (t, {version, tags, answers}, assertions) => {\n\tconst {ui, logs} = await mockInquirer({\n\t\tt, answers: {confirm: true, ...answers}, mocks: {\n\t\t\t'./npm/util.js': {\n\t\t\t\tcheckIgnoreStrategy: sinon.stub().resolves(),\n\t\t\t\tprereleaseTags: sinon.stub().resolves(tags),\n\t\t\t},\n\t\t\t'./util.js': {\n\t\t\t\tgetNewFiles: sinon.stub().resolves({unpublished: [], firstTime: []}),\n\t\t\t\tgetNewDependencies: sinon.stub().resolves([]),\n\t\t\t\tgetPreReleasePrefix: sinon.stub().resolves(''),\n\t\t\t},\n\t\t\t'./git-util.js': {\n\t\t\t\tlatestTagOrFirstCommit: sinon.stub().resolves(`v${npPackage.version}`),\n\t\t\t\tcommitLogFromRevision: sinon.stub().resolves(''),\n\t\t\t},\n\t\t\t'./package-manager/index.js': {\n\t\t\t\tfindLockfile: sinon.stub().resolves(undefined),\n\t\t\t},\n\t\t\texeca: {\n\t\t\t\texeca: sinon.stub().resolves({stdout: 'https://registry.npmjs.org/'}),\n\t\t\t},\n\t\t},\n\t});\n\n\tconst results = await ui({\n\t\tpackageManager,\n\t\trunPublish: true,\n\t\tavailability: {},\n\t}, {\n\t\tpackage_: {\n\t\t\tname: 'foo',\n\t\t\tversion,\n\t\t\tfiles: ['*'],\n\t\t},\n\t});\n\n\tawait assertions({t, results, logs});\n});\n\ntest('choose next', testUi, {\n\tversion: '0.0.0',\n\ttags: ['next'],\n\tanswers: {\n\t\tversion: 'prerelease',\n\t\tprereleasePrefix: '',\n\t\ttag: 'next',\n\t},\n}, ({t, results: {version, tag}}) => {\n\tt.is(version.toString(), '0.0.1-0');\n\tt.is(tag, 'next');\n});\n\ntest('choose beta', testUi, {\n\tversion: '0.0.0',\n\ttags: ['beta', 'stable'],\n\tanswers: {\n\t\tversion: 'prerelease',\n\t\tprereleasePrefix: '',\n\t\ttag: 'beta',\n\t},\n}, ({t, results: {version, tag}}) => {\n\tt.is(version.toString(), '0.0.1-0');\n\tt.is(tag, 'beta');\n});\n\ntest('choose custom', testUi, {\n\tversion: '0.0.0',\n\ttags: ['next'],\n\tanswers: {\n\t\tversion: 'prerelease',\n\t\tprereleasePrefix: '',\n\t\ttag: 'Other (specify)',\n\t\tcustomTag: 'alpha',\n\t},\n}, ({t, results: {version, tag}}) => {\n\tt.is(version.toString(), '0.0.1-0');\n\tt.is(tag, 'alpha');\n});\n\ntest('choose custom - validation', testUi, {\n\tversion: '0.0.0',\n\ttags: ['next'],\n\tanswers: {\n\t\tversion: 'prerelease',\n\t\tprereleasePrefix: '',\n\t\ttag: 'Other (specify)',\n\t\tcustomTag: [\n\t\t\t{\n\t\t\t\tinput: '',\n\t\t\t\terror: 'Please specify a tag, for example, `next`.',\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: 'latest',\n\t\t\t\terror: 'It\\'s not possible to publish pre-releases under the `latest` tag. Please specify something else, for example, `next`.',\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: 'LAteSt',\n\t\t\t\terror: 'It\\'s not possible to publish pre-releases under the `latest` tag. Please specify something else, for example, `next`.',\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: 'alpha',\n\t\t\t},\n\t\t],\n\t},\n}, ({t, results: {version, tag}}) => {\n\tt.is(version.toString(), '0.0.1-0');\n\tt.is(tag, 'alpha');\n});\n\n// Assuming from version 0.0.0\nconst fixtures = [\n\t{version: 'premajor', expected: '1.0.0-0'},\n\t{version: 'preminor', expected: '0.1.0-0'},\n\t{version: 'prepatch', expected: '0.0.1-0'},\n\t{version: 'prerelease', expected: '0.0.1-0'},\n];\n\nfor (const {version, expected} of fixtures) {\n\ttest(`works for ${version}`, testUi, {\n\t\tversion: '0.0.0',\n\t\ttags: ['next'],\n\t\tanswers: {\n\t\t\tversion,\n\t\t\tprereleasePrefix: '',\n\t\t\ttag: 'next',\n\t\t},\n\t}, ({t, results: {version, tag}}) => {\n\t\tt.is(version.toString(), expected);\n\t\tt.is(tag, 'next');\n\t});\n}\n\n// Test that prerelease versions provided via CLI prompt for tag\nfor (const {version} of fixtures) {\n\ttest(`prompts for tag when ${version} is provided via CLI`, async t => {\n\t\tconst {ui} = await mockInquirer({\n\t\t\tt, answers: {tag: 'next', confirm: true}, mocks: {\n\t\t\t\t'./npm/util.js': {\n\t\t\t\t\tcheckIgnoreStrategy: sinon.stub().resolves(),\n\t\t\t\t\tprereleaseTags: sinon.stub().resolves(['next']),\n\t\t\t\t},\n\t\t\t\t'./util.js': {\n\t\t\t\t\tgetNewFiles: sinon.stub().resolves({unpublished: [], firstTime: []}),\n\t\t\t\t\tgetNewDependencies: sinon.stub().resolves([]),\n\t\t\t\t\tgetPreReleasePrefix: sinon.stub().resolves(''),\n\t\t\t\t},\n\t\t\t\t'./git-util.js': {\n\t\t\t\t\tlatestTagOrFirstCommit: sinon.stub().resolves(`v${npPackage.version}`),\n\t\t\t\t\tcommitLogFromRevision: sinon.stub().resolves(''),\n\t\t\t\t},\n\t\t\t\t'./package-manager/index.js': {\n\t\t\t\t\tfindLockfile: sinon.stub().resolves(undefined),\n\t\t\t\t},\n\t\t\t\texeca: {\n\t\t\t\t\texeca: sinon.stub().resolves({stdout: 'https://registry.npmjs.org/'}),\n\t\t\t\t},\n\t\t\t},\n\t\t});\n\n\t\tconst results = await ui({\n\t\t\tpackageManager,\n\t\t\trunPublish: true,\n\t\t\tavailability: {},\n\t\t\tversion,\n\t\t}, {\n\t\t\tpackage_: {\n\t\t\t\tname: 'foo',\n\t\t\t\tversion: '0.0.0',\n\t\t\t\tfiles: ['*'],\n\t\t\t},\n\t\t});\n\n\t\t// Verify that the tag was set via prompt\n\t\tt.is(results.tag, 'next');\n\t});\n}\n"
  },
  {
    "path": "test/ui/prompts/version.js",
    "content": "import test from 'ava';\nimport sinon from 'sinon';\nimport {npmConfig as packageManager} from '../../../source/package-manager/configs.js';\nimport {mockInquirer} from '../../_helpers/mock-inquirer.js';\n\nconst testUi = test.macro(async (t, {version, answers}, assertions) => {\n\tconst {ui, logs} = await mockInquirer({\n\t\tt, answers: {confirm: true, ...answers}, mocks: {\n\t\t\t'./npm/util.js': {\n\t\t\t\tcheckIgnoreStrategy: sinon.stub().resolves(),\n\t\t\t},\n\t\t\t'./util.js': {\n\t\t\t\tgetNewFiles: sinon.stub().resolves({unpublished: [], firstTime: []}),\n\t\t\t\tgetNewDependencies: sinon.stub().resolves([]),\n\t\t\t\tgetPreReleasePrefix: sinon.stub().resolves(''),\n\t\t\t},\n\t\t\t'./git-util.js': {\n\t\t\t\tlatestTagOrFirstCommit: sinon.stub().resolves('v1.0.0'),\n\t\t\t\tcommitLogFromRevision: sinon.stub().resolves(''),\n\t\t\t},\n\t\t\texeca: {\n\t\t\t\texeca: sinon.stub().resolves({stdout: 'https://registry.npmjs.org/'}),\n\t\t\t},\n\t\t},\n\t});\n\n\tconst results = await ui({\n\t\tpackageManager,\n\t\trunPublish: false,\n\t\tavailability: {},\n\t}, {\n\t\tpackage_: {\n\t\t\tname: 'foo',\n\t\t\tversion,\n\t\t\tfiles: ['*'],\n\t\t},\n\t});\n\n\tawait assertions({t, results, logs});\n});\n\ntest('choose major', testUi, {\n\tversion: '0.0.0',\n\tanswers: {\n\t\tversion: 'major',\n\t},\n}, ({t, results: {version}}) => {\n\tt.is(version.toString(), '1.0.0');\n});\n\ntest('choose minor', testUi, {\n\tversion: '0.0.0', answers: {\n\t\tversion: 'minor',\n\t},\n}, ({t, results: {version}}) => {\n\tt.is(version.toString(), '0.1.0');\n});\n\ntest('choose patch', testUi, {\n\tversion: '0.0.0', answers: {\n\t\tversion: 'patch',\n\t},\n}, ({t, results: {version}}) => {\n\tt.is(version.toString(), '0.0.1');\n});\n\ntest('choose premajor', testUi, {\n\tversion: '0.0.0', answers: {\n\t\tversion: 'premajor',\n\t\tprereleasePrefix: '',\n\t},\n}, ({t, results: {version}}) => {\n\tt.is(version.toString(), '1.0.0-0');\n});\n\ntest('choose preminor', testUi, {\n\tversion: '0.0.0', answers: {\n\t\tversion: 'preminor',\n\t\tprereleasePrefix: '',\n\t},\n}, ({t, results: {version}}) => {\n\tt.is(version.toString(), '0.1.0-0');\n});\n\ntest('choose prepatch', testUi, {\n\tversion: '0.0.0', answers: {\n\t\tversion: 'prepatch',\n\t\tprereleasePrefix: '',\n\t},\n}, ({t, results: {version}}) => {\n\tt.is(version.toString(), '0.0.1-0');\n});\n\ntest('choose prerelease', testUi, {\n\tversion: '0.0.1-0', answers: {\n\t\tversion: 'prerelease',\n\t\tprereleasePrefix: '',\n\t},\n}, ({t, results: {version}}) => {\n\tt.is(version.toString(), '0.0.1-1');\n});\n\ntest('choose custom', testUi, {\n\tversion: '0.0.0', answers: {\n\t\tversion: 'Other (specify)',\n\t\tcustomVersion: '1.0.0',\n\t},\n}, ({t, results: {version}}) => {\n\tt.is(version.toString(), '1.0.0');\n});\n\ntest('choose custom - validation', testUi, {\n\tversion: '1.0.0', answers: {\n\t\tversion: 'Other (specify)',\n\t\tcustomVersion: [\n\t\t\t{\n\t\t\t\tinput: 'major',\n\t\t\t\terror: 'Custom version should not be a SemVer increment.',\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: '200',\n\t\t\t\terror: 'Custom version 200 should be a valid SemVer version.',\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: '0.0.0',\n\t\t\t\terror: 'Custom version 0.0.0 should be higher than current version 1.0.0.',\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: '1.0.0',\n\t\t\t\terror: 'Custom version 1.0.0 should be higher than current version 1.0.0.',\n\t\t\t},\n\t\t\t{\n\t\t\t\tinput: '2.0.0',\n\t\t\t},\n\t\t],\n\t},\n}, ({t, results: {version}}) => {\n\tt.is(version.toString(), '2.0.0');\n});\n\ntest('choose prepatch with custom prerelease identifier', testUi, {\n\tversion: '1.0.0',\n\tanswers: {\n\t\tversion: 'prepatch',\n\t\tprereleasePrefix: 'beta',\n\t},\n}, ({t, results: {version}}) => {\n\tt.is(version.toString(), '1.0.1-beta.0');\n});\n\ntest('choose preminor with custom prerelease identifier', testUi, {\n\tversion: '1.0.0',\n\tanswers: {\n\t\tversion: 'preminor',\n\t\tprereleasePrefix: 'alpha',\n\t},\n}, ({t, results: {version}}) => {\n\tt.is(version.toString(), '1.1.0-alpha.0');\n});\n\ntest('choose premajor with custom prerelease identifier', testUi, {\n\tversion: '1.0.0',\n\tanswers: {\n\t\tversion: 'premajor',\n\t\tprereleasePrefix: 'rc',\n\t},\n}, ({t, results: {version}}) => {\n\tt.is(version.toString(), '2.0.0-rc.0');\n});\n\ntest('choose prerelease with custom prerelease identifier', testUi, {\n\tversion: '1.0.0-rc.0',\n\tanswers: {\n\t\tversion: 'prerelease',\n\t\tprereleasePrefix: 'rc',\n\t},\n}, ({t, results: {version}}) => {\n\tt.is(version.toString(), '1.0.0-rc.1');\n});\n\ntest('uses current prerelease identifier when available', testUi, {\n\tversion: '1.0.2-beta.3', // Current version has 'beta' identifier\n\tanswers: {\n\t\tversion: 'prerelease',\n\t\tprereleasePrefix: 'beta', // Accept the default which should be 'beta'\n\t},\n}, ({t, results: {version}}) => {\n\t// Should use 'beta' from current version\n\tt.is(version.toString(), '1.0.2-beta.4');\n});\n\ntest('handles numeric prerelease identifiers', testUi, {\n\tversion: '1.0.0-0', // Numeric prerelease identifier\n\tanswers: {\n\t\tversion: 'prerelease',\n\t\tprereleasePrefix: 'beta', // Should not suggest '0', user enters 'beta'\n\t},\n}, ({t, results: {version}}) => {\n\t// Should transition from numeric to string identifier\n\tt.is(version.toString(), '1.0.0-beta.0');\n});\n"
  },
  {
    "path": "test/ui/repo-url.js",
    "content": "import test from 'ava';\nimport sinon from 'sinon';\nimport {npmConfig as packageManager} from '../../source/package-manager/configs.js';\nimport {mockInquirer} from '../_helpers/mock-inquirer.js';\n\ntest('strips committish from hosted git info browse url', async t => {\n\tconst {ui} = await mockInquirer({\n\t\tt,\n\t\tanswers: {confirm: true},\n\t\tmocks: {\n\t\t\t'./npm/util.js': {\n\t\t\t\tcheckIgnoreStrategy: sinon.stub().resolves(),\n\t\t\t},\n\t\t\t'./util.js': {\n\t\t\t\tgetNewFiles: sinon.stub().resolves({unpublished: [], firstTime: []}),\n\t\t\t\tgetNewDependencies: sinon.stub().resolves([]),\n\t\t\t\tgetPreReleasePrefix: sinon.stub().resolves(''),\n\t\t\t},\n\t\t\t'./git-util.js': {\n\t\t\t\tlatestTagOrFirstCommit: sinon.stub().resolves('v1.0.0'),\n\t\t\t\tcommitLogFromRevision: sinon.stub().resolves(''),\n\t\t\t},\n\t\t\texeca: {\n\t\t\t\texeca: sinon.stub().resolves({stdout: 'https://registry.npmjs.org/'}),\n\t\t\t},\n\t\t},\n\t});\n\n\tconst results = await ui({\n\t\tpackageManager,\n\t\trunPublish: false,\n\t\tavailability: {},\n\t\tversion: 'patch',\n\t}, {\n\t\tpackage_: {\n\t\t\tname: 'foo',\n\t\t\tversion: '1.0.0',\n\t\t\tfiles: ['*'],\n\t\t\trepository: {\n\t\t\t\turl: 'git+https://github.com/org/repo.git#main',\n\t\t\t},\n\t\t},\n\t\trootDirectory: '/tmp',\n\t});\n\n\tt.is(results.repoUrl, 'https://github.com/org/repo');\n});\n"
  },
  {
    "path": "test/util/auto-group-list.js",
    "content": "import test from 'ava';\nimport stripAnsi from 'strip-ansi';\nimport {groupFilesInFolders} from '../../source/util.js';\n\nconst testJoinList = test.macro((t, {list, expected}) => {\n\tconst output = groupFilesInFolders(list);\n\tt.is(stripAnsi(output), expected);\n});\n\ntest('one item', testJoinList, {\n\tlist: [\n\t\t'scripts/a.sh',\n\t],\n\texpected: '- scripts/a.sh',\n});\n\ntest('mix of collapsed and expanded folders', testJoinList, {\n\tlist: [\n\t\t'scripts/a.sh',\n\t\t'scripts/b.sh',\n\t\t'scripts/c.sh',\n\t\t'test/_utils-1.js',\n\t\t'test/_utils-2.js',\n\t\t'test/_utils-3.js',\n\t\t'test/_utils-4.js',\n\t\t'test/_utils-5.js',\n\t\t'test/_utils-6.js',\n\t],\n\texpected: `- scripts/a.sh\n- scripts/b.sh\n- scripts/c.sh\n- test/* (6 files)`,\n});\n"
  },
  {
    "path": "test/util/get-minimum-node-version.js",
    "content": "import test from 'ava';\nimport {getMinimumNodeVersion} from '../../source/util.js';\n\ntest('returns minimum version from >=X range', t => {\n\tt.is(getMinimumNodeVersion('>=18'), '18.0.0');\n\tt.is(getMinimumNodeVersion('>=18.0.0'), '18.0.0');\n\tt.is(getMinimumNodeVersion('>=16.14.0'), '16.14.0');\n});\n\ntest('returns minimum version from ^X range', t => {\n\tt.is(getMinimumNodeVersion('^18'), '18.0.0');\n\tt.is(getMinimumNodeVersion('^18.0.0'), '18.0.0');\n\tt.is(getMinimumNodeVersion('^16.14.0'), '16.14.0');\n});\n\ntest('returns minimum version from X range', t => {\n\tt.is(getMinimumNodeVersion('18'), '18.0.0');\n\tt.is(getMinimumNodeVersion('18.0.0'), '18.0.0');\n});\n\ntest('returns minimum version from OR range', t => {\n\tt.is(getMinimumNodeVersion('18 || 20'), '18.0.0');\n\tt.is(getMinimumNodeVersion('>=16 || >=18'), '16.0.0');\n});\n\ntest('returns minimum version from complex range', t => {\n\tt.is(getMinimumNodeVersion('>=18.0.0 <20.0.0'), '18.0.0');\n\tt.is(getMinimumNodeVersion('>=16.14.0 <17.0.0 || >=18.0.0'), '16.14.0');\n});\n\ntest('returns undefined for invalid input', t => {\n\tt.is(getMinimumNodeVersion(undefined), undefined);\n\tt.is(getMinimumNodeVersion(null), undefined);\n\tt.is(getMinimumNodeVersion(''), undefined);\n\tt.is(getMinimumNodeVersion(123), undefined);\n});\n\ntest('returns undefined for invalid range', t => {\n\tt.is(getMinimumNodeVersion('invalid'), undefined);\n});\n"
  },
  {
    "path": "test/util/get-new-dependencies.js",
    "content": "import test from 'ava';\nimport {updatePackage} from 'write-package';\nimport {readPackage} from 'read-pkg';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/util.js')>>} */\nconst createFixture = _createFixture('../../source/util.js');\n\ntest('reports new dependencies since last release', createFixture, async ({$$, temporaryDirectory}) => {\n\tawait updatePackage(temporaryDirectory, {dependencies: {'dog-names': '^2.1.0'}});\n\tawait $$`git add .`;\n\tawait $$`git commit -m \"added\"`;\n\tawait $$`git tag v0.0.0`;\n\tawait updatePackage(temporaryDirectory, {dependencies: {'cat-names': '^3.1.0'}});\n}, async ({t, testedModule: {getNewDependencies}, temporaryDirectory}) => {\n\tconst package_ = await readPackage({cwd: temporaryDirectory});\n\n\tt.deepEqual(\n\t\tawait getNewDependencies(package_, temporaryDirectory),\n\t\t['cat-names'],\n\t);\n});\n\ntest('handles first time publish (no package.json in last release)', createFixture, async ({temporaryDirectory}) => {\n\tawait updatePackage(temporaryDirectory, {dependencies: {'cat-names': '^3.1.0'}});\n}, async ({t, testedModule: {getNewDependencies}, temporaryDirectory}) => {\n\tconst package_ = await readPackage({cwd: temporaryDirectory});\n\n\tt.deepEqual(\n\t\tawait getNewDependencies(package_, temporaryDirectory),\n\t\t['cat-names'],\n\t);\n});\n\ntest('handles first time publish (no package.json in last release) - no deps', createFixture, async ({temporaryDirectory}) => {\n\tawait updatePackage(temporaryDirectory, {name: '@np/foo'});\n}, async ({t, testedModule: {getNewDependencies}, temporaryDirectory}) => {\n\tconst package_ = await readPackage({cwd: temporaryDirectory});\n\n\tt.deepEqual(\n\t\tawait getNewDependencies(package_, temporaryDirectory),\n\t\t[],\n\t);\n});\n"
  },
  {
    "path": "test/util/get-new-files.js",
    "content": "import path from 'node:path';\nimport test from 'ava';\nimport esmock from 'esmock';\nimport {execa} from 'execa';\nimport {writePackage} from 'write-package';\nimport {createIntegrationTest} from '../_helpers/integration-test.js';\n\nconst createNewFilesFixture = test.macro(async (t, input, commands) => {\n\tconst {packageFiles, expected: {unpublished, firstTime}} = input;\n\n\tawait createIntegrationTest(t, async ({$$, temporaryDirectory}) => {\n\t\t/** @type {import('../../source/util.js')} */\n\t\tconst {getNewFiles} = await esmock('../../source/util.js', {}, {\n\t\t\t'node:process': {cwd: () => temporaryDirectory},\n\t\t\texeca: {execa: async (...arguments_) => execa(...arguments_, {cwd: temporaryDirectory})},\n\t\t});\n\n\t\tawait commands({t, $$, temporaryDirectory});\n\n\t\tawait writePackage(temporaryDirectory, {\n\t\t\tname: 'foo',\n\t\t\tversion: '0.0.0',\n\t\t\t...packageFiles.length > 0 ? {files: packageFiles} : {},\n\t\t});\n\n\t\tconst assertions = await t.try(async tt => {\n\t\t\ttt.deepEqual(\n\t\t\t\tawait getNewFiles(temporaryDirectory),\n\t\t\t\t{unpublished, firstTime},\n\t\t\t);\n\t\t});\n\n\t\tif (!assertions.passed) {\n\t\t\tt.log(input);\n\t\t}\n\n\t\tassertions.commit();\n\t});\n});\n\ntest('files to package with tags added', createNewFilesFixture, {\n\tpackageFiles: ['*.js'],\n\texpected: {\n\t\tunpublished: ['new'],\n\t\tfirstTime: ['index.js'],\n\t},\n}, async ({t, $$}) => {\n\tawait $$`git tag v0.0.0`;\n\tawait t.context.createFile('new');\n\tawait t.context.createFile('index.js');\n\tawait $$`git add -A`;\n\tawait $$`git commit -m \"added\"`;\n});\n\ntest('file `new` to package without tags added', createNewFilesFixture, {\n\tpackageFiles: ['index.js'],\n\texpected: {\n\t\tunpublished: ['new'],\n\t\tfirstTime: ['index.js', 'package.json'],\n\t},\n}, async ({t}) => {\n\tawait t.context.createFile('new');\n\tawait t.context.createFile('index.js');\n});\n\n(() => { // Wrapper to have constants with macro\n\tconst longPath = path.join('veryLonggggggDirectoryName', 'veryLonggggggDirectoryName');\n\tconst filePath1 = path.join(longPath, 'file1');\n\tconst filePath2 = path.join(longPath, 'file2');\n\n\ttest('files with long pathnames added', createNewFilesFixture, {\n\t\tpackageFiles: ['*.js'],\n\t\texpected: {\n\t\t\tunpublished: [filePath1, filePath2],\n\t\t\tfirstTime: [],\n\t\t},\n\t}, async ({t, $$}) => {\n\t\tawait $$`git tag v0.0.0`;\n\t\tawait t.context.createFile(filePath1);\n\t\tawait t.context.createFile(filePath2);\n\t\tawait $$`git add -A`;\n\t\tawait $$`git commit -m \"added\"`;\n\t});\n})();\n\ntest('no new files added', createNewFilesFixture, {\n\tpackageFiles: [],\n\texpected: {\n\t\tunpublished: [],\n\t\tfirstTime: [],\n\t},\n}, async ({$$}) => {\n\tawait $$`git tag v0.0.0`;\n});\n\ntest('ignores .git and .github files', createNewFilesFixture, {\n\tpackageFiles: ['*.js'],\n\texpected: {\n\t\tunpublished: [],\n\t\tfirstTime: ['index.js'],\n\t},\n}, async ({t, $$}) => {\n\tawait $$`git tag v0.0.0`;\n\tawait t.context.createFile('.github/workflows/main.yml');\n\tawait t.context.createFile('.github/pull_request_template.md');\n\tawait t.context.createFile('index.js');\n\tawait $$`git add -A`;\n\tawait $$`git commit -m \"added\"`;\n});\n"
  },
  {
    "path": "test/util/get-npm-package-access.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/util.js')>>} */\nconst createFixture = _createFixture('../../source/util.js', import.meta.url);\n\ntest('main', createFixture, [{\n\tcommand: 'npm access get status @my/pkg --json',\n\tstdout: '{\"@my/pkg\": \"public\"}',\n}], async ({t, testedModule: {getNpmPackageAccess}}) => {\n\tt.is(\n\t\tawait getNpmPackageAccess({name: '@my/pkg'}),\n\t\t'public',\n\t);\n});\n\ntest('with publishConfig.registry', createFixture, [{\n\tcommand: 'npm access get status @my/pkg --json --registry https://registry.npmjs.org/',\n\tstdout: '{\"@my/pkg\": \"public\"}',\n}], async ({t, testedModule: {getNpmPackageAccess}}) => {\n\tt.is(\n\t\tawait getNpmPackageAccess({\n\t\t\tname: '@my/pkg',\n\t\t\tpublishConfig: {\n\t\t\t\tregistry: 'https://registry.npmjs.org/',\n\t\t\t},\n\t\t}),\n\t\t'public',\n\t);\n});\n\ntest('with external registry', createFixture, [{\n\tcommand: 'npm access get status @my/pkg --json --registry http://my-internal-registry.local',\n\tstdout: '{\"@my/pkg\": \"private\"}',\n}], async ({t, testedModule: {getNpmPackageAccess}}) => {\n\tt.is(\n\t\tawait getNpmPackageAccess({\n\t\t\tname: '@my/pkg',\n\t\t\tpublishConfig: {\n\t\t\t\tregistry: 'http://my-internal-registry.local',\n\t\t\t},\n\t\t}),\n\t\t'private',\n\t);\n});\n"
  },
  {
    "path": "test/util/get-pre-release-prefix.js",
    "content": "import process from 'node:process';\nimport test from 'ava';\nimport {_createFixture} from '../_helpers/stub-execa.js';\nimport {getPreReleasePrefix as originalGetPreReleasePrefix} from '../../source/util.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/util.js')>>} */\nconst createFixture = _createFixture('../../source/util.js', import.meta.url);\n\ntest('returns preid postfix if set - npm', createFixture, [{\n\tcommand: 'npm config get preid',\n\tstdout: 'pre',\n}], async ({t, testedModule: {getPreReleasePrefix}}) => {\n\tt.is(\n\t\tawait getPreReleasePrefix({cli: 'npm'}),\n\t\t'pre',\n\t);\n});\n\ntest('returns preid postfix if set - yarn', createFixture, [{\n\tcommand: 'yarn config get preid',\n\tstdout: 'pre',\n}], async ({t, testedModule: {getPreReleasePrefix}}) => {\n\tt.is(\n\t\tawait getPreReleasePrefix({cli: 'yarn'}),\n\t\t'pre',\n\t);\n});\n\ntest('returns empty string if not set - npm', createFixture, [{\n\tcommand: 'npm config get preid',\n\tstdout: 'undefined',\n}], async ({t, testedModule: {getPreReleasePrefix}}) => {\n\tt.is(\n\t\tawait getPreReleasePrefix({cli: 'npm'}),\n\t\t'',\n\t);\n});\n\ntest('returns empty string if not set - yarn', createFixture, [{\n\tcommand: 'yarn config get preid',\n\tstdout: 'undefined',\n}], async ({t, testedModule: {getPreReleasePrefix}}) => {\n\tt.is(\n\t\tawait getPreReleasePrefix({cli: 'yarn'}),\n\t\t'',\n\t);\n});\n\ntest('no options passed', async t => {\n\tawait t.throwsAsync(\n\t\toriginalGetPreReleasePrefix(),\n\t\t{message: 'Config is missing key `cli`'},\n\t);\n\n\tawait t.throwsAsync(\n\t\toriginalGetPreReleasePrefix({}),\n\t\t{message: 'Config is missing key `cli`'},\n\t);\n});\n\ntest.serial('returns actual value', async t => {\n\tconst originalPreid = process.env.NPM_CONFIG_PREID;\n\tprocess.env.NPM_CONFIG_PREID = 'beta';\n\n\tt.is(await originalGetPreReleasePrefix({cli: 'npm'}), 'beta');\n\n\tprocess.env.NPM_CONFIG_PREID = originalPreid;\n});\n"
  },
  {
    "path": "test/util/get-tag-version-prefix.js",
    "content": "import test from 'ava';\nimport {_createFixture} from '../_helpers/stub-execa.js';\nimport {getTagVersionPrefix as originalGetTagVersionPrefix} from '../../source/util.js';\nimport {npmConfig, yarnConfig, pnpmConfig} from '../../source/package-manager/configs.js';\n\n/** @type {ReturnType<typeof _createFixture<import('../../source/util.js')>>} */\nconst createFixture = _createFixture('../../source/util.js', import.meta.url);\n\ntest('returns tag prefix - npm', createFixture, [{\n\tcommand: 'npm config get tag-version-prefix --workspaces=false',\n\tstdout: 'ver',\n}], async ({t, testedModule: {getTagVersionPrefix}}) => {\n\tt.is(\n\t\tawait getTagVersionPrefix(npmConfig),\n\t\t'ver',\n\t);\n});\n\ntest('returns preId postfix - yarn', createFixture, [{\n\tcommand: 'yarn config get version-tag-prefix',\n\tstdout: 'ver',\n}], async ({t, testedModule: {getTagVersionPrefix}}) => {\n\tt.is(\n\t\tawait getTagVersionPrefix(yarnConfig),\n\t\t'ver',\n\t);\n});\n\ntest('defaults to \"v\" when command fails', createFixture, [{\n\tcommand: 'npm config get tag-version-prefix --workspaces=false',\n\texitCode: 1,\n}], async ({t, testedModule: {getTagVersionPrefix}}) => {\n\tt.is(\n\t\tawait getTagVersionPrefix(npmConfig),\n\t\t'v',\n\t);\n});\n\ntest('returns tag prefix - pnpm (uses npm config)', createFixture, [{\n\tcommand: 'npm config get tag-version-prefix --workspaces=false',\n\tstdout: 'v',\n}], async ({t, testedModule: {getTagVersionPrefix}}) => {\n\tt.is(\n\t\tawait getTagVersionPrefix(pnpmConfig),\n\t\t'v',\n\t);\n});\n\ntest('returns custom tag prefix - pnpm', createFixture, [{\n\tcommand: 'npm config get tag-version-prefix --workspaces=false',\n\tstdout: 'ver',\n}], async ({t, testedModule: {getTagVersionPrefix}}) => {\n\tt.is(\n\t\tawait getTagVersionPrefix(pnpmConfig),\n\t\t'ver',\n\t);\n});\n\ntest('returns empty string tag prefix - pnpm', createFixture, [{\n\tcommand: 'npm config get tag-version-prefix --workspaces=false',\n\tstdout: '',\n}], async ({t, testedModule: {getTagVersionPrefix}}) => {\n\tt.is(\n\t\tawait getTagVersionPrefix(pnpmConfig),\n\t\t'',\n\t);\n});\n\ntest('pnpm defaults to \"v\" when npm config fails', createFixture, [{\n\tcommand: 'npm config get tag-version-prefix --workspaces=false',\n\texitCode: 1,\n}], async ({t, testedModule: {getTagVersionPrefix}}) => {\n\tt.is(\n\t\tawait getTagVersionPrefix(pnpmConfig),\n\t\t'v',\n\t);\n});\n\ntest('no options passed', async t => {\n\tawait t.throwsAsync(\n\t\toriginalGetTagVersionPrefix(),\n\t\t{message: 'Config is missing key `tagVersionPrefixCommand`'},\n\t);\n\n\tawait t.throwsAsync(\n\t\toriginalGetTagVersionPrefix({}),\n\t\t{message: 'Config is missing key `tagVersionPrefixCommand`'},\n\t);\n});\n"
  },
  {
    "path": "test/util/hyperlinks.js",
    "content": "import test from 'ava';\nimport esmock from 'esmock';\n\nconst MOCK_REPO_URL = 'https://github.com/unicorn/rainbow';\nconst MOCK_COMMIT_HASH = '5063f8a';\nconst MOCK_COMMIT_RANGE = `${MOCK_COMMIT_HASH}...master`;\n\nconst verifyLinks = test.macro(async (t, {linksSupported}, assertions) => {\n\t/** @type {typeof import('../../source/util.js')} */\n\tconst util = await esmock('../../source/util.js', {}, {\n\t\t'supports-hyperlinks': {\n\t\t\tstdout: linksSupported,\n\t\t\tstderr: linksSupported,\n\t\t},\n\t});\n\n\tawait assertions({t, util});\n});\n\ntest('linkifyIssues correctly links issues', verifyLinks, {\n\tlinksSupported: true,\n}, ({t, util: {linkifyIssues}}) => {\n\tt.is(linkifyIssues(MOCK_REPO_URL, 'Commit message - fixes #4'), 'Commit message - fixes \u001b]8;;https://github.com/unicorn/rainbow/issues/4\u0007#4\u001b]8;;\u0007');\n\tt.is(linkifyIssues(MOCK_REPO_URL, 'Commit message - fixes #3 #4'), 'Commit message - fixes \u001b]8;;https://github.com/unicorn/rainbow/issues/3\u0007#3\u001b]8;;\u0007 \u001b]8;;https://github.com/unicorn/rainbow/issues/4\u0007#4\u001b]8;;\u0007');\n\tt.is(linkifyIssues(MOCK_REPO_URL, 'Commit message - fixes foo/bar#4'), 'Commit message - fixes \u001b]8;;https://github.com/foo/bar/issues/4\u0007foo/bar#4\u001b]8;;\u0007');\n});\n\ntest('linkifyIssues returns raw message if url is not provided', verifyLinks, {\n\tlinksSupported: true,\n}, ({t, util: {linkifyIssues}}) => {\n\tconst message = 'Commit message - fixes #5';\n\tt.is(linkifyIssues(undefined, message), message);\n});\n\ntest('linkifyIssues returns raw message if terminalLink is not supported', verifyLinks, {\n\tlinksSupported: false,\n}, ({t, util: {linkifyIssues}}) => {\n\tconst message = 'Commit message - fixes #6';\n\tt.is(linkifyIssues(MOCK_REPO_URL, message), `${message} ${MOCK_REPO_URL}/issues/6`);\n});\n\ntest('linkifyCommit correctly links commits', verifyLinks, {\n\tlinksSupported: true,\n}, ({t, util: {linkifyCommit}}) => {\n\tt.is(linkifyCommit(MOCK_REPO_URL, MOCK_COMMIT_HASH), '\u001b]8;;https://github.com/unicorn/rainbow/commit/5063f8a\u00075063f8a\u001b]8;;\u0007');\n});\n\ntest('linkifyCommit returns raw commit hash if url is not provided', verifyLinks, {\n\tlinksSupported: true,\n}, ({t, util: {linkifyCommit}}) => {\n\tt.is(linkifyCommit(undefined, MOCK_COMMIT_HASH), MOCK_COMMIT_HASH);\n});\n\ntest('linkifyCommit returns raw commit hash if terminalLink is not supported', verifyLinks, {\n\tlinksSupported: false,\n}, ({t, util: {linkifyCommit}}) => {\n\tt.is(linkifyCommit(MOCK_REPO_URL, MOCK_COMMIT_HASH), `${MOCK_COMMIT_HASH} ${MOCK_REPO_URL}/commit/${MOCK_COMMIT_HASH}`);\n});\n\ntest('linkifyCommitRange returns raw commitRange if url is not provided', verifyLinks, {\n\tlinksSupported: true,\n}, ({t, util: {linkifyCommitRange}}) => {\n\tt.is(linkifyCommitRange(undefined, MOCK_COMMIT_RANGE), MOCK_COMMIT_RANGE);\n});\n\ntest('linkifyCommitRange returns raw commitRange if terminalLink is not supported', verifyLinks, {\n\tlinksSupported: false,\n}, ({t, util: {linkifyCommitRange}}) => {\n\tt.is(linkifyCommitRange(MOCK_REPO_URL, MOCK_COMMIT_RANGE), `${MOCK_COMMIT_RANGE} ${MOCK_REPO_URL}/compare/${MOCK_COMMIT_RANGE}`);\n});\n\ntest('linkifyCommitRange correctly links commit range', verifyLinks, {\n\tlinksSupported: true,\n}, ({t, util: {linkifyCommitRange}}) => {\n\tt.is(linkifyCommitRange(MOCK_REPO_URL, MOCK_COMMIT_RANGE), '\u001b]8;;https://github.com/unicorn/rainbow/compare/5063f8a...master\u00075063f8a...master\u001b]8;;\u0007');\n});\n"
  },
  {
    "path": "test/util/join-list.js",
    "content": "import test from 'ava';\nimport stripAnsi from 'strip-ansi';\nimport {joinList} from '../../source/util.js';\n\nconst testJoinList = test.macro((t, {list, expected}) => {\n\tconst output = joinList(list);\n\tt.is(stripAnsi(output), expected);\n});\n\ntest('one item', testJoinList, {\n\tlist: ['foo'],\n\texpected: '- foo',\n});\n\ntest('two items', testJoinList, {\n\tlist: ['foo', 'bar'],\n\texpected: '- foo\\n- bar',\n});\n\ntest('multiple items', testJoinList, {\n\tlist: ['foo', 'bar', 'baz'],\n\texpected: '- foo\\n- bar\\n- baz',\n});\n"
  },
  {
    "path": "test/util/parse-git-url.js",
    "content": "import test from 'ava';\nimport {parseGitUrl} from '../../source/util.js';\n\n// Valid URL formats\ntest('parses HTTPS URL with .git suffix', t => {\n\tt.is(parseGitUrl('https://github.com/owner/repo.git'), 'https://github.com/owner/repo');\n});\n\ntest('parses HTTPS URL without .git suffix', t => {\n\tt.is(parseGitUrl('https://github.com/owner/repo'), 'https://github.com/owner/repo');\n});\n\ntest('parses HTTP URL with .git suffix', t => {\n\tt.is(parseGitUrl('http://github.com/owner/repo.git'), 'https://github.com/owner/repo');\n});\n\ntest('parses HTTP URL without .git suffix', t => {\n\tt.is(parseGitUrl('http://github.com/owner/repo'), 'https://github.com/owner/repo');\n});\n\ntest('parses SSH URL (git@host:owner/repo.git)', t => {\n\tt.is(parseGitUrl('git@github.com:owner/repo.git'), 'https://github.com/owner/repo');\n});\n\ntest('parses SSH URL without .git suffix', t => {\n\tt.is(parseGitUrl('git@github.com:owner/repo'), 'https://github.com/owner/repo');\n});\n\ntest('parses SSH URL with fragment', t => {\n\tt.is(parseGitUrl('git@github.com:owner/repo.git#main'), 'https://github.com/owner/repo');\n});\n\ntest('parses SSH URL with query', t => {\n\tt.is(parseGitUrl('git@github.com:owner/repo.git?ref=main'), 'https://github.com/owner/repo');\n});\n\ntest('parses git+https URL', t => {\n\tt.is(parseGitUrl('git+https://github.com/owner/repo.git'), 'https://github.com/owner/repo');\n});\n\ntest('parses git+https URL without .git suffix', t => {\n\tt.is(parseGitUrl('git+https://github.com/owner/repo'), 'https://github.com/owner/repo');\n});\n\ntest('parses git+https URL with fragment', t => {\n\tt.is(\n\t\tparseGitUrl('git+https://git.company.com/owner/repo.git#main'),\n\t\t'https://git.company.com/owner/repo',\n\t);\n});\n\ntest('parses git+https URL with query and fragment without .git suffix', t => {\n\tt.is(\n\t\tparseGitUrl('git+https://git.company.com/owner/repo?ref=main#readme'),\n\t\t'https://git.company.com/owner/repo',\n\t);\n});\n\ntest('parses ssh:// URL', t => {\n\tt.is(parseGitUrl('ssh://git@github.com/owner/repo.git'), 'https://github.com/owner/repo');\n});\n\ntest('parses ssh:// URL without .git suffix', t => {\n\tt.is(parseGitUrl('ssh://git@github.com/owner/repo'), 'https://github.com/owner/repo');\n});\n\ntest('parses ssh:// URL with query', t => {\n\tt.is(\n\t\tparseGitUrl('ssh://git@git.company.com/owner/repo.git?ref=main'),\n\t\t'https://git.company.com/owner/repo',\n\t);\n});\n\ntest('parses ssh:// URL with fragment without .git suffix', t => {\n\tt.is(\n\t\tparseGitUrl('ssh://git@git.company.com/owner/repo#main'),\n\t\t'https://git.company.com/owner/repo',\n\t);\n});\n\n// GitHub Enterprise URLs\ntest('parses GitHub Enterprise HTTPS URL', t => {\n\tt.is(\n\t\tparseGitUrl('https://github.enterprise.com/org/project.git'),\n\t\t'https://github.enterprise.com/org/project',\n\t);\n});\n\ntest('parses GitHub Enterprise SSH URL', t => {\n\tt.is(\n\t\tparseGitUrl('git@github.enterprise.com:org/project.git'),\n\t\t'https://github.enterprise.com/org/project',\n\t);\n});\n\ntest('parses GitHub Enterprise SSH URL without .git suffix', t => {\n\tt.is(\n\t\tparseGitUrl('git@github.enterprise.com:org/project'),\n\t\t'https://github.enterprise.com/org/project',\n\t);\n});\n\ntest('parses GitHub Enterprise with subdomain', t => {\n\tt.is(\n\t\tparseGitUrl('git@git.company.internal:team/repo.git'),\n\t\t'https://git.company.internal/team/repo',\n\t);\n});\n\n// Special characters in names\ntest('handles hyphens in owner and repo names', t => {\n\tt.is(parseGitUrl('https://github.com/my-org/my-repo.git'), 'https://github.com/my-org/my-repo');\n});\n\ntest('handles underscores in owner and repo names', t => {\n\tt.is(parseGitUrl('https://github.com/my_org/my_repo.git'), 'https://github.com/my_org/my_repo');\n});\n\ntest('handles dots in owner and repo names', t => {\n\tt.is(parseGitUrl('https://github.com/my.org/my.repo.git'), 'https://github.com/my.org/my.repo');\n});\n\ntest('handles numbers in owner and repo names', t => {\n\tt.is(parseGitUrl('https://github.com/org123/repo456.git'), 'https://github.com/org123/repo456');\n});\n\ntest('handles mixed special characters', t => {\n\tt.is(\n\t\tparseGitUrl('https://github.com/my-org_123/my-repo_456.git'),\n\t\t'https://github.com/my-org_123/my-repo_456',\n\t);\n});\n\n// Ports in URLs\ntest('handles HTTPS URL with port', t => {\n\tt.is(\n\t\tparseGitUrl('https://github.com:443/owner/repo.git'),\n\t\t'https://github.com:443/owner/repo',\n\t);\n});\n\ntest('handles git+https URL with port', t => {\n\tt.is(\n\t\tparseGitUrl('git+https://github.com:443/owner/repo.git'),\n\t\t'https://github.com:443/owner/repo',\n\t);\n});\n\ntest('handles ssh:// URL with port', t => {\n\tt.is(\n\t\tparseGitUrl('ssh://git@github.com:2222/owner/repo.git'),\n\t\t'https://github.com:2222/owner/repo',\n\t);\n});\n\ntest('handles HTTP URL with custom port', t => {\n\tt.is(\n\t\tparseGitUrl('http://git.company.com:8080/team/project.git'),\n\t\t'https://git.company.com:8080/team/project',\n\t);\n});\n\n// Edge cases - invalid inputs\ntest('returns undefined for empty string', t => {\n\tt.is(parseGitUrl(''), undefined);\n});\n\ntest('returns undefined for non-string input (null)', t => {\n\tt.is(parseGitUrl(null), undefined);\n});\n\ntest('returns undefined for non-string input (undefined)', t => {\n\tt.is(parseGitUrl(undefined), undefined);\n});\n\ntest('returns undefined for non-string input (number)', t => {\n\tt.is(parseGitUrl(123), undefined);\n});\n\ntest('returns undefined for non-string input (object)', t => {\n\tt.is(parseGitUrl({}), undefined);\n});\n\n// Malformed URLs\ntest('returns undefined for URL with extra slashes', t => {\n\tt.is(parseGitUrl('https://github.com//owner//repo.git'), undefined);\n});\n\ntest('returns undefined for URL with missing owner', t => {\n\tt.is(parseGitUrl('https://github.com/repo.git'), undefined);\n});\n\ntest('returns undefined for URL with missing repo', t => {\n\tt.is(parseGitUrl('https://github.com/owner/.git'), undefined);\n});\n\ntest('returns undefined for shorthand notation (not handled by this function)', t => {\n\tt.is(parseGitUrl('github:owner/repo'), undefined);\n\tt.is(parseGitUrl('owner/repo'), undefined);\n});\n\ntest('returns undefined for git:// protocol (not supported)', t => {\n\tt.is(parseGitUrl('git://github.com/owner/repo.git'), undefined);\n});\n\ntest('returns undefined for ftp:// protocol', t => {\n\tt.is(parseGitUrl('ftp://github.com/owner/repo.git'), undefined);\n});\n\ntest('returns undefined for URL with path beyond repo', t => {\n\tt.is(parseGitUrl('https://github.com/owner/repo/extra/path.git'), undefined);\n});\n\n// ReDoS protection tests\ntest('handles very long hostname without catastrophic backtracking', t => {\n\tconst longHost = 'a'.repeat(10_000);\n\tconst url = `https://${longHost}.com/owner/repo.git`;\n\n\t// This should complete quickly (< 100ms) if ReDoS protection works\n\tconst start = Date.now();\n\tconst result = parseGitUrl(url);\n\tconst duration = Date.now() - start;\n\n\tt.is(result, `https://${longHost}.com/owner/repo`);\n\tt.true(duration < 100, `Parsing took ${duration}ms, expected < 100ms`);\n});\n\ntest('handles very long owner name without catastrophic backtracking', t => {\n\tconst longOwner = 'a'.repeat(10_000);\n\tconst url = `https://github.com/${longOwner}/repo.git`;\n\n\tconst start = Date.now();\n\tconst result = parseGitUrl(url);\n\tconst duration = Date.now() - start;\n\n\tt.is(result, `https://github.com/${longOwner}/repo`);\n\tt.true(duration < 100, `Parsing took ${duration}ms, expected < 100ms`);\n});\n\ntest('handles very long repo name without catastrophic backtracking', t => {\n\tconst longRepo = 'a'.repeat(10_000);\n\tconst url = `https://github.com/owner/${longRepo}.git`;\n\n\tconst start = Date.now();\n\tconst result = parseGitUrl(url);\n\tconst duration = Date.now() - start;\n\n\tt.is(result, `https://github.com/owner/${longRepo}`);\n\tt.true(duration < 100, `Parsing took ${duration}ms, expected < 100ms`);\n});\n\ntest('handles pathological input with many slashes without hanging', t => {\n\tconst url = 'https://' + '/'.repeat(10_000) + 'github.com/owner/repo.git';\n\n\tconst start = Date.now();\n\tconst result = parseGitUrl(url);\n\tconst duration = Date.now() - start;\n\n\t// Should fail gracefully and quickly\n\tt.is(result, undefined);\n\tt.true(duration < 100, `Parsing took ${duration}ms, expected < 100ms`);\n});\n\ntest('handles pathological input with many dots without hanging', t => {\n\tconst manyDots = '.'.repeat(10_000);\n\tconst url = `https://github.com/owner/${manyDots}repo.git`;\n\n\tconst start = Date.now();\n\tconst result = parseGitUrl(url);\n\tconst duration = Date.now() - start;\n\n\t// This is technically valid (dots followed by alphanumeric), just unusual\n\t// The important part is that it completes quickly without hanging\n\tt.is(result, `https://github.com/owner/${manyDots}repo`);\n\tt.true(duration < 100, `Parsing took ${duration}ms, expected < 100ms`);\n});\n\ntest('rejects repo name that is only dots', t => {\n\tconst onlyDots = '.'.repeat(100);\n\tconst url = `https://github.com/owner/${onlyDots}.git`;\n\n\tconst start = Date.now();\n\tconst result = parseGitUrl(url);\n\tconst duration = Date.now() - start;\n\n\t// Should reject because repo has no alphanumeric characters\n\tt.is(result, undefined);\n\tt.true(duration < 100, `Parsing took ${duration}ms, expected < 100ms`);\n});\n\ntest('handles pathological input with alternating patterns without hanging', t => {\n\tconst url = 'https://github.com/' + 'a/'.repeat(5000) + 'repo.git';\n\n\tconst start = Date.now();\n\tconst result = parseGitUrl(url);\n\tconst duration = Date.now() - start;\n\n\t// Should fail gracefully and quickly\n\tt.is(result, undefined);\n\tt.true(duration < 100, `Parsing took ${duration}ms, expected < 100ms`);\n});\n\ntest('handles extremely long malformed URL without hanging', t => {\n\tconst url = 'x'.repeat(100_000);\n\n\tconst start = Date.now();\n\tconst result = parseGitUrl(url);\n\tconst duration = Date.now() - start;\n\n\tt.is(result, undefined);\n\tt.true(duration < 100, `Parsing took ${duration}ms, expected < 100ms`);\n});\n\n// Real-world examples\ntest('parses real GitHub URL', t => {\n\tt.is(parseGitUrl('https://github.com/sindresorhus/np.git'), 'https://github.com/sindresorhus/np');\n});\n\ntest('parses real GitLab URL', t => {\n\tt.is(parseGitUrl('https://gitlab.com/gitlab-org/gitlab.git'), 'https://gitlab.com/gitlab-org/gitlab');\n});\n\ntest('parses real Bitbucket URL', t => {\n\tt.is(\n\t\tparseGitUrl('https://bitbucket.org/atlassian/python-bitbucket.git'),\n\t\t'https://bitbucket.org/atlassian/python-bitbucket',\n\t);\n});\n\ntest('parses self-hosted GitLab instance', t => {\n\tt.is(\n\t\tparseGitUrl('git@gitlab.company.com:frontend/webapp.git'),\n\t\t'https://gitlab.company.com/frontend/webapp',\n\t);\n});\n\n// Normalization tests\ntest('normalizes HTTP to HTTPS', t => {\n\tconst result = parseGitUrl('http://github.com/owner/repo.git');\n\tt.true(result.startsWith('https://'), 'Should convert HTTP to HTTPS');\n});\n\ntest('removes .git suffix consistently', t => {\n\tconst withGit = parseGitUrl('https://github.com/owner/repo.git');\n\tconst withoutGit = parseGitUrl('https://github.com/owner/repo');\n\tt.is(withGit, withoutGit, 'Should normalize .git suffix');\n});\n\n// Case sensitivity tests\ntest('preserves case in hostnames', t => {\n\tt.is(\n\t\tparseGitUrl('https://GitHub.COM/owner/repo.git'),\n\t\t'https://GitHub.COM/owner/repo',\n\t);\n});\n\ntest('preserves case in owner and repo names', t => {\n\tt.is(\n\t\tparseGitUrl('https://github.com/MyOrg/MyRepo.git'),\n\t\t'https://github.com/MyOrg/MyRepo',\n\t);\n});\n\n// Multiple protocol variations\ntest('handles git+https with custom host', t => {\n\tt.is(\n\t\tparseGitUrl('git+https://git.example.com/team/project.git'),\n\t\t'https://git.example.com/team/project',\n\t);\n});\n\ntest('handles git+https without .git suffix on custom host', t => {\n\tt.is(\n\t\tparseGitUrl('git+https://git.example.com/team/project'),\n\t\t'https://git.example.com/team/project',\n\t);\n});\n\ntest('handles ssh:// with custom host', t => {\n\tt.is(\n\t\tparseGitUrl('ssh://git@git.example.com/team/project.git'),\n\t\t'https://git.example.com/team/project',\n\t);\n});\n\ntest('handles ssh:// without .git suffix on custom host', t => {\n\tt.is(\n\t\tparseGitUrl('ssh://git@git.example.com/team/project'),\n\t\t'https://git.example.com/team/project',\n\t);\n});\n\n// Whitespace handling\ntest('does not trim whitespace (garbage in, garbage out)', t => {\n\tt.is(parseGitUrl('  https://github.com/owner/repo.git  '), undefined);\n});\n\ntest('does not handle URLs with internal whitespace', t => {\n\tt.is(parseGitUrl('https://github.com/owner /repo.git'), undefined);\n});\n\n// Edge case: Double .git suffix\ntest('handles double .git suffix correctly', t => {\n\t// With greedy matching, .git.git is interpreted as repo named \"repo.git\"\n\t// This is the safer assumption (could be an actual repo named repo.git)\n\tt.is(parseGitUrl('https://github.com/owner/repo.git.git'), 'https://github.com/owner/repo.git');\n});\n\ntest('handles double .git suffix in SSH format', t => {\n\tt.is(parseGitUrl('git@github.com:owner/repo.git.git'), 'https://github.com/owner/repo.git');\n});\n\ntest('handles double .git suffix in git+https format', t => {\n\tt.is(parseGitUrl('git+https://github.com/owner/repo.git.git'), 'https://github.com/owner/repo.git');\n});\n\n// Edge case: Repo name contains .git\ntest('handles repo name containing .git (e.g., my.git.git)', t => {\n\t// The repo is actually named \"my.git\", the URL is my.git.git\n\tt.is(parseGitUrl('https://github.com/owner/my.git.git'), 'https://github.com/owner/my.git');\n});\n\ntest('handles repo name that is exactly .git', t => {\n\t// Repo named \".git\" (unusual but technically valid - has alphanumeric chars)\n\tt.is(parseGitUrl('https://github.com/owner/.git.git'), 'https://github.com/owner/.git');\n});\n\ntest('handles repo with multiple .git patterns in name', t => {\n\tt.is(parseGitUrl('https://github.com/owner/my.git.project.git'), 'https://github.com/owner/my.git.project');\n});\n\n// Edge case: Scoped packages (@ symbol)\ntest('handles scoped package notation with @ symbol', t => {\n\t// @ is allowed in owner names (though unusual for git URLs)\n\tt.is(parseGitUrl('https://github.com/@scope/package.git'), 'https://github.com/@scope/package');\n});\n\ntest('handles SSH URL with @ in path', t => {\n\t// @ is allowed in paths\n\tt.is(parseGitUrl('git@github.com:@scope/package.git'), 'https://github.com/@scope/package');\n});\n\n// Edge case: Single character names\ntest('handles single character owner', t => {\n\tt.is(parseGitUrl('https://github.com/a/repo.git'), 'https://github.com/a/repo');\n});\n\ntest('handles single character repo', t => {\n\tt.is(parseGitUrl('https://github.com/owner/r.git'), 'https://github.com/owner/r');\n});\n\ntest('handles single character for both owner and repo', t => {\n\tt.is(parseGitUrl('https://github.com/a/b.git'), 'https://github.com/a/b');\n});\n\n// Edge case: IP addresses as hosts\ntest('handles IPv4 address as host', t => {\n\tt.is(parseGitUrl('https://192.168.1.1/owner/repo.git'), 'https://192.168.1.1/owner/repo');\n});\n\ntest('handles IPv4 address with port', t => {\n\tt.is(parseGitUrl('https://192.168.1.1:8080/owner/repo.git'), 'https://192.168.1.1:8080/owner/repo');\n});\n\ntest('handles localhost as host', t => {\n\tt.is(parseGitUrl('https://localhost/owner/repo.git'), 'https://localhost/owner/repo');\n});\n\ntest('handles localhost with port', t => {\n\tt.is(parseGitUrl('http://localhost:3000/owner/repo.git'), 'https://localhost:3000/owner/repo');\n});\n\n// Edge case: Multiple subdomains\ntest('handles multiple subdomains in host', t => {\n\tt.is(\n\t\tparseGitUrl('https://git.prod.company.example.com/owner/repo.git'),\n\t\t'https://git.prod.company.example.com/owner/repo',\n\t);\n});\n\ntest('handles deeply nested subdomains', t => {\n\tt.is(\n\t\tparseGitUrl('https://a.b.c.d.e.f.example.com/owner/repo.git'),\n\t\t'https://a.b.c.d.e.f.example.com/owner/repo',\n\t);\n});\n\n// Edge case: Protocol variations\ntest('handles uppercase HTTP protocol', t => {\n\tt.is(parseGitUrl('HTTP://github.com/owner/repo.git'), 'https://github.com/owner/repo');\n});\n\ntest('handles uppercase HTTPS protocol', t => {\n\tt.is(parseGitUrl('HTTPS://github.com/owner/repo.git'), 'https://github.com/owner/repo');\n});\n\ntest('handles mixed case protocol', t => {\n\tt.is(parseGitUrl('HtTpS://github.com/owner/repo.git'), 'https://github.com/owner/repo');\n});\n\n// Edge case: Malformed protocols\ntest('rejects malformed protocol (htp)', t => {\n\tt.is(parseGitUrl('htp://github.com/owner/repo.git'), undefined);\n});\n\ntest('rejects malformed protocol (htps)', t => {\n\tt.is(parseGitUrl('htps://github.com/owner/repo.git'), undefined);\n});\n\ntest('rejects protocol with extra characters', t => {\n\tt.is(parseGitUrl('httpss://github.com/owner/repo.git'), undefined);\n});\n\n// Edge case: Numeric owner and repo\ntest('handles fully numeric owner', t => {\n\tt.is(parseGitUrl('https://github.com/123456/repo.git'), 'https://github.com/123456/repo');\n});\n\ntest('handles fully numeric repo', t => {\n\tt.is(parseGitUrl('https://github.com/owner/789012.git'), 'https://github.com/owner/789012');\n});\n\ntest('handles both owner and repo as numbers', t => {\n\tt.is(parseGitUrl('https://github.com/123/456.git'), 'https://github.com/123/456');\n});\n\n// Edge case: Special separators\ntest('rejects SSH URL with slash instead of colon', t => {\n\tt.is(parseGitUrl('git@github.com/owner/repo.git'), undefined);\n});\n\ntest('rejects git@ URL without colon separator', t => {\n\tt.is(parseGitUrl('git@github.comowner/repo.git'), undefined);\n});\n\n// Edge case: Missing components\ntest('rejects URL with empty owner (double slash)', t => {\n\tt.is(parseGitUrl('https://github.com//repo.git'), undefined);\n});\n\ntest('rejects URL with empty repo', t => {\n\tt.is(parseGitUrl('https://github.com/owner//.git'), undefined);\n});\n\ntest('rejects SSH URL with empty owner', t => {\n\tt.is(parseGitUrl('git@github.com:/repo.git'), undefined);\n});\n\n// Edge case: Trailing characters\ntest('rejects URL with trailing slash after .git', t => {\n\tt.is(parseGitUrl('https://github.com/owner/repo.git/'), undefined);\n});\n\ntest('rejects URL with trailing text after .git', t => {\n\tt.is(parseGitUrl('https://github.com/owner/repo.git/extra'), undefined);\n});\n\n// Edge case: Special characters in names\ntest('handles all special chars together in name', t => {\n\tt.is(\n\t\tparseGitUrl('https://github.com/my-org_123.test/my-repo_456.test.git'),\n\t\t'https://github.com/my-org_123.test/my-repo_456.test',\n\t);\n});\n\ntest('rejects owner with only special characters (no alphanumeric)', t => {\n\tt.is(parseGitUrl('https://github.com/---/repo.git'), undefined);\n});\n\ntest('rejects repo with only special characters (no alphanumeric)', t => {\n\tt.is(parseGitUrl('https://github.com/owner/---.git'), undefined);\n});\n\ntest('rejects owner with only dots (no alphanumeric)', t => {\n\tt.is(parseGitUrl('https://github.com/.../repo.git'), undefined);\n});\n\n// Edge case: URL-like patterns in repo names\ntest('handles repo name with dots that looks URL-like', t => {\n\tt.is(\n\t\tparseGitUrl('https://github.com/owner/example.com.git'),\n\t\t'https://github.com/owner/example.com',\n\t);\n});\n\ntest('handles repo name with colons', t => {\n\t// Colons are valid in URLs, but [^\\s/] allows them\n\tt.is(\n\t\tparseGitUrl('https://github.com/owner/repo:v1.0.git'),\n\t\t'https://github.com/owner/repo:v1.0',\n\t);\n});\n\n// Edge case: git+ssh protocol (not supported)\ntest('rejects git+ssh protocol (not supported)', t => {\n\tt.is(parseGitUrl('git+ssh://git@github.com/owner/repo.git'), undefined);\n});\n\n// Edge case: ssh with different username (not supported)\ntest('rejects ssh with non-git username', t => {\n\tt.is(parseGitUrl('ssh://user@github.com/owner/repo.git'), undefined);\n});\n\ntest('rejects ssh with numeric username', t => {\n\tt.is(parseGitUrl('ssh://123@github.com/owner/repo.git'), undefined);\n});\n\n// Edge case: Query parameters and fragments (should reject)\ntest('strips query parameters', t => {\n\tt.is(parseGitUrl('https://github.com/owner/repo.git?ref=main'), 'https://github.com/owner/repo');\n});\n\ntest('strips fragment', t => {\n\tt.is(parseGitUrl('https://github.com/owner/repo.git#readme'), 'https://github.com/owner/repo');\n});\n\ntest('strips query and fragment', t => {\n\tt.is(\n\t\tparseGitUrl('https://github.com/owner/repo.git?ref=main#readme'),\n\t\t'https://github.com/owner/repo',\n\t);\n});\n\ntest('returns undefined when URL is only query', t => {\n\tt.is(parseGitUrl('?ref=main'), undefined);\n});\n\ntest('returns undefined when URL is only fragment', t => {\n\tt.is(parseGitUrl('#main'), undefined);\n});\n\n// Edge case: Unusual but valid repo names\ntest('handles repo name starting with dot', t => {\n\tt.is(parseGitUrl('https://github.com/owner/.dotfile.git'), 'https://github.com/owner/.dotfile');\n});\n\ntest('handles repo name with consecutive dots', t => {\n\tt.is(parseGitUrl('https://github.com/owner/my..repo.git'), 'https://github.com/owner/my..repo');\n});\n\ntest('handles repo name ending with dash', t => {\n\tt.is(parseGitUrl('https://github.com/owner/repo-.git'), 'https://github.com/owner/repo-');\n});\n\n// Edge case: Case sensitivity preservation\ntest('preserves exact case in all components', t => {\n\tt.is(\n\t\tparseGitUrl('https://GitHub.COM/MyOrg/MyRepo.git'),\n\t\t'https://GitHub.COM/MyOrg/MyRepo',\n\t);\n});\n\ntest('preserves case in SSH format', t => {\n\tt.is(\n\t\tparseGitUrl('git@GitHub.COM:MyOrg/MyRepo.git'),\n\t\t'https://GitHub.COM/MyOrg/MyRepo',\n\t);\n});\n"
  },
  {
    "path": "test/util/read-pkg.js",
    "content": "import {fileURLToPath} from 'node:url';\nimport path from 'node:path';\nimport test from 'ava';\nimport esmock from 'esmock';\nimport {temporaryDirectory} from 'tempy';\nimport {readPackage, npPackage, npRootDirectory} from '../../source/util.js';\n\nconst rootDirectory = fileURLToPath(new URL('../..', import.meta.url)).slice(0, -1);\n\ntest('without packagePath returns np package.json', async t => {\n\tconst {package_, rootDirectory: packageDirectory} = await readPackage();\n\n\tt.is(package_.name, 'np');\n\tt.is(packageDirectory, rootDirectory);\n});\n\ntest('with packagePath', async t => {\n\tconst fixtureDirectory = path.resolve(rootDirectory, 'test/fixtures/files/one-file');\n\tconst {package_, rootDirectory: packageDirectory} = await readPackage(fixtureDirectory);\n\n\tt.is(package_.name, 'foo');\n\tt.is(packageDirectory, fixtureDirectory);\n});\n\ntest('no package.json', async t => {\n\tawait t.throwsAsync(\n\t\treadPackage(temporaryDirectory()),\n\t\t{message: 'No `package.json` found. Make sure the current directory is a valid package.'},\n\t);\n});\n\ntest('npPackage', t => {\n\tt.is(npPackage.name, 'np');\n});\n\ntest('npRootDirectory', t => {\n\tt.is(npRootDirectory, rootDirectory);\n});\n\ntest('npRootDirectory is correct when process.cwd is different', async t => {\n\tconst cwd = temporaryDirectory();\n\n\t/** @type {import('../../source/util.js')} */\n\tconst util = await esmock('../../source/util.js', {}, {\n\t\t'node:process': {cwd},\n\t});\n\n\tt.is(util.npRootDirectory, rootDirectory);\n});\n"
  },
  {
    "path": "test/util/validate-engine-version-satisfies.js",
    "content": "import test from 'ava';\nimport {validateEngineVersionSatisfies, npPackage} from '../../source/util.js';\n\nconst testEngineRanges = test.macro((t, engine, {above, below}) => {\n\tconst range = npPackage.engines[engine];\n\n\t// Above minimum\n\tt.notThrows(() => validateEngineVersionSatisfies(engine, above));\n\n\tt.throws(\n\t\t() => validateEngineVersionSatisfies(engine, below), // Below minimum\n\t\t{message: `\\`np\\` requires ${engine} ${range}`},\n\t);\n});\n\ntest('node', testEngineRanges, 'node', {above: '99.7.0', below: '16.5.0'});\n\ntest('npm', testEngineRanges, 'npm', {above: '99.20.0', below: '7.18.0'});\n\ntest('git', testEngineRanges, 'git', {above: '99.12.0', below: '2.10.0'});\n\ntest('yarn', testEngineRanges, 'yarn', {above: '99.8.0', below: '1.6.0'});\n\n"
  },
  {
    "path": "test/version.js",
    "content": "import test from 'ava';\nimport sinon from 'sinon';\nimport {template as chalk} from 'chalk-template';\nimport semver from 'semver';\nimport Version from '../source/version.js';\n\nconst INCREMENT_LIST = 'patch, minor, major, prepatch, preminor, premajor, prerelease';\nconst INCREMENT_LIST_OR = 'patch, minor, major, prepatch, preminor, premajor, or prerelease';\n\n/** @param {string} input - Place `{ }` around the version parts to be highlighted. */\nconst makeNewFormattedVersion = input => {\n\tinput = input.replaceAll(/{([^}]*)}/g, '{cyan $1}'); // https://regex101.com/r/rZUIp4/1\n\treturn chalk(`{dim ${input}}`);\n};\n\ntest('new Version - valid', t => {\n\tt.is(new Version('1.0.0').toString(), '1.0.0');\n});\n\ntest('new Version - invalid', t => {\n\tt.throws(\n\t\t() => new Version('major'),\n\t\t{message: 'Version major should be a valid SemVer version.'},\n\t);\n});\n\ntest('new Version - valid w/ valid increment', t => {\n\tt.is(new Version('1.0.0', 'major').toString(), '2.0.0');\n});\n\ntest('new Version - invalid w/ valid increment', t => {\n\tt.throws(\n\t\t() => new Version('major', 'major'),\n\t\t{message: 'Version major should be a valid SemVer version.'},\n\t);\n});\n\ntest('new Version - valid w/ invalid increment', t => {\n\tt.throws(\n\t\t() => new Version('1.0.0', '2.0.0'),\n\t\t{message: `Increment 2.0.0 should be one of ${INCREMENT_LIST_OR}.`},\n\t);\n});\n\ntest('new Version - invalid w/ invalid increment', t => {\n\tt.throws(\n\t\t() => new Version('major', '2.0.0'),\n\t\t{message: 'Version major should be a valid SemVer version.'},\n\t);\n});\n\n// Input as SemVer increment is covered in constructor tests\ntest('setFrom - valid input as version', t => {\n\tt.is(new Version('1.0.0').setFrom('2.0.0').toString(), '2.0.0');\n});\n\ntest('setFrom - invalid input as version', t => {\n\tt.throws(\n\t\t() => new Version('1.0.0').setFrom('200'),\n\t\t{message: `New version 200 should either be one of ${INCREMENT_LIST}, or a valid SemVer version.`},\n\t);\n});\n\ntest('setFrom - valid input is not higher than version', t => {\n\tt.throws(\n\t\t() => new Version('1.0.0').setFrom('0.2.0'),\n\t\t{message: 'New version 0.2.0 should be higher than current version 1.0.0.'},\n\t);\n});\n\ntest('format', t => {\n\tt.is(new Version('0.0.0').format(), makeNewFormattedVersion('0.0.0'));\n});\n\ntest('format - set diff', t => {\n\tt.is(\n\t\tnew Version('1.0.0').format({previousVersion: '0.0.0'}),\n\t\tmakeNewFormattedVersion('{1}.0.0'),\n\t);\n});\n\ntest('format - major', t => {\n\tconst newVersion = makeNewFormattedVersion('{1}.0.0');\n\n\tt.is(new Version('0.0.0').setFrom('major').format(), newVersion);\n\tt.is(new Version('0.0.0').setFrom('1.0.0').format(), newVersion);\n});\n\ntest('format - minor', t => {\n\tconst newVersion = makeNewFormattedVersion('0.{1}.0');\n\n\tt.is(new Version('0.0.0').setFrom('minor').format(), newVersion);\n\tt.is(new Version('0.0.0').setFrom('0.1.0').format(), newVersion);\n});\n\ntest('format - patch', t => {\n\tconst newVersion = makeNewFormattedVersion('0.0.{1}');\n\n\tt.is(new Version('0.0.0').setFrom('patch').format(), newVersion);\n\tt.is(new Version('0.0.0').setFrom('0.0.1').format(), newVersion);\n});\n\ntest('format - premajor', t => {\n\tconst newVersion = makeNewFormattedVersion('{1}.0.0-{0}');\n\n\tt.is(new Version('0.0.0').setFrom('premajor').format(), newVersion);\n\tt.is(new Version('0.0.0').setFrom('1.0.0-0').format(), newVersion);\n});\n\ntest('format - preminor', t => {\n\tconst newVersion = makeNewFormattedVersion('0.{1}.0-{0}');\n\n\tt.is(new Version('0.0.0').setFrom('preminor').format(), newVersion);\n\tt.is(new Version('0.0.0').setFrom('0.1.0-0').format(), newVersion);\n});\n\ntest('format - prepatch', t => {\n\tconst newVersion = makeNewFormattedVersion('0.0.{1}-{0}');\n\n\tt.is(new Version('0.0.0').setFrom('prepatch').format(), newVersion);\n\tt.is(new Version('0.0.0').setFrom('0.0.1-0').format(), newVersion);\n});\n\ntest('format - prerelease', t => {\n\tconst newVersion = makeNewFormattedVersion('0.0.0-{1}');\n\n\tt.is(new Version('0.0.0-0').setFrom('prerelease').format(), newVersion);\n\tt.is(new Version('0.0.0-0').setFrom('0.0.0-1').format(), newVersion);\n});\n\ntest('format - prerelease as prepatch', t => {\n\tconst newVersion = makeNewFormattedVersion('0.0.{1}-{0}');\n\n\tt.is(new Version('0.0.0').setFrom('prerelease').format(), newVersion);\n\tt.is(new Version('0.0.0').setFrom('0.0.1-0').format(), newVersion);\n});\n\ntest('format - prerelease with multiple numbers', t => {\n\tconst newVersion = makeNewFormattedVersion('0.0.{1}-{0.0}');\n\tt.is(new Version('0.0.0').setFrom('0.0.1-0.0').format(), newVersion);\n});\n\ntest('format - prerelease with text', t => {\n\tconst newVersion = makeNewFormattedVersion('0.0.{1}-{alpha.0}');\n\tt.is(new Version('0.0.0').setFrom('0.0.1-alpha.0').format(), newVersion);\n});\n\ntest('format - prerelease diffs', t => {\n\tconst newVersion = makeNewFormattedVersion('0.0.0-1.{2}');\n\n\tt.is(\n\t\tnew Version('0.0.0-1.1').setFrom('0.0.0-1.2').format({previousVersion: '0.0.0-1.1'}),\n\t\tnewVersion,\n\t);\n\n\tt.is(\n\t\tnew Version('0.0.0-1.2').format({previousVersion: '0.0.0-1.1'}),\n\t\tnewVersion,\n\t);\n});\n\ntest('format - custom colors', t => {\n\tt.is(\n\t\tnew Version('1.2.3').format({color: 'green'}),\n\t\tchalk('{green 1.2.3}'),\n\t);\n\n\tt.is(\n\t\tnew Version('1.2.3', 'minor').format({diffColor: 'red'}),\n\t\tchalk('{dim 1.{red 3}.0}'),\n\t);\n\n\tt.is(\n\t\tnew Version('1.2.3', 'patch').format({color: 'bgBlack.red', diffColor: 'yellow'}),\n\t\tchalk('{bgBlack.red 1.2.{yellow 4}}'),\n\t);\n\n\tt.is(\n\t\tnew Version('1.2.3', 'prerelease').format({color: 'bgBlack.red', diffColor: 'yellow'}),\n\t\tchalk('{bgBlack.red 1.2.{yellow 4}-{yellow 0}}'),\n\t);\n});\n\ntest('format - previousVersion as SemVer instance', t => {\n\tconst previousVersion = semver.parse('0.0.0');\n\tconst newVersion = makeNewFormattedVersion('{1}.0.0');\n\n\tconst spy = sinon.spy(semver, 'parse');\n\n\tt.is(new Version('1.0.0').format({previousVersion}), newVersion);\n\tt.true(spy.calledOnce, 'semver.parse was called for previousVersion!');\n\n\tspy.resetHistory();\n\n\tt.is(new Version('1.0.0').format({previousVersion: '0.0.0'}), newVersion);\n\tt.true(spy.calledTwice, 'semver.parse was not called for previousVersion!');\n});\n\ntest('format - invalid previousVersion', t => {\n\tt.throws(\n\t\t() => new Version('1.0.0').format({previousVersion: '000'}),\n\t\t{message: 'Previous version 000 should be a valid SemVer version.'},\n\t);\n});\n\ntest('satisfies', t => {\n\tt.true(new Version('2.15.8').satisfies('>=2.15.8 <3.0.0 || >=3.10.1'));\n\tt.true(new Version('2.99.8').satisfies('>=2.15.8 <3.0.0 || >=3.10.1'));\n\tt.true(new Version('3.10.1').satisfies('>=2.15.8 <3.0.0 || >=3.10.1'));\n\tt.true(new Version('6.7.0-next.0').satisfies('<6.8.0'));\n\n\tt.false(new Version('3.0.0').satisfies('>=2.15.8 <3.0.0 || >=3.10.1'));\n\tt.false(new Version('3.10.0').satisfies('>=2.15.8 <3.0.0 || >=3.10.1'));\n\n\tt.throws(\n\t\t() => new Version('1.2.3').satisfies('=>1.0.0'),\n\t\t{message: 'Range =>1.0.0 is not a valid SemVer range.'},\n\t);\n});\n\ntest('isPrerelease', t => {\n\tt.false(new Version('1.0.0').isPrerelease());\n\tt.false(new Version('1.1.0').isPrerelease());\n\tt.false(new Version('1.0.1').isPrerelease());\n\n\tt.true(new Version('1.0.0-alpha.1').isPrerelease());\n\tt.true(new Version('1.0.0-beta').isPrerelease());\n\tt.true(new Version('2.0.0-rc.2').isPrerelease());\n});\n\ntest('optionally set prereleasePrefix', t => {\n\tt.is(new Version('1.0.0', 'prerelease', {prereleasePrefix: 'alpha'}).toString(), '1.0.1-alpha.0');\n\tt.is(new Version('1.0.0').setFrom('prerelease', {prereleasePrefix: 'beta'}).toString(), '1.0.1-beta.0');\n});\n\ntest('setFrom with explicit version and format - for UI display', t => {\n\t// This pattern is used in ui.js to display version info when user provides explicit version\n\tconst currentVersion = '1.0.0';\n\tconst explicitVersion = '2.5.0';\n\tconst formattedVersion = new Version(currentVersion).setFrom(explicitVersion).format();\n\n\tt.is(formattedVersion, makeNewFormattedVersion('{2}.5.0'));\n});\n"
  }
]