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
- Node.js and npm installed (plus pnpm, yarn or bun if you use one)
- A registry account (GitHub Packages, npmjs, etc.)
- An authentication token
# Verify npm is installednpm --version
# For GitHub Packages: generate a personal access token# GitHub → Settings → Developer settings → Personal access tokens# Scope: write:packages, read:packagesService Configuration
Put pilum.yaml in the package directory, next to package.json:
name: my-packagetype: npmdescription: 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
| Field | Description |
|---|---|
name | Pilum service name. The published package name comes from package.json. |
npm.scope | npm scope, e.g. @my-org. Used for the registry mapping in .npmrc. |
npm.token_env | Name of the environment variable holding the auth token |
Optional Fields
| Field | Type | Default | Description |
|---|---|---|---|
npm.registry | string | https://npm.pkg.github.com | Registry URL |
npm.access | string | restricted | restricted or public |
npm.package_manager | string | npm | npm, pnpm, yarn or bun |
build.cmd | string | - | 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.
| Value | Install command |
|---|---|
npm (default) | npm ci |
pnpm | pnpm install --frozen-lockfile |
yarn | yarn install --frozen-lockfile |
bun | bun 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:
- install dependencies [build] — The package manager’s install command
- snapshot package manifest [build, deploy] — Copies
package.jsonto.pilum-package.json.bak. If a backup from an earlier failed run exists, it is restored first. - set version [build] —
npm version <tag without v> --no-git-tag-version --allow-same-version. Skipped when--tagis empty orlatest. - build package [build] —
build.cmd. Skipped when it is empty. - resolve workspace dependencies [build] — pnpm only: pins
workspace:*dependencies to real versions - publish package [deploy] — Writes
.npmrcand runsnpm publish - restore package manifest [build, deploy] — Moves the backup back over
package.json. Markedalways_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.0The 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:
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):
if [ -z "$NODE_AUTH_TOKEN" ]; then echo "Error: NODE_AUTH_TOKEN environment variable is not set" exit 1fiecho "//npm.pkg.github.com/:_authToken=$NODE_AUTH_TOKEN" > .npmrcecho "@my-org:registry=https://npm.pkg.github.com" >> .npmrcnpm publish --access 'restricted'Example Commands
# Full pipeline: install, version, build, publishpilum deploy my-package --tag=v1.0.0
# Install, version and build only (no publish)pilum build my-package --tag=v1.0.0
# Preview commandspilum dry-run my-package --tag=v1.0.0Do 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: Publishon: 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: Publishon: 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 UnauthorizedMake sure the variable named in npm.token_env is set:
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/userVersion already exists
npm ERR! 403 Cannot publish over previously published versionUse 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
- Service Configuration — Full
pilum.yamlreference - Environments and CI — Running Pilum in CI
- CLI Commands — All commands and flags
- Troubleshooting — Common issues