create-forgeon 0.1.2 → 0.1.6

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.
Files changed (143) hide show
  1. package/README.md +19 -17
  2. package/bin/create-forgeon.mjs +22 -22
  3. package/package.json +1 -1
  4. package/src/cli/add-help.mjs +12 -12
  5. package/src/cli/add-options.mjs +54 -54
  6. package/src/cli/add-options.test.mjs +24 -24
  7. package/src/cli/help.mjs +20 -20
  8. package/src/cli/options.mjs +121 -121
  9. package/src/cli/options.test.mjs +41 -41
  10. package/src/cli/prompt-select.mjs +94 -94
  11. package/src/cli/prompt-select.test.mjs +148 -148
  12. package/src/constants.mjs +13 -13
  13. package/src/core/docs.mjs +128 -128
  14. package/src/core/docs.test.mjs +91 -91
  15. package/src/core/install.mjs +14 -14
  16. package/src/core/scaffold.mjs +48 -45
  17. package/src/core/validate.mjs +12 -12
  18. package/src/core/validate.test.mjs +73 -73
  19. package/src/databases/index.mjs +26 -26
  20. package/src/frameworks/index.mjs +32 -32
  21. package/src/infrastructure/proxy.mjs +12 -12
  22. package/src/modules/docs.mjs +70 -70
  23. package/src/modules/executor.mjs +39 -21
  24. package/src/modules/executor.test.mjs +95 -45
  25. package/src/modules/i18n.mjs +283 -0
  26. package/src/modules/registry.mjs +43 -35
  27. package/src/presets/i18n.mjs +228 -180
  28. package/src/presets/index.mjs +2 -2
  29. package/src/presets/proxy.mjs +32 -32
  30. package/src/run-add-module.mjs +47 -47
  31. package/src/run-create-forgeon.mjs +72 -72
  32. package/src/utils/fs.mjs +26 -26
  33. package/src/utils/values.mjs +20 -20
  34. package/templates/base/.dockerignore +7 -7
  35. package/templates/base/.editorconfig +11 -11
  36. package/templates/base/README.md +46 -46
  37. package/templates/base/apps/api/Dockerfile +24 -24
  38. package/templates/base/apps/api/package.json +39 -39
  39. package/templates/base/apps/api/prisma/migrations/0001_init/migration.sql +11 -11
  40. package/templates/base/apps/api/prisma/schema.prisma +14 -14
  41. package/templates/base/apps/api/prisma/seed.ts +19 -19
  42. package/templates/base/apps/api/src/app.module.ts +32 -32
  43. package/templates/base/apps/api/src/common/dto/echo-query.dto.ts +5 -5
  44. package/templates/base/apps/api/src/common/filters/app-exception.filter.ts +129 -129
  45. package/templates/base/apps/api/src/config/app.config.ts +12 -12
  46. package/templates/base/apps/api/src/health/health.controller.ts +30 -30
  47. package/templates/base/apps/api/src/main.ts +25 -25
  48. package/templates/base/apps/api/src/prisma/prisma.module.ts +8 -8
  49. package/templates/base/apps/api/src/prisma/prisma.service.ts +26 -26
  50. package/templates/base/apps/api/tsconfig.build.json +8 -8
  51. package/templates/base/apps/api/tsconfig.json +8 -8
  52. package/templates/base/apps/web/Dockerfile +12 -12
  53. package/templates/base/apps/web/index.html +11 -11
  54. package/templates/base/apps/web/package.json +21 -21
  55. package/templates/base/apps/web/src/App.tsx +35 -35
  56. package/templates/base/apps/web/src/main.tsx +8 -8
  57. package/templates/base/apps/web/src/styles.css +32 -32
  58. package/templates/base/apps/web/tsconfig.json +17 -17
  59. package/templates/base/apps/web/vite.config.ts +14 -14
  60. package/templates/base/docs/AI/ARCHITECTURE.md +37 -37
  61. package/templates/base/docs/AI/MODULE_SPEC.md +56 -56
  62. package/templates/base/docs/AI/PROJECT.md +31 -31
  63. package/templates/base/docs/AI/TASKS.md +57 -57
  64. package/templates/base/docs/README.md +6 -6
  65. package/templates/base/infra/caddy/Caddyfile +15 -15
  66. package/templates/base/infra/docker/.env.example +9 -9
  67. package/templates/base/infra/docker/caddy.Dockerfile +15 -15
  68. package/templates/base/infra/docker/compose.caddy.yml +44 -44
  69. package/templates/base/infra/docker/compose.nginx.yml +44 -44
  70. package/templates/base/infra/docker/compose.none.yml +37 -37
  71. package/templates/base/infra/docker/compose.yml +44 -44
  72. package/templates/base/infra/docker/nginx.Dockerfile +15 -15
  73. package/templates/base/infra/nginx/nginx.conf +31 -31
  74. package/templates/base/package.json +23 -23
  75. package/templates/base/packages/core/README.md +2 -2
  76. package/templates/base/packages/core/package.json +13 -13
  77. package/templates/base/packages/core/tsconfig.json +7 -7
  78. package/templates/base/packages/i18n/package.json +18 -18
  79. package/templates/base/packages/i18n/src/forgeon-i18n.module.ts +45 -45
  80. package/templates/base/packages/i18n/tsconfig.json +8 -8
  81. package/templates/base/pnpm-workspace.yaml +2 -2
  82. package/templates/base/resources/i18n/en/common.json +4 -4
  83. package/templates/base/resources/i18n/en/validation.json +2 -2
  84. package/templates/base/resources/i18n/uk/common.json +4 -4
  85. package/templates/base/resources/i18n/uk/validation.json +2 -2
  86. package/templates/base/tsconfig.base.json +16 -16
  87. package/templates/docs-fragments/AI_ARCHITECTURE/00_title.md +1 -1
  88. package/templates/docs-fragments/AI_ARCHITECTURE/10_layout_base.md +6 -6
  89. package/templates/docs-fragments/AI_ARCHITECTURE/11_layout_infra.md +1 -1
  90. package/templates/docs-fragments/AI_ARCHITECTURE/12_layout_i18n_resources.md +1 -1
  91. package/templates/docs-fragments/AI_ARCHITECTURE/20_env_base.md +4 -4
  92. package/templates/docs-fragments/AI_ARCHITECTURE/21_env_i18n.md +3 -3
  93. package/templates/docs-fragments/AI_ARCHITECTURE/30_default_db.md +7 -7
  94. package/templates/docs-fragments/AI_ARCHITECTURE/31_docker_runtime.md +5 -5
  95. package/templates/docs-fragments/AI_ARCHITECTURE/32_scope_freeze.md +5 -5
  96. package/templates/docs-fragments/AI_ARCHITECTURE/40_docs_generation.md +9 -9
  97. package/templates/docs-fragments/AI_ARCHITECTURE/50_extension_points.md +8 -8
  98. package/templates/docs-fragments/AI_PROJECT/00_title.md +1 -1
  99. package/templates/docs-fragments/AI_PROJECT/10_what_is.md +3 -3
  100. package/templates/docs-fragments/AI_PROJECT/20_structure_base.md +5 -5
  101. package/templates/docs-fragments/AI_PROJECT/21_structure_i18n.md +2 -0
  102. package/templates/docs-fragments/AI_PROJECT/22_structure_docker.md +1 -1
  103. package/templates/docs-fragments/AI_PROJECT/23_structure_docs.md +1 -1
  104. package/templates/docs-fragments/AI_PROJECT/30_run_dev.md +8 -8
  105. package/templates/docs-fragments/AI_PROJECT/31_run_docker.md +5 -5
  106. package/templates/docs-fragments/AI_PROJECT/32_proxy_notes.md +5 -5
  107. package/templates/docs-fragments/AI_PROJECT/32_proxy_notes_none.md +5 -5
  108. package/templates/docs-fragments/AI_PROJECT/33_i18n_notes.md +2 -0
  109. package/templates/docs-fragments/AI_PROJECT/40_change_boundaries_base.md +3 -3
  110. package/templates/docs-fragments/AI_PROJECT/41_change_boundaries_docker.md +1 -1
  111. package/templates/docs-fragments/README/00_title.md +3 -3
  112. package/templates/docs-fragments/README/10_stack.md +8 -8
  113. package/templates/docs-fragments/README/20_quick_start_dev_intro.md +6 -6
  114. package/templates/docs-fragments/README/21_quick_start_dev_db_docker.md +4 -4
  115. package/templates/docs-fragments/README/21_quick_start_dev_db_local.md +1 -1
  116. package/templates/docs-fragments/README/22_quick_start_dev_outro.md +7 -7
  117. package/templates/docs-fragments/README/30_quick_start_docker.md +7 -7
  118. package/templates/docs-fragments/README/30_quick_start_docker_none.md +9 -9
  119. package/templates/docs-fragments/README/31_proxy_preset_caddy.md +9 -9
  120. package/templates/docs-fragments/README/31_proxy_preset_nginx.md +8 -8
  121. package/templates/docs-fragments/README/31_proxy_preset_none.md +6 -6
  122. package/templates/docs-fragments/README/32_prisma_container_start.md +5 -5
  123. package/templates/docs-fragments/README/40_i18n.md +14 -8
  124. package/templates/docs-fragments/README/90_next_steps.md +7 -7
  125. package/templates/module-fragments/i18n/00_title.md +5 -0
  126. package/templates/module-fragments/i18n/10_overview.md +9 -0
  127. package/templates/module-fragments/i18n/20_scope.md +7 -0
  128. package/templates/module-fragments/i18n/90_status_implemented.md +3 -0
  129. package/templates/module-fragments/jwt-auth/00_title.md +1 -1
  130. package/templates/module-fragments/jwt-auth/10_overview.md +6 -6
  131. package/templates/module-fragments/jwt-auth/20_scope.md +7 -7
  132. package/templates/module-fragments/jwt-auth/90_status_planned.md +3 -3
  133. package/templates/module-fragments/queue/00_title.md +1 -1
  134. package/templates/module-fragments/queue/10_overview.md +6 -6
  135. package/templates/module-fragments/queue/20_scope.md +7 -7
  136. package/templates/module-fragments/queue/90_status_planned.md +3 -3
  137. package/templates/module-presets/i18n/apps/web/src/App.tsx +61 -0
  138. package/templates/module-presets/i18n/packages/i18n-contracts/package.json +14 -0
  139. package/templates/module-presets/i18n/packages/i18n-contracts/src/index.ts +7 -0
  140. package/templates/module-presets/i18n/packages/i18n-contracts/tsconfig.json +8 -0
  141. package/templates/module-presets/i18n/packages/i18n-web/package.json +17 -0
  142. package/templates/module-presets/i18n/packages/i18n-web/src/index.ts +50 -0
  143. package/templates/module-presets/i18n/packages/i18n-web/tsconfig.json +8 -0
