create-bcp-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/README.md CHANGED
@@ -8,16 +8,76 @@ cd my-app
8
8
  npm run dev
9
9
  ```
10
10
 
11
+ When running interactively, the generator asks:
12
+
13
+ ```text
14
+ Use Tailwind CSS? (Y/n)
15
+ Select a database:
16
+ 1) None
17
+ 2) MySQL
18
+ 3) PostgreSQL
19
+ 4) SQLite
20
+ 5) MongoDB
21
+ ```
22
+
23
+ ## Generated optional setup
24
+
25
+ ### Tailwind CSS
26
+
27
+ When Tailwind is enabled, the project includes:
28
+
29
+ - `tailwindcss`
30
+ - `@tailwindcss/cli`
31
+ - `concurrently`
32
+ - `app/globals.css`
33
+ - `app/api/bcp-styles/route.ts`
34
+ - a stylesheet link in `app/layout.tsx`
35
+ - starter Tailwind utility classes in `app/page.tsx`
36
+
37
+ The generated npm scripts compile Tailwind before starting BCP and keep Tailwind running in watch mode during development:
38
+
39
+ ```text
40
+ npm run css:build
41
+ npm run css:watch
42
+ npm run css:build:prod
43
+ ```
44
+
45
+ `npm run dev` performs an initial CSS build, then runs the Tailwind watcher and `bcp dev` together. `npm run build` creates a minified stylesheet before the BCP production build.
46
+
47
+ ### Database
48
+
49
+ The database choice adds a starter `lib/database.ts`, updates `.env.example`, and installs the matching driver:
50
+
51
+ - MySQL: `mysql2`
52
+ - PostgreSQL: `pg`
53
+ - SQLite: `better-sqlite3`
54
+ - MongoDB: `mongodb`
55
+ - None: no database dependency
56
+
57
+ The generator intentionally installs database drivers directly and does not force an ORM.
58
+
11
59
  ## Options
12
60
 
13
61
  ```text
14
- --no-install Create files without running npm install
15
- --bcp <specifier> Override dependencies.bcp
16
- -h, --help Show help
62
+ --no-install Create files without running npm install
63
+ --tailwind Enable Tailwind CSS without prompting
64
+ --no-tailwind Disable Tailwind CSS without prompting
65
+ --database <database> none | mysql | postgresql | sqlite | mongodb
66
+ -y, --yes Accept defaults (Tailwind enabled, no database)
67
+ --bcp <specifier> Override dependencies.bcp
68
+ -h, --help Show help
69
+ ```
70
+
71
+ Examples:
72
+
73
+ ```bash
74
+ npx create-bcp-app my-app --tailwind --database mysql
75
+ npx create-bcp-app my-app --no-tailwind --database mongodb
76
+ npx create-bcp-app my-app --yes
17
77
  ```
18
78
 
19
79
  The `--bcp` option is primarily useful for prerelease and local package testing, for example:
20
80
 
21
81
  ```bash
