create-efc-app 0.1.1 → 0.1.3

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 CHANGED
@@ -17,13 +17,13 @@ async function scaffold(opts) {
17
17
  await writeEfcConfig(dest, opts);
18
18
  await writeEntryPoint(dest, opts);
19
19
  await writeGitignore(dest);
20
- await writeEnvFiles(dest);
20
+ await writeEnvFiles(dest, opts);
21
21
  await writeExampleRoute(dest, opts);
22
22
  if (opts.tasks) await writeExampleTask(dest, opts);
23
23
  }
24
24
  async function writePackageJson(dest, opts) {
25
25
  const deps = {
26
- "express-file-cluster": "^0.1.1"
26
+ "express-file-cluster": "^0.1.3"
27
27
  };
28
28
  if (opts.database === "mongodb") deps["mongoose"] = "^8.0.0";
29
29
  if (opts.database === "postgresql") {
@@ -77,22 +77,14 @@ async function writeTsConfig(dest, opts) {
77
77
  }
78
78
  async function writeEfcConfig(dest, opts) {
79
79
  const ext = opts.language === "typescript" ? "ts" : "js";
80
- const tasks = opts.tasks ? `{
81
- backend: '${opts.taskBackend ?? "bullmq"}',
82
- redisUrl: process.env.REDIS_URL,
83
- concurrency: 5,
84
- }` : "false";
80
+ const tasks = opts.tasks ? `{ backend: '${opts.taskBackend ?? "bullmq"}', concurrency: 5 }` : "false";
85
81
  const content = `import type { EFCConfig } from 'express-file-cluster';
86
82
 
83
+ // Structural config only \u2014 runtime values (PORT, DATABASE_URL, JWT_SECRET, etc.) are read from .env
87
84
  const config: EFCConfig = {
88
- port: Number(process.env.PORT) || 3000,
89
85
  apiDir: './src/api',
90
86
  tasksDir: './src/tasks',
91
- database: '${opts.database}',
92
- databaseUrl: process.env.DATABASE_URL!,
93
87
  authStrategy: '${opts.authStrategy}',
94
- jwtSecret: process.env.JWT_SECRET!,
95
- cluster: process.env.NODE_ENV === 'production',
96
88
  tasks: ${tasks},
97
89
  globalMiddlewares: [],
98
90
  };
@@ -103,22 +95,19 @@ export default config;
103
95
  }
104
96
  async function writeEntryPoint(dest, opts) {
105
97
  const ext = opts.language === "typescript" ? "ts" : "js";
98
+ const taskLine = opts.tasks ? ` tasks: { backend: '${opts.taskBackend ?? "bullmq"}' },
99
+ ` : "";
106
100
  const content = `import { ignite } from 'express-file-cluster';
107
101
  import { fileURLToPath } from 'url';
108
102
  import path from 'path';
109
103
 
110
104
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
111
105
 
106
+ // PORT, DATABASE_URL, JWT_SECRET, CORS_ORIGINS are read from .env automatically
112
107
  ignite({
113
- port: Number(process.env.PORT) || 3000,
114
108
  apiDir: path.join(__dirname, 'api'),
115
109
  tasksDir: path.join(__dirname, 'tasks'),
116
- database: '${opts.database}',
117
- databaseUrl: process.env.DATABASE_URL,
118
- authStrategy: '${opts.authStrategy}',
119
- jwtSecret: process.env.JWT_SECRET,
120
- cluster: process.env.NODE_ENV === 'production',
121
- }).catch(console.error);
110
+ ${taskLine}}).catch(console.error);
122
111
  `;
123
112
  await fs.outputFile(path.join(dest, "src", `index.${ext}`), content);
124
113
  }
@@ -128,18 +117,21 @@ async function writeGitignore(dest) {
128
117
  "node_modules/\ndist/\n.env\n.env.local\n*.log\n"
129
118
  );
130
119
  }
131
- async function writeEnvFiles(dest) {
120
+ async function writeEnvFiles(dest, opts) {
132
121
  const secret = crypto.randomBytes(64).toString("hex");
122
+ const projectName = path.basename(dest);
123
+ const dbUrl = opts.database === "mongodb" ? `mongodb://localhost:27017/${projectName}` : `postgresql://localhost:5432/${projectName}`;
124
+ const dbExampleUrl = opts.database === "mongodb" ? `mongodb://localhost:27017/${projectName}` : `postgresql://user:password@localhost:5432/${projectName}`;
133
125
  const dotenv = `PORT=3000
134
126
  NODE_ENV=development
135
- DATABASE_URL=
127
+ DATABASE_URL=${dbUrl}
136
128
  JWT_SECRET=${secret}
137
129
  REDIS_URL=redis://localhost:6379
138
130
  CORS_ORIGINS=http://localhost:3000
139
131
  `;
140
132
  const example = `PORT=3000
141
133
  NODE_ENV=development
142
- DATABASE_URL=
134
+ DATABASE_URL=${dbExampleUrl}
143
135
  JWT_SECRET=<generate with: openssl rand -hex 64>
144
136
  REDIS_URL=redis://localhost:6379
145
137
  CORS_ORIGINS=http://localhost:3000,https://yourapp.com
@@ -283,12 +275,18 @@ async function main() {
283
275
  spinner2.start("Installing dependencies\u2026");
284
276
  await npmInstall(projectName);
285
277
  spinner2.stop("Dependencies installed");
278
+ spinner2.start("Installing efc CLI globally\u2026");
279
+ await npmInstallGlobal().catch(() => {
280
+ });
281
+ spinner2.stop("efc CLI ready");
286
282
  p.outro(
287
283
  chalk.green(`
288
284
  Your project is ready!
289
285
 
290
286
  `) + chalk.dim(` cd ${projectName}
291
287
  `) + chalk.dim(` efc start dev
288
+ `) + chalk.dim(`
289
+ (or: npm run dev)
292
290
  `)
293
291
  );
294
292
  }
@@ -301,6 +299,14 @@ function npmInstall(projectDir) {
301
299
  child.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`npm install failed`)));
302
300
  });
303
301
  }
302
+ function npmInstallGlobal() {
303
+ return new Promise((resolve, reject) => {
304
+ const child = spawn("npm", ["install", "-g", "express-file-cluster"], {
305
+ stdio: "ignore"
306
+ });
307
+ child.on("exit", (code) => code === 0 ? resolve() : reject(new Error("global install failed")));
308
+ });
309
+ }
304
310
  main().catch((err) => {
305
311
  console.error(err);
306
312
  process.exit(1);
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 chalk from 'chalk';\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(chalk.bgCyan(chalk.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 p.outro(\n chalk.green(`\\nYour project is ready!\\n\\n`) +\n chalk.dim(` cd ${projectName as string}\\n`) +\n chalk.dim(` efc start 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\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);\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.1.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 ? `{\n backend: '${opts.taskBackend ?? 'bullmq'}',\n redisUrl: process.env.REDIS_URL,\n concurrency: 5,\n }`\n : 'false';\n\n const content = `import type { EFCConfig } from 'express-file-cluster';\n\nconst config: EFCConfig = {\n port: Number(process.env.PORT) || 3000,\n apiDir: './src/api',\n tasksDir: './src/tasks',\n database: '${opts.database}',\n databaseUrl: process.env.DATABASE_URL!,\n authStrategy: '${opts.authStrategy}',\n jwtSecret: process.env.JWT_SECRET!,\n cluster: process.env.NODE_ENV === 'production',\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 content = `import { ignite } from 'express-file-cluster';\nimport { fileURLToPath } from 'url';\nimport path from 'path';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nignite({\n port: Number(process.env.PORT) || 3000,\n apiDir: path.join(__dirname, 'api'),\n tasksDir: path.join(__dirname, 'tasks'),\n database: '${opts.database}',\n databaseUrl: process.env.DATABASE_URL,\n authStrategy: '${opts.authStrategy}',\n jwtSecret: process.env.JWT_SECRET,\n cluster: process.env.NODE_ENV === 'production',\n}).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): Promise<void> {\n const secret = crypto.randomBytes(64).toString('hex');\n const dotenv = `PORT=3000\\nNODE_ENV=development\\nDATABASE_URL=\\nJWT_SECRET=${secret}\\nREDIS_URL=redis://localhost:6379\\nCORS_ORIGINS=http://localhost:3000\\n`;\n const example = `PORT=3000\\nNODE_ENV=development\\nDATABASE_URL=\\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 chalk from 'chalk';\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(chalk.bgCyan(chalk.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 chalk.green(`\\nYour project is ready!\\n\\n`) +\n chalk.dim(` cd ${projectName as string}\\n`) +\n chalk.dim(` efc start dev\\n`) +\n chalk.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.1.3',\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 } 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 apiDir: path.join(__dirname, 'api'),\n tasksDir: path.join(__dirname, 'tasks'),\n${taskLine}}).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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-efc-app",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Interactive scaffolder for Express File Cluster projects",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/index.ts CHANGED
@@ -93,10 +93,15 @@ async function main(): Promise<void> {
93
93
  await npmInstall(projectName as string);
94
94
  spinner.stop('Dependencies installed');
95
95
 
96
+ spinner.start('Installing efc CLI globally…');
97
+ await npmInstallGlobal().catch(() => {/* non-fatal */});
98
+ spinner.stop('efc CLI ready');
99
+
96
100
  p.outro(
97
101
  chalk.green(`\nYour project is ready!\n\n`) +
98
102
  chalk.dim(` cd ${projectName as string}\n`) +
99
- chalk.dim(` efc start dev\n`),
103
+ chalk.dim(` efc start dev\n`) +
104
+ chalk.dim(`\n (or: npm run dev)\n`),
100
105
  );
101
106
  }
102
107
 
@@ -110,6 +115,15 @@ function npmInstall(projectDir: string): Promise<void> {
110
115
  });
111
116
  }
112
117
 
118
+ function npmInstallGlobal(): Promise<void> {
119
+ return new Promise((resolve, reject) => {
120
+ const child = spawn('npm', ['install', '-g', 'express-file-cluster'], {
121
+ stdio: 'ignore',
122
+ });
123
+ child.on('exit', (code) => (code === 0 ? resolve() : reject(new Error('global install failed'))));
124
+ });
125
+ }
126
+
113
127
  main().catch((err) => {
114
128
  console.error(err);
115
129
  process.exit(1);
package/src/scaffold.ts CHANGED
@@ -21,14 +21,14 @@ export async function scaffold(opts: ScaffoldOptions): Promise<void> {
21
21
  await writeEfcConfig(dest, opts);
22
22
  await writeEntryPoint(dest, opts);
23
23
  await writeGitignore(dest);
24
- await writeEnvFiles(dest);
24
+ await writeEnvFiles(dest, opts);
25
25
  await writeExampleRoute(dest, opts);
26
26
  if (opts.tasks) await writeExampleTask(dest, opts);
27
27
  }
28
28
 
29
29
  async function writePackageJson(dest: string, opts: ScaffoldOptions): Promise<void> {
30
30
  const deps: Record<string, string> = {
31
- 'express-file-cluster': '^0.1.1',
31
+ 'express-file-cluster': '^0.1.3',
32
32
  };
33
33
  if (opts.database === 'mongodb') deps['mongoose'] = '^8.0.0';
34
34
  if (opts.database === 'postgresql') {
@@ -88,24 +88,16 @@ async function writeTsConfig(dest: string, opts: ScaffoldOptions): Promise<void>
88
88
  async function writeEfcConfig(dest: string, opts: ScaffoldOptions): Promise<void> {
89
89
  const ext = opts.language === 'typescript' ? 'ts' : 'js';
90
90
  const tasks = opts.tasks
91
- ? `{
92
- backend: '${opts.taskBackend ?? 'bullmq'}',
93
- redisUrl: process.env.REDIS_URL,
94
- concurrency: 5,
95
- }`
91
+ ? `{ backend: '${opts.taskBackend ?? 'bullmq'}', concurrency: 5 }`
96
92
  : 'false';
97
93
 
98
94
  const content = `import type { EFCConfig } from 'express-file-cluster';
99
95
 
96
+ // Structural config only — runtime values (PORT, DATABASE_URL, JWT_SECRET, etc.) are read from .env
100
97
  const config: EFCConfig = {
101
- port: Number(process.env.PORT) || 3000,
102
98
  apiDir: './src/api',
103
99
  tasksDir: './src/tasks',
104
- database: '${opts.database}',
105
- databaseUrl: process.env.DATABASE_URL!,
106
100
  authStrategy: '${opts.authStrategy}',
107
- jwtSecret: process.env.JWT_SECRET!,
108
- cluster: process.env.NODE_ENV === 'production',
109
101
  tasks: ${tasks},
110
102
  globalMiddlewares: [],
111
103
  };
@@ -117,22 +109,20 @@ export default config;
117
109
 
118
110
  async function writeEntryPoint(dest: string, opts: ScaffoldOptions): Promise<void> {
119
111
  const ext = opts.language === 'typescript' ? 'ts' : 'js';
112
+ const taskLine = opts.tasks
113
+ ? ` tasks: { backend: '${opts.taskBackend ?? 'bullmq'}' },\n`
114
+ : '';
120
115
  const content = `import { ignite } from 'express-file-cluster';
121
116
  import { fileURLToPath } from 'url';
122
117
  import path from 'path';
123
118
 
124
119
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
125
120
 
121
+ // PORT, DATABASE_URL, JWT_SECRET, CORS_ORIGINS are read from .env automatically
126
122
  ignite({
127
- port: Number(process.env.PORT) || 3000,
128
123
  apiDir: path.join(__dirname, 'api'),
129
124
  tasksDir: path.join(__dirname, 'tasks'),
130
- database: '${opts.database}',
131
- databaseUrl: process.env.DATABASE_URL,
132
- authStrategy: '${opts.authStrategy}',
133
- jwtSecret: process.env.JWT_SECRET,
134
- cluster: process.env.NODE_ENV === 'production',
135
- }).catch(console.error);
125
+ ${taskLine}}).catch(console.error);
136
126
  `;
137
127
  await fs.outputFile(path.join(dest, 'src', `index.${ext}`), content);
138
128
  }
@@ -144,10 +134,20 @@ async function writeGitignore(dest: string): Promise<void> {
144
134
  );
145
135
  }
