@lvnt/release-radar 1.3.0 → 1.4.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.
package/dist/checker.d.ts CHANGED
@@ -6,5 +6,8 @@ export declare class Checker {
6
6
  private storage;
7
7
  private notifier;
8
8
  constructor(tools: ToolConfig[], storage: Storage, notifier: Notifier);
9
- checkAll(): Promise<void>;
9
+ checkAll(): Promise<{
10
+ hasUpdates: boolean;
11
+ updateCount: number;
12
+ }>;
10
13
  }
package/dist/checker.js CHANGED
@@ -31,5 +31,6 @@ export class Checker {
31
31
  }
32
32
  await this.notifier.sendBatchedUpdates(updates);
33
33
  await this.notifier.sendBatchedFailures(failures);
34
+ return { hasUpdates: updates.length > 0, updateCount: updates.length };
34
35
  }
35
36
  }
@@ -61,4 +61,20 @@ describe('Checker', () => {
61
61
  expect(mockNotifier.sendBatchedUpdates).toHaveBeenCalledWith([]);
62
62
  expect(mockStorage.setVersion).not.toHaveBeenCalled();
63
63
  });
64
+ it('returns hasUpdates true when updates found', async () => {
65
+ mockStorage.getVersion.mockReturnValueOnce('1.11.1').mockReturnValueOnce('2.43.0');
66
+ vi.mocked(fetchVersion)
67
+ .mockResolvedValueOnce('1.12.0')
68
+ .mockResolvedValueOnce('2.43.0');
69
+ const result = await checker.checkAll();
70
+ expect(result.hasUpdates).toBe(true);
71
+ expect(result.updateCount).toBe(1);
72
+ });
73
+ it('returns hasUpdates false when no updates', async () => {
74
+ mockStorage.getVersion.mockReturnValue('1.12.0');
75
+ vi.mocked(fetchVersion).mockResolvedValue('1.12.0');
76
+ const result = await checker.checkAll();
77
+ expect(result.hasUpdates).toBe(false);
78
+ expect(result.updateCount).toBe(0);
79
+ });
64
80
  });
@@ -0,0 +1,13 @@
1
+ import type { DownloadsConfig } from './types.js';
2
+ export interface PublishResult {
3
+ success: boolean;
4
+ version?: string;
5
+ error?: string;
6
+ }
7
+ export declare class CliPublisher {
8
+ private downloadsConfig;
9
+ private cliPath;
10
+ constructor(downloadsConfig: DownloadsConfig, cliPath?: string);
11
+ isConfigured(): boolean;
12
+ publish(versions: Record<string, string>): Promise<PublishResult>;
13
+ }
@@ -0,0 +1,50 @@
1
+ // src/cli-publisher.ts
2
+ import { execSync } from 'child_process';
3
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
4
+ import { generateVersionsJson } from './versions-generator.js';
5
+ export class CliPublisher {
6
+ downloadsConfig;
7
+ cliPath;
8
+ constructor(downloadsConfig, cliPath = './cli') {
9
+ this.downloadsConfig = downloadsConfig;
10
+ this.cliPath = cliPath;
11
+ }
12
+ isConfigured() {
13
+ return Object.keys(this.downloadsConfig).length > 0 && existsSync(this.cliPath);
14
+ }
15
+ async publish(versions) {
16
+ if (!this.isConfigured()) {
17
+ return { success: false, error: 'CLI publisher not configured' };
18
+ }
19
+ try {
20
+ // Generate versions.json
21
+ const versionsJson = generateVersionsJson(versions, this.downloadsConfig);
22
+ // Write to data/ for reference
23
+ writeFileSync('./data/cli-versions.json', JSON.stringify(versionsJson, null, 2));
24
+ // Copy to CLI package
25
+ const cliVersionsPath = `${this.cliPath}/versions.json`;
26
+ writeFileSync(cliVersionsPath, JSON.stringify(versionsJson, null, 2));
27
+ // Read current CLI version
28
+ const pkgPath = `${this.cliPath}/package.json`;
29
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
30
+ const currentVersion = pkg.version;
31
+ // Bump patch version
32
+ const versionParts = currentVersion.split('.').map(Number);
33
+ versionParts[2]++;
34
+ const newVersion = versionParts.join('.');
35
+ pkg.version = newVersion;
36
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
37
+ // Build CLI
38
+ execSync('npm run build', { cwd: this.cliPath, stdio: 'pipe' });
39
+ // Publish to npm
40
+ execSync('npm publish --access public', { cwd: this.cliPath, stdio: 'pipe' });
41
+ console.log(`[CliPublisher] Published @lvnt/release-radar-cli v${newVersion}`);
42
+ return { success: true, version: newVersion };
43
+ }
44
+ catch (error) {
45
+ const message = error instanceof Error ? error.message : String(error);
46
+ console.error(`[CliPublisher] Failed to publish: ${message}`);
47
+ return { success: false, error: message };
48
+ }
49
+ }
50
+ }
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { Storage } from './storage.js';
8
8
  import { Notifier } from './notifier.js';
