create-bcp-app 0.1.7 → 0.1.9

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/README.md CHANGED
@@ -18,6 +18,9 @@ Select a database:
18
18
  3) PostgreSQL
19
19
  4) SQLite
20
20
  5) MongoDB
21
+ Select authentication:
22
+ 1) None
23
+ 2) JWT Cookie
21
24
  ```
22
25
 
23
26
  ## Generated application defaults
@@ -75,8 +78,71 @@ import "bcp/server-only";
75
78
 
76
79
  This prevents database code from being used in a page/client module graph. Query the database from an API route and call that API from client components.
77
80
 
81
+ For MySQL, the generated helper exports a reusable `db` pool and caches that pool on `globalThis` during development so hot reloads do not create a new connection pool every time:
82
+
83
+ ```ts
84
+ import {
85
+ db,
86
+ } from "@/lib/database";
87
+
88
+ const [rows] =
89
+ await db.query(
90
+ "SELECT 1"
91
+ );
92
+ ```
93
+
94
+ The generated MySQL `.env.example` uses separate connection settings:
95
+
96
+ ```dotenv
97
+ DB_HOST=localhost
98
+ DB_PORT=3306
99
+ DB_USER=
100
+ DB_PASSWORD=
101
+ DB_NAME=bcp_app
102
+ ```
103
+
78
104
  The generator intentionally installs database drivers directly and does not force an ORM.
79
105
 
106
+ ### JWT Cookie authentication
107
+
108
+ Selecting `JWT Cookie` creates a secure-by-default authentication foundation:
109
+
110
+ ```text
111
+ lib/
112
+ └─ auth.ts
113
+
114
+ app/api/auth/
115
+ ├─ login/route.ts
116
+ ├─ logout/route.ts
117
+ └─ me/route.ts
118
+ ```
119
+
120
+ The preset uses the session primitives from `bcp/server` and adds this environment variable:
121
+
122
+ ```dotenv
123
+ BCP_SESSION_SECRET=
124
+ ```
125
+
126
+ Set `BCP_SESSION_SECRET` to a cryptographically random secret of at least 32 bytes before using authentication.
127
+
128
+ The generated `authenticateCredentials(email, password)` intentionally returns `null` until the application connects it to its own user table and password-hash verification. This prevents a newly generated application from accepting unverified credentials or trusting identity data sent directly by the browser.
129
+
130
+ After implementing credential verification, the generated flow is:
131
+
132
+ ```text
133
+ POST /api/auth/login
134
+ -> authenticateCredentials()
135
+ -> createSession()
136
+ -> HttpOnly JWT cookie
137
+
138
+ GET /api/auth/me
139
+ -> getSession()
140
+ -> current user
141
+
142
+ POST /api/auth/logout
143
+ -> destroySession()
144
+ ```
145
+
80
146
  ## Options
81
147
 
82
148
  ```text
@@ -84,7 +150,8 @@ The generator intentionally installs database drivers directly and does not forc
84
150
  --tailwind Enable Tailwind CSS without prompting
85
151
  --no-tailwind Disable Tailwind CSS without prompting
86
152
  --database <database> none | mysql | postgresql | sqlite | mongodb
87
- -y, --yes Accept defaults (Tailwind enabled, no database)
153
+ --auth <preset> none | jwt-cookie
154
+ -y, --yes Accept defaults (Tailwind enabled, no database, no auth)
88
155
  --bcp <specifier> Override dependencies.bcp
89
156
  -h, --help Show help
90
157
  ```
@@ -92,13 +159,14 @@ The generator intentionally installs database drivers directly and does not forc
92
159
  Examples:
93
160
 
94
161
  ```bash
95
- npx create-bcp-app my-app --tailwind --database mysql
162
+ npx create-bcp-app my-app --tailwind --database mysql --auth jwt-cookie
96
163
  npx create-bcp-app my-app --no-tailwind --database mongodb
164
+ npx create-bcp-app my-app --auth jwt-cookie
97
165
  npx create-bcp-app my-app --yes
98
166
  ```
99
167
 
100
168
  The `--bcp` option is primarily useful for prerelease and local package testing, for example:
101
169
 
102
170
  ```bash
