-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(datasource): add devbox datasource module (#33418)
- Loading branch information
1 parent
5a9f369
commit 309da71
Showing
5 changed files
with
244 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export const defaultRegistryUrl = 'https://search.devbox.sh/v2/'; | ||
|
||
export const datasource = 'devbox'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import { getPkgReleases } from '..'; | ||
import * as httpMock from '../../../../test/http-mock'; | ||
import { EXTERNAL_HOST_ERROR } from '../../../constants/error-messages'; | ||
import { datasource, defaultRegistryUrl } from './common'; | ||
|
||
const packageName = 'nodejs'; | ||
|
||
function getPath(packageName: string): string { | ||
return `/pkg?name=${encodeURIComponent(packageName)}`; | ||
} | ||
|
||
const sampleReleases = [ | ||
{ | ||
version: '22.2.0', | ||
last_updated: '2024-05-22T06:18:38Z', | ||
}, | ||
{ | ||
version: '22.0.0', | ||
last_updated: '2024-05-12T16:19:40Z', | ||
}, | ||
{ | ||
version: '21.7.3', | ||
last_updated: '2024-04-19T21:36:04Z', | ||
}, | ||
]; | ||
|
||
describe('modules/datasource/devbox/index', () => { | ||
describe('getReleases', () => { | ||
it('throws for error', async () => { | ||
httpMock | ||
.scope(defaultRegistryUrl) | ||
.get(getPath(packageName)) | ||
.replyWithError('error'); | ||
await expect( | ||
getPkgReleases({ | ||
datasource, | ||
packageName, | ||
}), | ||
).rejects.toThrow(EXTERNAL_HOST_ERROR); | ||
}); | ||
}); | ||
|
||
it('returns null for 404', async () => { | ||
httpMock.scope(defaultRegistryUrl).get(getPath(packageName)).reply(404); | ||
expect( | ||
await getPkgReleases({ | ||
datasource, | ||
packageName, | ||
}), | ||
).toBeNull(); | ||
}); | ||
|
||
it('returns null for empty result', async () => { | ||
httpMock.scope(defaultRegistryUrl).get(getPath(packageName)).reply(200, {}); | ||
expect( | ||
await getPkgReleases({ | ||
datasource, | ||
packageName, | ||
}), | ||
).toBeNull(); | ||
}); | ||
|
||
it('returns null for empty 200 OK', async () => { | ||
httpMock | ||
.scope(defaultRegistryUrl) | ||
.get(getPath(packageName)) | ||
.reply(200, { versions: [] }); | ||
expect( | ||
await getPkgReleases({ | ||
datasource, | ||
packageName, | ||
}), | ||
).toBeNull(); | ||
}); | ||
|
||
it('throws for 5xx', async () => { | ||
httpMock.scope(defaultRegistryUrl).get(getPath(packageName)).reply(502); | ||
await expect( | ||
getPkgReleases({ | ||
datasource, | ||
packageName, | ||
}), | ||
).rejects.toThrow(EXTERNAL_HOST_ERROR); | ||
}); | ||
|
||
it('processes real data', async () => { | ||
httpMock.scope(defaultRegistryUrl).get(getPath(packageName)).reply(200, { | ||
name: 'nodejs', | ||
summary: 'Event-driven I/O framework for the V8 JavaScript engine', | ||
homepage_url: 'https://nodejs.org', | ||
license: 'MIT', | ||
releases: sampleReleases, | ||
}); | ||
const res = await getPkgReleases({ | ||
datasource, | ||
packageName, | ||
}); | ||
expect(res).toEqual({ | ||
homepage: 'https://nodejs.org', | ||
registryUrl: 'https://search.devbox.sh/v2', | ||
releases: [ | ||
{ | ||
version: '21.7.3', | ||
releaseTimestamp: '2024-04-19T21:36:04.000Z', | ||
}, | ||
{ | ||
version: '22.0.0', | ||
releaseTimestamp: '2024-05-12T16:19:40.000Z', | ||
}, | ||
{ | ||
version: '22.2.0', | ||
releaseTimestamp: '2024-05-22T06:18:38.000Z', | ||
}, | ||
], | ||
}); | ||
}); | ||
|
||
it('processes empty data', async () => { | ||
httpMock.scope(defaultRegistryUrl).get(getPath(packageName)).reply(200, { | ||
name: 'nodejs', | ||
summary: 'Event-driven I/O framework for the V8 JavaScript engine', | ||
homepage_url: 'https://nodejs.org', | ||
license: 'MIT', | ||
releases: [], | ||
}); | ||
const res = await getPkgReleases({ | ||
datasource, | ||
packageName, | ||
}); | ||
expect(res).toBeNull(); | ||
}); | ||
|
||
it('returns null when no body is returned', async () => { | ||
httpMock | ||
.scope(defaultRegistryUrl) | ||
.get(getPath(packageName)) | ||
.reply(200, undefined); | ||
const res = await getPkgReleases({ | ||
datasource, | ||
packageName, | ||
}); | ||
expect(res).toBeNull(); | ||
}); | ||
|
||
it('falls back to a default homepage_url', async () => { | ||
httpMock.scope(defaultRegistryUrl).get(getPath(packageName)).reply(200, { | ||
name: 'nodejs', | ||
summary: 'Event-driven I/O framework for the V8 JavaScript engine', | ||
homepage_url: undefined, | ||
license: 'MIT', | ||
releases: sampleReleases, | ||
}); | ||
const res = await getPkgReleases({ | ||
datasource, | ||
packageName, | ||
}); | ||
expect(res?.homepage).toBeUndefined(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { logger } from '../../../logger'; | ||
import { ExternalHostError } from '../../../types/errors/external-host-error'; | ||
import { HttpError } from '../../../util/http'; | ||
import { joinUrlParts } from '../../../util/url'; | ||
import * as devboxVersioning from '../../versioning/devbox'; | ||
import { Datasource } from '../datasource'; | ||
import type { GetReleasesConfig, ReleaseResult } from '../types'; | ||
import { datasource, defaultRegistryUrl } from './common'; | ||
import { DevboxResponse } from './schema'; | ||
|
||
export class DevboxDatasource extends Datasource { | ||
static readonly id = datasource; | ||
|
||
constructor() { | ||
super(datasource); | ||
} | ||
|
||
override readonly customRegistrySupport = true; | ||
override readonly releaseTimestampSupport = true; | ||
|
||
override readonly registryStrategy = 'first'; | ||
|
||
override readonly defaultVersioning = devboxVersioning.id; | ||
|
||
override readonly defaultRegistryUrls = [defaultRegistryUrl]; | ||
|
||
async getReleases({ | ||
registryUrl, | ||
packageName, | ||
}: GetReleasesConfig): Promise<ReleaseResult | null> { | ||
const res: ReleaseResult = { | ||
releases: [], | ||
}; | ||
|
||
logger.trace({ registryUrl, packageName }, 'fetching devbox release'); | ||
|
||
const devboxPkgUrl = joinUrlParts( | ||
registryUrl!, | ||
`/pkg?name=${encodeURIComponent(packageName)}`, | ||
); | ||
|
||
try { | ||
const response = await this.http.getJson(devboxPkgUrl, DevboxResponse); | ||
res.releases = response.body.releases; | ||
res.homepage = response.body.homepage; | ||
} catch (err) { | ||
// istanbul ignore else: not testable with nock | ||
if (err instanceof HttpError) { | ||
if (err.response?.statusCode !== 404) { | ||
throw new ExternalHostError(err); | ||
} | ||
} | ||
this.handleGenericErrors(err); | ||
} | ||
return res.releases.length ? res : null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { z } from 'zod'; | ||
|
||
export const DevboxRelease = z.object({ | ||
version: z.string(), | ||
last_updated: z.string(), | ||
}); | ||
|
||
export const DevboxResponse = z | ||
.object({ | ||
name: z.string(), | ||
summary: z.string().optional(), | ||
homepage_url: z.string().optional(), | ||
license: z.string().optional(), | ||
releases: DevboxRelease.array(), | ||
}) | ||
.transform((response) => ({ | ||
name: response.name, | ||
homepage: response.homepage_url, | ||
releases: response.releases.map((release) => ({ | ||
version: release.version, | ||
releaseTimestamp: release.last_updated, | ||
})), | ||
})); |