create-efc-app 0.1.2 → 0.1.4
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 +15 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/scaffold.ts +23 -23
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.
|
|
26
|
+
"express-file-cluster": "^0.1.4"
|
|
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";
|
|
106
|
-
const
|
|
98
|
+
const taskLine = opts.tasks ? ` tasks: { backend: '${opts.taskBackend ?? "bullmq"}' },
|
|
99
|
+
` : "";
|
|
100
|
+
const content = `import { ignite, gracefulShutdown } 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
|
-
|
|
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}}).then(gracefulShutdown).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
|
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 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"]}
|
|
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.4',\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 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":";;;AAAA,YAAY,OAAO;AACnB,OAAO,WAAW;AAClB,SAAS,aAAa;;;ACFtB,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,YAAY;AAYnB,eAAsB,SAAS,MAAsC;AACnE,QAAM,OAAO,KAAK,QAAQ,QAAQ,IAAI,GAAG,KAAK,WAAW;AACzD,QAAM,GAAG,UAAU,IAAI;AAEvB,QAAM,iBAAiB,MAAM,IAAI;AACjC,QAAM,cAAc,MAAM,IAAI;AAC9B,QAAM,eAAe,MAAM,IAAI;AAC/B,QAAM,gBAAgB,MAAM,IAAI;AAChC,QAAM,eAAe,IAAI;AACzB,QAAM,cAAc,MAAM,IAAI;AAC9B,QAAM,kBAAkB,MAAM,IAAI;AAClC,MAAI,KAAK,MAAO,OAAM,iBAAiB,MAAM,IAAI;AACnD;AAEA,eAAe,iBAAiB,MAAc,MAAsC;AAClF,QAAM,OAA+B;AAAA,IACnC,wBAAwB;AAAA,EAC1B;AACA,MAAI,KAAK,aAAa,UAAW,MAAK,UAAU,IAAI;AACpD,MAAI,KAAK,aAAa,cAAc;AAClC,SAAK,IAAI,IAAI;AACb,SAAK,aAAa,IAAI;AAAA,EACxB;AACA,MAAI,KAAK,SAAS,KAAK,gBAAgB,SAAU,MAAK,QAAQ,IAAI;AAClE,MAAI,KAAK,SAAS,KAAK,gBAAgB,UAAW,MAAK,SAAS,IAAI;AAEpE,QAAM,UAAkC;AAAA,IACtC,QAAQ;AAAA,EACV;AACA,MAAI,KAAK,aAAa,cAAc;AAClC,YAAQ,YAAY,IAAI;AACxB,YAAQ,aAAa,IAAI;AACzB,YAAQ,gBAAgB,IAAI;AAC5B,YAAQ,MAAM,IAAI;AAClB,YAAQ,KAAK,IAAI;AAAA,EACnB;AAEA,QAAM,MAAM;AAAA,IACV,MAAM,KAAK;AAAA,IACX,SAAS;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,MACP,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAEA,QAAM,GAAG,UAAU,KAAK,KAAK,MAAM,cAAc,GAAG,KAAK,EAAE,QAAQ,EAAE,CAAC;AACxE;AAEA,eAAe,cAAc,MAAc,MAAsC;AAC/E,MAAI,KAAK,aAAa,aAAc;AACpC,QAAM,SAAS;AAAA,IACb,iBAAiB;AAAA,MACf,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,kBAAkB;AAAA,MAClB,QAAQ;AAAA,MACR,iBAAiB;AAAA,MACjB,cAAc;AAAA,MACd,QAAQ;AAAA,MACR,SAAS;AAAA,IACX;AAAA,IACA,SAAS,CAAC,UAAU;AAAA,IACpB,SAAS,CAAC,gBAAgB,MAAM;AAAA,EAClC;AACA,QAAM,GAAG,UAAU,KAAK,KAAK,MAAM,eAAe,GAAG,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAC5E;AAEA,eAAe,eAAe,MAAc,MAAsC;AAChF,QAAM,MAAM,KAAK,aAAa,eAAe,OAAO;AACpD,QAAM,QAAQ,KAAK,QACf,eAAe,KAAK,eAAe,QAAQ,wBAC3C;AAEJ,QAAM,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAMC,KAAK,YAAY;AAAA,WACzB,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAMd,QAAM,GAAG,WAAW,KAAK,KAAK,MAAM,cAAc,GAAG,EAAE,GAAG,OAAO;AACnE;AAEA,eAAe,gBAAgB,MAAc,MAAsC;AACjF,QAAM,MAAM,KAAK,aAAa,eAAe,OAAO;AACpD,QAAM,WAAW,KAAK,QAClB,wBAAwB,KAAK,eAAe,QAAQ;AAAA,IACpD;AACJ,QAAM,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUhB,QAAQ;AAAA;AAER,QAAM,GAAG,WAAW,KAAK,KAAK,MAAM,OAAO,SAAS,GAAG,EAAE,GAAG,OAAO;AACrE;AAEA,eAAe,eAAe,MAA6B;AACzD,QAAM,GAAG;AAAA,IACP,KAAK,KAAK,MAAM,YAAY;AAAA,IAC5B;AAAA,EACF;AACF;AAEA,eAAe,cAAc,MAAc,MAAsC;AAC/E,QAAM,SAAS,OAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AACpD,QAAM,cAAc,KAAK,SAAS,IAAI;AACtC,QAAM,QACJ,KAAK,aAAa,YACd,6BAA6B,WAAW,KACxC,+BAA+B,WAAW;AAChD,QAAM,eACJ,KAAK,aAAa,YACd,6BAA6B,WAAW,KACxC,6CAA6C,WAAW;AAE9D,QAAM,SAAS;AAAA;AAAA,eAAiD,KAAK;AAAA,aAAgB,MAAM;AAAA;AAAA;AAAA;AAC3F,QAAM,UAAU;AAAA;AAAA,eAAiD,YAAY;AAAA;AAAA;AAAA;AAAA;AAC7E,QAAM,GAAG,WAAW,KAAK,KAAK,MAAM,MAAM,GAAG,MAAM;AACnD,QAAM,GAAG,WAAW,KAAK,KAAK,MAAM,cAAc,GAAG,OAAO;AAC9D;AAEA,eAAe,kBAAkB,MAAc,MAAsC;AACnF,QAAM,MAAM,KAAK,aAAa,eAAe,OAAO;AACpD,QAAM,UACJ,KAAK,aAAa,eACd;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA;AAAA;AAAA;AAAA;AAIN,QAAM,GAAG,WAAW,KAAK,KAAK,MAAM,OAAO,OAAO,UAAU,GAAG,EAAE,GAAG,OAAO;AAC7E;AAEA,eAAe,iBAAiB,MAAc,MAAsC;AAClF,QAAM,MAAM,KAAK,aAAa,eAAe,OAAO;AACpD,QAAM,UACJ,KAAK,aAAa,eACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAaA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMN,QAAM,GAAG,WAAW,KAAK,KAAK,MAAM,OAAO,SAAS,aAAa,GAAG,EAAE,GAAG,OAAO;AAClF;;;AD9LA,eAAe,OAAsB;AACnC,UAAQ,IAAI;AACZ,EAAE,QAAM,MAAM,OAAO,MAAM,MAAM,kBAAkB,CAAC,CAAC;AAErD,QAAM,cAAc,MAAQ,OAAK;AAAA,IAC/B,SAAS;AAAA,IACT,aAAa;AAAA,IACb,cAAc;AAAA,IACd,UAAU,CAAC,MAAO,CAAC,EAAE,KAAK,IAAI,6BAA6B;AAAA,EAC7D,CAAC;AACD,MAAM,WAAS,WAAW,GAAG;AAAE,IAAE,SAAO,WAAW;AAAG,YAAQ,KAAK,CAAC;AAAA,EAAG;AAEvE,QAAM,WAAW,MAAQ,SAAO;AAAA,IAC9B,SAAS;AAAA,IACT,SAAS;AAAA,MACP,EAAE,OAAO,cAAc,OAAO,cAAc,MAAM,cAAc;AAAA,MAChE,EAAE,OAAO,cAAc,OAAO,aAAa;AAAA,IAC7C;AAAA,EACF,CAAC;AACD,MAAM,WAAS,QAAQ,GAAG;AAAE,IAAE,SAAO,WAAW;AAAG,YAAQ,KAAK,CAAC;AAAA,EAAG;AAEpE,QAAM,WAAW,MAAQ,SAAO;AAAA,IAC9B,SAAS;AAAA,IACT,SAAS;AAAA,MACP,EAAE,OAAO,WAAW,OAAO,WAAW,MAAM,WAAW;AAAA,MACvD,EAAE,OAAO,cAAc,OAAO,cAAc,MAAM,cAAc;AAAA,IAClE;AAAA,EACF,CAAC;AACD,MAAM,WAAS,QAAQ,GAAG;AAAE,IAAE,SAAO,WAAW;AAAG,YAAQ,KAAK,CAAC;AAAA,EAAG;AAEpE,QAAM,eAAe,MAAQ,SAAO;AAAA,IAClC,SAAS;AAAA,IACT,SAAS;AAAA,MACP,EAAE,OAAO,aAAa,OAAO,aAAa,MAAM,2CAAsC;AAAA,MACtF,EAAE,OAAO,gBAAgB,OAAO,gBAAgB,MAAM,+BAA0B;AAAA,IAClF;AAAA,EACF,CAAC;AACD,MAAM,WAAS,YAAY,GAAG;AAAE,IAAE,SAAO,WAAW;AAAG,YAAQ,KAAK,CAAC;AAAA,EAAG;AAExE,QAAM,UAAU,MAAQ,UAAQ;AAAA,IAC9B,SAAS;AAAA,IACT,cAAc;AAAA,EAChB,CAAC;AACD,MAAM,WAAS,OAAO,GAAG;AAAE,IAAE,SAAO,WAAW;AAAG,YAAQ,KAAK,CAAC;AAAA,EAAG;AAEnE,QAAM,QAAQ,MAAQ,UAAQ;AAAA,IAC5B,SAAS;AAAA,IACT,cAAc;AAAA,EAChB,CAAC;AACD,MAAM,WAAS,KAAK,GAAG;AAAE,IAAE,SAAO,WAAW;AAAG,YAAQ,KAAK,CAAC;AAAA,EAAG;AAEjE,MAAI;AACJ,MAAI,OAAO;AACT,UAAM,UAAU,MAAQ,SAAO;AAAA,MAC7B,SAAS;AAAA,MACT,SAAS;AAAA,QACP,EAAE,OAAO,UAAU,OAAO,UAAU,MAAM,QAAQ;AAAA,QAClD,EAAE,OAAO,WAAW,OAAO,WAAW,MAAM,aAAa;AAAA,MAC3D;AAAA,IACF,CAAC;AACD,QAAM,WAAS,OAAO,GAAG;AAAE,MAAE,SAAO,WAAW;AAAG,cAAQ,KAAK,CAAC;AAAA,IAAG;AACnE,kBAAc;AAAA,EAChB;AAEA,QAAM,OAAwB;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAI,gBAAgB,UAAa,EAAE,YAAY;AAAA,EACjD;AAEA,QAAMA,WAAY,UAAQ;AAC1B,EAAAA,SAAQ,MAAM,2BAAsB;AAEpC,MAAI;AACF,UAAM,SAAS,IAAI;AACnB,IAAAA,SAAQ,KAAK,iBAAiB;AAAA,EAChC,SAAS,KAAK;AACZ,IAAAA,SAAQ,KAAK,4BAA4B;AACzC,YAAQ,MAAM,GAAG;AACjB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,EAAAA,SAAQ,MAAM,+BAA0B;AACxC,QAAM,WAAW,WAAqB;AACtC,EAAAA,SAAQ,KAAK,wBAAwB;AAErC,EAAAA,SAAQ,MAAM,mCAA8B;AAC5C,QAAM,iBAAiB,EAAE,MAAM,MAAM;AAAA,EAAgB,CAAC;AACtD,EAAAA,SAAQ,KAAK,eAAe;AAE5B,EAAE;AAAA,IACA,MAAM,MAAM;AAAA;AAAA;AAAA,CAA8B,IAC1C,MAAM,IAAI,QAAQ,WAAqB;AAAA,CAAI,IAC3C,MAAM,IAAI;AAAA,CAAmB,IAC7B,MAAM,IAAI;AAAA;AAAA,CAAyB;AAAA,EACrC;AACF;AAEA,SAAS,WAAW,YAAmC;AACrD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,QAAQ,MAAM,OAAO,CAAC,SAAS,GAAG;AAAA,MACtC,KAAK;AAAA,MACL,OAAO;AAAA,IACT,CAAC;AACD,UAAM,GAAG,QAAQ,CAAC,SAAU,SAAS,IAAI,QAAQ,IAAI,OAAO,IAAI,MAAM,oBAAoB,CAAC,CAAE;AAAA,EAC/F,CAAC;AACH;AAEA,SAAS,mBAAkC;AACzC,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,QAAQ,MAAM,OAAO,CAAC,WAAW,MAAM,sBAAsB,GAAG;AAAA,MACpE,OAAO;AAAA,IACT,CAAC;AACD,UAAM,GAAG,QAAQ,CAAC,SAAU,SAAS,IAAI,QAAQ,IAAI,OAAO,IAAI,MAAM,uBAAuB,CAAC,CAAE;AAAA,EAClG,CAAC;AACH;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,MAAM,GAAG;AACjB,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["spinner"]}
|
package/package.json
CHANGED
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.
|
|
31
|
+
'express-file-cluster': '^0.1.4',
|
|
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';
|
|
120
|
-
const
|
|
112
|
+
const taskLine = opts.tasks
|
|
113
|
+
? ` tasks: { backend: '${opts.taskBackend ?? 'bullmq'}' },\n`
|
|
114
|
+
: '';
|
|
115
|
+
const content = `import { ignite, gracefulShutdown } 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
|
-
|
|
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}}).then(gracefulShutdown).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
|
|
150
|
-
const
|
|
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
|
}
|