103
- npx create-bcp-app my-app --bcp file:../bcp-0.1.7.tgz
171
+ npx create-bcp-app my-app --bcp file:../bcp-0.1.8.tgz
104
172
  ```
@@ -14,7 +14,9 @@ import {
14
14
  createBcpApp,
15
15
  } from "../src/index.mjs";
16
16
  import {
17
+ AUTH_CHOICES,
17
18
  DATABASE_CHOICES,
19
+ normalizeAuth,
18
20
  normalizeDatabase,
19
21
  } from "../src/project-options.mjs";
20
22
 
@@ -39,6 +41,8 @@ let tailwind =
39
41
  undefined;
40
42
  let database =
41
43
  undefined;
44
+ let auth =
45
+ undefined;
42
46
  let acceptDefaults =
43
47
  false;
44
48
 
@@ -105,6 +109,28 @@ for (
105
109
  continue;
106
110
  }
107
111
 
112
+ if (
113
+ value === "--auth"
114
+ ) {
115
+ const nextValue =
116
+ args[
117
+ index + 1
118
+ ];
119
+
120
+ if (!nextValue) {
121
+ fail(
122
+ "--auth requires a value."
123
+ );
124
+ }
125
+
126
+ auth =
127
+ normalizeAuth(
128
+ nextValue
129
+ );
130
+ index++;
131
+ continue;
132
+ }
133
+
108
134
  if (
109
135
  value === "--bcp"
110
136
  ) {
@@ -156,6 +182,7 @@ if (!projectDirectory) {
156
182
  await resolveInteractiveOptions({
157
183
  tailwind,
158
184
  database,
185
+ auth,
159
186
  acceptDefaults,
160
187
  });
161
188
 
@@ -168,6 +195,8 @@ if (!projectDirectory) {
168
195
  selected.tailwind,
169
196
  database:
170
197
  selected.database,
198
+ auth:
199
+ selected.auth,
171
200
  packageManager:
172
201
  "npm",
173
202
  });
@@ -191,6 +220,9 @@ if (!projectDirectory) {
191
220
  console.log(
192
221
  `Database: ${formatDatabaseName(result.database)}`
193
222
  );
223
+ console.log(
224
+ `Authentication: ${formatAuthName(result.auth)}`
225
+ );
194
226
  console.log("");
195
227
  console.log(
196
228
  "Next steps:"
@@ -221,12 +253,15 @@ if (!projectDirectory) {
221
253
  async function resolveInteractiveOptions({
222
254
  tailwind: selectedTailwind,
223
255
  database: selectedDatabase,
256
+ auth: selectedAuth,
224
257
  acceptDefaults: useDefaults,
225
258
  }) {
226
259
  let resolvedTailwind =
227
260
  selectedTailwind;
228
261
  let resolvedDatabase =
229
262
  selectedDatabase;
263
+ let resolvedAuth =
264
+ selectedAuth;
230
265
 
231
266
  const interactive =
232
267
  Boolean(
@@ -244,6 +279,11 @@ async function resolveInteractiveOptions({
244
279
  resolvedDatabase ??
245
280
  "none"
246
281
  ),
282
+ auth:
283
+ normalizeAuth(
284
+ resolvedAuth ??
285
+ "none"
286
+ ),
247
287
  };
248
288
  }
249
289
 
@@ -257,6 +297,11 @@ async function resolveInteractiveOptions({
257
297
  resolvedDatabase ??
258
298
  "none"
259
299
  ),
300
+ auth:
301
+ normalizeAuth(
302
+ resolvedAuth ??
303
+ "none"
304
+ ),
260
305
  };
261
306
  }
262
307
 
@@ -294,6 +339,16 @@ async function resolveInteractiveOptions({
294
339
  prompt
295
340
  );
296
341
  }
342
+
343
+ if (
344
+ resolvedAuth ===
345
+ undefined
346
+ ) {
347
+ resolvedAuth =
348
+ await askAuth(
349
+ prompt
350
+ );
351
+ }
297
352
  } finally {
298
353
  prompt.close();
299
354
  }
@@ -307,6 +362,10 @@ async function resolveInteractiveOptions({
307
362
  normalizeDatabase(
308
363
  resolvedDatabase
309
364
  ),
365
+ auth:
366
+ normalizeAuth(
367
+ resolvedAuth
368
+ ),
310
369
  };
311
370
  }
312
371
 
@@ -427,6 +486,77 @@ async function askDatabase(
427
486
  }
428
487
  }
429
488
 
489
+ async function askAuth(
490
+ prompt
491
+ ) {
492
+ const labels = {
493
+ none: "None",
494
+ "jwt-cookie":
495
+ "JWT Cookie",
496
+ };
497
+
498
+ console.log(
499
+ "Select authentication:"
500
+ );
501
+
502
+ AUTH_CHOICES.forEach(
503
+ (
504
+ value,
505
+ index
506
+ ) => {
507
+ console.log(
508
+ ` ${index + 1}) ${labels[value]}`
509
+ );
510
+ }
511
+ );
512
+
513
+ while (true) {
514
+ const answer =
515
+ (
516
+ await prompt.question(
517
+ "Authentication (1): "
518
+ )
519
+ ).trim();
520
+
521
+ if (!answer) {
522
+ return "none";
523
+ }
524
+
525
+ const index =
526
+ Number(
527
+ answer
528
+ ) - 1;
529
+
530
+ if (
531
+ Number.isInteger(
532
+ index
533
+ ) &&
534
+ index >= 0 &&
535
+ index <
536
+ AUTH_CHOICES.length
537
+ ) {
538
+ return AUTH_CHOICES[
539
+ index
540
+ ];
541
+ }
542
+
543
+ const normalized =
544
+ answer.toLowerCase();
545
+
546
+ if (
547
+ AUTH_CHOICES.includes(
548
+ normalized
549
+ )
550
+ ) {
551
+ return normalized;
552
+ }
553
+
554
+ console.log(
555
+ "Choose a number from the list or enter an authentication preset name."
556
+ );
557
+ }
558
+ }
559
+
430
560
  function formatDatabaseName(
431
561
  value
432
562
  ) {
@@ -442,6 +572,19 @@ function formatDatabaseName(
442
572
  value;
443
573
  }
444
574
 
575
+ function formatAuthName(
576
+ value
577
+ ) {
578
+ const names = {
579
+ none: "None",
580
+ "jwt-cookie":
581
+ "JWT Cookie",
582
+ };
583
+
584
+ return names[value] ??
585
+ value;
586
+ }
587
+
445
588
  function printHelp() {
446
589
  console.log(`