9
9
  import { Checker } from './checker.js';
10
10
  import { generateVersionsJson } from './versions-generator.js';
11
+ import { CliPublisher } from './cli-publisher.js';
11
12
  const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
12
13
  const CHAT_ID = process.env.TELEGRAM_CHAT_ID;
13
14
  const CONFIG_PATH = './config/tools.json';
@@ -15,6 +16,8 @@ if (!BOT_TOKEN || !CHAT_ID) {
15
16
  console.error('Missing TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID environment variables');
16
17
  process.exit(1);
17
18
  }
19
+ // Type-safe constants after validation
20
+ const validatedChatId = CHAT_ID;
18
21
  // Load config
19
22
  let configData = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
20
23
  const DOWNLOADS_PATH = './config/downloads.json';
@@ -28,8 +31,9 @@ catch {
28
31
  // Initialize components
29
32
  const bot = new TelegramBot(BOT_TOKEN, { polling: true });
30
33
  const storage = new Storage('./data/versions.json');
31
- const notifier = new Notifier(bot, CHAT_ID);
34
+ const notifier = new Notifier(bot, validatedChatId);
32
35
  const checker = new Checker(configData.tools, storage, notifier);
36
+ const cliPublisher = new CliPublisher(downloadsConfig);
33
37
  // Track scheduled task for rescheduling
34
38
  let scheduledTask = null;
35
39
  let lastCheckTime = null;
@@ -53,23 +57,42 @@ function scheduleChecks(intervalHours) {
53
57
  scheduledTask = cron.schedule(cronExpression, async () => {
54
58
  console.log(`[${new Date().toISOString()}] Running scheduled check`);
55
59
  lastCheckTime = new Date();
56
- await checker.checkAll();
60
+ const result = await checker.checkAll();
57
61
  nextCheckTime = calculateNextCheckTime(intervalHours);
62
+ // Auto-publish CLI if updates were detected
63
+ if (result.hasUpdates && cliPublisher.isConfigured()) {
64
+ const state = storage.load();
65
+ const publishResult = await cliPublisher.publish(state.versions);
66
+ if (publishResult.success) {
67
+ await bot.sendMessage(validatedChatId, `📦 CLI published: v${publishResult.version}`);
68
+ }
69
+ }
58
70
  });
59
71
  console.log(`Scheduled checks every ${intervalHours} hours`);
60
72
  }
61
73
  // Bot commands
62
74
  bot.onText(/\/check/, async (msg) => {
63
- if (msg.chat.id.toString() !== CHAT_ID)
75
+ if (msg.chat.id.toString() !== validatedChatId)
64
76
  return;
65
- await bot.sendMessage(CHAT_ID, 'Checking for updates...');
77
+ await bot.sendMessage(validatedChatId, 'Checking for updates...');
66
78
  lastCheckTime = new Date();
67
- await checker.checkAll();
79
+ const result = await checker.checkAll();
68
80
  nextCheckTime = calculateNextCheckTime(configData.checkIntervalHours);
69
- await bot.sendMessage(CHAT_ID, 'Check complete.');
81
+ // Auto-publish CLI if updates were detected
82
+ if (result.hasUpdates && cliPublisher.isConfigured()) {
83
+ const state = storage.load();
84
+ const publishResult = await cliPublisher.publish(state.versions);
85
+ if (publishResult.success) {
86
+ await bot.sendMessage(validatedChatId, `📦 CLI published: v${publishResult.version}`);
87
+ }
88
+ else {
89
+ await bot.sendMessage(validatedChatId, `⚠️ CLI publish failed: ${publishResult.error}`);
90
+ }
91
+ }
92
+ await bot.sendMessage(validatedChatId, 'Check complete.');
70
93
  });
71
94
  bot.onText(/\/status/, async (msg) => {
72
- if (msg.chat.id.toString() !== CHAT_ID)
95
+ if (msg.chat.id.toString() !== validatedChatId)
73
96
  return;
74
97
  const state = storage.load();
75
98
  const lines = Object.entries(state.versions)
@@ -98,24 +121,24 @@ bot.onText(/\/status/, async (msg) => {
98
121
  message += '\nNext check: soon';
99
122
  }
100
123
  }
101
- await bot.sendMessage(CHAT_ID, message);
124
+ await bot.sendMessage(validatedChatId, message);
102
125
  });
103
126
  bot.onText(/\/interval$/, async (msg) => {
104
- if (msg.chat.id.toString() !== CHAT_ID)
127
+ if (msg.chat.id.toString() !== validatedChatId)
105
128
  return;
106
- await bot.sendMessage(CHAT_ID, `Check interval: every ${configData.checkIntervalHours} hours`);
129
+ await bot.sendMessage(validatedChatId, `Check interval: every ${configData.checkIntervalHours} hours`);
107
130
  });
108
131
  bot.onText(/\/setinterval(?:\s+(\d+))?/, async (msg, match) => {
109
- if (msg.chat.id.toString() !== CHAT_ID)
132
+ if (msg.chat.id.toString() !== validatedChatId)
110
133
  return;
111
134
  const hoursStr = match?.[1];
112
135
  if (!hoursStr) {
113
- await bot.sendMessage(CHAT_ID, 'Usage: /setinterval <hours>\nExample: /setinterval 12');
136
+ await bot.sendMessage(validatedChatId, 'Usage: /setinterval <hours>\nExample: /setinterval 12');
114
137
  return;
115
138
  }
116
139
  const hours = parseInt(hoursStr, 10);
117
140
  if (hours < 1 || hours > 24) {
118
- await bot.sendMessage(CHAT_ID, 'Interval must be between 1 and 24 hours');
141
+ await bot.sendMessage(validatedChatId, 'Interval must be between 1 and 24 hours');
119
142
  return;
120
143
  }
121
144
  // Update config
@@ -123,21 +146,38 @@ bot.onText(/\/setinterval(?:\s+(\d+))?/, async (msg, match) => {
123
146
  writeFileSync(CONFIG_PATH, JSON.stringify(configData, null, 2));
124
147
  // Reschedule
125
148
  scheduleChecks(hours);
126
- await bot.sendMessage(CHAT_ID, `Check interval updated to every ${hours} hours`);
149
+ await bot.sendMessage(validatedChatId, `Check interval updated to every ${hours} hours`);
127
150
  });
128
151
  bot.onText(/\/generate/, async (msg) => {
129
- if (msg.chat.id.toString() !== CHAT_ID)
152
+ if (msg.chat.id.toString() !== validatedChatId)
130
153
  return;
131
154
  if (Object.keys(downloadsConfig).length === 0) {
132
- await bot.sendMessage(CHAT_ID, 'No downloads.json configured.');
155
+ await bot.sendMessage(validatedChatId, 'No downloads.json configured.');
133
156
  return;
134
157
  }
135
158
  const state = storage.load();
136
159
  const versionsJson = generateVersionsJson(state.versions, downloadsConfig);
137
- const outputPath = './data/versions.json';
160
+ const outputPath = './data/cli-versions.json';
138
161
  writeFileSync(outputPath, JSON.stringify(versionsJson, null, 2));
139
162
  await bot.sendMessage(CHAT_ID, `Generated versions.json with ${versionsJson.tools.length} tools.\nPath: ${outputPath}`);
140
163
  });
164
+ bot.onText(/\/publishcli/, async (msg) => {
165
+ if (msg.chat.id.toString() !== validatedChatId)
166
+ return;
167
+ if (!cliPublisher.isConfigured()) {
168
+ await bot.sendMessage(validatedChatId, 'CLI publisher not configured. Check downloads.json and cli/ directory.');
169
+ return;
170
+ }
171
+ await bot.sendMessage(validatedChatId, 'Publishing CLI...');
172
+ const state = storage.load();
173
+ const result = await cliPublisher.publish(state.versions);
174
+ if (result.success) {
175
+ await bot.sendMessage(validatedChatId, `✅ CLI published: v${result.version}`);
176
+ }
177
+ else {
178
+ await bot.sendMessage(validatedChatId, `❌ CLI publish failed: ${result.error}`);
179
+ }
180
+ });
141
181
  // Start scheduled checks
142
182
  scheduleChecks(configData.checkIntervalHours);
143
183
  console.log(`ReleaseRadar started. Checking every ${configData.checkIntervalHours} hours.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvnt/release-radar",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Monitor tool versions and notify via Telegram when updates are detected",
5
5
  "main": "dist/index.js",
6
6
  "bin": {