@review-my-code/rmcode 0.1.2 → 0.1.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.
Files changed (2) hide show
  1. package/dist/cli.js +121 -14
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ const path_1 = require("path");
9
9
  const readline_1 = require("readline");
10
10
  const API_URL = process.env.RMC_API_URL || "https://review-my-code.com";
11
11
  const APP_URL = process.env.RMC_APP_URL || "https://review-my-code.com";
12
- const VERSION = "0.1.2";
12
+ const VERSION = "0.1.3";
13
13
  const FREE_PLAN_CREDITS_PER_MONTH = 30;
14
14
  const REQUEST_TIMEOUT_MS = 290_000;
15
15
  const POLL_TIMEOUT_MS = 10 * 60_000;
@@ -834,6 +834,7 @@ ${header}
834
834
  ${style("rmcode <file>", c.cyan)} Review a single local file
835
835
  ${style("rmcode login", c.cyan)} Set up your API key
836
836
  ${style("rmcode install", c.cyan)} Install the GitHub App for automatic PR reviews
837
+ ${style("rmcode update", c.cyan)} Update rmcode to the latest version
837
838
  ${style("rmcode help", c.cyan)} Show this help
838
839
 
839
840
  ${style("OPTIONS", c.bold)}
@@ -889,18 +890,120 @@ ${header}
889
890
  ${style("https://review-my-code.com", c.dim)}