447
590
  create-bcp-app
@@ -452,23 +595,25 @@ Usage:
452
595
  Interactive setup:
453
596
  - Choose whether to use Tailwind CSS
454
597
  - Choose a database: None, MySQL, PostgreSQL, SQLite or MongoDB
598
+ - Choose authentication: None or JWT Cookie
455
599
 
456
600
  Options:
457
601
  --no-install Create files without running npm install
458
602
  --tailwind Enable Tailwind CSS without prompting
459
603
  --no-tailwind Disable Tailwind CSS without prompting
460
604
  --database <database> none | mysql | postgresql | sqlite | mongodb
461
- -y, --yes Accept defaults (Tailwind enabled, no database)
605
+ --auth <preset> none | jwt-cookie
606
+ -y, --yes Accept defaults (Tailwind enabled, no database, no auth)
462
607
  --bcp <specifier> Override the package specifier stored under dependencies.bcp
463
608
  -h, --help Show this help message
464
609
 
465
610
  Examples:
466
611
  npx create-bcp-app my-app
467
- npx create-bcp-app my-app --tailwind --database mysql
612
+ npx create-bcp-app my-app --tailwind --database mysql --auth jwt-cookie
468
613
  npx create-bcp-app my-app --no-tailwind --database mongodb
469
614
  npx create-bcp-app my-app --yes
470
615
  npx create-bcp-app my-app --no-install
