@kyubiware/commit-mint 0.7.2 → 0.7.3

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/README.md CHANGED
@@ -31,10 +31,23 @@ API key. The key is saved to `~/.commit-mint` (INI).
31
31
  cmint # interactive: stage → checks → review → commit
32
32
  cmint -a # auto-group, generate messages, commit everything
33
33
  cmint config # edit provider, model, locale, etc.
34
+ cmint update # update cmint to the latest published version
34
35
  ```
35
36
 
36
37
  See [all flags](#all-flags) for the full list.
37
38
 
39
+ ## Self-update (`cmint update`)
40
+
41
+ `cmint update` checks the npm registry for a newer version and, if one exists,
42
+ runs the appropriate global install command for your package manager (detected
43
+ from `npm_config_user_agent` — npm, pnpm, yarn, or bun; falls back to npm).
44
+
45
+ - If you're already on the latest version, it exits silently.
46
+ - Otherwise it prints `current → latest` and asks for confirmation before
47
+ running the install (live npm output is streamed to your terminal).
48
+ - `cmint update -y` (or `--yes`) skips the confirmation prompt — useful in
49
+ scripts.
50
+
38
51
  ## Pre-flight checks (`.cmintrc`)
39
52
 
40
53
  `.cmintrc` is commit-mint's pre-commit check system. The config syntax is
package/dist/cli.mjs CHANGED
@@ -29,7 +29,7 @@ var __exportAll = (all, no_symbols) => {
29
29
  //#region package.json
30
30
  var package_default = {
31
31
  name: "@kyubiware/commit-mint",
32
- version: "0.7.2",
32
+ version: "0.7.3",
33
33
  description: "🌿 AI-powered git commit tool — auto-group changed files, generate messages, run pre-commit checks",
34
34
  type: "module",
35
35
  bin: { "cmint": "./dist/cli.mjs" },
@@ -2311,7 +2311,7 @@ async function agentCommand(flags) {
2311
2311
  //#endregion
2312
2312
  //#region src/services/update-check.ts
2313
2313
  const REGISTRY_URL = "https://registry.npmjs.org/-/package/@kyubiware/commit-mint/dist-tags";
2314
- const PACKAGE_NAME = "@kyubiware/commit-mint";
2314
+ const PACKAGE_NAME$1 = "@kyubiware/commit-mint";
2315
2315
  const TTL_MS = 1440 * 60 * 1e3;
2316
2316
  const FETCH_TIMEOUT_MS = 5e3;
2317
2317
  let cachePath = join(os.homedir(), ".cache", "commit-mint", "update-check.json");
@@ -2396,7 +2396,7 @@ async function fetchLatest(parentSignal) {
2396
2396
  }
2397
2397
  function displayNag(current, latest) {
2398
2398
  debug("displayNag: %s → %s", current, latest);
2399
- const message = `Update available: ${yellow(current)} → ${green(latest)}\nRun ${cyan(`npm update -g ${PACKAGE_NAME}`)} to update`;
2399
+ const message = `Update available: ${yellow(current)} → ${green(latest)}\nRun ${cyan(`npm update -g ${PACKAGE_NAME$1}`)} to update`;
2400
2400
  log.warn(message);
2401
2401
  }
2402
2402
  /**
@@ -3372,6 +3372,149 @@ async function logsCommand(flags) {
3372
3372
  for (const line of lines) console.log(line);
3373
3373
  }
3374
3374
  //#endregion
3375
+ //#region src/services/updater.ts
3376
+ const PACKAGE_NAME = "@kyubiware/commit-mint";
3377
+ /**
3378
+ * Detect the active package manager from the `npm_config_user_agent` env var.
3379
+ * Format: "<pm>/<version> <...>" — e.g. "npm/10.2.0 node/v20.10.0".
3380
+ * Empty/undefined input and unrecognized prefixes fall back to "npm".
3381
+ */
3382
+ function detectPackageManager(userAgent) {
3383
+ if (!userAgent) {
3384
+ debug("updater: empty userAgent, defaulting to npm");
3385
+ return "npm";
3386
+ }
3387
+ const prefix = userAgent.split("/")[0];
3388
+ switch (prefix) {
3389
+ case "pnpm": return "pnpm";
3390
+ case "yarn": return "yarn";
3391
+ case "bun": return "bun";
3392
+ case "npm": return "npm";
3393
+ default:
3394
+ debug("updater: unknown userAgent prefix '%s', defaulting to npm", prefix);
3395
+ return "npm";
3396
+ }
3397
+ }
3398
+ /**
3399
+ * Build the global install shell command for the given package manager.
3400
+ * Returned as a single string suitable for `execa(cmd, [], { shell: true })`.
3401
+ */
3402
+ function buildUpdateCommand(pm, packageName = PACKAGE_NAME) {
3403
+ switch (pm) {
3404
+ case "npm": return `npm install -g ${packageName}@latest`;
3405
+ case "pnpm": return `pnpm add -g ${packageName}@latest`;
3406
+ case "yarn": return `yarn global add ${packageName}@latest`;
3407
+ case "bun": return `bun add -g ${packageName}@latest`;
3408
+ }
3409
+ }
3410
+ /**
3411
+ * Fetch the latest version from the npm registry via `npm view <pkg> version`.
3412
+ * Returns the trimmed version string on success, or null on any failure
3413
+ * (non-zero exit, empty stdout, thrown error). Never throws.
3414
+ */
3415
+ async function fetchLatestVersion(packageName = PACKAGE_NAME) {
3416
+ debug("updater: fetching latest version for %s", packageName);
3417
+ try {
3418
+ const result = await execa("npm", [
3419
+ "view",
3420
+ packageName,
3421
+ "version"
3422
+ ], { reject: false });
3423
+ if (result.exitCode !== 0) {
3424
+ debug("updater: npm view exited %d", result.exitCode);
3425
+ return null;
3426
+ }
3427
+ const trimmed = result.stdout.trim();
3428
+ if (!trimmed) {
3429
+ debug("updater: npm view returned empty stdout");
3430
+ return null;
3431
+ }
3432
+ debug("updater: latest=%s", trimmed);
3433
+ return trimmed;
3434
+ } catch (err) {
3435
+ debug("updater: fetchLatestVersion error — %s", err instanceof Error ? err.message : String(err));
3436
+ return null;
3437
+ }
3438
+ }
3439
+ /**
3440
+ * True iff `latest` is strictly greater than `current` per semver. Returns
3441
+ * false on invalid semver input rather than throwing.
3442
+ */
3443
+ function isUpdateAvailable(current, latest) {
3444
+ try {
3445
+ return semver.gt(latest, current);
3446
+ } catch (err) {
3447
+ debug("updater: isUpdateAvailable error — %s", err instanceof Error ? err.message : String(err));
3448
+ return false;
3449
+ }
3450
+ }
3451
+ /**
3452
+ * Run the global install command for the given package manager. Streams
3453
+ * the installer's live output to the user's terminal via `stdio: "inherit"`.
3454
+ * Returns true iff the install exits with code 0.
3455
+ */
3456
+ async function runUpdate(pm, packageName = PACKAGE_NAME) {
3457
+ const command = buildUpdateCommand(pm, packageName);
3458
+ debug("updater: running %s", command);
3459
+ try {
3460
+ return (await execa(command, [], {
3461
+ shell: true,
3462
+ reject: false,
3463
+ stdio: "inherit"
3464
+ })).exitCode === 0;
3465
+ } catch (err) {
3466
+ debug("updater: runUpdate error — %s", err instanceof Error ? err.message : String(err));
3467
+ return false;
3468
+ }
3469
+ }
3470
+ //#endregion
3471
+ //#region src/commands/update.ts
3472
+ /**
3473
+ * Self-update flow for the installed `cmint` package. Detects the active
3474
+ * package manager from `npm_config_user_agent`, asks the npm registry for the
3475
+ * latest published version, and — if newer — runs the equivalent global
3476
+ * install command after a confirmation prompt (skippable with `--yes`).
3477
+ *
3478
+ * Never throws. Registry failures and install failures are reported through
3479
+ * `p.outro(...)` and `process.exit(1)`; cancellation and "already current"
3480
+ * resolve normally so the CLI exits cleanly.
3481
+ */
3482
+ async function updateCommand(currentVersion, flags) {
3483
+ p.intro("cmint update");
3484
+ const pm = detectPackageManager(process.env.npm_config_user_agent);
3485
+ p.log.info(`Package manager: ${pm}`);
3486
+ p.log.message("Checking latest version...");
3487
+ const latest = await fetchLatestVersion();
3488
+ if (latest === null) {
3489
+ p.outro(red("Could not reach the npm registry. Check your connection and try again."));
3490
+ process.exit(1);
3491
+ return;
3492
+ }
3493
+ if (!isUpdateAvailable(currentVersion, latest)) {
3494
+ p.outro(`Already up-to-date: v${currentVersion}`);
3495
+ return;
3496
+ }
3497
+ p.log.step(`${dim(currentVersion)} → ${green(latest)}`);
3498
+ const cmd = buildUpdateCommand(pm);
3499
+ if (flags?.yes !== true) {
3500
+ const confirmed = await p.confirm({
3501
+ message: `Run \`${cmd}\`?`,
3502
+ initialValue: true
3503
+ });
3504
+ if (p.isCancel(confirmed) || !confirmed) {
3505
+ p.outro("Update cancelled.");
3506
+ return;
3507
+ }
3508
+ }
3509
+ p.log.message(`Running ${cyan(cmd)}...`);
3510
+ if (await runUpdate(pm)) {
3511
+ p.outro(green(`Updated to v${latest}`));
3512
+ return;
3513
+ }
3514
+ p.outro(red("Update failed. See output above."));
3515
+ process.exit(1);
3516
+ }
3517
+ //#endregion
3375
3518
  //#region src/cli.ts
