create-matwad-app 1.0.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 +51 -0
- package/bin/cli.js +8 -0
- package/package.json +35 -0
- package/src/index.js +161 -0
- package/templates/client/jsconfig.json +7 -0
- package/templates/client/package.json +41 -0
- package/templates/client/src/App.jsx +4 -0
- package/templates/client/src/index.css +1 -0
- package/templates/client/src/main.jsx +11 -0
- package/templates/client/src/routes.jsx +9 -0
- package/templates/client/vite.config.js +23 -0
- package/templates/server/.env.local +2 -0
- package/templates/server/package.json +22 -0
- package/templates/server/src/app.js +15 -0
- package/templates/server/src/db/connection.js +7 -0
- package/templates/server/src/db/knexfile.js +67 -0
- package/templates/server/src/server.js +7 -0
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# create-matwad-app
|
|
2
|
+
|
|
3
|
+
A CLI tool to scaffold a modern full-stack web application.
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
|
|
7
|
+
- **Server**: Express.js, Knex.js, PostgreSQL (w/ Supabase support)
|
|
8
|
+
- **Client**: Vite, React, TailwindCSS v4, Shadcn/UI, React Router (Data API)
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx create-matwad-app <project-name>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Or globally:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g create-matwad-app
|
|
20
|
+
create-matwad-app my-app
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- Interactive project generation.
|
|
26
|
+
- Automated dependency installation for both server and client.
|
|
27
|
+
- Automated `shadcn/ui` initialization.
|
|
28
|
+
- Pre-configured TailwindCSS v4.
|
|
29
|
+
- Ready-to-use directory structure.
|
|
30
|
+
|
|
31
|
+
## Getting Started
|
|
32
|
+
|
|
33
|
+
1. Create your app:
|
|
34
|
+
```bash
|
|
35
|
+
npx create-matwad-app my-awesome-app
|
|
36
|
+
```
|
|
37
|
+
2. Start the development servers:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Terminal 1
|
|
41
|
+
cd my-awesome-app/server
|
|
42
|
+
npm run dev
|
|
43
|
+
|
|
44
|
+
# Terminal 2
|
|
45
|
+
cd my-awesome-app/client
|
|
46
|
+
npm run dev
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
ISC
|
package/bin/cli.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-matwad-app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI to scaffold a full-stack app with Express, Knex, Vite, React, Tailwind, and Shadcn UI.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-matwad-app": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src",
|
|
12
|
+
"templates"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"create-app",
|
|
16
|
+
"scaffold",
|
|
17
|
+
"express",
|
|
18
|
+
"react",
|
|
19
|
+
"vite",
|
|
20
|
+
"knex",
|
|
21
|
+
"shadcn"
|
|
22
|
+
],
|
|
23
|
+
"author": "xyberr",
|
|
24
|
+
"license": "ISC",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"commander": "^13.0.0",
|
|
31
|
+
"fs-extra": "^11.3.0",
|
|
32
|
+
"kolorist": "^1.8.0",
|
|
33
|
+
"prompts": "^2.4.2"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import prompts from "prompts";
|
|
5
|
+
import { red, green, bold, blue, cyan } from "kolorist";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import fse from "fs-extra";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
export async function run() {
|
|
13
|
+
const program = new Command();
|
|
14
|
+
let projectName;
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.name("create-matwad-app")
|
|
18
|
+
.arguments("[project-name]")
|
|
19
|
+
.usage("[project-name] [options]")
|
|
20
|
+
.action((name) => {
|
|
21
|
+
projectName = name;
|
|
22
|
+
})
|
|
23
|
+
.parse(process.argv);
|
|
24
|
+
|
|
25
|
+
if (!projectName) {
|
|
26
|
+
const response = await prompts({
|
|
27
|
+
type: "text",
|
|
28
|
+
name: "projectName",
|
|
29
|
+
message: "Project name:",
|
|
30
|
+
initial: "matwad-app",
|
|
31
|
+
});
|
|
32
|
+
projectName = response.projectName;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!projectName) {
|
|
36
|
+
console.log(red("Operation cancelled"));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const root = path.join(process.cwd(), projectName);
|
|
41
|
+
|
|
42
|
+
if (fs.existsSync(root)) {
|
|
43
|
+
const { overwrite } = await prompts({
|
|
44
|
+
type: "confirm",
|
|
45
|
+
name: "overwrite",
|
|
46
|
+
message: `Target directory "${projectName}" is not empty. Remove existing files and continue?`,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!overwrite) {
|
|
50
|
+
console.log(red("Operation cancelled"));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await fse.emptyDir(root);
|
|
55
|
+
} else {
|
|
56
|
+
fs.mkdirSync(root, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(`Scaffolding project in ${bold(root)}...`);
|
|
60
|
+
|
|
61
|
+
const templateDir = path.resolve(__dirname, "../templates");
|
|
62
|
+
const serverTemplate = path.join(templateDir, "server");
|
|
63
|
+
const clientTemplate = path.join(templateDir, "client");
|
|
64
|
+
|
|
65
|
+
// 1. Scaffold Server
|
|
66
|
+
const serverDir = path.join(root, "server");
|
|
67
|
+
console.log(`\n${blue(bold("Setting up Server..."))}`);
|
|
68
|
+
await fse.copy(serverTemplate, serverDir);
|
|
69
|
+
|
|
70
|
+
// Update server package.json name
|
|
71
|
+
const serverPkgPath = path.join(serverDir, "package.json");
|
|
72
|
+
const serverPkg = await fse.readJson(serverPkgPath);
|
|
73
|
+
serverPkg.name = `${projectName}-server`;
|
|
74
|
+
await fse.writeJson(serverPkgPath, serverPkg, { spaces: 2 });
|
|
75
|
+
|
|
76
|
+
console.log(cyan("Installing server dependencies..."));
|
|
77
|
+
await runCommand("npm", ["install"], { cwd: serverDir });
|
|
78
|
+
|
|
79
|
+
// 2. Scaffold Client
|
|
80
|
+
const clientDir = path.join(root, "client");
|
|
81
|
+
console.log(`\n${blue(bold("Setting up Client..."))}`);
|
|
82
|
+
|
|
83
|
+
// Run create-vite
|
|
84
|
+
// We use --yes to skip confirmation if needed, and --template react for JS
|
|
85
|
+
console.log(cyan("Running create-vite..."));
|
|
86
|
+
await runCommand(
|
|
87
|
+
"npm",
|
|
88
|
+
[
|
|
89
|
+
"create",
|
|
90
|
+
"vite@latest",
|
|
91
|
+
"client",
|
|
92
|
+
"--",
|
|
93
|
+
"--template",
|
|
94
|
+
"react",
|
|
95
|
+
"--no-interactive",
|
|
96
|
+
"--no-rolldown",
|
|
97
|
+
],
|
|
98
|
+
{ cwd: root }
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Overwrite with Client Template
|
|
102
|
+
console.log(cyan("Applying generic template overrides..."));
|
|
103
|
+
await fse.copy(clientTemplate, clientDir, { overwrite: true });
|
|
104
|
+
|
|
105
|
+
// Update client package.json name
|
|
106
|
+
const clientPkgPath = path.join(clientDir, "package.json");
|
|
107
|
+
const clientPkg = await fse.readJson(clientPkgPath);
|
|
108
|
+
clientPkg.name = `${projectName}-client`;
|
|
109
|
+
await fse.writeJson(clientPkgPath, clientPkg, { spaces: 2 });
|
|
110
|
+
|
|
111
|
+
// Cleanup default Vite files
|
|
112
|
+
const appCssPath = path.join(clientDir, "src/App.css");
|
|
113
|
+
if (fse.existsSync(appCssPath)) {
|
|
114
|
+
await fse.remove(appCssPath);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 3. Initialize Shadcn
|
|
118
|
+
console.log(`\n${blue(bold("Initializing Shadcn (Interactive)..."))}`);
|
|
119
|
+
console.log(cyan("Please follow the prompts to configure shadcn/ui:"));
|
|
120
|
+
|
|
121
|
+
console.log(cyan("Installing base client dependencies..."));
|
|
122
|
+
await runCommand("npm", ["install"], { cwd: clientDir });
|
|
123
|
+
|
|
124
|
+
// Run shadcn init
|
|
125
|
+
await runCommand("npx", ["shadcn@latest", "init"], {
|
|
126
|
+
cwd: clientDir,
|
|
127
|
+
stdio: "inherit",
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
console.log(`\n${green("Success!")} Created ${bold(projectName)} at ${root}`);
|
|
131
|
+
console.log("\nNext steps:");
|
|
132
|
+
console.log(` cd ${projectName}`);
|
|
133
|
+
console.log("\n To start the server:");
|
|
134
|
+
console.log(` cd server`);
|
|
135
|
+
console.log(` npm run dev`);
|
|
136
|
+
console.log("\n To start the client:");
|
|
137
|
+
console.log(` cd client`);
|
|
138
|
+
console.log(` npm run dev`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function runCommand(command, args, options = {}) {
|
|
142
|
+
return new Promise((resolve, reject) => {
|
|
143
|
+
const child = spawn(command, args, {
|
|
144
|
+
stdio: "inherit",
|
|
145
|
+
shell: true,
|
|
146
|
+
...options,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
child.on("close", (code) => {
|
|
150
|
+
if (code !== 0) {
|
|
151
|
+
reject(
|
|
152
|
+
new Error(
|
|
153
|
+
`Command ${command} ${args.join(" ")} failed with code ${code}`
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
resolve();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "client",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"preview": "vite preview"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@hookform/resolvers": "^5.2.2",
|
|
14
|
+
"@radix-ui/react-avatar": "^1.1.11",
|
|
15
|
+
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
16
|
+
"@radix-ui/react-label": "^2.1.8",
|
|
17
|
+
"@radix-ui/react-separator": "^1.1.8",
|
|
18
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
19
|
+
"@tailwindcss/vite": "^4.1.18",
|
|
20
|
+
"class-variance-authority": "^0.7.1",
|
|
21
|
+
"clsx": "^2.1.1",
|
|
22
|
+
"lucide-react": "^0.562.0",
|
|
23
|
+
"react": "^19.2.0",
|
|
24
|
+
"react-dom": "^19.2.0",
|
|
25
|
+
"react-router-dom": "^7.11.0",
|
|
26
|
+
"tailwind-merge": "^3.4.0",
|
|
27
|
+
"tailwindcss": "^4.1.18"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@eslint/js": "^9.39.1",
|
|
31
|
+
"@types/react": "^19.2.5",
|
|
32
|
+
"@types/react-dom": "^19.2.3",
|
|
33
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
34
|
+
"eslint": "^9.39.1",
|
|
35
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
36
|
+
"eslint-plugin-react-refresh": "^0.4.24",
|
|
37
|
+
"globals": "^16.5.0",
|
|
38
|
+
"tw-animate-css": "^1.4.0",
|
|
39
|
+
"vite": "^7.2.4"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StrictMode } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import "./index.css";
|
|
4
|
+
import { RouterProvider } from "react-router-dom";
|
|
5
|
+
import { router } from "./routes";
|
|
6
|
+
|
|
7
|
+
createRoot(document.getElementById("root")).render(
|
|
8
|
+
<StrictMode>
|
|
9
|
+
<RouterProvider router={router} />
|
|
10
|
+
</StrictMode>
|
|
11
|
+
);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import react from "@vitejs/plugin-react";
|
|
3
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
4
|
+
import path from "path";
|
|
5
|
+
|
|
6
|
+
// https://vite.dev/config/
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
plugins: [react(), tailwindcss()],
|
|
9
|
+
resolve: {
|
|
10
|
+
alias: {
|
|
11
|
+
"@": path.resolve(__dirname, "./src"),
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
server: {
|
|
15
|
+
proxy: {
|
|
16
|
+
"/api": {
|
|
17
|
+
target: "http://localhost:3000/api",
|
|
18
|
+
changeOrigin: true,
|
|
19
|
+
rewrite: (path) => path.replace(/^\/api/, ""),
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "src/server.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "nodemon -e js,env --exec node --env-file .env.local src/server.js",
|
|
9
|
+
"start": "node --env-file .env.local src/server.js"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [],
|
|
12
|
+
"author": "",
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"express": "^4.21.1",
|
|
16
|
+
"knex": "^3.1.0",
|
|
17
|
+
"pg": "^8.13.1"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"nodemon": "^3.1.7"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
|
|
3
|
+
const app = express();
|
|
4
|
+
|
|
5
|
+
app.use(express.json());
|
|
6
|
+
|
|
7
|
+
app.get("/", (req, res) => {
|
|
8
|
+
res.send("Hello from Express Server!");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
app.get("/api/health", (req, res) => {
|
|
12
|
+
res.json({ status: "ok" });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export default app;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Load environment variables
|
|
2
|
+
try {
|
|
3
|
+
process.loadEnvFile(".env.local");
|
|
4
|
+
} catch (err) {}
|
|
5
|
+
|
|
6
|
+
export const development = {
|
|
7
|
+
client: "pg",
|
|
8
|
+
connection: {
|
|
9
|
+
connectionString: process.env.SUPABASE_DB_URL,
|
|
10
|
+
// just in case
|
|
11
|
+
ssl: {
|
|
12
|
+
rejectUnauthorized: false,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
pool: {
|
|
16
|
+
min: 0,
|
|
17
|
+
max: 10,
|
|
18
|
+
},
|
|
19
|
+
migrations: {
|
|
20
|
+
tableName: "knex_migrations",
|
|
21
|
+
},
|
|
22
|
+
seeds: {
|
|
23
|
+
directory: "./seeds",
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const staging = {
|
|
28
|
+
client: "pg",
|
|
29
|
+
connection: {
|
|
30
|
+
connectionString: process.env.SUPABASE_DB_URL,
|
|
31
|
+
// just in case
|
|
32
|
+
ssl: {
|
|
33
|
+
rejectUnauthorized: false,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
pool: {
|
|
37
|
+
min: 0,
|
|
38
|
+
max: 10,
|
|
39
|
+
},
|
|
40
|
+
migrations: {
|
|
41
|
+
tableName: "knex_migrations",
|
|
42
|
+
},
|
|
43
|
+
seeds: {
|
|
44
|
+
directory: "./seeds",
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const production = {
|
|
49
|
+
client: "pg",
|
|
50
|
+
connection: {
|
|
51
|
+
connectionString: process.env.SUPABASE_DB_URL,
|
|
52
|
+
// just in case
|
|
53
|
+
ssl: {
|
|
54
|
+
rejectUnauthorized: false,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
pool: {
|
|
58
|
+
min: 0,
|
|
59
|
+
max: 10,
|
|
60
|
+
},
|
|
61
|
+
migrations: {
|
|
62
|
+
tableName: "knex_migrations",
|
|
63
|
+
},
|
|
64
|
+
seeds: {
|
|
65
|
+
directory: "./seeds",
|
|
66
|
+
},
|
|
67
|
+
};
|