471
- npx create-bcp-app my-app --bcp file:../bcp-0.1.2.tgz
616
+ npx create-bcp-app my-app --bcp file:../bcp-0.1.8.tgz
472
617
  `);
473
618
  }
474
619
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bcp-app",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Create a new BCP Framework application.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.d.mts CHANGED
@@ -5,12 +5,17 @@ export type BcpDatabase =
5
5
  | "sqlite"
6
6
  | "mongodb";
7
7
 
8
+ export type BcpAuthPreset =
9
+ | "none"
10
+ | "jwt-cookie";
11
+
8
12
  export interface CreateBcpAppOptions {
9
13
  projectDirectory: string;
10
14
  install?: boolean;
11
15
  bcpPackage?: string;
12
16
  tailwind?: boolean;
13
17
  database?: BcpDatabase;
18
+ auth?: BcpAuthPreset;
14
19
  packageManager?: "npm";
15
20
  }
16
21
 
@@ -20,6 +25,7 @@ export interface CreateBcpAppResult {
20
25
  packageSpecifier: string;
21
26
  tailwind: boolean;
22
27
  database: BcpDatabase;
28
+ auth: BcpAuthPreset;
23
29
  }
24
30
 
25
31
  export function createBcpApp(
package/src/index.mjs CHANGED
@@ -9,6 +9,7 @@ import {
9
9
 
10
10
  import {
11
11
  applyProjectOptions,
12
+ normalizeAuth,
12
13
  normalizeDatabase,
13
14
  } from "./project-options.mjs";
14
15
 
@@ -42,6 +43,10 @@ export async function createBcpApp(
42
43
  normalizeDatabase(
43
44
  options.database
44
45
  );
46
+ const auth =
47
+ normalizeAuth(
48
+ options.auth
49
+ );
45
50
 
46
51
  ensureTargetDirectory(
47
52
  targetDirectory
@@ -119,6 +124,7 @@ export async function createBcpApp(
119
124
  packageJson,
120
125
  tailwind,
121
126
  database,
127
+ auth,
122
128
  });
123
129
 
124
130
  fs.writeFileSync(
@@ -152,6 +158,8 @@ export async function createBcpApp(
152
158
  selectedOptions.tailwind,
153
159
  database:
154
160
  selectedOptions.database,
161
+ auth:
162
+ selectedOptions.auth,
155
163
  };
156
164
  }
157
165
 
@@ -9,6 +9,11 @@ export const DATABASE_CHOICES = [
9
9
  "mongodb",
10
10
  ];
11
11
 
12
+ export const AUTH_CHOICES = [
13
+ "none",
14
+ "jwt-cookie",
15
+ ];
16
+
12
17
  const DATABASE_PRESETS = {
13
18
  mysql: {
14
19
  dependencies: {
@@ -16,25 +21,68 @@ const DATABASE_PRESETS = {
16
21
  },
17
22
  devDependencies: {},
18
23
  env: [
19
- "DATABASE_URL=mysql://root:password@localhost:3306/bcp_app",
24
+ "DB_HOST=localhost",
25
+ "DB_PORT=3306",
26
+ "DB_USER=",
27
+ "DB_PASSWORD=",
28
+ "DB_NAME=bcp_app",
20
29
  ],
21
30
  source: `import "bcp/server-only";
22
31
 
23
32
  import mysql from "mysql2/promise";
24
33
 
25
- const connectionString =
26
- process.env.DATABASE_URL;
34
+ const globalForDatabase =
35
+ globalThis as typeof globalThis & {
36
+ __bcpDb?: mysql.Pool;
37
+ };
27
38
 
