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 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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { run } from "../src/index.js";
4
+
5
+ run().catch((err) => {
6
+ console.error(err);
7
+ process.exit(1);
8
+ });
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,7 @@
1
+ {
2
+ "compilerOptions": {
3
+ "paths": {
4
+ "@/*": ["./src/*"]
5
+ }
6
+ }
7
+ }
@@ -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,4 @@
1
+ // This file is overwritten by create-matwad-app to be independent of the default Vite template
2
+ export default function App() {
3
+ return null;
4
+ }
@@ -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,9 @@
1
+ import { createBrowserRouter } from "react-router-dom";
2
+ // import App from "./App"; // Optional, or use a layout
3
+
4
+ export const router = createBrowserRouter([
5
+ {
6
+ path: "/",
7
+ element: <div>Hello World</div>,
8
+ },
9
+ ]);
@@ -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,2 @@
1
+ SUPABASE_DB_URL=your_supabase_connection_string_here
2
+ PORT=3000
@@ -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,7 @@
1
+ import knex from "knex";
2
+ import * as config from "./knexfile.js";
3
+
4
+ const environment = process.env.NODE_ENV || "development";
5
+ const connection = knex(config[environment]);
6
+
7
+ export default connection;
@@ -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
+ };
@@ -0,0 +1,7 @@
1
+ import app from "./app.js";
2
+
3
+ const PORT = process.env.PORT || 3000;
4
+
5
+ app.listen(PORT, () => {
6
+ console.log(`Server running on http://localhost:${PORT}`);
7
+ });