UNPKG

@codecovevienna/gittt-cli

Version:

Tracking time with CLI into a git repository

281 lines (280 loc) 12.9 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProjectHelper = void 0; const shelljs_1 = __importDefault(require("shelljs")); const types_1 = require("../types"); const _1 = require("./"); class ProjectHelper { constructor(gitHelper, fileHelper) { this.getGitttProject = () => __awaiter(this, void 0, void 0, function* () { let project = undefined; try { const gitttFile = yield this.fileHelper.getGitttFile(); project = { name: gitttFile.name, records: [] }; if (gitttFile.requiresRoles) { project.requiresRoles = gitttFile.requiresRoles; } _1.LogHelper.debug("Got project from yaml file"); } catch (err) { _1.LogHelper.debug("Error getting project from .gittt.yml file, trying git config"); try { project = this.getProjectFromGit(); } catch (err) { _1.LogHelper.debug("Unable to get project from git config", err); throw err; } } return project; }); this.initProject = () => __awaiter(this, void 0, void 0, function* () { try { const project = yield this.getGitttProject(); yield this.fileHelper.initProject(project); yield this.gitHelper.commitChanges(`Initialized project`); return project; } catch (err) { _1.LogHelper.debug("Error initializing project", err); throw new Error("Error initializing project"); } }); this.addRecordsToProject = (records, project, uniqueOnly, nonOverlappingOnly) => __awaiter(this, void 0, void 0, function* () { const selectedProject = project ? project : yield this.getGitttProject(); if (!selectedProject) { return; } records = records.filter((record) => { let shouldAddRecord = true; // Checks uniqueness only against existing records, the provided records have to be unique in the first place! if (uniqueOnly === true) { shouldAddRecord = _1.RecordHelper.isRecordUnique(record, selectedProject.records); } if (nonOverlappingOnly === true) { shouldAddRecord = _1.RecordHelper.isRecordOverlapping(record, selectedProject.records); } if (shouldAddRecord) { return record; } _1.LogHelper.warn(`Could not add record (amount: ${record.amount}, end: ${record.end}, type: ${record.type}) to ${selectedProject.name}`); }); if (records.length === 1) { let record = records[0]; _1.LogHelper.info(`Adding record (amount: ${record.amount}, type: ${record.type}, role: ${record.role}) to ${selectedProject.name}`); record = _1.RecordHelper.setRecordDefaults(record); selectedProject.records.push(record); yield this.fileHelper.saveProjectObject(selectedProject); // TODO differ between types const hourString = record.amount === 1 ? "hour" : "hours"; if (record.message) { yield this.gitHelper.commitChanges(`Added ${record.amount} ${hourString} to ${selectedProject.name}: "${record.message}"`); } else { yield this.gitHelper.commitChanges(`Added ${record.amount} ${hourString} to ${selectedProject.name}`); } } else if (records.length > 1) { if (records.length > 1) { records.forEach((record) => { record = _1.RecordHelper.setRecordDefaults(record); selectedProject.records.push(record); }); _1.LogHelper.info(`Adding (${records.length}) records to ${selectedProject.name}`); yield this.fileHelper.saveProjectObject(selectedProject); yield this.gitHelper.commitChanges(`Added ${records.length} records to ${selectedProject.name}`); } } }); this.addRecordToProject = (record, project, uniqueOnly, nonOverlappingOnly) => __awaiter(this, void 0, void 0, function* () { return this.addRecordsToProject([record], project, uniqueOnly, nonOverlappingOnly); }); // TODO projectName optional? find it by .git folder this.getTotalHours = (projectName) => __awaiter(this, void 0, void 0, function* () { const project = yield this.fileHelper.findProjectByName(projectName); if (!project) { throw new Error(`Project "${projectName}" not found`); } return project.records.reduce((prev, curr) => { if (curr.type === types_1.RECORD_TYPES.Time) { return prev + curr.amount; } else { return prev; } }, 0); }); this.getProjectByName = (name) => __awaiter(this, void 0, void 0, function* () { const projects = yield this.fileHelper.findAllProjects(); let foundProject = projects.find((p) => p.name === name); if (!foundProject || !name) { try { foundProject = yield this.getGitttProject(); if (foundProject) { // Loads the records from the filesystem to avoid empty record array foundProject = yield this.fileHelper.findProjectByName(foundProject.name); } else { throw new Error("Unable to get gittt project"); } } catch (err) { _1.LogHelper.debug(`Unable to get project from git directory: ${err.message}`); throw err; } } if (!foundProject) { throw new Error("Unable to get gittt project"); } return foundProject; }); this.getAllProjects = () => __awaiter(this, void 0, void 0, function* () { return this.fileHelper.findAllProjects(); }); this.getProjectFromGit = () => { _1.LogHelper.debug("Checking number of remote urls"); const gitRemoteExec = shelljs_1.default.exec("git remote", { silent: true, }); if (gitRemoteExec.code !== 0) { if (gitRemoteExec.code === 128) { _1.LogHelper.debug(`"git remote" returned with exit code 128`); throw new types_1.GitNoRepoError("Current directory does not appear to be a valid git repository"); } _1.LogHelper.debug("Error executing git remote", new Error(gitRemoteExec.stdout)); throw new types_1.GitRemoteError("Unable to get remotes from git config"); } const remotes = gitRemoteExec.stdout.trim().split("\n"); if (remotes.length > 1) { _1.LogHelper.debug("Found more than one remotes, trying to find origin"); const hasOrigin = remotes.indexOf("origin") !== -1; if (!hasOrigin) { _1.LogHelper.error(`Unable to find any remote called "origin"`); throw new types_1.GitNoOriginError(`Unable to find any remote called "origin"`); } } _1.LogHelper.debug("Trying to find project name from .git folder"); const gitConfigExec = shelljs_1.default.exec("git config remote.origin.url", { silent: true, }); if (gitConfigExec.code !== 0 || gitConfigExec.stdout.length < 4) { _1.LogHelper.debug("Error executing git config remote.origin.url", new Error(gitConfigExec.stdout)); throw new types_1.GitNoUrlError("Unable to get URL from git config"); } const originUrl = gitConfigExec.stdout.trim(); return (0, _1.parseProjectNameFromGitUrl)(originUrl); }; this.getOrAskForProjectFromGit = () => __awaiter(this, void 0, void 0, function* () { let projectName; let projectMeta; let projectRequiresRoles; try { const gitttProject = yield this.getGitttProject(); projectName = gitttProject.name; projectRequiresRoles = gitttProject.requiresRoles; } catch (e) { if (e instanceof types_1.GitRemoteError || e instanceof types_1.GitNoRepoError || e instanceof types_1.GitttFileError) { const selectedProjectName = yield _1.QuestionHelper. chooseProjectFile(yield this.fileHelper.findAllProjects()); const split = selectedProjectName.split("/"); // TODO find a better way? if (split.length == 2) { const [domain, name] = split; projectName = name.replace(".json", ""); projectMeta = ProjectHelper.domainToProjectMeta(domain); } else { // Handles projects with no domain information projectName = split[0].replace(".json", ""); projectMeta = undefined; } } else { throw e; } } const project = yield this.fileHelper.findProjectByName(projectName, projectMeta); if (!project) { throw new Error(`Unable to find project "${projectName}" on disk`); } // requiresRoles from .gittt.yml file overwrites the config if it is set. if (undefined !== projectRequiresRoles) { project.requiresRoles = projectRequiresRoles; } return project; }); this.gitHelper = gitHelper; this.fileHelper = fileHelper; } } exports.ProjectHelper = ProjectHelper; /** * Extracts the domain from a IProjectMetaData object * * @param projectMeta - MetaData object * @returns Domain of the meta data as formatted string */ ProjectHelper.projectMetaToDomain = (projectMeta) => { const { host, port } = projectMeta; return `${host.replace(/\./gi, "_")}${port ? "_" + port : ""}`; }; /** * Constructs a meta data object from a formatted domain string * * @param domain - formatted domain string * @returns Meta data object based on the formatted string */ ProjectHelper.domainToProjectMeta = (domain) => { const split = domain.split("_"); const potentialPort = parseInt(split[split.length - 1], 10); let port = 0; let splitClean = []; if (!isNaN(potentialPort)) { port = potentialPort; splitClean = split.slice(0, split.length - 1); } else { splitClean = split; } return { host: splitClean.join("."), port, }; }; /** * @param project - IProject object * @returns Filename of the project file */ ProjectHelper.projectToProjectFilename = (project) => { return `${project.name}.json`; }; /** * @param project - IProject object * @returns Path of the project file */ ProjectHelper.getProjectPath = (project) => { if (!project.meta) { return `${ProjectHelper.projectToProjectFilename(project)}`; } else { return `${ProjectHelper.projectMetaToDomain(project.meta)}/${ProjectHelper.projectToProjectFilename(project)}`; } };