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.
- package/README.md +19 -17
- package/bin/create-forgeon.mjs +22 -22
- package/package.json +1 -1
- package/src/cli/add-help.mjs +12 -12
- package/src/cli/add-options.mjs +54 -54
- package/src/cli/add-options.test.mjs +24 -24
- package/src/cli/help.mjs +20 -20
- package/src/cli/options.mjs +121 -121
- package/src/cli/options.test.mjs +41 -41
- package/src/cli/prompt-select.mjs +94 -94
- package/src/cli/prompt-select.test.mjs +148 -148
- package/src/constants.mjs +13 -13
- package/src/core/docs.mjs +128 -128
- package/src/core/docs.test.mjs +91 -91
- package/src/core/install.mjs +14 -14
- package/src/core/scaffold.mjs +48 -45
- package/src/core/validate.mjs +12 -12
- package/src/core/validate.test.mjs +73 -73
- package/src/databases/index.mjs +26 -26
- package/src/frameworks/index.mjs +32 -32
- package/src/infrastructure/proxy.mjs +12 -12
- package/src/modules/docs.mjs +70 -70
- package/src/modules/executor.mjs +39 -21
- package/src/modules/executor.test.mjs +95 -45
- package/src/modules/i18n.mjs +283 -0
- package/src/modules/registry.mjs +43 -35
- package/src/presets/i18n.mjs +228 -180
- package/src/presets/index.mjs +2 -2
- package/src/presets/proxy.mjs +32 -32
- package/src/run-add-module.mjs +47 -47
- package/src/run-create-forgeon.mjs +72 -72
- package/src/utils/fs.mjs +26 -26
- package/src/utils/values.mjs +20 -20
- package/templates/base/.dockerignore +7 -7
- package/templates/base/.editorconfig +11 -11
- package/templates/base/README.md +46 -46
- package/templates/base/apps/api/Dockerfile +24 -24
- package/templates/base/apps/api/package.json +39 -39
- package/templates/base/apps/api/prisma/migrations/0001_init/migration.sql +11 -11
- package/templates/base/apps/api/prisma/schema.prisma +14 -14
- package/templates/base/apps/api/prisma/seed.ts +19 -19
- package/templates/base/apps/api/src/app.module.ts +32 -32
- package/templates/base/apps/api/src/common/dto/echo-query.dto.ts +5 -5
- package/templates/base/apps/api/src/common/filters/app-exception.filter.ts +129 -129
- package/templates/base/apps/api/src/config/app.config.ts +12 -12
- package/templates/base/apps/api/src/health/health.controller.ts +30 -30
- package/templates/base/apps/api/src/main.ts +25 -25
- package/templates/base/apps/api/src/prisma/prisma.module.ts +8 -8
- package/templates/base/apps/api/src/prisma/prisma.service.ts +26 -26
- package/templates/base/apps/api/tsconfig.build.json +8 -8
- package/templates/base/apps/api/tsconfig.json +8 -8
- package/templates/base/apps/web/Dockerfile +12 -12
- package/templates/base/apps/web/index.html +11 -11
- package/templates/base/apps/web/package.json +21 -21
- package/templates/base/apps/web/src/App.tsx +35 -35
- package/templates/base/apps/web/src/main.tsx +8 -8
- package/templates/base/apps/web/src/styles.css +32 -32
- package/templates/base/apps/web/tsconfig.json +17 -17
- package/templates/base/apps/web/vite.config.ts +14 -14
- package/templates/base/docs/AI/ARCHITECTURE.md +37 -37
- package/templates/base/docs/AI/MODULE_SPEC.md +56 -56
- package/templates/base/docs/AI/PROJECT.md +31 -31
- package/templates/base/docs/AI/TASKS.md +57 -57
- package/templates/base/docs/README.md +6 -6
- package/templates/base/infra/caddy/Caddyfile +15 -15
- package/templates/base/infra/docker/.env.example +9 -9
- package/templates/base/infra/docker/caddy.Dockerfile +15 -15
- package/templates/base/infra/docker/compose.caddy.yml +44 -44
- package/templates/base/infra/docker/compose.nginx.yml +44 -44
- package/templates/base/infra/docker/compose.none.yml +37 -37
- package/templates/base/infra/docker/compose.yml +44 -44
- package/templates/base/infra/docker/nginx.Dockerfile +15 -15
- package/templates/base/infra/nginx/nginx.conf +31 -31
- package/templates/base/package.json +23 -23
- package/templates/base/packages/core/README.md +2 -2
- package/templates/base/packages/core/package.json +13 -13
- package/templates/base/packages/core/tsconfig.json +7 -7
- package/templates/base/packages/i18n/package.json +18 -18
- package/templates/base/packages/i18n/src/forgeon-i18n.module.ts +45 -45
- package/templates/base/packages/i18n/tsconfig.json +8 -8
- package/templates/base/pnpm-workspace.yaml +2 -2
- package/templates/base/resources/i18n/en/common.json +4 -4
- package/templates/base/resources/i18n/en/validation.json +2 -2
- package/templates/base/resources/i18n/uk/common.json +4 -4
- package/templates/base/resources/i18n/uk/validation.json +2 -2
- package/templates/base/tsconfig.base.json +16 -16
- package/templates/docs-fragments/AI_ARCHITECTURE/00_title.md +1 -1
- package/templates/docs-fragments/AI_ARCHITECTURE/10_layout_base.md +6 -6
- package/templates/docs-fragments/AI_ARCHITECTURE/11_layout_infra.md +1 -1
- package/templates/docs-fragments/AI_ARCHITECTURE/12_layout_i18n_resources.md +1 -1
- package/templates/docs-fragments/AI_ARCHITECTURE/20_env_base.md +4 -4
- package/templates/docs-fragments/AI_ARCHITECTURE/21_env_i18n.md +3 -3
- package/templates/docs-fragments/AI_ARCHITECTURE/30_default_db.md +7 -7
- package/templates/docs-fragments/AI_ARCHITECTURE/31_docker_runtime.md +5 -5
- package/templates/docs-fragments/AI_ARCHITECTURE/32_scope_freeze.md +5 -5
- package/templates/docs-fragments/AI_ARCHITECTURE/40_docs_generation.md +9 -9
- package/templates/docs-fragments/AI_ARCHITECTURE/50_extension_points.md +8 -8
- package/templates/docs-fragments/AI_PROJECT/00_title.md +1 -1
- package/templates/docs-fragments/AI_PROJECT/10_what_is.md +3 -3
- package/templates/docs-fragments/AI_PROJECT/20_structure_base.md +5 -5
- package/templates/docs-fragments/AI_PROJECT/21_structure_i18n.md +2 -0
- package/templates/docs-fragments/AI_PROJECT/22_structure_docker.md +1 -1
- package/templates/docs-fragments/AI_PROJECT/23_structure_docs.md +1 -1
- package/templates/docs-fragments/AI_PROJECT/30_run_dev.md +8 -8
- package/templates/docs-fragments/AI_PROJECT/31_run_docker.md +5 -5
- package/templates/docs-fragments/AI_PROJECT/32_proxy_notes.md +5 -5
- package/templates/docs-fragments/AI_PROJECT/32_proxy_notes_none.md +5 -5
- package/templates/docs-fragments/AI_PROJECT/33_i18n_notes.md +2 -0
- package/templates/docs-fragments/AI_PROJECT/40_change_boundaries_base.md +3 -3
- package/templates/docs-fragments/AI_PROJECT/41_change_boundaries_docker.md +1 -1
- package/templates/docs-fragments/README/00_title.md +3 -3
- package/templates/docs-fragments/README/10_stack.md +8 -8
- package/templates/docs-fragments/README/20_quick_start_dev_intro.md +6 -6
- package/templates/docs-fragments/README/21_quick_start_dev_db_docker.md +4 -4
- package/templates/docs-fragments/README/21_quick_start_dev_db_local.md +1 -1
- package/templates/docs-fragments/README/22_quick_start_dev_outro.md +7 -7
- package/templates/docs-fragments/README/30_quick_start_docker.md +7 -7
- package/templates/docs-fragments/README/30_quick_start_docker_none.md +9 -9
- package/templates/docs-fragments/README/31_proxy_preset_caddy.md +9 -9
- package/templates/docs-fragments/README/31_proxy_preset_nginx.md +8 -8
- package/templates/docs-fragments/README/31_proxy_preset_none.md +6 -6
- package/templates/docs-fragments/README/32_prisma_container_start.md +5 -5
- package/templates/docs-fragments/README/40_i18n.md +14 -8
- package/templates/docs-fragments/README/90_next_steps.md +7 -7
- package/templates/module-fragments/i18n/00_title.md +5 -0
- package/templates/module-fragments/i18n/10_overview.md +9 -0
- package/templates/module-fragments/i18n/20_scope.md +7 -0
- package/templates/module-fragments/i18n/90_status_implemented.md +3 -0
- package/templates/module-fragments/jwt-auth/00_title.md +1 -1
- package/templates/module-fragments/jwt-auth/10_overview.md +6 -6
- package/templates/module-fragments/jwt-auth/20_scope.md +7 -7
- package/templates/module-fragments/jwt-auth/90_status_planned.md +3 -3
- package/templates/module-fragments/queue/00_title.md +1 -1
- package/templates/module-fragments/queue/10_overview.md +6 -6
- package/templates/module-fragments/queue/20_scope.md +7 -7
- package/templates/module-fragments/queue/90_status_planned.md +3 -3
- package/templates/module-presets/i18n/apps/web/src/App.tsx +61 -0
- package/templates/module-presets/i18n/packages/i18n-contracts/package.json +14 -0
- package/templates/module-presets/i18n/packages/i18n-contracts/src/index.ts +7 -0
- package/templates/module-presets/i18n/packages/i18n-contracts/tsconfig.json +8 -0
- package/templates/module-presets/i18n/packages/i18n-web/package.json +17 -0
- package/templates/module-presets/i18n/packages/i18n-web/src/index.ts +50 -0
- 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
|
+
}
|
package/src/utils/values.mjs
CHANGED
|
@@ -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
|
package/templates/base/README.md
CHANGED
|
@@ -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
|
|