mirror of https://github.com/theNewDynamic/gohugo-theme-ananke.git

Patrick Kollitsch
09.17.2025 50996e875fc4e9f3a6110e3863357689f4f9cb90
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import { readFile, writeFile, access, mkdir } from 'node:fs/promises';
import path from 'node:path';
import fs from 'node:fs';
import dotenv from 'dotenv';
import { homedir } from 'node:os';
 
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 || 'theNewDynamic/gohugo-theme-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();