pubz 0.4.0 → 0.5.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.
Files changed (3) hide show
  1. package/README.md +60 -51
  2. package/dist/cli.js +106 -9
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -21,6 +21,7 @@ bunx pubz
21
21
  9. Transforms `workspace:` definitions to hard version numbers (so `npm` can be used for publishing with OIDC support).
22
22
  10. Publishes to npm
23
23
  11. Prompts you to create a `git tag` and push it
24
+ 12. Generates a changelog and creates a GitHub Release
24
25
 
25
26
  ## Options
26
27
 
@@ -186,49 +187,35 @@ Discovering packages...
186
187
 
187
188
  Found 1 publishable package(s):
188
189
 
189
- • pubz@0.2.2
190
+ • pubz@0.4.0
190
191
 
191
192
  Step 1: Version Management
192
193
  ──────────────────────────────
193
194
 
194
- Current version: 0.2.2
195
-
196
- ? Bump version before publishing? [Y/n]
197
- ? Select version bump type:
198
-
199
- > 1) patch (0.2.2 -> 0.2.3)
200
- 2) minor (0.2.2 -> 0.3.0)
201
- 3) major (0.2.2 -> 1.0.0)
202
-
203
- Enter choice [1-3] (default: 1): 1
204
-
205
- Updating version to 0.2.3 in all packages...
206
-
207
- Updated pubz: 0.2.2 -> 0.2.3
208
- M package.json
209
- Committing version bump...
210
- [main 945e1a3] chore: release v0.2.3
211
- 1 file changed, 1 insertion(+), 1 deletion(-)
212
- Changes committed
195
+ Current version: 0.4.0
213
196
 
197
+ ? Bump version before publishing? [Y/n] n
214
198
  ? Select publish target:
215
199
 
