create-efc-app 0.1.0 → 0.1.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 +18 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +15 -1
- package/src/scaffold.ts +3 -3
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ async function scaffold(opts) {
|
|
|
23
23
|
}
|
|
24
24
|
async function writePackageJson(dest, opts) {
|
|
25
25
|
const deps = {
|
|
26
|
-
"express-file-cluster": "^0.1.
|
|
26
|
+
"express-file-cluster": "^0.1.2"
|
|
27
27
|
};
|
|
28
28
|
if (opts.database === "mongodb") deps["mongoose"] = "^8.0.0";
|
|
29
29
|
if (opts.database === "postgresql") {
|
|
@@ -135,12 +135,14 @@ NODE_ENV=development
|
|
|
135
135
|
DATABASE_URL=
|
|
136
136
|
JWT_SECRET=${secret}
|
|
137
137
|
REDIS_URL=redis://localhost:6379
|
|
138
|
+
CORS_ORIGINS=http://localhost:3000
|
|
138
139
|
`;
|
|
139
140
|
const example = `PORT=3000
|
|
140
141
|
NODE_ENV=development
|
|
141
142
|
DATABASE_URL=
|
|
142
143
|
JWT_SECRET=<generate with: openssl rand -hex 64>
|
|
143
144
|
REDIS_URL=redis://localhost:6379
|
|
145
|
+
CORS_ORIGINS=http://localhost:3000,https://yourapp.com
|
|
144
146
|
`;
|
|
145
147
|
await fs.outputFile(path.join(dest, ".env"), dotenv);
|
|
146
148
|
await fs.outputFile(path.join(dest, ".env.example"), example);
|
|
@@ -281,12 +283,18 @@ async function main() {
|
|
|
281
283
|
spinner2.start("Installing dependencies\u2026");
|
|
282
284
|
await npmInstall(projectName);
|
|
283
285
|
spinner2.stop("Dependencies installed");
|
|
286
|
+
spinner2.start("Installing efc CLI globally\u2026");
|
|
287
|
+
await npmInstallGlobal().catch(() => {
|
|
288
|
+
});
|
|
289
|
+
spinner2.stop("efc CLI ready");
|
|
284
290
|
p.outro(
|
|
285
291
|
chalk.green(`
|
|
286
292
|
Your project is ready!
|
|
287
293
|
|
|
288
294
|
`) + chalk.dim(` cd ${projectName}
|
|
289
|
-
`) + chalk.dim(`
|
|
295
|
+
`) + chalk.dim(` efc start dev
|
|
296
|
+
`) + chalk.dim(`
|
|
297
|
+
(or: npm run dev)
|
|
290
298
|
`)
|
|
291
299
|
);
|
|
292
300
|
}
|
|
@@ -299,6 +307,14 @@ function npmInstall(projectDir) {
|
|
|
299
307
|
child.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`npm install failed`)));
|
|
300
308
|
});
|
|
301
309
|
}
|
|
310
|
+
function npmInstallGlobal() {
|
|
311
|
+
return new Promise((resolve, reject) => {
|
|
312
|
+
const child = spawn("npm", ["install", "-g", "express-file-cluster"], {
|
|
313
|
+
stdio: "ignore"
|
|
314
|
+
});
|
|
315
|
+
child.on("exit", (code) => code === 0 ? resolve() : reject(new Error("global install failed")));
|
|
316
|
+
});
|
|
317
|
+
}
|
|
302
318
|
main().catch((err) => {
|
|
303
319
|
console.error(err);
|
|
304
320
|
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(` 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\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.0',\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\\n`;\n const example = `PORT=3000\\nNODE_ENV=development\\nDATABASE_URL=\\nJWT_SECRET=<generate with: openssl rand -hex 64>\\nREDIS_URL=redis://localhost:6379\\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);\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.2',\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"]}
|
package/package.json
CHANGED
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(`
|
|
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
|
@@ -28,7 +28,7 @@ export async function scaffold(opts: ScaffoldOptions): Promise<void> {
|
|
|
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.
|
|
31
|
+
'express-file-cluster': '^0.1.2',
|
|
32
32
|
};
|
|
33
33
|
if (opts.database === 'mongodb') deps['mongoose'] = '^8.0.0';
|
|
34
34
|
if (opts.database === 'postgresql') {
|
|
@@ -146,8 +146,8 @@ async function writeGitignore(dest: string): Promise<void> {
|
|
|
146
146
|
|
|
147
147
|
async function writeEnvFiles(dest: string): Promise<void> {
|
|
148
148
|
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\n`;
|
|
150
|
-
const example = `PORT=3000\nNODE_ENV=development\nDATABASE_URL=\nJWT_SECRET=<generate with: openssl rand -hex 64>\nREDIS_URL=redis://localhost:6379\n`;
|
|
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`;
|
|
151
151
|
await fs.outputFile(path.join(dest, '.env'), dotenv);
|
|
152
152
|
await fs.outputFile(path.join(dest, '.env.example'), example);
|
|
153
153
|
}
|