Skip to content

Commit 1f162bc

Browse files
authored
docs: add semantic release simulation docs (#608)
1 parent 9626cdb commit 1f162bc

File tree

4 files changed

+264
-3
lines changed

4 files changed

+264
-3
lines changed

.env.release.sample

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# GitHub token
2+
# You can use a classic one or a fine-grained one
3+
#
4+
# ## Clasic
5+
# https://github.com/settings/tokens/new
6+
# Selecting "public_repo" scope is enough
7+
#
8+
# ## Fine-grained
9+
# https://github.com/settings/personal-access-tokens/new
10+
# Default settings are okay (as it's a public repo)
11+
#
12+
#GH_TOKEN=
13+
14+
# NPM token
15+
# You can use a classic one or a granular access one
16+
# https://www.npmjs.com/settings
17+
#
18+
# ## Classic
19+
# "Read-only" is enough
20+
#
21+
# ## Granular Access
22+
# "Read-only" access to packages and scopes is enough.
23+
# If being a package collaborator, you can select this package.
24+
# Otherwise select "All packages".
25+
#NPM_TOKEN=

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,5 +373,7 @@ $RECYCLE.BIN/
373373
*.lnk
374374

375375
# End of https://www.toptal.com/developers/gitignore/api/angular,vim,visualstudiocode,node,webstorm+iml,macos,linux,windows
376+
.npmrc
376377
# Manual edits
377378
# - Comment `.idea` in Angular to include IDE configs
379+
# - Add `.npmrc` to set registry to simulate releases, but avoid committing that

CONTRIBUTING.md

Lines changed: 237 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ Every exported member must be documented using [TSDoc](https://tsdoc.org/). Spec
114114

115115
At least, it must provide the visibility of the exported member. Mainly either [`@alpha`](https://api-extractor.com/pages/tsdoc/tag_alpha/), [`@beta`](https://api-extractor.com/pages/tsdoc/tag_beta/), [`@internal`](https://api-extractor.com/pages/tsdoc/tag_internal/) or [`@public`](https://api-extractor.com/pages/tsdoc/tag_public/)
116116

117-
Checkout [API Report](#api-report) section for more information
117+
Check out [API Report](#api-report) section for more information
118118

119119
#### Testing
120120

@@ -134,6 +134,17 @@ To easily reproduce locally CI/CD jobs, most commands run by CI/CD are stored in
134134

135135
For instance, you can run `make build` (or just `make` in this case) to ensure the command used to build is the same one the CI/CD job will use.
136136

137+
#### Release and versioning
138+
139+
Every release is identified by a version number that follows [Semantic Versioning 2.0 specification](https://semver.org/)
140+
141+
In order to automatically detect if a new release is needed and what kind of version number bump is needed (major, minor or patch), commit messages since last release are analyzed.
142+
That's why [commit messages follow a convention](#commit-messages). So that they can be analyzed later to automate the version bump and release process.
143+
144+
Every push to main branch analyzes all commits since latest release. Based on commit messages there's enough information to decide if a new release is needed. If a new version is needed the release process associates the commit with a tag, creates a release in GitHub with notes generated from commit messages and publishes a new package version to NPM.
145+
146+
See [release task](#release) for more information about how to operate releases.
147+
137148
### Tasks
138149

139150
#### Package management
@@ -245,7 +256,231 @@ To generate API Reference docs, which will end up in the [documentation](#docume
245256

246257
#### Docs
247258

248-
Checkout [`ngx-meta` docs `README.md` to operate docs project](projects/ngx-meta/docs/README.md)
259+
Check out [`ngx-meta` docs `README.md` to operate docs project](projects/ngx-meta/docs/README.md)
260+
261+
#### Release
262+
263+
[Semantic Release] is used to create releases by analyzing [commit messages' semantic information](#commit-messages). Here you'll find how to simulate a release.
264+
265+
##### Dry run: working on a branch
266+
267+
Semantic Release [behaves differently depending on the checked out branch](https://semantic-release.gitbook.io/semantic-release/usage/workflow-configuration#workflow-configuration).
268+
269+
If you're working on a branch and want to force a release, you can temporarily
270+
add your branch to the release configuration.
271+
272+
For instance, add the following to the `branches` configuration in the `.releaserc.js` file to simulate a beta release as if the branch was in the main branch
273+
274+
```json
275+
{
276+
"name": "current-branch-where-work-is-being-done",
277+
"prerelease": "beta",
278+
"channel": false
279+
}
280+
```
281+
282+
###### Beware of the `channel` option
283+
284+
Default is `undefined` for first release branch, but branch name for the rest. So in order to properly simulate the release process, you'll have to manually configure that. [`false` can be used to indicate default distribution channel](https://semantic-release.gitbook.io/semantic-release/usage/workflow-configuration#branches-properties).
285+
286+
###### Branch needs to be pushed
287+
288+
Otherwise, it [does not exist for semantic release](https://github.com/semantic-release/semantic-release/blob/v23.1.1/lib/branches/expand.js#L11). Same for commits on that branch. If not pushed, it's like they don't exist.
289+
290+
##### Dry run: minimal
291+
292+
To have a minimal idea of what would happen when running the release process, you can run [Semantic Release] in [dry run mode](https://semantic-release.gitbook.io/semantic-release/usage/configuration#dryrun). This includes: authentication checks, commit analysis to see if a new release is needed and the release notes for it in case it's needed.
293+
294+
> [!WARNING]
295+
> Push permissions to the repository are needed to pass the `git` auth check.
296+
> That's a [hardcoded check](https://github.com/semantic-release/semantic-release/blob/v23.1.1/index.js#L86-L102). So there's unfortunately no way to bypass this
297+
> If you don't have permissions, check out the [next dry run approach](#dry-run-publish)
298+
299+
> [!TIP]
300+
> 👇 Env vars are needed for plugins which check authentication (GitHub & NPM).
301+
> You can also comment them out in the configuration file if you're not interested in how they behave. If interested in them and worrying about security, don't: read-only tokens are okay.
302+
303+
First, some env vars need to be defined. Check out [sample release env file] to see which ones with current configuration.
304+
305+
[sample release env file]: .env.release.sample
306+
307+
> [!TIP]
308+
> You can copy the sample file into a `.env` file and set the credentials there. The file is ignored in git, won't be commited by accident.
309+
> Then, use a tool like [ohmyzsh's `dotenv` plugin](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/dotenv) or
310+
> [use `direnv`](https://github.com/direnv/direnv) to automatically load the env vars into your shell.
311+
312+
> [!TIP]
313+
> To simulate a release, you can (and indeed should) use read-only tokens.
314+
> Check out the [sample release env file] for more details
315+
316+
With env vars set, run
317+
318+
```shell
319+
# 👇 --dry-run is the default mode when not on CI, but just to be explicit & moar sure
320+
pnpm semantic-release --dry-run
321+
```
322+
323+
This is useful to know the version bumped and release notes generated, but prepare and publish NPM steps are skipped. Keep reading to know how to dry run them.
324+
325+
##### "Dry run": publish
326+
327+
What if you want to simulate what would be published to the NPM registry? Or how the generated release notes would look in GitHub? Or how the generated CHANGELOG.md would look like? Keep reading to see how to safely test that.
328+
329+
However, bear in mind that it won't be a dry run (hence the quotes). Semantic Release will be called as if actually performing a release, but with a different registry and repository. There's [no way of dry running a release with semantic release that includes prepare, publish, success or failure steps](https://github.com/semantic-release/semantic-release/blob/v23.1.1/lib/definitions/plugins.js#L72).
330+
331+
###### Private, local registry
332+
333+
A private registry can be used to simulate packaging and publishing steps. [Verdaccio] is a popular choice at this time.
334+
335+
Install [Verdaccio] and run it:
336+
337+
```shell
338+
pnpm i -g verdaccio # you may be instructed to run a setup step
339+
verdaccio
340+
```
341+
342+
Now let's configure this project to use [Verdaccio]. [Semantic Release's NPM plugin] will also [read that configuration in order to publish there and not to the official NPM registry](https://github.com/semantic-release/npm?tab=readme-ov-file#npm-configuration).
343+
344+
```shell
345+
npm config set registry http://localhost:4873/ --location project
346+
```
347+
348+
> [!NOTE]
349+
> The `--location project` flag is added so just NPM configuration for this project is changed. Instead of the [default behaviour which changes the user's configuration](https://docs.npmjs.com/cli/v10/commands/npm-config#set). A `.npmrc` file will be created in the repo if it doesn't exist. Don't worry, it's git ignored. It won't end up in version control.
350+
351+
Time for authentication! Create a user in the fresh [Verdaccio] private registry.
352+
353+
> [!NOTE]
354+
> Skip this step if you already have a user for the private registry.
355+
> Ensure you're logged in instead by running `npm login`
356+
357+
```shell
358+
npm adduser ngx-meta
359+
# 👆 Registry should be Verdaccio's localhost URL
360+
# Any username, password email can be set in there.
361+
```
362+
363+
> [!NOTE]
364+
> Skip this step if you already have a token for the user.
365+
366+
> [!TIP]
367+
> If you forgot the token for the user, you can use `npm token ls` to list existing tokens.
368+
> However the full token won't be listed there. Copy the id. Then remove it by running `npm token revoke <tokenId>`. Create a new one now :)
369+
370+
Now, we need a token. Let's create one:
371+
372+
```shell
373+
npm token create
374+
```
375+
376+
Take note of it. This token will be used by [Semantic Release] to authenticate against the registry as seen in the [minimal dry run](#dry-run-minimal)
377+
378+
###### Different repository
379+
380+
Unfortunately [Semantic Release] [hardcodes pushing to the git repository](https://github.com/semantic-release/semantic-release/blob/v23.1.1/index.js#L207-L212). So there's no way to dry-run that part :(
381+
382+
Then what it can be done is to use another git repository for this purpose. Options:
383+
384+
**🔒 SAFEST (but a bit cumbersome): Create a fork/copy of the repository**
385+
386+
To copy all existing tags needed to simulate a release at the current state. Or [copy it if you can't fork it as you own it](https://stackoverflow.com/a/10966784/3263250).
387+
388+
Then, when using [Semantic Release], set the repository URL to this new repository:
389+
390+
```shell
391+
pnpm semantic-release --repository-url https://github.com/user/ngx.git
392+
```
393+
394+
**⚡️ TRICKY (but fastest): Use a noop git remote**
395+
396+
> [!CAUTION] > **It's tricky and dangerous**. If not setup properly, or Semantic Release implementation changes, this could end up actually performing actions on the actual repository. Also, you'll need to later clear the objects created by semantic release which will be in your local git repository. Unless you know what you're doing and are very careful, **skip this approach**.
397+
398+
A trick that can be done is to [create a dummy remote that does nothing](https://stackoverflow.com/a/30543552/3263250) by setting `.` as the repository URL.
399+
400+
This way Semantic Release will push everything to this no-op repository URL / remote URL.
401+
402+
```shell
403+
pnpm semantic-release --repository-url .
404+
```
405+
406+
> [!CAUTION]
407+
> Disable the GitHub plugin in the configuration if using this approach.
408+
> Otherwise release notes & comments will be created as usual. That's another reason not to use this approach.
409+
410+
> [!IMPORTANT]
411+
> Semantic Release's release notes generator plugin needs to be disabled if using this approach. As otherwise [it fails when trying to read parts from `.` which is not a URL](https://github.com/semantic-release/release-notes-generator/blob/v13.0.0/index.js#L37-L39)
412+
413+
> [!NOTE]
414+
> You could use `.` as the URL for the `origin` git remote. [Semantic Release would grab that and use it for the repository URL](https://github.com/semantic-release/semantic-release/blob/v23.1.1/lib/get-config.js#L73) if not set anywhere else. But it would end up [trying to be converted to a URL](https://github.com/semantic-release/semantic-release/blob/v23.1.1/lib/get-git-auth-url.js#L79) anyway. Plus the repository URL may be grabbed from somewhere else therefore using the real repository to operate, which would end up making real changes in there. Also the remote to change would have to be `origin`, which is [hardcoded in the implementation](https://github.com/semantic-release/semantic-release/blob/v23.1.1/lib/git.js#L174). Finally, [`pushURL` is not respected by semantic release](https://github.com/semantic-release/semantic-release/blob/v23.1.1/lib/git.js#L234-L236) so the `origin` URL has to be changed.
415+
416+
_Cleanup_
417+
418+
After running semantic release with this approach, you'll end up with the git objects that semantic release created in your local repository. You'll need to clean them up to avoid weird scenarios in the future (like fetching a tag that already exists as was created during a "dry-run")
419+
420+
This means [currently](https://github.com/semantic-release/semantic-release/blob/v23.1.1/index.js#L207-L212):
421+
422+
```shell
423+
git tag -d <createdTag> # you can use `git tag -l` to list them
424+
git notes prune -v # to clear notes about non-existing tags
425+
# 👆 Seems tag deletion already deletes the note though
426+
```
427+
428+
If using Semantic Release's git plugin (or another that writes to git), you'll need to remove the created commit by it too.
429+
430+
###### Running the release
431+
432+
With the private registry setup and the new repository created, you can run the release.
433+
434+
Bear in mind to [mangle the configuration if working on a branch](#dry-run-working-on-a-branch). Set up the needed env vars [as seen in minimal dry run](#dry-run-minimal). With following differences:
435+
436+
- **GitHub**: if just interested in NPM publish or using the tricky approach for the repository to use, you can skip that part by commenting out the plugin in the configuration. Otherwise, provide a token that can write to the repository created for running the release simulation.
437+
- **NPM**: provide the auth token instead obtained in the [private local registry step](#private-local-registry)
438+
439+
Let's check out everything is ready:
440+
441+
```shell
442+
npm config get registry
443+
# 👆 Output should be local private registry URL, not NPMjs one
444+
echo $NPM_TOKEN
445+
# 👆 Output doesn't start with `npm_`
446+
# Should return NPM auth token created for local registry
447+
cat .releaserc.js
448+
# 👆 GitHub plugin commented if:
449+
# - Env var not configured
450+
# - Read-only access token provided
451+
# - Using local repository trick
452+
# Release notes plugin generator commented if using local repository trick
453+
# Branch configuration altered if working on a branch
454+
```
455+
456+
> ![IMPORTANT]
457+
> Just [one more thing](https://www.youtube.com/watch?v=hvlHi7iTdaw&t=6s) 😜
458+
> [Verdaccio doesn't support `provenance`](https://github.com/orgs/verdaccio/discussions/3903)
459+
> So disable `publishConfig.provenance` in the built `package.json`. Otherwise, Verdaccio won't accept the package submission.
460+
461+
Finally, run the release rehearsal
462+
463+
```shell
464+
pnpm semantic-release \
465+
--repository-url <anotherRepository> \
466+
--no-ci
467+
```
468+
469+
> [!NOTE]
470+
> If using the local repository trick [as a different repository](#different-repository), remember to do the clean-up!
471+
472+
> [!NOTE]
473+
> To try again releasing the same release, you can [unpublish it from Verdaccio](https://github.com/verdaccio/verdaccio/issues/365#issuecomment-337901629):
474+
>
475+
> ```shell
476+
> npm unpublish --force <pkg>@<version> --registry http://localhost:4873
477+
> ```
478+
>
479+
> Be careful of specifying the registry. You don't want the package to be actually removed from NPM registry!
480+
481+
[Semantic Release]: https://github.com/semantic-release/semantic-release
482+
[Semantic Release's NPM plugin]: https://github.com/semantic-release/npm
483+
[Verdaccio]: https://verdaccio.org/
249484
250485
### Tools
251486

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
"lint-staged": "lint-staged",
2525
"commitlint-edit-msg": "commitlint --verbose --edit",
2626
"commitlint-last": "commitlint --verbose --from HEAD~1",
27-
"semantic-release": "semantic-release",
2827
"ng-lint-staged": "ng-lint-staged lint --max-warnings 0 --fix --",
2928
"cache-clean": "ng cache clean",
3029
"commit": "pnpx @commitlint/prompt-cli"

0 commit comments

Comments
 (0)