@@ -1,72 +1,72 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
- import { printHelp } from './cli/help.mjs';
5
- import { parseCliArgs, promptForMissingOptions } from './cli/options.mjs';
6
- import { DEFAULT_DB, DEFAULT_FRONTEND, DEFAULT_PROXY, FIXED_DOCKER_ENABLED } from './constants.mjs';
7
- import { runInstall } from './core/install.mjs';
8
- import { scaffoldProject } from './core/scaffold.mjs';
9
- import { validatePresetSupport } from './core/validate.mjs';
10
- import { parseBoolean } from './utils/values.mjs';
11
-
12
- export async function runCreateForgeon(argv = process.argv.slice(2)) {
13
- const { options: parsedOptions, positional } = parseCliArgs(argv);
14
- const options = { ...parsedOptions };
15
-
16
- if (options.help) {
17
- printHelp();
18
- return;
19
- }
20
-
21
- if (!options.name && positional.length > 0) {
22
- [options.name] = positional;
23
- }
24
-
25
- const promptedOptions = await promptForMissingOptions(options);
26
-
27
- if (!promptedOptions.name || promptedOptions.name.trim().length === 0) {
28
- throw new Error('Project name is required.');
29
- }
30
-
31
- const frontend = (promptedOptions.frontend ?? DEFAULT_FRONTEND).toString().toLowerCase();
32
- const db = (promptedOptions.db ?? DEFAULT_DB).toString().toLowerCase();
33
- const i18nEnabled = parseBoolean(promptedOptions.i18n, true);
34
- const dockerEnabled = FIXED_DOCKER_ENABLED;
35
- const proxy = (promptedOptions.proxy ?? DEFAULT_PROXY).toString().toLowerCase();
36
- const installEnabled = parseBoolean(promptedOptions.install, false);
37
-
38
- validatePresetSupport({ frontend, db, dockerEnabled, proxy });
39
-
40
- const projectName = promptedOptions.name.trim();
41
- const targetRoot = path.resolve(process.cwd(), projectName);
42
- if (fs.existsSync(targetRoot)) {
43
- throw new Error(`Target directory already exists: ${targetRoot}`);
44
- }
45
-
46
- const srcDir = path.dirname(fileURLToPath(import.meta.url));
47
- const packageRoot = path.resolve(srcDir, '..');
48
- const templateRoot = path.join(packageRoot, 'templates', 'base');
49
-
50
- scaffoldProject({
51
- templateRoot,
52
- packageRoot,
53
- targetRoot,
54
- projectName,
55
- frontend,
56
- db,
57
- i18nEnabled,
58
- proxy,
59
- });
60
-
61
- if (installEnabled) {
62
- runInstall(targetRoot);
63
- }
64
-
65
- console.log('Forgeon scaffold generated.');
66
- console.log(`- path: ${targetRoot}`);
67
- console.log(`- frontend: ${frontend}`);
68
- console.log(`- db: ${db}`);
69
- console.log(`- i18n: ${i18nEnabled}`);
70
- console.log(`- docker: ${dockerEnabled}`);
71
- console.log(`- proxy: ${proxy}`);
72
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { printHelp } from './cli/help.mjs';
5
+ import { parseCliArgs, promptForMissingOptions } from './cli/options.mjs';
6
+ import { DEFAULT_DB, DEFAULT_FRONTEND, DEFAULT_PROXY, FIXED_DOCKER_ENABLED } from './constants.mjs';
7
+ import { runInstall } from './core/install.mjs';
8
+ import { scaffoldProject } from './core/scaffold.mjs';
9
+ import { validatePresetSupport } from './core/validate.mjs';
10
+ import { parseBoolean } from './utils/values.mjs';
11
+
12
+ export async function runCreateForgeon(argv = process.argv.slice(2)) {
13
+ const { options: parsedOptions, positional } = parseCliArgs(argv);
14
+ const options = { ...parsedOptions };
15
+
16
+ if (options.help) {
17
+ printHelp();
18
+ return;
19
+ }
20
+
21
+ if (!options.name && positional.length > 0) {
22
+ [options.name] = positional;
23
+ }
24
+
25
+ const promptedOptions = await promptForMissingOptions(options);
26
+
27
+ if (!promptedOptions.name || promptedOptions.name.trim().length === 0) {
28
+ throw new Error('Project name is required.');
29
+ }
30
+
31
+ const frontend = (promptedOptions.frontend ?? DEFAULT_FRONTEND).toString().toLowerCase();
32
+ const db = (promptedOptions.db ?? DEFAULT_DB).toString().toLowerCase();
33
+ const i18nEnabled = parseBoolean(promptedOptions.i18n, true);
34
+ const dockerEnabled = FIXED_DOCKER_ENABLED;
35
+ const proxy = (promptedOptions.proxy ?? DEFAULT_PROXY).toString().toLowerCase();
36
+ const installEnabled = parseBoolean(promptedOptions.install, false);
37
+
38
+ validatePresetSupport({ frontend, db, dockerEnabled, proxy });
39
+
40
+ const projectName = promptedOptions.name.trim();
41
+ const targetRoot = path.resolve(process.cwd(), projectName);
42
+ if (fs.existsSync(targetRoot)) {
43
+ throw new Error(`Target directory already exists: ${targetRoot}`);
44
+ }
45
+
46
+ const srcDir = path.dirname(fileURLToPath(import.meta.url));
47
+ const packageRoot = path.resolve(srcDir, '..');
48
+ const templateRoot = path.join(packageRoot, 'templates', 'base');
49
+
50
+ scaffoldProject({
51
+ templateRoot,
52
+ packageRoot,
53
+ targetRoot,
54
+ projectName,
55
+ frontend,
56
+ db,
57
+ i18nEnabled,
58
+ proxy,
59
+ });
60
+
61
+ if (installEnabled) {
62
+ runInstall(targetRoot);
63
+ }
64
+
65
+ console.log('Forgeon scaffold generated.');
66
+ console.log(`- path: ${targetRoot}`);
67
+ console.log(`- frontend: ${frontend}`);
68
+ console.log(`- db: ${db}`);
69
+ console.log(`- i18n: ${i18nEnabled}`);
70
+ console.log(`- docker: ${dockerEnabled}`);
71
+ console.log(`- proxy: ${proxy}`);
72
+ }
package/src/utils/fs.mjs CHANGED
@@ -1,26 +1,26 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
-
4
- export function copyRecursive(source, destination) {
5
- const stat = fs.statSync(source);
6
-
7
- if (stat.isDirectory()) {
8
- fs.mkdirSync(destination, { recursive: true });
9
- for (const entry of fs.readdirSync(source)) {
10
- copyRecursive(path.join(source, entry), path.join(destination, entry));
11
- }
12
- return;
13
- }
14
-
15
- fs.copyFileSync(source, destination);
16
- }
17
-
18
- export function writeJson(filePath, data) {
19
- fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
20
- }
21
-
22
- export function removeIfExists(targetPath) {
23
- if (fs.existsSync(targetPath)) {
24
- fs.rmSync(targetPath, { recursive: true, force: true });
25
- }
26
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ export function copyRecursive(source, destination) {
5
+ const stat = fs.statSync(source);
6
+
7
+ if (stat.isDirectory()) {
8
+ fs.mkdirSync(destination, { recursive: true });
9
+ for (const entry of fs.readdirSync(source)) {
10
+ copyRecursive(path.join(source, entry), path.join(destination, entry));
11
+ }
12
+ return;
13
+ }
14
+
15
+ fs.copyFileSync(source, destination);
16
+ }
17
+
18
+ export function writeJson(filePath, data) {
19
+ fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
20
+ }
21
+
22
+ export function removeIfExists(targetPath) {
23
+ if (fs.existsSync(targetPath)) {
24
+ fs.rmSync(targetPath, { recursive: true, force: true });
25
+ }
26
+ }
@@ -1,20 +1,20 @@
1
- export function parseBoolean(value, fallback) {
2
- if (value === undefined) return fallback;
3
- if (typeof value === 'boolean') return value;
4
-
5
- const normalized = String(value).trim().toLowerCase();
6
- if (['true', '1', 'yes', 'y'].includes(normalized)) return true;
7
- if (['false', '0', 'no', 'n'].includes(normalized)) return false;
8
-
9
- throw new Error(`Invalid boolean value: ${value}`);
10
- }
11
-
12
- export function toKebabCase(value) {
13
- return (
14
- value
15
- .trim()
16
- .toLowerCase()
17
- .replace(/[^a-z0-9]+/g, '-')
18
- .replace(/^-+|-+$/g, '') || 'forgeon-app'
19
- );
20
- }
1
+ export function parseBoolean(value, fallback) {
2
+ if (value === undefined) return fallback;
3
+ if (typeof value === 'boolean') return value;
4
+
5
+ const normalized = String(value).trim().toLowerCase();
6
+ if (['true', '1', 'yes', 'y'].includes(normalized)) return true;
7
+ if (['false', '0', 'no', 'n'].includes(normalized)) return false;
8
+
9
+ throw new Error(`Invalid boolean value: ${value}`);
10
+ }
11
+
12
+ export function toKebabCase(value) {
13
+ return (
14
+ value
15
+ .trim()
16
+ .toLowerCase()
17
+ .replace(/[^a-z0-9]+/g, '-')
18
+ .replace(/^-+|-+$/g, '') || 'forgeon-app'
19
+ );
20
+ }
@@ -1,7 +1,7 @@
1
- **/node_modules
2
- **/dist
3
- .git
4
- .gitignore
5
- .DS_Store
6
- pnpm-debug.log
7
- *.log
1
+ **/node_modules
2
+ **/dist
3
+ .git
4
+ .gitignore
5
+ .DS_Store
6
+ pnpm-debug.log
7
+ *.log
@@ -1,11 +1,11 @@
1
- root = true
2
-
3
- [*.{ts,tsx,js,mjs,cjs,json,md,yml,yaml,css,html,prisma,sql,conf}]
4
- charset = utf-8
5
- end_of_line = lf
6
- insert_final_newline = true
7
- indent_style = space
8
- indent_size = 2
9
-
10
- [Makefile]
11
- indent_style = tab
1
+ root = true
2
+
3
+ [*.{ts,tsx,js,mjs,cjs,json,md,yml,yaml,css,html,prisma,sql,conf}]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ indent_style = space
8
+ indent_size = 2
9
+
10
+ [Makefile]
11
+ indent_style = tab
@@ -1,47 +1,47 @@
1
- # Forgeon Fullstack Scaffold
2
-
3
- Canonical monorepo scaffold for NestJS + frontend with shared packages, built-in docs, optional i18n (enabled by default), and default DB stack Prisma + Postgres.
4
-
5
- ## Quick Start (Dev)
6
-
7
- 1. Install dependencies:
8
- ```bash
9
- pnpm install
10
- ```
11
- 2. Start local Postgres (Docker):
12
- ```bash
13
- docker compose --env-file infra/docker/.env.example -f infra/docker/compose.yml up db -d
14
- ```
15
- 3. Run API + web in dev mode:
16
- ```bash
17
- pnpm dev
18
- ```
19
- 4. Open:
20
- - Web: `http://localhost:5173`
21
- - API health: `http://localhost:3000/api/health`
22
-
23
- ## Quick Start (Docker)
24
-
25
- ```bash
26
- docker compose --env-file infra/docker/.env.example -f infra/docker/compose.yml up --build
27
- ```
28
-
29
- Open `http://localhost:8080`.
30
-
31
- ## i18n Toggle
32
-
33
- Set in env:
34
- - `I18N_ENABLED=true|false`
35
- - `I18N_DEFAULT_LANG=en`
36
- - `I18N_FALLBACK_LANG=en`
37
-
38
- When `I18N_ENABLED=false`, API runs without loading i18n module.
39
-
40
- ## Prisma In Docker Start
41
-
42
- API container starts with:
43
- 1. `prisma migrate deploy`
44
- 2. `node apps/api/dist/main.js`
45
-
46
- This keeps container startup production-like while still simple.
1
+ # Forgeon Fullstack Scaffold
2
+
3
+ Canonical monorepo scaffold for NestJS + frontend with shared packages, built-in docs, optional i18n (enabled by default), and default DB stack Prisma + Postgres.
4
+
5
+ ## Quick Start (Dev)
6
+
7
+ 1. Install dependencies:
8
+ ```bash
9
+ pnpm install
10
+ ```
11
+ 2. Start local Postgres (Docker):
12
+ ```bash
13
+ docker compose --env-file infra/docker/.env.example -f infra/docker/compose.yml up db -d
14
+ ```
15
+ 3. Run API + web in dev mode:
16
+ ```bash
17
+ pnpm dev
18
+ ```
19
+ 4. Open:
20
+ - Web: `http://localhost:5173`
21
+ - API health: `http://localhost:3000/api/health`
22
+
23
+ ## Quick Start (Docker)
24
+
25
+ ```bash
26
+ docker compose --env-file infra/docker/.env.example -f infra/docker/compose.yml up --build
27
+ ```
28
+
29
+ Open `http://localhost:8080`.
30
+
31
+ ## i18n Toggle
32
+
33
+ Set in env:
34
+ - `I18N_ENABLED=true|false`
35
+ - `I18N_DEFAULT_LANG=en`
36
+ - `I18N_FALLBACK_LANG=en`
37
+
38
+ When `I18N_ENABLED=false`, API runs without loading i18n module.
39
+
40
+ ## Prisma In Docker Start
41
+
42
+ API container starts with:
43
+ 1. `prisma migrate deploy`
44
+ 2. `node apps/api/dist/main.js`
45
+
46
+ This keeps container startup production-like while still simple.
47
47
 
@@ -1,24 +1,24 @@
1
- FROM node:20-alpine
2
-
3
- WORKDIR /app
4
- RUN corepack enable
5
-
6
- COPY package.json pnpm-workspace.yaml tsconfig.base.json ./
7
- COPY apps/api/package.json apps/api/package.json
8
- COPY apps/api/prisma apps/api/prisma
9
- COPY packages/core/package.json packages/core/package.json
10
- COPY packages/i18n/package.json packages/i18n/package.json
11
-
12
- RUN pnpm install --frozen-lockfile=false
13
-
14
- COPY apps/api apps/api
15
- COPY packages/core packages/core
16
- COPY packages/i18n packages/i18n
17
- COPY resources resources
18
-
19
- RUN pnpm --filter @forgeon/i18n build
20
- RUN pnpm --filter @forgeon/api prisma:generate
21
- RUN pnpm --filter @forgeon/api build
22
-
23
- EXPOSE 3000
24
- CMD ["sh", "-c", "pnpm --filter @forgeon/api prisma:migrate:deploy && node apps/api/dist/main.js"]
1
+ FROM node:20-alpine
2
+
3
+ WORKDIR /app
4
+ RUN corepack enable
5
+
6
+ COPY package.json pnpm-workspace.yaml tsconfig.base.json ./
7
+ COPY apps/api/package.json apps/api/package.json
8
+ COPY apps/api/prisma apps/api/prisma
9
+ COPY packages/core/package.json packages/core/package.json
10
+ COPY packages/i18n/package.json packages/i18n/package.json
11
+
12
+ RUN pnpm install --frozen-lockfile=false
13
+
14
+ COPY apps/api apps/api
15
+ COPY packages/core packages/core
16
+ COPY packages/i18n packages/i18n
17
+ COPY resources resources
18
+
19
+ RUN pnpm --filter @forgeon/i18n build
20
+ RUN pnpm --filter @forgeon/api prisma:generate
21
+ RUN pnpm --filter @forgeon/api build
22
+
23
+ EXPOSE 3000
24
+ CMD ["sh", "-c", "pnpm --filter @forgeon/api prisma:migrate:deploy && node apps/api/dist/main.js"]
@@ -1,40 +1,40 @@
1
- {
2
- "name": "@forgeon/api",
3
- "version": "0.1.0",
4
- "private": true,
5
- "scripts": {
6
- "predev": "pnpm --filter @forgeon/i18n build",
7
- "build": "tsc -p tsconfig.build.json",
8
- "dev": "ts-node --transpile-only src/main.ts",
9
- "start": "node dist/main.js",
10
- "prisma:generate": "prisma generate --schema prisma/schema.prisma",
11
- "prisma:migrate:dev": "prisma migrate dev --schema prisma/schema.prisma",
12
- "prisma:migrate:deploy": "prisma migrate deploy --schema prisma/schema.prisma",
13
- "prisma:studio": "prisma studio --schema prisma/schema.prisma",
14
- "prisma:seed": "ts-node --transpile-only prisma/seed.ts"
15
- },
16
- "dependencies": {
17
- "@forgeon/i18n": "workspace:*",
18
- "@nestjs/common": "^11.0.1",
19
- "@nestjs/config": "^4.0.2",
20
- "@nestjs/core": "^11.0.1",
21
- "@nestjs/platform-express": "^11.0.1",
22
- "@prisma/client": "^6.18.0",
23
- "class-transformer": "^0.5.1",
24
- "class-validator": "^0.14.1",
25
- "nestjs-i18n": "^10.5.1",
26
- "reflect-metadata": "^0.2.2",
27
- "rxjs": "^7.8.1"
28
- },
29
- "devDependencies": {
30
- "@types/express": "^5.0.0",
31
- "@types/node": "^22.10.7",
32
- "prisma": "^6.18.0",
33
- "ts-node": "^10.9.2",
34
- "typescript": "^5.7.3"
35
- },
36
- "prisma": {
37
- "seed": "ts-node --transpile-only prisma/seed.ts"
38
- }
39
- }
1
+ {
2
+ "name": "@forgeon/api",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "predev": "pnpm --filter @forgeon/i18n build",
7
+ "build": "tsc -p tsconfig.build.json",
8
+ "dev": "ts-node --transpile-only src/main.ts",
9
+ "start": "node dist/main.js",
10
+ "prisma:generate": "prisma generate --schema prisma/schema.prisma",
11
+ "prisma:migrate:dev": "prisma migrate dev --schema prisma/schema.prisma",
12
+ "prisma:migrate:deploy": "prisma migrate deploy --schema prisma/schema.prisma",
13
+ "prisma:studio": "prisma studio --schema prisma/schema.prisma",
14
+ "prisma:seed": "ts-node --transpile-only prisma/seed.ts"
15
+ },
16
+ "dependencies": {
17
+ "@forgeon/i18n": "workspace:*",
18
+ "@nestjs/common": "^11.0.1",
19
+ "@nestjs/config": "^4.0.2",
20
+ "@nestjs/core": "^11.0.1",
21
+ "@nestjs/platform-express": "^11.0.1",
22
+ "@prisma/client": "^6.18.0",
23
+ "class-transformer": "^0.5.1",
24
+ "class-validator": "^0.14.1",
25
+ "nestjs-i18n": "^10.5.1",
26
+ "reflect-metadata": "^0.2.2",
27
+ "rxjs": "^7.8.1"
28
+ },
29
+ "devDependencies": {
30
+ "@types/express": "^5.0.0",
31
+ "@types/node": "^22.10.7",
32
+ "prisma": "^6.18.0",
33
+ "ts-node": "^10.9.2",
34
+ "typescript": "^5.7.3"
35
+ },
36
+ "prisma": {
37
+ "seed": "ts-node --transpile-only prisma/seed.ts"
38
+ }
39
+ }
40
40
 
@@ -1,12 +1,12 @@
1
- -- CreateTable
2
- CREATE TABLE "User" (
3
- "id" TEXT NOT NULL,
4
- "email" TEXT NOT NULL,
5
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
6
- "updatedAt" TIMESTAMP(3) NOT NULL,
7
-
8
- CONSTRAINT "User_pkey" PRIMARY KEY ("id")
9
- );
10
-
11
- -- CreateIndex
1
+ -- CreateTable
2
+ CREATE TABLE "User" (
3
+ "id" TEXT NOT NULL,
4
+ "email" TEXT NOT NULL,
5
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
6
+ "updatedAt" TIMESTAMP(3) NOT NULL,
7
+
8
+ CONSTRAINT "User_pkey" PRIMARY KEY ("id")
9
+ );
10
+
11
+ -- CreateIndex
12
12
  CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
@@ -1,15 +1,15 @@
1
- generator client {
2
- provider = "prisma-client-js"
3
- }
4
-
5
- datasource db {
6
- provider = "postgresql"
7
- url = env("DATABASE_URL")
8
- }
9
-
10
- model User {
11
- id String @id @default(cuid())
12
- email String @unique
13
- createdAt DateTime @default(now())
14
- updatedAt DateTime @updatedAt
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "postgresql"
7
+ url = env("DATABASE_URL")
8
+ }
9
+
10
+ model User {
11
+ id String @id @default(cuid())
12
+ email String @unique
13
+ createdAt DateTime @default(now())
14
+ updatedAt DateTime @updatedAt
15
15
  }
@@ -1,20 +1,20 @@
1
- import { PrismaClient } from '@prisma/client';
2
-
3
- const prisma = new PrismaClient();
4
-
5
- async function main() {
6
- await prisma.user.upsert({
7
- where: { email: 'seed@example.com' },
8
- update: {},
9
- create: { email: 'seed@example.com' },
10
- });
11
- }
12
-
13
- main()
14
- .catch((error) => {
15
- console.error(error);
16
- process.exit(1);
17
- })
18
- .finally(async () => {
19
- await prisma.$disconnect();
1
+ import { PrismaClient } from '@prisma/client';
2
+
3
+ const prisma = new PrismaClient();
4
+
5
+ async function main() {
6
+ await prisma.user.upsert({
7
+ where: { email: 'seed@example.com' },
8
+ update: {},
9
+ create: { email: 'seed@example.com' },
10
+ });
11
+ }
12
+
13
+ main()
14
+ .catch((error) => {
15
+ console.error(error);
16
+ process.exit(1);
17
+ })
18
+ .finally(async () => {
19
+ await prisma.$disconnect();
20
20
  });
@@ -1,34 +1,34 @@
1
- import { Module } from '@nestjs/common';
2
- import { ConfigModule } from '@nestjs/config';
3
- import { ForgeonI18nModule } from '@forgeon/i18n';
4
- import { join } from 'path';
5
- import appConfig from './config/app.config';
6
- import { HealthController } from './health/health.controller';
7
- import { PrismaModule } from './prisma/prisma.module';
8
- import { AppExceptionFilter } from './common/filters/app-exception.filter';
9
-
10
- const i18nEnabled = (process.env.I18N_ENABLED ?? 'true').toLowerCase() !== 'false';
11
- const i18nDefaultLang = process.env.I18N_DEFAULT_LANG ?? 'en';
12
- const i18nFallbackLang = process.env.I18N_FALLBACK_LANG ?? 'en';
13
- const i18nPath = join(__dirname, '..', '..', '..', 'resources', 'i18n');
14
-
15
- @Module({
16
- imports: [
17
- ConfigModule.forRoot({
18
- isGlobal: true,
19
- load: [appConfig],
20
- envFilePath: '.env',
21
- }),
22
- ForgeonI18nModule.register({
23
- enabled: i18nEnabled,
24
- defaultLang: i18nDefaultLang,
25
- fallbackLang: i18nFallbackLang,
26
- path: i18nPath,
27
- }),
28
- PrismaModule,
29
- ],
30
- controllers: [HealthController],
31
- providers: [AppExceptionFilter],
32
- })
1
+ import { Module } from '@nestjs/common';
2
+ import { ConfigModule } from '@nestjs/config';
3
+ import { ForgeonI18nModule } from '@forgeon/i18n';
4
+ import { join } from 'path';
5
+ import appConfig from './config/app.config';
6
+ import { HealthController } from './health/health.controller';
7
+ import { PrismaModule } from './prisma/prisma.module';
8
+ import { AppExceptionFilter } from './common/filters/app-exception.filter';
9
+
10
+ const i18nEnabled = (process.env.I18N_ENABLED ?? 'true').toLowerCase() !== 'false';
11
+ const i18nDefaultLang = process.env.I18N_DEFAULT_LANG ?? 'en';
12
+ const i18nFallbackLang = process.env.I18N_FALLBACK_LANG ?? 'en';
13
+ const i18nPath = join(__dirname, '..', '..', '..', 'resources', 'i18n');
14
+
15
+ @Module({
16
+ imports: [
17
+ ConfigModule.forRoot({
18
+ isGlobal: true,
19
+ load: [appConfig],
20
+ envFilePath: '.env',
21
+ }),
22
+ ForgeonI18nModule.register({
23
+ enabled: i18nEnabled,
24
+ defaultLang: i18nDefaultLang,
25
+ fallbackLang: i18nFallbackLang,
26
+ path: i18nPath,
27
+ }),
28
+ PrismaModule,
29
+ ],
30
+ controllers: [HealthController],
31
+ providers: [AppExceptionFilter],
32
+ })
33
33
  export class AppModule {}
34
34