Full Code of sindresorhus/np for AI

main 7aac522a97b5 cached
172 files
281.2 KB
82.8k tokens
94 symbols
1 requests
Download .txt
Showing preview only (319K chars total). Download the full file or copy to clipboard to get everything.
Repository: sindresorhus/np
Branch: main
Commit: 7aac522a97b5
Files: 172
Total size: 281.2 KB

Directory structure:
gitextract_s7w_v3kb/

├── .editorconfig
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── pull_request_template.md
│   └── workflows/
│       └── main.yml
├── .gitignore
├── .npmrc
├── license
├── package.json
├── readme.md
├── source/
│   ├── .npmignore
│   ├── cli-implementation.js
│   ├── cli.js
│   ├── config.js
│   ├── git-tasks.js
│   ├── git-util.js
│   ├── index.js
│   ├── npm/
│   │   ├── enable-2fa.js
│   │   ├── handle-npm-error.js
│   │   ├── oidc.js
│   │   ├── publish.js
│   │   └── util.js
│   ├── package-manager/
│   │   ├── configs.js
│   │   ├── index.js
│   │   └── types.d.ts
│   ├── prerequisite-tasks.js
│   ├── release-task-helper.js
│   ├── ui.js
│   ├── util.js
│   └── version.js
└── test/
    ├── _helpers/
    │   ├── integration-test.d.ts
    │   ├── integration-test.js
    │   ├── listr-renderer.js
    │   ├── listr.js
    │   ├── mock-inquirer.js
    │   ├── stub-execa.d.ts
    │   ├── stub-execa.js
    │   ├── util.js
    │   ├── verify-cli.d.ts
    │   └── verify-cli.js
    ├── cli.js
    ├── config.js
    ├── fixtures/
    │   ├── config/
    │   │   ├── contents-option/
    │   │   │   ├── .np-config.json
    │   │   │   └── dist/
    │   │   │       └── package.json
    │   │   ├── flag-precedence/
    │   │   │   ├── .np-config.json
    │   │   │   └── package.json
    │   │   ├── homedir1/
    │   │   │   └── .np-config.json
    │   │   ├── homedir2/
    │   │   │   └── .np-config.js
    │   │   ├── homedir3/
    │   │   │   └── .np-config.cjs
    │   │   ├── homedir4/
    │   │   │   └── .np-config.js
    │   │   ├── homedir5/
    │   │   │   └── .np-config.mjs
    │   │   ├── local1/
    │   │   │   └── .np-config.json
    │   │   ├── local2/
    │   │   │   └── .np-config.js
    │   │   ├── local3/
    │   │   │   └── .np-config.cjs
    │   │   ├── local4/
    │   │   │   ├── .np-config.js
    │   │   │   └── package.json
    │   │   ├── local5/
    │   │   │   └── .np-config.mjs
    │   │   ├── package.json
    │   │   └── pkg-dir/
    │   │       └── package.json
    │   ├── files/
    │   │   ├── dot-github/
    │   │   │   ├── .github/
    │   │   │   │   └── pull_request_template.md
    │   │   │   ├── index.js
    │   │   │   └── package.json
    │   │   ├── files-and-npmignore/
    │   │   │   ├── package.json
    │   │   │   ├── readme.md
    │   │   │   └── source/
    │   │   │       ├── .npmignore
    │   │   │       ├── bar.js
    │   │   │       ├── foo.js
    │   │   │       ├── index.d.ts
    │   │   │       └── index.test-d.ts
    │   │   ├── files-slash/
    │   │   │   ├── index.js
    │   │   │   └── package.json
    │   │   ├── gitignore/
    │   │   │   ├── .gitignore
    │   │   │   ├── index.d.ts
    │   │   │   ├── index.js
    │   │   │   ├── index.test-d.ts
    │   │   │   ├── package.json
    │   │   │   └── readme.md
    │   │   ├── has-readme-and-license/
    │   │   │   ├── index.js
    │   │   │   ├── license.md
    │   │   │   ├── package.json
    │   │   │   └── readme.md
    │   │   ├── main/
    │   │   │   ├── bar.js
    │   │   │   ├── foo.js
    │   │   │   └── package.json
    │   │   ├── missing-bin/
    │   │   │   ├── index.js
    │   │   │   └── package.json
    │   │   ├── missing-main/
    │   │   │   ├── package.json
    │   │   │   └── source/
    │   │   │       └── index.js
    │   │   ├── npmignore/
    │   │   │   ├── .npmignore
    │   │   │   ├── index.d.ts
    │   │   │   ├── index.js
    │   │   │   ├── index.test-d.ts
    │   │   │   ├── package.json
    │   │   │   └── readme.md
    │   │   ├── npmignore-and-gitignore/
    │   │   │   ├── .gitignore
    │   │   │   ├── .npmignore
    │   │   │   ├── package.json
    │   │   │   ├── readme.md
    │   │   │   ├── script/
    │   │   │   │   └── build.js
    │   │   │   └── source/
    │   │   │       └── index.ts
    │   │   ├── one-file/
    │   │   │   ├── index.js
    │   │   │   └── package.json
    │   │   ├── prepare-script/
    │   │   │   ├── index.js
    │   │   │   └── package.json
    │   │   ├── source-and-dist-dir/
    │   │   │   ├── dist/
    │   │   │   │   └── index.js
    │   │   │   ├── package.json
    │   │   │   └── source/
    │   │   │       ├── bar.js
    │   │   │       └── foo.js
    │   │   └── source-dir/
    │   │       ├── package.json
    │   │       └── source/
    │   │           ├── bar.js
    │   │           └── foo.js
    │   └── readme.md
    ├── git-util/
    │   ├── commit-log-from-revision.js
    │   ├── default-branch.js
    │   ├── delete-tag.js
    │   ├── get-current-branch.js
    │   ├── has-upstream.js
    │   ├── is-head-detached.js
    │   ├── latest-tag-or-first-commit.js
    │   ├── latest-tag.js
    │   ├── multiple-initial-commits.js
    │   ├── new-files-since-last-release.js
    │   ├── previous-tag-or-first-commit.js
    │   ├── push-graceful.js
    │   ├── read-file-from-last-release.js
    │   ├── remove-last-commit.js
    │   ├── root.js
    │   ├── verify-current-branch-is-release-branch.js
    │   ├── verify-recent-git-version.js
    │   ├── verify-remote-history-is-clean.js
    │   ├── verify-remote-is-valid.js
    │   ├── verify-tag-does-not-exist-on-remote.js
    │   └── verify-working-tree-is-clean.js
    ├── index.js
    ├── npm/
    │   ├── enable-2fa.js
    │   ├── handle-npm-error.js
    │   ├── oidc.js
    │   ├── publish.js
    │   └── util/
    │       ├── check-connection.js
    │       ├── check-ignore-strategy.js
    │       ├── collaborators.js
    │       ├── entry-points.js
    │       ├── is-external-registry.js
    │       ├── is-package-name-available.js
    │       ├── login.js
    │       ├── packed-files.js
    │       ├── prerelease-tags.js
    │       ├── username.js
    │       └── verify-recent-npm-version.js
    ├── package-manager.js
    ├── release-task-helper.js
    ├── tasks/
    │   ├── git-tasks.js
    │   └── prerequisite-tasks.js
    ├── ui/
    │   ├── new-files-dependencies.d.ts
    │   ├── new-files-dependencies.js
    │   ├── prompts/
    │   │   ├── tags.js
    │   │   └── version.js
    │   └── repo-url.js
    ├── util/
    │   ├── auto-group-list.js
    │   ├── get-minimum-node-version.js
    │   ├── get-new-dependencies.js
    │   ├── get-new-files.js
    │   ├── get-npm-package-access.js
    │   ├── get-pre-release-prefix.js
    │   ├── get-tag-version-prefix.js
    │   ├── hyperlinks.js
    │   ├── join-list.js
    │   ├── parse-git-url.js
    │   ├── read-pkg.js
    │   └── validate-engine-version-satisfies.js
    └── version.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.yml]
indent_style = space
indent_size = 2


================================================
FILE: .gitattributes
================================================
* text=auto eol=lf


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve np
---

<!--- Provide a short summary of the issue in the title above -->

## Description

<!-- Write a clear and concise description of what the issue is -->

## Steps to reproduce

<!--- Let us how how we can reproduce the issue on our end -->

1.
2.
3.

## Expected behavior

<!--- Tell us what you expected to happen -->

## Environment

<!-- Mention which versions of np, Node.js, npm and Git you're using, as well as your OS and version -->

np - x.x.x
Node.js - x.x.x
npm - x.x.x
Git - x.x.x
OS - XXX x.x.x


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea
---

<!--- Provide a short summary of the request in the title above -->

## Description

<!-- Write a clear and concise description of the feature -->

**Is the feature request related to a problem?**

