From 73a83bcd5010b7269ca8b4b27b8816a96e5b86b4 Mon Sep 17 00:00:00 2001 From: "Angel M. Adames" Date: Mon, 18 Mar 2024 15:48:52 -0400 Subject: [PATCH] chore: Refactor clone command, use git command instead --- cli.ts | 4 +- src/commands/git/checkout.ts | 36 +++++++++ src/commands/{clone/index.ts => git/clone.ts} | 36 +++------ src/commands/git/index.ts | 17 +++++ src/utils/git.ts | 76 ++++++++++--------- src/utils/interfaces.ts | 12 +-- src/utils/log.ts | 10 +-- test/utils/cmd.test.ts | 2 +- test/utils/file-system.test.ts | 22 ++++-- test/utils/log.test.ts | 2 +- 10 files changed, 132 insertions(+), 85 deletions(-) create mode 100644 src/commands/git/checkout.ts rename src/commands/{clone/index.ts => git/clone.ts} (52%) create mode 100644 src/commands/git/index.ts diff --git a/cli.ts b/cli.ts index 9a06691..5a87392 100755 --- a/cli.ts +++ b/cli.ts @@ -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(); @@ -17,7 +17,7 @@ cli .version('0.0.1') .addCommand(configCommand()) .addCommand(setupCommand()) - .addCommand(cloneCommand()) + .addCommand(gitCommand()) .addCommand(cleanCommand()) .addCommand(environmentCommand()) .addCommand(composeCommand()) diff --git a/src/commands/git/checkout.ts b/src/commands/git/checkout.ts new file mode 100644 index 0000000..3887f92 --- /dev/null +++ b/src/commands/git/checkout.ts @@ -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(); diff --git a/src/commands/clone/index.ts b/src/commands/git/clone.ts similarity index 52% rename from src/commands/clone/index.ts rename to src/commands/git/clone.ts index 149b07f..e483bdb 100644 --- a/src/commands/clone/index.ts +++ b/src/commands/git/clone.ts @@ -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(); @@ -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(); diff --git a/src/commands/git/index.ts b/src/commands/git/index.ts new file mode 100644 index 0000000..3a090f9 --- /dev/null +++ b/src/commands/git/index.ts @@ -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(); diff --git a/src/utils/git.ts b/src/utils/git.ts index 316549f..215544c 100644 --- a/src/utils/git.ts +++ b/src/utils/git.ts @@ -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) { - if (!localRepoExists(workingDir)) { - log.error(`Repo does not exist at: ${workingDir}`); - throw new Error('Repo not found.'); + checkout({ path, ref }: Omit) { + 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) => { + 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): string => { + return `${path}/${getRepoName({ repo })}`; +}; -export const localRepoExists = (path: string) => { +export const localRepoExists = ({ path }: Pick) => { return isDirectory(`${path}/.git`); }; @@ -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) => { + 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; diff --git a/src/utils/interfaces.ts b/src/utils/interfaces.ts index fe7c37c..4b5ead4 100644 --- a/src/utils/interfaces.ts +++ b/src/utils/interfaces.ts @@ -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; @@ -32,3 +26,9 @@ export interface ComposeExecParams { files?: Array; cmd: Array; } + +export interface GitParams { + repo: string; + path: string; + ref: string; +} diff --git a/src/utils/log.ts b/src/utils/log.ts index b3fdd81..efbdc47 100644 --- a/src/utils/log.ts +++ b/src/utils/log.ts @@ -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); }, }; diff --git a/test/utils/cmd.test.ts b/test/utils/cmd.test.ts index 8b4fe8e..8596596 100644 --- a/test/utils/cmd.test.ts +++ b/test/utils/cmd.test.ts @@ -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'; diff --git a/test/utils/file-system.test.ts b/test/utils/file-system.test.ts index 96d6dc3..098cc8e 100644 --- a/test/utils/file-system.test.ts +++ b/test/utils/file-system.test.ts @@ -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'; @@ -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', () => { @@ -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(); }); }); @@ -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', () => { @@ -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.', + ); }); }); }); diff --git a/test/utils/log.test.ts b/test/utils/log.test.ts index 8122c13..1bf4a05 100644 --- a/test/utils/log.test.ts +++ b/test/utils/log.test.ts @@ -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', () => ({