Skip to content

Instantly share code, notes, and snippets.

@coderbyheart
Last active June 3, 2026 06:33
Show Gist options
  • Select an option

  • Save coderbyheart/980b245791a834a1e2ef62b22cf79855 to your computer and use it in GitHub Desktop.

Select an option

Save coderbyheart/980b245791a834a1e2ef62b22cf79855 to your computer and use it in GitHub Desktop.
Publish a pre-release ("dev") version to NPM.
/*
* Publish a pre-release ("dev") version to NPM.
*
* Computes the next dev version as `<next-target>-dev.<n>`, where:
* - <next-target> is the latest published stable version with either the
* patch, the minor, or the major bumped by one and lower components reset
* to 0 (e.g. `14.0.4` → `14.0.5` for a fix bump, `14.0.4` → `14.1.0` for a
* minor bump, or `14.0.4` → `15.0.0` for a major bump). The bump kind is
* controlled by the `--major` and `--fix` flags; the default is a minor
* bump.
* - <n> is one greater than the highest existing dev counter for
* <next-target>, or 1 if no dev release exists yet for it.
*
* The version is written to package.json, `npm publish --tag dev` is run,
* and package.json is restored afterwards.
*/
import { execSync } from 'node:child_process'
import { readFileSync, writeFileSync } from 'node:fs'
import path from 'node:path'
const packageJsonPath = path.join(process.cwd(), 'package.json')
const parseStable = (
v: string,
): { major: number; minor: number; patch: number } | null => {
const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v)
if (m === null) return null
return {
major: parseInt(m[1]!, 10),
minor: parseInt(m[2]!, 10),
patch: parseInt(m[3]!, 10),
}
}
const cmp = (
a: { major: number; minor: number; patch: number },
b: { major: number; minor: number; patch: number },
): number => a.major - b.major || a.minor - b.minor || a.patch - b.patch
const packageName = '@nrfcloud/billing-service-proto'
const versionsOutput = execSync(`npm view ${packageName} versions --json`, {
encoding: 'utf8',
})
const versions = JSON.parse(versionsOutput) as string[]
const stableVersions = versions
.map(parseStable)
.filter(
(v): v is { major: number; minor: number; patch: number } => v !== null,
)
if (stableVersions.length === 0) {
throw new Error(`No stable versions found for ${packageName}`)
}
const latestStable = stableVersions.reduce((a, b) => (cmp(a, b) >= 0 ? a : b))
const args = process.argv.slice(2)
const bumpMajor = args.includes('--major')
const bumpFix = args.includes('--fix')
if (bumpMajor && bumpFix) {
throw new Error('Cannot use --major and --fix together')
}
const nextTarget = bumpMajor
? `${latestStable.major + 1}.0.0`
: bumpFix
? `${latestStable.major}.${latestStable.minor}.${latestStable.patch + 1}`
: `${latestStable.major}.${latestStable.minor + 1}.0`
const devPattern = new RegExp(
`^${nextTarget.replace(/\./g, '\\.')}-dev\\.(\\d+)$`,
)
const devCounters = versions
.map((v) => devPattern.exec(v))
.filter((m): m is RegExpExecArray => m !== null)
.map((m) => parseInt(m[1]!, 10))
const nextDev = (devCounters.length === 0 ? 0 : Math.max(...devCounters)) + 1
const nextVersion = `${nextTarget}-dev.${nextDev}`
console.log(
`Latest stable: ${latestStable.major}.${latestStable.minor}.${latestStable.patch}`,
)
console.log(`Publishing pre-release: ${nextVersion}`)
const originalPackageJson = readFileSync(packageJsonPath, 'utf8')
const pkg = JSON.parse(originalPackageJson) as { version: string }
pkg.version = nextVersion
const trailingNewline = originalPackageJson.endsWith('\n') ? '\n' : ''
writeFileSync(
packageJsonPath,
JSON.stringify(pkg, null, 2) + trailingNewline,
'utf8',
)
try {
execSync(`npm publish --tag dev`, {
stdio: 'inherit',
cwd: process.cwd(),
})
} finally {
writeFileSync(packageJsonPath, originalPackageJson, 'utf8')
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment