@gabimoncha/cursor-rules
Version:
A CLI for bootstrapping Cursor rules to a project
128 lines • 4.98 kB
JavaScript
import { execSync } from "node:child_process";
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import pc from "picocolors";
import semver from "semver";
import { fileExists } from "../core/fileExists.js";
import { getPackageManager, getPackageName, getVersion, } from "../core/packageJsonParse.js";
import { logger } from "../shared/logger.js";
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
// Function to check for updates and notify user
export async function checkForUpdates() {
try {
const { currentVersion, latestVersion, updateAvailable } = await getLatestVersion();
if (updateAvailable) {
const isLocal = checkIfLocal();
logger.debug(`cursor-rules installed ${isLocal ? "locally" : "globally"}`);
const updateCommand = await getPackageManager(isLocal ? "upgrade" : "global");
const updateMessage = [
"",
pc.bold(pc.yellow("Update available! ")) +
pc.dim(`${currentVersion} → `) +
pc.bold(pc.cyan(`${latestVersion}`)),
pc.dim("Run: ") + pc.bold(updateCommand) + pc.dim(" to update"),
"",
].join("\n ");
return updateMessage;
}
}
catch (error) {
// Silently fail if update check fails
logger.debug("Failed to check for updates:", error);
}
}
async function getLatestVersion() {
try {
const currentVersion = await getVersion();
const cachedData = readCache();
if (cachedData && Date.now() - cachedData.timestamp < CACHE_TTL) {
writeCache({
latestVersion: cachedData.latestVersion,
timestamp: Date.now(),
});
return {
currentVersion,
latestVersion: cachedData.latestVersion,
updateAvailable: semver.gt(cachedData.latestVersion, currentVersion),
};
}
const packageName = await getPackageName();
const response = await fetch(`https://registry.npmjs.org/${packageName}`);
if (!response.ok) {
logger.debug(`Failed to fetch latest version: ${response.status}`);
return {
currentVersion,
latestVersion: currentVersion,
updateAvailable: false,
};
}
const data = (await response.json());
const latestVersion = data["dist-tags"]?.latest;
if (!latestVersion) {
return {
currentVersion,
latestVersion: currentVersion,
updateAvailable: false,
};
}
// Compare versions (simple string comparison works for semver format)
const updateAvailable = semver.gt(latestVersion, currentVersion);
// Cache the result
try {
writeCache({
latestVersion,
timestamp: Date.now(),
});
logger.debug("Update check result cached");
}
catch (error) {
logger.debug("Error caching update check result:", error);
}
return { currentVersion, latestVersion, updateAvailable };
}
catch (error) {
throw new Error("Error checking for updates:", { cause: error });
}
}
// Get the cache directory path for storing update check results
function getCacheDir() {
const isLocal = checkIfLocal();
// Use the user's home directory for the cache
const homeDir = process.env.HOME || ".";
const cacheDir = path.join(isLocal ? process.cwd() : homeDir, ".cursor-rules-cli", "cache");
// Ensure the cache directory exists
if (!existsSync(cacheDir)) {
mkdirSync(cacheDir, { recursive: true });
}
// Ensure .gitignore exists and add .cursor-rules-cli to it
if (isLocal) {
const gitignorePath = path.join(process.cwd(), ".gitignore");
const hasGitignore = fileExists(gitignorePath);
if (!hasGitignore) {
return cacheDir;
}
const gitignore = readFileSync(gitignorePath, "utf-8");
if (!gitignore.includes(".cursor-rules-cli")) {
appendFileSync(gitignorePath, "\n.cursor-rules-cli");
}
}
return cacheDir;
}
function checkIfLocal() {
const cursorRulesPath = execSync("which cursor-rules").toString();
return cursorRulesPath.includes("node_modules");
}
function readCache() {
const cacheFile = path.join(getCacheDir(), "update-check.json");
if (existsSync(cacheFile)) {
const cacheContent = readFileSync(cacheFile, "utf-8");
return JSON.parse(cacheContent);
}
return null;
}
async function writeCache(data) {
const cacheFile = path.join(getCacheDir(), "update-check.json");
fs.writeFile(cacheFile, JSON.stringify(data));
}
//# sourceMappingURL=checkForUpdates.js.map