@releasekit/publish 0.2.0 → 0.3.0-next.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.
@@ -30,9 +30,9 @@ function getDefaultConfig() {
30
30
  githubRelease: {
31
31
  enabled: true,
32
32
  draft: true,
33
- generateNotes: true,
34
- perPackage: false,
35
- prerelease: "auto"
33
+ perPackage: true,
34
+ prerelease: "auto",
35
+ releaseNotes: "auto"
36
36
  },
37
37
  verify: {
38
38
  npm: {
@@ -80,10 +80,9 @@ function toPublishConfig(config) {
80
80
  githubRelease: {
81
81
  enabled: config.githubRelease?.enabled ?? defaults.githubRelease.enabled,
82
82
  draft: config.githubRelease?.draft ?? defaults.githubRelease.draft,
83
- generateNotes: config.githubRelease?.generateNotes ?? defaults.githubRelease.generateNotes,
84
83
  perPackage: config.githubRelease?.perPackage ?? defaults.githubRelease.perPackage,
85
84
  prerelease: config.githubRelease?.prerelease ?? defaults.githubRelease.prerelease,
86
- notesFile: config.githubRelease?.notesFile
85
+ releaseNotes: config.githubRelease?.releaseNotes ?? defaults.githubRelease.releaseNotes
87
86
  },
88
87
  verify: {
89
88
  npm: {
@@ -584,6 +583,9 @@ async function runGitCommitStage(ctx) {
584
583
  return;
585
584
  }
586
585
  const filePaths = input.updates.map((u) => path3.resolve(cwd, u.filePath));
586
+ if (ctx.additionalFiles) {
587
+ filePaths.push(...ctx.additionalFiles.map((f) => path3.resolve(cwd, f)));
588
+ }
587
589
  if (filePaths.length === 0) {
588
590
  info2("No files to commit");
589
591
  return;
@@ -716,6 +718,62 @@ async function runGitPushStage(ctx) {
716
718
  // src/stages/github-release.ts
717
719
  import * as fs4 from "fs";
718
720
  import { debug as debug3, info as info4, success as success4, warn as warn2 } from "@releasekit/core";
721
+ function resolveNotes(notesSetting, tag, changelogs, pipelineNotes) {
722
+ if (notesSetting === "none") {
723
+ return { useGithubNotes: false };
724
+ }
725
+ if (notesSetting === "github") {
726
+ return { useGithubNotes: true };
727
+ }
728
+ if (notesSetting !== "auto") {
729
+ const body = readFileIfExists(notesSetting);
730
+ if (body) return { body, useGithubNotes: false };
731
+ debug3(`Notes file not found: ${notesSetting}, falling back to GitHub auto-notes`);
732
+ return { useGithubNotes: true };
733
+ }
734
+ if (pipelineNotes) {
735
+ const body = findNotesForTag(tag, pipelineNotes);
736
+ if (body) return { body, useGithubNotes: false };
737
+ }
738
+ const packageBody = formatChangelogForTag(tag, changelogs);
739
+ if (packageBody) {
740
+ return { body: packageBody, useGithubNotes: false };
741
+ }
742
+ return { useGithubNotes: true };
743
+ }
744
+ function isVersionOnlyTag(tag) {
745
+ return /^v?\d+\.\d+\.\d+/.test(tag);
746
+ }
747
+ function findNotesForTag(tag, notes) {
748
+ for (const [packageName, body] of Object.entries(notes)) {
749
+ if (tag.startsWith(`${packageName}@`) && body.trim()) {
750
+ return body;
751
+ }
752
+ }
753
+ const entries = Object.values(notes).filter((b) => b.trim());
754
+ if (entries.length === 1 && isVersionOnlyTag(tag)) return entries[0];
755
+ return void 0;
756
+ }
757
+ function readFileIfExists(filePath) {
758
+ try {
759
+ const content = fs4.readFileSync(filePath, "utf-8").trim();
760
+ return content || void 0;
761
+ } catch {
762
+ return void 0;
763
+ }
764
+ }
765
+ function formatChangelogForTag(tag, changelogs) {
766
+ if (changelogs.length === 0) return void 0;
767
+ const changelog = changelogs.find((c) => tag.startsWith(`${c.packageName}@`));
768
+ const target = changelog ?? (changelogs.length === 1 && isVersionOnlyTag(tag) ? changelogs[0] : void 0);
769
+ if (!target || target.entries.length === 0) return void 0;
770
+ const lines = [];
771
+ for (const entry of target.entries) {
772
+ const scope = entry.scope ? `**${entry.scope}:** ` : "";
773
+ lines.push(`- ${scope}${entry.description}`);
774
+ }
775
+ return lines.join("\n");
776
+ }
719
777
  async function runGithubReleaseStage(ctx) {
720
778
  const { config, cliOptions, output } = ctx;
721
779
  const dryRun = cliOptions.dryRun;
@@ -728,14 +786,6 @@ async function runGithubReleaseStage(ctx) {
728
786
  info4("No tags available for GitHub release");
729
787
  return;
730
788
  }
731
- let notesBody;
732
- if (config.githubRelease.notesFile) {
733
- try {
734
- notesBody = fs4.readFileSync(config.githubRelease.notesFile, "utf-8");
735
- } catch {
736
- debug3(`Could not read notes file: ${config.githubRelease.notesFile}`);
737
- }
738
- }
739
789
  const firstTag = tags[0];
740
790
  if (!firstTag) return;
741
791
  const tagsToRelease = config.githubRelease.perPackage ? tags : [firstTag];
@@ -758,9 +808,15 @@ async function runGithubReleaseStage(ctx) {
758
808
  if (isPreRel) {
759
809
  ghArgs.push("--prerelease");
760
810
  }
761
- if (notesBody) {
762
- ghArgs.push("--notes", notesBody);
763
- } else if (config.githubRelease.generateNotes) {
811
+ const { body, useGithubNotes } = resolveNotes(
812
+ config.githubRelease.releaseNotes,
813
+ tag,
814
+ ctx.input.changelogs,
815
+ ctx.releaseNotes
816
+ );
817
+ if (body) {
818
+ ghArgs.push("--notes", body);
819
+ } else if (useGithubNotes) {
764
820
  ghArgs.push("--generate-notes");
765
821
  }
766
822
  try {
@@ -1136,6 +1192,8 @@ async function runPipeline(input, config, options) {
1136
1192
  cliOptions: options,
1137
1193
  packageManager: detectPackageManager(cwd),
1138
1194
  cwd,
1195
+ releaseNotes: options.releaseNotes,
1196
+ additionalFiles: options.additionalFiles,
1139
1197
  output: {
1140
1198
  dryRun: options.dryRun,
1141
1199
  git: { committed: false, tags: [], pushed: false },
package/dist/cli.cjs CHANGED
@@ -59,9 +59,9 @@ function getDefaultConfig() {
59
59
  githubRelease: {
60
60
  enabled: true,
61
61
  draft: true,
62
- generateNotes: true,
63
- perPackage: false,
64
- prerelease: "auto"
62
+ perPackage: true,
63
+ prerelease: "auto",
64
+ releaseNotes: "auto"
65
65
  },
66
66
  verify: {
67
67
  npm: {
@@ -109,10 +109,9 @@ function toPublishConfig(config) {
109
109
  githubRelease: {
110
110
  enabled: config.githubRelease?.enabled ?? defaults.githubRelease.enabled,
111
111
  draft: config.githubRelease?.draft ?? defaults.githubRelease.draft,
112
- generateNotes: config.githubRelease?.generateNotes ?? defaults.githubRelease.generateNotes,
113
112
  perPackage: config.githubRelease?.perPackage ?? defaults.githubRelease.perPackage,
114
113
  prerelease: config.githubRelease?.prerelease ?? defaults.githubRelease.prerelease,
115
- notesFile: config.githubRelease?.notesFile
114
+ releaseNotes: config.githubRelease?.releaseNotes ?? defaults.githubRelease.releaseNotes
116
115
  },
117
116
  verify: {
118
117
  npm: {
@@ -551,6 +550,9 @@ async function runGitCommitStage(ctx) {
551
550
  return;
552
551
  }
553
552
  const filePaths = input.updates.map((u) => path2.resolve(cwd, u.filePath));
553
+ if (ctx.additionalFiles) {
554
+ filePaths.push(...ctx.additionalFiles.map((f) => path2.resolve(cwd, f)));
555
+ }
554
556
  if (filePaths.length === 0) {
555
557
  (0, import_core4.info)("No files to commit");
556
558
  return;
@@ -699,6 +701,62 @@ function getDistTag(version, defaultTag = "latest") {
699
701
  }
700
702
 
701
703
  // src/stages/github-release.ts
704
+ function resolveNotes(notesSetting, tag, changelogs, pipelineNotes) {
705
+ if (notesSetting === "none") {
706
+ return { useGithubNotes: false };
707
+ }
708
+ if (notesSetting === "github") {
709
+ return { useGithubNotes: true };
710
+ }
711
+ if (notesSetting !== "auto") {
712
+ const body = readFileIfExists(notesSetting);
713
+ if (body) return { body, useGithubNotes: false };
714
+ (0, import_core6.debug)(`Notes file not found: ${notesSetting}, falling back to GitHub auto-notes`);
715
+ return { useGithubNotes: true };
716
+ }
717
+ if (pipelineNotes) {
718
+ const body = findNotesForTag(tag, pipelineNotes);
719
+ if (body) return { body, useGithubNotes: false };
720
+ }
721
+ const packageBody = formatChangelogForTag(tag, changelogs);
722
+ if (packageBody) {
723
+ return { body: packageBody, useGithubNotes: false };
724
+ }
725
+ return { useGithubNotes: true };
726
+ }
727
+ function isVersionOnlyTag(tag) {
728
+ return /^v?\d+\.\d+\.\d+/.test(tag);
729
+ }
730
+ function findNotesForTag(tag, notes) {
731
+ for (const [packageName, body] of Object.entries(notes)) {
732
+ if (tag.startsWith(`${packageName}@`) && body.trim()) {
733
+ return body;
734
+ }
735
+ }
736
+ const entries = Object.values(notes).filter((b) => b.trim());
737
+ if (entries.length === 1 && isVersionOnlyTag(tag)) return entries[0];
738
+ return void 0;
739
+ }
740
+ function readFileIfExists(filePath) {
741
+ try {
742
+ const content = fs3.readFileSync(filePath, "utf-8").trim();
743
+ return content || void 0;
744
+ } catch {
745
+ return void 0;
746
+ }
747
+ }
748
+ function formatChangelogForTag(tag, changelogs) {
749
+ if (changelogs.length === 0) return void 0;
750
+ const changelog = changelogs.find((c) => tag.startsWith(`${c.packageName}@`));
751
+ const target = changelog ?? (changelogs.length === 1 && isVersionOnlyTag(tag) ? changelogs[0] : void 0);
752
+ if (!target || target.entries.length === 0) return void 0;
753
+ const lines = [];
754
+ for (const entry of target.entries) {
755
+ const scope = entry.scope ? `**${entry.scope}:** ` : "";
756
+ lines.push(`- ${scope}${entry.description}`);
757
+ }
758
+ return lines.join("\n");
759
+ }
702
760
  async function runGithubReleaseStage(ctx) {
703
761
  const { config, cliOptions, output } = ctx;
704
762
  const dryRun = cliOptions.dryRun;
@@ -711,14 +769,6 @@ async function runGithubReleaseStage(ctx) {
711
769
  (0, import_core6.info)("No tags available for GitHub release");
712
770
  return;
713
771
  }
714
- let notesBody;
715
- if (config.githubRelease.notesFile) {
716
- try {
717
- notesBody = fs3.readFileSync(config.githubRelease.notesFile, "utf-8");
718
- } catch {
719
- (0, import_core6.debug)(`Could not read notes file: ${config.githubRelease.notesFile}`);
720
- }
721
- }
722
772
  const firstTag = tags[0];
723
773
  if (!firstTag) return;
724
774
  const tagsToRelease = config.githubRelease.perPackage ? tags : [firstTag];
@@ -741,9 +791,15 @@ async function runGithubReleaseStage(ctx) {
741
791
  if (isPreRel) {
742
792
  ghArgs.push("--prerelease");
743
793
  }
744
- if (notesBody) {
745
- ghArgs.push("--notes", notesBody);
746
- } else if (config.githubRelease.generateNotes) {
794
+ const { body, useGithubNotes } = resolveNotes(
795
+ config.githubRelease.releaseNotes,
796
+ tag,
797
+ ctx.input.changelogs,
798
+ ctx.releaseNotes
799
+ );
800
+ if (body) {
801
+ ghArgs.push("--notes", body);
802
+ } else if (useGithubNotes) {
747
803
  ghArgs.push("--generate-notes");
748
804
  }
749
805
  try {
@@ -1148,6 +1204,8 @@ async function runPipeline(input, config, options) {
1148
1204
  cliOptions: options,
1149
1205
  packageManager: detectPackageManager(cwd),
1150
1206
  cwd,
1207
+ releaseNotes: options.releaseNotes,
1208
+ additionalFiles: options.additionalFiles,
1151
1209
  output: {
1152
1210
  dryRun: options.dryRun,
1153
1211
  git: { committed: false, tags: [], pushed: false },
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  loadConfig,
6
6
  parseInput,
7
7
  runPipeline
8
- } from "./chunk-H7AKSLZP.js";
8
+ } from "./chunk-GOBII36Q.js";
9
9
 
10
10
  // src/cli.ts
11
11
  import { EXIT_CODES, setJsonMode, setLogLevel } from "@releasekit/core";
package/dist/index.cjs CHANGED
@@ -82,9 +82,9 @@ function getDefaultConfig() {
82
82
  githubRelease: {
83
83
  enabled: true,
84
84
  draft: true,
85
- generateNotes: true,
86
- perPackage: false,
87
- prerelease: "auto"
85
+ perPackage: true,
86
+ prerelease: "auto",
87
+ releaseNotes: "auto"
88
88
  },
89
89
  verify: {
90
90
  npm: {
@@ -132,10 +132,9 @@ function toPublishConfig(config) {
132
132
  githubRelease: {
133
133
  enabled: config.githubRelease?.enabled ?? defaults.githubRelease.enabled,
134
134
  draft: config.githubRelease?.draft ?? defaults.githubRelease.draft,
135
- generateNotes: config.githubRelease?.generateNotes ?? defaults.githubRelease.generateNotes,
136
135
  perPackage: config.githubRelease?.perPackage ?? defaults.githubRelease.perPackage,
137
136
  prerelease: config.githubRelease?.prerelease ?? defaults.githubRelease.prerelease,
138
- notesFile: config.githubRelease?.notesFile
137
+ releaseNotes: config.githubRelease?.releaseNotes ?? defaults.githubRelease.releaseNotes
139
138
  },
140
139
  verify: {
141
140
  npm: {
@@ -595,6 +594,9 @@ async function runGitCommitStage(ctx) {
595
594
  return;
596
595
  }
597
596
  const filePaths = input.updates.map((u) => path2.resolve(cwd, u.filePath));
597
+ if (ctx.additionalFiles) {
598
+ filePaths.push(...ctx.additionalFiles.map((f) => path2.resolve(cwd, f)));
599
+ }
598
600
  if (filePaths.length === 0) {
599
601
  (0, import_core4.info)("No files to commit");
600
602
  return;
@@ -743,6 +745,62 @@ function getDistTag(version, defaultTag = "latest") {
743
745
  }
744
746
 
745
747
  // src/stages/github-release.ts
748
+ function resolveNotes(notesSetting, tag, changelogs, pipelineNotes) {
749
+ if (notesSetting === "none") {
750
+ return { useGithubNotes: false };
751
+ }
752
+ if (notesSetting === "github") {
753
+ return { useGithubNotes: true };
754
+ }
755
+ if (notesSetting !== "auto") {
756
+ const body = readFileIfExists(notesSetting);
757
+ if (body) return { body, useGithubNotes: false };
758
+ (0, import_core6.debug)(`Notes file not found: ${notesSetting}, falling back to GitHub auto-notes`);
759
+ return { useGithubNotes: true };
760
+ }
761
+ if (pipelineNotes) {
762
+ const body = findNotesForTag(tag, pipelineNotes);
763
+ if (body) return { body, useGithubNotes: false };
764
+ }
765
+ const packageBody = formatChangelogForTag(tag, changelogs);
766
+ if (packageBody) {
767
+ return { body: packageBody, useGithubNotes: false };
768
+ }
769
+ return { useGithubNotes: true };
770
+ }
771
+ function isVersionOnlyTag(tag) {
772
+ return /^v?\d+\.\d+\.\d+/.test(tag);
773
+ }
774
+ function findNotesForTag(tag, notes) {
775
+ for (const [packageName, body] of Object.entries(notes)) {
776
+ if (tag.startsWith(`${packageName}@`) && body.trim()) {
777
+ return body;
778
+ }
779
+ }
780
+ const entries = Object.values(notes).filter((b) => b.trim());
781
+ if (entries.length === 1 && isVersionOnlyTag(tag)) return entries[0];
782
+ return void 0;
783
+ }
784
+ function readFileIfExists(filePath) {
785
+ try {
786
+ const content = fs3.readFileSync(filePath, "utf-8").trim();
787
+ return content || void 0;
788
+ } catch {
789
+ return void 0;
790
+ }
791
+ }
792
+ function formatChangelogForTag(tag, changelogs) {
793
+ if (changelogs.length === 0) return void 0;
794
+ const changelog = changelogs.find((c) => tag.startsWith(`${c.packageName}@`));
795
+ const target = changelog ?? (changelogs.length === 1 && isVersionOnlyTag(tag) ? changelogs[0] : void 0);
796
+ if (!target || target.entries.length === 0) return void 0;
797
+ const lines = [];
798
+ for (const entry of target.entries) {
799
+ const scope = entry.scope ? `**${entry.scope}:** ` : "";
800
+ lines.push(`- ${scope}${entry.description}`);
801
+ }
802
+ return lines.join("\n");
803
+ }
746
804
  async function runGithubReleaseStage(ctx) {
747
805
  const { config, cliOptions, output } = ctx;
748
806
  const dryRun = cliOptions.dryRun;
@@ -755,14 +813,6 @@ async function runGithubReleaseStage(ctx) {
755
813
  (0, import_core6.info)("No tags available for GitHub release");
756
814
  return;
757
815
  }
758
- let notesBody;
759
- if (config.githubRelease.notesFile) {
760
- try {
761
- notesBody = fs3.readFileSync(config.githubRelease.notesFile, "utf-8");
762
- } catch {
763
- (0, import_core6.debug)(`Could not read notes file: ${config.githubRelease.notesFile}`);
764
- }
765
- }
766
816
  const firstTag = tags[0];
767
817
  if (!firstTag) return;
768
818
  const tagsToRelease = config.githubRelease.perPackage ? tags : [firstTag];
@@ -785,9 +835,15 @@ async function runGithubReleaseStage(ctx) {
785
835
  if (isPreRel) {
786
836
  ghArgs.push("--prerelease");
787
837
  }
788
- if (notesBody) {
789
- ghArgs.push("--notes", notesBody);
790
- } else if (config.githubRelease.generateNotes) {
838
+ const { body, useGithubNotes } = resolveNotes(
839
+ config.githubRelease.releaseNotes,
840
+ tag,
841
+ ctx.input.changelogs,
842
+ ctx.releaseNotes
843
+ );
844
+ if (body) {
845
+ ghArgs.push("--notes", body);
846
+ } else if (useGithubNotes) {
791
847
  ghArgs.push("--generate-notes");
792
848
  }
793
849
  try {
@@ -1192,6 +1248,8 @@ async function runPipeline(input, config, options) {
1192
1248
  cliOptions: options,
1193
1249
  packageManager: detectPackageManager(cwd),
1194
1250
  cwd,
1251
+ releaseNotes: options.releaseNotes,
1252
+ additionalFiles: options.additionalFiles,
1195
1253
  output: {
1196
1254
  dryRun: options.dryRun,
1197
1255
  git: { committed: false, tags: [], pushed: false },
package/dist/index.d.cts CHANGED
@@ -31,10 +31,10 @@ interface GitConfig {
31
31
  interface GitHubReleaseConfig {
32
32
  enabled: boolean;
33
33
  draft: boolean;
34
- generateNotes: boolean;
35
34
  perPackage: boolean;
36
35
  prerelease: 'auto' | boolean;
37
- notesFile?: string;
36
+ /** 'auto' | 'github' | 'none' | file path */
37
+ releaseNotes: string;
38
38
  }
39
39
  interface VerifyRegistryConfig {
40
40
  enabled: boolean;
@@ -66,6 +66,10 @@ interface PublishCliOptions {
66
66
  skipVerification: boolean;
67
67
  json: boolean;
68
68
  verbose: boolean;
69
+ /** Per-package release notes keyed by package name, from the notes pipeline. */
70
+ releaseNotes?: Record<string, string>;
71
+ /** Additional files to stage in the git commit (e.g., changelog files from the notes step). */
72
+ additionalFiles?: string[];
69
73
  }
70
74
  interface PublishResult {
71
75
  packageName: string;
@@ -111,6 +115,10 @@ interface PipelineContext {
111
115
  packageManager: PackageManager;
112
116
  output: PublishOutput;
113
117
  cwd: string;
118
+ /** Per-package release notes keyed by package name, passed from the notes pipeline. */
119
+ releaseNotes?: Record<string, string>;
120
+ /** Additional files to stage in the git commit (e.g., changelog files from the notes step). */
121
+ additionalFiles?: string[];
114
122
  }
115
123
 
116
124
  declare function loadConfig(options?: LoadOptions): PublishConfig;
package/dist/index.d.ts CHANGED
@@ -31,10 +31,10 @@ interface GitConfig {
31
31
  interface GitHubReleaseConfig {
32
32
  enabled: boolean;
33
33
  draft: boolean;
34
- generateNotes: boolean;
35
34
  perPackage: boolean;
36
35
  prerelease: 'auto' | boolean;
37
- notesFile?: string;
36
+ /** 'auto' | 'github' | 'none' | file path */
37
+ releaseNotes: string;
38
38
  }
39
39
  interface VerifyRegistryConfig {
40
40
  enabled: boolean;
@@ -66,6 +66,10 @@ interface PublishCliOptions {
66
66
  skipVerification: boolean;
67
67
  json: boolean;
68
68
  verbose: boolean;
69
+ /** Per-package release notes keyed by package name, from the notes pipeline. */
70
+ releaseNotes?: Record<string, string>;
71
+ /** Additional files to stage in the git commit (e.g., changelog files from the notes step). */
72
+ additionalFiles?: string[];
69
73
  }
70
74
  interface PublishResult {
71
75
  packageName: string;
@@ -111,6 +115,10 @@ interface PipelineContext {
111
115
  packageManager: PackageManager;
112
116
  output: PublishOutput;
113
117
  cwd: string;
118
+ /** Per-package release notes keyed by package name, passed from the notes pipeline. */
119
+ releaseNotes?: Record<string, string>;
120
+ /** Additional files to stage in the git commit (e.g., changelog files from the notes step). */
121
+ additionalFiles?: string[];
114
122
  }
115
123
 
116
124
  declare function loadConfig(options?: LoadOptions): PublishConfig;
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  parseInput,
17
17
  runPipeline,
18
18
  updateCargoVersion
19
- } from "./chunk-H7AKSLZP.js";
19
+ } from "./chunk-GOBII36Q.js";
20
20
  export {
21
21
  BasePublishError,
22
22
  PipelineError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@releasekit/publish",
3
- "version": "0.2.0",
3
+ "version": "0.3.0-next.0",
4
4
  "description": "Publish packages to npm and crates.io with git tagging and GitHub releases",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -52,8 +52,8 @@
52
52
  "semver": "^7.7.4",
53
53
  "smol-toml": "^1.6.0",
54
54
  "zod": "^4.3.6",
55
- "@releasekit/config": "0.1.0",
56
- "@releasekit/core": "0.1.0"
55
+ "@releasekit/core": "0.1.0",
56
+ "@releasekit/config": "0.1.0"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@biomejs/biome": "^2.4.6",