create-zerra-app 1.1.0 ā 1.2.1
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/index.js +146 -11
- package/package.json +1 -1
- package/templates/js-auth/api/auth/login.js +15 -0
- package/templates/js-auth/api/auth/register.js +24 -0
- package/templates/js-auth/api/protected/_middleware.js +17 -0
- package/templates/js-auth/api/protected/secret.js +6 -0
- package/templates/js-auth/package.json +6 -0
- package/templates/js-auth/services/auth.js +11 -0
- package/templates/js-base/package.json +1 -1
package/index.js
CHANGED
|
@@ -27,36 +27,79 @@ program
|
|
|
27
27
|
],
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
|
-
type: "
|
|
31
|
-
name: "
|
|
32
|
-
message: "
|
|
33
|
-
|
|
30
|
+
type: "list",
|
|
31
|
+
name: "language",
|
|
32
|
+
message: "Choose your primary language:",
|
|
33
|
+
choices: [
|
|
34
|
+
{ name: "JavaScript", value: "js" },
|
|
35
|
+
{ name: "TypeScript", value: "ts" },
|
|
36
|
+
],
|
|
37
|
+
default: "js",
|
|
34
38
|
},
|
|
35
39
|
{
|
|
36
40
|
type: "confirm",
|
|
37
|
-
name: "
|
|
38
|
-
message: "
|
|
41
|
+
name: "includeAuth",
|
|
42
|
+
message: "Include Authentication Starter (JWT + Bcrypt)?",
|
|
39
43
|
default: true,
|
|
40
44
|
},
|
|
41
45
|
{
|
|
42
46
|
type: "checkbox",
|
|
43
47
|
name: "features",
|
|
44
|
-
message: "
|
|
48
|
+
message: "Select advanced features to enable:",
|
|
45
49
|
choices: [
|
|
46
50
|
{ name: "Beautiful Request Logging", value: "logging", checked: true },
|
|
47
51
|
{ name: "Dynamic Routing ([id].js)", value: "dynamicRouting", checked: true },
|
|
48
52
|
{ name: "File-Based Middleware (_middleware.js)", value: "middleware", checked: true },
|
|
49
53
|
{ name: "Auto-load Environment Variables (.env)", value: "dotenv", checked: true },
|
|
50
54
|
{ name: "Automatic Input Validation (Schema)", value: "validation", checked: true },
|
|
51
|
-
{ name: "Multipart File Uploads (req.files)", value: "multipart", checked: true }
|
|
55
|
+
{ name: "Multipart File Uploads (req.files)", value: "multipart", checked: true },
|
|
56
|
+
{ name: "Smart Error Handling (_error.js)", value: "errors", checked: true },
|
|
57
|
+
{ name: "Dev Dashboard (/__zerra)", value: "dashboard", checked: true }
|
|
52
58
|
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
type: "confirm",
|
|
62
|
+
name: "installDeps",
|
|
63
|
+
message: "Install dependencies automatically?",
|
|
64
|
+
default: true,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
type: "confirm",
|
|
68
|
+
name: "initGit",
|
|
69
|
+
message: "Initialize git repository?",
|
|
70
|
+
default: true,
|
|
53
71
|
}
|
|
54
72
|
]);
|
|
55
73
|
|
|
74
|
+
// 2. Project Preview Summary
|
|
75
|
+
console.log(`\nš Project Configuration:`);
|
|
76
|
+
console.log(` - Project Name: \x1b[36m${projectName}\x1b[0m`);
|
|
77
|
+
console.log(` - Database: \x1b[33m${answers.database.replace('js-', '')}\x1b[0m`);
|
|
78
|
+
console.log(` - Auth Starter: \x1b[32m${answers.includeAuth ? 'Enabled' : 'Disabled'}\x1b[0m`);
|
|
79
|
+
console.log(` - Language: \x1b[36m${answers.language.toUpperCase()}\x1b[0m`);
|
|
80
|
+
console.log(` - Features: \x1b[35m${answers.features.join(', ') || 'None'}\x1b[0m`);
|
|
81
|
+
console.log(` - Auto-Install: \x1b[32m${answers.installDeps ? 'Yes' : 'No'}\x1b[0m`);
|
|
82
|
+
console.log(` - Git Init: \x1b[32m${answers.initGit ? 'Yes' : 'No'}\x1b[0m`);
|
|
83
|
+
|
|
84
|
+
const { confirmProceed } = await inquirer.prompt([
|
|
85
|
+
{
|
|
86
|
+
type: "confirm",
|
|
87
|
+
name: "confirmProceed",
|
|
88
|
+
message: "Does this look correct?",
|
|
89
|
+
default: true,
|
|
90
|
+
}
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
if (!confirmProceed) {
|
|
94
|
+
console.log("\nā Project creation cancelled.\n");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
56
98
|
const baseTemplatePath = path.join(__dirname, "templates", "js-base");
|
|
57
99
|
const dbTemplatePath = path.join(__dirname, "templates", answers.database);
|
|
100
|
+
const authTemplatePath = path.join(__dirname, "templates", "js-auth");
|
|
58
101
|
|
|
59
|
-
console.log(`\nšļø
|
|
102
|
+
console.log(`\nšļø Building your Zerra application...`);
|
|
60
103
|
|
|
61
104
|
try {
|
|
62
105
|
// 1. Copy the Base Template (Foundation)
|
|
@@ -87,14 +130,91 @@ program
|
|
|
87
130
|
}
|
|
88
131
|
}
|
|
89
132
|
|
|
133
|
+
// 2.5 Overlay Auth Starter (if selected)
|
|
134
|
+
if (answers.includeAuth && fs.existsSync(authTemplatePath)) {
|
|
135
|
+
console.log(` š Adding Auth Starter...`);
|
|
136
|
+
await fs.copy(authTemplatePath, targetPath, {
|
|
137
|
+
overwrite: true,
|
|
138
|
+
filter: (src) => !src.endsWith("package.json"),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const authPkgPath = path.join(authTemplatePath, "package.json");
|
|
142
|
+
const targetPkgPath = path.join(targetPath, "package.json");
|
|
143
|
+
|
|
144
|
+
if (fs.existsSync(authPkgPath) && fs.existsSync(targetPkgPath)) {
|
|
145
|
+
const basePkg = await fs.readJson(targetPkgPath);
|
|
146
|
+
const authPkg = await fs.readJson(authPkgPath);
|
|
147
|
+
basePkg.dependencies = { ...(basePkg.dependencies || {}), ...(authPkg.dependencies || {}) };
|
|
148
|
+
await fs.writeJson(targetPkgPath, basePkg, { spaces: 2 });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
90
152
|
// 3. Customize project name in package.json
|
|
91
153
|
const pkgPath = path.join(targetPath, "package.json");
|
|
92
154
|
if (fs.existsSync(pkgPath)) {
|
|
93
155
|
const pkg = await fs.readJson(pkgPath);
|
|
94
156
|
pkg.name = projectName;
|
|
157
|
+
|
|
158
|
+
if (answers.language === 'ts') {
|
|
159
|
+
pkg.devDependencies = {
|
|
160
|
+
...(pkg.devDependencies || {}),
|
|
161
|
+
"typescript": "^5.0.0",
|
|
162
|
+
"@types/node": "^20.0.0",
|
|
163
|
+
"tsx": "^4.0.0"
|
|
164
|
+
};
|
|
165
|
+
pkg.scripts = {
|
|
166
|
+
...(pkg.scripts || {}),
|
|
167
|
+
"dev": "tsx watch server.js",
|
|
168
|
+
"build": "tsc",
|
|
169
|
+
"start": "node server.js"
|
|
170
|
+
};
|
|
171
|
+
}
|
|
95
172
|
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
96
173
|
}
|
|
97
174
|
|
|
175
|
+
// 3.5 Handle TypeScript File Renaming and tsconfig
|
|
176
|
+
if (answers.language === 'ts') {
|
|
177
|
+
console.log(` TypeScript-ifying your project...`);
|
|
178
|
+
|
|
179
|
+
// Generate tsconfig.json
|
|
180
|
+
const tsconfig = {
|
|
181
|
+
compilerOptions: {
|
|
182
|
+
target: "ESNext",
|
|
183
|
+
module: "CommonJS",
|
|
184
|
+
moduleResolution: "node",
|
|
185
|
+
esModuleInterop: true,
|
|
186
|
+
forceConsistentCasingInFileNames: true,
|
|
187
|
+
strict: true,
|
|
188
|
+
skipLibCheck: true,
|
|
189
|
+
outDir: "./dist"
|
|
190
|
+
},
|
|
191
|
+
include: ["api/**/*", "services/**/*", "server.js", "zerra.config.json"]
|
|
192
|
+
};
|
|
193
|
+
await fs.writeJson(path.join(targetPath, "tsconfig.json"), tsconfig, { spaces: 2 });
|
|
194
|
+
|
|
195
|
+
// Recursive function to rename .js to .ts
|
|
196
|
+
const renameJsToTs = async (dir) => {
|
|
197
|
+
const files = await fs.readdir(dir);
|
|
198
|
+
for (const file of files) {
|
|
199
|
+
const fullPath = path.join(dir, file);
|
|
200
|
+
const stat = await fs.stat(fullPath);
|
|
201
|
+
if (stat.isDirectory()) {
|
|
202
|
+
await renameJsToTs(fullPath);
|
|
203
|
+
} else if (file.endsWith(".js") && !file.startsWith("server.js")) {
|
|
204
|
+
const newPath = fullPath.replace(/\.js$/, ".ts");
|
|
205
|
+
await fs.move(fullPath, newPath);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
if (fs.existsSync(path.join(targetPath, "api"))) {
|
|
211
|
+
await renameJsToTs(path.join(targetPath, "api"));
|
|
212
|
+
}
|
|
213
|
+
if (fs.existsSync(path.join(targetPath, "services"))) {
|
|
214
|
+
await renameJsToTs(path.join(targetPath, "services"));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
98
218
|
// 4. Generate zerra.config.json based on feature selection
|
|
99
219
|
const featureConfig = {
|
|
100
220
|
logging: answers.features.includes('logging'),
|
|
@@ -102,11 +222,26 @@ program
|
|
|
102
222
|
middleware: answers.features.includes('middleware'),
|
|
103
223
|
dotenv: answers.features.includes('dotenv'),
|
|
104
224
|
validation: answers.features.includes('validation'),
|
|
105
|
-
multipart: answers.features.includes('multipart')
|
|
225
|
+
multipart: answers.features.includes('multipart'),
|
|
226
|
+
errors: answers.features.includes('errors'),
|
|
227
|
+
dashboard: answers.features.includes('dashboard')
|
|
106
228
|
};
|
|
107
229
|
|
|
108
230
|
const configJsonPath = path.join(targetPath, 'zerra.config.json');
|
|
109
|
-
await fs.writeJson(configJsonPath, { features: featureConfig }, { spaces: 2 });
|
|
231
|
+
await fs.writeJson(configJsonPath, { features: featureConfig, plugins: [] }, { spaces: 2 });
|
|
232
|
+
|
|
233
|
+
// 4.5 Generate .gitignore for the new project
|
|
234
|
+
const gitignoreContent = `node_modules/
|
|
235
|
+
dist/
|
|
236
|
+
build/
|
|
237
|
+
.env
|
|
238
|
+
.env.local
|
|
239
|
+
.env.*.local
|
|
240
|
+
*.log
|
|
241
|
+
.DS_Store
|
|
242
|
+
${answers.language === 'ts' ? '*.tsbuildinfo' : ''}
|
|
243
|
+
`;
|
|
244
|
+
await fs.writeFile(path.join(targetPath, '.gitignore'), gitignoreContent);
|
|
110
245
|
|
|
111
246
|
const { execSync } = require("child_process");
|
|
112
247
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const auth = require('../../services/auth');
|
|
2
|
+
|
|
3
|
+
module.exports = async (req, res) => {
|
|
4
|
+
const { email, password } = req.body;
|
|
5
|
+
|
|
6
|
+
// Mock user for the starter
|
|
7
|
+
const mockUser = { id: 1, email: 'user@example.com', password: auth.hashPassword('password123') };
|
|
8
|
+
|
|
9
|
+
if (email === mockUser.email && auth.comparePassword(password, mockUser.password)) {
|
|
10
|
+
const token = auth.generateToken(mockUser);
|
|
11
|
+
return res.json({ token, message: "Login successful" });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
res.status(401).json({ error: "Invalid credentials" });
|
|
15
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const auth = require('../../services/auth');
|
|
2
|
+
|
|
3
|
+
// Note: This is a starter. In a real app, you would save the user to your database.
|
|
4
|
+
module.exports = async (req, res) => {
|
|
5
|
+
const { email, password } = req.body;
|
|
6
|
+
|
|
7
|
+
if (!email || !password) {
|
|
8
|
+
return res.status(400).json({ error: "Email and password are required" });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const hashedPassword = auth.hashPassword(password);
|
|
12
|
+
|
|
13
|
+
// Here you would: await db.users.create({ email, password: hashedPassword })
|
|
14
|
+
|
|
15
|
+
res.status(201).json({
|
|
16
|
+
message: "User registered successfully (Starter Mock)",
|
|
17
|
+
user: { email }
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
module.exports.schema = {
|
|
22
|
+
email: 'string',
|
|
23
|
+
password: 'string'
|
|
24
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const auth = require('../../services/auth');
|
|
2
|
+
|
|
3
|
+
module.exports = async (req, res, next) => {
|
|
4
|
+
const token = req.headers.authorization?.split(' ')[1];
|
|
5
|
+
|
|
6
|
+
if (!token) {
|
|
7
|
+
return res.status(401).json({ error: "No token provided" });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const decoded = auth.verifyToken(token);
|
|
12
|
+
req.user = decoded;
|
|
13
|
+
await next();
|
|
14
|
+
} catch (e) {
|
|
15
|
+
res.status(401).json({ error: "Invalid or expired token" });
|
|
16
|
+
}
|
|
17
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const jwt = require('jsonwebtoken');
|
|
2
|
+
const bcrypt = require('bcryptjs');
|
|
3
|
+
|
|
4
|
+
const JWT_SECRET = process.env.JWT_SECRET || 'zerra-secret-key';
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
hashPassword: (password) => bcrypt.hashSync(password, 10),
|
|
8
|
+
comparePassword: (password, hash) => bcrypt.compareSync(password, hash),
|
|
9
|
+
generateToken: (user) => jwt.sign({ id: user.id, email: user.email }, JWT_SECRET, { expiresIn: '1d' }),
|
|
10
|
+
verifyToken: (token) => jwt.verify(token, JWT_SECRET)
|
|
11
|
+
};
|