hostdb 0.10.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/cli/bin.ts ADDED
@@ -0,0 +1,527 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * hostdb CLI
4
+ *
5
+ * Query and download database binaries from hostdb releases.
6
+ */
7
+
8
+ import {
9
+ loadDatabasesJson,
10
+ loadReleasesJson,
11
+ type Platform,
12
+ type PlatformAsset,
13
+ } from '../lib/databases.js'
14
+
15
+ // Aliases for databases
16
+ const DATABASE_ALIASES: Record<string, string> = {
17
+ postgres: 'postgresql',
18
+ pg: 'postgresql',
19
+ mongo: 'mongodb',
20
+ maria: 'mariadb',
21
+ ch: 'clickhouse',
22
+ }
23
+
24
+ // Aliases for platforms - maps to array of platforms
25
+ const PLATFORM_ALIASES: Record<string, Platform[]> = {
26
+ // macOS
27
+ mac: ['darwin-arm64', 'darwin-x64'],
28
+ macos: ['darwin-arm64', 'darwin-x64'],
29
+ darwin: ['darwin-arm64', 'darwin-x64'],
30
+ osx: ['darwin-arm64', 'darwin-x64'],
31
+ apple: ['darwin-arm64', 'darwin-x64'],
32
+ // macOS specific
33
+ 'mac-arm': ['darwin-arm64'],
34
+ 'mac-intel': ['darwin-x64'],
35
+ 'm1': ['darwin-arm64'],
36
+ 'm2': ['darwin-arm64'],
37
+ 'm3': ['darwin-arm64'],
38
+ 'm4': ['darwin-arm64'],
39
+ // Windows
40
+ win: ['win32-x64'],
41
+ windows: ['win32-x64'],
42
+ win32: ['win32-x64'],
43
+ win64: ['win32-x64'],
44
+ // Linux
45
+ linux: ['linux-x64', 'linux-arm64'],
46
+ ubuntu: ['linux-x64', 'linux-arm64'],
47
+ debian: ['linux-x64', 'linux-arm64'],
48
+ // Linux specific
49
+ 'linux-amd64': ['linux-x64'],
50
+ 'linux-aarch64': ['linux-arm64'],
51
+ // Architecture shortcuts
52
+ x64: ['linux-x64', 'darwin-x64', 'win32-x64'],
53
+ arm64: ['linux-arm64', 'darwin-arm64'],
54
+ arm: ['linux-arm64', 'darwin-arm64'],
55
+ amd64: ['linux-x64', 'darwin-x64', 'win32-x64'],
56
+ aarch64: ['linux-arm64', 'darwin-arm64'],
57
+ // Direct platform names
58
+ 'linux-x64': ['linux-x64'],
59
+ 'linux-arm64': ['linux-arm64'],
60
+ 'darwin-x64': ['darwin-x64'],
61
+ 'darwin-arm64': ['darwin-arm64'],
62
+ 'win32-x64': ['win32-x64'],
63
+ }
64
+
65
+ function resolveDatabase(input: string): string | null {
66
+ const lower = input.toLowerCase()
67
+ if (DATABASE_ALIASES[lower]) {
68
+ return DATABASE_ALIASES[lower]
69
+ }
70
+ return lower
71
+ }
72
+
73
+ function resolvePlatforms(input: string): Platform[] | null {
74
+ const lower = input.toLowerCase()
75
+ if (PLATFORM_ALIASES[lower]) {
76
+ return PLATFORM_ALIASES[lower]
77
+ }
78
+ return null
79
+ }
80
+
81
+ function isVersionString(input: string): boolean {
82
+ // Matches version patterns like 8.4.3, 17.7.0, 25.12.3.21
83
+ return /^\d+(\.\d+)+$/.test(input)
84
+ }
85
+
86
+ function sortVersionsDesc(versions: string[]): string[] {
87
+ return [...versions].sort((a, b) => {
88
+ const partsA = a.split('.').map((p) => parseInt(p, 10) || 0)
89
+ const partsB = b.split('.').map((p) => parseInt(p, 10) || 0)
90
+ for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
91
+ const diff = (partsB[i] || 0) - (partsA[i] || 0)
92
+ if (diff !== 0) return diff
93
+ }
94
+ return 0
95
+ })
96
+ }
97
+
98
+ /**
99
+ * Resolve a platform alias to a single target platform from available platforms
100
+ */
101
+ function resolveTargetPlatform(
102
+ platformInput: string,
103
+ availablePlatforms: Partial<Record<Platform, PlatformAsset>>,
104
+ ): Platform {
105
+ const platforms = resolvePlatforms(platformInput)
106
+
107
+ if (platforms && platforms.length === 1) {
108
+ const target = platforms[0]
109
+ if (!availablePlatforms[target]) {
110
+ console.error(`Error: Platform '${platformInput}' not found`)
111
+ console.error(`\nAvailable: ${Object.keys(availablePlatforms).join(', ')}`)
112
+ process.exit(1)
113
+ }
114
+ return target
115
+ }
116
+
117
+ if (platforms) {
118
+ // Multiple platforms from alias - find first available
119
+ const target = platforms.find((p) => availablePlatforms[p])
120
+ if (!target) {
121
+ console.error(`Error: No matching platform for '${platformInput}'`)
122
+ console.error(`\nAvailable: ${Object.keys(availablePlatforms).join(', ')}`)
123
+ process.exit(1)
124
+ }
125
+ return target
126
+ }
127
+
128
+ // Try as direct platform name
129
+ const target = platformInput as Platform
130
+ if (!availablePlatforms[target]) {
131
+ console.error(`Error: Platform '${platformInput}' not found`)
132
+ console.error(`\nAvailable: ${Object.keys(availablePlatforms).join(', ')}`)
133
+ process.exit(1)
134
+ }
135
+ return target
136
+ }
137
+
138
+ function printUsage() {
139
+ console.log(`
140
+ hostdb - Query database binaries from hostdb releases
141
+
142
+ Usage:
143
+ hostdb list [filters...] [--json] List/filter databases, versions, platforms
144
+ hostdb url <db> <version> <platform> Get download URL
145
+ hostdb info <db> <version> <platform> Get full release info as JSON
146
+
147
+ Filters (combine any):
148
+ <database> Filter by database (mysql, postgres, mongodb, etc.)
149
+ <version> Filter by version (8.4.3, 17.7.0, etc.)
150
+ <platform> Filter by platform (mac, linux, windows, arm64, etc.)
151
+
152
+ Examples:
153
+ hostdb list List all databases
154
+ hostdb list --json List all databases as JSON
155
+ hostdb list mysql List MySQL versions
156
+ hostdb list mysql --json List MySQL versions as JSON
157
+ hostdb list mac List all versions available on macOS
158
+ hostdb list mysql mac List MySQL versions for macOS
159
+ hostdb list postgres linux arm64 List PostgreSQL for Linux ARM64
160
+ hostdb list mysql 8.4.3 List platforms for MySQL 8.4.3
161
+ hostdb list mysql 8.4.3 mac Show MySQL 8.4.3 for macOS
162
+
163
+ hostdb url mysql 8.4.3 darwin-arm64 Get download URL
164
+ hostdb info mysql 8.4.3 darwin-arm64 Get full info as JSON
165
+
166
+ Platform Aliases:
167
+ mac, macos, darwin, osx → darwin-x64, darwin-arm64
168
+ win, windows → win32-x64
169
+ linux, ubuntu, debian → linux-x64, linux-arm64
170
+ arm64, arm, aarch64 → linux-arm64, darwin-arm64
171
+ x64, amd64 → linux-x64, darwin-x64, win32-x64
172
+ m1, m2, m3, m4 → darwin-arm64
173
+
174
+ Database Aliases:
175
+ postgres, pg → postgresql
176
+ mongo → mongodb
177
+ maria → mariadb
178
+ ch → clickhouse
179
+ `)
180
+ }
181
+
182
+ function cmdList(filters: string[], jsonOutput: boolean): void {
183
+ const releases = loadReleasesJson()
184
+ const databases = loadDatabasesJson()
185
+
186
+ let dbFilter: string | null = null
187
+ let versionFilter: string | null = null
188
+ let platformFilter: Platform[] | null = null
189
+
190
+ // Parse filters
191
+ for (const filter of filters) {
192
+ const platforms = resolvePlatforms(filter)
193
+ if (platforms) {
194
+ platformFilter = platformFilter
195
+ ? platformFilter.filter((p) => platforms.includes(p))
196
+ : platforms
197
+ continue
198
+ }
199
+
200
+ if (isVersionString(filter)) {
201
+ versionFilter = filter
202
+ continue
203
+ }
204
+
205
+ // Assume it's a database name
206
+ const resolved = resolveDatabase(filter)
207
+ if (resolved && releases.databases[resolved]) {
208
+ dbFilter = resolved
209
+ } else if (resolved) {
210
+ console.error(`Error: Database '${filter}' not found`)
211
+ console.error(`\nAvailable: ${Object.keys(releases.databases).join(', ')}`)
212
+ process.exit(1)
213
+ }
214
+ }
215
+
216
+ // Determine what to show based on filters
217
+ if (!dbFilter && !versionFilter && !platformFilter) {
218
+ // No filters: show all databases
219
+ const result = Object.keys(releases.databases).sort().map((db) => {
220
+ const versions = Object.keys(releases.databases[db])
221
+ const info = databases.databases[db]
222
+ return {
223
+ database: db,
224
+ displayName: info?.displayName || db,
225
+ type: info?.type || '',
226
+ versions: versions.length,
227
+ }
228
+ })
229
+
230
+ if (jsonOutput) {
231
+ console.log(JSON.stringify(result, null, 2))
232
+ } else {
233
+ console.log('Available databases:\n')
234
+ for (const r of result) {
235
+ console.log(` ${r.database.padEnd(15)} ${r.displayName.padEnd(15)} ${r.type.padEnd(20)} (${r.versions} versions)`)
236
+ }
237
+ }
238
+ return
239
+ }
240
+
241
+ if (dbFilter && !versionFilter) {
242
+ // Database specified, no version: show versions
243
+ const dbReleases = releases.databases[dbFilter]
244
+ let versions = sortVersionsDesc(Object.keys(dbReleases))
245
+
246
+ // Filter by platform if specified
247
+ if (platformFilter) {
248
+ versions = versions.filter((v) => {
249
+ const release = dbReleases[v]
250
+ return platformFilter!.some((p) => release.platforms[p])
251
+ })
252
+ }
253
+
254
+ const result = versions.map((v) => {
255
+ const release = dbReleases[v]
256
+ const availablePlatforms = Object.keys(release.platforms) as Platform[]
257
+ const filteredPlatforms = platformFilter
258
+ ? availablePlatforms.filter((p) => platformFilter!.includes(p))
259
+ : availablePlatforms
260
+ return {
261
+ version: v,
262
+ platforms: filteredPlatforms,
263
+ releasedAt: release.releasedAt,
264
+ }
265
+ })
266
+
267
+ if (jsonOutput) {
268
+ console.log(JSON.stringify({ database: dbFilter, versions: result }, null, 2))
269
+ } else {
270
+ const platformLabel = platformFilter ? ` (${platformFilter.join(', ')})` : ''
271
+ console.log(`Versions for ${dbFilter}${platformLabel}:\n`)
272
+ for (const r of result) {
273
+ console.log(` ${r.version.padEnd(15)} (${r.platforms.join(', ')})`)
274
+ }
275
+ }
276
+ return
277
+ }
278
+
279
+ if (dbFilter && versionFilter && !platformFilter) {
280
+ // Database and version: show platforms
281
+ const dbReleases = releases.databases[dbFilter]
282
+ if (!dbReleases[versionFilter]) {
283
+ console.error(`Error: Version '${versionFilter}' not found for ${dbFilter}`)
284
+ console.error(`\nAvailable: ${sortVersionsDesc(Object.keys(dbReleases)).join(', ')}`)
285
+ process.exit(1)
286
+ }
287
+
288
+ const release = dbReleases[versionFilter]
289
+ const result = (Object.keys(release.platforms) as Platform[]).sort().map((p) => {
290
+ const asset = release.platforms[p]!
291
+ return {
292
+ platform: p,
293
+ url: asset.url,
294
+ sha256: asset.sha256,
295
+ size: asset.size,
296
+ sizeMB: (asset.size / 1024 / 1024).toFixed(1),
297
+ }
298
+ })
299
+
300
+ if (jsonOutput) {
301
+ console.log(JSON.stringify({
302
+ database: dbFilter,
303
+ version: versionFilter,
304
+ releaseTag: release.releaseTag,
305
+ releasedAt: release.releasedAt,
306
+ platforms: result,
307
+ }, null, 2))
308
+ } else {
309
+ console.log(`Platforms for ${dbFilter} ${versionFilter}:\n`)
310
+ for (const r of result) {
311
+ console.log(` ${r.platform.padEnd(15)} ${r.sizeMB} MB`)
312
+ }
313
+ }
314
+ return
315
+ }
316
+
317
+ if (dbFilter && versionFilter && platformFilter) {
318
+ // All three: show specific assets
319
+ const dbReleases = releases.databases[dbFilter]
320
+ if (!dbReleases[versionFilter]) {
321
+ console.error(`Error: Version '${versionFilter}' not found for ${dbFilter}`)
322
+ console.error(`\nAvailable: ${sortVersionsDesc(Object.keys(dbReleases)).join(', ')}`)
323
+ process.exit(1)
324
+ }
325
+
326
+ const release = dbReleases[versionFilter]
327
+ const matchingPlatforms = platformFilter.filter((p) => release.platforms[p])
328
+
329
+ if (matchingPlatforms.length === 0) {
330
+ console.error(`Error: No matching platforms for ${dbFilter} ${versionFilter}`)
331
+ console.error(`\nAvailable: ${Object.keys(release.platforms).join(', ')}`)
332
+ console.error(`Requested: ${platformFilter.join(', ')}`)
333
+ process.exit(1)
334
+ }
335
+
336
+ const result = matchingPlatforms.map((p) => {
337
+ const asset = release.platforms[p]!
338
+ return {
339
+ database: dbFilter,
340
+ version: versionFilter,
341
+ platform: p,
342
+ url: asset.url,
343
+ sha256: asset.sha256,
344
+ size: asset.size,
345
+ releaseTag: release.releaseTag,
346
+ releasedAt: release.releasedAt,
347
+ }
348
+ })
349
+
350
+ if (jsonOutput) {
351
+ console.log(JSON.stringify(result.length === 1 ? result[0] : result, null, 2))
352
+ } else {
353
+ for (const r of result) {
354
+ const sizeMB = (r.size / 1024 / 1024).toFixed(1)
355
+ console.log(`${r.database} ${r.version} ${r.platform}`)
356
+ console.log(` URL: ${r.url}`)
357
+ console.log(` SHA256: ${r.sha256}`)
358
+ console.log(` Size: ${sizeMB} MB`)
359
+ if (result.length > 1) console.log()
360
+ }
361
+ }
362
+ return
363
+ }
364
+
365
+ if (!dbFilter && platformFilter) {
366
+ // Platform only: show all databases/versions for that platform
367
+ const result: Array<{ database: string; version: string; platforms: string[] }> = []
368
+
369
+ for (const db of Object.keys(releases.databases).sort()) {
370
+ const dbReleases = releases.databases[db]
371
+ for (const version of sortVersionsDesc(Object.keys(dbReleases))) {
372
+ const release = dbReleases[version]
373
+ const matchingPlatforms = platformFilter.filter((p) => release.platforms[p])
374
+ if (matchingPlatforms.length > 0) {
375
+ result.push({
376
+ database: db,
377
+ version,
378
+ platforms: matchingPlatforms,
379
+ })
380
+ }
381
+ }
382
+ }
383
+
384
+ if (jsonOutput) {
385
+ console.log(JSON.stringify(result, null, 2))
386
+ } else {
387
+ console.log(`Releases for ${platformFilter.join(', ')}:\n`)
388
+ let currentDb = ''
389
+ for (const r of result) {
390
+ if (r.database !== currentDb) {
391
+ if (currentDb) console.log()
392
+ console.log(` ${r.database}:`)
393
+ currentDb = r.database
394
+ }
395
+ console.log(` ${r.version.padEnd(15)} (${r.platforms.join(', ')})`)
396
+ }
397
+ }
398
+ return
399
+ }
400
+
401
+ // Fallback
402
+ console.error('Invalid filter combination')
403
+ process.exit(1)
404
+ }
405
+
406
+ function cmdUrl(database: string, version: string, platform: string) {
407
+ const releases = loadReleasesJson()
408
+
409
+ const db = resolveDatabase(database)
410
+ if (!db || !releases.databases[db]) {
411
+ console.error(`Error: Database '${database}' not found`)
412
+ console.error(`\nAvailable: ${Object.keys(releases.databases).sort().join(', ')}`)
413
+ process.exit(1)
414
+ }
415
+
416
+ if (!releases.databases[db][version]) {
417
+ console.error(`Error: Version '${version}' not found for ${db}`)
418
+ console.error(`\nAvailable: ${sortVersionsDesc(Object.keys(releases.databases[db])).join(', ')}`)
419
+ process.exit(1)
420
+ }
421
+
422
+ const release = releases.databases[db][version]
423
+ const targetPlatform = resolveTargetPlatform(platform, release.platforms)
424
+ const asset = release.platforms[targetPlatform]!
425
+
426
+ console.log(asset.url)
427
+ }
428
+
429
+ function cmdInfo(database: string, version: string, platform: string) {
430
+ const releases = loadReleasesJson()
431
+
432
+ const db = resolveDatabase(database)
433
+ if (!db || !releases.databases[db]) {
434
+ console.error(`Error: Database '${database}' not found`)
435
+ console.error(`\nAvailable: ${Object.keys(releases.databases).sort().join(', ')}`)
436
+ process.exit(1)
437
+ }
438
+
439
+ if (!releases.databases[db][version]) {
440
+ console.error(`Error: Version '${version}' not found for ${db}`)
441
+ console.error(`\nAvailable: ${sortVersionsDesc(Object.keys(releases.databases[db])).join(', ')}`)
442
+ process.exit(1)
443
+ }
444
+
445
+ const release = releases.databases[db][version]
446
+ const targetPlatform = resolveTargetPlatform(platform, release.platforms)
447
+ const asset = release.platforms[targetPlatform]!
448
+
449
+ console.log(JSON.stringify({
450
+ database: db,
451
+ version,
452
+ platform: targetPlatform,
453
+ url: asset.url,
454
+ sha256: asset.sha256,
455
+ size: asset.size,
456
+ releaseTag: release.releaseTag,
457
+ releasedAt: release.releasedAt,
458
+ }, null, 2))
459
+ }
460
+
461
+ function main() {
462
+ const args = process.argv.slice(2)
463
+
464
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
465
+ printUsage()
466
+ process.exit(0)
467
+ }
468
+
469
+ // Check for --json flag
470
+ const jsonOutput = args.includes('--json')
471
+ const filteredArgs = args.filter((a) => a !== '--json')
472
+
473
+ const command = filteredArgs[0]
474
+
475
+ switch (command) {
476
+ case 'list':
477
+ case 'ls':
478
+ cmdList(filteredArgs.slice(1), jsonOutput)
479
+ break
480
+
481
+ case 'url':
482
+ if (!filteredArgs[1] || !filteredArgs[2] || !filteredArgs[3]) {
483
+ console.error('Error: Missing arguments')
484
+ console.error('Usage: hostdb url <database> <version> <platform>')
485
+ process.exit(1)
486
+ }
487
+ cmdUrl(filteredArgs[1], filteredArgs[2], filteredArgs[3])
488
+ break
489
+
490
+ case 'info':
491
+ if (!filteredArgs[1] || !filteredArgs[2] || !filteredArgs[3]) {
492
+ console.error('Error: Missing arguments')
493
+ console.error('Usage: hostdb info <database> <version> <platform>')
494
+ process.exit(1)
495
+ }
496
+ cmdInfo(filteredArgs[1], filteredArgs[2], filteredArgs[3])
497
+ break
498
+
499
+ case 'versions': {
500
+ if (!filteredArgs[1]) {
501
+ console.error('Error: Missing database argument')
502
+ console.error('Usage: hostdb versions <database>')
503
+ process.exit(1)
504
+ }
505
+ const db = resolveDatabase(filteredArgs[1])
506
+ cmdList(db ? [db] : [filteredArgs[1]], jsonOutput)
507
+ break
508
+ }
509
+
510
+ case 'platforms': {
511
+ if (!filteredArgs[1] || !filteredArgs[2]) {
512
+ console.error('Error: Missing arguments')
513
+ console.error('Usage: hostdb platforms <database> <version>')
514
+ process.exit(1)
515
+ }
516
+ const db = resolveDatabase(filteredArgs[1])
517
+ cmdList(db ? [db, filteredArgs[2]] : [filteredArgs[1], filteredArgs[2]], jsonOutput)
518
+ break
519
+ }
520
+
521
+ default:
522
+ // Try to interpret as list with filters
523
+ cmdList(filteredArgs, jsonOutput)
524
+ }
525
+ }
526
+
527
+ main()