<!-- Describe what the problem is (e.g. I'm always frustrated when […]) -->

## Possible implementation

<!-- If possible, let us know how you think the feature should be implemented -->

## Alternatives 

<!-- Include any alternative solutions and features you've considered -->


================================================
FILE: .github/pull_request_template.md
================================================
<!--

Thanks for submitting a pull request 🙌

**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.

Try 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.

-->


================================================
FILE: .github/workflows/main.yml
================================================
name: CI
on:
  - push
  - pull_request
jobs:
  test:
    name: Node.js ${{ matrix.node-version }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        node-version:
          - 24
          - 20
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: ${{ matrix.node-version }}
      - run: git config --global user.name "Github Actions"
      - run: git config --global user.email "actions@users.noreply.github.com"
      - run: npm install
      - run: npm test


================================================
FILE: .gitignore
================================================
node_modules
yarn.lock


================================================
FILE: .npmrc
================================================
package-lock=false


================================================
FILE: license
================================================
MIT License

Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)

Permission 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:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE 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.


================================================
FILE: package.json
================================================
{
	"name": "np",
	"version": "11.0.2",
	"description": "A better `npm publish`",
	"license": "MIT",
	"repository": "sindresorhus/np",
	"funding": "https://github.com/sindresorhus/np?sponsor=1",
	"type": "module",
	"bin": "./source/cli.js",
	"engines": {
		"node": ">=20",
		"npm": ">=9",
		"git": ">=2.11.0",
		"yarn": ">=1.7.0",
		"pnpm": ">=8",
		"bun": ">=1"
	},
	"scripts": {
		"test": "xo && ava"
	},
	"files": [
		"source"
	],
	"keywords": [
		"cli-app",
		"cli",
		"npm",
		"publish",
		"git",
		"push",
		"version",
		"bump",
		"commit"
	],
	"dependencies": {
		"chalk": "^5.6.2",
		"chalk-template": "^1.1.2",
		"clipboardy": "^5.0.2",
		"cosmiconfig": "^9.0.0",
		"del": "^8.0.1",
		"escape-goat": "^4.0.0",
		"escape-string-regexp": "^5.0.0",
		"execa": "^9.6.1",
		"exit-hook": "^5.0.1",
		"github-url-from-git": "^1.5.0",
		"hosted-git-info": "^9.0.2",
		"ignore-walk": "^8.0.0",
		"import-local": "^3.2.0",
		"inquirer": "^13.2.0",
		"is-installed-globally": "^1.0.0",
		"is-interactive": "^2.0.0",
		"is-scoped": "^3.0.0",
		"issue-regex": "^4.3.0",
		"listr": "^0.14.3",
		"listr-input": "^0.2.1",
		"log-symbols": "^7.0.1",
		"meow": "^14.0.0",
		"new-github-release-url": "^2.0.0",
		"npm-name": "^8.0.0",
		"onetime": "^7.0.0",
		"open": "^11.0.0",
		"p-memoize": "^8.0.0",
		"package-directory": "^8.0.0",
		"path-exists": "^5.0.0",
		"read-package-up": "^12.0.0",
		"read-pkg": "^10.0.0",
		"rxjs": "^7.8.2",
		"semver": "^7.7.3",
		"symbol-observable": "^4.0.0",
		"terminal-link": "^5.0.0",
		"update-notifier": "^7.3.1"
	},
	"devDependencies": {
		"@sindresorhus/is": "^7.2.0",
		"@types/semver": "^7.7.1",
		"ava": "^6.4.1",
		"common-tags": "^1.8.2",
		"esmock": "^2.7.3",
		"fs-extra": "^11.3.3",
		"map-obj": "^6.0.0",
		"sinon": "^21.0.1",
		"strip-ansi": "^7.1.2",
		"tempy": "^3.1.0",
		"write-package": "^7.2.0",
		"xo": "^1.2.3"
	},
	"ava": {
		"files": [
			"!test/fixtures",
			"!test/_helpers"
		],
		"environmentVariables": {
			"FORCE_HYPERLINK": "1",
			"HOME": "/tmp",
			"NODE_ENV": "test",
			"GIT_AUTHOR_NAME": "test",
			"GIT_COMMITTER_NAME": "test",
			"GIT_AUTHOR_EMAIL": "test@example.com",
			"GIT_COMMITTER_EMAIL": "test@example.com"
		},
		"nodeArguments": [
			"--loader=esmock",
			"--no-warnings=ExperimentalWarning"
		]
	},
	"xo": {
		"ignores": [
			"test/fixtures"
		]
	}
}


================================================
FILE: readme.md
================================================
# np [![XO code style](https://shields.io/badge/code_style-5ed9c7?logo=xo&labelColor=gray&logoSize=auto&logoWidth=20)](https://github.com/xojs/xo)

> A better `npm publish`

<img src="media/screenshot.gif" width="688">

## Why

- [Interactive UI](#interactive-ui)
- Ensures you are publishing from your release branch (`main` and `master` by default)
- Ensures the working directory is clean and that there are no unpulled changes
- Reinstalls dependencies to ensure your project works with the latest dependency tree
- Ensures your Node.js and npm versions are supported by the project and its dependencies
- Runs the tests
- Bumps the version in package.json and npm-shrinkwrap.json (if present) and creates a git tag
- 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)
- Publishes the new version to npm, optionally under a dist-tag
- Rolls back the project to its previous state in case publishing fails
- Pushes commits and tags (newly & previously created) to GitHub/GitLab
- Supports [two-factor authentication](https://docs.npmjs.com/getting-started/using-two-factor-authentication)
- Enables two-factor authentication on new repositories
  <br>
  <sub>(does not apply to external registries)</sub>
- Opens a prefilled GitHub Releases draft after publish
- Warns about the possibility of extraneous files being published
- See exactly what will be executed with [preview mode](https://github.com/sindresorhus/np/issues/391), without pushing or publishing anything remotely
- Supports [GitHub Packages](https://github.com/features/packages)
- Supports npm 9+, Yarn (Classic and Berry), pnpm 8+, and Bun

### Why not

- Monorepos are not supported.
- 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.

## Prerequisite

- Node.js 20 or later
- npm 9 or later
- Git 2.11 or later

## Install

```sh
npm install --global np
```

## Usage

```
$ np --help

  Usage
    $ np <version>

    Version can be:
      patch | minor | major | prepatch | preminor | premajor | prerelease | 1.2.3

  Options
    --any-branch            Allow publishing from any branch
    --branch                Name of the release branch (default: main | master)
    --no-cleanup            Skips cleanup of node_modules
    --no-tests              Skips tests
    --yolo                  Skips cleanup and testing
    --no-publish            Skips publishing
    --preview               Show tasks without actually executing them
    --tag                   Publish under a given dist-tag
    --contents              Subdirectory to publish
    --no-release-draft      Skips opening a GitHub release draft
    --release-draft-only    Only opens a GitHub release draft for the latest published version
    --no-release-notes      Skips generating release notes when opening a GitHub release draft
    --test-script           Name of npm run script to run tests before publishing (default: test)
    --no-2fa                Don't enable 2FA on new packages (not recommended)
    --message               Version bump commit message, '%s' will be replaced with version (default: '%s' with npm and 'v%s' with yarn)
    --package-manager       Use a specific package manager (default: 'packageManager' field in package.json)
    --provenance            Publish with npm provenance statements (CI-only)
    --remote                Git remote to push to (default: origin)

  Examples
    $ np
    $ np patch
    $ np 1.0.2
    $ np 1.0.2-beta.3 --tag=beta
    $ np 1.0.2-beta.3 --tag=beta --contents=dist
```

## Interactive UI

Run `np` without arguments to launch the interactive UI that guides you through publishing a new version.

<img src="media/screenshot-ui.png" width="1290">

## Config

`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.

Currently, these are the flags you can configure:

- `anyBranch` - Allow publishing from any branch (`false` by default).
- `branch` - Name of the release branch (`main` or `master` by default).
- `cleanup` - Cleanup `node_modules` (`true` by default).
- `tests` - Run `npm test` (`true` by default).
- `yolo` - Skip cleanup and testing (`false` by default).
- `publish` - Publish (`true` by default).
- `preview` - Show tasks without actually executing them (`false` by default).
- `tag` - Publish under a given dist-tag (`latest` by default).
- `contents` - Subdirectory to publish (`.` by default).
- `releaseDraft` - Open a GitHub release draft after releasing (`true` by default).
- `releaseNotes` - Auto-generate release notes when opening a GitHub release draft (`true` by default).
- `testScript` - Name of npm run script to run tests before publishing (`test` by default).
- `2fa` - Enable 2FA on new packages (`true` by default) (setting this to `false` is not recommended).
- `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`.
- `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.
- `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).
- `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.

For example, this configures `np` to use `unit-test` as a test script, and to use `dist` as the subdirectory to publish:

`package.json`
```json
{
	"name": "superb-package",
	"np": {
		"testScript": "unit-test",
		"contents": "dist"
	}
}
```

`.np-config.json`
```json
{
	"testScript": "unit-test",
	"contents": "dist"
}
```

`.np-config.js` or `.np-config.cjs`
```js
module.exports = {
	testScript: 'unit-test',
	contents: 'dist'
};
```

`.np-config.mjs`
```js
export default {
	testScript: 'unit-test',
	contents: 'dist'
};
```

_**Note:** The global config only applies when using the global `np` binary, and is never inherited when using a local binary._

## Tips

### npm hooks

You 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.

For example, here we build the documentation before tagging the release:

```json
{
	"name": "my-awesome-package",
	"scripts": {
		"version": "./build-docs && git add docs"
	}
}
```

### Release script

You 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).

```json
{
	"name": "my-awesome-package",
	"scripts": {
		"release": "np"
	},
	"devDependencies": {
		"np": "*"
	}
}
```

### User-defined tests

If 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.

For example, `np --test-script=publish-test` would run the `publish-test` script instead of the default `test`.

```json
{
	"name": "my-awesome-package",
	"scripts": {
		"test": "ava --watch",
		"publish-test": "ava"
	},
	"devDependencies": {
		"np": "*"
	}
}
```

### Signed Git tag

Set the [`sign-git-tag`](https://docs.npmjs.com/misc/config#sign-git-tag) npm config to have the Git tag signed:

```
$ npm config set sign-git-tag true
```

Or set the [`version-sign-git-tag`](https://yarnpkg.com/lang/en/docs/cli/version/#toc-git-tags) Yarn config:

```
$ yarn config set version-sign-git-tag true
```

### Private packages

<img src="media/private-packages.png" width="260" align="right">

You can use `np` for packages that aren't publicly published to npm (perhaps installed from a private git repo).

Set `"private": true` in your `package.json` and the publishing step will be skipped. All other steps
including versioning and pushing tags will still be completed.

### Public scoped packages

To 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`:

```json
"publishConfig": {
	"access": "public"
}
```

If publishing a scoped package for the first time, `np` will prompt you to ask if you want to publish it publicly.

**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.

### Private Org-scoped packages

To 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`:

```json
"publishConfig": {
	"access": "restricted"
}
```

### Publish to a custom registry

Set the [`registry` option](https://docs.npmjs.com/misc/config#registry) in package.json to the URL of your registry:

```json
"publishConfig": {
	"registry": "https://my-internal-registry.local"
}
```

### Package managers

If 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).

### Publish with a CI

If you use a Continuous Integration server to publish your tagged commits, use the `--no-publish` flag to skip the publishing step of `np`.

### Publish to gh-pages

To 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`.

```sh
npm install --save-dev branchsite
```

```json
"scripts": {
	"deploy": "np",
	"postdeploy": "bs"
}
```

### Initial version

For 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.

### Release an update to an old major version

To release a minor/patch version for an old major version, create a branch from the major version's git tag and run `np`:

```console
$ git checkout -b fix-old-bug v1.0.0 # Where 1.0.0 is the previous major version
# Create some commits…
$ git push --set-upstream origin HEAD
$ np patch --any-branch --tag=v1
```

### The prerequisite step runs forever on macOS

If 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`.

```
Host *
 AddKeysToAgent yes
 UseKeychain yes
```

If 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/).

### Ignore strategy

The [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`.

## FAQ

### I get an error when publishing my package through Yarn

If you get an error like this…

```shell
❯ Prerequisite check
✔ Ping npm registry
✔ Check npm version
✔ Check yarn version
✖ Verify user is authenticated

npm ERR! code E403
npm ERR! 403 Forbidden - GET https://registry.yarnpkg.com/-/package/my-awesome-package/collaborators?format=cli - Forbidden
```

…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`:

```json
"publishConfig": {
	"registry": "https://registry.npmjs.org"
}
```

### np hangs during the "Publishing package" step

If `np` hangs indefinitely during publishing, common causes include:

**Lifecycle scripts that don't exit**

npm 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.

```json
{
	"scripts": {
		"test": "vitest",
		"publish": "npm run test && np"
	}
}
```

**Solution**: Don't name your scripts `publish`, `prepublish`, or `postpublish` (these are reserved npm lifecycle hooks). Use names like `release` instead:

```json
{
	"scripts": {
		"test": "vitest run",
		"release": "np"
	}
}
```

**Tests running in watch mode**

If your test script runs in watch mode, it won't exit after running tests.

**Solution**: Ensure your test command exits after running:

```json
{
	"scripts": {
		"test": "vitest run",
		"test:dev": "vitest"
	}
}
```

**Registry configuration issues**

A missing trailing slash in `.npmrc` registry configuration can cause hangs.

**Solution**: Ensure registry URLs have a trailing slash:

```npmrc
@ORG:registry=https://npm.pkg.github.com/
```

## Maintainers

- [Sindre Sorhus](https://github.com/sindresorhus)
- [Tommy Mitchell](https://github.com/tommy-mitchell)


================================================
FILE: source/.npmignore
================================================
*.d.ts


================================================
FILE: source/cli-implementation.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import logSymbols from 'log-symbols';
import meow from 'meow';
import updateNotifier from 'update-notifier';
import isInteractive from 'is-interactive';
import {gracefulExit} from 'exit-hook';
import {getPackageManagerConfig} from './package-manager/index.js';
import config from './config.js';
import * as util from './util.js';
import * as git from './git-util.js';
import * as npm from './npm/util.js';
import {getOidcProvider} from './npm/oidc.js';
import {SEMVER_INCREMENTS} from './version.js';
import ui from './ui.js';
import np from './index.js';

/** @typedef {typeof cli} CLI */

const cli = meow(`
	Usage
	  $ np <version>

	  Version can be:
	    ${SEMVER_INCREMENTS.join(' | ')} | 1.2.3

	Options
	  --any-branch           Allow publishing from any branch
	  --branch               Name of the release branch (default: main | master)
	  --no-cleanup           Skips cleanup of node_modules
	  --no-tests             Skips tests
	  --yolo                 Skips cleanup and testing
	  --no-publish           Skips publishing
	  --preview              Show tasks without actually executing them
	  --tag                  Publish under a given dist-tag
	  --contents             Subdirectory to publish
	  --no-release-draft     Skips opening a GitHub release draft
	  --release-draft-only   Only opens a GitHub release draft for the latest published version
	  --no-release-notes     Skips generating release notes when opening a GitHub release draft
	  --test-script          Name of npm run script to run tests before publishing (default: test)
	  --no-2fa               Don't enable 2FA on new packages (not recommended)
	  --message              Version bump commit message, '%s' will be replaced with version (default: '%s' with npm and 'v%s' with yarn)
	  --package-manager      Use a specific package manager (default: 'packageManager' field in package.json)
	  --provenance           Publish with npm provenance statements (CI-only)
	  --remote               Git remote to push to (default: origin)

	Examples
	  $ np
	  $ np patch
	  $ np 1.0.2
	  $ np 1.0.2-beta.3 --tag=beta
	  $ np 1.0.2-beta.3 --tag=beta --contents=dist
`, {
	importMeta: import.meta,
	booleanDefault: undefined,
	// Don't use `default` for flags - we apply defaults later so config can override them
	flags: {
		anyBranch: {
			type: 'boolean',
		},
		branch: {
			type: 'string',
		},
		cleanup: {
			type: 'boolean',
		},
		tests: {
			type: 'boolean',
		},
		yolo: {
			type: 'boolean',
		},
		publish: {
			type: 'boolean',
		},
		releaseDraft: {
			type: 'boolean',
		},
		releaseDraftOnly: {
			type: 'boolean',
		},
		releaseNotes: {
			type: 'boolean',
		},
		tag: {
			type: 'string',
		},
		packageManager: {
			type: 'string',
		},
		contents: {
			type: 'string',
		},
		preview: {
			type: 'boolean',
		},
		testScript: {
			type: 'string',
		},
		'2fa': {
			type: 'boolean',
		},
		message: {
			type: 'string',
		},
		provenance: {
			type: 'boolean',
		},
		remote: {
			type: 'string',
		},
	},
});

updateNotifier({pkg: cli.pkg}).notify();

/** @typedef {Awaited<ReturnType<typeof getOptions>>['options']} Options */

async function getOptions() {
	// Load config from cwd first to get `contents` option before reading package
	const initialConfig = await config(process.cwd());
	const contents = cli.flags.contents ?? initialConfig?.contents;

	const {package_, rootDirectory} = await util.readPackage(contents);

	const localConfig = await config(rootDirectory);

	// Filter out undefined CLI flags (not provided by user)
	const explicitCliFlags = Object.fromEntries(Object.entries(cli.flags).filter(([, value]) => value !== undefined));

	// Merge: local config → explicit CLI flags → defaults
	const flags = {
		cleanup: true,
		tests: true,
		publish: true,
		releaseDraft: true,
		releaseNotes: true,
		'2fa': true,
		...localConfig,
		...explicitCliFlags,
	};

	// Workaround for unintended auto-casing behavior from `meow`.
	if ('2Fa' in flags) {
		flags['2fa'] = flags['2Fa'];
	}

	if (flags.packageManager) {
		package_.packageManager = flags.packageManager;
	}

	const packageManager = getPackageManagerConfig(rootDirectory, package_);

	if (packageManager.throwOnExternalRegistry && npm.isExternalRegistry(package_)) {
		throw new Error(`External registry is not yet supported with ${packageManager.id}.`);
	}

	const runPublish = !flags.releaseDraftOnly && flags.publish && !package_.private;

	const availability = runPublish
		? await npm.isPackageNameAvailable(package_)
		: {
			isAvailable: false,
			isUnknown: false,
		};

	// Use current (latest) version when 'releaseDraftOnly', otherwise try to use the first argument.
	const version = flags.releaseDraftOnly ? package_.version : cli.input.at(0);

	const branch = flags.branch ?? await git.defaultBranch();

	const options = await ui({
		...flags,
		packageManager,
		runPublish,
		availability,
		version,
		branch,
	}, {package_, rootDirectory});

	return {
		options: {...options, packageManager},
		rootDirectory,
		package_,
	};
}

try {
	const {options, rootDirectory, package_} = await getOptions();

	if (!options.confirm) {
		gracefulExit();
	}

	// Check authentication early, before Listr starts (so login can be interactive)
	if (options.runPublish) {
		// Skip auth check if OIDC is available (will be handled by npm publish itself)
		if (getOidcProvider()) {
			console.log('OIDC authentication detected - skipping auth check');
		} else {
			const externalRegistry = npm.isExternalRegistry(package_)
				? package_.publishConfig.registry
				: false;

			try {
				await npm.username({externalRegistry});
			} catch (error) {
				if (error.isNotLoggedIn && isInteractive()) {
					console.log('\nYou must be logged in to publish. Running `npm login`...\n');
					await npm.login({externalRegistry});
				} else {
					throw error;
				}
			}
		}
	}

	console.log(); // Prints a newline for readability
	const newPackage = await np(options.version.toString(), options, {package_, rootDirectory});

	if (options.preview || options.releaseDraftOnly) {
		gracefulExit();
	}

	console.log(`\n ${newPackage.name} ${newPackage.version} published 🎉`);
} catch (error) {
	if (error.name === 'ExitPromptError') {
		process.exit(0);
	}

	console.error(`\n${logSymbols.error} ${error?.stack ?? error}`);
	gracefulExit(1);
}


================================================
FILE: source/cli.js
================================================
#!/usr/bin/env node
import {debuglog} from 'node:util';
import importLocal from 'import-local';
import isInstalledGlobally from 'is-installed-globally';

const log = debuglog('np');

// Prefer the local installation
if (!importLocal(import.meta.url)) {
	if (isInstalledGlobally) {
		log('Using global install of np.');
	}

	await import('./cli-implementation.js');
}


================================================
FILE: source/config.js
================================================
import os from 'node:os';
import isInstalledGlobally from 'is-installed-globally';
import {cosmiconfig} from 'cosmiconfig';

export default async function getConfig(rootDirectory) {
	const searchPlaces = [
		'.np-config.json',
		'.np-config.js',
		'.np-config.cjs',
		'.np-config.mjs',
		'package.json',
	];

	const explorer = cosmiconfig('np', {
		searchPlaces,
		stopDir: rootDirectory,
	});

	// Always read project config
	const {config: projectConfig} = (await explorer.search(rootDirectory)) ?? {};

	// When globally installed, also read global config and merge (project wins)
	if (isInstalledGlobally) {
		const globalExplorer = cosmiconfig('np', {
			searchPlaces: searchPlaces.filter(place => place !== 'package.json'),
			stopDir: os.homedir(),
		});

		const {config: globalConfig} = (await globalExplorer.search(os.homedir())) ?? {};

		return {
			...globalConfig,
			...projectConfig,
		};
	}

	return projectConfig;
}


================================================
FILE: source/git-tasks.js
================================================
import Listr from 'listr';
import * as git from './git-util.js';

const gitTasks = options => {
	const tasks = [
		{
			title: 'Check current branch',
			task: () => git.verifyCurrentBranchIsReleaseBranch(options.branch),
		},
		{
			title: 'Check local working tree',
			task: () => git.verifyWorkingTreeIsClean(),
		},
		{
			title: 'Check remote history',
			task: () => git.verifyRemoteHistoryIsClean(),
		},
	];

	if (options.anyBranch) {
		tasks.shift();
	}

	return new Listr(tasks);
};

export default gitTasks;


================================================
FILE: source/git-util.js
================================================
import path from 'node:path';
import {execa} from 'execa';
import escapeStringRegexp from 'escape-string-regexp';
import ignoreWalker from 'ignore-walk';
import semver from 'semver';
import * as util from './util.js';

const gitNetworkTimeout = 120_000; // 2 minutes for remote git operations

export const latestTag = async () => {
	const {stdout} = await execa('git', ['describe', '--abbrev=0', '--tags']);
	return stdout;
};

export const root = async () => {
	const {stdout} = await execa('git', ['rev-parse', '--show-toplevel']);
	return stdout;
};

export const newFilesSinceLastRelease = async rootDirectory => {
	try {
		const {stdout} = await execa('git', ['diff', '--name-only', '--diff-filter=A', await latestTag(), 'HEAD']);
		if (stdout.trim().length === 0) {
			return [];
		}

		const result = stdout.trim().split('\n').map(row => row.trim());
		return result;
	} catch {
		// Get all files under version control
		return ignoreWalker({
			path: rootDirectory,
			ignoreFiles: ['.gitignore'],
		});
	}
};

export const readFileFromLastRelease = async file => {
	const rootPath = await root();
	const filePathFromRoot = path.relative(rootPath, path.resolve(rootPath, file));
	const {stdout: oldFile} = await execa('git', ['show', `${await latestTag()}:${filePathFromRoot}`]);
	return oldFile;
};

/** Returns an array of all tags. */
const tagList = async () => {
	const {stdout} = await execa('git', ['tag']);
	return stdout ? stdout.split('\n') : [];
};

/** Returns an array of tags sorted by semver in ascending order. Non-semver tags are excluded. */
const tagListSortedBySemver = async () => {
	const tags = await tagList();
	return tags
		.filter(tag => semver.valid(tag))
		.sort((a, b) => semver.compare(a, b));
};

const firstCommit = async () => {
	const {stdout} = await execa('git', ['rev-list', '--max-parents=0', 'HEAD']);
	// Repository may have multiple initial commits (e.g., from merging unrelated histories).
	// Return just the first one.
	return stdout.split('\n')[0];
};

export const previousTagOrFirstCommit = async () => {
	const tags = await tagListSortedBySemver();

	if (tags.length === 0) {
		return;
	}

	if (tags.length === 1) {
		return firstCommit();
	}

	try {
		// Return the tag before the latest one (sorted by semver).
		const latest = await latestTag();
		const index = tags.indexOf(latest);

		if (index === -1 || index === 0) {
			return firstCommit();
		}

		return tags[index - 1];
	} catch {
		// Fallback to the first commit.
		return firstCommit();
	}
};

export const latestTagOrFirstCommit = async () => {
	let latest;
	try {
		// In case a previous tag exists, we use it to compare the current repo status to.
		latest = await latestTag();
	} catch {
		// Otherwise, we fallback to using the first commit for comparison.
		latest = await firstCommit();
	}

	return latest;
};

export const hasUpstream = async () => {
	const escapedCurrentBranch = escapeStringRegexp(await getCurrentBranch());
	const {stdout} = await execa('git', ['status', '--short', '--branch', '--porcelain']);

	return new RegExp(String.raw`^## ${escapedCurrentBranch}\.\.\..+\/${escapedCurrentBranch}`).test(stdout);
};

export const getCurrentBranch = async () => {
	const {stdout} = await execa('git', ['symbolic-ref', '--short', 'HEAD']);
	return stdout;
};

export const verifyCurrentBranchIsReleaseBranch = async releaseBranch => {
	const currentBranch = await getCurrentBranch();
	if (currentBranch !== releaseBranch) {
		throw new Error(`Not on \`${releaseBranch}\` branch. Use --any-branch to publish anyway, or set a different release branch using --branch.`);
	}
};

export const isHeadDetached = async () => {
	try {
		// Command will fail with code 1 if the HEAD is detached.
		await execa('git', ['symbolic-ref', '--quiet', 'HEAD']);
		return false;
	} catch {
		return true;
	}
};

const isWorkingTreeClean = async () => {
	try {
		const {stdout: status} = await execa('git', ['status', '--porcelain']);
		if (status !== '') {
			return false;
		}

		return true;
	} catch {
		return false;
	}
};

export const verifyWorkingTreeIsClean = async () => {
	if (!(await isWorkingTreeClean())) {
		throw new Error('Unclean working tree. Commit or stash changes first.');
	}
};

const hasRemote = async () => {
	try {
		await execa('git', ['rev-parse', '@{u}']);
	} catch { // Has no remote if command fails
		return false;
	}

	return true;
};

const hasUnfetchedChangesFromRemote = async () => {
	// Inherit stdin to allow SSH password prompts for password-protected keys
	const {stdout: possibleNewChanges} = await execa('git', ['fetch', '--dry-run'], {stdin: 'inherit', timeout: gitNetworkTimeout});

	// There are unfetched changes if output is not empty.
	return Boolean(possibleNewChanges);
};

const isRemoteHistoryClean = async () => {
	const {stdout: history} = await execa('git', ['rev-list', '--count', '--left-only', '@{u}...HEAD']);

	// Remote history is clean if there are 0 revisions.
	return history === '0';
};

export const verifyRemoteHistoryIsClean = async () => {
	if (!(await hasRemote())) {
		return;
	}

	if (await hasUnfetchedChangesFromRemote()) {
		throw new Error('Remote history differs. Please run `git fetch` and pull changes.');
	}

	if (!(await isRemoteHistoryClean())) {
		throw new Error('Remote history differs. Please pull changes.');
	}
};

export const verifyRemoteIsValid = async remote => {
	try {
		// Inherit stdin to allow SSH password prompts for password-protected keys
		await execa('git', ['ls-remote', remote ?? 'origin', 'HEAD'], {stdin: 'inherit', timeout: gitNetworkTimeout});
	} catch (error) {
		throw new Error(error.stderr.replace('fatal:', 'Git fatal error:'));
	}
};

export const fetch = async () => {
	// Inherit stdin to allow SSH password prompts for password-protected keys
	await execa('git', ['fetch'], {stdin: 'inherit', timeout: gitNetworkTimeout});
};

const hasLocalBranch = async branch => {
	try {
		await execa('git', ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`]);
		return true;
	} catch {
		return false;
	}
};

export const defaultBranch = async () => {
	for (const branch of ['main', 'master', 'gh-pages']) {
		// eslint-disable-next-line no-await-in-loop
		if (await hasLocalBranch(branch)) {
			return branch;
		}
	}

	throw new Error('Could not infer the default Git branch. Please specify one with the --branch flag or with a np config.');
};

const tagExistsOnRemote = async tagName => {
	try {
		const {stdout: revInfo} = await execa('git', ['rev-parse', '--quiet', '--verify', `refs/tags/${tagName}`]);

		if (revInfo) {
			return true;
		}

		return false;
	} catch (error) {
		// Command fails with code 1 and no output if the tag does not exist, even though `--quiet` is provided
		// https://github.com/sindresorhus/np/pull/73#discussion_r72385685
		if (error.stdout === '' && error.stderr === '') {
			return false;
		}

		throw error;
	}
};

export const verifyTagDoesNotExistOnRemote = async tagName => {
	if (await tagExistsOnRemote(tagName)) {
		throw new Error(`Git tag \`${tagName}\` already exists.`);
	}
};

export const commitLogFromRevision = async revision => {
	const {stdout} = await execa('git', ['log', '--format=%s %h', `${revision}..HEAD`]);
	return stdout;
};

const push = async (remote, tagArgument = '--follow-tags') => {
	// Inherit stdin to allow SSH password prompts for password-protected keys
	await execa('git', ['push', ...(remote ? [remote] : []), tagArgument], {stdin: 'inherit', timeout: gitNetworkTimeout});
};

export const pushGraceful = async (remoteIsOnGitHub, remote) => {
	try {
		await push(remote);
	} catch (error) {
		if (remoteIsOnGitHub && error.stderr && error.stderr.includes('GH006')) {
			// Try to push tags only, when commits can't be pushed due to branch protection
			await push(remote, '--tags');
			return {pushed: 'tags', reason: 'Branch protection: np can`t push the commits. Push them manually.'};
		}

		throw error;
	}
};

export const deleteTag = async tagName => {
	await execa('git', ['tag', '--delete', tagName]);
};

export const removeLastCommit = async () => {
	await execa('git', ['reset', '--hard', 'HEAD~1']);
};

const gitVersion = async () => {
	const {stdout} = await execa('git', ['version']);
	const match = /git version (?<version>\d+\.\d+\.\d+).*/.exec(stdout);
	return match && match.groups.version;
};

export const verifyRecentGitVersion = async () => {
	const installedVersion = await gitVersion();
	util.validateEngineVersionSatisfies('git', installedVersion);
};

export const verifyUserConfigIsSet = async () => {
	const [nameResult, emailResult] = await Promise.allSettled([
		execa('git', ['config', 'user.name']),
		execa('git', ['config', 'user.email']),
	]);

	if (nameResult.status !== 'fulfilled' || !nameResult.value.stdout || emailResult.status !== 'fulfilled' || !emailResult.value.stdout) {
		throw new Error([
			'Git user configuration is not set.',
			'',
			'Please set your git user name and email:',
			'  git config --global user.name "Your Name"',
			'  git config --global user.email "you@example.com"',
		].join('\n'));
	}
};


================================================
FILE: source/index.js
================================================
import {execa} from 'execa';
import {deleteAsync} from 'del';
// NOTE: We intentionally use the original `listr` package instead of `listr2`.
// listr2's DefaultRenderer uses log-update which has known issues with terminal scrolling
// that cause it to overwrite content printed before listr2 started (like our inquirer prompts).
// See: https://github.com/cenk1cenk2/listr2/issues/296
import Listr from 'listr';
import {
	merge,
	catchError,
	filter,
	finalize,
	from,
	mergeMap,
	throwError,
} from 'rxjs';
import hostedGitInfo from 'hosted-git-info';
import onetime from 'onetime';
import {asyncExitHook} from 'exit-hook';
import logSymbols from 'log-symbols';
import prerequisiteTasks from './prerequisite-tasks.js';
import gitTasks from './git-tasks.js';
import {getPackagePublishArguments, runPublish} from './npm/publish.js';
import enable2fa, {getEnable2faArguments} from './npm/enable-2fa.js';
import handleNpmError from './npm/handle-npm-error.js';
import {getOidcProvider} from './npm/oidc.js';
import releaseTaskHelper from './release-task-helper.js';
import {findLockfile, printCommand} from './package-manager/index.js';
import * as util from './util.js';
import * as git from './git-util.js';
import * as npm from './npm/util.js';

/** @type {(cmd: string, args: string[], options?: import('execa').Options) => any} */
const exec = (command, arguments_, options) => {
	// Use `Observable` support if merged https://github.com/sindresorhus/execa/pull/26
	const subProcess = execa(command, arguments_, options);

	return merge(subProcess.stdout, subProcess.stderr, subProcess).pipe(
		filter(Boolean),
		catchError(error => {
			// Include stderr in error message for better diagnostics
			if (error.stderr) {
				error.message = `${error.shortMessage}\n${error.stderr}`;
			}

			throw error;
		}),
	);
};

/**
@param {string} input
@param {import('./cli-implementation.js').Options} options
@param {{package_: import('read-pkg').NormalizedPackageJson; rootDirectory: string}} context
*/
const np = async (input = 'patch', {packageManager, ...options}, {package_, rootDirectory}) => {
	// TODO: Remove sometime far in the future
	if (options.skipCleanup) {
		options.cleanup = false;
	}

	const runTests = options.tests && !options.yolo;
	const runCleanup = options.cleanup && !options.yolo;
	const lockfile = findLockfile(rootDirectory, packageManager);
	const isOnGitHub = options.repoUrl && hostedGitInfo.fromUrl(options.repoUrl)?.type === 'github';
	const testScript = options.testScript || 'test';

	if (options.releaseDraftOnly) {
		await releaseTaskHelper(options, package_, packageManager);
		return package_;
	}

	let publishStatus = 'UNKNOWN';
	let pushedObjects;

	const rollback = onetime(async () => {
		console.log('\nPublish failed. Rolling back to the previous state…');

		const tagVersionPrefix = await util.getTagVersionPrefix(packageManager);

		const latestTag = await git.latestTag();
		const versionInLatestTag = latestTag.slice(tagVersionPrefix.length);

		async function getPackageVersion() {
			const package_ = await util.readPackage(rootDirectory);
			return package_.version;
		}

		try {
			// Verify that the package's version has been bumped before deleting the last tag and commit.
			if (versionInLatestTag === await getPackageVersion() && versionInLatestTag !== package_.version) {
				await git.deleteTag(latestTag);
				await git.removeLastCommit();
			}

			console.log('Successfully rolled back the project to its previous state.');
		} catch (error) {
			console.log(`Couldn't roll back because of the following error:\n${error}`);
		}
	});

	asyncExitHook(async () => {
		if (options.preview || publishStatus === 'SUCCESS') {
			return;
		}

		if (publishStatus === 'FAILED') {
			await rollback();
		} else {
			console.log('\nAborted!');
		}
	}, {wait: 2000});

	// Don't enable 2FA when using OIDC (Trusted Publishing) as it's already managed
	const shouldEnable2FA = options['2fa'] && options.availability.isAvailable && !options.availability.isUnknown && !package_.private && !npm.isExternalRegistry(package_) && !getOidcProvider();

	// To prevent the process from hanging due to watch mode (e.g. when running `vitest`)
	const ciEnvOptions = {env: {CI: 'true'}};

	/** @param {typeof options} _options */
	function getPublishCommand(_options) {
		const publishCommand = packageManager.publishCommand || (arguments_ => [packageManager.cli, arguments_]);
		const arguments_ = getPackagePublishArguments(_options);
		return publishCommand(arguments_);
	}

	const tasks = new Listr([
		{
			title: 'Prerequisite check',
			enabled: () => options.runPublish,
			task: () => prerequisiteTasks(input, package_, options, {packageManager, rootDirectory}),
		},
		{
			title: 'Git',
			task: () => gitTasks(options),
		},
		{
			title: 'Cleanup',
			enabled: () => runCleanup && !lockfile,
			task: () => deleteAsync('node_modules'),
		},
		{
			title: `Installing dependencies using ${packageManager.id}`,
			enabled: () => runCleanup,
			task: () => new Listr([
				{
					title: 'Running install command',
					task() {
						const installCommand = lockfile ? packageManager.installCommand : packageManager.installCommandNoLockfile;
						return exec(...installCommand);
					},
				},
				{
					title: 'Checking working tree is still clean', // If lockfile was out of date and tracked by git, this will fail
					task: () => git.verifyWorkingTreeIsClean(),
				},
			]),
		},
		{
			title: 'Running tests',
			enabled: () => runTests,
			task: () => exec(packageManager.cli, ['run', testScript], ciEnvOptions),
		},
		{
			title: 'Bumping version',
			skip() {
				if (options.preview) {
					const [cli, arguments_] = packageManager.versionCommand(input);

					if (options.message) {
						arguments_.push('--message', options.message.replaceAll('%s', input));
					}

					return `[Preview] Command not executed: ${printCommand([cli, arguments_])}`;
				}
			},
			task() {
				const [cli, arguments_] = packageManager.versionCommand(input);

				if (options.message) {
					arguments_.push('--message', options.message);
				}

				// Inherit stdin to allow GPG password prompts for commit signing
				return exec(cli, arguments_, {stdin: 'inherit'});
			},
		},
		...options.runPublish
			? [
				{
					title: 'Publishing package',
					skip() {
						if (options.preview) {
							const command = getPublishCommand(options);
							return `[Preview] Command not executed: ${printCommand(command)}.`;
						}
					},
					/** @type {(context, task) => Listr.ListrTaskResult<any>} */
					task(context, task) {
						let hasError = false;

						return runPublish(getPublishCommand(options), {cwd: rootDirectory})
							.pipe(catchError(error => handleNpmError(error, task, otp => {
								context.otp = otp;

								return runPublish(getPublishCommand({...options, otp}), {cwd: rootDirectory});
							})))
							.pipe(
								// Note: Cannot use `async` here as the `await` will not finish before the error propagates.
								catchError(error => {
									hasError = true;
									return from(rollback()).pipe(
										mergeMap(() => throwError(() => new Error(`Error publishing package:\n${error.message}\n\nThe project was rolled back to its previous state.`))),
										catchError(() => throwError(() => new Error(`Error publishing package:\n${error.message}\n\nThe project was rolled back to its previous state.`))),
									);
								}),
								finalize(() => {
									publishStatus = hasError ? 'FAILED' : 'SUCCESS';
								}),
							);
					},
				},
				...shouldEnable2FA
					? [{
						title: 'Enabling two-factor authentication',
						async skip() {
							if (options.preview) {
								const arguments_ = await getEnable2faArguments(package_.name, options);
								return `[Preview] Command not executed: npm ${arguments_.join(' ')}.`;
							}
						},
						task: (context, task) => enable2fa(task, package_.name, {otp: context.otp}),
					}]
					: [],
			]
			: [],
		{
			title: 'Pushing tags',
			async skip() {
				if (!options.remote && !(await git.hasUpstream())) {
					return 'Upstream branch not found; not pushing.';
				}

				if (options.preview) {
					const remote = options.remote ? `${options.remote} ` : '';
					return `[Preview] Command not executed: git push ${remote}--follow-tags.`;
				}

				if (publishStatus === 'FAILED' && options.runPublish) {
					return 'Couldn\'t publish package to npm; not pushing.';
				}
			},
			async task() {
				pushedObjects = await git.pushGraceful(isOnGitHub, options.remote);
			},
		},
		...options.releaseDraft
			? [{
				title: 'Creating release draft on GitHub',
				enabled: () => isOnGitHub === true,
				skip() {
					if (options.preview) {
						return '[Preview] GitHub Releases draft will not be opened in preview mode.';
					}
				},
				task: () => releaseTaskHelper(options, package_, packageManager),
			}]
			: [],
	], {
		showSubtasks: false,
		renderer: options.renderer ?? 'default',
		clearOutput: !options.preview && !options.releaseDraftOnly,
	});

	if (!options.runPublish) {
		publishStatus = 'SUCCESS';
	}

	await tasks.run();

	if (pushedObjects) {
		console.error(`\n${logSymbols.error} ${pushedObjects.reason}`);
	}

	const {package_: newPackage} = await util.readPackage();
	return newPackage;
};

export default np;


================================================
FILE: source/npm/enable-2fa.js
================================================
import {execa} from 'execa';
import {from, catchError} from 'rxjs';
import Version from '../version.js';
import handleNpmError from './handle-npm-error.js';
import {npmNetworkTimeout, version as npmVersionCheck} from './util.js';

export const getEnable2faArguments = async (packageName, options) => {
	const npmVersion = await npmVersionCheck();
	const arguments_ = new Version(npmVersion).satisfies('>=9.0.0')
		? ['access', 'set', 'mfa=publish', packageName]
		: ['access', '2fa-required', packageName];

	if (options && options.otp) {
		arguments_.push('--otp', options.otp);
	}

	return arguments_;
};

const enable2fa = async (packageName, options) => execa('npm', await getEnable2faArguments(packageName, options), {timeout: npmNetworkTimeout});

const tryEnable2fa = (task, packageName, options) => from(enable2fa(packageName, options)).pipe(catchError(error => handleNpmError(error, task, otp => enable2fa(packageName, {otp}))));

export default tryEnable2fa;


================================================
FILE: source/npm/handle-npm-error.js
================================================
import listrInput from 'listr-input';
import chalk from 'chalk';
import {throwError, catchError} from 'rxjs';

const handleNpmError = (error, task, message, executor) => {
	if (typeof message === 'function') {
		executor = message;
		message = undefined;
	}

	// `one-time pass` is for npm and `Two factor authentication` is for Yarn.
	if (
		error.stderr.includes('one-time pass') // Npm
		|| error.stdout.includes('Two factor authentication') // Yarn v1
		|| error.stdout.includes('One-time password:') // Yarn berry
	) {
		const {title} = task;
		task.title = `${title} ${chalk.yellow('(waiting for input…)')}`;

		return listrInput('Enter OTP:', {
			done(otp) {
				task.title = title;
				return executor(otp);
			},
			autoSubmit: value => value.length === 6,
		}).pipe(catchError(error => handleNpmError(error, task, 'OTP was incorrect, try again:', executor)));
	}

	// Attempting to privately publish a scoped package without the correct npm plan
	// https://stackoverflow.com/a/44862841/10292952
	if (
		error.code === 402
		|| error.stderr.includes('npm ERR! 402 Payment Required') // Npm/pnpm
		|| error.stdout.includes('Response Code: 402 (Payment Required)') // Yarn Berry
	) {
		throw new Error('You cannot publish a scoped package privately without a paid plan. Did you mean to publish publicly?');
	}

	return throwError(() => error);
};

export default handleNpmError;


================================================
FILE: source/npm/oidc.js
================================================
import process from 'node:process';

const oidcProviders = [
	{
		id: 'github',
		name: 'GitHub Actions',
		// See https://github.com/npm/cli/blob/7da8fdd3625dd5541af57052c90fe1eabb41eb96/lib/utils/oidc.js#L49-L67
		// See https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-cloud-providers#adding-permissions-settings
		validate: () =>
			process.env.GITHUB_ACTIONS
			&& process.env.ACTIONS_ID_TOKEN_REQUEST_URL
			&& process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN,
	},
	{
		id: 'gitlab',
		name: 'GitLab CI',
		// See https://github.com/npm/cli/blob/7da8fdd3625dd5541af57052c90fe1eabb41eb96/lib/utils/oidc.js#L37-L47
		validate: () => process.env.GITLAB_CI && process.env.NPM_ID_TOKEN,
	},
];

export const getOidcProvider = () => {
	for (const provider of oidcProviders) {
		if (provider.validate()) {
			return provider.id;
		}
	}
};


================================================
FILE: source/npm/publish.js
================================================
import {execa} from 'execa';
import {merge, filter, catchError} from 'rxjs';
import open from 'open';

export const getPackagePublishArguments = options => {
	const arguments_ = ['publish'];

	if (options.tag) {
		arguments_.push('--tag', options.tag);
	}

	if (options.otp) {
		arguments_.push('--otp', options.otp);
	}

	if (options.publishScoped) {
		arguments_.push('--access', 'public');
	}

	if (options.provenance) {
		arguments_.push('--provenance');
	}

	return arguments_;
};

// 3 minutes timeout for publish operations (like git network operations)
// Publishing can take time for large packages or slow connections
const publishTimeout = 180_000;

export function runPublish(arguments_, options = {}) {
	const execaOptions = {
		stdin: 'pipe',
		// Timeout to prevent infinite hangs (e.g., from lifecycle scripts in watch mode)
		timeout: publishTimeout,
	};

	// `npm` 8.5+ has a bug where `npm publish <folder>` publishes from cwd instead of <folder>.
	// We work around this by changing cwd to the target directory.
	// https://github.com/npm/cli/issues/5136
	if (options.cwd) {
		execaOptions.cwd = options.cwd;
	}

	const subprocess = execa(...arguments_, execaOptions);

	let outputBuffer = '';

	const handleAuthPrompt = data => {
		outputBuffer += data.toString();

		// Detect npm's browser authentication prompt
		// Example: "Authenticate your account at:\nhttps://www.npmjs.com/auth/cli/xyz"
		if (outputBuffer.includes('Authenticate your account at:')) {
			const urlMatch = outputBuffer.match(/https:\/\/www\.npmjs\.com\/auth\/cli\/\S+/);
			if (urlMatch) {
				const authUrl = urlMatch[0];
				// Auto-open browser for authentication (ignore errors if browser fails to open)
				(async () => {
					try {
						await open(authUrl);
					} catch {}
				})();

				// Automatically send ENTER to continue (skip "Press ENTER" prompt)
				subprocess.stdin?.write('\n');
				// Clear buffer after handling to prevent repeated triggers
				outputBuffer = '';
			}
		}

		// Prevent buffer from growing indefinitely
		if (outputBuffer.length > 10_000) {
			outputBuffer = outputBuffer.slice(-5000);
		}
	};

	// Monitor both stdout and stderr for the authentication prompt
	subprocess.stdout?.on('data', handleAuthPrompt);
	subprocess.stderr?.on('data', handleAuthPrompt);

	return merge(subprocess.stdout, subprocess.stderr, subprocess).pipe(
		filter(Boolean),
		catchError(error => {
			// Include stderr in error message for better diagnostics
			if (error.stderr) {
				error.message = `${error.shortMessage}\n${error.stderr}`;
			}

			throw error;
		}),
	);
}


================================================
FILE: source/npm/util.js
================================================
import path from 'node:path';
import {pathExists} from 'path-exists';
import {execa} from 'execa';
import npmName from 'npm-name';
import chalk from 'chalk-template';
import * as util from '../util.js';

export const version = async () => {
	const {stdout} = await execa('npm', ['--version']);
	return stdout;
};

export const npmNetworkTimeout = 15_000; // 15 seconds for npm registry calls

const throwIfNpmTimeout = error => {
	if (error.timedOut) {
		error.message = 'Connection to npm registry timed out';
		throw error;
	}
};

export const checkConnection = async () => {
	try {
		await execa('npm', ['ping'], {timeout: npmNetworkTimeout});
		return true;
	} catch (error) {
		throwIfNpmTimeout(error);
		throw new Error('Connection to npm registry failed');
	}
};

export const username = async ({externalRegistry}) => {
	const arguments_ = ['whoami'];

	if (externalRegistry) {
		arguments_.push('--registry', externalRegistry);
	}

	try {
		const {stdout} = await execa('npm', arguments_, {timeout: npmNetworkTimeout});
		return stdout;
	} catch (error) {
		throwIfNpmTimeout(error);
		const isNotLoggedIn = /ENEEDAUTH|E401/.test(error.stderr);
		const message = isNotLoggedIn
			? 'You must be logged in. Use `npm login` and try again.'
			: 'Authentication error. Use `npm whoami` to troubleshoot.';
		const authError = new Error(message);
		authError.isNotLoggedIn = isNotLoggedIn;
		throw authError;
	}
};

export const login = async ({externalRegistry}) => {
	const arguments_ = ['login'];

	if (externalRegistry) {
		arguments_.push('--registry', externalRegistry);
	}

	try {
		await execa('npm', arguments_, {
			stdin: 'inherit',
			stdout: 'inherit',
			stderr: 'pipe',
		});
	} catch (error) {
		// User canceled the login prompt
		if (error.stderr?.includes('canceled')) {
			const cancelError = new Error('Login canceled');
			cancelError.name = 'ExitPromptError';
			throw cancelError;
		}

		throw error;
	}
};

const NPM_DEFAULT_REGISTRIES = new Set([
	// https://docs.npmjs.com/cli/v10/using-npm/registry
	'https://registry.npmjs.org',
	// https://docs.npmjs.com/cli/v10/commands/npm-profile#registry
	'https://registry.npmjs.org/',
]);
export const isExternalRegistry = package_ => {
	const registry = package_.publishConfig?.registry;
	if (typeof registry !== 'string') {
		return false;
	}

	const normalizedRegistry = registry.trim();
	const httpsVariant = normalizedRegistry.replace(/^http:\/\//, 'https://');

	return !NPM_DEFAULT_REGISTRIES.has(normalizedRegistry)
		&& !NPM_DEFAULT_REGISTRIES.has(httpsVariant);
};

export const collaborators = async package_ => {
	const packageName = package_.name;
	util.assert(typeof packageName === 'string', 'Package name is required');

	const arguments_ = ['access', 'list', 'collaborators', packageName, '--json'];

	if (package_.publishConfig?.registry) {
		arguments_.push('--registry', package_.publishConfig.registry);
	}

	try {
		const {stdout} = await execa('npm', arguments_, {timeout: npmNetworkTimeout});
		return stdout;
	} catch (error) {
		throwIfNpmTimeout(error);

		// Ignore non-existing package error
		if (error.stderr.includes('code E404')) {
			return false;
		}

		// External registries often don't support this endpoint, so ignore errors.
		// The whoami check is sufficient for verifying authentication.
		// See: https://github.com/sindresorhus/np/issues/420
		if (isExternalRegistry(package_)) {
			return false;
		}

		throw error;
	}
};

export const prereleaseTags = async packageName => {
	util.assert(typeof packageName === 'string', 'Package name is required');

	let tags = [];
	try {
		const {stdout} = await execa('npm', ['view', '--json', packageName, 'dist-tags'], {timeout: npmNetworkTimeout});
		tags = Object.keys(JSON.parse(stdout))
			.filter(tag => tag !== 'latest');
	} catch (error) {
		throwIfNpmTimeout(error);
		// HACK: NPM is mixing JSON with plain text errors. Luckily, the error
		// always starts with 'npm ERR!' (npm <10) or 'npm error' (npm >=10)
		// so as a solution, until npm/cli#2740 is fixed, we can remove anything
		// starting with 'npm ERR!' or 'npm error'
		/** @type {string} */
		const errorMessage = error.stderr;
		const errorJSON = errorMessage
			.split('\n')
			.filter(line => !line.startsWith('npm ERR!') && !line.startsWith('npm error'))
			.join('\n');

		try {
			const parsed = JSON.parse(errorJSON);
			// Only handle E404 errors gracefully; throw all other errors
			if (parsed?.error?.code !== 'E404') {
				throw error;
			}
		} catch {
			// If JSON parsing fails, we can't determine the error type, so throw the original error
			throw error;
		}
	}

	if (tags.length === 0) {
		tags.push('next');
	}

	return tags;
};

export const isPackageNameAvailable = async package_ => {
	const arguments_ = [package_.name];
	const availability = {
		isAvailable: false,
		isUnknown: false,
	};

	if (isExternalRegistry(package_)) {
		arguments_.push({
			registryUrl: package_.publishConfig.registry,
		});
	}

	try {
		availability.isAvailable = await npmName(...arguments_) || false;
	} catch {
		availability.isUnknown = true;
	}

	return availability;
};

export const verifyRecentNpmVersion = async () => {
	const npmVersion = await version();
	util.validateEngineVersionSatisfies('npm', npmVersion);
};

export const checkIgnoreStrategy = async ({files}, rootDirectory) => {
	const npmignoreExistsInPackageRootDirectory = await pathExists(path.resolve(rootDirectory, '.npmignore'));

	if (!files && !npmignoreExistsInPackageRootDirectory) {
		console.log(chalk`
		\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.
		`);
	}
};

export const getFilesToBePacked = async rootDirectory => {
	const {stdout} = await execa('npm', [
		'pack',
		'--dry-run',
		'--json',
		'--silent',
		// TODO: Remove this once [npm/cli#7354](https://github.com/npm/cli/issues/7354) is resolved.
		'--foreground-scripts=false',
	], {cwd: rootDirectory});

	try {
		// HACK: NPM lifecycle scripts can output text even with --silent and --foreground-scripts=false.
		// For example, Husky's prepare script outputs "> package@version prepare" and "> husky install".
		// We extract only the JSON portion by finding the first '[' character.
		// Related: https://github.com/sindresorhus/np/issues/742
		const {files} = JSON.parse(stdout.slice(Math.max(0, stdout.indexOf('[')))).at(0);
		return files.map(file => file.path);
	} catch (error) {
		throw new Error('Failed to parse output of npm pack', {cause: error});
	}
};

const isValidEntryPoint = value => typeof value === 'string' && !value.includes('*');

const getExportsFiles = exports => {
	const files = [];

	const extract = value => {
		if (isValidEntryPoint(value)) {
			files.push(value);
		} else if (typeof value === 'object' && value !== null) {
			for (const subvalue of Object.values(value)) {
				extract(subvalue);
			}
		}
	};

	extract(exports);
	return files;
};

export const getPackageEntryPoints = package_ => {
	const entryPoints = [];

	if (isValidEntryPoint(package_.main)) {
		entryPoints.push({field: 'main', path: package_.main});
	}

	if (typeof package_.bin === 'string') {
		if (isValidEntryPoint(package_.bin)) {
			entryPoints.push({field: 'bin', path: package_.bin});
		}
	} else if (typeof package_.bin === 'object' && package_.bin !== null) {
		for (const [name, binPath] of Object.entries(package_.bin)) {
			if (isValidEntryPoint(binPath)) {
				entryPoints.push({field: `bin.${name}`, path: binPath});
			}
		}
	}

	if (package_.exports) {
		for (const file of getExportsFiles(package_.exports)) {
			entryPoints.push({field: 'exports', path: file});
		}
	}

	return entryPoints;
};

export const verifyPackageEntryPoints = async (package_, rootDirectory) => {
	const packedFiles = new Set(await getFilesToBePacked(rootDirectory));
	const entryPoints = getPackageEntryPoints(package_);

	const seenPaths = new Set();
	const missingEntryPoints = [];

	for (const entryPoint of entryPoints) {
		const normalizedPath = entryPoint.path.replace(/^\.\//, '');

		if (seenPaths.has(normalizedPath)) {
			continue;
		}

		seenPaths.add(normalizedPath);

		if (!packedFiles.has(normalizedPath)) {
			missingEntryPoints.push(entryPoint);
		}
	}

	if (missingEntryPoints.length > 0) {
		const missing = missingEntryPoints.map(({field, path: entryPath}) => `  "${field}": ${entryPath}`).join('\n');
		throw new Error(`Missing entry points in published files:\n${missing}\n\nEnsure these files exist and are included in the "files" field.`);
	}
};

export const getPublishedPackageEngines = async package_ => {
	const arguments_ = ['view', '--json', package_.name, 'engines'];

	if (package_.publishConfig?.registry) {
		arguments_.push('--registry', package_.publishConfig.registry);
	}

	try {
		const {stdout} = await execa('npm', arguments_, {timeout: npmNetworkTimeout});

		// Handle empty response (package exists but has no engines field)
		if (stdout.trim() === '') {
			return undefined;
		}

		return JSON.parse(stdout);
	} catch (error) {
		throwIfNpmTimeout(error);

		// Package doesn't exist yet (first publish)
		if (error.stderr?.includes('E404')) {
			return undefined;
		}

		// External registries often don't support this endpoint, so ignore errors.
		// See: https://github.com/sindresorhus/np/issues/420
		if (isExternalRegistry(package_)) {
			return undefined;
		}

		throw error;
	}
};


================================================
FILE: source/package-manager/configs.js
================================================
/** @type {import('./types.d.ts').PackageManagerConfig} */
export const npmConfig = {
	cli: 'npm',
	id: 'npm',
	installCommand: ['npm', ['ci', '--engine-strict']],
	installCommandNoLockfile: ['npm', ['install', '--no-package-lock', '--no-production', '--engine-strict']],
	versionCommand: version => ['npm', ['version', version]],
	getRegistryCommand: ['npm', ['config', 'get', 'registry', '--workspaces=false']],
	tagVersionPrefixCommand: ['npm', ['config', 'get', 'tag-version-prefix', '--workspaces=false']],
	lockfiles: ['package-lock.json', 'npm-shrinkwrap.json'],
};

/** @type {import('./types.d.ts').PackageManagerConfig} */
export const pnpmConfig = {
	cli: 'pnpm',
	id: 'pnpm',
	installCommand: ['pnpm', ['install']],
	installCommandNoLockfile: ['pnpm', ['install']],
	versionCommand: version => ['pnpm', ['version', version]],
	// By default, pnpm config returns `undefined` instead of `v` for tag-version-prefix, so for consistent default behavior, use npm.
	tagVersionPrefixCommand: ['npm', ['config', 'get', 'tag-version-prefix', '--workspaces=false']],
	// Disable duplicated pnpm Git checks
	publishCommand: arguments_ => ['pnpm', [...arguments_, '--no-git-checks']],
	getRegistryCommand: ['pnpm', ['config', 'get', 'registry']],
	lockfiles: ['pnpm-lock.yaml'],
};

/** @type {import('./types.d.ts').PackageManagerConfig} */
export const yarnConfig = {
	cli: 'yarn',
	id: 'yarn',
	installCommand: ['yarn', ['install', '--frozen-lockfile', '--production=false']],
	installCommandNoLockfile: ['yarn', ['install', '--production=false']],
	getRegistryCommand: ['yarn', ['config', 'get', 'registry']],
	tagVersionPrefixCommand: ['yarn', ['config', 'get', 'version-tag-prefix']],
	versionCommand: version => ['yarn', ['version', '--new-version', version]],
	lockfiles: ['yarn.lock'],
};

/** @type {import('./types.d.ts').PackageManagerConfig} */
export const yarnBerryConfig = {
	cli: 'yarn',
	id: 'yarn-berry',
	installCommand: ['yarn', ['install', '--immutable']],
	installCommandNoLockfile: ['yarn', ['install']],
	// Yarn berry doesn't support git committing/tagging, so we use npm instead
	versionCommand: version => ['npm', ['version', version]],
	tagVersionPrefixCommand: ['yarn', ['config', 'get', 'version-tag-prefix']],
	// Yarn berry offloads publishing to npm, e.g. `yarn npm publish x.y.z`
	publishCommand: arguments_ => ['yarn', ['npm', ...arguments_]],
	getRegistryCommand: ['yarn', ['config', 'get', 'npmRegistryServer']],
	throwOnExternalRegistry: true,
	lockfiles: ['yarn.lock'],
};

/** @type {import('./types.d.ts').PackageManagerConfig} */
export const bunConfig = {
	cli: 'bun',
	id: 'bun',
	installCommand: ['bun', ['install', '--frozen-lockfile']],
	installCommandNoLockfile: ['bun', ['install', '--no-save']],
	versionCommand: version => ['npm', ['version', version]],
	// Bun doesn't support publishing, so we use npm instead. See https://github.com/oven-sh/bun/issues/5050
	publishCommand: arguments_ => ['npm', arguments_],
	// TODO: Bun doesn't support config get registry, this should be added in the future. See https://github.com/oven-sh/bun/issues/7140
	getRegistryCommand: ['npm', ['config', 'get', 'registry', '--workspaces=false']],
	tagVersionPrefixCommand: ['npm', ['config', 'get', 'tag-version-prefix', '--workspaces=false']],
	lockfiles: ['bun.lockb', 'bun.lock'],
};


================================================
FILE: source/package-manager/index.js
================================================
import fs from 'node:fs';
import path from 'node:path';
import semver from 'semver';
import * as configs from './configs.js';

/**
@param {string} rootDirectory
@param {import('./types.d.ts').PackageManagerConfig} config
*/
export function findLockfile(rootDirectory, config) {
	return config.lockfiles
		.map(filename => path.resolve(rootDirectory || '.', filename))
		.find(filepath => fs.existsSync(filepath));
}

/**
@param {string} rootDirectory
@param {import('read-pkg').NormalizedPackageJson} package_
*/
export function getPackageManagerConfig(rootDirectory, package_) {
	const config = configFromPackageManagerField(package_);
	return config || configFromLockfile(rootDirectory) || configs.npmConfig;
}

/** @param {import('read-pkg').NormalizedPackageJson} package_ */
function configFromPackageManagerField(package_) {
	if (typeof package_.packageManager !== 'string') {
		return undefined;
	}

	const [packageManager, version] = package_.packageManager.split('@');

	if (packageManager === 'yarn' && version && semver.gte(version, '2.0.0')) {
		return configs.yarnBerryConfig;
	}

	if (packageManager === 'npm') {
		return configs.npmConfig;
	}

	if (packageManager === 'pnpm') {
		return configs.pnpmConfig;
	}

	if (packageManager === 'yarn') {
		return configs.yarnConfig;
	}

	if (packageManager === 'bun') {
		return configs.bunConfig;
	}

	throw new Error(`Invalid package manager: ${package_.packageManager}`);
}

/** @param {string} rootDirectory */
function configFromLockfile(rootDirectory, options = [configs.npmConfig, configs.pnpmConfig, configs.yarnConfig]) {
	const foundConfig = options.find(config => findLockfile(rootDirectory, config));

	// If yarn.lock is found, check if it's Yarn Berry by looking for .yarnrc.yml
	if (foundConfig === configs.yarnConfig) {
		const yarnrcYmlPath = path.resolve(rootDirectory || '.', '.yarnrc.yml');
		if (fs.existsSync(yarnrcYmlPath)) {
			return configs.yarnBerryConfig;
		}
	}

	return foundConfig;
}

/** @param {import('./types.d.ts').Command} command */
export function printCommand([cli, arguments_]) {
	return `${cli} ${arguments_.join(' ')}`;
}


================================================
FILE: source/package-manager/types.d.ts
================================================
export type PackageManager = 'npm' | 'yarn' | 'pnpm';

/**
CLI and arguments, which can be passed to `execa`.
*/
export type Command = [cli: string, args: string[]];

export type PackageManagerConfig = {
	/**
 	The main CLI, e.g. the `npm` in `npm install`, `npm test`, etc.
  	*/
	cli: PackageManager;

	/**
 	How 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).
  	*/
	id: string;

	/**
 	How to install packages when there is a lockfile, e.g. `["npm", ["install"]]`.
  	*/
	installCommand: Command;

	/**
	How to install packages when there is no lockfile, e.g. `["npm", ["install"]]`.
	*/
	installCommandNoLockfile: Command;

	/**
 	Given a version string, return a version command e.g. `version => ["npm", ["version", version]]`.
  	*/
	versionCommand: (version: string) => [cli: string, args: string[]];

	/**
 	Modify the actual publish command. Defaults to `args => [config.cli, args]`.
  	*/
	publishCommand?: (arguments_: string[]) => Command;

	/**
 	CLI command which is expected to output the npm registry to use, e.g. `['npm', ['config', 'get', 'registry']]`.
  	*/
	getRegistryCommand: Command;

	/**
 	CLI command expected to output the version tag prefix (often "v"). e,g. `['npm', ['config', 'get', 'tag-version-prefix']]`.
  	*/
	tagVersionPrefixCommand: Command;

	/**
 	Set to true if the package manager doesn't support external registries. `np` will throw if one is detected and this is set.
  	*/
	throwOnExternalRegistry?: boolean;

	/**
 	List of lockfile names expected for this package manager, relative to CWD. e.g. `['package-lock.json', 'npm-shrinkwrap.json']`.
  	*/
	lockfiles: string[];
};


================================================
FILE: source/prerequisite-tasks.js
================================================
import process from 'node:process';
import Listr from 'listr';
import {execa} from 'execa';
import semver from 'semver';
import Version from './version.js';
import * as util from './util.js';
import * as git from './git-util.js';
import * as npm from './npm/util.js';
import {getOidcProvider} from './npm/oidc.js';

const prerequisiteTasks = (input, package_, options, {packageManager, rootDirectory}) => {
	const isExternalRegistry = npm.isExternalRegistry(package_);
	let newVersion;

	const tasks = [
		{
			title: 'Ping npm registry',
			enabled: () => !package_.private && !isExternalRegistry,
			task: async () => npm.checkConnection(),
		},
		{
			title: `Check ${packageManager.cli} version`,
			async task() {
				const {stdout: version} = await execa(packageManager.cli, ['--version']);
				util.validateEngineVersionSatisfies(packageManager.cli, version);
			},
		},
		{
			title: 'Verify user is authenticated',
			enabled: () => process.env.NODE_ENV !== 'test' && !package_.private,
			skip() {
				if (getOidcProvider()) {
					return 'Environment support for OIDC authentication detected - Skipping whoami check';
				}
			},
			async task() {
				const externalRegistry = package_.publishConfig?.registry;
				const username = await npm.username({externalRegistry});

				const collaborators = await npm.collaborators(package_);
				if (!collaborators) {
					return;
				}

				const json = JSON.parse(collaborators);
				const permissions = json[username];
				if (!permissions || !permissions.includes('write')) {
					throw new Error('You do not have write permissions required to publish this package.');
				}
			},
		},
		{
			title: 'Check git version',
			task: async () => git.verifyRecentGitVersion(),
		},
		{
			title: 'Check git user configuration',
			task: async () => git.verifyUserConfigIsSet(),
		},
		{
			title: 'Check git remote',
			task: async () => git.verifyRemoteIsValid(options.remote),
		},
		{
			title: 'Validate version',
			task() {
				newVersion = input instanceof Version
					? input
					: new Version(package_.version).setFrom(input);
			},
		},
		{
			title: 'Check for pre-release version',
			task() {
				if (!package_.private && newVersion.isPrerelease() && !options.tag) {
					throw 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');
				}
			},
		},
		{
			title: 'Check for Node.js engine support drop',
			enabled: () => !options.yolo && !package_.private,
			async task() {
				const publishedEngines = await npm.getPublishedPackageEngines(package_);

				// Skip if this is the first publish or if published package has no engines.node
				if (!publishedEngines?.node) {
					return;
				}

				const localNodeEngine = package_.engines?.node;

				// Skip if local package has no engines.node (we can't compare)
				if (!localNodeEngine) {
					return;
				}

				const publishedMinimum = util.getMinimumNodeVersion(publishedEngines.node);
				const localMinimum = util.getMinimumNodeVersion(localNodeEngine);

				// Skip if we couldn't parse either version
				if (!publishedMinimum || !localMinimum) {
					return;
				}

				// Check if the minimum Node.js version has increased
				if (semver.gt(localMinimum, publishedMinimum)) {
					const diff = semver.diff(package_.version, newVersion.toString());

					// Only major and premajor releases are allowed to drop Node.js support
					// For pre-1.0.0 packages, minor bumps are considered breaking changes per semver
					if (diff !== 'major' && diff !== 'premajor' && semver.major(package_.version) >= 1) {
						throw new Error(`Raising minimum Node.js version from ${publishedMinimum} to ${localMinimum} requires a major version bump. The current release is a ${diff} bump.`);
					}
				}
			},
		},
		{
			title: 'Verify package entry points',
			enabled: () => !options.yolo,
			async task() {
				await npm.verifyPackageEntryPoints(package_, rootDirectory);
			},
		},
		{
			title: 'Check git tag existence',
			async task() {
				await git.fetch();

				const tagPrefix = await util.getTagVersionPrefix(packageManager);

				await git.verifyTagDoesNotExistOnRemote(`${tagPrefix}${newVersion}`);
			},
		},
	];

	return new Listr(tasks);
};

export default prerequisiteTasks;


================================================
FILE: source/release-task-helper.js
================================================
import open from 'open';
import newGithubReleaseUrl from 'new-github-release-url';
import clipboard from 'clipboardy';
import {getTagVersionPrefix, getPreReleasePrefix} from './util.js';
import Version from './version.js';

// GitHub has a URL limit of ~8195 characters. We use a conservative limit to be safe.
const URL_LENGTH_LIMIT = 7900;
const CLIPBOARD_PLACEHOLDER = '<!-- Paste release notes from clipboard -->';

const releaseTaskHelper = async (options, package_, packageManager) => {
	if (!options.repoUrl) {
		throw new Error('Missing `repository` field in package.json. This is required for creating GitHub releases.');
	}

	const newVersion = options.releaseDraftOnly
		? new Version(package_.version)
		: new Version(package_.version).setFrom(options.version.toString(), {prereleasePrefix: await getPreReleasePrefix(packageManager)});

	const tag = await getTagVersionPrefix(packageManager) + newVersion.toString();

	const releaseNotes = options.releaseNotes ? options.generateReleaseNotes(tag) : '';

	// Try to generate URL with full release notes
	let url = newGithubReleaseUrl({
		repoUrl: options.repoUrl,
		tag,
		body: releaseNotes,
		isPrerelease: newVersion.isPrerelease(),
	});

	// If the URL is too long, copy release notes to clipboard and use a placeholder
	if (url.length > URL_LENGTH_LIMIT) {
		await clipboard.write(releaseNotes);
		url = newGithubReleaseUrl({
			repoUrl: options.repoUrl,
			tag,
			body: CLIPBOARD_PLACEHOLDER,
			isPrerelease: newVersion.isPrerelease(),
		});
		console.log('\nRelease notes are too long for URL. Copied to clipboard instead.');
	}

	await open(url);
};

export default releaseTaskHelper;


================================================
FILE: source/ui.js
================================================
import inquirer from 'inquirer';
import chalk from 'chalk';
import githubUrlFromGit from 'github-url-from-git';
import hostedGitInfo from 'hosted-git-info';
import {htmlEscape} from 'escape-goat';
import isScoped from 'is-scoped';
import isInteractive from 'is-interactive';
import {execa} from 'execa';
import semver from 'semver';
import Version, {SEMVER_INCREMENTS} from './version.js';
import * as util from './util.js';
import * as git from './git-util.js';
import * as npm from './npm/util.js';

const PRERELEASE_INCREMENTS = new Set([
	'prepatch',
	'preminor',
	'premajor',
	'prerelease',
]);

const printCommitLog = async (repoUrl, registryUrl, fromLatestTag, releaseBranch) => {
	const revision = fromLatestTag ? await git.latestTagOrFirstCommit() : await git.previousTagOrFirstCommit();
	if (!revision) {
		throw new Error('The package has not been published yet.');
	}

	const log = await git.commitLogFromRevision(revision);

	if (!log) {
		return {
			hasCommits: false,
			hasUnreleasedCommits: false,
			generateReleaseNotes() {},
		};
	}

	let hasUnreleasedCommits = false;
	let commitRangeText = `${revision}...${releaseBranch}`;

	let commits = log.split('\n')
		.map(commit => {
			const splitIndex = commit.lastIndexOf(' ');
			return {
				message: commit.slice(0, splitIndex),
				id: commit.slice(splitIndex + 1),
			};
		});

	if (!fromLatestTag) {
		const latestTag = await git.latestTag();

		// Version bump commit created by np, following the semver specification.
		const versionBumpCommitName = latestTag.match(/v\d+\.\d+\.\d+/) && latestTag.slice(1); // Name v1.0.1 becomes 1.0.1
		const versionBumpCommitIndex = commits.findIndex(commit => commit.message === versionBumpCommitName);

		if (versionBumpCommitIndex > 0) {
			commitRangeText = `${revision}...${latestTag}`;
			hasUnreleasedCommits = true;
		}

		if (await git.isHeadDetached()) {
			commitRangeText = `${revision}...${latestTag}`;
		}

		// Get rid of unreleased commits and of the version bump commit.
		commits = commits.slice(versionBumpCommitIndex + 1);
	}

	const history = commits.map(commit => {
		const commitMessage = util.linkifyIssues(repoUrl, commit.message);
		const commitId = util.linkifyCommit(repoUrl, commit.id);
		return `- ${commitMessage}  ${commitId}`;
	}).join('\n');

	const generateReleaseNotes = nextTag => commits.map(commit =>
		`- ${htmlEscape(commit.message)}  ${commit.id}`).join('\n') + `\n\n---\n\n${repoUrl}/compare/${revision}...${nextTag}`;

	const commitRange = util.linkifyCommitRange(repoUrl, commitRangeText);
	console.log(`${chalk.bold('Commits:')}\n${history}\n\n${chalk.bold('Commit Range:')}\n${commitRange}\n\n${chalk.bold('Registry:')}\n${registryUrl}\n`);

	return {
		hasCommits: true,
		hasUnreleasedCommits,
		generateReleaseNotes,
	};
};

const checkNewFilesAndDependencies = async (package_, rootDirectory) => {
	const newFiles = await util.getNewFiles(rootDirectory);
	const newDependencies = await util.getNewDependencies(package_, rootDirectory);

	const noNewFirstTimeFiles = !newFiles.firstTime || newFiles.firstTime.length === 0;
	const noNewDependencies = !newDependencies || newDependencies.length === 0;

	// Only prompt for first-time files and new dependencies (things that WILL be published)
	if (noNewFirstTimeFiles && noNewDependencies) {
		return {
			confirmed: true,
			unpublishedFiles: newFiles.unpublished || [],
		};
	}

	const messages = [];
	if (newFiles.firstTime.length > 0) {
		messages.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.`);
	}

	if (newDependencies.length > 0) {
		messages.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.`);
	}

	if (!isInteractive()) {
		console.log(messages.join('\n'));
		return {
			confirmed: true,
			unpublishedFiles: newFiles.unpublished || [],
		};
	}

	const answers = await inquirer.prompt([{
		type: 'confirm',
		name: 'confirm',
		message: `${messages.join('\n')}\nContinue?`,
		default: false,
	}]);

	return {
		confirmed: answers.confirm,
		unpublishedFiles: newFiles.unpublished || [],
	};
};

const displayUnpublishedFilesWarning = unpublishedFiles => {
	if (!unpublishedFiles || unpublishedFiles.length === 0) {
		return;
	}

	console.log([
		'',
		chalk.yellow('⚠ WARNING: The following new files will NOT be published:'),
		chalk.dim(util.groupFilesInFolders(unpublishedFiles)),
		'',
		chalk.yellow('These files are excluded by your package.json "files" field.'),
		chalk.yellow('If you intended to publish them, add them to the "files" field.'),
		'',
	].join('\n'));
};

/**
@param {import('./cli-implementation.js').CLI['flags']} options
@param {{package_: import('read-pkg').NormalizedPackageJson; rootDirectory: string}} context
*/
const ui = async ({packageManager, ...options}, {package_, rootDirectory}) => { // eslint-disable-line complexity
	const oldVersion = package_.version;
	const extraBaseUrls = ['gitlab.com'];
	const repoUrl = package_.repository && (() => {
		// Try to parse with hosted-git-info first to handle shorthand URLs like "github:foo/bar"
		const gitInfo = hostedGitInfo.fromUrl(package_.repository.url);
		if (gitInfo?.browse) {
			return gitInfo.browse({noCommittish: true});
		}

		// Fall back to github-url-from-git for GitLab and other known hosts
		const githubUrl = githubUrlFromGit(package_.repository.url, {extraBaseUrls});
		if (githubUrl) {
			return githubUrl;
		}

		// Final fallback: parse any git URL format (handles GitHub Enterprise and other hosts)
		return util.parseGitUrl(package_.repository.url);
	})();

	const {stdout: registryUrl} = await execa(...packageManager.getRegistryCommand);
	const releaseBranch = options.branch;

	let unpublishedFiles;
	if (options.runPublish) {
		await npm.checkIgnoreStrategy(package_, rootDirectory);

		const {confirmed, unpublishedFiles: files} = await checkNewFilesAndDependencies(package_, rootDirectory);
		unpublishedFiles = files;
		if (!confirmed) {
			return {
				...options,
				confirm: confirmed,
			};
		}
	}

	if (options.releaseDraftOnly) {
		console.log(`\nCreate a release draft on GitHub for ${chalk.bold.magenta(package_.name)} ${chalk.dim(`(current: ${oldVersion})`)}\n`);
	} else {
		const versionText = options.version
			? chalk.dim(`(current: ${oldVersion}, next: ${new Version(oldVersion).setFrom(options.version, {prereleasePrefix: await util.getPreReleasePrefix(packageManager)}).format()})`)
			: chalk.dim(`(current: ${oldVersion})`);

		console.log(`\nPublish a new version of ${chalk.bold.magenta(package_.name)} ${versionText}\n`);
	}

	const useLatestTag = !options.releaseDraftOnly;
	const {hasCommits, hasUnreleasedCommits, generateReleaseNotes} = await printCommitLog(repoUrl, registryUrl, useLatestTag, releaseBranch);

	// Display unpublished files warning after commit log
	displayUnpublishedFilesWarning(unpublishedFiles);

	if (hasUnreleasedCommits && options.releaseDraftOnly) {
		const answers = await inquirer.prompt({
			confirm: {
				type: 'confirm',
				message: 'Unreleased commits found. They won\'t be included in the release draft. Continue?',
				default: false,
			},
		});

		if (!answers.confirm) {
			return {
				...options,
				...answers,
			};
		}
	}

	// Non-interactive mode - return before prompting
	// But if it's a prerelease without a tag, we need to prompt for the tag
	if (options.version) {
		const prereleasePrefix = await util.getPreReleasePrefix(packageManager);
		const versionObject = new Version(oldVersion).setFrom(options.version, {prereleasePrefix});
		const needsTag = options.runPublish && versionObject.isPrerelease() && !options.tag;

		if (!needsTag) {
			return {
				...options,
				confirm: true,
				repoUrl,
				generateReleaseNotes,
			};
		}

		// Prompt for tag only
		const answers = await inquirer.prompt({
			tag: {
				type: 'select',
				message: 'How should this pre-release version be tagged in npm?',
				async choices() {
					const existingPrereleaseTags = await npm.prereleaseTags(package_.name);

					return [
						...existingPrereleaseTags,
						new inquirer.Separator(),
						{
							name: 'Other (specify)',
							value: undefined,
						},
					];
				},
			},
			customTag: {
				type: 'input',
				message: 'Tag',
				when: answers => answers.tag === undefined,
				validate(input) {
					if (input.length === 0) {
						return 'Please specify a tag, for example, `next`.';
					}

					if (input.toLowerCase() === 'latest') {
						return 'It\'s not possible to publish pre-releases under the `latest` tag. Please specify something else, for example, `next`.';
					}

					return true;
				},
			},
		});

		return {
			...options,
			tag: answers.tag || answers.customTag || options.tag,
			confirm: true,
			repoUrl,
			generateReleaseNotes,
		};
	}

	if (!hasCommits) {
		const answers = await inquirer.prompt({
			confirm: {
				type: 'confirm',
				message: 'No commits found since previous release, continue?',
				default: false,
			},
		});

		if (!answers.confirm) {
			return {
				...options,
				...answers,
			};
		}
	}

	if (options.availability.isUnknown) {
		if (!isScoped(package_.name)) {
			throw new Error('Unknown availability, but package is not scoped. This shouldn\'t happen');
		}

		const answers = await inquirer.prompt({
			confirm: {
				type: 'confirm',
				when: isScoped(package_.name) && options.runPublish,
				message: `Failed to check availability of scoped repo name ${chalk.bold.magenta(package_.name)}. Do you want to try and publish it anyway?`,
				default: false,
			},
		});

		if (!answers.confirm) {
			return {
				...options,
				...answers,
			};
		}
	}

	const needsPrereleaseTag = answers => {
		if (!options.runPublish || options.tag) {
			return false;
		}

		// Check if version is a prerelease increment
		if (answers.version) {
			return PRERELEASE_INCREMENTS.has(answers.version);
		}

		// Check if custom version is a prerelease
		return answers.customVersion?.isPrerelease();
	};

	const alreadyPublicScoped = packageManager.id === 'yarn-berry' && options.runPublish && await util.getNpmPackageAccess(package_) === 'public';

	// Note that inquirer question.when is a bit confusing. Only `false` will cause the question to be skipped.
	// Any other value like `true` and `undefined` means ask the question.
	// so we make sure to always return an explicit boolean here to make it less confusing
	// see https://github.com/SBoudrias/Inquirer.js/pull/1340
	const needToAskForPublish = (() => {
		if (alreadyPublicScoped || !isScoped(package_.name) || !options.availability.isAvailable || options.availability.isUnknown || !options.runPublish) {
			return false;
		}

		// Only ask if access is not explicitly set and not using an external registry
		return !package_.publishConfig?.access && !npm.isExternalRegistry(package_);
	})();

	// Extract prerelease identifier from current version if it exists, otherwise use npm config
	const currentPrerelease = semver.prerelease(oldVersion);
	// Only use the prefix if it's a string (not a number like in '1.0.0-0')
	const currentPrereleasePrefix = typeof currentPrerelease?.[0] === 'string' ? currentPrerelease[0] : undefined;
	const configPrereleasePrefix = await util.getPreReleasePrefix(packageManager);
	const defaultPrereleasePrefix = currentPrereleasePrefix ?? configPrereleasePrefix;

	const answers = await inquirer.prompt({
		version: {
			type: 'select',
			message: 'Select SemVer increment or specify new version',
			pageSize: SEMVER_INCREMENTS.length + 2,
			default: 0,
			choices: [
				...SEMVER_INCREMENTS.map(increment => ({
					name: `${increment} 	${new Version(oldVersion, increment, {prereleasePrefix: defaultPrereleasePrefix}).format()}`,
					value: increment,
				})),
				new inquirer.Separator(),
				{
					name: 'Other (specify)',
					value: undefined,
				},
			],
		},
		customVersion: {
			type: 'input',
			message: 'Version',
			when: answers => answers.version === undefined,
			filter(input) {
				if (SEMVER_INCREMENTS.includes(input)) {
					throw new Error('Custom version should not be a SemVer increment.');
				}

				const version = new Version(oldVersion);

				try {
					// Version error handling does validation
					version.setFrom(input);
				} catch (error) {
					if (error.message.includes('valid SemVer version')) {
						throw new Error(`Custom version ${input} should be a valid SemVer version.`);
					}

					error.message = error.message.replace('New', 'Custom');

					throw error;
				}

				return version;
			},
		},
		prereleasePrefix: {
			type: 'input',
			message: 'Prerelease identifier',
			// Use || not ?? to treat empty string as falsy (show 'rc' instead of empty default)
			default: defaultPrereleasePrefix || 'rc',
			when(answers) {
				// Only ask when a prerelease increment was selected from the menu
				if (!answers.version) {
					return false;
				}

				return PRERELEASE_INCREMENTS.has(answers.version);
			},
		},
		tag: {
			type: 'select',
			message: 'How should this pre-release version be tagged in npm?',
			when: answers => needsPrereleaseTag(answers),
			async choices() {
				const existingPrereleaseTags = await npm.prereleaseTags(package_.name);

				return [
					...existingPrereleaseTags,
					new inquirer.Separator(),
					{
						name: 'Other (specify)',
						value: undefined,
					},
				];
			},
		},
		customTag: {
			type: 'input',
			message: 'Tag',
			when: answers => answers.tag === undefined && needsPrereleaseTag(answers),
			validate(input) {
				if (input.length === 0) {
					return 'Please specify a tag, for example, `next`.';
				}

				if (input.toLowerCase() === 'latest') {
					return 'It\'s not possible to publish pre-releases under the `latest` tag. Please specify something else, for example, `next`.';
				}

				return true;
			},
		},
		publishScoped: {
			type: 'confirm',
			when: needToAskForPublish,
			message: `This scoped repo ${chalk.bold.magenta(package_.name)} hasn't been published. Do you want to publish it publicly?`,
			default: false,
		},
	});

	// Create Version object with custom prerelease prefix if provided
	let version;
	if (answers.version) {
		// Use || not ?? to treat empty string as falsy (fall back to default/rc)
		const prereleasePrefix = answers.prereleasePrefix || defaultPrereleasePrefix;
		version = new Version(oldVersion, answers.version, {prereleasePrefix});
	} else if (answers.customVersion) {
		version = answers.customVersion;
	} else {
		version = options.version;
	}

	return {
		...options,
		version,
		tag: answers.tag || answers.customTag || options.tag,
		publishScoped: alreadyPublicScoped || answers.publishScoped,
		confirm: true,
		repoUrl,
		generateReleaseNotes,
	};
};

export default ui;


================================================
FILE: source/util.js
================================================
import process from 'node:process';
import {fileURLToPath} from 'node:url';
import path from 'node:path';
import {readPackageUp} from 'read-package-up';
import {parsePackage} from 'read-pkg';
import issueRegex from 'issue-regex';
import terminalLink from 'terminal-link';
import {execa} from 'execa';
import pMemoize from 'p-memoize';
import chalk from 'chalk';
import semver from 'semver';
import Version from './version.js';
import * as git from './git-util.js';
import * as npm from './npm/util.js';

export const assert = (condition, message) => {
	if (!condition) {
		throw new Error(message);
	}
};

export const readPackage = async (packagePath = process.cwd()) => {
	const packageResult = await readPackageUp({cwd: packagePath});

	if (!packageResult) {
		throw new Error('No `package.json` found. Make sure the current directory is a valid package.');
	}

	return {package_: packageResult.packageJson, rootDirectory: path.dirname(packageResult.path)};
};

const _npRootDirectory = fileURLToPath(new URL('..', import.meta.url));

// Re-define `npRootDirectory` for trailing slash consistency.
export const {package_: npPackage, rootDirectory: npRootDirectory} = await readPackage(_npRootDirectory);

export const linkifyIssues = (url, message) => {
	if (!url) {
		return message;
	}

	return message.replace(issueRegex(), issue => {
		const issuePart = issue.replace('#', '/issues/');

		if (issue.startsWith('#')) {
			return terminalLink(issue, `${url}${issuePart}`);
		}

		return terminalLink(issue, `https://github.com/${issuePart}`);
	});
};

export const linkifyCommit = (url, commit) => {
	if (!url) {
		return commit;
	}

	return terminalLink(commit, `${url}/commit/${commit}`);
};

export const linkifyCommitRange = (url, commitRange) => {
	if (!url) {
		return commitRange;
	}

	return terminalLink(commitRange, `${url}/compare/${commitRange}`);
};

/*
Git URL patterns for parsing various formats.

Patterns use greedy matching + cleanRepo logic to handle edge cases like:
- URLs with double .git suffix (repo.git.git)
- Repos with .git in their name (my.git.git where repo is my.git)

Using [^\s/?#] to exclude whitespace, query params (?), and fragments (#)
Query params and fragments are stripped before matching.
*/
const GIT_URL_PATTERNS = [
	/// https://host/owner/repo.git or https://host/owner/repo
	// Case-insensitive protocol matching via /i flag
	{
		regex: /^https?:\/\/([^\s/?#]+)\/([^\s/?#]+)\/([^\s/?#]+)(\.git)?$/i,
		transform: (host, owner, repo) => `https://${host}/${owner}/${repo}`,
	},
	/// git@host:owner/repo.git (common SSH format)
	// Using [^\s:?#] and [^\s/?#] creates clear boundaries
	{
		regex: /^git@([^\s:?#]+):([^\s/?#]+)\/([^\s/?#]+)(\.git)?$/,
		transform: (host, owner, repo) => `https://${host}/${owner}/${repo}`,
	},
	/// git+https://host/owner/repo.git
	{
		regex: /^git\+https:\/\/([^\s/?#]+)\/([^\s/?#]+)\/([^\s/?#]+)(\.git)?$/i,
		transform: (host, owner, repo) => `https://${host}/${owner}/${repo}`,
	},
	/// ssh://git@host/owner/repo.git
	{
		regex: /^ssh:\/\/git@([^\s/?#]+)\/([^\s/?#]+)\/([^\s/?#]+)(\.git)?$/i,
		transform: (host, owner, repo) => `https://${host}/${owner}/${repo}`,
	},
];

const ALPHANUMERIC_REGEX = /[a-z\d]/i;
const isValidGitPathComponent = value => Boolean(value) && ALPHANUMERIC_REGEX.test(value);

/**
Parse a git URL to extract the HTTPS browse URL.

Handles various git URL formats including GitHub Enterprise.

This function uses carefully crafted regex patterns that avoid ReDoS vulnerabilities:
- All patterns are anchored with ^ and $ to prevent partial matches
- Character classes use negated sets [^...] which are linear-time
- No nested quantifiers or overlapping alternatives
- Greedy quantifiers with explicit bounds prevent exponential backtracking

@param {string} url - The git URL to parse.
@returns {string | undefined} - The HTTPS browse URL or undefined if parsing fails.

@example
```
parseGitUrl('git@github.com:owner/repo.git');
//=> 'https://github.com/owner/repo'

parseGitUrl('https://github.com/owner/repo.git');
//=> 'https://github.com/owner/repo'

parseGitUrl('github:owner/repo');
//=> undefined (use hosted-git-info for this)
```
*/
export const parseGitUrl = url => {
	if (typeof url !== 'string' || url.length === 0) {
		return;
	}

	const cleanUrl = url.split(/[?#]/, 1)[0];
	if (cleanUrl.length === 0) {
		return;
	}

	for (const {regex, transform} of GIT_URL_PATTERNS) {
		const match = cleanUrl.match(regex);
		if (match) {
			const [, host, owner, repo] = match;

			// Remove .git suffix if present in the captured repo name
			const cleanRepo = repo.endsWith('.git') ? repo.slice(0, -4) : repo;

			// Validate that none of the components are empty
			if (!host) {
				continue;
			}

			// Validate that owner and repo contain at least one alphanumeric character
			// This prevents pathological inputs like all dots or special chars
			if (!isValidGitPathComponent(owner) || !isValidGitPathComponent(cleanRepo)) {
				continue;
			}

			return transform(host, owner, cleanRepo);
		}
	}
};

/** @type {(config: import('./package-manager/types.js').PackageManagerConfig) => Promise<string>} */
export const getTagVersionPrefix = pMemoize(async config => {
	assert(config && Object.hasOwn(config, 'tagVersionPrefixCommand'), 'Config is missing key `tagVersionPrefixCommand`');

	try {
		const {stdout} = await execa(...config.tagVersionPrefixCommand);

		return stdout;
	} catch {
		return 'v';
	}
});

export const joinList = list => chalk.reset(list.map(item => `- ${item}`).join('\n'));

export const groupFilesInFolders = (files, groupingMinimumDepth = 1, groupingThresholdCount = 5) => {
	const groups = {};
	for (const file of files) {
		const groupKey = path.join(...file.split(path.sep).slice(0, groupingMinimumDepth));
		groups[groupKey] = [...groups[groupKey] ?? [], file];
	}

	const lines = [];
	for (const [folder, filesInFolder] of Object.entries(groups)) {
		if (filesInFolder.length > groupingThresholdCount) {
			lines.push(`- ${folder}/* ${chalk.bold.white(`(${filesInFolder.length} files)`)}`);
			continue;
		}

		for (const file of filesInFolder) {
			lines.push(`- ${file}`);
		}
	}

	return chalk.reset(lines.join('\n'));
};

export const getNewFiles = async rootDirectory => {
	const listNewFiles = await git.newFilesSinceLastRelease(rootDirectory);
	const listPackageFiles = await npm.getFilesToBePacked(rootDirectory);

	return {
		unpublished: listNewFiles.filter(file => !listPackageFiles.includes(file) && !file.startsWith('.git')),
		firstTime: listNewFiles.filter(file => listPackageFiles.includes(file)),
	};
};

export const getNewDependencies = async (newPackage, rootDirectory) => {
	let oldPackageFile;

	try {
		oldPackageFile = await git.readFileFromLastRelease(path.resolve(rootDirectory, 'package.json'));
	} catch {
		// Handle first time publish
		return Object.keys(newPackage.dependencies ?? {});
	}

	const oldPackage = parsePackage(oldPackageFile);

	const newDependencies = [];

	for (const dependency of Object.keys(newPackage.dependencies ?? {})) {
		if (!oldPackage.dependencies?.[dependency]) {
			newDependencies.push(dependency);
		}
	}

	return newDependencies;
};

/** @type {(config: import('./package-manager/types.js').PackageManagerConfig) => Promise<string>} */
export const getPreReleasePrefix = pMemoize(async config => {
	assert(config && Object.hasOwn(config, 'cli'), 'Config is missing key `cli`');

	try {
		const {stdout} = await execa(config.cli, ['config', 'get', 'preid']);

		return stdout === 'undefined' ? '' : stdout;
	} catch {
		return '';
	}
});

export const validateEngineVersionSatisfies = (engine, version) => {
	const engineRange = npPackage.engines[engine];
	if (!new Version(version).satisfies(engineRange)) {
		throw new Error(`\`np\` requires ${engine} ${engineRange}`);
	}
};

export async function getNpmPackageAccess(package_) {
	const arguments_ = ['access', 'get', 'status', package_.name, '--json'];

	if (package_.publishConfig?.registry) {
		arguments_.push('--registry', package_.publishConfig.registry);
	}

	try {
		const {stdout} = await execa('npm', arguments_, {timeout: npm.npmNetworkTimeout});
		return JSON.parse(stdout)[package_.name]; // Note: returns "private" for non-existent packages
	} catch (error) {
		if (error.timedOut) {
			error.message = 'Connection to npm registry timed out';
		}

		throw error;
	}
}

export const getMinimumNodeVersion = range => {
	if (!range || typeof range !== 'string') {
		return undefined;
	}

	try {
		const minVersion = semver.minVersion(range);
		return minVersion?.version;
	} catch {
		return undefined;
	}
};


================================================
FILE: source/version.js
================================================
import semver from 'semver';
import {template as chalk} from 'chalk-template';

/** @type {string[]} Allowed `SemVer` release types. */
export const SEMVER_INCREMENTS = ['patch', 'minor', 'major', 'prepatch', 'preminor', 'premajor', 'prerelease'];
export const SEMVER_INCREMENTS_LIST = SEMVER_INCREMENTS.join(', ');
const SEMVER_INCREMENTS_LIST_LAST_OR = `${SEMVER_INCREMENTS.slice(0, -1).join(', ')}, or ${SEMVER_INCREMENTS.slice(-1)}`;

/** @typedef {semver.SemVer} SemVerInstance */
/** @typedef {semver.ReleaseType} SemVerIncrement */
/** @typedef {import('chalk').ColorName | import('chalk').ModifierName} ColorName */

/** @param {string} input @returns {input is SemVerIncrement} */
const isSemVersionIncrement = input => SEMVER_INCREMENTS.includes(input);

/** @param {string} input */
const isInvalidSemVersion = input => Boolean(!semver.valid(input));

/**
Formats the first difference between two versions to the given `diffColor`. Useful for `prerelease` diffs.

@param {string[]} current @param {string[]} previous @param {ColorName} diffColor
*/
const formatFirstDifference = (current, previous, diffColor) => {
	const firstDifferenceIndex = current.findIndex((part, i) => previous.at(i) !== part);
	current[firstDifferenceIndex] = `{${diffColor} ${current.at(firstDifferenceIndex)}}`;
	return current.join('.');
};

export default class Version {
	/** @type {SemVerInstance} */
	#version;
	/** @type {SemVerIncrement | undefined} */
	#diff = undefined;
	/** @type {string | undefined} */
	#prereleasePrefix = undefined;

	toString() {
		return this.#version.version;
	}

	/**
	Sets `this.#version` to the given version.

	@param {string} version
	@throws If `version` is an invalid `SemVer` version.
	*/
	#trySetVersion(version) {
		this.#version = semver.parse(version);

		if (this.#version === null) {
			throw new Error(`Version ${version} should be a valid SemVer version.`);
		}
	}

	/**
	@param {string} version - A valid `SemVer` version.
	@param {SemVerIncrement} [increment] - Optionally increment `version`.
	@param {object} [options]
	@param {string} [options.prereleasePrefix] - A prefix to use for `prerelease` versions.
	*/
	constructor(version, increment, {prereleasePrefix} = {}) {
		this.#prereleasePrefix = prereleasePrefix;
		this.#trySetVersion(version);

		if (increment) {
			if (!isSemVersionIncrement(increment)) {
				throw new Error(`Increment ${increment} should be one of ${SEMVER_INCREMENTS_LIST_LAST_OR}.`);
			}

			this.setFrom(increment);
		}
	}

	/**
	Sets 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.

	@param {string | SemVerIncrement} input - A new valid `SemVer` version or a `SemVer` increment to increase the current version by.
	@param {object} [options]
	@param {string} [options.prereleasePrefix] - A prefix to use for `prerelease` versions.
	@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.
	*/
	setFrom(input, {prereleasePrefix = ''} = {}) {
		this.#prereleasePrefix ??= prereleasePrefix;
		const previousVersion = this.toString();

		if (isSemVersionIncrement(input)) {
			this.#version.inc(input, this.#prereleasePrefix);
		} else {
			if (isInvalidSemVersion(input)) {
				throw new Error(`New version ${input} should either be one of ${SEMVER_INCREMENTS_LIST}, or a valid SemVer version.`);
			}

			if (this.#isGreaterThanOrEqualTo(input)) {
				throw new Error(`New version ${input} should be higher than current version ${this.toString()}.`);
			}

			this.#trySetVersion(input);
		}

		// Set `this.#diff` to format version diffs
		this.#diff = semver.diff(previousVersion, this.#version);
		return this;
	}

	/**
	Formats the current version with `options.color`, pretty-printing the version's diff with `options.diffColor` if possible.

	If 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.

	@param {object} options
	@param {ColorName} [options.color = 'dim']
	@param {ColorName} [options.diffColor = 'cyan']
	@param {string} [options.prereleasePrefix]
	@returns {string} A color-formatted version string.
	*/
	format({color = 'dim', diffColor = 'cyan', previousVersion} = {}) {
		if (typeof previousVersion === 'string') {
			const previousSemver = semver.parse(previousVersion);

			if (previousSemver === null) {
				throw new Error(`Previous version ${previousVersion} should be a valid SemVer version.`);
			}

			previousVersion = previousSemver;
		}

		if (!this.#diff) {
			if (!previousVersion) {
				return chalk(`{${color} ${this.toString()}}`);
			}

			this.#diff = semver.diff(previousVersion, this.#version);
		}

		const {major, minor, patch, prerelease} = this.#version;
		const previousPrerelease = semver.prerelease(previousVersion);

		if (prerelease && previousPrerelease) {
			const prereleaseDiff = formatFirstDifference(prerelease, previousPrerelease, diffColor);
			return chalk(`{${color} ${major}.${minor}.${patch}-${prereleaseDiff}}`);
		}

		/* eslint-disable unicorn/no-nested-ternary */
		return (
			this.#diff === 'major'
				? chalk(`{${color} {${diffColor} ${major}}.${minor}.${patch}}`)
				: this.#diff === 'minor'
					? chalk(`{${color} ${major}.{${diffColor} ${minor}}.${patch}}`)
					: this.#diff === 'patch'
						? chalk(`{${color} ${major}.${minor}.{${diffColor} ${patch}}}`)
						: this.#diff === 'premajor'
							? chalk(`{${color} {${diffColor} ${major}}.${minor}.${patch}-{${diffColor} ${prerelease.join('.')}}}`)
							: this.#diff === 'preminor'
								? chalk(`{${color} ${major}.{${diffColor} ${minor}}.${patch}-{${diffColor} ${prerelease.join('.')}}}`)
								: this.#diff === 'prepatch'
									? chalk(`{${color} ${major}.${minor}.{${diffColor} ${patch}}-{${diffColor} ${prerelease.join('.')}}}`)
									: this.#diff === 'prerelease' ? chalk(`{${color} ${major}.${minor}.${patch}-{${diffColor} ${prerelease.join('.')}}}`) : ''
		);
		/* eslint-enable unicorn/no-nested-ternary */
	}

	/**
	If the current version satisifes the given `SemVer` range.

	@param {string} range
	@throws If `range` is invalid.
	*/
	satisfies(range) {
		if (!semver.validRange(range)) {
			throw new Error(`Range ${range} is not a valid SemVer range.`);
		}

		return semver.satisfies(this.#version, range, {
			includePrerelease: true,
		});
	}

	/**
	If the current version has any `prerelease` components.
	*/
	isPrerelease() {
		return Boolean(semver.prerelease(this.#version));
	}

	/**
	If the current version is the same as or higher than the given version.

	@param {string} otherVersion
	*/
	#isGreaterThanOrEqualTo(otherVersion) {
		return semver.gte(this.#version, otherVersion);
	}
}


================================================
FILE: test/_helpers/integration-test.d.ts
================================================
import type {Macro, ExecutionContext} from 'ava';
import type {Execa$} from 'execa';

type Context = {
	firstCommitMessage: string;
	getCommitMessage: (sha: string) => Promise<string>;
	createFile: (file: string, content?: string) => Promise<void>;
	commitNewFile: () => Promise<{
		sha: string;
		commitMessage: string;
	}>;
};

type CommandsFunctionParameters = [{
	t: ExecutionContext<Context>;
	$$: Execa$<string>;
	temporaryDirectory: string;
}];

type AssertionsFunctionParameters<MockType> = [{
	t: ExecutionContext<Context>;
	testedModule: MockType;
	$$: Execa$<string>;
	temporaryDirectory: string;
}];

export type CreateFixtureMacro<MockType> = Macro<[
	commands: (...arguments_: CommandsFunctionParameters) => Promise<void>,
	assertions: (...arguments_: AssertionsFunctionParameters<MockType>) => Promise<void>,
], Context>;

export function _createFixture<MockType>(source: string): CreateFixtureMacro<MockType>;


================================================
FILE: test/_helpers/integration-test.js
================================================
/* eslint-disable ava/no-ignored-test-files */
import crypto from 'node:crypto';
import path from 'node:path';
import fs from 'fs-extra';
import test from 'ava';
import esmock from 'esmock';
import {$, execa} from 'execa';
import {temporaryDirectoryTask} from 'tempy';

const createEmptyGitRepo = async ($$, temporaryDirectory) => {
	const firstCommitMessage = '"init1"';

	await $$`git init`;

	// `git tag` needs an initial commit
	await fs.createFile(path.resolve(temporaryDirectory, 'temp'));
	await $$`git add temp`;
	await $$`git commit -m ${firstCommitMessage}`;
	await $$`git rm temp`;
	await $$`git commit -m "init2"`;

	return firstCommitMessage;
};

export const createIntegrationTest = async (t, assertions) => {
	await temporaryDirectoryTask(async temporaryDirectory => {
		const $$ = $({cwd: temporaryDirectory});

		t.context.firstCommitMessage = await createEmptyGitRepo($$, temporaryDirectory);

		// From https://stackoverflow.com/a/3357357/10292952
		t.context.getCommitMessage = async sha => {
			const {stdout: commitMessage} = await $$`git log --format=%B -n 1 ${sha}`;
			return commitMessage.trim();
		};

		t.context.createFile = async (file, content = '') => fs.outputFile(path.resolve(temporaryDirectory, file), content);

		t.context.commitNewFile = async () => {
			await t.context.createFile(`new-${crypto.randomUUID()}`);
			await $$`git add .`;
			await $$`git commit -m "added"`;

			const {stdout: lastCommitSha} = await $$`git rev-parse --short HEAD`;

			return {
				sha: lastCommitSha,
				commitMessage: await t.context.getCommitMessage(lastCommitSha),
			};
		};

		await assertions({$$, temporaryDirectory});
	});
};

export const _createFixture = source => test.macro(async (t, commands, assertions) => {
	await createIntegrationTest(t, async ({$$, temporaryDirectory}) => {
		const testedModule = await esmock(source, {}, {
			'node:process': {cwd: () => temporaryDirectory},
			execa: {execa: async (command, commandArguments, options = {}) => execa(command, commandArguments, {...options, cwd: temporaryDirectory})},
		});

		await commands({t, $$, temporaryDirectory});
		await assertions({
			t, testedModule, $$, temporaryDirectory,
		});
	});
});


================================================
FILE: test/_helpers/listr-renderer.js
================================================
let tasks;

export class SilentRenderer {
	constructor(_tasks) {
		tasks = _tasks;
	}

	static get tasks() {
		return tasks;
	}

	static get nonTTY() {
		return true;
	}

	static clearTasks() {
		tasks = [];
	}

	render() {}

	end() {}
}


================================================
FILE: test/_helpers/listr.js
================================================
import {SilentRenderer} from './listr-renderer.js';

export const run = async listr => {
	listr.setRenderer(SilentRenderer);
	await listr.run();
};

export const assertTaskFailed = (t, taskTitle) => {
	const task = SilentRenderer.tasks.find(task => task.title === taskTitle);
	t.true(task.hasFailed(), `Task '${taskTitle}' did not fail!`);
};

export const assertTaskDisabled = (t, taskTitle) => {
	const task = SilentRenderer.tasks.find(task => task.title === taskTitle);
	t.true(!task.isEnabled(), `Task '${taskTitle}' was enabled!`);
};

export const assertTaskDoesntExist = (t, taskTitle) => {
	t.true(SilentRenderer.tasks.every(task => task.title !== taskTitle), `Task '${taskTitle}' exists!`);
};

export const assertTaskSkipped = (t, taskTitle) => {
	const task = SilentRenderer.tasks.find(task => task.title === taskTitle);
	t.true(task.isSkipped(), `Task '${taskTitle}' was not skipped!`);
};


================================================
FILE: test/_helpers/mock-inquirer.js
================================================
import esmock from 'esmock';
import is from '@sindresorhus/is';
import stripAnsi from 'strip-ansi';
import mapObject from 'map-obj';

/** @typedef {import('ava').ExecutionContext<Record<string, never>>} ExecutionContext */
/** @typedef {string | boolean} ShortAnswer */
/** @typedef {Record<'input' | 'error', string> | Record<'choice', string> | Record<'confirm', boolean>} LongAnswer */
/** @typedef {ShortAnswer | LongAnswer} Answer */
/** @typedef {Record<string, Answer>} Answers  */
/** @typedef {import('inquirer').DistinctQuestion & {name?: never}} Prompt */

/**
Mocks `inquirer.prompt` and answers each prompt in the program with the provided `inputAnswers`.

This only handles prompts of type `input`, `list`, and `confirm`. If other prompt types are added, they must be implemented here.

Logs for debugging are outputted on test failure.

@see https://gist.github.com/yyx990803/f61f347b6892078c40a9e8e77b9bd984

@param {object} o Test input and actual prompts
@param {ExecutionContext} o.t
@param {Answers} o.inputAnswers Test input
@param {Record<string, Prompt> | Prompt[]} o.prompts Actual prompts
*/
const mockPrompt = async ({t, inputAnswers, prompts}) => {
	const answers = {};

	// Ensure `prompts` is an object
	if (Array.isArray(prompts)) {
		const promptsObject = {};

		for (const prompt of prompts) {
			promptsObject[prompt.name] = prompt;
		}

		prompts = promptsObject;
	}

	t.log('prompts:', Object.keys(prompts));

	/* eslint-disable no-await-in-loop */
	for (const [name, prompt] of Object.entries(prompts)) {
		if (prompt.when !== undefined) {
			if (is.boolean(prompt.when) && !prompt.when) {
				t.log(`skipping prompt '${name}'`);
				continue;
			}

			if (is.function(prompt.when) && !prompt.when(answers)) {
				t.log(`skipping prompt '${name}'`);
				continue;
			}
		}

		t.log(`getting input for prompt '${name}'`);

		const setValue = value => {
			if (prompt.validate) {
				const result = prompt.validate(value);

				if (result !== true) {
					if (is.string(result)) {
						throw new Error(result);
					}

					if (result === false) {
						throw new Error('You must provide a valid value');
					}
				}
			}

			if (is.string(value)) {
				t.log(`filtering value '${value}' for prompt '${name}'`);
			} else {
				t.log(`filtering value for prompt '${name}':`, value);
			}

			answers[name] = prompt.filter
				? prompt.filter(value) // eslint-disable-line unicorn/no-array-callback-reference
				: value;

			t.log(`got value '${answers[name]}' for prompt '${name}'`);
		};

		/** @param {Answer} input */
		const chooseValue = async input => {
			t.is(prompt.type, 'select');
			let choices;

			if (is.asyncFunction(prompt.choices)) {
				choices = await prompt.choices(answers);
			} else if (is.function(prompt.choices)) {
				choices = prompt.choices(answers);
			} else {
				choices = prompt.choices;
			}

			t.log(`choices for prompt '${name}':`, choices);

			const value = choices.find(choice => {
				if (is.object(choice)) {
					return choice.name && stripAnsi(choice.name).startsWith(input.choice ?? input);
				}

				if (is.string(choice)) {
					return stripAnsi(choice).startsWith(input.choice ?? input);
				}

				return false;
			});

			// `value.value` could exist but literally be `undefined`
			setValue(Object.hasOwn(value, 'value') ? value.value : value);
		};

		const input = inputAnswers[name];

		if (is.undefined(input)) {
			t.fail(`Expected input for prompt '${name}'.`);
			continue;
		}

		if (is.string(input)) {
			t.log(`found input for prompt '${name}': '${input}'`);
		} else {
			t.log(`found input for prompt '${name}':`, input);
		}

		/** @param {Answer} input */
		const handleInput = async input => {
			if (is.string(input)) {
				if (['input'].includes(prompt.type)) {
					setValue(input);
				} else if (['select'].includes(prompt.type)) {
					return chooseValue(input);
				} else {
					t.fail('Incorrect input type');
				}

				return;
			}

			if (input.input !== undefined) {
				t.is(prompt.type, 'input');
				setValue(input.input);
				return;
			}

			if (input.choice !== undefined) {
				await chooseValue(input);
				return;
			}

			if (is.boolean(input.confirm) || is.boolean(input)) {
				t.is(prompt.type, 'confirm');
				setValue(input.confirm ?? input);
			}
		};

		// Multiple inputs for the given prompt
		if (is.array(input)) {
			for (const attempt of input) {
				if (attempt.error) {
					await t.throwsAsync(
						handleInput(attempt),
						{message: attempt.error},
					);
				} else {
					await handleInput(attempt);
				}
			}
		}

		await handleInput(input);
	}
	/* eslint-enable no-await-in-loop */

	return answers;
};

/**
Fixes relative module paths for use with `esmock`. Allows specifiying the same relative location in test files as in source files.
@param {import('esmock').MockMap} mocks
*/
const fixRelativeMocks = mocks => mapObject(mocks, (key, value) => [key.replace('./', '../../source/'), value]);

/**
Mocks `inquirer` for testing `source/ui.js`.

@param {object} o Test input and optional global mocks
@param {ExecutionContext} o.t
@param {Answers} o.answers Test input
@param {import('esmock').MockMap} [o.mocks] Optional global mocks
*/
export const mockInquirer = async ({t, answers, mocks = {}}) => {
	/** @type {string[]} */
	const logs = [];

	/** @type {import('../../source/ui.js')} */
	const ui = await esmock('../../source/ui.js', import.meta.url, {
		inquirer: {
			async prompt(prompts) {
				let uiAnswers = {};

				const assertions = await t.try(async tt => {
					uiAnswers = await mockPrompt({t: tt, inputAnswers: answers, prompts});
				});

				assertions.commit({retainLogs: !assertions.passed});
				return uiAnswers;
			},
		},
	}, {
		...fixRelativeMocks(mocks),
		import: {
			console: {log: (...arguments_) => logs.push(...arguments_)},
		},
	});

	return {ui, logs};
};


================================================
FILE: test/_helpers/stub-execa.d.ts
================================================
import type {Macro, ExecutionContext} from 'ava';
import type {ExecaReturnValue} from 'execa';

type AssertionsFunctionParameters<MockType> = [{
	t: ExecutionContext;
	testedModule: MockType;
}];

export type CreateFixtureMacro<MockType> = Macro<[
	commands: ExecaReturnValue[],
	assertions: (...arguments_: AssertionsFunctionParameters<MockType>) => Promise<void>,
]>;

export function _createFixture<MockType>(source: string, importMeta: string): CreateFixtureMacro<MockType>;


================================================
FILE: test/_helpers/stub-execa.js
================================================
/* eslint-disable ava/no-ignored-test-files */
import test from 'ava';
import esmock from 'esmock';
import sinon from 'sinon';
import {execa} from 'execa';

// Default stubs for common commands that should pass by default
const defaultCommands = [
	{command: 'npm --version', stdout: '10.0.0'},
	{command: 'npm ping', stdout: ''},
	{command: 'npm view --json test engines', stdout: ''},
	{command: 'git version', stdout: 'git version 2.40.0'},
	{command: 'git ls-remote origin HEAD', stdout: 'abc123\tHEAD'},
	{command: 'git fetch', stdout: ''},
	{command: 'git config --get tag.gpgSign', stdout: ''},
];

/**
Stubs `execa` to return a specific result when called with the given commands.

A command passes if its exit code is 0, or if there's no exit code and no stderr.

Resolves or throws the given result.

@param {import('execa').ExecaReturnValue[]} commands
*/
const makeExecaStub = commands => {
	const normalizedCommands = [...defaultCommands, ...commands].map(result => {
		const [command, ...commandArguments] = result.command.split(' ');
		return {
			...result,
			command,
			commandArguments,
		};
	});

	return sinon.stub().callsFake((command, commandArguments = [], options) => {
		for (let index = normalizedCommands.length - 1; index >= 0; index--) {
			const result = normalizedCommands[index];

			if (result.command !== command) {
				continue;
			}

			if (!areArgumentsEqual(result.commandArguments, commandArguments)) {
				continue;
			}

			if (!matchesOptions(result.options, options)) {
				continue;
			}

			const passes = result.exitCode === 0 || (!result.exitCode && !result.stderr);

			if (passes) {
				return Promise.resolve(result);
			}

			return Promise.reject(Object.assign(new Error(), result)); // eslint-disable-line unicorn/error-message
		}
	});
};

const areArgumentsEqual = (left, right) => left.length === right.length && left.every((value, index) => value === right[index]);

const matchesOptions = (expectedOptions, actualOptions) => {
	if (!expectedOptions) {
		return true;
	}

	if (!actualOptions) {
		return false;
	}

	return Object.entries(expectedOptions).every(([key, value]) => Object.is(actualOptions[key], value));
};

const stubExeca = commands => {
	const execaStub = makeExecaStub(commands);

	return {
		execa: {
			async execa(...arguments_) {
				// Only call real execa if stub doesn't have a match
				const result = execaStub(...arguments_);
				if (result === undefined) {
					return execa(...arguments_);
				}

				return result;
			},
		},
	};
};

export const _createFixture = (source, importMeta) => test.macro(async (t, commands, assertions) => {
	const testedModule = await esmock(source, importMeta, {}, stubExeca(commands));
	await assertions({t, testedModule});
});


================================================
FILE: test/_helpers/util.js
================================================
import {fileURLToPath} from 'node:url';
import path from 'node:path';

export const runIfExists = async (function_, ...arguments_) => {
	if (typeof function_ === 'function') {
		await function_(...arguments_);
	}
};

const __dirname = path.dirname(fileURLToPath(import.meta.url));

export const getFixture = fixture => path.resolve(__dirname, '..', 'fixtures', ...fixture.split('/'));


================================================
FILE: test/_helpers/verify-cli.d.ts
================================================
import type {Macro, ExecutionContext} from 'ava';

type VerifyCliMacro = Macro<[
	binPath: string,
	args: string | string[],
	expectedLines: string[],
], Record<string, never>>;

export const cliPasses: VerifyCliMacro;
export const cliFails: VerifyCliMacro;


================================================
FILE: test/_helpers/verify-cli.js
================================================
/* eslint-disable ava/no-ignored-test-files */
import test from 'ava';
import {execa} from 'execa';

const trim = stdout => stdout.split('\n').map(line => line.trim());

const _verifyCli = shouldPass => test.macro(async (t, binaryPath, arguments_, expectedLines) => {
	const {exitCode, stdout} = await execa(binaryPath, [arguments_].flat(), {reject: false});
	const receivedLines = trim(stdout);

	t.deepEqual(receivedLines, expectedLines, 'CLI output different than expectations!');
	t.is(exitCode, shouldPass ? 0 : 1, 'CLI exited with the wrong exit code!');
});

export const cliPasses = _verifyCli(true);
export const cliFails = _verifyCli(false);


================================================
FILE: test/cli.js
================================================
import path from 'node:path';
import test from 'ava';
import {npPackage, npRootDirectory as rootDirectory} from '../source/util.js';
import {cliPasses} from './_helpers/verify-cli.js';

const cli = path.resolve(rootDirectory, 'source', 'cli-implementation.js');

test('flags: --help', cliPasses, cli, '--help', [
	'',
	'A better `npm publish`',
	'',
	'Usage',
	'$ np <version>',
	'',
	'Version can be:',
	'patch | minor | major | prepatch | preminor | premajor | prerelease | 1.2.3',
	'',
	'Options',
	'--any-branch           Allow publishing from any branch',
	'--branch               Name of the release branch (default: main | master)',
	'--no-cleanup           Skips cleanup of node_modules',
	'--no-tests             Skips tests',
	'--yolo                 Skips cleanup and testing',
	'--no-publish           Skips publishing',
	'--preview              Show tasks without actually executing them',
	'--tag                  Publish under a given dist-tag',
	'--contents             Subdirectory to publish',
	'--no-release-draft     Skips opening a GitHub release draft',
	'--release-draft-only   Only opens a GitHub release draft for the latest published version',
	'--no-release-notes     Skips generating release notes when opening a GitHub release draft',
	'--test-script          Name of npm run script to run tests before publishing (default: test)',
	'--no-2fa               Don\'t enable 2FA on new packages (not recommended)',
	'--message              Version bump commit message, \'%s\' will be replaced with version (default: \'%s\' with npm and \'v%s\' with yarn)',
	'--package-manager      Use a specific package manager (default: \'packageManager\' field in package.json)',
	'--provenance           Publish with npm provenance statements (CI-only)',
	'--remote               Git remote to push to (default: origin)',
	'',
	'Examples',
	'$ np',
	'$ np patch',
	'$ np 1.0.2',
	'$ np 1.0.2-beta.3 --tag=beta',
	'$ np 1.0.2-beta.3 --tag=beta --contents=dist',
	'',
]);

test('flags: --version', cliPasses, cli, '--version', [npPackage.version]);


================================================
FILE: test/config.js
================================================
import path from 'node:path';
import test from 'ava';
import esmock from 'esmock';
import {readPackage} from '../source/util.js';

const testedModulePath = '../source/config.js';

const getFixture = fixture => path.resolve('test', 'fixtures', 'config', fixture);

const getConfigWhenGlobalBinaryIsUsed = async pathPackageDirectory => {
	const getConfig = await esmock(testedModulePath, {
		'is-installed-globally': true,
	});
	return getConfig(pathPackageDirectory);
};

const getConfigWhenLocalBinaryIsUsed = async pathPackageDirectory => {
	const getConfig = await esmock(testedModulePath, {
		'is-installed-globally': false,
	});
	return getConfig(pathPackageDirectory);
};

const useGlobalBinary = test.macro(async (t, packageDirectory, source) => {
	const config = await getConfigWhenGlobalBinaryIsUsed(getFixture(packageDirectory));
	t.deepEqual(config, {source});
});

const useLocalBinary = test.macro(async (t, packageDirectory, source) => {
	const config = await getConfigWhenLocalBinaryIsUsed(getFixture(packageDirectory));
	t.deepEqual(config, {source});
});

test(
	'returns config from package directory when global binary is used and `package.json` exists in package directory',
	useGlobalBinary,
	'pkg-dir',
	'package.json',
);

test(
	'returns config from package directory when global binary is used and `.np-config.json` exists in package directory',
	useGlobalBinary,
	'local1',
	'packagedir/.np-config.json',
);

test(
	'returns config from package directory when global binary is used and `.np-config.js` as CJS exists in package directory',
	useGlobalBinary,
	'local2',
	'packagedir/.np-config.js',
);

test(
	'returns config from package directory when global binary is used and `.np-config.cjs` exists in package directory',
	useGlobalBinary,
	'local3',
	'packagedir/.np-config.cjs',
);

test(
	'returns config from package directory when global binary is used and `.np-config.js` as ESM exists in package directory',
	useGlobalBinary,
	'local4',
	'packagedir/.np-config.js',
);

test(
	'returns config from package directory when global binary is used and `.np-config.mjs` exists in package directory',
	useGlobalBinary,
	'local5',
	'packagedir/.np-config.mjs',
);

test('global binary merges global and project config with project taking precedence', async t => {
	const fixtureDirectory = getFixture('pkg-dir');

	// Create a temporary home directory with global config
	const temporaryHome = getFixture('homedir1');

	const getConfig = await esmock(testedModulePath, {
		'is-installed-globally': true,
		'node:os': {homedir: () => temporaryHome},
	});

	const config = await getConfig(fixtureDirectory);

	// Should have project config
	t.is(config.source, 'package.json');
});

test(
	'returns config from package directory when local binary is used and `package.json` exists in package directory',
	useLocalBinary,
	'pkg-dir',
	'package.json',
);

test(
	'returns config from package directory when local binary is used and `.np-config.json` exists in package directory',
	useLocalBinary,
	'local1',
	'packagedir/.np-config.json',
);

test(
	'returns config from package directory when local binary is used and `.np-config.js` as CJS exists in package directory',
	useLocalBinary,
	'local2',
	'packagedir/.np-config.js',
);

test(
	'returns config from package directory when local binary is used and `.np-config.cjs` exists in package directory',
	useLocalBinary,
	'local3',
	'packagedir/.np-config.cjs',
);

test(
	'returns config from package directory when local binary is used and `.np-config.js` as ESM exists in package directory',
	useLocalBinary,
	'local4',
	'packagedir/.np-config.js',
);

test(
	'returns config from package directory when local binary is used and `.np-config.mjs` exists in package directory',
	useLocalBinary,
	'local5',
	'packagedir/.np-config.mjs',
);

test('`contents` option in config allows reading package from subdirectory', async t => {
	const fixtureDirectory = getFixture('contents-option');

	// Load config from fixture directory (simulates loading from process.cwd())
	const getConfig = await esmock(testedModulePath, {
		'is-installed-globally': false,
	});

	const config = await getConfig(fixtureDirectory);

	// Config should have contents option
	t.is(config.contents, 'dist');

	// Using contents from config should read package from subdirectory
	const contentsPath = path.join(fixtureDirectory, config.contents);
	const {package_, rootDirectory} = await readPackage(contentsPath);

	t.is(package_.name, 'from-dist');
	t.is(rootDirectory, contentsPath);
});

test('config values override defaults', async t => {
	const fixtureDirectory = getFixture('flag-precedence');

	const getConfig = await esmock(testedModulePath, {
		'is-installed-globally': false,
	});

	const config = await getConfig(fixtureDirectory);

	// Config should override default values
	t.is(config.tests, false);
	t.is(config.cleanup, false);
	t.is(config.publish, false);
});


================================================
FILE: test/fixtures/config/contents-option/.np-config.json
================================================
{
	"contents": "dist"
}


================================================
FILE: test/fixtures/config/contents-option/dist/package.json
================================================
{
	"name": "from-dist",
	"version": "1.0.0"
}


================================================
FILE: test/fixtures/config/flag-precedence/.np-config.json
================================================
{
	"tests": false,
	"cleanup": false,
	"publish": false
}


================================================
FILE: test/fixtures/config/flag-precedence/package.json
================================================
{
	"name": "test-flag-precedence",
	"version": "1.0.0"
}


================================================
FILE: test/fixtures/config/homedir1/.np-config.json
================================================
{
	"source": "homedir/.np-config.json"
}


================================================
FILE: test/fixtures/config/homedir2/.np-config.js
================================================
module.exports = {
	source: 'homedir/.np-config.js'
};


================================================
FILE: test/fixtures/config/homedir3/.np-config.cjs
================================================
module.exports = {
	source: 'homedir/.np-config.cjs'
};


================================================
FILE: test/fixtures/config/homedir4/.np-config.js
================================================
export default {
	source: 'homedir/.np-config.js'
};


================================================
FILE: test/fixtures/config/homedir5/.np-config.mjs
================================================
export default {
	source: 'homedir/.np-config.mjs'
};


================================================
FILE: test/fixtures/config/local1/.np-config.json
================================================
{
	"source": "packagedir/.np-config.json"
}


================================================
FILE: test/fixtures/config/local2/.np-config.js
================================================
module.exports = {
	source: 'packagedir/.np-config.js'
};


================================================
FILE: test/fixtures/config/local3/.np-config.cjs
================================================
module.exports = {
	source: 'packagedir/.np-config.cjs'
};


================================================
FILE: test/fixtures/config/local4/.np-config.js
================================================
export default {
	source: 'packagedir/.np-config.js'
};


================================================
FILE: test/fixtures/config/local4/package.json
================================================
{
	"name": "use-type-module-for-config-fixtures",
	"type": "module"
}


================================================
FILE: test/fixtures/config/local5/.np-config.mjs
================================================
export default {
	source: 'packagedir/.np-config.mjs'
};


================================================
FILE: test/fixtures/config/package.json
================================================
{
	"name": "override-type-module-for-config-fixtures"
}


================================================
FILE: test/fixtures/config/pkg-dir/package.json
================================================
{
	"name": "test-fixtures",
	"np": {
		"source": "package.json"
	}
}


================================================
FILE: test/fixtures/files/dot-github/.github/pull_request_template.md
================================================
<!--

Thanks for submitting a pull request 🙌

**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.

Try 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.

-->


================================================
FILE: test/fixtures/files/dot-github/index.js
================================================
console.log('foo');


================================================
FILE: test/fixtures/files/dot-github/package.json
================================================
{
	"name": "foo",
	"version": "0.0.0",
	"files": ["index.js"]
}


================================================
FILE: test/fixtures/files/files-and-npmignore/package.json
================================================
{
	"name": "foo",
	"version": "0.0.0",
	"files": ["source"]
}


================================================
FILE: test/fixtures/files/files-and-npmignore/readme.md
================================================
# Foo


================================================
FILE: test/fixtures/files/files-and-npmignore/source/.npmignore
================================================
index.test-d.ts


================================================
FILE: test/fixtures/files/files-and-npmignore/source/bar.js
================================================
console.log('bar');


================================================
FILE: test/fixtures/files/files-and-npmignore/source/foo.js
================================================
console.log('foo');


================================================
FILE: test/fixtures/files/files-and-npmignore/source/index.d.ts
================================================
export function foo(): string;
export function bar(): string;


================================================
FILE: test/fixtures/files/files-and-npmignore/source/index.test-d.ts
================================================
import {expectType} from 'tsd';
import {foo, bar} from './index.js';

expectType<string>(foo());
expectType<string>(bar());


================================================
FILE: test/fixtures/files/files-slash/index.js
================================================
console.log('foo');


================================================
FILE: test/fixtures/files/files-slash/package.json
================================================
{
	"name": "foo",
	"version": "0.0.0",
	"files": ["/index.js"]
}


================================================
FILE: test/fixtures/files/gitignore/.gitignore
================================================
dist


================================================
FILE: test/fixtures/files/gitignore/index.d.ts
================================================
export default function foo(): string;


================================================
FILE: test/fixtures/files/gitignore/index.js
================================================
export default function foo() {
	return 'bar';
}


================================================
FILE: test/fixtures/files/gitignore/index.test-d.ts
================================================
import {expectType} from 'tsd';
import foo from './index.js';

expectType<string>(foo());


================================================
FILE: test/fixtures/files/gitignore/package.json
================================================
{
	"name": "foo",
	"version": "0.0.0",
	"files": ["dist"]
}


================================================
FILE: test/fixtures/files/gitignore/readme.md
================================================
# Foo


================================================
FILE: test/fixtures/files/has-readme-and-license/index.js
================================================
console.log('foo');


================================================
FILE: test/fixtures/files/has-readme-and-license/license.md
================================================
MIT


================================================
FILE: test/fixtures/files/has-readme-and-license/package.json
================================================
{
	"name": "foo",
	"version": "0.0.0",
	"files": ["index.js"]
}


================================================
FILE: test/fixtures/files/has-readme-and-license/readme.md
================================================
# Foo


================================================
FILE: test/fixtures/files/main/bar.js
================================================
console.log('bar');


================================================
FILE: test/fixtures/files/main/foo.js
================================================
console.log('foo');


================================================
FILE: test/fixtures/files/main/package.json
================================================
{
	"name": "foo",
	"version": "0.0.0",
	"main": "foo.js",
	"files": ["bar.js"]
}


================================================
FILE: test/fixtures/files/missing-bin/index.js
================================================
export default 'foo';


================================================
FILE: test/fixtures/files/missing-bin/package.json
================================================
{
	"name": "missing-bin",
	"version": "0.0.0",
	"bin": "./cli.js",
	"files": ["index.js"]
}


================================================
FILE: test/fixtures/files/missing-main/package.json
================================================
{
	"name": "missing-main",
	"version": "0.0.0",
	"main": "dist/index.js",
	"files": ["source"]
}


================================================
FILE: test/fixtures/files/missing-main/source/index.js
================================================
export default 'foo';


================================================
FILE: test/fixtures/files/npmignore/.npmignore
================================================
index.test-d.ts


================================================
FILE: test/fixtures/files/npmignore/index.d.ts
================================================
export default function foo(): string;


================================================
FILE: test/fixtures/files/npmignore/index.js
================================================
export default function foo() {
	return 'bar';
}


================================================
FILE: test/fixtures/files/npmignore/index.test-d.ts
================================================
import {expectType} from 'tsd';
import foo from './index.js';

expectType<string>(foo());


================================================
FILE: test/fixtures/files/npmignore/package.json
================================================
{
	"name": "foo",
	"version": "0.0.0"
}


================================================
FILE: test/fixtures/files/npmignore/readme.md
================================================
# Foo


================================================
FILE: test/fixtures/files/npmignore-and-gitignore/.gitignore
================================================
dist


================================================
FILE: test/fixtures/files/npmignore-and-gitignore/.npmignore
================================================
script/
source/


================================================
FILE: test/fixtures/files/npmignore-and-gitignore/package.json
================================================
{
	"name": "foo",
	"version": "0.0.0"
}


================================================
FILE: test/fixtures/files/npmignore-and-gitignore/readme.md
================================================
# Foo


================================================
FILE: test/fixtures/files/npmignore-and-gitignore/script/build.js
================================================
// ... yada yada yada


================================================
FILE: test/fixtures/files/npmignore-and-gitignore/source/index.ts
================================================
export default function foo() {
	return 'bar';
}


================================================
FILE: test/fixtures/files/one-file/index.js
================================================
console.log('foo');


================================================
FILE: test/fixtures/files/one-file/package.json
================================================
{
	"name": "foo",
	"version": "0.0.0",
	"files": ["index.js"]
}


================================================
FILE: test/fixtures/files/prepare-script/index.js
================================================
export default function foo() {
	return 'bar';
}


================================================
FILE: test/fixtures/files/prepare-script/package.json
================================================
{
	"name": "foo",
	"version": "0.0.0",
	"files": ["index.js"],
	"scripts": {
		"prepare": "echo '> foo@0.0.0 prepare' && echo '> test prepare script'"
	}
}


================================================
FILE: test/fixtures/files/source-and-dist-dir/dist/index.js
================================================
console.log('foo');
console.log('bar');


================================================
FILE: test/fixtures/files/source-and-dist-dir/package.json
================================================
{
	"name": "foo",
	"version": "0.0.0",
	"files": ["source"]
}


================================================
FILE: test/fixtures/files/source-and-dist-dir/source/bar.js
================================================
console.log('bar');


================================================
FILE: test/fixtures/files/source-and-dist-dir/source/foo.js
================================================
console.log('foo');


================================================
FILE: test/fixtures/files/source-dir/package.json
================================================
{
	"name": "foo",
	"version": "0.0.0",
	"files": ["source"]
}


================================================
FILE: test/fixtures/files/source-dir/source/bar.js
================================================
console.log('bar');


================================================
FILE: test/fixtures/files/source-dir/source/foo.js
================================================
console.log('foo');


================================================
FILE: test/fixtures/readme.md
================================================
The directory is for the resources
in the script npmignore.js


================================================
FILE: test/git-util/commit-log-from-revision.js
================================================
import test from 'ava';
import {stripIndent} from 'common-tags';
import {_createFixture} from '../_helpers/integration-test.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js');

test('returns single commit', createFixture, async () => {
	//
}, async ({t, testedModule: {commitLogFromRevision}, $$}) => {
	await $$`git tag v0.0.0`;
	const {sha, commitMessage} = await t.context.commitNewFile();

	t.is(await commitLogFromRevision('v0.0.0'), `${commitMessage} ${sha}`);
});

test('returns multiple commits, from newest to oldest', createFixture, async () => {
	//
}, async ({t, testedModule: {commitLogFromRevision}, $$}) => {
	await $$`git tag v0.0.0`;
	const commit1 = await t.context.commitNewFile();
	const commit2 = await t.context.commitNewFile();
	const commit3 = await t.context.commitNewFile();

	const commitLog = stripIndent`
		${commit3.commitMessage} ${commit3.sha}
		${commit2.commitMessage} ${commit2.sha}
		${commit1.commitMessage} ${commit1.sha}
	`;

	t.is(await commitLogFromRevision('v0.0.0'), commitLog);
});


================================================
FILE: test/git-util/default-branch.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/integration-test.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js');

test('main', createFixture, async ({$$}) => {
	await $$`git checkout -B main`;
}, async ({t, testedModule: {defaultBranch}}) => {
	t.is(await defaultBranch(), 'main');
});

test('master', createFixture, async ({$$}) => {
	await $$`git checkout -B master`;
	await $$`git update-ref -d refs/heads/main`;
}, async ({t, testedModule: {defaultBranch}}) => {
	t.is(await defaultBranch(), 'master');
});

test('gh-pages', createFixture, async ({$$}) => {
	await $$`git checkout -B gh-pages`;
	await $$`git update-ref -d refs/heads/main`;
	await $$`git update-ref -d refs/heads/master`;
}, async ({t, testedModule: {defaultBranch}}) => {
	t.is(await defaultBranch(), 'gh-pages');
});

test('fails', createFixture, async ({$$}) => {
	await $$`git checkout -B unicorn`;
	await $$`git update-ref -d refs/heads/main`;
	await $$`git update-ref -d refs/heads/master`;
}, async ({t, testedModule: {defaultBranch}}) => {
	await t.throwsAsync(
		defaultBranch(),
		{message: 'Could not infer the default Git branch. Please specify one with the --branch flag or with a np config.'},
	);
});


================================================
FILE: test/git-util/delete-tag.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/integration-test.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js');

test('deletes given tag', createFixture, async ({$$}) => {
	await $$`git tag v0.0.0`;
	await $$`git tag v1.0.0`;
}, async ({t, testedModule: {deleteTag}, $$}) => {
	await deleteTag('v1.0.0');
	const {stdout: tags} = await $$`git tag`;

	t.is(tags, 'v0.0.0');
});

test('deletes given tag from a large list', createFixture, async ({$$}) => {
	await $$`git tag v0.0.0`;
	await $$`git tag v1.0.0`;
	await $$`git tag v2.0.0`;
	await $$`git tag v3.0.0`;
	await $$`git tag v4.0.0`;
}, async ({t, testedModule: {deleteTag}, $$}) => {
	await deleteTag('v2.0.0');
	const {stdout: tags} = await $$`git tag`;

	t.deepEqual(
		tags.split('\n'),
		['v0.0.0', 'v1.0.0', 'v3.0.0', 'v4.0.0'],
	);
});

test('throws if tag not found', createFixture, async () => {
	//
}, async ({t, testedModule: {deleteTag}}) => {
	await t.throwsAsync(
		deleteTag('v1.0.0'),
		{message: /error: tag 'v1\.0\.0' not found\./},
	);
});


================================================
FILE: test/git-util/get-current-branch.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/integration-test.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js');

test('returns current branch', createFixture, async ({$$}) => {
	await $$`git switch -c unicorn`;
}, async ({t, testedModule: {getCurrentBranch}}) => {
	const currentBranch = await getCurrentBranch();
	t.is(currentBranch, 'unicorn');
});


================================================
FILE: test/git-util/has-upstream.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/integration-test.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js');

test('no upstream', createFixture, async () => {
	//
}, async ({t, testedModule: {hasUpstream}}) => {
	t.false(await hasUpstream());
});


================================================
FILE: test/git-util/is-head-detached.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/integration-test.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js');

test('not detached', createFixture, async () => {
	//
}, async ({t, testedModule: {isHeadDetached}}) => {
	t.false(await isHeadDetached());
});

test('detached', createFixture, async ({$$}) => {
	const {stdout: firstCommitSha} = await $$`git rev-list --max-parents=0 HEAD`;
	await $$`git checkout ${firstCommitSha}`;
}, async ({t, testedModule: {isHeadDetached}}) => {
	t.true(await isHeadDetached());
});


================================================
FILE: test/git-util/latest-tag-or-first-commit.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/integration-test.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js');

// From https://stackoverflow.com/a/3357357/10292952
const getCommitMessage = async ($$, sha) => $$`git log --format=%B -n 1 ${sha}`;

test('one tag', createFixture, async ({$$}) => {
	await $$`git tag v0.0.0`;
}, async ({t, testedModule: {latestTagOrFirstCommit}}) => {
	const result = await latestTagOrFirstCommit();
	t.is(result, 'v0.0.0');
});

test('two tags', createFixture, async ({t, $$}) => {
	await $$`git tag v0.0.0`;
	await t.context.commitNewFile();
	await $$`git tag v1.0.0`;
}, async ({t, testedModule: {latestTagOrFirstCommit}}) => {
	const result = await latestTagOrFirstCommit();
	t.is(result, 'v1.0.0');
});

test('no tags (fallback)', createFixture, async () => {
	//
}, async ({t, testedModule: {latestTagOrFirstCommit}, $$}) => {
	const result = await latestTagOrFirstCommit();
	const {stdout: firstCommitMessage} = await getCommitMessage($$, result);

	t.is(firstCommitMessage.trim(), '"init1"');
});


================================================
FILE: test/git-util/latest-tag.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/integration-test.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js');

test('returns latest tag', createFixture, async ({$$}) => {
	await $$`git tag v0.0.0`;
}, async ({t, testedModule: {latestTag}}) => {
	t.is(await latestTag(), 'v0.0.0');
});

test('returns latest tag - multiple set', createFixture, async ({t, $$}) => {
	await $$`git tag v0.0.0`;
	/* eslint-disable no-await-in-loop */
	for (const major of [1, 2, 3, 4]) {
		await t.context.commitNewFile();
		await $$`git tag v${major}.0.0`;
	}
	/* eslint-enable no-await-in-loop */
}, async ({t, testedModule: {latestTag}}) => {
	t.is(await latestTag(), 'v4.0.0');
});


================================================
FILE: test/git-util/multiple-initial-commits.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/integration-test.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js');

test('repository with multiple initial commits', createFixture, async ({t, $$}) => {
	// Get the current branch name before creating orphan branch
	const {stdout: initialBranch} = await $$`git branch --show-current`;

	// Create a second orphan branch to simulate multiple initial commits
	await $$`git checkout --orphan other-branch`;
	await t.context.createFile('other-file');
	await $$`git add other-file`;
	await $$`git commit -m orphan`;

	// Merge the orphan branch into the initial branch, creating multiple root commits
	await $$`git checkout ${initialBranch}`;
	await $$`git merge --allow-unrelated-histories other-branch -m merge`;
}, async ({t, testedModule: {latestTagOrFirstCommit, commitLogFromRevision}}) => {
	// This should not throw an error even with multiple initial commits
	const result = await latestTagOrFirstCommit();

	// Verify result is a valid commit hash (single line)
	t.false(result.includes('\n'), 'Result should be a single commit hash');
	t.is(result.length, 40, 'Result should be a 40-character SHA-1 hash');

	// This was the operation that failed in the original issue
	await t.notThrowsAsync(async () => {
		await commitLogFromRevision(result);
	}, 'commitLogFromRevision should work with the returned first commit');
});


================================================
FILE: test/git-util/new-files-since-last-release.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/integration-test.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js');

test('returns files added since latest tag', createFixture, async ({t, $$}) => {
	await $$`git tag v0.0.0`;
	await t.context.createFile('new');
	await t.context.createFile('index.js');
	await $$`git add .`;
	await $$`git commit -m "added"`;
}, async ({t, testedModule: {newFilesSinceLastRelease}, temporaryDirectory}) => {
	const newFiles = await newFilesSinceLastRelease(temporaryDirectory);
	t.deepEqual(
		newFiles.sort(),
		['new', 'index.js'].sort(),
	);
});

test('no files', createFixture, async ({$$}) => {
	await $$`git tag v0.0.0`;
}, async ({t, testedModule: {newFilesSinceLastRelease}, temporaryDirectory}) => {
	const newFiles = await newFilesSinceLastRelease(temporaryDirectory);
	t.deepEqual(newFiles, []);
});

test('uses ignoreWalker', createFixture, async ({t}) => {
	await t.context.createFile('index.js');
	await t.context.createFile('package.json');
	await t.context.createFile('package-lock.json');
	await t.context.createFile('.gitignore', 'package-lock.json\n.git'); // ignoreWalker doesn't ignore `.git`: npm/ignore-walk#2
}, async ({t, testedModule: {newFilesSinceLastRelease}, temporaryDirectory}) => {
	const newFiles = await newFilesSinceLastRelease(temporaryDirectory);
	t.deepEqual(
		newFiles.sort(),
		['index.js', 'package.json', '.gitignore'].sort(),
	);
});


================================================
FILE: test/git-util/previous-tag-or-first-commit.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/integration-test.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js');

test('no tags', createFixture, () => {
	//
}, async ({t, testedModule: {previousTagOrFirstCommit}}) => {
	const result = await previousTagOrFirstCommit();
	t.is(result, undefined);
});

test('one tag - fallback to first commit', createFixture, async ({$$}) => {
	await $$`git tag v0.0.0`;
}, async ({t, testedModule: {previousTagOrFirstCommit}}) => {
	const result = await previousTagOrFirstCommit();
	const commitMessage = await t.context.getCommitMessage(result);

	t.is(commitMessage, t.context.firstCommitMessage);
});

test('two tags', createFixture, async ({t, $$}) => {
	await $$`git tag v0.0.0`;
	await t.context.commitNewFile();
	await $$`git tag v1.0.0`;
}, async ({t, testedModule: {previousTagOrFirstCommit}}) => {
	const result = await previousTagOrFirstCommit();
	t.is(result, 'v0.0.0');
});

test('multiple tags', createFixture, async ({t, $$}) => {
	await $$`git tag v0.0.0`;
	/* eslint-disable no-await-in-loop */
	for (const major of [1, 2, 3, 4]) {
		await t.context.commitNewFile();
		await $$`git tag v${major}.0.0`;
	}
	/* eslint-enable no-await-in-loop */
}, async ({t, testedModule: {previousTagOrFirstCommit}}) => {
	const result = await previousTagOrFirstCommit();
	t.is(result, 'v3.0.0');
});

test('tags created out of order - should sort by semver not creation date', createFixture, async ({t, $$}) => {
	// Create tags out of semver order (simulating a hotfix scenario)
	await $$`git tag v1.0.0`;
	await t.context.commitNewFile();
	await $$`git tag v1.2.0`;
	await t.context.commitNewFile();
	await $$`git tag v1.2.1`;
	await t.context.commitNewFile();
	// Create a hotfix tag for an older version (created after v1.2.1 but semver is lower)
	await $$`git tag v1.0.1`;
	await t.context.commitNewFile();
	await $$`git tag v1.2.2`;
}, async ({t, testedModule: {previousTagOrFirstCommit}}) => {
	// Should return v1.2.1 (semver previous), not v1.0.1 (creation date previous)
	const result = await previousTagOrFirstCommit();
	t.is(result, 'v1.2.1');
});

test.todo('test fallback case');


================================================
FILE: test/git-util/push-graceful.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/stub-execa.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js', import.meta.url);

test('succeeds', createFixture, [{
	command: 'git push --follow-tags',
	exitCode: 0,
}], async ({t, testedModule: {pushGraceful}}) => {
	await t.notThrowsAsync(pushGraceful());
});

test('fails w/ remote on GitHub and bad branch permission', createFixture, [
	{
		command: 'git push --follow-tags',
		stderr: 'GH006',
	},
	{
		command: 'git push --tags',
		exitCode: 0,
	},
], async ({t, testedModule: {pushGraceful}}) => {
	const {pushed, reason} = await pushGraceful(true);

	t.is(pushed, 'tags');
	t.is(reason, 'Branch protection: np can`t push the commits. Push them manually.');
});

test('throws', createFixture, [{
	command: 'git push --follow-tags',
	exitCode: 1,
}], async ({t, testedModule: {pushGraceful}}) => {
	await t.throwsAsync(pushGraceful(false));
});

test('pushes to custom remote', createFixture, [{
	command: 'git push upstream --follow-tags',
	exitCode: 0,
}], async ({t, testedModule: {pushGraceful}}) => {
	await t.notThrowsAsync(pushGraceful(false, 'upstream'));
});

test('throws with custom remote', createFixture, [{
	command: 'git push upstream --follow-tags',
	exitCode: 1,
}], async ({t, testedModule: {pushGraceful}}) => {
	await t.throwsAsync(pushGraceful(false, 'upstream'));
});

test('pushes tags only to custom remote on branch protection error', createFixture, [
	{
		command: 'git push upstream --follow-tags',
		stderr: 'GH006',
	},
	{
		command: 'git push upstream --tags',
		exitCode: 0,
	},
], async ({t, testedModule: {pushGraceful}}) => {
	const {pushed, reason} = await pushGraceful(true, 'upstream');

	t.is(pushed, 'tags');
	t.is(reason, 'Branch protection: np can`t push the commits. Push them manually.');
});



================================================
FILE: test/git-util/read-file-from-last-release.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/integration-test.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js');

test('returns content of a given file', createFixture, async ({t, $$}) => {
	await t.context.createFile('unicorn.txt', 'unicorn-1');
	await $$`git add .`;
	await $$`git commit -m "added"`;
	await $$`git tag v0.0.0`;
	await t.context.createFile('unicorn.txt', 'unicorn-2');
	await $$`git add .`;
	await $$`git commit -m "added"`;
}, async ({t, testedModule: {readFileFromLastRelease}}) => {
	const file = await readFileFromLastRelease('unicorn.txt');
	t.is(file, 'unicorn-1');
});

test('fails if file not in previous release', createFixture, async ({t, $$}) => {
	await $$`git tag v0.0.0`;
	await t.context.createFile('unicorn.txt', 'unicorn');
	await $$`git add .`;
	await $$`git commit -m "added"`;
}, async ({t, testedModule: {readFileFromLastRelease}}) => {
	await t.throwsAsync(
		readFileFromLastRelease('unicorn.txt'),
		{message: /fatal: path '[^']*' exists on disk, but not in 'v0\.0\.0'/},
	);
});

test('no previous release', createFixture, async ({t, $$}) => {
	await t.context.createFile('unicorn.txt', 'unicorn');
	await $$`git add .`;
	await $$`git commit -m "added"`;
}, async ({t, testedModule: {readFileFromLastRelease}}) => {
	await t.throwsAsync(
		readFileFromLastRelease('unicorn.txt'),
		{message: /fatal: No names found, cannot describe anything./},
	);
});

// These errors could probably be handled in 'readFileFromLastRelease'


================================================
FILE: test/git-util/remove-last-commit.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/integration-test.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js');

test('removes latest commit', createFixture, async ({t, $$}) => {
	await t.context.createFile('index.js');
	await $$`git add -A`;
	await $$`git commit -m "added"`;
}, async ({t, testedModule: {removeLastCommit}, $$}) => {
	const {stdout: commitsBefore} = await $$`git log --pretty="%s"`;
	t.true(commitsBefore.includes('"added"'));

	await removeLastCommit();

	const {stdout: commitsAfter} = await $$`git log --pretty="%s"`;
	t.false(commitsAfter.includes('"added"'));
});


================================================
FILE: test/git-util/root.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/integration-test.js';
import {npRootDirectory} from '../../source/util.js';
import {root} from '../../source/git-util.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js');

test('returns np root dir', async t => {
	t.is(await root(), npRootDirectory);
});

test('returns root dir of temp dir', createFixture, () => {
	//
}, async ({t, testedModule: git, temporaryDirectory}) => {
	t.is(await git.root(), temporaryDirectory);
});


================================================
FILE: test/git-util/verify-current-branch-is-release-branch.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/integration-test.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js');

test('on release branch', createFixture, async ({$$}) => {
	await $$`git switch -c unicorn`;
}, async ({t, testedModule: {verifyCurrentBranchIsReleaseBranch}}) => {
	await t.notThrowsAsync(verifyCurrentBranchIsReleaseBranch('unicorn'));
});

test('not on release branch', createFixture, async ({$$}) => {
	await $$`git switch -c unicorn`;
}, async ({t, testedModule: {verifyCurrentBranchIsReleaseBranch}}) => {
	await t.throwsAsync(
		verifyCurrentBranchIsReleaseBranch('main'),
		{message: 'Not on `main` branch. Use --any-branch to publish anyway, or set a different release branch using --branch.'},
	);
});


================================================
FILE: test/git-util/verify-recent-git-version.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/stub-execa.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js', import.meta.url);

test('satisfied', createFixture, [{
	command: 'git version',
	stdout: 'git version 2.12.0', // One higher than minimum
}], async ({t, testedModule: {verifyRecentGitVersion}}) => {
	await t.notThrowsAsync(verifyRecentGitVersion());
});

test('not satisfied', createFixture, [{
	command: 'git version',
	stdout: 'git version 2.10.0', // One lower than minimum
}], async ({t, testedModule: {verifyRecentGitVersion}}) => {
	await t.throwsAsync(
		verifyRecentGitVersion(),
		{message: '`np` requires git >=2.11.0'},
	);
});


================================================
FILE: test/git-util/verify-remote-history-is-clean.js
================================================
import test from 'ava';
import {_createFixture as _createStubFixture} from '../_helpers/stub-execa.js';
import {_createFixture as _createIntegrationFixture} from '../_helpers/integration-test.js';

/** @type {ReturnType<typeof _createStubFixture<import('../../source/git-util.js')>>} */
const createStubFixture = _createStubFixture('../../source/git-util.js', import.meta.url);

/** @type {ReturnType<typeof _createIntegrationFixture<import('../../source/git-util.js')>>} */
const createIntegrationFixture = _createIntegrationFixture('../../source/git-util.js');

test('unfetched changes', createStubFixture, [
	{
		command: 'git rev-parse @{u}',
		exitCode: 0,
	},
	{
		command: 'git fetch --dry-run',
		stdout: 'From https://github.com/sindresorhus/np', // Has unfetched changes
	},
], async ({t, testedModule: {verifyRemoteHistoryIsClean}}) => {
	await t.throwsAsync(
		verifyRemoteHistoryIsClean(),
		{message: 'Remote history differs. Please run `git fetch` and pull changes.'},
	);
});

test('unclean remote history', createStubFixture, [
	{
		command: 'git rev-parse @{u}',
		exitCode: 0,
	},
	{
		command: 'git fetch --dry-run',
		exitCode: 0,
	},
	{
		command: 'git rev-list --count --left-only @{u}...HEAD',
		stdout: '1', // Has unpulled changes
	},
], async ({t, testedModule: {verifyRemoteHistoryIsClean}}) => {
	await t.throwsAsync(
		verifyRemoteHistoryIsClean(),
		{message: 'Remote history differs. Please pull changes.'},
	);
});

test('clean fetched remote history', createStubFixture, [
	{
		command: 'git rev-parse @{u}',
		exitCode: 0,
	},
	{
		command: 'git fetch --dry-run',
		exitCode: 0,
	},
	{
		command: 'git rev-list --count --left-only @{u}...HEAD',
		stdout: '0', // No changes
	},
], async ({t, testedModule: {verifyRemoteHistoryIsClean}}) => {
	await t.notThrowsAsync(verifyRemoteHistoryIsClean());
});

test('no remote', createIntegrationFixture, async () => {
	//
}, async ({t, testedModule: {verifyRemoteHistoryIsClean}}) => {
	await t.notThrowsAsync(verifyRemoteHistoryIsClean());
});


================================================
FILE: test/git-util/verify-remote-is-valid.js
================================================
import test from 'ava';
import {_createFixture as _createStubFixture} from '../_helpers/stub-execa.js';
import {_createFixture as _createIntegrationFixture} from '../_helpers/integration-test.js';

/** @type {ReturnType<typeof _createStubFixture<import('../../source/git-util.js')>>} */
const createStubFixture = _createStubFixture('../../source/git-util.js', import.meta.url);

/** @type {ReturnType<typeof _createIntegrationFixture<import('../../source/git-util.js')>>} */
const createIntegrationFixture = _createIntegrationFixture('../../source/git-util.js');

test('has remote', createStubFixture, [{
	command: 'git ls-remote origin HEAD',
	exitCode: 0,
}], async ({t, testedModule: {verifyRemoteIsValid}}) => {
	await t.notThrowsAsync(verifyRemoteIsValid());
});

test('no remote', createIntegrationFixture, async () => {
	//
}, async ({t, testedModule: {verifyRemoteIsValid}}) => {
	await t.throwsAsync(
		verifyRemoteIsValid(),
		{message: /^Git fatal error:/m},
	);
});

test('has custom remote', createStubFixture, [{
	command: 'git ls-remote upstream HEAD',
	exitCode: 0,
}], async ({t, testedModule: {verifyRemoteIsValid}}) => {
	await t.notThrowsAsync(verifyRemoteIsValid('upstream'));
});


================================================
FILE: test/git-util/verify-tag-does-not-exist-on-remote.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/stub-execa.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js', import.meta.url);

test('exists', createFixture, [{
	command: 'git rev-parse --quiet --verify refs/tags/v0.0.0',
	stdout: '123456789', // Some hash
}], async ({t, testedModule: {verifyTagDoesNotExistOnRemote}}) => {
	await t.throwsAsync(
		verifyTagDoesNotExistOnRemote('v0.0.0'),
		{message: 'Git tag `v0.0.0` already exists.'},
	);
});

test('does not exist', createFixture, [{
	command: 'git rev-parse --quiet --verify refs/tags/v0.0.0',
	exitCode: 1,
	stderr: '',
	stdout: '',
}], async ({t, testedModule: {verifyTagDoesNotExistOnRemote}}) => {
	await t.notThrowsAsync(verifyTagDoesNotExistOnRemote('v0.0.0'));
});


================================================
FILE: test/git-util/verify-working-tree-is-clean.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/integration-test.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/git-util.js')>>} */
const createFixture = _createFixture('../../source/git-util.js');

test('clean', createFixture, async ({t, $$}) => {
	await t.context.createFile('index.js');
	await $$`git add .`;
	await $$`git commit -m "added"`;
}, async ({t, testedModule: {verifyWorkingTreeIsClean}}) => {
	await t.notThrowsAsync(verifyWorkingTreeIsClean());
});

test('not clean', createFixture, async ({t}) => {
	await t.context.createFile('index.js');
}, async ({t, testedModule: {verifyWorkingTreeIsClean}}) => {
	await t.throwsAsync(
		verifyWorkingTreeIsClean(),
		{message: 'Unclean working tree. Commit or stash changes first.'},
	);
});


================================================
FILE: test/index.js
================================================
import path from 'node:path';
import process from 'node:process';
import test from 'ava';
import sinon from 'sinon';
import esmock from 'esmock';
import {of, throwError} from 'rxjs';
import {npmConfig as packageManager} from '../source/package-manager/configs.js';
import * as util from '../source/util.js';

const defaultOptions = {
	cleanup: true,
	tests: true,
	publish: true,
	packageManager,
	runPublish: true,
	availability: {
		isAvailable: false,
		isUnknown: false,
	},
	renderer: 'silent',
};

const npPackageResult = await util.readPackage();

const getNpMock = async () => esmock('../source/index.js', {}, {
	execa: {execa: sinon.stub().resolves({stdout: '10.0.0', stderr: ''})},
	'../source/git-util.js': {
		hasUpstream: sinon.stub().returns(true),
		pushGraceful: sinon.stub(),
		verifyWorkingTreeIsClean: sinon.stub(),
		verifyCurrentBranchIsReleaseBranch: sinon.stub(),
		verifyRemoteHistoryIsClean: sinon.stub(),
		verifyRemoteIsValid: sinon.stub(),
		verifyRecentGitVersion: sinon.stub(),
		fetch: sinon.stub(),
		verifyTagDoesNotExistOnRemote: sinon.stub(),
	},
	'../source/npm/util.js': {
		...await import('../source/npm/util.js'),
		checkConnection: sinon.stub().resolves(),
	},
});

const npFails = test.macro(async (t, inputs, message) => {
	const npMock = await getNpMock();
	await t.throwsAsync(
		Promise.all(inputs.map(input => npMock(input, defaultOptions, npPackageResult))),
		{message},
	);
});

test('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\./);

test('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');

test('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+/);

const fakeExecaReturn = () => Object.assign(
	Promise.resolve({pipe: sinon.stub()}),
	{stdout: '', stderr: ''},
);

const fakeObservableReturn = () => of('');

const fakeObservableReject = error => throwError(() => Object.assign(new Error(error), {stdout: '', stderr: error}));

test('skip enabling 2FA if the package exists', async t => {
	const enable2faStub = sinon.stub();

	/** @type {typeof np} */
	const npMock = await esmock('../source/index.js', {
		del: {deleteAsync: sinon.stub()},
		execa: {execa: sinon.stub().returns(fakeExecaReturn())},
		'../source/prerequisite-tasks.js': sinon.stub(),
		'../source/git-tasks.js': sinon.stub(),
		'../source/git-util.js': {
			hasUpstream: sinon.stub().returns(true),
			pushGraceful: sinon.stub(),
			verifyWorkingTreeIsClean: sinon.stub(),
		},
		'../source/npm/enable-2fa.js': enable2faStub,
		'../source/npm/publish.js': {
			getPackagePublishArguments: sinon.stub().returns([]),
			runPublish: sinon.stub().returns(fakeObservableReturn()),
		},
	});

	await t.notThrowsAsync(npMock('1.0.0', {
		...defaultOptions,
		availability: {
			isAvailable: false,
			isUnknown: false,
		},
	}, npPackageResult));

	t.true(enable2faStub.notCalled);
});

test('skip enabling 2FA if the `2fa` option is false', async t => {
	const enable2faStub = sinon.stub();

	/** @type {typeof np} */
	const npMock = await esmock('../source/index.js', {
		del: {deleteAsync: sinon.stub()},
		execa: {execa: sinon.stub().returns(fakeExecaReturn())},
		'../source/prerequisite-tasks.js': sinon.stub(),
		'../source/git-tasks.js': sinon.stub(),
		'../source/git-util.js': {
			hasUpstream: sinon.stub().returns(true),
			pushGraceful: sinon.stub(),
			verifyWorkingTreeIsClean: sinon.stub(),
		},
		'../source/npm/enable-2fa.js': enable2faStub,
		'../source/npm/publish.js': {
			getPackagePublishArguments: sinon.stub().returns([]),
			runPublish: sinon.stub().returns(fakeObservableReturn()),
		},
	});

	await t.notThrowsAsync(npMock('1.0.0', {
		...defaultOptions,
		availability: {
			isAvailable: true,
			isUnknown: false,
		},
		'2fa': false,
	}, npPackageResult));

	t.true(enable2faStub.notCalled);
});

test('skip enabling 2FA in trusted publishing (OIDC) contexts', async t => {
	const enable2faStub = sinon.stub();

	/** @type {typeof np} */
	const npMock = await esmock('../source/index.js', {
		del: {deleteAsync: sinon.stub()},
		execa: {execa: sinon.stub().returns(fakeExecaReturn())},
		'../source/prerequisite-tasks.js': sinon.stub(),
		'../source/git-tasks.js': sinon.stub(),
		'../source/git-util.js': {
			hasUpstream: sinon.stub().returns(true),
			pushGraceful: sinon.stub(),
			verifyWorkingTreeIsClean: sinon.stub(),
		},
		'../source/npm/enable-2fa.js': enable2faStub,
		'../source/npm/publish.js': {
			getPackagePublishArguments: sinon.stub().returns([]),
			runPublish: sinon.stub().returns(fakeObservableReturn()),
		},
		'../source/npm/oidc.js': {
			getOidcProvider: () => 'github',
		},
	});

	await t.notThrowsAsync(npMock('1.0.0', {
		...defaultOptions,
		availability: {
			isAvailable: true,
			isUnknown: false,
		},
		'2fa': true,
	}, npPackageResult));

	t.true(enable2faStub.notCalled);
});

test('rollback is called when publish fails', async t => {
	const deleteTagStub = sinon.stub().resolves();
	const removeLastCommitStub = sinon.stub().resolves();

	/** @type {typeof np} */
	const npMock = await esmock('../source/index.js', {
		del: {deleteAsync: sinon.stub()},
		execa: {execa: sinon.stub().returns(fakeExecaReturn())},
		'../source/prerequisite-tasks.js': sinon.stub(),
		'../source/git-tasks.js': sinon.stub(),
		'../source/git-util.js': {
			hasUpstream: sinon.stub().returns(true),
			pushGraceful: sinon.stub(),
			verifyWorkingTreeIsClean: sinon.stub(),
			latestTag: sinon.stub().resolves('v1.0.0'),
			deleteTag: deleteTagStub,
			removeLastCommit: removeLastCommitStub,
		},
		'../source/npm/enable-2fa.js': sinon.stub(),
		'../source/npm/publish.js': {
			getPackagePublishArguments: sinon.stub().returns([]),
			runPublish: sinon.stub().returns(fakeObservableReject('npm ERR! publish failed')),
		},
		'../source/util.js': {
			...util,
			readPackage: sinon.stub().resolves({version: '1.0.0'}),
			getTagVersionPrefix: sinon.stub().resolves('v'),
		},
	});

	await t.throwsAsync(
		npMock('1.0.0', {
			...defaultOptions,
		}, {package_: {version: '0.9.0'}, rootDirectory: process.cwd()}),
		{message: /Error publishing package/},
	);

	t.true(deleteTagStub.calledOnce, 'deleteTag should be called once');
	t.true(removeLastCommitStub.calledOnce, 'removeLastCommit should be called once');
});

test('publish uses rootDirectory from context as cwd', async t => {
	const contentsDirectory = path.resolve('dist');
	let publishCwd;

	/** @type {typeof np} */
	const npMock = await esmock('../source/index.js', {
		del: {deleteAsync: sinon.stub()},
		execa: {execa: sinon.stub().returns(fakeExecaReturn())},
		'../source/prerequisite-tasks.js': sinon.stub(),
		'../source/git-tasks.js': sinon.stub(),
		'../source/git-util.js': {
			hasUpstream: sinon.stub().returns(true),
			pushGraceful: sinon.stub(),
			verifyWorkingTreeIsClean: sinon.stub(),
		},
		'../source/npm/enable-2fa.js': sinon.stub(),
		'../source/npm/publish.js': {
			getPackagePublishArguments: sinon.stub().returns([]),
			runPublish: sinon.stub().callsFake((_arguments, options) => {
				publishCwd = options?.cwd;
				return fakeObservableReturn();
			}),
		},
	});

	await npMock('1.0.0', defaultOptions, {package_: npPackageResult.package_, rootDirectory: contentsDirectory});

	t.is(publishCwd, contentsDirectory, 'publish should use rootDirectory from context as cwd');
});


================================================
FILE: test/npm/enable-2fa.js
================================================
import test from 'ava';
import {_createFixture} from '../_helpers/stub-execa.js';

/** @type {ReturnType<typeof _createFixture<import('../../source/npm/enable-2fa.js')>>} */
const createFixture = _createFixture('../../source/npm/enable-2fa.js', import.meta.url);

const npmVersionFixtures = [
	{version: '8.0.0', accessArgs: ['access', '2fa-required']},
	{version: '9.0.0', accessArgs: ['access', 'set', 'mfa=publish']},
];

for (const {version, accessArgs} of npmVersionFixtures) {
	const npmVersionCommand = [{
		command: 'npm --version',
		stdout: version,
	}];

	test(`npm v${version} - no options`, createFixture, npmVersionCommand, async ({t, testedModule: {getEnable2faArguments}}) => {
		t.deepEqual(
			await getEnable2faArguments('np'),
			[...accessArgs, 'np'],
		);
	});

	test(`npm v${version} - options, no otp`, createFixture, npmVersionCommand, async ({t, testedModule: {getEnable2faArguments}}) => {
		t.deepEqual(
			await getEnable2faArguments('np', {confirm: true}),
			[...accessArgs, 'np'],
		);
	});

	test(`npm v${version} - options, with otp`, createFixture, npmVersionCommand, async ({t, testedModule: {getEnable2faArguments}}) => {
		t.deepEqual(
			await getEnable2faArguments('np', {otp: '123456'}),
			[...accessArgs, 'np', '--otp', '123456'],
		);
	});
}



================================================
FILE: test/npm/handle-npm-error.js
================================================
import test from 'ava';
import handleNpmError from '../../source/npm/handle-npm-error.js';

const makeError = ({code, stdout, stderr}) => ({
	code,
	stdout: stdout ?? '',
	stderr: stderr ?? '',
});

test('error code 402 - privately publish scoped package', t => {
	t.throws(
		() => handleNpmError(makeError({code: 402})),
		{message: 'You cannot publish a scoped package privately without a paid plan. Did you mean to publish publicly?'},
	);

	t.throws(
		() => handleNpmError(makeError({stderr: 'npm ERR! 402 Payment Required'})),
		{message: 'You cannot publish a scoped package privately without a paid plan. Did you mean to publish publicly?'},
	);
});


================================================
FILE: test/npm/oidc.js
================================================
import test from 'ava';
import esmock from 'esmock';

test('detects GitHub Actions', async t => {
	const {getOidcProvider} = await esmock('../../source/npm/oidc.js', {
		'node:process': {
			env: {
				GITHUB_ACTIONS: 'true',
				ACTIONS_ID_TOKEN_REQUEST_URL: 'https://example.com',
				ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',
			},
		},
	});

	t.is(getOidcProvider(), 'github');
});

test('detects GitLab CI', async t => {
	const {getOidcProvider} = await esmock('../../source/npm/oidc.js', {
		'node:process': {
			env: {
				GITLAB_CI: 'true',
				NPM_ID_TOKEN: 'token',
			},
		},
	});

	t.is(getOidcProvider(), 'gitlab');
});

test('detects no OIDC', async t => {
	const {getOidcProvider} = await esmock('../../source/npm/oidc.js', {
		'node:process': {
			env: {},
		},
	});

	t.is(getOidcProvider(), undefined);
});


================================================
FILE: test/npm/publish.js
================================================
import test from 'ava';
import {firstValueFrom} from 'rxjs';
import {getPackagePublishArguments, runPublish} from '../../source/npm/publish.js';

test('no options set', t => {
	t.deepEqual(
		getPackagePublishArguments({}),
		['publish'],
	);
});

test('options.tag', t => {
	t.deepEqual(
		getPackagePublishArguments({tag: 'beta'}),
		['publish', '--tag', 'beta'],
	);
});

test('options.otp', t => {
	t.deepEqual(
		getPackagePublishArguments({otp: '123456'}),
		['publish', '--otp', '123456'],
	);
});

test('options.publishScoped', t => {
	t.deepEqual(
		getPackagePublishArguments({publishScoped: true}),
		['publish', '--access', 'public'],
	);
});

test('options.provenance', t => {
	t.deepEqual(
		getPackagePublishArguments({provenance: true}),
		['publish', '--provenance'],
	);
});

test('runPublish uses cwd option when provided', async t => {
	const observable = runPublish(['echo', ['test']], {cwd: '/tmp'});
	// Should complete successfully
	await t.notThrowsAsync(firstValueFrom(observable));
});

test('runPublish returns an Observable that completes successfully', async t => {
	const observable = runPublish(['echo', ['test']]);
	t.not(observable, undefined);
	// Process should complete successfully with our default options
	await t.notThrowsAsync(firstValueFrom(observable));
});


================================================
FILE: test/npm/util/check-connection.js
================================================
import test from 'ava';
import {_createFixture} from '../../_helpers/stub-execa.js';

/** @type {ReturnType<typeof _createFixture<import('../../../source/npm/util.js')>>} */
const createFixture = _createFixture('../../../source/npm/util.js', import.meta.url);

test('success', createFixture, [{
	command: 'npm ping',
	exitCode: 0,
	options: {timeout: 15_000},
}], async ({t, testedModule: npm}) => {
	t.true(await npm.checkConnection());
});

test('fail', createFixture, [{
	command: 'npm ping',
	exitCode: 1,
}], async ({t, testedModule: npm}) => {
	await t.throwsAsync(
		npm.checkConnection(),
		{message: 'Connection to npm registry failed'},
	);
});

test('timeout', createFixture, [{
	command: 'npm ping',
	exitCode: 1,
	timedOut: true,
}], async ({t, testedModule: npm}) => {
	await t.throwsAsync(
		npm.checkConnection(),
		{message: 'Connection to npm registry timed out'},
	);
});


================================================
FILE: test/npm/util/check-ignore-strategy.js
================================================
import path from 'node:path';
import test from 'ava';
import esmock from 'esmock';
import stripAnsi from 'strip-ansi';
import {oneLine} from 'common-tags';

const checkIgnoreStrategy = test.macro(async (t, {fixture = '', files, expected = ''} = {}) => {
	let output = '';

	/** @type {import('../../../source/npm/util.js')} */
	const {checkIgnoreStrategy} = await esmock('../../../source/npm/util.js', {
		import: {console: {log: (...arguments_) => output = arguments_.join('')}}, // eslint-disable-line no-return-assign
	});

	const fixtureDirectory = path.resolve('test/fixtures/files', fixture);
	const package_ = files ? {files} : {};

	await checkIgnoreStrategy(package_, fixtureDirectory);

	output = stripAnsi(output).trim();
	t.is(output, expected);
});

const ignoreStrategyMessage = oneLine`
	Warning: No files field specified in package.json nor is a .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.
`;

test('no files, no .npmignore', checkIgnoreStrategy, {fixture: 'main', expected: ignoreStrategyMessage});

test('no files w/ .npmignore', checkIgnoreStrategy, {fixture: 'npmignore', expected: ''});

test('files, no .npmignore', checkIgnoreStrategy, {fixture: 'main', files: ['index.js'], expected: ''});

test('files w/ .npmignore', checkIgnoreStrategy, {fixture: 'npmignore', files: ['index.js'], expected: ''});


================================================
FILE: test/npm/util/collaborators.js
================================================
import test from 'ava';
import {stripIndent} from 'common-tags';
import {_createFixture} from '../../_helpers/stub-execa.js';
import * as npm from '../../../source/npm/util.js';

/** @type {ReturnType<typeof _createFixture<import('../../../source/npm/util.js')>>} */
const createFixture = _createFixture('../../../source/npm/util.js', import.meta.url);

test('package.name not a string', async t => {
	await t.throwsAsync(
		npm.collaborators({name: 1}),
		{message: 'Package name is required'},
	);
});

const accessCommand = (name = 'np') => `npm access list collaborators ${name} --json`;

const collaboratorsStdout = stripIndent`
	{
		"sindresorhus": "read-write",
		"samverschueren": "read-write",
		"itaisteinherz": "read-write"
	}
`;

test('main', createFixture, [{
	command: accessCommand(),
	stdout: collaboratorsStdout,
}], async ({t, testedModule: {collaborators}}) => {
	t.deepEqual(
		await collaborators({name: 'np'}),
		collaboratorsStdout,
	);
});

// TODO: this is timing out, seemingly the command isn't matching for Sinon
// eslint-disable-next-line ava/no-skip-test
test.skip('external registry', createFixture, [{
	command: `${accessCommand()} --registry http://my-internal-registry.local`,
	stdout: collaboratorsStdout,
}], async ({t, testedModule: {collaborators}}) => {
	const output = await collaborators({
		name: 'np',
		publishConfig: {
			registry: 'http://my-internal-registry.local',
		},
	});

	t.deepEqual(
		JSON.parse(output),
		JSON.parse(collaboratorsStdout),
	);
});

test('non-existent', createFixture, [{
	command: accessCommand('non-existent'),
	stderr: 'npm ERR! code E404\nnpm ERR! 404 Not Found',
}], async ({t, testedModule: {collaborators}}) => {
	t.is(
		await collaborators({name: 'non-existent'}),
		false,
	);
});

test('error on default registry', createFixture, [{
	command: accessCommand('@private/pkg'),
	stderr: 'npm ERR! code E403\nnpm ERR! 403 403 Forbidden',
}], async ({t, testedModule: {collaborators}}) => {
	const {stderr} = await t.throwsAsync(collaborators({name: '@private/pkg'}));
	t.is(stderr, 'npm ERR! code E403\nnpm ERR! 403 403 Forbidden');
});

test('error on external registry - returns false', createFixture, [{
	command: `${accessCommand('@private/pkg')} --registry http://my-internal-registry.local`,
	stderr: 'npm ERR! code E403\nnpm ERR! 403 403 Forbidden',
}], async ({t, testedModule: {collaborators}}) => {
	// Errors should return false instead of throwing, since external registries
	// often don't support the collaborators endpoint.
	// See: https://github.com/sindresorhus/np/issues/420
	t.is(await collaborators({
		name: '@private/pkg',
		publishConfig: {
			registry: 'http://my-internal-registry.local',
		},
	}), false);
});


================================================
FILE: test/npm/util/entry-points.js
================================================
import path from 'node:path';
import test from 'ava';
import * as npm from '../../../source/npm/util.js';

const getFixture = name => path.resolve('test', 'fixtures', 'files', name);

test('getPackageEntryPoints - main', t => {
	t.deepEqual(
		npm.getPackageEntryPoints({main: 'index.js'}),
		[{field: 'main', path: 'index.js'}],
	);

	t.deepEqual(
		npm.getPackageEntryPoints({main: './dist/index.js'}),
		[{field: 'main', path: './dist/index.js'}],
	);
});

test('getPackageEntryPoints - bin as string', t => {
	t.deepEqual(
		npm.getPackageEntryPoints({name: 'my-cli', bin: './cli.js'}),
		[{field: 'bin', path: './cli.js'}],
	);
});

test('getPackageEntryPoints - bin as object', t => {
	t.deepEqual(
		npm.getPackageEntryPoints({bin: {foo: './bin/foo.js', bar: './bin/bar.js'}}),
		[
			{field: 'bin.foo', path: './bin/foo.js'},
			{field: 'bin.bar', path: './bin/bar.js'},
		],
	);
});

test('getPackageEntryPoints - exports as string', t => {
	t.deepEqual(
		npm.getPackageEntryPoints({exports: './index.js'}),
		[{field: 'exports', path: './index.js'}],
	);
});

test('getPackageEntryPoints - exports with subpaths', t => {
	const entryPoints = npm.getPackageEntryPoints({
		exports: {
			'.': './index.js',
			'./foo': './foo.js',
		},
	});

	t.deepEqual(entryPoints, [
		{field: 'exports', path: './index.js'},
		{field: 'exports', path: './foo.js'},
	]);
});

test('getPackageEntryPoints - exports with conditions', t => {
	const entryPoints = npm.getPackageEntryPoints({
		exports: {
			'.': {
				import: './index.mjs',
				require: './index.cjs',
			},
		},
	});

	t.deepEqual(entryPoints, [
		{field: 'exports', path: './index.mjs'},
		{field: 'exports', path: './index.cjs'},
	]);
});

test('getPackageEntryPoints - exports with nested conditions', t => {
	const entryPoints = npm.getPackageEntryPoints({
		exports: {
			'.': {
				node: {
					import: './index.node.mjs',
					require: './index.node.cjs',
				},
				default: './index.js',
			},
		},
	});

	t.deepEqual(entryPoints, [
		{field: 'exports', path: './index.node.mjs'},
		{field: 'exports', path: './index.node.cjs'},
		{field: 'exports', path: './index.js'},
	]);
});

test('getPackageEntryPoints - combined main, bin, and exports', t => {
	const entryPoints = npm.getPackageEntryPoints({
		main: './index.js',
		bin: './cli.js',
		exports: {
			'.': './index.js',
			'./cli': './cli.js',
		},
	});

	t.deepEqual(entryPoints, [
		{field: 'main', path: './index.js'},
		{field: 'bin', path: './cli.js'},
		{field: 'exports', path: './index.js'},
		{field: 'exports', path: './cli.js'},
	]);
});

test('getPackageEntryPoints - empty package', t => {
	t.deepEqual(npm.getPackageEntryPoints({}), []);
	t.deepEqual(npm.getPackageEntryPoints({name: 'foo', version: '1.0.0'}), []);
});

test('getPackageEntryPoints - exports with wildcard patterns are skipped', t => {
	const entryPoints = npm.getPackageEntryPoints({
		exports: {
			'.': './index.js',
			'./features/*.js': './src/features/*.js',
			'./utils/*': './src/utils/*',
		},
	});

	// Only non-wildcard exports should be included
	t.deepEqual(entryPoints, [
		{field: 'exports', path: './index.js'},
	]);
});

test('getPackageEntryPoints - exports with null values are skipped', t => {
	const entryPoints = npm.getPackageEntryPoints({
		exports: {
			'.': './index.js',
			'./internal/*': null,
		},
	});

	t.deepEqual(entryPoints, [
		{field: 'exports', path: './index.js'},
	]);
});

test('getPackageEntryPoints - invalid main values are skipped', t => {
	t.deepEqual(npm.getPackageEntryPoints({main: null}), []);
	t.deepEqual(npm.getPackageEntryPoints({main: 123}), []);
	t.deepEqual(npm.getPackageEntryPoints({main: {}}), []);
});

test('getPackageEntryPoints - invalid bin values are skipped', t => {
	t.deepEqual(npm.getPackageEntryPoints({bin: null}), []);
	t.deepEqual(npm.getPackageEntryPoints({bin: 123}), []);
	t.deepEqual(npm.getPackageEntryPoints({bin: {foo: null, bar: './bar.js'}}), [
		{field: 'bin.bar', path: './bar.js'},
	]);
});

test('getPackageEntryPoints - duplicate paths from main and exports', t => {
	const entryPoints = npm.getPackageEntryPoints({
		main: './index.js',
		exports: './index.js',
	});

	// Both are returned (deduplication happens in verifyPackageEntryPoints)
	t.deepEqual(entryPoints, [
		{field: 'main', path: './index.js'},
		{field: 'exports', path: './index.js'},
	]);
});

test('verifyPackageEntryPoints - missing main', async t => {
	const fixtureDirectory = getFixture('missing-main');

	await t.throwsAsync(
		npm.verifyPackageEntryPoints({main: 'dist/index.js'}, fixtureDirectory),
		{message: /Missing entry points.*"main": dist\/index\.js/s},
	);
});

test('verifyPackageEntryPoints - missing bin', async t => {
	const fixtureDirectory = getFixture('missing-bin');

	await t.throwsAsync(
		npm.verifyPackageEntryPoints({bin: './cli.js'}, fixtureDirectory),
		{message: /Missing entry points.*"bin": \.\/cli\.js/s},
	);
});

test('verifyPackageEntryPoints - valid entry points', async t => {
	const fixtureDirectory = getFixture('one-file');

	await t.notThrowsAsync(npm.verifyPackageEntryPoints({main: 'index.js'}, fixtureDirectory));
});


================================================
FILE: test/npm/util/is-external-registry.js
================================================
import test from 'ava';
import * as npm from '../../../source/npm/util.js';

test('main', t => {
	t.true(npm.isExternalRegistry({publishConfig: {registry: 'https://my-internal-registry.local'}}));

	t.false(npm.isExternalRegistry({name: 'foo'}));
	t.false(npm.isExternalRegistry({publishConfig: {registry: true}}));
	t.false(npm.isExternalRegistry({publishConfig: 'not an object'}));
	t.false(npm.isExternalRegistry({publishConfig: {registry: 'https://registry.npmjs.org'}}));
	t.false(npm.isExternalRegistry({publishConfig: {registry: 'https://registry.npmjs.org/'}}));

	// Test normalization: whitespace trimming
	t.false(npm.isExternalRegistry({publishConfig: {registry: ' https://registry.npmjs.org '}}));
	t.false(npm.isExternalRegistry({publishConfig: {registry: '	https://registry.npmjs.org/	'}}));

	// Test normalization: http variant
	t.false(npm.isExternalRegistry({publishConfig: {registry: 'http://registry.npmjs.org'}}));
	t.false(npm.isExternalRegistry({publishConfig: {registry: 'http://registry.npmjs.org/'}}));
});


================================================
FILE: test/npm/util/is-package-name-available.js
================================================
import test from 'ava';
import esmock from 'esmock';
import sinon from 'sinon';

const externalRegistry = 'http://my-internal-registry.local';

const createFixture = test.macro(async (t, {name = 'foo', npmNameStub, expected, isExternalRegistry = false}) => {
	/** @type {import('../../../source/npm/util.js')} */
	const npm = await esmock('../../../source/npm/util.js', {
		'npm-name': npmNameStub,
	});

	const package_ = isExternalRegistry
		? {name, publishConfig: {registry: externalRegistry}}
		: {name};

	const availability = await npm.isPackageNameAvailable(package_);
	t.like(availability, expected);
});

test('available', createFixture, {
	npmNameStub: sinon.stub().resolves(true),
	expected: {isAvailable: true, isUnknown: false},
});

test('unavailable', createFixture, {
	npmNameStub: sinon.stub().resolves(false),
	expected: {isAvailable: false, isUnknown: false},
});

test('bad package nam
Download .txt
gitextract_s7w_v3kb/

├── .editorconfig
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── pull_request_template.md
│   └── workflows/
│       └── main.yml
├── .gitignore
├── .npmrc
├── license
├── package.json
├── readme.md
├── source/
│   ├── .npmignore
│   ├── cli-implementation.js
│   ├── cli.js
│   ├── config.js
│   ├── git-tasks.js
│   ├── git-util.js
│   ├── index.js
│   ├── npm/
│   │   ├── enable-2fa.js
│   │   ├── handle-npm-error.js
│   │   ├── oidc.js
│   │   ├── publish.js
│   │   └── util.js
│   ├── package-manager/
│   │   ├── configs.js
│   │   ├── index.js
│   │   └── types.d.ts
│   ├── prerequisite-tasks.js
│   ├── release-task-helper.js
│   ├── ui.js
│   ├── util.js
│   └── version.js
└── test/
    ├── _helpers/
    │   ├── integration-test.d.ts
    │   ├── integration-test.js
    │   ├── listr-renderer.js
    │   ├── listr.js
    │   ├── mock-inquirer.js
    │   ├── stub-execa.d.ts
    │   ├── stub-execa.js
    │   ├── util.js
    │   ├── verify-cli.d.ts
    │   └── verify-cli.js
    ├── cli.js
    ├── config.js
    ├── fixtures/
    │   ├── config/
    │   │   ├── contents-option/
    │   │   │   ├── .np-config.json
    │   │   │   └── dist/
    │   │   │       └── package.json
    │   │   ├── flag-precedence/
    │   │   │   ├── .np-config.json
    │   │   │   └── package.json
    │   │   ├── homedir1/
    │   │   │   └── .np-config.json
    │   │   ├── homedir2/
    │   │   │   └── .np-config.js
    │   │   ├── homedir3/
    │   │   │   └── .np-config.cjs
    │   │   ├── homedir4/
    │   │   │   └── .np-config.js
    │   │   ├── homedir5/
    │   │   │   └── .np-config.mjs
    │   │   ├── local1/
    │   │   │   └── .np-config.json
    │   │   ├── local2/
    │   │   │   └── .np-config.js
    │   │   ├── local3/
    │   │   │   └── .np-config.cjs
    │   │   ├── local4/
    │   │   │   ├── .np-config.js
    │   │   │   └── package.json
    │   │   ├── local5/
    │   │   │   └── .np-config.mjs
    │   │   ├── package.json
    │   │   └── pkg-dir/
    │   │       └── package.json
    │   ├── files/
    │   │   ├── dot-github/
    │   │   │   ├── .github/
    │   │   │   │   └── pull_request_template.md
    │   │   │   ├── index.js
    │   │   │   └── package.json
    │   │   ├── files-and-npmignore/
    │   │   │   ├── package.json
    │   │   │   ├── readme.md
    │   │   │   └── source/
    │   │   │       ├── .npmignore
    │   │   │       ├── bar.js
    │   │   │       ├── foo.js
    │   │   │       ├── index.d.ts
    │   │   │       └── index.test-d.ts
    │   │   ├── files-slash/
    │   │   │   ├── index.js
    │   │   │   └── package.json
    │   │   ├── gitignore/
    │   │   │   ├── .gitignore
    │   │   │   ├── index.d.ts
    │   │   │   ├── index.js
    │   │   │   ├── index.test-d.ts
    │   │   │   ├── package.json
    │   │   │   └── readme.md
    │   │   ├── has-readme-and-license/
    │   │   │   ├── index.js
    │   │   │   ├── license.md
    │   │   │   ├── package.json
    │   │   │   └── readme.md
    │   │   ├── main/
    │   │   │   ├── bar.js
    │   │   │   ├── foo.js
    │   │   │   └── package.json
    │   │   ├── missing-bin/
    │   │   │   ├── index.js
    │   │   │   └── package.json
    │   │   ├── missing-main/
    │   │   │   ├── package.json
    │   │   │   └── source/
    │   │   │       └── index.js
    │   │   ├── npmignore/
    │   │   │   ├── .npmignore
    │   │   │   ├── index.d.ts
    │   │   │   ├── index.js
    │   │   │   ├── index.test-d.ts
    │   │   │   ├── package.json
    │   │   │   └── readme.md
    │   │   ├── npmignore-and-gitignore/
    │   │   │   ├── .gitignore
    │   │   │   ├── .npmignore
    │   │   │   ├── package.json
    │   │   │   ├── readme.md
    │   │   │   ├── script/
    │   │   │   │   └── build.js
    │   │   │   └── source/
    │   │   │       └── index.ts
    │   │   ├── one-file/
    │   │   │   ├── index.js
    │   │   │   └── package.json
    │   │   ├── prepare-script/
    │   │   │   ├── index.js
    │   │   │   └── package.json
    │   │   ├── source-and-dist-dir/
    │   │   │   ├── dist/
    │   │   │   │   └── index.js
    │   │   │   ├── package.json
    │   │   │   └── source/
    │   │   │       ├── bar.js
    │   │   │       └── foo.js
    │   │   └── source-dir/
    │   │       ├── package.json
    │   │       └── source/
    │   │           ├── bar.js
    │   │           └── foo.js
    │   └── readme.md
    ├── git-util/
    │   ├── commit-log-from-revision.js
    │   ├── default-branch.js
    │   ├── delete-tag.js
    │   ├── get-current-branch.js
    │   ├── has-upstream.js
    │   ├── is-head-detached.js
    │   ├── latest-tag-or-first-commit.js
    │   ├── latest-tag.js
    │   ├── multiple-initial-commits.js
    │   ├── new-files-since-last-release.js
    │   ├── previous-tag-or-first-commit.js
    │   ├── push-graceful.js
    │   ├── read-file-from-last-release.js
    │   ├── remove-last-commit.js
    │   ├── root.js
    │   ├── verify-current-branch-is-release-branch.js
    │   ├── verify-recent-git-version.js
    │   ├── verify-remote-history-is-clean.js
    │   ├── verify-remote-is-valid.js
    │   ├── verify-tag-does-not-exist-on-remote.js
    │   └── verify-working-tree-is-clean.js
    ├── index.js
    ├── npm/
    │   ├── enable-2fa.js
    │   ├── handle-npm-error.js
    │   ├── oidc.js
    │   ├── publish.js
    │   └── util/
    │       ├── check-connection.js
    │       ├── check-ignore-strategy.js
    │       ├── collaborators.js
    │       ├── entry-points.js
    │       ├── is-external-registry.js
    │       ├── is-package-name-available.js
    │       ├── login.js
    │       ├── packed-files.js
    │       ├── prerelease-tags.js
    │       ├── username.js
    │       └── verify-recent-npm-version.js
    ├── package-manager.js
    ├── release-task-helper.js
    ├── tasks/
    │   ├── git-tasks.js
    │   └── prerequisite-tasks.js
    ├── ui/
    │   ├── new-files-dependencies.d.ts
    │   ├── new-files-dependencies.js
    │   ├── prompts/
    │   │   ├── tags.js
    │   │   └── version.js
    │   └── repo-url.js
    ├── util/
    │   ├── auto-group-list.js
    │   ├── get-minimum-node-version.js
    │   ├── get-new-dependencies.js
    │   ├── get-new-files.js
    │   ├── get-npm-package-access.js
    │   ├── get-pre-release-prefix.js
    │   ├── get-tag-version-prefix.js
    │   ├── hyperlinks.js
    │   ├── join-list.js
    │   ├── parse-git-url.js
    │   ├── read-pkg.js
    │   └── validate-engine-version-satisfies.js
    └── version.js
Download .txt
SYMBOL INDEX (94 symbols across 27 files)

FILE: source/cli-implementation.js
  function getOptions (line 119) | async function getOptions() {

FILE: source/config.js
  function getConfig (line 5) | async function getConfig(rootDirectory) {

FILE: source/index.js
  function getPackageVersion (line 84) | async function getPackageVersion() {
  function getPublishCommand (line 121) | function getPublishCommand(_options) {
  method task (line 148) | task() {
  method skip (line 166) | skip() {
  method task (line 177) | task() {
  method skip (line 192) | skip() {
  method task (line 199) | task(context, task) {
  method skip (line 226) | async skip() {
  method skip (line 239) | async skip() {
  method task (line 253) | async task() {
  method skip (line 261) | skip() {

FILE: source/npm/handle-npm-error.js
  method done (line 21) | done(otp) {

FILE: source/npm/publish.js
  function runPublish (line 31) | function runPublish(arguments_, options = {}) {

FILE: source/npm/util.js
  constant NPM_DEFAULT_REGISTRIES (line 79) | const NPM_DEFAULT_REGISTRIES = new Set([

FILE: source/package-manager/index.js
  function findLockfile (line 10) | function findLockfile(rootDirectory, config) {
  function getPackageManagerConfig (line 20) | function getPackageManagerConfig(rootDirectory, package_) {
  function configFromPackageManagerField (line 26) | function configFromPackageManagerField(package_) {
  function configFromLockfile (line 57) | function configFromLockfile(rootDirectory, options = [configs.npmConfig,...
  function printCommand (line 72) | function printCommand([cli, arguments_]) {

FILE: source/package-manager/types.d.ts
  type PackageManager (line 1) | type PackageManager = 'npm' | 'yarn' | 'pnpm';
  type Command (line 6) | type Command = [cli: string, args: string[]];
  type PackageManagerConfig (line 8) | type PackageManagerConfig = {

FILE: source/prerequisite-tasks.js
  method task (line 23) | async task() {
  method skip (line 31) | skip() {
  method task (line 36) | async task() {
  method task (line 66) | task() {
  method task (line 74) | task() {
  method task (line 83) | async task() {
  method task (line 121) | async task() {
  method task (line 127) | async task() {

FILE: source/release-task-helper.js
  constant URL_LENGTH_LIMIT (line 8) | const URL_LENGTH_LIMIT = 7900;
  constant CLIPBOARD_PLACEHOLDER (line 9) | const CLIPBOARD_PLACEHOLDER = '<!-- Paste release notes from clipboard -...

FILE: source/ui.js
  constant PRERELEASE_INCREMENTS (line 15) | const PRERELEASE_INCREMENTS = new Set([
  method generateReleaseNotes (line 34) | generateReleaseNotes() {}
  method choices (line 245) | async choices() {
  method validate (line 262) | validate(input) {
  method filter (line 382) | filter(input) {
  method when (line 410) | when(answers) {
  method choices (line 423) | async choices() {
  method validate (line 440) | validate(input) {

FILE: source/util.js
  constant GIT_URL_PATTERNS (line 79) | const GIT_URL_PATTERNS = [
  constant ALPHANUMERIC_REGEX (line 104) | const ALPHANUMERIC_REGEX = /[a-z\d]/i;
  function getNpmPackageAccess (line 257) | async function getNpmPackageAccess(package_) {

FILE: source/version.js
  constant SEMVER_INCREMENTS (line 5) | const SEMVER_INCREMENTS = ['patch', 'minor', 'major', 'prepatch', 'premi...
  constant SEMVER_INCREMENTS_LIST (line 6) | const SEMVER_INCREMENTS_LIST = SEMVER_INCREMENTS.join(', ');
  constant SEMVER_INCREMENTS_LIST_LAST_OR (line 7) | const SEMVER_INCREMENTS_LIST_LAST_OR = `${SEMVER_INCREMENTS.slice(0, -1)...
  class Version (line 30) | class Version {
    method toString (line 38) | toString() {
    method #trySetVersion (line 48) | #trySetVersion(version) {
    method constructor (line 62) | constructor(version, increment, {prereleasePrefix} = {}) {
    method setFrom (line 83) | setFrom(input, {prereleasePrefix = ''} = {}) {
    method format (line 117) | format({color = 'dim', diffColor = 'cyan', previousVersion} = {}) {
    method satisfies (line 169) | satisfies(range) {
    method isPrerelease (line 182) | isPrerelease() {
    method #isGreaterThanOrEqualTo (line 191) | #isGreaterThanOrEqualTo(otherVersion) {

FILE: test/_helpers/integration-test.d.ts
  type Context (line 4) | type Context = {
  type CommandsFunctionParameters (line 14) | type CommandsFunctionParameters = [{
  type AssertionsFunctionParameters (line 20) | type AssertionsFunctionParameters<MockType> = [{
  type CreateFixtureMacro (line 27) | type CreateFixtureMacro<MockType> = Macro<[

FILE: test/_helpers/listr-renderer.js
  class SilentRenderer (line 3) | class SilentRenderer {
    method constructor (line 4) | constructor(_tasks) {
    method tasks (line 8) | static get tasks() {
    method nonTTY (line 12) | static get nonTTY() {
    method clearTasks (line 16) | static clearTasks() {
    method render (line 20) | render() {}
    method end (line 22) | end() {}

FILE: test/_helpers/mock-inquirer.js
  method prompt (line 204) | async prompt(prompts) {

FILE: test/_helpers/stub-execa.d.ts
  type AssertionsFunctionParameters (line 4) | type AssertionsFunctionParameters<MockType> = [{
  type CreateFixtureMacro (line 9) | type CreateFixtureMacro<MockType> = Macro<[

FILE: test/_helpers/stub-execa.js
  method execa (line 83) | async execa(...arguments_) {

FILE: test/_helpers/verify-cli.d.ts
  type VerifyCliMacro (line 3) | type VerifyCliMacro = Macro<[

FILE: test/fixtures/files/gitignore/index.js
  function foo (line 1) | function foo() {

FILE: test/fixtures/files/npmignore-and-gitignore/source/index.ts
  function foo (line 1) | function foo() {

FILE: test/fixtures/files/npmignore/index.js
  function foo (line 1) | function foo() {

FILE: test/fixtures/files/prepare-script/index.js
  function foo (line 1) | function foo() {

FILE: test/release-task-helper.js
  method getTagVersionPrefix (line 86) | async getTagVersionPrefix() {
  method getPreReleasePrefix (line 89) | async getPreReleasePrefix() {
  method 'new-github-release-url' (line 93) | 'new-github-release-url'(options_) {
  method getTagVersionPrefix (line 139) | async getTagVersionPrefix() {
  method getPreReleasePrefix (line 142) | async getPreReleasePrefix() {
  method 'new-github-release-url' (line 146) | 'new-github-release-url'(options_) {

FILE: test/ui/new-files-dependencies.d.ts
  type Context (line 4) | type Context = {
  type CommandsFunctionParameters (line 8) | type CommandsFunctionParameters = [{
  type ListItem (line 14) | type ListItem = `- ${string}`;
  type Expected (line 16) | type Expected = {
  type AssertionsFunctionParameters (line 22) | type AssertionsFunctionParameters = [{
  type CreateFixtureMacro (line 29) | type CreateFixtureMacro = Macro<[

FILE: test/util/hyperlinks.js
  constant MOCK_REPO_URL (line 4) | const MOCK_REPO_URL = 'https://github.com/unicorn/rainbow';
  constant MOCK_COMMIT_HASH (line 5) | const MOCK_COMMIT_HASH = '5063f8a';
  constant MOCK_COMMIT_RANGE (line 6) | const MOCK_COMMIT_RANGE = `${MOCK_COMMIT_HASH}...master`;

FILE: test/version.js
  constant INCREMENT_LIST (line 7) | const INCREMENT_LIST = 'patch, minor, major, prepatch, preminor, premajo...
  constant INCREMENT_LIST_OR (line 8) | const INCREMENT_LIST_OR = 'patch, minor, major, prepatch, preminor, prem...
Condensed preview — 172 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (319K chars).
[
  {
    "path": ".editorconfig",
    "chars": 175,
    "preview": "root = true\n\n[*]\nindent_style = tab\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newlin"
  },
  {
    "path": ".gitattributes",
    "chars": 19,
    "preview": "* text=auto eol=lf\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 577,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve np\n---\n\n<!--- Provide a short summary of the issue in the"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 532,
    "preview": "---\nname: Feature request\nabout: Suggest an idea\n---\n\n<!--- Provide a short summary of the request in the title above --"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 457,
    "preview": "<!--\n\nThanks for submitting a pull request 🙌\n\n**Note:** Please don't create a pull request which has significant changes"
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 560,
    "preview": "name: CI\non:\n  - push\n  - pull_request\njobs:\n  test:\n    name: Node.js ${{ matrix.node-version }}\n    runs-on: ubuntu-la"
  },
  {
    "path": ".gitignore",
    "chars": 23,
    "preview": "node_modules\nyarn.lock\n"
  },
  {
    "path": ".npmrc",
    "chars": 19,
    "preview": "package-lock=false\n"
  },
  {
    "path": "license",
    "chars": 1117,
    "preview": "MIT License\n\nCopyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)\n\nPermission is hereby grant"
  },
  {
    "path": "package.json",
    "chars": 2330,
    "preview": "{\n\t\"name\": \"np\",\n\t\"version\": \"11.0.2\",\n\t\"description\": \"A better `npm publish`\",\n\t\"license\": \"MIT\",\n\t\"repository\": \"sind"
  },
  {
    "path": "readme.md",
    "chars": 15129,
    "preview": "# np [![XO code style](https://shields.io/badge/code_style-5ed9c7?logo=xo&labelColor=gray&logoSize=auto&logoWidth=20)](h"
  },
  {
    "path": "source/.npmignore",
    "chars": 7,
    "preview": "*.d.ts\n"
  },
  {
    "path": "source/cli-implementation.js",
    "chars": 6367,
    "preview": "#!/usr/bin/env node\nimport process from 'node:process';\nimport logSymbols from 'log-symbols';\nimport meow from 'meow';\ni"
  },
  {
    "path": "source/cli.js",
    "chars": 367,
    "preview": "#!/usr/bin/env node\nimport {debuglog} from 'node:util';\nimport importLocal from 'import-local';\nimport isInstalledGlobal"
  },
  {
    "path": "source/config.js",
    "chars": 934,
    "preview": "import os from 'node:os';\nimport isInstalledGlobally from 'is-installed-globally';\nimport {cosmiconfig} from 'cosmiconfi"
  },
  {
    "path": "source/git-tasks.js",
    "chars": 520,
    "preview": "import Listr from 'listr';\nimport * as git from './git-util.js';\n\nconst gitTasks = options => {\n\tconst tasks = [\n\t\t{\n\t\t\t"
  },
  {
    "path": "source/git-util.js",
    "chars": 9079,
    "preview": "import path from 'node:path';\nimport {execa} from 'execa';\nimport escapeStringRegexp from 'escape-string-regexp';\nimport"
  },
  {
    "path": "source/index.js",
    "chars": 9308,
    "preview": "import {execa} from 'execa';\nimport {deleteAsync} from 'del';\n// NOTE: We intentionally use the original `listr` package"
  },
  {
    "path": "source/npm/enable-2fa.js",
    "chars": 969,
    "preview": "import {execa} from 'execa';\nimport {from, catchError} from 'rxjs';\nimport Version from '../version.js';\nimport handleNp"
  },
  {
    "path": "source/npm/handle-npm-error.js",
    "chars": 1388,
    "preview": "import listrInput from 'listr-input';\nimport chalk from 'chalk';\nimport {throwError, catchError} from 'rxjs';\n\nconst han"
  },
  {
    "path": "source/npm/oidc.js",
    "chars": 912,
    "preview": "import process from 'node:process';\n\nconst oidcProviders = [\n\t{\n\t\tid: 'github',\n\t\tname: 'GitHub Actions',\n\t\t// See https"
  },
  {
    "path": "source/npm/publish.js",
    "chars": 2588,
    "preview": "import {execa} from 'execa';\nimport {merge, filter, catchError} from 'rxjs';\nimport open from 'open';\n\nexport const getP"
  },
  {
    "path": "source/npm/util.js",
    "chars": 9553,
    "preview": "import path from 'node:path';\nimport {pathExists} from 'path-exists';\nimport {execa} from 'execa';\nimport npmName from '"
  },
  {
    "path": "source/package-manager/configs.js",
    "chars": 3320,
    "preview": "/** @type {import('./types.d.ts').PackageManagerConfig} */\nexport const npmConfig = {\n\tcli: 'npm',\n\tid: 'npm',\n\tinstallC"
  },
  {
    "path": "source/package-manager/index.js",
    "chars": 2121,
    "preview": "import fs from 'node:fs';\nimport path from 'node:path';\nimport semver from 'semver';\nimport * as configs from './configs"
  },
  {
    "path": "source/package-manager/types.d.ts",
    "chars": 1717,
    "preview": "export type PackageManager = 'npm' | 'yarn' | 'pnpm';\n\n/**\nCLI and arguments, which can be passed to `execa`.\n*/\nexport "
  },
  {
    "path": "source/prerequisite-tasks.js",
    "chars": 4351,
    "preview": "import process from 'node:process';\nimport Listr from 'listr';\nimport {execa} from 'execa';\nimport semver from 'semver';"
  },
  {
    "path": "source/release-task-helper.js",
    "chars": 1656,
    "preview": "import open from 'open';\nimport newGithubReleaseUrl from 'new-github-release-url';\nimport clipboard from 'clipboardy';\ni"
  },
  {
    "path": "source/ui.js",
    "chars": 14856,
    "preview": "import inquirer from 'inquirer';\nimport chalk from 'chalk';\nimport githubUrlFromGit from 'github-url-from-git';\nimport h"
  },
  {
    "path": "source/util.js",
    "chars": 8589,
    "preview": "import process from 'node:process';\nimport {fileURLToPath} from 'node:url';\nimport path from 'node:path';\nimport {readPa"
  },
  {
    "path": "source/version.js",
    "chars": 6957,
    "preview": "import semver from 'semver';\nimport {template as chalk} from 'chalk-template';\n\n/** @type {string[]} Allowed `SemVer` re"
  },
  {
    "path": "test/_helpers/integration-test.d.ts",
    "chars": 926,
    "preview": "import type {Macro, ExecutionContext} from 'ava';\nimport type {Execa$} from 'execa';\n\ntype Context = {\n\tfirstCommitMessa"
  },
  {
    "path": "test/_helpers/integration-test.js",
    "chars": 2196,
    "preview": "/* eslint-disable ava/no-ignored-test-files */\nimport crypto from 'node:crypto';\nimport path from 'node:path';\nimport fs"
  },
  {
    "path": "test/_helpers/listr-renderer.js",
    "chars": 238,
    "preview": "let tasks;\n\nexport class SilentRenderer {\n\tconstructor(_tasks) {\n\t\ttasks = _tasks;\n\t}\n\n\tstatic get tasks() {\n\t\treturn ta"
  },
  {
    "path": "test/_helpers/listr.js",
    "chars": 902,
    "preview": "import {SilentRenderer} from './listr-renderer.js';\n\nexport const run = async listr => {\n\tlistr.setRenderer(SilentRender"
  },
  {
    "path": "test/_helpers/mock-inquirer.js",
    "chars": 5853,
    "preview": "import esmock from 'esmock';\nimport is from '@sindresorhus/is';\nimport stripAnsi from 'strip-ansi';\nimport mapObject fro"
  },
  {
    "path": "test/_helpers/stub-execa.d.ts",
    "chars": 479,
    "preview": "import type {Macro, ExecutionContext} from 'ava';\nimport type {ExecaReturnValue} from 'execa';\n\ntype AssertionsFunctionP"
  },
  {
    "path": "test/_helpers/stub-execa.js",
    "chars": 2749,
    "preview": "/* eslint-disable ava/no-ignored-test-files */\nimport test from 'ava';\nimport esmock from 'esmock';\nimport sinon from 's"
  },
  {
    "path": "test/_helpers/util.js",
    "chars": 385,
    "preview": "import {fileURLToPath} from 'node:url';\nimport path from 'node:path';\n\nexport const runIfExists = async (function_, ...a"
  },
  {
    "path": "test/_helpers/verify-cli.d.ts",
    "chars": 258,
    "preview": "import type {Macro, ExecutionContext} from 'ava';\n\ntype VerifyCliMacro = Macro<[\n\tbinPath: string,\n\targs: string | strin"
  },
  {
    "path": "test/_helpers/verify-cli.js",
    "chars": 652,
    "preview": "/* eslint-disable ava/no-ignored-test-files */\nimport test from 'ava';\nimport {execa} from 'execa';\n\nconst trim = stdout"
  },
  {
    "path": "test/cli.js",
    "chars": 2060,
    "preview": "import path from 'node:path';\nimport test from 'ava';\nimport {npPackage, npRootDirectory as rootDirectory} from '../sour"
  },
  {
    "path": "test/config.js",
    "chars": 4929,
    "preview": "import path from 'node:path';\nimport test from 'ava';\nimport esmock from 'esmock';\nimport {readPackage} from '../source/"
  },
  {
    "path": "test/fixtures/config/contents-option/.np-config.json",
    "chars": 24,
    "preview": "{\n\t\"contents\": \"dist\"\n}\n"
  },
  {
    "path": "test/fixtures/config/contents-option/dist/package.json",
    "chars": 46,
    "preview": "{\n\t\"name\": \"from-dist\",\n\t\"version\": \"1.0.0\"\n}\n"
  },
  {
    "path": "test/fixtures/config/flag-precedence/.np-config.json",
    "chars": 58,
    "preview": "{\n\t\"tests\": false,\n\t\"cleanup\": false,\n\t\"publish\": false\n}\n"
  },
  {
    "path": "test/fixtures/config/flag-precedence/package.json",
    "chars": 57,
    "preview": "{\n\t\"name\": \"test-flag-precedence\",\n\t\"version\": \"1.0.0\"\n}\n"
  },
  {
    "path": "test/fixtures/config/homedir1/.np-config.json",
    "chars": 41,
    "preview": "{\n\t\"source\": \"homedir/.np-config.json\"\n}\n"
  },
  {
    "path": "test/fixtures/config/homedir2/.np-config.js",
    "chars": 55,
    "preview": "module.exports = {\n\tsource: 'homedir/.np-config.js'\n};\n"
  },
  {
    "path": "test/fixtures/config/homedir3/.np-config.cjs",
    "chars": 56,
    "preview": "module.exports = {\n\tsource: 'homedir/.np-config.cjs'\n};\n"
  },
  {
    "path": "test/fixtures/config/homedir4/.np-config.js",
    "chars": 53,
    "preview": "export default {\n\tsource: 'homedir/.np-config.js'\n};\n"
  },
  {
    "path": "test/fixtures/config/homedir5/.np-config.mjs",
    "chars": 54,
    "preview": "export default {\n\tsource: 'homedir/.np-config.mjs'\n};\n"
  },
  {
    "path": "test/fixtures/config/local1/.np-config.json",
    "chars": 44,
    "preview": "{\n\t\"source\": \"packagedir/.np-config.json\"\n}\n"
  },
  {
    "path": "test/fixtures/config/local2/.np-config.js",
    "chars": 58,
    "preview": "module.exports = {\n\tsource: 'packagedir/.np-config.js'\n};\n"
  },
  {
    "path": "test/fixtures/config/local3/.np-config.cjs",
    "chars": 59,
    "preview": "module.exports = {\n\tsource: 'packagedir/.np-config.cjs'\n};\n"
  },
  {
    "path": "test/fixtures/config/local4/.np-config.js",
    "chars": 56,
    "preview": "export default {\n\tsource: 'packagedir/.np-config.js'\n};\n"
  },
  {
    "path": "test/fixtures/config/local4/package.json",
    "chars": 70,
    "preview": "{\n\t\"name\": \"use-type-module-for-config-fixtures\",\n\t\"type\": \"module\"\n}\n"
  },
  {
    "path": "test/fixtures/config/local5/.np-config.mjs",
    "chars": 57,
    "preview": "export default {\n\tsource: 'packagedir/.np-config.mjs'\n};\n"
  },
  {
    "path": "test/fixtures/config/package.json",
    "chars": 56,
    "preview": "{\n\t\"name\": \"override-type-module-for-config-fixtures\"\n}\n"
  },
  {
    "path": "test/fixtures/config/pkg-dir/package.json",
    "chars": 69,
    "preview": "{\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",
    "chars": 457,
    "preview": "<!--\n\nThanks for submitting a pull request 🙌\n\n**Note:** Please don't create a pull request which has significant changes"
  },
  {
    "path": "test/fixtures/files/dot-github/index.js",
    "chars": 20,
    "preview": "console.log('foo');\n"
  },
  {
    "path": "test/fixtures/files/dot-github/package.json",
    "chars": 64,
    "preview": "{\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",
    "chars": 62,
    "preview": "{\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",
    "chars": 6,
    "preview": "# Foo\n"
  },
  {
    "path": "test/fixtures/files/files-and-npmignore/source/.npmignore",
    "chars": 16,
    "preview": "index.test-d.ts\n"
  },
  {
    "path": "test/fixtures/files/files-and-npmignore/source/bar.js",
    "chars": 20,
    "preview": "console.log('bar');\n"
  },
  {
    "path": "test/fixtures/files/files-and-npmignore/source/foo.js",
    "chars": 20,
    "preview": "console.log('foo');\n"
  },
  {
    "path": "test/fixtures/files/files-and-npmignore/source/index.d.ts",
    "chars": 62,
    "preview": "export function foo(): string;\nexport function bar(): string;\n"
  },
  {
    "path": "test/fixtures/files/files-and-npmignore/source/index.test-d.ts",
    "chars": 124,
    "preview": "import {expectType} from 'tsd';\nimport {foo, bar} from './index.js';\n\nexpectType<string>(foo());\nexpectType<string>(bar("
  },
  {
    "path": "test/fixtures/files/files-slash/index.js",
    "chars": 20,
    "preview": "console.log('foo');\n"
  },
  {
    "path": "test/fixtures/files/files-slash/package.json",
    "chars": 65,
    "preview": "{\n\t\"name\": \"foo\",\n\t\"version\": \"0.0.0\",\n\t\"files\": [\"/index.js\"]\n}\n"
  },
  {
    "path": "test/fixtures/files/gitignore/.gitignore",
    "chars": 5,
    "preview": "dist\n"
  },
  {
    "path": "test/fixtures/files/gitignore/index.d.ts",
    "chars": 39,
    "preview": "export default function foo(): string;\n"
  },
  {
    "path": "test/fixtures/files/gitignore/index.js",
    "chars": 49,
    "preview": "export default function foo() {\n\treturn 'bar';\n}\n"
  },
  {
    "path": "test/fixtures/files/gitignore/index.test-d.ts",
    "chars": 90,
    "preview": "import {expectType} from 'tsd';\nimport foo from './index.js';\n\nexpectType<string>(foo());\n"
  },
  {
    "path": "test/fixtures/files/gitignore/package.json",
    "chars": 60,
    "preview": "{\n\t\"name\": \"foo\",\n\t\"version\": \"0.0.0\",\n\t\"files\": [\"dist\"]\n}\n"
  },
  {
    "path": "test/fixtures/files/gitignore/readme.md",
    "chars": 6,
    "preview": "# Foo\n"
  },
  {
    "path": "test/fixtures/files/has-readme-and-license/index.js",
    "chars": 20,
    "preview": "console.log('foo');\n"
  },
  {
    "path": "test/fixtures/files/has-readme-and-license/license.md",
    "chars": 4,
    "preview": "MIT\n"
  },
  {
    "path": "test/fixtures/files/has-readme-and-license/package.json",
    "chars": 64,
    "preview": "{\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",
    "chars": 6,
    "preview": "# Foo\n"
  },
  {
    "path": "test/fixtures/files/main/bar.js",
    "chars": 20,
    "preview": "console.log('bar');\n"
  },
  {
    "path": "test/fixtures/files/main/foo.js",
    "chars": 20,
    "preview": "console.log('foo');\n"
  },
  {
    "path": "test/fixtures/files/main/package.json",
    "chars": 81,
    "preview": "{\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",
    "chars": 22,
    "preview": "export default 'foo';\n"
  },
  {
    "path": "test/fixtures/files/missing-bin/package.json",
    "chars": 92,
    "preview": "{\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",
    "chars": 97,
    "preview": "{\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",
    "chars": 22,
    "preview": "export default 'foo';\n"
  },
  {
    "path": "test/fixtures/files/npmignore/.npmignore",
    "chars": 16,
    "preview": "index.test-d.ts\n"
  },
  {
    "path": "test/fixtures/files/npmignore/index.d.ts",
    "chars": 39,
    "preview": "export default function foo(): string;\n"
  },
  {
    "path": "test/fixtures/files/npmignore/index.js",
    "chars": 49,
    "preview": "export default function foo() {\n\treturn 'bar';\n}\n"
  },
  {
    "path": "test/fixtures/files/npmignore/index.test-d.ts",
    "chars": 90,
    "preview": "import {expectType} from 'tsd';\nimport foo from './index.js';\n\nexpectType<string>(foo());\n"
  },
  {
    "path": "test/fixtures/files/npmignore/package.json",
    "chars": 40,
    "preview": "{\n\t\"name\": \"foo\",\n\t\"version\": \"0.0.0\"\n}\n"
  },
  {
    "path": "test/fixtures/files/npmignore/readme.md",
    "chars": 6,
    "preview": "# Foo\n"
  },
  {
    "path": "test/fixtures/files/npmignore-and-gitignore/.gitignore",
    "chars": 5,
    "preview": "dist\n"
  },
  {
    "path": "test/fixtures/files/npmignore-and-gitignore/.npmignore",
    "chars": 16,
    "preview": "script/\nsource/\n"
  },
  {
    "path": "test/fixtures/files/npmignore-and-gitignore/package.json",
    "chars": 40,
    "preview": "{\n\t\"name\": \"foo\",\n\t\"version\": \"0.0.0\"\n}\n"
  },
  {
    "path": "test/fixtures/files/npmignore-and-gitignore/readme.md",
    "chars": 6,
    "preview": "# Foo\n"
  },
  {
    "path": "test/fixtures/files/npmignore-and-gitignore/script/build.js",
    "chars": 22,
    "preview": "// ... yada yada yada\n"
  },
  {
    "path": "test/fixtures/files/npmignore-and-gitignore/source/index.ts",
    "chars": 49,
    "preview": "export default function foo() {\n\treturn 'bar';\n}\n"
  },
  {
    "path": "test/fixtures/files/one-file/index.js",
    "chars": 20,
    "preview": "console.log('foo');\n"
  },
  {
    "path": "test/fixtures/files/one-file/package.json",
    "chars": 64,
    "preview": "{\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",
    "chars": 49,
    "preview": "export default function foo() {\n\treturn 'bar';\n}\n"
  },
  {
    "path": "test/fixtures/files/prepare-script/package.json",
    "chars": 156,
    "preview": "{\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' &&"
  },
  {
    "path": "test/fixtures/files/source-and-dist-dir/dist/index.js",
    "chars": 40,
    "preview": "console.log('foo');\nconsole.log('bar');\n"
  },
  {
    "path": "test/fixtures/files/source-and-dist-dir/package.json",
    "chars": 62,
    "preview": "{\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",
    "chars": 20,
    "preview": "console.log('bar');\n"
  },
  {
    "path": "test/fixtures/files/source-and-dist-dir/source/foo.js",
    "chars": 20,
    "preview": "console.log('foo');\n"
  },
  {
    "path": "test/fixtures/files/source-dir/package.json",
    "chars": 62,
    "preview": "{\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",
    "chars": 20,
    "preview": "console.log('bar');\n"
  },
  {
    "path": "test/fixtures/files/source-dir/source/foo.js",
    "chars": 20,
    "preview": "console.log('foo');\n"
  },
  {
    "path": "test/fixtures/readme.md",
    "chars": 62,
    "preview": "The directory is for the resources\nin the script npmignore.js\n"
  },
  {
    "path": "test/git-util/commit-log-from-revision.js",
    "chars": 1131,
    "preview": "import test from 'ava';\nimport {stripIndent} from 'common-tags';\nimport {_createFixture} from '../_helpers/integration-t"
  },
  {
    "path": "test/git-util/default-branch.js",
    "chars": 1314,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _c"
  },
  {
    "path": "test/git-util/delete-tag.js",
    "chars": 1142,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _c"
  },
  {
    "path": "test/git-util/get-current-branch.js",
    "chars": 479,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _c"
  },
  {
    "path": "test/git-util/has-upstream.js",
    "chars": 378,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _c"
  },
  {
    "path": "test/git-util/is-head-detached.js",
    "chars": 647,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _c"
  },
  {
    "path": "test/git-util/latest-tag-or-first-commit.js",
    "chars": 1165,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _c"
  },
  {
    "path": "test/git-util/latest-tag.js",
    "chars": 795,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _c"
  },
  {
    "path": "test/git-util/multiple-initial-commits.js",
    "chars": 1502,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _c"
  },
  {
    "path": "test/git-util/new-files-since-last-release.js",
    "chars": 1535,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _c"
  },
  {
    "path": "test/git-util/previous-tag-or-first-commit.js",
    "chars": 2255,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _c"
  },
  {
    "path": "test/git-util/push-graceful.js",
    "chars": 1914,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _createF"
  },
  {
    "path": "test/git-util/read-file-from-last-release.js",
    "chars": 1595,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _c"
  },
  {
    "path": "test/git-util/remove-last-commit.js",
    "chars": 715,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _c"
  },
  {
    "path": "test/git-util/root.js",
    "chars": 598,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\nimport {npRootDirectory} from '."
  },
  {
    "path": "test/git-util/verify-current-branch-is-release-branch.js",
    "chars": 852,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _c"
  },
  {
    "path": "test/git-util/verify-recent-git-version.js",
    "chars": 772,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _createF"
  },
  {
    "path": "test/git-util/verify-remote-history-is-clean.js",
    "chars": 2022,
    "preview": "import test from 'ava';\nimport {_createFixture as _createStubFixture} from '../_helpers/stub-execa.js';\nimport {_createF"
  },
  {
    "path": "test/git-util/verify-remote-is-valid.js",
    "chars": 1202,
    "preview": "import test from 'ava';\nimport {_createFixture as _createStubFixture} from '../_helpers/stub-execa.js';\nimport {_createF"
  },
  {
    "path": "test/git-util/verify-tag-does-not-exist-on-remote.js",
    "chars": 852,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _createF"
  },
  {
    "path": "test/git-util/verify-working-tree-is-clean.js",
    "chars": 790,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/integration-test.js';\n\n/** @type {ReturnType<typeof _c"
  },
  {
    "path": "test/index.js",
    "chars": 7728,
    "preview": "import path from 'node:path';\nimport process from 'node:process';\nimport test from 'ava';\nimport sinon from 'sinon';\nimp"
  },
  {
    "path": "test/npm/enable-2fa.js",
    "chars": 1287,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _createF"
  },
  {
    "path": "test/npm/handle-npm-error.js",
    "chars": 659,
    "preview": "import test from 'ava';\nimport handleNpmError from '../../source/npm/handle-npm-error.js';\n\nconst makeError = ({code, st"
  },
  {
    "path": "test/npm/oidc.js",
    "chars": 824,
    "preview": "import test from 'ava';\nimport esmock from 'esmock';\n\ntest('detects GitHub Actions', async t => {\n\tconst {getOidcProvide"
  },
  {
    "path": "test/npm/publish.js",
    "chars": 1302,
    "preview": "import test from 'ava';\nimport {firstValueFrom} from 'rxjs';\nimport {getPackagePublishArguments, runPublish} from '../.."
  },
  {
    "path": "test/npm/util/check-connection.js",
    "chars": 891,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _crea"
  },
  {
    "path": "test/npm/util/check-ignore-strategy.js",
    "chars": 1445,
    "preview": "import path from 'node:path';\nimport test from 'ava';\nimport esmock from 'esmock';\nimport stripAnsi from 'strip-ansi';\ni"
  },
  {
    "path": "test/npm/util/collaborators.js",
    "chars": 2716,
    "preview": "import test from 'ava';\nimport {stripIndent} from 'common-tags';\nimport {_createFixture} from '../../_helpers/stub-execa"
  },
  {
    "path": "test/npm/util/entry-points.js",
    "chars": 5129,
    "preview": "import path from 'node:path';\nimport test from 'ava';\nimport * as npm from '../../../source/npm/util.js';\n\nconst getFixt"
  },
  {
    "path": "test/npm/util/is-external-registry.js",
    "chars": 1034,
    "preview": "import test from 'ava';\nimport * as npm from '../../../source/npm/util.js';\n\ntest('main', t => {\n\tt.true(npm.isExternalR"
  },
  {
    "path": "test/npm/util/is-package-name-available.js",
    "chars": 1356,
    "preview": "import test from 'ava';\nimport esmock from 'esmock';\nimport sinon from 'sinon';\n\nconst externalRegistry = 'http://my-int"
  },
  {
    "path": "test/npm/util/login.js",
    "chars": 831,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _crea"
  },
  {
    "path": "test/npm/util/packed-files.js",
    "chars": 2099,
    "preview": "import path from 'node:path';\nimport test from 'ava';\nimport {getFilesToBePacked} from '../../../source/npm/util.js';\nim"
  },
  {
    "path": "test/npm/util/prerelease-tags.js",
    "chars": 3971,
    "preview": "import test from 'ava';\nimport {stripIndent} from 'common-tags';\nimport {_createFixture} from '../../_helpers/stub-execa"
  },
  {
    "path": "test/npm/util/username.js",
    "chars": 1685,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _crea"
  },
  {
    "path": "test/npm/util/verify-recent-npm-version.js",
    "chars": 714,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _crea"
  },
  {
    "path": "test/package-manager.js",
    "chars": 2677,
    "preview": "import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport test from 'ava';\nimport {getPac"
  },
  {
    "path": "test/release-task-helper.js",
    "chars": 4311,
    "preview": "import test from 'ava';\nimport sinon from 'sinon';\nimport esmock from 'esmock';\n\nconst verifyRelease = test.macro(async "
  },
  {
    "path": "test/tasks/git-tasks.js",
    "chars": 4793,
    "preview": "import test from 'ava';\nimport {SilentRenderer} from '../_helpers/listr-renderer.js';\nimport {_createFixture} from '../_"
  },
  {
    "path": "test/tasks/prerequisite-tasks.js",
    "chars": 23650,
    "preview": "import process from 'node:process';\nimport test from 'ava';\nimport {npmConfig, yarnConfig} from '../../source/package-ma"
  },
  {
    "path": "test/ui/new-files-dependencies.d.ts",
    "chars": 812,
    "preview": "import type {Macro, ExecutionContext} from 'ava';\nimport type {PackageJson} from 'read-pkg';\n\ntype Context = {\n\tcreateFi"
  },
  {
    "path": "test/ui/new-files-dependencies.js",
    "chars": 4894,
    "preview": "import test from 'ava';\nimport sinon from 'sinon';\nimport {execa} from 'execa';\nimport {removePackageDependencies, updat"
  },
  {
    "path": "test/ui/prompts/tags.js",
    "chars": 4628,
    "preview": "import test from 'ava';\nimport sinon from 'sinon';\nimport {npmConfig as packageManager} from '../../../source/package-ma"
  },
  {
    "path": "test/ui/prompts/version.js",
    "chars": 4888,
    "preview": "import test from 'ava';\nimport sinon from 'sinon';\nimport {npmConfig as packageManager} from '../../../source/package-ma"
  },
  {
    "path": "test/ui/repo-url.js",
    "chars": 1224,
    "preview": "import test from 'ava';\nimport sinon from 'sinon';\nimport {npmConfig as packageManager} from '../../source/package-manag"
  },
  {
    "path": "test/util/auto-group-list.js",
    "chars": 703,
    "preview": "import test from 'ava';\nimport stripAnsi from 'strip-ansi';\nimport {groupFilesInFolders} from '../../source/util.js';\n\nc"
  },
  {
    "path": "test/util/get-minimum-node-version.js",
    "chars": 1376,
    "preview": "import test from 'ava';\nimport {getMinimumNodeVersion} from '../../source/util.js';\n\ntest('returns minimum version from "
  },
  {
    "path": "test/util/get-new-dependencies.js",
    "chars": 1765,
    "preview": "import test from 'ava';\nimport {updatePackage} from 'write-package';\nimport {readPackage} from 'read-pkg';\nimport {_crea"
  },
  {
    "path": "test/util/get-new-files.js",
    "chars": 2990,
    "preview": "import path from 'node:path';\nimport test from 'ava';\nimport esmock from 'esmock';\nimport {execa} from 'execa';\nimport {"
  },
  {
    "path": "test/util/get-npm-package-access.js",
    "chars": 1281,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/stub-execa.js';\n\n/** @type {ReturnType<typeof _createF"
  },
  {
    "path": "test/util/get-pre-release-prefix.js",
    "chars": 1847,
    "preview": "import process from 'node:process';\nimport test from 'ava';\nimport {_createFixture} from '../_helpers/stub-execa.js';\nim"
  },
  {
    "path": "test/util/get-tag-version-prefix.js",
    "chars": 2550,
    "preview": "import test from 'ava';\nimport {_createFixture} from '../_helpers/stub-execa.js';\nimport {getTagVersionPrefix as origina"
  },
  {
    "path": "test/util/hyperlinks.js",
    "chars": 3276,
    "preview": "import test from 'ava';\nimport esmock from 'esmock';\n\nconst MOCK_REPO_URL = 'https://github.com/unicorn/rainbow';\nconst "
  },
  {
    "path": "test/util/join-list.js",
    "chars": 510,
    "preview": "import test from 'ava';\nimport stripAnsi from 'strip-ansi';\nimport {joinList} from '../../source/util.js';\n\nconst testJo"
  },
  {
    "path": "test/util/parse-git-url.js",
    "chars": 20862,
    "preview": "import test from 'ava';\nimport {parseGitUrl} from '../../source/util.js';\n\n// Valid URL formats\ntest('parses HTTPS URL w"
  },
  {
    "path": "test/util/read-pkg.js",
    "chars": 1465,
    "preview": "import {fileURLToPath} from 'node:url';\nimport path from 'node:path';\nimport test from 'ava';\nimport esmock from 'esmock"
  },
  {
    "path": "test/util/validate-engine-version-satisfies.js",
    "chars": 751,
    "preview": "import test from 'ava';\nimport {validateEngineVersionSatisfies, npPackage} from '../../source/util.js';\n\nconst testEngin"
  },
  {
    "path": "test/version.js",
    "chars": 7770,
    "preview": "import test from 'ava';\nimport sinon from 'sinon';\nimport {template as chalk} from 'chalk-template';\nimport semver from "
  }
]

About this extraction

This page contains the full source code of the sindresorhus/np GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 172 files (281.2 KB), approximately 82.8k tokens, and a symbol index with 94 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!