create-efc-app 0.2.1 → 0.2.2
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/dist/index.js +129 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19,6 +19,9 @@ async function scaffold(opts) {
|
|
|
19
19
|
await writeGitignore(dest);
|
|
20
20
|
await writeEnvFiles(dest, opts);
|
|
21
21
|
await writeExampleRoute(dest, opts);
|
|
22
|
+
await writeAuthRoutes(dest, opts);
|
|
23
|
+
await writeAdminRoutes(dest, opts);
|
|
24
|
+
await writeUserRoutes(dest, opts);
|
|
22
25
|
if (opts.tasks) await writeExampleTask(dest, opts);
|
|
23
26
|
}
|
|
24
27
|
async function writePackageJson(dest, opts) {
|
|
@@ -33,7 +36,7 @@ async function writePackageJson(dest, opts) {
|
|
|
33
36
|
if (opts.tasks && opts.taskBackend === "bullmq") deps["bullmq"] = "^5.0.0";
|
|
34
37
|
if (opts.tasks && opts.taskBackend === "pg-boss") deps["pg-boss"] = "^10.0.0";
|
|
35
38
|
const devDeps = {
|
|
36
|
-
vitest: "^
|
|
39
|
+
vitest: "^4.1.9"
|
|
37
40
|
};
|
|
38
41
|
if (opts.language === "typescript") {
|
|
39
42
|
devDeps["typescript"] = "^5.5.0";
|
|
@@ -82,8 +85,6 @@ async function writeEfcConfig(dest, opts) {
|
|
|
82
85
|
|
|
83
86
|
// Structural config only \u2014 runtime values (PORT, DATABASE_URL, JWT_SECRET, etc.) are read from .env
|
|
84
87
|
const config: EFCConfig = {
|
|
85
|
-
apiDir: './src/api',
|
|
86
|
-
tasksDir: './src/tasks',
|
|
87
88
|
authStrategy: '${opts.authStrategy}',
|
|
88
89
|
tasks: ${tasks},
|
|
89
90
|
globalMiddlewares: [],
|
|
@@ -98,16 +99,10 @@ async function writeEntryPoint(dest, opts) {
|
|
|
98
99
|
const taskLine = opts.tasks ? ` tasks: { backend: '${opts.taskBackend ?? "bullmq"}' },
|
|
99
100
|
` : "";
|
|
100
101
|
const content = `import { ignite, gracefulShutdown } from 'express-file-cluster';
|
|
101
|
-
import { fileURLToPath } from 'url';
|
|
102
|
-
import path from 'path';
|
|
103
|
-
|
|
104
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
105
102
|
|
|
106
103
|
// PORT, DATABASE_URL, JWT_SECRET, CORS_ORIGINS are read from .env automatically
|
|
107
104
|
ignite({
|
|
108
105
|
cluster: ${opts.cluster},
|
|
109
|
-
apiDir: path.join(__dirname, 'api'),
|
|
110
|
-
tasksDir: path.join(__dirname, 'tasks'),
|
|
111
106
|
${taskLine}}).then(gracefulShutdown).catch(console.error);
|
|
112
107
|
`;
|
|
113
108
|
await fs.outputFile(path.join(dest, "src", `index.${ext}`), content);
|
|
@@ -175,6 +170,127 @@ export default defineTask(async (payload) => {
|
|
|
175
170
|
`;
|
|
176
171
|
await fs.outputFile(path.join(dest, "src", "tasks", `SendEmail.${ext}`), content);
|
|
177
172
|
}
|
|
173
|
+
async function writeAuthRoutes(dest, opts) {
|
|
174
|
+
const ext = opts.language === "typescript" ? "ts" : "js";
|
|
175
|
+
const loginContent = opts.language === "typescript" ? `import { issueToken } from 'express-file-cluster/auth';
|
|
176
|
+
import type { Request, Response } from 'express';
|
|
177
|
+
|
|
178
|
+
export const POST = async (req: Request, res: Response) => {
|
|
179
|
+
const { email, password } = req.body;
|
|
180
|
+
|
|
181
|
+
if (email === 'admin@example.com' && password === 'admin') {
|
|
182
|
+
await issueToken(res, { id: '1', role: 'admin', email });
|
|
183
|
+
return res.json({ message: 'Logged in as admin' });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (email === 'user@example.com' && password === 'user') {
|
|
187
|
+
await issueToken(res, { id: '2', role: 'user', email });
|
|
188
|
+
return res.json({ message: 'Logged in as user' });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
res.status(401).json({ error: 'Invalid credentials' });
|
|
192
|
+
};
|
|
193
|
+
` : `import { issueToken } from 'express-file-cluster/auth';
|
|
194
|
+
|
|
195
|
+
export const POST = async (req, res) => {
|
|
196
|
+
const { email, password } = req.body;
|
|
197
|
+
|
|
198
|
+
if (email === 'admin@example.com' && password === 'admin') {
|
|
199
|
+
await issueToken(res, { id: '1', role: 'admin', email });
|
|
200
|
+
return res.json({ message: 'Logged in as admin' });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (email === 'user@example.com' && password === 'user') {
|
|
204
|
+
await issueToken(res, { id: '2', role: 'user', email });
|
|
205
|
+
return res.json({ message: 'Logged in as user' });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
res.status(401).json({ error: 'Invalid credentials' });
|
|
209
|
+
};
|
|
210
|
+
`;
|
|
211
|
+
const logoutContent = opts.language === "typescript" ? `import { revokeToken } from 'express-file-cluster/auth';
|
|
212
|
+
import type { Request, Response } from 'express';
|
|
213
|
+
|
|
214
|
+
export const POST = async (_req: Request, res: Response) => {
|
|
215
|
+
revokeToken(res);
|
|
216
|
+
res.json({ message: 'Logged out successfully' });
|
|
217
|
+
};
|
|
218
|
+
` : `import { revokeToken } from 'express-file-cluster/auth';
|
|
219
|
+
|
|
220
|
+
export const POST = async (_req, res) => {
|
|
221
|
+
revokeToken(res);
|
|
222
|
+
res.json({ message: 'Logged out successfully' });
|
|
223
|
+
};
|
|
224
|
+
`;
|
|
225
|
+
await fs.outputFile(path.join(dest, "src", "api", "auth", `login.${ext}`), loginContent);
|
|
226
|
+
await fs.outputFile(path.join(dest, "src", "api", "auth", `logout.${ext}`), logoutContent);
|
|
227
|
+
}
|
|
228
|
+
async function writeAdminRoutes(dest, opts) {
|
|
229
|
+
const ext = opts.language === "typescript" ? "ts" : "js";
|
|
230
|
+
const content = opts.language === "typescript" ? `import { requireAuth } from 'express-file-cluster/auth';
|
|
231
|
+
import type { Request, Response } from 'express';
|
|
232
|
+
|
|
233
|
+
export const middlewares = [requireAuth];
|
|
234
|
+
|
|
235
|
+
export const GET = async (req: Request, res: Response) => {
|
|
236
|
+
const user = (req as any).user;
|
|
237
|
+
if (user?.role !== 'admin') {
|
|
238
|
+
return res.status(403).json({ error: 'Forbidden: Admin access required' });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
res.json({
|
|
242
|
+
message: 'Welcome to the Admin Panel',
|
|
243
|
+
stats: { users: 120, revenue: 5000 }
|
|
244
|
+
});
|
|
245
|
+
};
|
|
246
|
+
` : `import { requireAuth } from 'express-file-cluster/auth';
|
|
247
|
+
|
|
248
|
+
export const middlewares = [requireAuth];
|
|
249
|
+
|
|
250
|
+
export const GET = async (req, res) => {
|
|
251
|
+
const user = req.user;
|
|
252
|
+
if (user?.role !== 'admin') {
|
|
253
|
+
return res.status(403).json({ error: 'Forbidden: Admin access required' });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
res.json({
|
|
257
|
+
message: 'Welcome to the Admin Panel',
|
|
258
|
+
stats: { users: 120, revenue: 5000 }
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
`;
|
|
262
|
+
await fs.outputFile(path.join(dest, "src", "api", "admin", `dashboard.${ext}`), content);
|
|
263
|
+
}
|
|
264
|
+
async function writeUserRoutes(dest, opts) {
|
|
265
|
+
const ext = opts.language === "typescript" ? "ts" : "js";
|
|
266
|
+
const content = opts.language === "typescript" ? `import { requireAuth } from 'express-file-cluster/auth';
|
|
267
|
+
import type { Request, Response } from 'express';
|
|
268
|
+
|
|
269
|
+
export const middlewares = [requireAuth];
|
|
270
|
+
|
|
271
|
+
export const GET = async (req: Request, res: Response) => {
|
|
272
|
+
const user = (req as any).user;
|
|
273
|
+
|
|
274
|
+
res.json({
|
|
275
|
+
message: 'User Profile Panel',
|
|
276
|
+
user
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
` : `import { requireAuth } from 'express-file-cluster/auth';
|
|
280
|
+
|
|
281
|
+
export const middlewares = [requireAuth];
|
|
282
|
+
|
|
283
|
+
export const GET = async (req, res) => {
|
|
284
|
+
const user = req.user;
|
|
285
|
+
|
|
286
|
+
res.json({
|
|
287
|
+
message: 'User Profile Panel',
|
|
288
|
+
user
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
`;
|
|
292
|
+
await fs.outputFile(path.join(dest, "src", "api", "user", `profile.${ext}`), content);
|
|
293
|
+
}
|
|
178
294
|
|
|
179
295
|
// src/index.ts
|
|
180
296
|
async function main() {
|
|
@@ -305,7 +421,10 @@ function npmInstallGlobal() {
|
|
|
305
421
|
const child = spawn("npm", ["install", "-g", "express-file-cluster"], {
|
|
306
422
|
stdio: "ignore"
|
|
307
423
|
});
|
|
308
|
-
child.on(
|
|
424
|
+
child.on(
|
|
425
|
+
"exit",
|
|
426
|
+
(code) => code === 0 ? resolve() : reject(new Error("global install failed"))
|
|
427
|
+
);
|
|
309
428
|
});
|
|
310
429
|
}
|
|
311
430
|
main().catch((err) => {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/scaffold.ts"],"sourcesContent":["import * as p from '@clack/prompts';\nimport pc from 'picocolors';\nimport { spawn } from 'node:child_process';\nimport { scaffold, type ScaffoldOptions } from './scaffold.js';\n\nasync function main(): Promise<void> {\n console.log();\n p.intro(pc.bgCyan(pc.black(' create-efc-app ')));\n\n const projectName = await p.text({\n message: 'Project name:',\n placeholder: 'my-api',\n defaultValue: 'my-api',\n validate: (v) => (!v.trim() ? 'Project name is required' : undefined),\n });\n if (p.isCancel(projectName)) { p.cancel('Cancelled'); process.exit(0); }\n\n const language = await p.select({\n message: 'Language:',\n options: [\n { value: 'typescript', label: 'TypeScript', hint: 'recommended' },\n { value: 'javascript', label: 'JavaScript' },\n ],\n });\n if (p.isCancel(language)) { p.cancel('Cancelled'); process.exit(0); }\n\n const database = await p.select({\n message: 'Database:',\n options: [\n { value: 'mongodb', label: 'MongoDB', hint: 'Mongoose' },\n { value: 'postgresql', label: 'PostgreSQL', hint: 'Drizzle ORM' },\n ],\n });\n if (p.isCancel(database)) { p.cancel('Cancelled'); process.exit(0); }\n\n const authStrategy = await p.select({\n message: 'Authentication strategy:',\n options: [\n { value: 'http-only', label: 'http-only', hint: 'secure cookie — recommended for SSR' },\n { value: 'localStorage', label: 'localStorage', hint: 'bearer token — for SPAs' },\n ],\n });\n if (p.isCancel(authStrategy)) { p.cancel('Cancelled'); process.exit(0); }\n\n const cluster = await p.confirm({\n message: 'Enable multi-core clustering?',\n initialValue: true,\n });\n if (p.isCancel(cluster)) { p.cancel('Cancelled'); process.exit(0); }\n\n const tasks = await p.confirm({\n message: 'Enable background tasks?',\n initialValue: true,\n });\n if (p.isCancel(tasks)) { p.cancel('Cancelled'); process.exit(0); }\n\n let taskBackend: ScaffoldOptions['taskBackend'];\n if (tasks) {\n const backend = await p.select({\n message: 'Task queue backend:',\n options: [\n { value: 'bullmq', label: 'BullMQ', hint: 'Redis' },\n { value: 'pg-boss', label: 'pg-boss', hint: 'PostgreSQL' },\n ],\n });\n if (p.isCancel(backend)) { p.cancel('Cancelled'); process.exit(0); }\n taskBackend = backend as ScaffoldOptions['taskBackend'];\n }\n\n const opts: ScaffoldOptions = {\n projectName: projectName as string,\n language: language as ScaffoldOptions['language'],\n database: database as ScaffoldOptions['database'],\n authStrategy: authStrategy as ScaffoldOptions['authStrategy'],\n cluster: cluster as boolean,\n tasks: tasks as boolean,\n ...(taskBackend !== undefined && { taskBackend }),\n };\n\n const spinner = p.spinner();\n spinner.start('Scaffolding project…');\n\n try {\n await scaffold(opts);\n spinner.stop('Project created');\n } catch (err) {\n spinner.stop('Failed to scaffold project');\n console.error(err);\n process.exit(1);\n }\n\n spinner.start('Installing dependencies…');\n await npmInstall(projectName as string);\n spinner.stop('Dependencies installed');\n\n spinner.start('Installing efc CLI globally…');\n await npmInstallGlobal().catch(() => {/* non-fatal */});\n spinner.stop('efc CLI ready');\n\n p.outro(\n pc.green(`\\nYour project is ready!\\n\\n`) +\n pc.dim(` cd ${projectName as string}\\n`) +\n pc.dim(` efc start dev\\n`) +\n pc.dim(`\\n (or: npm run dev)\\n`),\n );\n}\n\nfunction npmInstall(projectDir: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn('npm', ['install'], {\n cwd: projectDir,\n stdio: 'ignore',\n });\n child.on('exit', (code) => (code === 0 ? resolve() : reject(new Error(`npm install failed`))));\n });\n}\n\nfunction npmInstallGlobal(): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn('npm', ['install', '-g', 'express-file-cluster'], {\n stdio: 'ignore',\n });\n child.on('exit', (code) => (code === 0 ? resolve() : reject(new Error('global install failed'))));\n });\n}\n\nmain().catch((err) => {\n console.error(err);\n process.exit(1);\n});\n","import fs from 'fs-extra';\nimport path from 'node:path';\nimport crypto from 'node:crypto';\n\nexport interface ScaffoldOptions {\n projectName: string;\n language: 'typescript' | 'javascript';\n database: 'mongodb' | 'postgresql';\n authStrategy: 'http-only' | 'localStorage';\n cluster: boolean;\n tasks: boolean;\n taskBackend?: 'bullmq' | 'pg-boss';\n}\n\nexport async function scaffold(opts: ScaffoldOptions): Promise<void> {\n const dest = path.resolve(process.cwd(), opts.projectName);\n await fs.ensureDir(dest);\n\n await writePackageJson(dest, opts);\n await writeTsConfig(dest, opts);\n await writeEfcConfig(dest, opts);\n await writeEntryPoint(dest, opts);\n await writeGitignore(dest);\n await writeEnvFiles(dest, opts);\n await writeExampleRoute(dest, opts);\n if (opts.tasks) await writeExampleTask(dest, opts);\n}\n\nasync function writePackageJson(dest: string, opts: ScaffoldOptions): Promise<void> {\n const deps: Record<string, string> = {\n 'express-file-cluster': '^0.2.1',\n };\n if (opts.database === 'mongodb') deps['mongoose'] = '^8.0.0';\n if (opts.database === 'postgresql') {\n deps['pg'] = '^8.0.0';\n deps['drizzle-orm'] = '^0.33.0';\n }\n if (opts.tasks && opts.taskBackend === 'bullmq') deps['bullmq'] = '^5.0.0';\n if (opts.tasks && opts.taskBackend === 'pg-boss') deps['pg-boss'] = '^10.0.0';\n\n const devDeps: Record<string, string> = {\n vitest: '^2.0.0',\n };\n if (opts.language === 'typescript') {\n devDeps['typescript'] = '^5.5.0';\n devDeps['@types/node'] = '^22.0.0';\n devDeps['@types/express'] = '^4.17.21';\n devDeps['tsup'] = '^8.2.0';\n devDeps['tsx'] = '^4.0.0';\n }\n\n const pkg = {\n name: opts.projectName,\n version: '0.1.0',\n type: 'module',\n scripts: {\n dev: 'efc start dev',\n build: 'efc build prod',\n start: 'efc start prod',\n test: 'efc run tests',\n },\n dependencies: deps,\n devDependencies: devDeps,\n };\n\n await fs.writeJson(path.join(dest, 'package.json'), pkg, { spaces: 2 });\n}\n\nasync function writeTsConfig(dest: string, opts: ScaffoldOptions): Promise<void> {\n if (opts.language !== 'typescript') return;\n const config = {\n compilerOptions: {\n target: 'ES2022',\n module: 'NodeNext',\n moduleResolution: 'NodeNext',\n strict: true,\n esModuleInterop: true,\n skipLibCheck: true,\n outDir: './dist',\n rootDir: './src',\n },\n include: ['src/**/*'],\n exclude: ['node_modules', 'dist'],\n };\n await fs.writeJson(path.join(dest, 'tsconfig.json'), config, { spaces: 2 });\n}\n\nasync function writeEfcConfig(dest: string, opts: ScaffoldOptions): Promise<void> {\n const ext = opts.language === 'typescript' ? 'ts' : 'js';\n const tasks = opts.tasks\n ? `{ backend: '${opts.taskBackend ?? 'bullmq'}', concurrency: 5 }`\n : 'false';\n\n const content = `import type { EFCConfig } from 'express-file-cluster';\n\n// Structural config only — runtime values (PORT, DATABASE_URL, JWT_SECRET, etc.) are read from .env\nconst config: EFCConfig = {\n apiDir: './src/api',\n tasksDir: './src/tasks',\n authStrategy: '${opts.authStrategy}',\n tasks: ${tasks},\n globalMiddlewares: [],\n};\n\nexport default config;\n`;\n await fs.outputFile(path.join(dest, `efc.config.${ext}`), content);\n}\n\nasync function writeEntryPoint(dest: string, opts: ScaffoldOptions): Promise<void> {\n const ext = opts.language === 'typescript' ? 'ts' : 'js';\n const taskLine = opts.tasks\n ? ` tasks: { backend: '${opts.taskBackend ?? 'bullmq'}' },\\n`\n : '';\n const content = `import { ignite, gracefulShutdown } from 'express-file-cluster';\nimport { fileURLToPath } from 'url';\nimport path from 'path';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n// PORT, DATABASE_URL, JWT_SECRET, CORS_ORIGINS are read from .env automatically\nignite({\n cluster: ${opts.cluster},\n apiDir: path.join(__dirname, 'api'),\n tasksDir: path.join(__dirname, 'tasks'),\n${taskLine}}).then(gracefulShutdown).catch(console.error);\n`;\n await fs.outputFile(path.join(dest, 'src', `index.${ext}`), content);\n}\n\nasync function writeGitignore(dest: string): Promise<void> {\n await fs.outputFile(\n path.join(dest, '.gitignore'),\n 'node_modules/\\ndist/\\n.env\\n.env.local\\n*.log\\n',\n );\n}\n\nasync function writeEnvFiles(dest: string, opts: ScaffoldOptions): Promise<void> {\n const secret = crypto.randomBytes(64).toString('hex');\n const projectName = path.basename(dest);\n const dbUrl =\n opts.database === 'mongodb'\n ? `mongodb://localhost:27017/${projectName}`\n : `postgresql://localhost:5432/${projectName}`;\n const dbExampleUrl =\n opts.database === 'mongodb'\n ? `mongodb://localhost:27017/${projectName}`\n : `postgresql://user:password@localhost:5432/${projectName}`;\n\n const dotenv = `PORT=3000\\nNODE_ENV=development\\nDATABASE_URL=${dbUrl}\\nJWT_SECRET=${secret}\\nREDIS_URL=redis://localhost:6379\\nCORS_ORIGINS=http://localhost:3000\\n`;\n const example = `PORT=3000\\nNODE_ENV=development\\nDATABASE_URL=${dbExampleUrl}\\nJWT_SECRET=<generate with: openssl rand -hex 64>\\nREDIS_URL=redis://localhost:6379\\nCORS_ORIGINS=http://localhost:3000,https://yourapp.com\\n`;\n await fs.outputFile(path.join(dest, '.env'), dotenv);\n await fs.outputFile(path.join(dest, '.env.example'), example);\n}\n\nasync function writeExampleRoute(dest: string, opts: ScaffoldOptions): Promise<void> {\n const ext = opts.language === 'typescript' ? 'ts' : 'js';\n const content =\n opts.language === 'typescript'\n ? `import type { Request, Response } from 'express';\n\nexport const GET = async (_req: Request, res: Response) => {\n res.json({ status: 'OK', timestamp: new Date().toISOString() });\n};\n`\n : `export const GET = async (_req, res) => {\n res.json({ status: 'OK', timestamp: new Date().toISOString() });\n};\n`;\n await fs.outputFile(path.join(dest, 'src', 'api', `health.${ext}`), content);\n}\n\nasync function writeExampleTask(dest: string, opts: ScaffoldOptions): Promise<void> {\n const ext = opts.language === 'typescript' ? 'ts' : 'js';\n const content =\n opts.language === 'typescript'\n ? `import { defineTask } from 'express-file-cluster/tasks';\n\ninterface SendEmailPayload {\n to: string;\n subject: string;\n body: string;\n}\n\nexport default defineTask<SendEmailPayload>(async (payload) => {\n // TODO: wire up your mailer\n console.log('[SendEmail] Sending to', payload.to);\n});\n`\n : `import { defineTask } from 'express-file-cluster/tasks';\n\nexport default defineTask(async (payload) => {\n console.log('[SendEmail] Sending to', payload.to);\n});\n`;\n await fs.outputFile(path.join(dest, 'src', 'tasks', `SendEmail.${ext}`), content);\n}\n"],"mappings":"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","names":["spinner"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/scaffold.ts"],"sourcesContent":["import * as p from '@clack/prompts';\nimport pc from 'picocolors';\nimport { spawn } from 'node:child_process';\nimport { scaffold, type ScaffoldOptions } from './scaffold.js';\n\nasync function main(): Promise<void> {\n console.log();\n p.intro(pc.bgCyan(pc.black(' create-efc-app ')));\n\n const projectName = await p.text({\n message: 'Project name:',\n placeholder: 'my-api',\n defaultValue: 'my-api',\n validate: (v) => (!v.trim() ? 'Project name is required' : undefined),\n });\n if (p.isCancel(projectName)) {\n p.cancel('Cancelled');\n process.exit(0);\n }\n\n const language = await p.select({\n message: 'Language:',\n options: [\n { value: 'typescript', label: 'TypeScript', hint: 'recommended' },\n { value: 'javascript', label: 'JavaScript' },\n ],\n });\n if (p.isCancel(language)) {\n p.cancel('Cancelled');\n process.exit(0);\n }\n\n const database = await p.select({\n message: 'Database:',\n options: [\n { value: 'mongodb', label: 'MongoDB', hint: 'Mongoose' },\n { value: 'postgresql', label: 'PostgreSQL', hint: 'Drizzle ORM' },\n ],\n });\n if (p.isCancel(database)) {\n p.cancel('Cancelled');\n process.exit(0);\n }\n\n const authStrategy = await p.select({\n message: 'Authentication strategy:',\n options: [\n { value: 'http-only', label: 'http-only', hint: 'secure cookie — recommended for SSR' },\n { value: 'localStorage', label: 'localStorage', hint: 'bearer token — for SPAs' },\n ],\n });\n if (p.isCancel(authStrategy)) {\n p.cancel('Cancelled');\n process.exit(0);\n }\n\n const cluster = await p.confirm({\n message: 'Enable multi-core clustering?',\n initialValue: true,\n });\n if (p.isCancel(cluster)) {\n p.cancel('Cancelled');\n process.exit(0);\n }\n\n const tasks = await p.confirm({\n message: 'Enable background tasks?',\n initialValue: true,\n });\n if (p.isCancel(tasks)) {\n p.cancel('Cancelled');\n process.exit(0);\n }\n\n let taskBackend: ScaffoldOptions['taskBackend'];\n if (tasks) {\n const backend = await p.select({\n message: 'Task queue backend:',\n options: [\n { value: 'bullmq', label: 'BullMQ', hint: 'Redis' },\n { value: 'pg-boss', label: 'pg-boss', hint: 'PostgreSQL' },\n ],\n });\n if (p.isCancel(backend)) {\n p.cancel('Cancelled');\n process.exit(0);\n }\n taskBackend = backend as ScaffoldOptions['taskBackend'];\n }\n\n const opts: ScaffoldOptions = {\n projectName: projectName as string,\n language: language as ScaffoldOptions['language'],\n database: database as ScaffoldOptions['database'],\n authStrategy: authStrategy as ScaffoldOptions['authStrategy'],\n cluster: cluster as boolean,\n tasks: tasks as boolean,\n ...(taskBackend !== undefined && { taskBackend }),\n };\n\n const spinner = p.spinner();\n spinner.start('Scaffolding project…');\n\n try {\n await scaffold(opts);\n spinner.stop('Project created');\n } catch (err) {\n spinner.stop('Failed to scaffold project');\n console.error(err);\n process.exit(1);\n }\n\n spinner.start('Installing dependencies…');\n await npmInstall(projectName as string);\n spinner.stop('Dependencies installed');\n\n spinner.start('Installing efc CLI globally…');\n await npmInstallGlobal().catch(() => {\n /* non-fatal */\n });\n spinner.stop('efc CLI ready');\n\n p.outro(\n pc.green(`\\nYour project is ready!\\n\\n`) +\n pc.dim(` cd ${projectName as string}\\n`) +\n pc.dim(` efc start dev\\n`) +\n pc.dim(`\\n (or: npm run dev)\\n`),\n );\n}\n\nfunction npmInstall(projectDir: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn('npm', ['install'], {\n cwd: projectDir,\n stdio: 'ignore',\n });\n child.on('exit', (code) => (code === 0 ? resolve() : reject(new Error(`npm install failed`))));\n });\n}\n\nfunction npmInstallGlobal(): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn('npm', ['install', '-g', 'express-file-cluster'], {\n stdio: 'ignore',\n });\n child.on('exit', (code) =>\n code === 0 ? resolve() : reject(new Error('global install failed')),\n );\n });\n}\n\nmain().catch((err) => {\n console.error(err);\n process.exit(1);\n});\n","import fs from 'fs-extra';\nimport path from 'node:path';\nimport crypto from 'node:crypto';\n\nexport interface ScaffoldOptions {\n projectName: string;\n language: 'typescript' | 'javascript';\n database: 'mongodb' | 'postgresql';\n authStrategy: 'http-only' | 'localStorage';\n cluster: boolean;\n tasks: boolean;\n taskBackend?: 'bullmq' | 'pg-boss';\n}\n\nexport async function scaffold(opts: ScaffoldOptions): Promise<void> {\n const dest = path.resolve(process.cwd(), opts.projectName);\n await fs.ensureDir(dest);\n\n await writePackageJson(dest, opts);\n await writeTsConfig(dest, opts);\n await writeEfcConfig(dest, opts);\n await writeEntryPoint(dest, opts);\n await writeGitignore(dest);\n await writeEnvFiles(dest, opts);\n await writeExampleRoute(dest, opts);\n await writeAuthRoutes(dest, opts);\n await writeAdminRoutes(dest, opts);\n await writeUserRoutes(dest, opts);\n if (opts.tasks) await writeExampleTask(dest, opts);\n}\n\nasync function writePackageJson(dest: string, opts: ScaffoldOptions): Promise<void> {\n const deps: Record<string, string> = {\n 'express-file-cluster': '^0.2.1',\n };\n if (opts.database === 'mongodb') deps['mongoose'] = '^8.0.0';\n if (opts.database === 'postgresql') {\n deps['pg'] = '^8.0.0';\n deps['drizzle-orm'] = '^0.33.0';\n }\n if (opts.tasks && opts.taskBackend === 'bullmq') deps['bullmq'] = '^5.0.0';\n if (opts.tasks && opts.taskBackend === 'pg-boss') deps['pg-boss'] = '^10.0.0';\n\n const devDeps: Record<string, string> = {\n vitest: '^4.1.9',\n };\n if (opts.language === 'typescript') {\n devDeps['typescript'] = '^5.5.0';\n devDeps['@types/node'] = '^22.0.0';\n devDeps['@types/express'] = '^4.17.21';\n devDeps['tsup'] = '^8.2.0';\n devDeps['tsx'] = '^4.0.0';\n }\n\n const pkg = {\n name: opts.projectName,\n version: '0.1.0',\n type: 'module',\n scripts: {\n dev: 'efc start dev',\n build: 'efc build prod',\n start: 'efc start prod',\n test: 'efc run tests',\n },\n dependencies: deps,\n devDependencies: devDeps,\n };\n\n await fs.writeJson(path.join(dest, 'package.json'), pkg, { spaces: 2 });\n}\n\nasync function writeTsConfig(dest: string, opts: ScaffoldOptions): Promise<void> {\n if (opts.language !== 'typescript') return;\n const config = {\n compilerOptions: {\n target: 'ES2022',\n module: 'NodeNext',\n moduleResolution: 'NodeNext',\n strict: true,\n esModuleInterop: true,\n skipLibCheck: true,\n outDir: './dist',\n rootDir: './src',\n },\n include: ['src/**/*'],\n exclude: ['node_modules', 'dist'],\n };\n await fs.writeJson(path.join(dest, 'tsconfig.json'), config, { spaces: 2 });\n}\n\nasync function writeEfcConfig(dest: string, opts: ScaffoldOptions): Promise<void> {\n const ext = opts.language === 'typescript' ? 'ts' : 'js';\n const tasks = opts.tasks\n ? `{ backend: '${opts.taskBackend ?? 'bullmq'}', concurrency: 5 }`\n : 'false';\n\n const content = `import type { EFCConfig } from 'express-file-cluster';\n\n// Structural config only — runtime values (PORT, DATABASE_URL, JWT_SECRET, etc.) are read from .env\nconst config: EFCConfig = {\n authStrategy: '${opts.authStrategy}',\n tasks: ${tasks},\n globalMiddlewares: [],\n};\n\nexport default config;\n`;\n await fs.outputFile(path.join(dest, `efc.config.${ext}`), content);\n}\n\nasync function writeEntryPoint(dest: string, opts: ScaffoldOptions): Promise<void> {\n const ext = opts.language === 'typescript' ? 'ts' : 'js';\n const taskLine = opts.tasks ? ` tasks: { backend: '${opts.taskBackend ?? 'bullmq'}' },\\n` : '';\n const content = `import { ignite, gracefulShutdown } from 'express-file-cluster';\n\n// PORT, DATABASE_URL, JWT_SECRET, CORS_ORIGINS are read from .env automatically\nignite({\n cluster: ${opts.cluster},\n${taskLine}}).then(gracefulShutdown).catch(console.error);\n`;\n await fs.outputFile(path.join(dest, 'src', `index.${ext}`), content);\n}\n\nasync function writeGitignore(dest: string): Promise<void> {\n await fs.outputFile(\n path.join(dest, '.gitignore'),\n 'node_modules/\\ndist/\\n.env\\n.env.local\\n*.log\\n',\n );\n}\n\nasync function writeEnvFiles(dest: string, opts: ScaffoldOptions): Promise<void> {\n const secret = crypto.randomBytes(64).toString('hex');\n const projectName = path.basename(dest);\n const dbUrl =\n opts.database === 'mongodb'\n ? `mongodb://localhost:27017/${projectName}`\n : `postgresql://localhost:5432/${projectName}`;\n const dbExampleUrl =\n opts.database === 'mongodb'\n ? `mongodb://localhost:27017/${projectName}`\n : `postgresql://user:password@localhost:5432/${projectName}`;\n\n const dotenv = `PORT=3000\\nNODE_ENV=development\\nDATABASE_URL=${dbUrl}\\nJWT_SECRET=${secret}\\nREDIS_URL=redis://localhost:6379\\nCORS_ORIGINS=http://localhost:3000\\n`;\n const example = `PORT=3000\\nNODE_ENV=development\\nDATABASE_URL=${dbExampleUrl}\\nJWT_SECRET=<generate with: openssl rand -hex 64>\\nREDIS_URL=redis://localhost:6379\\nCORS_ORIGINS=http://localhost:3000,https://yourapp.com\\n`;\n await fs.outputFile(path.join(dest, '.env'), dotenv);\n await fs.outputFile(path.join(dest, '.env.example'), example);\n}\n\nasync function writeExampleRoute(dest: string, opts: ScaffoldOptions): Promise<void> {\n const ext = opts.language === 'typescript' ? 'ts' : 'js';\n const content =\n opts.language === 'typescript'\n ? `import type { Request, Response } from 'express';\n\nexport const GET = async (_req: Request, res: Response) => {\n res.json({ status: 'OK', timestamp: new Date().toISOString() });\n};\n`\n : `export const GET = async (_req, res) => {\n res.json({ status: 'OK', timestamp: new Date().toISOString() });\n};\n`;\n await fs.outputFile(path.join(dest, 'src', 'api', `health.${ext}`), content);\n}\n\nasync function writeExampleTask(dest: string, opts: ScaffoldOptions): Promise<void> {\n const ext = opts.language === 'typescript' ? 'ts' : 'js';\n const content =\n opts.language === 'typescript'\n ? `import { defineTask } from 'express-file-cluster/tasks';\n\ninterface SendEmailPayload {\n to: string;\n subject: string;\n body: string;\n}\n\nexport default defineTask<SendEmailPayload>(async (payload) => {\n // TODO: wire up your mailer\n console.log('[SendEmail] Sending to', payload.to);\n});\n`\n : `import { defineTask } from 'express-file-cluster/tasks';\n\nexport default defineTask(async (payload) => {\n console.log('[SendEmail] Sending to', payload.to);\n});\n`;\n await fs.outputFile(path.join(dest, 'src', 'tasks', `SendEmail.${ext}`), content);\n}\n\nasync function writeAuthRoutes(dest: string, opts: ScaffoldOptions): Promise<void> {\n const ext = opts.language === 'typescript' ? 'ts' : 'js';\n const loginContent =\n opts.language === 'typescript'\n ? `import { issueToken } from 'express-file-cluster/auth';\nimport type { Request, Response } from 'express';\n\nexport const POST = async (req: Request, res: Response) => {\n const { email, password } = req.body;\n \n if (email === 'admin@example.com' && password === 'admin') {\n await issueToken(res, { id: '1', role: 'admin', email });\n return res.json({ message: 'Logged in as admin' });\n }\n \n if (email === 'user@example.com' && password === 'user') {\n await issueToken(res, { id: '2', role: 'user', email });\n return res.json({ message: 'Logged in as user' });\n }\n \n res.status(401).json({ error: 'Invalid credentials' });\n};\n`\n : `import { issueToken } from 'express-file-cluster/auth';\n\nexport const POST = async (req, res) => {\n const { email, password } = req.body;\n \n if (email === 'admin@example.com' && password === 'admin') {\n await issueToken(res, { id: '1', role: 'admin', email });\n return res.json({ message: 'Logged in as admin' });\n }\n \n if (email === 'user@example.com' && password === 'user') {\n await issueToken(res, { id: '2', role: 'user', email });\n return res.json({ message: 'Logged in as user' });\n }\n \n res.status(401).json({ error: 'Invalid credentials' });\n};\n`;\n\n const logoutContent =\n opts.language === 'typescript'\n ? `import { revokeToken } from 'express-file-cluster/auth';\nimport type { Request, Response } from 'express';\n\nexport const POST = async (_req: Request, res: Response) => {\n revokeToken(res);\n res.json({ message: 'Logged out successfully' });\n};\n`\n : `import { revokeToken } from 'express-file-cluster/auth';\n\nexport const POST = async (_req, res) => {\n revokeToken(res);\n res.json({ message: 'Logged out successfully' });\n};\n`;\n\n await fs.outputFile(path.join(dest, 'src', 'api', 'auth', `login.${ext}`), loginContent);\n await fs.outputFile(path.join(dest, 'src', 'api', 'auth', `logout.${ext}`), logoutContent);\n}\n\nasync function writeAdminRoutes(dest: string, opts: ScaffoldOptions): Promise<void> {\n const ext = opts.language === 'typescript' ? 'ts' : 'js';\n const content =\n opts.language === 'typescript'\n ? `import { requireAuth } from 'express-file-cluster/auth';\nimport type { Request, Response } from 'express';\n\nexport const middlewares = [requireAuth];\n\nexport const GET = async (req: Request, res: Response) => {\n const user = (req as any).user;\n if (user?.role !== 'admin') {\n return res.status(403).json({ error: 'Forbidden: Admin access required' });\n }\n \n res.json({\n message: 'Welcome to the Admin Panel',\n stats: { users: 120, revenue: 5000 }\n });\n};\n`\n : `import { requireAuth } from 'express-file-cluster/auth';\n\nexport const middlewares = [requireAuth];\n\nexport const GET = async (req, res) => {\n const user = req.user;\n if (user?.role !== 'admin') {\n return res.status(403).json({ error: 'Forbidden: Admin access required' });\n }\n \n res.json({\n message: 'Welcome to the Admin Panel',\n stats: { users: 120, revenue: 5000 }\n });\n};\n`;\n\n await fs.outputFile(path.join(dest, 'src', 'api', 'admin', `dashboard.${ext}`), content);\n}\n\nasync function writeUserRoutes(dest: string, opts: ScaffoldOptions): Promise<void> {\n const ext = opts.language === 'typescript' ? 'ts' : 'js';\n const content =\n opts.language === 'typescript'\n ? `import { requireAuth } from 'express-file-cluster/auth';\nimport type { Request, Response } from 'express';\n\nexport const middlewares = [requireAuth];\n\nexport const GET = async (req: Request, res: Response) => {\n const user = (req as any).user;\n \n res.json({\n message: 'User Profile Panel',\n user\n });\n};\n`\n : `import { requireAuth } from 'express-file-cluster/auth';\n\nexport const middlewares = [requireAuth];\n\nexport const GET = async (req, res) => {\n const user = req.user;\n \n res.json({\n message: 'User Profile Panel',\n user\n });\n};\n`;\n\n await fs.outputFile(path.join(dest, 'src', 'api', 'user', `profile.${ext}`), content);\n}\n"],"mappings":"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","names":["spinner"]}
|