216
200
  > 1) Public npm registry (https://registry.npmjs.org)
217
201
  2) GitHub Packages (https://npm.pkg.github.com)
218
202
 
219
- Enter choice [1-2] (default: 1):
203
+ Enter choice [1-2] (default: 1):
220
204
 
221
205
  Publishing to: https://registry.npmjs.org
222
206
 
207
+ Verifying npm authentication...
208
+ Authenticated as zdavison
209
+
223
210
  Step 2: Building Packages
224
211
  ──────────────────────────────
225
212
 
226
213
  Running build...
227
214
 
228
215
  $ bun build src/cli.ts --outdir dist --target node
229
- Bundled 7 modules in 7ms
216
+ Bundled 10 modules in 5ms
230
217
 
231
- cli.js 28.65 KB (entry point)
218
+ cli.js 41.27 KB (entry point)
232
219
 
233
220
 
234
221
  Build completed successfully
@@ -242,46 +229,68 @@ Step 3: Publishing to npm
242
229
 
243
230
  About to publish the following packages:
244
231
 
245
- • pubz@0.2.3
232
+ • pubz@0.4.0
246
233
 
247
234
  Registry: https://registry.npmjs.org
248
235
 
249
- ? Continue? [Y/n]
236
+ ? Continue? [Y/n]
237
+
238
+ Preparing packages for publish...
250
239
 
251
240
  Publishing packages...
252
241
 
253
- Publishing pubz@0.2.3...
254
- npm notice
255
- npm notice 📦 pubz@0.2.3
256
- npm notice Tarball Contents
257
- npm notice 717B package.json
258
- npm notice 2.3kB README.md
259
- npm notice 28.7kB dist/cli.js
260
- npm notice Tarball Details
261
- npm notice name: pubz
262
- npm notice version: 0.2.3
263
- npm notice filename: pubz-0.2.3.tgz
264
- npm notice package size: 8.3 kB
265
- npm notice unpacked size: 31.6 kB
266
- npm notice shasum: b8cd25d62b05d5cd6a4ecc8ff6ef6e522e7b2aa5
267
- npm notice integrity: sha512-jihTMvUxMeXNX[...]2+gtHJexETkWA==
268
- npm notice total files: 3
269
- npm notice
270
- + pubz@0.2.3
242
+ Publishing pubz@0.4.0...
243
+ npm notice
244
+ npm notice 📦 pubz@0.4.0
245
+ npm notice === Tarball Contents ===
246
+ npm notice 7.1kB README.md
247
+ npm notice 41.3kB dist/cli.js
248
+ npm notice 697B package.json
249
+ npm notice === Tarball Details ===
250
+ npm notice name: pubz
251
+ npm notice version: 0.4.0
252
+ npm notice filename: pubz-0.4.0.tgz
253
+ npm notice package size: 12.0 kB
254
+ npm notice unpacked size: 49.1 kB
255
+ npm notice shasum: 3026a7936458dcaa84030a0ce2e206b9f74aa65d
256
+ npm notice integrity: sha512-6vKMOsC7sZa87[...]w8KNx1fD45u/A==
257
+ npm notice total files: 3
258
+ npm notice
259
+ npm notice Publishing to https://registry.npmjs.org/ with tag latest and public access
260
+ Authenticate your account at:
261
+ https://www.npmjs.com/auth/cli/c47d9bee-2a1e-4adf-9aab-63d15acfade2
262
+ Press ENTER to open in the browser...
263
+
264
+ + pubz@0.4.0
271
265
  pubz published successfully
272
266
 
273
267
  ══════════════════════════════
274
268
  Publishing complete!
275
269
 
276
- Published version: 0.2.3
277
-
278
- ? Create a git tag for v0.2.3? [Y/n]
279
-
280
- Tag v0.2.3 created
281
- ? Push tag to origin? [Y/n]
270
+ Published version: 0.4.0
271
+
272
+ Changes since v0.2.12:
273
+ 5553c95 Fix ENTER to open browser not working.
274
+ 9aaddff Fix tag/push/release branch when using --yes.
275
+ 0ce3ab8 Generate changlog and attach it to release page / print it out during publish.
276
+ 5a29ca4 Merge branch 'main' of github.com:mm-zacharydavison/pubz
277
+ b4c47fc Clean up README.md formatting
278
+ 2da403c Update README.md
279
+ 88a4211 Update README with image and usage instructions
280
+ 8a8148a Update README.md
281
+ 2b45d21 Transform 'workspace:' definitions on publish, and restore them before any commit.
282
+
283
+ ? Create a git tag for v0.4.0? [Y/n]
284
+
285
+ Tag v0.4.0 created
286
+ ? Push tag to origin? [Y/n]
287
+ remote: This repository moved. Please use the new location:
288
+ remote: git@github.com:zdavison/pubz.git
282
289
  To github.com:mm-zacharydavison/pubz.git
283
- * [new tag] v0.2.3 -> v0.2.3
284
- Tag v0.2.3 pushed to origin
290
+ * [new tag] v0.4.0 -> v0.4.0
291
+ Tag v0.4.0 pushed to origin
292
+ ? Create a GitHub release? [Y/n]
293
+ Release created: https://github.com/zdavison/pubz/releases/tag/v0.4.0
285
294
 
286
295
  Done!
287
296
  ```
package/dist/cli.js CHANGED
@@ -97,6 +97,7 @@ async function discoverPackages(cwd) {
97
97
  const rootPackageJson = await readPackageJson(rootPackageJsonPath);
98
98
  const workspacePatterns = getWorkspacePatterns(rootPackageJson);
99
99
  let packageDirs = [];
100
+ const rootIsPublishable = !rootPackageJson.private && rootPackageJson.name && rootPackageJson.version;
100
101
  if (workspacePatterns.length > 0) {
101
102
  for (const pattern of workspacePatterns) {
102
103
  const matches = await glob(pattern, cwd);
@@ -118,6 +119,10 @@ async function discoverPackages(cwd) {
118
119
  }
119
120
  const packages = [];
120
121
  const packageNames = new Set;
122
+ if (rootIsPublishable) {
123
+ packageNames.add(rootPackageJson.name);
124
+ packages.push(await packageFromPath(cwd, rootPackageJsonPath, rootPackageJson, []));
125
+ }
121
126
  for (const dir of packageDirs) {
122
127
  const pkgPath = resolve(cwd, dir);
123
128
  const pkgJsonPath = join2(pkgPath, "package.json");
@@ -552,6 +557,10 @@ async function pushGitTag(version, cwd, dryRun) {
552
557
  console.log(`[DRY RUN] Would push git tag: ${tagName}`);
553
558
  return { success: true };
554
559
  }
560
+ const branchResult = await run("git", ["push"], cwd);
561
+ if (branchResult.code !== 0) {
562
+ return { success: false, error: "Failed to push bump commit to origin" };
563
+ }
555
564
  const result = await run("git", ["push", "origin", tagName], cwd);
556
565
  if (result.code !== 0) {
557
566
  return { success: false, error: `Failed to push tag ${tagName}` };
@@ -621,15 +630,18 @@ async function getCommitsSince(ref, cwd) {
621
630
  function isReleaseCommit(message) {
622
631
  return /^chore: release v/.test(message);
623
632
  }
633
+ function isMergeCommit(message) {
634
+ return /^Merge /.test(message);
635
+ }
624
636
  function formatChangelogTerminal(commits) {
625
- const filtered = commits.filter((c) => !isReleaseCommit(c.message));
637
+ const filtered = commits.filter((c) => !isReleaseCommit(c.message) && !isMergeCommit(c.message));
626
638
  if (filtered.length === 0)
627
639
  return "";
628
640
  return filtered.map((c) => ` ${dim(c.sha)} ${c.message}`).join(`
629
641
  `);
630
642
  }
631
643
  function formatChangelogMarkdown(commits, repoUrl) {
632
- const filtered = commits.filter((c) => !isReleaseCommit(c.message));
644
+ const filtered = commits.filter((c) => !isReleaseCommit(c.message) && !isMergeCommit(c.message));
633
645
  if (filtered.length === 0)
634
646
  return "";
635
647
  return filtered.map((c) => {
@@ -638,7 +650,11 @@ function formatChangelogMarkdown(commits, repoUrl) {
638
650
  }).join(`
639
651
  `);
640
652
  }
653
+ async function fetchTags(cwd) {
654
+ await runSilent("git", ["fetch", "--tags"], cwd);
655
+ }
641
656
  async function generateChangelog(cwd) {
657
+ await fetchTags(cwd);
642
658
  const [previousTag, repoUrl] = await Promise.all([
643
659
  getPreviousTag(cwd),
644
660
  getRepoUrl(cwd)
@@ -651,6 +667,40 @@ async function generateChangelog(cwd) {
651
667
  const markdown = formatChangelogMarkdown(commits, repoUrl);
652
668
  return { commits, terminal, markdown, previousTag, repoUrl };
653
669
  }
670
+ async function isClaudeAvailable() {
671
+ const result = await runSilent("which", ["claude"], process.cwd());
672
+ return result.code === 0;
673
+ }
674
+ async function generateAIReleaseNotes(commits, version) {
675
+ const filtered = commits.filter((c) => !isReleaseCommit(c.message));
676
+ if (filtered.length === 0)
677
+ return null;
678
+ const commitList = filtered.map((c) => `- ${c.sha} ${c.message}`).join(`
679
+ `);
680
+ const prompt2 = `You are writing release notes for version ${version} of a software package.
681
+
682
+ Here are the commits included in this release:
683
+ ${commitList}
684
+
685
+ Write concise, user-friendly release notes in markdown. Group related changes under headings if appropriate (e.g. Features, Bug Fixes, Improvements). Focus on what changed and why it matters to users — not implementation details. Do not include a title or version header. Output only the markdown body.`;
686
+ return new Promise((resolve2) => {
687
+ const proc = spawn3("claude", ["-p", prompt2], {
688
+ stdio: ["ignore", "pipe", "pipe"]
689
+ });
690
+ let output = "";
691
+ proc.stdout?.on("data", (data) => {
692
+ output += data.toString();
693
+ });
694
+ proc.on("close", (code) => {
695
+ if (code === 0 && output.trim()) {
696
+ resolve2(output.trim());
697
+ } else {
698
+ resolve2(null);
699
+ }
700
+ });
701
+ proc.on("error", () => resolve2(null));
702
+ });
703
+ }
654
704
  async function createGitHubRelease(version, body, cwd, dryRun) {
655
705
  const tagName = `v${version}`;
656
706
  if (dryRun) {
@@ -738,6 +788,9 @@ async function restoreWorkspaceProtocol(transforms) {
738
788
  console.log(` Restored workspace references in ${byPath.size} package(s)`);
739
789
  }
740
790
  }
791
+ function isValidVersion(version) {
792
+ return /^\d+\.\d+\.\d+(-.+)?$/.test(version);
793
+ }
741
794
  function bumpVersion(version, type) {
742
795
  if (type === "none")
743
796
  return version;
@@ -988,7 +1041,13 @@ async function main() {
988
1041
  newVersion = bumpVersion(currentVersion, options.version);
989
1042
  console.log(`Bumping version (${options.version}): ${yellow(currentVersion)} → ${green(newVersion)}`);
990
1043
  } else {
991
- newVersion = options.version;
1044
+ const cleaned = options.version.startsWith("v") ? options.version.slice(1) : options.version;
1045
+ if (!isValidVersion(cleaned)) {
1046
+ console.error(red(bold("Error:")) + ` Invalid version "${options.version}". Expected format: major.minor.patch (e.g. 1.2.3, 1.2.3-beta)`);
1047
+ closePrompt();
1048
+ process.exit(1);
1049
+ }
1050
+ newVersion = cleaned;
992
1051
  console.log(`Using explicit version: ${green(newVersion)}`);
993
1052
  }
994
1053
  console.log("");
@@ -1011,7 +1070,7 @@ async function main() {
1011
1070
  } else if (!skipAllPrompts) {
1012
1071
  const shouldBump = skipConfirms || await confirm("Bump version before publishing?");
1013
1072
  if (shouldBump) {
1014
- const bumpType = await select("Select version bump type:", [
1073
+ const bumpChoice = await select("Select version bump type:", [
1015
1074
  {
1016
1075
  label: `patch (${previewBump(currentVersion, "patch")})`,
1017
1076
  value: "patch"
@@ -1023,9 +1082,27 @@ async function main() {
1023
1082
  {
1024
1083
  label: `major (${previewBump(currentVersion, "major")})`,
1025
1084
  value: "major"
1085
+ },
1086
+ {
1087
+ label: "custom version",
1088
+ value: "custom"
1026
1089
  }
1027
1090
  ]);
1028
- newVersion = bumpVersion(currentVersion, bumpType);
1091
+ if (bumpChoice === "custom") {
1092
+ let customVersion = "";
1093
+ while (!customVersion) {
1094
+ const input = await prompt(` Enter version: `);
1095
+ const cleaned = input.startsWith("v") ? input.slice(1) : input;
1096
+ if (isValidVersion(cleaned)) {
1097
+ customVersion = cleaned;
1098
+ } else {
1099
+ console.log(yellow(" Invalid version. Expected format: major.minor.patch (e.g. 1.2.3, 1.2.3-beta)"));
1100
+ }
1101
+ }
1102
+ newVersion = customVersion;
1103
+ } else {
1104
+ newVersion = bumpVersion(currentVersion, bumpChoice);
1105
+ }
1029
1106
  console.log("");
1030
1107
  console.log(`Updating version to ${green(newVersion)} in all packages...`);
1031
1108
  console.log("");
@@ -1200,6 +1277,26 @@ async function main() {
1200
1277
  console.log(changelog.terminal);
1201
1278
  console.log("");
1202
1279
  }
1280
+ let releaseNotes = changelog.markdown;
1281
+ if (!options.ci && changelog.commits.length > 0) {
1282
+ const claudeAvailable = await isClaudeAvailable();
1283
+ if (claudeAvailable) {
1284
+ const useAI = await confirm("Generate release notes with AI (claude)?");
1285
+ if (useAI) {
1286
+ console.log(cyan("Generating AI release notes..."));
1287
+ const aiNotes = await generateAIReleaseNotes(changelog.commits, newVersion);
1288
+ if (aiNotes) {
1289
+ releaseNotes = aiNotes;
1290
+ console.log("");
1291
+ console.log(bold("AI-generated release notes:"));
1292
+ console.log(aiNotes);
1293
+ console.log("");
1294
+ } else {
1295
+ console.log(yellow("AI generation failed, falling back to commit list."));
1296
+ }
1297
+ }
1298
+ }
1299
+ }
1203
1300
  if (!options.dryRun) {
1204
1301
  if (options.ci) {
1205
1302
  console.log(cyan("Creating git tag..."));
@@ -1207,9 +1304,9 @@ async function main() {
1207
1304
  if (tagResult.success) {
1208
1305
  console.log(cyan("Pushing tag to origin..."));
1209
1306
  await pushGitTag(newVersion, cwd, options.dryRun);
1210
- if (changelog.markdown) {
1307
+ if (releaseNotes) {
1211
1308
  console.log(cyan("Creating GitHub release..."));
1212
- const releaseResult = await createGitHubRelease(newVersion, changelog.markdown, cwd, options.dryRun);
1309
+ const releaseResult = await createGitHubRelease(newVersion, releaseNotes, cwd, options.dryRun);
1213
1310
  if (releaseResult.success && releaseResult.url) {
1214
1311
  console.log(` Release created: ${cyan(releaseResult.url)}`);
1215
1312
  } else if (!releaseResult.success) {
@@ -1229,10 +1326,10 @@ async function main() {
1229
1326
  const shouldPush = skipConfirms || await confirm("Push tag to origin?");
1230
1327
  if (shouldPush) {
1231
1328
  await pushGitTag(newVersion, cwd, options.dryRun);
1232
- if (changelog.markdown) {
1329
+ if (releaseNotes) {
1233
1330
  const shouldRelease = skipConfirms || await confirm("Create a GitHub release?");
1234
1331
  if (shouldRelease) {
1235
- const releaseResult = await createGitHubRelease(newVersion, changelog.markdown, cwd, options.dryRun);
1332
+ const releaseResult = await createGitHubRelease(newVersion, releaseNotes, cwd, options.dryRun);
1236
1333
  if (releaseResult.success && releaseResult.url) {
1237
1334
  console.log(` Release created: ${cyan(releaseResult.url)}`);
1238
1335
  } else if (!releaseResult.success) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pubz",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Interactive CLI for publishing npm packages (single or monorepo)",
5
5
  "type": "module",
6
6
  "bin": {