From 9a9b970e3bf17eec7b9317452456db11baac9afd Mon Sep 17 00:00:00 2001 From: Romain Lenzotti Date: Wed, 26 Apr 2023 16:32:34 +0200 Subject: [PATCH] feat(cli): add Yarn berry support and support DIContext --- packages/cli-core/package.json | 4 +- packages/cli-core/src/CliCore.ts | 3 + .../src/interfaces/CommandMetadata.ts | 20 +- .../src/interfaces/CommandParameters.ts | 6 + packages/cli-core/src/services/CliFs.ts | 16 + packages/cli-core/src/services/CliPlugins.ts | 7 +- packages/cli-core/src/services/CliService.ts | 81 ++-- .../src/services/ProjectPackageJson.spec.ts | 428 +++++------------- .../src/services/ProjectPackageJson.ts | 321 ++++++------- .../services/packageManagers/BaseManager.ts | 47 ++ .../packageManagers/NpmManager.spec.ts | 68 +++ .../services/packageManagers/NpmManager.ts | 23 + .../packageManagers/PNpmManager.spec.ts | 69 +++ .../services/packageManagers/PNpmManager.ts | 23 + .../packageManagers/YarnBerryManager.spec.ts | 69 +++ .../packageManagers/YarnBerryManager.ts | 42 ++ .../packageManagers/YarnManager.spec.ts | 69 +++ .../services/packageManagers/YarnManager.ts | 24 + .../src/utils/getCommandMetadata.spec.ts | 2 + .../cli-core/src/utils/getCommandMetadata.ts | 13 +- packages/cli-core/src/utils/getPackageJson.ts | 39 -- packages/cli-core/src/utils/index.ts | 2 +- .../src/utils/resolveConfiguration.ts | 24 + .../generate.controller.integration.spec.ts | 12 +- .../generate.model.integration.spec.ts | 6 +- .../generate.schema.integration.spec.ts | 12 +- .../test/init/init.integration.spec.ts | 33 +- .../test/init/init.integration.spec.ts | 6 +- .../generate.controller.integration.spec.ts | 6 +- .../init/init.integration.spec.ts | 34 +- packages/cli-testing/src/CliPlatformTest.ts | 45 +- packages/cli-testing/src/FakeCliFs.ts | 19 + packages/cli/src/Cli.ts | 21 +- .../cli/src/commands/init/InitCmd.spec.ts | 13 +- packages/cli/src/commands/init/InitCmd.ts | 33 +- .../commands/init/config/FeaturesPrompt.ts | 15 +- .../init/prompts/getFeaturesPrompt.spec.ts | 2 +- .../init/prompts/getFeaturesPrompt.ts | 4 +- packages/cli/src/commands/run/RunCmd.spec.ts | 3 +- ...generate.async-factory.integration.spec.ts | 6 +- .../generate.controller.integration.spec.ts | 12 +- ...nerate.decorator-class.integration.spec.ts | 4 +- ...ate.decorator-endpoint.integration.spec.ts | 6 +- ...rate.decorator-generic.integration.spec.ts | 4 +- ...erate.decorator-method.integration.spec.ts | 4 +- ...e.decorator-middleware.integration.spec.ts | 8 +- ...erate.exception-filter.integration.spec.ts | 4 +- ...nerate.response-filter.integration.spec.ts | 6 +- .../init/init.integration.spec.ts | 127 ++---- yarn.lock | 5 + 50 files changed, 1010 insertions(+), 840 deletions(-) create mode 100644 packages/cli-core/src/services/packageManagers/BaseManager.ts create mode 100644 packages/cli-core/src/services/packageManagers/NpmManager.spec.ts create mode 100644 packages/cli-core/src/services/packageManagers/NpmManager.ts create mode 100644 packages/cli-core/src/services/packageManagers/PNpmManager.spec.ts create mode 100644 packages/cli-core/src/services/packageManagers/PNpmManager.ts create mode 100644 packages/cli-core/src/services/packageManagers/YarnBerryManager.spec.ts create mode 100644 packages/cli-core/src/services/packageManagers/YarnBerryManager.ts create mode 100644 packages/cli-core/src/services/packageManagers/YarnManager.spec.ts create mode 100644 packages/cli-core/src/services/packageManagers/YarnManager.ts delete mode 100644 packages/cli-core/src/utils/getPackageJson.ts create mode 100644 packages/cli-core/src/utils/resolveConfiguration.ts diff --git a/packages/cli-core/package.json b/packages/cli-core/package.json index 498d2dc64..40a5e0156 100644 --- a/packages/cli-core/package.json +++ b/packages/cli-core/package.json @@ -33,6 +33,7 @@ "@tsed/normalize-path": ">=7.14.2", "@types/fs-extra": "^9.0.13", "@types/inquirer": "^8.2.4", + "uuid": "^8.3.2", "ajv": "8.11.0", "axios": "0.26.1", "chalk": "4.1.2", @@ -66,6 +67,7 @@ }, "devDependencies": { "@types/axios": "0.14.0", + "@types/uuid": "8.3.4", "@types/commander": "2.12.2", "@types/consolidate": "0.14.1", "@types/figures": "3.0.1", @@ -83,4 +85,4 @@ "@tsed/core": ">=7.14.2", "@tsed/di": ">=7.14.2" } -} \ No newline at end of file +} diff --git a/packages/cli-core/src/CliCore.ts b/packages/cli-core/src/CliCore.ts index 9afb9dcf8..f2d1a489b 100644 --- a/packages/cli-core/src/CliCore.ts +++ b/packages/cli-core/src/CliCore.ts @@ -12,6 +12,7 @@ import {createInjector} from "./utils/createInjector"; import {loadPlugins} from "./utils/loadPlugins"; import {CliError} from "./domains/CliError"; import semver from "semver"; +import {resolveConfiguration} from "./utils/resolveConfiguration"; function isHelpManual(argv: string[]) { return argv.includes("-h") || argv.includes("--help"); @@ -47,6 +48,8 @@ export class CliCore { } static async create(settings: Partial, module: Type = CliCore): Promise { + settings = resolveConfiguration(settings); + const injector = this.createInjector(settings); settings.plugins && (await loadPlugins(injector)); diff --git a/packages/cli-core/src/interfaces/CommandMetadata.ts b/packages/cli-core/src/interfaces/CommandMetadata.ts index 5699c8194..0f6495a20 100644 --- a/packages/cli-core/src/interfaces/CommandMetadata.ts +++ b/packages/cli-core/src/interfaces/CommandMetadata.ts @@ -1,16 +1,6 @@ -import {CommandArg, CommandOptions} from "./CommandParameters"; +import {CommandArg, CommandOptions, CommandParameters} from "./CommandParameters"; -export interface CommandMetadata { - /** - * name commands - */ - name: string; - - alias?: string; - /** - * CommandProvider description - */ - description: string; +export interface CommandMetadata extends CommandParameters { /** * CommandProvider arguments */ @@ -24,5 +14,9 @@ export interface CommandMetadata { [key: string]: CommandOptions; }; - allowUnknownOption?: boolean; + allowUnknownOption: boolean; + + enableFeatures: string[]; + + disableReadUpPkg: boolean; } diff --git a/packages/cli-core/src/interfaces/CommandParameters.ts b/packages/cli-core/src/interfaces/CommandParameters.ts index 82e1c6d9b..4762b9fe3 100644 --- a/packages/cli-core/src/interfaces/CommandParameters.ts +++ b/packages/cli-core/src/interfaces/CommandParameters.ts @@ -56,6 +56,8 @@ export interface CommandParameters { * name commands */ name: string; + + alias?: string; /** * CommandProvider description */ @@ -75,5 +77,9 @@ export interface CommandParameters { allowUnknownOption?: boolean; + enableFeatures?: string[]; + + disableReadUpPkg?: boolean; + [key: string]: any; } diff --git a/packages/cli-core/src/services/CliFs.ts b/packages/cli-core/src/services/CliFs.ts index ec82b5b4b..4c2c54fd8 100644 --- a/packages/cli-core/src/services/CliFs.ts +++ b/packages/cli-core/src/services/CliFs.ts @@ -21,6 +21,22 @@ export class CliFs { return this.raw.readFile(file, encoding) as any; } + readFileSync(file: string | Buffer | number, encoding?: any) { + return this.raw.readFileSync(file, encoding) as any; + } + + async readJson(file: string, encoding?: any) { + const content = await this.readFile(file, encoding); + + return JSON.parse(content); + } + + async readJsonSync(file: string, encoding?: any) { + const content = this.readFileSync(file, encoding) as any; + + return JSON.parse(content); + } + async writeJson(file: string | Buffer | number, data: any, options?: WriteFileOptions | string): Promise { await this.raw.writeFile(file, JSON.stringify(data, null, 2), options || ({encoding: "utf8"} as any)); } diff --git a/packages/cli-core/src/services/CliPlugins.ts b/packages/cli-core/src/services/CliPlugins.ts index 6ae5c4315..4246f165b 100644 --- a/packages/cli-core/src/services/CliPlugins.ts +++ b/packages/cli-core/src/services/CliPlugins.ts @@ -56,7 +56,12 @@ export class CliPlugins { ...tasks, { title: "Install", - task: createSubTasks(() => this.packageJson.install(ctx), {...ctx, concurrent: false}) + task: createSubTasks( + () => { + return this.packageJson.install(ctx); + }, + {...ctx, concurrent: false} + ) } ]; } diff --git a/packages/cli-core/src/services/CliService.ts b/packages/cli-core/src/services/CliService.ts index ebe900d69..caa1ea558 100644 --- a/packages/cli-core/src/services/CliService.ts +++ b/packages/cli-core/src/services/CliService.ts @@ -1,10 +1,10 @@ import {classOf} from "@tsed/core"; -import {Constant, Inject, Injectable, InjectorService, Provider} from "@tsed/di"; +import {Constant, DIContext, getContext, Inject, Injectable, InjectorService, Provider, runInContext} from "@tsed/di"; import {Argument, Command} from "commander"; import Inquirer from "inquirer"; +import {v4} from "uuid"; import {CommandStoreKeys} from "../domains/CommandStoreKeys"; import {CommandProvider} from "../interfaces/CommandProvider"; -import {CommandMetadata} from "../interfaces/CommandMetadata"; import {CommandArg, CommandOptions} from "../interfaces/CommandParameters"; import {PROVIDER_TYPE_COMMAND} from "../registries/CommandRegistry"; import {createSubTasks, createTasksRunner} from "../utils/createTasksRunner"; @@ -17,6 +17,7 @@ import {ProjectPackageJson} from "./ProjectPackageJson"; // @ts-ignore import inquirer_autocomplete_prompt from "inquirer-autocomplete-prompt"; import {mapCommanderOptions} from "../utils/mapCommanderOptions"; +import {CommandMetadata} from "@tsed/cli-core"; Inquirer.registerPrompt("autocomplete", inquirer_autocomplete_prompt); @@ -59,17 +60,25 @@ export class CliService { * Run lifecycle * @param cmdName * @param data + * @param $ctx */ - public async runLifecycle(cmdName: string, data: any = {}) { - data = await this.beforePrompt(cmdName, data); - data = await this.prompt(cmdName, data); + public async runLifecycle(cmdName: string, data: any = {}, $ctx: DIContext) { + return runInContext($ctx, async () => { + data = await this.beforePrompt(cmdName, data); - await this.dispatch(cmdName, data); + $ctx.set("data", data); + + data = await this.prompt(cmdName, data); + await this.dispatch(cmdName, data, $ctx); + }); } - public async dispatch(cmdName: string, data: any) { + public async dispatch(cmdName: string, data: any, $ctx: DIContext) { try { - await this.exec(cmdName, data); + $ctx.set("dispatchCmd", cmdName); + $ctx.set("data", data); + + await this.exec(cmdName, data, $ctx); } catch (er) { await this.injector.emit("$onFinish", er); await this.injector.destroy(); @@ -80,8 +89,8 @@ export class CliService { await this.injector.destroy(); } - public async exec(cmdName: string, ctx: any) { - const initialTasks = await this.getTasks(cmdName, ctx); + public async exec(cmdName: string, data: any, $ctx: DIContext) { + const initialTasks = await this.getTasks(cmdName, data); if (initialTasks.length) { const tasks = [ @@ -89,12 +98,12 @@ export class CliService { { title: "Install dependencies", enabled: () => this.reinstallAfterRun && (this.projectPkg.rewrite || this.projectPkg.reinstall), - task: createSubTasks(() => this.projectPkg.install(ctx), {...ctx, concurrent: false}) + task: createSubTasks(() => this.projectPkg.install(data), {...data, concurrent: false}) }, - ...(await this.getPostInstallTasks(cmdName, ctx)) + ...(await this.getPostInstallTasks(cmdName, data)) ]; - return createTasksRunner(tasks, this.mapContext(cmdName, ctx)); + return createTasksRunner(tasks, this.mapData(cmdName, data, $ctx)); } } @@ -144,31 +153,32 @@ export class CliService { /** * Run lifecycle * @param cmdName - * @param ctx + * @param data */ - public async getTasks(cmdName: string, ctx: any) { + public async getTasks(cmdName: string, data: any) { + const $ctx = getContext()!; const provider = this.commands.get(cmdName); const instance = this.injector.get(provider.token)!; - ctx = this.mapContext(cmdName, ctx); + data = this.mapData(cmdName, data, $ctx); if (instance.$beforeExec) { - await instance.$beforeExec(ctx); + await instance.$beforeExec(data); } - return [...(await instance.$exec(ctx)), ...(await this.hooks.emit(CommandStoreKeys.EXEC_HOOKS, cmdName, ctx))]; + return [...(await instance.$exec(data)), ...(await this.hooks.emit(CommandStoreKeys.EXEC_HOOKS, cmdName, data))]; } - public async getPostInstallTasks(cmdName: string, ctx: any) { + public async getPostInstallTasks(cmdName: string, data: any) { const provider = this.commands.get(cmdName); const instance = this.injector.get(provider.useClass)!; - ctx = this.mapContext(cmdName, ctx); + data = this.mapData(cmdName, data, getContext()!); return [ - ...(instance.$postInstall ? await instance.$postInstall(ctx) : []), - ...(await this.hooks.emit(CommandStoreKeys.POST_INSTALL_HOOKS, cmdName, ctx)), - ...(instance.$afterPostInstall ? await instance.$afterPostInstall(ctx) : []) + ...(instance.$postInstall ? await instance.$postInstall(data) : []), + ...(await this.hooks.emit(CommandStoreKeys.POST_INSTALL_HOOKS, cmdName, data)), + ...(instance.$afterPostInstall ? await instance.$afterPostInstall(data) : []) ]; } @@ -194,7 +204,16 @@ export class CliService { rawArgs }; - return this.runLifecycle(name, data); + const $ctx = new DIContext({ + id: v4(), + injector: this.injector, + logger: this.injector.logger + }); + + $ctx.set("data", data); + $ctx.set("command", metadata); + + return this.runLifecycle(name, data, $ctx); }; if (alias) { @@ -217,23 +236,25 @@ export class CliService { this.injector.getProviders(PROVIDER_TYPE_COMMAND).forEach((provider) => this.build(provider)); } - private mapContext(cmdName: string, ctx: any) { + private mapData(cmdName: string, data: any, $ctx: DIContext) { const provider = this.commands.get(cmdName); const instance = this.injector.get(provider.useClass)!; - const verbose = ctx.verbose; + const verbose = data.verbose; if (instance.$mapContext) { - ctx = instance.$mapContext(JSON.parse(JSON.stringify(ctx))); - ctx.verbose = verbose; + data = instance.$mapContext(JSON.parse(JSON.stringify(data))); + data.verbose = verbose; } - if (ctx.verbose) { + if (data.verbose) { this.injector.logger.level = "debug"; } else { this.injector.logger.level = "info"; } - return ctx; + $ctx.set("data", data); + + return data; } /** diff --git a/packages/cli-core/src/services/ProjectPackageJson.spec.ts b/packages/cli-core/src/services/ProjectPackageJson.spec.ts index aab10acb7..d4cf7b6b9 100644 --- a/packages/cli-core/src/services/ProjectPackageJson.spec.ts +++ b/packages/cli-core/src/services/ProjectPackageJson.spec.ts @@ -1,31 +1,77 @@ -import {CliExeca, CliFs, createSubTasks, createTasksRunner, PackageManager, ProjectPackageJson} from "@tsed/cli-core"; +import {CliFs, createSubTasks, createTasksRunner, ProjectPackageJson} from "@tsed/cli-core"; import {CliPlatformTest} from "@tsed/cli-testing"; import {join, resolve} from "path"; import filedirname from "filedirname"; +import {NpmManager} from "./packageManagers/NpmManager"; +import {YarnManager} from "./packageManagers/YarnManager"; +import {PNpmManager} from "./packageManagers/PNpmManager"; const [, dir] = filedirname(); -async function getProjectPackageJsonFixture() { - const cliExeca = { - run: jest.fn().mockReturnThis(), - pipe: jest.fn().mockResolvedValue(undefined) - }; +async function getProjectPackageJsonFixture({hasYarn = true, hasPnpm = true} = {}) { const cliFs = { - writeFileSync: jest.fn() + writeFileSync: jest.fn(), + exists: jest.fn().mockReturnValue(false) + }; + + const npmManager = { + name: "npm", + add: jest.fn().mockReturnThis(), + addDev: jest.fn().mockReturnThis(), + install: jest.fn().mockReturnThis(), + init: jest.fn().mockReturnThis(), + has: jest.fn().mockReturnValue(true), + pipe: jest.fn() + }; + + const yarnManager = { + name: "yarn", + add: jest.fn().mockReturnThis(), + addDev: jest.fn().mockReturnThis(), + install: jest.fn().mockReturnThis(), + init: jest.fn().mockReturnThis(), + has: jest.fn().mockReturnValue(hasYarn), + pipe: jest.fn() + }; + + const pnpmManager = { + name: "pnpm", + add: jest.fn().mockReturnThis(), + addDev: jest.fn().mockReturnThis(), + install: jest.fn().mockReturnThis(), + init: jest.fn().mockReturnThis(), + has: jest.fn().mockReturnValue(hasPnpm), + pipe: jest.fn() }; const projectPackageJson = await CliPlatformTest.invoke(ProjectPackageJson, [ - { - token: CliExeca, - use: cliExeca - }, { token: CliFs, use: cliFs + }, + { + token: NpmManager, + use: npmManager + }, + { + token: YarnManager, + use: yarnManager + }, + { + token: PNpmManager, + use: pnpmManager } ]); - return {projectPackageJson, cliExeca, cliFs}; + return { + projectPackageJson, + cliFs, + packageManager: { + npm: npmManager, + yarn: yarnManager, + pnpm: pnpmManager + } as any + }; } const rootDir = resolve(join(dir, "__mock__")); @@ -55,102 +101,46 @@ describe("ProjectPackageJson", () => { }); afterEach(() => CliPlatformTest.reset()); - describe("with Yarn", () => { - it("should read package.json and add dependencies", async () => { - const {projectPackageJson, cliExeca, cliFs} = await getProjectPackageJsonFixture(); - - jest.spyOn(projectPackageJson, "hasYarn").mockReturnValue(true); + it.each([ + { + manager: "yarn", + hasYarn: true, + hasPnpm: true, + expected: "yarn" + }, + { + manager: "yarn", + hasYarn: false, + hasPnpm: false, + expected: "npm" + }, + { + manager: "npm", + hasYarn: false, + hasPnpm: false, + expected: "npm" + }, + { + manager: "pnpm", + hasYarn: false, + hasPnpm: true, + expected: "pnpm" + }, + { + manager: "pnpm", + hasYarn: false, + hasPnpm: false, + expected: "npm" + } + ])( + "should use package manager $expected to install project (asked: $manager, hasYarn: $hasYarn, hasPnpm: $hasPnpm, expected: $expected)", + async ({manager, hasYarn, hasPnpm, expected}) => { + const {projectPackageJson, cliFs, packageManager} = await getProjectPackageJsonFixture({hasYarn, hasPnpm}); projectPackageJson.set("name", "name"); projectPackageJson.set("version", "1.0.0"); projectPackageJson.set("description", "description"); - projectPackageJson.addScripts({ - build: "echo 0" - }); - projectPackageJson.addDependencies({ - module1: "latest", - module2: "alpha", - module3: "6.0.0" - }); - - projectPackageJson.addDevDependencies({ - "dev-module1": "latest", - "dev-module2": "alpha", - "dev-module3": "6.0.0" - }); - expect(projectPackageJson.toJSON()).toEqual({ - dependencies: { - module1: "latest", - module2: "alpha", - module3: "6.0.0" - }, - description: "description", - devDependencies: { - "dev-module1": "latest", - "dev-module2": "alpha", - "dev-module3": "6.0.0" - }, - name: "name", - scripts: { - build: "echo 0" - }, - tsed: { - packageManager: "yarn" - }, - version: "1.0.0" - }); - - // WHEN - await taskFixtureRunner(() => projectPackageJson.install({packageManager: PackageManager.YARN})); - - const expectedJson = { - name: "name", - version: "1.0.0", - description: "description", - scripts: { - build: "echo 0" - }, - dependencies: { - module3: "6.0.0" - }, - devDependencies: { - "dev-module3": "6.0.0" - }, - tsed: { - packageManager: "yarn" - } - }; - - expect(cliFs.writeFileSync).toHaveBeenCalledWith(resolve(join(rootDir, "package.json")), JSON.stringify(expectedJson, null, 2), { - encoding: "utf8" - }); - - expect(cliExeca.run).toHaveBeenCalledWith("yarn", ["install"], { - cwd: rootDir, - env: process.env - }); - expect(cliExeca.run).toHaveBeenCalledWith("yarn", ["add", "module1", "module2@alpha"], { - cwd: rootDir, - env: process.env - }); - expect(cliExeca.run).toHaveBeenCalledWith("yarn", ["add", "-D", "dev-module1", "dev-module2@alpha"], { - cwd: rootDir, - env: process.env - }); - }); - }); - - describe("with NPM", () => { - it("should read package.json and add dependencies (asked for yarn, but fallback to npm)", async () => { - const {projectPackageJson, cliExeca, cliFs} = await getProjectPackageJsonFixture(); - cliExeca.run.mockResolvedValue(undefined); - - jest.spyOn(projectPackageJson, "hasYarn").mockReturnValue(false); - - projectPackageJson.set("name", "name"); - projectPackageJson.set("version", "1.0.0"); - projectPackageJson.set("description", "description"); projectPackageJson.addScripts({ build: "echo 0" }); @@ -166,32 +156,8 @@ describe("ProjectPackageJson", () => { "dev-module3": "6.0.0" }); - expect(projectPackageJson.getPackageManager(PackageManager.YARN)).toEqual(PackageManager.NPM); - - expect(projectPackageJson.toJSON()).toEqual({ - dependencies: { - module1: "latest", - module2: "alpha", - module3: "6.0.0" - }, - description: "description", - devDependencies: { - "dev-module1": "latest", - "dev-module2": "alpha", - "dev-module3": "6.0.0" - }, - name: "name", - scripts: { - build: "echo 0" - }, - tsed: { - packageManager: "yarn" - }, - version: "1.0.0" - }); - // WHEN - await taskFixtureRunner(() => projectPackageJson.install({packageManager: PackageManager.YARN})); + await taskFixtureRunner(() => projectPackageJson.install({packageManager: manager as any})); const expectedJson = { name: "name", @@ -207,209 +173,23 @@ describe("ProjectPackageJson", () => { "dev-module3": "6.0.0" }, tsed: { - packageManager: "yarn" + packageManager: expected } }; - expect(cliFs.writeFileSync).toHaveBeenCalledWith(resolve(join(rootDir, "package.json")), JSON.stringify(expectedJson, null, 2), { - encoding: "utf8" - }); - - expect(cliExeca.run).toHaveBeenCalledWith("npm", ["install", "--no-production", "--legacy-peer-deps"], { - cwd: rootDir, - env: process.env - }); - expect(cliExeca.run).toHaveBeenCalledWith("npm", ["install", "--save", "--legacy-peer-deps", "module1", "module2@alpha"], { + const opts = { cwd: rootDir, - env: process.env - }); - expect(cliExeca.run).toHaveBeenCalledWith( - "npm", - ["install", "--save-dev", "--legacy-peer-deps", "dev-module1", "dev-module2@alpha"], - { - cwd: rootDir, - env: process.env - } - ); - }); - it("should read package.json and add dependencies (asked for npm)", async () => { - const {projectPackageJson, cliExeca, cliFs} = await getProjectPackageJsonFixture(); - cliExeca.run.mockResolvedValue(undefined); - - jest.spyOn(projectPackageJson, "hasYarn").mockReturnValue(false); - - projectPackageJson.setPreference("packageManager", PackageManager.NPM); - - projectPackageJson.set("name", "name"); - projectPackageJson.set("version", "1.0.0"); - projectPackageJson.set("description", "description"); - projectPackageJson.addScripts({ - build: "echo 0" - }); - projectPackageJson.addDependencies({ - module1: "latest", - module2: "alpha", - module3: "6.0.0" - }); - - projectPackageJson.addDevDependencies({ - "dev-module1": "latest", - "dev-module2": "alpha", - "dev-module3": "6.0.0" - }); - - expect(projectPackageJson.toJSON()).toEqual({ - dependencies: { - module1: "latest", - module2: "alpha", - module3: "6.0.0" - }, - description: "description", - devDependencies: { - "dev-module1": "latest", - "dev-module2": "alpha", - "dev-module3": "6.0.0" - }, - name: "name", - scripts: { - build: "echo 0" - }, - tsed: { - packageManager: "npm" - }, - version: "1.0.0" - }); - - // WHEN - await taskFixtureRunner(() => projectPackageJson.install({packageManager: PackageManager.NPM})); - - const expectedJson = { - name: "name", - version: "1.0.0", - description: "description", - scripts: { - build: "echo 0" - }, - dependencies: { - module3: "6.0.0" - }, - devDependencies: { - "dev-module3": "6.0.0" - }, - tsed: { - packageManager: "npm" - } - }; - - expect(cliFs.writeFileSync).toHaveBeenCalledWith(resolve(join(rootDir, "package.json")), JSON.stringify(expectedJson, null, 2), { - encoding: "utf8" - }); - - expect(cliExeca.run).toHaveBeenCalledWith("npm", ["install", "--no-production", "--legacy-peer-deps"], { - cwd: rootDir, - env: process.env - }); - expect(cliExeca.run).toHaveBeenCalledWith("npm", ["install", "--save", "--legacy-peer-deps", "module1", "module2@alpha"], { - cwd: rootDir, - env: process.env - }); - expect(cliExeca.run).toHaveBeenCalledWith( - "npm", - ["install", "--save-dev", "--legacy-peer-deps", "dev-module1", "dev-module2@alpha"], - { - cwd: rootDir, - env: process.env - } - ); - }); - }); - - describe("with PNPM", () => { - it("should read package.json and add dependencies (asked for pnpm)", async () => { - const {projectPackageJson, cliExeca, cliFs} = await getProjectPackageJsonFixture(); - cliExeca.run.mockResolvedValue(undefined); - - jest.spyOn(projectPackageJson, "hasYarn").mockReturnValue(false); - - projectPackageJson.setPreference("packageManager", PackageManager.PNPM); - - projectPackageJson.set("name", "name"); - projectPackageJson.set("version", "1.0.0"); - projectPackageJson.set("description", "description"); - projectPackageJson.addScripts({ - build: "echo 0" - }); - projectPackageJson.addDependencies({ - module1: "latest", - module2: "alpha", - module3: "6.0.0" - }); - - projectPackageJson.addDevDependencies({ - "dev-module1": "latest", - "dev-module2": "alpha", - "dev-module3": "6.0.0" - }); - - expect(projectPackageJson.toJSON()).toEqual({ - dependencies: { - module1: "latest", - module2: "alpha", - module3: "6.0.0" - }, - description: "description", - devDependencies: { - "dev-module1": "latest", - "dev-module2": "alpha", - "dev-module3": "6.0.0" - }, - name: "name", - scripts: { - build: "echo 0" - }, - tsed: { - packageManager: "pnpm" - }, - version: "1.0.0" - }); - - // WHEN - await taskFixtureRunner(() => projectPackageJson.install({packageManager: PackageManager.PNPM})); - - const expectedJson = { - name: "name", - version: "1.0.0", - description: "description", - scripts: { - build: "echo 0" - }, - dependencies: { - module3: "6.0.0" - }, - devDependencies: { - "dev-module3": "6.0.0" - }, - tsed: { - packageManager: "pnpm" - } + env: {...process.env, GH_TOKEN: undefined}, + packageManager: expected }; expect(cliFs.writeFileSync).toHaveBeenCalledWith(resolve(join(rootDir, "package.json")), JSON.stringify(expectedJson, null, 2), { encoding: "utf8" }); - - expect(cliExeca.run).toHaveBeenCalledWith("pnpm", ["install", "--dev"], { - cwd: rootDir, - env: process.env - }); - expect(cliExeca.run).toHaveBeenCalledWith("pnpm", ["add", "--save-prod", "module1", "module2@alpha"], { - cwd: rootDir, - env: process.env - }); - expect(cliExeca.run).toHaveBeenCalledWith("pnpm", ["add", "--save-dev", "dev-module1", "dev-module2@alpha"], { - cwd: rootDir, - env: process.env - }); - }); - }); + expect(projectPackageJson.packageManager(manager).name).toEqual(expected); + expect(packageManager[expected].install).toHaveBeenCalledWith(opts); + expect(packageManager[expected].add).toHaveBeenCalledWith(["module1", "module2@alpha"], opts); + expect(packageManager[expected].addDev).toHaveBeenCalledWith(["dev-module1", "dev-module2@alpha"], opts); + } + ); }); diff --git a/packages/cli-core/src/services/ProjectPackageJson.ts b/packages/cli-core/src/services/ProjectPackageJson.ts index 05bbece36..e89416938 100644 --- a/packages/cli-core/src/services/ProjectPackageJson.ts +++ b/packages/cli-core/src/services/ProjectPackageJson.ts @@ -1,19 +1,23 @@ import {getValue, setValue} from "@tsed/core"; import {Configuration, Inject, Injectable} from "@tsed/di"; -import {join} from "path"; +import {dirname, join} from "path"; import {EMPTY, throwError} from "rxjs"; import {catchError} from "rxjs/operators"; import semver from "semver"; import {PackageJson} from "../interfaces/PackageJson"; -import {PackageManager, ProjectPreferences} from "../interfaces/ProjectPreferences"; -import {CliExeca} from "./CliExeca"; +import {ProjectPreferences} from "../interfaces/ProjectPreferences"; import {CliFs} from "./CliFs"; import {isValidVersion} from "../utils/isValidVersion"; -import {getPackageJson} from "../utils/getPackageJson"; import {Options} from "execa"; +import {BaseManager} from "./packageManagers/BaseManager"; +import {YarnManager} from "./packageManagers/YarnManager"; +import {YarnBerryManager} from "./packageManagers/YarnBerryManager"; +import {NpmManager} from "./packageManagers/NpmManager"; +import {PNpmManager} from "./packageManagers/PNpmManager"; +import readPkgUp from "read-pkg-up"; export interface InstallOptions { - packageManager?: PackageManager; + packageManager?: string; [key: string]: any; } @@ -54,35 +58,21 @@ function mapPackagesWithValidVersion(deps: any) { }, {}); } -function defaultPreferences(pkg?: any): Record { - let packageManager = PackageManager.YARN; - - if (getValue(pkg, "scripts.build", "").includes("pnpm ")) { - packageManager = PackageManager.PNPM; - } else if (getValue(pkg, "scripts.build", "").includes("npm ")) { - packageManager = PackageManager.NPM; - } - - return { - packageManager - }; -} - -@Injectable() +@Injectable({ + imports: [YarnManager, YarnBerryManager, NpmManager, PNpmManager] +}) export class ProjectPackageJson { public rewrite = false; public reinstall = false; - @Inject(CliExeca) - protected cliExeca: CliExeca; - - @Inject(CliFs) - protected fs: CliFs; - private GH_TOKEN: string; private raw: PackageJson; - constructor(@Configuration() private configuration: Configuration) { + constructor( + @Configuration() private configuration: Configuration, + protected fs: CliFs, + @Inject("package:manager") protected packageManagers: BaseManager[] + ) { this.setRaw({ name: "", version: "1.0.0", @@ -92,6 +82,7 @@ export class ProjectPackageJson { devDependencies: {} }); this.read(); + this.packageManagers = packageManagers.filter((manager) => manager.has()); } get path() { @@ -148,13 +139,16 @@ export class ProjectPackageJson { return this.raw[this.configuration.name]; } + get availablePackageManagers() { + return this.packageManagers.map((manager) => manager.name); + } + toJSON() { return this.raw; } read() { const pkg = this.getPackageJson(); - this.setRaw(pkg); return this; } @@ -166,22 +160,32 @@ export class ProjectPackageJson { this.raw = { ...pkg, [this.configuration.name]: { - ...defaultPreferences(pkg), ...(projectPreferences && projectPreferences(pkg)), ...preferences } }; } - getRunCmd() { - switch (this.preferences.packageManager) { - case "npm": - return "npm run"; - case "pnpm": - return "pnpm run"; - default: - return "yarn run"; + packageManager(name?: string): BaseManager { + if (this.preferences.packageManager) { + name = this.preferences.packageManager; } + + name = name || "yarn"; + + let selectedPackageManager = this.packageManagers.find((manager) => manager.name === name); + + if (!selectedPackageManager) { + selectedPackageManager = this.packageManagers.find((manager) => manager.name === "npm")!; + } + + this.setPreference("packageManager", selectedPackageManager.name); + + return selectedPackageManager; + } + + getRunCmd() { + return this.packageManager()!.runCmd; } addDevDependency(pkg: string, version?: string) { @@ -244,6 +248,7 @@ export class ProjectPackageJson { setPreference(key: keyof ProjectPreferences, value: any) { setValue(this.raw, `${this.configuration.name}.${key}`, value); this.rewrite = true; + return; } @@ -265,7 +270,6 @@ export class ProjectPackageJson { const originalPkg = this.getPackageJson(); this.rewrite = false; - this.raw = { ...originalPkg, ...this.raw, @@ -290,29 +294,51 @@ export class ProjectPackageJson { return this; } - hasYarn() { - try { - this.cliExeca.runSync(PackageManager.YARN, ["--version"]); + init(options: InstallOptions = {}) { + const packageManager = this.packageManager(options.packageManager); + options.packageManager = packageManager.name; - return true; - } catch (er) { - return false; - } + options = { + ...options, + cwd: this.dir, + env: { + ...process.env, + GH_TOKEN: this.GH_TOKEN + } + }; + + this.write(); + this.rewrite = true; + + return packageManager.init(options as any); } install(options: InstallOptions = {}) { - const packageManager = this.getPackageManager(options.packageManager); - let tasks: {title: string; skip: () => boolean; task: () => any}[]; - switch (packageManager) { - case "pnpm": - tasks = this.installWithPnpm(options); - break; - case "npm": - tasks = this.installWithNpm(options); - break; - default: - tasks = this.installWithYarn(options); - } + const packageManager = this.packageManager(options.packageManager); + options.packageManager = packageManager.name; + + const devDeps = mapPackagesWithInvalidVersion(this.devDependencies); + const deps = mapPackagesWithInvalidVersion(this.dependencies); + + options = { + ...options, + cwd: this.dir, + env: { + ...process.env, + GH_TOKEN: this.GH_TOKEN + } + }; + + const errorPipe = () => + catchError((error: any) => { + if (error.stderr.startsWith("error Your lockfile needs to be updated")) { + return throwError( + new Error(`yarn.lock file is outdated. Run ${packageManager.name}, commit the updated lockfile and try again.`) + ); + } + + return throwError(error); + }); return [ { @@ -322,7 +348,21 @@ export class ProjectPackageJson { this.write(); } }, - ...tasks, + { + title: `Installing dependencies using ${packageManager.name}`, + skip: () => !this.reinstall, + task: () => packageManager.install(options as any).pipe(errorPipe()) + }, + { + title: `Add dependencies using ${packageManager.name}`, + skip: () => !deps.length, + task: () => packageManager.add(deps, options as any).pipe(errorPipe()) + }, + { + title: `Add devDependencies using ${packageManager.name}`, + skip: () => !devDeps.length, + task: () => packageManager.addDev(devDeps, options as any).pipe(errorPipe()) + }, { title: "Clean", task: () => { @@ -342,11 +382,12 @@ export class ProjectPackageJson { return this.fs.importModule(mod, this.dir); } - public runScript(npmTask: string, {ignoreError, ...opts}: {ignoreError?: boolean} & Options & Record = {}) { + public runScript(scriptName: string, {ignoreError, ...opts}: {ignoreError?: boolean} & Options & Record = {}) { const options = { cwd: this.dir, ...opts }; + const errorPipe = () => catchError((error: any) => { if (ignoreError) { @@ -356,152 +397,54 @@ export class ProjectPackageJson { return throwError(error); }); - return this.cliExeca.run(this.getPackageManager(), ["run", npmTask], options).pipe(errorPipe()); - } - - getPackageManager(packageManager?: PackageManager): PackageManager { - if (this.preferences.packageManager) { - packageManager = this.preferences.packageManager; - } - - packageManager = packageManager || PackageManager.YARN; - - if (packageManager === PackageManager.YARN && !this.hasYarn()) { - packageManager = PackageManager.NPM; - } - - return packageManager; + return this.packageManager().runScript(scriptName, options).pipe(errorPipe()); } setGhToken(GH_TOKEN: string) { this.GH_TOKEN = GH_TOKEN; } - protected installWithYarn({verbose}: any) { - const devDeps = mapPackagesWithInvalidVersion(this.devDependencies); - const deps = mapPackagesWithInvalidVersion(this.dependencies); - const options = { - cwd: this.dir, - env: { - ...process.env, - GH_TOKEN: this.GH_TOKEN - } - }; + protected getPackageJson() { + const cwd = this.configuration.get("project.rootDir"); + const disableReadUpPkg = this.configuration.get("disableReadUpPkg"); + const name = this.configuration.get("name"); - const errorPipe = () => - catchError((error: any) => { - if (error.stderr.startsWith("error Your lockfile needs to be updated")) { - return throwError(new Error("yarn.lock file is outdated. Run yarn, commit the updated lockfile and try again.")); - } + const pkgPath = join(String(cwd), "package.json"); + const fileExists = this.fs.exists(pkgPath); - return throwError(error); + if (!disableReadUpPkg && !fileExists) { + const result = readPkgUp.sync({ + cwd }); - return [ - { - title: "Installing dependencies using Yarn", - skip: () => !this.reinstall, - task: () => this.cliExeca.run(PackageManager.YARN, ["install", verbose && "--verbose"].filter(Boolean), options).pipe(errorPipe()) - }, - { - title: "Add dependencies using Yarn", - skip: () => !deps.length, - task: () => - this.cliExeca.run(PackageManager.YARN, ["add", verbose && "--verbose", ...deps].filter(Boolean), options).pipe(errorPipe()) - }, - { - title: "Add devDependencies using Yarn", - skip: () => !devDeps.length, - task: () => - this.cliExeca - .run(PackageManager.YARN, ["add", "-D", verbose && "--verbose", ...devDeps].filter(Boolean), options) - .pipe(errorPipe()) - } - ]; - } + if (result && result.path) { + const pkgPath = dirname(result.path); + this.configuration.set("project.root", pkgPath); - protected installWithNpm({verbose}: any) { - const devDeps = mapPackagesWithInvalidVersion(this.devDependencies); - const deps = mapPackagesWithInvalidVersion(this.dependencies); - const options = { - cwd: this.dir, - env: { - ...process.env, - GH_TOKEN: this.GH_TOKEN - } - }; + const pkg = this.fs.readJsonSync(result.path, {encoding: "utf8"}); - return [ - { - title: "Installing dependencies using npm", - skip: () => { - return !this.reinstall; - }, - task: () => { - return this.cliExeca.run( - PackageManager.NPM, - ["install", "--no-production", "--legacy-peer-deps", verbose && "--verbose"].filter(Boolean), - options - ); - } - }, - { - title: "Add dependencies using npm", - skip: () => !deps.length, - task: () => - this.cliExeca.run( - PackageManager.NPM, - ["install", "--save", "--legacy-peer-deps", verbose && "--verbose", ...deps].filter(Boolean), - options - ) - }, - { - title: "Add devDependencies using npm", - skip: () => !devDeps.length, - task: () => - this.cliExeca.run( - PackageManager.NPM, - ["install", "--save-dev", "--legacy-peer-deps", verbose && "--verbose", ...devDeps].filter(Boolean), - options - ) + return {...this.getEmptyPackageJson(name), ...pkg} as any; } - ]; - } + } - protected installWithPnpm({verbose}: any) { - const devDeps = mapPackagesWithInvalidVersion(this.devDependencies); - const deps = mapPackagesWithInvalidVersion(this.dependencies); - const options = { - cwd: this.dir, - env: { - ...process.env, - GH_TOKEN: this.GH_TOKEN - } - }; + if (disableReadUpPkg && fileExists) { + const pkg = this.fs.readJsonSync(pkgPath, {encoding: "utf8"}); + this.configuration.set("project.root", pkgPath); - return [ - { - title: "Installing dependencies using pnpm", - skip: () => { - return !this.reinstall; - }, - task: () => this.cliExeca.run(PackageManager.PNPM, ["install", "--dev", verbose && "--verbose"].filter(Boolean), options) - }, - { - title: "Add dependencies using pnpm", - skip: () => !deps.length, - task: () => this.cliExeca.run(PackageManager.PNPM, ["add", "--save-prod", verbose && "--verbose", ...deps].filter(Boolean), options) - }, - { - title: "Add devDependencies using pnpm", - skip: () => !devDeps.length, - task: () => - this.cliExeca.run(PackageManager.PNPM, ["add", "--save-dev", verbose && "--verbose", ...devDeps].filter(Boolean), options) - } - ]; + return {...this.getEmptyPackageJson(name), ...pkg} as any; + } + + return this.getEmptyPackageJson(name); } - protected getPackageJson() { - return getPackageJson(this.configuration); + protected getEmptyPackageJson(name: string) { + return { + name, + version: "1.0.0", + description: "", + scripts: {}, + dependencies: {}, + devDependencies: {} + }; } } diff --git a/packages/cli-core/src/services/packageManagers/BaseManager.ts b/packages/cli-core/src/services/packageManagers/BaseManager.ts new file mode 100644 index 000000000..248450d1a --- /dev/null +++ b/packages/cli-core/src/services/packageManagers/BaseManager.ts @@ -0,0 +1,47 @@ +import {Inject} from "@tsed/di"; +import {Observable} from "rxjs"; +import execa from "execa"; +import {CliExeca} from "../CliExeca"; + +export type ManagerCmdOpts = {verbose?: boolean} & execa.Options; +export type ManagerCmdSyncOpts = {verbose?: boolean} & execa.SyncOptions; + +export abstract class BaseManager { + abstract readonly name: string; + abstract readonly cmd: string; + protected verboseOpt = "--verbose"; + + @Inject(CliExeca) + protected cliExeca: CliExeca; + + get runCmd() { + return `${this.cmd} run`; + } + + has() { + try { + this.cliExeca.runSync(this.cmd, ["--version"]); + + return true; + } catch (er) { + return false; + } + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async init(opts: ManagerCmdSyncOpts): Promise {} + + abstract install(options: ManagerCmdOpts): Observable; + + abstract add(deps: string[], options: ManagerCmdOpts): Observable; + + abstract addDev(deps: string[], options: ManagerCmdOpts): Observable; + + runScript(script: string, options: ManagerCmdOpts) { + return this.run("run", [script], options); + } + + protected run(cmd: string, args: any[], options: {verbose?: boolean} & execa.Options) { + return this.cliExeca.run(this.cmd, [cmd, options.verbose && this.verboseOpt, ...args].filter(Boolean) as string[], options); + } +} diff --git a/packages/cli-core/src/services/packageManagers/NpmManager.spec.ts b/packages/cli-core/src/services/packageManagers/NpmManager.spec.ts new file mode 100644 index 000000000..785d555ad --- /dev/null +++ b/packages/cli-core/src/services/packageManagers/NpmManager.spec.ts @@ -0,0 +1,68 @@ +import {CliPlatformTest} from "@tsed/cli-testing"; +import {NpmManager} from "./NpmManager"; +import {CliExeca} from "@tsed/cli-core"; + +async function getManagerFixture() { + const cliExeca = { + runSync: jest.fn(), + run: jest.fn() + }; + const manager = await CliPlatformTest.invoke(NpmManager, [ + { + token: CliExeca, + use: cliExeca + } + ]); + return {cliExeca, manager}; +} +describe("NpmManager", () => { + beforeEach(() => CliPlatformTest.create()); + afterEach(() => CliPlatformTest.reset()); + + describe("runCmd()", () => { + it("should return the runCmd", async () => { + const {manager} = await getManagerFixture(); + + const result = manager.runCmd; + + expect(result).toEqual("npm run"); + }); + }); + + describe("add()", () => { + it("should add dependencies", async () => { + const {cliExeca, manager} = await getManagerFixture(); + + await manager.add(["deps"], {}); + + expect(cliExeca.run).toHaveBeenCalledWith("npm", ["install", "--no-production", "--legacy-peer-deps", "--save", "deps"], {}); + }); + }); + describe("addDev()", () => { + it("should add dependencies", async () => { + const {cliExeca, manager} = await getManagerFixture(); + + await manager.addDev(["deps"], {}); + + expect(cliExeca.run).toHaveBeenCalledWith("npm", ["install", "--no-production", "--legacy-peer-deps", "--save-dev", "deps"], {}); + }); + }); + describe("install()", () => { + it("should run the install cmd", async () => { + const {cliExeca, manager} = await getManagerFixture(); + + await manager.install({}); + + expect(cliExeca.run).toHaveBeenCalledWith("npm", ["install", "--no-production", "--legacy-peer-deps"], {}); + }); + }); + describe("runScript()", () => { + it("should run script", async () => { + const {cliExeca, manager} = await getManagerFixture(); + + await manager.runScript("name", {}); + + expect(cliExeca.run).toHaveBeenCalledWith("npm", ["run", "name"], {}); + }); + }); +}); diff --git a/packages/cli-core/src/services/packageManagers/NpmManager.ts b/packages/cli-core/src/services/packageManagers/NpmManager.ts new file mode 100644 index 000000000..65701a606 --- /dev/null +++ b/packages/cli-core/src/services/packageManagers/NpmManager.ts @@ -0,0 +1,23 @@ +import {BaseManager, ManagerCmdOpts} from "./BaseManager"; +import {Injectable} from "@tsed/di"; +import {Observable} from "rxjs"; + +@Injectable({ + type: "package:manager" +}) +export class NpmManager extends BaseManager { + readonly name: string = "npm"; + readonly cmd: string = "npm"; + + add(deps: string[], options: ManagerCmdOpts): Observable { + return this.run("install", ["--no-production", "--legacy-peer-deps", "--save", ...deps], options); + } + + addDev(deps: string[], options: ManagerCmdOpts): Observable { + return this.run("install", ["--no-production", "--legacy-peer-deps", "--save-dev", ...deps], options); + } + + install(options: ManagerCmdOpts): Observable { + return this.run("install", ["--no-production", "--legacy-peer-deps"], options); + } +} diff --git a/packages/cli-core/src/services/packageManagers/PNpmManager.spec.ts b/packages/cli-core/src/services/packageManagers/PNpmManager.spec.ts new file mode 100644 index 000000000..6e3fdda62 --- /dev/null +++ b/packages/cli-core/src/services/packageManagers/PNpmManager.spec.ts @@ -0,0 +1,69 @@ +import {CliPlatformTest} from "@tsed/cli-testing"; +import {CliExeca} from "@tsed/cli-core"; +import {PNpmManager} from "./PNpmManager"; + +async function getManagerFixture() { + const cliExeca = { + runSync: jest.fn(), + run: jest.fn() + }; + const manager = await CliPlatformTest.invoke(PNpmManager, [ + { + token: CliExeca, + use: cliExeca + } + ]); + return {cliExeca, manager}; +} + +describe("PNpmManager", () => { + beforeEach(() => CliPlatformTest.create()); + afterEach(() => CliPlatformTest.reset()); + + describe("runCmd()", () => { + it("should return the runCmd", async () => { + const {manager} = await getManagerFixture(); + + const result = manager.runCmd; + + expect(result).toEqual("pnpm run"); + }); + }); + + describe("add()", () => { + it("should add dependencies", async () => { + const {cliExeca, manager} = await getManagerFixture(); + + await manager.add(["deps"], {}); + + expect(cliExeca.run).toHaveBeenCalledWith("pnpm", ["add", "--save-prod", "deps"], {}); + }); + }); + describe("addDev()", () => { + it("should add dependencies", async () => { + const {cliExeca, manager} = await getManagerFixture(); + + await manager.addDev(["deps"], {}); + + expect(cliExeca.run).toHaveBeenCalledWith("pnpm", ["add", "--save-dev", "deps"], {}); + }); + }); + describe("install()", () => { + it("should run the install cmd", async () => { + const {cliExeca, manager} = await getManagerFixture(); + + await manager.install({}); + + expect(cliExeca.run).toHaveBeenCalledWith("pnpm", ["install", "--dev"], {}); + }); + }); + describe("runScript()", () => { + it("should run script", async () => { + const {cliExeca, manager} = await getManagerFixture(); + + await manager.runScript("name", {}); + + expect(cliExeca.run).toHaveBeenCalledWith("pnpm", ["run", "name"], {}); + }); + }); +}); diff --git a/packages/cli-core/src/services/packageManagers/PNpmManager.ts b/packages/cli-core/src/services/packageManagers/PNpmManager.ts new file mode 100644 index 000000000..6520f064e --- /dev/null +++ b/packages/cli-core/src/services/packageManagers/PNpmManager.ts @@ -0,0 +1,23 @@ +import {Injectable} from "@tsed/di"; +import {Observable} from "rxjs"; +import {BaseManager, ManagerCmdOpts} from "./BaseManager"; + +@Injectable({ + type: "package:manager" +}) +export class PNpmManager extends BaseManager { + readonly name = "pnpm"; + readonly cmd = "pnpm"; + + add(deps: string[], options: ManagerCmdOpts): Observable { + return this.run("add", ["--save-prod", ...deps], options); + } + + addDev(deps: string[], options: ManagerCmdOpts): Observable { + return this.run("add", ["--save-dev", ...deps], options); + } + + install(options: ManagerCmdOpts): Observable { + return this.run("install", ["--dev"].filter(Boolean), options); + } +} diff --git a/packages/cli-core/src/services/packageManagers/YarnBerryManager.spec.ts b/packages/cli-core/src/services/packageManagers/YarnBerryManager.spec.ts new file mode 100644 index 000000000..7a30ba183 --- /dev/null +++ b/packages/cli-core/src/services/packageManagers/YarnBerryManager.spec.ts @@ -0,0 +1,69 @@ +import {CliPlatformTest} from "@tsed/cli-testing"; +import {CliExeca} from "@tsed/cli-core"; +import {YarnBerryManager} from "./YarnBerryManager"; + +async function getManagerFixture() { + const cliExeca = { + runSync: jest.fn(), + run: jest.fn() + }; + const manager = await CliPlatformTest.invoke(YarnBerryManager, [ + { + token: CliExeca, + use: cliExeca + } + ]); + return {cliExeca, manager}; +} + +describe("YarnBerryManager", () => { + beforeEach(() => CliPlatformTest.create()); + afterEach(() => CliPlatformTest.reset()); + + describe("runCmd()", () => { + it("should return the runCmd", async () => { + const {manager} = await getManagerFixture(); + + const result = manager.runCmd; + + expect(result).toEqual("yarn run"); + }); + }); + + describe("add()", () => { + it("should add dependencies", async () => { + const {cliExeca, manager} = await getManagerFixture(); + + await manager.add(["deps"], {}); + + expect(cliExeca.run).toHaveBeenCalledWith("yarn", ["add", "deps"], {}); + }); + }); + describe("addDev()", () => { + it("should add dependencies", async () => { + const {cliExeca, manager} = await getManagerFixture(); + + await manager.addDev(["deps"], {}); + + expect(cliExeca.run).toHaveBeenCalledWith("yarn", ["add", "-D", "deps"], {}); + }); + }); + describe("install()", () => { + it("should run the install cmd", async () => { + const {cliExeca, manager} = await getManagerFixture(); + + await manager.install({}); + + expect(cliExeca.run).toHaveBeenCalledWith("yarn", ["install"], {}); + }); + }); + describe("runScript()", () => { + it("should run script", async () => { + const {cliExeca, manager} = await getManagerFixture(); + + await manager.runScript("name", {}); + + expect(cliExeca.run).toHaveBeenCalledWith("yarn", ["run", "name"], {}); + }); + }); +}); diff --git a/packages/cli-core/src/services/packageManagers/YarnBerryManager.ts b/packages/cli-core/src/services/packageManagers/YarnBerryManager.ts new file mode 100644 index 000000000..2810c9a29 --- /dev/null +++ b/packages/cli-core/src/services/packageManagers/YarnBerryManager.ts @@ -0,0 +1,42 @@ +import {Inject, Injectable} from "@tsed/di"; +import execa from "execa"; +import {Observable} from "rxjs"; +import {BaseManager, ManagerCmdOpts, ManagerCmdSyncOpts} from "./BaseManager"; +import {join} from "path"; +import {CliYaml} from "../CliYaml"; + +@Injectable({ + type: "package:manager" +}) +export class YarnBerryManager extends BaseManager { + readonly name = "yarn_berry"; + readonly cmd = "yarn"; + + @Inject() + protected cliYaml: CliYaml; + + async init(options: ManagerCmdSyncOpts) { + // init yarn v1 + this.install(options); + + // then switch write file + await this.cliYaml.write(join(options.cwd!, ".yarnrc.yml"), { + nodeLinker: "node-modules" + }); + + // then switch to berry + this.cliExeca.runSync(this.cmd, ["set", "version", "berry"]); + } + + add(deps: string[], options: ManagerCmdOpts) { + return this.run("add", deps, options); + } + + addDev(deps: string[], options: ManagerCmdOpts) { + return this.run("add", ["-D", ...deps], options); + } + + install(options: {verbose?: boolean} & execa.Options): Observable { + return this.run("install", [options.verbose && "--verbose"], options); + } +} diff --git a/packages/cli-core/src/services/packageManagers/YarnManager.spec.ts b/packages/cli-core/src/services/packageManagers/YarnManager.spec.ts new file mode 100644 index 000000000..b4457a45e --- /dev/null +++ b/packages/cli-core/src/services/packageManagers/YarnManager.spec.ts @@ -0,0 +1,69 @@ +import {CliPlatformTest} from "@tsed/cli-testing"; +import {CliExeca} from "@tsed/cli-core"; +import {YarnManager} from "./YarnManager"; + +async function getManagerFixture() { + const cliExeca = { + runSync: jest.fn(), + run: jest.fn() + }; + const manager = await CliPlatformTest.invoke(YarnManager, [ + { + token: CliExeca, + use: cliExeca + } + ]); + return {cliExeca, manager}; +} + +describe("YarnManager", () => { + beforeEach(() => CliPlatformTest.create()); + afterEach(() => CliPlatformTest.reset()); + + describe("runCmd()", () => { + it("should return the runCmd", async () => { + const {manager} = await getManagerFixture(); + + const result = manager.runCmd; + + expect(result).toEqual("yarn run"); + }); + }); + + describe("add()", () => { + it("should add dependencies", async () => { + const {cliExeca, manager} = await getManagerFixture(); + + await manager.add(["deps"], {}); + + expect(cliExeca.run).toHaveBeenCalledWith("yarn", ["add", "deps"], {}); + }); + }); + describe("addDev()", () => { + it("should add dependencies", async () => { + const {cliExeca, manager} = await getManagerFixture(); + + await manager.addDev(["deps"], {}); + + expect(cliExeca.run).toHaveBeenCalledWith("yarn", ["add", "-D", "deps"], {}); + }); + }); + describe("install()", () => { + it("should run the install cmd", async () => { + const {cliExeca, manager} = await getManagerFixture(); + + await manager.install({}); + + expect(cliExeca.run).toHaveBeenCalledWith("yarn", ["install"], {}); + }); + }); + describe("runScript()", () => { + it("should run script", async () => { + const {cliExeca, manager} = await getManagerFixture(); + + await manager.runScript("name", {}); + + expect(cliExeca.run).toHaveBeenCalledWith("yarn", ["run", "name"], {}); + }); + }); +}); diff --git a/packages/cli-core/src/services/packageManagers/YarnManager.ts b/packages/cli-core/src/services/packageManagers/YarnManager.ts new file mode 100644 index 000000000..038d7fe0e --- /dev/null +++ b/packages/cli-core/src/services/packageManagers/YarnManager.ts @@ -0,0 +1,24 @@ +import {Injectable} from "@tsed/di"; +import execa from "execa"; +import {Observable} from "rxjs"; +import {BaseManager, ManagerCmdOpts} from "./BaseManager"; + +@Injectable({ + type: "package:manager" +}) +export class YarnManager extends BaseManager { + readonly name = "yarn"; + readonly cmd = "yarn"; + + add(deps: string[], options: ManagerCmdOpts) { + return this.run("add", deps, options); + } + + addDev(deps: string[], options: ManagerCmdOpts) { + return this.run("add", ["-D", ...deps], options); + } + + install(options: {verbose?: boolean} & execa.Options): Observable { + return this.run("install", [options.verbose && "--verbose"], options); + } +} diff --git a/packages/cli-core/src/utils/getCommandMetadata.spec.ts b/packages/cli-core/src/utils/getCommandMetadata.spec.ts index 81fbe6fb9..ee027907a 100644 --- a/packages/cli-core/src/utils/getCommandMetadata.spec.ts +++ b/packages/cli-core/src/utils/getCommandMetadata.spec.ts @@ -16,6 +16,8 @@ describe("getCommandMetadata", () => { description: "description", name: "name", alias: "g", + disableReadUpPkg: false, + enableFeatures: [], options: {} }); }); diff --git a/packages/cli-core/src/utils/getCommandMetadata.ts b/packages/cli-core/src/utils/getCommandMetadata.ts index 6f3d7c052..59494cd91 100644 --- a/packages/cli-core/src/utils/getCommandMetadata.ts +++ b/packages/cli-core/src/utils/getCommandMetadata.ts @@ -1,15 +1,19 @@ import {Store, Type} from "@tsed/core"; import {CommandStoreKeys} from "../domains/CommandStoreKeys"; import {CommandParameters} from "../interfaces/CommandParameters"; +import {CommandMetadata} from "../interfaces/CommandMetadata"; -export function getCommandMetadata(token: Type) { +export function getCommandMetadata(token: Type): CommandMetadata { const { name, alias, args = {}, allowUnknownOption, description, - options = {} + options = {}, + enableFeatures, + disableReadUpPkg, + ...opts } = Store.from(token)?.get(CommandStoreKeys.COMMAND) as CommandParameters; return { @@ -18,6 +22,9 @@ export function getCommandMetadata(token: Type) { args, description, options, - allowUnknownOption: !!allowUnknownOption + allowUnknownOption: !!allowUnknownOption, + enableFeatures: enableFeatures || [], + disableReadUpPkg: !!disableReadUpPkg, + ...opts }; } diff --git a/packages/cli-core/src/utils/getPackageJson.ts b/packages/cli-core/src/utils/getPackageJson.ts deleted file mode 100644 index 063dd472c..000000000 --- a/packages/cli-core/src/utils/getPackageJson.ts +++ /dev/null @@ -1,39 +0,0 @@ -import {Configuration} from "@tsed/di"; -import readPkgUp from "read-pkg-up"; -import {dirname, join} from "path"; -import Fs from "fs-extra"; - -function useReadPkgUp(configuration: Configuration) { - return !(process.argv.includes("init") && !Fs.existsSync(join(String(configuration.project?.rootDir), "package.json"))); -} - -function getEmptyPackageJson(configuration: Configuration) { - return { - name: configuration.name, - version: "1.0.0", - description: "", - scripts: {}, - dependencies: {}, - devDependencies: {} - }; -} - -export function getPackageJson(configuration: Configuration) { - if (useReadPkgUp(configuration)) { - const result = readPkgUp.sync({ - cwd: configuration.project?.rootDir - }); - - if (result && result.path) { - const pkgPath = dirname(result.path); - - configuration.set("project.root", pkgPath); - - const pkg = Fs.readJsonSync(result.path, {encoding: "utf8"}); - - return {...getEmptyPackageJson(configuration), ...pkg} as any; - } - } - - return getEmptyPackageJson(configuration); -} diff --git a/packages/cli-core/src/utils/index.ts b/packages/cli-core/src/utils/index.ts index 9cb88537f..c02bdfe83 100644 --- a/packages/cli-core/src/utils/index.ts +++ b/packages/cli-core/src/utils/index.ts @@ -6,6 +6,6 @@ export * from "./mapCommanderArgs"; export * from "./mapCommanderOptions"; export * from "./parseOption"; export * from "./createTasksRunner"; -export * from "./getPackageJson"; +export * from "./resolveConfiguration"; export * from "./getTemplateDirectory"; export {default as filedirname} from "filedirname"; diff --git a/packages/cli-core/src/utils/resolveConfiguration.ts b/packages/cli-core/src/utils/resolveConfiguration.ts new file mode 100644 index 000000000..c710b5e09 --- /dev/null +++ b/packages/cli-core/src/utils/resolveConfiguration.ts @@ -0,0 +1,24 @@ +import {TokenProvider} from "@tsed/di"; +import {getCommandMetadata} from "@tsed/cli-core"; +import {getValue} from "@tsed/core"; + +export function resolveConfiguration(settings: any) { + const argv = getValue(settings, "argv", process.argv); + const configResolvers = getValue(settings, "configResolvers", []); + const commands = getValue(settings, "commands", []); + + const commandOpts = commands + .map((token: TokenProvider) => getCommandMetadata(token)) + .find((commandOpts: any) => argv.includes(commandOpts.name)); + + settings = { + ...settings, + commands, + command: commandOpts || {}, + argv + }; + + configResolvers.map((resolver: (settings: any) => void) => resolver(settings)); + + return settings; +} diff --git a/packages/cli-plugin-jest/test/integrations/generate/generate.controller.integration.spec.ts b/packages/cli-plugin-jest/test/integrations/generate/generate.controller.integration.spec.ts index 6bd65b13b..9e7165adc 100644 --- a/packages/cli-plugin-jest/test/integrations/generate/generate.controller.integration.spec.ts +++ b/packages/cli-plugin-jest/test/integrations/generate/generate.controller.integration.spec.ts @@ -13,9 +13,7 @@ describe("Generate Controller", () => { afterEach(() => CliPlatformTest.reset()); it("should generate the template with the right options (simple path)", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -24,7 +22,7 @@ describe("Generate Controller", () => { devDependencies: {} }); - await cliService.exec("generate", { + await CliPlatformTest.exec("generate", { rootDir: "./project-data", type: "controller", name: "Test", @@ -45,9 +43,7 @@ describe("Generate Controller", () => { expect(result).toContain('import { TestController } from "./TestController";'); }); it("should generate the template with the right options (complex path)", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -56,7 +52,7 @@ describe("Generate Controller", () => { devDependencies: {} }); - await cliService.exec("generate", { + await CliPlatformTest.exec("generate", { rootDir: "./project-data", type: "controller", name: "users/User", diff --git a/packages/cli-plugin-mongoose/test/integrations/generate/generate.model.integration.spec.ts b/packages/cli-plugin-mongoose/test/integrations/generate/generate.model.integration.spec.ts index 3ac6b919d..015dd1ce4 100644 --- a/packages/cli-plugin-mongoose/test/integrations/generate/generate.model.integration.spec.ts +++ b/packages/cli-plugin-mongoose/test/integrations/generate/generate.model.integration.spec.ts @@ -13,9 +13,7 @@ describe("Generate Model", () => { afterEach(() => CliPlatformTest.reset()); it("should generate the template", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -24,7 +22,7 @@ describe("Generate Model", () => { devDependencies: {} }); - await cliService.exec("generate", { + await CliPlatformTest.exec("generate", { rootDir: "./project-data", type: "mongoose:model", name: "Product" diff --git a/packages/cli-plugin-mongoose/test/integrations/generate/generate.schema.integration.spec.ts b/packages/cli-plugin-mongoose/test/integrations/generate/generate.schema.integration.spec.ts index dedb013aa..e54ab4fa4 100644 --- a/packages/cli-plugin-mongoose/test/integrations/generate/generate.schema.integration.spec.ts +++ b/packages/cli-plugin-mongoose/test/integrations/generate/generate.schema.integration.spec.ts @@ -1,5 +1,4 @@ import {GenerateCmd} from "@tsed/cli"; -import {CliService, ProjectPackageJson} from "@tsed/cli-core"; import {CliPlatformTest, FakeCliFs} from "@tsed/cli-testing"; import {TEMPLATE_DIR} from "../../../src"; @@ -13,10 +12,7 @@ describe("Generate Schema", () => { afterEach(() => CliPlatformTest.reset()); it("should generate the template", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -25,7 +21,7 @@ describe("Generate Schema", () => { devDependencies: {} }); - await cliService.exec("generate", { + await CliPlatformTest.exec("generate", { rootDir: "./project-data", type: "mongoose:schema", name: "Product" @@ -34,8 +30,8 @@ describe("Generate Schema", () => { expect(FakeCliFs.getKeys()).toEqual(["project-name/src/models", "project-name/src/models/ProductSchema.ts"]); const result = FakeCliFs.entries.get("project-name/src/models/ProductSchema.ts"); - expect(result).toContain('import { Property } from "@tsed/schema";'); - expect(result).toContain('import { Schema } from "@tsed/mongoose";'); + expect(result).toContain("import { Property } from \"@tsed/schema\";"); + expect(result).toContain("import { Schema } from \"@tsed/mongoose\";"); expect(result).toContain("@Schema()"); expect(result).toContain("export class ProductSchema {"); }); diff --git a/packages/cli-plugin-oidc-provider/test/init/init.integration.spec.ts b/packages/cli-plugin-oidc-provider/test/init/init.integration.spec.ts index c18e38f70..600e117da 100644 --- a/packages/cli-plugin-oidc-provider/test/init/init.integration.spec.ts +++ b/packages/cli-plugin-oidc-provider/test/init/init.integration.spec.ts @@ -1,38 +1,19 @@ -import {CliService, ProjectPackageJson} from "@tsed/cli-core"; import {CliPlatformTest, FakeCliFs} from "@tsed/cli-testing"; -import {ensureDirSync, existsSync, readFileSync, writeFileSync} from "fs-extra"; import {InitCmd, TEMPLATE_DIR} from "@tsed/cli"; -import {dirname} from "path"; import "@tsed/cli-plugin-oidc-provider"; -import filedirname from "filedirname"; - -const [, dir] = filedirname(); - -function readFile(file: string, content: string, rewrite = false) { - const path = `${dir}/${file}` - - ensureDirSync(dirname(path)) - - if (!existsSync(path) || rewrite) { - writeFileSync(path, content, {encoding: "utf8"}); - } - - return readFileSync(path, {encoding: "utf8"}); -} describe("Init OIDC Provider project", () => { beforeEach(() => CliPlatformTest.bootstrap({ templateDir: TEMPLATE_DIR, - commands: [InitCmd] + commands: [InitCmd], + args: ["init"] }) ); afterEach(() => CliPlatformTest.reset()); it("should generate a project with oidc", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -41,7 +22,7 @@ describe("Init OIDC Provider project", () => { devDependencies: {} }); - await cliService.exec("init", { + await CliPlatformTest.exec("init", { platform: "express", rootDir: "./project-data", projectName: "project-data", @@ -64,9 +45,7 @@ describe("Init OIDC Provider project", () => { expect(configContent).toContain("path: \"/oidc\""); }); it("should generate a project with oidc and swagger", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -75,7 +54,7 @@ describe("Init OIDC Provider project", () => { devDependencies: {} }); - await cliService.exec("init", { + await CliPlatformTest.exec("init", { platform: "express", rootDir: "./project-data", projectName: "project-data", diff --git a/packages/cli-plugin-typegraphql/test/init/init.integration.spec.ts b/packages/cli-plugin-typegraphql/test/init/init.integration.spec.ts index 3071bba45..b01f2a55c 100644 --- a/packages/cli-plugin-typegraphql/test/init/init.integration.spec.ts +++ b/packages/cli-plugin-typegraphql/test/init/init.integration.spec.ts @@ -16,9 +16,7 @@ describe("Init TypeGraphQL project", () => { afterEach(() => CliPlatformTest.reset()); it("should generate a project with typegraphql", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -27,7 +25,7 @@ describe("Init TypeGraphQL project", () => { devDependencies: {} }); - await cliService.exec("init", { + await CliPlatformTest.exec("init", { platform: "express", convention: "default", rootDir: "./project-data", diff --git a/packages/cli-plugin-typeorm/test/integrations/generate/generate.controller.integration.spec.ts b/packages/cli-plugin-typeorm/test/integrations/generate/generate.controller.integration.spec.ts index 5238c8cb0..091bd05c2 100644 --- a/packages/cli-plugin-typeorm/test/integrations/generate/generate.controller.integration.spec.ts +++ b/packages/cli-plugin-typeorm/test/integrations/generate/generate.controller.integration.spec.ts @@ -29,9 +29,7 @@ describe("Generate DataSource", () => { afterEach(() => CliPlatformTest.reset()); it("should generate the template with the right options (simple path)", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -40,7 +38,7 @@ describe("Generate DataSource", () => { devDependencies: {} }); - await cliService.exec("generate", { + await CliPlatformTest.exec("generate", { rootDir: "./project-data", type: "typeorm:datasource", name: "Test", diff --git a/packages/cli-plugin-typeorm/test/integrations/init/init.integration.spec.ts b/packages/cli-plugin-typeorm/test/integrations/init/init.integration.spec.ts index f61db37dc..7ff4fc8aa 100644 --- a/packages/cli-plugin-typeorm/test/integrations/init/init.integration.spec.ts +++ b/packages/cli-plugin-typeorm/test/integrations/init/init.integration.spec.ts @@ -24,23 +24,15 @@ describe("TypeORM: Init cmd", () => { beforeEach(() => { return CliPlatformTest.bootstrap({ templateDir: TEMPLATE_DIR, - commands: [InitCmd] + commands: [InitCmd], + argv: ["init"] }); } ); afterEach(() => CliPlatformTest.reset()); it("should generate a project with the right options", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - - jest.spyOn(projectPackageJson as any, "getPackageJson").mockReturnValue({ - dependencies: {}, - devDependencies: {}, - scripts: {} - }); - - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -49,7 +41,7 @@ describe("TypeORM: Init cmd", () => { devDependencies: {} }); - await cliService.exec("init", { + await CliPlatformTest.exec("init", { verbose: false, root: ".", tsedVersion: "5.58.1", @@ -67,7 +59,7 @@ describe("TypeORM: Init cmd", () => { features: [ FeatureType.DB, FeatureType.TYPEORM, - FeatureType.TYPEORM_MYSQL, + FeatureType.TYPEORM_MYSQL ], srcDir: "src", pnpm: false, @@ -114,16 +106,7 @@ describe("TypeORM: Init cmd", () => { expect(datasource).toEqual(readFile("data/MysqlDatasource.ts.txt", datasource, false)); }); it("should not generate database if any option is selected", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - - jest.spyOn(projectPackageJson as any, "getPackageJson").mockReturnValue({ - dependencies: {}, - devDependencies: {}, - scripts: {} - }); - - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -132,7 +115,7 @@ describe("TypeORM: Init cmd", () => { devDependencies: {} }); - await cliService.exec("init", { + await CliPlatformTest.exec("init", { verbose: false, root: ".", tsedVersion: "5.58.1", @@ -147,8 +130,7 @@ describe("TypeORM: Init cmd", () => { db: true, typeorm: true, mysql: true, - features: [ - ], + features: [], srcDir: "src", pnpm: false, npm: false, diff --git a/packages/cli-testing/src/CliPlatformTest.ts b/packages/cli-testing/src/CliPlatformTest.ts index defc1c4ff..cb24fedcc 100644 --- a/packages/cli-testing/src/CliPlatformTest.ts +++ b/packages/cli-testing/src/CliPlatformTest.ts @@ -7,13 +7,18 @@ import { createInjector, DITest, Env, + getCommandMetadata, InjectorService, + ProjectPackageJson, + resolveConfiguration, TokenProvider } from "@tsed/cli-core"; import {Type} from "@tsed/core"; import {FakeCliExeca} from "./FakeCliExeca"; import {FakeCliFs} from "./FakeCliFs"; import {FakeCliHttpClient} from "./FakeCliHttpClient"; +import {DIContext, runInContext} from "@tsed/di"; +import {v4} from "uuid"; export interface InvokeOptions { token: TokenProvider; @@ -22,7 +27,7 @@ export interface InvokeOptions { export class CliPlatformTest extends DITest { static async bootstrap(options: Partial = {}) { - DITest.injector = CliPlatformTest.createInjector({ + options = resolveConfiguration({ name: "tsed", project: { rootDir: options.rootDir || "./project-name", @@ -33,6 +38,8 @@ export class CliPlatformTest extends DITest { ...options }); + DITest.injector = CliPlatformTest.createInjector(options); + DITest.injector .addProvider(CliHttpClient, { useClass: FakeCliHttpClient @@ -52,11 +59,13 @@ export class CliPlatformTest extends DITest { } static async create(options: Partial = {}, rootModule: Type = CliCore) { - DITest.injector = CliPlatformTest.createInjector({ + options = resolveConfiguration({ name: "tsed", ...options }); + DITest.injector = CliPlatformTest.createInjector(options); + DITest.injector.addProvider(CliCore, { useClass: rootModule }); @@ -78,7 +87,8 @@ export class CliPlatformTest extends DITest { rootDir: "./tmp", srcDir: "src", ...(options.project || {}) - } + }, + disableReadUpPkg: true }); injector.settings.env = Env.TEST; @@ -113,4 +123,33 @@ export class CliPlatformTest extends DITest { return await func(...deps); }; } + + static setPackageJson(pkg: any) { + const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); + + (projectPackageJson as any).setRaw(pkg); + } + + /** + * Invoke command with a new context without running prompts + * @param cmdName + * @param initialData + */ + static async exec(cmdName: string, initialData: any) { + const $ctx = new DIContext({ + id: v4(), + injector: this.injector, + logger: this.injector.logger + }); + + const metadata = this.injector.settings + .get("commands") + .map((token: TokenProvider) => getCommandMetadata(token)) + .find((commandOpts: any) => cmdName === commandOpts.name); + + $ctx.set("data", initialData); + $ctx.set("command", metadata); + + return runInContext($ctx, () => this.injector.get(CliService)!.exec(cmdName, initialData, $ctx)); + } } diff --git a/packages/cli-testing/src/FakeCliFs.ts b/packages/cli-testing/src/FakeCliFs.ts index d3a6b2f79..5b03c4b72 100644 --- a/packages/cli-testing/src/FakeCliFs.ts +++ b/packages/cli-testing/src/FakeCliFs.ts @@ -9,6 +9,8 @@ export class FakeCliFs { return normalizePath(Array.from(FakeCliFs.entries.keys()).sort((a, b) => (a < b ? -1 : 1))); } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore findUpFile() { return null; } @@ -22,6 +24,23 @@ export class FakeCliFs { return FakeCliFs.entries.get(normalizePath(file))!; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + readFileSync(file: string | Buffer | number, encoding?: any): string { + return FakeCliFs.entries.get(normalizePath(file))!; + } + + async readJson(file: string | Buffer | number, encoding?: any): Promise { + const content = await this.readFile(file, encoding); + + return JSON.parse(content); + } + + readJsonSync(file: string | Buffer | number, encoding?: any): Promise { + const content = this.readFileSync(file, encoding); + + return JSON.parse(content); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars writeFileSync(path: PathLike | number, data: any, options?: WriteFileOptions): void { FakeCliFs.entries.set(normalizePath(path), data); diff --git a/packages/cli/src/Cli.ts b/packages/cli/src/Cli.ts index 4bce8bf92..0d5d26105 100644 --- a/packages/cli/src/Cli.ts +++ b/packages/cli/src/Cli.ts @@ -6,8 +6,6 @@ import alias from "module-alias"; import {PKG, TEMPLATE_DIR} from "./constants"; import commands from "./commands"; import {ArchitectureConvention, ProjectConvention} from "./interfaces"; -import {InitCmdContext} from "./commands/init/interfaces/InitCmdContext"; -import {GenerateCmdContext} from "./commands/generate/GenerateCmd"; export class Cli extends CliCore { static defaults = { @@ -70,15 +68,16 @@ export class Cli extends CliCore { return super.bootstrap(opts, Cli); } - static async dispatch(cmd: "init", context: InitCmdContext): Promise; - static async dispatch(cmd: "generate", context: GenerateCmdContext): Promise; - static async dispatch(cmd: string, context: any) { - this.createAliases(); - - const cli = await this.create({...Cli.defaults} as any, Cli); - - await cli.cliService.dispatch(cmd, context); - } + // + // static async dispatch(cmd: "init", context: InitCmdContext): Promise; + // static async dispatch(cmd: "generate", context: GenerateCmdContext): Promise; + // static async dispatch(cmd: string, context: any) { + // this.createAliases(); + // + // const cli = await this.create({...Cli.defaults} as any, Cli); + // + // await cli.cliService.dispatch(cmd, context); + // } static createAliases() { alias.addAliases({ diff --git a/packages/cli/src/commands/init/InitCmd.spec.ts b/packages/cli/src/commands/init/InitCmd.spec.ts index 6fe5d9784..e4a30819e 100644 --- a/packages/cli/src/commands/init/InitCmd.spec.ts +++ b/packages/cli/src/commands/init/InitCmd.spec.ts @@ -5,7 +5,7 @@ describe("InitCmd", () => { describe("checkPrecondition()", () => { it("should throw error (platform)", () => { const result = catchError(() => { - InitCmd.checkPrecondition({ + InitCmd.checkPrecondition([], { platform: "wrong" } as any); }); @@ -14,7 +14,7 @@ describe("InitCmd", () => { it("should throw error (architecture)", () => { const result = catchError(() => { - InitCmd.checkPrecondition({ + InitCmd.checkPrecondition([], { architecture: "wrong" } as any); }); @@ -23,7 +23,7 @@ describe("InitCmd", () => { it("should throw error (convention)", () => { const result = catchError(() => { - InitCmd.checkPrecondition({ + InitCmd.checkPrecondition([], { convention: "wrong" } as any); }); @@ -32,7 +32,7 @@ describe("InitCmd", () => { it("should not throw error (package manager)", () => { const result = catchError(() => { - InitCmd.checkPrecondition({ + InitCmd.checkPrecondition(["npm"], { packageManager: "npm" } as any); }); @@ -41,7 +41,7 @@ describe("InitCmd", () => { it("should throw error (package manager)", () => { const result = catchError(() => { - InitCmd.checkPrecondition({ + InitCmd.checkPrecondition(["yarn", "npm", "pnpm"], { packageManager: "unknown" } as any); }); @@ -50,7 +50,8 @@ describe("InitCmd", () => { it("should throw error (features)", () => { const result = catchError(() => { - InitCmd.checkPrecondition({ + InitCmd.checkPrecondition(["yarn", "npm", "pnpm"], { + packageManager: "yarn", features: ["wrong"] } as any); }); diff --git a/packages/cli/src/commands/init/InitCmd.ts b/packages/cli/src/commands/init/InitCmd.ts index 48859bc60..996cf6a37 100644 --- a/packages/cli/src/commands/init/InitCmd.ts +++ b/packages/cli/src/commands/init/InitCmd.ts @@ -87,7 +87,8 @@ import {fillImports} from "../../utils/fillImports"; defaultValue: false, description: "Skip the prompt." } - } + }, + disableReadUpPkg: true }) export class InitCmd implements CommandProvider { @Configuration() @@ -120,7 +121,7 @@ export class InitCmd implements CommandProvider { @Inject() protected fs: CliFs; - static checkPrecondition(ctx: InitCmdContext) { + static checkPrecondition(availablePackageManagers: string[], ctx: InitCmdContext) { const isValid = (types: any, value: any) => (value ? Object.values(types).includes(value) : true); if (!isValid(PlatformType, ctx.platform)) { @@ -137,10 +138,8 @@ export class InitCmd implements CommandProvider { throw new Error(`Invalid selected convention: ${ctx.convention}. Possible values: ${Object.values(ProjectConvention).join(", ")}.`); } - if (!isValid(PackageManager, ctx.packageManager)) { - throw new Error( - `Invalid selected package manager: ${ctx.packageManager}. Possible values: ${Object.values(PackageManager).join(", ")}.` - ); + if (!availablePackageManagers.includes(ctx.packageManager)) { + throw new Error(`Invalid selected package manager: ${ctx.packageManager}. Possible values: ${availablePackageManagers.join(", ")}.`); } if (ctx.features) { @@ -172,6 +171,8 @@ export class InitCmd implements CommandProvider { return []; } + const packageManagers = this.packageJson.availablePackageManagers; + return [ { type: "input", @@ -183,7 +184,7 @@ export class InitCmd implements CommandProvider { return paramCase(input); } }, - ...getFeaturesPrompt(initialOptions) + ...getFeaturesPrompt(packageManagers, initialOptions) ]; } @@ -211,11 +212,6 @@ export class InitCmd implements CommandProvider { ctx.convention && this.packageJson.setPreference("convention", ctx.convention); ctx.GH_TOKEN && this.packageJson.setGhToken(ctx.GH_TOKEN); - this.addDependencies(ctx); - this.addDevDependencies(ctx); - this.addScripts(ctx); - this.addFeatures(ctx); - await createTasksRunner( [ { @@ -226,6 +222,17 @@ export class InitCmd implements CommandProvider { baseDir: "/init" }) }, + { + title: "Initialize package.json", + task: async () => { + await this.packageJson.init(ctx); + + this.addDependencies(ctx); + this.addDevDependencies(ctx); + this.addScripts(ctx); + this.addFeatures(ctx); + } + }, { title: "Install plugins", task: createSubTasks(() => this.packageJson.install(ctx), {...ctx, concurrent: false}) @@ -244,7 +251,7 @@ export class InitCmd implements CommandProvider { } async $exec(ctx: InitCmdContext) { - InitCmd.checkPrecondition(ctx); + InitCmd.checkPrecondition(this.packageJson.availablePackageManagers, ctx); const subTasks = [ ...(await this.cliService.getTasks("generate", { diff --git a/packages/cli/src/commands/init/config/FeaturesPrompt.ts b/packages/cli/src/commands/init/config/FeaturesPrompt.ts index 0adb013db..e71c450ec 100644 --- a/packages/cli/src/commands/init/config/FeaturesPrompt.ts +++ b/packages/cli/src/commands/init/config/FeaturesPrompt.ts @@ -1,4 +1,3 @@ -import {PackageManager} from "@tsed/cli-core"; import {isPlatform} from "../utils/isPlatform"; import {hasFeature, hasValue} from "../utils/hasFeature"; import {InitOptions} from "../interfaces/InitOptions"; @@ -291,21 +290,25 @@ export const FeaturesMap: Record = { "webpack-cli": "latest" } }, - [PackageManager.YARN]: { + yarn: { name: "Yarn", checked: true }, - [PackageManager.NPM]: { + yarn_berry: { + name: "Yarn Berry", + checked: false + }, + npm: { name: "NPM", checked: false }, - [PackageManager.PNPM]: { + pnpm: { name: "PNPM - experimental", checked: false } }; -export const FeaturesPrompt = () => [ +export const FeaturesPrompt = (availablePackageManagers: string[]) => [ { message: "Choose the target platform:", type: "list", @@ -416,6 +419,6 @@ export const FeaturesPrompt = () => [ message: "Choose the package manager:", type: "list", name: "packageManager", - choices: [PackageManager.YARN, PackageManager.NPM, PackageManager.PNPM] + choices: availablePackageManagers } ]; diff --git a/packages/cli/src/commands/init/prompts/getFeaturesPrompt.spec.ts b/packages/cli/src/commands/init/prompts/getFeaturesPrompt.spec.ts index 653c85e04..a5987a082 100644 --- a/packages/cli/src/commands/init/prompts/getFeaturesPrompt.spec.ts +++ b/packages/cli/src/commands/init/prompts/getFeaturesPrompt.spec.ts @@ -2,7 +2,7 @@ import {getFeaturesPrompt} from "./getFeaturesPrompt"; describe("getFeaturesPrompt", () => { it("should add a provider info", async () => { - const prompt = getFeaturesPrompt({}); + const prompt = getFeaturesPrompt(["yarn", "npm", "pnpm"], {}); expect(prompt).toBeInstanceOf(Array); expect(prompt).toMatchInlineSnapshot(` diff --git a/packages/cli/src/commands/init/prompts/getFeaturesPrompt.ts b/packages/cli/src/commands/init/prompts/getFeaturesPrompt.ts index 66dd7ff62..a7850de2a 100644 --- a/packages/cli/src/commands/init/prompts/getFeaturesPrompt.ts +++ b/packages/cli/src/commands/init/prompts/getFeaturesPrompt.ts @@ -14,8 +14,8 @@ function mapChoices(item: any, options: Partial) { }); } -export function getFeaturesPrompt(options: Partial) { - return FeaturesPrompt().map((item: any) => { +export function getFeaturesPrompt(availablePackageManagers: string[], options: Partial) { + return FeaturesPrompt(availablePackageManagers).map((item: any) => { return cleanObject({ ...item, choices: item.choices?.length ? mapChoices(item, options) : undefined diff --git a/packages/cli/src/commands/run/RunCmd.spec.ts b/packages/cli/src/commands/run/RunCmd.spec.ts index 71f7a01f7..cde0b90ab 100644 --- a/packages/cli/src/commands/run/RunCmd.spec.ts +++ b/packages/cli/src/commands/run/RunCmd.spec.ts @@ -76,7 +76,8 @@ describe("RunCmd", () => { }; const cliFs = { exists: jest.fn().mockReturnValue(true), - readFile: jest.fn().mockResolvedValue(JSON.stringify({compilerOptions: {outDir: "./lib"}})) + readFile: jest.fn().mockResolvedValue(JSON.stringify({compilerOptions: {outDir: "./lib"}})), + readJsonSync: jest.fn().mockResolvedValue({}) }; const command = await CliPlatformTest.invoke(RunCmd, [ { diff --git a/packages/cli/test/integrations/generate/generate.async-factory.integration.spec.ts b/packages/cli/test/integrations/generate/generate.async-factory.integration.spec.ts index 5e67249d3..e4a29a881 100644 --- a/packages/cli/test/integrations/generate/generate.async-factory.integration.spec.ts +++ b/packages/cli/test/integrations/generate/generate.async-factory.integration.spec.ts @@ -12,9 +12,7 @@ describe("Generate AsyncFactory", () => { afterEach(() => CliPlatformTest.reset()); it("should generate a template with the right options", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -23,7 +21,7 @@ describe("Generate AsyncFactory", () => { devDependencies: {} }); - await cliService.exec("generate", { + await CliPlatformTest.exec("generate", { rootDir: "./project-data", type: "async.factory", name: "Test" diff --git a/packages/cli/test/integrations/generate/generate.controller.integration.spec.ts b/packages/cli/test/integrations/generate/generate.controller.integration.spec.ts index 95d9c9962..14b5a3a06 100644 --- a/packages/cli/test/integrations/generate/generate.controller.integration.spec.ts +++ b/packages/cli/test/integrations/generate/generate.controller.integration.spec.ts @@ -12,9 +12,7 @@ describe("Generate Controller", () => { afterEach(() => CliPlatformTest.reset()); it("should generate the template with the right options (simple path)", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -23,7 +21,7 @@ describe("Generate Controller", () => { devDependencies: {} }); - await cliService.exec("generate", { + await CliPlatformTest.exec("generate", { rootDir: "./project-data", type: "controller", name: "Test", @@ -39,9 +37,7 @@ describe("Generate Controller", () => { expect(result).toContain("TestController"); }); it("should generate the template with the right options (complex path)", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -50,7 +46,7 @@ describe("Generate Controller", () => { devDependencies: {} }); - await cliService.exec("generate", { + await CliPlatformTest.exec("generate", { rootDir: "./project-data", type: "controller", name: "users/User", diff --git a/packages/cli/test/integrations/generate/generate.decorator-class.integration.spec.ts b/packages/cli/test/integrations/generate/generate.decorator-class.integration.spec.ts index 75be16e5d..4d7296f52 100644 --- a/packages/cli/test/integrations/generate/generate.decorator-class.integration.spec.ts +++ b/packages/cli/test/integrations/generate/generate.decorator-class.integration.spec.ts @@ -14,7 +14,7 @@ describe("Generate class decorator", () => { it("should generate a template with the right options", async () => { const cliService = CliPlatformTest.get(CliService); const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -23,7 +23,7 @@ describe("Generate class decorator", () => { devDependencies: {} }); - await cliService.exec("generate", { + await CliPlatformTest.exec("generate", { rootDir: "./project-data", type: "decorator", templateType: "class", diff --git a/packages/cli/test/integrations/generate/generate.decorator-endpoint.integration.spec.ts b/packages/cli/test/integrations/generate/generate.decorator-endpoint.integration.spec.ts index b8cdf7534..7fbda17d9 100644 --- a/packages/cli/test/integrations/generate/generate.decorator-endpoint.integration.spec.ts +++ b/packages/cli/test/integrations/generate/generate.decorator-endpoint.integration.spec.ts @@ -13,9 +13,7 @@ describe("Generate endpoint decorator", () => { afterEach(() => CliPlatformTest.reset()); it("should generate a template with the right options", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -24,7 +22,7 @@ describe("Generate endpoint decorator", () => { devDependencies: {} }); - await cliService.exec("generate", { + await CliPlatformTest.exec("generate", { rootDir: "./project-data", type: "decorator", templateType: "endpoint", diff --git a/packages/cli/test/integrations/generate/generate.decorator-generic.integration.spec.ts b/packages/cli/test/integrations/generate/generate.decorator-generic.integration.spec.ts index 937e0706d..d82d043d1 100644 --- a/packages/cli/test/integrations/generate/generate.decorator-generic.integration.spec.ts +++ b/packages/cli/test/integrations/generate/generate.decorator-generic.integration.spec.ts @@ -15,7 +15,7 @@ describe("Generate generic decorator", () => { it("should generate a template with the right options", async () => { const cliService = CliPlatformTest.get(CliService); const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -24,7 +24,7 @@ describe("Generate generic decorator", () => { devDependencies: {} }); - await cliService.exec("generate", { + await CliPlatformTest.exec("generate", { rootDir: "./project-data", type: "decorator", templateType: "generic", diff --git a/packages/cli/test/integrations/generate/generate.decorator-method.integration.spec.ts b/packages/cli/test/integrations/generate/generate.decorator-method.integration.spec.ts index 76a0b9899..12b6a723b 100644 --- a/packages/cli/test/integrations/generate/generate.decorator-method.integration.spec.ts +++ b/packages/cli/test/integrations/generate/generate.decorator-method.integration.spec.ts @@ -14,7 +14,7 @@ describe("Generate method decorator", () => { it("should generate a template with the right options", async () => { const cliService = CliPlatformTest.get(CliService); const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -23,7 +23,7 @@ describe("Generate method decorator", () => { devDependencies: {} }); - await cliService.exec("generate", { + await CliPlatformTest.exec("generate", { rootDir: "./project-data", type: "decorator", templateType: "method", diff --git a/packages/cli/test/integrations/generate/generate.decorator-middleware.integration.spec.ts b/packages/cli/test/integrations/generate/generate.decorator-middleware.integration.spec.ts index b38d41076..0815a644b 100644 --- a/packages/cli/test/integrations/generate/generate.decorator-middleware.integration.spec.ts +++ b/packages/cli/test/integrations/generate/generate.decorator-middleware.integration.spec.ts @@ -15,7 +15,7 @@ describe("Generate middleware decorator", () => { const cliService = CliPlatformTest.get(CliService); const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -24,7 +24,7 @@ describe("Generate middleware decorator", () => { devDependencies: {} }); - await cliService.exec("generate", { + await CliPlatformTest.exec("generate", { rootDir: "./project-data", type: "decorator", templateType: "middleware", @@ -47,7 +47,7 @@ describe("Generate middleware decorator", () => { it("should generate a template with the right options (after)", async () => { const cliService = CliPlatformTest.get(CliService); const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -56,7 +56,7 @@ describe("Generate middleware decorator", () => { devDependencies: {} }); - await cliService.exec("generate", { + await CliPlatformTest.exec("generate", { rootDir: "./project-data", type: "decorator", templateType: "middleware", diff --git a/packages/cli/test/integrations/generate/generate.exception-filter.integration.spec.ts b/packages/cli/test/integrations/generate/generate.exception-filter.integration.spec.ts index b8c488903..85adba81c 100644 --- a/packages/cli/test/integrations/generate/generate.exception-filter.integration.spec.ts +++ b/packages/cli/test/integrations/generate/generate.exception-filter.integration.spec.ts @@ -14,7 +14,7 @@ describe("Generate Exception Filter", () => { it("should generate a template with the right options", async () => { const cliService = CliPlatformTest.get(CliService); const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -23,7 +23,7 @@ describe("Generate Exception Filter", () => { devDependencies: {} }); - await cliService.exec("generate", { + await CliPlatformTest.exec("generate", { rootDir: "./project-data", type: "exception-filter", name: "Http" diff --git a/packages/cli/test/integrations/generate/generate.response-filter.integration.spec.ts b/packages/cli/test/integrations/generate/generate.response-filter.integration.spec.ts index a122c5102..02e4c764b 100644 --- a/packages/cli/test/integrations/generate/generate.response-filter.integration.spec.ts +++ b/packages/cli/test/integrations/generate/generate.response-filter.integration.spec.ts @@ -12,9 +12,7 @@ describe("Generate Response Filter", () => { afterEach(() => CliPlatformTest.reset()); it("should generate a template with the right options", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -23,7 +21,7 @@ describe("Generate Response Filter", () => { devDependencies: {} }); - await cliService.exec("generate", { + await CliPlatformTest.exec("generate", { rootDir: "./project-data", type: "response-filter", name: "JSON" diff --git a/packages/cli/test/integrations/init/init.integration.spec.ts b/packages/cli/test/integrations/init/init.integration.spec.ts index 09a8da730..e03a24cde 100644 --- a/packages/cli/test/integrations/init/init.integration.spec.ts +++ b/packages/cli/test/integrations/init/init.integration.spec.ts @@ -1,7 +1,7 @@ -import {CliService, PackageManager, ProjectPackageJson} from "@tsed/cli-core"; +import {PackageManager} from "@tsed/cli-core"; import {CliPlatformTest, FakeCliFs} from "@tsed/cli-testing"; -import {ensureDirSync, existsSync, readFileSync, writeFileSync} from "fs-extra"; -import {dirname, join} from "path"; +import {ensureDirSync, writeFileSync} from "fs-extra"; +import {join} from "path"; import {InitCmd, ProjectConvention, TEMPLATE_DIR} from "../../../src"; import filedirname from "filedirname"; @@ -11,7 +11,8 @@ describe("Init cmd", () => { beforeEach(() => { return CliPlatformTest.bootstrap({ templateDir: TEMPLATE_DIR, - commands: [InitCmd] + commands: [InitCmd], + argv: ["init"] }); } ); @@ -19,29 +20,20 @@ describe("Init cmd", () => { describe("Express.js", () => { it("should generate a project with the right options", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - - jest.spyOn(projectPackageJson as any, "getPackageJson").mockReturnValue({ - dependencies: {}, - devDependencies: {}, - scripts: {} - }); - - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", scripts: {}, dependencies: {}, devDependencies: {} - }); + }) - await cliService.exec("init", { + await CliPlatformTest.exec("init", { platform: "express", rootDir: "./project-data", projectName: "project-data", - tsedVersion: "5.58.1", + tsedVersion: "5.58.1", packageManager: "yarn" }); @@ -77,6 +69,7 @@ describe("Init cmd", () => { expect(content).toContain("import \"@tsed/ajv\""); expect(content).toMatchSnapshot(); + const pkg = JSON.parse(FakeCliFs.entries.get("project-name/package.json")!); expect(pkg).toEqual({ "dependencies": { @@ -119,16 +112,7 @@ describe("Init cmd", () => { expect(dockerFile).toContain("RUN yarn install --pure-lockfile"); }); it("should generate a project with swagger", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - - jest.spyOn(projectPackageJson as any, "getPackageJson").mockReturnValue({ - dependencies: {}, - devDependencies: {}, - scripts: {} - }); - - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -137,7 +121,7 @@ describe("Init cmd", () => { devDependencies: {} }); - await cliService.exec("init", { + await CliPlatformTest.exec("init", { platform: "express", rootDir: "./project-data", projectName: "project-data", @@ -219,16 +203,7 @@ describe("Init cmd", () => { }); }); it("should generate a project with NPM", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - - jest.spyOn(projectPackageJson as any, "getPackageJson").mockReturnValue({ - dependencies: {}, - devDependencies: {}, - scripts: {} - }); - - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -237,7 +212,7 @@ describe("Init cmd", () => { devDependencies: {} }); - await cliService.exec("init", { + await CliPlatformTest.exec("init", { platform: "express", rootDir: "./project-data", projectName: "project-data", @@ -313,38 +288,41 @@ describe("Init cmd", () => { }); }); it("should generate a project with Convention ANGULAR", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - - jest.spyOn(projectPackageJson as any, "getPackageJson").mockReturnValue({ + CliPlatformTest.setPackageJson({ + name: "", + version: "1.0.0", + description: "", + scripts: {}, dependencies: {}, devDependencies: {}, - scripts: {} - }); - - projectPackageJson.setRaw = (pkg) => { - // @ts-ignore - projectPackageJson.raw = { - name: "", - version: "1.0.0", - description: "", - scripts: {}, - dependencies: {}, - devDependencies: {}, - tsed: { - convention: "angular" - } + tsed: { + convention: "angular" } - }; - - await cliService.exec("init", { + }) + + // projectPackageJson.setRaw = (pkg) => { + // // @ts-ignore + // projectPackageJson.raw = { + // name: "", + // version: "1.0.0", + // description: "", + // scripts: {}, + // dependencies: {}, + // devDependencies: {}, + // tsed: { + // convention: "angular" + // } + // } + // }; + + await CliPlatformTest.exec("init", { platform: "express", rootDir: "./project-data", projectName: "project-data", tsedVersion: "5.58.1", convention: ProjectConvention.ANGULAR, swagger: true - }); + }) expect(FakeCliFs.getKeys()).toEqual([ "./project-name", @@ -388,16 +366,7 @@ describe("Init cmd", () => { describe("Koa.js", () => { it("should generate a project with the right options", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - - jest.spyOn(projectPackageJson as any, "getPackageJson").mockReturnValue({ - dependencies: {}, - devDependencies: {}, - scripts: {} - }); - - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -406,7 +375,7 @@ describe("Init cmd", () => { devDependencies: {} }); - await cliService.exec("init", { + await CliPlatformTest.exec("init", { platform: "koa", rootDir: "./project-data", projectName: "project-data", @@ -485,16 +454,8 @@ describe("Init cmd", () => { describe("shared configuration", () => { it("should configuration directory", async () => { - const cliService = CliPlatformTest.get(CliService); - const projectPackageJson = CliPlatformTest.get(ProjectPackageJson); - - jest.spyOn(projectPackageJson as any, "getPackageJson").mockReturnValue({ - dependencies: {}, - devDependencies: {}, - scripts: {} - }); - projectPackageJson.setRaw({ + CliPlatformTest.setPackageJson({ name: "", version: "1.0.0", description: "", @@ -503,7 +464,7 @@ describe("Init cmd", () => { devDependencies: {} }); - await cliService.exec("init", { + await CliPlatformTest.exec("init", { platform: "express", rootDir: "./project-data", projectName: "project-data", diff --git a/yarn.lock b/yarn.lock index 009da027b..6c23aa544 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3687,6 +3687,11 @@ resolved "https://registry.yarnpkg.com/@types/url-parse/-/url-parse-1.4.8.tgz#c3825047efbca1295b7f1646f38203d9145130d6" integrity sha512-zqqcGKyNWgTLFBxmaexGUKQyWqeG7HjXj20EuQJSJWwXe54BjX0ihIo5cJB9yAQzH8dNugJ9GvkBYMjPXs/PJw== +"@types/uuid@8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + "@types/webpack-dev-server@^3": version "3.11.6" resolved "https://registry.yarnpkg.com/@types/webpack-dev-server/-/webpack-dev-server-3.11.6.tgz#d8888cfd2f0630203e13d3ed7833a4d11b8a34dc"