Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/cli/command/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import type { Cli } from 'clipanion';
import type { CliMode } from '../utils';
import { logger } from '../utils/logger';
import { DownloadFileCommand } from './download-file';
import {
InstallGemCommand,
InstallGemEnvCommand,
InstallGemShortCommand,
InstallGemShortEnvCommand,
} from './install-gem';
import {
InstallNpmCommand,
InstallNpmEnvCommand,
Expand Down Expand Up @@ -36,9 +42,15 @@ export function prepareCommands(cli: Cli, mode: CliMode | null): void {
cli.register(InstallNpmShortCommand);
cli.register(InstallNpmShortEnvCommand);
return;
} else if (mode === 'install-gem') {
cli.register(InstallGemShortCommand);
cli.register(InstallGemShortEnvCommand);
return;
}

cli.register(DownloadFileCommand);
cli.register(InstallGemCommand);
cli.register(InstallGemEnvCommand);
cli.register(InstallNpmCommand);
cli.register(InstallNpmEnvCommand);
cli.register(InstallToolCommand);
Expand Down
39 changes: 39 additions & 0 deletions src/cli/command/install-gem.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { env } from 'node:process';
import { Cli } from 'clipanion';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { prepareCommands } from '.';

const mocks = vi.hoisted(() => ({
installTool: vi.fn(),
prepareTools: vi.fn(),
}));

vi.mock('../install-tool', () => mocks);
vi.mock('../prepare-tool', () => mocks);

describe('index', () => {
beforeEach(() => {
delete env.RAKE_VERSION;
});

test('install-gem', async () => {
const cli = new Cli({ binaryName: 'install-gem' });
prepareCommands(cli, 'install-gem');

expect(await cli.run(['rake'])).toBe(1);

env.RAKE_VERSION = '13.0.6';
expect(await cli.run(['rake'])).toBe(0);
expect(mocks.installTool).toHaveBeenCalledOnce();
expect(mocks.installTool).toHaveBeenCalledWith(
'rake',
'13.0.6',
false,
'gem',
);
expect(await cli.run(['rake', '-d'])).toBe(0);

mocks.installTool.mockRejectedValueOnce(new Error('test'));
expect(await cli.run(['rake'])).toBe(1);
});
});
95 changes: 95 additions & 0 deletions src/cli/command/install-gem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Command, Option } from 'clipanion';
import prettyMilliseconds from 'pretty-ms';
import * as t from 'typanion';
import { installTool } from '../install-tool';
import { logger, validateVersion } from '../utils';
import { getVersion } from './utils';

export class InstallGemEnvCommand extends Command {
static override paths = [['install', 'gem']];

static override usage = Command.Usage({
description: 'Installs a gem package into the container.',
examples: [
[
'Installs rake with version via environment variable',
'RAKE_VERSION=13.0.6 $0 install gem rake',
],
],
});

name = Option.String();

dryRun = Option.Boolean('-d,--dry-run', false);

override async execute(): Promise<number | void> {
const version = getVersion(this.name);

if (!version) {
logger.fatal(`No version found for ${this.name}`);
return 1;
}

return await this.cli.run([
...this.path,
...(this.dryRun ? ['-d'] : []),
this.name,
version,
]);
}
}

export class InstallGemCommand extends InstallGemEnvCommand {
static override usage = Command.Usage({
description: 'Installs a gem package into the container.',
examples: [['Installs rake 13.0.6', '$0 install gem rake 13.0.6']],
});

version = Option.String({
required: true,
validator: t.cascade(t.isString(), validateVersion()),
});

override async execute(): Promise<number | void> {
const start = Date.now();
let error = false;

logger.info(`Installing gem package ${this.name} v${this.version}...`);
try {
return await installTool(this.name, this.version, this.dryRun, 'gem');
} catch (err) {
logger.fatal(err);
error = true;
return 1;
} finally {
logger.info(
`Installed gem package ${this.name} ${
error ? 'with errors ' : ''
}in ${prettyMilliseconds(Date.now() - start)}.`,
);
}
}
}

export class InstallGemShortEnvCommand extends InstallGemEnvCommand {
static override paths = [Command.Default];

static override usage = Command.Usage({
description: 'Installs a gem package into the container.',
examples: [
[
'Installs rake with version via environment variable',
'RAKE_VERSION=13.0.6 $0 rake',
],
],
});
}

export class InstallGemShortCommand extends InstallGemCommand {
static override paths = [Command.Default];

static override usage = Command.Usage({
description: 'Installs a gem package into the container.',
examples: [['Installs rake v13.0.6', '$0 rake 13.0.6']],
});
}
12 changes: 6 additions & 6 deletions src/cli/command/install-npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export class InstallNpmEnvCommand extends Command {
description: 'Installs a npm package into the container.',
examples: [
[
'Installs corepack with version via environment variable',
'COREPACK_VERSION=0.9.0 $0 install npm corepack',
'Installs del-cli with version via environment variable',
'DEL_CLI_VERSION=5.0.0 $0 install npm del-cli',
],
],
});
Expand Down Expand Up @@ -42,7 +42,7 @@ export class InstallNpmEnvCommand extends Command {
export class InstallNpmCommand extends InstallNpmEnvCommand {
static override usage = Command.Usage({
description: 'Installs a npm package into the container.',
examples: [['Installs corepack 0.9.0', '$0 install npm corepack 0.9.0']],
examples: [['Installs del-cli 5.0.0', '$0 install npm del-cli 5.0.0']],
});

version = Option.String({
Expand Down Expand Up @@ -78,8 +78,8 @@ export class InstallNpmShortEnvCommand extends InstallNpmEnvCommand {
description: 'Installs a npm package into the container.',
examples: [
[
'Installs corepack with version via environment variable',
'NODE_VERSION=0.9.0 $0 corepack',
'Installs del-cli with version via environment variable',
'DEL_CLI_VERSION=5.0.0 $0 del-cli',
],
],
});
Expand All @@ -90,6 +90,6 @@ export class InstallNpmShortCommand extends InstallNpmCommand {

static override usage = Command.Usage({
description: 'Installs a npm package into the container.',
examples: [['Installs corepack v0.9.0', '$0 corepack 0.9.0']],
examples: [['Installs del-cli v5.0.0', '$0 del-cli 5.0.0']],
});
}
30 changes: 29 additions & 1 deletion src/cli/install-tool/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@ import {
InstallYarnSlimService,
} from '../tools/node/npm';
import { InstallNodeBaseService } from '../tools/node/utils';
import {
InstallBundlerService,
InstallCocoapodsService,
} from '../tools/ruby/gem';
import { InstallRubyBaseService } from '../tools/ruby/utils';
import { logger } from '../utils';
import { InstallLegacyToolService } from './install-legacy-tool.service';
import { INSTALL_TOOL_TOKEN, InstallToolService } from './install-tool.service';

export type InstallToolType = 'npm';
export type InstallToolType = 'gem' | 'npm';

function prepareContainer(): Container {
logger.trace('preparing container');
Expand All @@ -33,6 +38,8 @@ function prepareContainer(): Container {

// tool services
container.bind(INSTALL_TOOL_TOKEN).to(InstallBowerService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallBundlerService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallCocoapodsService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallCorepackService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallDartService);
container.bind(INSTALL_TOOL_TOKEN).to(InstallDockerService);
Expand All @@ -59,6 +66,27 @@ export function installTool(
const container = prepareContainer();
if (type) {
switch (type) {
case 'gem': {
@injectable()
class InstallGenericDemService extends InstallRubyBaseService {
override readonly name: string = tool;

override needsPrepare(): boolean {
return false;
}

override async test(version: string): Promise<void> {
try {
// some npm packages may not have a `--version` flag
await super.test(version);
} catch (err) {
logger.debug(err);
}
}
}
container.bind(INSTALL_TOOL_TOKEN).to(InstallGenericDemService);
break;
}
case 'npm': {
@injectable()
class InstallGenericNpmService extends InstallNodeBaseService {
Expand Down
8 changes: 4 additions & 4 deletions src/cli/install-tool/install-tool-base.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ export abstract class InstallToolBaseService {

let content = `#!/bin/bash

if [[ -z "\${CONTAINERBASE_ENV+x}" ]]; then
. ${this.pathSvc.envFile}
fi
`;
if [[ -z "\${CONTAINERBASE_ENV+x}" ]]; then
. ${this.pathSvc.envFile}
fi
`;

if (exports) {
content += `export ${exports}\n`;
Expand Down
2 changes: 2 additions & 0 deletions src/cli/tools/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export const NoPrepareTools = [
'bower',
'bundler',
'cocoapods',
'corepack',
'flux',
'lerna',
Expand Down
17 changes: 17 additions & 0 deletions src/cli/tools/ruby/gem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { execa } from 'execa';
import { injectable } from 'inversify';
import { InstallRubyBaseService } from './utils';

@injectable()
export class InstallBundlerService extends InstallRubyBaseService {
override readonly name: string = 'bundler';
}

@injectable()
export class InstallCocoapodsService extends InstallRubyBaseService {
override readonly name: string = 'cocoapods';

override async test(_version: string): Promise<void> {
await execa('pod', ['--version', '--allow-root'], { stdio: 'inherit' });
}
}
Loading