@getcodesentinel/codesentinel 1.17.5 → 1.19.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/README.md CHANGED
@@ -149,6 +149,7 @@ codesentinel report [path]
149
149
  codesentinel check [path]
150
150
  codesentinel ci [path]
151
151
  codesentinel dependency-risk <dependency[@version]>
152
+ codesentinel update
152
153
  ```
153
154
 
154
155
  Examples:
@@ -173,8 +174,11 @@ codesentinel ci --baseline-ref origin/main --max-risk-delta 0.03 --no-new-cycles
173
174
  codesentinel ci --baseline-ref auto --fail-on error
174
175
  codesentinel dependency-risk react
175
176
  codesentinel dependency-risk react@19.0.0
177
+ codesentinel update
176
178
  ```
177
179
 
180
+ Use `codesentinel update` to manually check for a newer CLI release and install it without waiting for the startup notifier.
181
+
178
182
  Author identity mode:
179
183
 
180
184
  ```bash
package/dist/index.js CHANGED
@@ -2809,10 +2809,10 @@ var runGit = async (repositoryPath, args) => {
2809
2809
  };
2810
2810
  var tryRunGit = async (repositoryPath, args) => {
2811
2811
  try {
2812
- const { stdout } = await execFileAsync("git", ["-C", repositoryPath, ...args], {
2812
+ const { stdout: stdout2 } = await execFileAsync("git", ["-C", repositoryPath, ...args], {
2813
2813
  encoding: "utf8"
2814
2814
  });
2815
- return { ok: true, stdout: stdout.trim() };
2815
+ return { ok: true, stdout: stdout2.trim() };
2816
2816
  } catch (error) {
2817
2817
  const message = error instanceof Error ? error.message : "unknown git error";
2818
2818
  return { ok: false, message };
@@ -3336,6 +3336,12 @@ var ANSI = {
3336
3336
  green: "\x1B[32m",
3337
3337
  yellow: "\x1B[33m"
3338
3338
  };
3339
+ var hideCursor = () => {
3340
+ stderr.write("\x1B[?25l");
3341
+ };
3342
+ var showCursor = () => {
3343
+ stderr.write("\x1B[?25h");
3344
+ };
3339
3345
  var parsePrereleaseIdentifier = (identifier) => {
3340
3346
  if (/^\d+$/.test(identifier)) {
3341
3347
  return Number.parseInt(identifier, 10);
@@ -3451,6 +3457,9 @@ var parseNpmViewVersionOutput = (output) => {
3451
3457
  var renderUpdateInProgressMessage = (packageName) => `Updating CodeSentinel via \`npm install -g ${packageName}\`...
3452
3458
  `;
3453
3459
  var renderUpdateSuccessMessage = () => "\u{1F389} Update ran successfully! Please restart CodeSentinel.\n";
3460
+ var renderAlreadyUpToDateMessage = (currentVersion) => `CodeSentinel is already up to date (${currentVersion}).
3461
+ `;
3462
+ var renderUpdateCheckFailedMessage = () => "CodeSentinel could not check for updates right now. Please try again later.\n";
3454
3463
  var readCache = async () => {
3455
3464
  try {
3456
3465
  const raw = await readFile2(UPDATE_CACHE_PATH, "utf8");
@@ -3536,7 +3545,7 @@ var renderUpdatePrompt = (packageName, latestVersion, currentVersion, selectedIn
3536
3545
  return `${prefix} ${text}`;
3537
3546
  }),
3538
3547
  "",
3539
- ` ${ANSI.dim}Use \u2191/\u2193 to choose. Press enter to continue${ANSI.reset}`
3548
+ ` ${ANSI.dim}Use \u2191/\u2193 to choose. Press enter to continue. Press q or Ctrl+C to exit.${ANSI.reset}`
3540
3549
  ];
3541
3550
  stderr.write(lines.join("\n"));
3542
3551
  return lines.length;
