create-pnpm-custom-app 1.0.2 → 1.0.4
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 +11 -9
- package/bin/cli.js +113 -82
- package/package.json +13 -5
- package/templates/.github/copilot-instructions.md +1 -0
- package/templates/CONTRIBUTING.md +8 -1
- package/templates/README.md +3 -3
- package/templates/apps/api/package.json +25 -25
- package/templates/apps/api/src/app.ts +4 -6
- package/templates/apps/api/src/config/config.ts +2 -2
- package/templates/apps/api/src/config/logger.ts +3 -3
- package/templates/apps/api/src/db/mongo.ts +3 -3
- package/templates/apps/api/src/index.ts +2 -2
- package/templates/apps/api/src/middlewares/middleware.ts +8 -14
- package/templates/apps/api/src/models/example.model.ts +7 -7
- package/templates/apps/api/src/routes/routes.ts +3 -3
- package/templates/apps/api/src/schemas/swagger.schema.ts +5 -5
- package/templates/apps/api/src/services/example.service.ts +9 -9
- package/templates/apps/api/src/tests/health.test.ts +9 -9
- package/templates/apps/api/src/tests/helpers/test-helpers.ts +4 -4
- package/templates/apps/api/src/tests/mocks/mocks.ts +3 -3
- package/templates/apps/api/src/tests/setup.ts +1 -1
- package/templates/apps/api/src/types/fastify.d.ts +3 -3
- package/templates/apps/web/app/[locale]/(routes)/page.tsx +61 -33
- package/templates/apps/web/app/[locale]/layout.tsx +1 -3
- package/templates/apps/web/app/components/layout/Footer.component.tsx +3 -5
- package/templates/apps/web/app/components/layout/Nav.component.tsx +4 -14
- package/templates/apps/web/app/globals.css +44 -2
- package/templates/apps/web/eslint.config.mjs +1 -7
- package/templates/apps/web/lib/utils.ts +2 -2
- package/templates/apps/web/package.json +12 -12
- package/templates/apps/web/tsconfig.json +7 -1
- package/templates/package.json +2 -2
- package/templates/packages/shared/eslint.config.js +11 -11
- package/templates/packages/shared/src/index.ts +2 -3
package/README.md
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
### Recommended: Use npx (always latest version)
|
|
21
21
|
|
|
22
|
+
[](https://www.npmjs.com/package/create-pnpm-custom-app)
|
|
23
|
+
|
|
22
24
|
```bash
|
|
23
25
|
npx create-pnpm-custom-app@latest my-project
|
|
24
26
|
```
|
|
@@ -104,7 +106,7 @@ my-project/
|
|
|
104
106
|
```bash
|
|
105
107
|
# Frontend
|
|
106
108
|
cp apps/web/.env.example apps/web/.env.local
|
|
107
|
-
|
|
109
|
+
|
|
108
110
|
# Backend
|
|
109
111
|
cp apps/api/.env.example apps/api/.env
|
|
110
112
|
```
|
|
@@ -118,7 +120,7 @@ my-project/
|
|
|
118
120
|
```bash
|
|
119
121
|
# Terminal 1: Frontend (http://localhost:3000)
|
|
120
122
|
pnpm --filter web dev
|
|
121
|
-
|
|
123
|
+
|
|
122
124
|
# Terminal 2: Backend (http://localhost:3002)
|
|
123
125
|
pnpm --filter api dev
|
|
124
126
|
```
|
|
@@ -160,14 +162,14 @@ pnpm --filter api lint # Lint code
|
|
|
160
162
|
|
|
161
163
|
## Tech Stack
|
|
162
164
|
|
|
163
|
-
| Category
|
|
164
|
-
|
|
|
165
|
+
| Category | Technologies |
|
|
166
|
+
| ------------ | ----------------------------------------------------------- |
|
|
165
167
|
| **Frontend** | Next.js 16, React 19, TypeScript, Tailwind CSS 4, next-intl |
|
|
166
|
-
| **Backend**
|
|
167
|
-
| **Auth**
|
|
168
|
-
| **Testing**
|
|
169
|
-
| **Tooling**
|
|
170
|
-
| **Docs**
|
|
168
|
+
| **Backend** | Fastify 5, TypeScript, MongoDB, Mongoose, Pino |
|
|
169
|
+
| **Auth** | @fastify/jwt, bcrypt |
|
|
170
|
+
| **Testing** | Jest, Supertest |
|
|
171
|
+
| **Tooling** | pnpm, ESLint, Prettier, TypeScript |
|
|
172
|
+
| **Docs** | Swagger/OpenAPI, JSDoc |
|
|
171
173
|
|
|
172
174
|
## Requirements
|
|
173
175
|
|
package/bin/cli.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { Command } from
|
|
4
|
-
import prompts from
|
|
5
|
-
import chalk from
|
|
6
|
-
import ora from
|
|
7
|
-
import fs from
|
|
8
|
-
import path from
|
|
9
|
-
import { fileURLToPath } from
|
|
10
|
-
import { execSync } from
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import prompts from "prompts";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import ora from "ora";
|
|
7
|
+
import fs from "fs-extra";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import { execSync } from "child_process";
|
|
11
11
|
|
|
12
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
13
|
const __dirname = path.dirname(__filename);
|
|
@@ -15,31 +15,34 @@ const __dirname = path.dirname(__filename);
|
|
|
15
15
|
const program = new Command();
|
|
16
16
|
|
|
17
17
|
program
|
|
18
|
-
.name(
|
|
19
|
-
.description(
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
.name("create-pnpm-custom-app")
|
|
19
|
+
.description(
|
|
20
|
+
"Scaffold a professional full-stack monorepo with Next.js, Fastify, and pnpm workspaces",
|
|
21
|
+
)
|
|
22
|
+
.version("1.0.0")
|
|
23
|
+
.argument("[project-name]", "Name of the project")
|
|
22
24
|
.parse(process.argv);
|
|
23
25
|
|
|
24
26
|
const args = program.args;
|
|
25
27
|
|
|
26
28
|
async function main() {
|
|
27
|
-
console.log(chalk.bold.cyan(
|
|
29
|
+
console.log(chalk.bold.cyan("\n🚀 Create PNPM Custom App\n"));
|
|
28
30
|
|
|
29
31
|
let projectName = args[0];
|
|
30
|
-
let githubUser =
|
|
32
|
+
let githubUser = "";
|
|
31
33
|
|
|
32
34
|
if (!projectName) {
|
|
33
35
|
const response = await prompts({
|
|
34
|
-
type:
|
|
35
|
-
name:
|
|
36
|
-
message:
|
|
37
|
-
initial:
|
|
38
|
-
validate: (value) =>
|
|
36
|
+
type: "text",
|
|
37
|
+
name: "projectName",
|
|
38
|
+
message: "What is your project name?",
|
|
39
|
+
initial: "my-app",
|
|
40
|
+
validate: (value) =>
|
|
41
|
+
value.length > 0 ? true : "Project name is required",
|
|
39
42
|
});
|
|
40
43
|
|
|
41
44
|
if (!response.projectName) {
|
|
42
|
-
console.log(chalk.red(
|
|
45
|
+
console.log(chalk.red("\n❌ Project creation cancelled."));
|
|
43
46
|
process.exit(1);
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -48,9 +51,10 @@ async function main() {
|
|
|
48
51
|
|
|
49
52
|
// Ask for GitHub username
|
|
50
53
|
const githubResponse = await prompts({
|
|
51
|
-
type:
|
|
52
|
-
name:
|
|
53
|
-
message:
|
|
54
|
+
type: "text",
|
|
55
|
+
name: "githubUser",
|
|
56
|
+
message:
|
|
57
|
+
"What is your GitHub username? (optional, used only for repository links)",
|
|
54
58
|
initial: projectName,
|
|
55
59
|
validate: (value) => true, // Optional field
|
|
56
60
|
});
|
|
@@ -66,43 +70,49 @@ async function main() {
|
|
|
66
70
|
|
|
67
71
|
console.log(chalk.gray(`\n📁 Creating project at: ${projectPath}\n`));
|
|
68
72
|
|
|
69
|
-
const spinner = ora(
|
|
73
|
+
const spinner = ora("Creating project structure...").start();
|
|
70
74
|
|
|
71
75
|
try {
|
|
72
76
|
// Create project directory
|
|
73
77
|
fs.mkdirSync(projectPath, { recursive: true });
|
|
74
78
|
|
|
75
79
|
// Copy all templates
|
|
76
|
-
const templatesDir = path.join(__dirname,
|
|
80
|
+
const templatesDir = path.join(__dirname, "../templates");
|
|
77
81
|
fs.copySync(templatesDir, projectPath);
|
|
78
82
|
|
|
79
83
|
// Rename gitignore files (npm publish removes .gitignore)
|
|
80
|
-
const gitignoreRootPath = path.join(projectPath,
|
|
84
|
+
const gitignoreRootPath = path.join(projectPath, "gitignore-root");
|
|
81
85
|
if (fs.existsSync(gitignoreRootPath)) {
|
|
82
|
-
fs.renameSync(gitignoreRootPath, path.join(projectPath,
|
|
86
|
+
fs.renameSync(gitignoreRootPath, path.join(projectPath, ".gitignore"));
|
|
83
87
|
}
|
|
84
88
|
|
|
85
|
-
const webGitignorePath = path.join(projectPath,
|
|
89
|
+
const webGitignorePath = path.join(projectPath, "apps/web/gitignore");
|
|
86
90
|
if (fs.existsSync(webGitignorePath)) {
|
|
87
|
-
fs.renameSync(
|
|
91
|
+
fs.renameSync(
|
|
92
|
+
webGitignorePath,
|
|
93
|
+
path.join(projectPath, "apps/web/.gitignore"),
|
|
94
|
+
);
|
|
88
95
|
}
|
|
89
96
|
|
|
90
|
-
const apiGitignorePath = path.join(projectPath,
|
|
97
|
+
const apiGitignorePath = path.join(projectPath, "apps/api/gitignore");
|
|
91
98
|
if (fs.existsSync(apiGitignorePath)) {
|
|
92
|
-
fs.renameSync(
|
|
99
|
+
fs.renameSync(
|
|
100
|
+
apiGitignorePath,
|
|
101
|
+
path.join(projectPath, "apps/api/.gitignore"),
|
|
102
|
+
);
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
// Replace project name in package.json files
|
|
96
106
|
const packageJsonPaths = [
|
|
97
|
-
path.join(projectPath,
|
|
98
|
-
path.join(projectPath,
|
|
99
|
-
path.join(projectPath,
|
|
100
|
-
path.join(projectPath,
|
|
107
|
+
path.join(projectPath, "package.json"),
|
|
108
|
+
path.join(projectPath, "apps/web/package.json"),
|
|
109
|
+
path.join(projectPath, "apps/api/package.json"),
|
|
110
|
+
path.join(projectPath, "packages/shared/package.json"),
|
|
101
111
|
];
|
|
102
112
|
|
|
103
113
|
packageJsonPaths.forEach((pkgPath) => {
|
|
104
114
|
if (fs.existsSync(pkgPath)) {
|
|
105
|
-
const content = fs.readFileSync(pkgPath,
|
|
115
|
+
const content = fs.readFileSync(pkgPath, "utf-8");
|
|
106
116
|
const updated = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
107
117
|
fs.writeFileSync(pkgPath, updated);
|
|
108
118
|
}
|
|
@@ -110,76 +120,97 @@ async function main() {
|
|
|
110
120
|
|
|
111
121
|
// Replace project name in all other files (tsx, ts, md, json, etc.)
|
|
112
122
|
const filesToReplace = [
|
|
113
|
-
path.join(projectPath,
|
|
114
|
-
path.join(projectPath,
|
|
115
|
-
path.join(projectPath,
|
|
116
|
-
path.join(projectPath,
|
|
117
|
-
path.join(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
path.join(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
path.join(projectPath,
|
|
126
|
-
path.join(projectPath,
|
|
127
|
-
path.join(projectPath,
|
|
128
|
-
path.join(projectPath,
|
|
123
|
+
path.join(projectPath, "README.md"),
|
|
124
|
+
path.join(projectPath, "CONTRIBUTING.md"),
|
|
125
|
+
path.join(projectPath, "apps/web/app/layout.tsx"),
|
|
126
|
+
path.join(projectPath, "apps/web/app/manifest.json"),
|
|
127
|
+
path.join(
|
|
128
|
+
projectPath,
|
|
129
|
+
"apps/web/app/components/layout/Nav.component.tsx",
|
|
130
|
+
),
|
|
131
|
+
path.join(
|
|
132
|
+
projectPath,
|
|
133
|
+
"apps/web/app/components/layout/Footer.component.tsx",
|
|
134
|
+
),
|
|
135
|
+
path.join(projectPath, "apps/web/app/[locale]/(routes)/page.tsx"),
|
|
136
|
+
path.join(projectPath, "apps/web/app/opengraph-image.tsx"),
|
|
137
|
+
path.join(projectPath, "apps/web/app/twitter-image.tsx"),
|
|
138
|
+
path.join(projectPath, "apps/web/app/apple-icon.tsx"),
|
|
139
|
+
path.join(projectPath, "apps/web/app/icon.tsx"),
|
|
140
|
+
path.join(projectPath, "apps/web/messages/en.json"),
|
|
141
|
+
path.join(projectPath, "apps/web/messages/es.json"),
|
|
142
|
+
path.join(projectPath, "apps/api/src/app.ts"),
|
|
143
|
+
path.join(projectPath, "packages/shared/src/index.ts"),
|
|
144
|
+
path.join(projectPath, ".github/copilot-instructions.md"),
|
|
129
145
|
];
|
|
130
146
|
|
|
131
147
|
filesToReplace.forEach((filePath) => {
|
|
132
148
|
if (fs.existsSync(filePath)) {
|
|
133
|
-
let content = fs.readFileSync(filePath,
|
|
149
|
+
let content = fs.readFileSync(filePath, "utf-8");
|
|
134
150
|
content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
135
151
|
content = content.replace(/\{\{GITHUB_USER\}\}/g, githubUser);
|
|
136
152
|
fs.writeFileSync(filePath, content);
|
|
137
153
|
}
|
|
138
154
|
});
|
|
139
155
|
|
|
140
|
-
spinner.succeed(chalk.green(
|
|
156
|
+
spinner.succeed(chalk.green("Project structure created!"));
|
|
141
157
|
|
|
142
158
|
// Install dependencies
|
|
143
|
-
spinner.start(
|
|
144
|
-
|
|
159
|
+
spinner.start(
|
|
160
|
+
"Installing dependencies with pnpm... This may take a few minutes.",
|
|
161
|
+
);
|
|
162
|
+
|
|
145
163
|
try {
|
|
146
|
-
execSync(
|
|
147
|
-
spinner.succeed(chalk.green(
|
|
164
|
+
execSync("pnpm install", { cwd: projectPath, stdio: "ignore" });
|
|
165
|
+
spinner.succeed(chalk.green("Dependencies installed successfully!"));
|
|
148
166
|
} catch (error) {
|
|
149
|
-
spinner.warn(
|
|
150
|
-
|
|
167
|
+
spinner.warn(
|
|
168
|
+
chalk.yellow("Could not install dependencies automatically."),
|
|
169
|
+
);
|
|
170
|
+
console.log(
|
|
171
|
+
chalk.gray(" Run `pnpm install` manually inside the project."),
|
|
172
|
+
);
|
|
151
173
|
}
|
|
152
174
|
|
|
153
175
|
// Success message
|
|
154
|
-
console.log(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
console.log(chalk.
|
|
158
|
-
console.log(chalk.gray(
|
|
159
|
-
console.log(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
176
|
+
console.log(
|
|
177
|
+
chalk.bold.green(`\n✅ Project "${projectName}" created successfully!\n`),
|
|
178
|
+
);
|
|
179
|
+
console.log(chalk.cyan("📦 Project structure:"));
|
|
180
|
+
console.log(chalk.gray(" ├── apps/"));
|
|
181
|
+
console.log(
|
|
182
|
+
chalk.gray(
|
|
183
|
+
" │ ├── web/ (Next.js 16 + TypeScript + Tailwind 4)",
|
|
184
|
+
),
|
|
185
|
+
);
|
|
186
|
+
console.log(
|
|
187
|
+
chalk.gray(" │ └── api/ (Fastify + MongoDB + TypeScript)"),
|
|
188
|
+
);
|
|
189
|
+
console.log(chalk.gray(" ├── packages/"));
|
|
190
|
+
console.log(
|
|
191
|
+
chalk.gray(" │ └── shared/ (Shared types and interfaces)"),
|
|
192
|
+
);
|
|
193
|
+
console.log(chalk.gray(" └── docs/ (Project documentation)\n"));
|
|
194
|
+
|
|
195
|
+
console.log(chalk.cyan("🚀 Next steps:"));
|
|
164
196
|
console.log(chalk.white(` 1. cd ${projectName}`));
|
|
165
|
-
console.log(chalk.white(
|
|
166
|
-
console.log(chalk.gray(
|
|
167
|
-
console.log(chalk.gray(
|
|
168
|
-
console.log(chalk.white(
|
|
169
|
-
console.log(chalk.gray(
|
|
170
|
-
console.log(chalk.gray(
|
|
171
|
-
console.log(chalk.white(
|
|
172
|
-
|
|
173
|
-
console.log(chalk.bold.magenta(
|
|
174
|
-
|
|
197
|
+
console.log(chalk.white(" 2. Copy .env.example files and configure:"));
|
|
198
|
+
console.log(chalk.gray(" - apps/web/.env.local"));
|
|
199
|
+
console.log(chalk.gray(" - apps/api/.env"));
|
|
200
|
+
console.log(chalk.white(" 3. Start development:"));
|
|
201
|
+
console.log(chalk.gray(" - Frontend: pnpm --filter web dev"));
|
|
202
|
+
console.log(chalk.gray(" - Backend: pnpm --filter api dev"));
|
|
203
|
+
console.log(chalk.white(" 4. Read the README.md for more information\n"));
|
|
204
|
+
|
|
205
|
+
console.log(chalk.bold.magenta("Happy coding! 🎉\n"));
|
|
175
206
|
} catch (error) {
|
|
176
|
-
spinner.fail(chalk.red(
|
|
207
|
+
spinner.fail(chalk.red("Failed to create project"));
|
|
177
208
|
console.error(error);
|
|
178
209
|
process.exit(1);
|
|
179
210
|
}
|
|
180
211
|
}
|
|
181
212
|
|
|
182
213
|
main().catch((error) => {
|
|
183
|
-
console.error(chalk.red(
|
|
214
|
+
console.error(chalk.red("An error occurred:"), error);
|
|
184
215
|
process.exit(1);
|
|
185
216
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pnpm-custom-app",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "A professional CLI to scaffold full-stack monorepo projects with Next.js, Fastify, and pnpm workspaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,12 +26,20 @@
|
|
|
26
26
|
"node": ">=20.0.0"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"chalk": "^5.
|
|
30
|
-
"commander": "^
|
|
31
|
-
"fs-extra": "^11.
|
|
32
|
-
"ora": "^
|
|
29
|
+
"chalk": "^5.6.2",
|
|
30
|
+
"commander": "^14.0.2",
|
|
31
|
+
"fs-extra": "^11.3.3",
|
|
32
|
+
"ora": "^9.1.0",
|
|
33
33
|
"prompts": "^2.4.2"
|
|
34
34
|
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/pelayotrives/create-pnpm-custom-app"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/pelayotrives/create-pnpm-custom-app#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/pelayotrives/create-pnpm-custom-app/issues"
|
|
42
|
+
},
|
|
35
43
|
"files": [
|
|
36
44
|
"bin",
|
|
37
45
|
"templates"
|
|
@@ -77,7 +77,7 @@ pnpm --filter api test:watch
|
|
|
77
77
|
|
|
78
78
|
We follow [Conventional Commits](https://www.conventionalcommits.org/):
|
|
79
79
|
|
|
80
|
-
```
|
|
80
|
+
```txt
|
|
81
81
|
<type>(<scope>): <subject>
|
|
82
82
|
|
|
83
83
|
<body>
|
|
@@ -108,11 +108,13 @@ docs(readme): update installation instructions
|
|
|
108
108
|
1. **Update documentation** - Update README.md if needed
|
|
109
109
|
2. **Add tests** - Ensure new features have test coverage
|
|
110
110
|
3. **Run quality checks**:
|
|
111
|
+
|
|
111
112
|
```bash
|
|
112
113
|
pnpm run lint
|
|
113
114
|
pnpm run build
|
|
114
115
|
pnpm run test
|
|
115
116
|
```
|
|
117
|
+
|
|
116
118
|
4. **Create pull request** - Use a clear title and description
|
|
117
119
|
5. **Link issues** - Reference related issues in the PR description
|
|
118
120
|
6. **Wait for review** - Address feedback from maintainers
|
|
@@ -121,18 +123,22 @@ docs(readme): update installation instructions
|
|
|
121
123
|
|
|
122
124
|
```markdown
|
|
123
125
|
## Description
|
|
126
|
+
|
|
124
127
|
Brief description of changes
|
|
125
128
|
|
|
126
129
|
## Type of Change
|
|
130
|
+
|
|
127
131
|
- [ ] Bug fix
|
|
128
132
|
- [ ] New feature
|
|
129
133
|
- [ ] Breaking change
|
|
130
134
|
- [ ] Documentation update
|
|
131
135
|
|
|
132
136
|
## Testing
|
|
137
|
+
|
|
133
138
|
Describe the tests you ran
|
|
134
139
|
|
|
135
140
|
## Checklist
|
|
141
|
+
|
|
136
142
|
- [ ] Code follows project style guidelines
|
|
137
143
|
- [ ] Self-review completed
|
|
138
144
|
- [ ] Comments added for complex code
|
|
@@ -177,6 +183,7 @@ Describe the tests you ran
|
|
|
177
183
|
## Questions?
|
|
178
184
|
|
|
179
185
|
If you have questions, please:
|
|
186
|
+
|
|
180
187
|
1. Check existing documentation
|
|
181
188
|
2. Search existing issues
|
|
182
189
|
3. Create a new issue with the `question` label
|
package/templates/README.md
CHANGED
|
@@ -281,10 +281,10 @@ pnpm --filter api test:coverage # With coverage
|
|
|
281
281
|
Example test structure:
|
|
282
282
|
|
|
283
283
|
```typescript
|
|
284
|
-
import { describe, it, expect } from
|
|
284
|
+
import { describe, it, expect } from "@jest/globals";
|
|
285
285
|
|
|
286
|
-
describe(
|
|
287
|
-
it(
|
|
286
|
+
describe("Feature Name", () => {
|
|
287
|
+
it("should do something", () => {
|
|
288
288
|
expect(true).toBe(true);
|
|
289
289
|
});
|
|
290
290
|
});
|
|
@@ -13,35 +13,35 @@
|
|
|
13
13
|
"lint": "eslint src --ext .ts"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@fastify/cors": "^
|
|
17
|
-
"@fastify/jwt": "^
|
|
18
|
-
"@fastify/multipart": "^9.0
|
|
19
|
-
"@fastify/rate-limit": "^10.
|
|
20
|
-
"@fastify/swagger": "^9.
|
|
21
|
-
"@fastify/swagger-ui": "^5.
|
|
16
|
+
"@fastify/cors": "^11.2.0",
|
|
17
|
+
"@fastify/jwt": "^10.0.0",
|
|
18
|
+
"@fastify/multipart": "^9.4.0",
|
|
19
|
+
"@fastify/rate-limit": "^10.3.0",
|
|
20
|
+
"@fastify/swagger": "^9.6.1",
|
|
21
|
+
"@fastify/swagger-ui": "^5.2.4",
|
|
22
22
|
"@{{PROJECT_NAME}}/shared": "workspace:*",
|
|
23
|
-
"bcrypt": "^
|
|
24
|
-
"dotenv": "^
|
|
25
|
-
"fastify": "^5.2
|
|
26
|
-
"mongoose": "^
|
|
27
|
-
"pino": "^
|
|
28
|
-
"pino-pretty": "^13.
|
|
23
|
+
"bcrypt": "^6.0.0",
|
|
24
|
+
"dotenv": "^17.2.3",
|
|
25
|
+
"fastify": "^5.7.2",
|
|
26
|
+
"mongoose": "^9.1.5",
|
|
27
|
+
"pino": "^10.3.0",
|
|
28
|
+
"pino-pretty": "^13.1.3"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@eslint/js": "^9.
|
|
32
|
-
"@jest/globals": "^
|
|
33
|
-
"@types/bcrypt": "^
|
|
34
|
-
"@types/jest": "^
|
|
35
|
-
"@types/node": "^22.
|
|
36
|
-
"@types/supertest": "^6.0.
|
|
37
|
-
"eslint": "^9.
|
|
38
|
-
"jest": "^
|
|
39
|
-
"supertest": "^7.
|
|
40
|
-
"ts-jest": "^29.
|
|
31
|
+
"@eslint/js": "^9.39.2",
|
|
32
|
+
"@jest/globals": "^30.2.0",
|
|
33
|
+
"@types/bcrypt": "^6.0.0",
|
|
34
|
+
"@types/jest": "^30.0.0",
|
|
35
|
+
"@types/node": "^22.19.7",
|
|
36
|
+
"@types/supertest": "^6.0.3",
|
|
37
|
+
"eslint": "^9.39.2",
|
|
38
|
+
"jest": "^30.2.0",
|
|
39
|
+
"supertest": "^7.2.2",
|
|
40
|
+
"ts-jest": "^29.4.6",
|
|
41
41
|
"ts-node": "^10.9.2",
|
|
42
|
-
"tsx": "^4.
|
|
43
|
-
"typescript": "^5.
|
|
44
|
-
"typescript-eslint": "^8.
|
|
42
|
+
"tsx": "^4.21.0",
|
|
43
|
+
"typescript": "^5.9.3",
|
|
44
|
+
"typescript-eslint": "^8.54.0"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
|
47
47
|
"node": ">=20.0.0"
|
|
@@ -30,7 +30,7 @@ function registerJWT(app: FastifyInstance) {
|
|
|
30
30
|
'JWT_SECRET is not defined. Please copy apps/api/.env.example to apps/api/.env and set JWT_SECRET'
|
|
31
31
|
);
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
app.register(jwt, {
|
|
35
35
|
secret: config.jwtSecret,
|
|
36
36
|
sign: {
|
|
@@ -46,7 +46,7 @@ function registerRateLimit(app: FastifyInstance) {
|
|
|
46
46
|
if (config.env === 'test') {
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
app.register(rateLimit, {
|
|
51
51
|
global: false,
|
|
52
52
|
});
|
|
@@ -70,9 +70,7 @@ async function registerSwagger(app: FastifyInstance) {
|
|
|
70
70
|
description: 'Development server',
|
|
71
71
|
},
|
|
72
72
|
],
|
|
73
|
-
tags: [
|
|
74
|
-
{ name: 'Health', description: 'Health check endpoints' },
|
|
75
|
-
],
|
|
73
|
+
tags: [{ name: 'Health', description: 'Health check endpoints' }],
|
|
76
74
|
},
|
|
77
75
|
});
|
|
78
76
|
|
|
@@ -99,7 +97,7 @@ function registerMultipart(app: FastifyInstance) {
|
|
|
99
97
|
|
|
100
98
|
/**
|
|
101
99
|
* Builds and configures the Fastify application
|
|
102
|
-
*
|
|
100
|
+
*
|
|
103
101
|
* @returns Configured Fastify instance
|
|
104
102
|
*/
|
|
105
103
|
export async function buildApp(): Promise<FastifyInstance> {
|
|
@@ -15,12 +15,12 @@ const env = process.env.NODE_ENV ?? 'development';
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Application configuration loaded from environment variables
|
|
18
|
-
*
|
|
18
|
+
*
|
|
19
19
|
* @remarks
|
|
20
20
|
* Required environment variables:
|
|
21
21
|
* - MONGODB_URI: MongoDB connection string
|
|
22
22
|
* - JWT_SECRET: Secret for JWT token signing
|
|
23
|
-
*
|
|
23
|
+
*
|
|
24
24
|
* Optional environment variables:
|
|
25
25
|
* - PORT: Server port (default: 3002)
|
|
26
26
|
* - CORS_ORIGIN: Allowed CORS origin (default: *)
|
|
@@ -3,9 +3,9 @@ import pino from 'pino';
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Returns logger configuration based on environment
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* @returns Logger config or false to disable logging in test
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
9
|
* @remarks
|
|
10
10
|
* - Test/E2E: Logging disabled
|
|
11
11
|
* - Production: Structured JSON logs at 'info' level
|
|
@@ -46,7 +46,7 @@ export function getLoggerConfig(): false | object {
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Application-wide logger instance using Pino
|
|
49
|
-
*
|
|
49
|
+
*
|
|
50
50
|
* @example
|
|
51
51
|
* ```typescript
|
|
52
52
|
* logger.info({ userId: '123' }, 'User logged in');
|
|
@@ -2,11 +2,11 @@ import mongoose from 'mongoose';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Establishes connection to MongoDB
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* @param uri - MongoDB connection URI
|
|
7
7
|
* @returns Mongoose connection object
|
|
8
8
|
* @throws Error if connection fails
|
|
9
|
-
*
|
|
9
|
+
*
|
|
10
10
|
* @example
|
|
11
11
|
* ```typescript
|
|
12
12
|
* await connectMongo('mongodb://localhost:27017/mydb');
|
|
@@ -20,7 +20,7 @@ export async function connectMongo(uri: string) {
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Disconnects from MongoDB
|
|
23
|
-
*
|
|
23
|
+
*
|
|
24
24
|
* @remarks
|
|
25
25
|
* Call this when shutting down the application or when database
|
|
26
26
|
* connection is no longer needed.
|
|
@@ -4,14 +4,14 @@ import config from './config/config.js';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Starts the API server
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* - Connects to MongoDB
|
|
9
9
|
* - Starts the Fastify server on configured port
|
|
10
10
|
* - Logs server URL when ready
|
|
11
11
|
*/
|
|
12
12
|
async function start() {
|
|
13
13
|
const app = await buildApp();
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
try {
|
|
16
16
|
if (!config.mongoUri) {
|
|
17
17
|
app.log.error(
|