diff --git a/bin/index.js b/bin/index.js index fd9525c..9731533 100755 --- a/bin/index.js +++ b/bin/index.js @@ -6,13 +6,7 @@ const cli = require("cac")(); const chalk = require("chalk"); const clear = require("clear"); -const { rootLogger } = require("ts-jest"); -const { - configuration, - Install, - Check, - Update, -} = require("../dist/main.umd.js"); +const { Install, Check, Update } = require("../dist/main.umd.js"); const { version } = require("../package.json"); clear(); @@ -29,11 +23,7 @@ cli "The root folder of the project, the one where the documentation folder is or will be located." ) .action((options) => { - const install = new Install({ - ...options, - ...configuration, - packageVersion: version, - }); + const install = new Install(options, version); install.run(); }); @@ -47,11 +37,7 @@ cli "The root folder of the project, the one where the documentation folder is located." ) .action((options) => { - const check = new Check({ - ...options, - ...configuration, - packageVersion: version, - }); + const check = new Check(options, version); check.run(); }); @@ -65,14 +51,10 @@ cli "The root folder of the project, the one where the documentation folder is located." ) .action((options) => { - const update = new Update({ - ...options, - ...configuration, - packageVersion: version, - }); + const update = new Update(options, version); update.run(); }); cli.help(); -cli.version(require("../package.json").version); +cli.version(version); cli.parse(); diff --git a/src/__tests__/check.spec.ts b/src/__tests__/check.spec.ts index cdd3003..972f93b 100644 --- a/src/__tests__/check.spec.ts +++ b/src/__tests__/check.spec.ts @@ -41,12 +41,9 @@ describe("deven-cli", () => { lazy: false, }), }); - check = new Check({ - basePath: "fake_test_folder", - ...configuration, - moduleBasePath: path.join(".", "src"), - packageVersion: "1.0.0", - }); + check = new Check({ basePath: "fake_test_folder" }, "1.0.0"); + // installation path during tests is relative to the uncompiled source files + check.ownInstallationBasePath = path.join(__dirname, "../.."); }); describe("check", () => { diff --git a/src/__tests__/command.spec.ts b/src/__tests__/command.spec.ts index 76c3c8f..a0f22bb 100644 --- a/src/__tests__/command.spec.ts +++ b/src/__tests__/command.spec.ts @@ -1,7 +1,7 @@ import * as path from "path"; import * as fs from "fs-extra"; import { configuration } from "../shared/configuration"; -import { Command } from "../commands/command"; +import { BaseCommand } from "../commands/command"; import mockFs from "mock-fs"; import { mockProcessExit, @@ -15,7 +15,7 @@ let mockStdout: jest.SpyInstance; let mockStderr: jest.SpyInstance; let mockLog: jest.SpyInstance; -let command: Command; +let command: BaseCommand; describe("deven-cli", () => { afterEach(() => { mockFs.restore(); @@ -38,12 +38,14 @@ describe("deven-cli", () => { lazy: false, }), }); - command = new Command({ - basePath: "fake_test_folder", - ...configuration, - moduleBasePath: path.join(".", "src"), - packageVersion: "1.0.0", - }); + command = new BaseCommand( + { + basePath: "fake_test_folder", + }, + "1.0.0" + ); + // installation path during tests is relative to the uncompiled source files + command.ownInstallationBasePath = path.join(__dirname, "../.."); }); describe("command", () => { @@ -78,32 +80,32 @@ describe("deven-cli", () => { ); }); it("return false when the config file doesn't exist", async () => { - expect(command.existsConfigFile).toBeFalsy(); + expect(command.existsConfigFile()).toBeFalsy(); }); it("return true when the config file exists", async () => { fs.writeFileSync(command.configFilePath, ""); - expect(command.existsConfigFile).toBeTruthy(); + expect(command.existsConfigFile()).toBeTruthy(); }); it("return false when the outdated doc folder doesn't exist", async () => { - expect(command.existsOutdatedDocFolder).toBeFalsy(); + expect(command.existsOutdatedDocFolder()).toBeFalsy(); }); it("return true when the outdated doc folder exists", async () => { fs.mkdirSync(command.outdatedDocPath); - expect(command.existsOutdatedDocFolder).toBeTruthy(); + expect(command.existsOutdatedDocFolder()).toBeTruthy(); }); it("return the list of all the available chapters (except the readme)", async () => { - expect(command.docsSourceFiles.length).toBe(8); + expect(command.findDocsSourceFiles().length).toBe(8); }); it("return the list of all local chapters (except the readme)", async () => { fs.copySync(command.docsSourcePath, command.docsPath); - expect(command.docsFiles.length).toBe(8); + expect(command.findDocsFiles().length).toBe(8); }); it("return the list of all the chapters", async () => { fs.copySync(command.docsSourcePath, command.docsPath); - expect(command.coverage).toBe(100); + expect(command.coverage()).toBe(100); fs.rmSync(path.join(command.docsPath, "CONTRIBUTE.md")); fs.rmSync(path.join(command.docsPath, "TESTING.md")); - expect(command.coverage).toBe(75); + expect(command.coverage()).toBe(75); }); }); }); diff --git a/src/__tests__/install.spec.ts b/src/__tests__/install.spec.ts index 073709b..6eaa4e1 100644 --- a/src/__tests__/install.spec.ts +++ b/src/__tests__/install.spec.ts @@ -44,12 +44,14 @@ describe("deven-cli", () => { }), }); enquirer.prompt = jest.fn().mockResolvedValue({ confirm: true }); - install = new Install({ - basePath: "fake_test_folder", - ...configuration, - moduleBasePath: path.join(".", "src"), - packageVersion: "1.0.0", - }); + install = new Install( + { + basePath: "fake_test_folder", + }, + "1.0.0" + ); + // installation path during tests is relative to the uncompiled source files + install.ownInstallationBasePath = path.join(__dirname, "../.."); }); describe("install", () => { diff --git a/src/__tests__/update.spec.ts b/src/__tests__/update.spec.ts index a2d870b..3885077 100644 --- a/src/__tests__/update.spec.ts +++ b/src/__tests__/update.spec.ts @@ -41,12 +41,14 @@ describe("deven-cli", () => { lazy: false, }), }); - update = new Update({ - basePath: "fake_test_folder", - ...configuration, - moduleBasePath: path.join(".", "src"), - packageVersion: "1.0.0", - }); + update = new Update( + { + basePath: "fake_test_folder", + }, + "1.0.0" + ); + // installation path during tests is relative to the uncompiled source files + update.ownInstallationBasePath = path.join(__dirname, "../.."); }); describe("update", () => { @@ -175,13 +177,17 @@ describe("deven-cli", () => { fs.writeFileSync(update.readmePath, ""); fs.writeFileSync(update.configFilePath, ""); update.updateChapters(); - expect(update.docsFiles.length).toBe(update.docsSourceFiles.length); + expect(update.findDocsFiles().length).toBe( + update.findDocsSourceFiles().length + ); }); it("creates the docs folder and copies the missing files into it", async () => { fs.writeFileSync(update.readmePath, ""); fs.writeFileSync(update.configFilePath, ""); update.updateChapters(); - expect(update.docsFiles.length).toBe(update.docsSourceFiles.length); + expect(update.findDocsFiles().length).toBe( + update.findDocsSourceFiles().length + ); }); it("updates the version in the config file ", async () => { diff --git a/src/commands/check.ts b/src/commands/check.ts index 6d2b956..b86c160 100644 --- a/src/commands/check.ts +++ b/src/commands/check.ts @@ -3,32 +3,32 @@ import Table from "cli-table3"; import { logger } from "../Logger"; import { messages } from "../shared/messages"; -import { Command } from "./command"; +import { BaseCommand, ExecutableCommand } from "./command"; -export class Check extends Command { +export class Check extends BaseCommand implements ExecutableCommand { public preliminaryCheck(): void { - if (this.existsConfigFile) { + if (this.existsConfigFile()) { logger.info(messages.check.checkConfigExists); } else { logger.error(messages.check.checkConfigNotExists); process.exit(1); } - if (this.existsDocsFolder) { + if (this.existsDocsFolder()) { logger.info(messages.check.checkFolderExist); } else { logger.error(messages.check.checkFolderNotExist); process.exit(1); } - if (this.existsOutdatedDocFolder) { + if (this.existsOutdatedDocFolder()) { logger.info(messages.check.checkOutdatedFolderNotExist); } else { logger.error(messages.check.checkOutdatedFolderExist); process.exit(1); } - if (this.existsReadme) { + if (this.existsReadme()) { logger.info(messages.check.readmeExists); } else { logger.error(messages.check.checkFolderNotExist); @@ -42,15 +42,15 @@ export class Check extends Command { style: { head: ["cyan"] }, head: [ `Files (vers. ${this.packageVersion})`, - `Project (${Math.round(this.coverage)}% file coverage)`, + `Project (${Math.round(this.coverage())}% file coverage)`, ], colWidths: [40], }); - this.docsSourceFiles.forEach((f) => { + this.findDocsSourceFiles().forEach((f) => { table.push([ f.replace(".md", ""), - this.docsFiles.includes(f) + this.findDocsFiles().includes(f) ? chalk.green(`Found`) : chalk.red("Not found"), ]); diff --git a/src/commands/command.ts b/src/commands/command.ts index ff16053..43a0b76 100644 --- a/src/commands/command.ts +++ b/src/commands/command.ts @@ -12,26 +12,49 @@ export type Config = { packageVersion: string; }; -export class Command { +export interface ExecutableCommand< + CommandParams extends BaseCliParams = BaseCliParams +> extends BaseCommand { + preliminaryCheck(): void; + run(): void; +} + +export type BaseCliParams = { + basePath: string | undefined; +}; + +export class BaseCommand { + // __dirname is equivalent to the installation path of dist/main.umd.js. + // This way the commands can be executed locally as well with "npm run self:install/check/update". + // Exposed for overwriting in tests, since the behaviour in tests is different. + public ownInstallationBasePath = path.join(__dirname, ".."); + + static rootSourceFolder = "src/root"; + static docsSourceFolder = "src/docs"; + + static docsFolderName = "docs"; + static outdatedDocFolderName = "doc"; + static docsBackupFolderName = "_docs_backup_please_rename"; + static configFilename = "deven-skeleton-install.config.json"; + private basePath: string; - private docsFolderName: string; - private outdatedDocFolderName: string; - private docsBackupFolderName: string; - private moduleBasePath: string; - private configFilename: string; - private rootFolderName: string; public packageVersion: string; + constructor(params: CliParams, packageVersion: string) { + this.basePath = params.basePath || path.normalize("."); + this.packageVersion = packageVersion; + } + public getDocsFilePath(filename: string): string { return path.join(this.docsPath, filename); } get docsPath(): string { - return path.join(this.basePath, this.docsFolderName); + return path.join(this.basePath, BaseCommand.docsFolderName); } get outdatedDocPath(): string { - return path.join(this.basePath, this.outdatedDocFolderName); + return path.join(this.basePath, BaseCommand.outdatedDocFolderName); } get readmePath(): string { @@ -39,7 +62,7 @@ export class Command { } get docsBackupPath(): string { - return path.join(this.basePath, this.docsBackupFolderName); + return path.join(this.basePath, BaseCommand.docsBackupFolderName); } get readmeBackupPath(): string { @@ -47,72 +70,68 @@ export class Command { } get docsSourcePath(): string { - return path.join(this.moduleBasePath, this.docsFolderName); + return path.join( + this.ownInstallationBasePath, + BaseCommand.docsSourceFolder + ); } get readmeSourcePath(): string { - return path.join(this.moduleBasePath, this.rootFolderName, "README.md"); + return path.join( + this.ownInstallationBasePath, + BaseCommand.rootSourceFolder, + "README.md" + ); } get configFileSourcePath(): string { return path.join( - this.moduleBasePath, - this.rootFolderName, - this.configFilename + this.ownInstallationBasePath, + BaseCommand.rootSourceFolder, + BaseCommand.configFilename ); } get configFilePath(): string { - return path.join(this.basePath, this.configFilename); + return path.join(this.basePath, BaseCommand.configFilename); } - get existsDocsFolder(): boolean { + existsDocsFolder(): boolean { return fs.existsSync(this.docsPath); } - get existsOutdatedDocFolder(): boolean { + existsOutdatedDocFolder(): boolean { return fs.existsSync(this.outdatedDocPath); } - get existsReadme(): boolean { + existsReadme(): boolean { return fs.existsSync(this.readmePath); } - get existsBackupDocsFolder(): boolean { + existsBackupDocsFolder(): boolean { return fs.existsSync(this.docsBackupPath); } - get existsBackupReadme(): boolean { + existsBackupReadme(): boolean { return fs.existsSync(this.readmeBackupPath); } - get existsConfigFile(): boolean { + existsConfigFile(): boolean { return fs.existsSync(this.configFilePath); } - get docsSourceFiles(): string[] { + findDocsSourceFiles(): string[] { return fs.readdirSync(this.docsSourcePath); } - get docsFiles(): string[] { + findDocsFiles(): string[] { return fs.readdirSync(this.docsPath); } - get coverage(): number { - const found = this.docsSourceFiles.filter((x) => - this.docsFiles.includes(x) + coverage(): number { + const found = this.findDocsSourceFiles().filter((x) => + this.findDocsFiles().includes(x) ); - return (found.length / this.docsSourceFiles.length) * 100; - } - - constructor(config: Config) { - this.basePath = config.basePath || path.normalize("."); - this.docsFolderName = config.docsFolderName; - this.outdatedDocFolderName = config.outdatedDocFolderName; - this.docsBackupFolderName = config.docsBackupFolderName; - this.moduleBasePath = config.moduleBasePath; - this.configFilename = config.configFilename; - this.rootFolderName = config.rootFolderName; - this.packageVersion = config.packageVersion; + return (found.length / this.findDocsSourceFiles().length) * 100; } } diff --git a/src/commands/install.ts b/src/commands/install.ts index 176d840..bbd25d9 100644 --- a/src/commands/install.ts +++ b/src/commands/install.ts @@ -2,29 +2,29 @@ import * as fs from "fs-extra"; import { prompt } from "enquirer"; import { logger } from "../Logger"; import { messages } from "../shared/messages"; -import { Command } from "./command"; +import { BaseCommand, ExecutableCommand } from "./command"; -export class Install extends Command { +export class Install extends BaseCommand implements ExecutableCommand { public async preliminaryCheck(): Promise { - if (this.existsConfigFile) { + if (this.existsConfigFile()) { logger.error(messages.install.checkConfigExists); console.log(""); process.exit(1); } - if (this.existsDocsFolder && this.existsBackupDocsFolder) { + if (this.existsDocsFolder() && this.existsBackupDocsFolder()) { logger.error(messages.install.checkFolderExist); console.log(""); process.exit(1); } - if (this.existsReadme && this.existsBackupReadme) { + if (this.existsReadme() && this.existsBackupReadme()) { logger.error(messages.install.checkReadmeExists); console.log(""); process.exit(1); } - if (this.existsReadme) { + if (this.existsReadme()) { const canRenameReadme: { confirm: boolean } = await prompt({ type: "confirm", initial: "y", @@ -41,11 +41,11 @@ export class Install extends Command { } public backupDocsFolder(): void { - if (!this.existsDocsFolder) { + if (!this.existsDocsFolder()) { return; } - if (this.existsBackupDocsFolder) { + if (this.existsBackupDocsFolder()) { logger.error(messages.install.backupFolderExists.message); process.exit(1); } @@ -56,7 +56,7 @@ export class Install extends Command { } public cloneDocsFolder = (): void => { - if (this.existsDocsFolder) { + if (this.existsDocsFolder()) { throw Error(messages.install.docsFolderExists.message); } fs.copySync(this.docsSourcePath, this.docsPath); @@ -64,10 +64,10 @@ export class Install extends Command { }; public backupReadme(): void { - if (!this.existsReadme) { + if (!this.existsReadme()) { return; } - if (this.existsBackupReadme) { + if (this.existsBackupReadme()) { logger.error(messages.install.backupReadmeExists.message); console.log(""); process.exit(1); @@ -78,7 +78,7 @@ export class Install extends Command { } public cloneReadme = (): void => { - if (this.existsReadme) { + if (this.existsReadme()) { throw Error(messages.install.readmeExists.message); } fs.copySync(this.readmeSourcePath, this.readmePath); @@ -86,7 +86,7 @@ export class Install extends Command { }; public createsConfigFile = (): void => { - if (this.existsConfigFile) { + if (this.existsConfigFile()) { logger.error(messages.install.configFileExists); console.log(""); process.exit(1); diff --git a/src/commands/update.ts b/src/commands/update.ts index 15d0686..0146a71 100644 --- a/src/commands/update.ts +++ b/src/commands/update.ts @@ -6,9 +6,9 @@ import semverCompare from "semver-compare"; import { logger } from "../Logger"; import { Message, messages } from "../shared/messages"; -import { Command } from "./command"; +import { BaseCommand, ExecutableCommand } from "./command"; -export class Update extends Command { +export class Update extends BaseCommand implements ExecutableCommand { private renameFile( oldFileName: string, newFileName: string, @@ -26,16 +26,16 @@ export class Update extends Command { } public preliminaryCheck(): void { - if (this.existsConfigFile) { + if (this.existsConfigFile()) { logger.info(messages.check.checkConfigExists); } else { logger.error(messages.check.checkConfigNotExists); process.exit(1); } - if (this.existsOutdatedDocFolder) { + if (this.existsOutdatedDocFolder()) { logger.info(messages.check.checkOutdatedFolderExist); - if (this.existsDocsFolder) { + if (this.existsDocsFolder()) { logger.error(messages.update.outdatedDocFolderCannotBeRenamed); process.exit(1); } @@ -45,7 +45,7 @@ export class Update extends Command { } public updateOutdatedFolderName() { - if (!this.existsOutdatedDocFolder) { + if (!this.existsOutdatedDocFolder()) { return; } @@ -78,17 +78,17 @@ export class Update extends Command { } public updateChapters() { - if (!this.existsDocsFolder) { + if (!this.existsDocsFolder()) { fs.mkdirSync(this.docsPath); } - if (!this.existsReadme) { + if (!this.existsReadme()) { fs.copySync(this.readmeSourcePath, this.readmePath); logger.info(messages.install.readmeCloneSuccesful); } - const missingFiles = this.docsSourceFiles.filter( - (value) => !this.docsFiles.includes(value) + const missingFiles = this.findDocsSourceFiles().filter( + (value) => !this.findDocsFiles().includes(value) ); if (missingFiles.length === 0) { @@ -112,7 +112,7 @@ export class Update extends Command { missingFiles.forEach((f) => { table.push([ f.replace(".md", ""), - this.docsFiles.includes(f) + this.findDocsFiles().includes(f) ? chalk.green(`Copied`) : chalk.red("Not copied"), ]); @@ -124,7 +124,7 @@ export class Update extends Command { } public updateConfigFile() { - if (!this.existsConfigFile) { + if (!this.existsConfigFile()) { logger.error(messages.update.configFileNotExist.message); process.exit(1); // Although this code is unreachable, it's currently required for test execution,