From 185efadd86279c21ccb74a6ae4afa59f9d2e7dad Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Sun, 8 Sep 2024 00:41:34 -0700 Subject: [PATCH 01/21] feat: add shared lib as deps & remove config --- package.json | 1 + src/@types/config.ts | 40 ------------------------- src/@types/index.ts | 1 - src/generate/utils/command-utils.ts | 2 +- src/generate/utils/nonopininated-cmd.ts | 2 +- src/generate/utils/opinionated-cmd.ts | 2 +- src/index.ts | 1 - src/types.ts | 1 - src/utils/compiler.ts | 2 +- 9 files changed, 5 insertions(+), 47 deletions(-) delete mode 100644 src/@types/config.ts delete mode 100644 src/@types/index.ts delete mode 100644 src/types.ts diff --git a/package.json b/package.json index 7891adb..18e9920 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ }, "dependencies": { "@expressots/boost-ts": "1.3.0", + "@expressots/shared": "file:../shared/expressots-shared-0.0.1-beta.tgz", "axios": "^1.7.3", "chalk-animation": "2.0.3", "cli-progress": "3.12.0", diff --git a/src/@types/config.ts b/src/@types/config.ts deleted file mode 100644 index 9d788c4..0000000 --- a/src/@types/config.ts +++ /dev/null @@ -1,40 +0,0 @@ -export const enum Pattern { - LOWER_CASE = "lowercase", - KEBAB_CASE = "kebab-case", - PASCAL_CASE = "PascalCase", - CAMEL_CASE = "camelCase", -} - -interface IProviders { - prisma?: { - schemaName: string; - schemaPath: string; - entitiesPath: string; - entityNamePattern: string; - }; -} - -/** - * The configuration object for the Expresso CLI. - * - * @property {Pattern} scaffoldPattern - The pattern to use when scaffolding files. - * @property {string} sourceRoot - The root directory for the source files. - * @property {boolean} opinionated - Whether or not to use the opinionated configuration. - * - * @see [ExpressoConfig](https://expresso-ts.com/docs) - */ -export interface ExpressoConfig { - scaffoldPattern: Pattern; - sourceRoot: string; - opinionated: boolean; - providers?: IProviders; - scaffoldSchematics?: { - entity?: string; - controller?: string; - usecase?: string; - dto?: string; - module?: string; - provider?: string; - middleware?: string; - }; -} diff --git a/src/@types/index.ts b/src/@types/index.ts deleted file mode 100644 index 5c62e04..0000000 --- a/src/@types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./config"; diff --git a/src/generate/utils/command-utils.ts b/src/generate/utils/command-utils.ts index 9dd1dae..7000346 100644 --- a/src/generate/utils/command-utils.ts +++ b/src/generate/utils/command-utils.ts @@ -11,7 +11,7 @@ import { import { printError } from "../../utils/cli-ui"; import { verifyIfFileExists } from "../../utils/verify-file-exists"; import Compiler from "../../utils/compiler"; -import { ExpressoConfig, Pattern } from "../../types"; +import { ExpressoConfig, Pattern } from "@expressots/shared"; export const enum PathStyle { None = "none", diff --git a/src/generate/utils/nonopininated-cmd.ts b/src/generate/utils/nonopininated-cmd.ts index a7b8a4b..182ba40 100644 --- a/src/generate/utils/nonopininated-cmd.ts +++ b/src/generate/utils/nonopininated-cmd.ts @@ -3,7 +3,7 @@ import { anyCaseToKebabCase, anyCaseToPascalCase, } from "@expressots/boost-ts"; -import { ExpressoConfig } from "../../@types"; +import { ExpressoConfig } from "@expressots/shared"; import { printGenerateSuccess } from "../../utils/cli-ui"; import { diff --git a/src/generate/utils/opinionated-cmd.ts b/src/generate/utils/opinionated-cmd.ts index 3c48006..d8a09c8 100644 --- a/src/generate/utils/opinionated-cmd.ts +++ b/src/generate/utils/opinionated-cmd.ts @@ -20,7 +20,7 @@ import { addModuleToContainer, addModuleToContainerNestedPath, } from "../../utils/add-module-to-container"; -import { ExpressoConfig } from "../../@types"; +import { ExpressoConfig } from "@expressots/shared"; /** * Process commands for opinionated service scaffolding diff --git a/src/index.ts b/src/index.ts index 814fd72..5656364 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,3 @@ -export * from "./types"; export * from "./generate"; export * from "./utils"; export * from "./new"; diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index 0303157..0000000 --- a/src/types.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./@types"; diff --git a/src/utils/compiler.ts b/src/utils/compiler.ts index 37e2035..14d4c26 100644 --- a/src/utils/compiler.ts +++ b/src/utils/compiler.ts @@ -1,7 +1,7 @@ import { existsSync } from "node:fs"; import path from "path"; import { RegisterOptions, Service } from "ts-node"; -import { ExpressoConfig } from "../types"; +import { ExpressoConfig } from "@expressots/shared"; import { printError } from "./cli-ui"; /** From 880d40537fe5e5459023a3bf0faf41a2568ae494 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Mon, 9 Sep 2024 02:53:46 -0700 Subject: [PATCH 02/21] feat: add env configuration from shared --- expressots.config.ts | 6 +++++- package.json | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/expressots.config.ts b/expressots.config.ts index a8f1b4a..7b564e5 100644 --- a/expressots.config.ts +++ b/expressots.config.ts @@ -1,9 +1,13 @@ -import { ExpressoConfig, Pattern } from "./src/types"; +import { ExpressoConfig, Pattern } from "@expressots/shared"; const config: ExpressoConfig = { sourceRoot: "src", scaffoldPattern: Pattern.KEBAB_CASE, opinionated: false, + env: { + development: ".env.development", + production: ".env.production", + }, /* scaffoldSchematics: { entity: "model", provider: "adapter", diff --git a/package.json b/package.json index 18e9920..46f9274 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ }, "dependencies": { "@expressots/boost-ts": "1.3.0", - "@expressots/shared": "file:../shared/expressots-shared-0.0.1-beta.tgz", + "@expressots/shared": "file:../shared/expressots-shared-0.0.1-beta.1.tgz", "axios": "^1.7.3", "chalk-animation": "2.0.3", "cli-progress": "3.12.0", From c395d05a843b9a2a4efd43097b4b75d94a34918a Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Sun, 27 Oct 2024 21:50:28 -0700 Subject: [PATCH 03/21] fix: restore shared dependency in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 46f9274..361a139 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,6 @@ }, "dependencies": { "@expressots/boost-ts": "1.3.0", - "@expressots/shared": "file:../shared/expressots-shared-0.0.1-beta.1.tgz", "axios": "^1.7.3", "chalk-animation": "2.0.3", "cli-progress": "3.12.0", @@ -67,6 +66,7 @@ "@codecov/vite-plugin": "^0.0.1-beta.9", "@commitlint/cli": "19.2.1", "@commitlint/config-conventional": "19.1.0", + "@expressots/shared": "file:../shared/expressots-shared-0.0.1-beta.1.tgz", "@release-it/conventional-changelog": "7.0.2", "@types/chalk-animation": "1.6.1", "@types/cli-progress": "3.11.0", From 124a4ecaa58279eb8f31b8dc934127615d6064a7 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Wed, 13 Nov 2024 19:20:57 -0800 Subject: [PATCH 04/21] feat: migrate from Vitest to Jest for testing framework --- .eslintrc.cjs | 2 +- .gitignore | 3 +- jest.config.ts | 24 +++++++++++ package.json | 17 ++++---- .../__tests__/project.commands.spec.ts | 7 ---- tsconfig.json | 2 +- vitest.config.ts | 41 ------------------- 7 files changed, 36 insertions(+), 60 deletions(-) create mode 100644 jest.config.ts delete mode 100644 src/commands/__tests__/project.commands.spec.ts delete mode 100644 vitest.config.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 64dec25..53e0874 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -20,7 +20,7 @@ module.exports = { "node_modules/*", "expressots.config.ts", "commitlint.config.ts", - "vitest.config.ts", + "jest.config.ts", ".eslintrc.cjs", "coverage/*", ], diff --git a/.gitignore b/.gitignore index 7ef4f15..c10c608 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ expressots.config.ts *.container.ts -coverage/ \ No newline at end of file +coverage/ +.early.coverage diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 0000000..b07211a --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,24 @@ +import type { JestConfigWithTsJest } from "ts-jest"; + +const config: JestConfigWithTsJest = { + testEnvironment: "node", + roots: ["/src"], + testRegex: ".*\\.spec\\.ts$", + testPathIgnorePatterns: ["/node_modules/", "/bin/"], + collectCoverageFrom: ["src/**/*.ts", "!**/*.spec.ts", "src/**/index.ts"], + moduleNameMapper: { + "^@src/(.*)$": "/src/$1", + }, + setupFiles: ["reflect-metadata"], + transform: { + "^.+\\.ts$": [ + "ts-jest", + { + tsconfig: "tsconfig.json", + // Add any ts-jest specific options here + }, + ], + }, +}; + +export default config; diff --git a/package.json b/package.json index 361a139..9e1e397 100644 --- a/package.json +++ b/package.json @@ -44,9 +44,9 @@ "lint": "eslint \"./src/**/*.ts\"", "lint:fix": "eslint \"./src/**/*.ts\" --fix", "release": "release-it", - "test": "vitest run --reporter default", - "test:watch": "vitest", - "coverage": "vitest run --coverage" + "test": "jest", + "coverage": "jest --coverage", + "test:watch": "jest --watch" }, "dependencies": { "@expressots/boost-ts": "1.3.0", @@ -66,32 +66,31 @@ "@codecov/vite-plugin": "^0.0.1-beta.9", "@commitlint/cli": "19.2.1", "@commitlint/config-conventional": "19.1.0", - "@expressots/shared": "file:../shared/expressots-shared-0.0.1-beta.1.tgz", + "@expressots/shared": "0.1.0", "@release-it/conventional-changelog": "7.0.2", "@types/chalk-animation": "1.6.1", "@types/cli-progress": "3.11.0", "@types/degit": "2.8.3", "@types/inquirer": "9.0.3", + "@types/jest": "^29.5.14", "@types/mustache": "4.2.2", "@types/node": "20.12.7", "@types/yargs": "17.0.22", "@typescript-eslint/eslint-plugin": "7.6.0", "@typescript-eslint/parser": "7.6.0", - "@vitest/coverage-v8": "1.4.0", "chalk": "4.1.2", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "husky": "9.0.11", + "jest": "^29.7.0", "prettier": "3.2.5", "reflect-metadata": "0.2.2", "release-it": "16.3.0", "rimraf": "5.0.5", "shx": "0.3.4", + "ts-jest": "^29.2.5", "ts-node-dev": "2.0.0", - "typescript": "5.2.2", - "vite": "5.2.8", - "vite-tsconfig-paths": "4.3.2", - "vitest": "1.4.0" + "typescript": "5.2.2" }, "release-it": { "git": { diff --git a/src/commands/__tests__/project.commands.spec.ts b/src/commands/__tests__/project.commands.spec.ts deleted file mode 100644 index e331cb5..0000000 --- a/src/commands/__tests__/project.commands.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, it, expect } from "vitest"; - -describe("project.commands", () => { - it("should return a list of commands", () => { - expect(true).toBe(true); - }); -}); diff --git a/tsconfig.json b/tsconfig.json index 91d0322..dcf9ee3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,7 @@ "skipLibCheck": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, - "types": ["node", "reflect-metadata", "vitest/globals"] + "types": ["node", "reflect-metadata", "jest"] }, "include": ["./src/**/*.ts"], "exclude": ["node_modules", "test/**/*.spec.ts", "./bin/**/*"] diff --git a/vitest.config.ts b/vitest.config.ts deleted file mode 100644 index aa2776a..0000000 --- a/vitest.config.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { defineConfig } from "vitest/config"; -import { codecovVitePlugin } from "@codecov/vite-plugin"; -import tsconfigPaths from "vite-tsconfig-paths"; - -/** - * @see {@link https://vitejs.dev/config/} - * @see {@link https://vitest.dev/config/} - */ -export default defineConfig({ - plugins: [ - tsconfigPaths(), - codecovVitePlugin({ - enableBundleAnalysis: process.env.CODECOV_TOKEN !== undefined, - bundleName: "expresso-ts-cli-coverage", - uploadToken: process.env.CODECOV_TOKEN, - }), - ], - test: { - globals: true, - environment: "node", - setupFiles: ["reflect-metadata"], - exclude: ["**/node_modules/**", "**/benchmark/**", "**/bin/**"], - coverage: { - all: true, - include: ["./src/**"], - exclude: ["**/node_modules/**", "**/bin/**", "**/index.ts/**"], - thresholds: { - global: { - statements: 85, - branches: 85, - functions: 85, - lines: 85, - }, - }, - reporter: ["text", "html", "json"], - provider: "v8", - }, - // ref: https://vitest.dev/config/#testtimeout - testTimeout: 10000, - }, -}); From 1bd6436c0fb89314073597ac6b0e833d8bcf5d86 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Thu, 14 Nov 2024 09:12:54 -0800 Subject: [PATCH 05/21] chore: update package version to 3.0.0-beta.1 and integrate BUNDLE_VERSION in CLI output --- package.json | 2 +- src/cli.ts | 6 ++++++ src/info/form.ts | 20 ++++---------------- src/new/form.ts | 3 ++- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 9e1e397..e2fdcf1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@expressots/cli", - "version": "1.12.0", + "version": "3.0.0-beta.1", "description": "Expressots CLI - modern, fast, lightweight nodejs web framework (@cli)", "author": "Richard Zampieri", "license": "MIT", diff --git a/src/cli.ts b/src/cli.ts index 5af8b34..dffe835 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -18,6 +18,12 @@ import { createExternalProviderCMD } from "./providers/create/cli"; import { printError } from "./utils/cli-ui"; import { scriptsCommand } from "./scripts"; +/** + * The current version of the ExpressoTS Bundle. + * core, adapters, and cli. + */ +export const BUNDLE_VERSION = "2.16.2"; + stdout.write(`\n${[chalk.bold.green("🐎 Expressots")]}\n\n`); yargs(hideBin(process.argv)) diff --git a/src/info/form.ts b/src/info/form.ts index 37a9f54..1418a65 100644 --- a/src/info/form.ts +++ b/src/info/form.ts @@ -1,9 +1,9 @@ import chalk from "chalk"; -import path from "path"; import fs from "fs"; import os from "os"; +import path from "path"; +import { BUNDLE_VERSION } from "../cli"; import { printError, printSuccess } from "../utils/cli-ui"; -import axios from "axios"; function getInfosFromPackage() { try { @@ -34,17 +34,5 @@ export const infoForm = (): void => { console.log(chalk.green("System information:")); console.log(chalk.white(`\tOS Version: ${os.version()}`)); console.log(chalk.white(`\tNodeJS version: ${process.version}`)); - currentCLIVersion(); -}; - -async function currentCLIVersion(): Promise { - try { - const response = await axios.get( - "https://api.github.com/repos/expressots/expressots-cli/releases", - ); - const latestRelease = `v${response.data[0].tag_name}`; - printSuccess("CLI version:", latestRelease); - } catch (error: Error | any) { - printError("Error:", error.message); - } -} + printSuccess("CLI version:", BUNDLE_VERSION); +}; \ No newline at end of file diff --git a/src/new/form.ts b/src/new/form.ts index 982c837..6598b39 100644 --- a/src/new/form.ts +++ b/src/new/form.ts @@ -8,6 +8,7 @@ import path from "node:path"; import { centerText } from "../utils/center-text"; import { printError } from "../utils/cli-ui"; import { changePackageName } from "../utils/change-package-info"; +import { BUNDLE_VERSION } from "../cli"; async function packageManagerInstall({ packageManager, @@ -220,7 +221,7 @@ const projectForm = async ( try { const emitter = degit( - `expressots/expressots/templates/${templates[template]}`, + `expressots/templates/${templates[template]}#${BUNDLE_VERSION}`, ); await emitter.clone(answer.name); From 7255a6958bd2366f299c9e9d384bc9d6bed51391 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Thu, 14 Nov 2024 09:13:20 -0800 Subject: [PATCH 06/21] fix: add newline at end of file in infoForm function --- src/info/form.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/info/form.ts b/src/info/form.ts index 1418a65..9aa310a 100644 --- a/src/info/form.ts +++ b/src/info/form.ts @@ -35,4 +35,4 @@ export const infoForm = (): void => { console.log(chalk.white(`\tOS Version: ${os.version()}`)); console.log(chalk.white(`\tNodeJS version: ${process.version}`)); printSuccess("CLI version:", BUNDLE_VERSION); -}; \ No newline at end of file +}; From e7593662d3803afc877c00b25dc9e19d8f6f1327 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Thu, 14 Nov 2024 22:04:02 -0800 Subject: [PATCH 07/21] feat: update scripts for build process and enhance package.json configurations --- .eslintrc.cjs | 1 + package.json | 11 +++++------ scripts/chmod.js | 23 +++++++++++++++++++++++ scripts/cp.js | 35 +++++++++++++++++++++++++++++++++++ scripts/rm.js | 24 ++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 scripts/chmod.js create mode 100644 scripts/cp.js create mode 100644 scripts/rm.js diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 53e0874..ce3b248 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -23,6 +23,7 @@ module.exports = { "jest.config.ts", ".eslintrc.cjs", "coverage/*", + "scripts/*", ], rules: { "@typescript-eslint/interface-name-prefix": "off", diff --git a/package.json b/package.json index e2fdcf1..b013b0b 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "expressots": "bin/cli.js" }, "engines": { - "node": ">=18.0.0" + "node": ">=18.20.5" }, "funding": { "type": "github", @@ -35,9 +35,9 @@ "start:build": "npm run build && npm run start", "start": "node ./bin/cli.js", "start:dev": "tsnd ./src/cli.ts", - "build": "npm run clean && tsc -p tsconfig.json && yarn cp:templates && chmod +x ./bin/cli.js", - "cp:templates": "cp -r ./src/generate/templates ./bin/generate/templates", - "clean": "rimraf ./bin", + "build": "npm run clean && tsc -p tsconfig.json && npm run cp:templates && node scripts/chmod.js ./bin/cli.js", + "cp:templates": "node scripts/cp.js ./src/generate/templates ./bin/generate/templates", + "clean": "node scripts/rm.js bin", "prepublish": "npm run build && npm pack", "publish": "npm publish --tag latest", "format": "prettier --write \"./src/**/*.ts\" --cache", @@ -49,7 +49,7 @@ "test:watch": "jest --watch" }, "dependencies": { - "@expressots/boost-ts": "1.3.0", + "@expressots/boost-ts": "file:../boost-ts/expressots-boost-ts-1.3.0.tgz", "axios": "^1.7.3", "chalk-animation": "2.0.3", "cli-progress": "3.12.0", @@ -86,7 +86,6 @@ "prettier": "3.2.5", "reflect-metadata": "0.2.2", "release-it": "16.3.0", - "rimraf": "5.0.5", "shx": "0.3.4", "ts-jest": "^29.2.5", "ts-node-dev": "2.0.0", diff --git a/scripts/chmod.js b/scripts/chmod.js new file mode 100644 index 0000000..80e0d05 --- /dev/null +++ b/scripts/chmod.js @@ -0,0 +1,23 @@ +const { execSync } = require("child_process"); + +function makeExecutable(targetFile) { + if (process.platform !== "win32") { // Check if the OS is not Windows + try { + execSync(`chmod +x ${targetFile}`); + console.log(`Made ${targetFile} executable.`); + } catch (error) { + console.error(`Error making ${targetFile} executable:`, error.message); + process.exit(1); + } + } else { + console.log(`Skipping chmod on Windows for ${targetFile}`); + } +} + +if (process.argv.length !== 3) { + console.error("Usage: node chmod.js "); + process.exit(1); +} + +const targetFile = process.argv[2]; +makeExecutable(targetFile); \ No newline at end of file diff --git a/scripts/cp.js b/scripts/cp.js new file mode 100644 index 0000000..3d2089c --- /dev/null +++ b/scripts/cp.js @@ -0,0 +1,35 @@ +const fs = require("fs"); +const path = require("path"); + +function copyRecursiveSync(src, dest) { + const exists = fs.existsSync(src); + const stats = exists && fs.statSync(src); + const isDirectory = exists && stats.isDirectory(); + + if (isDirectory) { + fs.mkdirSync(dest, { recursive: true }); + fs.readdirSync(src).forEach(function (childItemName) { + copyRecursiveSync( + path.join(src, childItemName), + path.join(dest, childItemName), + ); + }); + } else { + fs.copyFileSync(src, dest); + } +} + +if (process.argv.length < 4) { + process.stderr.write( + "Usage: node copy.js ... \n", + ); + process.exit(1); +} + +const destination = process.argv[process.argv.length - 1]; + +for (let i = 2; i < process.argv.length - 1; i++) { + const origin = process.argv[i]; + copyRecursiveSync(origin, path.join(destination, path.basename(origin))); + process.stdout.write(`Copied: ${origin} to ${destination}\n`); +} \ No newline at end of file diff --git a/scripts/rm.js b/scripts/rm.js new file mode 100644 index 0000000..6ee4423 --- /dev/null +++ b/scripts/rm.js @@ -0,0 +1,24 @@ +const fs = require("fs").promises; + +const removeTarget = async (target) => { + try { + const targetExists = await fs.stat(target).catch(() => null); + if (!targetExists) { + process.stdout.write(`Directory '${target}' does not exist.\n`); + return; + } + await fs.rm(target, { recursive: true, force: true }); + process.stdout.write(`Removed: ${target}\n`); + } catch (error) { + process.stderr.write(`Error: Unable to remove '${target}'\n`); + process.exit(1); + } +}; + +if (process.argv.length !== 3) { + process.stderr.write("Usage: node rm.js \n"); + process.exit(1); +} + +const target = process.argv[2]; +removeTarget(target); From 2447b21354d680c19bc59de33c820ad8cd061737 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Thu, 14 Nov 2024 23:29:03 -0800 Subject: [PATCH 08/21] feat: enhance pm install proc with improved command handling and progress feedback --- src/new/form.ts | 54 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/src/new/form.ts b/src/new/form.ts index 6598b39..dbc987e 100644 --- a/src/new/form.ts +++ b/src/new/form.ts @@ -25,43 +25,68 @@ async function packageManagerInstall({ ? `${packageManager}.cmd` : packageManager; - const installProcess = spawn(command, ["install", "--prefer-offline"], { + let installCommand: string = "install --prefer-offline"; + if (packageManager === "yarn") { + installCommand = "install --ignore-engines"; + } else if (packageManager === "bun" || packageManager === "pnpm" || packageManager === "yarn") { + installCommand = "install"; + } + + const installProcess = spawn(command, [installCommand], { cwd: directory, shell: true, timeout: 600000, }); - // eslint-disable-next-line prefer-const - let installTimeout: NodeJS.Timeout; - - installProcess.on("error", (error) => { - clearTimeout(installTimeout); - reject(new Error(`Failed to start subprocess: ${error.message}`)); - }); + // Simulate incremental progress + let progress = 0; + const interval = setInterval(() => { + if (progress < 90) { + progress += 5; + progressBar.update(progress); + } + }, 1000); + // Handle stdout for meaningful output or progress feedback installProcess.stdout?.on("data", (data: Buffer) => { const output = data.toString().trim(); - const npmProgressMatch = output.match( + // Remove all data from || to the end of the line + const cleanedOutput = output.replace(/\|\|.*$/g, ""); + + // Match and handle npm-specific progress + const npmProgressMatch = cleanedOutput.match( /\[(\d+)\/(\d+)\] (?:npm )?([\w\s]+)\.{3}/, ); if (npmProgressMatch) { const [, current, total, task] = npmProgressMatch; - const progress = Math.round( + progress = Math.round( (parseInt(current) / parseInt(total)) * 100, ); progressBar.update(progress, { doing: task }); } else { - progressBar.increment(5, { doing: output }); + // Update "task" without changing the progress + progressBar.update(progress, { doing: cleanedOutput }); } }); + // Handle errors + installProcess.on("error", (error) => { + clearInterval(interval); // Stop interval on error + progressBar.stop(); + reject(new Error(`Failed to start subprocess: ${error.message}`)); + }); + + // Finalize progress on close installProcess.on("close", (code) => { - clearTimeout(installTimeout); + clearInterval(interval); // Stop interval when the process ends if (code === 0) { + progressBar.update(100, { doing: "Complete!" }); // Finalize progress + progressBar.stop(); resolve("Installation Done!"); } else { + progressBar.stop(); reject( new Error( `${packageManager} install exited with code ${code}`, @@ -69,11 +94,6 @@ async function packageManagerInstall({ ); } }); - - installTimeout = setTimeout(() => { - installProcess.kill("SIGKILL"); - reject(new Error("Installation took too long. Aborted!")); - }, 600000); }); } From 6461e506c2dc3cc26657edd08f1bf2bb3a737ba2 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Thu, 14 Nov 2024 23:29:34 -0800 Subject: [PATCH 09/21] fix: improve readability of package manager install command logic --- src/new/form.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/new/form.ts b/src/new/form.ts index dbc987e..d8a0aa6 100644 --- a/src/new/form.ts +++ b/src/new/form.ts @@ -28,7 +28,11 @@ async function packageManagerInstall({ let installCommand: string = "install --prefer-offline"; if (packageManager === "yarn") { installCommand = "install --ignore-engines"; - } else if (packageManager === "bun" || packageManager === "pnpm" || packageManager === "yarn") { + } else if ( + packageManager === "bun" || + packageManager === "pnpm" || + packageManager === "yarn" + ) { installCommand = "install"; } From aec5d24b2b01cea9fa8e764bfd18ea82a4ab9ecb Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Fri, 15 Nov 2024 00:03:53 -0800 Subject: [PATCH 10/21] feat: refactor string case utilities and update dependencies --- package.json | 3 +- src/generate/utils/command-utils.ts | 2 +- src/generate/utils/nonopininated-cmd.ts | 2 +- src/generate/utils/opinionated-cmd.ts | 2 +- src/generate/utils/string-utils.ts | 63 +++++++++++++++++++++++++ 5 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 src/generate/utils/string-utils.ts diff --git a/package.json b/package.json index b013b0b..e6beb6a 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,7 @@ "test:watch": "jest --watch" }, "dependencies": { - "@expressots/boost-ts": "file:../boost-ts/expressots-boost-ts-1.3.0.tgz", - "axios": "^1.7.3", + "axios": "1.7.7", "chalk-animation": "2.0.3", "cli-progress": "3.12.0", "cli-table3": "0.6.5", diff --git a/src/generate/utils/command-utils.ts b/src/generate/utils/command-utils.ts index 7000346..4f73f9d 100644 --- a/src/generate/utils/command-utils.ts +++ b/src/generate/utils/command-utils.ts @@ -6,7 +6,7 @@ import { anyCaseToKebabCase, anyCaseToPascalCase, anyCaseToLowerCase, -} from "@expressots/boost-ts"; +} from "./string-utils"; import { printError } from "../../utils/cli-ui"; import { verifyIfFileExists } from "../../utils/verify-file-exists"; diff --git a/src/generate/utils/nonopininated-cmd.ts b/src/generate/utils/nonopininated-cmd.ts index 182ba40..e1ee281 100644 --- a/src/generate/utils/nonopininated-cmd.ts +++ b/src/generate/utils/nonopininated-cmd.ts @@ -2,7 +2,7 @@ import { anyCaseToCamelCase, anyCaseToKebabCase, anyCaseToPascalCase, -} from "@expressots/boost-ts"; +} from "./string-utils"; import { ExpressoConfig } from "@expressots/shared"; import { printGenerateSuccess } from "../../utils/cli-ui"; diff --git a/src/generate/utils/opinionated-cmd.ts b/src/generate/utils/opinionated-cmd.ts index d8a09c8..e4663d7 100644 --- a/src/generate/utils/opinionated-cmd.ts +++ b/src/generate/utils/opinionated-cmd.ts @@ -2,7 +2,7 @@ import { anyCaseToCamelCase, anyCaseToKebabCase, anyCaseToPascalCase, -} from "@expressots/boost-ts"; +} from "./string-utils"; import * as nodePath from "node:path"; import fs from "fs"; import { printGenerateSuccess } from "../../utils/cli-ui"; diff --git a/src/generate/utils/string-utils.ts b/src/generate/utils/string-utils.ts new file mode 100644 index 0000000..1c408d8 --- /dev/null +++ b/src/generate/utils/string-utils.ts @@ -0,0 +1,63 @@ +/** + * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to camelCase. + * @param str - The input string to be converted. + * @returns The converted string in camelCase. + */ +export function anyCaseToCamelCase(str: string): string { + return str + .replace(/[-_]+(.)?/g, (_, char) => (char ? char.toUpperCase() : '')) + .replace(/^[A-Z]/, (char) => char.toLowerCase()); +} + +/** + * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to kebab-case. + * @param str - The input string to be converted. + * @returns The converted string in kebab-case. + */ +export function anyCaseToKebabCase(str: string): string { + return str + .replace(/([a-z0-9])([A-Z])/g, '$1-$2') // Convert camelCase and PascalCase to kebab-case + .replace(/_/g, '-') // Convert snake_case to kebab-case + .toLowerCase(); // Ensure all characters are lowercase +} + +/** + * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to PascalCase. + * @param str - The input string to be converted. + * @returns The converted string in PascalCase. + */ +export function anyCaseToPascalCase(str: string): string { + return str + .replace(/[-_]+(.)?/g, (_, char) => (char ? char.toUpperCase() : '')) + .replace(/^[a-z]/, (char) => char.toUpperCase()); +} + +/** + * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to snake_case. + * @param str - The input string to be converted. + * @returns The converted string in snake_case. + */ +export function anyCaseToSnakeCase(str: string): string { + return str + .replace(/([a-z0-9])([A-Z])/g, '$1_$2') + .replace(/[-]+/g, '_') + .toLowerCase(); +} + +/** + * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to UPPER CASE. + * @param str - The input string to be converted. + * @returns The converted string in UPPER CASE. + */ +export function anyCaseToUpperCase(str: string): string { + return str.replace(/[-_]+(.)?/g, (_, char) => (char ? char.toUpperCase() : '')).toUpperCase(); +} + +/** + * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to lower case. + * @param str - The input string to be converted. + * @returns The converted string in lower case. + */ +export function anyCaseToLowerCase(str: string): string { + return str.replace(/[-_]+(.)?/g, (_, char) => (char ? char.toLowerCase() : '')).toLowerCase(); +} \ No newline at end of file From a95accc0deed1db83803b427c344d611fed9657b Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Fri, 15 Nov 2024 00:04:09 -0800 Subject: [PATCH 11/21] fix: standardize string utility function formatting and improve readability --- src/generate/utils/string-utils.ts | 38 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/generate/utils/string-utils.ts b/src/generate/utils/string-utils.ts index 1c408d8..3b7532a 100644 --- a/src/generate/utils/string-utils.ts +++ b/src/generate/utils/string-utils.ts @@ -4,9 +4,9 @@ * @returns The converted string in camelCase. */ export function anyCaseToCamelCase(str: string): string { - return str - .replace(/[-_]+(.)?/g, (_, char) => (char ? char.toUpperCase() : '')) - .replace(/^[A-Z]/, (char) => char.toLowerCase()); + return str + .replace(/[-_]+(.)?/g, (_, char) => (char ? char.toUpperCase() : "")) + .replace(/^[A-Z]/, (char) => char.toLowerCase()); } /** @@ -15,10 +15,10 @@ export function anyCaseToCamelCase(str: string): string { * @returns The converted string in kebab-case. */ export function anyCaseToKebabCase(str: string): string { - return str - .replace(/([a-z0-9])([A-Z])/g, '$1-$2') // Convert camelCase and PascalCase to kebab-case - .replace(/_/g, '-') // Convert snake_case to kebab-case - .toLowerCase(); // Ensure all characters are lowercase + return str + .replace(/([a-z0-9])([A-Z])/g, "$1-$2") // Convert camelCase and PascalCase to kebab-case + .replace(/_/g, "-") // Convert snake_case to kebab-case + .toLowerCase(); // Ensure all characters are lowercase } /** @@ -27,9 +27,9 @@ export function anyCaseToKebabCase(str: string): string { * @returns The converted string in PascalCase. */ export function anyCaseToPascalCase(str: string): string { - return str - .replace(/[-_]+(.)?/g, (_, char) => (char ? char.toUpperCase() : '')) - .replace(/^[a-z]/, (char) => char.toUpperCase()); + return str + .replace(/[-_]+(.)?/g, (_, char) => (char ? char.toUpperCase() : "")) + .replace(/^[a-z]/, (char) => char.toUpperCase()); } /** @@ -38,10 +38,10 @@ export function anyCaseToPascalCase(str: string): string { * @returns The converted string in snake_case. */ export function anyCaseToSnakeCase(str: string): string { - return str - .replace(/([a-z0-9])([A-Z])/g, '$1_$2') - .replace(/[-]+/g, '_') - .toLowerCase(); + return str + .replace(/([a-z0-9])([A-Z])/g, "$1_$2") + .replace(/[-]+/g, "_") + .toLowerCase(); } /** @@ -50,7 +50,9 @@ export function anyCaseToSnakeCase(str: string): string { * @returns The converted string in UPPER CASE. */ export function anyCaseToUpperCase(str: string): string { - return str.replace(/[-_]+(.)?/g, (_, char) => (char ? char.toUpperCase() : '')).toUpperCase(); + return str + .replace(/[-_]+(.)?/g, (_, char) => (char ? char.toUpperCase() : "")) + .toUpperCase(); } /** @@ -59,5 +61,7 @@ export function anyCaseToUpperCase(str: string): string { * @returns The converted string in lower case. */ export function anyCaseToLowerCase(str: string): string { - return str.replace(/[-_]+(.)?/g, (_, char) => (char ? char.toLowerCase() : '')).toLowerCase(); -} \ No newline at end of file + return str + .replace(/[-_]+(.)?/g, (_, char) => (char ? char.toLowerCase() : "")) + .toLowerCase(); +} From 8fe9df5ff38e4683f2cb314df0ae9e2d1133e74a Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Sun, 17 Nov 2024 19:32:18 -0800 Subject: [PATCH 12/21] feat: update package dependencies and enhance CLI command handling --- package.json | 2 +- src/cli.ts | 2 +- src/commands/project.commands.ts | 19 ++++--------------- src/new/form.ts | 14 ++++++-------- 4 files changed, 12 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index e6beb6a..408aa1f 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "release-it": "16.3.0", "shx": "0.3.4", "ts-jest": "^29.2.5", - "ts-node-dev": "2.0.0", + "tsx": "^4.19.2", "typescript": "5.2.2" }, "release-it": { diff --git a/src/cli.ts b/src/cli.ts index dffe835..56aaa19 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -22,7 +22,7 @@ import { scriptsCommand } from "./scripts"; * The current version of the ExpressoTS Bundle. * core, adapters, and cli. */ -export const BUNDLE_VERSION = "2.16.2"; +export const BUNDLE_VERSION = "3.0.0-beta.1"; stdout.write(`\n${[chalk.bold.green("🐎 Expressots")]}\n\n`); diff --git a/src/commands/project.commands.ts b/src/commands/project.commands.ts index 500d109..41c001d 100644 --- a/src/commands/project.commands.ts +++ b/src/commands/project.commands.ts @@ -45,22 +45,13 @@ function getOutDir(): string { * @returns The configuration */ const opinionatedConfig: Array = [ - "--transpile-only", - "--clear", - "-r", - "dotenv/config", + "--watch", "-r", "tsconfig-paths/register", "./src/main.ts", ]; -const nonOpinionatedConfig: Array = [ - "--transpile-only", - "--clear", - "-r", - "dotenv/config", - "./src/main.ts", -]; +const nonOpinionatedConfig: Array = ["--watch", "./src/main.ts"]; /** * Dev command module @@ -191,7 +182,7 @@ export const runCommand = async ({ switch (command) { case "dev": execCmd( - "tsnd", + "tsx", opinionated ? opinionatedConfig : nonOpinionatedConfig, ); break; @@ -219,14 +210,12 @@ export const runCommand = async ({ let config: Array = []; if (opinionated) { config = [ - "-r", - "dotenv/config", "-r", `./${outDir}/register-path.js`, `./${outDir}/src/main.js`, ]; } else { - config = ["-r", "dotenv/config", `./${outDir}/main.js`]; + config = [`./${outDir}/main.js`]; } clearScreen(); execCmd("node", config); diff --git a/src/new/form.ts b/src/new/form.ts index d8a0aa6..2b72c5e 100644 --- a/src/new/form.ts +++ b/src/new/form.ts @@ -28,11 +28,9 @@ async function packageManagerInstall({ let installCommand: string = "install --prefer-offline"; if (packageManager === "yarn") { installCommand = "install --ignore-engines"; - } else if ( - packageManager === "bun" || - packageManager === "pnpm" || - packageManager === "yarn" - ) { + } else if (packageManager === "pnpm") { + installCommand = "install --silent"; + } else if (packageManager === "bun" || packageManager === "yarn") { installCommand = "install"; } @@ -243,9 +241,11 @@ const projectForm = async ( const [_, template] = answer.template.match(/(.*) ::/) as Array; + const repo: string = `expressots/templates/${templates[template]}#${BUNDLE_VERSION}`; + try { const emitter = degit( - `expressots/templates/${templates[template]}#${BUNDLE_VERSION}`, + `expressots/templates/${templates[template]}`, ); await emitter.clone(answer.name); @@ -275,8 +275,6 @@ const projectForm = async ( name: projectName, }); - renameEnvFile(answer.name); - progressBar.update(100); progressBar.stop(); From 88e7b7cde0d95f97dedc48ccff10966fb3b70d7c Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Mon, 18 Nov 2024 01:39:53 -0800 Subject: [PATCH 13/21] refactor: streamline package manager install command arguments and improve logic --- src/new/form.ts | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/new/form.ts b/src/new/form.ts index 2b72c5e..596eabb 100644 --- a/src/new/form.ts +++ b/src/new/form.ts @@ -19,22 +19,16 @@ async function packageManagerInstall({ directory: string; progressBar: SingleBar; }) { - return new Promise((resolve, reject) => { - const isWindows: boolean = process.platform === "win32"; - const command: string = isWindows - ? `${packageManager}.cmd` - : packageManager; - - let installCommand: string = "install --prefer-offline"; - if (packageManager === "yarn") { - installCommand = "install --ignore-engines"; - } else if (packageManager === "pnpm") { - installCommand = "install --silent"; - } else if (packageManager === "bun" || packageManager === "yarn") { - installCommand = "install"; - } + const command: string = + process.platform === "win32" ? `${packageManager}.cmd` : packageManager; - const installProcess = spawn(command, [installCommand], { + const args = ["install", "--prefer-offline", "--silent"]; + if (packageManager === "yarn") { + args.push("--ignore-engines"); + args.splice(args.indexOf("--prefer-offline"), 1); + } + return new Promise((resolve, reject) => { + const installProcess = spawn(command, args, { cwd: directory, shell: true, timeout: 600000, From 97c7ee4b1af90d65332372a34f7feac7fb00466a Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Mon, 18 Nov 2024 02:04:36 -0800 Subject: [PATCH 14/21] fix: update development command to use tsx and adjust template copy path --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 408aa1f..e083ece 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,9 @@ "prepare": "husky", "start:build": "npm run build && npm run start", "start": "node ./bin/cli.js", - "start:dev": "tsnd ./src/cli.ts", + "start:dev": "tsx ./src/cli.ts", "build": "npm run clean && tsc -p tsconfig.json && npm run cp:templates && node scripts/chmod.js ./bin/cli.js", - "cp:templates": "node scripts/cp.js ./src/generate/templates ./bin/generate/templates", + "cp:templates": "node scripts/cp.js ./src/generate/templates ./bin/generate/", "clean": "node scripts/rm.js bin", "prepublish": "npm run build && npm pack", "publish": "npm publish --tag latest", From 95fd9e64dcd0141a3dcd519d0c7138e624a14858 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Mon, 18 Nov 2024 15:50:15 -0800 Subject: [PATCH 15/21] feat: add alias for CLI command and improve module import syntax --- package.json | 3 ++- src/generate/templates/nonopinionated/module.tpl | 3 +-- src/providers/create/form.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e083ece..16958bd 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "url": "https://github.com/expressots/expressots-cli/issues" }, "bin": { - "expressots": "bin/cli.js" + "expressots": "bin/cli.js", + "ex": "bin/cli.js" }, "engines": { "node": ">=18.20.5" diff --git a/src/generate/templates/nonopinionated/module.tpl b/src/generate/templates/nonopinionated/module.tpl index beef305..7f7de62 100644 --- a/src/generate/templates/nonopinionated/module.tpl +++ b/src/generate/templates/nonopinionated/module.tpl @@ -1,4 +1,3 @@ -import { ContainerModule } from "inversify"; -import { CreateModule } from "@expressots/core"; +import { ContainerModule, CreateModule } from "@expressots/core"; export const {{moduleName}}{{schematic}}: ContainerModule = CreateModule([]); diff --git a/src/providers/create/form.ts b/src/providers/create/form.ts index 0554f48..0494989 100644 --- a/src/providers/create/form.ts +++ b/src/providers/create/form.ts @@ -57,7 +57,7 @@ export const createExternalProvider = async ( } try { - const emitter = degit(`expressots/expressots-provider-template`); + const emitter = degit(`expressots/templates/provider`); await emitter.clone(providerInfo.providerName); changePackageName({ From 8f2ab9075272699f60e9a9c1e7ecc1be50f2df59 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Mon, 18 Nov 2024 16:03:10 -0800 Subject: [PATCH 16/21] chore: update Node.js engine requirement to version 20.18.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 16958bd..2a6e80a 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "ex": "bin/cli.js" }, "engines": { - "node": ">=18.20.5" + "node": ">=20.18.0" }, "funding": { "type": "github", From 9a2902456b1fc7c795ccec60420767bccbfd637e Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Mon, 18 Nov 2024 20:50:45 -0800 Subject: [PATCH 17/21] feat: add remove provider command and enhance add provider functionality --- src/cli.ts | 3 +- src/help/form.ts | 7 +- src/providers/add/cli.ts | 31 ++++++- src/providers/add/form.ts | 166 +++++++++++++++++++++++--------------- 4 files changed, 137 insertions(+), 70 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 56aaa19..03c941b 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -13,7 +13,7 @@ import { generateProject } from "./generate"; import { helpCommand } from "./help/cli"; import { infoProject } from "./info"; import { createProject } from "./new"; -import { addProviderCMD } from "./providers"; +import { addProviderCMD, removeProviderCMD } from "./providers"; import { createExternalProviderCMD } from "./providers/create/cli"; import { printError } from "./utils/cli-ui"; import { scriptsCommand } from "./scripts"; @@ -34,6 +34,7 @@ yargs(hideBin(process.argv)) .command(prodCommand) .command(createExternalProviderCMD()) .command(addProviderCMD()) + .command(removeProviderCMD()) .command(generateProject()) .command(scriptsCommand()) .command(infoProject()) diff --git a/src/help/form.ts b/src/help/form.ts index a027853..812641a 100644 --- a/src/help/form.ts +++ b/src/help/form.ts @@ -27,7 +27,12 @@ const helpForm = async (): Promise => { ["dto", "g d", "Generate a dto"], ["entity", "g e", "Generate an entity"], ["provider", "g p", "Generate internal provider"], - ["provider", "add", "Add external provider to the project"], + [ + "provider", + "add", + "Add provider to the project. Use -d to add as dev dependency", + ], + ["provider", "remove", "Remove provider from the project"], ["provider", "create", "Create external provider"], ["module", "g mo", "Generate a module"], ["middleware", "g mi", "Generate a middleware"], diff --git a/src/providers/add/cli.ts b/src/providers/add/cli.ts index 7d92fe6..55bc610 100644 --- a/src/providers/add/cli.ts +++ b/src/providers/add/cli.ts @@ -1,5 +1,5 @@ import { Argv, CommandModule } from "yargs"; -import { addExternalProvider } from "./form"; +import { addProvider, removeProvider } from "./form"; // eslint-disable-next-line @typescript-eslint/ban-types type CommandModuleArgs = {}; @@ -17,13 +17,36 @@ export const addProviderCMD = (): CommandModule => { .option("version", { describe: "The provider version to be installed", type: "string", - default: "latest", + default: false, alias: "v", + }) + .option("dev", { + describe: "Add provider as a dev dependency", + type: "boolean", + default: false, + alias: "d", }); return yargs; }, - handler: async ({ provider, version }) => { - await addExternalProvider(provider, version); + handler: async ({ provider, version, dev }) => { + await addProvider(provider, version, dev); + }, + }; +}; + +export const removeProviderCMD = (): CommandModule => { + return { + command: "remove ", + describe: "Remove provider from the project.", + builder: (yargs: Argv): Argv => { + yargs.positional("provider", { + describe: "The provider to be removed from the project", + type: "string", + }); + return yargs; + }, + handler: async ({ provider: packageName }) => { + await removeProvider(packageName); }, }; }; diff --git a/src/providers/add/form.ts b/src/providers/add/form.ts index 88e99e1..8d53299 100644 --- a/src/providers/add/form.ts +++ b/src/providers/add/form.ts @@ -4,103 +4,141 @@ import fs from "node:fs"; import { exit } from "node:process"; import { printError } from "../../utils/cli-ui"; -export const addExternalProvider = async ( - provider: string, - version: string, -): Promise => { - await installProvider(provider, version); +type PackageManagerConfig = { + install: string; + addDev: string; + remove: string; }; -async function installProvider(provider: string, version: string) { - const packageManager = fs.existsSync( - "package-lock.json" || "yarn.lock" || "pnpm-lock.yaml", - ) - ? "npm" - : fs.existsSync("yarn.lock") - ? "yarn" - : fs.existsSync("pnpm-lock.yaml") - ? "pnpm" - : null; - - if (packageManager) { - console.log(`Installing ${provider} provider ...`); - const currentVersion = version === "latest" ? "" : `@${version}`; - await execProcess({ - commandArg: packageManager, - args: ["add", `${provider}${currentVersion}`, "--prefer-offline"], - directory: process.cwd(), - }); - } else { - printError( - "No package manager found in the project", - "install-provider", - ); - return; +type PackageManager = { + npm: PackageManagerConfig; + yarn: PackageManagerConfig; + pnpm: PackageManagerConfig; +}; + +const PACKAGE_MANAGERS: PackageManager = { + npm: { + install: "install", + addDev: "install --save-dev", + remove: "uninstall", + }, + yarn: { + install: "add", + addDev: "add --dev", + remove: "remove", + }, + pnpm: { + install: "add", + addDev: "add --save-dev", + remove: "remove", + }, +}; + +function detectPackageManager(): string | null { + const lockFiles = ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"]; + const managers = Object.keys(PACKAGE_MANAGERS); + + for (let i = 0; i < lockFiles.length; i++) { + if (fs.existsSync(lockFiles[i])) { + return managers[i]; + } } + return null; } async function execProcess({ - commandArg, + command, args, directory, }: { - commandArg: string; + command: string; args: string[]; directory: string; -}) { +}): Promise { return new Promise((resolve, reject) => { - const isWindows: boolean = process.platform === "win32"; - const command: string = isWindows ? `${commandArg}.cmd` : commandArg; + const isWindows = process.platform === "win32"; + const execCommand = isWindows ? `${command}.cmd` : command; - const installProcess = spawn(command, args, { + const processRunner = spawn(execCommand, args, { cwd: directory, shell: true, }); - console.log( - chalk.bold.blue(`Executing: ${commandArg} ${args.join(" ")}`), - ); + console.log(chalk.bold.blue(`Executing: ${command} ${args.join(" ")}`)); console.log( chalk.yellow("-------------------------------------------------"), ); - installProcess.stdout.on("data", (data) => { - console.log(chalk.green(data.toString().trim())); // Display regular messages in green + processRunner.stdout.on("data", (data) => { + console.log(chalk.green(data.toString().trim())); }); - installProcess.stderr.on("data", (data) => { - console.error(chalk.red(data.toString().trim())); // Display error messages in red + processRunner.stderr.on("data", (data) => { + console.error(chalk.red(data.toString().trim())); }); - installProcess.on("close", (code) => { + processRunner.on("close", (code) => { if (code === 0) { console.log( - chalk.bold.green( - "-------------------------------------------------", - ), + chalk.bold.green("Operation completed successfully!\n"), ); - console.log(chalk.bold.green("Installation Done!\n")); - resolve("Installation Done!"); + resolve(); } else { console.error( - chalk.bold.red("---------------------------------------"), - ); - console.error( - chalk.bold.red( - `Command ${command} ${args.join( - " ", - )} exited with code ${code}`, - ), - ); - reject( - new Error( - `Command ${command} ${args.join( - " ", - )} exited with code ${code}`, - ), + chalk.bold.red(`Command failed with exit code ${code}`), ); + reject(new Error(`Command failed with exit code ${code}`)); exit(1); } }); }); } + +export async function addProvider( + packageName: string, + version?: string, + isDevDependency = false, +): Promise { + const packageManager = detectPackageManager(); + + if (!packageManager) { + printError("No package manager found in the project", "add-package"); + return; + } + + const pkgManagerConfig: PackageManagerConfig = + PACKAGE_MANAGERS[packageManager as keyof PackageManager]; + + const command = isDevDependency + ? pkgManagerConfig.addDev + : pkgManagerConfig.install; + const versionSuffix = version && version !== "latest" ? `@${version}` : ""; + + console.log( + `${isDevDependency ? "Adding devDependency" : "Installing"} ${packageName}...`, + ); + await execProcess({ + command: packageManager, + args: [...command.split(" "), `${packageName}${versionSuffix}`], + directory: process.cwd(), + }); +} + +export async function removeProvider(packageName: string): Promise { + const packageManager = detectPackageManager(); + + if (!packageManager) { + printError("No package manager found in the project", "remove-package"); + return; + } + + const command = + PACKAGE_MANAGERS[packageManager as keyof PackageManager].remove; + + console.log(`Removing ${packageName}...`); + await execProcess({ + command: packageManager, + args: [...command.split(" "), packageName], + directory: process.cwd(), + }); +} From 6d99bc34adaf54c7e152cc34601c3db4e2480928 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Tue, 19 Nov 2024 00:17:53 -0800 Subject: [PATCH 18/21] refactor: remove BaseController inheritance and adjusting method signatures --- .../opinionated/controller-service-delete.tpl | 20 +++++----------- .../opinionated/controller-service-get.tpl | 22 ++++++----------- .../opinionated/controller-service-patch.tpl | 24 ++++++------------- .../opinionated/controller-service-post.tpl | 21 ++++++---------- .../opinionated/controller-service-put.tpl | 24 ++++++------------- .../opinionated/controller-service.tpl | 3 +-- .../templates/opinionated/module-service.tpl | 3 +-- src/generate/templates/opinionated/module.tpl | 3 +-- 8 files changed, 37 insertions(+), 83 deletions(-) diff --git a/src/generate/templates/opinionated/controller-service-delete.tpl b/src/generate/templates/opinionated/controller-service-delete.tpl index 7d6c0e2..3764b47 100644 --- a/src/generate/templates/opinionated/controller-service-delete.tpl +++ b/src/generate/templates/opinionated/controller-service-delete.tpl @@ -1,21 +1,13 @@ -import { BaseController, StatusCode } from "@expressots/core"; -import { controller, Delete, param, response } from "@expressots/adapter-express"; -import { Response } from "express"; +import { controller, Delete, param } from "@expressots/adapter-express"; +import { inject } from "@expressots/core"; import { {{className}}UseCase } from "./{{fileName}}.usecase"; -import { I{{className}}RequestDTO, I{{className}}ResponseDTO } from "./{{fileName}}.dto"; @controller("/{{{route}}}") -export class {{className}}Controller extends BaseController { - constructor(private {{useCase}}UseCase: {{className}}UseCase) { - super(); - } +export class {{className}}Controller { + @inject({{className}}UseCase) private {{useCase}}UseCase: {{className}}UseCase; @Delete("/:id") - execute(@param("id") id: string, @response() res: Response): I{{className}}ResponseDTO { - return this.callUseCase( - this.{{useCase}}UseCase.execute(id), - res, - StatusCode.OK, - ); + execute(@param("id") id: string) { + return this.{{useCase}}UseCase.execute(id); } } diff --git a/src/generate/templates/opinionated/controller-service-get.tpl b/src/generate/templates/opinionated/controller-service-get.tpl index ed8ad1f..1d87d32 100644 --- a/src/generate/templates/opinionated/controller-service-get.tpl +++ b/src/generate/templates/opinionated/controller-service-get.tpl @@ -1,21 +1,13 @@ -import { BaseController, StatusCode } from "@expressots/core"; -import { controller, Get, response } from "@expressots/adapter-express"; -import { Response } from "express"; +import { controller, Get } from "@expressots/adapter-express"; +import { inject } from "@expressots/core"; import { {{className}}UseCase } from "./{{fileName}}.usecase"; -import { I{{className}}ResponseDTO } from "./{{fileName}}.dto"; @controller("/{{{route}}}") -export class {{className}}Controller extends BaseController { - constructor(private {{useCase}}UseCase: {{className}}UseCase) { - super(); - } - +export class {{className}}Controller { + @inject({{className}}UseCase) private {{useCase}}UseCase: {{className}}UseCase; + @Get("/") - execute(@response() res: Response): I{{className}}ResponseDTO { - return this.callUseCase( - this.{{useCase}}UseCase.execute(), - res, - StatusCode.OK, - ); + execute() { + return this.{{useCase}}UseCase.execute(); } } diff --git a/src/generate/templates/opinionated/controller-service-patch.tpl b/src/generate/templates/opinionated/controller-service-patch.tpl index c7dd160..8ebdc36 100644 --- a/src/generate/templates/opinionated/controller-service-patch.tpl +++ b/src/generate/templates/opinionated/controller-service-patch.tpl @@ -1,24 +1,14 @@ -import { BaseController, StatusCode } from "@expressots/core"; -import { controller, Patch, body, param, response } from "@expressots/adapter-express"; -import { Response } from "express"; +import { controller, Patch, body } from "@expressots/adapter-express"; +import { inject } from "@expressots/core"; import { {{className}}UseCase } from "./{{fileName}}.usecase"; -import { I{{className}}RequestDTO, I{{className}}ResponseDTO } from "./{{fileName}}.dto"; +import { I{{className}}RequestDTO } from "./{{fileName}}.dto"; @controller("/{{{route}}}") -export class {{className}}Controller extends BaseController { - constructor(private {{useCase}}UseCase: {{className}}UseCase) { - super(); - } +export class {{className}}Controller { + @inject({{className}}UseCase) private {{useCase}}UseCase: {{className}}UseCase; @Patch("/") - execute( - @body() payload: I{{className}}RequestDTO, - @response() res: Response, - ): I{{className}}ResponseDTO { - return this.callUseCase( - this.{{useCase}}UseCase.execute(payload), - res, - StatusCode.OK, - ); + execute(@body() payload: I{{className}}RequestDTO) { + return this.{{useCase}}UseCase.execute(payload); } } diff --git a/src/generate/templates/opinionated/controller-service-post.tpl b/src/generate/templates/opinionated/controller-service-post.tpl index ac8481d..f9be0f0 100644 --- a/src/generate/templates/opinionated/controller-service-post.tpl +++ b/src/generate/templates/opinionated/controller-service-post.tpl @@ -1,21 +1,14 @@ -import { BaseController, StatusCode } from "@expressots/core"; -import { controller, Post, body, response } from "@expressots/adapter-express"; -import { Response } from "express"; +import { body, controller, Post } from "@expressots/adapter-express"; +import { inject } from "@expressots/core"; import { {{className}}UseCase } from "./{{fileName}}.usecase"; -import { I{{className}}RequestDTO, I{{className}}ResponseDTO } from "./{{fileName}}.dto"; +import { I{{className}}RequestDTO } from "./{{fileName}}.dto"; @controller("/{{{route}}}") -export class {{className}}Controller extends BaseController { - constructor(private {{useCase}}UseCase: {{className}}UseCase) { - super(); - } +export class {{className}}Controller { + @inject({{className}}UseCase) private {{useCase}}UseCase: {{className}}UseCase; @Post("/") - execute(@body() payload: I{{className}}RequestDTO, @response() res: Response): I{{className}}ResponseDTO { - return this.callUseCase( - this.{{useCase}}UseCase.execute(payload), - res, - StatusCode.Created, - ); + execute(@body() payload: I{{className}}RequestDTO) { + return this.{{useCase}}UseCase.execute(payload); } } diff --git a/src/generate/templates/opinionated/controller-service-put.tpl b/src/generate/templates/opinionated/controller-service-put.tpl index 806c782..153662c 100644 --- a/src/generate/templates/opinionated/controller-service-put.tpl +++ b/src/generate/templates/opinionated/controller-service-put.tpl @@ -1,24 +1,14 @@ -import { BaseController, StatusCode } from "@expressots/core"; -import { controller, Put, body, param, response } from "@expressots/adapter-express"; -import { Response } from "express"; +import { body, controller, Put } from "@expressots/adapter-express"; +import { inject } from "@expressots/core"; import { {{className}}UseCase } from "./{{fileName}}.usecase"; -import { I{{className}}RequestDTO, I{{className}}ResponseDTO } from "./{{fileName}}.dto"; +import { I{{className}}RequestDTO } from "./{{fileName}}.dto"; @controller("/{{{route}}}") -export class {{className}}Controller extends BaseController { - constructor(private {{useCase}}UseCase: {{className}}UseCase) { - super(); - } +export class {{className}}Controller { + @inject({{className}}UseCase) private {{useCase}}UseCase: {{className}}UseCase; @Put("/") - execute( - @body() payload: I{{className}}RequestDTO, - @response() res: Response, - ): I{{className}}ResponseDTO { - return this.callUseCase( - this.{{useCase}}UseCase.execute(payload), - res, - StatusCode.OK, - ); + execute(@body() payload: I{{className}}RequestDTO) { + return this.{{useCase}}UseCase.execute(payload); } } diff --git a/src/generate/templates/opinionated/controller-service.tpl b/src/generate/templates/opinionated/controller-service.tpl index cad0b78..6969e5a 100644 --- a/src/generate/templates/opinionated/controller-service.tpl +++ b/src/generate/templates/opinionated/controller-service.tpl @@ -1,8 +1,7 @@ -import { BaseController } from "@expressots/core"; import { controller, {{method}} } from "@expressots/adapter-express"; @controller("/{{{route}}}") -export class {{className}}Controller extends BaseController { +export class {{className}}Controller { @{{method}}("/") execute() { return "Ok"; diff --git a/src/generate/templates/opinionated/module-service.tpl b/src/generate/templates/opinionated/module-service.tpl index 9ef58e7..255eeec 100644 --- a/src/generate/templates/opinionated/module-service.tpl +++ b/src/generate/templates/opinionated/module-service.tpl @@ -1,5 +1,4 @@ -import { ContainerModule } from "inversify"; -import { CreateModule } from "@expressots/core"; +import { CreateModule, ContainerModule } from "@expressots/core"; import { {{className}}Controller } from "{{{path}}}"; export const {{moduleName}}Module: ContainerModule = CreateModule([{{className}}Controller]); diff --git a/src/generate/templates/opinionated/module.tpl b/src/generate/templates/opinionated/module.tpl index 45df72d..e32c88f 100644 --- a/src/generate/templates/opinionated/module.tpl +++ b/src/generate/templates/opinionated/module.tpl @@ -1,4 +1,3 @@ -import { ContainerModule } from "inversify"; -import { CreateModule } from "@expressots/core"; +import { CreateModule, ContainerModule } from "@expressots/core"; export const {{moduleName}}Module: ContainerModule = CreateModule([]); From dbb484099b90c435d79607bf7e5541e3446e7ffd Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Tue, 19 Nov 2024 15:09:06 -0800 Subject: [PATCH 19/21] refactor: update app container file references and improve module extraction logic --- .gitignore | 2 +- src/utils/add-module-to-container.ts | 31 ++++++++++++++++++---------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index c10c608..fe87823 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,7 @@ expressots.config.ts *.tgz -*.container.ts +app.ts coverage/ .early.coverage diff --git a/src/utils/add-module-to-container.ts b/src/utils/add-module-to-container.ts index 0700757..66a5d38 100644 --- a/src/utils/add-module-to-container.ts +++ b/src/utils/add-module-to-container.ts @@ -4,12 +4,12 @@ import fs from "node:fs"; import { printError } from "./cli-ui"; import Compiler from "./compiler"; -const APP_CONTAINER = "app.container.ts"; +const APP_CONTAINER = "app.ts"; type AppContainerType = { regex: RegExp; path: string; - content: RegExpMatchArray; + content: string; modules: string[]; imports: string[]; notImports: string[]; @@ -20,6 +20,7 @@ async function validateAppContainer(): Promise { const imports: string[] = []; const notImports: string[] = []; + // Locate the container file const path = globSync(`./${sourceRoot}/${APP_CONTAINER}`, { absolute: true, ignore: "**/node_modules/**", @@ -33,8 +34,10 @@ async function validateAppContainer(): Promise { process.exit(1); } + // Read the container file const fileContent = await fs.promises.readFile(path[0], "utf8"); + // Collect imports and other lines fileContent.split("\n").forEach((line: string) => { if (line.startsWith("import")) { imports.push(line); @@ -43,25 +46,30 @@ async function validateAppContainer(): Promise { } }); - // Validate the file content - const moduleDeclarationRegex = /.create\(\s*\[([\s\S]*?)]/; - const moduleDeclarationMatch = fileContent.match(moduleDeclarationRegex); + // Regex to detect and extract modules from configContainer + const moduleRegex = /this\.configContainer\(\s*\[\s*([\s\S]*?)\s*]\s*\)/; - if (!moduleDeclarationMatch) { - printError("Container format incorrect!", APP_CONTAINER); + const moduleMatch = fileContent.match(moduleRegex); + + if (!moduleMatch) { + printError( + "The App class does not contain a valid configContainer([]) declaration!", + APP_CONTAINER, + ); process.exit(1); } - const modules = moduleDeclarationMatch[1] + // Extract modules if present + const modules = moduleMatch[1] .trim() .split(",") .filter((m) => m.trim() !== "") .map((m) => m.trim()); return { - regex: moduleDeclarationRegex, + regex: moduleRegex, path: path[0], - content: moduleDeclarationMatch, + content: fileContent, modules, imports, notImports, @@ -73,6 +81,7 @@ async function addModuleToContainer( modulePath?: string, path?: string, ) { + console.log("To chamando esse cara"); const containerData: AppContainerType = await validateAppContainer(); const moduleName = (name[0].toUpperCase() + name.slice(1)).trimStart(); @@ -109,7 +118,7 @@ async function addModuleToContainer( containerData.modules.push(`${moduleName}Module`); const newModule = containerData.modules.join(", "); - const newModuleDeclaration = `.create([${newModule}]`; + const newModuleDeclaration = `this.configContainer([${newModule}])`; const newFileContent = [ ...containerData.imports, From 4f5c54dac3d6b4717820e2b8349e52abe47da931 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Tue, 19 Nov 2024 15:14:28 -0800 Subject: [PATCH 20/21] fix: update module declaration syntax in add-module-to-container utility --- src/utils/add-module-to-container.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/add-module-to-container.ts b/src/utils/add-module-to-container.ts index 66a5d38..34ef530 100644 --- a/src/utils/add-module-to-container.ts +++ b/src/utils/add-module-to-container.ts @@ -166,7 +166,7 @@ async function addModuleToContainerNestedPath(name: string, path?: string) { containerData.modules.push(`${moduleName}Module`); const newModule = containerData.modules.join(", "); - const newModuleDeclaration = `.create([${newModule}]`; + const newModuleDeclaration = `this.configContainer([${newModule}])`; const newFileContent = [ ...containerData.imports, From fc3f48df45d4e16fa86512adb1a107833950d8a0 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Tue, 19 Nov 2024 15:33:18 -0800 Subject: [PATCH 21/21] feat: add test dir ESLint & update Jest config for improved testing structure --- .eslintrc.cjs | 1 + jest.config.ts | 36 ++++++++++++++++++------------------ test/test.spec.ts | 6 ++++-- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index ce3b248..7aa6b3e 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -24,6 +24,7 @@ module.exports = { ".eslintrc.cjs", "coverage/*", "scripts/*", + "test/**/*.spec.ts", ], rules: { "@typescript-eslint/interface-name-prefix": "off", diff --git a/jest.config.ts b/jest.config.ts index b07211a..206eaad 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,24 +1,24 @@ import type { JestConfigWithTsJest } from "ts-jest"; const config: JestConfigWithTsJest = { - testEnvironment: "node", - roots: ["/src"], - testRegex: ".*\\.spec\\.ts$", - testPathIgnorePatterns: ["/node_modules/", "/bin/"], - collectCoverageFrom: ["src/**/*.ts", "!**/*.spec.ts", "src/**/index.ts"], - moduleNameMapper: { - "^@src/(.*)$": "/src/$1", - }, - setupFiles: ["reflect-metadata"], - transform: { - "^.+\\.ts$": [ - "ts-jest", - { - tsconfig: "tsconfig.json", - // Add any ts-jest specific options here - }, - ], - }, + testEnvironment: "node", + roots: ["/src", "/test"], + testRegex: ".*\\.spec\\.ts$", + testPathIgnorePatterns: ["/node_modules/", "/bin/"], + collectCoverageFrom: ["src/**/*.ts", "!**/*.spec.ts", "src/**/index.ts"], + moduleNameMapper: { + "^@src/(.*)$": "/src/$1", + }, + setupFiles: ["reflect-metadata"], + transform: { + "^.+\\.ts$": [ + "ts-jest", + { + tsconfig: "tsconfig.json", + // Add any ts-jest specific options here + }, + ], + }, }; export default config; diff --git a/test/test.spec.ts b/test/test.spec.ts index 5628bdb..a673e4c 100644 --- a/test/test.spec.ts +++ b/test/test.spec.ts @@ -1,3 +1,5 @@ -it("pass", () => { - expect(true).toBe(true); +describe("Test", () => { + it("pass", () => { + expect(true).toBe(true); + }); });