diff --git a/.gitignore b/.gitignore index 04c01ba..86e68cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules/ -dist/ \ No newline at end of file +dist/ +.scope/ +coverage/ \ No newline at end of file diff --git a/README.md b/README.md index 5e195fd..11ba764 100644 --- a/README.md +++ b/README.md @@ -17,14 +17,19 @@ npm i scope-tags -D - [x] Add support for multiple tsconfig.json's - [ ] Add tests for this ? - [x] `.scope` metadata initialization - - [ ] `tags.json` - - [ ] `database.json` + - [x] `tags.json` + - [x] `database.json` - [ ] Basic command line interface and tag management: - [ ] Reading `tags.json` - [ ] Adding, deleting, updating tags - [x] Basic file to module mapping - [ ] Tags should have (possibly nested) categories - [ ] Add [np package](https://www.npmjs.com/package/np) to handle publishing to npm + +### Assertions to add + +- [ ] On loading `tags.json` assert that all parents exist in database, if not then these modules won't be displayed + ### Nice to have - [ ] If eslint available, compare changed files before and after linting. Then, ommit files which only have these changes from scope report. diff --git a/jest.config.js b/jest.config.js index 48aee5e..8058bb2 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,5 +3,9 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', transformIgnorePatterns: ['/node_modules/'], - roots: ["/test/"] + roots: ["/test/", "/src/"], + collectCoverage: true, + collectCoverageFrom: ["**/src/**", "!**/node_modules/**"], + coverageDirectory: './coverage', + coverageReporters: ['json', 'lcovonly', 'text', 'clover'] }; \ No newline at end of file diff --git a/src/Console/Menu.ts b/src/Console/Menu.ts index 0c16387..984a28f 100644 --- a/src/Console/Menu.ts +++ b/src/Console/Menu.ts @@ -1,10 +1,11 @@ import { TagManager } from "./TagManager"; import { FileTagsDatabase } from "../Scope/FileTagsDatabase"; import { ConfigFile } from "../Scope/ConfigFile"; -import { TagsDefinitionFile } from "../Scope/TagsDefinitionFile"; +import { Module, TagsDefinitionFile } from "../Scope/TagsDefinitionFile"; import { ModuleManager } from "./ModuleManager"; const { Select } = require('enquirer') + export class Menu { private _config: ConfigFile; @@ -20,7 +21,7 @@ export class Menu { public async start() { const prompt = new Select({ name: 'Menu', - message: "Scope Tags ", + message: "Scope Tags", choices: [ { name: 'Start', value: this.start }, { name: 'Manage tags', value: this._manageTags }, @@ -41,11 +42,17 @@ export class Menu { } } - private async _manageTags() { + public async _manageTags() { const tagManager = new TagManager(this._tags, this); await tagManager.start(); } + public async manageTagsFromModule(module: Module) { + const tagManager = new TagManager(this._tags, this); + await tagManager.manageTagsFromModule(module); + } + + private async _manageModules() { const tagManager = new ModuleManager(this._tags, this); await tagManager.start(); diff --git a/src/Console/ModuleManager.ts b/src/Console/ModuleManager.ts index 7f7d464..c3ac293 100644 --- a/src/Console/ModuleManager.ts +++ b/src/Console/ModuleManager.ts @@ -24,16 +24,18 @@ export class ModuleManager { const prompt = new Select({ name: 'Module manager', - message: this._getHeaderFromModule(fromModule), + message: this._getModulePathAsHeader(fromModule), choices: [ ...choices, - { name: 'New module', value: this._addModule }, - { name: 'Add tag', value: this._addTagToModule }, - fromModule ? { - name: "Back to " + fromModule.name, value: this._goBack - } : { - name: "Main menu", value: this._exit - } + { role: 'separator' }, + { name: 'Add new module here', value: this._addModule }, + ...fromModule ? [ + { name: 'Manage tags of: ' + fromModule.name, value: this._manageTagsFromModule }, + { name: 'Delete this module', value: this._deleteModule }, + { name: "Back to: " + (fromModule.parent || "root"), value: this._goBack }, + ] : [ + { name: "Back to menu", value: this._exit } + ], ], result(value: any) { const mapped = this.map(value); @@ -75,11 +77,11 @@ export class ModuleManager { }, }); - const isExclusivePrompt = await new Confirm({ + const isExclusivePrompt = new Confirm({ name: "exclusive", message: 'Should the module be exclusive?\n(Meaning: only one tag per file can be added from this module)', question: '2 Should the module be exclusive?\n(Meaning: only one tag per file can be added from this module)', - }).run(); + }); try { @@ -105,6 +107,30 @@ export class ModuleManager { await this.start.call(this, fromModule); } + private async _deleteModule(fromModule: Module) { + let returnModule: string = fromModule.name; + + try { + this._tags.deleteModule(fromModule); + this._modulesWereModified = true; + returnModule = fromModule.parent || ""; + console.log(`Deleted module ${fromModule.name}`); + } catch (e) { + console.log((e as Error).message); + } + await this.start(this._tags.getModuleByName(returnModule)); + } + + private async _manageTagsFromModule(module?: Module) { + if (!module) { + console.log("Cannot manage tags of undefined module"); + return; + } + + await this._menu.manageTagsFromModule(module); + await this.start(module); + } + private async _goBack(fromModule?: Module) { if (!fromModule) { throw new Error("Can't go back to undefind module"); @@ -126,14 +152,18 @@ export class ModuleManager { return modules.map(module => ({ name: module.name, value: module })); } - private _getHeaderFromModule(module: Module): string { - if (!module.parent) { - return `${module.name}`; + private _getModulePathAsHeader(module?: Module): string { + let header = "Root >"; + + if (!module) { + return header; } - let header = ""; - const moduleParents = this._tags.getModuleParents(module); - moduleParents.forEach(module => header += `${module.name} > `); - header += module.name; + + const parentList = this._tags.getModuleParentNames(module); + + parentList.push(module.name); + parentList.forEach(parent => header += ` ${parent} >`); + return header; } } \ No newline at end of file diff --git a/src/Console/TagManager.ts b/src/Console/TagManager.ts index 7fec53c..3ad24f9 100644 --- a/src/Console/TagManager.ts +++ b/src/Console/TagManager.ts @@ -1,4 +1,4 @@ -import { TagsDefinitionFile } from "../Scope/TagsDefinitionFile"; +import { Module, TagsDefinitionFile } from "../Scope/TagsDefinitionFile"; import { Menu } from "./Menu"; const { Select, Toggle } = require('enquirer') @@ -20,8 +20,8 @@ export class TagManager { name: 'Tag manager', message: 'Manage tags', choices: [ - { name: 'By name', value: this._manageTagsByName }, - { name: 'By module', value: this._manageTagsByModule }, + // { name: 'By name', value: this._manageTagsByName }, + // { name: 'By module', value: this._manageTagsByModule }, { name: "Main menu", value: this._exit } ], result(value: any) { @@ -34,6 +34,11 @@ export class TagManager { await answer.call(this); } + public manageTagsFromModule(module: Module) { + console.log("Tags of module: " + module.name); + + } + private async _exit() { if (this._tagsWereModified) { this._tags.save(); diff --git a/src/Scope/TagsDefinitionFile.ts b/src/Scope/TagsDefinitionFile.ts index 0482e32..3a4f66a 100644 --- a/src/Scope/TagsDefinitionFile.ts +++ b/src/Scope/TagsDefinitionFile.ts @@ -84,6 +84,33 @@ export class TagsDefinitionFile implements IJSONFileDatabase this._tagsDatabaseData.modules.push(newModule); } + public deleteModule(moduleToDelete: Module) { + if (!moduleToDelete) { + throw new Error("Can't remove undefined module"); + } + if (moduleToDelete.tags.length) { + const tags = moduleToDelete.tags.map(tag => tag.name); + throw new Error(` + Can't remove module ${moduleToDelete.name} which has tags: ${tags.join(", ")}` + ); + } + if (moduleToDelete.children.length) { + const children = moduleToDelete.children.join(", "); + throw new Error(` + Can't remove module ${moduleToDelete.name} with child modules: ${children}` + ); + } + const moduleToDeleteIndex = this._tagsDatabaseData.modules.indexOf(moduleToDelete); + if (moduleToDeleteIndex === -1) { + throw new Error(`Module ${moduleToDelete.name} not found in database`); + } + + const parentModule = this.getModuleByName(moduleToDelete.parent); + if (parentModule) { + parentModule.children.splice(parentModule.children.indexOf(moduleToDelete.name), 1); + } + this._tagsDatabaseData.modules.splice(moduleToDeleteIndex, 1); + } public getTags(): Set { return this._allTags; @@ -111,10 +138,10 @@ export class TagsDefinitionFile implements IJSONFileDatabase return this._tagsDatabaseData.modules.filter(module => children.includes(module.name)); } - public getModuleParents(module: Module) { + public getModuleParentNames(module: Module): Array { const parents = []; - while (!module.parent) { - parents.push(module); + while (module.parent) { + parents.push(module.parent); module = this.getModuleByName(module.parent) || {} as Module; } return parents; @@ -127,7 +154,6 @@ export class TagsDefinitionFile implements IJSONFileDatabase description: "Default tag added on initialization", module: TagsDefinitionFile.getDefaultModule().name, }; - return defaultTag; } @@ -140,7 +166,6 @@ export class TagsDefinitionFile implements IJSONFileDatabase parent: null, children: [], } - return defaultModule; } } \ No newline at end of file diff --git a/src/scope.ts b/src/scope.ts index e328c5d..21cc5a8 100755 --- a/src/scope.ts +++ b/src/scope.ts @@ -9,7 +9,6 @@ import { YesNoMenu } from "./Console/YesNoMenu"; // Will be needed to get output from script const [, , ...args] = process.argv; -console.log("scope tags v0.0.2 " + args); // Find git repository const root: string = getGitProjectRoot();