@wrongstack/plugins 0.250.0 → 0.255.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.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execFileSync, execSync } from 'child_process';
2
- import { watch, existsSync, readdirSync, readFileSync } from 'fs';
2
+ import { watch, existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
3
3
  import * as path from 'path';
4
4
  import { isAbsolute, join } from 'path';
5
5
  import { expectDefined, deepMerge as deepMerge$1 } from '@wrongstack/core';
@@ -135,7 +135,7 @@ async function runAutoDoc(input, api) {
135
135
  const results = [];
136
136
  for (const file of input.files) {
137
137
  try {
138
- const { readFileSync: readFileSync2, writeFileSync } = await import('fs');
138
+ const { readFileSync: readFileSync2, writeFileSync: writeFileSync2 } = await import('fs');
139
139
  let content;
140
140
  try {
141
141
  content = readFileSync2(file, "utf-8");
@@ -152,7 +152,7 @@ async function runAutoDoc(input, api) {
152
152
  results.push({ file, entity: entity.name });
153
153
  }
154
154
  if (!input.dryRun && results.length > 0) {
155
- writeFileSync(file, modified, "utf-8");
155
+ writeFileSync2(file, modified, "utf-8");
156
156
  api.log.info(`auto-doc: updated ${file}`);
157
157
  }
158
158
  } catch (err) {
@@ -2255,8 +2255,8 @@ var plugin9 = {
2255
2255
  if (isAbsolute(outputPath) || outputPath.includes("..")) {
2256
2256
  return { ok: false, error: 'outputPath must be a relative path without ".." components' };
2257
2257
  }
2258
- const { writeFileSync } = await import('fs');
2259
- writeFileSync(outputPath, result, "utf-8");
2258
+ const { writeFileSync: writeFileSync2 } = await import('fs');
2259
+ writeFileSync2(outputPath, result, "utf-8");
2260
2260
  return {
2261
2261
  ok: true,
2262
2262
  outputPath,
@@ -2319,8 +2319,8 @@ var plugin9 = {
2319
2319
  if (isAbsolute(outputPath) || outputPath.includes("..")) {
2320
2320
  return { ok: false, error: 'outputPath must be a relative path without ".." components' };
2321
2321
  }
2322
- const { writeFileSync } = await import('fs');
2323
- writeFileSync(outputPath, result, "utf-8");
2322
+ const { writeFileSync: writeFileSync2 } = await import('fs');
2323
+ writeFileSync2(outputPath, result, "utf-8");
2324
2324
  return {
2325
2325
  ok: true,
2326
2326
  templatePath,
@@ -2439,10 +2439,25 @@ function getPackageJson(cwd) {
2439
2439
  return null;
2440
2440
  }
2441
2441
  }
2442
+ function collectManifests(root) {
2443
+ const paths = [];
2444
+ const rootPkg = join(root, "package.json");
2445
+ if (existsSync(rootPkg)) paths.push(rootPkg);
2446
+ for (const group of ["packages", "apps"]) {
2447
+ const groupDir = join(root, group);
2448
+ if (!existsSync(groupDir)) continue;
2449
+ for (const entry of readdirSync(groupDir, { withFileTypes: true })) {
2450
+ if (!entry.isDirectory()) continue;
2451
+ const candidate = join(groupDir, entry.name, "package.json");
2452
+ if (existsSync(candidate)) paths.push(candidate);
2453
+ }
2454
+ }
2455
+ return paths;
2456
+ }
2442
2457
  function parseVersion(v) {
2443
2458
  const m = v.match(/^v?(\d+)\.(\d+)\.(\d+)/);
2444
2459
  if (!m) return [0, 0, 0];
2445
- return [Number.parseInt(expectDefined(m[1])), Number.parseInt(expectDefined(m[2])), Number.parseInt(expectDefined(m[3]))];
2460
+ return [Number.parseInt(expectDefined(m[1]), 10), Number.parseInt(expectDefined(m[2]), 10), Number.parseInt(expectDefined(m[3]), 10)];
2446
2461
  }
2447
2462
  function bumpVersion(version, part) {
2448
2463
  let [major, minor, patch] = parseVersion(version);
@@ -2460,6 +2475,15 @@ function bumpVersion(version, part) {
2460
2475
  }
2461
2476
  return `${major}.${minor}.${patch}`;
2462
2477
  }
2478
+ function parseConventional(subject) {
2479
+ const m = subject.match(/^(\w+)(!)?(?:\(([^)]+)\))?(!)?:\s+(.+)/);
2480
+ return {
2481
+ type: m?.[1] ?? "chore",
2482
+ breaking: !!(m?.[2] ?? m?.[4]),
2483
+ scope: m?.[3],
2484
+ message: m?.[5] ?? subject
2485
+ };
2486
+ }
2463
2487
  function getRecentCommits(sinceTag, cwd) {
2464
2488
  const range = sinceTag ? `${sinceTag}..HEAD` : "-30";
2465
2489
  const output = runGit2(["log", range, "--format=%H %s"], cwd);
@@ -2468,25 +2492,12 @@ function getRecentCommits(sinceTag, cwd) {
2468
2492
  const spaceIdx = line.indexOf(" ");
2469
2493
  const hash = line.slice(0, spaceIdx);
2470
2494
  const message = line.slice(spaceIdx + 1);
2471
- const m = message.match(/^(\w+)(!)?(?:\(([^)]+)\))?:\s(.+)/);
2472
- const type = m?.[1] ?? "chore";
2473
- const breaking = !!m?.[2];
2474
- const scope = m?.[2];
2475
- const msg = m?.[3] ?? message;
2476
- return { hash, type, scope, message: msg, breaking };
2495
+ return { hash, ...parseConventional(message) };
2477
2496
  });
2478
2497
  }
2479
2498
  function determineBump(commits) {
2480
- for (const c of commits) {
2481
- if (c.breaking || c.type === "feat!:" || c.type === "fix!") {
2482
- return "major";
2483
- }
2484
- }
2485
- for (const c of commits) {
2486
- if (c.type === "feat" || c.type === "refactor" && c.scope) {
2487
- return "minor";
2488
- }
2489
- }
2499
+ if (commits.some((c) => c.breaking)) return "major";
2500
+ if (commits.some((c) => c.type === "feat")) return "minor";
2490
2501
  return "patch";
2491
2502
  }
2492
2503
  function generateChangelog(commits) {
@@ -2548,12 +2559,13 @@ var plugin10 = {
2548
2559
  version: "0.1.0",
2549
2560
  description: "Conventional-commit-driven semver version bumps with changelog generation",
2550
2561
  apiVersion: API_VERSION9,
2551
- capabilities: { tools: true },
2562
+ capabilities: { tools: true, slashCommands: true },
2552
2563
  defaultConfig: {
2553
2564
  tagPrefix: "v",
2554
2565
  changelogFile: "CHANGELOG.md",
2555
2566
  autoTag: true,
2556
- tagMessage: "Release {{version}}"
2567
+ tagMessage: "Release {{version}}",
2568
+ defaultPart: "patch"
2557
2569
  },
2558
2570
  configSchema: {
2559
2571
  type: "object",
@@ -2561,12 +2573,114 @@ var plugin10 = {
2561
2573
  tagPrefix: { type: "string", default: "v" },
2562
2574
  changelogFile: { type: "string", default: "CHANGELOG.md" },
2563
2575
  autoTag: { type: "boolean", default: true },
2564
- tagMessage: { type: "string", default: "Release {{version}}" }
2576
+ tagMessage: { type: "string", default: "Release {{version}}" },
2577
+ defaultPart: { type: "string", enum: ["major", "minor", "patch", "auto"], default: "patch" }
2565
2578
  }
2566
2579
  },
2567
2580
  setup(api) {
2568
2581
  const tagPrefix = api.config.extensions?.["semver-bump"]?.["tagPrefix"] ?? "v";
2569
2582
  const autoTag = api.config.extensions?.["semver-bump"]?.["autoTag"] ?? true;
2583
+ const VALID_PARTS = ["major", "minor", "patch", "auto"];
2584
+ function readDefaultPart(cfg) {
2585
+ const raw = cfg.extensions?.["semver-bump"]?.["defaultPart"];
2586
+ return VALID_PARTS.includes(raw) ? raw : "patch";
2587
+ }
2588
+ let defaultPart = readDefaultPart(api.config);
2589
+ api.onConfigChange?.((next) => {
2590
+ defaultPart = readDefaultPart(next);
2591
+ });
2592
+ async function performBump(part, dryRun, cwd) {
2593
+ const pkg = getPackageJson(cwd);
2594
+ if (!pkg) {
2595
+ return { ok: false, error: "No package.json found" };
2596
+ }
2597
+ const currentVersion = pkg.version;
2598
+ let bumpPart = part;
2599
+ let commits = [];
2600
+ if (part === "auto") {
2601
+ let lastTag;
2602
+ try {
2603
+ const tagsOutput = runGit2(["describe", "--tags", "--abbrev=0"], cwd);
2604
+ lastTag = tagsOutput || void 0;
2605
+ } catch {
2606
+ }
2607
+ try {
2608
+ commits = getRecentCommits(lastTag, cwd);
2609
+ } catch (err) {
2610
+ const msg = err instanceof Error ? err.message : String(err);
2611
+ return { ok: false, error: `Git error: ${msg}`, bumpPart: "patch" };
2612
+ }
2613
+ bumpPart = determineBump(commits);
2614
+ } else {
2615
+ bumpPart = part;
2616
+ }
2617
+ const newVersion = bumpVersion(currentVersion, bumpPart);
2618
+ if (dryRun) {
2619
+ return {
2620
+ ok: true,
2621
+ dryRun: true,
2622
+ currentVersion,
2623
+ suggestedBump: bumpPart,
2624
+ newVersion,
2625
+ commitCount: part === "auto" ? commits.length : void 0,
2626
+ message: `Would bump ${currentVersion} \u2192 ${newVersion} (${bumpPart})`
2627
+ };
2628
+ }
2629
+ const root = cwd ?? process.cwd();
2630
+ const bumpScript = join(root, "scripts", "bump-version.mjs");
2631
+ const changed = collectManifests(root);
2632
+ if (existsSync(bumpScript)) {
2633
+ try {
2634
+ execFileSync(process.execPath, [bumpScript, "set", newVersion], {
2635
+ cwd: root,
2636
+ stdio: ["pipe", "pipe", "pipe"],
2637
+ timeout: 3e4,
2638
+ windowsHide: true
2639
+ });
2640
+ } catch (err) {
2641
+ const msg = err instanceof Error ? err.message : String(err);
2642
+ return { ok: false, error: `bump script failed: ${msg}` };
2643
+ }
2644
+ for (const rel of ["package.json", "package-lock.json", "src/lib/utils.ts", "index.html"]) {
2645
+ const p = join(root, "website", rel);
2646
+ if (existsSync(p)) changed.push(p);
2647
+ }
2648
+ } else {
2649
+ for (const manifest of changed) {
2650
+ const pkgData = JSON.parse(readFileSync(manifest, "utf-8"));
2651
+ pkgData.version = newVersion;
2652
+ writeFileSync(manifest, JSON.stringify(pkgData, null, 2) + "\n", "utf-8");
2653
+ }
2654
+ }
2655
+ try {
2656
+ runGit2(["add", "--", ...changed], cwd);
2657
+ runGit2(["commit", "-m", `chore: bump version to ${newVersion}`], cwd);
2658
+ } catch {
2659
+ }
2660
+ if (autoTag) {
2661
+ try {
2662
+ runGit2(["tag", "-a", `${tagPrefix}${newVersion}`, "-m", `Release ${newVersion}`], cwd);
2663
+ } catch {
2664
+ }
2665
+ }
2666
+ api.log.info("semver-bump: bumped", { from: currentVersion, to: newVersion, bump: bumpPart });
2667
+ api.metrics.counter("version_bump", 1, { bump: bumpPart });
2668
+ await api.session.append({
2669
+ type: "semver-bump:bumped",
2670
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2671
+ from: currentVersion,
2672
+ to: newVersion,
2673
+ bump: bumpPart
2674
+ });
2675
+ return {
2676
+ ok: true,
2677
+ currentVersion,
2678
+ newVersion,
2679
+ bump: bumpPart,
2680
+ tag: `${tagPrefix}${newVersion}`,
2681
+ message: `Bumped ${currentVersion} \u2192 ${newVersion} (${bumpPart})`
2682
+ };
2683
+ }
2570
2684
  api.tools.register({
2571
2685
  name: "semver_bump",
2572
2686
  description: "Determine the next version bump from conventional commits since the last tag, or force a specific bump. Creates a git tag.",
@@ -2575,7 +2689,7 @@ var plugin10 = {
2575
2689
  properties: {
2576
2690
  cwd: { type: "string", description: "Working directory (defaults to project root)" },
2577
2691
  dryRun: { type: "boolean", default: false },
2578
- part: { type: "string", enum: ["major", "minor", "patch", "auto"], default: "patch", description: "Version part to bump (defaults to patch; use auto to infer from commits)" }
2692
+ part: { type: "string", enum: ["major", "minor", "patch", "auto"], default: defaultPart, description: "Version part to bump. Omitted \u2192 the configured default (/settings semver-part, factory default: patch). Use auto to infer from commits." }
2579
2693
  }
2580
2694
  },
2581
2695
  permission: "confirm",
@@ -2583,76 +2697,62 @@ var plugin10 = {
2583
2697
  async execute(input) {
2584
2698
  const cwd = input["cwd"];
2585
2699
  const dryRun = input["dryRun"] ?? false;
2586
- const part = input["part"] ?? "auto";
2587
- const pkg = getPackageJson(cwd);
2588
- if (!pkg) {
2589
- return { ok: false, error: "No package.json found" };
2590
- }
2591
- const currentVersion = pkg.version;
2592
- let bumpPart = part;
2593
- let commits = [];
2594
- if (part === "auto") {
2700
+ const part = input["part"] ?? defaultPart;
2701
+ return performBump(part, dryRun, cwd);
2702
+ }
2703
+ });
2704
+ api.slashCommands.register({
2705
+ name: "semver",
2706
+ description: "Show the current version or bump it (patch/minor/major/auto)",
2707
+ category: "Run",
2708
+ argsHint: "[status|patch|minor|major|auto] [--dry]",
2709
+ help: [
2710
+ "/semver Show current version, latest tag and the suggested bump",
2711
+ "/semver status Same as bare /semver",
2712
+ "/semver patch Bump the patch version (commit + tag)",
2713
+ "/semver minor Bump the minor version (commit + tag)",
2714
+ "/semver major Bump the major version (commit + tag)",
2715
+ "/semver auto Infer the bump from conventional commits since the last tag",
2716
+ "/semver <part> --dry Preview without writing anything"
2717
+ ].join("\n"),
2718
+ async run(args, ctx) {
2719
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
2720
+ const dry = tokens.includes("--dry") || tokens.includes("--dry-run");
2721
+ const mode = tokens.find((t) => !t.startsWith("--")) ?? "status";
2722
+ const cwd = ctx?.cwd;
2723
+ if (mode === "status") {
2724
+ const pkg = getPackageJson(cwd);
2725
+ if (!pkg) return { message: "No package.json found" };
2595
2726
  let lastTag;
2596
2727
  try {
2597
- const tagsOutput = runGit2(["describe", "--tags", "--abbrev=0"], cwd);
2598
- lastTag = tagsOutput || void 0;
2728
+ lastTag = runGit2(["describe", "--tags", "--abbrev=0"], cwd) || void 0;
2599
2729
  } catch {
2600
2730
  }
2731
+ let suggestion = "patch";
2732
+ let commitCount = 0;
2601
2733
  try {
2602
- commits = getRecentCommits(lastTag, cwd);
2603
- } catch (err) {
2604
- const msg = err instanceof Error ? err.message : String(err);
2605
- return { ok: false, error: `Git error: ${msg}`, bumpPart: "patch" };
2734
+ const commits = getRecentCommits(lastTag, cwd);
2735
+ commitCount = commits.length;
2736
+ suggestion = determineBump(commits);
2737
+ } catch {
2606
2738
  }
2607
- bumpPart = determineBump(commits);
2608
- } else {
2609
- bumpPart = part;
2610
- }
2611
- const newVersion = bumpVersion(currentVersion, bumpPart);
2612
- if (dryRun) {
2613
2739
  return {
2614
- ok: true,
2615
- dryRun: true,
2616
- currentVersion,
2617
- suggestedBump: bumpPart,
2618
- newVersion,
2619
- commitCount: part === "auto" ? commits.length : void 0,
2620
- message: `Would bump ${currentVersion} \u2192 ${newVersion} (${bumpPart})`
2740
+ message: [
2741
+ `Current version: ${pkg.version}`,
2742
+ `Latest tag: ${lastTag ?? "(none)"}`,
2743
+ `Commits since: ${commitCount}`,
2744
+ `Suggested bump: ${suggestion} \u2192 ${bumpVersion(pkg.version, suggestion)}`,
2745
+ `Default part: ${defaultPart} (change: /settings semver-part)`,
2746
+ "",
2747
+ "Run /semver patch|minor|major|auto to apply (add --dry to preview)."
2748
+ ].join("\n")
2621
2749
  };
2622
2750
  }
2623
- const fs = await import('fs');
2624
- const pkgPath = cwd ? `${cwd}/package.json` : "package.json";
2625
- const pkgData = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
2626
- pkgData.version = newVersion;
2627
- fs.writeFileSync(pkgPath, JSON.stringify(pkgData, null, 2) + "\n", "utf-8");
2628
- try {
2629
- runGit2(["add", "package.json"], cwd);
2630
- runGit2(["commit", "-m", `chore: bump version to ${newVersion}`], cwd);
2631
- } catch {
2751
+ if (mode !== "patch" && mode !== "minor" && mode !== "major" && mode !== "auto") {
2752
+ return { message: `Unknown mode "${mode}". Use status, patch, minor, major or auto.` };
2632
2753
  }
2633
- if (autoTag) {
2634
- try {
2635
- runGit2(["tag", "-a", `${tagPrefix}${newVersion}`, "-m", `Release ${newVersion}`], cwd);
2636
- } catch {
2637
- }
2638
- }
2639
- api.log.info("semver-bump: bumped", { from: currentVersion, to: newVersion, bump: bumpPart });
2640
- api.metrics.counter("version_bump", 1, { bump: bumpPart });
2641
- await api.session.append({
2642
- type: "semver-bump:bumped",
2643
- ts: (/* @__PURE__ */ new Date()).toISOString(),
2644
- from: currentVersion,
2645
- to: newVersion,
2646
- bump: bumpPart
2647
- });
2648
- return {
2649
- ok: true,
2650
- currentVersion,
2651
- newVersion,
2652
- bump: bumpPart,
2653
- tag: `${tagPrefix}${newVersion}`,
2654
- message: `Bumped ${currentVersion} \u2192 ${newVersion} (${bumpPart})`
2655
- };
2754
+ const result = await performBump(mode, dry, cwd);
2755
+ return { message: String(result["message"] ?? result["error"] ?? JSON.stringify(result)) };
2656
2756
  }
2657
2757
  });
2658
2758
  api.tools.register({
@@ -2677,7 +2777,7 @@ var plugin10 = {
2677
2777
  latestTag = tagsOutput || null;
2678
2778
  if (latestTag) {
2679
2779
  const countOutput = runGit2(["rev-list", "--count", `${latestTag}..HEAD`], cwd);
2680
- commitsSinceTag = Number.parseInt(countOutput) || 0;
2780
+ commitsSinceTag = Number.parseInt(countOutput, 10) || 0;
2681
2781
  }
2682
2782
  } catch {
2683
2783
  latestTag = null;
@@ -2718,15 +2818,7 @@ var plugin10 = {
2718
2818
  const spaceIdx = line.indexOf(" ");
2719
2819
  const hash = line.slice(0, spaceIdx);
2720
2820
  const message = line.slice(spaceIdx + 1);
2721
- const m = message.match(/^(\w+)(!)?(?:\(([^)]+)\))?:\s(.+)/);
2722
- const type = m?.[1] ?? "chore";
2723
- return {
2724
- hash,
2725
- type,
2726
- scope: m?.[2],
2727
- message: m?.[3] ?? message,
2728
- breaking: !!m?.[2]
2729
- };
2821
+ return { hash, ...parseConventional(message) };
2730
2822
  });
2731
2823
  } catch (err) {
2732
2824
  return { ok: false, error: `Failed to get git log: ${err}` };
@@ -9,6 +9,18 @@ import { Plugin } from '@wrongstack/core';
9
9
  * - semver_changelog: Generate a changelog between two versions
10
10
  */
11
11
 
12
+ type BumpType = 'major' | 'minor' | 'patch' | 'auto';
13
+ interface ConventionalCommit {
14
+ hash: string;
15
+ type: string;
16
+ scope?: string | undefined;
17
+ message: string;
18
+ breaking: boolean;
19
+ }
20
+ /** Parse a conventional-commit subject line. Accepts the breaking `!` both
21
+ * before and after the scope (`feat!: x`, `feat(api)!: x`). */
22
+ declare function parseConventional(subject: string): Omit<ConventionalCommit, 'hash'>;
23
+ declare function determineBump(commits: ConventionalCommit[]): BumpType;
12
24
  declare const plugin: Plugin;
13
25
 
14
- export { plugin as default };
26
+ export { plugin as default, determineBump, parseConventional };
@@ -1,6 +1,7 @@
1
1
  import { expectDefined } from '@wrongstack/core';
2
2
  import { execFileSync } from 'child_process';
3
- import { existsSync, readFileSync } from 'fs';
3
+ import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
4
+ import { join } from 'path';
4
5
 
5
6
  // src/semver-bump/index.ts
6
7
  var API_VERSION = "^0.1.10";
@@ -28,10 +29,25 @@ function getPackageJson(cwd) {
28
29
  return null;
29
30
  }
30
31
  }
32
+ function collectManifests(root) {
33
+ const paths = [];
34
+ const rootPkg = join(root, "package.json");
35
+ if (existsSync(rootPkg)) paths.push(rootPkg);
36
+ for (const group of ["packages", "apps"]) {
37
+ const groupDir = join(root, group);
38
+ if (!existsSync(groupDir)) continue;
39
+ for (const entry of readdirSync(groupDir, { withFileTypes: true })) {
40
+ if (!entry.isDirectory()) continue;
41
+ const candidate = join(groupDir, entry.name, "package.json");
42
+ if (existsSync(candidate)) paths.push(candidate);
43
+ }
44
+ }
45
+ return paths;
46
+ }
31
47
  function parseVersion(v) {
32
48
  const m = v.match(/^v?(\d+)\.(\d+)\.(\d+)/);
33
49
  if (!m) return [0, 0, 0];
34
- return [Number.parseInt(expectDefined(m[1])), Number.parseInt(expectDefined(m[2])), Number.parseInt(expectDefined(m[3]))];
50
+ return [Number.parseInt(expectDefined(m[1]), 10), Number.parseInt(expectDefined(m[2]), 10), Number.parseInt(expectDefined(m[3]), 10)];
35
51
  }
36
52
  function bumpVersion(version, part) {
37
53
  let [major, minor, patch] = parseVersion(version);
@@ -49,6 +65,15 @@ function bumpVersion(version, part) {
49
65
  }
50
66
  return `${major}.${minor}.${patch}`;
51
67
  }
68
+ function parseConventional(subject) {
69
+ const m = subject.match(/^(\w+)(!)?(?:\(([^)]+)\))?(!)?:\s+(.+)/);
70
+ return {
71
+ type: m?.[1] ?? "chore",
72
+ breaking: !!(m?.[2] ?? m?.[4]),
73
+ scope: m?.[3],
74
+ message: m?.[5] ?? subject
75
+ };
76
+ }
52
77
  function getRecentCommits(sinceTag, cwd) {
53
78
  const range = sinceTag ? `${sinceTag}..HEAD` : "-30";
54
79
  const output = runGit(["log", range, "--format=%H %s"], cwd);
@@ -57,25 +82,12 @@ function getRecentCommits(sinceTag, cwd) {
57
82
  const spaceIdx = line.indexOf(" ");
58
83
  const hash = line.slice(0, spaceIdx);
59
84
  const message = line.slice(spaceIdx + 1);
60
- const m = message.match(/^(\w+)(!)?(?:\(([^)]+)\))?:\s(.+)/);
61
- const type = m?.[1] ?? "chore";
62
- const breaking = !!m?.[2];
63
- const scope = m?.[2];
64
- const msg = m?.[3] ?? message;
65
- return { hash, type, scope, message: msg, breaking };
85
+ return { hash, ...parseConventional(message) };
66
86
  });
67
87
  }
68
88
  function determineBump(commits) {
69
- for (const c of commits) {
70
- if (c.breaking || c.type === "feat!:" || c.type === "fix!") {
71
- return "major";
72
- }
73
- }
74
- for (const c of commits) {
75
- if (c.type === "feat" || c.type === "refactor" && c.scope) {
76
- return "minor";
77
- }
78
- }
89
+ if (commits.some((c) => c.breaking)) return "major";
90
+ if (commits.some((c) => c.type === "feat")) return "minor";
79
91
  return "patch";
80
92
  }
81
93
  function generateChangelog(commits) {
@@ -137,12 +149,13 @@ var plugin = {
137
149
  version: "0.1.0",
138
150
  description: "Conventional-commit-driven semver version bumps with changelog generation",
139
151
  apiVersion: API_VERSION,
140
- capabilities: { tools: true },
152
+ capabilities: { tools: true, slashCommands: true },
141
153
  defaultConfig: {
142
154
  tagPrefix: "v",
143
155
  changelogFile: "CHANGELOG.md",
144
156
  autoTag: true,
145
- tagMessage: "Release {{version}}"
157
+ tagMessage: "Release {{version}}",
158
+ defaultPart: "patch"
146
159
  },
147
160
  configSchema: {
148
161
  type: "object",
@@ -150,12 +163,114 @@ var plugin = {
150
163
  tagPrefix: { type: "string", default: "v" },
151
164
  changelogFile: { type: "string", default: "CHANGELOG.md" },
152
165
  autoTag: { type: "boolean", default: true },
153
- tagMessage: { type: "string", default: "Release {{version}}" }
166
+ tagMessage: { type: "string", default: "Release {{version}}" },
167
+ defaultPart: { type: "string", enum: ["major", "minor", "patch", "auto"], default: "patch" }
154
168
  }
155
169
  },
156
170
  setup(api) {
157
171
  const tagPrefix = api.config.extensions?.["semver-bump"]?.["tagPrefix"] ?? "v";
158
172
  const autoTag = api.config.extensions?.["semver-bump"]?.["autoTag"] ?? true;
173
+ const VALID_PARTS = ["major", "minor", "patch", "auto"];
174
+ function readDefaultPart(cfg) {
175
+ const raw = cfg.extensions?.["semver-bump"]?.["defaultPart"];
176
+ return VALID_PARTS.includes(raw) ? raw : "patch";
177
+ }
178
+ let defaultPart = readDefaultPart(api.config);
179
+ api.onConfigChange?.((next) => {
180
+ defaultPart = readDefaultPart(next);
181
+ });
182
+ async function performBump(part, dryRun, cwd) {
183
+ const pkg = getPackageJson(cwd);
184
+ if (!pkg) {
185
+ return { ok: false, error: "No package.json found" };
186
+ }
187
+ const currentVersion = pkg.version;
188
+ let bumpPart = part;
189
+ let commits = [];
190
+ if (part === "auto") {
191
+ let lastTag;
192
+ try {
193
+ const tagsOutput = runGit(["describe", "--tags", "--abbrev=0"], cwd);
194
+ lastTag = tagsOutput || void 0;
195
+ } catch {
196
+ }
197
+ try {
198
+ commits = getRecentCommits(lastTag, cwd);
199
+ } catch (err) {
200
+ const msg = err instanceof Error ? err.message : String(err);
201
+ return { ok: false, error: `Git error: ${msg}`, bumpPart: "patch" };
202
+ }
203
+ bumpPart = determineBump(commits);
204
+ } else {
205
+ bumpPart = part;
206
+ }
207
+ const newVersion = bumpVersion(currentVersion, bumpPart);
208
+ if (dryRun) {
209
+ return {
210
+ ok: true,
211
+ dryRun: true,
212
+ currentVersion,
213
+ suggestedBump: bumpPart,
214
+ newVersion,
215
+ commitCount: part === "auto" ? commits.length : void 0,
216
+ message: `Would bump ${currentVersion} \u2192 ${newVersion} (${bumpPart})`
217
+ };
218
+ }
219
+ const root = cwd ?? process.cwd();
220
+ const bumpScript = join(root, "scripts", "bump-version.mjs");
221
+ const changed = collectManifests(root);
222
+ if (existsSync(bumpScript)) {
223
+ try {
224
+ execFileSync(process.execPath, [bumpScript, "set", newVersion], {
225
+ cwd: root,
226
+ stdio: ["pipe", "pipe", "pipe"],
227
+ timeout: 3e4,
228
+ windowsHide: true
229
+ });
230
+ } catch (err) {
231
+ const msg = err instanceof Error ? err.message : String(err);
232
+ return { ok: false, error: `bump script failed: ${msg}` };
233
+ }
234
+ for (const rel of ["package.json", "package-lock.json", "src/lib/utils.ts", "index.html"]) {
235
+ const p = join(root, "website", rel);
236
+ if (existsSync(p)) changed.push(p);
237
+ }
238
+ } else {
239
+ for (const manifest of changed) {
240
+ const pkgData = JSON.parse(readFileSync(manifest, "utf-8"));
241
+ pkgData.version = newVersion;
242
+ writeFileSync(manifest, JSON.stringify(pkgData, null, 2) + "\n", "utf-8");
243
+ }
244
+ }
245
+ try {
246
+ runGit(["add", "--", ...changed], cwd);
247
+ runGit(["commit", "-m", `chore: bump version to ${newVersion}`], cwd);
248
+ } catch {
249
+ }
250
+ if (autoTag) {
251
+ try {
252
+ runGit(["tag", "-a", `${tagPrefix}${newVersion}`, "-m", `Release ${newVersion}`], cwd);
253
+ } catch {
254
+ }
255
+ }
256
+ api.log.info("semver-bump: bumped", { from: currentVersion, to: newVersion, bump: bumpPart });
257
+ api.metrics.counter("version_bump", 1, { bump: bumpPart });
258
+ await api.session.append({
259
+ type: "semver-bump:bumped",
260
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
261
+ from: currentVersion,
262
+ to: newVersion,
263
+ bump: bumpPart
264
+ });
265
+ return {
266
+ ok: true,
267
+ currentVersion,
268
+ newVersion,
269
+ bump: bumpPart,
270
+ tag: `${tagPrefix}${newVersion}`,
271
+ message: `Bumped ${currentVersion} \u2192 ${newVersion} (${bumpPart})`
272
+ };
273
+ }
159
274
  api.tools.register({
160
275
  name: "semver_bump",
161
276
  description: "Determine the next version bump from conventional commits since the last tag, or force a specific bump. Creates a git tag.",
@@ -164,7 +279,7 @@ var plugin = {
164
279
  properties: {
165
280
  cwd: { type: "string", description: "Working directory (defaults to project root)" },
166
281
  dryRun: { type: "boolean", default: false },
167
- part: { type: "string", enum: ["major", "minor", "patch", "auto"], default: "patch", description: "Version part to bump (defaults to patch; use auto to infer from commits)" }
282
+ part: { type: "string", enum: ["major", "minor", "patch", "auto"], default: defaultPart, description: "Version part to bump. Omitted \u2192 the configured default (/settings semver-part, factory default: patch). Use auto to infer from commits." }
168
283
  }
169
284
  },
170
285
  permission: "confirm",
@@ -172,76 +287,62 @@ var plugin = {
172
287
  async execute(input) {
173
288
  const cwd = input["cwd"];
174
289
  const dryRun = input["dryRun"] ?? false;
175
- const part = input["part"] ?? "auto";
176
- const pkg = getPackageJson(cwd);
177
- if (!pkg) {
178
- return { ok: false, error: "No package.json found" };
179
- }
180
- const currentVersion = pkg.version;
181
- let bumpPart = part;
182
- let commits = [];
183
- if (part === "auto") {
290
+ const part = input["part"] ?? defaultPart;
291
+ return performBump(part, dryRun, cwd);
292
+ }
293
+ });
294
+ api.slashCommands.register({
295
+ name: "semver",
296
+ description: "Show the current version or bump it (patch/minor/major/auto)",
297
+ category: "Run",
298
+ argsHint: "[status|patch|minor|major|auto] [--dry]",
299
+ help: [
300
+ "/semver Show current version, latest tag and the suggested bump",
301
+ "/semver status Same as bare /semver",
302
+ "/semver patch Bump the patch version (commit + tag)",
303
+ "/semver minor Bump the minor version (commit + tag)",
304
+ "/semver major Bump the major version (commit + tag)",
305
+ "/semver auto Infer the bump from conventional commits since the last tag",
306
+ "/semver <part> --dry Preview without writing anything"
307
+ ].join("\n"),
308
+ async run(args, ctx) {
309
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
310
+ const dry = tokens.includes("--dry") || tokens.includes("--dry-run");
311
+ const mode = tokens.find((t) => !t.startsWith("--")) ?? "status";
312
+ const cwd = ctx?.cwd;
313
+ if (mode === "status") {
314
+ const pkg = getPackageJson(cwd);
315
+ if (!pkg) return { message: "No package.json found" };
184
316
  let lastTag;
185
317
  try {
186
- const tagsOutput = runGit(["describe", "--tags", "--abbrev=0"], cwd);
187
- lastTag = tagsOutput || void 0;
318
+ lastTag = runGit(["describe", "--tags", "--abbrev=0"], cwd) || void 0;
188
319
  } catch {
189
320
  }
321
+ let suggestion = "patch";
322
+ let commitCount = 0;
190
323
  try {
191
- commits = getRecentCommits(lastTag, cwd);
192
- } catch (err) {
193
- const msg = err instanceof Error ? err.message : String(err);
194
- return { ok: false, error: `Git error: ${msg}`, bumpPart: "patch" };
324
+ const commits = getRecentCommits(lastTag, cwd);
325
+ commitCount = commits.length;
326
+ suggestion = determineBump(commits);
327
+ } catch {
195
328
  }
196
- bumpPart = determineBump(commits);
197
- } else {
198
- bumpPart = part;
199
- }
200
- const newVersion = bumpVersion(currentVersion, bumpPart);
201
- if (dryRun) {
202
329
  return {
203
- ok: true,
204
- dryRun: true,
205
- currentVersion,
206
- suggestedBump: bumpPart,
207
- newVersion,
208
- commitCount: part === "auto" ? commits.length : void 0,
209
- message: `Would bump ${currentVersion} \u2192 ${newVersion} (${bumpPart})`
330
+ message: [
331
+ `Current version: ${pkg.version}`,
332
+ `Latest tag: ${lastTag ?? "(none)"}`,
333
+ `Commits since: ${commitCount}`,
334
+ `Suggested bump: ${suggestion} \u2192 ${bumpVersion(pkg.version, suggestion)}`,
335
+ `Default part: ${defaultPart} (change: /settings semver-part)`,
336
+ "",
337
+ "Run /semver patch|minor|major|auto to apply (add --dry to preview)."
338
+ ].join("\n")
210
339
  };
211
340
  }
212
- const fs = await import('fs');
213
- const pkgPath = cwd ? `${cwd}/package.json` : "package.json";
214
- const pkgData = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
215
- pkgData.version = newVersion;
216
- fs.writeFileSync(pkgPath, JSON.stringify(pkgData, null, 2) + "\n", "utf-8");
217
- try {
218
- runGit(["add", "package.json"], cwd);
219
- runGit(["commit", "-m", `chore: bump version to ${newVersion}`], cwd);
220
- } catch {
221
- }
222
- if (autoTag) {
223
- try {
224
- runGit(["tag", "-a", `${tagPrefix}${newVersion}`, "-m", `Release ${newVersion}`], cwd);
225
- } catch {
226
- }
341
+ if (mode !== "patch" && mode !== "minor" && mode !== "major" && mode !== "auto") {
342
+ return { message: `Unknown mode "${mode}". Use status, patch, minor, major or auto.` };
227
343
  }
228
- api.log.info("semver-bump: bumped", { from: currentVersion, to: newVersion, bump: bumpPart });
229
- api.metrics.counter("version_bump", 1, { bump: bumpPart });
230
- await api.session.append({
231
- type: "semver-bump:bumped",
232
- ts: (/* @__PURE__ */ new Date()).toISOString(),
233
- from: currentVersion,
234
- to: newVersion,
235
- bump: bumpPart
236
- });
237
- return {
238
- ok: true,
239
- currentVersion,
240
- newVersion,
241
- bump: bumpPart,
242
- tag: `${tagPrefix}${newVersion}`,
243
- message: `Bumped ${currentVersion} \u2192 ${newVersion} (${bumpPart})`
244
- };
344
+ const result = await performBump(mode, dry, cwd);
345
+ return { message: String(result["message"] ?? result["error"] ?? JSON.stringify(result)) };
245
346
  }
246
347
  });
247
348
  api.tools.register({
@@ -266,7 +367,7 @@ var plugin = {
266
367
  latestTag = tagsOutput || null;
267
368
  if (latestTag) {
268
369
  const countOutput = runGit(["rev-list", "--count", `${latestTag}..HEAD`], cwd);
269
- commitsSinceTag = Number.parseInt(countOutput) || 0;
370
+ commitsSinceTag = Number.parseInt(countOutput, 10) || 0;
270
371
  }
271
372
  } catch {
272
373
  latestTag = null;
@@ -307,15 +408,7 @@ var plugin = {
307
408
  const spaceIdx = line.indexOf(" ");
308
409
  const hash = line.slice(0, spaceIdx);
309
410
  const message = line.slice(spaceIdx + 1);
310
- const m = message.match(/^(\w+)(!)?(?:\(([^)]+)\))?:\s(.+)/);
311
- const type = m?.[1] ?? "chore";
312
- return {
313
- hash,
314
- type,
315
- scope: m?.[2],
316
- message: m?.[3] ?? message,
317
- breaking: !!m?.[2]
318
- };
411
+ return { hash, ...parseConventional(message) };
319
412
  });
320
413
  } catch (err) {
321
414
  return { ok: false, error: `Failed to get git log: ${err}` };
@@ -345,4 +438,4 @@ var plugin = {
345
438
  };
346
439
  var semver_bump_default = plugin;
347
440
 
348
- export { semver_bump_default as default };
441
+ export { semver_bump_default as default, determineBump, parseConventional };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/plugins",
3
- "version": "0.250.0",
3
+ "version": "0.255.0",
4
4
  "description": "Official WrongStack plugin collection — auto-doc, git-autocommit, shell-check, cost-tracker, file-watcher, web-search, json-path, cron, template-engine, semver-bump",
5
5
  "license": "MIT",
6
6
  "author": "ECOSTACK TECHNOLOGY OÜ",
@@ -57,13 +57,13 @@
57
57
  "dist"
58
58
  ],
59
59
  "devDependencies": {
60
- "@types/node": "^25.9.2",
60
+ "@types/node": "^25.9.3",
61
61
  "tsup": "^8.5.1",
62
62
  "typescript": "^6.0.3",
63
63
  "vitest": "^4.1.8"
64
64
  },
65
65
  "dependencies": {
66
- "@wrongstack/core": "0.250.0"
66
+ "@wrongstack/core": "0.255.0"
67
67
  },
68
68
  "scripts": {
69
69
  "build": "tsup",