Publishing Helper Modules
How to prepare and publish helper modules to GitHub Packages under the @your-org scope. Publishing is CI/CD only - bumping the version field in a module's package.json and pushing to main triggers the publish automatically. There is no manual npm publish.
On This Page
- Package Identity
- Dependency Rules
- npmrc Configuration
- Required Scripts
- Test Directory Structure
- README Requirements
- CI/CD Publishing
- Version Bumping
- Troubleshooting
Package Identity
| Field | Required Value |
|---|---|
name | @your-org/js-helper-* or @your-org/js-server-helper-* |
license | MIT |
private | false |
publishConfig.registry | https://npm.pkg.github.com (no trailing slash, no scope suffix) |
Dependency Rules
Foundation Modules (Zero Dependencies)
js-helper-utils and js-helper-debug are fully self-contained. They must never depend on each other or on any other helper module.
Other Modules
All other modules may depend on the foundation modules via peer dependencies. This avoids duplicate installations and ensures a single shared instance.
npmrc Configuration
Use machine-level ~/.npmrc with environment variable support. Do not create per-module .npmrc files.
# One-time setup
npm config set @your-org:registry https://npm.pkg.github.com
npm config set //npm.pkg.github.com/:_authToken '${GITHUB_READ_PACKAGES_TOKEN}'
npm config set registry https://registry.npmjs.org/Load the token via the project environment script:
source init-env.sh
# Select: 1) devSee docs/dev/npmrc-setup.md for the complete setup guide.
Required Scripts
Every module package.json must include:
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"test": "node --test _test/test.js"
}
}Test Directory Structure
module-name/
_test/
test.js # Tests using node:test and node:assert/strict
package.json # private: true, references module as "file:../"
mock-data/ # (optional) JSON fixtures_test/package.jsonmust have"private": true- Reference the module under test as
"file:../"in dependencies - Reference published
@your-orgpackages by version for peer dependencies - No
.npmrcin_test/- use global npmrc
README Requirements
Every module README should include:
- Badges: Test status, License, Node.js version
- One-line description
- Foundation module note (if applicable)
- Exported functions list with signatures
- Configuration table with defaults
- Installation instructions
- Usage example with code
CI/CD Publishing
A single unified workflow .github/workflows/ci-helper-modules.yml handles both testing and publishing. Tests always run; publishing runs only when a module's package.json version field actually changed.
How it works:
- Push to
main(or open a PR) detectjob comparesHEAD~1:package.jsontoHEAD:package.jsonfor every changed module- Any file change → module queued for testing
- Version bump → module queued for publishing (in addition to testing)
test-offlineandtest-dynamodbjobs run the full test suite for every queued modulepublish-*jobs run only on main pushes, only for modules with a version bump, and only after their matching test job succeeds (needs: [detect, test-*])- Before
npm publish, a safety-net step runsnpm view <pkg>@<version>- if the version is already on the registry it logs a clear message and skips publish (no failure)
Why this design:
- No race conditions (tests run before publish in the same workflow)
- No duplicate workflow runs per commit
- Non-version commits don't attempt to publish, eliminating the
409 You cannot publish over the previously published versionsfailure mode - Revert or force-push scenarios are handled gracefully by the registry safety-net
CI authentication:
NODE_AUTH_TOKENis set at job level (not step level) so all steps can access private packagesregistry-url: 'https://npm.pkg.github.com'insetup-nodecreates proper.npmrcpermissions: packages: writegrants publish access
No manual .npmrc files needed in CI - GitHub Actions handles authentication automatically.
Version Bumping
Follow semantic versioning:
- Patch (1.0.x): Bug fixes, documentation updates
- Minor (1.x.0): New features, non-breaking changes
- Major (x.0.0): Breaking API changes
Version bumping is what triggers publishing. The detect job in ci-helper-modules.yml only schedules a publish when package.json's version field differs between HEAD~1 and HEAD. A commit without a version bump will run tests but not publish.
Bump the version in package.json, commit with a conventional commit message, and push to main:
# Example
git commit -m "feat(js-helper-debug): v1.3.0 - zero dependencies, improved text output"
git push origin mainYou can also bundle multiple bumps in a single commit - CI will publish each one in parallel.
Troubleshooting
401 Unauthorized during npm install
- Environment variable
GITHUB_READ_PACKAGES_TOKENnot set - Run
source init-env.shto load environment - Verify:
echo $GITHUB_READ_PACKAGES_TOKEN
403 Forbidden during npm publish
NODE_AUTH_TOKENnot available in the CI step- Ensure it is set at job-level
env, not step-level - Check
permissions: packages: writein workflow
Registry URL issues
publishConfig.registrymust be exactlyhttps://npm.pkg.github.com- No trailing slash (
/) - No scope suffix (
/@your-org)
Further Reading
- CI/CD Publishing - operational details of the unified
ci-helper-modules.ymlworkflow - Peer Dependencies - the dependency strategy that publishing relies on
- Module Testing - badges and the testing tiers gating publish