@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 +4 -0
- package/dist/index.js +280 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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:
|
|
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
|
|
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("
|
|
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
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
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
|