@koalarx/nest 4.0.2 → 4.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,6 +26,7 @@ import {
26
26
  resolveProjectFeatures
27
27
  } from "../../utils/install-module.js";
28
28
  import { fixLintConfig } from "./fix-lint-config.js";
29
+ import { finalizeNewProjectSetup } from "../../utils/install-workspace-config.js";
29
30
  async function promptAuthStrategies(template) {
30
31
  const isCrud = template === Template.CRUD_SAMPLE;
31
32
  return assertNotCancel(await p.multiselect({
@@ -180,6 +181,8 @@ export async function runNew(args = []) {
180
181
  auth: authStrategies,
181
182
  features
182
183
  });
184
+ spinner.message("Configurando workspace (.vscode e .env)...");
185
+ finalizeNewProjectSetup(project.name, project.packageManager);
183
186
  spinner.stop("Projeto criado com sucesso!");
184
187
  const projectFeatures = resolveProjectFeatures(features, authStrategies);
185
188
  const extrasSummary = [
@@ -196,7 +199,8 @@ export async function runNew(args = []) {
196
199
  `${color.bold("Gerenciador:")} ${project.packageManager}`,
197
200
  `${color.bold("Autenticação:")} ${formatAuthStrategies(authStrategies)}`,
198
201
  `${color.bold("Extras:")} ${extrasSummary || color.dim("nenhum")}`,
199
- `${color.dim("Depois:")} cd ${project.name} && kl-nest add <feature>`
202
+ `${color.dim("Depois:")} cd ${project.name} && ${project.packageManager} start`,
203
+ `${color.dim("Extras:")} kl-nest add <feature>`
200
204
  ].join(`
201
205
  `), "Resumo");
202
206
  }
@@ -5,6 +5,13 @@ import {
5
5
  resolveNewProjectOptions
6
6
  } from "./domain.js";
7
7
  import { resolveProjectFeatures } from "../utils/install-module.js";
8
+ export const WORKSPACE_SETUP_PATHS = [
9
+ ".vscode/launch.json",
10
+ ".vscode/settings.json",
11
+ ".vscode/tasks.json",
12
+ ".vscode/extensions.json",
13
+ ".env"
14
+ ];
8
15
  export const CORE_REQUIRED_PATHS = [
9
16
  "src/core/env.ts",
10
17
  "src/host/main.ts",
@@ -165,7 +172,7 @@ export function buildProjectExpectation(template, auth, features) {
165
172
  };
166
173
  }
167
174
  export function requiredPathsForExpectation(expectation) {
168
- const paths = [...CORE_REQUIRED_PATHS];
175
+ const paths = [...CORE_REQUIRED_PATHS, ...WORKSPACE_SETUP_PATHS];
169
176
  if (expectation.template === Template.CRUD_SAMPLE) {
170
177
  paths.push(...CRUD_TEMPLATE_REQUIRED_PATHS);
171
178
  }
@@ -0,0 +1,79 @@
1
+ import { cpSync, existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { getSourceCodePath } from "./get-source-code-path.js";
4
+ import { resolveProjectPath } from "./resolve-project-path.js";
5
+ const PACKAGE_MANAGER_COMMAND = {
6
+ bun: "bun",
7
+ npm: "npm",
8
+ pnpm: "pnpm"
9
+ };
10
+ export function projectNameToSnakeCase(projectName) {
11
+ const baseName = path.isAbsolute(projectName) ? path.basename(projectName) : projectName;
12
+ return baseName.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").replace(/__+/g, "_").toLowerCase().replace(/^_+|_+$/g, "");
13
+ }
14
+ function patchDatabaseUrl(content, databaseName) {
15
+ if (!/^DATABASE_URL=/m.test(content)) {
16
+ return content;
17
+ }
18
+ return content.replace(/^(DATABASE_URL=.+\/)([^/\r\n]+)(\s*)$/m, `$1${databaseName}$3`);
19
+ }
20
+ function patchJsonFile(filePath, patch) {
21
+ const content = readFileSync(filePath, "utf8");
22
+ const json = JSON.parse(content);
23
+ patch(json);
24
+ writeFileSync(filePath, `${JSON.stringify(json, null, 2)}
25
+ `);
26
+ }
27
+ function runScriptCommand(packageManager, script) {
28
+ return `${PACKAGE_MANAGER_COMMAND[packageManager]} run ${script}`;
29
+ }
30
+ export function installWorkspaceConfig(projectName, packageManager) {
31
+ const projectRoot = resolveProjectPath(projectName);
32
+ const sourceVscode = path.join(getSourceCodePath(), ".vscode");
33
+ const targetVscode = path.join(projectRoot, ".vscode");
34
+ cpSync(sourceVscode, targetVscode, { recursive: true });
35
+ const pmCommand = PACKAGE_MANAGER_COMMAND[packageManager];
36
+ patchJsonFile(path.join(targetVscode, "launch.json"), (launch) => {
37
+ const config = launch.configurations[0];
38
+ config.runtimeExecutable = pmCommand;
39
+ config.runtimeArgs = ["run", "start:debug"];
40
+ config.cwd = "${workspaceFolder}";
41
+ });
42
+ patchJsonFile(path.join(targetVscode, "tasks.json"), (tasks) => {
43
+ for (const task of tasks.tasks) {
44
+ if (task.command.includes("start:dev")) {
45
+ task.command = runScriptCommand(packageManager, "start:dev");
46
+ } else if (task.command.includes("test")) {
47
+ task.command = runScriptCommand(packageManager, "test");
48
+ } else if (task.command.includes("migration:run")) {
49
+ task.command = runScriptCommand(packageManager, "migration:run");
50
+ }
51
+ task.options = { cwd: "${workspaceFolder}" };
52
+ }
53
+ });
54
+ patchJsonFile(path.join(targetVscode, "settings.json"), (settings) => {
55
+ settings["npm.packageManager"] = packageManager;
56
+ });
57
+ const gitignoreSource = path.join(getSourceCodePath(), ".gitignore");
58
+ if (existsSync(gitignoreSource)) {
59
+ cpSync(gitignoreSource, path.join(projectRoot, ".gitignore"), {
60
+ force: true
61
+ });
62
+ }
63
+ }
64
+ export function createEnvFromExample(projectName) {
65
+ const projectRoot = resolveProjectPath(projectName);
66
+ const examplePath = path.join(projectRoot, ".env.example");
67
+ const envPath = path.join(projectRoot, ".env");
68
+ if (!existsSync(examplePath)) {
69
+ return;
70
+ }
71
+ const databaseName = projectNameToSnakeCase(projectName);
72
+ const envContent = patchDatabaseUrl(readFileSync(examplePath, "utf8"), databaseName);
73
+ writeFileSync(examplePath, envContent);
74
+ writeFileSync(envPath, envContent);
75
+ }
76
+ export function finalizeNewProjectSetup(projectName, packageManager) {
77
+ installWorkspaceConfig(projectName, packageManager);
78
+ createEnvFromExample(projectName);
79
+ }
@@ -1,61 +1,81 @@
1
- export function patchInfraModuleForCache(content) {
2
- if (content.includes("CacheServiceProvider")) {
3
- return content;
4
- }
5
- const withAuth = content.includes("ILoggedUserInfoService");
6
- let patched = content.replace("import { ILoggingService } from '@/domain/common/ilogging.service';", `import { ICacheService } from '@/domain/common/icache.service';
7
- import { ILoggingService } from '@/domain/common/ilogging.service';
8
- import { IRedLockService } from '@/domain/common/ired-lock.service';`).replace("import { LoggingService } from '@/infra/common/logging.service';", `import { CacheServiceProvider } from '@/infra/common/cache-service.provider';
9
- import { LoggingService } from '@/infra/common/logging.service';
10
- import { RedLockService } from '@/infra/common/red-lock.service';`).replace(` providers: [
11
- { provide: ILoggingService, useClass: LoggingService },`, ` providers: [
12
- CacheServiceProvider,
13
- { provide: ICacheService, useExisting: CacheServiceProvider },
14
- { provide: ILoggingService, useClass: LoggingService },
15
- { provide: IRedLockService, useClass: RedLockService },`);
16
- if (withAuth) {
17
- return patched.replace(" exports: [RepositoryModule, ILoggingService, ILoggedUserInfoService],", ` exports: [
18
- RepositoryModule,
19
- ICacheService,
20
- ILoggingService,
21
- IRedLockService,
22
- ILoggedUserInfoService,
23
- ],`);
24
- }
25
- return patched.replace(" exports: [RepositoryModule, ILoggingService],", ` exports: [
26
- RepositoryModule,
27
- ICacheService,
28
- ILoggingService,
29
- IRedLockService,
30
- ],`);
1
+ function hasCacheProviders(content) {
2
+ return content.includes("{ provide: ICacheService, useExisting: CacheServiceProvider }");
3
+ }
4
+ function hasAuthProviders(content) {
5
+ return content.includes("{ provide: ILoggedUserInfoService, useClass: LoggedUserInfoService }");
31
6
  }
32
- export const SLIM_INFRA_MODULE = `import { ILoggingService } from '@/domain/common/ilogging.service';
33
- import { Module } from '@nestjs/common';
34
- import { LoggingService } from '@/infra/common/logging.service';
35
- import { RepositoryModule } from '@/infra/repositories/repository.module';
7
+ export function buildInfraModule({ cache, auth }) {
8
+ const importLines = [
9
+ ...cache ? ["import { ICacheService } from '@/domain/common/icache.service';"] : [],
10
+ "import { ILoggingService } from '@/domain/common/ilogging.service';",
11
+ ...auth ? [
12
+ "import { ILoggedUserInfoService } from '@/domain/services/ilogged-user-info.service';"
13
+ ] : [],
14
+ ...cache ? ["import { IRedLockService } from '@/domain/common/ired-lock.service';"] : [],
15
+ "import { Module } from '@nestjs/common';",
16
+ ...cache ? [
17
+ "import { CacheServiceProvider } from '@/infra/common/cache-service.provider';"
18
+ ] : [],
19
+ "import { LoggingService } from '@/infra/common/logging.service';",
20
+ ...auth ? [
21
+ "import { LoggedUserInfoService } from '@/infra/services/logged-user-info.service';"
22
+ ] : [],
23
+ ...cache ? ["import { RedLockService } from '@/infra/common/red-lock.service';"] : [],
24
+ "import { RepositoryModule } from '@/infra/repositories/repository.module';"
25
+ ];
26
+ const providerLines = [
27
+ ...cache ? [
28
+ " CacheServiceProvider,",
29
+ " { provide: ICacheService, useExisting: CacheServiceProvider },"
30
+ ] : [],
31
+ " { provide: ILoggingService, useClass: LoggingService },",
32
+ ...cache ? [" { provide: IRedLockService, useClass: RedLockService },"] : [],
33
+ ...auth ? [
34
+ " { provide: ILoggedUserInfoService, useClass: LoggedUserInfoService },"
35
+ ] : []
36
+ ];
37
+ const exportLines = [
38
+ " RepositoryModule,",
39
+ ...cache ? [" ICacheService,"] : [],
40
+ " ILoggingService,",
41
+ ...cache ? [" IRedLockService,"] : [],
42
+ ...auth ? [" ILoggedUserInfoService,"] : []
43
+ ];
44
+ return `${importLines.join(`
45
+ `)}
36
46
 
37
47
  @Module({
38
48
  imports: [RepositoryModule],
39
- providers: [{ provide: ILoggingService, useClass: LoggingService }],
40
- exports: [RepositoryModule, ILoggingService],
49
+ providers: [
50
+ ${providerLines.join(`
51
+ `)}
52
+ ],
53
+ exports: [
54
+ ${exportLines.join(`
55
+ `)}
56
+ ],
41
57
  })
42
58
  export class InfraModule {}
43
59
  `;
44
- export function patchInfraModuleForAuth(content) {
45
- if (content.includes("ILoggedUserInfoService")) {
60
+ }
61
+ export function patchInfraModuleForCache(content) {
62
+ if (hasCacheProviders(content)) {
46
63
  return content;
47
64
  }
48
- let patched = content.replace("import { ILoggingService } from '@/domain/common/ilogging.service';", `import { ILoggingService } from '@/domain/common/ilogging.service';
49
- import { ILoggedUserInfoService } from '@/domain/services/ilogged-user-info.service';`).replace("import { LoggingService } from '@/infra/common/logging.service';", `import { LoggingService } from '@/infra/common/logging.service';
50
- import { LoggedUserInfoService } from '@/infra/services/logged-user-info.service';`).replace(" { provide: ILoggingService, useClass: LoggingService },", ` { provide: ILoggingService, useClass: LoggingService },
51
- { provide: ILoggedUserInfoService, useClass: LoggedUserInfoService },`);
52
- if (patched.includes("IRedLockService,")) {
53
- return patched.replace(` IRedLockService,
54
- ],`, ` IRedLockService,
55
- ILoggedUserInfoService,
56
- ],`);
65
+ return buildInfraModule({
66
+ cache: true,
67
+ auth: hasAuthProviders(content) || content.includes("ILoggedUserInfoService")
68
+ });
69
+ }
70
+ export const SLIM_INFRA_MODULE = buildInfraModule({ cache: false, auth: false });
71
+ export function patchInfraModuleForAuth(content) {
72
+ if (hasAuthProviders(content)) {
73
+ return content;
57
74
  }
58
- return patched.replace(" exports: [RepositoryModule, ILoggingService],", " exports: [RepositoryModule, ILoggingService, ILoggedUserInfoService],");
75
+ return buildInfraModule({
76
+ cache: hasCacheProviders(content) || content.includes("ICacheService"),
77
+ auth: true
78
+ });
59
79
  }
60
80
  export function stripInfraModuleCache(_content) {
61
81
  return SLIM_INFRA_MODULE;
@@ -0,0 +1,7 @@
1
+ {
2
+ "recommendations": [
3
+ "esbenp.prettier-vscode",
4
+ "dbaeumer.vscode-eslint",
5
+ "PKief.material-icon-theme"
6
+ ]
7
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "version": "0.2.0",
3
+ "configurations": [
4
+ {
5
+ "type": "node",
6
+ "request": "launch",
7
+ "name": "NestJS Debug",
8
+ "runtimeExecutable": "bun",
9
+ "runtimeArgs": ["run", "start:debug"],
10
+ "cwd": "${workspaceFolder}",
11
+ "autoAttachChildProcesses": true,
12
+ "restart": true,
13
+ "console": "integratedTerminal"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,77 @@
1
+ {
2
+ "material-icon-theme.activeIconPack": "nest",
3
+ "editor.fontFamily": "Fira Code",
4
+ "editor.fontSize": 15,
5
+ "editor.fontLigatures": true,
6
+ "workbench.startupEditor": "newUntitledFile",
7
+ "js/ts.suggest.autoImports": true,
8
+ "js/ts.updateImportsOnFileMove.enabled": "always",
9
+ "editor.rulers": [80, 120],
10
+ "extensions.ignoreRecommendations": true,
11
+ "files.associations": {
12
+ ".env.*": "dotenv",
13
+ ".prettierrc": "json"
14
+ },
15
+ "editor.parameterHints.enabled": false,
16
+ "editor.renderLineHighlight": "gutter",
17
+ "editor.lineHeight": 26,
18
+ "editor.suggestSelection": "first",
19
+ "explorer.confirmDelete": false,
20
+ "workbench.editor.labelFormat": "short",
21
+ "editor.acceptSuggestionOnCommitCharacter": false,
22
+ "explorer.compactFolders": false,
23
+ "git.enableSmartCommit": true,
24
+ "explorer.confirmDragAndDrop": false,
25
+ "terminal.integrated.fontSize": 14,
26
+ "breadcrumbs.enabled": true,
27
+ "editor.tabSize": 2,
28
+ "workbench.iconTheme": "material-icon-theme",
29
+ "material-icon-theme.languages.associations": {
30
+ "dotenv": "tune"
31
+ },
32
+ "material-icon-theme.files.associations": {
33
+ "*.e2e-spec.ts": "test-js",
34
+ "*.dto.ts": "diff",
35
+ "*.handler.ts": "esbuild",
36
+ "*.request.ts": "log",
37
+ "*.schema.ts": "scheme",
38
+ "*.response.ts": "conduct",
39
+ "*.validator.ts": "commitlint"
40
+ },
41
+ "material-icon-theme.folders.associations": {
42
+ "entities": "class",
43
+ "migrations": "tools",
44
+ "schemas": "class",
45
+ "domain": "class",
46
+ "infra": "app",
47
+ "dtos": "typescript",
48
+ "repositories": "mappings"
49
+ },
50
+ "[javascript]": {
51
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
52
+ "editor.formatOnSave": true,
53
+ "editor.codeActionsOnSave": {
54
+ "source.fixAll.eslint": "explicit"
55
+ }
56
+ },
57
+ "[typescript]": {
58
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
59
+ "editor.formatOnSave": true,
60
+ "editor.codeActionsOnSave": {
61
+ "source.fixAll.eslint": "explicit"
62
+ }
63
+ },
64
+ "prettier.documentSelectors": ["**/*.ts", "**/*.tsx", "**/*.js"],
65
+ "npm.scriptExplorerAction": "run",
66
+ "npm.packageManager": "bun",
67
+ "editor.wordWrap": "on",
68
+ "editor.wrappingIndent": "indent",
69
+ "eslint.workingDirectories": [{ "mode": "auto" }],
70
+ "eslint.validate": [
71
+ "javascript",
72
+ "javascriptreact",
73
+ "typescript",
74
+ "typescriptreact"
75
+ ],
76
+ "files.eol": "\n"
77
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "version": "2.0.0",
3
+ "tasks": [
4
+ {
5
+ "type": "shell",
6
+ "command": "bun run start:dev",
7
+ "options": {
8
+ "cwd": "${workspaceFolder}"
9
+ },
10
+ "group": {
11
+ "kind": "build",
12
+ "isDefault": true
13
+ },
14
+ "isBackground": true,
15
+ "problemMatcher": [],
16
+ "label": "Start API (dev)",
17
+ "detail": "nest start --watch"
18
+ },
19
+ {
20
+ "type": "shell",
21
+ "command": "bun run test",
22
+ "options": {
23
+ "cwd": "${workspaceFolder}"
24
+ },
25
+ "group": "test",
26
+ "label": "Run tests"
27
+ },
28
+ {
29
+ "type": "shell",
30
+ "command": "bun run migration:run",
31
+ "options": {
32
+ "cwd": "${workspaceFolder}"
33
+ },
34
+ "label": "Run migrations"
35
+ }
36
+ ]
37
+ }
@@ -1,17 +1,34 @@
1
1
  import { DATA_SOURCE_PROVIDER_TOKEN } from '@/infra/database/data-source-factory';
2
2
  import { Inject, Injectable } from '@nestjs/common';
3
- import { TypeOrmHealthIndicator } from '@nestjs/terminus';
3
+ import {
4
+ HealthIndicatorResult,
5
+ HealthIndicatorService,
6
+ } from '@nestjs/terminus';
4
7
  import { DataSource } from 'typeorm';
5
8
 
6
9
  @Injectable()
7
10
  export class DatabaseIndicator {
8
11
  constructor(
9
- private readonly typeOrm: TypeOrmHealthIndicator,
12
+ private readonly healthIndicatorService: HealthIndicatorService,
10
13
  @Inject(DATA_SOURCE_PROVIDER_TOKEN)
11
14
  private readonly dataSource: DataSource,
12
15
  ) {}
13
16
 
14
- isHealthy() {
15
- return this.typeOrm.pingCheck('db', { connection: this.dataSource });
17
+ async isHealthy(): Promise<HealthIndicatorResult> {
18
+ const indicator = this.healthIndicatorService.check('db');
19
+
20
+ try {
21
+ if (!this.dataSource.isInitialized) {
22
+ return indicator.down({ message: 'DataSource not initialized' });
23
+ }
24
+
25
+ await this.dataSource.query('SELECT 1');
26
+
27
+ return indicator.up();
28
+ } catch (error) {
29
+ const message = error instanceof Error ? error.message : String(error);
30
+
31
+ return indicator.down({ message });
32
+ }
16
33
  }
17
34
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koalarx/nest",
3
- "version": "4.0.2",
3
+ "version": "4.0.5",
4
4
  "description": "CLI para criar APIs NestJS com arquitetura DDD — copia módulos prontos para o seu repositório.",
5
5
  "license": "MIT",
6
6
  "type": "module",