{"id":51716507,"url":"https://github.com/teambit/pnpm-monorepo-packages-demo","last_synced_at":"2026-07-17T03:36:11.957Z","repository":{"id":371544632,"uuid":"1300873536","full_name":"teambit/pnpm-monorepo-packages-demo","owner":"teambit","description":"Publish packages from a plain pnpm monorepo to bit.cloud — no Bit tooling required","archived":false,"fork":false,"pushed_at":"2026-07-15T14:50:22.000Z","size":74,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-15T15:25:41.492Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/teambit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-14T19:25:01.000Z","updated_at":"2026-07-15T14:51:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/teambit/pnpm-monorepo-packages-demo","commit_stats":null,"previous_names":["teambit/pnpm-monorepo-packages-demo"],"tags_count":29,"template":false,"template_full_name":null,"purl":"pkg:github/teambit/pnpm-monorepo-packages-demo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teambit%2Fpnpm-monorepo-packages-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teambit%2Fpnpm-monorepo-packages-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teambit%2Fpnpm-monorepo-packages-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teambit%2Fpnpm-monorepo-packages-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/teambit","download_url":"https://codeload.github.com/teambit/pnpm-monorepo-packages-demo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teambit%2Fpnpm-monorepo-packages-demo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35566571,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-17T02:00:06.162Z","response_time":116,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-17T03:36:11.381Z","updated_at":"2026-07-17T03:36:11.941Z","avatar_url":"https://github.com/teambit.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pnpm Monorepo Packages Demo\n\n[![CI](https://github.com/teambit/pnpm-monorepo-packages-demo/actions/workflows/ci.yml/badge.svg)](https://github.com/teambit/pnpm-monorepo-packages-demo/actions/workflows/ci.yml)\n\n**A completely ordinary pnpm monorepo that publishes to [bit.cloud](https://bit.cloud) — no Bit CLI, no Bit config, no lock-in.**\n\nThe entire Bit integration is **one committed line** ([`.npmrc`](./.npmrc)) mapping the `@evinova-demo` scope to the bit.cloud registry:\n\n```ini\n@evinova-demo:registry=https://node-registry.bit.cloud\n```\n\nCredentials never enter the repo. One-time locally: `pnpm config set \"//node-registry.bit.cloud/:_authToken\" \u003ctoken\u003e` (or `npm login --registry=https://node-registry.bit.cloud`). In CI: a `BIT_CLOUD_TOKEN` GitHub secret plus the same `pnpm config set` step, run once before install.\n\nPackages publish under the `@evinova-demo` placeholder scope — point it at your own bit.cloud org with a single find-and-replace.\n\nEverything else is the tooling your team already uses: pnpm workspaces, TypeScript, vitest, Changesets, GitHub Actions.\n\n## What this demonstrates\n\n| Capability | Where in this repo | Docs |\n|---|---|---|\n| Publish packages to bit.cloud with plain `npm`/`pnpm` | [`package.json` scripts](./package.json), any `packages/*` | [Publishing packages](https://bit.cloud/docs/packages/publishing-packages) |\n| Bulk publish (all packages, atomically via `--batch`) | `pnpm publish:all` / [manual workflow](./.github/workflows/publish-manual.yml) | [Managing packages](https://bit.cloud/docs/packages/managing-packages) |\n| Individual publish (one package) | `pnpm --filter @evinova-demo/general.button publish` / manual workflow dropdown | [Managing packages](https://bit.cloud/docs/packages/managing-packages) |\n| Automated releases — one atomic batch publish | [Changesets](./.changeset) + [`release.yml`](./.github/workflows/release.yml) | [Publishing packages](https://bit.cloud/docs/packages/publishing-packages) |\n| Registry auth, local and CI, zero secrets in-repo | [`.npmrc`](./.npmrc) + `BIT_CLOUD_TOKEN` | [Configuring .npmrc](https://bit.cloud/docs/packages/configuring-npmrc) |\n| Works alongside npmjs / other registries | scoped registry — only `@evinova-demo/*` touches bit.cloud | [External registries](https://bit.cloud/docs/packages/external-registries) |\n| Hosted docs \u0026 READMEs per package/version | each package's `README.md`, rendered on its bit.cloud page | [Managing packages](https://bit.cloud/docs/packages/managing-packages) |\n| Atomic bulk publish (one request, one CI build) | `pnpm publish -r --batch` via `pnpm publish:all` | [Publishing packages](https://bit.cloud/docs/packages/publishing-packages) |\n| Live component previews from plain packages | `*.composition.tsx` files in [button](./packages/button) \u0026 [card](./packages/card) | [Managing packages](https://bit.cloud/docs/packages/managing-packages) |\n| Real source on the component page | every package ships src/ (tests excluded) + sourcemaps alongside dist/ | [Managing packages](https://bit.cloud/docs/packages/managing-packages) |\n\n## The packages\n\n```mermaid\ngraph TD\n    button[\"@evinova-demo/general.button\"] --\u003e theme[\"@evinova-demo/general.theme\"]\n    button --\u003e utils[\"@evinova-demo/general.utils\"]\n    card[\"@evinova-demo/general.card\"] --\u003e theme\n    card --\u003e utils\n    hooks[\"@evinova-demo/general.hooks\"]\n```\n\n| Package | Description |\n|---|---|\n| [`@evinova-demo/general.theme`](./packages/theme) | Design tokens — colors, spacing, radii, typography |\n| [`@evinova-demo/general.utils`](./packages/utils) | Framework-free helpers (`cx`, `truncate`, `formatDate`) |\n| [`@evinova-demo/general.hooks`](./packages/hooks) | React hooks (`useToggle`, `useDebounce`) |\n| [`@evinova-demo/general.button`](./packages/button) | Button component consuming theme + utils |\n| [`@evinova-demo/general.card`](./packages/card) | Card component consuming theme + utils |\n\nInternal dependencies use pnpm's `workspace:*` protocol. At publish time pnpm rewrites them to the real versions (e.g. `0.1.0`), so consumers installing `@evinova-demo/general.button` pull `general.theme` and `general.utils` from the bit.cloud registry automatically.\n\n**How package names map to components.** `@evinova-demo/general.button` becomes the component `evinova-demo.general/button` on bit.cloud: the npm scope (`@evinova-demo`) maps to the org, and the segment before the first dot in the name (`general`) is the bit.cloud scope. Scope-less package names like `@evinova-demo/button` fall back to the org's `general` scope instead. Publishing to a scope that doesn't exist yet auto-creates it, so there's no separate provisioning step before the first publish.\n\n## Quickstart\n\n### 1. Get a bit.cloud token\n\nGrab a token from your [bit.cloud settings](https://bit.cloud/settings/access-tokens) (or `bit config get user.token` on a machine where you've run `bit login` — no Bit CLI needed otherwise).\n\n```bash\npnpm config set \"//node-registry.bit.cloud/:_authToken\" \"\u003cyour token\u003e\"\n```\n\nPrefer an interactive login instead? `npm login --registry=https://node-registry.bit.cloud` (or `bit login`) works too — both produce the same token, just choose whichever fits your workflow. Either way the credential is stored user-level, never in this repo.\n\n### 2. Install, build, test\n\n```bash\npnpm install\npnpm build\npnpm test\n```\n\n### 3. Publish — your choice of granularity\n\n```bash\npnpm publish:dry                                        # rehearsal, publishes nothing\npnpm publish:all                                        # bulk: pnpm publish -r --batch — atomic, one request, one Ripple CI build\npnpm --filter @evinova-demo/general.button publish      # individual: one package\n```\n\n`publish:all` sends every package to the registry in a single batched request (`pnpm publish -r --batch`): all five publish together or none do. Cross-package links (`workspace:*` → real versions) land correctly in the dependency graph regardless of the order packages are declared in.\n\n\u003e **Interdependent packages should ship as a batch.** `button` and `card` depend on `theme` and `utils` — a batch guarantees those links resolve as first-class dependencies in the graph no matter the order. Reserve individual publishing for a package whose dependencies are already on the registry; if you publish one-by-one, publish dependencies before dependents.\n\nPublished packages appear at **https://bit.cloud/evinova-demo** — each with its README rendered, versions listed, and install instructions for npm/pnpm/yarn.\n\n## CI/CD (GitHub Actions)\n\n| Workflow | Trigger | What it does |\n|---|---|---|\n| [CI](./.github/workflows/ci.yml) | every PR / push to main | install → build → test |\n| [Release](./.github/workflows/release.yml) | push to main | Changesets opens a \"Version Packages\" PR; merging it publishes the whole workspace as **one atomic batch request** — all-or-nothing, one combined Ripple CI build; pnpm skips versions already on the registry, so only the new versions actually ship |\n| [Publish (manual)](./.github/workflows/publish-manual.yml) | manual dispatch | dropdown: publish one package or all, on demand |\n\nOne-time setup: add `BIT_CLOUD_TOKEN` as a repo secret (`Settings → Secrets and variables → Actions`).\n\n### Day-to-day release flow\n\n```bash\n# 1. Make a change, then declare it:\npnpm changeset          # pick packages + semver bump, describe the change\n# 2. Merge the PR. The Release workflow opens \"chore: version packages\".\n# 3. Merge that PR → the whole workspace publishes to bit.cloud as one atomic\n#    batch request (all-or-nothing, one combined Ripple CI build). pnpm skips\n#    any version already on the registry, so only the newly-bumped packages\n#    actually ship. Done.\n```\n\n(A changeset on a shared package like `utils` cascades: Changesets automatically patch-bumps the packages that depend on it — `button` and `card` — so downstream consumers always get a compatible, republished version.)\n\n**Controlling the release notes.** The message you write in `pnpm changeset` travels with the release: it becomes the entry in each affected package's `CHANGELOG.md` (written by the Version Packages PR), shows up in the matching GitHub Release, and ships inside the published tarball (see each package's `files` field) so consumers get the changelog right in `node_modules`. Write changeset messages for the consumers of your packages — they're what people see next to each version.\n\n## How it works — no magic\n\nEverything on the bit.cloud side is derived from what a standard npm client already sends. The entire pipeline:\n\n```mermaid\nflowchart LR\n    A[\"pnpm publish -r --batch\u003cbr/\u003e(standard npm PUT)\"] --\u003e B[\"node-registry.bit.cloud\u003cbr/\u003enpm-protocol registry\"]\n    B --\u003e C[\"Mirror as component\u003cbr/\u003e@evinova-demo/general.button → evinova-demo.general/button\"]\n    C --\u003e D[\"Ripple CI builds the version\u003cbr/\u003ecompile · render compositions · extract API\"]\n    D --\u003e E[\"Component page + installable version\u003cbr/\u003e(npm GET, any client)\"]\n```\n\n1. **Publish is just the npm protocol.** `pnpm publish` sends the same HTTP PUT it would send to npmjs — manifest plus tarball (`--batch` bundles the workspace into one atomic request). No plugin, no wrapper, no post-processing step in your repo or CI.\n2. **The name is the address.** `@evinova-demo/general.button` deterministically maps to component `evinova-demo.general/button`: npm scope = org, segment before the dot = bit.cloud scope, the rest = component name. That's why there's no provisioning — the name carries all the routing.\n3. **The registry opens the tarball instead of just storing it.** Files are classified by name: `README.md` → overview page, `*.composition.*` → live examples, everything else under `src/` → the source view. `description` and `keywords` from package.json become the component description and labels. **Everything you see on the component page traces back to a file you chose to ship in `files`.**\n4. **Every version gets built.** Ripple CI compiles the version, renders the compositions into live previews, and extracts the API reference. A version becomes installable only after its build succeeds — so the registry can't serve you something that doesn't build.\n5. **Install is the npm protocol again.** Consumers run plain `pnpm add` / `npm install`; internal dependencies resolve from the same registry because the published manifests carry real versions (pnpm rewrote `workspace:*` at publish time).\n\n## Why bit.cloud as a registry?\n\n- **Zero migration** — this repo is proof: one `.npmrc` file, standard tooling.\n- **Docs included** — every package version gets its README, changelog and metadata rendered; no separate docs site to maintain.\n- **Scoped, not global** — only `@evinova-demo/*` resolves from bit.cloud; everything else stays on npmjs (or proxy npmjs through bit.cloud — see [external registries](https://bit.cloud/docs/packages/external-registries)).\n- **Team \u0026 org management** — access control per org/scope on [bit.cloud](https://bit.cloud/evinova-demo).\n- **A path to more** — the same packages can later graduate to full Bit components (compositions, previews, dependency graphs, Ripple CI) without changing how consumers install them.\n- **Every version is a built component** — each published version is mirrored as a component and built by Ripple CI; a version only becomes installable once its build succeeds.\n- **Compositions and docs ship from your tarball** — no separate docs site: your `README.md` becomes the overview page, and any `*.composition.*` file becomes a live, rendered example on the component's bit.cloud page. Composition files import their own package via a relative path (e.g. `./dist/index.js`, since that's what actually ships in the tarball) and import any other packages by their package name.\n- **Real source on the component page** — every package ships its `src/` directory and sourcemaps alongside `dist/`. Consumers still install and import the prebuilt bundle via `exports`, but bit.cloud picks up the source files from the published tarball and renders them on the component page. Test files stay out of the tarball (excluded via the `files` field) — they live in the repo and run in CI.\n\n## Troubleshooting\n\n- **401/403 on publish** — token missing/expired, or your bit.cloud user lacks write access to the `evinova-demo` org.\n- **\"version already exists\"** — the registry is immutable per version (a feature); bump with `pnpm changeset` and republish.\n- **`Ignored project-level auth setting \"//node-registry.bit.cloud/:_authToken\" in .npmrc: environment variables are not expanded in registry credentials that come from a project .npmrc`** — pnpm ≥11 no longer honors credentials committed in a project `.npmrc`. Configure the token user-level instead: `pnpm config set \"//node-registry.bit.cloud/:_authToken\" \u003ctoken\u003e` (or `npm login --registry=https://node-registry.bit.cloud`); CI does the same via the \"Configure registry auth\" step in the workflows.\n- **`ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`** — pnpm ≥11's supply-chain policy blocks installing packages published more recently than `minimumReleaseAge` (minutes) allows. This demo sets `minimumReleaseAge: 0` in [`pnpm-workspace.yaml`](./pnpm-workspace.yaml) so it's always installable; real projects often raise this to quarantine brand-new releases.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteambit%2Fpnpm-monorepo-packages-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fteambit%2Fpnpm-monorepo-packages-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteambit%2Fpnpm-monorepo-packages-demo/lists"}