mod_07 - Publishing Packages¶
Publishing to npm means your code runs on thousands of machines you'll never see
One npm publish and your library is available to every developer with an internet connection. That's power and responsibility in equal measure. A broken release takes down builds. A malicious release gets reported to npm security. An accidental publish of internal code gets your npm account suspended. Know the mechanics before you hit publish
npm login and authentication¶
# Login to the npm registry
npm login
# Prompts for:
# Username: 0x1ris
# Password: ********
# Email: mahmoud@example.com
# (One-time password if 2FA is enabled)
# Verify you're logged in
npm whoami
# Check which registry you're publishing to
npm config get registry
# Login with a specific registry (for scoped packages or private registries)
npm login --registry=https://registry.mycompany.com
Authentication tokens¶
npm supports access tokens for CI/CD - never use your password in scripts
# Generate a publish token (from npm website or CLI)
npm token create
npm token list
npm token revoke <token-id>
# Use token in CI:
# NPM_TOKEN environment variable
# .npmrc: //registry.npmjs.org/:_authToken=${NPM_TOKEN}
Two-factor authentication¶
Enable 2FA for publishing - non-negotiable if you maintain popular packages
# Enable 2FA on npm (authorization only, or authorization and publishing)
npm profile enable-2fa auth-and-writes
# With 2FA enabled, publishing requires an OTP:
npm publish --otp=123456
npm publish¶
# Publish the current package
npm publish
# Publish with a tag (default: "latest")
npm publish --tag beta
# Publish a scoped package (requires the scope to match your npm org)
npm publish --access public # scoped packages are private by default
Before publishing, npm does: 1. Runs prepublishOnly script (if defined) - use this for validation 2. Checks "files" field or .npmignore to determine what gets included 3. Creates the tarball 4. Uploads to the registry
What gets published¶
npm publishes everything except: * Files in .gitignore (unless .npmignore exists, in which case .npmignore takes precedence) * Certain always-excluded files (.env, .git/, *.orig) * Hidden files starting with . (except .npmignore, package.json, and select others)
Check before publishing:
# Dry run - shows what would be published without actually publishing
npm publish --dry-run
# List files that will be in the tarball
npm pack --dry-run
# Create actual tarball (inspect contents)
npm pack
tar -tvf *.tgz
Always run npm pack --dry-run before your first publish. You don't want to accidentally publish: * .env files with secrets * Test fixtures with mock passwords * Internal documentation * Build artifacts from previous runs * node_modules
.npmignore¶
If you don't want to use the "files" whitelist approach, use .npmignore
# .npmignore
tests/
src/
*
.env
.DS_Store
This file follows .gitignore syntax. If .npmignore exists , it overrides .gitignore - files ignored by git might be included in the npm package unless .npmignore also ignores them
npm unpublish and deprecate¶
Unpublishing¶
You can unpublish a package within 72 hours of publication
# Unpublish a specific version
npm unpublish my-package@1.0.0
# Unpublish the entire package (only within 72 hours of first publish)
npm unpublish my-package --force
Warning: Unpublishing breaks every project that depends on your package. npm has a policy against unpublishing packages that others depend on. After 72 hours , you can't unpublish - only deprecate
Deprecating¶
Instead of unpublishing , deprecate a version
npm deprecate my-package@1.0.0 "Critical security issue - upgrade to 1.0.1"
Deprecated versions show a warning banner when installed. The package remains available (existing installs don't break) but users see:
npm WARN deprecated my-package@1.0.0: Critical security issue - upgrade to 1.0.1
Always deprecate instead of unpublish for anything older than 72 hours.
scoped packages¶
Scoped packages live under an npm organization or username
# Publish a scoped package
# package.json: "name": "@my-org/package-name"
npm publish --access public
# Scoped packages default to private - you need --access public to publish publicly
Scoped packages are the standard for: * Organizations with multiple packages (@google-labs/, @aws-sdk/, @angular/) * Company-internal packages on private registries * Preventing name collision on the public registry
{
"name": "@0x1ris/parser",
"version": "1.0.0",
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
}
}
versioning¶
# Bump version and create a git tag
npm version patch # 1.0.0 -> 1.0.1 (bug fixes)
npm version minor # 1.0.1 -> 1.1.0 (new features, backward compatible)
npm version major # 1.1.0 -> 2.0.0 (breaking changes)
# Custom version
npm version 1.5.0 # sets to exactly 1.5.0
# Pre-release versions
npm version premajor --preid beta # 1.0.0 -> 2.0.0-beta.0
npm version prerelease --preid beta # 2.0.0-beta.0 -> 2.0.0-beta.1
Each command: 1. Updates the version field in package.json 2. Commits the change to git 3. Creates a git tag (v1.0.1)
Versioning workflow¶
# For a feature release:
git checkout main
npm version minor # 1.1.0 -> 1.2.0
npm publish # push to registry
git push --tags # push tags to remote
# For a hotfix:
git checkout main
npm version patch # 1.2.0 -> 1.2.1
npm publish
git push --tags
package.json fields for publishing¶
"files"¶
Whitelist what gets published
{
"files": [
"dist/",
"LICENSE",
"README.md",
"package.json"
]
}
Without this, your entire project directory (except .gitignored stuff) goes to npm
"main"¶
Entry point for CommonJS consumers
{
"main": "./dist/index.js"
}
"bin"¶
Expose executables from your package
{
"bin": {
"my-cli": "./bin/cli.js"
}
}
When installed globally , my-cli becomes available as a system command. When installed locally , npx my-cli works. The referenced file must start with #!/usr/bin/env node
"types"¶
TypeScript declaration entry point
{
"types": "./dist/index.d.ts"
}
"exports"¶
Full conditional exports support for ESM and CJS
{
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
},
"./utils": {
"import": "./dist/utils.js",
"require": "./dist/utils.cjs"
}
}
}
"engines"¶
Specify Node version compatibility
{
"engines": {
"node": ">=18.0.0"
}
}
README and documentation¶
Your npm package page is generated from README.md
A good README includes:
- Installation -
npm install your-package - Quick start - minimal working example
- API reference - all exported functions/classes with parameters
- Examples - real usage patterns
- Configuration - environment variables , options
- Security - known security considerations , reporting process
npm renders the first 500KB of README.md on the package page. Put the most important stuff at the top
# My Parser
Zero-dependency parser for log files
## Install
```bash
npm install @0x1ris/parser
Usage¶
import { parse } from '@0x1ris/parser'
const result = parse('error: connection refused')
API¶
...
## security: what not to publish
**Before publishing, check your package contents:**
```bash
# Inspect the tarball
npm pack
tar -tvf *.tgz
# Or use the dry run
npm publish --dry-run
Never publish:¶
.envfiles or any files with secrets , tokens , or passwordsnode_modules- (npm excludes these automatically , but verify)- Test files with mock credentials or internal infrastructure names
- Source maps that expose original source code
- Build logs or debug output
- Internal documentation or architecture diagrams
If you accidentally publish a secret:
# 1. Immediately deprecate the version
npm deprecate my-package@bad-version "Contains sensitive data - do not use"
# 2. Rotate the exposed secret (API key, token, password)
# 3. npm support can unpublish if within 72 hours
# 4. Review what was exposed and audit damage
Reproducible builds¶
Set the "private": true field in package.json for internal packages as a safety net
{
"private": true,
"scripts": {
"prepublishOnly": "node -e 'console.error(\"This is private\")' && exit 1"
}
}
This crashes the publish if "private": true is somehow removed
publishing checklist¶
- Run
npm pack --dry-runand review the file list - Run
npm auditon your dependencies - Run your test suite
- Run
npm run build(if applicable) - Check that
"main","exports", and"types"point to correct files - Verify
.npmignoreor"files"excludes everything unnecessary - Make sure README.md exists and is up to date
- Set
"private": false(or don't include it) - Run
npm version <patch|minor|major>to bump - Run
npm publish(ornpm publish --access publicfor scoped packages) - Run
git push --tags
summary¶
npm loginauthenticates you - use tokens for CI , not passwordsnpm publish --dry-runshows what will be published - always check first- Use
npm unpublishonly within 72 hours -npm deprecateafter that - Scoped packages (
@scope/pkg) are the standard - use--access public npm version patch|minor|majorbumps version and creates git tags- Set
"files"field to whitelist published files - prevent secret leaks prepublishOnlyscript validates before publish- Run
npm pack --dry-runbefore every first publish to a new package - Accidental secret publish = deprecate + rotate + audit
prerequisites¶
mod_06_manage_dep.md - managing dependencies
next -> this is the last file in the async/modules section