146
136
 
147
- async function writeEnvFiles(dest: string): Promise<void> {
137
+ async function writeEnvFiles(dest: string, opts: ScaffoldOptions): Promise<void> {
148
138
  const secret = crypto.randomBytes(64).toString('hex');
149
- const dotenv = `PORT=3000\nNODE_ENV=development\nDATABASE_URL=\nJWT_SECRET=${secret}\nREDIS_URL=redis://localhost:6379\nCORS_ORIGINS=http://localhost:3000\n`;
150
- const example = `PORT=3000\nNODE_ENV=development\nDATABASE_URL=\nJWT_SECRET=<generate with: openssl rand -hex 64>\nREDIS_URL=redis://localhost:6379\nCORS_ORIGINS=http://localhost:3000,https://yourapp.com\n`;
139
+ const projectName = path.basename(dest);
140
+ const dbUrl =
141
+ opts.database === 'mongodb'
142
+ ? `mongodb://localhost:27017/${projectName}`
143
+ : `postgresql://localhost:5432/${projectName}`;
144
+ const dbExampleUrl =
145
+ opts.database === 'mongodb'
146
+ ? `mongodb://localhost:27017/${projectName}`
147
+ : `postgresql://user:password@localhost:5432/${projectName}`;
148
+
149
+ const dotenv = `PORT=3000\nNODE_ENV=development\nDATABASE_URL=${dbUrl}\nJWT_SECRET=${secret}\nREDIS_URL=redis://localhost:6379\nCORS_ORIGINS=http://localhost:3000\n`;
150
+ 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`;
151
151
  await fs.outputFile(path.join(dest, '.env'), dotenv);
152
152
  await fs.outputFile(path.join(dest, '.env.example'), example);
153
153
  }