@@ -3568,6 +3577,7 @@ var promptInstall = async (packageName, latestVersion, currentVersion) => {
3568
3577
  stdin.setRawMode(previousRawMode);
3569
3578
  }
3570
3579
  clearPromptArea();
3580
+ showCursor();
3571
3581
  if (choice === "install") {
3572
3582
  stderr.write(`${ANSI.yellow}${renderUpdateInProgressMessage(packageName)}${ANSI.reset}`);
3573
3583
  } else {
@@ -3577,7 +3587,11 @@ var promptInstall = async (packageName, latestVersion, currentVersion) => {
3577
3587
  };
3578
3588
  const onKeypress = (_str, key) => {
3579
3589
  if (key.ctrl === true && key.name === "c") {
3580
- cleanup("interrupt");
3590
+ cleanup("skip");
3591
+ return;
3592
+ }
3593
+ if (key.name === "q") {
3594
+ cleanup("skip");
3581
3595
  return;
3582
3596
  }
3583
3597
  if (key.name === "up") {
@@ -3596,6 +3610,7 @@ var promptInstall = async (packageName, latestVersion, currentVersion) => {
3596
3610
  };
3597
3611
  stdin.on("keypress", onKeypress);
3598
3612
  if (typeof stdin.setRawMode === "function") {
3613
+ hideCursor();
3599
3614
  stdin.setRawMode(true);
3600
3615
  }
3601
3616
  stdin.resume();
@@ -3606,6 +3621,35 @@ var installLatestVersion = async (packageName) => {
3606
3621
  const result = await runCommand("npm", ["install", "-g", `${packageName}@latest`], "inherit");
3607
3622
  return result.code === 0;
3608
3623
  };
3624
+ var runManualCliUpdate = async (input) => {
3625
+ const latestVersion = await fetchLatestVersion(input.packageName);
3626
+ if (latestVersion === null) {
3627
+ stderr.write(renderUpdateCheckFailedMessage());
3628
+ return 1;
3629
+ }
3630
+ const comparison = compareVersions(latestVersion, input.currentVersion);
3631
+ if (comparison === null) {
3632
+ stderr.write(renderUpdateCheckFailedMessage());
3633
+ return 1;
3634
+ }
3635
+ if (comparison <= 0) {
3636
+ stderr.write(renderAlreadyUpToDateMessage(input.currentVersion));
3637
+ return 0;
3638
+ }
3639
+ const choice = await promptInstall(input.packageName, latestVersion, input.currentVersion);
3640
+ if (choice !== "install") {
3641
+ return 0;
3642
+ }
3643
+ const installed = await installLatestVersion(input.packageName);
3644
+ if (installed) {
3645
+ stderr.write(renderUpdateSuccessMessage());
3646
+ return 0;
3647
+ }
3648
+ stderr.write(
3649
+ "CodeSentinel update failed. You can retry with: npm install -g @getcodesentinel/codesentinel@latest\n"
3650
+ );
3651
+ return 1;
3652
+ };
3609
3653
  var checkForCliUpdates = async (input) => {
3610
3654
  try {
3611
3655
  const nowMs = Date.now();
@@ -3630,9 +3674,6 @@ var checkForCliUpdates = async (input) => {
3630
3674
  return;
3631
3675
  }
3632
3676
  const choice = await promptInstall(input.packageName, latestVersion, input.currentVersion);
3633
- if (choice === "interrupt") {
3634
- process.exit(130);
3635
- }
3636
3677
  if (choice !== "install") {
3637
3678
  return;
3638
3679
  }
@@ -3649,6 +3690,217 @@ var checkForCliUpdates = async (input) => {
3649
3690
  }
3650
3691
  };
3651
3692
 
3693
+ // src/application/interactive-menu.ts
3694
+ import { spawn as spawn2 } from "child_process";
3695
+ import { stderr as stderr2, stdin as stdin2, stdout } from "process";
3696
+ import { clearScreenDown as clearScreenDown2, cursorTo as cursorTo2, emitKeypressEvents as emitKeypressEvents2, moveCursor } from "readline";
3697
+ import { createInterface as createPromisesInterface } from "readline/promises";
3698
+ var ANSI2 = {
3699
+ reset: "\x1B[0m",
3700
+ bold: "\x1B[1m",
3701
+ dim: "\x1B[2m",
3702
+ cyan: "\x1B[36m",
3703
+ green: "\x1B[32m"
3704
+ };
3705
+ var renderMenu = (currentVersion, actions, selectedIndex) => {
3706
+ const optionLabels = actions.map((action, index) => `${index + 1}. ${action.label}`);
3707
+ const labelWidth = optionLabels.reduce((max, label) => Math.max(max, label.length), 0);
3708
+ const lines = [
3709
+ ` ${ANSI2.bold}${ANSI2.cyan}CodeSentinel${ANSI2.reset} ${ANSI2.dim}v${currentVersion}${ANSI2.reset}`,
3710
+ "",
3711
+ " Choose an action:",
3712
+ "",
3713
+ ...actions.map((action, index) => {
3714
+ const selected = index === selectedIndex;
3715
+ const prefix = selected ? `${ANSI2.green}>${ANSI2.reset}` : " ";
3716
+ const label = optionLabels[index]?.padEnd(labelWidth, " ") ?? "";
3717
+ const renderedLabel = selected ? `${ANSI2.bold}${label}${ANSI2.reset}` : label;
3718
+ return `${prefix} ${renderedLabel} ${ANSI2.dim}${action.description}${ANSI2.reset}`;
3719
+ }),
3720
+ "",
3721
+ ` ${ANSI2.dim}Use \u2191/\u2193 to choose. Press enter to continue. Press q or Ctrl+C to exit.${ANSI2.reset}`
3722
+ ];
3723
+ stderr2.write(lines.join("\n"));
3724
+ };
3725
+ var clearTerminal = () => {
3726
+ cursorTo2(stderr2, 0, 0);
3727
+ clearScreenDown2(stderr2);
3728
+ };
3729
+ var hideCursor2 = () => {
3730
+ stderr2.write("\x1B[?25l");
3731
+ };
3732
+ var showCursor2 = () => {
3733
+ stderr2.write("\x1B[?25h");
3734
+ };
3735
+ var promptSelection = async (currentVersion, actions) => {
3736
+ if (!stdin2.isTTY || !stderr2.isTTY || typeof stdin2.setRawMode !== "function") {
3737
+ return "exit";
3738
+ }
3739
+ return await new Promise((resolve6) => {
3740
+ emitKeypressEvents2(stdin2);
3741
+ let selectedIndex = 0;
3742
+ const previousRawMode = stdin2.isRaw;
3743
+ const redraw = () => {
3744
+ clearTerminal();
3745
+ renderMenu(currentVersion, actions, selectedIndex);
3746
+ moveCursor(stderr2, -1, 0);
3747
+ };
3748
+ const cleanup = (selection) => {
3749
+ stdin2.off("keypress", onKeypress);
3750
+ stdin2.pause();
3751
+ stdin2.setRawMode(previousRawMode);
3752
+ clearTerminal();
3753
+ showCursor2();
3754
+ resolve6(selection);
3755
+ };
3756
+ const onKeypress = (_str, key) => {
3757
+ if (key.ctrl === true && key.name === "c") {
3758
+ cleanup("exit");
3759
+ return;
3760
+ }
3761
+ if (key.name === "q") {
3762
+ cleanup("exit");
3763
+ return;
3764
+ }
3765
+ if (key.name === "up") {
3766
+ selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : actions.length - 1;
3767
+ redraw();
3768
+ return;
3769
+ }
3770
+ if (key.name === "down") {
3771
+ selectedIndex = selectedIndex < actions.length - 1 ? selectedIndex + 1 : 0;
3772
+ redraw();
3773
+ return;
3774
+ }
3775
+ if (key.name === "return" || key.name === "enter") {
3776
+ cleanup(selectedIndex);
3777
+ }
3778
+ };
3779
+ stdin2.on("keypress", onKeypress);
3780
+ hideCursor2();
3781
+ stdin2.setRawMode(true);
3782
+ stdin2.resume();
3783
+ redraw();
3784
+ });
3785
+ };
3786
+ var createPrompt = () => createPromisesInterface({
3787
+ input: stdin2,
3788
+ output: stderr2
3789
+ });
3790
+ var promptText = async (prompt, label, defaultValue) => {
3791
+ const suffix = defaultValue === void 0 ? "" : ` [${defaultValue}]`;
3792
+ const answer = await prompt.question(
3793
+ `${label}${suffix}: `
3794
+ );
3795
+ const trimmed = answer.trim();
3796
+ return trimmed.length > 0 ? trimmed : defaultValue ?? "";
3797
+ };
3798
+ var buildDependencyRiskArgs = async () => {
3799
+ const prompt = createPrompt();
3800
+ try {
3801
+ const dependency = await promptText(prompt, "Dependency spec", "");
3802
+ if (dependency.length === 0) {
3803
+ stderr2.write("A dependency spec is required.\n");
3804
+ return null;
3805
+ }
3806
+ return ["dependency-risk", dependency];
3807
+ } finally {
3808
+ prompt.close();
3809
+ }
3810
+ };
3811
+ var waitForReturnToMenu = async () => {
3812
+ const prompt = createPromisesInterface({
3813
+ input: stdin2,
3814
+ output: stderr2
3815
+ });
3816
+ try {
3817
+ await prompt.question("Press enter to return to the menu...");
3818
+ } finally {
3819
+ prompt.close();
3820
+ }
3821
+ };
3822
+ var runCliCommand = async (scriptPath2, args) => {
3823
+ return await new Promise((resolve6, reject) => {
3824
+ const child = spawn2(process.execPath, [...process.execArgv, scriptPath2, ...args], {
3825
+ stdio: "inherit",
3826
+ env: {
3827
+ ...process.env,
3828
+ CODESENTINEL_NO_UPDATE_NOTIFIER: "1"
3829
+ }
3830
+ });
3831
+ child.on("error", (error) => {
3832
+ reject(error);
3833
+ });
3834
+ child.on("close", (code) => {
3835
+ resolve6(code ?? 1);
3836
+ });
3837
+ });
3838
+ };
3839
+ var runInteractiveCliMenu = async (input) => {
3840
+ if (!stdin2.isTTY || !stderr2.isTTY || !stdout.isTTY) {
3841
+ stderr2.write("Interactive menu requires a TTY.\n");
3842
+ return 1;
3843
+ }
3844
+ const actions = [
3845
+ {
3846
+ label: "Run overview",
3847
+ description: "combined analyze + explain + report",
3848
+ commandBuilder: () => ["run"]
3849
+ },
3850
+ {
3851
+ label: "Analyze repository",
3852
+ description: "structural and health scoring summary",
3853
+ commandBuilder: () => ["analyze"]
3854
+ },
3855
+ {
3856
+ label: "Explain hotspots",
3857
+ description: "top findings in markdown by default",
3858
+ commandBuilder: () => ["explain", "--format", "md"]
3859
+ },
3860
+ {
3861
+ label: "Generate report",
3862
+ description: "create a full report for a repository",
3863
+ commandBuilder: () => ["report", "--format", "md"]
3864
+ },
3865
+ {
3866
+ label: "Run policy check",
3867
+ description: "execute governance gates",
3868
+ commandBuilder: () => ["check"]
3869
+ },
3870
+ {
3871
+ label: "Scan dependency risk",
3872
+ description: "inspect a package from the registry",
3873
+ commandBuilder: buildDependencyRiskArgs
3874
+ }
3875
+ ];
3876
+ while (true) {
3877
+ const selectedIndex = await promptSelection(input.currentVersion, actions);
3878
+ if (selectedIndex === "exit") {
3879
+ stderr2.write("\n");
3880
+ return 0;
3881
+ }
3882
+ const selectedAction = actions[selectedIndex];
3883
+ if (selectedAction === void 0) {
3884
+ stderr2.write("\n");
3885
+ return 1;
3886
+ }
3887
+ const args = await selectedAction.commandBuilder();
3888
+ if (args === null) {
3889
+ await waitForReturnToMenu();
3890
+ continue;
3891
+ }
3892
+ const exitCode = await runCliCommand(input.scriptPath, args);
3893
+ if (exitCode !== 0) {
3894
+ stderr2.write(`
3895
+ Command exited with code ${exitCode}.
3896
+ `);
3897
+ } else {
3898
+ stderr2.write("\n");
3899
+ }
3900
+ await waitForReturnToMenu();
3901
+ }
3902
+ };
3903
+
3652
3904
  // src/application/run-analyze-command.ts
3653
3905
  import { resolve as resolve3 } from "path";
3654
3906
 
@@ -7486,6 +7738,12 @@ var scoringProfileOption = () => new Option(
7486
7738
  "scoring profile: default (balanced) or personal (down-weights single-maintainer ownership penalties for risk and health ownership)"
7487
7739
  ).choices(["default", "personal"]).default("default");
7488
7740
  program.name("codesentinel").description("Structural and evolutionary risk analysis for TypeScript/JavaScript codebases").version(version);
7741
+ program.command("update").description("check for a newer CodeSentinel version and install it").action(async () => {
7742
+ process.exitCode = await runManualCliUpdate({
7743
+ packageName: "@getcodesentinel/codesentinel",
7744
+ currentVersion: version
7745
+ });
7746
+ });
7489
7747
  program.command("analyze").argument("[path]", "path to the project to analyze").addOption(scoringProfileOption()).addOption(
7490
7748
  new Option(
7491
7749
  "--author-identity <mode>",
@@ -8011,22 +8269,27 @@ program.command("ci").argument("[path]", "path to the project to analyze").addOp
8011
8269
  }
8012
8270
  }
8013
8271
  );
8014
- if (process.argv.length <= 2) {
8015
- program.outputHelp();
8016
- process.exit(0);
8017
- }
8018
8272
  var executablePath = process.argv[0] ?? "";
8019
8273
  var scriptPath = process.argv[1] ?? "";
8020
8274
  var argv = process.argv[2] === "--" ? [executablePath, scriptPath, ...process.argv.slice(3)] : process.argv;
8021
8275
  if (argv.length <= 2) {
8276
+ if (process.stdin.isTTY && process.stdout.isTTY && process.stderr.isTTY) {
8277
+ process.exitCode = await runInteractiveCliMenu({
8278
+ currentVersion: version,
8279
+ scriptPath
8280
+ });
8281
+ process.exit(process.exitCode ?? 0);
8282
+ }
8022
8283
  program.outputHelp();
8023
8284
  process.exit(0);
8024
8285
  }
8025
- await checkForCliUpdates({
8026
- packageName: "@getcodesentinel/codesentinel",
8027
- currentVersion: version,
8028
- argv: process.argv,
8029
- env: process.env
8030
- });
8286
+ if (argv[2] !== "update") {
8287
+ await checkForCliUpdates({
8288
+ packageName: "@getcodesentinel/codesentinel",
8289
+ currentVersion: version,
8290
+ argv: process.argv,
8291
+ env: process.env
8292
+ });
8293
+ }
8031
8294
  await program.parseAsync(argv);
8032
8295
  //# sourceMappingURL=index.js.map