@exanderal/stackcraft 0.1.1 → 0.1.2
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.
- package/README.md +54 -7
- package/dist/create/index.d.ts.map +1 -1
- package/dist/create/index.js +12 -0
- package/dist/create/index.js.map +1 -1
- package/dist/create/scaffolders/__tests__/web-vite.test.js +1 -0
- package/dist/create/scaffolders/__tests__/web-vite.test.js.map +1 -1
- package/dist/create/scaffolders/api-nestjs-rest.d.ts.map +1 -1
- package/dist/create/scaffolders/api-nestjs-rest.js +19 -2
- package/dist/create/scaffolders/api-nestjs-rest.js.map +1 -1
- package/dist/create/types.d.ts +2 -0
- package/dist/create/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/templates/api-nestjs-rest/package.json +5 -2
- package/templates/api-nestjs-rest/project.json +1 -1
- package/templates/api-nestjs-rest/src/app.module.ts +8 -5
- package/templates/api-nestjs-rest/src/common/entities/base.entity.ts +16 -0
- package/templates/api-nestjs-rest/src/common/repositories/entity.repository.ts +21 -0
- package/templates/api-nestjs-rest/src/common/repositories/readonly-entity.repository.ts +22 -0
- package/templates/api-nestjs-rest/src/common/services/entity.service.ts +24 -0
- package/templates/api-nestjs-rest/src/common/services/readonly-entity.service.ts +23 -0
- package/templates/api-nestjs-rest/src/modules/database/database.module.ts +18 -0
- package/templates/api-nestjs-rest/src/modules/health/__tests__/health.controller.spec.ts +20 -0
- package/templates/api-nestjs-rest/src/modules/health/__tests__/health.service.spec.ts +18 -0
- package/templates/api-nestjs-rest/src/modules/health/health.controller.ts +12 -0
- package/templates/api-nestjs-rest/src/modules/health/health.module.ts +9 -0
- package/templates/api-nestjs-rest/src/modules/health/health.service.ts +8 -0
- package/templates/api-nestjs-rest/test/app.e2e-spec.ts +9 -5
- package/templates/base/package.json +3 -1
- package/templates/base/pnpm-workspace.yaml +1 -0
- package/templates/base/tools/generators/generators.json +9 -0
- package/templates/base/tools/generators/module/crud-files/__fileName__.controller.ts__tmpl__ +36 -0
- package/templates/base/tools/generators/module/files/__fileName__.model.ts__tmpl__ +8 -0
- package/templates/base/tools/generators/module/files/__fileName__.module.ts__tmpl__ +12 -0
- package/templates/base/tools/generators/module/files/__fileName__.repository.ts__tmpl__ +15 -0
- package/templates/base/tools/generators/module/files/__fileName__.service.ts__tmpl__ +11 -0
- package/templates/base/tools/generators/module/files/__tests__/__fileName__.integration.spec.ts__tmpl__ +31 -0
- package/templates/base/tools/generators/module/index.js +44 -0
- package/templates/base/tools/generators/module/schema.json +24 -0
- package/templates/base/tools/generators/package.json +6 -0
- package/templates/web-nextjs/package.json +3 -0
- package/templates/web-nextjs/postcss.config.mjs +7 -0
- package/templates/web-nextjs/src/app/globals.css +1 -0
- package/templates/web-nextjs/src/components/TemplateComponent/TemplateComponent.behaviour.ts +39 -0
- package/templates/web-nextjs/src/components/TemplateComponent/TemplateComponent.tsx +14 -0
- package/templates/web-vite/package.json +2 -0
- package/templates/web-vite/src/components/TemplateComponent/TemplateComponent.behaviour.ts +39 -0
- package/templates/web-vite/src/components/TemplateComponent/TemplateComponent.tsx +14 -0
- package/templates/web-vite/src/index.css +1 -8
- package/templates/web-vite/vite.config.ts +3 -3
- package/templates/api-nestjs-rest/src/app.controller.spec.ts +0 -22
- package/templates/api-nestjs-rest/src/app.controller.ts +0 -12
- package/templates/api-nestjs-rest/src/app.service.ts +0 -8
- package/templates/web-nextjs/app/globals.css +0 -10
- /package/templates/web-nextjs/{app → src/app}/favicon.ico +0 -0
- /package/templates/web-nextjs/{app → src/app}/layout.tsx +0 -0
- /package/templates/web-nextjs/{app → src/app}/page.tsx +0 -0
package/README.md
CHANGED
|
@@ -10,21 +10,68 @@ Spin up a production-ready monorepo in one command.
|
|
|
10
10
|
npx @exanderal/stackcraft
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Follow the prompts
|
|
13
|
+
Follow the prompts — you'll have an Nx monorepo with deps installed and ready to run.
|
|
14
14
|
|
|
15
15
|
## What you get
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
```
|
|
18
|
+
your-project/
|
|
19
|
+
├── apps/
|
|
20
|
+
│ ├── backend/ # NestJS REST API
|
|
21
|
+
│ └── web/ # Vite + React or Next.js
|
|
22
|
+
├── packages/ # shared code
|
|
23
|
+
└── tools/
|
|
24
|
+
└── generators/ # local Nx code generators
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Backend (`apps/backend`)
|
|
28
|
+
|
|
29
|
+
- NestJS with TypeORM
|
|
30
|
+
- PostgreSQL or MySQL
|
|
31
|
+
- Repository/Service abstraction layer (`EntityRepository`, `EntityService`)
|
|
32
|
+
- Module-based structure under `src/modules/` — each module owns its model, repository, and service
|
|
33
|
+
- HTTP controllers live separately in `src/api/`
|
|
34
|
+
|
|
35
|
+
### Frontend (`apps/web`)
|
|
36
|
+
|
|
37
|
+
- Vite + React or Next.js
|
|
38
|
+
- Tailwind CSS v4
|
|
39
|
+
- TypeScript
|
|
40
|
+
|
|
41
|
+
## Running the project
|
|
42
|
+
|
|
43
|
+
Each app has the same scripts:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
pnpm dev # start all apps in parallel
|
|
47
|
+
pnpm build # build all apps
|
|
48
|
+
pnpm test # run all tests
|
|
49
|
+
pnpm lint # lint all apps
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Or target a specific app:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
pnpm nx run backend:dev
|
|
56
|
+
pnpm nx run web:dev
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Code generation
|
|
60
|
+
|
|
61
|
+
Generate a new backend module:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
pnpm generate:module --name=trainer
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This creates `apps/backend/src/modules/trainer/` with a model, repository, service, module, and integration test. Add `--crud` to also generate a CRUD controller in `src/api/trainer/`.
|
|
21
68
|
|
|
22
69
|
## Stack
|
|
23
70
|
|
|
24
71
|
- **Monorepo** — Nx
|
|
25
72
|
- **Package manager** — pnpm or npm
|
|
26
|
-
- **Backend** — NestJS
|
|
27
|
-
- **Frontend** — Vite + React or Next.js
|
|
73
|
+
- **Backend** — NestJS, TypeORM, PostgreSQL/MySQL
|
|
74
|
+
- **Frontend** — Vite + React or Next.js, Tailwind CSS v4
|
|
28
75
|
|
|
29
76
|
## Roadmap
|
|
30
77
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/create/index.ts"],"names":[],"mappings":"AAKA,wBAAsB,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/create/index.ts"],"names":[],"mappings":"AAKA,wBAAsB,MAAM,kBAgE3B"}
|
package/dist/create/index.js
CHANGED
|
@@ -23,6 +23,17 @@ export async function create() {
|
|
|
23
23
|
cancel('Cancelled.');
|
|
24
24
|
process.exit(0);
|
|
25
25
|
}
|
|
26
|
+
const database = await select({
|
|
27
|
+
message: 'Database',
|
|
28
|
+
options: [
|
|
29
|
+
{ value: 'postgres', label: 'PostgreSQL', hint: 'recommended' },
|
|
30
|
+
{ value: 'mysql', label: 'MySQL' },
|
|
31
|
+
],
|
|
32
|
+
});
|
|
33
|
+
if (isCancel(database)) {
|
|
34
|
+
cancel('Cancelled.');
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
26
37
|
const packageManager = await select({
|
|
27
38
|
message: 'Package manager',
|
|
28
39
|
options: [
|
|
@@ -38,6 +49,7 @@ export async function create() {
|
|
|
38
49
|
projectName: projectName,
|
|
39
50
|
frontend: frontend,
|
|
40
51
|
backend: 'nestjs-rest',
|
|
52
|
+
database: database,
|
|
41
53
|
packageManager: packageManager,
|
|
42
54
|
targetDir: resolve(process.cwd(), projectName),
|
|
43
55
|
};
|
package/dist/create/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/create/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAA;AACtF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAGxC,MAAM,CAAC,KAAK,UAAU,MAAM;IAC1B,KAAK,CAAC,kDAAkD,CAAC,CAAA;IAEzD,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;QAC7B,OAAO,EAAE,cAAc;QACvB,WAAW,EAAE,QAAQ;QACrB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;KAC/C,CAAC,CAAA;IACF,IAAI,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,YAAY,CAAC,CAAA;QACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC;QAC5B,OAAO,EAAE,UAAU;QACnB,OAAO,EAAE;YACP,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,iCAAiC,EAAE;YACjF,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,+BAA+B,EAAE;SAC7E;KACF,CAAC,CAAA;IACF,IAAI,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvB,MAAM,CAAC,YAAY,CAAC,CAAA;QACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC;QAClC,OAAO,EAAE,iBAAiB;QAC1B,OAAO,EAAE;YACP,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE;YACrD,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE;SAC/B;KACF,CAAC,CAAA;IACF,IAAI,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;QAC7B,MAAM,CAAC,YAAY,CAAC,CAAA;QACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,MAAM,GAAkB;QAC5B,WAAW,EAAE,WAAqB;QAClC,QAAQ,EAAE,QAAoB;QAC9B,OAAO,EAAE,aAAwB;QACjC,cAAc,EAAE,cAAgC;QAChD,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,WAAqB,CAAC;KACzD,CAAA;IAED,MAAM,CAAC,GAAG,OAAO,EAAE,CAAA;IACnB,CAAC,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAA;IACtC,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAA;IAC/C,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAEf,KAAK,CAAC,MAAM,MAAM,CAAC,WAAW,OAAO,MAAM,CAAC,cAAc,MAAM,CAAC,CAAA;AACnE,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/create/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-vite.test.js","sourceRoot":"","sources":["../../../../src/create/scaffolders/__tests__/web-vite.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE5D,EAAE,CAAC,IAAI,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,CAAC;IACjC,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,SAAS,CAAC;IAC3C,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,SAAS,CAAC;IACxC,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,CAAC;IACtC,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,CAAC;IACvC,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,SAAS,CAAC;CAChD,CAAC,CAAC,CAAA;AAEH,MAAM,MAAM,GAAG;IACb,WAAW,EAAE,QAAQ;IACrB,QAAQ,EAAE,MAAe;IACzB,OAAO,EAAE,aAAsB;IAC/B,cAAc,EAAE,MAAe;IAC/B,SAAS,EAAE,aAAa;CACzB,CAAA;AAED,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAA;IAEnC,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;QAClD,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAA;QAEvD,MAAM,YAAY,CAAC,MAAM,CAAC,CAAA;QAE1B,MAAM,CAAC,KAAK,CAAC,CAAC,oBAAoB,CAChC,MAAM,CAAC,gBAAgB,CAAC,UAAU,CAAC,EACnC,EAAE,SAAS,EAAE,IAAI,EAAE,CACpB,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;QAC/C,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAA;QAEvD,MAAM,YAAY,CAAC,MAAM,CAAC,CAAA;QAE1B,MAAM,CAAC,EAAE,CAAC,CAAC,oBAAoB,CAC7B,MAAM,CAAC,gBAAgB,CAAC,UAAU,CAAC,EACnC,MAAM,CAAC,gBAAgB,CAAC,UAAU,CAAC,EACnC,EAAE,SAAS,EAAE,IAAI,EAAE,CACpB,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
1
|
+
{"version":3,"file":"web-vite.test.js","sourceRoot":"","sources":["../../../../src/create/scaffolders/__tests__/web-vite.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE5D,EAAE,CAAC,IAAI,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,CAAC;IACjC,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,SAAS,CAAC;IAC3C,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,SAAS,CAAC;IACxC,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,CAAC;IACtC,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,CAAC;IACvC,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,SAAS,CAAC;CAChD,CAAC,CAAC,CAAA;AAEH,MAAM,MAAM,GAAG;IACb,WAAW,EAAE,QAAQ;IACrB,QAAQ,EAAE,MAAe;IACzB,OAAO,EAAE,aAAsB;IAC/B,QAAQ,EAAE,UAAmB;IAC7B,cAAc,EAAE,MAAe;IAC/B,SAAS,EAAE,aAAa;CACzB,CAAA;AAED,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAA;IAEnC,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;QAClD,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAA;QAEvD,MAAM,YAAY,CAAC,MAAM,CAAC,CAAA;QAE1B,MAAM,CAAC,KAAK,CAAC,CAAC,oBAAoB,CAChC,MAAM,CAAC,gBAAgB,CAAC,UAAU,CAAC,EACnC,EAAE,SAAS,EAAE,IAAI,EAAE,CACpB,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;QAC/C,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAA;QAEvD,MAAM,YAAY,CAAC,MAAM,CAAC,CAAA;QAE1B,MAAM,CAAC,EAAE,CAAC,CAAC,oBAAoB,CAC7B,MAAM,CAAC,gBAAgB,CAAC,UAAU,CAAC,EACnC,MAAM,CAAC,gBAAgB,CAAC,UAAU,CAAC,EACnC,EAAE,SAAS,EAAE,IAAI,EAAE,CACpB,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-nestjs-rest.d.ts","sourceRoot":"","sources":["../../../src/create/scaffolders/api-nestjs-rest.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"api-nestjs-rest.d.ts","sourceRoot":"","sources":["../../../src/create/scaffolders/api-nestjs-rest.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAWhD,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,aAAa,iBAa7D"}
|
|
@@ -1,14 +1,31 @@
|
|
|
1
|
-
import { mkdir } from 'node:fs/promises';
|
|
1
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { copyTemplate } from './utils/copy.js';
|
|
5
5
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
6
|
const TEMPLATES_DIR = join(__dirname, '..', '..', '..', 'templates');
|
|
7
|
+
const DB_CONFIG = {
|
|
8
|
+
postgres: { type: 'postgres', port: '5432', driver: 'pg', driverVersion: '^8.0.0', typesPackage: '@types/pg', typesVersion: '^8.0.0' },
|
|
9
|
+
mysql: { type: 'mysql', port: '3306', driver: 'mysql2', driverVersion: '^3.0.0', typesPackage: null, typesVersion: null },
|
|
10
|
+
};
|
|
7
11
|
export async function scaffoldNestjsRest(config) {
|
|
8
|
-
const appDir = join(config.targetDir, 'apps', '
|
|
12
|
+
const appDir = join(config.targetDir, 'apps', 'backend');
|
|
9
13
|
await mkdir(appDir, { recursive: true });
|
|
14
|
+
const db = DB_CONFIG[config.database];
|
|
10
15
|
await copyTemplate(join(TEMPLATES_DIR, 'api-nestjs-rest'), appDir, {
|
|
11
16
|
projectName: config.projectName,
|
|
17
|
+
dbType: db.type,
|
|
18
|
+
dbPort: db.port,
|
|
12
19
|
});
|
|
20
|
+
await injectDbDriver(appDir, db);
|
|
21
|
+
}
|
|
22
|
+
async function injectDbDriver(appDir, db) {
|
|
23
|
+
const pkgPath = join(appDir, 'package.json');
|
|
24
|
+
const pkg = JSON.parse(await readFile(pkgPath, 'utf-8'));
|
|
25
|
+
pkg.dependencies[db.driver] = db.driverVersion;
|
|
26
|
+
if (db.typesPackage) {
|
|
27
|
+
pkg.devDependencies[db.typesPackage] = db.typesVersion;
|
|
28
|
+
}
|
|
29
|
+
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
13
30
|
}
|
|
14
31
|
//# sourceMappingURL=api-nestjs-rest.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-nestjs-rest.js","sourceRoot":"","sources":["../../../src/create/scaffolders/api-nestjs-rest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"api-nestjs-rest.js","sourceRoot":"","sources":["../../../src/create/scaffolders/api-nestjs-rest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAC7D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAE9C,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AACzD,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,CAAA;AAEpE,MAAM,SAAS,GAAG;IAChB,QAAQ,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE;IACtI,KAAK,EAAK,EAAE,IAAI,EAAE,OAAO,EAAK,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE;CACvH,CAAA;AAEV,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAqB;IAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,CAAA;IACxD,MAAM,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAExC,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IAErC,MAAM,YAAY,CAAC,IAAI,CAAC,aAAa,EAAE,iBAAiB,CAAC,EAAE,MAAM,EAAE;QACjE,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,MAAM,EAAE,EAAE,CAAC,IAAI;QACf,MAAM,EAAE,EAAE,CAAC,IAAI;KAChB,CAAC,CAAA;IAEF,MAAM,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;AAClC,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,MAAc,EAAE,EAA4C;IACxF,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;IAExD,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,CAAA;IAE9C,IAAI,EAAE,CAAC,YAAY,EAAE,CAAC;QACpB,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,YAAY,CAAA;IACxD,CAAC;IAED,MAAM,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAA;AACxE,CAAC"}
|
package/dist/create/types.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export type PackageManager = 'pnpm' | 'npm';
|
|
2
2
|
export type Frontend = 'vite' | 'nextjs';
|
|
3
3
|
export type Backend = 'nestjs-rest';
|
|
4
|
+
export type Database = 'postgres' | 'mysql';
|
|
4
5
|
export interface ProjectConfig {
|
|
5
6
|
projectName: string;
|
|
6
7
|
frontend: Frontend;
|
|
7
8
|
backend: Backend;
|
|
9
|
+
database: Database;
|
|
8
10
|
packageManager: PackageManager;
|
|
9
11
|
targetDir: string;
|
|
10
12
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/create/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,CAAA;AAC3C,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAA;AACxC,MAAM,MAAM,OAAO,GAAG,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/create/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,CAAA;AAC3C,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAA;AACxC,MAAM,MAAM,OAAO,GAAG,aAAa,CAAA;AACnC,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAA;AAE3C,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,QAAQ,CAAA;IAClB,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,QAAQ,CAAA;IAClB,cAAc,EAAE,cAAc,CAAA;IAC9B,SAAS,EAAE,MAAM,CAAA;CAClB"}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "{{projectName}}-
|
|
2
|
+
"name": "{{projectName}}-backend",
|
|
3
3
|
"version": "0.0.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"author": "",
|
|
@@ -22,10 +22,13 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@nestjs/common": "^11.0.1",
|
|
25
|
+
"@nestjs/config": "^3.0.0",
|
|
25
26
|
"@nestjs/core": "^11.0.1",
|
|
26
27
|
"@nestjs/platform-express": "^11.0.1",
|
|
28
|
+
"@nestjs/typeorm": "^10.0.0",
|
|
27
29
|
"reflect-metadata": "^0.2.2",
|
|
28
|
-
"rxjs": "^7.8.1"
|
|
30
|
+
"rxjs": "^7.8.1",
|
|
31
|
+
"typeorm": "^0.3.0"
|
|
29
32
|
},
|
|
30
33
|
"devDependencies": {
|
|
31
34
|
"@eslint/eslintrc": "^3.2.0",
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { Module } from '@nestjs/common';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { ConfigModule } from '@nestjs/config';
|
|
3
|
+
import { DatabaseModule } from './modules/database/database.module';
|
|
4
|
+
import { HealthModule } from './modules/health/health.module';
|
|
4
5
|
|
|
5
6
|
@Module({
|
|
6
|
-
imports: [
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
imports: [
|
|
8
|
+
ConfigModule.forRoot({ isGlobal: true }),
|
|
9
|
+
DatabaseModule,
|
|
10
|
+
HealthModule,
|
|
11
|
+
],
|
|
9
12
|
})
|
|
10
13
|
export class AppModule {}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CreateDateColumn,
|
|
3
|
+
PrimaryGeneratedColumn,
|
|
4
|
+
UpdateDateColumn,
|
|
5
|
+
} from 'typeorm';
|
|
6
|
+
|
|
7
|
+
export abstract class BaseEntity {
|
|
8
|
+
@PrimaryGeneratedColumn('uuid')
|
|
9
|
+
id: string;
|
|
10
|
+
|
|
11
|
+
@CreateDateColumn()
|
|
12
|
+
createdAt: Date;
|
|
13
|
+
|
|
14
|
+
@UpdateDateColumn()
|
|
15
|
+
updatedAt: Date;
|
|
16
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DeepPartial } from 'typeorm';
|
|
2
|
+
import { BaseEntity } from '../entities/base.entity';
|
|
3
|
+
import { ReadonlyEntityRepository } from './readonly-entity.repository';
|
|
4
|
+
|
|
5
|
+
export abstract class EntityRepository<
|
|
6
|
+
T extends BaseEntity,
|
|
7
|
+
> extends ReadonlyEntityRepository<T> {
|
|
8
|
+
async create(data: DeepPartial<T>): Promise<T> {
|
|
9
|
+
const entity = this.repo.create(data);
|
|
10
|
+
return this.repo.save(entity);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async update(id: string, data: DeepPartial<T>): Promise<T> {
|
|
14
|
+
await this.repo.update(id, data as any);
|
|
15
|
+
return this.findById(id) as Promise<T>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async remove(id: string): Promise<void> {
|
|
19
|
+
await this.repo.delete(id);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { FindManyOptions, FindOptionsWhere, In, Repository } from 'typeorm';
|
|
2
|
+
import { BaseEntity } from '../entities/base.entity';
|
|
3
|
+
|
|
4
|
+
export abstract class ReadonlyEntityRepository<T extends BaseEntity> {
|
|
5
|
+
constructor(protected readonly repo: Repository<T>) {}
|
|
6
|
+
|
|
7
|
+
findAll(options?: FindManyOptions<T>): Promise<T[]> {
|
|
8
|
+
return this.repo.find(options);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
findByIds(ids: string[]): Promise<T[]> {
|
|
12
|
+
return this.repo.findBy({ id: In(ids) } as FindOptionsWhere<T>);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
findById(id: string): Promise<T | null> {
|
|
16
|
+
return this.repo.findOneBy({ id } as FindOptionsWhere<T>);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
findOne(where: FindOptionsWhere<T>): Promise<T | null> {
|
|
20
|
+
return this.repo.findOneBy(where);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { DeepPartial } from 'typeorm';
|
|
2
|
+
import { BaseEntity } from '../entities/base.entity';
|
|
3
|
+
import { EntityRepository } from '../repositories/entity.repository';
|
|
4
|
+
import { ReadonlyEntityService } from './readonly-entity.service';
|
|
5
|
+
|
|
6
|
+
export abstract class EntityService<
|
|
7
|
+
T extends BaseEntity,
|
|
8
|
+
> extends ReadonlyEntityService<T> {
|
|
9
|
+
constructor(protected readonly repository: EntityRepository<T>) {
|
|
10
|
+
super(repository);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
create(data: DeepPartial<T>): Promise<T> {
|
|
14
|
+
return this.repository.create(data);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
update(id: string, data: DeepPartial<T>): Promise<T> {
|
|
18
|
+
return this.repository.update(id, data);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
remove(id: string): Promise<void> {
|
|
22
|
+
return this.repository.remove(id);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FindManyOptions, FindOptionsWhere } from 'typeorm';
|
|
2
|
+
import { BaseEntity } from '../entities/base.entity';
|
|
3
|
+
import { ReadonlyEntityRepository } from '../repositories/readonly-entity.repository';
|
|
4
|
+
|
|
5
|
+
export abstract class ReadonlyEntityService<T extends BaseEntity> {
|
|
6
|
+
constructor(protected readonly repository: ReadonlyEntityRepository<T>) {}
|
|
7
|
+
|
|
8
|
+
findAll(options?: FindManyOptions<T>): Promise<T[]> {
|
|
9
|
+
return this.repository.findAll(options);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
findByIds(ids: string[]): Promise<T[]> {
|
|
13
|
+
return this.repository.findByIds(ids);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
findById(id: string): Promise<T | null> {
|
|
17
|
+
return this.repository.findById(id);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
findOne(where: FindOptionsWhere<T>): Promise<T | null> {
|
|
21
|
+
return this.repository.findOne(where);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
3
|
+
|
|
4
|
+
@Module({
|
|
5
|
+
imports: [
|
|
6
|
+
TypeOrmModule.forRoot({
|
|
7
|
+
type: '{{dbType}}' as 'postgres' | 'mysql',
|
|
8
|
+
host: process.env.DB_HOST ?? 'localhost',
|
|
9
|
+
port: parseInt(process.env.DB_PORT ?? '{{dbPort}}', 10),
|
|
10
|
+
username: process.env.DB_USER ?? 'postgres',
|
|
11
|
+
password: process.env.DB_PASSWORD ?? '',
|
|
12
|
+
database: process.env.DB_NAME ?? '{{projectName}}',
|
|
13
|
+
entities: [],
|
|
14
|
+
synchronize: process.env.NODE_ENV !== 'production',
|
|
15
|
+
}),
|
|
16
|
+
],
|
|
17
|
+
})
|
|
18
|
+
export class DatabaseModule {}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
2
|
+
import { HealthController } from './health.controller';
|
|
3
|
+
import { HealthService } from './health.service';
|
|
4
|
+
|
|
5
|
+
describe('HealthController', () => {
|
|
6
|
+
let controller: HealthController;
|
|
7
|
+
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
10
|
+
controllers: [HealthController],
|
|
11
|
+
providers: [HealthService],
|
|
12
|
+
}).compile();
|
|
13
|
+
|
|
14
|
+
controller = module.get<HealthController>(HealthController);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('returns ok status', () => {
|
|
18
|
+
expect(controller.check()).toEqual({ status: 'ok' });
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
2
|
+
import { HealthService } from './health.service';
|
|
3
|
+
|
|
4
|
+
describe('HealthService', () => {
|
|
5
|
+
let service: HealthService;
|
|
6
|
+
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
9
|
+
providers: [HealthService],
|
|
10
|
+
}).compile();
|
|
11
|
+
|
|
12
|
+
service = module.get<HealthService>(HealthService);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('returns ok status', () => {
|
|
16
|
+
expect(service.check()).toEqual({ status: 'ok' });
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Controller, Get } from '@nestjs/common';
|
|
2
|
+
import { HealthService } from './health.service';
|
|
3
|
+
|
|
4
|
+
@Controller('health')
|
|
5
|
+
export class HealthController {
|
|
6
|
+
constructor(private readonly healthService: HealthService) {}
|
|
7
|
+
|
|
8
|
+
@Get()
|
|
9
|
+
check() {
|
|
10
|
+
return this.healthService.check();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { HealthController } from './health.controller';
|
|
3
|
+
import { HealthService } from './health.service';
|
|
4
|
+
|
|
5
|
+
@Module({
|
|
6
|
+
controllers: [HealthController],
|
|
7
|
+
providers: [HealthService],
|
|
8
|
+
})
|
|
9
|
+
export class HealthModule {}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Test, TestingModule } from '@nestjs/testing';
|
|
2
1
|
import { INestApplication } from '@nestjs/common';
|
|
2
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
3
3
|
import * as request from 'supertest';
|
|
4
4
|
import { App } from 'supertest/types';
|
|
5
5
|
import { AppModule } from './../src/app.module';
|
|
6
6
|
|
|
7
|
-
describe('
|
|
7
|
+
describe('App (e2e)', () => {
|
|
8
8
|
let app: INestApplication<App>;
|
|
9
9
|
|
|
10
10
|
beforeEach(async () => {
|
|
@@ -16,10 +16,14 @@ describe('AppController (e2e)', () => {
|
|
|
16
16
|
await app.init();
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
await app.close();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('/health (GET)', () => {
|
|
20
24
|
return request(app.getHttpServer())
|
|
21
|
-
.get('/')
|
|
25
|
+
.get('/health')
|
|
22
26
|
.expect(200)
|
|
23
|
-
.expect('
|
|
27
|
+
.expect({ status: 'ok' });
|
|
24
28
|
});
|
|
25
29
|
});
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
"dev": "nx run-many -t dev",
|
|
7
7
|
"build": "nx run-many -t build",
|
|
8
8
|
"test": "nx run-many -t test",
|
|
9
|
-
"lint": "nx run-many -t lint"
|
|
9
|
+
"lint": "nx run-many -t lint",
|
|
10
|
+
"generate:module": "nx g @local/generators:module"
|
|
10
11
|
},
|
|
11
12
|
"devDependencies": {
|
|
13
|
+
"@nx/devkit": "^20.0.0",
|
|
12
14
|
"nx": "^20.0.0"
|
|
13
15
|
}
|
|
14
16
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Controller,
|
|
3
|
+
Delete,
|
|
4
|
+
Get,
|
|
5
|
+
Param,
|
|
6
|
+
ParseUUIDPipe,
|
|
7
|
+
Query,
|
|
8
|
+
} from '@nestjs/common';
|
|
9
|
+
import { FindManyOptions } from 'typeorm';
|
|
10
|
+
import { <%= className %>Model } from '../../modules/<%= fileName %>/<%= fileName %>.model';
|
|
11
|
+
import { <%= className %>Service } from '../../modules/<%= fileName %>/<%= fileName %>.service';
|
|
12
|
+
|
|
13
|
+
@Controller('<%= fileName %>s')
|
|
14
|
+
export class <%= className %>Controller {
|
|
15
|
+
constructor(private readonly service: <%= className %>Service) {}
|
|
16
|
+
|
|
17
|
+
@Get()
|
|
18
|
+
findAll(@Query() query: FindManyOptions<<%= className %>Model>) {
|
|
19
|
+
return this.service.findAll(query);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@Get('by-ids')
|
|
23
|
+
findByIds(@Query('ids') ids: string) {
|
|
24
|
+
return this.service.findByIds(ids.split(','));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@Get(':id')
|
|
28
|
+
findById(@Param('id', ParseUUIDPipe) id: string) {
|
|
29
|
+
return this.service.findById(id);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Delete(':id')
|
|
33
|
+
remove(@Param('id', ParseUUIDPipe) id: string) {
|
|
34
|
+
return this.service.remove(id);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
3
|
+
import { <%= className %>Model } from './<%= fileName %>.model';
|
|
4
|
+
import { <%= className %>Repository } from './<%= fileName %>.repository';
|
|
5
|
+
import { <%= className %>Service } from './<%= fileName %>.service';
|
|
6
|
+
|
|
7
|
+
@Module({
|
|
8
|
+
imports: [TypeOrmModule.forFeature([<%= className %>Model])],
|
|
9
|
+
providers: [<%= className %>Service, <%= className %>Repository],
|
|
10
|
+
exports: [<%= className %>Service, <%= className %>Repository],
|
|
11
|
+
})
|
|
12
|
+
export class <%= className %>Module {}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
3
|
+
import { Repository } from 'typeorm';
|
|
4
|
+
import { EntityRepository } from '../../common/repositories/entity.repository';
|
|
5
|
+
import { <%= className %>Model } from './<%= fileName %>.model';
|
|
6
|
+
|
|
7
|
+
@Injectable()
|
|
8
|
+
export class <%= className %>Repository extends EntityRepository<<%= className %>Model> {
|
|
9
|
+
constructor(
|
|
10
|
+
@InjectRepository(<%= className %>Model)
|
|
11
|
+
repo: Repository<<%= className %>Model>,
|
|
12
|
+
) {
|
|
13
|
+
super(repo);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { EntityService } from '../../common/services/entity.service';
|
|
3
|
+
import { <%= className %>Model } from './<%= fileName %>.model';
|
|
4
|
+
import { <%= className %>Repository } from './<%= fileName %>.repository';
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class <%= className %>Service extends EntityService<<%= className %>Model> {
|
|
8
|
+
constructor(repository: <%= className %>Repository) {
|
|
9
|
+
super(repository);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Test } from '@nestjs/testing';
|
|
2
|
+
import { getRepositoryToken } from '@nestjs/typeorm';
|
|
3
|
+
import { Repository } from 'typeorm';
|
|
4
|
+
import { <%= className %>Model } from '../<%= fileName %>.model';
|
|
5
|
+
import { <%= className %>Module } from '../<%= fileName %>.module';
|
|
6
|
+
import { <%= className %>Service } from '../<%= fileName %>.service';
|
|
7
|
+
|
|
8
|
+
describe('<%= className %>Service', () => {
|
|
9
|
+
let service: <%= className %>Service;
|
|
10
|
+
|
|
11
|
+
beforeAll(async () => {
|
|
12
|
+
const module = await Test.createTestingModule({
|
|
13
|
+
imports: [<%= className %>Module],
|
|
14
|
+
})
|
|
15
|
+
.overrideProvider(getRepositoryToken(<%= className %>Model))
|
|
16
|
+
.useValue({
|
|
17
|
+
find: jest.fn(),
|
|
18
|
+
findOne: jest.fn(),
|
|
19
|
+
findOneBy: jest.fn(),
|
|
20
|
+
save: jest.fn(),
|
|
21
|
+
delete: jest.fn(),
|
|
22
|
+
} satisfies Partial<Repository<<%= className %>Model>>)
|
|
23
|
+
.compile();
|
|
24
|
+
|
|
25
|
+
service = module.get(<%= className %>Service);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('is defined', () => {
|
|
29
|
+
expect(service).toBeDefined();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const { formatFiles, generateFiles, names } = require('@nx/devkit');
|
|
2
|
+
const { execSync } = require('node:child_process');
|
|
3
|
+
const { join } = require('node:path');
|
|
4
|
+
|
|
5
|
+
module.exports = async function (tree, options) {
|
|
6
|
+
const n = names(options.name);
|
|
7
|
+
const modulePath = `apps/backend/src/modules/${n.fileName}`;
|
|
8
|
+
|
|
9
|
+
generateFiles(
|
|
10
|
+
tree,
|
|
11
|
+
join(__dirname, 'files'),
|
|
12
|
+
modulePath,
|
|
13
|
+
{ ...n, tmpl: '' },
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
if (options.crud) {
|
|
17
|
+
generateFiles(
|
|
18
|
+
tree,
|
|
19
|
+
join(__dirname, 'crud-files'),
|
|
20
|
+
`apps/backend/src/api/${n.fileName}`,
|
|
21
|
+
{ ...n, tmpl: '' },
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
await formatFiles(tree);
|
|
26
|
+
|
|
27
|
+
console.log(`\n✓ Module created at ${modulePath}/`);
|
|
28
|
+
console.log(` → Import ${n.className}Module in app.module.ts\n`);
|
|
29
|
+
|
|
30
|
+
if (options.crud) {
|
|
31
|
+
console.log(`✓ Controller created at apps/backend/src/api/${n.fileName}/`);
|
|
32
|
+
console.log(` → Add ${n.className}Controller to a module and import ${n.className}Module there\n`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return () => {
|
|
36
|
+
const targets = [modulePath];
|
|
37
|
+
if (options.crud) targets.push(`apps/backend/src/api/${n.fileName}`);
|
|
38
|
+
|
|
39
|
+
execSync(`npx prettier --write ${targets.join(' ')}`, {
|
|
40
|
+
cwd: tree.root,
|
|
41
|
+
stdio: 'inherit',
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"cli": "nx",
|
|
4
|
+
"id": "module",
|
|
5
|
+
"title": "Create a NestJS module",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"name": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Module name, singular lowercase (e.g. trainer)",
|
|
11
|
+
"$default": {
|
|
12
|
+
"$source": "argv",
|
|
13
|
+
"index": 0
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"crud": {
|
|
17
|
+
"type": "boolean",
|
|
18
|
+
"description": "Generate a CRUD controller in src/api/<name>/",
|
|
19
|
+
"default": false,
|
|
20
|
+
"x-prompt": "Generate a CRUD controller?"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"required": ["name"]
|
|
24
|
+
}
|
|
@@ -14,10 +14,13 @@
|
|
|
14
14
|
"react-dom": "19.2.4"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
+
"@tailwindcss/postcss": "^4.0.0",
|
|
17
18
|
"@types/node": "^20",
|
|
18
19
|
"@types/react": "^19",
|
|
19
20
|
"@types/react-dom": "^19",
|
|
20
21
|
"babel-plugin-react-compiler": "1.0.0",
|
|
22
|
+
"postcss": "^8.0.0",
|
|
23
|
+
"tailwindcss": "^4.0.0",
|
|
21
24
|
"typescript": "^5"
|
|
22
25
|
}
|
|
23
26
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
|
|
3
|
+
type UseTemplateComponent = {
|
|
4
|
+
state: {
|
|
5
|
+
count: number;
|
|
6
|
+
};
|
|
7
|
+
handlers: {
|
|
8
|
+
handleCountIncrement: () => void;
|
|
9
|
+
handleCountDecrement: () => void;
|
|
10
|
+
handleCountReset: () => void;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const useTemplateComponent = (): UseTemplateComponent => {
|
|
15
|
+
const [count, setCount] = useState<number>(0);
|
|
16
|
+
|
|
17
|
+
const handleCountIncrement = () => {
|
|
18
|
+
setCount((prev) => prev + 1);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const handleCountDecrement = () => {
|
|
22
|
+
setCount((prev) => prev - 1);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const handleCountReset = () => {
|
|
26
|
+
setCount(0);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
state: {
|
|
31
|
+
count,
|
|
32
|
+
},
|
|
33
|
+
handlers: {
|
|
34
|
+
handleCountIncrement,
|
|
35
|
+
handleCountDecrement,
|
|
36
|
+
handleCountReset,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useTemplateComponent } from "./TemplateComponent.behaviour";
|
|
3
|
+
|
|
4
|
+
export const TemplateComponent = () => {
|
|
5
|
+
const { state, handlers } = useTemplateComponent();
|
|
6
|
+
return (
|
|
7
|
+
<div>
|
|
8
|
+
<div>Count: {state.count}</div>
|
|
9
|
+
<button onClick={handlers.handleCountIncrement}>Increment</button>
|
|
10
|
+
<button onClick={handlers.handleCountDecrement}>Decrement</button>
|
|
11
|
+
<button onClick={handlers.handleCountReset}>Reset</button>
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
|
|
3
|
+
type UseTemplateComponent = {
|
|
4
|
+
state: {
|
|
5
|
+
count: number;
|
|
6
|
+
};
|
|
7
|
+
handlers: {
|
|
8
|
+
handleCountIncrement: () => void;
|
|
9
|
+
handleCountDecrement: () => void;
|
|
10
|
+
handleCountReset: () => void;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const useTemplateComponent = (): UseTemplateComponent => {
|
|
15
|
+
const [count, setCount] = useState<number>(0);
|
|
16
|
+
|
|
17
|
+
const handleCountIncrement = () => {
|
|
18
|
+
setCount((prev) => prev + 1);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const handleCountDecrement = () => {
|
|
22
|
+
setCount((prev) => prev - 1);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const handleCountReset = () => {
|
|
26
|
+
setCount(0);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
state: {
|
|
31
|
+
count,
|
|
32
|
+
},
|
|
33
|
+
handlers: {
|
|
34
|
+
handleCountIncrement,
|
|
35
|
+
handleCountDecrement,
|
|
36
|
+
handleCountReset,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useTemplateComponent } from "./TemplateComponent.behaviour";
|
|
3
|
+
|
|
4
|
+
export const TemplateComponent = () => {
|
|
5
|
+
const { state, handlers } = useTemplateComponent();
|
|
6
|
+
return (
|
|
7
|
+
<div>
|
|
8
|
+
<div>Count: {state.count}</div>
|
|
9
|
+
<button onClick={handlers.handleCountIncrement}>Increment</button>
|
|
10
|
+
<button onClick={handlers.handleCountDecrement}>Decrement</button>
|
|
11
|
+
<button onClick={handlers.handleCountReset}>Reset</button>
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
2
2
|
import react from '@vitejs/plugin-react'
|
|
3
|
+
import { defineConfig } from 'vite'
|
|
3
4
|
|
|
4
|
-
// https://vite.dev/config/
|
|
5
5
|
export default defineConfig({
|
|
6
|
-
plugins: [react()],
|
|
6
|
+
plugins: [react(), tailwindcss()],
|
|
7
7
|
})
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { Test, TestingModule } from '@nestjs/testing';
|
|
2
|
-
import { AppController } from './app.controller';
|
|
3
|
-
import { AppService } from './app.service';
|
|
4
|
-
|
|
5
|
-
describe('AppController', () => {
|
|
6
|
-
let appController: AppController;
|
|
7
|
-
|
|
8
|
-
beforeEach(async () => {
|
|
9
|
-
const app: TestingModule = await Test.createTestingModule({
|
|
10
|
-
controllers: [AppController],
|
|
11
|
-
providers: [AppService],
|
|
12
|
-
}).compile();
|
|
13
|
-
|
|
14
|
-
appController = app.get<AppController>(AppController);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
describe('root', () => {
|
|
18
|
-
it('should return "Hello World!"', () => {
|
|
19
|
-
expect(appController.getHello()).toBe('Hello World!');
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
});
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Controller, Get } from '@nestjs/common';
|
|
2
|
-
import { AppService } from './app.service';
|
|
3
|
-
|
|
4
|
-
@Controller()
|
|
5
|
-
export class AppController {
|
|
6
|
-
constructor(private readonly appService: AppService) {}
|
|
7
|
-
|
|
8
|
-
@Get()
|
|
9
|
-
getHello(): string {
|
|
10
|
-
return this.appService.getHello();
|
|
11
|
-
}
|
|
12
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|