diff --git a/.github/renovate.json b/.github/renovate.json index 0b2362e818..7f1535b31b 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -91,6 +91,7 @@ "nix", "node", "npm", + "nub", "pdm", "pipenv", "pixi", @@ -146,6 +147,7 @@ "nix", "node", "npm", + "nub", "pdm", "pipenv", "pixi", diff --git a/docs/custom-registries.md b/docs/custom-registries.md index 22daeb4547..4ded1bca1f 100644 --- a/docs/custom-registries.md +++ b/docs/custom-registries.md @@ -611,6 +611,21 @@ https://github.com/containerbase/node-re2-prebuild/releases/download/1.20.9/linu https://github.com/containerbase/node-re2-prebuild/releases/download/1.20.9/linux-x64-108.br ``` +## `nub` + +Nub releases are downloaded from: + +- `https://github.com/nubjs/nub/releases` + +Samples: + +```txt +https://github.com/nubjs/nub/releases/download/v0.9.3/nub-linux-x64.tar.gz +https://github.com/nubjs/nub/releases/download/v0.9.3/nub-linux-x64.tar.gz.sha256 +https://github.com/nubjs/nub/releases/download/v0.9.3/nub-linux-arm64.tar.gz +https://github.com/nubjs/nub/releases/download/v0.9.3/nub-linux-arm64.tar.gz.sha256 +``` + ## `php` PHP releases are downloaded from: diff --git a/src/cli/install-tool/index.ts b/src/cli/install-tool/index.ts index 983f24bb31..2d0ea0bba7 100644 --- a/src/cli/install-tool/index.ts +++ b/src/cli/install-tool/index.ts @@ -81,6 +81,7 @@ import { YarnVersionResolver, } from '../tools/node/resolver.ts'; import { NpmBaseInstallService } from '../tools/node/utils.ts'; +import { NubInstallService } from '../tools/nub.ts'; import { ComposerInstallService, ComposerVersionResolver, @@ -181,6 +182,7 @@ async function prepareInstallContainer(): Promise { container.bind(INSTALL_TOOL_TOKEN).to(NixInstallService); container.bind(INSTALL_TOOL_TOKEN).to(NugetInstallService); container.bind(INSTALL_TOOL_TOKEN).to(NodeInstallService); + container.bind(INSTALL_TOOL_TOKEN).to(NubInstallService); container.bind(INSTALL_TOOL_TOKEN).to(PaketInstallService); container.bind(INSTALL_TOOL_TOKEN).to(PhpInstallService); container.bind(INSTALL_TOOL_TOKEN).to(PixiInstallService); diff --git a/src/cli/tools/index.ts b/src/cli/tools/index.ts index 7e0b817fc6..113291a609 100644 --- a/src/cli/tools/index.ts +++ b/src/cli/tools/index.ts @@ -33,6 +33,7 @@ export const NoPrepareTools = [ 'maven', 'mise', 'nix', + 'nub', 'nuget', 'npm', 'paket', diff --git a/src/cli/tools/nub.spec.ts b/src/cli/tools/nub.spec.ts new file mode 100644 index 0000000000..313f3b4826 --- /dev/null +++ b/src/cli/tools/nub.spec.ts @@ -0,0 +1,75 @@ +import { arch } from 'node:os'; +import { join } from 'node:path'; +import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; +import { CompressionService, LinkToolService } from '../services/index.ts'; +import { NubInstallService } from './nub.ts'; +import { scope } from '~test/http-mock.ts'; +import { ensurePaths } from '~test/path.ts'; +import { checksum, toolContext } from '~test/tool.ts'; + +const { execaMock } = vi.hoisted(() => ({ execaMock: vi.fn() })); +vi.mock('execa', () => ({ execa: execaMock })); +vi.mock('node:os', async (importOriginal) => ({ + ...(await importOriginal()), + arch: vi.fn(() => 'x64'), +})); + +const baseUrl = 'https://github.com'; +const archive = 'nub archive'; + +describe('cli/tools/nub', () => { + beforeAll(async () => { + await ensurePaths(['tmp', 'opt/containerbase/bin']); + }); + + beforeEach(() => { + vi.mocked(arch).mockReturnValue('x64'); + execaMock.mockResolvedValue({ failed: false }); + }); + + test.each([ + { hostArch: 'x64', ghArch: 'x64', version: '0.9.3' }, + { hostArch: 'arm64', ghArch: 'arm64', version: '0.9.2' }, + ] as const)('install on $ghArch', async ({ hostArch, ghArch, version }) => { + vi.mocked(arch).mockReturnValue(hostArch); + const { svc, pathSvc } = await toolContext(NubInstallService); + const filename = `nub-linux-${ghArch}.tar.gz`; + const releaseUrl = `/nubjs/nub/releases/download/v${version}`; + scope(baseUrl) + .get(`${releaseUrl}/${filename}.sha256`) + .reply(200, `${checksum(archive)} ${filename}\n`) + .get(`${releaseUrl}/${filename}`) + .reply(200, archive); + const extract = vi.spyOn(CompressionService.prototype, 'extract'); + + await expect(svc.install(version)).resolves.toBeUndefined(); + + expect(extract).toHaveBeenCalledExactlyOnceWith({ + file: expect.stringContaining(filename), + cwd: pathSvc.versionedToolPath('nub', version), + }); + }); + + test('link', async () => { + const { svc, pathSvc } = await toolContext(NubInstallService); + const spy = vi.spyOn(LinkToolService.prototype, 'shellwrapper'); + + await expect(svc.link('0.9.3')).resolves.toBeUndefined(); + + expect(spy).toHaveBeenCalledExactlyOnceWith('nub', { + srcDir: join(pathSvc.versionedToolPath('nub', '0.9.3'), 'bin'), + }); + }); + + test('runs the tool test', async () => { + const { svc } = await toolContext(NubInstallService); + + await expect(svc.test('0.9.3')).resolves.toBeUndefined(); + + expect(execaMock).toHaveBeenCalledWith( + 'nub', + ['--version'], + expect.any(Object), + ); + }); +}); diff --git a/src/cli/tools/nub.ts b/src/cli/tools/nub.ts new file mode 100644 index 0000000000..711552e643 --- /dev/null +++ b/src/cli/tools/nub.ts @@ -0,0 +1,46 @@ +import { join } from 'node:path'; +import { injectFromHierarchy, injectable } from 'inversify'; +import { BaseInstallService } from '../install-tool/base-install.service.ts'; + +@injectable() +@injectFromHierarchy() +export class NubInstallService extends BaseInstallService { + readonly name = 'nub'; + + private get ghArch(): string { + switch (this.envSvc.arch) { + case 'arm64': + return 'arm64'; + case 'amd64': + return 'x64'; + } + } + + override async install(version: string): Promise { + const baseUrl = `https://github.com/nubjs/nub/releases/download/v${version}/`; + const filename = `nub-linux-${this.ghArch}.tar.gz`; + const url = `${baseUrl}${filename}`; + + const expectedChecksum = await this.getChecksum(`${url}.sha256`); + + const file = await this.http.download({ + url, + checksumType: 'sha256', + expectedChecksum, + }); + + await this.pathSvc.ensureToolPath(this.name); + const path = await this.pathSvc.createVersionedToolPath(this.name, version); + // Preserve the release layout: bin/ and runtime/ are siblings. + await this.compress.extract({ file, cwd: path }); + } + + override async link(version: string): Promise { + const src = join(this.pathSvc.versionedToolPath(this.name, version), 'bin'); + await this.shellwrapper({ srcDir: src }); + } + + override async test(_version: string): Promise { + await this._spawn(this.name, ['--version']); + } +} diff --git a/test/latest/Dockerfile b/test/latest/Dockerfile index 2fb382f340..7faf08d458 100644 --- a/test/latest/Dockerfile +++ b/test/latest/Dockerfile @@ -212,7 +212,7 @@ RUN prepare-tool all RUN set -ex; [ -d /usr/local/erlang ] && echo "works" || exit 1; #-------------------------------------- -# test: apm, bazelisk, buf, bun, deno, devbox, gh, helmfile, kustomize, skopeo, tofu, vendir +# test: apm, bazelisk, buf, bun, deno, devbox, gh, helmfile, kustomize, nub, skopeo, tofu, vendir #-------------------------------------- FROM base AS teste @@ -225,6 +225,9 @@ RUN install-tool buf v1.73.0 # renovate: datasource=npm RUN install-tool bun 1.4.2 +# renovate: datasource=github-releases packageName=nubjs/nub +RUN install-tool nub 0.9.3 + # renovate: datasource=github-releases packageName=denoland/deno RUN install-tool deno 2.9.7 diff --git a/test/latest/Dockerfile.arm64 b/test/latest/Dockerfile.arm64 index 4cdeea6d13..f525632936 100644 --- a/test/latest/Dockerfile.arm64 +++ b/test/latest/Dockerfile.arm64 @@ -48,6 +48,14 @@ FROM base AS test-bun # renovate: datasource=npm RUN install-tool bun 1.4.2 +#-------------------------------------- +# Image: nub +#-------------------------------------- +FROM base AS test-nub + +# renovate: datasource=github-releases packageName=nubjs/nub +RUN install-tool nub 0.9.3 + #-------------------------------------- # Image: deno #-------------------------------------- @@ -220,6 +228,7 @@ FROM base COPY --from=test-bazelisk /.dummy /.dummy COPY --from=test-bun /.dummy /.dummy +COPY --from=test-nub /.dummy /.dummy COPY --from=test-deno /.dummy /.dummy COPY --from=test-apko /.dummy /.dummy COPY --from=test-apm /.dummy /.dummy