28
- if (!connectionString) {
29
- throw new Error(
30
- "DATABASE_URL is required for MySQL."
31
- );
39
+ export const db =
40
+ globalForDatabase.__bcpDb ??
41
+ mysql.createPool({
42
+ host:
43
+ process.env.DB_HOST ??
44
+ "localhost",
45
+
46
+ port:
47
+ Number(
48
+ process.env.DB_PORT ??
49
+ 3306
50
+ ),
51
+
52
+ user:
53
+ process.env.DB_USER ??
54
+ "root",
55
+
56
+ password:
57
+ process.env.DB_PASSWORD ??
58
+ "",
59
+
60
+ database:
61
+ process.env.DB_NAME ??
62
+ "bcp_app",
63
+
64
+ waitForConnections:
65
+ true,
66
+
67
+ connectionLimit:
68
+ 10,
69
+
70
+ queueLimit:
71
+ 0,
72
+
73
+ charset:
74
+ "utf8mb4",
75
+ });
76
+
77
+ if (
78
+ process.env.NODE_ENV !==
79
+ "production"
80
+ ) {
81
+ globalForDatabase.__bcpDb =
82
+ db;
32
83
  }
33
84
 
34
- export const db =
35
- mysql.createPool(
36
- connectionString
37
- );
85
+ export default db;
38
86
  `,
39
87
  },
40
88
  postgresql: {
@@ -149,6 +197,241 @@ export async function getDatabase() {
149
197
  },
150
198
  };
151
199
 
200
+ const JWT_COOKIE_AUTH_FILES = {
201
+ "lib/auth.ts": `import "bcp/server-only";
202
+
203
+ import {
204
+ createSession,
205
+ destroySession,
206
+ getSession,
207
+ } from "bcp/server";
208
+
209
+ export interface AuthenticatedUser {
210
+ id: string | number;
211
+ email: string;
212
+ role?: string;
213
+ }
214
+
215
+ export interface UserSession {
216
+ userId: string | number;
217
+ email: string;
218
+ role?: string;
219
+ }
220
+
221
+ export async function authenticateCredentials(
222
+ email: string,
223
+ password: string
224
+ ): Promise<AuthenticatedUser | null> {
225
+ /*
226
+ * Secure starter behavior: authentication is disabled until the
227
+ * application verifies the supplied credentials against its own user
228
+ * store and password-hash strategy.
229
+ */
230
+ void email;
231
+ void password;
232
+
233
+ return null;
234
+ }
235
+
236
+ export async function createUserSession(
237
+ user: AuthenticatedUser
238
+ ): Promise<void> {
239
+ await createSession({
240
+ userId:
241
+ user.id,
242
+ email:
243
+ user.email,
244
+ ...(user.role === undefined
245
+ ? {}
246
+ : {
247
+ role:
248
+ user.role,
249
+ }),
250
+ });
251
+ }
252
+
253
+ export function getCurrentUser() {
254
+ return getSession<UserSession>();
255
+ }
256
+
257
+ export function clearUserSession() {
258
+ return destroySession();
259
+ }
260
+ `,
261
+ "app/api/auth/login/route.ts": `import {
262
+ json,
263
+ } from "bcp/server";
264
+
265
+ import {
266
+ authenticateCredentials,
267
+ createUserSession,
268
+ } from "@/lib/auth";
269
+
270
+ export async function POST(
271
+ request: Request
272
+ ) {
273
+ let body: unknown;
274
+
275
+ try {
276
+ body =
277
+ await request.json();
278
+ } catch {
279
+ return json(
280
+ {
281
+ error:
282
+ "Invalid JSON body.",
283
+ },
284
+ {
285
+ status:
286
+ 400,
287
+ }
288
+ );
289
+ }
290
+
291
+ if (
292
+ !body ||
293
+ typeof body !== "object" ||
294
+ Array.isArray(body)
295
+ ) {
296
+ return json(
297
+ {
298
+ error:
299
+ "Email and password are required.",
300
+ },
301
+ {
302
+ status:
303
+ 400,
304
+ }
305
+ );
306
+ }
307
+
308
+ const input =
309
+ body as Record<
310
+ string,
311
+ unknown
312
+ >;
313
+ const email =
314
+ typeof input.email ===
315
+ "string"
316
+ ? input.email.trim()
317
+ : "";
318
+ const password =
319
+ typeof input.password ===
320
+ "string"
321
+ ? input.password
322
+ : "";
323
+
324
+ if (
325
+ !email ||
326
+ !password
327
+ ) {
328
+ return json(
329
+ {
330
+ error:
331
+ "Email and password are required.",
332
+ },
333
+ {
334
+ status:
335
+ 400,
336
+ }
337
+ );
338
+ }
339
+
340
+ const user =
341
+ await authenticateCredentials(
342
+ email,
343
+ password
344
+ );
345
+
346
+ if (!user) {
347
+ return json(
348
+ {
349
+ error:
350
+ "Invalid credentials.",
351
+ },
352
+ {
353
+ status:
354
+ 401,
355
+ }
356
+ );
357
+ }
358
+
359
+ await createUserSession(
360
+ user
361
+ );
362
+
363
+ return json({
364
+ success:
365
+ true,
366
+ user: {
367
+ id:
368
+ user.id,
369
+ email:
370
+ user.email,
371
+ role:
372
+ user.role ??
373
+ null,
374
+ },
375
+ });
376
+ }
377
+ `,
378
+ "app/api/auth/logout/route.ts": `import {
379
+ json,
380
+ } from "bcp/server";
381
+
382
+ import {
383
+ clearUserSession,
384
+ } from "@/lib/auth";
385
+
386
+ export async function POST() {
387
+ await clearUserSession();
388
+
389
+ return json({
390
+ success:
391
+ true,
392
+ });
393
+ }
394
+ `,
395
+ "app/api/auth/me/route.ts": `import {
396
+ json,
397
+ } from "bcp/server";
398
+
399
+ import {
400
+ getCurrentUser,
401
+ } from "@/lib/auth";
402
+
403
+ export async function GET() {
404
+ const session =
405
+ await getCurrentUser();
406
+
407
+ if (!session) {
408
+ return json(
409
+ {
410
+ error:
411
+ "Unauthorized.",
412
+ },
413
+ {
414
+ status:
415
+ 401,
416
+ }
417
+ );
418
+ }
419
+
420
+ return json({
421
+ user: {
422
+ id:
423
+ session.userId,
424
+ email:
425
+ session.email,
426
+ role:
427
+ session.role ??
428
+ null,
429
+ },
430
+ });
431
+ }
432
+ `,
433
+ };
434
+
152
435
  export function normalizeDatabase(
153
436
  value
154
437
  ) {
@@ -172,16 +455,44 @@ export function normalizeDatabase(
172
455
  return normalized;
173
456
  }
174
457
 
458
+ export function normalizeAuth(
459
+ value
460
+ ) {
461
+ const normalized =
462
+ String(
463
+ value ?? "none"
464
+ )
465
+ .trim()
466
+ .toLowerCase();
467
+
468
+ if (
469
+ !AUTH_CHOICES.includes(
470
+ normalized
471
+ )
472
+ ) {
473
+ throw new Error(
474
+ `Unsupported authentication preset: ${normalized}. Choose one of: ${AUTH_CHOICES.join(", ")}.`
475
+ );
476
+ }
477
+
478
+ return normalized;
479
+ }
480
+
175
481
  export function applyProjectOptions({
176
482
  targetDirectory,
177
483
  packageJson,
178
484
  tailwind = false,
179
485
  database = "none",
486
+ auth = "none",
180
487
  }) {
181
488
  const selectedDatabase =
182
489
  normalizeDatabase(
183
490
  database
184
491
  );
492
+ const selectedAuth =
493
+ normalizeAuth(
494
+ auth
495
+ );
185
496
 
186
497
  if (tailwind) {
187
498
  configureTailwind(
@@ -214,7 +525,17 @@ export function applyProjectOptions({
214
525
  );
215
526
  appendEnvExample(
216
527
  targetDirectory,
217
- preset.env
528
+ preset.env,
529
+ "Database"
530
+ );
531
+ }
532
+
533
+ if (
534
+ selectedAuth ===
535
+ "jwt-cookie"
536
+ ) {
537
+ configureJwtCookieAuth(
538
+ targetDirectory
218
539
  );
219
540
  }
220
541
 
@@ -225,9 +546,39 @@ export function applyProjectOptions({
225
546
  ),
226
547
  database:
227
548
  selectedDatabase,
549
+ auth:
550
+ selectedAuth,
228
551
  };
229
552
  }
230
553
 
554
+ function configureJwtCookieAuth(
555
+ targetDirectory
556
+ ) {
557
+ for (
558
+ const [
559
+ relativePath,
560
+ source,
561
+ ]
562
+ of Object.entries(
563
+ JWT_COOKIE_AUTH_FILES
564
+ )
565
+ ) {
566
+ writeFile(
567
+ targetDirectory,
568
+ relativePath,
569
+ source
570
+ );
571
+ }
572
+
573
+ appendEnvExample(
574
+ targetDirectory,
575
+ [
576
+ "BCP_SESSION_SECRET=",
577
+ ],
578
+ "Authentication"
579
+ );
580
+ }
581
+
231
582
  function configureTailwind(
232
583
  targetDirectory,
233
584
  packageJson
@@ -415,7 +766,8 @@ function appendGitIgnore(
415
766
 
416
767
  function appendEnvExample(
417
768
  targetDirectory,
418
- lines
769
+ lines,
770
+ section
419
771
  ) {
420
772
  const filePath =
421
773
  path.join(
@@ -434,7 +786,7 @@ function appendEnvExample(
434
786
 
435
787
  fs.writeFileSync(
436
788
  filePath,
437
- `${current}${current ? "\n\n" : ""}# Database\n${lines.join("\n")}\n`,
789
+ `${current}${current ? "\n\n" : ""}# ${section}\n${lines.join("\n")}\n`,
438
790
  "utf8"
439
791
  );
440
792
  }