The package
dicom-validator-ts is a TypeScript library for validating DICOM files against the DICOM standard (PS3.3, PS3.6). It checks VR format compliance, VM constraints, and IOD structure, and ships both a programmatic API and a CLI. That's the ten-second pitch — the rest of this post is about getting it onto npm and keeping it there automatically.
Step 1 — Get your versioning straight before you publish anything
The part that trips up first-timers: package.json and git tags use different conventions, and both are correct.
"version"inpackage.json:"0.1.0"— novprefix. This follows the semver spec, which defines version strings without a prefix.- git tag:
v0.1.0—vprefix. React, Next.js, Vite, tsup, vitest — virtually every mainstream npm package follows this convention.
The only ecosystems where you consistently see tags without the v are Go modules (where the v is actually part of the module path semantics) and some monorepos with their own naming schemes.
So: package.json says 0.1.0, git tag says v0.1.0. Both correct, different purposes.
Step 2 — Understand what npm version actually does
npm version patch is three operations bundled into one command:
- Bumps
"version"inpackage.jsonfrom0.1.0to0.1.1(andpackage-lock.jsonif present) - Creates a git commit containing that change
- Creates a git tag
v0.1.1
The default commit message is just the bare version number — 0.1.1. If your project follows conventional commits, pass -m to override:
npm version patch -m "release: v%s"
%s expands to the new version, so the commit reads release: v0.1.1.
One hard constraint: npm version refuses to run if the working tree isn't clean — meaning no staged-but-uncommitted changes. Untracked files are fine; git add-ed files that haven't been committed yet will cause an error. The intended workflow is:
# commit your fix first
git add -p
git commit -m "fix: handle edge case in tag parser"
# then bump the version
npm version patch
# push both the commit and the tag
git push origin main --tags
The version bump gets its own isolated commit. It shouldn't be tangled up with the fix that warranted it.
For the very first publish where the version is already set correctly and you just need the tag, there's a way to create the tag without changing the version number:
npm version 0.1.0 --allow-same-version
This creates the git commit and v0.1.0 tag without touching the version field.
The patch in npm version patch follows semver semantics — use minor for backwards-compatible new features (0.1.0 → 0.2.0) and major for breaking changes (0.1.0 → 1.0.0). All three variants commit, tag, and update package.json in the same way.
Step 3 — Enable 2FA before your first publish
npm has required two-factor authentication on publish operations since 2022 to protect against supply-chain attacks. The threat model: an attacker who steals your login session can't silently push a malicious version without also having physical access to your TOTP device.
Enable it at https://www.npmjs.com/settings/<your-username>/profile under Two-Factor Authentication.
For interactive publishing from your own machine, this doesn't cause friction — npm publish prompts for the OTP and proceeds. The wall appears when you move to automation.
Step 4 — Publish manually
# confirm the name is available (404 = free)
npm view dicom-validator-ts
# verify what will be bundled
npm pack --dry-run
# publish
npm publish
npm pack --dry-run is worth running at least once. It prints exactly which files will be in the tarball:
npm notice 📦 dicom-validator-ts@0.1.0
npm notice Tarball Contents
npm notice 3.4kB CONTRIBUTING.md
npm notice 1.1kB LICENSE
npm notice 4.8kB README.md
npm notice 1.4kB SECURITY.md
npm notice 66.0kB dist/chunk-JGZJCGTU.cjs
npm notice 64.0kB dist/chunk-VVDHN4EG.mjs
npm notice 6.3kB dist/cli/index.cjs
npm notice 5.9kB dist/cli/index.mjs
npm notice 6.5kB dist/index.d.ts
npm notice 444B dist/index.mjs
npm notice 1.2kB package.json
npm notice total files: 15
The files field in package.json controls inclusion. If it's absent, npm applies its own exclusion rules — but verify that src/, tests/, and any .env files aren't slipping in.
One warning appeared during publish that's worth addressing before going further:
npm warn publish "bin[dicom-validator]" script name was cleaned
This means the bin field in package.json had a character npm had to sanitize. Running npm pkg fix resolves it.
Step 5 — Set up GitHub Actions for automated publish
The workflow triggers on any tag matching v*:
# .github/workflows/publish.yml
name: Publish to npm
on:
push:
tags:
- 'v*'
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run build
- run: npm run lint
- run: npm test
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
setup-node with registry-url configures .npmrc so that NODE_AUTH_TOKEN is picked up automatically by npm publish. Without registry-url, the environment variable is silently ignored and the publish fails with an auth error in CI even though the same command works locally.
The token you need — and the checkbox that matters
Go to npmjs.com → account → Access Tokens → Generate New Token → Granular Access Token.
Set Read and write on your package. Then — and this is what caused the GitHub Actions failure:
Check "Bypass two-factor authentication (2FA)".
Without it, CI hits this wall:
npm error code EOTP
npm error This operation requires a one-time password from your authenticator.
npm error You can provide a one-time password by passing --otp=<code> to the command you ran.
There's no keyboard attached to a GitHub Actions runner, so there's no way to enter a TOTP code interactively. The bypass flag tells npm: this token is trusted automation, skip the OTP requirement. Store the token as a repository secret named NPM_TOKEN under Settings → Secrets and variables → Actions.
The release flow then becomes:
# after committing your changes
npm version patch # bumps, commits, tags
git push origin main --tags # triggers the Actions workflow
The short version
package.jsonversion: nov. Git tag:vprefix. Both are right, different systems.npm version patchbumpspackage.json, commits, and tags in one step — but requires a clean working tree (staged changes block it; untracked files don't).- Enable 2FA on your npm account. It won't block manual publishing (you enter the OTP interactively), but it will block GitHub Actions unless your Granular Access Token has Bypass 2FA checked.
- Run
npm pack --dry-runbefore the first publish to verify what's actually going into the tarball. - Include
registry-url: 'https://registry.npmjs.org'in thesetup-nodestep, orNODE_AUTH_TOKENwon't be read.
A few things worth keeping in mind
npm and GitHub are entirely separate systems. An npm account and a GitHub account with the same username are coincidentally named, not linked. The Collaborators shown on an npm package page are npm accounts. Connecting them in your npm profile settings is cosmetic — it adds a GitHub link to your npm profile page — but GitHub Actions doesn't require or use that link.
You can undo a publish, with conditions. npm unpublish <package>@<version> works within 72 hours of publishing. After that, the version is permanent. Even within the window, the same package name + version cannot be reused for 24 hours after unpublishing. (Verify current terms at docs.npmjs.com — policy details can change.)
The 72-hour window exists because of the "left-pad incident" in 2016: a developer unpublished hundreds of small packages he maintained, including an 11-line string-padding utility that half the JavaScript ecosystem depended on transitively. Builds broke worldwide for hours. npm introduced the time-limited unpublish policy afterward to prevent repeat incidents while still allowing quick corrections after accidental publishes.
repository, homepage, and bugs in package.json surface on the npm listing page. These cost nothing to add and make the package immediately more navigable:
{
"repository": {
"type": "git",
"url": "https://github.com/tatsu/dicom-validator-ts.git"
},
"homepage": "https://github.com/tatsu/dicom-validator-ts#readme",
"bugs": {
"url": "https://github.com/tatsu/dicom-validator-ts/issues"
}
}
They take effect on the next npm publish and can be changed at any time.
About Tatsuhiko Arai (新井 竜彦)
Embedded software engineer (Qt, C/C++, Python). Medical imaging (DICOM) contractor. AWS All Certifications Engineer – Japan (2024–2025).

