UNPKG

@codecovevienna/gittt-cli

Version:

Tracking time with CLI into a git repository

1,001 lines 64.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; 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.App = void 0; const axios_1 = __importDefault(require("axios")); const chalk_1 = __importDefault(require("chalk")); const commander_1 = require("commander"); const lodash_1 = __importStar(require("lodash")); const moment_1 = __importDefault(require("moment")); const path_1 = __importDefault(require("path")); const helper_1 = require("./helper"); const types_1 = require("./types"); // eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-explicit-any const packageJson = require("./package.json"); const APP_NAME = packageJson.name; const APP_VERSION = packageJson.version; const APP_CONFIG_DIR = ".gittt-cli"; const JIRA_ENDPOINT_VERSION = "v2"; class App { start() { if (process.argv.length === 2) { this.commander.help(); } else { this.commander.parse(process.argv); } } exit(msg, code) { if (code === 0) { helper_1.LogHelper.warn(msg); } else { helper_1.LogHelper.error(msg); } process.exit(code); } setup() { return __awaiter(this, void 0, void 0, function* () { this.configDir = path_1.default.join(helper_1.FileHelper.getHomeDir(), `${APP_CONFIG_DIR}`); this.fileHelper = new helper_1.FileHelper(this.configDir, "config.json", "timer.json", "projects"); this.configHelper = helper_1.ConfigHelper.getInstance(this.fileHelper); if (!(yield this.configHelper.isInitialized())) { if (yield helper_1.QuestionHelper.confirmSetup()) { yield this.initConfigDir(); helper_1.LogHelper.info("Initialized git-time-tracker (GITTT) you are good to go now ;)\n\n"); } else { this.exit(`${APP_NAME} does not work without setup, bye!`, 0); } } this.gitHelper = new helper_1.GitHelper(this.configDir, this.fileHelper); this.projectHelper = new helper_1.ProjectHelper(this.gitHelper, this.fileHelper); this.timerHelper = new helper_1.TimerHelper(this.fileHelper, this.projectHelper); this.importHelper = new helper_1.ImportHelper(); this.authHelper = new helper_1.AuthHelper(); this.initCommander(); }); } // TODO should be moved to config helper, but gitHelper needs a valid config dir initConfigDir() { return __awaiter(this, void 0, void 0, function* () { if (!(yield this.fileHelper.configDirExists())) { yield this.fileHelper.createConfigDir(); this.gitHelper = new helper_1.GitHelper(this.configDir, this.fileHelper); if (!(yield this.fileHelper.isConfigFileValid())) { const gitUrl = yield helper_1.QuestionHelper.askGitUrl(); helper_1.LogHelper.info("Initializing local repo"); yield this.gitHelper.initRepo(gitUrl); // TODO remove reset=true? helper_1.LogHelper.info("Pulling repo..."); yield this.gitHelper.pullRepo(); // Check if a valid config file is already in the repo if (!(yield this.fileHelper.isConfigFileValid())) { helper_1.LogHelper.info("Initializing gittt config file"); yield this.fileHelper.initConfigFile(gitUrl); helper_1.LogHelper.info("Committing created config file"); yield this.gitHelper.commitChanges("Initialized config file"); helper_1.LogHelper.info("Pushing changes to remote repo"); yield this.gitHelper.pushChanges(); } } else { yield this.gitHelper.pullRepo(); } } else { if (yield this.fileHelper.isConfigFileValid()) { this.gitHelper = new helper_1.GitHelper(this.configDir, this.fileHelper); yield this.gitHelper.pullRepo(); helper_1.LogHelper.info(`Config directory ${this.configDir} already initialized`); } else { helper_1.LogHelper.warn(`Config file exists, but is invalid`); this.exit("Invalid config file", 1); // TODO reinitialize? } } }); } handleRole(cmd, interactive, project, record, inputRole) { var _a; return __awaiter(this, void 0, void 0, function* () { const multipieHelper = new helper_1.MultipieHelper(); const updatedRecord = record; // Project does not require a role, just return the original record if (!project.requiresRoles) { return updatedRecord; } if (!interactive) { if (!inputRole) { helper_1.LogHelper.error("No role option found"); return cmd.help(); } // get roles if (project.requiresRoles) { const availableRoles = yield multipieHelper.getValidRoles(project, record); const role = (_a = availableRoles.find((role_) => role_.name == inputRole)) === null || _a === void 0 ? void 0 : _a.value; if (role === undefined) { helper_1.LogHelper.info(`Available roles for ${project.name} are: \n${availableRoles .map(availableRole => ` - ${availableRole.name}`) .sort() .join(",\n")}`); this.exit(`Role "${inputRole}" is not available in this project`, 1); } updatedRecord.role = role; } } else { updatedRecord.role = yield helper_1.QuestionHelper.chooseRole(project, record); } return updatedRecord; }); } exportAction(options) { return __awaiter(this, void 0, void 0, function* () { helper_1.LogHelper.print(`Gathering projects...`); let projectsToExport = []; const { project, directory, filename, type } = options; if (project) { const projectToExport = yield this.fileHelper.findProjectByName(project); if (!projectToExport) { this.exit(`✗ Project "${project}" not found`, 1); } else { projectsToExport.push(projectToExport); } } else { projectsToExport = yield this.fileHelper.findAllProjects(); } helper_1.LogHelper.info(`✓ Got all ${projectsToExport.length} projects`); helper_1.ExportHelper.export(directory, filename, type, projectsToExport); helper_1.LogHelper.info(`✓ Export done`); }); } linkAction(options) { return __awaiter(this, void 0, void 0, function* () { const interactiveMode = process.argv.length === 3; let project; try { if (!interactiveMode) { project = yield this.projectHelper.getProjectByName(options.project); } else { project = yield this.projectHelper.getOrAskForProjectFromGit(); } } catch (err) { return this.exit(err.message, 1); } if (!project) { return this.exit("No valid git project", 1); } const integration = yield helper_1.QuestionHelper.chooseIntegration(); helper_1.LogHelper.debug(`Trying to find links for "${project.name}"`); // Check for previous data const prevIntegrationLink = (yield this.configHelper .findLinksByProject(project, integration))[0]; switch (integration) { case "Jira": let prevJiraLink; if (prevIntegrationLink) { helper_1.LogHelper.info(`Found link for "${project.name}", enriching dialog with previous data`); prevJiraLink = prevIntegrationLink; } const jiraLink = yield helper_1.QuestionHelper.askJiraLink(project, prevJiraLink, JIRA_ENDPOINT_VERSION); try { yield this.configHelper.addOrUpdateLink(jiraLink); } catch (err) { helper_1.LogHelper.debug(`Unable to add link to config file`, err); return this.exit(`Unable to add link to config file`, 1); } break; case "Multipie": let prevMultipieLink; if (prevIntegrationLink) { helper_1.LogHelper.info(`Found link for "${project.name}", enriching dialog with previous data`); prevMultipieLink = prevIntegrationLink; } const multiPieInputLink = yield helper_1.QuestionHelper.askMultipieLink(project, prevMultipieLink); const multipieAuth = this.authHelper.getAuthClient(multiPieInputLink); try { const authResponse = yield multipieAuth.owner.getToken(multiPieInputLink.username, multiPieInputLink.password); helper_1.LogHelper.debug(`Got offline access refresh token`); const offlineToken = { projectName: multiPieInputLink.projectName, linkType: multiPieInputLink.linkType, endpoint: multiPieInputLink.endpoint, clientSecret: multiPieInputLink.clientSecret, refreshToken: authResponse.refreshToken, }; try { yield this.configHelper.addOrUpdateLink(offlineToken); } catch (err) { helper_1.LogHelper.debug(`Unable to add link to config file`, err); return this.exit(`Unable to add link to config file`, 1); } } catch (err) { helper_1.LogHelper.debug(`Unable to authenticate user`, err); return this.exit(`Unable to authenticate user`, 1); } break; default: this.exit(`Integration "${integration}" not implemented`, 1); break; } }); } publishAction(options) { return __awaiter(this, void 0, void 0, function* () { const interactiveMode = process.argv.length === 3; let projects = []; try { if (!interactiveMode) { if (options.all) { projects = yield this.projectHelper.getAllProjects(); } else { const project = yield this.projectHelper.getProjectByName(options.project); if (project) { projects = [project]; } } } else { const project = yield this.projectHelper.getOrAskForProjectFromGit(); if (project) { projects = [project]; } } } catch (err) { return this.exit(err.message, 1); } if (projects.length < 1 || !projects) { return this.exit("No valid git project", 1); } const projectIntegrationLinks = yield Promise.all(projects.map((project) => __awaiter(this, void 0, void 0, function* () { const links = yield this.configHelper.findLinksByProject(project); return { name: project.name, meta: project.meta, records: project.records, integrationLinks: links }; }))); if (projectIntegrationLinks.length === 1 && projectIntegrationLinks[0].integrationLinks.length === 0) { helper_1.LogHelper.warn(`Unable to find a link for "${projectIntegrationLinks[0].name}"`); if (yield helper_1.QuestionHelper.confirmLinkCreation()) { yield this.linkAction(options); return yield this.publishAction(options); } else { return this.exit(`Unable to publish without link`, 1); } } else { for (const project of projectIntegrationLinks) { if (project.integrationLinks.length === 0) { helper_1.LogHelper.warn(`Unable to find a link for "${project.name}"`); } } } const logs = yield this.gitHelper.logChanges(); if (logs.length > 0) { if (yield helper_1.QuestionHelper.confirmPushLocalChanges()) { yield this.gitHelper.pushChanges(); } else { return this.exit("Unable to publish with local changes", 1); } } const publishSummary = []; for (const project of projectIntegrationLinks) { for (const link of project.integrationLinks) { switch (link.linkType) { case "Jira": const jiraLink = link; // Map local project to jira key if (jiraLink.issue) { helper_1.LogHelper.info(`Mapping "${project.name}" to Jira issue "${jiraLink.issue}" within project "${jiraLink.key}"`); } else { helper_1.LogHelper.info(`Mapping "${project.name}" to Jira project "${jiraLink.key}"`); } if (!jiraLink.host) { // Handle deprecated config return this.exit('The configuration of this jira link is deprecated, please consider updating the link with "gittt link"', 1); } const jiraUrl = `${jiraLink.host}${jiraLink.endpoint}`; helper_1.LogHelper.debug(`Publishing to ${jiraUrl}`); try { const publishResult = yield axios_1.default .post(jiraUrl, { projectKey: jiraLink.key, issueKey: jiraLink.issue, project, }, { headers: { "Authorization": `Basic ${jiraLink.hash}`, "Cache-Control": "no-cache", "Content-Type": "application/json", }, }); const data = publishResult.data; if (data.success) { publishSummary.push({ success: true, type: link.linkType, }); } else { publishSummary.push({ success: false, type: link.linkType, reason: `Publishing failed [${publishResult.status}]` }); } } catch (err) { delete err.config; delete err.request; delete err.response; helper_1.LogHelper.debug("Publish request failed", err); publishSummary.push({ success: false, type: link.linkType, reason: `Publish request failed, please consider updating the link` }); } break; case "Multipie": const multipieLink = link; try { let authorizationHeader = ""; if (multipieLink.username) { // Legacy flow helper_1.LogHelper.debug("Found username parameter in link configuration, using legacy auth method"); authorizationHeader = this.authHelper.getLegacyAuth(multipieLink); } else { const multipieAuth = this.authHelper.getAuthClient(multipieLink); const { refreshToken } = multipieLink; if (!refreshToken) { this.exit(`Unable to find refresh token for this project, please login via 'gittt link'`, 1); return; } const offlineToken = yield multipieAuth.createToken("", refreshToken, {}); helper_1.LogHelper.debug(`Refreshing token to get access token`); const refreshedToken = yield offlineToken.refresh(); helper_1.LogHelper.debug(`Got access token`); authorizationHeader = `Bearer ${refreshedToken.accessToken}`; } const multipieUrl = `${multipieLink.endpoint}`; helper_1.LogHelper.debug(`Publishing to ${multipieUrl}`); const publishResult = yield axios_1.default .post(multipieUrl, project, { headers: { "Authorization": authorizationHeader, "Cache-Control": "no-cache", "Content-Type": "application/json", }, }); const data = publishResult.data; if (data && (publishResult.status === 200 || publishResult.status === 201)) { publishSummary.push({ success: true, type: link.linkType, }); } else { publishSummary.push({ success: false, type: link.linkType, reason: `Publishing failed [${publishResult.status}]` }); } } catch (err) { delete err.config; delete err.request; delete err.response; helper_1.LogHelper.debug("Publish request failed", err); publishSummary.push({ success: false, type: link.linkType, reason: `Publish request failed, please consider updating the link` }); } break; default: publishSummary.push({ success: false, type: "unknown", reason: `Link type "${link.linkType}" not implemented` }); break; } } } for (const item of publishSummary) { if (item.success) { helper_1.LogHelper.info(`✓ Successfully published to ${item.type}`); } else { helper_1.LogHelper.warn(`✗ Unable to publish to ${item.type}: ${item.reason}`); } } if (publishSummary.filter(item => item.success === false).length > 0) { this.exit(`One or more errors occurred while publishing data`, 1); } }); } editAction(options, cmd) { return __awaiter(this, void 0, void 0, function* () { const interactiveMode = process.argv.length === 3; let project; // TODO move to own function, is used multiple times try { if (!interactiveMode) { project = yield this.projectHelper.getProjectByName(options.project); } else { project = yield this.projectHelper.getOrAskForProjectFromGit(); } } catch (err) { return this.exit(err.message, 1); } if (!project) { return this.exit("No valid git project", 1); } const projectWithRecords = yield this.fileHelper.findProjectByName(project.name); if (!projectWithRecords) { return this.exit(`Unable to find project "${project.name}"`, 1); } if (projectWithRecords.records.length === 0) { return this.exit(`No records found for "${project.name}"`, 1); } const { records } = projectWithRecords; let recordsToEdit; let chosenRecord; if (!interactiveMode) { const recordGuid = options.guid; const chosenRecords = records.filter((rc) => { return rc.guid === recordGuid; }); chosenRecord = chosenRecords[0]; if (!chosenRecord) { return this.exit(`No records found for guid "${recordGuid}"`, 1); } } else { recordsToEdit = yield helper_1.RecordHelper.filterRecordsByYear(records); recordsToEdit = yield helper_1.RecordHelper.filterRecordsByMonth(recordsToEdit); recordsToEdit = yield helper_1.RecordHelper.filterRecordsByDay(recordsToEdit); chosenRecord = yield helper_1.QuestionHelper.chooseRecord(recordsToEdit); } let updatedRecord = Object.assign({}, chosenRecord); let year; let month; let day; let hour; let minute; let amount; let message; if (!interactiveMode) { if (options.type) { updatedRecord.type = options.type; } else { helper_1.LogHelper.error("No type option found"); return cmd.help(); } if (!helper_1.ValidationHelper.validateNumber(options.amount)) { helper_1.LogHelper.error("No amount option found"); return cmd.help(); } if (!options.role && project.requiresRoles) { helper_1.LogHelper.error("No role option found"); return cmd.help(); } amount = parseFloat(options.amount); year = helper_1.ValidationHelper.validateNumber(options.year) ? parseInt(options.year, 10) : (0, moment_1.default)().year(); month = helper_1.ValidationHelper.validateNumber(options.month, 1, 12) ? parseInt(options.month, 10) : (0, moment_1.default)().month() + 1; day = helper_1.ValidationHelper.validateNumber(options.day, 1, 31) ? parseInt(options.day, 10) : (0, moment_1.default)().date(); hour = helper_1.ValidationHelper.validateNumber(options.hour, 0, 23) ? parseInt(options.hour, 10) : (0, moment_1.default)().hour(); minute = helper_1.ValidationHelper.validateNumber(options.minute, 0, 59) ? parseInt(options.minute, 10) : (0, moment_1.default)().minute(); message = (options.message && options.message.length > 0) ? options.message : undefined; } else { updatedRecord.type = yield helper_1.QuestionHelper.chooseType(chosenRecord.type); year = yield helper_1.QuestionHelper.askYear((0, moment_1.default)(chosenRecord.end).year()); month = yield helper_1.QuestionHelper.askMonth((0, moment_1.default)(chosenRecord.end).month() + 1); day = yield helper_1.QuestionHelper.askDay((0, moment_1.default)(chosenRecord.end).date()); hour = yield helper_1.QuestionHelper.askHour((0, moment_1.default)(chosenRecord.end).hour()); minute = yield helper_1.QuestionHelper.askMinute((0, moment_1.default)(chosenRecord.end).minute()); amount = yield helper_1.QuestionHelper.askAmount(chosenRecord.amount); message = yield helper_1.QuestionHelper.askMessage(chosenRecord.message); } updatedRecord.updated = Date.now(); updatedRecord.message = message; updatedRecord.amount = amount; updatedRecord = yield this.handleRole(cmd, interactiveMode, project, updatedRecord, options.role); const modifiedMoment = (0, moment_1.default)().set({ date: day, hour, millisecond: 0, minute, month: month - 1, second: 0, year, }); updatedRecord.end = modifiedMoment.unix() * 1000; const updatedRecords = records.map((rc) => { return rc.guid === updatedRecord.guid ? updatedRecord : rc; }); const updatedProject = projectWithRecords; updatedProject.records = updatedRecords; yield this.fileHelper.saveProjectObject(updatedProject); let changes = ""; if (updatedRecord.amount !== chosenRecord.amount) { changes += `amount: ${updatedRecord.amount}, `; } if (updatedRecord.end !== chosenRecord.end) { changes += `end: ${updatedRecord.end}, `; } if (updatedRecord.message !== chosenRecord.message) { changes += `message: ${updatedRecord.message}, `; } if (updatedRecord.type !== chosenRecord.type) { changes += `type: ${updatedRecord.type}, `; } if (updatedRecord.role !== chosenRecord.role) { changes += `role: ${updatedRecord.role}, `; } if (changes.length > 0) { changes = changes.slice(0, -2); } const commitMessage = changes.length > 0 ? `Updated record(${changes}) at ${updatedProject.name} ` : `Updated record at ${updatedProject.name} `; yield this.gitHelper.commitChanges(commitMessage); helper_1.LogHelper.info(commitMessage); }); } // TODO pretty much the same as editAction, refactor? removeAction(options, cmd) { return __awaiter(this, void 0, void 0, function* () { const interactiveMode = process.argv.length === 3; let project; try { try { if (!interactiveMode) { project = yield this.projectHelper.getProjectByName(options.project); } else { project = yield this.projectHelper.getOrAskForProjectFromGit(); } } catch (err) { return this.exit(err.message, 1); } } catch (err) { helper_1.LogHelper.debug("Unable to get project name from git folder", err); return this.exit("Unable to get project name from git folder", 1); } if (!project) { return this.exit("No valid git project", 1); } const projectWithRecords = yield this.fileHelper.findProjectByName(project.name); if (!projectWithRecords) { return this.exit(`Unable to find project "${project.name}"`, 1); } if (projectWithRecords.records.length === 0) { return this.exit(`No records found for "${project.name}"`, 1); } const { records } = projectWithRecords; let recordsToDelete; let chosenRecord; if (!interactiveMode) { if (!options.guid) { helper_1.LogHelper.error("No guid option found"); return cmd.help(); } const recordGuid = options.guid; const chosenRecords = records.filter((rc) => { return rc.guid === recordGuid; }); chosenRecord = chosenRecords[0]; if (!chosenRecord) { return this.exit(`No records found for guid "${recordGuid}"`, 1); } } else { recordsToDelete = yield helper_1.RecordHelper.filterRecordsByYear(records); recordsToDelete = yield helper_1.RecordHelper.filterRecordsByMonth(recordsToDelete); recordsToDelete = yield helper_1.RecordHelper.filterRecordsByDay(recordsToDelete); chosenRecord = yield helper_1.QuestionHelper.chooseRecord(recordsToDelete); } // TODO confirm deletion? const updatedRecords = records.filter((rc) => { return rc.guid !== chosenRecord.guid; }); const updatedProject = projectWithRecords; updatedProject.records = updatedRecords; yield this.fileHelper.saveProjectObject(updatedProject); const commitMessage = `Removed record ${chosenRecord.guid} from project ${updatedProject.name}`; yield this.gitHelper.commitChanges(commitMessage); helper_1.LogHelper.info(`Removed record (${(0, moment_1.default)(chosenRecord.end).format("DD.MM.YYYY, HH:mm:ss")}: ${chosenRecord.amount} ${chosenRecord.type} - "${lodash_1.default.truncate(chosenRecord.message)}") from project ${updatedProject.name}`); }); } commitAction(options, cmd) { return __awaiter(this, void 0, void 0, function* () { const interactiveMode = process.argv.length === 3; let amount; let message; let commitMessage; let project; try { if (!interactiveMode) { amount = parseFloat(options.amount); message = options.message; project = yield this.projectHelper.getProjectByName(options.project); } else { amount = yield helper_1.QuestionHelper.askAmount(1); project = yield this.projectHelper.getOrAskForProjectFromGit(); message = yield helper_1.QuestionHelper.askMessage(); } } catch (err) { return this.exit(err.message, 1); } if (isNaN(amount)) { return this.exit("No valid amount", 1); } if (!project) { return this.exit("No valid git project", 1); } if (message && message.length > 0) { commitMessage = message; } else { commitMessage = `Committed ${amount} hour${amount > 1 ? "s" : ""} to ${project.name}`; } commitMessage = yield (0, helper_1.appendTicketNumber)(commitMessage, yield this.gitHelper.getCurrentBranch()); try { const data = { amount, end: Date.now(), message: commitMessage, type: types_1.RECORD_TYPES.Time, }; const updatedData = yield this.handleRole(cmd, interactiveMode, project, data, options.role); yield this.projectHelper.addRecordToProject(updatedData, project); } catch (err) { helper_1.LogHelper.debug("Unable to add record to project", err); this.exit("Unable to add record to project", 1); } }); } addAction(options, cmd) { return __awaiter(this, void 0, void 0, function* () { const interactiveMode = process.argv.length === 3; let year; let month; let day; let hour; let minute; let amount; let message; let type; let project; try { if (!interactiveMode) { // TODO move to option of commander if (!helper_1.ValidationHelper.validateNumber(options.amount)) { helper_1.LogHelper.error("No amount option found"); return cmd.help(); } if (!options.type) { helper_1.LogHelper.error("No type option found"); return cmd.help(); } amount = parseFloat(options.amount); type = options.type; year = helper_1.ValidationHelper.validateNumber(options.year) ? parseInt(options.year, 10) : (0, moment_1.default)().year(); month = helper_1.ValidationHelper.validateNumber(options.month, 1, 12) ? parseInt(options.month, 10) : (0, moment_1.default)().month() + 1; day = helper_1.ValidationHelper.validateNumber(options.day, 1, 31) ? parseInt(options.day, 10) : (0, moment_1.default)().date(); hour = helper_1.ValidationHelper.validateNumber(options.hour, 0, 23) ? parseInt(options.hour, 10) : (0, moment_1.default)().hour(); minute = helper_1.ValidationHelper.validateNumber(options.minute, 0, 59) ? parseInt(options.minute, 10) : (0, moment_1.default)().minute(); message = (options.message && options.message.length > 0) ? options.message : undefined; project = yield this.projectHelper.getProjectByName(options.project); } else { project = yield this.projectHelper.getOrAskForProjectFromGit(); year = yield helper_1.QuestionHelper.askYear(); month = yield helper_1.QuestionHelper.askMonth(); day = yield helper_1.QuestionHelper.askDay(); hour = yield helper_1.QuestionHelper.askHour(); minute = yield helper_1.QuestionHelper.askMinute(); amount = yield helper_1.QuestionHelper.askAmount(1); message = yield helper_1.QuestionHelper.askMessage(); type = yield helper_1.QuestionHelper.chooseType(); } } catch (err) { return this.exit(err.message, 1); } const modifiedMoment = (0, moment_1.default)().set({ date: day, hour, millisecond: 0, minute, month: month - 1, second: 0, year, }); const end = modifiedMoment.unix() * 1000; let newRecord = { amount, end, message: message ? message : undefined, type, }; newRecord = yield this.handleRole(cmd, interactiveMode, project, newRecord, options.role); if (newRecord.message) { newRecord.message = yield (0, helper_1.appendTicketNumber)(newRecord.message, yield this.gitHelper.getCurrentBranch()); } try { yield this.projectHelper.addRecordToProject(newRecord, project); } catch (err) { helper_1.LogHelper.debug("Unable to add record to project", err); this.exit("Unable to add record to project", 1); } }); } importCsv(cmd, options) { return __awaiter(this, void 0, void 0, function* () { const interactiveMode = process.argv.length === 4; const filePath = cmd; if (!(0, lodash_1.isString)(filePath) || !helper_1.ValidationHelper.validateFile(filePath)) { return this.exit("Unable to get csv file path", 1); } let project; try { if (!interactiveMode) { project = yield this.projectHelper.getProjectByName(options.project); } else { project = yield this.projectHelper.getOrAskForProjectFromGit(); } } catch (err) { return this.exit(err.message, 1); } if (!project) { return this.exit("No valid git project", 1); } try { const records = yield this.importHelper.importCsv(filePath); helper_1.LogHelper.debug(`Parsed ${records.length} records from ${filePath}`); const uniqueRecords = lodash_1.default.uniqWith(records, lodash_1.default.isEqual); helper_1.LogHelper.debug(`Filtered out ${records.length - uniqueRecords.length} duplicates`); yield this.projectHelper.addRecordsToProject(uniqueRecords, project, true, false); } catch (err) { helper_1.LogHelper.debug("Error importing records from csv", err); this.exit(err.message, 1); } }); } infoAction(options) { return __awaiter(this, void 0, void 0, function* () { const interactiveMode = process.argv.length === 3; const order = types_1.ORDER_TYPE.indexOf(options.order) === -1 ? types_1.ORDER_TYPE[0] : options.order; const direction = types_1.ORDER_DIRECTION.indexOf(options.direction) === -1 ? types_1.ORDER_DIRECTION[0] : options.direction; let project; try { if (!interactiveMode) { project = yield this.projectHelper.getProjectByName(options.project); } else { project = yield this.projectHelper.getOrAskForProjectFromGit(); } } catch (err) { return this.exit(err.message, 1); } const projects = yield this.fileHelper.findAllProjects(); // get current Gittt project if (!project) { return this.exit("No valid git project", 1); } else { // check if the project is a gittt project const foundProject = projects.filter((p) => project && p.name === project.name)[0]; if (foundProject) { helper_1.LogHelper.info(""); helper_1.LogHelper.info(`Current project:`); const hours = yield this.projectHelper.getTotalHours(foundProject.name); helper_1.LogHelper.log(`Name:\t${foundProject.name}`); helper_1.LogHelper.log(`Hours:\t${hours}h`); const links = yield this.configHelper.findLinksByProject(project); for (const link of links) { switch (link.linkType) { case "Jira": const jiraLink = link; helper_1.LogHelper.log(""); helper_1.LogHelper.log("Jira link:"); helper_1.LogHelper.log(`> Host:\t\t${jiraLink.host}`); helper_1.LogHelper.log(`> Project:\t${jiraLink.key}`); if (jiraLink.issue) { helper_1.LogHelper.log(`> Issue:\t${jiraLink.issue}`); } break; case "Multipie": const multipieLink = link; helper_1.LogHelper.log(""); helper_1.LogHelper.log("Multipie link:"); helper_1.LogHelper.log(`> Host:\t\t${multipieLink.endpoint}`); helper_1.LogHelper.log(`> Project:\t${multipieLink.projectName}`); break; } } } else { helper_1.LogHelper.error("No gittt project in current git project."); } } helper_1.LogHelper.info(""); helper_1.LogHelper.info(`Projects:`); // add hours to projects const projectsWithHours = yield Promise.all(projects.map((prj) => __awaiter(this, void 0, void 0, function* () { return { hours: yield this.projectHelper.getTotalHours(prj.name), project: prj, }; }))); // order projects // sort mutates the array so we cannot save it to orderedProjects directly projectsWithHours.sort((a, b) => { if (order === "hours") { if (direction === "desc") { return (a.hours - b.hours) * -1; } return (a.hours - b.hours); } if (a.project.name < b.project.name) { return (direction === "desc") ? 1 : -1; } if (a.project.name > b.project.name) { return (direction === "desc") ? -1 : 1; } return 0; }); const orderedProjects = projectsWithHours; // print projects for (const prj of orderedProjects) { helper_1.LogHelper.log(`- ${prj.project.name}: ${prj.hours}h`); } }); } listAction(options) { return __awaiter(this, void 0, void 0, function* () { const interactiveMode = process.argv.length === 3; let project; try { if (!interactiveMode) { project = yield this.projectHelper.getProjectByName(options.project); } else { project = yield this.projectHelper.getOrAskForProjectFromGit(); } } catch (err) { return this.exit(err.message, 1); } if (!project) { return this.exit("No valid git project", 1); } if (project.records.length === 0) { return this.exit(`No records found for "${project.name}"`, 1); } // sorting newest to latest // because sort mutates the array we cannot assign it to records directly project.records.sort((a, b) => { const aStartTime = (0, moment_1.default)(a.end).subtract(a.amount, "hours"); const bStartTime = (0, moment_1.default)(b.end).subtract(b.amount, "hours"); return aStartTime.diff(bStartTime); }); const records = project.records; helper_1.LogHelper.info(`${project.name}`); helper_1.LogHelper.print(`--------------------------------------------------------------------------------`); helper_1.LogHelper.info(`TYPE\tAMOUNT\tTIME\t\t\tCOMMENT\t\t\t\tROLE`); helper_1.LogHelper.print(`--------------------------------------------------------------------------------`); let sumOfTime = 0; for (const record of records) { let line = ""; line += `${record.type}\t`; line += chalk_1.default.yellow.bold(`${record.amount.toFixed(2)}h\t`); line += `${(0, moment_1.default)(record.end).format("DD.MM.YYYY HH:mm:ss")}\t`; line += chalk_1.default.yellow.bold(`${(0, helper_1.toFixedLength)(record.message, 24)}\t`); line += chalk_1.default.yellow.bold(`${record.role}`); sumOfTime += record.amount; helper_1.LogHelper.print(line); } helper_1.LogHelper.print(`--------------------------------------------------------------------------------`); helper_1.LogHelper.info(`SUM:\t${sumOfTime}h`); }); } todayAction() { return __awaiter(this, void 0, void 0, function* () { const projects = yield this.fileHelper.findAllProjects(); const todaysRecords = projects.flatMap(project => { return project.records.filter(record => { const momentEnd = (0, moment_1.default)(record.end); const momentDayStart = (0, moment_1.default)().startOf('day'); const momentDayEnd = (0, moment_1.default)().endOf('day'); return momentEnd.isBetween(momentDayStart, momentDayEnd); }).map(record => { return { record, project }; }); }); // because sort mutates the array we cannot assign it to sortedTodaysRecords directly todaysRecords.sort((a, b) => { return (0, moment_1.default)(a.record.end).diff((0, moment_1.default)(b.record.end)); }); const sortedTodaysRecords = todaysRecords; helper_1.LogHelper.info(`${(0, moment_1.default)().format("dddd, MMMM D, YYYY")}`); helper_1.LogHelper.print(`-------------------------------------------------------------------------------------------------------`); helper_1.LogHelper.info(`TYPE\tAMOUNT\tTIME\t\tPROJECT\t\t\tCOMMENT\t\t\t\tROLE`); helper_1.LogHelper.print(`------------------