import fs from "node:fs";
|
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
import { homedir } from "node:os";
|
import path from "node:path";
|
import dotenv from "dotenv";
|
|
const userHomeDir = homedir();
|
|
// Load .env files
|
const GLOBAL_ENV_PATH = path.join(userHomeDir, ".env");
|
const LOCAL_ENV_PATH = path.resolve(".env");
|
|
/**
|
* Load environment variables from a file if it exists.
|
* @param {string} filePath
|
* @returns {object} Parsed environment variables
|
*/
|
function loadEnvFile(filePath) {
|
if (fs.existsSync(filePath)) {
|
return dotenv.parse(fs.readFileSync(filePath));
|
}
|
return {};
|
}
|
|
// Merge global and local .env configurations
|
const globalEnv = loadEnvFile(GLOBAL_ENV_PATH);
|
const localEnv = loadEnvFile(LOCAL_ENV_PATH);
|
process.env = { ...globalEnv, ...process.env, ...localEnv };
|
|
// Configurable values
|
const DISCORD_WEBHOOK = process.env.DISCORD_WEBHOOK || "";
|
const GITHUB_DEV_TOKEN = process.env.GITHUB_DEV_TOKEN || "";
|
const GITHUB_REPO = process.env.GITHUB_REPO || "gohugo-ananke/ananke";
|
const DEFAULT_MESSAGE_TEMPLATE = "New release: {{tag_name}} - {{html_url}}";
|
const MESSAGE_TEMPLATE =
|
process.env.MESSAGE_TEMPLATE || DEFAULT_MESSAGE_TEMPLATE;
|
const CACHE_DIR = "./cache";
|
const CACHE_FILE = "github-releases.json";
|
const CACHE_FILE_PATH = path.join(CACHE_DIR, CACHE_FILE);
|
|
/**
|
* Ensures the cache directory exists, creating it if necessary.
|
* @returns {Promise<void>}
|
*/
|
async function ensureCacheDirectory() {
|
try {
|
await access(CACHE_DIR);
|
} catch {
|
try {
|
await mkdir(CACHE_DIR, { recursive: true });
|
} catch (err) {
|
console.error(`Failed to create cache directory: ${err.message}`);
|
process.exit(1);
|
}
|
}
|
}
|
|
/**
|
* Reads the cache file or returns an empty array if not found.
|
* @returns {Promise<string[]>}
|
*/
|
async function readCache() {
|
try {
|
const data = await readFile(CACHE_FILE_PATH, "utf8");
|
return JSON.parse(data) || [];
|
} catch {
|
return [];
|
}
|
}
|
|
/**
|
* Writes data to the cache file.
|
* @param {string[]} data
|
*/
|
async function writeCache(data) {
|
await writeFile(CACHE_FILE_PATH, JSON.stringify(data, null, 2));
|
}
|
|
/**
|
* Fetches the latest release from the GitHub REST API.
|
* @returns {Promise<{ tag_name: string, html_url: string } | null>}
|
*/
|
async function fetchLatestRelease() {
|
try {
|
const response = await fetch(
|
"https://api.github.com/repos/" + GITHUB_REPO + "/releases",
|
{
|
headers: {
|
Authorization: `token ${GITHUB_DEV_TOKEN}`,
|
},
|
},
|
);
|
|
if (!response.ok) {
|
throw new Error(`GitHub API request failed: ${response.statusText}`);
|
}
|
|
const releases = await response.json();
|
if (!Array.isArray(releases) || releases.length === 0) {
|
console.log("No releases found.");
|
return null;
|
}
|
|
return releases[0];
|
} catch (err) {
|
console.error("Failed to fetch releases:", err.message);
|
return null;
|
}
|
}
|
|
/**
|
* Posts a message to Discord using a webhook.
|
* @param {string} message
|
*/
|
async function postToDiscord(message) {
|
try {
|
const response = await fetch(DISCORD_WEBHOOK, {
|
method: "POST",
|
headers: { "Content-Type": "application/json" },
|
body: JSON.stringify({ content: message }),
|
});
|
|
if (!response.ok) {
|
throw new Error(`Failed to post to Discord: ${response.statusText}`);
|
}
|
|
console.log("Posted to Discord successfully.");
|
} catch (err) {
|
console.error("Failed to post to Discord:", err.message);
|
}
|
}
|
|
/**
|
* Formats the release message using the template.
|
* @param {Record<string, string>} releaseData
|
* @returns {string}
|
*/
|
function formatMessage(releaseData) {
|
return MESSAGE_TEMPLATE.replace(
|
/{{\s*(\w+)\s*}}/g,
|
(_, key) => releaseData[key] || "",
|
);
|
}
|
|
/**
|
* Main function to fetch the latest GitHub release and post it to Discord.
|
*/
|
async function main() {
|
try {
|
await ensureCacheDirectory();
|
|
const cachedIds = await readCache();
|
const latestRelease = await fetchLatestRelease();
|
|
if (latestRelease && !cachedIds.includes(latestRelease.tag_name)) {
|
const message = formatMessage(latestRelease);
|
await postToDiscord(message);
|
|
cachedIds.push(latestRelease.tag_name);
|
await writeCache(cachedIds);
|
} else {
|
console.log("No new releases to post.");
|
}
|
} catch (err) {
|
console.error("Error:", err.message);
|
}
|
}
|
|
main();
|