spindb 0.34.5 → 0.35.3
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 +4 -4
- package/cli/commands/create.ts +22 -1
- package/cli/commands/engines.ts +56 -22
- package/cli/commands/menu/container-handlers.ts +17 -1
- package/cli/commands/menu/engine-handlers.ts +48 -29
- package/cli/ui/theme.ts +5 -2
- package/config/engines-registry.ts +56 -0
- package/config/engines.json +14 -3
- package/config/engines.schema.json +13 -0
- package/core/base-binary-manager.ts +6 -2
- package/core/base-document-binary-manager.ts +5 -2
- package/core/base-embedded-binary-manager.ts +5 -2
- package/core/base-server-binary-manager.ts +5 -2
- package/core/hostdb-client.ts +157 -22
- package/core/hostdb-metadata.ts +67 -43
- package/engines/clickhouse/binary-urls.ts +1 -1
- package/engines/cockroachdb/binary-urls.ts +9 -7
- package/engines/cockroachdb/hostdb-releases.ts +18 -106
- package/engines/cockroachdb/version-maps.ts +1 -1
- package/engines/couchdb/binary-urls.ts +1 -1
- package/engines/duckdb/binary-urls.ts +1 -1
- package/engines/duckdb/index.ts +4 -74
- package/engines/ferretdb/README.md +76 -38
- package/engines/ferretdb/backup.ts +18 -10
- package/engines/ferretdb/binary-manager.ts +233 -35
- package/engines/ferretdb/binary-urls.ts +69 -24
- package/engines/ferretdb/index.ts +424 -213
- package/engines/ferretdb/restore.ts +23 -16
- package/engines/ferretdb/version-maps.ts +36 -8
- package/engines/index.ts +3 -4
- package/engines/influxdb/binary-urls.ts +1 -1
- package/engines/mariadb/binary-urls.ts +2 -2
- package/engines/meilisearch/binary-urls.ts +1 -1
- package/engines/mysql/binary-urls.ts +2 -2
- package/engines/postgresql/binary-urls.ts +1 -1
- package/engines/qdrant/binary-urls.ts +1 -1
- package/engines/questdb/binary-manager.ts +16 -9
- package/engines/questdb/binary-urls.ts +9 -10
- package/engines/questdb/hostdb-releases.ts +19 -97
- package/engines/questdb/version-maps.ts +2 -2
- package/engines/redis/binary-urls.ts +1 -8
- package/engines/sqlite/binary-urls.ts +1 -1
- package/engines/sqlite/index.ts +4 -74
- package/engines/surrealdb/binary-urls.ts +9 -7
- package/engines/surrealdb/hostdb-releases.ts +18 -106
- package/engines/surrealdb/version-maps.ts +1 -1
- package/engines/typedb/binary-urls.ts +10 -8
- package/engines/typedb/hostdb-releases.ts +18 -113
- package/engines/typedb/version-maps.ts +1 -1
- package/engines/valkey/binary-urls.ts +1 -1
- package/package.json +4 -1
package/core/hostdb-client.ts
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared hostdb Client Module
|
|
3
3
|
*
|
|
4
|
-
* Provides centralized access to
|
|
5
|
-
*
|
|
4
|
+
* Provides centralized access to pre-built database binaries.
|
|
5
|
+
* Primary registry: registry.layerbase.host
|
|
6
|
+
* Fallback registry: GitHub releases (robertjbass/hostdb)
|
|
6
7
|
*
|
|
7
|
-
* hostdb provides pre-built database binaries for multiple platforms.
|
|
8
8
|
* This module handles fetching releases.json with caching to avoid
|
|
9
9
|
* repeated network requests.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { Platform, type Arch, type Engine } from '../types'
|
|
13
|
+
import { logDebug } from './error-handler'
|
|
14
|
+
|
|
15
|
+
// Registry base URLs
|
|
16
|
+
export const LAYERBASE_REGISTRY_BASE = 'https://registry.layerbase.host'
|
|
17
|
+
export const GITHUB_REGISTRY_BASE =
|
|
18
|
+
'https://github.com/robertjbass/hostdb/releases/download'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Toggle GitHub fallback for binary downloads and releases.json fetches.
|
|
22
|
+
* Set to `false` to exclusively test the Layerbase registry.
|
|
23
|
+
* Must be re-enabled before release (see PRE_RELEASE_TASKS.md).
|
|
24
|
+
*/
|
|
25
|
+
export const ENABLE_GITHUB_FALLBACK = false
|
|
13
26
|
|
|
14
27
|
// Platform definition in hostdb releases.json
|
|
15
28
|
export type HostdbPlatform = {
|
|
@@ -59,9 +72,21 @@ let cachedReleases: HostdbReleasesData | null = null
|
|
|
59
72
|
let cacheTimestamp = 0
|
|
60
73
|
const CACHE_TTL_MS = 5 * 60 * 1000 // 5 minutes
|
|
61
74
|
|
|
62
|
-
const
|
|
75
|
+
export const LAYERBASE_RELEASES_URL =
|
|
76
|
+
'https://registry.layerbase.host/releases.json'
|
|
77
|
+
export const GITHUB_RELEASES_URL =
|
|
63
78
|
'https://raw.githubusercontent.com/robertjbass/hostdb/main/releases.json'
|
|
64
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Get the list of releases.json URLs to try, respecting ENABLE_GITHUB_FALLBACK.
|
|
82
|
+
* When fallback is disabled, only the Layerbase URL is returned.
|
|
83
|
+
*/
|
|
84
|
+
export function getReleasesUrls(): string[] {
|
|
85
|
+
return ENABLE_GITHUB_FALLBACK
|
|
86
|
+
? [LAYERBASE_RELEASES_URL, GITHUB_RELEASES_URL]
|
|
87
|
+
: [LAYERBASE_RELEASES_URL]
|
|
88
|
+
}
|
|
89
|
+
|
|
65
90
|
/**
|
|
66
91
|
* Fetch releases.json from hostdb repository with caching.
|
|
67
92
|
*
|
|
@@ -74,27 +99,39 @@ export async function fetchHostdbReleases(): Promise<HostdbReleasesData> {
|
|
|
74
99
|
return cachedReleases
|
|
75
100
|
}
|
|
76
101
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
102
|
+
// Try layerbase registry first, fall back to GitHub (if enabled)
|
|
103
|
+
const urls = getReleasesUrls()
|
|
104
|
+
for (let i = 0; i < urls.length; i++) {
|
|
105
|
+
const url = urls[i]
|
|
106
|
+
const isLast = i === urls.length - 1
|
|
107
|
+
try {
|
|
108
|
+
const response = await fetch(url, {
|
|
109
|
+
signal: AbortSignal.timeout(5000),
|
|
110
|
+
})
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
113
|
+
}
|
|
84
114
|
|
|
85
|
-
|
|
115
|
+
const data = (await response.json()) as HostdbReleasesData
|
|
86
116
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
117
|
+
// Cache the results
|
|
118
|
+
cachedReleases = data
|
|
119
|
+
cacheTimestamp = Date.now()
|
|
90
120
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
121
|
+
return data
|
|
122
|
+
} catch (error) {
|
|
123
|
+
const err = error as Error
|
|
124
|
+
logDebug(`Failed to fetch releases from ${url}: ${err.message}`)
|
|
125
|
+
// If this was the last URL, rethrow
|
|
126
|
+
if (isLast) {
|
|
127
|
+
throw error
|
|
128
|
+
}
|
|
129
|
+
// Otherwise try the next URL
|
|
130
|
+
}
|
|
97
131
|
}
|
|
132
|
+
|
|
133
|
+
// Should be unreachable (loop always throws on last iteration)
|
|
134
|
+
throw new Error('Failed to fetch hostdb releases from all registries')
|
|
98
135
|
}
|
|
99
136
|
|
|
100
137
|
/**
|
|
@@ -175,7 +212,105 @@ export function buildHostdbUrl(
|
|
|
175
212
|
const tag = `${engine}-${version}`
|
|
176
213
|
const filename = `${engine}-${version}-${hostdbPlatform}.${extension}`
|
|
177
214
|
|
|
178
|
-
return
|
|
215
|
+
return `${LAYERBASE_REGISTRY_BASE}/${tag}/${filename}`
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Build a GitHub fallback URL for a hostdb release (same path scheme as layerbase).
|
|
220
|
+
*/
|
|
221
|
+
export function buildGithubFallbackUrl(
|
|
222
|
+
engine: Engine | string,
|
|
223
|
+
options: BuildHostdbUrlOptions,
|
|
224
|
+
): string {
|
|
225
|
+
const { version, hostdbPlatform, extension = 'tar.gz' } = options
|
|
226
|
+
const tag = `${engine}-${version}`
|
|
227
|
+
const filename = `${engine}-${version}-${hostdbPlatform}.${extension}`
|
|
228
|
+
|
|
229
|
+
return `${GITHUB_REGISTRY_BASE}/${tag}/${filename}`
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Convert a layerbase registry URL to its GitHub fallback equivalent.
|
|
234
|
+
* Returns null if the URL is not a layerbase URL or if GitHub fallback is disabled.
|
|
235
|
+
*/
|
|
236
|
+
export function getRegistryFallbackUrl(url: string): string | null {
|
|
237
|
+
if (!ENABLE_GITHUB_FALLBACK) return null
|
|
238
|
+
if (url.startsWith(LAYERBASE_REGISTRY_BASE)) {
|
|
239
|
+
return url.replace(LAYERBASE_REGISTRY_BASE, GITHUB_REGISTRY_BASE)
|
|
240
|
+
}
|
|
241
|
+
return null
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Fetch wrapper that tries the primary URL first, then falls back to the
|
|
246
|
+
* GitHub registry if the primary is a layerbase URL and the request fails
|
|
247
|
+
* with a 404, 5xx, or network error.
|
|
248
|
+
*
|
|
249
|
+
* AbortError (timeout) is never retried — it propagates immediately.
|
|
250
|
+
*/
|
|
251
|
+
export async function fetchWithRegistryFallback(
|
|
252
|
+
url: string,
|
|
253
|
+
options?: RequestInit,
|
|
254
|
+
): Promise<Response> {
|
|
255
|
+
try {
|
|
256
|
+
const response = await fetch(url, options)
|
|
257
|
+
if (response.status === 404 || response.status >= 500) {
|
|
258
|
+
const fallbackUrl = getRegistryFallbackUrl(url)
|
|
259
|
+
if (fallbackUrl) {
|
|
260
|
+
logDebug(
|
|
261
|
+
`Primary registry returned ${response.status}, trying GitHub fallback`,
|
|
262
|
+
)
|
|
263
|
+
return await fetch(fallbackUrl, options)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return response
|
|
267
|
+
} catch (error) {
|
|
268
|
+
const err = error as Error
|
|
269
|
+
// Never retry on timeout (AbortError)
|
|
270
|
+
if (err.name === 'AbortError') {
|
|
271
|
+
throw error
|
|
272
|
+
}
|
|
273
|
+
const fallbackUrl = getRegistryFallbackUrl(url)
|
|
274
|
+
if (fallbackUrl) {
|
|
275
|
+
logDebug(
|
|
276
|
+
`Primary registry fetch failed (${err.message}), trying GitHub fallback`,
|
|
277
|
+
)
|
|
278
|
+
return await fetch(fallbackUrl, options)
|
|
279
|
+
}
|
|
280
|
+
throw error
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Try fetching from multiple registry URLs in order, returning the first
|
|
286
|
+
* successful (response.ok) Response. Logs per-URL failures via the
|
|
287
|
+
* supplied logger callback.
|
|
288
|
+
*
|
|
289
|
+
* @param urls - URLs to try in order (e.g., layerbase then GitHub)
|
|
290
|
+
* @param logger - Callback for logging per-URL failures
|
|
291
|
+
* @param timeoutMs - Per-request timeout in milliseconds (default: 5000)
|
|
292
|
+
* @returns The first successful Response
|
|
293
|
+
* @throws Error if all URLs fail
|
|
294
|
+
*/
|
|
295
|
+
export async function fetchFromRegistryUrls(
|
|
296
|
+
urls: string[],
|
|
297
|
+
logger: (message: string) => void,
|
|
298
|
+
timeoutMs: number = 5000,
|
|
299
|
+
): Promise<Response> {
|
|
300
|
+
let lastError: Error | null = null
|
|
301
|
+
for (const url of urls) {
|
|
302
|
+
try {
|
|
303
|
+
const response = await fetch(url, {
|
|
304
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
305
|
+
})
|
|
306
|
+
if (response.ok) return response
|
|
307
|
+
logger(`Registry fetch from ${url}: HTTP ${response.status}`)
|
|
308
|
+
} catch (error) {
|
|
309
|
+
logger(`Registry fetch from ${url} failed: ${error}`)
|
|
310
|
+
lastError = error as Error
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
throw lastError ?? new Error('All release registries failed')
|
|
179
314
|
}
|
|
180
315
|
|
|
181
316
|
export type BuildDownloadUrlOptions = {
|
package/core/hostdb-metadata.ts
CHANGED
|
@@ -2,15 +2,19 @@
|
|
|
2
2
|
* Fetches metadata from hostdb (databases.json and downloads.json)
|
|
3
3
|
* to understand what tools each engine needs and how to install them.
|
|
4
4
|
*
|
|
5
|
+
* Primary registry: registry.layerbase.host
|
|
6
|
+
* Fallback registry: GitHub raw (robertjbass/hostdb)
|
|
7
|
+
*
|
|
5
8
|
* Architecture:
|
|
6
9
|
* - databases.json: Lists server, client, utilities, and enhanced CLI tools for each engine
|
|
7
10
|
* - downloads.json: Provides package manager commands for installing tools
|
|
8
11
|
*/
|
|
9
12
|
|
|
10
13
|
import { logDebug } from './error-handler'
|
|
14
|
+
import { LAYERBASE_REGISTRY_BASE } from './hostdb-client'
|
|
11
15
|
import type { Engine } from '../types'
|
|
12
16
|
|
|
13
|
-
const
|
|
17
|
+
const GITHUB_RAW_BASE =
|
|
14
18
|
'https://raw.githubusercontent.com/robertjbass/hostdb/main'
|
|
15
19
|
|
|
16
20
|
const CACHE_TTL_MS = 5 * 60 * 1000 // 5 minutes
|
|
@@ -22,12 +26,29 @@ type CliTools = {
|
|
|
22
26
|
enhanced: string[]
|
|
23
27
|
}
|
|
24
28
|
|
|
29
|
+
export type VersionEntryObject = {
|
|
30
|
+
enabled?: boolean
|
|
31
|
+
platforms?: string[]
|
|
32
|
+
dependencies?: Array<{
|
|
33
|
+
database: string
|
|
34
|
+
cascadeDelete: boolean
|
|
35
|
+
note?: string
|
|
36
|
+
}>
|
|
37
|
+
cliTools?: CliTools
|
|
38
|
+
}
|
|
39
|
+
|
|
25
40
|
type DatabaseEntry = {
|
|
26
41
|
displayName: string
|
|
27
42
|
cliTools: CliTools
|
|
28
|
-
versions: Record<string, boolean
|
|
29
|
-
|
|
30
|
-
|
|
43
|
+
versions: Record<string, boolean | VersionEntryObject>
|
|
44
|
+
platforms?: string[]
|
|
45
|
+
dependencies?: Array<{
|
|
46
|
+
database: string
|
|
47
|
+
cascadeDelete: boolean
|
|
48
|
+
note?: string
|
|
49
|
+
}>
|
|
50
|
+
spindbStatus?: string
|
|
51
|
+
hostedServiceAllowed?: boolean
|
|
31
52
|
}
|
|
32
53
|
|
|
33
54
|
type PackageManagerDef = {
|
|
@@ -69,7 +90,7 @@ let downloadsCache: { data: DownloadsJson; timestamp: number } | null = null
|
|
|
69
90
|
const inFlightRequests = new Map<string, Promise<unknown>>()
|
|
70
91
|
|
|
71
92
|
async function fetchWithCache<T>(
|
|
72
|
-
|
|
93
|
+
urls: string[],
|
|
73
94
|
getCache: () => { data: T; timestamp: number } | null,
|
|
74
95
|
setCache: (cache: { data: T; timestamp: number }) => void,
|
|
75
96
|
): Promise<T> {
|
|
@@ -80,35 +101,48 @@ async function fetchWithCache<T>(
|
|
|
80
101
|
return cache.data
|
|
81
102
|
}
|
|
82
103
|
|
|
83
|
-
// Check for in-flight request
|
|
84
|
-
const
|
|
104
|
+
// Check for in-flight request using the primary URL as key
|
|
105
|
+
const cacheKey = urls[0]
|
|
106
|
+
const inFlight = inFlightRequests.get(cacheKey)
|
|
85
107
|
if (inFlight) {
|
|
86
108
|
return inFlight as Promise<T>
|
|
87
109
|
}
|
|
88
110
|
|
|
89
|
-
// Create the fetch promise
|
|
111
|
+
// Create the fetch promise — try each URL in order
|
|
90
112
|
const fetchPromise = (async () => {
|
|
91
113
|
try {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
114
|
+
let lastError: Error | null = null
|
|
115
|
+
for (const url of urls) {
|
|
116
|
+
try {
|
|
117
|
+
const response = await fetch(url)
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
throw new Error(`Failed to fetch ${url}: ${response.status}`)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const data = (await response.json()) as T
|
|
123
|
+
setCache({ data, timestamp: Date.now() })
|
|
124
|
+
return data
|
|
125
|
+
} catch (error) {
|
|
126
|
+
lastError = error as Error
|
|
127
|
+
logDebug(`Metadata fetch from ${url} failed: ${lastError.message}`)
|
|
128
|
+
}
|
|
95
129
|
}
|
|
96
|
-
|
|
97
|
-
const data = (await response.json()) as T
|
|
98
|
-
setCache({ data, timestamp: Date.now() })
|
|
99
|
-
return data
|
|
130
|
+
throw lastError ?? new Error('All metadata URLs failed')
|
|
100
131
|
} finally {
|
|
101
|
-
inFlightRequests.delete(
|
|
132
|
+
inFlightRequests.delete(cacheKey)
|
|
102
133
|
}
|
|
103
134
|
})()
|
|
104
135
|
|
|
105
|
-
inFlightRequests.set(
|
|
136
|
+
inFlightRequests.set(cacheKey, fetchPromise)
|
|
106
137
|
return fetchPromise
|
|
107
138
|
}
|
|
108
139
|
|
|
109
140
|
export async function fetchDatabasesJson(): Promise<DatabasesJson> {
|
|
110
141
|
return fetchWithCache(
|
|
111
|
-
|
|
142
|
+
[
|
|
143
|
+
`${LAYERBASE_REGISTRY_BASE}/databases.json`,
|
|
144
|
+
`${GITHUB_RAW_BASE}/databases.json`,
|
|
145
|
+
],
|
|
112
146
|
() => databasesCache,
|
|
113
147
|
(c) => {
|
|
114
148
|
databasesCache = c
|
|
@@ -118,7 +152,10 @@ export async function fetchDatabasesJson(): Promise<DatabasesJson> {
|
|
|
118
152
|
|
|
119
153
|
export async function fetchDownloadsJson(): Promise<DownloadsJson> {
|
|
120
154
|
return fetchWithCache(
|
|
121
|
-
|
|
155
|
+
[
|
|
156
|
+
`${LAYERBASE_REGISTRY_BASE}/downloads.json`,
|
|
157
|
+
`${GITHUB_RAW_BASE}/downloads.json`,
|
|
158
|
+
],
|
|
122
159
|
() => downloadsCache,
|
|
123
160
|
(c) => {
|
|
124
161
|
downloadsCache = c
|
|
@@ -275,6 +312,16 @@ export async function getPackagesForTools(
|
|
|
275
312
|
}
|
|
276
313
|
}
|
|
277
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Check if a version entry is enabled.
|
|
317
|
+
* Handles both old schema (boolean) and new schema (object with optional enabled field).
|
|
318
|
+
* Objects are enabled by default unless explicitly `{ enabled: false }`.
|
|
319
|
+
*/
|
|
320
|
+
export function isVersionEnabled(value: boolean | VersionEntryObject): boolean {
|
|
321
|
+
if (typeof value === 'boolean') return value
|
|
322
|
+
return value.enabled !== false
|
|
323
|
+
}
|
|
324
|
+
|
|
278
325
|
/**
|
|
279
326
|
* Get available versions for a database engine from databases.json
|
|
280
327
|
* This is the authoritative source for what versions are actually available in hostdb.
|
|
@@ -290,9 +337,8 @@ export async function getAvailableVersions(
|
|
|
290
337
|
const entry = data[key]
|
|
291
338
|
if (!entry?.versions) return null
|
|
292
339
|
|
|
293
|
-
// Return only versions marked as available (true)
|
|
294
340
|
return Object.entries(entry.versions)
|
|
295
|
-
.filter(([,
|
|
341
|
+
.filter(([, value]) => isVersionEnabled(value))
|
|
296
342
|
.map(([version]) => version)
|
|
297
343
|
} catch (error) {
|
|
298
344
|
logDebug('Failed to fetch available versions from hostdb', {
|
|
@@ -302,25 +348,3 @@ export async function getAvailableVersions(
|
|
|
302
348
|
return null
|
|
303
349
|
}
|
|
304
350
|
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Get the latest LTS version for a database engine
|
|
308
|
-
* @param engine Engine (e.g., Engine.PostgreSQL or 'postgresql')
|
|
309
|
-
* @returns Latest LTS version string, or null if not found
|
|
310
|
-
*/
|
|
311
|
-
export async function getLatestLtsVersion(
|
|
312
|
-
engine: Engine | string,
|
|
313
|
-
): Promise<string | null> {
|
|
314
|
-
try {
|
|
315
|
-
const data = await fetchDatabasesJson()
|
|
316
|
-
const key = engine.toLowerCase()
|
|
317
|
-
const entry = data[key]
|
|
318
|
-
return entry?.latestLts || null
|
|
319
|
-
} catch (error) {
|
|
320
|
-
logDebug('Failed to fetch latest LTS version from hostdb', {
|
|
321
|
-
engine,
|
|
322
|
-
error: error instanceof Error ? error.message : String(error),
|
|
323
|
-
})
|
|
324
|
-
return null
|
|
325
|
-
}
|
|
326
|
-
}
|
|
@@ -35,7 +35,7 @@ export function getHostdbPlatform(
|
|
|
35
35
|
/**
|
|
36
36
|
* Build the download URL for ClickHouse binaries from hostdb
|
|
37
37
|
*
|
|
38
|
-
* Format: https://
|
|
38
|
+
* Format: https://registry.layerbase.host/clickhouse-{version}/clickhouse-{version}-{platform}-{arch}.tar.gz
|
|
39
39
|
*
|
|
40
40
|
* @param version - ClickHouse version (e.g., '25.12', '25.12.3.21')
|
|
41
41
|
* @param platform - Platform identifier (e.g., 'darwin', 'linux')
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CockroachDB binary URL generation
|
|
3
3
|
*
|
|
4
|
-
* Generates download URLs for CockroachDB binaries from
|
|
4
|
+
* Generates download URLs for CockroachDB binaries from the layerbase registry.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type
|
|
7
|
+
import { type Platform, type Arch, Engine } from '../../types'
|
|
8
8
|
import { normalizeVersion } from './version-maps'
|
|
9
|
-
|
|
10
|
-
const HOSTDB_BASE_URL =
|
|
11
|
-
'https://github.com/robertjbass/hostdb/releases/download'
|
|
9
|
+
import { buildHostdbUrl } from '../../core/hostdb-client'
|
|
12
10
|
|
|
13
11
|
/**
|
|
14
12
|
* Get the binary download URL for a specific version and platform
|
|
15
13
|
*
|
|
16
|
-
* URL format: https://
|
|
14
|
+
* URL format: https://registry.layerbase.host/cockroachdb-{version}/cockroachdb-{version}-{platform}-{arch}.{ext}
|
|
17
15
|
*
|
|
18
16
|
* @param version - CockroachDB version (e.g., '25.4.2' or '25')
|
|
19
17
|
* @param platform - Target platform (darwin, linux, win32)
|
|
@@ -27,7 +25,11 @@ export function getBinaryUrl(
|
|
|
27
25
|
const fullVersion = normalizeVersion(version)
|
|
28
26
|
const ext = platform === 'win32' ? 'zip' : 'tar.gz'
|
|
29
27
|
|
|
30
|
-
return
|
|
28
|
+
return buildHostdbUrl(Engine.CockroachDB, {
|
|
29
|
+
version: fullVersion,
|
|
30
|
+
hostdbPlatform: `${platform}-${arch}`,
|
|
31
|
+
extension: ext,
|
|
32
|
+
})
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
/**
|
|
@@ -1,114 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* hostdb Releases Module for CockroachDB
|
|
3
3
|
*
|
|
4
|
-
* Fetches
|
|
5
|
-
*
|
|
4
|
+
* Fetches CockroachDB binary information from the hostdb repository at
|
|
5
|
+
* https://github.com/robertjbass/hostdb
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { createHostdbReleases } from '../../core/hostdb-releases-factory'
|
|
9
9
|
import {
|
|
10
10
|
COCKROACHDB_VERSION_MAP,
|
|
11
11
|
SUPPORTED_MAJOR_VERSIONS,
|
|
12
12
|
} from './version-maps'
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}[]
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Fetch available CockroachDB versions from hostdb
|
|
33
|
-
* Returns a map of major version to available patch versions
|
|
34
|
-
*
|
|
35
|
-
* Falls back to local version maps if fetch fails
|
|
36
|
-
*/
|
|
37
|
-
export async function fetchAvailableVersions(): Promise<
|
|
38
|
-
Record<string, string[]>
|
|
39
|
-
> {
|
|
40
|
-
// Return cached versions if still valid
|
|
41
|
-
if (cachedVersions && Date.now() < cacheExpiry) {
|
|
42
|
-
return cachedVersions
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
const response = await fetch(HOSTDB_RELEASES_URL)
|
|
47
|
-
if (!response.ok) {
|
|
48
|
-
throw new Error(`HTTP ${response.status}`)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const releases = (await response.json()) as HostdbReleases
|
|
52
|
-
const cockroachdbReleases = releases.cockroachdb
|
|
53
|
-
|
|
54
|
-
if (!cockroachdbReleases?.versions) {
|
|
55
|
-
throw new Error('No CockroachDB versions found in releases.json')
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Group versions by major version (YY.MM format)
|
|
59
|
-
const versionMap: Record<string, string[]> = {}
|
|
60
|
-
|
|
61
|
-
for (const { version } of cockroachdbReleases.versions) {
|
|
62
|
-
// Extract major version (e.g., "25" from "25.4.2")
|
|
63
|
-
const majorMatch = version.match(/^(\d+)/)
|
|
64
|
-
if (!majorMatch) continue
|
|
65
|
-
|
|
66
|
-
const majorVersion = majorMatch[1]
|
|
67
|
-
if (!versionMap[majorVersion]) {
|
|
68
|
-
versionMap[majorVersion] = []
|
|
69
|
-
}
|
|
70
|
-
versionMap[majorVersion].push(version)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Sort versions within each major version (newest first)
|
|
74
|
-
for (const major of Object.keys(versionMap)) {
|
|
75
|
-
versionMap[major].sort((a, b) => {
|
|
76
|
-
const partsA = a.split('.').map(Number)
|
|
77
|
-
const partsB = b.split('.').map(Number)
|
|
78
|
-
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
|
79
|
-
const diff = (partsB[i] || 0) - (partsA[i] || 0)
|
|
80
|
-
if (diff !== 0) return diff
|
|
81
|
-
}
|
|
82
|
-
return 0
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Cache the results
|
|
87
|
-
cachedVersions = versionMap
|
|
88
|
-
cacheExpiry = Date.now() + CACHE_TTL_MS
|
|
89
|
-
|
|
90
|
-
logDebug('Fetched CockroachDB versions from hostdb', { versionMap })
|
|
91
|
-
return versionMap
|
|
92
|
-
} catch (error) {
|
|
93
|
-
logDebug(`Failed to fetch hostdb releases: ${error}`)
|
|
94
|
-
|
|
95
|
-
// Fall back to local version maps
|
|
96
|
-
const fallbackMap: Record<string, string[]> = {}
|
|
97
|
-
for (const major of SUPPORTED_MAJOR_VERSIONS) {
|
|
98
|
-
const fullVersion = COCKROACHDB_VERSION_MAP[major]
|
|
99
|
-
if (fullVersion) {
|
|
100
|
-
fallbackMap[major] = [fullVersion]
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return fallbackMap
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Clear the version cache (useful for testing)
|
|
110
|
-
*/
|
|
111
|
-
export function clearVersionCache(): void {
|
|
112
|
-
cachedVersions = null
|
|
113
|
-
cacheExpiry = 0
|
|
114
|
-
}
|
|
13
|
+
import { cockroachdbBinaryManager } from './binary-manager'
|
|
14
|
+
import { Engine } from '../../types'
|
|
15
|
+
|
|
16
|
+
const hostdbReleases = createHostdbReleases({
|
|
17
|
+
engine: Engine.CockroachDB,
|
|
18
|
+
displayName: 'CockroachDB',
|
|
19
|
+
versionMap: COCKROACHDB_VERSION_MAP,
|
|
20
|
+
supportedMajorVersions: SUPPORTED_MAJOR_VERSIONS,
|
|
21
|
+
groupingStrategy: 'single-digit',
|
|
22
|
+
listInstalled: () => cockroachdbBinaryManager.listInstalled(),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
export const fetchAvailableVersions = hostdbReleases.fetchAvailableVersions
|
|
26
|
+
export const getLatestVersion = hostdbReleases.getLatestVersion
|
|
@@ -37,7 +37,7 @@ export function getHostdbPlatform(
|
|
|
37
37
|
/**
|
|
38
38
|
* Build the download URL for CouchDB binaries from hostdb
|
|
39
39
|
*
|
|
40
|
-
* Format: https://
|
|
40
|
+
* Format: https://registry.layerbase.host/couchdb-{version}/couchdb-{version}-{platform}-{arch}.{ext}
|
|
41
41
|
*
|
|
42
42
|
* @param version - CouchDB version (e.g., '3', '3.5.1')
|
|
43
43
|
* @param platform - Platform identifier (e.g., 'darwin', 'linux', 'win32')
|
|
@@ -12,7 +12,7 @@ import { Engine, type Platform, type Arch } from '../../types'
|
|
|
12
12
|
/**
|
|
13
13
|
* Build the download URL for DuckDB binaries from hostdb
|
|
14
14
|
*
|
|
15
|
-
* Format: https://
|
|
15
|
+
* Format: https://registry.layerbase.host/duckdb-{version}/duckdb-{version}-{platform}-{arch}.{ext}
|
|
16
16
|
*
|
|
17
17
|
* @param version - DuckDB version (e.g., '1', '1.4.3')
|
|
18
18
|
* @param platform - Platform identifier (e.g., 'darwin', 'linux', 'win32')
|