@exadev/semantic-release-workspace 3.0.3 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -5
- package/dist/cli.js +24 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,11 +66,7 @@ Everything above describes `commitStrategy: 'per-package'`, the default: unchang
|
|
|
66
66
|
| Default publish plugins | `DEFAULT_PUBLISH_PLUGINS` (changelog, npm, github, git) | `SINGLE_COMMIT_DEFAULT_PUBLISH_PLUGINS` — the same list minus git |
|
|
67
67
|
| Crash recovery | A bump commit already sitting in git history (from this run or an earlier one) is recognised via a machine-parseable trailer, so a run that resumes after a partial push still forces the right dependent releases | Not needed: either the whole combined commit lands and pushes, or the run fails before touching git at all, so there is never a partial push to recover from |
|
|
68
68
|
|
|
69
|
-
Everything about *what* releases (topological order, forced-patch dependents, dependency-range classification, path-scoped commit analysis, unsupported-range rejection) is identical between the two modes — `commitStrategy` only changes how the result is committed, tagged, and pushed.
|
|
70
|
-
|
|
71
|
-
Each package's release tag comes from the `tagFormat` option, a lodash template with `${name}` and `${version}` placeholders, defaulting to `'${name}@${version}'`. Set it via the `tagFormat` field in a `--config` file or the `tagFormat` option to `releaseWorkspace()`. The template must contain `${version}` and may contain `${name}`; anything else is rejected before any package releases.
|
|
72
|
-
|
|
73
|
-
Override it when the tags are consumed as refs outside git: GitHub Actions pins composite actions as `owner/repo/path@ref`, and GitHub's workflow parser rejects a ref containing `@` -- so a repository of actions sets `'${name}-v${version}'` and every workflow pins `...@<action>-v<major>`. Note that switching an existing repository's format starts a fresh tag namespace: semantic-release will not see prior releases recorded under the old format, so rename existing tags to the new template as part of the switch.
|
|
69
|
+
Everything about *what* releases (topological order, forced-patch dependents, dependency-range classification, path-scoped commit analysis, unsupported-range rejection) is identical between the two modes — `commitStrategy` only changes how the result is committed, tagged, and pushed.
|
|
74
70
|
|
|
75
71
|
Set it via the `--commit-strategy <mode>` CLI flag, the `commitStrategy` field in a `--config` file, or the `commitStrategy` option to `releaseWorkspace()`; omit it and nothing changes.
|
|
76
72
|
|
|
@@ -84,6 +80,18 @@ One window is outside this tool's control: semantic-release pushes a package's t
|
|
|
84
80
|
|
|
85
81
|
If you need a run to be strictly all-or-nothing, use `commitStrategy: 'single'`, which makes one commit and one atomic push before anything publishes, and which retries the whole attempt against the new tip when that push loses a race.
|
|
86
82
|
|
|
83
|
+
## Tag format
|
|
84
|
+
|
|
85
|
+
Each package's release tag comes from the `tagFormat` option, a lodash template with `${name}` and `${version}` placeholders, defaulting to `'${name}@${version}'`. Set it with the `--tag-format <template>` CLI flag, the `tagFormat` field in a `--config` file, or the `tagFormat` option to `releaseWorkspace()`; when both the flag and the config file set it, the flag wins. The template must contain `${version}` and may contain `${name}`; anything else is rejected before any package releases.
|
|
86
|
+
|
|
87
|
+
Override it when the tags are consumed as refs outside git: GitHub Actions pins composite actions as `owner/repo/path@ref`, and GitHub's workflow parser rejects a ref containing `@` -- so a repository of actions sets `'${name}-v${version}'` and every workflow pins `...@<action>-v<major>`. Note that switching an existing repository's format starts a fresh tag namespace: semantic-release will not see prior releases recorded under the old format, so rename existing tags to the new template as part of the switch.
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
pnpm exec semantic-release-workspace release --tag-format '${name}-v${version}'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
A gated run records each package's tag in its state file, so `resume` takes no tag format of its own.
|
|
94
|
+
|
|
87
95
|
## Gating publish
|
|
88
96
|
|
|
89
97
|
`commitStrategy` changes how a release is committed; `gatePublish` changes *when* it gets published relative to being tagged and pushed -- a different, orthogonal axis. With `gatePublish: true`, each due package is tagged and pushed via [@exadev/release-gate](https://www.npmjs.com/package/@exadev/release-gate) (a real dependency of this package -- no separate install needed) but never published: nothing calls `npm publish`, creates a GitHub Release, or runs any other configured publish step, until a separate `resume` step does so explicitly -- from the same process, or a completely different one (a later CI job, once a deploy or smoke test has confirmed the release should actually go out).
|
|
@@ -219,6 +227,7 @@ Run it from the workspace root (or pass `--root <directory>`). A dry run analyse
|
|
|
219
227
|
| `--analyze-commits <json>` | Options for the wrapped @semantic-release/commit-analyzer (e.g. `'{"preset":"conventionalcommits","releaseRules":[...]}'`) |
|
|
220
228
|
| `--generate-notes <json>` | Options for the wrapped @semantic-release/release-notes-generator |
|
|
221
229
|
| `--commit-strategy <mode>` | `per-package` (default) or `single` — see [Commit strategies](#commit-strategies) |
|
|
230
|
+
| `--tag-format <template>` | Template for each package's release tag, with `${name}` and `${version}` placeholders; must contain `${version}` (default: `${name}@${version}`) — see [Tag format](#tag-format) |
|
|
222
231
|
| `--gate-publish` | Tag and push each due package, but defer publishing — see [Gating publish](#gating-publish). Requires `--gate-state-file`; rejected with `--commit-strategy single` |
|
|
223
232
|
| `--gate-state-file <path>` | With `--gate-publish`: where to write the state a later `resume` run needs |
|
|
224
233
|
| `--config <file>` | A config file (`.json`, `.yaml`, `.yml`, `.js`, `.cjs`, `.mjs`, `.ts`, `.cts`, or `.mts`, loaded via [cosmiconfig](https://github.com/cosmiconfig/cosmiconfig)) providing any of the above, plus `packagePlugins` (see [Per-package publish plugins](#per-package-publish-plugins)), through its default export; explicit flags win. TypeScript files are run by Node's own type stripping, so they may use only erasable type syntax (annotations and `import type`, not `enum` or `namespace`) |
|
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import { generateNotes } from "@semantic-release/release-notes-generator";
|
|
|
14
14
|
import semanticRelease from "semantic-release";
|
|
15
15
|
import { pathToFileURL } from "node:url";
|
|
16
16
|
//#region package.json
|
|
17
|
-
var version = "3.0
|
|
17
|
+
var version = "3.1.0";
|
|
18
18
|
//#endregion
|
|
19
19
|
//#region src/errors.ts
|
|
20
20
|
/**
|
|
@@ -1637,7 +1637,8 @@ const CONFIG_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
|
1637
1637
|
"analyzeCommits",
|
|
1638
1638
|
"generateNotes",
|
|
1639
1639
|
"commitStrategy",
|
|
1640
|
-
"gatePublish"
|
|
1640
|
+
"gatePublish",
|
|
1641
|
+
"tagFormat"
|
|
1641
1642
|
]);
|
|
1642
1643
|
const COMMIT_STRATEGIES = /* @__PURE__ */ new Set(["per-package", "single"]);
|
|
1643
1644
|
function isCommitStrategy(value) {
|
|
@@ -1659,9 +1660,10 @@ function createProgram() {
|
|
|
1659
1660
|
release.option("--analyze-commits <json>", "options for the wrapped @semantic-release/commit-analyzer, as a JSON object");
|
|
1660
1661
|
release.option("--generate-notes <json>", "options for the wrapped @semantic-release/release-notes-generator, as a JSON object");
|
|
1661
1662
|
release.option("--commit-strategy <mode>", "how the run commits its released changes: \"per-package\" (default; today's behaviour, one commit per release plus one per dependency bump) or \"single\" (one combined commit for the whole run, tagged once per released package)", parseCommitStrategy);
|
|
1663
|
+
release.option("--tag-format <template>", "template for each package's release tag, with ${name} and ${version} placeholders; must contain ${version} (default: ${name}@${version})", parseTagFormat);
|
|
1662
1664
|
release.option("--gate-publish", "tag and push each due package via @exadev/release-gate, but defer publishing -- requires --gate-state-file, and cannot be combined with --commit-strategy single");
|
|
1663
1665
|
release.option("--gate-state-file <path>", "with --gate-publish: where to write the state a later \"resume\" run needs to finish publishing");
|
|
1664
|
-
release.option("--config <file>", "config file (.json, .yaml, .yml, .js, .cjs, or .ts) providing any of the release options (dryRun, branches, plugins, packagePlugins, analyzeCommits, generateNotes, commitStrategy, gatePublish); explicit flags win");
|
|
1666
|
+
release.option("--config <file>", "config file (.json, .yaml, .yml, .js, .cjs, or .ts) providing any of the release options (dryRun, branches, plugins, packagePlugins, analyzeCommits, generateNotes, commitStrategy, tagFormat, gatePublish); explicit flags win");
|
|
1665
1667
|
release.action(runRelease);
|
|
1666
1668
|
const resume = program.command("resume");
|
|
1667
1669
|
resume.description("Finish publishing every package a --gate-publish release tagged and pushed but did not publish.");
|
|
@@ -1674,6 +1676,20 @@ function parseCommitStrategy(value) {
|
|
|
1674
1676
|
if (!isCommitStrategy(value)) throw new InvalidArgumentError(`--commit-strategy must be one of: ${[...COMMIT_STRATEGIES].join(", ")}`);
|
|
1675
1677
|
return value;
|
|
1676
1678
|
}
|
|
1679
|
+
function parseTagFormat(value) {
|
|
1680
|
+
return validateTagFormatOption(value, (message) => message);
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Runs `validateTagFormat` and turns its `ReleaseConfigurationError` into the `InvalidArgumentError` the CLI reports as a usage mistake. `describe` lets each caller say where the value came from (a flag needs no prefix, since commander already names the option; a config file entry needs the file's path).
|
|
1684
|
+
*/
|
|
1685
|
+
function validateTagFormatOption(value, describe) {
|
|
1686
|
+
try {
|
|
1687
|
+
return validateTagFormat(value);
|
|
1688
|
+
} catch (cause) {
|
|
1689
|
+
if (cause instanceof ReleaseConfigurationError) throw new InvalidArgumentError(describe(cause.message));
|
|
1690
|
+
throw cause;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1677
1693
|
const NO_CONFIG_FILE = {
|
|
1678
1694
|
dryRun: void 0,
|
|
1679
1695
|
branches: void 0,
|
|
@@ -1682,6 +1698,7 @@ const NO_CONFIG_FILE = {
|
|
|
1682
1698
|
analyzeCommits: void 0,
|
|
1683
1699
|
generateNotes: void 0,
|
|
1684
1700
|
commitStrategy: void 0,
|
|
1701
|
+
tagFormat: void 0,
|
|
1685
1702
|
gatePublish: void 0
|
|
1686
1703
|
};
|
|
1687
1704
|
async function runRelease(flags) {
|
|
@@ -1697,6 +1714,7 @@ async function runRelease(flags) {
|
|
|
1697
1714
|
analyzeCommits: flags.analyzeCommits === void 0 ? file.analyzeCommits : parseJsonObjectFlag(flags.analyzeCommits, "--analyze-commits"),
|
|
1698
1715
|
generateNotes: flags.generateNotes === void 0 ? file.generateNotes : parseJsonObjectFlag(flags.generateNotes, "--generate-notes"),
|
|
1699
1716
|
commitStrategy: flags.commitStrategy ?? file.commitStrategy,
|
|
1717
|
+
tagFormat: flags.tagFormat ?? file.tagFormat,
|
|
1700
1718
|
gatePublish
|
|
1701
1719
|
});
|
|
1702
1720
|
for (const pkg of outcome.packages) console.log(describeOutcome(pkg));
|
|
@@ -1758,7 +1776,7 @@ async function readReleaseConfigFile(path) {
|
|
|
1758
1776
|
const parsed = await readConfigFile(path);
|
|
1759
1777
|
if (!isJsonObject(parsed)) throw new InvalidArgumentError(`--config file ${path} must contain a JSON object`);
|
|
1760
1778
|
for (const key of Object.keys(parsed)) if (!CONFIG_OPTION_KEYS.has(key)) throw new InvalidArgumentError(`--config file ${path} has an unknown option "${key}"; recognised options: ${[...CONFIG_OPTION_KEYS].join(", ")}`);
|
|
1761
|
-
const { dryRun, branches, plugins, packagePlugins, analyzeCommits, generateNotes, commitStrategy, gatePublish } = parsed;
|
|
1779
|
+
const { dryRun, branches, plugins, packagePlugins, analyzeCommits, generateNotes, commitStrategy, tagFormat, gatePublish } = parsed;
|
|
1762
1780
|
if (dryRun !== void 0 && typeof dryRun !== "boolean") throw new InvalidArgumentError(`--config file ${path}: "dryRun" must be a boolean`);
|
|
1763
1781
|
if (branches !== void 0 && !isStringArray(branches)) throw new InvalidArgumentError(`--config file ${path}: "branches" must be an array of branch name strings`);
|
|
1764
1782
|
if (plugins !== void 0 && !Array.isArray(plugins)) throw new InvalidArgumentError(`--config file ${path}: "plugins" must be an array`);
|
|
@@ -1766,6 +1784,7 @@ async function readReleaseConfigFile(path) {
|
|
|
1766
1784
|
if (analyzeCommits !== void 0 && !isJsonObject(analyzeCommits)) throw new InvalidArgumentError(`--config file ${path}: "analyzeCommits" must be an object`);
|
|
1767
1785
|
if (generateNotes !== void 0 && !isJsonObject(generateNotes)) throw new InvalidArgumentError(`--config file ${path}: "generateNotes" must be an object`);
|
|
1768
1786
|
if (commitStrategy !== void 0 && (typeof commitStrategy !== "string" || !isCommitStrategy(commitStrategy))) throw new InvalidArgumentError(`--config file ${path}: "commitStrategy" must be one of: ${[...COMMIT_STRATEGIES].join(", ")}`);
|
|
1787
|
+
if (tagFormat !== void 0 && typeof tagFormat !== "string") throw new InvalidArgumentError(`--config file ${path}: "tagFormat" must be a string`);
|
|
1769
1788
|
if (gatePublish !== void 0 && typeof gatePublish !== "boolean") throw new InvalidArgumentError(`--config file ${path}: "gatePublish" must be a boolean`);
|
|
1770
1789
|
return {
|
|
1771
1790
|
dryRun,
|
|
@@ -1775,6 +1794,7 @@ async function readReleaseConfigFile(path) {
|
|
|
1775
1794
|
analyzeCommits,
|
|
1776
1795
|
generateNotes,
|
|
1777
1796
|
commitStrategy,
|
|
1797
|
+
tagFormat: tagFormat === void 0 ? void 0 : validateTagFormatOption(tagFormat, (message) => `--config file ${path}: ${message}`),
|
|
1778
1798
|
gatePublish
|
|
1779
1799
|
};
|
|
1780
1800
|
}
|
package/package.json
CHANGED