From 25510f04fa3ad0ae4e4ee724a140520db47bd863 Mon Sep 17 00:00:00 2001 From: Lau Josefsen Date: Fri, 22 Sep 2023 13:45:10 +0200 Subject: [PATCH] Needs testing --- bin/cmds/toggle.ts | 10 ++- src/config_loader.ts | 36 +++++--- src/disable_projects.ts | 108 ----------------------- src/toggle_projects.ts | 90 +++++++++++++++++++ tests/disable_projects.test.ts | 152 ++++++++++++++++----------------- tests/toggle_project_test.ts | 77 +++++++++++++++++ 6 files changed, 273 insertions(+), 200 deletions(-) delete mode 100644 src/disable_projects.ts create mode 100644 src/toggle_projects.ts create mode 100644 tests/toggle_project_test.ts diff --git a/bin/cmds/toggle.ts b/bin/cmds/toggle.ts index ed909b3..a66ff6a 100644 --- a/bin/cmds/toggle.ts +++ b/bin/cmds/toggle.ts @@ -1,7 +1,9 @@ import { loadConfig } from "../../src/config_loader"; import { Argv } from "yargs"; import { errorHandler } from "../../src/error_handler"; -import { resetDisabledProjects, logDisabledProjects, toggleProjectDisable } from "../../src/disable_projects"; +import { + logProjectStatus, resetToggledProjects, toggleProject +} from "../../src/toggle_projects"; import { tabCompleteToggle } from "../../src/tab_completion"; // noinspection JSUnusedGlobalSymbols @@ -19,14 +21,14 @@ export async function handler(argv: any) { switch (argv.project) { case "status": // give a status of disabled projects - logDisabledProjects(config); + logProjectStatus(config); break; case "reset": // set disabled projects to projects which defaultDisabled - resetDisabledProjects(config); + resetToggledProjects(config); break; default: - toggleProjectDisable(config, argv.project); + toggleProject(config, argv.project); } } catch (e) { errorHandler(e); diff --git a/src/config_loader.ts b/src/config_loader.ts index 067bca1..ac93c77 100644 --- a/src/config_loader.ts +++ b/src/config_loader.ts @@ -1,4 +1,4 @@ -import { Config } from "./types/config"; +import {Config, Project} from "./types/config"; import * as utils from "./utils"; import path from "path"; import assert, { AssertionError } from "assert"; @@ -7,16 +7,15 @@ import { validateYaml } from "./validate_yaml"; import fs from "fs-extra"; import yaml from "js-yaml"; import * as _ from "lodash"; -import { getDisabledProjects, getPreviouslySeenProjectsFromCache } from "./disable_projects"; import { Cache } from "./cache"; import { createActionGraphs } from "./graph"; +import {getToggledProjects} from "./toggle_projects"; export async function loadConfig(cwd: string, needs = true, shouldDisableProjects = true): Promise { const cnfPath = path.join(cwd, `.gitte.yml`); const dotenvPath = path.join(cwd, `.gitte-env`); const overridePath = path.join(cwd, ".gitte-override.yml"); const cachePath = path.join(cwd, ".gitte-cache.json"); - const projectsDisablePath = path.join(cwd, ".gitte-projects-disable"); let fileContent; @@ -60,8 +59,6 @@ export async function loadConfig(cwd: string, needs = true, shouldDisableProject yml = _.merge(yml, overrideYml); } - const seenProjects = getPreviouslySeenProjectsFromCache(cachePath); - // Write .gitte-cache.json const cache: Cache = { version: 1, @@ -70,19 +67,34 @@ export async function loadConfig(cwd: string, needs = true, shouldDisableProject }; fs.writeJsonSync(cachePath, cache, { spaces: 4 }); - const disabledProjects = getDisabledProjects(seenProjects, projectsDisablePath, yml); - if (shouldDisableProjects) { - disabledProjects.forEach((projectName) => { - _.unset(yml.projects, projectName); + assert(validateYaml(yml), "Invalid .gitte.yml file"); + + const toggledProjects = getToggledProjects({...yml, cwd}); + + const disabledProjects = shouldDisableProjects ? Object.entries(yml.projects).reduce((acc, [projectName, project]) => { + const toggledState: boolean | undefined = toggledProjects[projectName]; + + if ((project.defaultDisabled && toggledProjects[projectName] !== true) || toggledProjects[projectName] === false) { + acc.push(projectName); + } + return acc; + }, [] as string[]) : []; + + + // Unset default disabled projects unless they are toggled + if(shouldDisableProjects) { + Object.entries(yml.projects).forEach(([projectName, project]) => { + if (disabledProjects.includes(projectName)) { + _.unset(yml.projects, projectName); + } }); } - assert(validateYaml(yml), "Invalid .gitte.yml file"); - // For any action, replace needs with an empty array if undefined. Object.entries(yml.projects).forEach(([, project]) => { Object.entries(project.actions).forEach(([, action]) => { - action.needs = action.needs?.filter((need) => !disabledProjects.includes(need)) ?? []; + action.needs = action.needs || []; + action.needs = action.needs.filter((need) => !disabledProjects.includes(need)); action.priority = action.priority || null; action.searchFor = action.searchFor || []; }); diff --git a/src/disable_projects.ts b/src/disable_projects.ts deleted file mode 100644 index 0aaef5e..0000000 --- a/src/disable_projects.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { Config } from "./types/config"; -import path from "path"; -import assert from "assert"; -import fs from "fs-extra"; -import { loadCachePath } from "./cache"; -import chalk from "chalk"; -import { printHeader } from "./utils"; - -export function getDisabledProjects(seenProjects: string[], projectsDisablePath: string, cfg: Config): string[] { - // Load .gitte-projects-disable - if (!fs.pathExistsSync(projectsDisablePath)) { - fs.writeFileSync(projectsDisablePath, "", "utf8"); - } - - const projectsDisabled: string[] = fs - .readFileSync(projectsDisablePath, "utf8") - .toString() - .split("\n") - .filter((x) => x.length > 0); - - // Disable projects that were not previously seen and have "defaultDisabled" set to true - Object.entries(cfg.projects).forEach(([projectName, project]) => { - if (project.defaultDisabled && !seenProjects.includes(projectName) && !projectsDisabled.includes(projectName)) { - projectsDisabled.push(projectName); - } - }); - - // Write .gitte-projects-disable - fs.writeFileSync(projectsDisablePath, projectsDisabled.join("\n"), "utf8"); - - return projectsDisabled; -} - -export function getPreviouslySeenProjectsFromCache(cachePath: string): string[] { - const cache = loadCachePath(cachePath); - if (cache !== null) { - return cache.seenProjects; - } - return []; -} - -export function logDisabledProjects(cfg: Config): void { - const cwd = cfg.cwd; - const cachePath = path.join(cwd, ".gitte-cache.json"); - const projectsDisablePath = path.join(cwd, ".gitte-projects-disable"); - - const seenProjects = getPreviouslySeenProjectsFromCache(cachePath); - const projectsDisabled = getDisabledProjects(seenProjects, projectsDisablePath, cfg); - - Object.keys(cfg.projects).forEach((projectName) => { - if (projectsDisabled.includes(projectName)) { - console.log(chalk`{bold ${projectName}:} {red disabled}`); - } else { - console.log(chalk`{bold ${projectName}:} {green enabled}`); - } - }); -} - -export function resetDisabledProjects(cfg: Config): void { - // overwrite .gitte-projects-disable with defaultDisabled projects - const cwd = cfg.cwd; - const projectsDisablePath = path.join(cwd, ".gitte-projects-disable"); - - const defaultDisabledProjects = Object.entries(cfg.projects).reduce((carry, [projectName, project]) => { - if (project.defaultDisabled) { - return [...carry, projectName]; - } - return carry; - }, [] as string[]); - - fs.writeFileSync(projectsDisablePath, defaultDisabledProjects.join("\n"), "utf8"); - - printHeader("Disabled projects have been cleaned.", "SUCCESS"); - - logDisabledProjects(cfg); -} - -export function toggleProjectDisable(cfg: Config, projectName: string): void { - const cwd = cfg.cwd; - const cachePath = path.join(cwd, ".gitte-cache.json"); - const projectsDisablePath = path.join(cwd, ".gitte-projects-disable"); - - // assert the project exists in config - assert( - cfg.projects[projectName], - `Project "${projectName}" does not exist. (See "gitte list" to see available projects.)`, - ); - - const seenProjects = getPreviouslySeenProjectsFromCache(cachePath); - const projectsDisabled = getDisabledProjects(seenProjects, projectsDisablePath, cfg); - - let enabled = true; - - if (projectsDisabled.includes(projectName)) { - projectsDisabled.splice(projectsDisabled.indexOf(projectName), 1); - } else { - projectsDisabled.push(projectName); - enabled = false; - } - - fs.writeFileSync(projectsDisablePath, projectsDisabled.join("\n"), "utf8"); - - if (enabled) { - printHeader(`${projectName} has been enabled.`, "SUCCESS"); - } else { - printHeader(`${projectName} has been disabled.`, "SUCCESS"); - } -} diff --git a/src/toggle_projects.ts b/src/toggle_projects.ts new file mode 100644 index 0000000..bb95095 --- /dev/null +++ b/src/toggle_projects.ts @@ -0,0 +1,90 @@ +import {Config} from "./types/config"; +import path from "path"; +import assert from "assert"; +import fs from "fs-extra"; +import chalk from "chalk"; +import {printHeader} from "./utils"; + +const projectsToggleFileName = ".gitte-projects-toggled" + +export function getToggledProjects(cfg: Config): {[key: string]: boolean} { + const toggledProjectsFilePath = path.join(cfg.cwd, projectsToggleFileName); + + if (!fs.pathExistsSync(toggledProjectsFilePath)) { + fs.writeFileSync(toggledProjectsFilePath, "", "utf8"); + } + + return fs.readFileSync(toggledProjectsFilePath, "utf8").toString() + .split("\n") + .filter((x) => x.length > 0) + .map((x) => x.split(":")) + .reduce((carry, [projectName, enabled]) => { + carry[projectName] = enabled === "true"; + return carry; + }, {} as { [key: string]: boolean }); +} + +export function logProjectStatus(cfg: Config): void { + const toggledProjects = getToggledProjects(cfg); + + Object.entries(cfg.projects).forEach(([projectName, project]) => { + let enabled = !project.defaultDisabled + if(toggledProjects[projectName]) { + enabled = toggledProjects[projectName] + } + + if (enabled) { + console.log(chalk`{bold ${projectName}:} {green enabled}`); + } else { + console.log(chalk`{bold ${projectName}:} {red disabled}`); + } + }); +} + +export function resetToggledProjects(cfg: Config): void { + const toggledProjectsFilePath = path.join(cfg.cwd, projectsToggleFileName); + fs.writeFileSync(toggledProjectsFilePath, "", "utf8"); + + printHeader("Toggled projects have been cleaned.", "SUCCESS"); + + logProjectStatus(cfg); +} + +function saveToggledProjects(cfg: Config, toggledProjects: {[key: string]: boolean}): void { + const toggledProjectsFilePath = path.join(cfg.cwd, projectsToggleFileName); + fs.writeFileSync(toggledProjectsFilePath, Object.entries(toggledProjects) + .map(([projectName, enabled]) => `${projectName}:${enabled}`) + .join("\n"), "utf8"); +} + +export function toggleProject(cfg: Config, projectName: string): void { + + // assert the project exists in config + assert( + cfg.projects[projectName], + `Project "${projectName}" does not exist. (See "gitte list" to see available projects.)`, + ); + + const customToggles = getToggledProjects(cfg); + const defaultState = !cfg.projects[projectName].defaultDisabled; + const currentState = customToggles[projectName] ?? defaultState; + const desiredState = !currentState; + + if(desiredState === defaultState) { + // Delete the project from the custom toggles + delete customToggles[projectName]; + } + else { + // Add or change the project in the custom toggles + customToggles[projectName] = desiredState; + } + + // Write the custom toggles to file + saveToggledProjects(cfg, customToggles); + + if (desiredState) { + printHeader(`${projectName} has been enabled.`, "SUCCESS"); + } else { + printHeader(`${projectName} has been disabled.`, "SUCCESS"); + } +} diff --git a/tests/disable_projects.test.ts b/tests/disable_projects.test.ts index 3025083..6778dfd 100644 --- a/tests/disable_projects.test.ts +++ b/tests/disable_projects.test.ts @@ -1,76 +1,76 @@ -import fs from "fs-extra"; -import { when } from "jest-when"; -import { cnfStub, cwdStub } from "./utils/stubs"; -import _ from "lodash"; -import { - getPreviouslySeenProjectsFromCache, - resetDisabledProjects, - toggleProjectDisable, -} from "../src/disable_projects"; - -beforeEach(() => { - fs.pathExistsSync = jest.fn(); - fs.readFileSync = jest.fn(); - fs.writeFileSync = jest.fn(); - fs.readJsonSync = jest.fn(); - console.log = jest.fn(); - console.error = jest.fn(); -}); - -describe("getPreviouslySeenProjectsFromCache", () => { - test("finds projects from cache", () => { - const config = _.cloneDeep(cnfStub); - config.projects["projecta"].defaultDisabled = true; - - when(fs.pathExistsSync).calledWith(`${cwdStub}/.gitte-cache.json`).mockReturnValue(true); - when(fs.readJsonSync) - .calledWith(`${cwdStub}/.gitte-cache.json`) - .mockReturnValue({ - version: 1, - seenProjects: ["projecta"], - config: cnfStub, - }); - - const projectNames = getPreviouslySeenProjectsFromCache(`${cwdStub}/.gitte-cache.json`); - - expect(projectNames).toEqual(["projecta"]); - }); -}); - -describe("resetDisabledProjects", () => { - test("resets disabled projects", async () => { - const config = _.cloneDeep(cnfStub); - config.projects["projecta"].defaultDisabled = true; - - when(fs.readFileSync).calledWith(`${cwdStub}/.gitte-projects-disable`, "utf8").mockReturnValue(`projectd`); - when(fs.pathExistsSync).calledWith(`${cwdStub}/.gitte-projects-disable`).mockReturnValue(true); - - resetDisabledProjects(config); - - expect(fs.writeFileSync).toHaveBeenNthCalledWith(1, `${cwdStub}/.gitte-projects-disable`, `projecta`, "utf8"); - }); -}); - -describe("toggleProject", () => { - test("enable a project", () => { - const config = _.cloneDeep(cnfStub); - - when(fs.readFileSync).calledWith(`${cwdStub}/.gitte-projects-disable`, "utf8").mockReturnValue(`projecta`); - when(fs.pathExistsSync).calledWith(`${cwdStub}/.gitte-projects-disable`).mockReturnValue(true); - - toggleProjectDisable(config, "projecta"); - - expect(fs.writeFileSync).toHaveBeenLastCalledWith(`${cwdStub}/.gitte-projects-disable`, ``, "utf8"); - }); - - test("disable a project", () => { - const config = _.cloneDeep(cnfStub); - - when(fs.readFileSync).calledWith(`${cwdStub}/.gitte-projects-disable`, "utf8").mockReturnValue(``); - when(fs.pathExistsSync).calledWith(`${cwdStub}/.gitte-projects-disable`).mockReturnValue(true); - - toggleProjectDisable(config, "projecta"); - - expect(fs.writeFileSync).toHaveBeenLastCalledWith(`${cwdStub}/.gitte-projects-disable`, `projecta`, "utf8"); - }); -}); +// import fs from "fs-extra"; +// import { when } from "jest-when"; +// import { cnfStub, cwdStub } from "./utils/stubs"; +// import _ from "lodash"; +// import { +// getPreviouslySeenProjectsFromCache, +// resetDisabledProjects, +// toggleProjectDisable, +// } from "../src/disable_projects"; +// +// beforeEach(() => { +// fs.pathExistsSync = jest.fn(); +// fs.readFileSync = jest.fn(); +// fs.writeFileSync = jest.fn(); +// fs.readJsonSync = jest.fn(); +// console.log = jest.fn(); +// console.error = jest.fn(); +// }); +// +// describe("getPreviouslySeenProjectsFromCache", () => { +// test("finds projects from cache", () => { +// const config = _.cloneDeep(cnfStub); +// config.projects["projecta"].defaultDisabled = true; +// +// when(fs.pathExistsSync).calledWith(`${cwdStub}/.gitte-cache.json`).mockReturnValue(true); +// when(fs.readJsonSync) +// .calledWith(`${cwdStub}/.gitte-cache.json`) +// .mockReturnValue({ +// version: 1, +// seenProjects: ["projecta"], +// config: cnfStub, +// }); +// +// const projectNames = getPreviouslySeenProjectsFromCache(`${cwdStub}/.gitte-cache.json`); +// +// expect(projectNames).toEqual(["projecta"]); +// }); +// }); +// +// describe("resetDisabledProjects", () => { +// test("resets disabled projects", async () => { +// const config = _.cloneDeep(cnfStub); +// config.projects["projecta"].defaultDisabled = true; +// +// when(fs.readFileSync).calledWith(`${cwdStub}/.gitte-projects-disable`, "utf8").mockReturnValue(`projectd`); +// when(fs.pathExistsSync).calledWith(`${cwdStub}/.gitte-projects-disable`).mockReturnValue(true); +// +// resetDisabledProjects(config); +// +// expect(fs.writeFileSync).toHaveBeenNthCalledWith(1, `${cwdStub}/.gitte-projects-disable`, `projecta`, "utf8"); +// }); +// }); +// +// describe("toggleProject", () => { +// test("enable a project", () => { +// const config = _.cloneDeep(cnfStub); +// +// when(fs.readFileSync).calledWith(`${cwdStub}/.gitte-projects-disable`, "utf8").mockReturnValue(`projecta`); +// when(fs.pathExistsSync).calledWith(`${cwdStub}/.gitte-projects-disable`).mockReturnValue(true); +// +// toggleProjectDisable(config, "projecta"); +// +// expect(fs.writeFileSync).toHaveBeenLastCalledWith(`${cwdStub}/.gitte-projects-disable`, ``, "utf8"); +// }); +// +// test("disable a project", () => { +// const config = _.cloneDeep(cnfStub); +// +// when(fs.readFileSync).calledWith(`${cwdStub}/.gitte-projects-disable`, "utf8").mockReturnValue(``); +// when(fs.pathExistsSync).calledWith(`${cwdStub}/.gitte-projects-disable`).mockReturnValue(true); +// +// toggleProjectDisable(config, "projecta"); +// +// expect(fs.writeFileSync).toHaveBeenLastCalledWith(`${cwdStub}/.gitte-projects-disable`, `projecta`, "utf8"); +// }); +// }); diff --git a/tests/toggle_project_test.ts b/tests/toggle_project_test.ts new file mode 100644 index 0000000..5f2de9d --- /dev/null +++ b/tests/toggle_project_test.ts @@ -0,0 +1,77 @@ +import fs from "fs-extra"; +import { when } from "jest-when"; +import { cnfStub, cwdStub } from "./utils/stubs"; +import _ from "lodash"; +import { + getToggledProjects, + logProjectStatus, + resetToggledProjects, + toggleProject, +} from "../src/toggle_projects"; + +beforeEach(() => { + fs.pathExistsSync = jest.fn(); + fs.readFileSync = jest.fn(); + fs.writeFileSync = jest.fn(); + fs.readJsonSync = jest.fn(); + console.log = jest.fn(); + console.error = jest.fn(); +}); + +describe("getPreviouslySeenProjectsFromCache", () => { + test("finds projects from cache", () => { + const config = _.cloneDeep(cnfStub); + config.projects["projecta"].defaultDisabled = true; + + when(fs.pathExistsSync).calledWith(`${cwdStub}/.gitte-cache.json`).mockReturnValue(true); + when(fs.readJsonSync) + .calledWith(`${cwdStub}/.gitte-cache.json`) + .mockReturnValue({ + version: 1, + seenProjects: ["projecta"], + config: cnfStub, + }); + + const projectNames = getPreviouslySeenProjectsFromCache(`${cwdStub}/.gitte-cache.json`); + + expect(projectNames).toEqual(["projecta"]); + }); +}); + +describe("resetDisabledProjects", () => { + test("resets disabled projects", async () => { + const config = _.cloneDeep(cnfStub); + config.projects["projecta"].defaultDisabled = true; + + when(fs.readFileSync).calledWith(`${cwdStub}/.gitte-projects-disable`, "utf8").mockReturnValue(`projectd`); + when(fs.pathExistsSync).calledWith(`${cwdStub}/.gitte-projects-disable`).mockReturnValue(true); + + resetDisabledProjects(config); + + expect(fs.writeFileSync).toHaveBeenNthCalledWith(1, `${cwdStub}/.gitte-projects-disable`, `projecta`, "utf8"); + }); +}); + +describe("toggleProject", () => { + test("enable a project", () => { + const config = _.cloneDeep(cnfStub); + + when(fs.readFileSync).calledWith(`${cwdStub}/.gitte-projects-disable`, "utf8").mockReturnValue(`projecta`); + when(fs.pathExistsSync).calledWith(`${cwdStub}/.gitte-projects-disable`).mockReturnValue(true); + + toggleProject(config, "projecta"); + + expect(fs.writeFileSync).toHaveBeenLastCalledWith(`${cwdStub}/.gitte-projects-disable`, ``, "utf8"); + }); + + test("disable a project", () => { + const config = _.cloneDeep(cnfStub); + + when(fs.readFileSync).calledWith(`${cwdStub}/.gitte-projects-disable`, "utf8").mockReturnValue(``); + when(fs.pathExistsSync).calledWith(`${cwdStub}/.gitte-projects-disable`).mockReturnValue(true); + + toggleProject(config, "projecta"); + + expect(fs.writeFileSync).toHaveBeenLastCalledWith(`${cwdStub}/.gitte-projects-disable`, `projecta`, "utf8"); + }); +});