22
- npx create-bcp-app my-app --bcp file:../bcp-0.1.0.tgz
82
+ npx create-bcp-app my-app --bcp file:../bcp-0.1.4.tgz
23
83
  ```
@@ -1,10 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import path from "node:path";
4
+ import {
5
+ createInterface,
6
+ } from "node:readline/promises";
7
+
8
+ import {
9
+ stdin as input,
10
+ stdout as output,
11
+ } from "node:process";
4
12
 
5
13
  import {
6
14
  createBcpApp,
7
15
  } from "../src/index.mjs";
16
+ import {
17
+ DATABASE_CHOICES,
18
+ normalizeDatabase,
19
+ } from "../src/project-options.mjs";
8
20
 
9
21
  const args =
10
22
  process.argv.slice(2);
@@ -23,6 +35,12 @@ let install =
23
35
  true;
24
36
  let bcpPackage =
25
37
  undefined;
38
+ let tailwind =
39
+ undefined;
40
+ let database =
41
+ undefined;
42
+ let acceptDefaults =
43
+ false;
26
44
 
27
45
  for (
28
46
  let index = 0;
@@ -40,6 +58,53 @@ for (
40
58
  continue;
41
59
  }
42
60
 
61
+ if (
62
+ value === "--yes" ||
63
+ value === "-y"
64
+ ) {
65
+ acceptDefaults =
66
+ true;
67
+ continue;
68
+ }
69
+
70
+ if (
71
+ value === "--tailwind"
72
+ ) {
73
+ tailwind =
74
+ true;
75
+ continue;
76
+ }
77
+
78
+ if (
79
+ value === "--no-tailwind"
80
+ ) {
81
+ tailwind =
82
+ false;
83
+ continue;
84
+ }
85
+
86
+ if (
87
+ value === "--database"
88
+ ) {
89
+ const nextValue =
90
+ args[
91
+ index + 1
92
+ ];
93
+
94
+ if (!nextValue) {
95
+ fail(
96
+ "--database requires a value."
97
+ );
98
+ }
99
+
100
+ database =
101
+ normalizeDatabase(
102
+ nextValue
103
+ );
104
+ index++;
105
+ continue;
106
+ }
107
+
43
108
  if (
44
109
  value === "--bcp"
45
110
  ) {
@@ -87,11 +152,22 @@ if (!projectDirectory) {
87
152
  1;
88
153
  } else {
89
154
  try {
155
+ const selected =
156
+ await resolveInteractiveOptions({
157
+ tailwind,
158
+ database,
159
+ acceptDefaults,
160
+ });
161
+
90
162
  const result =
91
163
  await createBcpApp({
92
164
  projectDirectory,
93
165
  install,
94
166
  bcpPackage,
167
+ tailwind:
168
+ selected.tailwind,
169
+ database:
170
+ selected.database,
95
171
  packageManager:
96
172
  "npm",
97
173
  });
@@ -109,6 +185,12 @@ if (!projectDirectory) {
109
185
  console.log(
110
186
  `Location: ${result.targetDirectory}`
111
187
  );
188
+ console.log(
189
+ `Tailwind CSS: ${result.tailwind ? "Yes" : "No"}`
190
+ );
191
+ console.log(
192
+ `Database: ${formatDatabaseName(result.database)}`
193
+ );
112
194
  console.log("");
113
195
  console.log(
114
196
  "Next steps:"
@@ -136,6 +218,230 @@ if (!projectDirectory) {
136
218
  }
137
219
  }
138
220
 
221
+ async function resolveInteractiveOptions({
222
+ tailwind: selectedTailwind,
223
+ database: selectedDatabase,
224
+ acceptDefaults: useDefaults,
225
+ }) {
226
+ let resolvedTailwind =
227
+ selectedTailwind;
228
+ let resolvedDatabase =
229
+ selectedDatabase;
230
+
231
+ const interactive =
232
+ Boolean(
233
+ input.isTTY &&
234
+ output.isTTY
235
+ );
236
+
237
+ if (useDefaults) {
238
+ return {
239
+ tailwind:
240
+ resolvedTailwind ??
241
+ true,
242
+ database:
243
+ normalizeDatabase(
244
+ resolvedDatabase ??
245
+ "none"
246
+ ),
247
+ };
248
+ }
249
+
250
+ if (!interactive) {
251
+ return {
252
+ tailwind:
253
+ resolvedTailwind ??
254
+ false,
255
+ database:
256
+ normalizeDatabase(
257
+ resolvedDatabase ??
258
+ "none"
259
+ ),
260
+ };
261
+ }
262
+
263
+ const prompt =
264
+ createInterface({
265
+ input,
266
+ output,
267
+ });
268
+
269
+ try {
270
+ console.log("");
271
+ console.log(
272
+ "Configure your BCP app"
273
+ );
274
+ console.log("");
275
+
276
+ if (
277
+ resolvedTailwind ===
278
+ undefined
279
+ ) {
280
+ resolvedTailwind =
281
+ await askYesNo(
282
+ prompt,
283
+ "Use Tailwind CSS?",
284
+ true
285
+ );
286
+ }
287
+
288
+ if (
289
+ resolvedDatabase ===
290
+ undefined
291
+ ) {
292
+ resolvedDatabase =
293
+ await askDatabase(
294
+ prompt
295
+ );
296
+ }
297
+ } finally {
298
+ prompt.close();
299
+ }
300
+
301
+ return {
302
+ tailwind:
303
+ Boolean(
304
+ resolvedTailwind
305
+ ),
306
+ database:
307
+ normalizeDatabase(
308
+ resolvedDatabase
309
+ ),
310
+ };
311
+ }
312
+
313
+ async function askYesNo(
314
+ prompt,
315
+ label,
316
+ defaultValue
317
+ ) {
318
+ const suffix =
319
+ defaultValue
320
+ ? " (Y/n) "
321
+ : " (y/N) ";
322
+
323
+ while (true) {
324
+ const answer =
325
+ (
326
+ await prompt.question(
327
+ `${label}${suffix}`
328
+ )
329
+ )
330
+ .trim()
331
+ .toLowerCase();
332
+
333
+ if (!answer) {
334
+ return defaultValue;
335
+ }
336
+
337
+ if (
338
+ answer === "y" ||
339
+ answer === "yes"
340
+ ) {
341
+ return true;
342
+ }
343
+
344
+ if (
345
+ answer === "n" ||
346
+ answer === "no"
347
+ ) {
348
+ return false;
349
+ }
350
+
351
+ console.log(
352
+ "Please answer yes or no."
353
+ );
354
+ }
355
+ }
356
+
357
+ async function askDatabase(
358
+ prompt
359
+ ) {
360
+ const labels = {
361
+ none: "None",
362
+ mysql: "MySQL",
363
+ postgresql: "PostgreSQL",
364
+ sqlite: "SQLite",
365
+ mongodb: "MongoDB",
366
+ };
367
+
368
+ console.log(
369
+ "Select a database:"
370
+ );
371
+
372
+ DATABASE_CHOICES.forEach(
373
+ (
374
+ value,
375
+ index
376
+ ) => {
377
+ console.log(
378
+ ` ${index + 1}) ${labels[value]}`
379
+ );
380
+ }
381
+ );
382
+
383
+ while (true) {
384
+ const answer =
385
+ (
386
+ await prompt.question(
387
+ "Database (1): "
388
+ )
389
+ ).trim();
390
+
391
+ if (!answer) {
392
+ return "none";
393
+ }
394
+
395
+ const index =
396
+ Number(
397
+ answer
398
+ ) - 1;
399
+
400
+ if (
401
+ Number.isInteger(
402
+ index
403
+ ) &&
404
+ index >= 0 &&
405
+ index <
406
+ DATABASE_CHOICES.length
407
+ ) {
408
+ return DATABASE_CHOICES[
409
+ index
410
+ ];
411
+ }
412
+
413
+ const normalized =
414
+ answer.toLowerCase();
415
+
416
+ if (
417
+ DATABASE_CHOICES.includes(
418
+ normalized
419
+ )
420
+ ) {
421
+ return normalized;
422
+ }
423
+
424
+ console.log(
425
+ "Choose a number from the list or enter a database name."
426
+ );
427
+ }
428
+ }
429
+
430
+ function formatDatabaseName(
431
+ value
432
+ ) {
433
+ const names = {
434
+ none: "None",
435
+ mysql: "MySQL",
436
+ postgresql: "PostgreSQL",
437
+ sqlite: "SQLite",
438
+ mongodb: "MongoDB",
439
+ };
440
+
441
+ return names[value] ??
442
+ value;
443
+ }
444
+
139
445
  function printHelp() {
140
446
  console.log(`