890
891
  `);
891
892
  }
892
- // ── Version check ─────────────────────────────────────────────────────
893
- async function checkForUpdate() {
893
+ // ── Self-update ───────────────────────────────────────────────────────
894
+ const PACKAGE_NAME = "@review-my-code/rmcode";
895
+ const REGISTRY_LATEST_URL = "https://registry.npmjs.org/@review-my-code%2Frmcode/latest";
896
+ /** Resolved real path of the running entry script, for install detection. */
897
+ function entryScriptPath() {
898
+ const binPath = process.argv[1] || "";
894
899
  try {
895
- const resp = await fetch("https://registry.npmjs.org/@review-my-code/rmcode/latest", {
896
- signal: AbortSignal.timeout(3000),
897
- });
898
- if (!resp.ok)
899
- return;
900
- const data = (await resp.json());
901
- if (data.version && data.version !== VERSION) {
902
- console.log(style(`\n Update available: ${VERSION} ${data.version}`, c.yellow));
903
- console.log(style(` Run: npm install -g @review-my-code/rmcode\n`, c.dim));
900
+ return (0, fs_1.realpathSync)(binPath);
901
+ }
902
+ catch {
903
+ return binPath;
904
+ }
905
+ }
906
+ function isNpxInvocation(binPath) {
907
+ // npm's exec cache lives under .../_npx/<hash>/; older layouts use /npx/.
908
+ return /[\\/]_?npx[\\/]/.test(binPath);
909
+ }
910
+ function updateCommandFor(binPath) {
911
+ const ext = process.platform === "win32" ? ".cmd" : "";
912
+ if (/[\\/]pnpm[\\/]/.test(binPath))
913
+ return { cmd: `pnpm${ext}`, args: ["add", "-g", `${PACKAGE_NAME}@latest`] };
914
+ if (/[\\/]bun[\\/]/.test(binPath))
915
+ return { cmd: "bun", args: ["add", "-g", `${PACKAGE_NAME}@latest`] };
916
+ return {
917
+ cmd: `npm${ext}`,
918
+ args: ["install", "-g", `${PACKAGE_NAME}@latest`],
919
+ };
920
+ }
921
+ async function runUpdate() {
922
+ const binPath = entryScriptPath();
923
+ if (isNpxInvocation(binPath)) {
924
+ console.log(`\n ${style("Running via npx — already using the latest published version each run.", c.dim)}\n`);
925
+ return;
926
+ }
927
+ const { cmd, args } = updateCommandFor(binPath);
928
+ console.log(`\n ${style(`Updating ${PACKAGE_NAME} via ${cmd}...`, c.bold)}\n`);
929
+ const result = (0, child_process_1.spawnSync)(cmd, args, { stdio: "inherit" });
930
+ if (result.status === 0) {
931
+ const latest = await fetchLatestVersion().catch(() => null);
932
+ console.log(`\n ${style("✓", c.green)} ${latest
933
+ ? `Updated to v${latest}.`
934
+ : "Updated. Run rmcode --version to confirm."}\n`);
935
+ return;
936
+ }
937
+ console.error(style(`\n Update failed. Run it manually:`, c.red));
938
+ console.error(` ${style([cmd, ...args].join(" "), c.cyan, c.bold)}\n`);
939
+ process.exit(1);
940
+ }
941
+ // ── Update nudge ──────────────────────────────────────────────────────
942
+ //
943
+ // After a successful human-mode review, print at most one dim line when a
944
+ // newer version is published. The registry is queried at most once per
945
+ // 24h (cached in ~/.config/rmcode/update-check.json) and only after the
946
+ // review output has been printed. Any failure here is silently swallowed
947
+ // — the nudge must never affect review output or exit codes.
948
+ const UPDATE_CHECK_TIMEOUT_MS = 1_500;
949
+ const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
950
+ function updateCachePath() {
951
+ return (0, path_1.join)((0, os_1.homedir)(), ".config", "rmcode", "update-check.json");
952
+ }
953
+ async function fetchLatestVersion() {
954
+ const resp = await fetch(REGISTRY_LATEST_URL, {
955
+ signal: AbortSignal.timeout(UPDATE_CHECK_TIMEOUT_MS),
956
+ });
957
+ if (!resp.ok)
958
+ return null;
959
+ const data = (await resp.json());
960
+ return typeof data.version === "string" ? data.version : null;
961
+ }
962
+ /** Latest published version, from the daily cache when fresh. */
963
+ async function latestPublishedVersion() {
964
+ const cachePath = updateCachePath();
965
+ try {
966
+ const cached = JSON.parse((0, fs_1.readFileSync)(cachePath, "utf-8"));
967
+ if (typeof cached.latest === "string" &&
968
+ typeof cached.checkedAt === "number" &&
969
+ Date.now() - cached.checkedAt < UPDATE_CHECK_TTL_MS) {
970
+ return cached.latest;
971
+ }
972
+ }
973
+ catch {
974
+ // Missing or unreadable cache — fall through to a fresh fetch.
975
+ }
976
+ const latest = await fetchLatestVersion();
977
+ if (!latest)
978
+ return null;
979
+ try {
980
+ (0, fs_1.mkdirSync)((0, path_1.join)((0, os_1.homedir)(), ".config", "rmcode"), { recursive: true });
981
+ (0, fs_1.writeFileSync)(cachePath, JSON.stringify({ latest, checkedAt: Date.now() }));
982
+ }
983
+ catch {
984
+ // Cache writes are best-effort.
985
+ }
986
+ return latest;
987
+ }
988
+ /** Hand-rolled x.y.z compare: 1 if a > b, -1 if a < b, 0 if equal. */
989
+ function compareVersions(a, b) {
990
+ const pa = a.split(".").map((n) => parseInt(n, 10));
991
+ const pb = b.split(".").map((n) => parseInt(n, 10));
992
+ for (let i = 0; i < 3; i++) {
993
+ const da = pa[i] || 0;
994
+ const db = pb[i] || 0;
995
+ if (da !== db)
996
+ return da > db ? 1 : -1;
997
+ }
998
+ return 0;
999
+ }
1000
+ async function maybeShowUpdateNudge() {
1001
+ if (noColor || isCI)
1002
+ return;
1003
+ try {
1004
+ const latest = await latestPublishedVersion();
1005
+ if (latest && compareVersions(latest, VERSION) > 0) {
1006
+ console.log(style(` Update available ${VERSION} → ${latest} · run rmcode update`, c.dim));
904
1007
  }
905
1008
  }
906
1009
  catch {
@@ -958,6 +1061,10 @@ async function main() {
958
1061
  console.error(style(` Run: rmcode help\n`, c.dim));
959
1062
  process.exit(1);
960
1063
  }
1064
+ if (positional[0] === "update") {
1065
+ await runUpdate();
1066
+ return;
1067
+ }
961
1068
  if (positional[0] === "install") {
962
1069
  console.log(`\n ${style("Install the GitHub App for automatic PR reviews:", c.bold)}\n`);
963
1070
  console.log(` ${style("https://github.com/apps/rmcode-ai", c.cyan, c.bold)}\n`);
@@ -1056,7 +1163,7 @@ async function main() {
1056
1163
  }
1057
1164
  await runReview(code, [file], file, jsonMode, failOnFindings, "file", detectLanguage([file]));
1058
1165
  if (!jsonMode)
1059
- await checkForUpdate();
1166
+ await maybeShowUpdateNudge();
1060
1167
  return;
1061
1168
  }
1062
1169
  // Git modes
@@ -1108,7 +1215,7 @@ async function main() {
1108
1215
  console.log();
1109
1216
  }
1110
1217
  if (!jsonMode)
1111
- await checkForUpdate();
1218
+ await maybeShowUpdateNudge();
1112
1219
  }
1113
1220
  main()
1114
1221
  .then(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@review-my-code/rmcode",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "AI code review from your terminal. Catches logic errors, null risks, security holes, and broken error handling.",
5
5
  "keywords": [
6
6
  "code-review",