Publishing Helper Modules
Language: JavaScript
How a module's package.json, npmrc, test directory, and dependencies must be configured for the unified CI/CD pipeline to test and publish it. Publishing is CI-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.
Companion docs.
../../dev/cicd-publishing.md- operational walkthrough of the unifiedci-publish-helper-modules.ymlworkflow, GITHUB_TOKEN permissions, fresh-state recovery, failure modes, and CI-side troubleshooting (401 / 403 / registry URL).versioning/bump-checklist.md- the step-by-step version-bump procedure including SemVer classification, commit format, multi-module bumps, and post-publish verification.module-docs.md- what every module README must contain (badges, sections, ordering). This file scopes only thepackage.jsonand_test/layout.
This page scopes package-configuration rules; the companion docs scope how to run the pipeline and how to bump a version.
On This Page
- Package Identity
- Dependency Rules
- npmrc Configuration
- Registry Ignore File (
.npmignore) - Linter Configuration
- Required Scripts
- Test Directory Structure
- Same-Version Republish for Generated Packages
- Further Reading
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) |
type | "module" (mandatory - all helper modules are ESM) |
exports | An exports map pointing at the entry file (replaces the legacy "main" field) |
The "exports" Map
Every helper module is an ES Module ("type": "module") and uses an "exports" map instead of the legacy "main" field. The "exports" map controls what consumers can import and is the single source of truth for the package's entry point.
Required shape:
{
"type": "module",
"exports": {
".": "./[module].js",
"./package.json": "./package.json"
}
}Rules:
"."points at the module's entry file (e.g."./utils.js","./adapter.js")."./package.json"is always exported so tooling can read the package metadata.- Companion files consumed by the entry file (e.g.
[module].config.js,[module].errors.js,parts/*.js) are not listed as separate export paths. They are resolved relative to the entry file through normal ESM relative imports. The"exports"map exposes only the public entry point. parts/subdirectories are never exported. They are internal implementation files consumed by the entry file's relative imports, not by consumers. Seemodule-structure.md→ Parts Pattern.- The legacy
"main"field is not used. A package with both"main"and"exports"is misconfigured - remove"main". - Every export path must include the
.jsextension. ESM resolution requires explicit extensions for relative paths.
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. The full strategy is in dependencies.md.
npmrc Configuration
Use machine-level ~/.npmrc with environment variable support. Do not create per-module .npmrc files.
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 ../../dev/npmrc-setup.md for the complete setup guide and ../../dev/onboarding-github-packages.md for the GitHub token side.
Registry Ignore File (.npmignore)
Every published JS module must have a .npmignore file at its root.
Without it, npm pack includes everything that is not in .gitignore - meaning _test/, eslint.config.js, .github/, and other dev-only files all land in the published tarball. This bloats the package and exposes internal structure that consumers have no use for.
Rules:
- Place
.npmignoreat the module root (alongsidepackage.json). - It must exclude at minimum:
_test/,eslint.config.js,.github/,THOUGHTS.md. - It must include (i.e. not accidentally exclude): the main entry file,
README.md,ROBOTS.md,docs/,package.json, and anyparts/orsrc/subdirectories that contain production code. README.md,ROBOTS.md, anddocs/ship intentionally -README.mdlinks to both, and those links must resolve for anyone reading the package page or consuming the package.THOUGHTS.mdis an internal engineering decision journal for contributors. It must never ship in the published tarball. See THOUGHTS.md convention.- Verify with
npm pack --dry-runfrom the module root before publishing. The output lists every file that would be included.
Canonical reference: For the exact file contents and the complete exclusion list, refer to js-helper-utils - it is the simplest base module and serves as the reference implementation for all JS helper modules. Copy its .npmignore as your starting point and adjust only for module-specific additions (e.g. parts/ subdirectory modules need no adjustment; adapter modules with a schema/ folder may need to verify it is included).
Note: other languages have their own equivalent mechanisms (Python: MANIFEST.in or pyproject.toml exclude patterns; Java: Maven/Gradle publish configuration). Each language's publishing documentation covers its own convention. The principle is the same: every published package must explicitly control what ships.
Linter Configuration
Every JS module must have an eslint.config.js file at its root (ESLint flat config, required for ESLint v10+).
Linter config is a three-line re-export of the shared @superloomdev/js-helper-eslint-config package. The shared config is the single source of truth for all lint rules; per-module overrides are not permitted. The config must be present for npm run lint to work as part of the pre-publish gate.
Canonical consumer form:
import { base } from '@superloomdev/js-helper-eslint-config';
export default [ ...base ];Application-tier repos with JSX and browser globals use the app preset instead of base. See code-formatting.md → Shared ESLint Configuration for the full preset table.
Three properties of the shared config are policy, not preference:
ecmaVersion: 2022- matches the Node.js 24+ engine floor.- No
argsIgnorePattern/caughtErrorsIgnorePattern. Underscore-prefixed parameters (_param,catch (_err)) are forbidden; lint must flag them. Parity parameters keep their real name with// eslint-disable-line no-unused-vars; unused catch bindings usecatch {. Seecode-formatting.md-> Parameter Naming. js-helper-eslint-configis the head of the CI chain. It must publish before any other module. The CI workflow (ci-publish-helper-modules.yml) chainstest-eslint-configandpublish-eslint-configahead of all other test/publish jobs.
Note: other languages have their own linter conventions (Python: ruff or pylint config; etc.). Each language's module structure documentation covers its own convention.
Required Scripts
Every module package.json must include:
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"test": "node --test _test/test.js"
}
}The pre-publish gate requires both npm run lint (from the module root) and npm test (from _test/) to exit 0 locally before the version bump is pushed. See ../../dev/testing-local-modules.md → Pre-Publish Checklist.
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 (neverfile:for siblings - that breaks in CI; seepitfalls.mdentry 8) - No
.npmrcin_test/- use global npmrc
Same-Version Republish for Generated Packages
A generated reference theme package (e.g., Material, Carbon) completes its schemes from the base template at generation time. When the base template is republished at the same version with corrected values, the generated package must be regenerated and republished at the same version. The procedure:
- Regenerate all schemes from the corrected, registry-installed base.
- Verify the installed base shasum equals the registry shasum before writing.
- Byte-compare regenerated output against committed data files.
- Run the module's full test suite (including provenance and exact typography tests).
- Delete the exact queried
1.0.0version ID throughgh apiimmediately before the push that publishes it. - Push the known publishing commit.
- Compare local pack shasum to registry shasum after CI publishes.
- Regenerate every consumer lockfile that resolves the republished package.
Never delete a package version the active plan does not name. Never bump a version to clear a checksum mismatch. See pitfalls-migration.md - Generated profile retains stale values.
Further Reading
- CI/CD Publishing - operational details of the unified
ci-publish-helper-modules.ymlworkflow + CI troubleshooting (401 / 403 / registry URL). - Version Bump Checklist - SemVer classification + step-by-step bump procedure.
- Peer Dependencies - the dependency strategy that publishing relies on.
- Module Testing - badges and the testing tiers gating publish.
- Module README Structure - badges, Universal README Sections, and the structural-pass checklist.