@getcodesentinel/codesentinel 1.11.0 → 1.12.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
@@ -2463,7 +2463,7 @@ var resolveAutoBaselineRef = async (input) => {
2463
2463
 
2464
2464
  // src/index.ts
2465
2465
  import { readFileSync as readFileSync2 } from "fs";
2466
- import { dirname, resolve as resolve5 } from "path";
2466
+ import { dirname as dirname2, resolve as resolve5 } from "path";
2467
2467
  import { fileURLToPath } from "url";
2468
2468
 
2469
2469
  // src/application/format-analyze-output.ts
@@ -2857,6 +2857,337 @@ var parseLogLevel = (value) => {
2857
2857
  }
2858
2858
  };
2859
2859
 
2860
+ // src/application/check-for-updates.ts
2861
+ import { spawn } from "child_process";
2862
+ import { mkdir, readFile, writeFile } from "fs/promises";
2863
+ import { homedir } from "os";
2864
+ import { dirname, join as join3 } from "path";
2865
+ import { stderr, stdin } from "process";
2866
+ import { clearScreenDown, cursorTo, emitKeypressEvents, moveCursor } from "readline";
2867
+ var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
2868
+ var UPDATE_CACHE_PATH = join3(homedir(), ".cache", "codesentinel", "update-check.json");
2869
+ var SEMVER_PATTERN = /^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?:-(?<prerelease>[0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
2870
+ var ANSI = {
2871
+ reset: "\x1B[0m",
2872
+ bold: "\x1B[1m",
2873
+ dim: "\x1B[2m",
2874
+ cyan: "\x1B[36m",
2875
+ green: "\x1B[32m",
2876
+ yellow: "\x1B[33m"
2877
+ };
2878
+ var parsePrereleaseIdentifier = (identifier) => {
2879
+ if (/^\d+$/.test(identifier)) {
2880
+ return Number.parseInt(identifier, 10);
2881
+ }
2882
+ return identifier;
2883
+ };
2884
+ var parseSemver2 = (value) => {
2885
+ const match = SEMVER_PATTERN.exec(value.trim());
2886
+ if (match === null) {
2887
+ return null;
2888
+ }
2889
+ const groups = match.groups;
2890
+ if (groups === void 0) {
2891
+ return null;
2892
+ }
2893
+ const majorRaw = groups["major"];
2894
+ const minorRaw = groups["minor"];
2895
+ const patchRaw = groups["patch"];
2896
+ const prereleaseRaw = groups["prerelease"];
2897
+ if (majorRaw === void 0 || minorRaw === void 0 || patchRaw === void 0) {
2898
+ return null;
2899
+ }
2900
+ const prerelease = prereleaseRaw === void 0 || prereleaseRaw.length === 0 ? [] : prereleaseRaw.split(".").map(parsePrereleaseIdentifier);
2901
+ return {
2902
+ major: Number.parseInt(majorRaw, 10),
2903
+ minor: Number.parseInt(minorRaw, 10),
2904
+ patch: Number.parseInt(patchRaw, 10),
2905
+ prerelease
2906
+ };
2907
+ };
2908
+ var comparePrerelease = (left, right) => {
2909
+ if (left.length === 0 && right.length === 0) {
2910
+ return 0;
2911
+ }
2912
+ if (left.length === 0) {
2913
+ return 1;
2914
+ }
2915
+ if (right.length === 0) {
2916
+ return -1;
2917
+ }
2918
+ const length = Math.max(left.length, right.length);
2919
+ for (let index = 0; index < length; index += 1) {
2920
+ const leftValue = left[index];
2921
+ const rightValue = right[index];
2922
+ if (leftValue === void 0) {
2923
+ return -1;
2924
+ }
2925
+ if (rightValue === void 0) {
2926
+ return 1;
2927
+ }
2928
+ if (typeof leftValue === "number" && typeof rightValue === "number") {
2929
+ if (leftValue !== rightValue) {
2930
+ return leftValue > rightValue ? 1 : -1;
2931
+ }
2932
+ continue;
2933
+ }
2934
+ if (typeof leftValue === "number" && typeof rightValue === "string") {
2935
+ return -1;
2936
+ }
2937
+ if (typeof leftValue === "string" && typeof rightValue === "number") {
2938
+ return 1;
2939
+ }
2940
+ if (leftValue !== rightValue) {
2941
+ return leftValue > rightValue ? 1 : -1;
2942
+ }
2943
+ }
2944
+ return 0;
2945
+ };
2946
+ var compareVersions = (left, right) => {
2947
+ const leftParsed = parseSemver2(left);
2948
+ const rightParsed = parseSemver2(right);
2949
+ if (leftParsed === null || rightParsed === null) {
2950
+ return null;
2951
+ }
2952
+ if (leftParsed.major !== rightParsed.major) {
2953
+ return leftParsed.major > rightParsed.major ? 1 : -1;
2954
+ }
2955
+ if (leftParsed.minor !== rightParsed.minor) {
2956
+ return leftParsed.minor > rightParsed.minor ? 1 : -1;
2957
+ }
2958
+ if (leftParsed.patch !== rightParsed.patch) {
2959
+ return leftParsed.patch > rightParsed.patch ? 1 : -1;
2960
+ }
2961
+ return comparePrerelease(leftParsed.prerelease, rightParsed.prerelease);
2962
+ };
2963
+ var isTruthy = (value) => {
2964
+ if (value === void 0) {
2965
+ return false;
2966
+ }
2967
+ return ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
2968
+ };
2969
+ var parseNpmViewVersionOutput = (output) => {
2970
+ const trimmed = output.trim();
2971
+ if (trimmed.length === 0) {
2972
+ return null;
2973
+ }
2974
+ try {
2975
+ const parsed = JSON.parse(trimmed);
2976
+ if (typeof parsed === "string" && parsed.trim().length > 0) {
2977
+ return parsed.trim();
2978
+ }
2979
+ if (Array.isArray(parsed) && parsed.length > 0) {
2980
+ const latest = parsed.at(-1);
2981
+ if (typeof latest === "string" && latest.trim().length > 0) {
2982
+ return latest.trim();
2983
+ }
2984
+ }
2985
+ } catch {
2986
+ return trimmed;
2987
+ }
2988
+ return null;
2989
+ };
2990
+ var readCache = async () => {
2991
+ try {
2992
+ const raw = await readFile(UPDATE_CACHE_PATH, "utf8");
2993
+ const parsed = JSON.parse(raw);
2994
+ if (typeof parsed === "object" && parsed !== null && typeof parsed.lastCheckedAt === "string") {
2995
+ return { lastCheckedAt: parsed.lastCheckedAt };
2996
+ }
2997
+ } catch {
2998
+ return null;
2999
+ }
3000
+ return null;
3001
+ };
3002
+ var writeCache = async (cache) => {
3003
+ await mkdir(dirname(UPDATE_CACHE_PATH), { recursive: true });
3004
+ await writeFile(UPDATE_CACHE_PATH, JSON.stringify(cache), "utf8");
3005
+ };
3006
+ var shouldRunUpdateCheck = (input) => {
3007
+ if (!input.isInteractive) {
3008
+ return false;
3009
+ }
3010
+ if (isTruthy(input.env["CI"])) {
3011
+ return false;
3012
+ }
3013
+ if (isTruthy(input.env["CODESENTINEL_NO_UPDATE_NOTIFIER"])) {
3014
+ return false;
3015
+ }
3016
+ if (input.argv.some((argument) => argument === "--help" || argument === "-h")) {
3017
+ return false;
3018
+ }
3019
+ if (input.argv.some((argument) => argument === "--version" || argument === "-V")) {
3020
+ return false;
3021
+ }
3022
+ if (input.lastCheckedAt === null) {
3023
+ return true;
3024
+ }
3025
+ const lastCheckedMs = Date.parse(input.lastCheckedAt);
3026
+ if (!Number.isFinite(lastCheckedMs)) {
3027
+ return true;
3028
+ }
3029
+ return input.nowMs - lastCheckedMs >= UPDATE_CHECK_INTERVAL_MS;
3030
+ };
3031
+ var runCommand = async (command, args, mode) => {
3032
+ return await new Promise((resolvePromise, reject) => {
3033
+ const child = spawn(command, [...args], {
3034
+ stdio: mode === "inherit" ? "inherit" : ["ignore", "pipe", "pipe"]
3035
+ });
3036
+ let stdoutRaw = "";
3037
+ if (mode === "capture" && child.stdout !== null) {
3038
+ child.stdout.setEncoding("utf8");
3039
+ child.stdout.on("data", (chunk) => {
3040
+ stdoutRaw += chunk;
3041
+ });
3042
+ }
3043
+ child.on("error", (error) => {
3044
+ reject(error);
3045
+ });
3046
+ child.on("close", (code) => {
3047
+ resolvePromise({ code: code ?? 1, stdout: stdoutRaw });
3048
+ });
3049
+ });
3050
+ };
3051
+ var fetchLatestVersion = async (packageName) => {
3052
+ const result = await runCommand("npm", ["view", packageName, "version", "--json"], "capture");
3053
+ if (result.code !== 0) {
3054
+ return null;
3055
+ }
3056
+ return parseNpmViewVersionOutput(result.stdout);
3057
+ };
3058
+ var renderUpdatePrompt = (latestVersion, currentVersion, selectedIndex) => {
3059
+ const options = ["Install update now", "Not now (continue current command)"];
3060
+ const lines = [
3061
+ `${ANSI.cyan}${ANSI.bold}CodeSentinel Update Available${ANSI.reset}`,
3062
+ `${ANSI.dim}Current: ${currentVersion} Latest: ${latestVersion}${ANSI.reset}`,
3063
+ "",
3064
+ ...options.map((option, index) => {
3065
+ const selected = index === selectedIndex;
3066
+ const prefix = selected ? `${ANSI.green}>${ANSI.reset}` : " ";
3067
+ const text = selected ? `${ANSI.bold}${option}${ANSI.reset}` : option;
3068
+ return `${prefix} ${text}`;
3069
+ }),
3070
+ "",
3071
+ `${ANSI.dim}Use \u2191/\u2193 to choose, Enter to confirm.${ANSI.reset}`
3072
+ ];
3073
+ stderr.write(lines.join("\n"));
3074
+ return lines.length;
3075
+ };
3076
+ var promptInstall = async (latestVersion, currentVersion) => {
3077
+ if (!stdin.isTTY || !stderr.isTTY || typeof stdin.setRawMode !== "function") {
3078
+ stderr.write(
3079
+ `New version ${latestVersion} is available (current ${currentVersion}). Run: npm install -g @getcodesentinel/codesentinel@latest
3080
+ `
3081
+ );
3082
+ return "skip";
3083
+ }
3084
+ return await new Promise((resolve6) => {
3085
+ emitKeypressEvents(stdin);
3086
+ let selectedIndex = 0;
3087
+ let renderedLines = 0;
3088
+ const previousRawMode = stdin.isRaw;
3089
+ const clearPromptArea = () => {
3090
+ if (renderedLines > 0) {
3091
+ moveCursor(stderr, 0, -(renderedLines - 1));
3092
+ }
3093
+ cursorTo(stderr, 0);
3094
+ clearScreenDown(stderr);
3095
+ };
3096
+ const redraw = () => {
3097
+ clearPromptArea();
3098
+ renderedLines = renderUpdatePrompt(latestVersion, currentVersion, selectedIndex);
3099
+ };
3100
+ const cleanup = (choice) => {
3101
+ stdin.off("keypress", onKeypress);
3102
+ stdin.pause();
3103
+ if (typeof stdin.setRawMode === "function") {
3104
+ stdin.setRawMode(previousRawMode);
3105
+ }
3106
+ clearPromptArea();
3107
+ if (choice === "install") {
3108
+ stderr.write(`${ANSI.yellow}Installing latest CodeSentinel...${ANSI.reset}
3109
+ `);
3110
+ } else if (renderedLines > 0) {
3111
+ stderr.write("\n");
3112
+ }
3113
+ resolve6(choice);
3114
+ };
3115
+ const onKeypress = (_str, key) => {
3116
+ if (key.ctrl === true && key.name === "c") {
3117
+ cleanup("interrupt");
3118
+ return;
3119
+ }
3120
+ if (key.name === "up") {
3121
+ selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : 1;
3122
+ redraw();
3123
+ return;
3124
+ }
3125
+ if (key.name === "down") {
3126
+ selectedIndex = selectedIndex < 1 ? selectedIndex + 1 : 0;
3127
+ redraw();
3128
+ return;
3129
+ }
3130
+ if (key.name === "return" || key.name === "enter") {
3131
+ cleanup(selectedIndex === 0 ? "install" : "skip");
3132
+ }
3133
+ };
3134
+ stdin.on("keypress", onKeypress);
3135
+ if (typeof stdin.setRawMode === "function") {
3136
+ stdin.setRawMode(true);
3137
+ }
3138
+ stdin.resume();
3139
+ redraw();
3140
+ });
3141
+ };
3142
+ var installLatestVersion = async (packageName) => {
3143
+ const result = await runCommand("npm", ["install", "-g", `${packageName}@latest`], "inherit");
3144
+ return result.code === 0;
3145
+ };
3146
+ var checkForCliUpdates = async (input) => {
3147
+ try {
3148
+ const nowMs = Date.now();
3149
+ const cache = await readCache();
3150
+ const shouldCheck = shouldRunUpdateCheck({
3151
+ argv: input.argv,
3152
+ env: input.env,
3153
+ isInteractive: Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY),
3154
+ nowMs,
3155
+ lastCheckedAt: cache?.lastCheckedAt ?? null
3156
+ });
3157
+ if (!shouldCheck) {
3158
+ return;
3159
+ }
3160
+ await writeCache({ lastCheckedAt: new Date(nowMs).toISOString() });
3161
+ const latestVersion = await fetchLatestVersion(input.packageName);
3162
+ if (latestVersion === null) {
3163
+ return;
3164
+ }
3165
+ const comparison = compareVersions(latestVersion, input.currentVersion);
3166
+ if (comparison === null || comparison <= 0) {
3167
+ return;
3168
+ }
3169
+ const choice = await promptInstall(latestVersion, input.currentVersion);
3170
+ if (choice === "interrupt") {
3171
+ process.exit(130);
3172
+ }
3173
+ if (choice !== "install") {
3174
+ return;
3175
+ }
3176
+ const installed = await installLatestVersion(input.packageName);
3177
+ if (installed) {
3178
+ stderr.write(
3179
+ "CodeSentinel updated to latest version. Rerun your command to use the new version.\n"
3180
+ );
3181
+ process.exit(0);
3182
+ } else {
3183
+ stderr.write(
3184
+ "CodeSentinel update failed. You can retry with: npm install -g @getcodesentinel/codesentinel@latest\n"
3185
+ );
3186
+ }
3187
+ } catch {
3188
+ }
3189
+ };
3190
+
2860
3191
  // src/application/run-analyze-command.ts
2861
3192
  import { resolve as resolve3 } from "path";
2862
3193
 
@@ -5236,7 +5567,7 @@ var runAnalyzeCommand = async (inputPath, authorIdentityMode, options = {}, logg
5236
5567
  };
5237
5568
 
5238
5569
  // src/application/run-check-command.ts
5239
- import { readFile, writeFile } from "fs/promises";
5570
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
5240
5571
 
5241
5572
  // src/application/build-analysis-snapshot.ts
5242
5573
  var buildAnalysisSnapshot = async (inputPath, authorIdentityMode, options, logger) => {
@@ -5301,7 +5632,7 @@ var runCheckCommand = async (inputPath, authorIdentityMode, options, logger = cr
5301
5632
  let diff;
5302
5633
  if (options.baselinePath !== void 0) {
5303
5634
  logger.info(`loading baseline snapshot: ${options.baselinePath}`);
5304
- const baselineRaw = await readFile(options.baselinePath, "utf8");
5635
+ const baselineRaw = await readFile2(options.baselinePath, "utf8");
5305
5636
  try {
5306
5637
  baseline = parseSnapshot(baselineRaw);
5307
5638
  } catch (error) {
@@ -5327,7 +5658,7 @@ var runCheckCommand = async (inputPath, authorIdentityMode, options, logger = cr
5327
5658
  options.outputFormat
5328
5659
  );
5329
5660
  if (options.outputPath !== void 0) {
5330
- await writeFile(options.outputPath, rendered, "utf8");
5661
+ await writeFile2(options.outputPath, rendered, "utf8");
5331
5662
  logger.info(`check output written: ${options.outputPath}`);
5332
5663
  }
5333
5664
  return {
@@ -5340,7 +5671,7 @@ var runCheckCommand = async (inputPath, authorIdentityMode, options, logger = cr
5340
5671
  };
5341
5672
 
5342
5673
  // src/application/run-ci-command.ts
5343
- import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
5674
+ import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
5344
5675
  import { relative as relative2, resolve as resolve4 } from "path";
5345
5676
  var isPathOutsideBase = (value) => {
5346
5677
  return value === ".." || value.startsWith("../") || value.startsWith("..\\");
@@ -5366,7 +5697,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
5366
5697
  logger
5367
5698
  );
5368
5699
  if (options.snapshotPath !== void 0) {
5369
- await writeFile2(options.snapshotPath, JSON.stringify(current, null, 2), "utf8");
5700
+ await writeFile3(options.snapshotPath, JSON.stringify(current, null, 2), "utf8");
5370
5701
  logger.info(`snapshot written: ${options.snapshotPath}`);
5371
5702
  }
5372
5703
  let baseline;
@@ -5438,7 +5769,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
5438
5769
  diff = compareSnapshots(current, baseline);
5439
5770
  } else if (options.baselinePath !== void 0) {
5440
5771
  logger.info(`loading baseline snapshot: ${options.baselinePath}`);
5441
- const baselineRaw = await readFile2(options.baselinePath, "utf8");
5772
+ const baselineRaw = await readFile3(options.baselinePath, "utf8");
5442
5773
  try {
5443
5774
  baseline = parseSnapshot(baselineRaw);
5444
5775
  } catch (error) {
@@ -5460,7 +5791,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
5460
5791
 
5461
5792
  ${ciMarkdown}`;
5462
5793
  if (options.reportPath !== void 0) {
5463
- await writeFile2(options.reportPath, markdownSummary, "utf8");
5794
+ await writeFile3(options.reportPath, markdownSummary, "utf8");
5464
5795
  logger.info(`report written: ${options.reportPath}`);
5465
5796
  }
5466
5797
  const machineReadable = {
@@ -5472,7 +5803,7 @@ ${ciMarkdown}`;
5472
5803
  exitCode: gateResult.exitCode
5473
5804
  };
5474
5805
  if (options.jsonOutputPath !== void 0) {
5475
- await writeFile2(options.jsonOutputPath, JSON.stringify(machineReadable, null, 2), "utf8");
5806
+ await writeFile3(options.jsonOutputPath, JSON.stringify(machineReadable, null, 2), "utf8");
5476
5807
  logger.info(`ci machine output written: ${options.jsonOutputPath}`);
5477
5808
  }
5478
5809
  return {
@@ -5486,7 +5817,7 @@ ${ciMarkdown}`;
5486
5817
  };
5487
5818
 
5488
5819
  // src/application/run-report-command.ts
5489
- import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
5820
+ import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
5490
5821
  var runReportCommand = async (inputPath, authorIdentityMode, options, logger = createSilentLogger()) => {
5491
5822
  logger.info("building analysis snapshot");
5492
5823
  const current = await buildAnalysisSnapshot(
@@ -5499,7 +5830,7 @@ var runReportCommand = async (inputPath, authorIdentityMode, options, logger = c
5499
5830
  logger
5500
5831
  );
5501
5832
  if (options.snapshotPath !== void 0) {
5502
- await writeFile3(options.snapshotPath, JSON.stringify(current, null, 2), "utf8");
5833
+ await writeFile4(options.snapshotPath, JSON.stringify(current, null, 2), "utf8");
5503
5834
  logger.info(`snapshot written: ${options.snapshotPath}`);
5504
5835
  }
5505
5836
  let report;
@@ -5507,14 +5838,14 @@ var runReportCommand = async (inputPath, authorIdentityMode, options, logger = c
5507
5838
  report = createReport(current);
5508
5839
  } else {
5509
5840
  logger.info(`loading baseline snapshot: ${options.comparePath}`);
5510
- const baselineRaw = await readFile3(options.comparePath, "utf8");
5841
+ const baselineRaw = await readFile4(options.comparePath, "utf8");
5511
5842
  const baseline = parseSnapshot(baselineRaw);
5512
5843
  const diff = compareSnapshots(current, baseline);
5513
5844
  report = createReport(current, diff);
5514
5845
  }
5515
5846
  const rendered = formatReport(report, options.format);
5516
5847
  if (options.outputPath !== void 0) {
5517
- await writeFile3(options.outputPath, rendered, "utf8");
5848
+ await writeFile4(options.outputPath, rendered, "utf8");
5518
5849
  logger.info(`report written: ${options.outputPath}`);
5519
5850
  }
5520
5851
  return { report, rendered };
@@ -5568,7 +5899,7 @@ var runExplainCommand = async (inputPath, authorIdentityMode, options, logger =
5568
5899
 
5569
5900
  // src/index.ts
5570
5901
  var program = new Command();
5571
- var packageJsonPath = resolve5(dirname(fileURLToPath(import.meta.url)), "../package.json");
5902
+ var packageJsonPath = resolve5(dirname2(fileURLToPath(import.meta.url)), "../package.json");
5572
5903
  var { version } = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
5573
5904
  var parseRecentWindowDays = (value) => {
5574
5905
  const parsed = Number.parseInt(value, 10);
@@ -5886,5 +6217,11 @@ if (argv.length <= 2) {
5886
6217
  program.outputHelp();
5887
6218
  process.exit(0);
5888
6219
  }
6220
+ await checkForCliUpdates({
6221
+ packageName: "@getcodesentinel/codesentinel",
6222
+ currentVersion: version,
6223
+ argv: process.argv,
6224
+ env: process.env
6225
+ });
5889
6226
  await program.parseAsync(argv);
5890
6227
  //# sourceMappingURL=index.js.map