Skip to content

npm

Publish npm packages to GitHub Packages, npmjs.com, or any npm-compatible registry. Pilum installs dependencies, sets the package version from the release tag, builds, publishes, and then restores package.json.

Prerequisites

  1. Node.js and npm installed (plus pnpm, yarn or bun if you use one)
  2. A registry account (GitHub Packages, npmjs, etc.)
  3. An authentication token
Terminal window
# Verify npm is installed
npm --version
# For GitHub Packages: generate a personal access token
# GitHub → Settings → Developer settings → Personal access tokens
# Scope: write:packages, read:packages

Service Configuration

Put pilum.yaml in the package directory, next to package.json:

name: my-package
type: npm
description: My private npm package
npm:
scope: "@my-org"
registry: "https://npm.pkg.github.com"
token_env: "NODE_AUTH_TOKEN"
access: "restricted"
build:
cmd: "npm run build"

Use type: npm (or provider: npm).

Required Fields

FieldDescription
namePilum service name. The published package name comes from package.json.
npm.scopenpm scope, e.g. @my-org. Used for the registry mapping in .npmrc.
npm.token_envName of the environment variable holding the auth token

Optional Fields

FieldTypeDefaultDescription
npm.registrystringhttps://npm.pkg.github.comRegistry URL
npm.accessstringrestrictedrestricted or public
npm.package_managerstringnpmnpm, pnpm, yarn or bun
build.cmdstring-Build command, run with /bin/sh -c in the package directory

npm.package_manager works but is missing from the JSON schema, so editors using the schema may flag it as unknown.

Package Managers

npm.package_manager selects the install command. Publishing always uses npm publish.

ValueInstall command
npm (default)npm ci
pnpmpnpm install --frozen-lockfile
yarnyarn install --frozen-lockfile
bunbun install --frozen-lockfile

Only pnpm turns on the “resolve workspace dependencies” step.

pnpm

npm:
scope: "@my-org"
token_env: "NODE_AUTH_TOKEN"
package_manager: "pnpm"
build:
cmd: "pnpm run build"

pnpm Monorepo

For pnpm workspaces, use --filter with the ... suffix to build a package and all its workspace dependencies:

npm:
scope: "@my-org"
token_env: "NODE_AUTH_TOKEN"
package_manager: "pnpm"
build:
cmd: "pnpm --filter @my-org/my-package... build"

Before publishing, Pilum replaces workspace:* dependency specifiers in package.json with the concrete versions of the sibling packages, so the published manifest installs outside the workspace.

yarn

npm:
scope: "@my-org"
token_env: "NODE_AUTH_TOKEN"
package_manager: "yarn"
build:
cmd: "yarn build"

--frozen-lockfile is the Yarn 1 flag. Yarn Berry (2+) expects --immutable and may reject it.

bun

npm:
scope: "@my-org"
token_env: "NODE_AUTH_TOKEN"
package_manager: "bun"
build:
cmd: "bun run build"

Deployment Steps

All steps run in the package directory:

  1. install dependencies [build] — The package manager’s install command
  2. snapshot package manifest [build, deploy] — Copies package.json to .pilum-package.json.bak. If a backup from an earlier failed run exists, it is restored first.
  3. set version [build] — npm version <tag without v> --no-git-tag-version --allow-same-version. Skipped when --tag is empty or latest.
  4. build package [build] — build.cmd. Skipped when it is empty.
  5. resolve workspace dependencies [build] — pnpm only: pins workspace:* dependencies to real versions
  6. publish package [deploy] — Writes .npmrc and runs npm publish
  7. restore package manifest [build, deploy] — Moves the backup back over package.json. Marked always_run, so it runs even when an earlier step failed.

The working tree’s package.json is unchanged after the run, pass or fail.

Version Sync

Pilum strips a leading v from the tag, since npm versions don’t use it:

--tag=v2.1.0 → npm version 2.1.0
--tag=1.0.0 → npm version 1.0.0

The version is set with --no-git-tag-version --allow-same-version, so no git commit or tag is created.

Registry Configuration

GitHub Packages (default)

npm:
scope: "@my-org"
registry: "https://npm.pkg.github.com"
token_env: "NODE_AUTH_TOKEN"
access: "restricted"

Set the token:

Terminal window
export NODE_AUTH_TOKEN="ghp_xxxxx"

npmjs.com

npm:
scope: "@my-org"
registry: "https://registry.npmjs.org"
token_env: "NPM_TOKEN"
access: "public"

Custom Registry

npm:
scope: "@my-org"
registry: "https://npm.my-company.com"
token_env: "NPM_AUTH_TOKEN"
access: "restricted"

Generated Publish Script

The publish step runs this script (shown for the default registry):

Terminal window
if [ -z "$NODE_AUTH_TOKEN" ]; then
echo "Error: NODE_AUTH_TOKEN environment variable is not set"
exit 1
fi
echo "//npm.pkg.github.com/:_authToken=$NODE_AUTH_TOKEN" > .npmrc
echo "@my-org:registry=https://npm.pkg.github.com" >> .npmrc
npm publish --access 'restricted'

Example Commands

Terminal window
# Full pipeline: install, version, build, publish
pilum deploy my-package --tag=v1.0.0
# Install, version and build only (no publish)
pilum build my-package --tag=v1.0.0
# Preview commands
pilum dry-run my-package --tag=v1.0.0

Do not publish with --only-tags=deploy. That runs only snapshot, publish and restore: the version is not set and workspace:* dependencies are not resolved, so the package goes out with the version and specifiers that are in package.json.

CI/CD Integration

GitHub Actions (pilum-action)

name: Publish
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Publish with Pilum
uses: SID-Technologies/pilum-action@v1
with:
command: deploy
services: my-package
tag: ${{ github.event.release.tag_name }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}

GitHub Actions (manual install)

Release archives are named pilum_<version>_<os>_<arch>.tar.gz, so download by pattern:

name: Publish
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Pilum
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release download --repo SID-Technologies/Pilum --pattern '*_linux_amd64.tar.gz' --output pilum.tar.gz
tar xzf pilum.tar.gz pilum
sudo mv pilum /usr/local/bin/
- name: Publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
run: pilum deploy my-package --tag=${{ github.event.release.tag_name }}

Troubleshooting

Authentication failed

npm ERR! 401 Unauthorized

Make sure the variable named in npm.token_env is set:

Terminal window
export NODE_AUTH_TOKEN="ghp_xxxxx"

For GitHub Packages, the token needs the write:packages scope.

Package not found after publish

For GitHub Packages, the scope must match the GitHub org or user that owns the repository:

npm:
scope: "@my-org" # Must match your GitHub org/user

Version already exists

npm ERR! 403 Cannot publish over previously published version

Use a new tag. Registries don’t allow overwriting a published version. If you did not pass --tag, the version in package.json was published unchanged.

package.json left modified

A run was killed before the restore step. The next run restores it from .pilum-package.json.bak automatically, or restore it yourself with mv .pilum-package.json.bak package.json.

Next Steps