Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Forked from [`version-bump-prompt`](https://github.com/JS-DevTools/version-bump-
- Confirmation before bumping.
- Enable `--commit` `--tag` `--push` by default. (opt-out by `--no-push`, etc.)
- `-r` or `--recursive` to bump all packages in the monorepo.
- `--npm-tag` to update the npm publish tag (e.g. `latest`, `next`, `beta`) in `package.json`.
- `--execute` to execute the command, or execute a function before committing.
- Conventional Commits by default.
- Ships ESM and CJS bundles.
Expand Down
2 changes: 2 additions & 0 deletions src/cli/parse-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export async function parseArgs(): Promise<ParsedArgs> {
printCommits: args.printCommits,
recursive: args.recursive,
release: args.release,
npmTag: args.npmTag,
configFilePath: args.configFilePath,
}),
}
Expand Down Expand Up @@ -93,6 +94,7 @@ export function loadCliArgs(argv = process.argv) {
.option('-q, --quiet', 'Quiet mode')
.option('--current-version <version>', 'Current version')
.option('--print-commits', 'Print recent commits')
.option('--npm-tag [tag]', 'Npm publish tag', { default: undefined })
.option('-x, --execute <command>', 'Commands to execute after version bumps')
.option('--release <release>', `Release type or version number (e.g. 'major', 'minor', 'patch', 'prerelease', etc. default: ${bumpConfigDefaults.release})`)
.option('--configFilePath <configFilePath>', `Path to custom build.config file`)
Expand Down
4 changes: 4 additions & 0 deletions src/normalize-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface NormalizedOptions {
tag?: {
name: string
}
npmTag?: string | boolean
sign?: boolean
push: boolean
files: string[]
Expand Down Expand Up @@ -102,6 +103,8 @@ export async function normalizeOptions(raw: VersionBumpOptions): Promise<Normali
else if (raw.tag)
tag = { name: 'v' }

const npmTag = raw.npmTag

// NOTE: This must come AFTER `tag` and `push`, because it relies on them
let commit
if (typeof raw.commit === 'string')
Expand Down Expand Up @@ -203,6 +206,7 @@ export async function normalizeOptions(raw: VersionBumpOptions): Promise<Normali
ignoreScripts,
execute,
printCommits: raw.printCommits ?? true,
npmTag,
customVersion: raw.customVersion,
currentVersion: raw.currentVersion,
}
Expand Down
1 change: 1 addition & 0 deletions src/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface OperationState {
newVersion: string
commitMessage: string
tagName: string
npmTag?: string
updatedFiles: string[]
skippedFiles: string[]
}
Expand Down
42 changes: 42 additions & 0 deletions src/publish-config-tag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { Operation } from './operation'
import prompts from 'prompts'
import { prerelease } from 'semver'

export type PublishConfig = Partial<{
tag: string
access: string
provenance: boolean
registry: string
}>

export async function resolvePublishTag(operation: Operation) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I love this idea, I am a bit worried this could be a bit too annoying as I see 95% of the time I would want to publish to latest and in very rare cases I need to publish to a different tag (for example, when doing beta releases in another branch, I would only publish packages to the beta tag, and I could configure or update the command for a specific branch, prompting everytime can also increse the chance of mistakes).

@zcf0508 zcf0508 Jan 6, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have an idea that we could add a command parameter like --change-publish-tag or something.

By default, this parameter is false, and the publish tag prompt will not be shown. If mantainer wants to publish the current version to another tag like beta, he can run bumpp --chang-publish-tag locally and then the publish tag prompt will be shown to confirm the tag name.

if (!operation.options.npmTag) {
return
}

if (typeof operation.options.npmTag === 'string') {
operation.update({
npmTag: operation.options.npmTag,
})
return
}

const { newVersion } = operation.state
const pre = prerelease(newVersion)
const defaultTag = pre ? String(pre[0]) : 'latest'

const response = await prompts({
type: 'text',
name: 'tag',
message: 'Publish Tag',
initial: defaultTag,
})

const { tag } = response

if (tag) {
operation.update({
npmTag: tag,
})
}
}
8 changes: 8 additions & 0 deletions src/types/version-bump-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ export interface VersionBumpOptions {
*/
confirm?: boolean

/**
* Indicates whether to update the publish tag in package.json.
* Can be set to a custom tag string or `true` to prompt user.
*
* Defaults to `undefined` (no action).
*/
npmTag?: boolean | string

/**
* Indicates whether to bypass git commit hooks (`git commit --no-verify`).
*
Expand Down
27 changes: 26 additions & 1 deletion src/update-files.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Operation } from './operation'
import type { PublishConfig } from './publish-config-tag'
import { existsSync } from 'node:fs'
import * as path from 'node:path'
import { readJsoncFile, readTextFile, writeJsoncFile, writeTextFile } from './fs'
Expand Down Expand Up @@ -86,10 +87,34 @@ async function updateManifestFile(relPath: string, operation: Operation): Promis
if (isPackageLockManifest(file.data))
file.modified.push([['packages', '', 'version'], newVersion])

await writeJsoncFile(file)
modified = true
}

if (operation.state.npmTag) {
const currentTag = (file.data.publishConfig as PublishConfig)?.tag
const newTag = operation.state.npmTag

if (newTag === 'latest') {
if (currentTag) {
if (Object.keys((file.data as any).publishConfig).length === 1) {
file.modified.push([['publishConfig'], undefined])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not so sure if we should write the tag into the package.json

}
else {
file.modified.push([['publishConfig', 'tag'], undefined])
}
modified = true
}
}
else if (newTag !== currentTag) {
file.modified.push([['publishConfig', 'tag'], newTag])
modified = true
}
}

if (modified) {
await writeJsoncFile(file)
}

return modified
}

Expand Down
5 changes: 5 additions & 0 deletions src/version-bump.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { getNewVersion } from './get-new-version'
import { formatVersionString, gitCommit, gitPush, gitTag } from './git'
import { Operation } from './operation'
import { printRecentCommits } from './print-commits'
import { resolvePublishTag } from './publish-config-tag'
import { runNpmScript } from './run-npm-script'
import { NpmScript } from './types/version-bump-progress'
import { updateFiles } from './update-files'
Expand Down Expand Up @@ -60,6 +61,8 @@ export async function versionBump(arg: (VersionBumpOptions) | string = {}): Prom
await getCurrentVersion(operation)
await getNewVersion(operation, commits)

await resolvePublishTag(operation)

if (arg.confirm) {
printSummary(operation)

Expand Down Expand Up @@ -139,6 +142,8 @@ export async function versionBump(arg: (VersionBumpOptions) | string = {}): Prom
function printSummary(operation: Operation) {
console.log()
console.log(` files ${operation.options.files.map(i => c.bold(i)).join('\n ')}`)
if (operation.state.npmTag)
console.log(` npm tag ${c.cyan.bold(operation.state.npmTag)}`)
if (operation.options.commit)
console.log(` commit ${c.bold(formatVersionString(operation.options.commit.message, operation.state.newVersion))}`)
if (operation.options.tag)
Expand Down
24 changes: 24 additions & 0 deletions test/parse-args.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,30 @@ describe('loadCliArgs', async () => {
expect(result.args.sign).toBe(true)
})

it('sets the npmTag property to undefined if no npm-tag flag is present', () => {
const result = loadCliArgs([...defaultArgs])

expect(result.args.npmTag).toBe(undefined)
})

it('sets the npmTag property to true if `--npm-tag` is present', () => {
const result = loadCliArgs([...defaultArgs, '--npm-tag'])

expect(result.args.npmTag).toBe(true)
})

it('sets the npmTag property to "beta" if `--npm-tag beta` is present', () => {
const result = loadCliArgs([...defaultArgs, '--npm-tag', 'beta'])

expect(result.args.npmTag).toBe('beta')
})

it('sets the npmTag property to "beta" if `--npm-tag=beta` is present', () => {
const result = loadCliArgs([...defaultArgs, '--npm-tag=beta'])

expect(result.args.npmTag).toBe('beta')
})

it('should have configFilePath property set to the value of the `--configFilePath` flag', () => {
const result = loadCliArgs([...defaultArgs, '--configFilePath', 'test/fixtures/build.config.ts'])

Expand Down
Loading