Skip to content

Commit 9a24bbd

Browse files
Publish: document release and dev publishing
1 parent 62dea4d commit 9a24bbd

4 files changed

Lines changed: 207 additions & 0 deletions

File tree

CONTEXT.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Kirby Design System
2+
3+
Kirby is the design system used across Bankdata's products: a set of web components, an
4+
Angular component library, and the supporting tooling published to npm under the
5+
`@kirbydesign` scope.
6+
7+
## Language
8+
9+
### Publishing
10+
11+
**Release publish**:
12+
Publication of a package at the version recorded in its source `package.json`, triggered
13+
by pushing a protected tag. Carries the `latest` dist-tag and is supported.
14+
_Avoid_: official publish, production release, real release.
15+
16+
**Dev publish**:
17+
Publication of a publish closure at commit-stamped prerelease versions, dispatched
18+
manually by an admin so a consumer can verify an unreleased change in a real application.
19+
Permanent, unsupported, and never removed.
20+
_Avoid_: branch publish, PR publish, canary, snapshot, nightly.
21+
22+
**Dev version**:
23+
A version of the form `<release version>-dev-<short SHA>`. Identifies a commit rather than
24+
a branch, and is excluded from every SemVer range, so nothing resolves to it by accident.
25+
_Avoid_: prerelease, beta, RC — an `-rc.N` version means a release candidate, which is a
26+
different thing.
27+
28+
**Publish closure**:
29+
The package being published together with everything it depends on, in publish order. A
30+
consumer must install the whole closure, because the peer dependency ranges within it are
31+
pinned to exact versions.