141
447
  create-bcp-app
@@ -143,15 +449,26 @@ create-bcp-app
143
449
  Usage:
144
450
  npx create-bcp-app <project-name> [options]
145
451
 
452
+ Interactive setup:
453
+ - Choose whether to use Tailwind CSS
454
+ - Choose a database: None, MySQL, PostgreSQL, SQLite or MongoDB
455
+
146
456
  Options:
147
- --no-install Create files without running npm install
148
- --bcp <specifier> Override the package specifier stored under dependencies.bcp
149
- -h, --help Show this help message
457
+ --no-install Create files without running npm install
458
+ --tailwind Enable Tailwind CSS without prompting
459
+ --no-tailwind Disable Tailwind CSS without prompting
460
+ --database <database> none | mysql | postgresql | sqlite | mongodb
461
+ -y, --yes Accept defaults (Tailwind enabled, no database)
462
+ --bcp <specifier> Override the package specifier stored under dependencies.bcp
463
+ -h, --help Show this help message
150
464
 
151
465
  Examples:
152
466
  npx create-bcp-app my-app
467
+ npx create-bcp-app my-app --tailwind --database mysql
468
+ npx create-bcp-app my-app --no-tailwind --database mongodb
469
+ npx create-bcp-app my-app --yes
153
470
  npx create-bcp-app my-app --no-install
