Thanks for your interest in contributing.
Requirements: Node 18+, bun 1.3+.
bun install
bun run build
bun run testdualmark/
├── packages/
│ ├── core/ @dualmark/core
│ ├── converters/ @dualmark/converters
│ ├── astro/ @dualmark/astro
│ ├── cloudflare/ @dualmark/cloudflare
│ └── cli/ @dualmark/cli
├── examples/
├── apps/
└── spec/
- Fork and branch from
main. - Make changes. Add tests. Run
bun run testandbun run typecheck. - Add a changeset:
bun run changeset. Pick affected packages and bump type. - Open a PR. CI must pass.
- TypeScript strict mode.
- No
as any, no@ts-ignore, no@ts-expect-errorwithout an inline reason. - Prefer existing code style. Run
bun run format(Prettier) before committing. - Tests live next to source as
*.test.ts.
Releases are split into two explicit steps so a stray commit on main can never publish to npm.
When changesets land on main, the Version (PR bot) workflow opens or updates a PR titled chore: version packages. The PR contains:
package.jsonversion bumps for all affected@dualmark/*packages- Generated
CHANGELOG.mdentries - Removal of consumed
.changeset/*.mdfiles
Review and merge that PR when you're ready to cut a release.
After the version PR merges:
- Go to GitHub → Releases → Draft a new release.
- Click Choose a tag and create a new tag matching the version, e.g.
v0.3.0. - Set release title and notes (the changelog entry is a good source).
- Click Publish release.
Publishing the release fires the Release (npm publish) workflow which:
- Checks out the exact tag
- Builds + tests + typechecks all
@dualmark/*packages - Verifies at least one package version matches the tag
- Packs each
packages/*withbun pm pack --quiet(rewritesworkspace:*deps to concrete versions in the tarball) - Publishes each tarball with
npm publish <tarball> --provenance --access public --tag latest, which:- Uploads the pre-packed tarball
- Generates a Sigstore-backed npm provenance attestation tying the published artifact to this exact workflow run + commit SHA (visible as a "Provenance" badge on the package's npm page)
- Auth via npm Trusted Publishing (OIDC) — no
NPM_TOKENsecret. The workflow'sid-token: writeidentity authenticates the publish, and each@dualmark/*package must have this repo +release.ymlregistered as a trusted publisher on npmjs.com - Skips packages already at that version on the registry (idempotent re-runs), and does not abort on a single package's failure — it publishes every package it can, then fails the job at the end listing the ones that didn't. A re-run then picks up only the stragglers.
Why bun pm pack + npm publish instead of just bun publish? As of bun 1.3.5, bun publish does not yet support --provenance (tracked at oven-sh/bun#15601). When that ships in a stable bun release, the two-step flow collapses back to a single bun publish --provenance call.
If some packages fail to publish, the ones that succeeded are already on npm; fix the failing packages (usually a missing trusted-publisher registration — see below) and re-run the workflow via Actions → Release → Run workflow with the tag as input. The re-run skips whatever already published.
Because publishing is OIDC-based, a few operational rules apply that are easy to trip over:
- Every new
@dualmark/*package needs a one-time bootstrap. npm's trusted publishing requires the package to already exist on the registry before a trusted publisher can be attached (npm/cli#8544), so CI cannot make a package's first publish. When you add an adapter: publish@dualmark/<name>@<version>once manually (a localnpm publishwith 2FA, no CI token needed), then register its trusted publisher, then let CI take over. Skipping this makes the next release fail on the new package (the rest still publish). - Register the trusted publisher per package — repo
dodopayments/dualmark, workflow filerelease.yml. Via the npm web UI (Package → Settings → Trusted Publishers) or the CLI (npm trust github "@dualmark/<name>" --repository dodopayments/dualmark --file release.yml --allow-publish -y; requires npm ≥ 11.15.0 locally and account 2FA; add a short sleep between calls when looping to avoid rate limits). - Do not rename
release.yml. The trusted publisher is bound to the workflow filename. Renaming or moving this file silently breaks publishing for all packages until every registration is updated to the new filename. - Revoke the old
NPM_TOKENonce OIDC is proven. After the first successful OIDC release, delete theNPM_TOKENrepository secret and revoke the token on npmjs.com — leaving it in place preserves the long-lived-credential exposure this flow removed.
Consumers (and you) can verify any @dualmark/* tarball's provenance attestation with:
npm audit signatures
# or, per-package:
npm view @dualmark/core --json | grep -A2 attestationschangesets/action@v1 can do both versioning and publishing in one step, but that means every push to main with a pending changeset can publish. Splitting them puts the publish behind an explicit human action (drafting a release in the UI), and keeps the version PR loop convenient for contributors.
Be excellent to each other. We follow the Contributor Covenant.
By contributing, you agree your work is licensed under the Apache License 2.0 (see LICENSE and NOTICE).