versionary 0.27.0 → 0.28.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.
@@ -226,7 +226,7 @@ function prependChangelog(cwd, changelogFile, section, format = "markdown-change
226
226
  }
227
227
  const heading = "# Changelog\n\n";
228
228
  const bodyWithoutHeading = existing.replace(/^# Changelog\s*/u, "");
229
- const separator = existing.length > 0 ? "\n" : "";
229
+ const separator = bodyWithoutHeading.length > 0 ? "\n\n" : "";
230
230
  const next = `${`${heading}${section}${separator}${bodyWithoutHeading}`.trimEnd()}\n`;
231
231
  node_fs_1.default.writeFileSync(changelogPath, next, "utf8");
232
232
  }
@@ -157,13 +157,6 @@ function fetchRemoteReleaseBranch(cwd, branch) {
157
157
  });
158
158
  return remoteRef;
159
159
  }
160
- function resolveReleaseName(cwd, packagePath, packageConfig, strategy, strategyConfig) {
161
- const configuredName = packageConfig["package-name"]?.trim();
162
- if (configuredName) {
163
- return configuredName;
164
- }
165
- return strategy.readPackageName?.(cwd, strategyConfig) ?? packagePath;
166
- }
167
160
  function buildReleaseTargets(cwd, plan, loadedConfig) {
168
161
  const releaseTargets = plan.packages
169
162
  ? plan.packages
@@ -178,7 +171,7 @@ function buildReleaseTargets(cwd, plan, loadedConfig) {
178
171
  }
179
172
  const packageConfig = loadedConfig.packages?.[pkg.path] ?? {};
180
173
  const packageContext = (0, package_context_js_1.resolvePackageStrategyContext)(loadedConfig, pkg.path, packageConfig);
181
- const releaseName = resolveReleaseName(cwd, pkg.path, packageConfig, packageContext.strategy, packageContext.config);
174
+ const releaseName = (0, package_context_js_1.resolveReleaseName)(cwd, pkg.path, packageConfig, packageContext.strategy, packageContext.config);
182
175
  const tagPrefix = normalizeReleaseNameForTag(releaseName);
183
176
  return {
184
177
  path: pkg.path,
@@ -211,7 +204,7 @@ function buildPackageReleaseMetadata(cwd, plan, loadedConfig) {
211
204
  }
212
205
  const packageConfig = loadedConfig.packages?.[pkg.path] ?? {};
213
206
  const packageContext = (0, package_context_js_1.resolvePackageStrategyContext)(loadedConfig, pkg.path, packageConfig);
214
- const releaseName = resolveReleaseName(cwd, pkg.path, packageConfig, packageContext.strategy, packageContext.config);
207
+ const releaseName = (0, package_context_js_1.resolveReleaseName)(cwd, pkg.path, packageConfig, packageContext.strategy, packageContext.config);
215
208
  metadataByPath[pkg.path] = {
216
209
  releaseName,
217
210
  tagPrefix: normalizeReleaseNameForTag(releaseName),
@@ -389,7 +382,7 @@ function renderSimpleReviewRequestBody(version, previousVersion, commits, plan =
389
382
  }
390
383
  const packageConfig = loadedConfig.packages?.[packagePath] ?? {};
391
384
  const packageContext = (0, package_context_js_1.resolvePackageStrategyContext)(loadedConfig, packagePath, packageConfig);
392
- const releaseName = resolveReleaseName(cwd, packagePath, packageConfig, packageContext.strategy, packageContext.config);
385
+ const releaseName = (0, package_context_js_1.resolveReleaseName)(cwd, packagePath, packageConfig, packageContext.strategy, packageContext.config);
393
386
  return normalizeReleaseNameForTag(releaseName);
394
387
  };
395
388
  if (plan?.packages && plan.packages.length > 1) {
@@ -1,5 +1,6 @@
1
1
  import type { VersionaryConfig } from "../types/config.js";
2
2
  import type { VersionaryPluginContext } from "../types/plugins.js";
3
+ export declare function resolveTargetPackageName(cwd: string, config: VersionaryConfig, targetPath: string): string | undefined;
3
4
  export declare function extractReleaseNotes(content: string, version: string, changelogFormat: "markdown-changelog" | "r-news"): string;
4
5
  export declare function extractClosingReferencesFromNotes(notes: string): number[];
5
6
  export declare function resolveTargetChangelogFile(config: VersionaryConfig, rootChangelogFile: string, targetPath: string): string;
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveTargetPackageName = resolveTargetPackageName;
6
7
  exports.extractReleaseNotes = extractReleaseNotes;
7
8
  exports.extractClosingReferencesFromNotes = extractClosingReferencesFromNotes;
8
9
  exports.resolveTargetChangelogFile = resolveTargetChangelogFile;
@@ -15,6 +16,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
15
16
  const node_path_1 = __importDefault(require("node:path"));
16
17
  const load_config_js_1 = require("../config/load-config.js");
17
18
  const client_js_1 = require("../scm/client.js");
19
+ const package_context_js_1 = require("../strategy/package-context.js");
18
20
  const resolve_js_1 = require("../strategy/resolve.js");
19
21
  const plan_js_1 = require("./plan.js");
20
22
  const pr_js_1 = require("./pr.js");
@@ -23,6 +25,15 @@ const state_js_1 = require("./state.js");
23
25
  function escapeRegExp(input) {
24
26
  return input.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
25
27
  }
28
+ function resolveTargetPackageName(cwd, config, targetPath) {
29
+ const packageConfig = config.packages?.[targetPath] ?? {};
30
+ const { strategy, config: strategyConfig } = (0, package_context_js_1.resolvePackageStrategyContext)(config, targetPath, packageConfig);
31
+ const resolved = (0, package_context_js_1.resolveReleaseName)(cwd, targetPath, packageConfig, strategy, strategyConfig);
32
+ if (!resolved || resolved === targetPath) {
33
+ return undefined;
34
+ }
35
+ return resolved;
36
+ }
26
37
  function extractReleaseNotes(content, version, changelogFormat) {
27
38
  const lines = content.split("\n");
28
39
  const shortVersion = version.replace(/\.\d+$/u, "");
@@ -172,12 +183,12 @@ async function runReleaseDetailed(cwd = process.cwd(), options = {}) {
172
183
  }
173
184
  const scmClient = (0, client_js_1.getScmClient)();
174
185
  const releases = [];
175
- const referencesByTag = new Map();
186
+ const referenceReleases = [];
176
187
  for (const target of targets) {
177
188
  const targetChangelogFile = resolveTargetChangelogFile(loaded.config, changelogFile, target.path);
178
189
  const targetChangelogFormat = resolveTargetChangelogFormat(loaded.config, target.path);
179
190
  const releaseNotes = readReleaseNotes(cwd, target.version, targetChangelogFile, targetChangelogFormat);
180
- referencesByTag.set(target.tag, extractClosingReferencesFromNotes(releaseNotes));
191
+ const references = extractClosingReferencesFromNotes(releaseNotes);
181
192
  const outcome = await (0, recovery_js_1.executeIdempotentReleaseTarget)(cwd, {
182
193
  tag: target.tag,
183
194
  version: target.version,
@@ -197,21 +208,27 @@ async function runReleaseDetailed(cwd = process.cwd(), options = {}) {
197
208
  tagStatus: outcome.tagStatus,
198
209
  metadataStatus: outcome.metadataStatus,
199
210
  });
200
- const references = referencesByTag.get(outcome.tag) ?? [];
201
- if (references.length > 0 &&
202
- referenceCommentMode !== "off" &&
203
- scmClient.createReleaseReferenceComments) {
204
- await scmClient.createReleaseReferenceComments({
211
+ if (references.length > 0) {
212
+ referenceReleases.push({
213
+ name: resolveTargetPackageName(cwd, loaded.config, target.path),
214
+ tag: outcome.tag,
205
215
  version: target.version,
206
216
  releaseUrl: outcome.url,
207
217
  references,
208
- mode: referenceCommentMode,
209
- }, {
210
- cwd,
211
- logger: options.logger,
212
218
  });
213
219
  }
214
220
  }
221
+ if (referenceReleases.length > 0 &&
222
+ referenceCommentMode !== "off" &&
223
+ scmClient.createReleaseReferenceComments) {
224
+ await scmClient.createReleaseReferenceComments({
225
+ releases: referenceReleases,
226
+ mode: referenceCommentMode,
227
+ }, {
228
+ cwd,
229
+ logger: options.logger,
230
+ });
231
+ }
215
232
  const published = releases.map((outcome) => `${outcome.tag}: ${outcome.url} (tag=${outcome.tagStatus}, metadata=${outcome.metadataStatus})`);
216
233
  return {
217
234
  action: "release-published",
@@ -86,6 +86,42 @@ async function ensureLabels(octokit, repo, pullNumber, labels, branchContext) {
86
86
  throw new Error(`Failed applying labels to pull request #${pullNumber}: [${repoRef(repo)} base=${branchContext.baseBranch} head=${branchContext.headBranch}] ${message}`);
87
87
  }
88
88
  }
89
+ function groupReleasesByIssue(releases) {
90
+ const byIssue = new Map();
91
+ for (const release of releases) {
92
+ for (const reference of release.references) {
93
+ const bucket = byIssue.get(reference) ?? [];
94
+ if (!bucket.some((entry) => entry.tag === release.tag)) {
95
+ bucket.push(release);
96
+ }
97
+ byIssue.set(reference, bucket);
98
+ }
99
+ }
100
+ for (const bucket of byIssue.values()) {
101
+ bucket.sort((a, b) => a.tag.localeCompare(b.tag));
102
+ }
103
+ return new Map([...byIssue.entries()].sort(([a], [b]) => a - b));
104
+ }
105
+ function renderReleaseLink(release) {
106
+ if (release.name) {
107
+ return `[\`${release.name}\` v${release.version}](${release.releaseUrl})`;
108
+ }
109
+ return `[version ${release.version}](${release.releaseUrl})`;
110
+ }
111
+ function renderReleaseReferenceCommentBody(releases) {
112
+ const footer = "Released by [Versionary](https://github.com/jolars/versionary).";
113
+ if (releases.length === 1) {
114
+ const release = releases[0];
115
+ if (!release) {
116
+ throw new Error("Expected at least one release for comment body.");
117
+ }
118
+ return `This is included in ${renderReleaseLink(release)}. :tada:\n\n${footer}`;
119
+ }
120
+ const bullets = releases
121
+ .map((release) => `- ${renderReleaseLink(release)}`)
122
+ .join("\n");
123
+ return `This is included in the following releases: :tada:\n\n${bullets}\n\n${footer}`;
124
+ }
89
125
  function createGitHubPlugin() {
90
126
  return {
91
127
  name: "github",
@@ -275,11 +311,11 @@ function createGitHubPlugin() {
275
311
  async createReleaseReferenceComments(input, context) {
276
312
  const repo = getRepoFromEnv();
277
313
  const octokit = new rest_1.Octokit({ auth: getGitHubToken() });
278
- const uniqueReferences = [...new Set(input.references)].sort((a, b) => a - b);
279
- const commented = [];
280
314
  const mode = input.mode ?? "best-effort";
281
- for (const reference of uniqueReferences) {
282
- const body = `This is included in [version ${input.version}](${input.releaseUrl}). :tada:\n\nReleased by [Versionary](https://github.com/jolars/versionary).`;
315
+ const releasesByIssue = groupReleasesByIssue(input.releases);
316
+ const commented = [];
317
+ for (const [reference, releases] of releasesByIssue) {
318
+ const body = renderReleaseReferenceCommentBody(releases);
283
319
  try {
284
320
  await octokit.issues.createComment({
285
321
  owner: repo.owner,
@@ -41,10 +41,15 @@ export interface ScmReleaseMetadataResult {
41
41
  url: string;
42
42
  status?: "created" | "exists";
43
43
  }
44
- export interface ScmReleaseReferenceCommentsInput {
44
+ export interface ScmReleaseReferenceCommentsRelease {
45
+ name?: string;
46
+ tag: string;
45
47
  version: string;
46
48
  releaseUrl: string;
47
49
  references: number[];
50
+ }
51
+ export interface ScmReleaseReferenceCommentsInput {
52
+ releases: ScmReleaseReferenceCommentsRelease[];
48
53
  mode?: "best-effort" | "strict";
49
54
  }
50
55
  export interface ScmReleaseReferenceCommentsResult {
@@ -1,5 +1,6 @@
1
1
  import type { VersionaryConfig, VersionaryPackage } from "../types/config.js";
2
2
  import type { VersionStrategy } from "./types.js";
3
+ export declare function resolveReleaseName(cwd: string, packagePath: string, packageConfig: VersionaryPackage, strategy: VersionStrategy, strategyConfig: VersionaryConfig): string;
3
4
  export declare function resolvePackageStrategyContext(rootConfig: VersionaryConfig, packagePath: string, packageConfig: VersionaryPackage): {
4
5
  strategy: VersionStrategy;
5
6
  config: VersionaryConfig;
@@ -3,9 +3,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveReleaseName = resolveReleaseName;
6
7
  exports.resolvePackageStrategyContext = resolvePackageStrategyContext;
7
8
  const node_path_1 = __importDefault(require("node:path"));
8
9
  const resolve_js_1 = require("./resolve.js");
10
+ function resolveReleaseName(cwd, packagePath, packageConfig, strategy, strategyConfig) {
11
+ const configuredName = packageConfig["package-name"]?.trim();
12
+ if (configuredName) {
13
+ return configuredName;
14
+ }
15
+ return strategy.readPackageName?.(cwd, strategyConfig) ?? packagePath;
16
+ }
9
17
  function withVersionFile(config, versionFile) {
10
18
  return {
11
19
  ...config,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "versionary",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "Automatic release framework based on conventional commits and semantic versioning",
5
5
  "keywords": [
6
6
  "releasing",