@codecovevienna/gittt-cli
Version:
Tracking time with CLI into a git repository
281 lines (280 loc) • 12.9 kB
JavaScript
;
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)}`;
}
};