From 45902ae9cd3ba9d80ecccf39c26f500451d1378a Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Tue, 30 Jan 2024 21:24:13 -0500 Subject: [PATCH 01/25] support pnpm --- package.json | 8 +- readme.md | 22 ++-- source/cli-implementation.js | 29 ++--- source/index.js | 169 ++++++++++-------------------- source/npm/handle-npm-error.js | 2 +- source/npm/publish.js | 21 +--- source/npm/util.js | 15 --- source/package-manager/configs.js | 42 ++++++++ source/package-manager/index.js | 69 ++++++++++++ source/package-manager/types.d.ts | 14 +++ source/ui.js | 26 ++--- source/yarn.js | 16 --- test/npm/util/get-registry-url.js | 49 --------- test/ui/new-files-dependencies.js | 1 - test/ui/prompts/tags.js | 1 - test/ui/prompts/version.js | 1 - test/util/yarn.js | 15 --- 17 files changed, 226 insertions(+), 274 deletions(-) create mode 100644 source/package-manager/configs.js create mode 100644 source/package-manager/index.js create mode 100644 source/package-manager/types.d.ts delete mode 100644 source/yarn.js delete mode 100644 test/npm/util/get-registry-url.js delete mode 100644 test/util/yarn.js diff --git a/package.json b/package.json index bab6ae23..4d266119 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "node": ">=18", "npm": ">=9", "git": ">=2.11.0", - "yarn": ">=1.7.0" + "yarn": ">=1.7.0", + "pnpm": ">=8" }, "scripts": { "test": "xo && ava" @@ -40,7 +41,6 @@ "execa": "^8.0.1", "exit-hook": "^4.0.0", "github-url-from-git": "^1.5.0", - "has-yarn": "^3.0.0", "hosted-git-info": "^7.0.1", "ignore-walk": "^6.0.3", "import-local": "^3.1.0", @@ -52,7 +52,7 @@ "listr": "^0.14.3", "listr-input": "^0.2.1", "log-symbols": "^6.0.0", - "meow": "^12.1.1", + "meow": "^13.1.0", "new-github-release-url": "^2.0.0", "npm-name": "^7.1.1", "onetime": "^7.0.0", @@ -62,8 +62,8 @@ "p-timeout": "^6.1.2", "path-exists": "^5.0.0", "pkg-dir": "^8.0.0", - "read-pkg": "^9.0.1", "read-package-up": "^11.0.0", + "read-pkg": "^9.0.1", "rxjs": "^7.8.1", "semver": "^7.5.4", "symbol-observable": "^4.0.0", diff --git a/readme.md b/readme.md index 1f5930c8..23247595 100644 --- a/readme.md +++ b/readme.md @@ -74,7 +74,7 @@ $ np --help $ np Version can be: - major | minor | patch | premajor | preminor | prepatch | prerelease | 1.2.3 + patch | minor | major | prepatch | preminor | premajor | prerelease | 1.2.3 Options --any-branch Allow publishing from any branch @@ -85,13 +85,13 @@ $ np --help --no-publish Skips publishing --preview Show tasks without actually executing them --tag Publish under a given dist-tag - --no-yarn Don't use Yarn --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 --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) + --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) Examples $ np @@ -121,21 +121,21 @@ Currently, these are the flags you can configure: - `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). -- `yarn` - Use yarn if possible (`true` by default). - `contents` - Subdirectory to publish (`.` by default). - `releaseDraft` - Open a GitHub release draft after releasing (`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/api/packages.html#packagemanager), so only use if you can't update package.json for some reason. -For example, this configures `np` to never use Yarn and to use `dist` as the subdirectory to publish: +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": { - "yarn": false, + "testScript": "unit-test", "contents": "dist" } } @@ -144,7 +144,7 @@ For example, this configures `np` to never use Yarn and to use `dist` as the sub `.np-config.json` ```json { - "yarn": false, + "testScript": "unit-test", "contents": "dist" } ``` @@ -152,7 +152,7 @@ For example, this configures `np` to never use Yarn and to use `dist` as the sub `.np-config.js` or `.np-config.cjs` ```js module.exports = { - yarn: false, + testScript: 'unit-test', contents: 'dist' }; ``` @@ -160,7 +160,7 @@ module.exports = { `.np-config.mjs` ```js export default { - yarn: false, + testScript: 'unit-test', contents: 'dist' }; ``` @@ -276,6 +276,10 @@ Set the [`registry` option](https://docs.npmjs.com/misc/config#registry) in pack } ``` +### 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 user 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 [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`. diff --git a/source/cli-implementation.js b/source/cli-implementation.js index 9f7ffe45..7f5204d9 100755 --- a/source/cli-implementation.js +++ b/source/cli-implementation.js @@ -4,7 +4,6 @@ import 'symbol-observable'; // Important: This needs to be first to prevent weir import logSymbols from 'log-symbols'; import meow from 'meow'; import updateNotifier from 'update-notifier'; -import hasYarn from 'has-yarn'; import {gracefulExit} from 'exit-hook'; import config from './config.js'; import * as util from './util.js'; @@ -12,10 +11,9 @@ import * as git from './git-util.js'; import * as npm from './npm/util.js'; import {SEMVER_INCREMENTS} from './version.js'; import ui from './ui.js'; -import {checkIfYarnBerry} from './yarn.js'; import np from './index.js'; -const cli = meow(` +export const cli = meow(` Usage $ np @@ -31,13 +29,13 @@ const cli = meow(` --no-publish Skips publishing --preview Show tasks without actually executing them --tag Publish under a given dist-tag - --no-yarn Don't use Yarn --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 --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) Examples $ np @@ -80,9 +78,8 @@ const cli = meow(` tag: { type: 'string', }, - yarn: { - type: 'boolean', - default: hasYarn(), + packageManager: { + type: 'string', }, contents: { type: 'string', @@ -105,7 +102,7 @@ const cli = meow(` updateNotifier({pkg: cli.pkg}).notify(); -try { +export async function getOptions() { const {pkg, rootDir} = await util.readPkg(cli.flags.contents); const localConfig = await config(rootDir); @@ -119,6 +116,10 @@ try { flags['2fa'] = flags['2Fa']; } + if (flags.packageManager) { + pkg.packageManager = flags.packageManager; + } + const runPublish = !flags.releaseDraftOnly && flags.publish && !pkg.private; // TODO: does this need to run if `runPublish` is false? @@ -132,22 +133,26 @@ try { const branch = flags.branch ?? await git.defaultBranch(); - const isYarnBerry = flags.yarn && checkIfYarnBerry(pkg); - const options = await ui({ ...flags, runPublish, availability, version, branch, - }, {pkg, rootDir, isYarnBerry}); + }, {pkg, rootDir}); + + return {options, rootDir, pkg}; +} + +try { + const {options, rootDir, pkg} = await getOptions(); if (!options.confirm) { gracefulExit(); } console.log(); // Prints a newline for readability - const newPkg = await np(options.version, options, {pkg, rootDir, isYarnBerry}); + const newPkg = await np(options.version, options, {pkg, rootDir}); if (options.preview || options.releaseDraftOnly) { gracefulExit(); diff --git a/source/index.js b/source/index.js index 338dbaf3..03e99052 100644 --- a/source/index.js +++ b/source/index.js @@ -1,23 +1,23 @@ -import fs from 'node:fs'; -import path from 'node:path'; import {execa} from 'execa'; import {deleteAsync} from 'del'; import Listr from 'listr'; -import {merge, throwError, catchError, filter, finalize} from 'rxjs'; -import hasYarn from 'has-yarn'; +import {merge, catchError, filter, finalize} 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 publish, {getPackagePublishArguments} from './npm/publish.js'; +import {getPackagePublishArguments} from './npm/publish.js'; import enable2fa, {getEnable2faArgs} from './npm/enable-2fa.js'; import releaseTaskHelper from './release-task-helper.js'; import * as util from './util.js'; import * as git from './git-util.js'; import * as npm from './npm/util.js'; +import {findLockFile, getPackageManagerConfig, printCommand} from './package-manager/index.js'; +import handleNpmError from './npm/handle-npm-error.js'; +/** @type {(cmd: string, args: string[], options?: import('execa').Options) => any} */ const exec = (cmd, args, options) => { // Use `Observable` support if merged https://github.com/sindresorhus/execa/pull/26 const cp = execa(cmd, args, options); @@ -25,37 +25,26 @@ const exec = (cmd, args, options) => { return merge(cp.stdout, cp.stderr, cp).pipe(filter(Boolean)); }; -// eslint-disable-next-line complexity -const np = async (input = 'patch', options, {pkg, rootDir, isYarnBerry}) => { - if (!hasYarn() && options.yarn) { - throw new Error('Could not use Yarn without yarn.lock file'); - } +/** + * @param {string} input + * @param {any | typeof import('./cli-implementation.js').cli['unnormalizedFlags']} options + * @param {{pkg: import('read-pkg').NormalizedPackageJson; rootDir: string}} context + */ + +const np = async (input = 'patch', options, {pkg, rootDir}) => { + const pkgManager = getPackageManagerConfig(rootDir, pkg); + const publishCli = options.publishCli || pkgManager.cli; // TODO: Remove sometime far in the future if (options.skipCleanup) { options.cleanup = false; } - function getPackageManagerName() { - if (options.yarn === true) { - if (isYarnBerry) { - return 'Yarn Berry'; - } - - return 'Yarn'; - } - - return 'npm'; - } - const runTests = options.tests && !options.yolo; const runCleanup = options.cleanup && !options.yolo; - const pkgManager = options.yarn === true ? 'yarn' : 'npm'; - const pkgManagerName = getPackageManagerName(); - const hasLockFile = fs.existsSync(path.resolve(rootDir, options.yarn ? 'yarn.lock' : 'package-lock.json')) || fs.existsSync(path.resolve(rootDir, 'npm-shrinkwrap.json')); + const lockfile = findLockFile(rootDir, pkgManager); const isOnGitHub = options.repoUrl && hostedGitInfo.fromUrl(options.repoUrl)?.type === 'github'; const testScript = options.testScript || 'test'; - const testCommand = options.testScript ? ['run', testScript] : [testScript]; if (options.releaseDraftOnly) { await releaseTaskHelper(options, pkg); @@ -105,10 +94,6 @@ const np = async (input = 'patch', options, {pkg, rootDir, isYarnBerry}) => { const shouldEnable2FA = options['2fa'] && options.availability.isAvailable && !options.availability.isUnknown && !pkg.private && !npm.isExternalRegistry(pkg); - // Yarn berry doesn't support git commiting/tagging, so use npm - const shouldUseYarnForVersioning = options.yarn === true && !isYarnBerry; - const shouldUseNpmForVersioning = options.yarn === false || isYarnBerry; - // To prevent the process from hanging due to watch mode (e.g. when running `vitest`) const ciEnvOptions = {env: {CI: 'true'}}; @@ -122,122 +107,78 @@ const np = async (input = 'patch', options, {pkg, rootDir, isYarnBerry}) => { title: 'Git', task: () => gitTasks(options), }, - ...runCleanup ? [ - { - title: 'Cleanup', - enabled: () => !hasLockFile, - task: () => deleteAsync('node_modules'), - }, - { - title: `Installing dependencies using ${pkgManagerName}`, - enabled: () => options.yarn === true, - task() { - const args = isYarnBerry ? ['install', '--immutable'] : ['install', '--frozen-lockfile', '--production=false']; - return exec('yarn', args).pipe( - catchError(async error => { - if ((!error.stderr.startsWith('error Your lockfile needs to be updated'))) { - return; - } - - if (await git.checkIfFileGitIgnored('yarn.lock')) { - return; - } - - throw new Error('yarn.lock file is outdated. Run yarn, commit the updated lockfile and try again.'); - }), - ); - }, - }, - { - title: 'Installing dependencies using npm', - enabled: () => options.yarn === false, - task() { - const args = hasLockFile ? ['ci'] : ['install', '--no-package-lock', '--no-production']; - return exec('npm', [...args, '--engine-strict']); - }, - }, - ] : [], - ...runTests ? [ - { - title: `Running tests using ${pkgManagerName}`, - enabled: () => options.yarn === false, - task: () => exec('npm', testCommand, ciEnvOptions), - }, - { - title: `Running tests using ${pkgManagerName}`, - enabled: () => options.yarn === true, - task: () => exec('yarn', testCommand, ciEnvOptions).pipe( - catchError(error => { - if (error.message.includes(`Command "${testScript}" not found`)) { - return []; - } - - return throwError(() => error); - }), - ), - }, - ] : [], { - title: `Bumping version using ${pkgManagerName}`, - enabled: () => shouldUseYarnForVersioning, - skip() { - if (options.preview) { - let previewText = `[Preview] Command not executed: yarn version --new-version ${input}`; - - if (options.message) { - previewText += ` --message '${options.message.replaceAll('%s', input)}'`; - } - - return `${previewText}.`; - } - }, + title: 'Cleanup', + enabled: () => runCleanup && !lockfile, + task: () => deleteAsync('node_modules'), + }, + { + title: `Installing dependencies using ${pkgManager.nickname}`, + enabled: () => runCleanup, task() { - const args = ['version', '--new-version', input]; + return exec(...pkgManager.installCommand).pipe( + catchError(async error => { + const isLockfileComplaint = error.stderr.startsWith('error Your lockfile needs to be updated'); - if (options.message) { - args.push('--message', options.message); - } + if (isLockfileComplaint && await git.checkIfFileGitIgnored(lockfile.filename)) { + return; + } - return exec('yarn', args); + throw new Error(`Lockfile is outdated. Run ${printCommand(pkgManager.installCommand)}, commit the updated lockfile and try again.`, { + cause: error, + }); + }), + ); }, }, { - title: 'Bumping version using npm', - enabled: () => shouldUseNpmForVersioning, + title: 'Running tests', + enabled: () => runTests, + task: () => exec(pkgManager.cli, ['run', testScript], ciEnvOptions), + }, + { + title: 'Bumping version', skip() { if (options.preview) { - let previewText = `[Preview] Command not executed: npm version ${input}`; + const [cli, args] = pkgManager.versionCommand(input); if (options.message) { - previewText += ` --message '${options.message.replaceAll('%s', input)}'`; + args.push('--message', options.message.replaceAll('%s', input)); } - return `${previewText}.`; + return `[Preview] Command not executed: ${printCommand([cli, args])}`; } }, task() { - const args = ['version', input]; + const [cli, args] = pkgManager.versionCommand(input); if (options.message) { args.push('--message', options.message); } - return exec('npm', args); + return exec(cli, args); }, }, ...options.runPublish ? [ { - title: `Publishing package using ${pkgManagerName}`, + title: 'Publishing package', skip() { if (options.preview) { - const args = getPackagePublishArguments(options, isYarnBerry); - return `[Preview] Command not executed: ${pkgManager} ${args.join(' ')}.`; + const args = getPackagePublishArguments(options); + return `[Preview] Command not executed: ${publishCli} ${args.join(' ')}.`; } }, task(context, task) { let hasError = false; - return publish(context, pkgManager, isYarnBerry, task, options) + return exec(publishCli, getPackagePublishArguments(options)) + .pipe( + catchError(error => handleNpmError(error, task, otp => { + context.otp = otp; + + return exec(publishCli, getPackagePublishArguments({...options, otp})); + })), + ) .pipe( catchError(async error => { hasError = true; diff --git a/source/npm/handle-npm-error.js b/source/npm/handle-npm-error.js index 9ad57ba7..9709e2cb 100644 --- a/source/npm/handle-npm-error.js +++ b/source/npm/handle-npm-error.js @@ -28,7 +28,7 @@ const handleNpmError = (error, task, message, executor) => { // https://stackoverflow.com/a/44862841/10292952 if ( error.code === 402 - || error.stderr.includes('npm ERR! 402 Payment Required') // Npm + || 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?'); diff --git a/source/npm/publish.js b/source/npm/publish.js index a35a2d5a..8a499239 100644 --- a/source/npm/publish.js +++ b/source/npm/publish.js @@ -1,9 +1,5 @@ -import {execa} from 'execa'; -import {from, catchError} from 'rxjs'; -import handleNpmError from './handle-npm-error.js'; - -export const getPackagePublishArguments = (options, isYarnBerry) => { - const args = isYarnBerry ? ['npm', 'publish'] : ['publish']; +export const getPackagePublishArguments = options => { + const args = ['publish']; if (options.contents) { args.push(options.contents); @@ -23,16 +19,3 @@ export const getPackagePublishArguments = (options, isYarnBerry) => { return args; }; - -const pkgPublish = (pkgManager, isYarnBerry, options) => execa(pkgManager, getPackagePublishArguments(options, isYarnBerry)); - -const publish = (context, pkgManager, isYarnBerry, task, options) => - from(pkgPublish(pkgManager, isYarnBerry, options)).pipe( - catchError(error => handleNpmError(error, task, otp => { - context.otp = otp; - - return pkgPublish(pkgManager, isYarnBerry, {...options, otp}); - })), - ); - -export default publish; diff --git a/source/npm/util.js b/source/npm/util.js index aa4bccbd..a3250832 100644 --- a/source/npm/util.js +++ b/source/npm/util.js @@ -144,18 +144,3 @@ export const getFilesToBePacked = async rootDir => { const {files} = JSON.parse(stdout).at(0); return files.map(file => file.path); }; - -export const getRegistryUrl = async (pkgManager, pkg) => { - if (pkgManager === 'yarn-berry') { - const {stdout} = await execa('yarn', ['config', 'get', 'npmRegistryServer']); - return stdout; - } - - const args = ['config', 'get', 'registry']; - if (isExternalRegistry(pkg)) { - args.push('--registry', pkg.publishConfig.registry); - } - - const {stdout} = await execa(pkgManager, args); - return stdout; -}; diff --git a/source/package-manager/configs.js b/source/package-manager/configs.js new file mode 100644 index 00000000..73f29f88 --- /dev/null +++ b/source/package-manager/configs.js @@ -0,0 +1,42 @@ +/** @type {import('./types.d.ts').PackageManagerConfig} */ + +export const npmConfig = { + cli: 'npm', + nickname: 'npm', + installCommand: ['npm', ['install', '--engine-strict']], + versionCommand: version => ['npm', ['version', version]], + getRegistryCommand: ['npm', ['config', 'get', 'registry']], + lockfiles: ['package-lock.json', 'npm-shrinkwrap.json'], +}; +/** @type {import('./types.d.ts').PackageManagerConfig} */ + +export const pnpmConfig = { + cli: 'pnpm', + nickname: 'pnpm', + installCommand: ['pnpm', ['install']], + versionCommand: version => ['pnpm', ['version', version]], + getRegistryCommand: ['pnpm', ['config', 'get', 'registry']], + lockfiles: ['pnpm-lock.yaml'], +}; +/** @type {import('./types.d.ts').PackageManagerConfig} */ + +export const yarnConfig = { + cli: 'yarn', + nickname: 'yarn', + installCommand: ['yarn', ['install', '--frozen-lockfile', '--production=false']], + getRegistryCommand: ['yarn', ['config', 'get', 'registry']], + versionCommand: version => ['yarn', ['version', '--new-version', version]], + lockfiles: ['yarn.lock'], +}; +/** @type {import('./types.d.ts').PackageManagerConfig} */ + +export const yarnBerryConfig = { + cli: 'yarn', + nickname: 'yarn-berry', + installCommand: ['yarn', ['install', '--immutable']], + versionCommand: version => ['yarn', ['version', '--new-version', version]], + publishCli: 'npm', // Yarn berry doesn't support git committing/tagging, so use npm + getRegistryCommand: ['yarn', ['config', 'get', 'npmRegistryServer']], + throwOnExternalRegistry: true, + lockfiles: ['yarn.lock'], +}; diff --git a/source/package-manager/index.js b/source/package-manager/index.js new file mode 100644 index 00000000..108b87d8 --- /dev/null +++ b/source/package-manager/index.js @@ -0,0 +1,69 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import semver from 'semver'; +import {npmConfig, yarnBerryConfig, pnpmConfig, yarnConfig} from './configs.js'; + +/** + * @param {string} rootDir + * @param {import('./types.d.ts').PackageManagerConfig} config + */ +export function findLockFile(rootDir, config) { + return config.lockfiles + .map(filename => ({filename, filepath: path.resolve(rootDir, filename)})) + .find(({filepath}) => fs.existsSync(filepath)); +} + +/** + * @param {string} rootDir + * @param {import('read-pkg').NormalizedPackageJson} pkg + */ +export function getPackageManagerConfig(rootDir, pkg) { + let config = configFromPackageManagerField(pkg); + + if (config === npmConfig && findLockFile(rootDir, config)) { + // If npm and lockfile exists, use npm ci to install packages + config = { + ...config, + installCommand: ['npm', ['ci', '--engine-strict']], + }; + } + + return config || configFromLockfile(rootDir) || npmConfig; +} + +/** @param {import('read-pkg').NormalizedPackageJson} pkg */ +function configFromPackageManagerField(pkg) { + if (typeof pkg.packageManager !== 'string') { + return npmConfig; + } + + const [packageManager, version] = pkg.packageManager.split('@'); + + if (packageManager === 'yarn' && version && semver.gte(version, '2.0.0')) { + return yarnBerryConfig; + } + + if (packageManager === 'npm') { + return npmConfig; + } + + if (packageManager === 'pnpm') { + return pnpmConfig; + } + + if (packageManager === 'yarn') { + return yarnConfig; + } + + throw new Error(`Invalid package manager: ${pkg.packageManager}`); +} + +/** @param {string} rootDir */ +function configFromLockfile(rootDir, options = [npmConfig, pnpmConfig, yarnConfig]) { + return options.find(config => findLockFile(rootDir, config)); +} + +/** @param {import('./types.d.ts').Command} command */ +export function printCommand([cli, args]) { + return `${cli} ${args.join(' ')}`; +} diff --git a/source/package-manager/types.d.ts b/source/package-manager/types.d.ts new file mode 100644 index 00000000..3b147d6b --- /dev/null +++ b/source/package-manager/types.d.ts @@ -0,0 +1,14 @@ +export type PackageManager = 'npm' | 'yarn' | 'pnpm'; + +export type Command = [cli: string, args: string[]]; + +export type PackageManagerConfig = { + cli: PackageManager; + nickname: string; + installCommand: Command; + versionCommand: (version: string) => [cli: string, args: string[]]; + publishCli?: string; + getRegistryCommand: Command; + throwOnExternalRegistry?: boolean; + lockfiles: string[]; +}; diff --git a/source/ui.js b/source/ui.js index 3fb1a607..67c04de6 100644 --- a/source/ui.js +++ b/source/ui.js @@ -4,10 +4,12 @@ import githubUrlFromGit from 'github-url-from-git'; import {htmlEscape} from 'escape-goat'; import isScoped from 'is-scoped'; import isInteractive from 'is-interactive'; +import {execa} from 'execa'; 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'; +import {getPackageManagerConfig} from './package-manager/index.js'; const printCommitLog = async (repoUrl, registryUrl, fromLatestTag, releaseBranch) => { const revision = fromLatestTag ? await git.latestTagOrFirstCommit() : await git.previousTagOrFirstCommit(); @@ -119,29 +121,19 @@ const checkNewFilesAndDependencies = async (pkg, rootDir) => { return answers.confirm; }; -// eslint-disable-next-line complexity -const ui = async (options, {pkg, rootDir, isYarnBerry = false}) => { +/** @type {(options: Options, context: {rootDir: string; pkg: import('read-pkg').NormalizedPackageJson}) => Promise} */ +const ui = async (options, {pkg, rootDir}) => { const oldVersion = pkg.version; const extraBaseUrls = ['gitlab.com']; const repoUrl = pkg.repository && githubUrlFromGit(pkg.repository.url, {extraBaseUrls}); - const pkgManager = (() => { - if (!options.yarn) { - return 'npm'; - } - - if (isYarnBerry) { - return 'yarn-berry'; - } - - return 'yarn'; - })(); + const packageManagerConfig = getPackageManagerConfig(rootDir, pkg); - if (isYarnBerry && npm.isExternalRegistry(pkg)) { - throw new Error('External registry is not yet supported with Yarn Berry'); + if (packageManagerConfig.throwOnExternalRegistry && npm.isExternalRegistry(pkg)) { + throw new Error(`External registry is not yet supported with ${packageManagerConfig.nickname}.`); } - const registryUrl = await npm.getRegistryUrl(pkgManager, pkg); + const {stdout: registryUrl} = await execa(...packageManagerConfig.getRegistryCommand); const releaseBranch = options.branch; if (options.runPublish) { @@ -241,7 +233,7 @@ const ui = async (options, {pkg, rootDir, isYarnBerry = false}) => { && !options.tag ); - const alreadyPublicScoped = isYarnBerry && options.runPublish && await util.getNpmPackageAccess(pkg.name) === 'public'; + const alreadyPublicScoped = packageManagerConfig.nickname === 'yarn-berry' && options.runPublish && await util.getNpmPackageAccess(pkg.name) === '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. diff --git a/source/yarn.js b/source/yarn.js deleted file mode 100644 index d4da4275..00000000 --- a/source/yarn.js +++ /dev/null @@ -1,16 +0,0 @@ -import semver from 'semver'; - -export function checkIfYarnBerry(pkg) { - if (typeof pkg.packageManager !== 'string') { - return false; - } - - const match = pkg.packageManager.match(/^yarn@(.+)$/); - if (!match) { - return false; - } - - const [, yarnVersion] = match; - const versionParsed = semver.parse(yarnVersion); - return (versionParsed.major >= 2); -} diff --git a/test/npm/util/get-registry-url.js b/test/npm/util/get-registry-url.js deleted file mode 100644 index 318804da..00000000 --- a/test/npm/util/get-registry-url.js +++ /dev/null @@ -1,49 +0,0 @@ -import test from 'ava'; -import {_createFixture} from '../../_helpers/stub-execa.js'; - -/** @type {ReturnType>} */ -const createFixture = _createFixture('../../../source/npm/util.js', import.meta.url); - -test('npm', createFixture, [{ - command: 'npm config get registry', - stdout: 'https://registry.npmjs.org/', -}], async ({t, testedModule: npm}) => { - t.is( - await npm.getRegistryUrl('npm', {}), - 'https://registry.npmjs.org/', - ); -}); - -test('yarn', createFixture, [{ - command: 'yarn config get registry', - stdout: 'https://registry.yarnpkg.com', -}], async ({t, testedModule: npm}) => { - t.is( - await npm.getRegistryUrl('yarn', {}), - 'https://registry.yarnpkg.com', - ); -}); - -test('yarn-berry', createFixture, [{ - command: 'yarn config get npmRegistryServer', - stdout: 'https://registry.yarnpkg.com', -}], async ({t, testedModule: npm}) => { - t.is( - await npm.getRegistryUrl('yarn-berry', {}), - 'https://registry.yarnpkg.com', - ); -}); - -test('external', createFixture, [{ - command: 'npm config get registry --registry http://my-internal-registry.local', - stdout: 'http://my-internal-registry.local', -}], async ({t, testedModule: npm}) => { - t.is( - await npm.getRegistryUrl('npm', { - publishConfig: { - registry: 'http://my-internal-registry.local', - }, - }), - 'http://my-internal-registry.local', - ); -}); diff --git a/test/ui/new-files-dependencies.js b/test/ui/new-files-dependencies.js index 7c548cd7..fc5eb792 100644 --- a/test/ui/new-files-dependencies.js +++ b/test/ui/new-files-dependencies.js @@ -43,7 +43,6 @@ const createFixture = test.macro(async (t, pkg, commands, expected) => { const {ui, logs: logsArray} = await mockInquirer({t, answers: {confirm: {confirm: false}}, mocks: { './npm/util.js': { - getRegistryUrl: sinon.stub().resolves(''), checkIgnoreStrategy: sinon.stub().resolves(), }, 'node:process': {cwd: () => temporaryDir}, diff --git a/test/ui/prompts/tags.js b/test/ui/prompts/tags.js index 52423b40..62ccf4ef 100644 --- a/test/ui/prompts/tags.js +++ b/test/ui/prompts/tags.js @@ -6,7 +6,6 @@ import {mockInquirer} from '../../_helpers/mock-inquirer.js'; const testUi = test.macro(async (t, {version, tags, answers}, assertions) => { const {ui, logs} = await mockInquirer({t, answers: {confirm: true, ...answers}, mocks: { './npm/util.js': { - getRegistryUrl: sinon.stub().resolves(''), checkIgnoreStrategy: sinon.stub().resolves(), prereleaseTags: sinon.stub().resolves(tags), }, diff --git a/test/ui/prompts/version.js b/test/ui/prompts/version.js index eb1021fb..8cb125e9 100644 --- a/test/ui/prompts/version.js +++ b/test/ui/prompts/version.js @@ -5,7 +5,6 @@ import {mockInquirer} from '../../_helpers/mock-inquirer.js'; const testUi = test.macro(async (t, {version, answers}, assertions) => { const {ui, logs} = await mockInquirer({t, answers: {confirm: true, ...answers}, mocks: { './npm/util.js': { - getRegistryUrl: sinon.stub().resolves(''), checkIgnoreStrategy: sinon.stub().resolves(), }, './util.js': { diff --git a/test/util/yarn.js b/test/util/yarn.js deleted file mode 100644 index bae4fd36..00000000 --- a/test/util/yarn.js +++ /dev/null @@ -1,15 +0,0 @@ -import test from 'ava'; -import {checkIfYarnBerry} from '../../source/yarn.js'; - -test('checkIfYarnBerry', t => { - t.is(checkIfYarnBerry({}), false); - t.is(checkIfYarnBerry({ - packageManager: 'npm', - }), false); - t.is(checkIfYarnBerry({ - packageManager: 'yarn@1.0.0', - }), false); - t.is(checkIfYarnBerry({ - packageManager: 'yarn@2.0.0', - }), true); -}); From a56a460e6ff2e125bcd4ce8e382ef5bbed159ef2 Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Wed, 31 Jan 2024 00:15:40 -0500 Subject: [PATCH 02/25] getTagVersionPrefix --- source/index.js | 8 ++++---- source/package-manager/types.d.ts | 1 + source/prerequisite-tasks.js | 4 ++-- source/release-task-helper.js | 4 ++-- source/util.js | 9 +++------ 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/source/index.js b/source/index.js index 03e99052..a339d974 100644 --- a/source/index.js +++ b/source/index.js @@ -47,7 +47,7 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => { const testScript = options.testScript || 'test'; if (options.releaseDraftOnly) { - await releaseTaskHelper(options, pkg); + await releaseTaskHelper(options, pkg, pkgManager); return pkg; } @@ -57,7 +57,7 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => { const rollback = onetime(async () => { console.log('\nPublish failed. Rolling back to the previous stateā€¦'); - const tagVersionPrefix = await util.getTagVersionPrefix(options); + const tagVersionPrefix = await util.getTagVersionPrefix(pkgManager); const latestTag = await git.latestTag(); const versionInLatestTag = latestTag.slice(tagVersionPrefix.length); @@ -101,7 +101,7 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => { { title: 'Prerequisite check', enabled: () => options.runPublish, - task: () => prerequisiteTasks(input, pkg, options), + task: () => prerequisiteTasks(input, pkg, options, pkgManager), }, { title: 'Git', @@ -230,7 +230,7 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => { } }, // TODO: parse version outside of index - task: () => releaseTaskHelper(options, pkg), + task: () => releaseTaskHelper(options, pkg, pkgManager), }] : [], ], { showSubtasks: false, diff --git a/source/package-manager/types.d.ts b/source/package-manager/types.d.ts index 3b147d6b..d1cbd983 100644 --- a/source/package-manager/types.d.ts +++ b/source/package-manager/types.d.ts @@ -9,6 +9,7 @@ export type PackageManagerConfig = { versionCommand: (version: string) => [cli: string, args: string[]]; publishCli?: string; getRegistryCommand: Command; + tagVersionPrefixCommand: Command; throwOnExternalRegistry?: boolean; lockfiles: string[]; }; diff --git a/source/prerequisite-tasks.js b/source/prerequisite-tasks.js index 4f9fc597..2d165c8e 100644 --- a/source/prerequisite-tasks.js +++ b/source/prerequisite-tasks.js @@ -6,7 +6,7 @@ import * as util from './util.js'; import * as git from './git-util.js'; import * as npm from './npm/util.js'; -const prerequisiteTasks = (input, pkg, options) => { +const prerequisiteTasks = (input, pkg, options, pkgManager) => { const isExternalRegistry = npm.isExternalRegistry(pkg); let newVersion; @@ -77,7 +77,7 @@ const prerequisiteTasks = (input, pkg, options) => { async task() { await git.fetch(); - const tagPrefix = await util.getTagVersionPrefix(options); + const tagPrefix = await util.getTagVersionPrefix(pkgManager); await git.verifyTagDoesNotExistOnRemote(`${tagPrefix}${newVersion}`); }, diff --git a/source/release-task-helper.js b/source/release-task-helper.js index ebaf2269..0d25ed7c 100644 --- a/source/release-task-helper.js +++ b/source/release-task-helper.js @@ -3,12 +3,12 @@ import newGithubReleaseUrl from 'new-github-release-url'; import {getTagVersionPrefix, getPreReleasePrefix} from './util.js'; import Version from './version.js'; -const releaseTaskHelper = async (options, pkg) => { +const releaseTaskHelper = async (options, pkg, pkgManager) => { const newVersion = options.releaseDraftOnly ? new Version(pkg.version) : new Version(pkg.version).setFrom(options.version.toString(), {prereleasePrefix: await getPreReleasePrefix(options)}); - const tag = await getTagVersionPrefix(options) + newVersion.toString(); + const tag = await getTagVersionPrefix(pkgManager) + newVersion.toString(); const url = newGithubReleaseUrl({ repoUrl: options.repoUrl, diff --git a/source/util.js b/source/util.js index 379aba31..6d86b5fd 100644 --- a/source/util.js +++ b/source/util.js @@ -60,13 +60,10 @@ export const linkifyCommitRange = (url, commitRange) => { return terminalLink(commitRange, `${url}/compare/${commitRange}`); }; -export const getTagVersionPrefix = pMemoize(async options => { - ow(options, ow.object.hasKeys('yarn')); - +/** @type {(config: import('./package-manager/types.js').PackageManagerConfig) => Promise} */ +export const getTagVersionPrefix = pMemoize(async config => { try { - const {stdout} = options.yarn - ? await execa('yarn', ['config', 'get', 'version-tag-prefix']) - : await execa('npm', ['config', 'get', 'tag-version-prefix']); + const {stdout} = await execa(...config.tagVersionPrefixCommand); return stdout; } catch { From a100281e90d14e4fca32265f2e960ee0baa4c5b5 Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Wed, 31 Jan 2024 00:19:05 -0500 Subject: [PATCH 03/25] don't assume npm --- source/package-manager/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/package-manager/index.js b/source/package-manager/index.js index 108b87d8..8daa3d71 100644 --- a/source/package-manager/index.js +++ b/source/package-manager/index.js @@ -34,7 +34,7 @@ export function getPackageManagerConfig(rootDir, pkg) { /** @param {import('read-pkg').NormalizedPackageJson} pkg */ function configFromPackageManagerField(pkg) { if (typeof pkg.packageManager !== 'string') { - return npmConfig; + return undefined; } const [packageManager, version] = pkg.packageManager.split('@'); From c577293b7530fdfc63f9de3f088959e7c951b383 Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Wed, 31 Jan 2024 12:32:22 -0500 Subject: [PATCH 04/25] exec(...) -> from(execa(...)) --- source/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/index.js b/source/index.js index a339d974..481ec754 100644 --- a/source/index.js +++ b/source/index.js @@ -1,7 +1,7 @@ import {execa} from 'execa'; import {deleteAsync} from 'del'; import Listr from 'listr'; -import {merge, catchError, filter, finalize} from 'rxjs'; +import {merge, catchError, filter, finalize, from} from 'rxjs'; import hostedGitInfo from 'hosted-git-info'; import onetime from 'onetime'; import {asyncExitHook} from 'exit-hook'; @@ -168,10 +168,11 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => { return `[Preview] Command not executed: ${publishCli} ${args.join(' ')}.`; } }, + /** @type {(context, task) => Listr.ListrTaskResult} */ task(context, task) { let hasError = false; - return exec(publishCli, getPackagePublishArguments(options)) + return from(execa(publishCli, getPackagePublishArguments(options))) .pipe( catchError(error => handleNpmError(error, task, otp => { context.otp = otp; From 9a214043e2220bda412c9dcf14774d596da57d0f Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Wed, 31 Jan 2024 12:44:01 -0500 Subject: [PATCH 05/25] tagVersionPrefix commands + jsdoc --- source/index.js | 2 +- source/package-manager/configs.js | 5 +++++ source/package-manager/types.d.ts | 10 ++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/source/index.js b/source/index.js index 481ec754..f2ddd6b3 100644 --- a/source/index.js +++ b/source/index.js @@ -33,7 +33,7 @@ const exec = (cmd, args, options) => { const np = async (input = 'patch', options, {pkg, rootDir}) => { const pkgManager = getPackageManagerConfig(rootDir, pkg); - const publishCli = options.publishCli || pkgManager.cli; + const publishCli = pkgManager.publishCli || pkgManager.cli; // TODO: Remove sometime far in the future if (options.skipCleanup) { diff --git a/source/package-manager/configs.js b/source/package-manager/configs.js index 73f29f88..d89cd6c2 100644 --- a/source/package-manager/configs.js +++ b/source/package-manager/configs.js @@ -6,6 +6,7 @@ export const npmConfig = { installCommand: ['npm', ['install', '--engine-strict']], versionCommand: version => ['npm', ['version', version]], getRegistryCommand: ['npm', ['config', 'get', 'registry']], + tagVersionPrefixCommand: ['npm', ['config', 'get', 'tag-version-prefix']], lockfiles: ['package-lock.json', 'npm-shrinkwrap.json'], }; /** @type {import('./types.d.ts').PackageManagerConfig} */ @@ -15,6 +16,8 @@ export const pnpmConfig = { nickname: 'pnpm', installCommand: ['pnpm', ['install']], versionCommand: version => ['pnpm', ['version', version]], + tagVersionPrefixCommand: ['pnpm', ['config', 'get', 'tag-version-prefix']], + publishCli: 'npm', // pnpm does git cleanliness checks, which np already did, and which fail because when publishing package.json has been updated getRegistryCommand: ['pnpm', ['config', 'get', 'registry']], lockfiles: ['pnpm-lock.yaml'], }; @@ -25,6 +28,7 @@ export const yarnConfig = { nickname: 'yarn', installCommand: ['yarn', ['install', '--frozen-lockfile', '--production=false']], getRegistryCommand: ['yarn', ['config', 'get', 'registry']], + tagVersionPrefixCommand: ['yarn', ['config', 'get', 'version-tag-prefix']], versionCommand: version => ['yarn', ['version', '--new-version', version]], lockfiles: ['yarn.lock'], }; @@ -35,6 +39,7 @@ export const yarnBerryConfig = { nickname: 'yarn-berry', installCommand: ['yarn', ['install', '--immutable']], versionCommand: version => ['yarn', ['version', '--new-version', version]], + tagVersionPrefixCommand: ['yarn', ['config', 'get', 'version-tag-prefix']], publishCli: 'npm', // Yarn berry doesn't support git committing/tagging, so use npm getRegistryCommand: ['yarn', ['config', 'get', 'npmRegistryServer']], throwOnExternalRegistry: true, diff --git a/source/package-manager/types.d.ts b/source/package-manager/types.d.ts index d1cbd983..a7beb636 100644 --- a/source/package-manager/types.d.ts +++ b/source/package-manager/types.d.ts @@ -1,15 +1,25 @@ 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) */ nickname: string; + /** How to install packages, e.g. `["npm", ["install"]]` */ installCommand: Command; + /** Given a verison string, return a version command e.g. `version => ["npm", ["version", version]]` */ versionCommand: (version: string) => [cli: string, args: string[]]; + /** Use a different CLI to do the actual publish. Defaults to `cli`. */ publishCli?: string; + /** CLI command which is expected to output the npm registry to use, e.g. `['npm', ['config', 'get', 'registry']]` */ getRegistryCommand: Command; + /** */ 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[]; }; From 6a58244afa28fd7d3f8c4dc4b6457c22d74e82de Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Wed, 31 Jan 2024 13:27:28 -0500 Subject: [PATCH 06/25] more yarn stragglers --- source/prerequisite-tasks.js | 15 +++++---------- source/release-task-helper.js | 2 +- source/ui.js | 12 ++++++------ source/util.js | 8 +++----- 4 files changed, 15 insertions(+), 22 deletions(-) diff --git a/source/prerequisite-tasks.js b/source/prerequisite-tasks.js index 2d165c8e..ebc969b9 100644 --- a/source/prerequisite-tasks.js +++ b/source/prerequisite-tasks.js @@ -17,16 +17,11 @@ const prerequisiteTasks = (input, pkg, options, pkgManager) => { task: async () => npm.checkConnection(), }, { - title: 'Check npm version', - task: async () => npm.verifyRecentNpmVersion(), - }, - { - title: 'Check yarn version', - enabled: () => options.yarn === true, - async task() { - const {stdout: yarnVersion} = await execa('yarn', ['--version']); - util.validateEngineVersionSatisfies('yarn', yarnVersion); - }, + title: `Check ${pkgManager.cli} version`, + task: async () => { + const {stdout: version} = await execa(pkgManager.cli, ['--version']); + util.validateEngineVersionSatisfies(pkgManager.cli, version); + } }, { title: 'Verify user is authenticated', diff --git a/source/release-task-helper.js b/source/release-task-helper.js index 0d25ed7c..7f09c9f5 100644 --- a/source/release-task-helper.js +++ b/source/release-task-helper.js @@ -6,7 +6,7 @@ import Version from './version.js'; const releaseTaskHelper = async (options, pkg, pkgManager) => { const newVersion = options.releaseDraftOnly ? new Version(pkg.version) - : new Version(pkg.version).setFrom(options.version.toString(), {prereleasePrefix: await getPreReleasePrefix(options)}); + : new Version(pkg.version).setFrom(options.version.toString(), {prereleasePrefix: await getPreReleasePrefix(pkgManager)}); const tag = await getTagVersionPrefix(pkgManager) + newVersion.toString(); diff --git a/source/ui.js b/source/ui.js index 67c04de6..8174a134 100644 --- a/source/ui.js +++ b/source/ui.js @@ -127,13 +127,13 @@ const ui = async (options, {pkg, rootDir}) => { const extraBaseUrls = ['gitlab.com']; const repoUrl = pkg.repository && githubUrlFromGit(pkg.repository.url, {extraBaseUrls}); - const packageManagerConfig = getPackageManagerConfig(rootDir, pkg); + const pkgManager = getPackageManagerConfig(rootDir, pkg); - if (packageManagerConfig.throwOnExternalRegistry && npm.isExternalRegistry(pkg)) { - throw new Error(`External registry is not yet supported with ${packageManagerConfig.nickname}.`); + if (pkgManager.throwOnExternalRegistry && npm.isExternalRegistry(pkg)) { + throw new Error(`External registry is not yet supported with ${pkgManager.nickname}.`); } - const {stdout: registryUrl} = await execa(...packageManagerConfig.getRegistryCommand); + const {stdout: registryUrl} = await execa(...pkgManager.getRegistryCommand); const releaseBranch = options.branch; if (options.runPublish) { @@ -152,7 +152,7 @@ const ui = async (options, {pkg, rootDir}) => { console.log(`\nCreate a release draft on GitHub for ${chalk.bold.magenta(pkg.name)} ${chalk.dim(`(current: ${oldVersion})`)}\n`); } else { const versionText = options.version - ? chalk.dim(`(current: ${oldVersion}, next: ${new Version(oldVersion, options.version, {prereleasePrefix: await util.getPreReleasePrefix(options)}).format()})`) + ? chalk.dim(`(current: ${oldVersion}, next: ${new Version(oldVersion, options.version, {prereleasePrefix: await util.getPreReleasePrefix(pkgManager)}).format()})`) : chalk.dim(`(current: ${oldVersion})`); console.log(`\nPublish a new version of ${chalk.bold.magenta(pkg.name)} ${versionText}\n`); @@ -233,7 +233,7 @@ const ui = async (options, {pkg, rootDir}) => { && !options.tag ); - const alreadyPublicScoped = packageManagerConfig.nickname === 'yarn-berry' && options.runPublish && await util.getNpmPackageAccess(pkg.name) === 'public'; + const alreadyPublicScoped = pkgManager.nickname === 'yarn-berry' && options.runPublish && await util.getNpmPackageAccess(pkg.name) === '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. diff --git a/source/util.js b/source/util.js index 6d86b5fd..d64c269a 100644 --- a/source/util.js +++ b/source/util.js @@ -128,12 +128,10 @@ export const getNewDependencies = async (newPkg, rootDir) => { return newDependencies; }; -export const getPreReleasePrefix = pMemoize(async options => { - ow(options, ow.object.hasKeys('yarn')); - +/** @type {(config: import('./package-manager/types.js').PackageManagerConfig) => Promise} */ +export const getPreReleasePrefix = pMemoize(async config => { try { - const packageManager = options.yarn ? 'yarn' : 'npm'; - const {stdout} = await execa(packageManager, ['config', 'get', 'preid']); + const {stdout} = await execa(config.cli, ['config', 'get', 'preid']); return stdout === 'undefined' ? '' : stdout; } catch { From 2510e7048fdb1c6c96453938b40fb1a0568d13af Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Wed, 31 Jan 2024 13:39:10 -0500 Subject: [PATCH 07/25] xo --fix --- source/package-manager/configs.js | 2 +- source/prerequisite-tasks.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/package-manager/configs.js b/source/package-manager/configs.js index d89cd6c2..159c29de 100644 --- a/source/package-manager/configs.js +++ b/source/package-manager/configs.js @@ -17,7 +17,7 @@ export const pnpmConfig = { installCommand: ['pnpm', ['install']], versionCommand: version => ['pnpm', ['version', version]], tagVersionPrefixCommand: ['pnpm', ['config', 'get', 'tag-version-prefix']], - publishCli: 'npm', // pnpm does git cleanliness checks, which np already did, and which fail because when publishing package.json has been updated + publishCli: 'npm', // Pnpm does git cleanliness checks, which np already did, and which fail because when publishing package.json has been updated getRegistryCommand: ['pnpm', ['config', 'get', 'registry']], lockfiles: ['pnpm-lock.yaml'], }; diff --git a/source/prerequisite-tasks.js b/source/prerequisite-tasks.js index ebc969b9..529821a6 100644 --- a/source/prerequisite-tasks.js +++ b/source/prerequisite-tasks.js @@ -18,10 +18,10 @@ const prerequisiteTasks = (input, pkg, options, pkgManager) => { }, { title: `Check ${pkgManager.cli} version`, - task: async () => { + async task() { const {stdout: version} = await execa(pkgManager.cli, ['--version']); util.validateEngineVersionSatisfies(pkgManager.cli, version); - } + }, }, { title: 'Verify user is authenticated', From b56ed7b1bc0525811c96049d0a8ab60addf4d0aa Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Wed, 31 Jan 2024 13:41:01 -0500 Subject: [PATCH 08/25] a --- source/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/index.js b/source/index.js index f2ddd6b3..d6cdc31f 100644 --- a/source/index.js +++ b/source/index.js @@ -177,7 +177,7 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => { catchError(error => handleNpmError(error, task, otp => { context.otp = otp; - return exec(publishCli, getPackagePublishArguments({...options, otp})); + return execa(publishCli, getPackagePublishArguments({...options, otp})); })), ) .pipe( From 3f2fbf5ccd518bffa3342cd70549f45b288a6511 Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Wed, 31 Jan 2024 13:42:28 -0500 Subject: [PATCH 09/25] rm unused import --- source/util.js | 1 - 1 file changed, 1 deletion(-) diff --git a/source/util.js b/source/util.js index d64c269a..d2d45c2b 100644 --- a/source/util.js +++ b/source/util.js @@ -7,7 +7,6 @@ import issueRegex from 'issue-regex'; import terminalLink from 'terminal-link'; import {execa} from 'execa'; import pMemoize from 'p-memoize'; -import ow from 'ow'; import chalk from 'chalk'; import Version from './version.js'; import * as git from './git-util.js'; From 61b6cf2a75f8c82d377543806b18bfeabc075976 Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Thu, 1 Feb 2024 20:45:04 -0500 Subject: [PATCH 10/25] Simplify lockfile update check --- source/cli-implementation.js | 2 ++ source/git-util.js | 15 --------------- source/index.js | 27 +++++++++++---------------- source/package-manager/configs.js | 17 +++++++++++------ source/package-manager/index.js | 10 +++------- source/ui.js | 5 ++++- 6 files changed, 31 insertions(+), 45 deletions(-) diff --git a/source/cli-implementation.js b/source/cli-implementation.js index 7f5204d9..2e36ad8a 100755 --- a/source/cli-implementation.js +++ b/source/cli-implementation.js @@ -102,6 +102,8 @@ export const cli = meow(` updateNotifier({pkg: cli.pkg}).notify(); +/** @typedef {Awaited>['options']} Options */ + export async function getOptions() { const {pkg, rootDir} = await util.readPkg(cli.flags.contents); diff --git a/source/git-util.js b/source/git-util.js index cfe36356..037d4a27 100644 --- a/source/git-util.js +++ b/source/git-util.js @@ -270,18 +270,3 @@ export const verifyRecentGitVersion = async () => { const installedVersion = await gitVersion(); util.validateEngineVersionSatisfies('git', installedVersion); }; - -export const checkIfFileGitIgnored = async pathToFile => { - try { - const {stdout} = await execa('git', ['check-ignore', pathToFile]); - return Boolean(stdout); - } catch (error) { - // If file is not ignored, `git check-ignore` throws an empty error and exits. - // Check that and return false so as not to throw an unwanted error. - if (error.stdout === '' && error.stderr === '') { - return false; - } - - throw error; - } -}; diff --git a/source/index.js b/source/index.js index d6cdc31f..c6084f08 100644 --- a/source/index.js +++ b/source/index.js @@ -27,7 +27,7 @@ const exec = (cmd, args, options) => { /** * @param {string} input - * @param {any | typeof import('./cli-implementation.js').cli['unnormalizedFlags']} options + * @param {import('./cli-implementation.js').Options} options * @param {{pkg: import('read-pkg').NormalizedPackageJson; rootDir: string}} context */ @@ -115,21 +115,16 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => { { title: `Installing dependencies using ${pkgManager.nickname}`, enabled: () => runCleanup, - task() { - return exec(...pkgManager.installCommand).pipe( - catchError(async error => { - const isLockfileComplaint = error.stderr.startsWith('error Your lockfile needs to be updated'); - - if (isLockfileComplaint && await git.checkIfFileGitIgnored(lockfile.filename)) { - return; - } - - throw new Error(`Lockfile is outdated. Run ${printCommand(pkgManager.installCommand)}, commit the updated lockfile and try again.`, { - cause: error, - }); - }), - ); - }, + task: () => new Listr([ + { + title: 'Running install command', + task: () => exec(...pkgManager.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', diff --git a/source/package-manager/configs.js b/source/package-manager/configs.js index 159c29de..78a8077f 100644 --- a/source/package-manager/configs.js +++ b/source/package-manager/configs.js @@ -1,16 +1,21 @@ /** @type {import('./types.d.ts').PackageManagerConfig} */ - export const npmConfig = { cli: 'npm', nickname: 'npm', - installCommand: ['npm', ['install', '--engine-strict']], + installCommand: ['npm', ['ci', '--engine-strict']], versionCommand: version => ['npm', ['version', version]], getRegistryCommand: ['npm', ['config', 'get', 'registry']], tagVersionPrefixCommand: ['npm', ['config', 'get', 'tag-version-prefix']], lockfiles: ['package-lock.json', 'npm-shrinkwrap.json'], }; + /** @type {import('./types.d.ts').PackageManagerConfig} */ +export const npmConfigNoLockfile = { + ...npmConfig, + installCommand: ['npm', ['install', '--engine-strict']], +}; +/** @type {import('./types.d.ts').PackageManagerConfig} */ export const pnpmConfig = { cli: 'pnpm', nickname: 'pnpm', @@ -21,23 +26,23 @@ export const pnpmConfig = { getRegistryCommand: ['pnpm', ['config', 'get', 'registry']], lockfiles: ['pnpm-lock.yaml'], }; -/** @type {import('./types.d.ts').PackageManagerConfig} */ +/** @type {import('./types.d.ts').PackageManagerConfig} */ export const yarnConfig = { cli: 'yarn', nickname: 'yarn', - installCommand: ['yarn', ['install', '--frozen-lockfile', '--production=false']], + installCommand: ['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} */ +/** @type {import('./types.d.ts').PackageManagerConfig} */ export const yarnBerryConfig = { cli: 'yarn', nickname: 'yarn-berry', - installCommand: ['yarn', ['install', '--immutable']], + installCommand: ['yarn', ['install']], versionCommand: version => ['yarn', ['version', '--new-version', version]], tagVersionPrefixCommand: ['yarn', ['config', 'get', 'version-tag-prefix']], publishCli: 'npm', // Yarn berry doesn't support git committing/tagging, so use npm diff --git a/source/package-manager/index.js b/source/package-manager/index.js index 8daa3d71..373f0cfe 100644 --- a/source/package-manager/index.js +++ b/source/package-manager/index.js @@ -1,7 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; import semver from 'semver'; -import {npmConfig, yarnBerryConfig, pnpmConfig, yarnConfig} from './configs.js'; +import {npmConfig, yarnBerryConfig, pnpmConfig, yarnConfig, npmConfigNoLockfile} from './configs.js'; /** * @param {string} rootDir @@ -20,12 +20,8 @@ export function findLockFile(rootDir, config) { export function getPackageManagerConfig(rootDir, pkg) { let config = configFromPackageManagerField(pkg); - if (config === npmConfig && findLockFile(rootDir, config)) { - // If npm and lockfile exists, use npm ci to install packages - config = { - ...config, - installCommand: ['npm', ['ci', '--engine-strict']], - }; + if (config === npmConfig && !findLockFile(rootDir, config)) { + config = npmConfigNoLockfile } return config || configFromLockfile(rootDir) || npmConfig; diff --git a/source/ui.js b/source/ui.js index 8174a134..d3976437 100644 --- a/source/ui.js +++ b/source/ui.js @@ -121,7 +121,10 @@ const checkNewFilesAndDependencies = async (pkg, rootDir) => { return answers.confirm; }; -/** @type {(options: Options, context: {rootDir: string; pkg: import('read-pkg').NormalizedPackageJson}) => Promise} */ +/** + * @param {typeof import('./cli-implementation.js').cli['flags']} options + * @param {{pkg: import('read-pkg').NormalizedPackageJson; rootDir: string}} context + */ const ui = async (options, {pkg, rootDir}) => { const oldVersion = pkg.version; const extraBaseUrls = ['gitlab.com']; From 836f0ff33f79cbb15f2f96e5fcc2912dfd0e926c Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Thu, 1 Feb 2024 20:49:08 -0500 Subject: [PATCH 11/25] match old args --- source/package-manager/configs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/package-manager/configs.js b/source/package-manager/configs.js index 78a8077f..a7bed9fb 100644 --- a/source/package-manager/configs.js +++ b/source/package-manager/configs.js @@ -12,7 +12,7 @@ export const npmConfig = { /** @type {import('./types.d.ts').PackageManagerConfig} */ export const npmConfigNoLockfile = { ...npmConfig, - installCommand: ['npm', ['install', '--engine-strict']], + installCommand: ['npm', ['install', '--no-package-lock', '--no-production', '--engine-strict']], }; /** @type {import('./types.d.ts').PackageManagerConfig} */ From 10243c530f0ece91e0dcdc176b251748fb433e9a Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Thu, 1 Feb 2024 20:49:25 -0500 Subject: [PATCH 12/25] xo --fix --- source/package-manager/index.js | 2 +- source/ui.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/package-manager/index.js b/source/package-manager/index.js index 373f0cfe..02b3fb57 100644 --- a/source/package-manager/index.js +++ b/source/package-manager/index.js @@ -21,7 +21,7 @@ export function getPackageManagerConfig(rootDir, pkg) { let config = configFromPackageManagerField(pkg); if (config === npmConfig && !findLockFile(rootDir, config)) { - config = npmConfigNoLockfile + config = npmConfigNoLockfile; } return config || configFromLockfile(rootDir) || npmConfig; diff --git a/source/ui.js b/source/ui.js index d3976437..73965a39 100644 --- a/source/ui.js +++ b/source/ui.js @@ -122,7 +122,7 @@ const checkNewFilesAndDependencies = async (pkg, rootDir) => { }; /** - * @param {typeof import('./cli-implementation.js').cli['flags']} options + * @param {typeof import('./cli-implementation.js').cli['flags']} options * @param {{pkg: import('read-pkg').NormalizedPackageJson; rootDir: string}} context */ const ui = async (options, {pkg, rootDir}) => { From e8bc4b92da13cd188c7722ee44628fbe0eaf43e0 Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Thu, 8 Feb 2024 16:02:08 -0500 Subject: [PATCH 13/25] nickname -> id --- source/index.js | 2 +- source/package-manager/configs.js | 8 ++++---- source/package-manager/types.d.ts | 2 +- source/ui.js | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/source/index.js b/source/index.js index c6084f08..e96c0891 100644 --- a/source/index.js +++ b/source/index.js @@ -113,7 +113,7 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => { task: () => deleteAsync('node_modules'), }, { - title: `Installing dependencies using ${pkgManager.nickname}`, + title: `Installing dependencies using ${pkgManager.id}`, enabled: () => runCleanup, task: () => new Listr([ { diff --git a/source/package-manager/configs.js b/source/package-manager/configs.js index a7bed9fb..f3c52638 100644 --- a/source/package-manager/configs.js +++ b/source/package-manager/configs.js @@ -1,7 +1,7 @@ /** @type {import('./types.d.ts').PackageManagerConfig} */ export const npmConfig = { cli: 'npm', - nickname: 'npm', + id: 'npm', installCommand: ['npm', ['ci', '--engine-strict']], versionCommand: version => ['npm', ['version', version]], getRegistryCommand: ['npm', ['config', 'get', 'registry']], @@ -18,7 +18,7 @@ export const npmConfigNoLockfile = { /** @type {import('./types.d.ts').PackageManagerConfig} */ export const pnpmConfig = { cli: 'pnpm', - nickname: 'pnpm', + id: 'pnpm', installCommand: ['pnpm', ['install']], versionCommand: version => ['pnpm', ['version', version]], tagVersionPrefixCommand: ['pnpm', ['config', 'get', 'tag-version-prefix']], @@ -30,7 +30,7 @@ export const pnpmConfig = { /** @type {import('./types.d.ts').PackageManagerConfig} */ export const yarnConfig = { cli: 'yarn', - nickname: 'yarn', + id: 'yarn', installCommand: ['yarn', ['install', '--production=false']], getRegistryCommand: ['yarn', ['config', 'get', 'registry']], tagVersionPrefixCommand: ['yarn', ['config', 'get', 'version-tag-prefix']], @@ -41,7 +41,7 @@ export const yarnConfig = { /** @type {import('./types.d.ts').PackageManagerConfig} */ export const yarnBerryConfig = { cli: 'yarn', - nickname: 'yarn-berry', + id: 'yarn-berry', installCommand: ['yarn', ['install']], versionCommand: version => ['yarn', ['version', '--new-version', version]], tagVersionPrefixCommand: ['yarn', ['config', 'get', 'version-tag-prefix']], diff --git a/source/package-manager/types.d.ts b/source/package-manager/types.d.ts index a7beb636..2e5d8a81 100644 --- a/source/package-manager/types.d.ts +++ b/source/package-manager/types.d.ts @@ -7,7 +7,7 @@ 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) */ - nickname: string; + id: string; /** How to install packages, e.g. `["npm", ["install"]]` */ installCommand: Command; /** Given a verison string, return a version command e.g. `version => ["npm", ["version", version]]` */ diff --git a/source/ui.js b/source/ui.js index 73965a39..6ab8f022 100644 --- a/source/ui.js +++ b/source/ui.js @@ -133,7 +133,7 @@ const ui = async (options, {pkg, rootDir}) => { const pkgManager = getPackageManagerConfig(rootDir, pkg); if (pkgManager.throwOnExternalRegistry && npm.isExternalRegistry(pkg)) { - throw new Error(`External registry is not yet supported with ${pkgManager.nickname}.`); + throw new Error(`External registry is not yet supported with ${pkgManager.id}.`); } const {stdout: registryUrl} = await execa(...pkgManager.getRegistryCommand); @@ -236,7 +236,7 @@ const ui = async (options, {pkg, rootDir}) => { && !options.tag ); - const alreadyPublicScoped = pkgManager.nickname === 'yarn-berry' && options.runPublish && await util.getNpmPackageAccess(pkg.name) === 'public'; + const alreadyPublicScoped = pkgManager.id === 'yarn-berry' && options.runPublish && await util.getNpmPackageAccess(pkg.name) === '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. From 0cab44f1dae2e0c16235c65c90ed66f3b83690a0 Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Thu, 8 Feb 2024 18:08:43 -0500 Subject: [PATCH 14/25] start making tests pass --- source/cli-implementation.js | 2 +- source/package-manager/index.js | 2 +- source/util.js | 5 ++++ test/cli.js | 2 +- test/git-util/check-if-file-git-ignored.js | 20 ------------- test/index.js | 13 ++++++-- test/tasks/prerequisite-tasks.js | 35 +++++++++++----------- test/ui/prompts/tags.js | 3 ++ test/util/get-pre-release-prefix.js | 14 ++++----- test/util/get-tag-version-prefix.js | 11 +++---- 10 files changed, 52 insertions(+), 55 deletions(-) delete mode 100644 test/git-util/check-if-file-git-ignored.js diff --git a/source/cli-implementation.js b/source/cli-implementation.js index 2e36ad8a..57b03886 100755 --- a/source/cli-implementation.js +++ b/source/cli-implementation.js @@ -35,7 +35,7 @@ export const cli = meow(` --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) + --package-manager Use a specific package manager (default: 'packageManager' field in package.json) Examples $ np diff --git a/source/package-manager/index.js b/source/package-manager/index.js index 02b3fb57..f73c04bd 100644 --- a/source/package-manager/index.js +++ b/source/package-manager/index.js @@ -9,7 +9,7 @@ import {npmConfig, yarnBerryConfig, pnpmConfig, yarnConfig, npmConfigNoLockfile} */ export function findLockFile(rootDir, config) { return config.lockfiles - .map(filename => ({filename, filepath: path.resolve(rootDir, filename)})) + .map(filename => ({filename, filepath: path.resolve(rootDir || '.', filename)})) .find(({filepath}) => fs.existsSync(filepath)); } diff --git a/source/util.js b/source/util.js index d2d45c2b..c686420c 100644 --- a/source/util.js +++ b/source/util.js @@ -1,3 +1,4 @@ +import ow from 'ow' import process from 'node:process'; import {fileURLToPath} from 'node:url'; import path from 'node:path'; @@ -61,6 +62,8 @@ export const linkifyCommitRange = (url, commitRange) => { /** @type {(config: import('./package-manager/types.js').PackageManagerConfig) => Promise} */ export const getTagVersionPrefix = pMemoize(async config => { + ow(config, ow.object.hasKeys('tagVersionPrefixCommand')); + try { const {stdout} = await execa(...config.tagVersionPrefixCommand); @@ -129,6 +132,8 @@ export const getNewDependencies = async (newPkg, rootDir) => { /** @type {(config: import('./package-manager/types.js').PackageManagerConfig) => Promise} */ export const getPreReleasePrefix = pMemoize(async config => { + ow(config, ow.object.hasKeys('cli')); + try { const {stdout} = await execa(config.cli, ['config', 'get', 'preid']); diff --git a/test/cli.js b/test/cli.js index c898fd67..2406bd82 100644 --- a/test/cli.js +++ b/test/cli.js @@ -24,13 +24,13 @@ test('flags: --help', cliPasses, cli, '--help', [ '--no-publish Skips publishing', '--preview Show tasks without actually executing them', '--tag Publish under a given dist-tag', - '--no-yarn Don\'t use Yarn', '--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', '--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)', '', 'Examples', '$ np', diff --git a/test/git-util/check-if-file-git-ignored.js b/test/git-util/check-if-file-git-ignored.js deleted file mode 100644 index 1c5ba52b..00000000 --- a/test/git-util/check-if-file-git-ignored.js +++ /dev/null @@ -1,20 +0,0 @@ -import test from 'ava'; -import {temporaryDirectory} from 'tempy'; -import {checkIfFileGitIgnored} from '../../source/git-util.js'; - -test('returns true for ignored files', async t => { - t.true(await checkIfFileGitIgnored('yarn.lock')); -}); - -test('returns false for non-ignored files', async t => { - t.false(await checkIfFileGitIgnored('package.json')); -}); - -test('errors if path is outside of repo', async t => { - const temporary = temporaryDirectory(); - - await t.throwsAsync( - checkIfFileGitIgnored(`${temporary}/file.js`), - {message: /fatal:/}, - ); -}); diff --git a/test/index.js b/test/index.js index bf3ac759..94eba192 100644 --- a/test/index.js +++ b/test/index.js @@ -40,18 +40,24 @@ test('errors on too low version', npFails, /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: ''} +); + 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({pipe: 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': sinon.stub().returns({pipe: sinon.stub()}), @@ -74,18 +80,19 @@ test('skip enabling 2FA if the `2fa` option is false', async t => { /** @type {typeof np} */ const npMock = await esmock('../source/index.js', { del: {deleteAsync: sinon.stub()}, - execa: {execa: sinon.stub().returns({pipe: 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': sinon.stub().returns({pipe: sinon.stub()}), }); - await t.notThrowsAsync(npMock('1.0.0', { + await (npMock('1.0.0', { ...defaultOptions, availability: { isAvailable: true, diff --git a/test/tasks/prerequisite-tasks.js b/test/tasks/prerequisite-tasks.js index 2363ff0a..338b95a4 100644 --- a/test/tasks/prerequisite-tasks.js +++ b/test/tasks/prerequisite-tasks.js @@ -5,6 +5,7 @@ import {npPkg} from '../../source/util.js'; import {SilentRenderer} from '../_helpers/listr-renderer.js'; import {_createFixture} from '../_helpers/stub-execa.js'; import {run, assertTaskFailed, assertTaskDisabled} from '../_helpers/listr.js'; +import {npmConfig, yarnConfig} from '../../source/package-manager/configs.js' /** @type {ReturnType>} */ const createFixture = _createFixture('../../source/prerequisite-tasks.js', import.meta.url); @@ -21,7 +22,7 @@ test.serial('public-package published on npm registry: should fail when npm regi stderr: 'failed', }], async ({t, testedModule: prerequisiteTasks}) => { await t.throwsAsync( - run(prerequisiteTasks('1.0.0', {name: 'test'}, {})), + run(prerequisiteTasks('1.0.0', {name: 'test'}, {}, npmConfig)), {message: 'Connection to npm registry failed'}, ); @@ -33,7 +34,7 @@ test.serial('private package: should disable task pinging npm registry', createF stdout: '', }], async ({t, testedModule: prerequisiteTasks}) => { await t.notThrowsAsync( - run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0', private: true}, {yarn: false})), + run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0', private: true}, {}, npmConfig)), ); assertTaskDisabled(t, 'Ping npm registry'); @@ -44,7 +45,7 @@ test.serial('external registry: should disable task pinging npm registry', creat stdout: '', }], async ({t, testedModule: prerequisiteTasks}) => { await t.notThrowsAsync( - run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0', publishConfig: {registry: 'http://my.io'}}, {yarn: false})), + run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0', publishConfig: {registry: 'http://my.io'}}, {}, npmConfig)), ); assertTaskDisabled(t, 'Ping npm registry'); @@ -63,7 +64,7 @@ test.serial('should fail when npm version does not match range in `package.json` const depRange = npPkg.engines.npm; await t.throwsAsync( - run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {yarn: false})), + run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {}, npmConfig)), {message: `\`np\` requires npm ${depRange}`}, ); @@ -83,7 +84,7 @@ test.serial('should fail when yarn version does not match range in `package.json const depRange = npPkg.engines.yarn; await t.throwsAsync( - run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {yarn: true})), + run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {}, yarnConfig)), {message: `\`np\` requires yarn ${depRange}`}, ); @@ -103,7 +104,7 @@ test.serial('should fail when user is not authenticated at npm registry', create process.env.NODE_ENV = 'P'; await t.throwsAsync( - run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {yarn: false})), + run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {}, npmConfig)), {message: 'You do not have write permissions required to publish this package.'}, ); @@ -125,7 +126,7 @@ test.serial('should fail when user is not authenticated at external registry', c process.env.NODE_ENV = 'P'; await t.throwsAsync( - run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0', publishConfig: {registry: 'http://my.io'}}, {yarn: false})), + run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0', publishConfig: {registry: 'http://my.io'}}, {}, npmConfig)), {message: 'You do not have write permissions required to publish this package.'}, ); @@ -143,7 +144,7 @@ test.serial('private package: should disable task `verify user is authenticated` process.env.NODE_ENV = 'P'; await t.notThrowsAsync( - run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0', private: true}, {yarn: false})), + run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0', private: true}, {}, npmConfig)), ); process.env.NODE_ENV = 'test'; @@ -158,7 +159,7 @@ test.serial('should fail when git version does not match range in `package.json` const depRange = npPkg.engines.git; await t.throwsAsync( - run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {yarn: false})), + run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {}, npmConfig)), {message: `\`np\` requires git ${depRange}`}, ); @@ -172,7 +173,7 @@ test.serial('should fail when git remote does not exist', createFixture, [{ stderr: 'not found', }], async ({t, testedModule: prerequisiteTasks}) => { await t.throwsAsync( - run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {yarn: false})), + run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {}, npmConfig)), {message: 'not found'}, ); @@ -181,7 +182,7 @@ test.serial('should fail when git remote does not exist', createFixture, [{ test.serial('should fail when version is invalid', async t => { await t.throwsAsync( - run(actualPrerequisiteTasks('DDD', {name: 'test', version: '1.0.0'}, {yarn: false})), + run(actualPrerequisiteTasks('DDD', {name: 'test', version: '1.0.0'}, {}, npmConfig)), {message: 'New version DDD should either be one of patch, minor, major, prepatch, preminor, premajor, prerelease, or a valid SemVer version.'}, ); @@ -190,7 +191,7 @@ test.serial('should fail when version is invalid', async t => { test.serial('should fail when version is lower than latest version', async t => { await t.throwsAsync( - run(actualPrerequisiteTasks('0.1.0', {name: 'test', version: '1.0.0'}, {yarn: false})), + run(actualPrerequisiteTasks('0.1.0', {name: 'test', version: '1.0.0'}, {}, npmConfig)), {message: 'New version 0.1.0 should be higher than current version 1.0.0.'}, ); @@ -199,7 +200,7 @@ test.serial('should fail when version is lower than latest version', async t => test.serial('should fail when prerelease version of public package without dist tag given', async t => { await t.throwsAsync( - run(actualPrerequisiteTasks('2.0.0-1', {name: 'test', version: '1.0.0'}, {yarn: false})), + run(actualPrerequisiteTasks('2.0.0-1', {name: 'test', version: '1.0.0'}, {}, npmConfig)), {message: 'You must specify a dist-tag using --tag when publishing a pre-release version. This prevents accidentally tagging unstable versions as "latest". https://docs.npmjs.com/cli/dist-tag'}, ); @@ -211,7 +212,7 @@ test.serial('should not fail when prerelease version of public package with dist stdout: '', }], async ({t, testedModule: prerequisiteTasks}) => { await t.notThrowsAsync( - run(prerequisiteTasks('2.0.0-1', {name: 'test', version: '1.0.0'}, {yarn: false, tag: 'pre'})), + run(prerequisiteTasks('2.0.0-1', {name: 'test', version: '1.0.0'}, {tag: 'pre'}, npmConfig)), ); }); @@ -220,7 +221,7 @@ test.serial('should not fail when prerelease version of private package without stdout: '', }], async ({t, testedModule: prerequisiteTasks}) => { await t.notThrowsAsync( - run(prerequisiteTasks('2.0.0-1', {name: 'test', version: '1.0.0', private: true}, {yarn: false})), + run(prerequisiteTasks('2.0.0-1', {name: 'test', version: '1.0.0', private: true}, {}, npmConfig)), ); }); @@ -229,7 +230,7 @@ test.serial('should fail when git tag already exists', createFixture, [{ stdout: 'vvb', }], async ({t, testedModule: prerequisiteTasks}) => { await t.throwsAsync( - run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {yarn: false})), + run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {}, npmConfig)), {message: 'Git tag `v2.0.0` already exists.'}, ); @@ -241,6 +242,6 @@ test.serial('checks should pass', createFixture, [{ stdout: '', }], async ({t, testedModule: prerequisiteTasks}) => { await t.notThrowsAsync( - run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {yarn: false})), + run(prerequisiteTasks('2.0.0', {name: 'test', version: '1.0.0'}, {}, npmConfig)), ); }); diff --git a/test/ui/prompts/tags.js b/test/ui/prompts/tags.js index 62ccf4ef..6a8d1a56 100644 --- a/test/ui/prompts/tags.js +++ b/test/ui/prompts/tags.js @@ -17,6 +17,9 @@ const testUi = test.macro(async (t, {version, tags, answers}, assertions) => { latestTagOrFirstCommit: sinon.stub().resolves(`v${npPkg.version}`), commitLogFromRevision: sinon.stub().resolves(''), }, + './package-manager/index.js': { + findLockFile: sinon.stub().resolves(undefined), + } }}); const results = await ui({ diff --git a/test/util/get-pre-release-prefix.js b/test/util/get-pre-release-prefix.js index 113294c7..f99b2afc 100644 --- a/test/util/get-pre-release-prefix.js +++ b/test/util/get-pre-release-prefix.js @@ -12,7 +12,7 @@ test('returns preid postfix if set - npm', createFixture, [{ stdout: 'pre', }], async ({t, testedModule: {getPreReleasePrefix}}) => { t.is( - await getPreReleasePrefix({yarn: false}), + await getPreReleasePrefix({cli: 'npm'}), 'pre', ); }); @@ -22,7 +22,7 @@ test('returns preid postfix if set - yarn', createFixture, [{ stdout: 'pre', }], async ({t, testedModule: {getPreReleasePrefix}}) => { t.is( - await getPreReleasePrefix({yarn: true}), + await getPreReleasePrefix({cli: 'yarn'}), 'pre', ); }); @@ -32,7 +32,7 @@ test('returns empty string if not set - npm', createFixture, [{ stdout: 'undefined', }], async ({t, testedModule: {getPreReleasePrefix}}) => { t.is( - await getPreReleasePrefix({yarn: false}), + await getPreReleasePrefix({cli: 'npm'}), '', ); }); @@ -42,7 +42,7 @@ test('returns empty string if not set - yarn', createFixture, [{ stdout: 'undefined', }], async ({t, testedModule: {getPreReleasePrefix}}) => { t.is( - await getPreReleasePrefix({yarn: true}), + await getPreReleasePrefix({cli: 'yarn'}), '', ); }); @@ -52,13 +52,13 @@ test('no options passed', async t => { originalGetPreReleasePrefix(), {message: stripIndent` Expected argument to be of type \`object\` but received type \`undefined\` - Expected object to have keys \`["yarn"]\` + Expected object to have keys \`["cli"]\` `}, ); await t.throwsAsync( originalGetPreReleasePrefix({}), - {message: 'Expected object to have keys `["yarn"]`'}, + {message: 'Expected object to have keys `["cli"]`'}, ); }); @@ -66,7 +66,7 @@ test.serial('returns actual value', async t => { const originalPreid = process.env.NPM_CONFIG_PREID; process.env.NPM_CONFIG_PREID = 'beta'; - t.is(await originalGetPreReleasePrefix({yarn: false}), 'beta'); + t.is(await originalGetPreReleasePrefix({cli: 'npm'}), 'beta'); process.env.NPM_CONFIG_PREID = originalPreid; }); diff --git a/test/util/get-tag-version-prefix.js b/test/util/get-tag-version-prefix.js index aa387110..a7868cf3 100644 --- a/test/util/get-tag-version-prefix.js +++ b/test/util/get-tag-version-prefix.js @@ -2,6 +2,7 @@ import test from 'ava'; import {stripIndent} from 'common-tags'; import {_createFixture} from '../_helpers/stub-execa.js'; import {getTagVersionPrefix as originalGetTagVersionPrefix} from '../../source/util.js'; +import {npmConfig, yarnConfig} from '../../source/package-manager/configs.js' /** @type {ReturnType>} */ const createFixture = _createFixture('../../source/util.js', import.meta.url); @@ -11,7 +12,7 @@ test('returns tag prefix - npm', createFixture, [{ stdout: 'ver', }], async ({t, testedModule: {getTagVersionPrefix}}) => { t.is( - await getTagVersionPrefix({yarn: false}), + await getTagVersionPrefix(npmConfig), 'ver', ); }); @@ -21,7 +22,7 @@ test('returns preId postfix - yarn', createFixture, [{ stdout: 'ver', }], async ({t, testedModule: {getTagVersionPrefix}}) => { t.is( - await getTagVersionPrefix({yarn: true}), + await getTagVersionPrefix(yarnConfig), 'ver', ); }); @@ -31,7 +32,7 @@ test('defaults to "v" when command fails', createFixture, [{ exitCode: 1, }], async ({t, testedModule: {getTagVersionPrefix}}) => { t.is( - await getTagVersionPrefix({yarn: false}), + await getTagVersionPrefix(npmConfig), 'v', ); }); @@ -41,12 +42,12 @@ test('no options passed', async t => { originalGetTagVersionPrefix(), {message: stripIndent` Expected argument to be of type \`object\` but received type \`undefined\` - Expected object to have keys \`["yarn"]\` + Expected object to have keys \`["tagVersionPrefixCommand"]\` `}, ); await t.throwsAsync( originalGetTagVersionPrefix({}), - {message: 'Expected object to have keys `["yarn"]`'}, + {message: 'Expected object to have keys `["tagVersionPrefixCommand"]`'}, ); }); From b866b22f5487e3923afb5b49c25d460a260bbfe3 Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Thu, 8 Feb 2024 18:30:36 -0500 Subject: [PATCH 15/25] shooting stars --- source/index.js | 9 ++++----- source/package-manager/index.js | 12 ++++++------ source/ui.js | 6 +++--- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/source/index.js b/source/index.js index e96c0891..577420ed 100644 --- a/source/index.js +++ b/source/index.js @@ -26,11 +26,10 @@ const exec = (cmd, args, options) => { }; /** - * @param {string} input - * @param {import('./cli-implementation.js').Options} options - * @param {{pkg: import('read-pkg').NormalizedPackageJson; rootDir: string}} context - */ - +@param {string} input +@param {import('./cli-implementation.js').Options} options +@param {{pkg: import('read-pkg').NormalizedPackageJson; rootDir: string}} context +*/ const np = async (input = 'patch', options, {pkg, rootDir}) => { const pkgManager = getPackageManagerConfig(rootDir, pkg); const publishCli = pkgManager.publishCli || pkgManager.cli; diff --git a/source/package-manager/index.js b/source/package-manager/index.js index f73c04bd..dcd7a78c 100644 --- a/source/package-manager/index.js +++ b/source/package-manager/index.js @@ -4,9 +4,9 @@ import semver from 'semver'; import {npmConfig, yarnBerryConfig, pnpmConfig, yarnConfig, npmConfigNoLockfile} from './configs.js'; /** - * @param {string} rootDir - * @param {import('./types.d.ts').PackageManagerConfig} config - */ +@param {string} rootDir +@param {import('./types.d.ts').PackageManagerConfig} config +*/ export function findLockFile(rootDir, config) { return config.lockfiles .map(filename => ({filename, filepath: path.resolve(rootDir || '.', filename)})) @@ -14,9 +14,9 @@ export function findLockFile(rootDir, config) { } /** - * @param {string} rootDir - * @param {import('read-pkg').NormalizedPackageJson} pkg - */ +@param {string} rootDir +@param {import('read-pkg').NormalizedPackageJson} pkg +*/ export function getPackageManagerConfig(rootDir, pkg) { let config = configFromPackageManagerField(pkg); diff --git a/source/ui.js b/source/ui.js index 6ab8f022..e4ea2f25 100644 --- a/source/ui.js +++ b/source/ui.js @@ -122,9 +122,9 @@ const checkNewFilesAndDependencies = async (pkg, rootDir) => { }; /** - * @param {typeof import('./cli-implementation.js').cli['flags']} options - * @param {{pkg: import('read-pkg').NormalizedPackageJson; rootDir: string}} context - */ +@param {typeof import('./cli-implementation.js').cli['flags']} options +@param {{pkg: import('read-pkg').NormalizedPackageJson; rootDir: string}} context +*/ const ui = async (options, {pkg, rootDir}) => { const oldVersion = pkg.version; const extraBaseUrls = ['gitlab.com']; From b0eb552df8e991fc005e0332a9a594e3e22fdc80 Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Fri, 9 Feb 2024 09:23:14 -0500 Subject: [PATCH 16/25] xo --fix --- source/util.js | 2 +- test/index.js | 2 +- test/tasks/prerequisite-tasks.js | 2 +- test/ui/prompts/tags.js | 2 +- test/util/get-tag-version-prefix.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/source/util.js b/source/util.js index c686420c..9cd366cb 100644 --- a/source/util.js +++ b/source/util.js @@ -1,7 +1,7 @@ -import ow from 'ow' import process from 'node:process'; import {fileURLToPath} from 'node:url'; import path from 'node:path'; +import ow from 'ow'; import {readPackageUp} from 'read-package-up'; import {parsePackage} from 'read-pkg'; import issueRegex from 'issue-regex'; diff --git a/test/index.js b/test/index.js index 94eba192..d6cba99d 100644 --- a/test/index.js +++ b/test/index.js @@ -42,7 +42,7 @@ test('errors on too low version', npFails, const fakeExecaReturn = () => Object.assign( Promise.resolve({pipe: sinon.stub()}), - {stdout: '', stderr: ''} + {stdout: '', stderr: ''}, ); test('skip enabling 2FA if the package exists', async t => { diff --git a/test/tasks/prerequisite-tasks.js b/test/tasks/prerequisite-tasks.js index 338b95a4..4937a2e0 100644 --- a/test/tasks/prerequisite-tasks.js +++ b/test/tasks/prerequisite-tasks.js @@ -5,7 +5,7 @@ import {npPkg} from '../../source/util.js'; import {SilentRenderer} from '../_helpers/listr-renderer.js'; import {_createFixture} from '../_helpers/stub-execa.js'; import {run, assertTaskFailed, assertTaskDisabled} from '../_helpers/listr.js'; -import {npmConfig, yarnConfig} from '../../source/package-manager/configs.js' +import {npmConfig, yarnConfig} from '../../source/package-manager/configs.js'; /** @type {ReturnType>} */ const createFixture = _createFixture('../../source/prerequisite-tasks.js', import.meta.url); diff --git a/test/ui/prompts/tags.js b/test/ui/prompts/tags.js index 6a8d1a56..e95b4899 100644 --- a/test/ui/prompts/tags.js +++ b/test/ui/prompts/tags.js @@ -19,7 +19,7 @@ const testUi = test.macro(async (t, {version, tags, answers}, assertions) => { }, './package-manager/index.js': { findLockFile: sinon.stub().resolves(undefined), - } + }, }}); const results = await ui({ diff --git a/test/util/get-tag-version-prefix.js b/test/util/get-tag-version-prefix.js index a7868cf3..db714847 100644 --- a/test/util/get-tag-version-prefix.js +++ b/test/util/get-tag-version-prefix.js @@ -2,7 +2,7 @@ import test from 'ava'; import {stripIndent} from 'common-tags'; import {_createFixture} from '../_helpers/stub-execa.js'; import {getTagVersionPrefix as originalGetTagVersionPrefix} from '../../source/util.js'; -import {npmConfig, yarnConfig} from '../../source/package-manager/configs.js' +import {npmConfig, yarnConfig} from '../../source/package-manager/configs.js'; /** @type {ReturnType>} */ const createFixture = _createFixture('../../source/util.js', import.meta.url); From 9db2414db42159cd35cdddbbf011f8225558d075 Mon Sep 17 00:00:00 2001 From: Misha Kaletsky <15040698+mmkal@users.noreply.github.com> Date: Sat, 10 Feb 2024 08:55:06 -0500 Subject: [PATCH 17/25] restore notThrowsAsync --- test/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/index.js b/test/index.js index d6cba99d..7e3b3da2 100644 --- a/test/index.js +++ b/test/index.js @@ -92,7 +92,7 @@ test('skip enabling 2FA if the `2fa` option is false', async t => { '../source/npm/publish.js': sinon.stub().returns({pipe: sinon.stub()}), }); - await (npMock('1.0.0', { + await t.notThrowsAsync(npMock('1.0.0', { ...defaultOptions, availability: { isAvailable: true, From 4fd0608590dbfa8c8af300c8f6afe7cc29668ed1 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 11 Feb 2024 15:00:27 +0700 Subject: [PATCH 18/25] Update readme.md --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 23247595..b8dc4437 100644 --- a/readme.md +++ b/readme.md @@ -276,9 +276,9 @@ Set the [`registry` option](https://docs.npmjs.com/misc/config#registry) in pack } ``` -### Package Managers +### 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 user 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 [corepack docs](https://nodejs.org/api/corepack.html). +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 From a6d0906dbb40b5aa4db6ea94c928c46a49df60d8 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 11 Feb 2024 15:09:01 +0700 Subject: [PATCH 19/25] Update types.d.ts --- source/package-manager/types.d.ts | 44 ++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/source/package-manager/types.d.ts b/source/package-manager/types.d.ts index 2e5d8a81..d437c503 100644 --- a/source/package-manager/types.d.ts +++ b/source/package-manager/types.d.ts @@ -1,25 +1,51 @@ export type PackageManager = 'npm' | 'yarn' | 'pnpm'; -/** CLI and arguments, which can be passed to `execa` */ +/** +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. */ + /** + 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) */ + + /** + 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, e.g. `["npm", ["install"]]` */ + + /** + How to install packages, e.g. `["npm", ["install"]]`. + */ installCommand: Command; - /** Given a verison string, return a version command e.g. `version => ["npm", ["version", version]]` */ + + /** + Given a version string, return a version command e.g. `version => ["npm", ["version", version]]`. + */ versionCommand: (version: string) => [cli: string, args: string[]]; - /** Use a different CLI to do the actual publish. Defaults to `cli`. */ + + /** + Use a different CLI to do the actual publish. Defaults to `cli`. + */ publishCli?: string; - /** CLI command which is expected to output the npm registry to use, e.g. `['npm', ['config', 'get', 'registry']]` */ + + /** + CLI command which is expected to output the npm registry to use, e.g. `['npm', ['config', 'get', 'registry']]`. + */ getRegistryCommand: Command; + /** */ 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. */ + + /** + 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']`. */ + + /** + List of lockfile names expected for this package manager, relative to CWD. e.g. `['package-lock.json', 'npm-shrinkwrap.json']`. + */ lockfiles: string[]; }; From d9ca56549b98073271af370553bcf3180ecaaf4c Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Fri, 23 Feb 2024 15:47:36 -0500 Subject: [PATCH 20/25] Squashed commit of the following: commit 92903bcb1d68aa0ba5b45db1396e689f34d2dc8f Author: Mikael Finstad Date: Fri Feb 16 11:44:18 2024 +0800 Update source/package-manager/types.d.ts Co-authored-by: Tommy commit 9858c3dbd00cc7252dfa3be73aad0eb1aa1da834 Author: Mikael Finstad Date: Thu Feb 15 15:40:44 2024 +0800 fix bugs discovered when testing yarn berry --- source/index.js | 20 ++++++++++++++------ source/package-manager/configs.js | 22 +++++++++++----------- source/package-manager/index.js | 9 ++------- source/package-manager/types.d.ts | 13 +++++++++---- 4 files changed, 36 insertions(+), 28 deletions(-) diff --git a/source/index.js b/source/index.js index 577420ed..dd45765b 100644 --- a/source/index.js +++ b/source/index.js @@ -8,7 +8,7 @@ import {asyncExitHook} from 'exit-hook'; import logSymbols from 'log-symbols'; import prerequisiteTasks from './prerequisite-tasks.js'; import gitTasks from './git-tasks.js'; -import {getPackagePublishArguments} from './npm/publish.js'; +import {getPackagePublishArguments as getAdditionalPackagePublishArguments} from './npm/publish.js'; import enable2fa, {getEnable2faArgs} from './npm/enable-2fa.js'; import releaseTaskHelper from './release-task-helper.js'; import * as util from './util.js'; @@ -32,7 +32,7 @@ const exec = (cmd, args, options) => { */ const np = async (input = 'patch', options, {pkg, rootDir}) => { const pkgManager = getPackageManagerConfig(rootDir, pkg); - const publishCli = pkgManager.publishCli || pkgManager.cli; + const [publishCommand, publishArgsPrefix = []] = pkgManager.publishCli || [pkgManager.cli]; // TODO: Remove sometime far in the future if (options.skipCleanup) { @@ -96,6 +96,11 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => { // To prevent the process from hanging due to watch mode (e.g. when running `vitest`) const ciEnvOptions = {env: {CI: 'true'}}; + function getPackagePublishArguments(options) { + const args = getAdditionalPackagePublishArguments(options); + return [...publishArgsPrefix, ...args]; + } + const tasks = new Listr([ { title: 'Prerequisite check', @@ -117,7 +122,10 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => { task: () => new Listr([ { title: 'Running install command', - task: () => exec(...pkgManager.installCommand), + task() { + const installCommand = lockfile ? pkgManager.installCommand : pkgManager.installCommandNoLockfile; + return exec(...installCommand); + }, }, { title: 'Checking working tree is still clean', // If lockfile was out of date and tracked by git, this will fail @@ -159,19 +167,19 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => { skip() { if (options.preview) { const args = getPackagePublishArguments(options); - return `[Preview] Command not executed: ${publishCli} ${args.join(' ')}.`; + return `[Preview] Command not executed: ${publishCommand} ${args.join(' ')}.`; } }, /** @type {(context, task) => Listr.ListrTaskResult} */ task(context, task) { let hasError = false; - return from(execa(publishCli, getPackagePublishArguments(options))) + return from(execa(publishCommand, getPackagePublishArguments(options))) .pipe( catchError(error => handleNpmError(error, task, otp => { context.otp = otp; - return execa(publishCli, getPackagePublishArguments({...options, otp})); + return execa(publishCommand, getPackagePublishArguments({...options, otp})); })), ) .pipe( diff --git a/source/package-manager/configs.js b/source/package-manager/configs.js index f3c52638..aed13e8a 100644 --- a/source/package-manager/configs.js +++ b/source/package-manager/configs.js @@ -3,26 +3,22 @@ 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']], tagVersionPrefixCommand: ['npm', ['config', 'get', 'tag-version-prefix']], lockfiles: ['package-lock.json', 'npm-shrinkwrap.json'], }; -/** @type {import('./types.d.ts').PackageManagerConfig} */ -export const npmConfigNoLockfile = { - ...npmConfig, - installCommand: ['npm', ['install', '--no-package-lock', '--no-production', '--engine-strict']], -}; - /** @type {import('./types.d.ts').PackageManagerConfig} */ export const pnpmConfig = { cli: 'pnpm', id: 'pnpm', installCommand: ['pnpm', ['install']], + installCommandNoLockfile: ['pnpm', ['install']], versionCommand: version => ['pnpm', ['version', version]], tagVersionPrefixCommand: ['pnpm', ['config', 'get', 'tag-version-prefix']], - publishCli: 'npm', // Pnpm does git cleanliness checks, which np already did, and which fail because when publishing package.json has been updated + publishCli: ['npm'], // Pnpm does git cleanliness checks, which np already did, and which fail because when publishing package.json has been updated getRegistryCommand: ['pnpm', ['config', 'get', 'registry']], lockfiles: ['pnpm-lock.yaml'], }; @@ -31,7 +27,8 @@ export const pnpmConfig = { export const yarnConfig = { cli: 'yarn', id: 'yarn', - installCommand: ['yarn', ['install', '--production=false']], + 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]], @@ -42,10 +39,13 @@ export const yarnConfig = { export const yarnBerryConfig = { cli: 'yarn', id: 'yarn-berry', - installCommand: ['yarn', ['install']], - versionCommand: version => ['yarn', ['version', '--new-version', version]], + 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']], - publishCli: 'npm', // Yarn berry doesn't support git committing/tagging, so use npm + // Yarn berry offloads publishing to npm, e.g. `yarn npm publish x.y.z` + publishCli: ['yarn', ['npm']], getRegistryCommand: ['yarn', ['config', 'get', 'npmRegistryServer']], throwOnExternalRegistry: true, lockfiles: ['yarn.lock'], diff --git a/source/package-manager/index.js b/source/package-manager/index.js index dcd7a78c..252014f4 100644 --- a/source/package-manager/index.js +++ b/source/package-manager/index.js @@ -1,7 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; import semver from 'semver'; -import {npmConfig, yarnBerryConfig, pnpmConfig, yarnConfig, npmConfigNoLockfile} from './configs.js'; +import {npmConfig, yarnBerryConfig, pnpmConfig, yarnConfig} from './configs.js'; /** @param {string} rootDir @@ -18,12 +18,7 @@ export function findLockFile(rootDir, config) { @param {import('read-pkg').NormalizedPackageJson} pkg */ export function getPackageManagerConfig(rootDir, pkg) { - let config = configFromPackageManagerField(pkg); - - if (config === npmConfig && !findLockFile(rootDir, config)) { - config = npmConfigNoLockfile; - } - + const config = configFromPackageManagerField(pkg); return config || configFromLockfile(rootDir) || npmConfig; } diff --git a/source/package-manager/types.d.ts b/source/package-manager/types.d.ts index d437c503..9f2d1cd4 100644 --- a/source/package-manager/types.d.ts +++ b/source/package-manager/types.d.ts @@ -17,19 +17,24 @@ export type PackageManagerConfig = { id: string; /** - How to install packages, e.g. `["npm", ["install"]]`. + 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[]]; + versionCommand: (version: string) => [command: string, args: string[]]; /** - Use a different CLI to do the actual publish. Defaults to `cli`. + Use a different CLI (and prefix args) to do the actual publish. Defaults to [`cli`]. */ - publishCli?: string; + publishCli?: [command: string, prefixArgs?: string[]]; /** CLI command which is expected to output the npm registry to use, e.g. `['npm', ['config', 'get', 'registry']]`. From 3163835537d8b20290c0a77e80f422c42d153da6 Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Fri, 23 Feb 2024 15:51:20 -0500 Subject: [PATCH 21/25] publishCli + prefix args -> publishCommand function --- source/index.js | 17 +++++++++-------- source/package-manager/configs.js | 3 +-- source/package-manager/types.d.ts | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/source/index.js b/source/index.js index dd45765b..07a2d7e0 100644 --- a/source/index.js +++ b/source/index.js @@ -32,7 +32,6 @@ const exec = (cmd, args, options) => { */ const np = async (input = 'patch', options, {pkg, rootDir}) => { const pkgManager = getPackageManagerConfig(rootDir, pkg); - const [publishCommand, publishArgsPrefix = []] = pkgManager.publishCli || [pkgManager.cli]; // TODO: Remove sometime far in the future if (options.skipCleanup) { @@ -96,9 +95,11 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => { // To prevent the process from hanging due to watch mode (e.g. when running `vitest`) const ciEnvOptions = {env: {CI: 'true'}}; - function getPackagePublishArguments(options) { - const args = getAdditionalPackagePublishArguments(options); - return [...publishArgsPrefix, ...args]; + /** @param {typeof options} _options */ + function getPublishCommand(_options) { + const publishCommand = pkgManager.publishCommand || (args => [pkgManager.cli, args]); + const args = getAdditionalPackagePublishArguments(_options); + return publishCommand(args); } const tasks = new Listr([ @@ -166,20 +167,20 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => { title: 'Publishing package', skip() { if (options.preview) { - const args = getPackagePublishArguments(options); - return `[Preview] Command not executed: ${publishCommand} ${args.join(' ')}.`; + const command = getPublishCommand(options); + return `[Preview] Command not executed: ${printCommand(command)}.`; } }, /** @type {(context, task) => Listr.ListrTaskResult} */ task(context, task) { let hasError = false; - return from(execa(publishCommand, getPackagePublishArguments(options))) + return from(execa(...getPublishCommand(options))) .pipe( catchError(error => handleNpmError(error, task, otp => { context.otp = otp; - return execa(publishCommand, getPackagePublishArguments({...options, otp})); + return execa(...getPublishCommand({...options, otp})); })), ) .pipe( diff --git a/source/package-manager/configs.js b/source/package-manager/configs.js index aed13e8a..45bfc456 100644 --- a/source/package-manager/configs.js +++ b/source/package-manager/configs.js @@ -18,7 +18,6 @@ export const pnpmConfig = { installCommandNoLockfile: ['pnpm', ['install']], versionCommand: version => ['pnpm', ['version', version]], tagVersionPrefixCommand: ['pnpm', ['config', 'get', 'tag-version-prefix']], - publishCli: ['npm'], // Pnpm does git cleanliness checks, which np already did, and which fail because when publishing package.json has been updated getRegistryCommand: ['pnpm', ['config', 'get', 'registry']], lockfiles: ['pnpm-lock.yaml'], }; @@ -45,7 +44,7 @@ export const yarnBerryConfig = { 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` - publishCli: ['yarn', ['npm']], + publishCommand: args => ['yarn', ['npm', ...args]], getRegistryCommand: ['yarn', ['config', 'get', 'npmRegistryServer']], throwOnExternalRegistry: true, lockfiles: ['yarn.lock'], diff --git a/source/package-manager/types.d.ts b/source/package-manager/types.d.ts index 9f2d1cd4..80484a35 100644 --- a/source/package-manager/types.d.ts +++ b/source/package-manager/types.d.ts @@ -29,12 +29,12 @@ export type PackageManagerConfig = { /** Given a version string, return a version command e.g. `version => ["npm", ["version", version]]`. */ - versionCommand: (version: string) => [command: string, args: string[]]; + versionCommand: (version: string) => [cli: string, args: string[]]; /** - Use a different CLI (and prefix args) to do the actual publish. Defaults to [`cli`]. + Modify the actual publish command. Defaults to `args => [config.cli, args]`. */ - publishCli?: [command: string, prefixArgs?: string[]]; + publishCommand?: (args: string[]) => Command; /** CLI command which is expected to output the npm registry to use, e.g. `['npm', ['config', 'get', 'registry']]`. From c8cba01a6730dbc0fad9529b449e214d84ab46be Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Fri, 23 Feb 2024 16:02:49 -0500 Subject: [PATCH 22/25] @tommy-mitchell feedback --- source/.npmignore | 1 + source/cli-implementation.js | 4 +++- source/index.js | 10 +++++----- source/package-manager/index.js | 22 +++++++++++----------- source/ui.js | 4 ++-- source/util.js | 2 +- test/tasks/prerequisite-tasks.js | 2 +- test/ui/prompts/tags.js | 2 +- 8 files changed, 25 insertions(+), 22 deletions(-) create mode 100644 source/.npmignore diff --git a/source/.npmignore b/source/.npmignore new file mode 100644 index 00000000..cd4efd8e --- /dev/null +++ b/source/.npmignore @@ -0,0 +1 @@ +*.d.ts diff --git a/source/cli-implementation.js b/source/cli-implementation.js index 57b03886..c13fa608 100755 --- a/source/cli-implementation.js +++ b/source/cli-implementation.js @@ -13,7 +13,9 @@ import {SEMVER_INCREMENTS} from './version.js'; import ui from './ui.js'; import np from './index.js'; -export const cli = meow(` +/** @typedef {typeof cli} CLI */ + +const cli = meow(` Usage $ np diff --git a/source/index.js b/source/index.js index 07a2d7e0..f5484dba 100644 --- a/source/index.js +++ b/source/index.js @@ -8,14 +8,14 @@ import {asyncExitHook} from 'exit-hook'; import logSymbols from 'log-symbols'; import prerequisiteTasks from './prerequisite-tasks.js'; import gitTasks from './git-tasks.js'; -import {getPackagePublishArguments as getAdditionalPackagePublishArguments} from './npm/publish.js'; +import {getPackagePublishArguments} from './npm/publish.js'; import enable2fa, {getEnable2faArgs} from './npm/enable-2fa.js'; +import handleNpmError from './npm/handle-npm-error.js'; import releaseTaskHelper from './release-task-helper.js'; +import {findLockfile, getPackageManagerConfig, 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'; -import {findLockFile, getPackageManagerConfig, printCommand} from './package-manager/index.js'; -import handleNpmError from './npm/handle-npm-error.js'; /** @type {(cmd: string, args: string[], options?: import('execa').Options) => any} */ const exec = (cmd, args, options) => { @@ -40,7 +40,7 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => { const runTests = options.tests && !options.yolo; const runCleanup = options.cleanup && !options.yolo; - const lockfile = findLockFile(rootDir, pkgManager); + const lockfile = findLockfile(rootDir, pkgManager); const isOnGitHub = options.repoUrl && hostedGitInfo.fromUrl(options.repoUrl)?.type === 'github'; const testScript = options.testScript || 'test'; @@ -98,7 +98,7 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => { /** @param {typeof options} _options */ function getPublishCommand(_options) { const publishCommand = pkgManager.publishCommand || (args => [pkgManager.cli, args]); - const args = getAdditionalPackagePublishArguments(_options); + const args = getPackagePublishArguments(_options); return publishCommand(args); } diff --git a/source/package-manager/index.js b/source/package-manager/index.js index 252014f4..93252f72 100644 --- a/source/package-manager/index.js +++ b/source/package-manager/index.js @@ -1,16 +1,16 @@ import fs from 'node:fs'; import path from 'node:path'; import semver from 'semver'; -import {npmConfig, yarnBerryConfig, pnpmConfig, yarnConfig} from './configs.js'; +import * as configs from './configs.js'; /** @param {string} rootDir @param {import('./types.d.ts').PackageManagerConfig} config */ -export function findLockFile(rootDir, config) { +export function findLockfile(rootDir, config) { return config.lockfiles - .map(filename => ({filename, filepath: path.resolve(rootDir || '.', filename)})) - .find(({filepath}) => fs.existsSync(filepath)); + .map(filename => path.resolve(rootDir || '.', filename)) + .find(filepath => fs.existsSync(filepath)); } /** @@ -19,7 +19,7 @@ export function findLockFile(rootDir, config) { */ export function getPackageManagerConfig(rootDir, pkg) { const config = configFromPackageManagerField(pkg); - return config || configFromLockfile(rootDir) || npmConfig; + return config || configFromLockfile(rootDir) || configs.npmConfig; } /** @param {import('read-pkg').NormalizedPackageJson} pkg */ @@ -31,27 +31,27 @@ function configFromPackageManagerField(pkg) { const [packageManager, version] = pkg.packageManager.split('@'); if (packageManager === 'yarn' && version && semver.gte(version, '2.0.0')) { - return yarnBerryConfig; + return configs.yarnBerryConfig; } if (packageManager === 'npm') { - return npmConfig; + return configs.npmConfig; } if (packageManager === 'pnpm') { - return pnpmConfig; + return configs.pnpmConfig; } if (packageManager === 'yarn') { - return yarnConfig; + return configs.yarnConfig; } throw new Error(`Invalid package manager: ${pkg.packageManager}`); } /** @param {string} rootDir */ -function configFromLockfile(rootDir, options = [npmConfig, pnpmConfig, yarnConfig]) { - return options.find(config => findLockFile(rootDir, config)); +function configFromLockfile(rootDir, options = [configs.npmConfig, configs.pnpmConfig, configs.yarnConfig]) { + return options.find(config => findLockfile(rootDir, config)); } /** @param {import('./types.d.ts').Command} command */ diff --git a/source/ui.js b/source/ui.js index e4ea2f25..115c08a8 100644 --- a/source/ui.js +++ b/source/ui.js @@ -5,11 +5,11 @@ import {htmlEscape} from 'escape-goat'; import isScoped from 'is-scoped'; import isInteractive from 'is-interactive'; import {execa} from 'execa'; +import {getPackageManagerConfig} from './package-manager/index.js'; 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'; -import {getPackageManagerConfig} from './package-manager/index.js'; const printCommitLog = async (repoUrl, registryUrl, fromLatestTag, releaseBranch) => { const revision = fromLatestTag ? await git.latestTagOrFirstCommit() : await git.previousTagOrFirstCommit(); @@ -122,7 +122,7 @@ const checkNewFilesAndDependencies = async (pkg, rootDir) => { }; /** -@param {typeof import('./cli-implementation.js').cli['flags']} options +@param {import('./cli-implementation.js').CLI['flags']} options @param {{pkg: import('read-pkg').NormalizedPackageJson; rootDir: string}} context */ const ui = async (options, {pkg, rootDir}) => { diff --git a/source/util.js b/source/util.js index 9cd366cb..e0ea3e66 100644 --- a/source/util.js +++ b/source/util.js @@ -1,13 +1,13 @@ import process from 'node:process'; import {fileURLToPath} from 'node:url'; import path from 'node:path'; -import ow from 'ow'; 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 ow from 'ow'; import chalk from 'chalk'; import Version from './version.js'; import * as git from './git-util.js'; diff --git a/test/tasks/prerequisite-tasks.js b/test/tasks/prerequisite-tasks.js index 4937a2e0..b7480912 100644 --- a/test/tasks/prerequisite-tasks.js +++ b/test/tasks/prerequisite-tasks.js @@ -1,11 +1,11 @@ import process from 'node:process'; import test from 'ava'; +import {npmConfig, yarnConfig} from '../../source/package-manager/configs.js'; import actualPrerequisiteTasks from '../../source/prerequisite-tasks.js'; import {npPkg} from '../../source/util.js'; import {SilentRenderer} from '../_helpers/listr-renderer.js'; import {_createFixture} from '../_helpers/stub-execa.js'; import {run, assertTaskFailed, assertTaskDisabled} from '../_helpers/listr.js'; -import {npmConfig, yarnConfig} from '../../source/package-manager/configs.js'; /** @type {ReturnType>} */ const createFixture = _createFixture('../../source/prerequisite-tasks.js', import.meta.url); diff --git a/test/ui/prompts/tags.js b/test/ui/prompts/tags.js index e95b4899..1cf6acdc 100644 --- a/test/ui/prompts/tags.js +++ b/test/ui/prompts/tags.js @@ -18,7 +18,7 @@ const testUi = test.macro(async (t, {version, tags, answers}, assertions) => { commitLogFromRevision: sinon.stub().resolves(''), }, './package-manager/index.js': { - findLockFile: sinon.stub().resolves(undefined), + findLockfile: sinon.stub().resolves(undefined), }, }}); From 6ee23e5c0160b2991000260cc864c15d649b04df Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Fri, 23 Feb 2024 16:25:57 -0500 Subject: [PATCH 23/25] floating in space --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index b8dc4437..84699c54 100644 --- a/readme.md +++ b/readme.md @@ -91,7 +91,7 @@ $ np --help --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) + --package-manager Use a specific package manager (default: 'packageManager' field in package.json) Examples $ np From b54c77de766a3de2ba96fb70423152005604d4ee Mon Sep 17 00:00:00 2001 From: Misha Kaletsky <15040698+mmkal@users.noreply.github.com> Date: Mon, 26 Feb 2024 03:03:37 -0500 Subject: [PATCH 24/25] Tag version prefix jsdoc --- source/package-manager/types.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/package-manager/types.d.ts b/source/package-manager/types.d.ts index 80484a35..fbb3f7a2 100644 --- a/source/package-manager/types.d.ts +++ b/source/package-manager/types.d.ts @@ -41,7 +41,9 @@ export type PackageManagerConfig = { */ getRegistryCommand: Command; - /** */ + /** + CLI command expected to output the version tag prefix (often "v"). e,g. `['npm', ['config', 'get', 'tag-version-prefix']]` + */ tagVersionPrefixCommand: Command; /** From ec3b852ce60e83e71aac9a29ffa41bb4afbd3bbc Mon Sep 17 00:00:00 2001 From: Misha Kaletsky <15040698+mmkal@users.noreply.github.com> Date: Mon, 26 Feb 2024 03:07:47 -0500 Subject: [PATCH 25/25] =?UTF-8?q?Fix=20whitespace=20hopefully=20(done=20on?= =?UTF-8?q?=20=F0=9F=93=B1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/package-manager/types.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/package-manager/types.d.ts b/source/package-manager/types.d.ts index fbb3f7a2..7a3d6264 100644 --- a/source/package-manager/types.d.ts +++ b/source/package-manager/types.d.ts @@ -42,8 +42,8 @@ export type PackageManagerConfig = { getRegistryCommand: Command; /** - CLI command expected to output the version tag prefix (often "v"). e,g. `['npm', ['config', 'get', 'tag-version-prefix']]` - */ + CLI command expected to output the version tag prefix (often "v"). e,g. `['npm', ['config', 'get', 'tag-version-prefix']]`. + */ tagVersionPrefixCommand: Command; /**