@lvnt/release-radar 1.1.7 → 1.3.0

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,17 @@
1
+ {
2
+ "Ninja": {
3
+ "displayName": "Ninja",
4
+ "downloadUrl": "github.com/ninja-build/ninja/releases/download/v{{VERSION}}/ninja-linux.zip",
5
+ "filename": "ninja-{{VERSION}}-linux.zip"
6
+ },
7
+ "CMake": {
8
+ "displayName": "CMake",
9
+ "downloadUrl": "github.com/Kitware/CMake/releases/download/v{{VERSION}}/cmake-{{VERSION}}-linux-x86_64.tar.gz",
10
+ "filename": "cmake-{{VERSION}}-linux-x86_64.tar.gz"
11
+ },
12
+ "Git": {
13
+ "displayName": "Git for Windows",
14
+ "downloadUrl": "github.com/git-for-windows/git/releases/download/v{{VERSION}}.windows.1/Git-{{VERSION}}-64-bit.exe",
15
+ "filename": "Git-{{VERSION}}-64-bit.exe"
16
+ }
17
+ }
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { readFileSync, writeFileSync } from 'fs';
7
7
  import { Storage } from './storage.js';
8
8
  import { Notifier } from './notifier.js';
9
9
  import { Checker } from './checker.js';
10
+ import { generateVersionsJson } from './versions-generator.js';
10
11
  const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
11
12
  const CHAT_ID = process.env.TELEGRAM_CHAT_ID;
12
13
  const CONFIG_PATH = './config/tools.json';
@@ -16,6 +17,14 @@ if (!BOT_TOKEN || !CHAT_ID) {
16
17
  }
17
18
  // Load config
18
19
  let configData = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
20
+ const DOWNLOADS_PATH = './config/downloads.json';
21
+ let downloadsConfig = {};
22
+ try {
23
+ downloadsConfig = JSON.parse(readFileSync(DOWNLOADS_PATH, 'utf-8'));
24
+ }
25
+ catch {
26
+ console.log('No downloads.json found, CLI generation disabled');
27
+ }
19
28
  // Initialize components
20
29
  const bot = new TelegramBot(BOT_TOKEN, { polling: true });
21
30
  const storage = new Storage('./data/versions.json');
@@ -23,14 +32,29 @@ const notifier = new Notifier(bot, CHAT_ID);
23
32
  const checker = new Checker(configData.tools, storage, notifier);
24
33
  // Track scheduled task for rescheduling
25
34
  let scheduledTask = null;
35
+ let lastCheckTime = null;
36
+ let nextCheckTime = null;
37
+ function calculateNextCheckTime(intervalHours) {
38
+ const now = new Date();
39
+ const next = new Date(now);
40
+ next.setMinutes(0, 0, 0);
41
+ next.setHours(Math.ceil(now.getHours() / intervalHours) * intervalHours);
42
+ if (next <= now) {
43
+ next.setHours(next.getHours() + intervalHours);
44
+ }
45
+ return next;
46
+ }
26
47
  function scheduleChecks(intervalHours) {
27
48
  if (scheduledTask) {
28
49
  scheduledTask.stop();
29
50
  }
51
+ nextCheckTime = calculateNextCheckTime(intervalHours);
30
52
  const cronExpression = `0 */${intervalHours} * * *`;
31
53
  scheduledTask = cron.schedule(cronExpression, async () => {
32
54
  console.log(`[${new Date().toISOString()}] Running scheduled check`);
55
+ lastCheckTime = new Date();
33
56
  await checker.checkAll();
57
+ nextCheckTime = calculateNextCheckTime(intervalHours);
34
58
  });
35
59
  console.log(`Scheduled checks every ${intervalHours} hours`);
36
60
  }
