@lvnt/release-radar 1.7.16 → 1.9.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/README.md +16 -3
- package/bin/release-radar-updater.js +0 -0
- package/bin/release-radar.js +0 -0
- package/cli/src/ui.ts +91 -42
- package/config/downloads.json +102 -1
- package/config/tools.json +58 -1
- package/dist/asset-mirror.d.ts +20 -0
- package/dist/asset-mirror.js +116 -7
- package/dist/asset-mirror.test.js +47 -2
- package/dist/checker.d.ts +1 -1
- package/dist/checker.js +27 -11
- package/dist/checker.test.js +19 -5
- package/dist/index.js +155 -27
- package/dist/mirror-and-publish.js +85 -0
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
- package/dist/vsix-mirror.d.ts +0 -15
- package/dist/vsix-mirror.js +0 -96
- package/dist/vsix-mirror.test.js +0 -96
- /package/dist/{vsix-mirror.test.d.ts → mirror-and-publish.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -113,10 +113,12 @@ TELEGRAM_CHAT_ID=your_chat_id_here
|
|
|
113
113
|
|
|
114
114
|
### Tools Configuration
|
|
115
115
|
|
|
116
|
-
Edit `config/tools.json` to add/remove tools:
|
|
116
|
+
Edit `config/tools.json` to add/remove tools and configure scheduling:
|
|
117
117
|
|
|
118
118
|
```json
|
|
119
119
|
{
|
|
120
|
+
"scheduleMode": "daily",
|
|
121
|
+
"dailyCheckTime": "06:00",
|
|
120
122
|
"checkIntervalHours": 6,
|
|
121
123
|
"tools": [
|
|
122
124
|
{
|
|
@@ -128,6 +130,14 @@ Edit `config/tools.json` to add/remove tools:
|
|
|
128
130
|
}
|
|
129
131
|
```
|
|
130
132
|
|
|
133
|
+
#### Schedule Options
|
|
134
|
+
|
|
135
|
+
| Field | Values | Description |
|
|
136
|
+
|-------|--------|-------------|
|
|
137
|
+
| `scheduleMode` | `"daily"` or `"interval"` | Check once per day or every N hours |
|
|
138
|
+
| `dailyCheckTime` | `"HH:MM"` | Time for daily check (24-hour format, e.g., `"06:00"`) |
|
|
139
|
+
| `checkIntervalHours` | `1-24` | Hours between checks (interval mode) |
|
|
140
|
+
|
|
131
141
|
#### Tool Types
|
|
132
142
|
|
|
133
143
|
| Type | Required Fields | Description |
|
|
@@ -169,8 +179,11 @@ Placeholders:
|
|
|
169
179
|
|---------|-------------|
|
|
170
180
|
| `/check` | Manually trigger version check (auto-publishes CLI if updates found) |
|
|
171
181
|
| `/status` | Show all tracked versions + last/next check times |
|
|
172
|
-
| `/
|
|
173
|
-
| `/
|
|
182
|
+
| `/schedule` | Show current schedule mode and next check time |
|
|
183
|
+
| `/settime <HH:MM>` | Set daily check time (e.g., `/settime 06:00`) and switch to daily mode |
|
|
184
|
+
| `/setmode <daily\|interval>` | Switch between daily and interval modes |
|
|
185
|
+
| `/interval` | Show current check interval (interval mode) |
|
|
186
|
+
| `/setinterval <hours>` | Set check interval (1-24 hours) and switch to interval mode |
|
|
174
187
|
| `/generate` | Generate versions.json file locally |
|
|
175
188
|
| `/clipreview` | Preview tools/versions that will be included in CLI |
|
|
176
189
|
| `/publishcli` | Manually publish CLI with current tracked versions |
|
|
File without changes
|
package/bin/release-radar.js
CHANGED
|
File without changes
|
package/cli/src/ui.ts
CHANGED
|
@@ -132,21 +132,28 @@ export interface TableRow {
|
|
|
132
132
|
downloadedVersion: string;
|
|
133
133
|
status: 'new' | 'update' | 'current';
|
|
134
134
|
type: 'npm' | 'download';
|
|
135
|
+
category: 'tool' | 'vscode-extension';
|
|
135
136
|
}
|
|
136
137
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
interface ColWidths {
|
|
139
|
+
tool: number;
|
|
140
|
+
latest: number;
|
|
141
|
+
downloaded: number;
|
|
142
|
+
status: number;
|
|
143
|
+
type: number;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function calculateColWidths(rows: TableRow[]): ColWidths {
|
|
147
|
+
return {
|
|
140
148
|
tool: Math.max(4, ...rows.map(r => r.displayName.length)) + 2,
|
|
141
149
|
latest: Math.max(6, ...rows.map(r => r.version.length)) + 2,
|
|
142
150
|
downloaded: Math.max(10, ...rows.map(r => r.downloadedVersion.length)) + 2,
|
|
143
151
|
status: 8,
|
|
144
152
|
type: 4,
|
|
145
153
|
};
|
|
154
|
+
}
|
|
146
155
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// Header
|
|
156
|
+
function renderTableHeader(colWidths: ColWidths): void {
|
|
150
157
|
console.log(chalk.bold(
|
|
151
158
|
' ' +
|
|
152
159
|
'Tool'.padEnd(colWidths.tool) +
|
|
@@ -155,40 +162,76 @@ export function renderTable(rows: TableRow[]): void {
|
|
|
155
162
|
'Status'.padEnd(colWidths.status) +
|
|
156
163
|
'Type'
|
|
157
164
|
));
|
|
165
|
+
const totalWidth = colWidths.tool + colWidths.latest + colWidths.downloaded + colWidths.status + colWidths.type + 2;
|
|
166
|
+
console.log(chalk.gray('─'.repeat(totalWidth)));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function renderTableRow(row: TableRow, colWidths: ColWidths): void {
|
|
170
|
+
let statusText: string;
|
|
171
|
+
let statusColored: string;
|
|
172
|
+
switch (row.status) {
|
|
173
|
+
case 'new':
|
|
174
|
+
statusText = 'NEW'.padEnd(colWidths.status);
|
|
175
|
+
statusColored = chalk.blue(statusText);
|
|
176
|
+
break;
|
|
177
|
+
case 'update':
|
|
178
|
+
statusText = 'UPDATE'.padEnd(colWidths.status);
|
|
179
|
+
statusColored = chalk.yellow(statusText);
|
|
180
|
+
break;
|
|
181
|
+
case 'current':
|
|
182
|
+
statusText = '✓'.padEnd(colWidths.status);
|
|
183
|
+
statusColored = chalk.green(statusText);
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
const typeStr = row.type === 'npm' ? chalk.magenta('npm') : chalk.cyan('wget');
|
|
187
|
+
|
|
188
|
+
console.log(
|
|
189
|
+
' ' +
|
|
190
|
+
row.displayName.padEnd(colWidths.tool) +
|
|
191
|
+
row.version.padEnd(colWidths.latest) +
|
|
192
|
+
row.downloadedVersion.padEnd(colWidths.downloaded) +
|
|
193
|
+
statusColored +
|
|
194
|
+
typeStr
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function renderGroupHeader(title: string, colWidths: ColWidths): void {
|
|
199
|
+
const totalWidth = colWidths.tool + colWidths.latest + colWidths.downloaded + colWidths.status + colWidths.type + 2;
|
|
200
|
+
console.log('');
|
|
201
|
+
console.log(chalk.bold.underline(` ${title}`));
|
|
158
202
|
console.log(chalk.gray('─'.repeat(totalWidth)));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function renderTable(rows: TableRow[]): void {
|
|
206
|
+
const colWidths = calculateColWidths(rows);
|
|
159
207
|
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
break;
|
|
208
|
+
// Group rows by category
|
|
209
|
+
const tools = rows.filter(r => r.category === 'tool');
|
|
210
|
+
const extensions = rows.filter(r => r.category === 'vscode-extension');
|
|
211
|
+
|
|
212
|
+
// Render tools first
|
|
213
|
+
if (tools.length > 0) {
|
|
214
|
+
renderTableHeader(colWidths);
|
|
215
|
+
for (const row of tools) {
|
|
216
|
+
renderTableRow(row, colWidths);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Render VSCode extensions as a separate group
|
|
221
|
+
if (extensions.length > 0) {
|
|
222
|
+
renderGroupHeader('VSCode Extensions', colWidths);
|
|
223
|
+
for (const row of extensions) {
|
|
224
|
+
renderTableRow(row, colWidths);
|
|
178
225
|
}
|
|
179
|
-
const typeStr = row.type === 'npm' ? chalk.magenta('npm') : chalk.cyan('wget');
|
|
180
|
-
|
|
181
|
-
console.log(
|
|
182
|
-
' ' +
|
|
183
|
-
row.displayName.padEnd(colWidths.tool) +
|
|
184
|
-
row.version.padEnd(colWidths.latest) +
|
|
185
|
-
row.downloadedVersion.padEnd(colWidths.downloaded) +
|
|
186
|
-
statusColored +
|
|
187
|
-
typeStr
|
|
188
|
-
);
|
|
189
226
|
}
|
|
190
227
|
}
|
|
191
228
|
|
|
229
|
+
function isVscodeExtension(tool: VersionsJsonTool): boolean {
|
|
230
|
+
if (isNpmTool(tool)) return false;
|
|
231
|
+
// Check if filename ends with .vsix
|
|
232
|
+
return tool.filename.toLowerCase().endsWith('.vsix');
|
|
233
|
+
}
|
|
234
|
+
|
|
192
235
|
export async function promptToolSelection(
|
|
193
236
|
tools: VersionsJsonTool[],
|
|
194
237
|
downloaded: DownloadedState,
|
|
@@ -199,24 +242,30 @@ export async function promptToolSelection(
|
|
|
199
242
|
console.log(chalk.bold(`\nrelease-radar-cli`));
|
|
200
243
|
console.log(chalk.gray(`Last updated: ${new Date(generatedAt).toLocaleString()}\n`));
|
|
201
244
|
|
|
202
|
-
// Convert to table rows and display
|
|
203
|
-
const rows: TableRow[] =
|
|
204
|
-
displayName:
|
|
205
|
-
version:
|
|
206
|
-
downloadedVersion:
|
|
207
|
-
status:
|
|
208
|
-
type:
|
|
245
|
+
// Convert to table rows with category and display
|
|
246
|
+
const rows: TableRow[] = tools.map((tool, i) => ({
|
|
247
|
+
displayName: choices[i].displayName,
|
|
248
|
+
version: choices[i].version,
|
|
249
|
+
downloadedVersion: choices[i].downloadedVersion ?? '-',
|
|
250
|
+
status: choices[i].status,
|
|
251
|
+
type: choices[i].type === 'npm' ? 'npm' : 'download',
|
|
252
|
+
category: isVscodeExtension(tool) ? 'vscode-extension' : 'tool',
|
|
209
253
|
}));
|
|
210
254
|
|
|
211
255
|
renderTable(rows);
|
|
212
256
|
console.log('');
|
|
213
257
|
|
|
258
|
+
// Sort choices to match displayed order (tools first, then extensions)
|
|
259
|
+
const toolChoices = choices.filter((_, i) => !isVscodeExtension(tools[i]));
|
|
260
|
+
const extensionChoices = choices.filter((_, i) => isVscodeExtension(tools[i]));
|
|
261
|
+
const sortedChoices = [...toolChoices, ...extensionChoices];
|
|
262
|
+
|
|
214
263
|
const { selected } = await inquirer.prompt([
|
|
215
264
|
{
|
|
216
265
|
type: 'checkbox',
|
|
217
266
|
name: 'selected',
|
|
218
267
|
message: 'Select tools to download:',
|
|
219
|
-
choices:
|
|
268
|
+
choices: sortedChoices.map((choice) => ({
|
|
220
269
|
name: `${choice.displayName} ${choice.version}`,
|
|
221
270
|
value: choice,
|
|
222
271
|
checked: choice.status !== 'current',
|
package/config/downloads.json
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
"downloadUrl": "{{MIRROR_URL}}",
|
|
13
13
|
"filename": "claude-code-{{VERSION}}-win32-x64.vsix",
|
|
14
14
|
"mirror": {
|
|
15
|
-
"sourceUrl": "marketplace-api"
|
|
15
|
+
"sourceUrl": "marketplace-api",
|
|
16
|
+
"extensionId": "anthropic.claude-code",
|
|
17
|
+
"targetPlatform": "win32-x64"
|
|
16
18
|
}
|
|
17
19
|
},
|
|
18
20
|
"Claude Code CLI": {
|
|
@@ -89,5 +91,104 @@
|
|
|
89
91
|
"displayName": "PowerShell",
|
|
90
92
|
"downloadUrl": "github.com/PowerShell/PowerShell/releases/download/v{{VERSION}}/PowerShell-{{VERSION}}-win-x64.msi",
|
|
91
93
|
"filename": "PowerShell-{{VERSION}}-win-x64.msi"
|
|
94
|
+
},
|
|
95
|
+
"C/C++ Extension Pack": {
|
|
96
|
+
"displayName": "C/C++ Extension Pack",
|
|
97
|
+
"downloadUrl": "{{MIRROR_URL}}",
|
|
98
|
+
"filename": "cpptools-extension-pack-{{VERSION}}.vsix",
|
|
99
|
+
"mirror": {
|
|
100
|
+
"sourceUrl": "marketplace-api",
|
|
101
|
+
"extensionId": "ms-vscode.cpptools-extension-pack"
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"C/C++ Themes": {
|
|
105
|
+
"displayName": "C/C++ Themes",
|
|
106
|
+
"downloadUrl": "{{MIRROR_URL}}",
|
|
107
|
+
"filename": "cpptools-themes-{{VERSION}}.vsix",
|
|
108
|
+
"mirror": {
|
|
109
|
+
"sourceUrl": "marketplace-api",
|
|
110
|
+
"extensionId": "ms-vscode.cpptools-themes"
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
"YAML": {
|
|
114
|
+
"displayName": "YAML Extension",
|
|
115
|
+
"downloadUrl": "{{MIRROR_URL}}",
|
|
116
|
+
"filename": "vscode-yaml-{{VERSION}}.vsix",
|
|
117
|
+
"mirror": {
|
|
118
|
+
"sourceUrl": "marketplace-api",
|
|
119
|
+
"extensionId": "redhat.vscode-yaml"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"Python": {
|
|
123
|
+
"displayName": "Python Extension",
|
|
124
|
+
"downloadUrl": "{{MIRROR_URL}}",
|
|
125
|
+
"filename": "python-{{VERSION}}.vsix",
|
|
126
|
+
"mirror": {
|
|
127
|
+
"sourceUrl": "marketplace-api",
|
|
128
|
+
"extensionId": "ms-python.python"
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"Python Debugger": {
|
|
132
|
+
"displayName": "Python Debugger",
|
|
133
|
+
"downloadUrl": "{{MIRROR_URL}}",
|
|
134
|
+
"filename": "debugpy-{{VERSION}}.vsix",
|
|
135
|
+
"mirror": {
|
|
136
|
+
"sourceUrl": "marketplace-api",
|
|
137
|
+
"extensionId": "ms-python.debugpy"
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"Python Environments": {
|
|
141
|
+
"displayName": "Python Environments",
|
|
142
|
+
"downloadUrl": "{{MIRROR_URL}}",
|
|
143
|
+
"filename": "vscode-python-envs-{{VERSION}}.vsix",
|
|
144
|
+
"mirror": {
|
|
145
|
+
"sourceUrl": "marketplace-api",
|
|
146
|
+
"extensionId": "ms-python.vscode-python-envs"
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
"Pylance": {
|
|
150
|
+
"displayName": "Pylance",
|
|
151
|
+
"downloadUrl": "{{MIRROR_URL}}",
|
|
152
|
+
"filename": "vscode-pylance-{{VERSION}}.vsix",
|
|
153
|
+
"mirror": {
|
|
154
|
+
"sourceUrl": "marketplace-api",
|
|
155
|
+
"extensionId": "ms-python.vscode-pylance"
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"isort": {
|
|
159
|
+
"displayName": "isort Extension",
|
|
160
|
+
"downloadUrl": "{{MIRROR_URL}}",
|
|
161
|
+
"filename": "isort-{{VERSION}}.vsix",
|
|
162
|
+
"mirror": {
|
|
163
|
+
"sourceUrl": "marketplace-api",
|
|
164
|
+
"extensionId": "ms-python.isort"
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
"Go": {
|
|
168
|
+
"displayName": "Go Extension",
|
|
169
|
+
"downloadUrl": "{{MIRROR_URL}}",
|
|
170
|
+
"filename": "go-{{VERSION}}.vsix",
|
|
171
|
+
"mirror": {
|
|
172
|
+
"sourceUrl": "marketplace-api",
|
|
173
|
+
"extensionId": "golang.go"
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"GitHub Theme": {
|
|
177
|
+
"displayName": "GitHub Theme",
|
|
178
|
+
"downloadUrl": "{{MIRROR_URL}}",
|
|
179
|
+
"filename": "github-vscode-theme-{{VERSION}}.vsix",
|
|
180
|
+
"mirror": {
|
|
181
|
+
"sourceUrl": "marketplace-api",
|
|
182
|
+
"extensionId": "github.github-vscode-theme"
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
"Material Icon Theme": {
|
|
186
|
+
"displayName": "Material Icon Theme",
|
|
187
|
+
"downloadUrl": "{{MIRROR_URL}}",
|
|
188
|
+
"filename": "material-icon-theme-{{VERSION}}.vsix",
|
|
189
|
+
"mirror": {
|
|
190
|
+
"sourceUrl": "marketplace-api",
|
|
191
|
+
"extensionId": "pkief.material-icon-theme"
|
|
192
|
+
}
|
|
92
193
|
}
|
|
93
194
|
}
|
package/config/tools.json
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"checkIntervalHours":
|
|
2
|
+
"checkIntervalHours": 6,
|
|
3
|
+
"scheduleMode": "daily",
|
|
4
|
+
"dailyCheckTime": "06:00",
|
|
3
5
|
"tools": [
|
|
4
6
|
{
|
|
5
7
|
"name": "VSCode",
|
|
@@ -85,6 +87,61 @@
|
|
|
85
87
|
"name": "PowerShell",
|
|
86
88
|
"type": "github",
|
|
87
89
|
"repo": "PowerShell/PowerShell"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "C/C++ Extension Pack",
|
|
93
|
+
"type": "vscode-marketplace",
|
|
94
|
+
"extensionId": "ms-vscode.cpptools-extension-pack"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "C/C++ Themes",
|
|
98
|
+
"type": "vscode-marketplace",
|
|
99
|
+
"extensionId": "ms-vscode.cpptools-themes"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"name": "YAML",
|
|
103
|
+
"type": "vscode-marketplace",
|
|
104
|
+
"extensionId": "redhat.vscode-yaml"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"name": "Python",
|
|
108
|
+
"type": "vscode-marketplace",
|
|
109
|
+
"extensionId": "ms-python.python"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "Python Debugger",
|
|
113
|
+
"type": "vscode-marketplace",
|
|
114
|
+
"extensionId": "ms-python.debugpy"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "Python Environments",
|
|
118
|
+
"type": "vscode-marketplace",
|
|
119
|
+
"extensionId": "ms-python.vscode-python-envs"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"name": "Pylance",
|
|
123
|
+
"type": "vscode-marketplace",
|
|
124
|
+
"extensionId": "ms-python.vscode-pylance"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "isort",
|
|
128
|
+
"type": "vscode-marketplace",
|
|
129
|
+
"extensionId": "ms-python.isort"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"name": "Go",
|
|
133
|
+
"type": "vscode-marketplace",
|
|
134
|
+
"extensionId": "golang.go"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"name": "GitHub Theme",
|
|
138
|
+
"type": "vscode-marketplace",
|
|
139
|
+
"extensionId": "github.github-vscode-theme"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"name": "Material Icon Theme",
|
|
143
|
+
"type": "vscode-marketplace",
|
|
144
|
+
"extensionId": "pkief.material-icon-theme"
|
|
88
145
|
}
|
|
89
146
|
]
|
|
90
147
|
}
|
package/dist/asset-mirror.d.ts
CHANGED
|
@@ -4,14 +4,34 @@ export interface MirrorResult {
|
|
|
4
4
|
downloadUrl?: string;
|
|
5
5
|
error?: string;
|
|
6
6
|
}
|
|
7
|
+
export interface MirrorItem {
|
|
8
|
+
toolName: string;
|
|
9
|
+
version: string;
|
|
10
|
+
config: MirrorConfig;
|
|
11
|
+
filenameTemplate: string;
|
|
12
|
+
}
|
|
13
|
+
export interface BatchMirrorResult {
|
|
14
|
+
tag: string;
|
|
15
|
+
results: Map<string, MirrorResult>;
|
|
16
|
+
}
|
|
7
17
|
export declare class AssetMirror {
|
|
8
18
|
private repo;
|
|
19
|
+
/**
|
|
20
|
+
* Mirror a single tool (legacy method, still used for /mirror command)
|
|
21
|
+
*/
|
|
9
22
|
mirror(toolName: string, version: string, config: MirrorConfig, filenameTemplate: string): Promise<MirrorResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Mirror multiple tools into a single batch release
|
|
25
|
+
*/
|
|
26
|
+
mirrorBatch(items: MirrorItem[]): Promise<BatchMirrorResult>;
|
|
10
27
|
buildTag(toolName: string, version: string): string;
|
|
28
|
+
buildBatchTag(): string;
|
|
29
|
+
private findExistingAsset;
|
|
11
30
|
releaseExists(tag: string): Promise<boolean>;
|
|
12
31
|
private applyVersion;
|
|
13
32
|
private getSourceUrl;
|
|
14
33
|
private getMarketplaceVsixUrl;
|
|
15
34
|
private downloadFile;
|
|
16
35
|
private createRelease;
|
|
36
|
+
private createBatchRelease;
|
|
17
37
|
}
|
package/dist/asset-mirror.js
CHANGED
|
@@ -5,6 +5,9 @@ import { tmpdir } from 'os';
|
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
export class AssetMirror {
|
|
7
7
|
repo = 'lvntbkdmr/apps';
|
|
8
|
+
/**
|
|
9
|
+
* Mirror a single tool (legacy method, still used for /mirror command)
|
|
10
|
+
*/
|
|
8
11
|
async mirror(toolName, version, config, filenameTemplate) {
|
|
9
12
|
const tag = this.buildTag(toolName, version);
|
|
10
13
|
const filename = this.applyVersion(filenameTemplate, version);
|
|
@@ -38,10 +41,86 @@ export class AssetMirror {
|
|
|
38
41
|
return { success: false, error: message };
|
|
39
42
|
}
|
|
40
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Mirror multiple tools into a single batch release
|
|
46
|
+
*/
|
|
47
|
+
async mirrorBatch(items) {
|
|
48
|
+
const tag = this.buildBatchTag();
|
|
49
|
+
const results = new Map();
|
|
50
|
+
const downloadedFiles = [];
|
|
51
|
+
console.log(`[AssetMirror] Starting batch mirror for ${items.length} items, tag: ${tag}`);
|
|
52
|
+
// Download all files first
|
|
53
|
+
for (const item of items) {
|
|
54
|
+
const filename = this.applyVersion(item.filenameTemplate, item.version);
|
|
55
|
+
const downloadUrl = `github.com/${this.repo}/releases/download/${tag}/${filename}`;
|
|
56
|
+
try {
|
|
57
|
+
// Check if this specific file already exists in any release
|
|
58
|
+
const existingUrl = await this.findExistingAsset(item.toolName, item.version, filename);
|
|
59
|
+
if (existingUrl) {
|
|
60
|
+
console.log(`[AssetMirror] ${item.toolName} v${item.version} already mirrored, skipping`);
|
|
61
|
+
results.set(item.toolName, { success: true, downloadUrl: existingUrl });
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
console.log(`[AssetMirror] Getting source URL for ${item.toolName} v${item.version}...`);
|
|
65
|
+
const sourceUrl = await this.getSourceUrl(item.config, item.version);
|
|
66
|
+
const tempPath = join(tmpdir(), filename);
|
|
67
|
+
console.log(`[AssetMirror] Downloading to ${tempPath}...`);
|
|
68
|
+
await this.downloadFile(sourceUrl, tempPath);
|
|
69
|
+
downloadedFiles.push({ path: tempPath, filename });
|
|
70
|
+
results.set(item.toolName, { success: true, downloadUrl });
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
74
|
+
console.error(`[AssetMirror] Failed to download ${item.toolName}: ${message}`);
|
|
75
|
+
results.set(item.toolName, { success: false, error: message });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Create single release with all downloaded files
|
|
79
|
+
if (downloadedFiles.length > 0) {
|
|
80
|
+
try {
|
|
81
|
+
console.log(`[AssetMirror] Creating batch release ${tag} with ${downloadedFiles.length} assets...`);
|
|
82
|
+
await this.createBatchRelease(tag, downloadedFiles, items);
|
|
83
|
+
console.log(`[AssetMirror] Batch release created successfully`);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
87
|
+
console.error(`[AssetMirror] Failed to create batch release: ${message}`);
|
|
88
|
+
// Mark all as failed if release creation fails
|
|
89
|
+
for (const file of downloadedFiles) {
|
|
90
|
+
const toolName = items.find(i => this.applyVersion(i.filenameTemplate, i.version) === file.filename)?.toolName;
|
|
91
|
+
if (toolName) {
|
|
92
|
+
results.set(toolName, { success: false, error: message });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Cleanup temp files
|
|
97
|
+
for (const file of downloadedFiles) {
|
|
98
|
+
if (existsSync(file.path)) {
|
|
99
|
+
unlinkSync(file.path);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return { tag, results };
|
|
104
|
+
}
|
|
41
105
|
buildTag(toolName, version) {
|
|
42
106
|
const kebab = toolName.toLowerCase().replace(/\s+/g, '-');
|
|
43
107
|
return `${kebab}-v${version}`;
|
|
44
108
|
}
|
|
109
|
+
buildBatchTag() {
|
|
110
|
+
const now = new Date();
|
|
111
|
+
const date = now.toISOString().split('T')[0]; // YYYY-MM-DD
|
|
112
|
+
const time = now.toTimeString().split(' ')[0].replace(/:/g, ''); // HHMMSS
|
|
113
|
+
return `batch-${date}-${time}`;
|
|
114
|
+
}
|
|
115
|
+
async findExistingAsset(toolName, version, filename) {
|
|
116
|
+
// Check legacy per-tool release first
|
|
117
|
+
const legacyTag = this.buildTag(toolName, version);
|
|
118
|
+
if (await this.releaseExists(legacyTag)) {
|
|
119
|
+
return `github.com/${this.repo}/releases/download/${legacyTag}/${filename}`;
|
|
120
|
+
}
|
|
121
|
+
// Could also search batch releases, but for simplicity we skip if not in legacy
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
45
124
|
async releaseExists(tag) {
|
|
46
125
|
try {
|
|
47
126
|
execSync(`gh release view ${tag} --repo ${this.repo}`, {
|
|
@@ -59,14 +138,15 @@ export class AssetMirror {
|
|
|
59
138
|
}
|
|
60
139
|
async getSourceUrl(config, version) {
|
|
61
140
|
if (config.sourceUrl === 'marketplace-api') {
|
|
62
|
-
|
|
141
|
+
if (!config.extensionId) {
|
|
142
|
+
throw new Error('extensionId is required when sourceUrl is "marketplace-api"');
|
|
143
|
+
}
|
|
144
|
+
return this.getMarketplaceVsixUrl(config.extensionId, version, config.targetPlatform);
|
|
63
145
|
}
|
|
64
146
|
// For direct URLs, just return as-is (curl -L will follow redirects)
|
|
65
147
|
return config.sourceUrl;
|
|
66
148
|
}
|
|
67
|
-
async getMarketplaceVsixUrl(version) {
|
|
68
|
-
const extensionId = 'anthropic.claude-code';
|
|
69
|
-
const targetPlatform = 'win32-x64';
|
|
149
|
+
async getMarketplaceVsixUrl(extensionId, version, targetPlatform) {
|
|
70
150
|
const query = JSON.stringify({
|
|
71
151
|
filters: [{
|
|
72
152
|
criteria: [{ filterType: 7, value: extensionId }],
|
|
@@ -82,13 +162,23 @@ export class AssetMirror {
|
|
|
82
162
|
const response = execSync(cmd, { encoding: 'utf-8', timeout: 30000 });
|
|
83
163
|
const data = JSON.parse(response);
|
|
84
164
|
const versions = data.results?.[0]?.extensions?.[0]?.versions || [];
|
|
85
|
-
|
|
165
|
+
// Find matching version - with or without platform filter
|
|
166
|
+
const targetVersion = versions.find((v) => {
|
|
167
|
+
if (v.version !== version)
|
|
168
|
+
return false;
|
|
169
|
+
if (targetPlatform) {
|
|
170
|
+
return v.targetPlatform === targetPlatform;
|
|
171
|
+
}
|
|
172
|
+
// For universal extensions, targetPlatform is undefined/null
|
|
173
|
+
return !v.targetPlatform;
|
|
174
|
+
});
|
|
86
175
|
if (!targetVersion) {
|
|
87
|
-
|
|
176
|
+
const platformInfo = targetPlatform ? ` for ${targetPlatform}` : ' (universal)';
|
|
177
|
+
throw new Error(`Version ${version}${platformInfo} not found in marketplace for ${extensionId}`);
|
|
88
178
|
}
|
|
89
179
|
const vsixFile = targetVersion.files?.find((f) => f.assetType === 'Microsoft.VisualStudio.Services.VSIXPackage');
|
|
90
180
|
if (!vsixFile?.source) {
|
|
91
|
-
throw new Error(
|
|
181
|
+
throw new Error(`VSIX download URL not found in marketplace response for ${extensionId}`);
|
|
92
182
|
}
|
|
93
183
|
return vsixFile.source;
|
|
94
184
|
}
|
|
@@ -107,4 +197,23 @@ export class AssetMirror {
|
|
|
107
197
|
execSync(`gh release create "${tag}" "${filePath}#${filename}" ` +
|
|
108
198
|
`--repo ${this.repo} --title "${title}" --notes "${notes}"`, { encoding: 'utf-8', timeout: 300000 });
|
|
109
199
|
}
|
|
200
|
+
async createBatchRelease(tag, files, items) {
|
|
201
|
+
const date = new Date().toLocaleDateString('en-US', {
|
|
202
|
+
year: 'numeric',
|
|
203
|
+
month: 'long',
|
|
204
|
+
day: 'numeric',
|
|
205
|
+
});
|
|
206
|
+
const title = `Updates ${date}`;
|
|
207
|
+
// Build release notes listing all tools
|
|
208
|
+
const toolsList = items
|
|
209
|
+
.filter(item => files.some(f => f.filename === this.applyVersion(item.filenameTemplate, item.version)))
|
|
210
|
+
.map(item => `- ${item.toolName} ${item.version}`)
|
|
211
|
+
.join('\n');
|
|
212
|
+
const notes = `Mirrored for Nexus proxy access.\n\n**Included:**\n${toolsList}`;
|
|
213
|
+
// Build file arguments for gh release create
|
|
214
|
+
const fileArgs = files.map(f => `"${f.path}#${f.filename}"`).join(' ');
|
|
215
|
+
execSync(`gh release create "${tag}" ${fileArgs} ` +
|
|
216
|
+
`--repo ${this.repo} --title "${title}" --notes "${notes}"`, { encoding: 'utf-8', timeout: 600000 } // 10 minutes for multiple uploads
|
|
217
|
+
);
|
|
218
|
+
}
|
|
110
219
|
}
|