@shakil-dev/shakil-stack 1.2.0 → 1.3.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/CODE_OF_CONDUCT.md +74 -0
- package/LICENSE +21 -0
- package/README.md +84 -50
- package/bin/index.js +221 -23
- package/package.json +1 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
|
9
|
+
education, socio-economic status, nationality, personal appearance, race,
|
|
10
|
+
religion, or sexual identity and orientation.
|
|
11
|
+
|
|
12
|
+
## Our Standards
|
|
13
|
+
|
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
|
15
|
+
include:
|
|
16
|
+
|
|
17
|
+
* Using welcoming and inclusive language
|
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
|
19
|
+
* Gracefully accepting constructive criticism
|
|
20
|
+
* Focusing on what is best for the community
|
|
21
|
+
* Showing empathy towards other community members
|
|
22
|
+
|
|
23
|
+
Examples of unacceptable behavior by participants include:
|
|
24
|
+
|
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
|
26
|
+
advances
|
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
|
28
|
+
* Public or private harassment
|
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
|
30
|
+
address, without explicit permission
|
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
32
|
+
professional setting
|
|
33
|
+
|
|
34
|
+
## Our Responsibilities
|
|
35
|
+
|
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
|
38
|
+
response to any instances of unacceptable behavior.
|
|
39
|
+
|
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
|
44
|
+
threatening, offensive, or harmful.
|
|
45
|
+
|
|
46
|
+
## Scope
|
|
47
|
+
|
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
|
49
|
+
when an individual is representing the project or its community. Examples of
|
|
50
|
+
representing a project or community include using an official project e-mail
|
|
51
|
+
address, posting via an official social media account, or acting as an
|
|
52
|
+
appointed representative at an online or offline event. Representation of a
|
|
53
|
+
project may be further defined and clarified by project maintainers.
|
|
54
|
+
|
|
55
|
+
## Enforcement
|
|
56
|
+
|
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
58
|
+
reported by contacting the project team. All complaints will be reviewed and
|
|
59
|
+
investigated and will result in a response that is deemed necessary and
|
|
60
|
+
appropriate to the circumstances. The project team will maintain confidentiality
|
|
61
|
+
with regard to the reporter of an incident. Further details of specific
|
|
62
|
+
enforcement policies may be posted separately.
|
|
63
|
+
|
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
|
66
|
+
members of the project's leadership.
|
|
67
|
+
|
|
68
|
+
## Attribution
|
|
69
|
+
|
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
71
|
+
version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
|
72
|
+
|
|
73
|
+
[homepage]: http://contributor-covenant.org
|
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shakil Ahmed Billal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,79 +1,113 @@
|
|
|
1
|
-
# Shakil-Stack
|
|
1
|
+
# 🚀 Shakil-Stack
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@shakil-dev/shakil-stack)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://github.com/shakil-ahmed-billal/shakil-stack-cli/actions)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
**Shakil-Stack** is a high-performance, developer-first full-stack project generator. It scaffolds production-ready applications mirroring the **EchoNet** backend architecture paired with a state-of-the-art **Next.js** frontend.
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- **🛠️ Interactive Setup**: Choose your package manager (npm, pnpm, yarn) and auto-install dependencies.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 🌟 Key Features
|
|
12
|
+
|
|
13
|
+
### 🛡️ Backend Excellence (EchoNet Style)
|
|
14
|
+
- **Modular Architecture**: Clean, scalable `src/app/module` pattern.
|
|
15
|
+
- **Prisma 7+**: Next-gen database ORM with pre-configured PostgreSQL adapters.
|
|
16
|
+
- **Better Auth Integration**: Pre-built authentication schemas (User, Session, Account).
|
|
17
|
+
- **Security First**: Integrated Helmet, CORS, Rate Limiting, and XSS Sanitization.
|
|
18
|
+
- **Robust Error Handling**: Centralized global error handler and structured `ApiError` class.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
### 🌐 Modern Frontend (Next.js)
|
|
21
|
+
- **App Router**: Leveraging the latest Next.js 14/15 patterns.
|
|
22
|
+
- **TypeScript & Tailwind**: Pre-configured for visual excellence and type safety.
|
|
23
|
+
- **Clean Structure**: Dedicated directories for hooks, services, lib, and types.
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
### 🛠️ Developer Experience (CLI)
|
|
26
|
+
- **One-Command Scaffolding**: Setup your entire stack in seconds.
|
|
27
|
+
- **Smart Generation**: Scaffold modules (Controller, Service, Route) with zero boilerplate.
|
|
28
|
+
- **Built-in Utilities**: Quick access to Prisma and build commands.
|
|
29
|
+
|
|
30
|
+
---
|
|
22
31
|
|
|
32
|
+
## 🚀 Commands Guide
|
|
33
|
+
|
|
34
|
+
### 1. Initialize a New Project
|
|
35
|
+
Create your full-stack dream project interactively:
|
|
23
36
|
```bash
|
|
24
|
-
npx @shakil-dev/shakil-stack my-awesome-
|
|
37
|
+
npx @shakil-dev/shakil-stack init my-awesome-app
|
|
25
38
|
```
|
|
26
39
|
|
|
27
|
-
###
|
|
40
|
+
### 2. Scaffold a New Module (g)
|
|
41
|
+
Generate a complete module with all layers instantly:
|
|
42
|
+
```bash
|
|
43
|
+
# Must be run in the project root
|
|
44
|
+
shakil-stack g module Product
|
|
45
|
+
```
|
|
46
|
+
**Generated Files:**
|
|
47
|
+
- `product.controller.ts`: Request/Response handling.
|
|
48
|
+
- `product.service.ts`: Business logic & Database interaction.
|
|
49
|
+
- `product.route.ts`: API endpoint definitions.
|
|
50
|
+
- `product.interface.ts`: Data types and contracts.
|
|
51
|
+
- `product.validation.ts`: Zod schema validation.
|
|
52
|
+
- `product.constant.ts`: Reusable constants.
|
|
53
|
+
|
|
54
|
+
### 3. Production Build
|
|
55
|
+
Prepare your application for the real world:
|
|
56
|
+
```bash
|
|
57
|
+
shakil-stack build
|
|
58
|
+
```
|
|
28
59
|
|
|
60
|
+
### 4. Prisma Power Tools
|
|
61
|
+
Manage your database without leaving the CLI:
|
|
29
62
|
```bash
|
|
30
|
-
shakil-stack
|
|
63
|
+
shakil-stack prisma generate # Update Prisma Client
|
|
64
|
+
shakil-stack prisma migrate # Apply migrations to DB
|
|
31
65
|
```
|
|
32
66
|
|
|
67
|
+
---
|
|
68
|
+
|
|
33
69
|
## 📂 Project Structure
|
|
34
70
|
|
|
35
71
|
```text
|
|
36
|
-
my-awesome-
|
|
72
|
+
my-awesome-app/
|
|
37
73
|
├── backend/
|
|
38
|
-
│ ├── prisma/
|
|
39
|
-
│ │ └── schema.prisma (Better Auth & Prisma 7 ready)
|
|
74
|
+
│ ├── prisma/ # Schema & Config
|
|
40
75
|
│ ├── src/
|
|
41
|
-
│ │ ├── server.ts
|
|
42
|
-
│ │ └── app/
|
|
43
|
-
│
|
|
44
|
-
├──
|
|
76
|
+
│ │ ├── server.ts # Entry point
|
|
77
|
+
│ │ └── app/ # Core logic
|
|
78
|
+
│ │ ├── module/ # Feature-based modules
|
|
79
|
+
│ │ ├── middleware/ # Security & Global handlers
|
|
80
|
+
│ │ └── utils/ # Reusable helpers
|
|
81
|
+
│ └── tsconfig.json
|
|
82
|
+
├── frontend/ (Next.js)
|
|
45
83
|
│ ├── src/
|
|
46
|
-
│ │ ├── app/
|
|
47
|
-
│ │ ├── components/
|
|
48
|
-
│ │ └──
|
|
84
|
+
│ │ ├── app/ # Routes & Pages
|
|
85
|
+
│ │ ├── components/ # UI Components
|
|
86
|
+
│ │ └── services/ # API integration
|
|
49
87
|
└── README.md
|
|
50
88
|
```
|
|
51
89
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
After the generator finishes, follow these steps to start your development:
|
|
55
|
-
|
|
56
|
-
1. **Configure Database**: Update the `DATABASE_URL` in `backend/.env`.
|
|
57
|
-
2. **Start Backend**:
|
|
58
|
-
```bash
|
|
59
|
-
cd backend
|
|
60
|
-
pnpm dev # or npm run dev
|
|
61
|
-
```
|
|
62
|
-
3. **Start Frontend**:
|
|
63
|
-
```bash
|
|
64
|
-
cd frontend
|
|
65
|
-
pnpm dev # or npm run dev
|
|
66
|
-
```
|
|
90
|
+
---
|
|
67
91
|
|
|
68
|
-
##
|
|
92
|
+
## 🛠️ Post-Setup Checklist
|
|
93
|
+
1. **Environment**: Update `backend/.env` with your `DATABASE_URL`.
|
|
94
|
+
2. **Database**: Run `shakil-stack prisma migrate` to setup your tables.
|
|
95
|
+
3. **Launch**: Run `npm dev` in both folders.
|
|
69
96
|
|
|
70
|
-
|
|
71
|
-
- `pnpm build`: Builds the project using `tsup`.
|
|
72
|
-
- `pnpm start`: Runs the built project from `dist/`.
|
|
97
|
+
---
|
|
73
98
|
|
|
74
99
|
## 🤝 Contributing
|
|
100
|
+
Contributions make the open-source community an amazing place to learn, inspire, and create.
|
|
101
|
+
1. Fork the Project.
|
|
102
|
+
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`).
|
|
103
|
+
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`).
|
|
104
|
+
4. Push to the Branch (`git push origin feature/AmazingFeature`).
|
|
105
|
+
5. Open a Pull Request.
|
|
106
|
+
|
|
107
|
+
Please read our [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) before contributing.
|
|
75
108
|
|
|
76
|
-
|
|
109
|
+
## 📄 License
|
|
110
|
+
Distributed under the MIT License. See `LICENSE` for more information.
|
|
77
111
|
|
|
78
112
|
---
|
|
79
|
-
|
|
113
|
+
Built with ⚡ by **Shakil Ahmed Billal**
|
package/bin/index.js
CHANGED
|
@@ -3,11 +3,30 @@
|
|
|
3
3
|
const fs = require('fs-extra');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { execSync } = require('child_process');
|
|
6
|
+
const { Command } = require('commander');
|
|
6
7
|
const inquirer = require('inquirer');
|
|
7
8
|
const chalk = require('chalk');
|
|
8
9
|
const ora = require('ora');
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
const program = new Command();
|
|
12
|
+
|
|
13
|
+
// --- Utils ---
|
|
14
|
+
const getPackageManager = () => {
|
|
15
|
+
if (fs.existsSync('pnpm-lock.yaml')) return 'pnpm';
|
|
16
|
+
if (fs.existsSync('yarn.lock')) return 'yarn';
|
|
17
|
+
return 'npm';
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const runCommand = (command, cwd = process.cwd()) => {
|
|
21
|
+
try {
|
|
22
|
+
execSync(command, { stdio: 'inherit', cwd });
|
|
23
|
+
} catch (err) {
|
|
24
|
+
// console.error(chalk.red(`Failed to execute: ${command}`));
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// --- Command: Init ---
|
|
29
|
+
const initProject = async (name) => {
|
|
11
30
|
console.log(chalk.cyan('\n🚀 Initializing Shakil-Stack Project Generator...\n'));
|
|
12
31
|
|
|
13
32
|
const answers = await inquirer.prompt([
|
|
@@ -15,7 +34,7 @@ async function main() {
|
|
|
15
34
|
type: 'input',
|
|
16
35
|
name: 'projectName',
|
|
17
36
|
message: 'What is your project name?',
|
|
18
|
-
default: 'my-new-project',
|
|
37
|
+
default: name || 'my-new-project',
|
|
19
38
|
validate: (input) => (input ? true : 'Project name is required'),
|
|
20
39
|
},
|
|
21
40
|
{
|
|
@@ -42,12 +61,10 @@ async function main() {
|
|
|
42
61
|
}
|
|
43
62
|
|
|
44
63
|
try {
|
|
45
|
-
// 1. Create Root Directory
|
|
46
64
|
await fs.ensureDir(projectPath);
|
|
47
|
-
|
|
48
65
|
const spinner = ora(`🚀 Creating project: ${chalk.cyan(projectName)}...`).start();
|
|
49
66
|
|
|
50
|
-
//
|
|
67
|
+
// Backend Folder Structure
|
|
51
68
|
const backendDirs = [
|
|
52
69
|
'prisma',
|
|
53
70
|
'src/app/config',
|
|
@@ -65,11 +82,9 @@ async function main() {
|
|
|
65
82
|
await fs.ensureDir(path.join(projectPath, 'backend', dir));
|
|
66
83
|
}
|
|
67
84
|
|
|
68
|
-
//
|
|
85
|
+
// Frontend (create-next-app)
|
|
69
86
|
spinner.text = `📦 Running create-next-app for frontend...`;
|
|
70
87
|
spinner.stop();
|
|
71
|
-
|
|
72
|
-
// We run create-next-app in the project root to create the 'frontend' folder
|
|
73
88
|
const nextAppCmd = `npx create-next-app@latest frontend --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --use-${packageManager} --no-git`;
|
|
74
89
|
try {
|
|
75
90
|
execSync(nextAppCmd, { cwd: projectPath, stdio: 'inherit' });
|
|
@@ -78,7 +93,7 @@ async function main() {
|
|
|
78
93
|
await fs.ensureDir(path.join(projectPath, 'frontend'));
|
|
79
94
|
}
|
|
80
95
|
|
|
81
|
-
//
|
|
96
|
+
// Frontend extra folders
|
|
82
97
|
const frontendExtraFolders = ['config', 'hooks', 'lib', 'services', 'types'];
|
|
83
98
|
for (const folder of frontendExtraFolders) {
|
|
84
99
|
await fs.ensureDir(path.join(projectPath, 'frontend', 'src', folder));
|
|
@@ -86,13 +101,12 @@ async function main() {
|
|
|
86
101
|
|
|
87
102
|
spinner.start(`📂 Finalizing root files and backend code...`);
|
|
88
103
|
|
|
89
|
-
//
|
|
104
|
+
// Root Files
|
|
90
105
|
await fs.outputFile(path.join(projectPath, '.env'), 'DATABASE_URL="postgresql://user:password@localhost:5432/mydb"\nPORT=8000\nNODE_ENV=development\nJWT_SECRET="your-secret-key"');
|
|
91
106
|
await fs.outputFile(path.join(projectPath, '.gitignore'), 'node_modules\n.env\ndist\n*.db\n.next\n.DS_Store');
|
|
92
107
|
await fs.outputFile(path.join(projectPath, 'README.md'), `# ${projectName}\n\nGenerated with Full Shakil-Stack CLI.`);
|
|
93
108
|
|
|
94
|
-
//
|
|
95
|
-
|
|
109
|
+
// Backend Files Templates
|
|
96
110
|
const serverTs = `import { Server } from 'http';
|
|
97
111
|
import app from './app.js';
|
|
98
112
|
import config from './app/config/index.js';
|
|
@@ -379,7 +393,33 @@ export default defineConfig({
|
|
|
379
393
|
}
|
|
380
394
|
`;
|
|
381
395
|
|
|
382
|
-
|
|
396
|
+
const sendResponseTs = `import { Response } from 'express';
|
|
397
|
+
|
|
398
|
+
type IResponse<T> = {
|
|
399
|
+
statusCode: number;
|
|
400
|
+
success: boolean;
|
|
401
|
+
message?: string | null;
|
|
402
|
+
meta?: {
|
|
403
|
+
limit: number;
|
|
404
|
+
page: number;
|
|
405
|
+
total: number;
|
|
406
|
+
};
|
|
407
|
+
data: T;
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const sendResponse = <T>(res: Response, data: IResponse<T>) => {
|
|
411
|
+
res.status(data.statusCode).json({
|
|
412
|
+
success: data.success,
|
|
413
|
+
message: data.message || null,
|
|
414
|
+
meta: data.meta || null,
|
|
415
|
+
data: data.data || null,
|
|
416
|
+
});
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
export default sendResponse;
|
|
420
|
+
`;
|
|
421
|
+
|
|
422
|
+
// Writing Backend Files
|
|
383
423
|
await fs.outputFile(path.join(projectPath, 'backend', 'src', 'server.ts'), serverTs);
|
|
384
424
|
await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app.ts'), appTs);
|
|
385
425
|
await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'config', 'index.ts'), configTs);
|
|
@@ -390,6 +430,7 @@ export default defineConfig({
|
|
|
390
430
|
await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'middleware', 'notFound.ts'), notFoundTs);
|
|
391
431
|
await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'middleware', 'sanitizeRequest.ts'), sanitizeRequestTs);
|
|
392
432
|
await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'utils', 'catchAsync.ts'), catchAsyncTs);
|
|
433
|
+
await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'utils', 'sendResponse.ts'), sendResponseTs);
|
|
393
434
|
await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'utils', 'sanitizer.ts'), sanitizerTs);
|
|
394
435
|
await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'errorHelpers', 'ApiError.ts'), apiErrorTs);
|
|
395
436
|
await fs.outputFile(path.join(projectPath, 'backend', 'prisma', 'schema.prisma'), schemaPrisma);
|
|
@@ -398,7 +439,6 @@ export default defineConfig({
|
|
|
398
439
|
await fs.outputFile(path.join(projectPath, 'backend', '.gitignore'), 'node_modules\ndist\n.env');
|
|
399
440
|
await fs.outputFile(path.join(projectPath, 'backend', '.env'), 'DATABASE_URL="postgresql://user:password@localhost:5432/mydb"\nJWT_SECRET="your-secret-key"');
|
|
400
441
|
|
|
401
|
-
// Backend package.json
|
|
402
442
|
const backendPkg = {
|
|
403
443
|
name: `${projectName}-backend`,
|
|
404
444
|
version: '1.0.0',
|
|
@@ -446,15 +486,9 @@ export default defineConfig({
|
|
|
446
486
|
|
|
447
487
|
spinner.succeed(chalk.green(`✅ Project structure created! ✨`));
|
|
448
488
|
|
|
449
|
-
// 7. Dependency Installation
|
|
450
489
|
if (installDeps) {
|
|
451
490
|
console.log(chalk.yellow(`\n📦 Finalizing dependencies with ${packageManager}...\n`));
|
|
452
|
-
|
|
453
|
-
execSync(`cd "${path.join(projectPath, 'backend')}" && ${packageManager} install`, { stdio: 'inherit' });
|
|
454
|
-
console.log(chalk.green(`\n✅ Backend dependencies installed! ✨\n`));
|
|
455
|
-
} catch (err) {
|
|
456
|
-
console.log(chalk.red(`\n❌ Step failed. You can manually run '${packageManager} install' in the backend folder.\n`));
|
|
457
|
-
}
|
|
491
|
+
runCommand(`cd "${path.join(projectPath, 'backend')}" && ${packageManager} install`);
|
|
458
492
|
}
|
|
459
493
|
|
|
460
494
|
console.log(chalk.cyan(`To get started:`));
|
|
@@ -466,6 +500,170 @@ export default defineConfig({
|
|
|
466
500
|
console.error(error);
|
|
467
501
|
process.exit(1);
|
|
468
502
|
}
|
|
469
|
-
}
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
// --- Command: Generate Module ---
|
|
506
|
+
const generateModule = async (name) => {
|
|
507
|
+
if (!name) {
|
|
508
|
+
console.log(chalk.red('❌ Error: Module name is required.'));
|
|
509
|
+
process.exit(1);
|
|
510
|
+
}
|
|
470
511
|
|
|
471
|
-
|
|
512
|
+
const moduleName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
513
|
+
const lowercaseName = name.toLowerCase();
|
|
514
|
+
|
|
515
|
+
// Check if inside a shakil-stack project
|
|
516
|
+
const backendRoot = fs.existsSync('backend') ? 'backend' : '.';
|
|
517
|
+
const moduleDir = path.join(backendRoot, 'src', 'app', 'module', moduleName);
|
|
518
|
+
|
|
519
|
+
if (!fs.existsSync(path.join(backendRoot, 'src', 'app', 'module'))) {
|
|
520
|
+
console.log(chalk.red('❌ Error: This command must be run inside your shakil-stack project root or backend directory.'));
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (fs.existsSync(moduleDir)) {
|
|
525
|
+
console.log(chalk.red(`❌ Error: Module ${moduleName} already exists.`));
|
|
526
|
+
process.exit(1);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const spinner = ora(`🛠️ Generating module: ${chalk.cyan(moduleName)}...`).start();
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
await fs.ensureDir(moduleDir);
|
|
533
|
+
|
|
534
|
+
const files = {
|
|
535
|
+
'controller.ts': `import { Request, Response } from 'express';
|
|
536
|
+
import httpStatus from 'http-status';
|
|
537
|
+
import catchAsync from '../../utils/catchAsync.js';
|
|
538
|
+
import sendResponse from '../../utils/sendResponse.js';
|
|
539
|
+
import { ${moduleName}Service } from './${lowercaseName}.service.js';
|
|
540
|
+
|
|
541
|
+
const create${moduleName} = catchAsync(async (req: Request, res: Response) => {
|
|
542
|
+
const result = await ${moduleName}Service.create${moduleName}IntoDB(req.body);
|
|
543
|
+
sendResponse(res, {
|
|
544
|
+
statusCode: httpStatus.OK,
|
|
545
|
+
success: true,
|
|
546
|
+
message: '${moduleName} created successfully',
|
|
547
|
+
data: result,
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
export const ${moduleName}Controller = {
|
|
552
|
+
create${moduleName},
|
|
553
|
+
};
|
|
554
|
+
`,
|
|
555
|
+
'service.ts': `import { ${moduleName} } from '@prisma/client';
|
|
556
|
+
import prisma from '../../lib/prisma.js';
|
|
557
|
+
|
|
558
|
+
const create${moduleName}IntoDB = async (payload: any) => {
|
|
559
|
+
// Logic here
|
|
560
|
+
return payload;
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
export const ${moduleName}Service = {
|
|
564
|
+
create${moduleName}IntoDB,
|
|
565
|
+
};
|
|
566
|
+
`,
|
|
567
|
+
'route.ts': `import { Router } from 'express';
|
|
568
|
+
import { ${moduleName}Controller } from './${lowercaseName}.controller.js';
|
|
569
|
+
|
|
570
|
+
const router = Router();
|
|
571
|
+
|
|
572
|
+
router.post('/create-${lowercaseName}', ${moduleName}Controller.create${moduleName});
|
|
573
|
+
|
|
574
|
+
export const ${moduleName}Routes = router;
|
|
575
|
+
`,
|
|
576
|
+
'interface.ts': `export type I${moduleName} = {
|
|
577
|
+
// Define interface
|
|
578
|
+
};
|
|
579
|
+
`,
|
|
580
|
+
'validation.ts': `import { z } from 'zod';
|
|
581
|
+
|
|
582
|
+
const create${moduleName}ValidationSchema = z.object({
|
|
583
|
+
body: z.object({
|
|
584
|
+
// Define schema
|
|
585
|
+
}),
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
export const ${moduleName}Validations = {
|
|
589
|
+
create${moduleName}ValidationSchema,
|
|
590
|
+
};
|
|
591
|
+
`,
|
|
592
|
+
'constant.ts': `export const ${moduleName}SearchableFields = [];
|
|
593
|
+
`,
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
for (const [ext, content] of Object.entries(files)) {
|
|
597
|
+
await fs.outputFile(path.join(moduleDir, `${lowercaseName}.${ext}`), content);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
spinner.succeed(chalk.green(`✅ Module ${moduleName} generated successfully! ✨`));
|
|
601
|
+
console.log(chalk.gray(`Created at: ${moduleDir}`));
|
|
602
|
+
|
|
603
|
+
} catch (error) {
|
|
604
|
+
spinner.fail(chalk.red('❌ Failed to generate module.'));
|
|
605
|
+
console.error(error);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
// --- CLI Structure ---
|
|
610
|
+
program
|
|
611
|
+
.name('shakil-stack')
|
|
612
|
+
.description('Full-stack EchoNet-style project generator CLI')
|
|
613
|
+
.version('1.0.0');
|
|
614
|
+
|
|
615
|
+
program
|
|
616
|
+
.command('init')
|
|
617
|
+
.description('Initialize a new full-stack project')
|
|
618
|
+
.argument('[projectName]', 'Name of the project')
|
|
619
|
+
.action((projectName) => {
|
|
620
|
+
initProject(projectName);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
program
|
|
624
|
+
.command('generate')
|
|
625
|
+
.alias('g')
|
|
626
|
+
.description('Generate a new module')
|
|
627
|
+
.argument('<type>', 'Type of generation (module)')
|
|
628
|
+
.argument('<name>', 'Name of the module')
|
|
629
|
+
.action((type, name) => {
|
|
630
|
+
if (type === 'module') {
|
|
631
|
+
generateModule(name);
|
|
632
|
+
} else {
|
|
633
|
+
console.log(chalk.red(`❌ Error: Unknown generation type: ${type}`));
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
program
|
|
638
|
+
.command('build')
|
|
639
|
+
.description('Build the backend for production')
|
|
640
|
+
.action(() => {
|
|
641
|
+
const pm = getPackageManager();
|
|
642
|
+
const backendRoot = fs.existsSync('backend') ? 'backend' : '.';
|
|
643
|
+
console.log(chalk.cyan(`🏗️ Building backend with ${pm}...`));
|
|
644
|
+
runCommand(`${pm} run build`, backendRoot);
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
program
|
|
648
|
+
.command('prisma')
|
|
649
|
+
.description('Prisma utilities')
|
|
650
|
+
.argument('<subcommand>', 'generate | migrate')
|
|
651
|
+
.action((subcommand) => {
|
|
652
|
+
const backendRoot = fs.existsSync('backend') ? 'backend' : '.';
|
|
653
|
+
if (subcommand === 'generate') {
|
|
654
|
+
console.log(chalk.cyan('🔄 Generating Prisma client...'));
|
|
655
|
+
runCommand('npx prisma generate', backendRoot);
|
|
656
|
+
} else if (subcommand === 'migrate') {
|
|
657
|
+
console.log(chalk.cyan('🚀 Running Prisma migrations...'));
|
|
658
|
+
runCommand('npx prisma migrate dev', backendRoot);
|
|
659
|
+
} else {
|
|
660
|
+
console.log(chalk.red(`❌ Error: Unknown prisma subcommand: ${subcommand}`));
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// Handle default action (no command)
|
|
665
|
+
if (!process.argv.slice(2).length) {
|
|
666
|
+
initProject();
|
|
667
|
+
} else {
|
|
668
|
+
program.parse(process.argv);
|
|
669
|
+
}
|