@@ -39,7 +63,9 @@ bot.onText(/\/check/, async (msg) => {
39
63
  if (msg.chat.id.toString() !== CHAT_ID)
40
64
  return;
41
65
  await bot.sendMessage(CHAT_ID, 'Checking for updates...');
66
+ lastCheckTime = new Date();
42
67
  await checker.checkAll();
68
+ nextCheckTime = calculateNextCheckTime(configData.checkIntervalHours);
43
69
  await bot.sendMessage(CHAT_ID, 'Check complete.');
44
70
  });
45
71
  bot.onText(/\/status/, async (msg) => {
@@ -49,9 +75,29 @@ bot.onText(/\/status/, async (msg) => {
49
75
  const lines = Object.entries(state.versions)
50
76
  .map(([name, version]) => `${name}: ${version}`)
51
77
  .sort();
52
- const message = lines.length > 0
78
+ let message = lines.length > 0
53
79
  ? lines.join('\n')
54
80
  : 'No versions tracked yet. Run /check first.';
81
+ // Add timing info
82
+ message += '\n\n---';
83
+ if (lastCheckTime) {
84
+ const ago = Math.round((Date.now() - lastCheckTime.getTime()) / 60000);
85
+ message += `\nLast check: ${ago} min ago`;
86
+ }
87
+ else {
88
+ message += '\nLast check: not yet';
89
+ }
90
+ if (nextCheckTime) {
91
+ const mins = Math.round((nextCheckTime.getTime() - Date.now()) / 60000);
92
+ if (mins > 0) {
93
+ const hours = Math.floor(mins / 60);
94
+ const remainingMins = mins % 60;
95
+ message += `\nNext check: in ${hours > 0 ? hours + 'h ' : ''}${remainingMins}m`;
96
+ }
97
+ else {
98
+ message += '\nNext check: soon';
99
+ }
100
+ }
55
101
  await bot.sendMessage(CHAT_ID, message);
56
102
  });
57
103
  bot.onText(/\/interval$/, async (msg) => {
@@ -79,6 +125,19 @@ bot.onText(/\/setinterval(?:\s+(\d+))?/, async (msg, match) => {
79
125
  scheduleChecks(hours);
80
126
  await bot.sendMessage(CHAT_ID, `Check interval updated to every ${hours} hours`);
81
127
  });
128
+ bot.onText(/\/generate/, async (msg) => {
129
+ if (msg.chat.id.toString() !== CHAT_ID)
130
+ return;
131
+ if (Object.keys(downloadsConfig).length === 0) {
132
+ await bot.sendMessage(CHAT_ID, 'No downloads.json configured.');
133
+ return;
134
+ }
135
+ const state = storage.load();
136
+ const versionsJson = generateVersionsJson(state.versions, downloadsConfig);
137
+ const outputPath = './data/versions.json';
138
+ writeFileSync(outputPath, JSON.stringify(versionsJson, null, 2));
139
+ await bot.sendMessage(CHAT_ID, `Generated versions.json with ${versionsJson.tools.length} tools.\nPath: ${outputPath}`);
140
+ });
82
141
  // Start scheduled checks
83
142
  scheduleChecks(configData.checkIntervalHours);
84
143
  console.log(`ReleaseRadar started. Checking every ${configData.checkIntervalHours} hours.`);
package/dist/types.d.ts CHANGED
@@ -12,3 +12,23 @@ export interface Config {
12
12
  checkIntervalHours: number;
13
13
  tools: ToolConfig[];
14
14
  }
15
+ export interface DownloadConfig {
16
+ displayName: string;
17
+ downloadUrl: string;
18
+ filename: string;
19
+ }
20
+ export interface DownloadsConfig {
21
+ [toolName: string]: DownloadConfig;
22
+ }
23
+ export interface VersionsJsonTool {
24
+ name: string;
25
+ displayName: string;
26
+ version: string;
27
+ publishedAt: string;
28
+ downloadUrl: string;
29
+ filename: string;
30
+ }
31
+ export interface VersionsJson {
32
+ generatedAt: string;
33
+ tools: VersionsJsonTool[];
34
+ }
@@ -0,0 +1,2 @@
1
+ import type { DownloadsConfig, VersionsJson } from './types.js';
2
+ export declare function generateVersionsJson(versions: Record<string, string>, downloads: DownloadsConfig): VersionsJson;
@@ -0,0 +1,23 @@
1
+ export function generateVersionsJson(versions, downloads) {
2
+ const tools = [];
3
+ for (const [toolName, version] of Object.entries(versions)) {
4
+ const downloadConfig = downloads[toolName];
5
+ if (!downloadConfig)
6
+ continue;
7
+ const downloadUrl = '{{NEXUS_URL}}/' +
8
+ downloadConfig.downloadUrl.replace(/\{\{VERSION\}\}/g, version);
9
+ const filename = downloadConfig.filename.replace(/\{\{VERSION\}\}/g, version);
10
+ tools.push({
11
+ name: toolName,
12
+ displayName: downloadConfig.displayName,
13
+ version,
14
+ publishedAt: new Date().toISOString(),
15
+ downloadUrl,
16
+ filename,
17
+ });
18
+ }
19
+ return {
20
+ generatedAt: new Date().toISOString(),
21
+ tools,
22
+ };
23
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,47 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { generateVersionsJson } from './versions-generator.js';
3
+ describe('generateVersionsJson', () => {
4
+ it('merges version data with download config', () => {
5
+ const versions = {
6
+ 'Ninja': '1.12.0',
7
+ 'CMake': '3.28.0',
8
+ };
9
+ const downloads = {
10
+ 'Ninja': {
11
+ displayName: 'Ninja Build',
12
+ downloadUrl: 'github.com/ninja-build/ninja/releases/download/v{{VERSION}}/ninja-linux.zip',
13
+ filename: 'ninja-{{VERSION}}-linux.zip',
14
+ },
15
+ 'CMake': {
16
+ displayName: 'CMake',
17
+ downloadUrl: 'github.com/Kitware/CMake/releases/download/v{{VERSION}}/cmake-{{VERSION}}.tar.gz',
18
+ filename: 'cmake-{{VERSION}}.tar.gz',
19
+ },
20
+ };
21
+ const result = generateVersionsJson(versions, downloads);
22
+ expect(result.tools).toHaveLength(2);
23
+ expect(result.generatedAt).toBeDefined();
24
+ const ninja = result.tools.find(t => t.name === 'Ninja');
25
+ expect(ninja).toBeDefined();
26
+ expect(ninja.displayName).toBe('Ninja Build');
27
+ expect(ninja.version).toBe('1.12.0');
28
+ expect(ninja.downloadUrl).toBe('{{NEXUS_URL}}/github.com/ninja-build/ninja/releases/download/v1.12.0/ninja-linux.zip');
29
+ expect(ninja.filename).toBe('ninja-1.12.0-linux.zip');
30
+ });
31
+ it('only includes tools that have download config', () => {
32
+ const versions = {
33
+ 'Ninja': '1.12.0',
34
+ 'UnknownTool': '1.0.0',
35
+ };
36
+ const downloads = {
37
+ 'Ninja': {
38
+ displayName: 'Ninja',
39
+ downloadUrl: 'github.com/ninja/releases/{{VERSION}}/ninja.zip',
40
+ filename: 'ninja-{{VERSION}}.zip',
41
+ },
42
+ };
43
+ const result = generateVersionsJson(versions, downloads);
44
+ expect(result.tools).toHaveLength(1);
45
+ expect(result.tools[0].name).toBe('Ninja');
46
+ });
47
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvnt/release-radar",
3
- "version": "1.1.7",
3
+ "version": "1.3.0",
4
4
  "description": "Monitor tool versions and notify via Telegram when updates are detected",
5
5
  "main": "dist/index.js",
6
6
  "bin": {