@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 +4 -1
- package/dist/checker.js +1 -0
- package/dist/checker.test.js +16 -0
- package/dist/cli-publisher.d.ts +13 -0
- package/dist/cli-publisher.js +50 -0
- package/dist/index.js +57 -17
- package/package.json +1 -1
package/dist/checker.d.ts
CHANGED
package/dist/checker.js
CHANGED
package/dist/checker.test.js
CHANGED
|
@@ -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,
|
|
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() !==
|
|
75
|
+
if (msg.chat.id.toString() !== validatedChatId)
|
|
64
76
|
return;
|
|
65
|
-
await bot.sendMessage(
|
|
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
|
-
|
|
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() !==
|
|
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(
|
|
124
|
+
await bot.sendMessage(validatedChatId, message);
|
|
102
125
|
});
|
|
103
126
|
bot.onText(/\/interval$/, async (msg) => {
|
|
104
|
-
if (msg.chat.id.toString() !==
|
|
127
|
+
if (msg.chat.id.toString() !== validatedChatId)
|
|
105
128
|
return;
|
|
106
|
-
await bot.sendMessage(
|
|
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() !==
|
|
132
|
+
if (msg.chat.id.toString() !== validatedChatId)
|
|
110
133
|
return;
|
|
111
134
|
const hoursStr = match?.[1];
|
|
112
135
|
if (!hoursStr) {
|
|
113
|
-
await bot.sendMessage(
|
|
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(
|
|
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(
|
|
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() !==
|
|
152
|
+
if (msg.chat.id.toString() !== validatedChatId)
|
|
130
153
|
return;
|
|
131
154
|
if (Object.keys(downloadsConfig).length === 0) {
|
|
132
|
-
await bot.sendMessage(
|
|
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.`);
|