154
- npx create-bcp-app my-app --bcp file:../bcp-0.1.0.tgz
471
+ npx create-bcp-app my-app --bcp file:../bcp-0.1.2.tgz
155
472
  `);
156
473
  }
157
474
 
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "create-bcp-app",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Create a new BCP Framework application.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
+ "bcpFrameworkPackage": "@chidchanun/bcp",
7
8
  "bin": {
8
9
  "create-bcp-app": "bin/create-bcp-app.mjs"
9
10
  },
@@ -24,6 +25,5 @@
24
25
  },
25
26
  "publishConfig": {
26
27
  "access": "public"
27
- },
28
- "bcpFrameworkPackage": "@chidchanun/bcp"
28
+ }
29
29
  }
@@ -0,0 +1,31 @@
1
+ export type BcpDatabase =
2
+ | "none"
3
+ | "mysql"
4
+ | "postgresql"
5
+ | "sqlite"
6
+ | "mongodb";
7
+
8
+ export interface CreateBcpAppOptions {
9
+ projectDirectory: string;
10
+ install?: boolean;
11
+ bcpPackage?: string;
12
+ tailwind?: boolean;
13
+ database?: BcpDatabase;
14
+ packageManager?: "npm";
15
+ }
16
+
17
+ export interface CreateBcpAppResult {
18
+ targetDirectory: string;
19
+ projectName: string;
20
+ packageSpecifier: string;
21
+ tailwind: boolean;
22
+ database: BcpDatabase;
23
+ }
24
+
25
+ export function createBcpApp(
26
+ options: CreateBcpAppOptions
27
+ ): Promise<CreateBcpAppResult>;
28
+
29
+ export function validateProjectName(
30
+ value: unknown
31
+ ): string;
package/src/index.mjs CHANGED
@@ -7,6 +7,11 @@ import {
7
7
  fileURLToPath,
8
8
  } from "node:url";
9
9
 
10
+ import {
11
+ applyProjectOptions,
12
+ normalizeDatabase,
13
+ } from "./project-options.mjs";
14
+
10
15
  const packageRoot =
11
16
  path.resolve(
12
17
  path.dirname(
@@ -31,6 +36,12 @@ export async function createBcpApp(
31
36
  targetDirectory
32
37
  )
33
38
  );
39
+ const tailwind =
40
+ options.tailwind === true;
41
+ const database =
42
+ normalizeDatabase(
43
+ options.database
44
+ );
34
45
 
35
46
  ensureTargetDirectory(
36
47
  targetDirectory
@@ -102,6 +113,14 @@ export async function createBcpApp(
102
113
  },
103
114
  };
104
115
 
116
+ const selectedOptions =
117
+ applyProjectOptions({
118
+ targetDirectory,
119
+ packageJson,
120
+ tailwind,
121
+ database,
122
+ });
123
+
105
124
  fs.writeFileSync(
106
125
  path.join(
107
126
  targetDirectory,
@@ -129,6 +148,10 @@ export async function createBcpApp(
129
148
  targetDirectory,
130
149
  projectName,
131
150
  packageSpecifier,
151
+ tailwind:
152
+ selectedOptions.tailwind,
153
+ database:
154
+ selectedOptions.database,
132
155
  };
133
156
  }
134
157
 
@@ -0,0 +1,508 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export const DATABASE_CHOICES = [
5
+ "none",
6
+ "mysql",
7
+ "postgresql",
8
+ "sqlite",
9
+ "mongodb",
10
+ ];
11
+
12
+ const DATABASE_PRESETS = {
13
+ mysql: {
14
+ dependencies: {
15
+ mysql2: "^3.24.2",
16
+ },
17
+ devDependencies: {},
18
+ env: [
19
+ "DATABASE_URL=mysql://root:password@localhost:3306/bcp_app",
20
+ ],
21
+ source: `import mysql from "mysql2/promise";
22
+
23
+ const connectionString =
24
+ process.env.DATABASE_URL;
25
+
26
+ if (!connectionString) {
27
+ throw new Error(
28
+ "DATABASE_URL is required for MySQL."
29
+ );
30
+ }
31
+
32
+ export const db =
33
+ mysql.createPool(
34
+ connectionString
35
+ );
36
+ `,
37
+ },
38
+ postgresql: {
39
+ dependencies: {
40
+ pg: "^8.23.0",
41
+ },
42
+ devDependencies: {
43
+ "@types/pg": "^8.23.1",
44
+ },
45
+ env: [
46
+ "DATABASE_URL=postgresql://postgres:password@localhost:5432/bcp_app",
47
+ ],
48
+ source: `import {
49
+ Pool,
50
+ } from "pg";
51
+
52
+ const connectionString =
53
+ process.env.DATABASE_URL;
54
+
55
+ if (!connectionString) {
56
+ throw new Error(
57
+ "DATABASE_URL is required for PostgreSQL."
58
+ );
59
+ }
60
+
61
+ export const db =
62
+ new Pool({
63
+ connectionString,
64
+ });
65
+ `,
66
+ },
67
+ sqlite: {
68
+ dependencies: {
69
+ "better-sqlite3": "^13.0.3",
70
+ },
71
+ devDependencies: {
72
+ "@types/better-sqlite3": "^9.6.0",
73
+ },
74
+ env: [
75
+ "DATABASE_URL=./data/bcp.sqlite",
76
+ ],
77
+ source: `import fs from "node:fs";
78
+ import path from "node:path";
79
+
80
+ import Database from "better-sqlite3";
81
+
82
+ const databaseFile =
83
+ process.env.DATABASE_URL ??
84
+ "./data/bcp.sqlite";
85
+ const resolvedFile =
86
+ path.resolve(
87
+ databaseFile
88
+ );
89
+
90
+ fs.mkdirSync(
91
+ path.dirname(
92
+ resolvedFile
93
+ ),
94
+ {
95
+ recursive: true,
96
+ }
97
+ );
98
+
99
+ export const db =
100
+ new Database(
101
+ resolvedFile
102
+ );
103
+ `,
104
+ },
105
+ mongodb: {
106
+ dependencies: {
107
+ mongodb: "^7.6.0",
108
+ },
109
+ devDependencies: {},
110
+ env: [
111
+ "DATABASE_URL=mongodb://localhost:27017",
112
+ "DATABASE_NAME=bcp_app",
113
+ ],
114
+ source: `import {
115
+ MongoClient,
116
+ } from "mongodb";
117
+
118
+ const connectionString =
119
+ process.env.DATABASE_URL;
120
+
121
+ if (!connectionString) {
122
+ throw new Error(
123
+ "DATABASE_URL is required for MongoDB."
124
+ );
125
+ }
126
+
127
+ export const mongoClient =
128
+ new MongoClient(
129
+ connectionString
130
+ );
131
+
132
+ export async function getDatabase() {
133
+ await mongoClient.connect();
134
+
135
+ return mongoClient.db(
136
+ process.env.DATABASE_NAME ??
137
+ "bcp_app"
138
+ );
139
+ }
140
+ `,
141
+ },
142
+ };
143
+
144
+ export function normalizeDatabase(
145
+ value
146
+ ) {
147
+ const normalized =
148
+ String(
149
+ value ?? "none"
150
+ )
151
+ .trim()
152
+ .toLowerCase();
153
+
154
+ if (
155
+ !DATABASE_CHOICES.includes(
156
+ normalized
157
+ )
158
+ ) {
159
+ throw new Error(
160
+ `Unsupported database: ${normalized}. Choose one of: ${DATABASE_CHOICES.join(", ")}.`
161
+ );
162
+ }
163
+
164
+ return normalized;
165
+ }
166
+
167
+ export function applyProjectOptions({
168
+ targetDirectory,
169
+ packageJson,
170
+ tailwind = false,
171
+ database = "none",
172
+ }) {
173
+ const selectedDatabase =
174
+ normalizeDatabase(
175
+ database
176
+ );
177
+
178
+ if (tailwind) {
179
+ configureTailwind(
180
+ targetDirectory,
181
+ packageJson
182
+ );
183
+ }
184
+
185
+ if (
186
+ selectedDatabase !== "none"
187
+ ) {
188
+ const preset =
189
+ DATABASE_PRESETS[
190
+ selectedDatabase
191
+ ];
192
+
193
+ packageJson.dependencies = {
194
+ ...packageJson.dependencies,
195
+ ...preset.dependencies,
196
+ };
197
+ packageJson.devDependencies = {
198
+ ...packageJson.devDependencies,
199
+ ...preset.devDependencies,
200
+ };
201
+
202
+ writeFile(
203
+ targetDirectory,
204
+ "lib/database.ts",
205
+ preset.source
206
+ );
207
+ appendEnvExample(
208
+ targetDirectory,
209
+ preset.env
210
+ );
211
+ }
212
+
213
+ return {
214
+ tailwind:
215
+ Boolean(
216
+ tailwind
217
+ ),
218
+ database:
219
+ selectedDatabase,
220
+ };
221
+ }
222
+
223
+ function configureTailwind(
224
+ targetDirectory,
225
+ packageJson
226
+ ) {
227
+ packageJson.devDependencies = {
228
+ ...packageJson.devDependencies,
229
+ tailwindcss:
230
+ "^4.3.3",
231
+ "@tailwindcss/cli":
232
+ "^4.3.3",
233
+ concurrently:
234
+ "^10.0.5",
235
+ };
236
+
237
+ packageJson.scripts = {
238
+ ...packageJson.scripts,
239
+ dev:
240
+ "npm run css:build && concurrently -k -n tailwind,bcp \"npm run css:watch\" \"bcp dev\"",
241
+ build:
242
+ "npm run css:build:prod && bcp build",
243
+ "css:build":
244
+ "tailwindcss -i ./app/globals.css -o ./public/bcp.css",
245
+ "css:watch":
246
+ "tailwindcss -i ./app/globals.css -o ./public/bcp.css --watch",
247
+ "css:build:prod":
248
+ "tailwindcss -i ./app/globals.css -o ./public/bcp.css --minify",
249
+ };
250
+
251
+ writeFile(
252
+ targetDirectory,
253
+ "app/globals.css",
254
+ `@import "tailwindcss" source("../");
255
+
256
+ @theme {
257
+ --font-sans: Inter, ui-sans-serif, system-ui, sans-serif;
258
+ }
259
+ `
260
+ );
261
+
262
+ writeFile(
263
+ targetDirectory,
264
+ "app/api/bcp-styles/route.ts",
265
+ `import fs from "node:fs/promises";
266
+ import path from "node:path";
267
+
268
+ export async function GET() {
269
+ const stylesheet =
270
+ path.join(
271
+ process.cwd(),
272
+ "public",
273
+ "bcp.css"
274
+ );
275
+
276
+ try {
277
+ const css =
278
+ await fs.readFile(
279
+ stylesheet,
280
+ "utf8"
281
+ );
282
+
283
+ return new Response(
284
+ css,
285
+ {
286
+ headers: {
287
+ "Content-Type":
288
+ "text/css; charset=utf-8",
289
+ "Cache-Control":
290
+ "no-store, max-age=0",
291
+ },
292
+ }
293
+ );
294
+ } catch {
295
+ return new Response(
296
+ "/* BCP Tailwind stylesheet has not been built yet. */",
297
+ {
298
+ status: 503,
299
+ headers: {
300
+ "Content-Type":
301
+ "text/css; charset=utf-8",
302
+ "Cache-Control":
303
+ "no-store, max-age=0",
304
+ },
305
+ }
306
+ );
307
+ }
308
+ }
309
+ `
310
+ );
311
+
312
+ fs.mkdirSync(
313
+ path.join(
314
+ targetDirectory,
315
+ "public"
316
+ ),
317
+ {
318
+ recursive: true,
319
+ }
320
+ );
321
+
322
+ injectTailwindStylesheet(
323
+ targetDirectory
324
+ );
325
+ applyTailwindStarterStyles(
326
+ targetDirectory
327
+ );
328
+ appendGitIgnore(
329
+ targetDirectory,
330
+ "public/bcp.css"
331
+ );
332
+ }
333
+
334
+ function injectTailwindStylesheet(
335
+ targetDirectory
336
+ ) {
337
+ const layoutFile =
338
+ path.join(
339
+ targetDirectory,
340
+ "app",
341
+ "layout.tsx"
342
+ );
343
+ const source =
344
+ fs.readFileSync(
345
+ layoutFile,
346
+ "utf8"
347
+ );
348
+ const rootPattern =
349
+ /^([ \t]*)<div>[ \t]*(\r?\n)/m;
350
+ const match =
351
+ rootPattern.exec(
352
+ source
353
+ );
354
+
355
+ if (!match) {
356
+ throw new Error(
357
+ "BCP create-app could not attach the Tailwind stylesheet to app/layout.tsx."
358
+ );
359
+ }
360
+
361
+ const indent =
362
+ match[1];
363
+ const newline =
364
+ match[2];
365
+ const childIndent =
366
+ `${indent} `;
367
+ const propertyIndent =
368
+ `${childIndent} `;
369
+ const replacement =
370
+ `${indent}<div>${newline}` +
371
+ `${childIndent}<link${newline}` +
372
+ `${propertyIndent}rel="stylesheet"${newline}` +
373
+ `${propertyIndent}href="/api/bcp-styles"${newline}` +
374
+ `${childIndent}/>${newline}`;
375
+
376
+ fs.writeFileSync(
377
+ layoutFile,
378
+ source.replace(
379
+ rootPattern,
380
+ replacement
381
+ ),
382
+ "utf8"
383
+ );
384
+ }
385
+
386
+ function applyTailwindStarterStyles(
387
+ targetDirectory
388
+ ) {
389
+ const pageFile =
390
+ path.join(
391
+ targetDirectory,
392
+ "app",
393
+ "page.tsx"
394
+ );
395
+ let source =
396
+ fs.readFileSync(
397
+ pageFile,
398
+ "utf8"
399
+ );
400
+
401
+ source =
402
+ source.replace(
403
+ " <section>",
404
+ " <section className=\"mx-auto max-w-3xl space-y-6 px-6 py-16\">"
405
+ );
406
+ source =
407
+ source.replace(
408
+ " <h1>",
409
+ " <h1 className=\"text-4xl font-bold tracking-tight text-slate-900\">"
410
+ );
411
+ source =
412
+ source.replace(
413
+ " <p>",
414
+ " <p className=\"text-lg text-slate-600\">"
415
+ );
416
+
417
+ fs.writeFileSync(
418
+ pageFile,
419
+ source,
420
+ "utf8"
421
+ );
422
+ }
423
+
424
+ function appendGitIgnore(
425
+ targetDirectory,
426
+ entry
427
+ ) {
428
+ const filePath =
429
+ path.join(
430
+ targetDirectory,
431
+ ".gitignore"
432
+ );
433
+ const current =
434
+ fs.existsSync(
435
+ filePath
436
+ )
437
+ ? fs.readFileSync(
438
+ filePath,
439
+ "utf8"
440
+ ).trimEnd()
441
+ : "";
442
+ const lines =
443
+ new Set(
444
+ current
445
+ .split(/\r?\n/)
446
+ .filter(Boolean)
447
+ );
448
+
449
+ lines.add(entry);
450
+
451
+ fs.writeFileSync(
452
+ filePath,
453
+ `${Array.from(lines).join("\n")}\n`,
454
+ "utf8"
455
+ );
456
+ }
457
+
458
+ function appendEnvExample(
459
+ targetDirectory,
460
+ lines
461
+ ) {
462
+ const filePath =
463
+ path.join(
464
+ targetDirectory,
465
+ ".env.example"
466
+ );
467
+ const current =
468
+ fs.existsSync(
469
+ filePath
470
+ )
471
+ ? fs.readFileSync(
472
+ filePath,
473
+ "utf8"
474
+ ).trimEnd()
475
+ : "";
476
+
477
+ fs.writeFileSync(
478
+ filePath,
479
+ `${current}${current ? "\n\n" : ""}# Database\n${lines.join("\n")}\n`,
480
+ "utf8"
481
+ );
482
+ }
483
+
484
+ function writeFile(
485
+ targetDirectory,
486
+ relativePath,
487
+ content
488
+ ) {
489
+ const filePath =
490
+ path.join(
491
+ targetDirectory,
492
+ relativePath
493
+ );
494
+
495
+ fs.mkdirSync(
496
+ path.dirname(
497
+ filePath
498
+ ),
499
+ {
500
+ recursive: true,
501
+ }
502
+ );
503
+ fs.writeFileSync(
504
+ filePath,
505
+ content,
506
+ "utf8"
507
+ );
508
+ }
@@ -18,6 +18,7 @@
18
18
  "include": [
19
19
  "app/**/*.ts",
20
20
  "app/**/*.tsx",
21
+ "lib/**/*.ts",
21
22
  "bcp.config.ts"
22
23
  ],
23
24
  "exclude": [