Publishing Packages
How to publish monorepo packages to npm using the dual-mode export strategy.
Basilic distribution (create-basilic)
The public npm package is create-basilic, built from tools/create-basilic. Do not run scripts/prepare-publish.mjs for it (files: ["dist"] would drop the bundled template).
- Release Please opens a release PR from conventional commits (root version +
tools/create-basilic/package.json). - Maintainers merge that PR after scaffold acceptance. The tag is
vX.Y.Z. publish-create-basilic.ymlchecks out that SHA, assembles and packs once, tests the tarball, publishes with npm trusted publishing (id-tokenonly on that job), and attaches the same tarball + SHA-256 to the GitHub Release.- Prerelease versions (
0.1.0-next.1) use npm dist-tagnext, notlatest.
Maintainer setup (one-time)
- Claim npm
create-basilic(or an owned scope) and bind a trusted publisher to this repo,publish-create-basilic.yml, and thereleaseenvironment. - Install a GitHub App for Release Please (
RELEASE_PLEASE_APP_ID/RELEASE_PLEASE_APP_PRIVATE_KEY) so follow-on checks run. DefaultGITHUB_TOKENdoes not chain. - Protect the
releaseenvironment. Squash-merge withPR_TITLE+PR_BODY. Authorize who may merge release PRs.
Recovery
Defective release → patch + deprecate; never rewrite tags; never promise to un-generate adopter repos. If npm succeeded and the GitHub asset failed, re-attach the retained artifact. Never overwrite an npm version.
Dual-mode @repo/* packing
Packages use dual-mode exports. Pattern A (@repo/core, @repo/react, @repo/ui): workspace import is src/; prepack builds and rewrites to dist/ for npm. Pattern B (@repo/utils, @repo/error, @repo/email): workspace Node already uses dist/; prepack still rewrites remaining src/ paths (types/source) and packs files: ["dist"]. ESM details: ESM & TypeScript Strategy. This path is not how create-basilic is published. v1 does not publish every @repo/* package.
How It Works
The publishing process uses npm lifecycle hooks:
prepack Hook
Runs before pnpm pack or pnpm publish:
- Builds the package (
pnpm buildruns first via the prepack script) - Stores original
package.jsonexports/main/types in.package-originals.json - Transforms all export paths from
src/todist/(subpath exports preserved) - Sets
files: ["dist"]to ensure only built files are included
postpack Hook
Runs after packing:
- Reads the stored originals from
.package-originals.json - Restores the original
package.jsonconfiguration - Removes the temporary
.package-originals.jsonfile
Package Configuration
Packages that use these scripts should have:
Pattern A — development exports (in package.json):
{
"exports": {
".": {
"types": "./src/index.ts",
"import": "./src/index.ts"
}
}
}Publishing exports (automatically transformed by prepare-publish.mjs):
{
"exports": {
".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
"./async": { "types": "./dist/async/index.d.ts", "import": "./dist/async/index.js" }
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": ["dist"]
}Packages with subpath exports have all paths transformed; main and types are only set when a root (.) export exists.
Usage
These scripts are automatically invoked via npm/pnpm lifecycle hooks in package.json:
{
"scripts": {
"prepack": "pnpm build && node ../../scripts/prepare-publish.mjs",
"postpack": "node ../../scripts/restore-publish.mjs"
}
}Publishing a Package
- Build and test your package locally
- Update version in
package.json(or usepnpm version) - Publish:
cd packages/your-package pnpm publish
The prepack hook will automatically:
- Build the package
- Switch exports to
dist/ - Create the tarball
The postpack hook will automatically:
- Restore the original
package.json - Clean up temporary files
Scripts Reference
The publishing scripts (prepare-publish.mjs and restore-publish.mjs) are located in the /scripts directory.
Notes
- The scripts use
process.cwd()to find the package'spackage.json(they run from within each package directory) - If
.package-originals.jsondoesn't exist,restore-publish.mjsexits gracefully (useful for first-time runs) - These scripts are only needed for packages that will be published to npm
- Private workspace-only packages don't need these scripts
Related Documentation
- Package Conventions - Package architecture and organization
- Monorepo Structure - Turborepo organization and package dependencies
- Packages Reference - Complete reference for all shared packages