Releases¶
The repository releases two of its packages, each on its own: the Foundry VTT module and the browser extension. Each
has its own versions, tags, changelog and GitHub releases, made by semantic-release from the commits that change it.
The Release workflow releases both on every push to main (see Workflow); nothing in a release is done by
hand.
The release streams¶
The module |
The browser extension |
|
|---|---|---|
Configuration |
|
|
Run by |
|
|
Counts commits to |
|
|
Tags |
|
|
Changelog |
|
|
Version kept in |
|
|
Packaged by |
|
|
GitHub release |
the latest release, with the zip and |
a pre-release, with the Chromium zip and the Firefox add-on |
Release commit |
|
|
Both streams run from the repository’s root, so that each can commit files outside its package, such as the module’s
docs/conf.py. The extension’s stream runs after the module’s, from the module’s release commit if there is one.
Which commits count¶
A package’s releases are made from the commits that change files in its folder, or in the folders of the workspace
packages it depends on (workspace: dependencies in its package.json, development dependencies included, since a
package’s build takes them in). A fix(core) commit is released in both packages; a fix(extension) commit only in the
extension. A commit that changes both packages is released in both, and appears in both changelogs.
What a package depends on is read from the package.json files when semantic-release starts, so a new dependency
between packages counts from then on, with nothing else to change.
Commits that change only files outside every package, such as the root package.json, the workflows or the
documentation, are released with neither. That suits the commit types they usually have (build, ci, docs),
which release nothing anyway. A merge commit changes no folder of its own: the commits it merges are counted instead.
The version follows from the commits that count, as in Workflow: fix → patch, feat → minor, ! or
BREAKING CHANGE: → major. A merge to main may release the module, the extension, both or neither.
Why the extension’s releases are pre-releases¶
Copies of the module installed before 0.9.0 find updates at
https://github.com/Mondarth/EMMA/releases/latest/download/module.json, GitHub’s latest release. If an extension
release became the latest, they would find no manifest there. So the extension’s releases are always published as
pre-releases, which GitHub never makes the latest: they are alphas, too. The module’s releases stay the latest.
The release tooling: packages/release¶
semantic-release reads every commit on the branch, whichever package it changes. @mondarth/emma-release, a package of
the workspace, holds the plugin wrappers the release configurations use to keep each stream to its own commits. It is
release tooling, not part of any release: its own commits count towards neither stream.
packageFolders(root, folder)The folders whose commits make up a package’s releases: the package’s own and those of the workspace packages it depends on, directly or through others, sorted.
onlyCommitsTo(folders, plugin)Wraps a plugin so that each of its steps is told only of the commits that change the folders. The configurations wrap the three plugins that read the commits:
@semantic-release/commit-analyzer(the version),@semantic-release/release-notes-generator(the notes and changelog) and@semantic-release/github(the comments and labels it leaves on the issues and pull requests a release closes). It reads which files each commit changes with onegit diff-tree.asPrerelease(plugin)Wraps a plugin so that it is told the release is a pre-release, and not on the main release branch.
@semantic-release/githubdecides from the branch alone whether a release is a pre-release and whether it is the latest, so wrapped it publishes a pre-release that is never the latest.
A wrapped plugin is an inline plugin: the wrapper loads the plugin’s module and gives the configuration the plugin’s steps, each wrapped. As semantic-release reads a plugin’s steps when it reads the configuration, the configuration awaits the wrappers:
const FOLDERS = packageFolders(path.resolve(import.meta.dirname, '../..'), 'packages/extension');
export default {
plugins: [
await onlyCommitsTo(FOLDERS, ['@semantic-release/commit-analyzer', { preset: 'conventionalcommits' }]),
await asPrerelease(await onlyCommitsTo(FOLDERS, ['@semantic-release/github', { assets: [] }])),
],
};
In the release’s log, a wrapped plugin is named for the plugin and what the wrappers do, such as
@semantic-release/github, on packages/core, packages/extension, packages/setup, as a pre-release, and
onlyCommitsTo says how many of the commits count.
The package’s tests run semantic-release itself, in throwaway repositories, and the real @semantic-release/github
against a local stand-in for GitHub’s API. Its tests also check both release configurations: their tags, the folders
they count, and that the extension’s stream changes only the extension’s files.
Why not semantic-release-monorepo or multi-semantic-release¶
semantic-release-monorepo counts only the commits to a package’s own folder, so a fix to the shared code would release
neither package that is built from it. multi-semantic-release follows the dependencies between packages, but only by
releasing them: the shared packages would need versions and tags of their own, and it rewrites workspace: ranges to
versions while it releases. Wrapping the plugins keeps the repository’s existing tags and releases only what is
published.
Adding a package to the releases¶
Give the package a
release.config.js, from the extension’s: its folders frompackageFolders, a tag format that matches no other stream’s tags, its own changelog, and afailTitleof its own for@semantic-release/github, so that each stream reports a failing release in an issue of its own.Before its first release, tag the commit to count its commits from, such as
git tag <name>-v0.0.0 <commit>. Otherwise its first release is1.0.0and counts every commit before it.Add a
release:<name>script to the rootpackage.json, and a step that runs it to the Release workflow, after the others, with anidwhoseversionoutput the later jobs check.Add its configuration to
packages/release/tests/configs.test.ts.
Trying a release locally¶
The packaging scripts can be run on their own, from the repository’s root:
node packages/module/scripts/release/package.ts <version>stamps, builds and zips the module; discard its changes topackages/module/module.jsonanddocs/conf.pyafterwards.node packages/extension/scripts/release/package.ts <version>stamps, builds and packages the extension intopackages/extension/releases/, unsigned unlessAMO_JWT_ISSUERandAMO_JWT_SECRETare set; discard its change topackages/extension/package.jsonafterwards.
pnpm --filter @mondarth/emma-release test runs the release tooling’s tests.