Skip to content

Instantly share code, notes, and snippets.

@coderbyheart
Last active May 18, 2026 12:43
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.

Publishing a pre-release

While a change is still under review in a pull request, you can publish a pre-release to npm so that consumer services can pick it up and validate the changes end-to-end before the PR is merged.

The script inspects the versions published to npm, takes the latest stable release, bumps the minor version, and appends a -dev.<n> suffix with an incremented counter. For example, if the latest release is 14.0.4 and no dev release exists yet for 14.1.0, the first invocation publishes 14.1.0-dev.1, the next 14.1.0-dev.2, and so on.

For a breaking change, bump the major version instead by passing --major — e.g. with a latest release of 14.0.4, this publishes 15.0.0-dev.1:

npm run publish:dev -- --major
``` Pre-releases are published under the `dev`
dist-tag and with restricted access.

To install the version that was just published, install it like this:

```bash
npm i --save-exact nrfcloud/[email protected]
/*
* 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
* minor or the major bumped by one and lower components reset to 0
* (e.g. `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` flag;
* 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, { dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const packageJsonPath = path.join(__dirname, '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 bumpMajor = process.argv.slice(2).includes('--major')
const nextTarget = bumpMajor
? `${latestStable.major + 1}.0.0`
: `${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: __dirname,
})
} finally {
writeFileSync(packageJsonPath, originalPackageJson, 'utf8')
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment