Skip to content

Commit

Permalink
chore: Refactor clone command, use git command instead
Browse files Browse the repository at this point in the history
  • Loading branch information
angelmadames committed Mar 18, 2024
1 parent 65188fc commit 73a83bc
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 85 deletions.
4 changes: 2 additions & 2 deletions cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
import { Command } from 'commander';
import { configCommand } from './src/commands/config';
import { setupCommand } from './src/commands/setup';
import { cloneCommand } from './src/commands/clone';
import { cleanCommand } from './src/commands/clean';
import { environmentCommand } from './src/commands/environment';
import { composeCommand } from './src/commands/compose';
import { dependenciesCommand } from './src/commands/dependencies';
import { gitCommand } from './src/commands/git';

const cli = new Command();

Expand All @@ -17,7 +17,7 @@ cli
.version('0.0.1')
.addCommand(configCommand())
.addCommand(setupCommand())
.addCommand(cloneCommand())
.addCommand(gitCommand())
.addCommand(cleanCommand())
.addCommand(environmentCommand())
.addCommand(composeCommand())
Expand Down
36 changes: 36 additions & 0 deletions src/commands/git/checkout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import chalk from 'chalk';
import { Command } from 'commander';
import { projectConfig } from '../../config/project';
import git from '../../utils/git';
import sharedOptions from '../../utils/shared-options';

export const gitCheckoutCommand = () => {
const command = new Command();
const config = projectConfig();

command
.name('checkout')
.description(
'Checkout all configured git repositories defined in the config.json\n' +
', to the specified git ref.',
)
.addOption(sharedOptions.gitRef().default(config.git.default_ref))
.addOption(sharedOptions.reposRoot().default(config.paths.repos_root))
.addOption(sharedOptions.repos().default(config.repositories))
.action((options) => {
console.log(`Git org > ${chalk.bold(options.gitOrg)}`);
console.log(`Git ref > ${chalk.bold(options.gitRef)}`);
console.log(`Repos path > ${chalk.bold(options.reposRoot)}`);

for (const repo of options.repos) {
git.checkout({
path: `${options.reposRoot}/${repo}`,
ref: options.gitRef,
});
}
});

return command;
};

export default gitCheckoutCommand();
36 changes: 10 additions & 26 deletions src/commands/clone/index.ts → src/commands/git/clone.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import chalk from 'chalk';
import { Command } from 'commander';
import { projectConfig } from '../../config/project';
import Git from '../../utils/git';
import git from '../../utils/git';
import sharedOptions from '../../utils/shared-options';

export const cloneCommand = () => {
export const gitCloneCommand = () => {
const command = new Command();
const config = projectConfig();

Expand All @@ -13,43 +14,26 @@ export const cloneCommand = () => {
'Clones all configured git repositories defined in the config.json\n' +
"file in the root directory. Uses 'git clone' strategy.",
)
.option('-c, --checkout', 'Checkout to git ref instead of clone')
.addOption(sharedOptions.gitRef().default(config.git.default_ref))
.addOption(sharedOptions.reposRoot().default(config.paths.repos_root))
.addOption(sharedOptions.gitOrg().default(config.git.org_url))
.addOption(sharedOptions.repos().default(config.repositories))
.addOption(sharedOptions.info())
.action((options) => {
console.log(`Git org > ${options.gitOrg}`);
console.log(`Git ref > ${options.gitRef}`);
console.log(`Repos path > ${options.reposRoot}`);

if (options.info) return;
console.log(`Git org > ${chalk.bold(options.gitOrg)}`);
console.log(`Git ref > ${chalk.bold(options.gitRef)}`);
console.log(`Repos path > ${chalk.bold(options.reposRoot)}`);

for (const repo of options.repos) {
const repoUrl = `${options.gitOrg}/${repo}`;
const git = new Git({
workingDir: options.reposRoot,
git.clone({
ref: options.gitRef,
repo: repo,
path: options.reposRoot,
repo: repoUrl,
});

if (options.checkout) {
git.checkout({
workingDir: `${options.reposRoot}/${repo}`,
ref: options.gitRef,
});
} else {
git.clone({
workingDir: options.reposRoot,
repo: repoUrl,
ref: options.gitRef,
});
}
}
});

return command;
};

export default cloneCommand();
export default gitCloneCommand();
17 changes: 17 additions & 0 deletions src/commands/git/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Command } from 'commander';
import { gitCheckoutCommand } from './checkout';
import { gitCloneCommand } from './clone';

export const gitCommand = () => {
const command = new Command();

command
.name('git')
.description('All git commands abstracted by DEMS')
.addCommand(gitCloneCommand())
.addCommand(gitCheckoutCommand());

return command;
};

export default gitCommand();
76 changes: 39 additions & 37 deletions src/utils/git.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,43 @@
import cmd from './cmd';
import { createPath, isDirectory } from './file-system';
import type { GitParams } from './interfaces';
import log from './log';

export default class Git {
repoUrl: string;
repoPath: string;
gitRef: string;

constructor({ repo, workingDir, ref }: GitParams) {
this.repoPath = `${workingDir}/${repo.split('/').pop()}`;
this.repoUrl = repo;
this.gitRef = ref;
}
const git = {
clone({ path, repo, ref }: GitParams) {
if (!isDirectory(path)) createPath({ path });
const repoPath = getRepoPath({
path,
repo,
});

clone({ workingDir, repo, ref }: GitParams) {
createPath({ path: workingDir });
if (localRepoExists(this.repoPath)) {
if (localRepoExists({ path: repoPath })) {
log.warning(`Repo ${repo} already cloned.`);
} else {
Bun.spawnSync(['git', 'clone', `${repo}.git`, '-b', ref], {
stdin: 'inherit',
cwd: workingDir,
});

cmd.run(`git -C ${path} clone ${repo}.git -b ${ref}`);
log.success(`Repo ${repo} was cloned successfully!`);
}
}
},

checkout({ workingDir, ref }: Pick<GitParams, 'workingDir' | 'ref'>) {
if (!localRepoExists(workingDir)) {
log.error(`Repo does not exist at: ${workingDir}`);
throw new Error('Repo not found.');
checkout({ path, ref }: Omit<GitParams, 'repo'>) {
if (!localRepoExists({ path })) {
log.error(`${path} is not a valid Git repository.`);
throw new Error(`Repo not found in ${path}.`);
}
Bun.spawnSync(['git', `-C ${workingDir}`, 'checkout', `${ref}`]);
cmd.run(`git -C ${path} checkout ${ref}`);
log.success(`Repo was checked out to ref ${ref} successfully!`);
}
},
};

remoteRepoExists(repo: string = this.repoUrl) {
const proc = Bun.spawnSync(['git', 'ls-remote', repo], {
stdin: 'inherit',
});
export const getRepoName = ({ repo }: Pick<GitParams, 'repo'>) => {
return repo.split('/').pop();
};

if (proc.exitCode === 0) {
log.info(`Remote repo: ${repo} is valid.`);
} else {
throw new Error(`Remote repo: ${repo} is not valid or does not exist.`);
}
}
}
export const getRepoPath = ({ path, repo }: Omit<GitParams, 'ref'>): string => {
return `${path}/${getRepoName({ repo })}`;
};

export const localRepoExists = (path: string) => {
export const localRepoExists = ({ path }: Pick<GitParams, 'path'>) => {
return isDirectory(`${path}/.git`);
};

Expand All @@ -67,3 +55,17 @@ export const validateLocalGitRepo = (path: string) => {
throw new Error(`Local path ${path} is not a valid git repository.`);
}
};

export const remoteRepoExists = ({ repo }: Pick<GitParams, 'repo'>) => {
const proc = Bun.spawnSync(['git', 'ls-remote', repo], {
stdin: 'inherit',
});

if (proc.exitCode === 0) {
log.info(`Remote repo: ${repo} is valid.`);
} else {
throw new Error(`Remote repo: ${repo} is not valid or does not exist.`);
}
};

export default git;
12 changes: 6 additions & 6 deletions src/utils/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ export interface PathModificationOperation {
force?: boolean;
}

export interface GitParams {
workingDir: string;
repo: string;
ref: string;
}

export interface ComposeFilesParams {
prefix?: string;
filesDir?: string;
Expand All @@ -32,3 +26,9 @@ export interface ComposeExecParams {
files?: Array<string>;
cmd: Array<string>;
}

export interface GitParams {
repo: string;
path: string;
ref: string;
}
10 changes: 5 additions & 5 deletions src/utils/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@ import chalk from 'chalk';

const log = {
info: (text?: any, ...optionalParams: any[]) => {
return console.log(chalk.blue(text), optionalParams);
return console.log(chalk.blue(text), ...optionalParams);
},

success: (text?: any, ...optionalParams: any[]) => {
return console.log(chalk.green(text), optionalParams);
return console.log(chalk.green(text), ...optionalParams);
},

warning: (text?: any, ...optionalParams: any[]) => {
return console.log(chalk.yellow(text), optionalParams);
return console.log(chalk.yellow(text), ...optionalParams);
},

dimmedWarning: (text?: any, ...optionalParams: any[]) => {
return console.log(chalk.dim.yellow(text), optionalParams);
return console.log(chalk.dim.yellow(text), ...optionalParams);
},

error: (text?: any, ...optionalParams: any[]) => {
return console.log(chalk.red(text), optionalParams);
return console.log(chalk.red(text), ...optionalParams);
},
};

Expand Down
2 changes: 1 addition & 1 deletion test/utils/cmd.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, mock, test, type jest } from 'bun:test';
import { describe, expect, type jest, mock, test } from 'bun:test';
import { execSync } from 'node:child_process';
import cmd from '../../src/utils/cmd';
import { removeExtraSpaces } from '../../src/utils/string';
Expand Down
22 changes: 15 additions & 7 deletions test/utils/file-system.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { describe, expect, mock, test, jest, beforeEach } from 'bun:test';
import { beforeEach, describe, expect, jest, mock, test } from 'bun:test';
import fs from 'node:fs';
import {
copyFile,
createFile,
createPath,
isDirectory,
isFile,
copyFile,
createPath,
} from '../../src/utils/file-system';
import log from '../../src/utils/log';

Expand Down Expand Up @@ -160,7 +160,9 @@ describe('Utils: file-system', () => {
copyFile({ source, target });

expect(fs.copyFileSync).toHaveBeenCalledWith(source, target, 0);
expect(log.success).toHaveBeenCalledWith(`File: ${source} copied to ${target}.`);
expect(log.success).toHaveBeenCalledWith(
`File: ${source} copied to ${target}.`,
);
});

test('logs warning if target file already exists', () => {
Expand All @@ -172,7 +174,9 @@ describe('Utils: file-system', () => {

copyFile({ source, target });

expect(log.warning).toHaveBeenCalledWith(`Path: ${target} already exists.`);
expect(log.warning).toHaveBeenCalledWith(
`Path: ${target} already exists.`,
);
expect(fs.copyFileSync).not.toHaveBeenCalled();
});
});
Expand All @@ -184,7 +188,9 @@ describe('Utils: file-system', () => {
createPath({ path: 'test-path' });

expect(fs.existsSync).toHaveBeenCalledWith('test-path');
expect(fs.mkdirSync).toHaveBeenCalledWith('test-path', { recursive: true });
expect(fs.mkdirSync).toHaveBeenCalledWith('test-path', {
recursive: true,
});
});

test('logs a warning if the directory already exists', () => {
Expand All @@ -195,7 +201,9 @@ describe('Utils: file-system', () => {

expect(fs.existsSync).toHaveBeenCalledWith('test-path');
expect(fs.mkdirSync).not.toHaveBeenCalled();
expect(log.warning).toHaveBeenCalledWith('Path: test-path already exists.');
expect(log.warning).toHaveBeenCalledWith(
'Path: test-path already exists.',
);
});
});
});
2 changes: 1 addition & 1 deletion test/utils/log.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, mock, test, jest, beforeEach } from 'bun:test';
import { beforeEach, describe, expect, jest, mock, test } from 'bun:test';
import log from '../../src/utils/log';

mock.module('../../src/utils/log', () => ({
Expand Down

0 comments on commit 73a83bc

Please sign in to comment.