@releasekit/publish 0.4.1 → 0.6.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.
@@ -93,6 +93,9 @@ var EXIT_CODES = {
93
93
  VERSION_ERROR: 8,
94
94
  PUBLISH_ERROR: 9
95
95
  };
96
+ function sanitizePackageName(name) {
97
+ return name.startsWith("@") ? name.slice(1).replace(/\//g, "-") : name;
98
+ }
96
99
 
97
100
  // ../config/dist/index.js
98
101
  import * as fs2 from "fs";
@@ -230,7 +233,13 @@ var GitHubReleaseConfigSchema = z.object({
230
233
  * - 'generated': Use GitHub's auto-generated notes.
231
234
  * - 'none': No body.
232
235
  */
233
- body: z.enum(["auto", "releaseNotes", "changelog", "generated", "none"]).default("auto")
236
+ body: z.enum(["auto", "releaseNotes", "changelog", "generated", "none"]).default("auto"),
237
+ /**
238
+ * Template string for the GitHub release title when a package name is resolved.
239
+ * Available variables: ${packageName} (original scoped name), ${version} (e.g. "v1.0.0").
240
+ * Version-only tags (e.g. "v1.0.0") always use the tag as-is.
241
+ */
242
+ titleTemplate: z.string().default("${packageName}: ${version}")
234
243
  });
235
244
  var VerifyRegistryConfigSchema = z.object({
236
245
  enabled: z.boolean().default(true),
@@ -274,7 +283,8 @@ var PublishConfigSchema = z.object({
274
283
  draft: true,
275
284
  perPackage: true,
276
285
  prerelease: "auto",
277
- body: "auto"
286
+ body: "auto",
287
+ titleTemplate: "${packageName}: ${version}"
278
288
  }),
279
289
  verify: VerifyConfigSchema.default({
280
290
  npm: {
@@ -547,7 +557,8 @@ function getDefaultConfig() {
547
557
  draft: true,
548
558
  perPackage: true,
549
559
  prerelease: "auto",
550
- body: "auto"
560
+ body: "auto",
561
+ titleTemplate: "${packageName}: ${version}"
551
562
  },
552
563
  verify: {
553
564
  npm: {
@@ -597,7 +608,8 @@ function toPublishConfig(config) {
597
608
  draft: config.githubRelease?.draft ?? defaults.githubRelease.draft,
598
609
  perPackage: config.githubRelease?.perPackage ?? defaults.githubRelease.perPackage,
599
610
  prerelease: config.githubRelease?.prerelease ?? defaults.githubRelease.prerelease,
600
- body: config.githubRelease?.body ?? defaults.githubRelease.body
611
+ body: config.githubRelease?.body ?? defaults.githubRelease.body,
612
+ titleTemplate: config.githubRelease?.titleTemplate ?? defaults.githubRelease.titleTemplate
601
613
  },
602
614
  verify: {
603
615
  npm: {
@@ -1264,9 +1276,9 @@ function resolveNotes(bodySource, tag, changelogs, releaseNotesEnabled, pipeline
1264
1276
  return { useGithubNotes: true };
1265
1277
  }
1266
1278
  if (bodySource === "changelog") {
1267
- const packageBody2 = formatChangelogForTag(tag, changelogs);
1268
- if (packageBody2) {
1269
- return { body: packageBody2, useGithubNotes: false };
1279
+ const packageBody = formatChangelogForTag(tag, changelogs);
1280
+ if (packageBody) {
1281
+ return { body: packageBody, useGithubNotes: false };
1270
1282
  }
1271
1283
  warn("No changelog found for tag, falling back to GitHub auto-notes");
1272
1284
  return { useGithubNotes: true };
@@ -1274,30 +1286,45 @@ function resolveNotes(bodySource, tag, changelogs, releaseNotesEnabled, pipeline
1274
1286
  if (releaseNotesEnabled && pipelineNotes) {
1275
1287
  const body = findNotesForTag(tag, pipelineNotes);
1276
1288
  if (body) return { body, useGithubNotes: false };
1277
- }
1278
- const packageBody = formatChangelogForTag(tag, changelogs);
1279
- if (packageBody) {
1280
- return { body: packageBody, useGithubNotes: false };
1289
+ warn(`Release notes configured but no content found for tag ${tag}, falling back to GitHub auto-generated notes`);
1281
1290
  }
1282
1291
  return { useGithubNotes: true };
1283
1292
  }
1284
1293
  function isVersionOnlyTag(tag) {
1285
1294
  return /^v?\d+\.\d+\.\d+/.test(tag);
1286
1295
  }
1287
- function getTitleFromTag(tag) {
1288
- const atIndex = tag.lastIndexOf("@");
1289
- if (atIndex === -1) {
1290
- return tag;
1296
+ function resolveTagPackage(tag, packageNames) {
1297
+ const sorted = [...packageNames].sort((a, b) => sanitizePackageName(b).length - sanitizePackageName(a).length);
1298
+ for (const packageName of sorted) {
1299
+ const atPrefix = `${packageName}@`;
1300
+ if (tag.startsWith(atPrefix)) {
1301
+ return { packageName, version: tag.slice(atPrefix.length) };
1302
+ }
1303
+ const dashPrefix = `${sanitizePackageName(packageName)}-`;
1304
+ if (tag.startsWith(dashPrefix)) {
1305
+ const versionPart = tag.slice(dashPrefix.length);
1306
+ if (/^v?\d/.test(versionPart)) {
1307
+ return { packageName, version: versionPart };
1308
+ }
1309
+ }
1291
1310
  }
1292
- const packageName = tag.slice(0, atIndex);
1293
- const version = tag.slice(atIndex + 1);
1294
- return `${packageName} @ ${version}`;
1311
+ return null;
1312
+ }
1313
+ function getTitleFromTag(tag, packageNames, titleTemplate) {
1314
+ const applyTemplate = (packageName, version) => titleTemplate.replace(/\$\{packageName\}/g, packageName).replace(/\$\{version\}/g, version);
1315
+ if (packageNames.length > 0) {
1316
+ const resolved = resolveTagPackage(tag, packageNames);
1317
+ if (resolved) return applyTemplate(resolved.packageName, resolved.version);
1318
+ }
1319
+ const atIndex = tag.lastIndexOf("@");
1320
+ if (atIndex === -1) return tag;
1321
+ return applyTemplate(tag.slice(0, atIndex), tag.slice(atIndex + 1));
1295
1322
  }
1296
1323
  function findNotesForTag(tag, notes) {
1297
- for (const [packageName, body] of Object.entries(notes)) {
1298
- if (tag.startsWith(`${packageName}@`) && body.trim()) {
1299
- return body;
1300
- }
1324
+ const resolved = resolveTagPackage(tag, Object.keys(notes));
1325
+ if (resolved) {
1326
+ const body = notes[resolved.packageName];
1327
+ if (body?.trim()) return body;
1301
1328
  }
1302
1329
  const entries = Object.values(notes).filter((b) => b.trim());
1303
1330
  if (entries.length === 1 && isVersionOnlyTag(tag)) return entries[0];
@@ -1305,8 +1332,16 @@ function findNotesForTag(tag, notes) {
1305
1332
  }
1306
1333
  function formatChangelogForTag(tag, changelogs) {
1307
1334
  if (changelogs.length === 0) return void 0;
1308
- const changelog = changelogs.find((c) => tag.startsWith(`${c.packageName}@`));
1309
- const target = changelog ?? (changelogs.length === 1 && isVersionOnlyTag(tag) ? changelogs[0] : void 0);
1335
+ const resolved = resolveTagPackage(
1336
+ tag,
1337
+ changelogs.map((c) => c.packageName)
1338
+ );
1339
+ let target;
1340
+ if (resolved) {
1341
+ target = changelogs.find((c) => c.packageName === resolved.packageName);
1342
+ } else if (changelogs.length === 1 && isVersionOnlyTag(tag)) {
1343
+ target = changelogs[0];
1344
+ }
1310
1345
  if (!target || target.entries.length === 0) return void 0;
1311
1346
  const lines = [];
1312
1347
  for (const entry of target.entries) {
@@ -1343,7 +1378,11 @@ async function runGithubReleaseStage(ctx) {
1343
1378
  success: false
1344
1379
  };
1345
1380
  const ghArgs = ["release", "create", tag];
1346
- ghArgs.push("--title", getTitleFromTag(tag));
1381
+ const titlePackageNames = [
1382
+ ...ctx.input.changelogs.map((c) => c.packageName),
1383
+ ...ctx.releaseNotes ? Object.keys(ctx.releaseNotes) : []
1384
+ ];
1385
+ ghArgs.push("--title", getTitleFromTag(tag, [...new Set(titlePackageNames)], config.githubRelease.titleTemplate));
1347
1386
  if (config.githubRelease.draft) {
1348
1387
  ghArgs.push("--draft");
1349
1388
  }
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  runPipeline,
10
10
  setJsonMode,
11
11
  setLogLevel
12
- } from "./chunk-ZMMZ7S6G.js";
12
+ } from "./chunk-53BGK2JF.js";
13
13
 
14
14
  // src/cli.ts
15
15
  import { realpathSync } from "fs";
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  parseInput,
17
17
  runPipeline,
18
18
  updateCargoVersion
19
- } from "./chunk-ZMMZ7S6G.js";
19
+ } from "./chunk-53BGK2JF.js";
20
20
  export {
21
21
  BasePublishError,
22
22
  PipelineError,
@@ -140,6 +140,29 @@ Set `"draft": false` to publish releases immediately.
140
140
 
141
141
  ---
142
142
 
143
+ ## Release Title (`titleTemplate`)
144
+
145
+ Controls the title of each GitHub Release when a package name is resolved from the tag.
146
+
147
+ ```json
148
+ {
149
+ "publish": {
150
+ "githubRelease": {
151
+ "titleTemplate": "${packageName}: ${version}"
152
+ }
153
+ }
154
+ }
155
+ ```
156
+
157
+ | Variable | Value |
158
+ |----------|-------|
159
+ | `${packageName}` | Original scoped package name, e.g. `@scope/pkg` |
160
+ | `${version}` | Version string extracted from the tag, e.g. `v1.0.0` |
161
+
162
+ The default produces titles like `@releasekit/version: v1.0.0`. Version-only tags (e.g. `v1.0.0` with no package prefix) always use the tag string directly.
163
+
164
+ ---
165
+
143
166
  ## Per-Package Releases
144
167
 
145
168
  In a monorepo, a separate GitHub Release is created for each published package by default.
@@ -168,7 +191,8 @@ Set `"perPackage": false` to create a single release for the entire repo.
168
191
  "draft": true,
169
192
  "prerelease": "auto",
170
193
  "perPackage": true,
171
- "body": "auto"
194
+ "body": "auto",
195
+ "titleTemplate": "${packageName}: ${version}"
172
196
  }
173
197
  }
174
198
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@releasekit/publish",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "Publish packages to npm and crates.io with git tagging and GitHub releases",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",
@@ -63,8 +63,8 @@
63
63
  "tsup": "^8.5.1",
64
64
  "typescript": "^5.9.3",
65
65
  "vitest": "^4.1.0",
66
- "@releasekit/core": "0.0.0",
67
- "@releasekit/config": "0.0.0"
66
+ "@releasekit/config": "0.0.0",
67
+ "@releasekit/core": "0.0.0"
68
68
  },
69
69
  "engines": {
70
70
  "node": ">=20"