3376
3519
  const { version } = package_default;
3377
3520
  cli({
@@ -3419,19 +3562,34 @@ cli({
3419
3562
  default: false
3420
3563
  }
3421
3564
  },
3422
- commands: [command({
3423
- name: "logs",
3424
- description: "Show debug logs from the last cmint run",
3425
- flags: { lines: {
3426
- type: Number,
3427
- description: "Number of lines to show from the end",
3428
- alias: "n"
3429
- } }
3430
- }, async (argv) => {
3431
- await logsCommand(argv.flags);
3432
- }), command({ name: "config" }, async () => {
3433
- await configCommand();
3434
- })]
3565
+ commands: [
3566
+ command({
3567
+ name: "logs",
3568
+ description: "Show debug logs from the last cmint run",
3569
+ flags: { lines: {
3570
+ type: Number,
3571
+ description: "Number of lines to show from the end",
3572
+ alias: "n"
3573
+ } }
3574
+ }, async (argv) => {
3575
+ await logsCommand(argv.flags);
3576
+ }),
3577
+ command({ name: "config" }, async () => {
3578
+ await configCommand();
3579
+ }),
3580
+ command({
3581
+ name: "update",
3582
+ description: "Update cmint to the latest published version",
3583
+ flags: { yes: {
3584
+ type: Boolean,
3585
+ description: "Skip confirmation prompt",
3586
+ alias: "y",
3587
+ default: false
3588
+ } }
3589
+ }, async (argv) => {
3590
+ await updateCommand(version, argv.flags);
3591
+ })
3592
+ ]
3435
3593
  }, (argv) => {
3436
3594
  writeSessionHeader();
3437
3595
  setDebug(argv.flags.debug);