@chappibunny/repolens 0.4.1

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.
@@ -0,0 +1,26 @@
1
+ const isVerbose = process.argv.includes("--verbose");
2
+ const isTest = process.env.NODE_ENV === "test";
3
+
4
+ export function log(...args) {
5
+ if (!isTest && isVerbose) {
6
+ console.log("[RepoLens]", ...args);
7
+ }
8
+ }
9
+
10
+ export function info(...args) {
11
+ if (!isTest) {
12
+ console.log("[RepoLens]", ...args);
13
+ }
14
+ }
15
+
16
+ export function warn(...args) {
17
+ if (!isTest) {
18
+ console.warn("[RepoLens]", ...args);
19
+ }
20
+ }
21
+
22
+ export function error(...args) {
23
+ if (!isTest) {
24
+ console.error("[RepoLens]", ...args);
25
+ }
26
+ }
@@ -0,0 +1,55 @@
1
+ import { log, warn } from "./logger.js";
2
+
3
+ function sleep(ms) {
4
+ return new Promise((resolve) => setTimeout(resolve, ms));
5
+ }
6
+
7
+ function isRetryableStatus(status) {
8
+ return status === 429 || status === 500 || status === 502 || status === 503 || status === 504;
9
+ }
10
+
11
+ export async function fetchWithRetry(url, options = {}, config = {}) {
12
+ const {
13
+ retries = 3,
14
+ baseDelayMs = 500,
15
+ maxDelayMs = 4000,
16
+ label = "request"
17
+ } = config;
18
+
19
+ let attempt = 0;
20
+ let lastError = null;
21
+
22
+ while (attempt <= retries) {
23
+ try {
24
+ const response = await fetch(url, options);
25
+
26
+ if (!isRetryableStatus(response.status)) {
27
+ return response;
28
+ }
29
+
30
+ const retryAfterHeader = response.headers.get("retry-after");
31
+ const retryAfterMs = retryAfterHeader
32
+ ? Number(retryAfterHeader) * 1000
33
+ : null;
34
+
35
+ const delay = retryAfterMs || Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
36
+
37
+ warn(`${label} failed with retryable status ${response.status}. Retrying in ${delay}ms (attempt ${attempt + 1}/${retries + 1})`);
38
+ await sleep(delay);
39
+ } catch (error) {
40
+ lastError = error;
41
+
42
+ const delay = Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
43
+ warn(`${label} threw error: ${error.message}. Retrying in ${delay}ms (attempt ${attempt + 1}/${retries + 1})`);
44
+ await sleep(delay);
45
+ }
46
+
47
+ attempt += 1;
48
+ }
49
+
50
+ if (lastError) {
51
+ throw lastError;
52
+ }
53
+
54
+ throw new Error(`${label} failed after ${retries + 1} attempts`);
55
+ }
@@ -0,0 +1,150 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import fetch from "node-fetch";
5
+ import { info, warn } from "./logger.js";
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ const CHECK_INTERVAL = 1000 * 60 * 60 * 24; // 24 hours
11
+ const CACHE_FILE = path.join(process.env.HOME || process.env.USERPROFILE, ".repolens-update-check.json");
12
+
13
+ async function getCurrentVersion() {
14
+ try {
15
+ const pkgPath = path.join(__dirname, "../../package.json");
16
+ const pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
17
+ return pkg.version;
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ async function getLatestVersion() {
24
+ try {
25
+ const response = await fetch("https://registry.npmjs.org/repolens/latest", {
26
+ timeout: 3000
27
+ });
28
+
29
+ if (!response.ok) return null;
30
+
31
+ const data = await response.json();
32
+ return data.version;
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ async function readCache() {
39
+ try {
40
+ const cache = JSON.parse(await fs.readFile(CACHE_FILE, "utf8"));
41
+ return cache;
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+
47
+ async function writeCache(data) {
48
+ try {
49
+ await fs.writeFile(CACHE_FILE, JSON.stringify(data), "utf8");
50
+ } catch {
51
+ // Fail silently - not critical
52
+ }
53
+ }
54
+
55
+ function compareVersions(current, latest) {
56
+ const currentParts = current.split(".").map(Number);
57
+ const latestParts = latest.split(".").map(Number);
58
+
59
+ for (let i = 0; i < 3; i++) {
60
+ if (latestParts[i] > currentParts[i]) return "outdated";
61
+ if (latestParts[i] < currentParts[i]) return "ahead";
62
+ }
63
+
64
+ return "current";
65
+ }
66
+
67
+ export async function checkForUpdates() {
68
+ // Skip in CI environments
69
+ if (process.env.CI || process.env.GITHUB_ACTIONS) {
70
+ return;
71
+ }
72
+
73
+ const currentVersion = await getCurrentVersion();
74
+ if (!currentVersion) return;
75
+
76
+ // Check cache to avoid frequent API calls
77
+ const cache = await readCache();
78
+ const now = Date.now();
79
+
80
+ if (cache && (now - cache.lastCheck < CHECK_INTERVAL)) {
81
+ // Recently checked, show cached result if outdated
82
+ if (cache.isOutdated) {
83
+ showUpdateMessage(currentVersion, cache.latestVersion);
84
+ }
85
+ return;
86
+ }
87
+
88
+ // Perform check
89
+ const latestVersion = await getLatestVersion();
90
+ if (!latestVersion) {
91
+ // Failed to fetch, update cache timestamp only
92
+ await writeCache({ lastCheck: now, isOutdated: false, latestVersion: null });
93
+ return;
94
+ }
95
+
96
+ const comparison = compareVersions(currentVersion, latestVersion);
97
+
98
+ if (comparison === "outdated") {
99
+ showUpdateMessage(currentVersion, latestVersion);
100
+ await writeCache({ lastCheck: now, isOutdated: true, latestVersion });
101
+ } else {
102
+ await writeCache({ lastCheck: now, isOutdated: false, latestVersion });
103
+ }
104
+ }
105
+
106
+ function showUpdateMessage(current, latest) {
107
+ console.log("");
108
+ warn("┌────────────────────────────────────────────────────────────┐");
109
+ warn("│ 📦 Update Available │");
110
+ warn("├────────────────────────────────────────────────────────────┤");
111
+ warn(`│ Current: ${current.padEnd(10)} → Latest: ${latest.padEnd(10)} │`);
112
+ warn("│ │");
113
+ warn("│ Run one of these commands to update: │");
114
+ warn("│ │");
115
+ warn("│ • npm install -g repolens@latest (global install) │");
116
+ warn("│ • npm install repolens@latest (local install) │");
117
+ warn("│ • npx repolens@latest <command> (always latest) │");
118
+ warn("│ │");
119
+ warn("│ Release notes: https://github.com/CHAPIBUNNY/repolens │");
120
+ warn("└────────────────────────────────────────────────────────────┘");
121
+ console.log("");
122
+ }
123
+
124
+ export async function forceCheckForUpdates() {
125
+ const currentVersion = await getCurrentVersion();
126
+ if (!currentVersion) {
127
+ info("Could not determine current version");
128
+ return;
129
+ }
130
+
131
+ info(`Current version: ${currentVersion}`);
132
+ info("Checking for updates...");
133
+
134
+ const latestVersion = await getLatestVersion();
135
+ if (!latestVersion) {
136
+ warn("Could not fetch latest version from npm registry");
137
+ return;
138
+ }
139
+
140
+ const comparison = compareVersions(currentVersion, latestVersion);
141
+
142
+ if (comparison === "outdated") {
143
+ info(`Latest version: ${latestVersion}`);
144
+ showUpdateMessage(currentVersion, latestVersion);
145
+ } else if (comparison === "ahead") {
146
+ info("You're running a pre-release or development version");
147
+ } else {
148
+ info("✓ You're running the latest version!");
149
+ }
150
+ }