@theholocron/semantic-release-config 8.9.0-alpha.1 → 8.9.0-alpha.16
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 +15 -1
- package/dist/index.d.ts +124 -1
- package/dist/index.js +217 -3
- package/package.json +13 -10
package/README.md
CHANGED
|
@@ -51,6 +51,20 @@ export default defineConfig();
|
|
|
51
51
|
| `exec` | `{ prepareCmd?, publishCmd? }` | `undefined` | Commands for `@semantic-release/exec`. Omit if not needed. |
|
|
52
52
|
| `assets` | `string[]` | `['CHANGELOG.md', 'package.json', 'packages/*/package.json']` | Files committed by `@semantic-release/git` after release. |
|
|
53
53
|
|
|
54
|
+
## Releasing a version group
|
|
55
|
+
|
|
56
|
+
A monorepo that releases several folders on their own lines uses `groupReleaseConfig()`. It builds on `defineConfig()` (same rules, notes and plugin order) and takes the same options plus a `group`: tags are `<group>-v<version>`, the analyzer and notes generator see only commits that touched the group's `paths` (default `[group]`), and the changelog, release commit assets and message are the group's.
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
import { groupReleaseConfig, ungroupedReleaseConfig } from "@theholocron/semantic-release-config";
|
|
60
|
+
|
|
61
|
+
export default process.env.RELEASE_GROUP
|
|
62
|
+
? groupReleaseConfig({ group: process.env.RELEASE_GROUP, branches: ["main"] })
|
|
63
|
+
: ungroupedReleaseConfig({ groups: ["clients"], branches: ["main"] });
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`ungroupedReleaseConfig()` is the mirror for everything in no group: it skips commits confined to the `groups` folders. `folderScoped`, `ungroupedScoped`, `commitFiles` and `inScope` are exported too.
|
|
67
|
+
|
|
54
68
|
## Plugin pipeline
|
|
55
69
|
|
|
56
70
|
The config wires up this fixed plugin order:
|
|
@@ -60,7 +74,7 @@ The config wires up this fixed plugin order:
|
|
|
60
74
|
3. `@semantic-release/changelog` — writes `CHANGELOG.md`
|
|
61
75
|
4. `@semantic-release/exec` — repo-specific version bump and publish commands _(only when `exec` is passed)_
|
|
62
76
|
5. `@semantic-release/git` — commits updated files back to the repo
|
|
63
|
-
6. `@semantic-release/github` — creates a GitHub release
|
|
77
|
+
6. `@semantic-release/github` — creates a GitHub release, and labels the issues and pull requests it ships `released` on every branch (the plugin's default is `released on @<channel>` on a prerelease branch)
|
|
64
78
|
|
|
65
79
|
## Named exports
|
|
66
80
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/** Runs `git` with `args` in `cwd`, feeding it `input` on stdin, and returns stdout. */
|
|
2
|
+
export type GitRun = (args: string[], input: string, cwd: string) => string;
|
|
3
|
+
/**
|
|
4
|
+
* The repo-relative files each of `hashes` changed, from one
|
|
5
|
+
* `git diff-tree --stdin` call. A commit that changes nothing (or a merge
|
|
6
|
+
* commit) maps to `[]`. Paths come back verbatim, not quoted, whatever they
|
|
7
|
+
* contain.
|
|
8
|
+
*/
|
|
9
|
+
export declare function commitFiles(cwd: string, hashes: readonly string[], run?: GitRun): Map<string, string[]>;
|
|
1
10
|
interface ExecOptions {
|
|
2
11
|
prepareCmd?: string;
|
|
3
12
|
publishCmd?: string;
|
|
@@ -13,6 +22,7 @@ interface Options {
|
|
|
13
22
|
npm?: boolean | NpmOptions;
|
|
14
23
|
assets?: string[];
|
|
15
24
|
}
|
|
25
|
+
/** The `@semantic-release/commit-analyzer` plugin with the Conventional Commits preset: `feat` releases a minor, `fix`, `perf`, `refactor` and `chore(deps)` a patch, and a breaking change a major. */
|
|
16
26
|
export declare const commitAnalyzer: (string | {
|
|
17
27
|
preset: string;
|
|
18
28
|
releaseRules: ({
|
|
@@ -32,6 +42,7 @@ export declare const commitAnalyzer: (string | {
|
|
|
32
42
|
scope?: undefined;
|
|
33
43
|
})[];
|
|
34
44
|
})[];
|
|
45
|
+
/** The `@semantic-release/release-notes-generator` plugin with the Conventional Commits preset, grouping commits into changelog sections by type. */
|
|
35
46
|
export declare const releaseNotesGenerator: (string | {
|
|
36
47
|
preset: string;
|
|
37
48
|
presetConfig: {
|
|
@@ -50,4 +61,116 @@ export declare const releaseNotesGenerator: (string | {
|
|
|
50
61
|
* @see https://semantic-release.gitbook.io/semantic-release/usage/configuration
|
|
51
62
|
*/
|
|
52
63
|
export declare function defineConfig({ branches, exec, npm, assets }?: Options): object;
|
|
53
|
-
|
|
64
|
+
/** The slice of a semantic-release commit this module reads. */
|
|
65
|
+
interface ReleaseCommit {
|
|
66
|
+
/** Full commit hash. */
|
|
67
|
+
hash: string;
|
|
68
|
+
[key: string]: unknown;
|
|
69
|
+
}
|
|
70
|
+
/** The slice of a semantic-release plugin context this module reads; everything else passes through untouched. */
|
|
71
|
+
interface ReleaseContext {
|
|
72
|
+
/** The directory semantic-release runs in: the repo being released. */
|
|
73
|
+
cwd?: string;
|
|
74
|
+
/** The commits analysed for this release. */
|
|
75
|
+
commits?: ReleaseCommit[];
|
|
76
|
+
[key: string]: unknown;
|
|
77
|
+
}
|
|
78
|
+
/** A semantic-release plugin step that looks at commits. */
|
|
79
|
+
type CommitStep = "analyzeCommits" | "generateNotes" | "success";
|
|
80
|
+
type StepFn = (pluginConfig: unknown, context: ReleaseContext) => unknown;
|
|
81
|
+
/** A plugin module that implements `Step`. */
|
|
82
|
+
type StepPlugin<Step extends CommitStep> = Partial<Record<Step, StepFn>>;
|
|
83
|
+
/** What the folder-scoped release plugin depends on, each replaceable in a test. */
|
|
84
|
+
interface FolderScopedDeps {
|
|
85
|
+
/**
|
|
86
|
+
* Loads a plugin module by specifier, resolved from `cwd` (the repo being released). Defaults to a
|
|
87
|
+
* dynamic `import()` of the module as `cwd` resolves it, which is how semantic-release loads its own plugins.
|
|
88
|
+
*/
|
|
89
|
+
load?: (specifier: string, cwd: string) => Promise<unknown>;
|
|
90
|
+
/** Lists the files each commit changed. Defaults to `git diff-tree`. */
|
|
91
|
+
listFiles?: typeof commitFiles;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* An inline semantic-release plugin that runs `step` of the stock plugin
|
|
95
|
+
* (`@semantic-release/commit-analyzer` for `analyzeCommits`,
|
|
96
|
+
* `@semantic-release/release-notes-generator` for `generateNotes`) over only
|
|
97
|
+
* the commits that changed a file at or under `paths`.
|
|
98
|
+
*
|
|
99
|
+
* After a monorepo import, `lastTag..HEAD` spans every folder's commits, so an
|
|
100
|
+
* unscoped analyzer would release a folder for a commit that never touched it.
|
|
101
|
+
* The wrapped plugin is loaded on first use, so it only needs to be installed
|
|
102
|
+
* where a release actually runs. Commits are listed with one `git diff-tree`
|
|
103
|
+
* call, in `context.cwd`.
|
|
104
|
+
*/
|
|
105
|
+
export declare function folderScoped<Step extends CommitStep>(step: Step, paths: readonly string[], deps?: FolderScopedDeps): StepPlugin<Step>;
|
|
106
|
+
/**
|
|
107
|
+
* The mirror of {@link folderScoped}, for the release of everything that is in **no**
|
|
108
|
+
* version group: it sees only commits that changed a file outside every group folder. A commit
|
|
109
|
+
* confined to a group (and the group's imported history) belongs to that group's release; letting the
|
|
110
|
+
* repo-wide one analyze it would release the repo for a change that is not in it, and write the
|
|
111
|
+
* group's commits into the repo's changelog.
|
|
112
|
+
*/
|
|
113
|
+
export declare function ungroupedScoped<Step extends CommitStep>(step: Step, groups: readonly string[], deps?: FolderScopedDeps): StepPlugin<Step>;
|
|
114
|
+
/** The file `specifier` resolves to for the repo at `cwd`, else for `semantic-release`; `requireFrom` is a seam for tests. */
|
|
115
|
+
export declare function resolvePlugin(specifier: string, cwd: string, requireFrom?: (file: string) => {
|
|
116
|
+
resolve(id: string): string;
|
|
117
|
+
}): string;
|
|
118
|
+
/**
|
|
119
|
+
* Splits the GitHub plugin's entry so its "released in…" comments and labels go only to the PRs and
|
|
120
|
+
* issues of the commits `scoped` keeps. The entry stays as it was, so it still publishes the GitHub
|
|
121
|
+
* release, but with the commenting turned off; `scoped` (a {@link folderScoped} or
|
|
122
|
+
* {@link ungroupedScoped} `success` step) then does the commenting with the entry's own config.
|
|
123
|
+
*/
|
|
124
|
+
export declare function commentOnlyFor(entry: string | [string, Record<string, unknown> | undefined], scoped: StepPlugin<"success">): ((string | [string, Record<string, unknown> | undefined]) | [object, unknown])[];
|
|
125
|
+
/** What `defineConfig()` from `@theholocron/semantic-release-config` accepts. */
|
|
126
|
+
type DefineConfigOptions = NonNullable<Parameters<typeof defineConfig>[0]>;
|
|
127
|
+
interface GroupReleaseOptions extends DefineConfigOptions {
|
|
128
|
+
/**
|
|
129
|
+
* The version group: a folder of the repo (`clients`, `observability`, `cli`) that
|
|
130
|
+
* releases on its own. It prefixes the tags (`clients-v1.43.2`) and the release commit.
|
|
131
|
+
*/
|
|
132
|
+
group: string;
|
|
133
|
+
/** Repo-relative folders whose commits release this group. Defaults to `[group]`. */
|
|
134
|
+
paths?: readonly string[];
|
|
135
|
+
/** The group's changelog. Defaults to `<group>/CHANGELOG.md`. */
|
|
136
|
+
changelogFile?: string;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* A semantic-release config that releases one version group of a monorepo,
|
|
140
|
+
* built on the org's `defineConfig()` so its rules, notes layout and plugin
|
|
141
|
+
* order stay the same:
|
|
142
|
+
*
|
|
143
|
+
* - tags are `<group>-v<version>`, so each group finds its own last release;
|
|
144
|
+
* - the commit analyzer and notes generator see only commits that touched the
|
|
145
|
+
* group's `paths` ({@link folderScoped});
|
|
146
|
+
* - the changelog, the release commit's assets and its message are the group's.
|
|
147
|
+
*
|
|
148
|
+
* Run one `semantic-release` per group. Cross-group dependencies resolve from
|
|
149
|
+
* the registry, so `paths` is the group's folder unless more is listed.
|
|
150
|
+
*/
|
|
151
|
+
export declare function groupReleaseConfig(options: GroupReleaseOptions): object;
|
|
152
|
+
/**
|
|
153
|
+
* Whether any of `files` (repo-relative, `/`-separated) sits at or under one of
|
|
154
|
+
* `paths` (repo-relative folders). `clients` matches `clients/http/x.ts` but
|
|
155
|
+
* not `clients-old/x.ts`.
|
|
156
|
+
*/
|
|
157
|
+
export declare function inScope(files: readonly string[], paths: readonly string[]): boolean;
|
|
158
|
+
/**
|
|
159
|
+
* Whether any of `files` sits **outside** every one of `folders`: the mirror of
|
|
160
|
+
* {@link inScope}. A commit that only touches the version groups' folders is out;
|
|
161
|
+
* one that touches anything else, even beside a group's files, is in.
|
|
162
|
+
*/
|
|
163
|
+
export declare function outsideFolders(files: readonly string[], folders: readonly string[]): boolean;
|
|
164
|
+
interface UngroupedReleaseOptions extends DefineConfigOptions {
|
|
165
|
+
/** The version groups released on their own: folders of the repo whose commits this release ignores. */
|
|
166
|
+
groups: readonly string[];
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The semantic-release config for everything that is in no version group, built on the org's
|
|
170
|
+
* `defineConfig()`: tags, changelog and release commit are the repo's own, as before. The commit
|
|
171
|
+
* analyzer and notes generator skip commits confined to the `groups` folders, because those
|
|
172
|
+
* belong to the groups' releases ({@link ungroupedScoped}). With no groups it is `defineConfig()`
|
|
173
|
+
* unchanged.
|
|
174
|
+
*/
|
|
175
|
+
export declare function ungroupedReleaseConfig({ groups, ...options }: UngroupedReleaseOptions): object;
|
|
176
|
+
export type { CommitStep, DefineConfigOptions, FolderScopedDeps, GroupReleaseOptions, ReleaseCommit, ReleaseContext, StepPlugin, UngroupedReleaseOptions };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,42 @@
|
|
|
1
|
-
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
// commit-files/commit-files.ts
|
|
6
|
+
const defaultGitRun = (args, input, cwd) => execFileSync("git", args, {
|
|
7
|
+
cwd,
|
|
8
|
+
input,
|
|
9
|
+
encoding: "utf8",
|
|
10
|
+
maxBuffer: 268435456
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* The repo-relative files each of `hashes` changed, from one
|
|
14
|
+
* `git diff-tree --stdin` call. A commit that changes nothing (or a merge
|
|
15
|
+
* commit) maps to `[]`. Paths come back verbatim, not quoted, whatever they
|
|
16
|
+
* contain.
|
|
17
|
+
*/
|
|
18
|
+
function commitFiles(cwd, hashes, run = defaultGitRun) {
|
|
19
|
+
const files = new Map(hashes.map((hash) => [hash, []]));
|
|
20
|
+
if (hashes.length === 0) return files;
|
|
21
|
+
const out = run([
|
|
22
|
+
"-c",
|
|
23
|
+
"core.quotePath=false",
|
|
24
|
+
"diff-tree",
|
|
25
|
+
"--stdin",
|
|
26
|
+
"--name-only",
|
|
27
|
+
"-r",
|
|
28
|
+
"--root"
|
|
29
|
+
], `${hashes.join("\n")}\n`, cwd);
|
|
30
|
+
let current;
|
|
31
|
+
for (const line of out.split("\n")) {
|
|
32
|
+
if (line === "") continue;
|
|
33
|
+
const known = files.get(line);
|
|
34
|
+
if (known) current = known;
|
|
35
|
+
else current?.push(line);
|
|
36
|
+
}
|
|
37
|
+
return files;
|
|
38
|
+
}
|
|
39
|
+
// define-config/define-config.ts
|
|
2
40
|
const releaseRules = [
|
|
3
41
|
{
|
|
4
42
|
type: "feat",
|
|
@@ -64,10 +102,12 @@ const types = [
|
|
|
64
102
|
hidden: true
|
|
65
103
|
}
|
|
66
104
|
];
|
|
105
|
+
/** The `@semantic-release/commit-analyzer` plugin with the Conventional Commits preset: `feat` releases a minor, `fix`, `perf`, `refactor` and `chore(deps)` a patch, and a breaking change a major. */
|
|
67
106
|
const commitAnalyzer = ["@semantic-release/commit-analyzer", {
|
|
68
107
|
preset: "conventionalcommits",
|
|
69
108
|
releaseRules
|
|
70
109
|
}];
|
|
110
|
+
/** The `@semantic-release/release-notes-generator` plugin with the Conventional Commits preset, grouping commits into changelog sections by type. */
|
|
71
111
|
const releaseNotesGenerator = ["@semantic-release/release-notes-generator", {
|
|
72
112
|
preset: "conventionalcommits",
|
|
73
113
|
presetConfig: { types }
|
|
@@ -97,8 +137,182 @@ function defineConfig({ branches = ["main"], exec, npm, assets } = {}) {
|
|
|
97
137
|
assets: resolvedAssets,
|
|
98
138
|
message: defaultMessage
|
|
99
139
|
}],
|
|
100
|
-
"@semantic-release/github"
|
|
140
|
+
["@semantic-release/github", { releasedLabels: ["released"] }]
|
|
141
|
+
]
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
// in-scope/in-scope.ts
|
|
145
|
+
/**
|
|
146
|
+
* Whether any of `files` (repo-relative, `/`-separated) sits at or under one of
|
|
147
|
+
* `paths` (repo-relative folders). `clients` matches `clients/http/x.ts` but
|
|
148
|
+
* not `clients-old/x.ts`.
|
|
149
|
+
*/
|
|
150
|
+
function inScope(files, paths) {
|
|
151
|
+
const folders = paths.map((path) => path.replace(/^\.\//, "").replace(/\/+$/, ""));
|
|
152
|
+
return files.some((file) => folders.some((folder) => file === folder || file.startsWith(`${folder}/`)));
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Whether any of `files` sits **outside** every one of `folders`: the mirror of
|
|
156
|
+
* {@link inScope}. A commit that only touches the version groups' folders is out;
|
|
157
|
+
* one that touches anything else, even beside a group's files, is in.
|
|
158
|
+
*/
|
|
159
|
+
function outsideFolders(files, folders) {
|
|
160
|
+
const normalized = folders.map((folder) => folder.replace(/^\.\//, "").replace(/\/+$/, ""));
|
|
161
|
+
return files.some((file) => !normalized.some((folder) => file === folder || file.startsWith(`${folder}/`)));
|
|
162
|
+
}
|
|
163
|
+
// folder-scoped/folder-scoped.ts
|
|
164
|
+
/** The semantic-release plugin that implements each commit-reading step. `success` is the GitHub plugin's "released in…" comments. */
|
|
165
|
+
const MODULES = {
|
|
166
|
+
analyzeCommits: "@semantic-release/commit-analyzer",
|
|
167
|
+
generateNotes: "@semantic-release/release-notes-generator",
|
|
168
|
+
success: "@semantic-release/github"
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* An inline semantic-release plugin that runs `step` of the stock plugin
|
|
172
|
+
* (`@semantic-release/commit-analyzer` for `analyzeCommits`,
|
|
173
|
+
* `@semantic-release/release-notes-generator` for `generateNotes`) over only
|
|
174
|
+
* the commits that changed a file at or under `paths`.
|
|
175
|
+
*
|
|
176
|
+
* After a monorepo import, `lastTag..HEAD` spans every folder's commits, so an
|
|
177
|
+
* unscoped analyzer would release a folder for a commit that never touched it.
|
|
178
|
+
* The wrapped plugin is loaded on first use, so it only needs to be installed
|
|
179
|
+
* where a release actually runs. Commits are listed with one `git diff-tree`
|
|
180
|
+
* call, in `context.cwd`.
|
|
181
|
+
*/
|
|
182
|
+
function folderScoped(step, paths, deps = {}) {
|
|
183
|
+
return scopedBy(step, (files) => inScope(files, paths), deps);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The mirror of {@link folderScoped}, for the release of everything that is in **no**
|
|
187
|
+
* version group: it sees only commits that changed a file outside every group folder. A commit
|
|
188
|
+
* confined to a group (and the group's imported history) belongs to that group's release; letting the
|
|
189
|
+
* repo-wide one analyze it would release the repo for a change that is not in it, and write the
|
|
190
|
+
* group's commits into the repo's changelog.
|
|
191
|
+
*/
|
|
192
|
+
function ungroupedScoped(step, groups, deps = {}) {
|
|
193
|
+
return scopedBy(step, (files) => outsideFolders(files, groups), deps);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* `import()` of `specifier` the way semantic-release finds its own plugins: as the repo at `cwd` resolves it,
|
|
197
|
+
* else as `semantic-release` does (the stock plugins are its dependencies, not the repo's). A bare `import()`
|
|
198
|
+
* would resolve from this file, inside astromech, where neither is visible.
|
|
199
|
+
*/
|
|
200
|
+
function importFrom(specifier, cwd) {
|
|
201
|
+
return import(pathToFileURL(resolvePlugin(specifier, cwd)).href);
|
|
202
|
+
}
|
|
203
|
+
/** The file `specifier` resolves to for the repo at `cwd`, else for `semantic-release`; `requireFrom` is a seam for tests. */
|
|
204
|
+
function resolvePlugin(specifier, cwd, requireFrom = createRequire) {
|
|
205
|
+
const fromRepo = requireFrom(join(cwd, "package.json"));
|
|
206
|
+
try {
|
|
207
|
+
return fromRepo.resolve(specifier);
|
|
208
|
+
} catch {
|
|
209
|
+
return requireFrom(fromRepo.resolve("semantic-release")).resolve(specifier);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function scopedBy(step, keep, { load = importFrom, listFiles = commitFiles }) {
|
|
213
|
+
const scoped = async (pluginConfig, context) => {
|
|
214
|
+
const { commits = [], cwd = process.cwd() } = context;
|
|
215
|
+
const inner = await load(MODULES[step], cwd);
|
|
216
|
+
const files = listFiles(cwd, commits.map((commit) => commit.hash));
|
|
217
|
+
const kept = commits.filter((commit) => keep(files.get(commit.hash) ?? []));
|
|
218
|
+
return inner[step]?.(pluginConfig, {
|
|
219
|
+
...context,
|
|
220
|
+
commits: kept
|
|
221
|
+
});
|
|
222
|
+
};
|
|
223
|
+
return { [step]: scoped };
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Splits the GitHub plugin's entry so its "released in…" comments and labels go only to the PRs and
|
|
227
|
+
* issues of the commits `scoped` keeps. The entry stays as it was, so it still publishes the GitHub
|
|
228
|
+
* release, but with the commenting turned off; `scoped` (a {@link folderScoped} or
|
|
229
|
+
* {@link ungroupedScoped} `success` step) then does the commenting with the entry's own config.
|
|
230
|
+
*/
|
|
231
|
+
function commentOnlyFor(entry, scoped) {
|
|
232
|
+
const [name, config] = Array.isArray(entry) ? entry : [entry, void 0];
|
|
233
|
+
return [[name, {
|
|
234
|
+
...config,
|
|
235
|
+
successComment: false,
|
|
236
|
+
releasedLabels: false
|
|
237
|
+
}], [scoped, config]];
|
|
238
|
+
}
|
|
239
|
+
// group-release-config/group-release-config.ts
|
|
240
|
+
const GROUP = /^[a-z][a-z0-9-]*$/;
|
|
241
|
+
/**
|
|
242
|
+
* A semantic-release config that releases one version group of a monorepo,
|
|
243
|
+
* built on the org's `defineConfig()` so its rules, notes layout and plugin
|
|
244
|
+
* order stay the same:
|
|
245
|
+
*
|
|
246
|
+
* - tags are `<group>-v<version>`, so each group finds its own last release;
|
|
247
|
+
* - the commit analyzer and notes generator see only commits that touched the
|
|
248
|
+
* group's `paths` ({@link folderScoped});
|
|
249
|
+
* - the changelog, the release commit's assets and its message are the group's.
|
|
250
|
+
*
|
|
251
|
+
* Run one `semantic-release` per group. Cross-group dependencies resolve from
|
|
252
|
+
* the registry, so `paths` is the group's folder unless more is listed.
|
|
253
|
+
*/
|
|
254
|
+
function groupReleaseConfig(options) {
|
|
255
|
+
const { group, paths = [group], changelogFile = `${group}/CHANGELOG.md`, assets, ...rest } = options;
|
|
256
|
+
if (!GROUP.test(group)) throw new TypeError(`release group must be a lowercase folder name like "clients", got ${JSON.stringify(group)}`);
|
|
257
|
+
if (paths.length === 0) throw new TypeError(`release group ${JSON.stringify(group)} needs at least one path`);
|
|
258
|
+
const base = defineConfig({
|
|
259
|
+
...rest,
|
|
260
|
+
assets: assets ?? [
|
|
261
|
+
changelogFile,
|
|
262
|
+
`${group}/**/package.json`,
|
|
263
|
+
"!**/node_modules/**"
|
|
101
264
|
]
|
|
265
|
+
});
|
|
266
|
+
const scope = (entry) => {
|
|
267
|
+
const [name, config] = Array.isArray(entry) ? entry : [entry, void 0];
|
|
268
|
+
switch (name) {
|
|
269
|
+
case "@semantic-release/commit-analyzer": return [[folderScoped("analyzeCommits", paths), config]];
|
|
270
|
+
case "@semantic-release/release-notes-generator": return [[folderScoped("generateNotes", paths), config]];
|
|
271
|
+
case "@semantic-release/changelog": return [[name, {
|
|
272
|
+
...config,
|
|
273
|
+
changelogFile
|
|
274
|
+
}]];
|
|
275
|
+
case "@semantic-release/git": return [[name, {
|
|
276
|
+
...config,
|
|
277
|
+
message: groupMessage(config?.["message"], group)
|
|
278
|
+
}]];
|
|
279
|
+
case "@semantic-release/github": return commentOnlyFor(entry, folderScoped("success", paths));
|
|
280
|
+
default: return [entry];
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
return {
|
|
284
|
+
...base,
|
|
285
|
+
tagFormat: `${group}-v\${version}`,
|
|
286
|
+
plugins: base.plugins.flatMap(scope)
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/** `chore(release): <version> …` becomes `chore(release): <group> <version> …`. */
|
|
290
|
+
function groupMessage(message, group) {
|
|
291
|
+
return message.replace(/^chore\(release\): /, `chore(release): ${group} `);
|
|
292
|
+
}
|
|
293
|
+
// ungrouped-release-config/ungrouped-release-config.ts
|
|
294
|
+
/**
|
|
295
|
+
* The semantic-release config for everything that is in no version group, built on the org's
|
|
296
|
+
* `defineConfig()`: tags, changelog and release commit are the repo's own, as before. The commit
|
|
297
|
+
* analyzer and notes generator skip commits confined to the `groups` folders, because those
|
|
298
|
+
* belong to the groups' releases ({@link ungroupedScoped}). With no groups it is `defineConfig()`
|
|
299
|
+
* unchanged.
|
|
300
|
+
*/
|
|
301
|
+
function ungroupedReleaseConfig({ groups, ...options }) {
|
|
302
|
+
const base = defineConfig(options);
|
|
303
|
+
if (groups.length === 0) return base;
|
|
304
|
+
const scope = (entry) => {
|
|
305
|
+
const [name, config] = Array.isArray(entry) ? entry : [entry, void 0];
|
|
306
|
+
switch (name) {
|
|
307
|
+
case "@semantic-release/commit-analyzer": return [[ungroupedScoped("analyzeCommits", groups), config]];
|
|
308
|
+
case "@semantic-release/release-notes-generator": return [[ungroupedScoped("generateNotes", groups), config]];
|
|
309
|
+
case "@semantic-release/github": return commentOnlyFor(entry, ungroupedScoped("success", groups));
|
|
310
|
+
default: return [entry];
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
return {
|
|
314
|
+
...base,
|
|
315
|
+
plugins: base.plugins.flatMap(scope)
|
|
102
316
|
};
|
|
103
317
|
}
|
|
104
|
-
export { commitAnalyzer, defineConfig, releaseNotesGenerator };
|
|
318
|
+
export { commentOnlyFor, commitAnalyzer, commitFiles, defineConfig, folderScoped, groupReleaseConfig, inScope, outsideFolders, releaseNotesGenerator, resolvePlugin, ungroupedReleaseConfig, ungroupedScoped };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theholocron/semantic-release-config",
|
|
3
|
-
"version": "8.9.0-alpha.
|
|
3
|
+
"version": "8.9.0-alpha.16",
|
|
4
4
|
"description": "A semantic-release configuration for automated versioning and publishing.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"semantic-release",
|
|
@@ -33,12 +33,15 @@
|
|
|
33
33
|
"dist/"
|
|
34
34
|
],
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@
|
|
37
|
-
"
|
|
36
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
37
|
+
"@semantic-release/release-notes-generator": "^14.1.1",
|
|
38
|
+
"@vitest/coverage-v8": "^5.1.0",
|
|
39
|
+
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
40
|
+
"tsdown": "^0.23.5",
|
|
38
41
|
"typescript": "^5.9.3",
|
|
39
|
-
"vitest": "^5.0
|
|
40
|
-
"@theholocron/tsconfig": "8.9.0-alpha.
|
|
41
|
-
"@theholocron/vitest-config": "8.9.0-alpha.
|
|
42
|
+
"vitest": "^5.1.0",
|
|
43
|
+
"@theholocron/tsconfig": "8.9.0-alpha.16",
|
|
44
|
+
"@theholocron/vitest-config": "8.9.0-alpha.16"
|
|
42
45
|
},
|
|
43
46
|
"peerDependencies": {
|
|
44
47
|
"@semantic-release/changelog": "^6",
|
|
@@ -62,10 +65,10 @@
|
|
|
62
65
|
"access": "public"
|
|
63
66
|
},
|
|
64
67
|
"scripts": {
|
|
65
|
-
"
|
|
66
|
-
"
|
|
68
|
+
"build": "tsdown",
|
|
69
|
+
"lint:code": "eslint .",
|
|
70
|
+
"test:unit": "vitest run --coverage",
|
|
67
71
|
"test:watch": "vitest",
|
|
68
|
-
"
|
|
69
|
-
"verification.unitTests": "vitest run"
|
|
72
|
+
"typecheck": "tsc --noEmit"
|
|
70
73
|
}
|
|
71
74
|
}
|