RELEASING.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Releasing
2+
3+
Kirby has two kinds of publish. Both run on CI and authenticate to npm with
4+
[trusted publishing][trusted-publishing] over OIDC — there are no npm tokens anywhere in
5+
this repository.
6+
7+
| | Release publish | Dev publish |
8+
| --------- | ---------------------------------------- | ---------------------------------------- |
9+
| Trigger | Pushing a protected tag | Manual dispatch of `publish-dev.yml` |
10+
| Who | Repository admins | Repository admins |
11+
| Version | Exactly what is in `libs/*/package.json` | `<version>-dev-<short SHA>` |
12+
| dist-tag | `latest` | `dev` |
13+
| Supported | Yes | No — see [Dev publishes](#dev-publishes) |
14+
15+
## Publishable packages
16+
17+
| Package | Source | Depends on |
18+
| --------------------------------- | ------------------------- | -------------- |
19+
| `@kirbydesign/core` | `libs/core` ||
20+
| `@kirbydesign/designsystem` | `libs/designsystem` | `core` |
21+
| `@kirbydesign/extensions-angular` | `libs/extensions/angular` | `designsystem` |
22+
| `@kirbydesign/stylelint-plugin` | `libs/stylelint-plugin` ||
23+
24+
## Release publishes
25+
26+
1. Open a release PR bumping the version in the relevant `libs/*/package.json`.
27+
2. Once merged, an admin pushes the matching tag. Tag creation is restricted by a
28+
repository ruleset, which is what makes releasing admin-only.
29+
30+
| Tag | Workflow | Publishes |
31+
| --------------------------- | -------------------------------- | ---------------------- |
32+
| `vX.Y.Z` | `publish.yml` | `core`, `designsystem` |
33+
| `vX.Y.Z-extensions-angular` | `publish-extensions-angular.yml` | `extensions-angular` |
34+
| `vX.Y.Z-stylelint-plugin` | `publish-stylelint-plugin.yml` | `stylelint-plugin` |
35+
36+
## Dev publishes
37+
38+
A dev publish lets a consumer install an unreleased change into a real application before
39+
it is released.
40+
41+
> [!WARNING]
42+
> Dev versions are **permanent, unsupported, and never removed**. npm's OIDC tokens
43+
> authenticate `npm publish` only — `npm unpublish`, `npm deprecate` and `npm dist-tag rm`
44+
> all require a long-lived token, which this repository deliberately does not have. Never
45+
> reference a dev version from a production dependency.
46+
47+
### Producing one
48+
49+
Actions → **Publish dev version to npm****Run workflow** → pick the branch and the
50+
package → **Run workflow**. The run then waits on the `npm-dev-publish` environment; an
51+
admin clicks **Review deployments****Approve and deploy** on the run page. Admins can
52+
approve their own dispatch.
53+
54+
When it finishes, the run summary lists every published version and the exact
55+
`npm install` command to hand to the consumer.
56+
57+
### What gets published
58+
59+
A dev publish publishes the chosen package **and everything it depends on**, because these
60+
are peer dependencies and npm enforces them:
61+
62+
| Chosen | Published |
63+
| -------------------- | -------------------------------------------- |
64+
| `core` | `core` |
65+
| `designsystem` | `core`, `designsystem` |
66+
| `extensions-angular` | `core`, `designsystem`, `extensions-angular` |
67+
| `stylelint-plugin` | `stylelint-plugin` |
68+
69+
Inter-package peer dependency ranges are rewritten to the exact dev versions. This is not
70+
cosmetic: a prerelease never satisfies a caret range, so a dev `designsystem` still asking
71+
for `"@kirbydesign/core": "^0.0.92"` would make the consumer's `npm install` fail with
72+
`ERESOLVE`. Install the whole set the run summary lists, not just one package.
73+
74+
### Versions and tags
75+
76+
A dev version looks like `11.11.0-dev-abc1234` — the package's current version with the
77+
commit appended as a prerelease identifier. Two consequences worth knowing:
78+
79+
- **Nothing resolves to it by accident.** npm excludes prereleases from ranges, so
80+
`11.11.0-dev-abc1234` satisfies neither `^11.11.0` nor `>=11.0.0`. A consumer only ever
81+
gets a dev build by asking for that exact version.
82+
- **The `dev-` infix is required.** A bare SHA such as `0123456` is a leading-zero numeric
83+
identifier and not valid SemVer, which would break roughly one publish in 270.
84+
85+
Every dev publish uses the single dist-tag `dev`, which points at whichever dev publish ran
86+
most recently. It carries no meaning — always pin the exact version.
87+
88+
Re-running a dispatch for the same commit is safe: any package whose dev version already
89+
exists is skipped, so a run that failed part way through can simply be re-run.
90+
91+
## Running the publish script locally
92+
93+
`npm run publish -- <package>` off CI does **not** publish. It builds and writes a tarball
94+
to `dist/`, which you can install with `npm install <path to tarball>`. Dev publishes are
95+
refused entirely outside CI, because they rewrite `libs/*/package.json` in place.
96+
97+
## Adding a new publishable package
98+
99+
1. Publish the first version manually — a trusted publisher cannot be registered for a
100+
package that does not yet exist on the registry.
101+
2. Register a trusted publisher for its release workflow.
102+
3. Register a second trusted publisher for `publish-dev.yml`, with environment
103+
`npm-dev-publish`.
104+
4. Add it to `sourcePackageJsonPaths` and `publishChains` in `scripts/publish.js`, and to
105+
the `package` input of `.github/workflows/publish-dev.yml`.
106+
107+
Each package may have up to 10 trusted publishers, but an existing one cannot be edited —
108+
only deleted and recreated.
109+
110+
[trusted-publishing]: https://docs.npmjs.com/trusted-publishers

docs/adr/0001-dev-publishing.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Dev publishing
2+
3+
## Context
4+
5+
Consumers of Kirby need to verify an unreleased change inside their own application before
6+
it is released. Kirby publishes to npm with trusted publishing over OIDC, which binds trust
7+
to a specific workflow **filename** and authenticates `npm publish` and `npm stage publish`
8+
— and nothing else.
9+
10+
## Decision
11+
12+
A dev publish is a manually dispatched publication of a package and its dependency closure,
13+
at `<version>-dev-<short SHA>`, under the `dev` dist-tag.
14+
15+
- It lives in its own workflow, `publish-dev.yml`, which calls the same `scripts/publish.js`
16+
as the release workflows.
17+
- It is gated by the GitHub environment `npm-dev-publish`, whose name is also registered in
18+
each package's npm trusted publisher configuration.
19+
- Dev versions are permanent.
20+
21+
## Considered options
22+
23+
**Extending `publish.yml` in place** instead of a separate workflow. Rejected: it puts
24+
conditional logic into the highest-stakes workflow in the repository, where a dev-publish
25+
bug could break releasing. A separate file costs four npm registrations, which is a one-off.
26+
27+
**An `if: github.actor` check** instead of an environment. Rejected: `workflow_dispatch` is
28+
available to anyone with write access, and a dispatched run executes the workflow file as it
29+
exists on the selected ref — so the check is deletable by exactly the person it is meant to
30+
stop. Environment protection rules are repository settings and cannot be edited from a
31+
branch.
32+
33+
**An environment without registering its name with npm.** Rejected: an attacker could strip
34+
the `environment` key from their branch's copy of the workflow to skip the reviewer.
35+
Registering the environment name means such a run produces an OIDC token npm rejects, so the
36+
two halves close each other's gap.
37+
38+
**A bare SHA suffix**, `11.11.0-abc1234`. Rejected: about 3.7% of short SHAs are all digits,
39+
and a leading-zero numeric identifier is not valid SemVer, so roughly one publish in 270
40+
would fail confusingly. The `dev-` infix makes the version valid for every possible SHA.
41+
42+
**Per-branch or per-SHA dist-tags.** Rejected: dist-tags accumulate permanently and cannot
43+
be removed under OIDC. `@kirbydesign/core` already carries six dead dist-tags from earlier
44+
ad-hoc experiments. A single reused `dev` tag adds exactly one, forever.
45+
46+
## Consequences
47+
48+
Each package needs a trusted publisher registered against `publish-dev.yml` with the
49+
`npm-dev-publish` environment. Registrations cannot be edited, only deleted and recreated,
50+
and a package must already exist on the registry before one can be created.
51+
52+
Every dev publish requires an approval click, including from the admin who dispatched it.
53+
54+
Dev versions accumulate permanently and there is no cleanup job — not by oversight, but
55+
because OIDC cannot authenticate `unpublish`, `deprecate`, or `dist-tag rm`. Reintroducing a
56+
long-lived token to enable cleanup would defeat the reason for using trusted publishing.
57+
58+
Because peer ranges within a closure are pinned to exact dev versions, a consumer must
59+
install the whole closure rather than a single package.

readme.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ The Kirby Cookbook, containing samples, status of components etc. can be accesse
3232
- [Migration Guides](#migration-guides)
3333
- [Folder Structure](#folder-structure)
3434
- [Scripts](#scripts)
35+
- [Releasing](#releasing)
3536
- [Contributing](#contributing)
3637

3738
## Installation
@@ -209,6 +210,12 @@ Use them in your terminal like: `npm run <script>` :
209210
We use [nx][nx] to run common tasks like building, linting and testing projects.
210211
This is done with `npx nx <target name> <project name>`, e.g. `npx nx lint designsystem` preferrably _from the root of the workspace_ to ensure config paths are resolved correctly.
211212

213+
## Releasing
214+
215+
Packages are published to npm from CI. See the [releasing guide](./RELEASING.md) for how
216+
releases are cut, and how to publish a dev version of an unreleased change so a consumer
217+
can try it in a real application.
218+
212219
## Contributing
213220

214221
If you wish to contribute new features, bug fixes or something third to the project have a look at the [contribution guidelines](./.github/CONTRIBUTING.md).

0 commit comments

Comments
 (0)