@kreciko/setup-templates 0.1.0
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 +109 -0
- package/bin/setup-templates.mjs +7 -0
- package/package.json +23 -0
- package/src/config.mjs +7 -0
- package/src/index.mjs +113 -0
- package/src/utils/fs.mjs +56 -0
- package/templates/base/LICENSE +8 -0
- package/templates/base/biome.json +42 -0
- package/templates/docker/.dockerignore +34 -0
- package/templates/docker/Dockerfile.backend +27 -0
- package/templates/docker/Dockerfile.frontend +21 -0
- package/templates/docker/Makefile +23 -0
- package/templates/docker/compose.dev.yaml +41 -0
- package/templates/docker/compose.yaml +31 -0
- package/templates/express/tsconfig.json +28 -0
- package/templates/frontend/tsconfig.app.json +26 -0
- package/templates/frontend/tsconfig.json +13 -0
- package/templates/frontend/tsconfig.node.json +24 -0
- package/templates/frontend/vite.config.ts +17 -0
- package/templates/nginx/nginx.conf +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# setup-templates
|
|
2
|
+
|
|
3
|
+
๐ Zero-config CLI for quickly adding standard project files to an existing repository.
|
|
4
|
+
|
|
5
|
+
> Perfect for use with **npx** - no installation, no configuration, no pain.
|
|
6
|
+
|
|
7
|
+
## โจ What is it?
|
|
8
|
+
|
|
9
|
+
`setup-templates` is a CLI tool that adds ready-made, proven configuration templates to your project in seconds.
|
|
10
|
+
Instead of copying files between repositories - run one command and choose what you want to install.
|
|
11
|
+
|
|
12
|
+
## ๐งฐ What's included?
|
|
13
|
+
|
|
14
|
+
- **Express** - TypeScript configuration for Express.js
|
|
15
|
+
- **Frontend** - React + Vite + TypeScript (development & build only)
|
|
16
|
+
- **Docker** - `Dockerfile` and `docker-compose` for development and production
|
|
17
|
+
- **Nginx** - production server for frontend build and API reverse proxy
|
|
18
|
+
- **Base** - MIT license, Biome config and .gitignore
|
|
19
|
+
|
|
20
|
+
## โถ๏ธ Quick start
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @kretostan/setup-templates
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
That's it. The CLI will launch an interactive menu where you select which templates to add to your project.
|
|
27
|
+
|
|
28
|
+
### ๐ Specific version (reproducible builds)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx @kretostan/setup-templates@1.2.3
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### ๐งโ๐ป Local usage (teams / CI)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install --save-dev @kretostan/setup-templates
|
|
38
|
+
npx @kretostan/setup-templates
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## ๐ฆ What can you install?
|
|
42
|
+
|
|
43
|
+
You can safely install multiple templates in the same project.
|
|
44
|
+
Depending on the selected templates, setup-templates will add the following files and directories to your project.
|
|
45
|
+
Existing files are not overwritten without confirmation.
|
|
46
|
+
|
|
47
|
+
## ๐ What files will be created?
|
|
48
|
+
|
|
49
|
+
### ๐งฑ Base
|
|
50
|
+
```
|
|
51
|
+
โโ .gitignore
|
|
52
|
+
โโ biome.json
|
|
53
|
+
โโ LICENSE
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### ๐ณ Docker
|
|
57
|
+
```
|
|
58
|
+
โโ Dockerfile.backend
|
|
59
|
+
โโ Dockerfile.frontend
|
|
60
|
+
โโ .dockerignore
|
|
61
|
+
โโ Makefile
|
|
62
|
+
โโ compose.dev.yaml
|
|
63
|
+
โโ compose.yaml
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### ๐ฅ Express.js
|
|
67
|
+
```
|
|
68
|
+
โโ tsconfig.json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
> Express template provides a minimal TypeScript setup, intended to be extended by the user.
|
|
72
|
+
|
|
73
|
+
### ๐ Frontend (React + Vite)
|
|
74
|
+
```
|
|
75
|
+
โโ tsconfig.app.json
|
|
76
|
+
โโ tsconfig.json
|
|
77
|
+
โโ tsconfig.node.json
|
|
78
|
+
โโ vite.config.ts
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### ๐ Nginx
|
|
82
|
+
```
|
|
83
|
+
โโ nginx.conf
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## ๐ก Why setup-templates?
|
|
87
|
+
|
|
88
|
+
- โก works instantly via npx
|
|
89
|
+
- ๐ฆ no manual file copying
|
|
90
|
+
- ๐งญ interactive template selection
|
|
91
|
+
- ๐งฉ easy to extend with new presets
|
|
92
|
+
- ๐งผ consistent project structure across your team
|
|
93
|
+
|
|
94
|
+
## โ๏ธ Requirements
|
|
95
|
+
|
|
96
|
+
- Node.js >= 24
|
|
97
|
+
- npm
|
|
98
|
+
|
|
99
|
+
> (Node 24+ required due to modern Node APIs)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
## ๐ ๏ธ Libraries used
|
|
103
|
+
|
|
104
|
+
- chalk - colored terminal output
|
|
105
|
+
- ora - spinners
|
|
106
|
+
- prompts - interactive menu
|
|
107
|
+
|
|
108
|
+
## ๐ License
|
|
109
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kreciko/setup-templates",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Installer that adds project template files into an existing repo",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"setup-templates": "bin/setup-templates.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"templates",
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=24"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"chalk": "^5.6.2",
|
|
20
|
+
"ora": "^9.1.0",
|
|
21
|
+
"prompts": "^2.4.2"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/config.mjs
ADDED
package/src/index.mjs
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import prompts from "prompts";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { copyDir, ensureProjectFolder } from "./utils/fs.mjs";
|
|
6
|
+
import { TEMPLATES_DIR } from "./config.mjs";
|
|
7
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
8
|
+
|
|
9
|
+
function safeCwd() {
|
|
10
|
+
try {
|
|
11
|
+
return process.cwd();
|
|
12
|
+
} catch {
|
|
13
|
+
const fallback = process.env.INIT_CWD || process.env.PWD || process.env.HOME || "/";
|
|
14
|
+
process.chdir(fallback);
|
|
15
|
+
return process.cwd();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function main(argv) {
|
|
20
|
+
const cwd = process.cwd();
|
|
21
|
+
ensureProjectFolder(cwd);
|
|
22
|
+
|
|
23
|
+
const args = new Set(argv.slice(2));
|
|
24
|
+
const hasFlags = [...args].some(arg => arg.startsWith("--"));
|
|
25
|
+
const selections = {
|
|
26
|
+
base: args.has("--base"),
|
|
27
|
+
docker: args.has("--docker"),
|
|
28
|
+
nginx: args.has("--nginx"),
|
|
29
|
+
express: args.has("--express"),
|
|
30
|
+
frontend: args.has("--frontend"),
|
|
31
|
+
force: args.has("--force"),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const spinner = ora();
|
|
35
|
+
if (!hasFlags) {
|
|
36
|
+
const response = await prompts([
|
|
37
|
+
{
|
|
38
|
+
type: "multiselect",
|
|
39
|
+
name: "features",
|
|
40
|
+
message: " What config would you like to include?",
|
|
41
|
+
choices: [
|
|
42
|
+
{ title: chalk.white("Base"), value: "base" },
|
|
43
|
+
{ title: chalk.blueBright("Docker (Frontend + Backend)"), value: "docker" },
|
|
44
|
+
{ title: chalk.green("Nginx"), value: "nginx" },
|
|
45
|
+
{ title: chalk.yellowBright("Express.js (ESM + TS)"), value: "express"},
|
|
46
|
+
{ title: chalk.magentaBright("React + Vite + TS"), value: "frontend" },
|
|
47
|
+
],
|
|
48
|
+
hint: "Space to select. Return to submit.",
|
|
49
|
+
instructions: false,
|
|
50
|
+
min: 1,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: "confirm",
|
|
54
|
+
name: "force",
|
|
55
|
+
message: " Do you want to overwrite existing files?",
|
|
56
|
+
initial: false,
|
|
57
|
+
},
|
|
58
|
+
], {
|
|
59
|
+
onCancel: () => {
|
|
60
|
+
spinner.fail(chalk.redBright(" Canceled"));
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const set = new Set(response.features ?? []);
|
|
66
|
+
selections.base = set.has("base");
|
|
67
|
+
selections.docker = set.has("docker");
|
|
68
|
+
selections.nginx = set.has("nginx");
|
|
69
|
+
selections.express = set.has("express");
|
|
70
|
+
selections.frontend = set.has("frontend");
|
|
71
|
+
selections.force = !!response.force;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
spinner.start(chalk.whiteBright("Copying files"));
|
|
75
|
+
await sleep(1000);
|
|
76
|
+
let copied = 0;
|
|
77
|
+
|
|
78
|
+
let warning = "";
|
|
79
|
+
let info = "";
|
|
80
|
+
const rootCheck = new Set(["base", "docker", "nginx",]);
|
|
81
|
+
const frontCheck = new Set(["base", "frontend"]);
|
|
82
|
+
const backCheck = new Set(["base", "express"]);
|
|
83
|
+
Object.keys(selections).forEach(option => {
|
|
84
|
+
if (!selections.force) warning = " Skipping files due to disabled force option.";
|
|
85
|
+
if (!selections[option]) return;
|
|
86
|
+
info = ensureProjectFolder(cwd);
|
|
87
|
+
switch (info) {
|
|
88
|
+
case "root":
|
|
89
|
+
rootCheck.has(option)
|
|
90
|
+
? copied += copyDir(path.join(TEMPLATES_DIR, option), cwd, selections.force)
|
|
91
|
+
: info = " Some options are unavailable on this level.";
|
|
92
|
+
break;
|
|
93
|
+
case "frontend":
|
|
94
|
+
frontCheck.has(option)
|
|
95
|
+
? copied += copyDir(path.join(TEMPLATES_DIR, option), cwd, selections.force)
|
|
96
|
+
: info = " Some options are unavailable on this level.";
|
|
97
|
+
break;
|
|
98
|
+
case "backend":
|
|
99
|
+
backCheck.has(option)
|
|
100
|
+
? copied += copyDir(path.join(TEMPLATES_DIR, option), cwd, selections.force)
|
|
101
|
+
: info = " Some options are unavailable on this level.";
|
|
102
|
+
break;
|
|
103
|
+
default:
|
|
104
|
+
console.log("Error while validating folder.");
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (warning.length > 0) spinner.warn(chalk.yellow(warning));
|
|
109
|
+
if (info.length > 0) spinner.info(chalk.blueBright(info));
|
|
110
|
+
|
|
111
|
+
spinner.info(chalk.whiteBright(` Files copied: ${copied}`));
|
|
112
|
+
spinner.succeed(chalk.whiteBright(" All configuration information you can find in README.md"));
|
|
113
|
+
}
|
package/src/utils/fs.mjs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
|
|
6
|
+
function exists(path) {
|
|
7
|
+
try { fs.accessSync(path); return true; } catch { return false; }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function ensureProjectFolder(cwd) {
|
|
11
|
+
const spinner = ora();
|
|
12
|
+
if (!exists(path.join(cwd, "package.json"))) {
|
|
13
|
+
spinner.fail(chalk.red(" Package.json is missing."));
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
17
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
18
|
+
|
|
19
|
+
if (pkg.workspaces && !cwd.endsWith("backend") && !cwd.endsWith("frontend")) return "root";
|
|
20
|
+
if ((pkg.dependencies || pkg.devDependencies) && cwd.endsWith("backend")) return "backend";
|
|
21
|
+
if ((pkg.dependencies || pkg.devDependencies) && cwd.endsWith("frontend")) return "frontend";
|
|
22
|
+
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function listFiles(directory) {
|
|
27
|
+
let out = [];
|
|
28
|
+
for (const file of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
29
|
+
const fullPath = path.join(directory, file.name);
|
|
30
|
+
if (file.isDirectory()) {
|
|
31
|
+
out.push(...listFiles(fullPath));
|
|
32
|
+
} else {
|
|
33
|
+
out.push(fullPath);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function copyDir(srcDir, destDir, force = false) {
|
|
40
|
+
const files = listFiles(srcDir);
|
|
41
|
+
let count = 0;
|
|
42
|
+
|
|
43
|
+
for (const srcFile of files) {
|
|
44
|
+
const relative = path.relative(srcDir, srcFile);
|
|
45
|
+
const destFile = path.join(destDir, relative);
|
|
46
|
+
const destDirName = path.dirname(destFile);
|
|
47
|
+
|
|
48
|
+
if (exists(destFile) && !force) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
fs.mkdirSync(destDirName, { recursive: true });
|
|
52
|
+
fs.copyFileSync(srcFile, destFile);
|
|
53
|
+
count++;
|
|
54
|
+
}
|
|
55
|
+
return count;
|
|
56
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
Copyright ยฉ 2025 Jakub Kret
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the โSoftwareโ), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
5
|
+
|
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED โAS ISโ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.3.13/schema.json",
|
|
3
|
+
"vcs": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"clientKind": "git",
|
|
6
|
+
"useIgnoreFile": true
|
|
7
|
+
},
|
|
8
|
+
"files": {
|
|
9
|
+
"ignoreUnknown": false
|
|
10
|
+
},
|
|
11
|
+
"formatter": {
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"indentStyle": "tab"
|
|
14
|
+
},
|
|
15
|
+
"css": {
|
|
16
|
+
"parser": {
|
|
17
|
+
"tailwindDirectives": true
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"linter": {
|
|
21
|
+
"enabled": true,
|
|
22
|
+
"rules": {
|
|
23
|
+
"recommended": true,
|
|
24
|
+
"correctness": {
|
|
25
|
+
"useUniqueElementIds": "off"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"javascript": {
|
|
30
|
+
"formatter": {
|
|
31
|
+
"quoteStyle": "double"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"assist": {
|
|
35
|
+
"enabled": true,
|
|
36
|
+
"actions": {
|
|
37
|
+
"source": {
|
|
38
|
+
"organizeImports": "on"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules
|
|
3
|
+
.pnpm-store
|
|
4
|
+
.yarn
|
|
5
|
+
|
|
6
|
+
# Logs
|
|
7
|
+
*.log
|
|
8
|
+
npm-debug.log*
|
|
9
|
+
yarn-error.log*
|
|
10
|
+
|
|
11
|
+
# Build outputs (if you build in Docker)
|
|
12
|
+
dist
|
|
13
|
+
build
|
|
14
|
+
.out
|
|
15
|
+
.next
|
|
16
|
+
.nuxt
|
|
17
|
+
coverage
|
|
18
|
+
|
|
19
|
+
# Env
|
|
20
|
+
.env
|
|
21
|
+
.env.*
|
|
22
|
+
!.env.example
|
|
23
|
+
|
|
24
|
+
# VCS / IDE / OS
|
|
25
|
+
.git
|
|
26
|
+
.gitignore
|
|
27
|
+
.vscode
|
|
28
|
+
.idea
|
|
29
|
+
.DS_Store
|
|
30
|
+
|
|
31
|
+
# temp / cache
|
|
32
|
+
tmp
|
|
33
|
+
temp
|
|
34
|
+
.cache
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
FROM node:alpine AS deps
|
|
2
|
+
WORKDIR /app
|
|
3
|
+
COPY package.json package-lock.json ./
|
|
4
|
+
COPY frontend/package.json ./frontend/package.json
|
|
5
|
+
COPY backend/package.json ./backend/package.json
|
|
6
|
+
RUN npm install
|
|
7
|
+
|
|
8
|
+
FROM deps AS dev
|
|
9
|
+
WORKDIR /app
|
|
10
|
+
COPY backend ./backend
|
|
11
|
+
EXPOSE 3001
|
|
12
|
+
CMD ["npm", "--workspace", "backend", "run", "dev"]
|
|
13
|
+
|
|
14
|
+
FROM deps AS build
|
|
15
|
+
WORKDIR /app
|
|
16
|
+
COPY backend ./backend
|
|
17
|
+
RUN npm --workspace backend run build
|
|
18
|
+
|
|
19
|
+
FROM node:alpine AS prod
|
|
20
|
+
WORKDIR /app
|
|
21
|
+
COPY package.json package-lock.json ./
|
|
22
|
+
COPY frontend/package.json ./frontend/package.json
|
|
23
|
+
COPY backend/package.json ./backend/package.json
|
|
24
|
+
RUN npm ci --omit=dev
|
|
25
|
+
COPY --from=build /app/backend/dist ./backend/dist
|
|
26
|
+
EXPOSE 3000
|
|
27
|
+
CMD ["npm", "--workspace", "backend", "start"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
FROM node:alpine AS deps
|
|
2
|
+
WORKDIR /app
|
|
3
|
+
COPY package.json package-lock.json ./
|
|
4
|
+
COPY frontend/package.json ./frontend/package.json
|
|
5
|
+
COPY backend/package.json ./backend/package.json
|
|
6
|
+
RUN npm install
|
|
7
|
+
|
|
8
|
+
FROM deps AS dev
|
|
9
|
+
WORKDIR /app
|
|
10
|
+
COPY frontend ./frontend
|
|
11
|
+
EXPOSE 5173
|
|
12
|
+
CMD ["npm", "--workspace", "frontend", "run", "dev"]
|
|
13
|
+
|
|
14
|
+
FROM deps AS build
|
|
15
|
+
WORKDIR /app
|
|
16
|
+
COPY frontend ./frontend
|
|
17
|
+
RUN npm --workspace frontend run build
|
|
18
|
+
|
|
19
|
+
FROM nginx:alpine AS prod
|
|
20
|
+
COPY --from=build /app/frontend/dist /usr/share/nginx/html
|
|
21
|
+
EXPOSE 80
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
dev-up:
|
|
2
|
+
docker compose -p app-dev -f compose.dev.yaml --env-file .env.development up --build -d
|
|
3
|
+
|
|
4
|
+
dev-start:
|
|
5
|
+
docker compose -p app-dev -f compose.dev.yaml --env-file .env.development up --build -d
|
|
6
|
+
|
|
7
|
+
dev-stop:
|
|
8
|
+
docker compose -p app-dev stop
|
|
9
|
+
|
|
10
|
+
dev-down:
|
|
11
|
+
docker compose -p app-dev down
|
|
12
|
+
|
|
13
|
+
prod-up:
|
|
14
|
+
docker compose -p app-prod -f compose.yaml --env-file .env up --build -d
|
|
15
|
+
|
|
16
|
+
prod-start:
|
|
17
|
+
docker compose -p app-prod -f compose.yaml --env-file .env up --build -d
|
|
18
|
+
|
|
19
|
+
prod-stop:
|
|
20
|
+
docker compose -p app-prod stop
|
|
21
|
+
|
|
22
|
+
prod-down:
|
|
23
|
+
docker compose -p app-prod down
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
services:
|
|
2
|
+
backend:
|
|
3
|
+
build:
|
|
4
|
+
context: .
|
|
5
|
+
target: dev
|
|
6
|
+
dockerfile: Dockerfile.backend
|
|
7
|
+
ports:
|
|
8
|
+
- "${BACKEND_PORT:-3001}:3001"
|
|
9
|
+
env_file:
|
|
10
|
+
- ./.env.development
|
|
11
|
+
environment:
|
|
12
|
+
# - NODE_ENV=${NODE_ENV:-development}
|
|
13
|
+
- PORT=${BACKEND_PORT:-3001}
|
|
14
|
+
volumes:
|
|
15
|
+
- ./backend:/app/backend
|
|
16
|
+
- backend_node_modules:/app/backend/node_modules
|
|
17
|
+
healthcheck:
|
|
18
|
+
test: ["CMD-SHELL", "exit 0"]
|
|
19
|
+
disable: true
|
|
20
|
+
|
|
21
|
+
frontend:
|
|
22
|
+
build:
|
|
23
|
+
context: .
|
|
24
|
+
target: dev
|
|
25
|
+
dockerfile: Dockerfile.frontend
|
|
26
|
+
ports:
|
|
27
|
+
- "${FRONTEND_PORT:-5173}:5173"
|
|
28
|
+
env_file:
|
|
29
|
+
- ./.env.development
|
|
30
|
+
environment:
|
|
31
|
+
- NODE_ENV=${NODE_ENV:-development}
|
|
32
|
+
- CHOKIDAR_USEPOLLING=true
|
|
33
|
+
volumes:
|
|
34
|
+
- ./frontend:/app/frontend
|
|
35
|
+
- frontend_node_modules:/app/frontend/node_modules
|
|
36
|
+
depends_on:
|
|
37
|
+
- backend
|
|
38
|
+
|
|
39
|
+
volumes:
|
|
40
|
+
backend_node_modules:
|
|
41
|
+
frontend_node_modules:
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
services:
|
|
2
|
+
backend:
|
|
3
|
+
build:
|
|
4
|
+
context: .
|
|
5
|
+
target: prod
|
|
6
|
+
dockerfile: Dockerfile.backend
|
|
7
|
+
environment:
|
|
8
|
+
# - NODE_ENV=production
|
|
9
|
+
- PORT=3000
|
|
10
|
+
- APP_URL=http://localhost
|
|
11
|
+
expose:
|
|
12
|
+
- "3000"
|
|
13
|
+
healthcheck:
|
|
14
|
+
test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3000/health',res=>process.exit(res.statusCode===200?0:1)).on('error',()=>process.exit(1))\""]
|
|
15
|
+
interval: 5s
|
|
16
|
+
timeout: 2s
|
|
17
|
+
retries: 10
|
|
18
|
+
start_period: 5s
|
|
19
|
+
|
|
20
|
+
frontend:
|
|
21
|
+
build:
|
|
22
|
+
context: .
|
|
23
|
+
target: prod
|
|
24
|
+
dockerfile: Dockerfile.frontend
|
|
25
|
+
ports:
|
|
26
|
+
- "80:80"
|
|
27
|
+
volumes:
|
|
28
|
+
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
|
29
|
+
depends_on:
|
|
30
|
+
backend:
|
|
31
|
+
condition: service_healthy
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"rootDir": "./src",
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
|
|
6
|
+
"module": "NodeNext",
|
|
7
|
+
"target": "ESNext",
|
|
8
|
+
"moduleResolution": "nodenext",
|
|
9
|
+
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"declarationMap": true,
|
|
13
|
+
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noImplicitReturns": true,
|
|
16
|
+
"esModuleInterop": true,
|
|
17
|
+
|
|
18
|
+
"noUncheckedIndexedAccess": true,
|
|
19
|
+
"exactOptionalPropertyTypes": false,
|
|
20
|
+
"strict": true,
|
|
21
|
+
"types": ["node"],
|
|
22
|
+
|
|
23
|
+
"baseUrl": "../backend",
|
|
24
|
+
"paths": { "@/*": ["src/*"] }
|
|
25
|
+
},
|
|
26
|
+
"compileOnSave": true,
|
|
27
|
+
"include": ["src"]
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"useDefineForClassFields": true,
|
|
6
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"types": ["vite/client"],
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
"jsx": "react-jsx",
|
|
17
|
+
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noUnusedLocals": true,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"erasableSyntaxOnly": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true,
|
|
23
|
+
"noUncheckedSideEffectImports": true
|
|
24
|
+
},
|
|
25
|
+
"include": ["src"]
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
+
"target": "ES2023",
|
|
5
|
+
"lib": ["ES2023"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"verbatimModuleSyntax": true,
|
|
13
|
+
"moduleDetection": "force",
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
|
|
16
|
+
"strict": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"erasableSyntaxOnly": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedSideEffectImports": true
|
|
22
|
+
},
|
|
23
|
+
"include": ["vite.config.ts"]
|
|
24
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import react from "@vitejs/plugin-react-swc";
|
|
3
|
+
import { defineConfig } from 'vite';
|
|
4
|
+
|
|
5
|
+
// https://vite.dev/config/
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [react()],
|
|
8
|
+
server: {
|
|
9
|
+
host: "0.0.0.0",
|
|
10
|
+
port: 5173
|
|
11
|
+
},
|
|
12
|
+
resolve: {
|
|
13
|
+
alias: {
|
|
14
|
+
"@": path.resolve(__dirname, "./src"),
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
server {
|
|
2
|
+
listen 80;
|
|
3
|
+
|
|
4
|
+
# Root directory where the built frontend (HTML/CSS/JS) files are located
|
|
5
|
+
root /usr/share/nginx/html;
|
|
6
|
+
|
|
7
|
+
# Default file to serve
|
|
8
|
+
index index.html;
|
|
9
|
+
|
|
10
|
+
# Gzip compression
|
|
11
|
+
gzip on;
|
|
12
|
+
gzip_comp_level 5; # Compression on level 1โ9 (5 = sweet spot)
|
|
13
|
+
gzip_min_length 1024; # Do not compress very small files
|
|
14
|
+
gzip_vary on; # Add header Vary: Accept-Encoding
|
|
15
|
+
|
|
16
|
+
# MIME types
|
|
17
|
+
gzip_types
|
|
18
|
+
text/plain
|
|
19
|
+
text/css
|
|
20
|
+
application/javascript
|
|
21
|
+
application/json
|
|
22
|
+
application/xml
|
|
23
|
+
text/xml
|
|
24
|
+
image/svg+xml;
|
|
25
|
+
|
|
26
|
+
# Cache static assets for a long time (1 year)
|
|
27
|
+
# Good for production builds where filenames include hashes (e.g. main.abc123.js)
|
|
28
|
+
location ~* \.(js|css|png|jpg|jpeg|gif|svg|woff2)$ {
|
|
29
|
+
expires 1y;
|
|
30
|
+
add_header Cache-Control "public, immutable";
|
|
31
|
+
try_files $uri =404;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Main frontend route โ needed for SPA frameworks (React, Vue, Svelte, etc.)
|
|
35
|
+
# Any unknown route falls back to index.html
|
|
36
|
+
location / {
|
|
37
|
+
try_files $uri /index.html;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# API reverse proxy โ forwards /api/* requests to backend service
|
|
41
|
+
# "backend" should match the service name in docker-compose (or another host)
|
|
42
|
+
location ^~ /api/ {
|
|
43
|
+
# Forward requests to the backend service
|
|
44
|
+
# Trailing slash removes /api prefix before passing the request upstream
|
|
45
|
+
proxy_pass http://backend:3000/;
|
|
46
|
+
|
|
47
|
+
# Preserve original request information
|
|
48
|
+
# Allows backend to know the real host, client IP and protocol
|
|
49
|
+
proxy_set_header Host $host;
|
|
50
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
51
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
52
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
53
|
+
|
|
54
|
+
# Enable WebSocket and other connection upgrades
|
|
55
|
+
proxy_http_version 1.1;
|
|
56
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
57
|
+
proxy_set_header Connection "upgrade";
|
|
58
|
+
|
|
59
|
+
# Prevent hanging connections and slow upstreams
|
|
60
|
+
proxy_connect_timeout 5s;
|
|
61
|
+
proxy_send_timeout 60s;
|
|
62
|
+
proxy_read_timeout 60s;
|
|
63
|
+
|
|
64
|
+
# Maximum allowed request body size (e.g. file uploads)
|
|
65
|
+
client_max_body_size 20m;
|
|
66
|
+
}
|
|
67
|
+
}
|