create-bcp-app 0.1.10 → 0.1.12

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.
@@ -4,7 +4,6 @@ import path from "node:path";
4
4
  import {
5
5
  createInterface,
6
6
  } from "node:readline/promises";
7
-
8
7
  import {
9
8
  stdin as input,
10
9
  stdout as output,
@@ -20,288 +19,327 @@ import {
20
19
  normalizeDatabase,
21
20
  } from "../src/project-options.mjs";
22
21
 
23
- const args =
24
- process.argv.slice(2);
22
+ await main();
25
23
 
26
- if (
27
- args.includes("--help") ||
28
- args.includes("-h")
29
- ) {
30
- printHelp();
31
- process.exit(0);
32
- }
33
-
34
- let projectDirectory =
35
- null;
36
- let install =
37
- true;
38
- let bcpPackage =
39
- undefined;
40
- let tailwind =
41
- undefined;
42
- let database =
43
- undefined;
44
- let auth =
45
- undefined;
46
- let acceptDefaults =
47
- false;
48
-
49
- for (
50
- let index = 0;
51
- index < args.length;
52
- index++
53
- ) {
54
- const value =
55
- args[index];
24
+ async function main() {
25
+ const args =
26
+ process.argv.slice(2);
56
27
 
57
28
  if (
58
- value === "--no-install"
29
+ args.includes("--help") ||
30
+ args.includes("-h")
59
31
  ) {
60
- install =
61
- false;
62
- continue;
32
+ printHelp();
33
+ return;
63
34
  }
64
35
 
65
- if (
66
- value === "--yes" ||
67
- value === "-y"
68
- ) {
69
- acceptDefaults =
70
- true;
71
- continue;
72
- }
36
+ let parsed;
73
37
 
74
- if (
75
- value === "--tailwind"
76
- ) {
77
- tailwind =
78
- true;
79
- continue;
38
+ try {
39
+ parsed =
40
+ parseArguments(
41
+ args
42
+ );
43
+ } catch (error) {
44
+ reportError(
45
+ error
46
+ );
47
+ return;
80
48
  }
81
49
 
82
- if (
83
- value === "--no-tailwind"
84
- ) {
85
- tailwind =
86
- false;
87
- continue;
50
+ if (!parsed.projectDirectory) {
51
+ printHelp();
52
+ process.exitCode =
53
+ 1;
54
+ return;
88
55
  }
89
56
 
90
- if (
91
- value === "--database"
92
- ) {
93
- const nextValue =
94
- args[
95
- index + 1
96
- ];
57
+ let prompt =
58
+ null;
97
59
 
98
- if (!nextValue) {
99
- fail(
100
- "--database requires a value."
60
+ try {
61
+ const resolved =
62
+ await resolveOptions(
63
+ parsed
101
64
  );
102
- }
103
65
 
104
- database =
105
- normalizeDatabase(
106
- nextValue
107
- );
108
- index++;
109
- continue;
110
- }
66
+ prompt =
67
+ resolved.prompt;
111
68
 
112
- if (
113
- value === "--auth"
114
- ) {
115
- const nextValue =
116
- args[
117
- index + 1
118
- ];
69
+ console.log("");
70
+ console.log(
71
+ parsed.install
72
+ ? `Creating BCP app and installing dependencies in ${path.resolve(parsed.projectDirectory)}...`
73
+ : `Creating BCP app in ${path.resolve(parsed.projectDirectory)}...`
74
+ );
75
+ console.log("");
119
76
 
120
- if (!nextValue) {
121
- fail(
122
- "--auth requires a value."
123
- );
124
- }
77
+ const result =
78
+ await createBcpApp({
79
+ projectDirectory:
80
+ parsed.projectDirectory,
81
+ install:
82
+ parsed.install,
83
+ bcpPackage:
84
+ parsed.bcpPackage,
85
+ tailwind:
86
+ resolved.tailwind,
87
+ database:
88
+ resolved.database,
89
+ auth:
90
+ resolved.auth,
91
+ packageManager:
92
+ "npm",
93
+ });
125
94
 
126
- auth =
127
- normalizeAuth(
128
- nextValue
129
- );
130
- index++;
131
- continue;
95
+ printSuccess(
96
+ result,
97
+ parsed.install
98
+ );
99
+ } catch (error) {
100
+ reportError(
101
+ error
102
+ );
103
+ } finally {
104
+ prompt?.close();
132
105
  }
106
+ }
133
107
 
134
- if (
135
- value === "--bcp"
108
+ function parseArguments(
109
+ args
110
+ ) {
111
+ let projectDirectory =
112
+ null;
113
+ let install =
114
+ true;
115
+ let bcpPackage =
116
+ undefined;
117
+ let tailwind =
118
+ undefined;
119
+ let database =
120
+ undefined;
121
+ let auth =
122
+ undefined;
123
+ let acceptDefaults =
124
+ false;
125
+
126
+ for (
127
+ let index = 0;
128
+ index < args.length;
129
+ index++
136
130
  ) {
137
- const nextValue =
138
- args[
139
- index + 1
140
- ];
131
+ const value =
132
+ args[index];
141
133
 
142
- if (!nextValue) {
143
- fail(
144
- "--bcp requires a package specifier."
145
- );
134
+ if (
135
+ value === "--no-install"
136
+ ) {
137
+ install =
138
+ false;
139
+ continue;
146
140
  }
147
141
 
148
- bcpPackage =
149
- nextValue;
150
- index++;
151
- continue;
152
- }
142
+ if (
143
+ value === "--yes" ||
144
+ value === "-y"
145
+ ) {
146
+ acceptDefaults =
147
+ true;
148
+ continue;
149
+ }
153
150
 
154
- if (
155
- value.startsWith("-")
156
- ) {
157
- fail(
158
- `Unknown option: ${value}`
159
- );
160
- }
151
+ if (
152
+ value === "--tailwind"
153
+ ) {
154
+ tailwind =
155
+ true;
156
+ continue;
157
+ }
161
158
 
162
- if (
163
- projectDirectory !==
164
- null
165
- ) {
166
- fail(
167
- "Only one project directory may be provided."
168
- );
169
- }
159
+ if (
160
+ value === "--no-tailwind"
161
+ ) {
162
+ tailwind =
163
+ false;
164
+ continue;
165
+ }
170
166
 
171
- projectDirectory =
172
- value;
173
- }
167
+ if (
168
+ value === "--database"
169
+ ) {
170
+ const nextValue =
171
+ args[
172
+ index + 1
173
+ ];
174
+
175
+ if (!nextValue) {
176
+ throw new Error(
177
+ "--database requires a value."
178
+ );
179
+ }
174
180
 
175
- if (!projectDirectory) {
176
- printHelp();
177
- process.exitCode =
178
- 1;
179
- } else {
180
- try {
181
- const selected =
182
- await resolveInteractiveOptions({
183
- tailwind,
184
- database,
185
- auth,
186
- acceptDefaults,
187
- });
181
+ database =
182
+ normalizeDatabase(
183
+ nextValue
184
+ );
185
+ index++;
186
+ continue;
187
+ }
188
188
 
189
- const result =
190
- await createBcpApp({
191
- projectDirectory,
192
- install,
193
- bcpPackage,
194
- tailwind:
195
- selected.tailwind,
196
- database:
197
- selected.database,
198
- auth:
199
- selected.auth,
200
- packageManager:
201
- "npm",
202
- });
189
+ if (
190
+ value === "--auth"
191
+ ) {
192
+ const nextValue =
193
+ args[
194
+ index + 1
195
+ ];
196
+
197
+ if (!nextValue) {
198
+ throw new Error(
199
+ "--auth requires a value."
200
+ );
201
+ }
203
202
 
204
- const relative =
205
- path.relative(
206
- process.cwd(),
207
- result.targetDirectory
208
- ) || ".";
203
+ auth =
204
+ normalizeAuth(
205
+ nextValue
206
+ );
207
+ index++;
208
+ continue;
209
+ }
209
210
 
210
- console.log("");
211
- console.log(
212
- `Created BCP app: ${result.projectName}`
213
- );
214
- console.log(
215
- `Location: ${result.targetDirectory}`
216
- );
217
- console.log(
218
- `Tailwind CSS: ${result.tailwind ? "Yes" : "No"}`
219
- );
220
- console.log(
221
- `Database: ${formatDatabaseName(result.database)}`
222
- );
223
- console.log(
224
- `Authentication: ${formatAuthName(result.auth)}`
225
- );
226
- console.log("");
227
- console.log(
228
- "Next steps:"
229
- );
230
- console.log(
231
- ` cd ${quoteIfNeeded(relative)}`
232
- );
211
+ if (
212
+ value === "--bcp"
213
+ ) {
214
+ const nextValue =
215
+ args[
216
+ index + 1
217
+ ];
218
+
219
+ if (!nextValue) {
220
+ throw new Error(
221
+ "--bcp requires a package specifier."
222
+ );
223
+ }
233
224
 
234
- if (!install) {
235
- console.log(
236
- " npm install"
225
+ bcpPackage =
226
+ nextValue;
227
+ index++;
228
+ continue;
229
+ }
230
+
231
+ if (
232
+ value.startsWith("-")
233
+ ) {
234
+ throw new Error(
235
+ `Unknown option: ${value}`
237
236
  );
238
237
  }
239
238
 
240
- console.log(
241
- " npm run dev"
242
- );
243
- console.log("");
244
- } catch (error) {
245
- fail(
246
- error instanceof Error
247
- ? error.message
248
- : String(error)
249
- );
239
+ if (
240
+ projectDirectory !==
241
+ null
242
+ ) {
243
+ throw new Error(
244
+ "Only one project directory may be provided."
245
+ );
246
+ }
247
+
248
+ projectDirectory =
249
+ value;
250
250
  }
251
+
252
+ return {
253
+ projectDirectory,
254
+ install,
255
+ bcpPackage,
256
+ tailwind,
257
+ database,
258
+ auth,
259
+ acceptDefaults,
260
+ };
251
261
  }
252
262
 
253
- async function resolveInteractiveOptions({
263
+ async function resolveOptions({
254
264
  tailwind: selectedTailwind,
255
265
  database: selectedDatabase,
256
266
  auth: selectedAuth,
257
- acceptDefaults: useDefaults,
267
+ acceptDefaults,
258
268
  }) {
259
- let resolvedTailwind =
269
+ let tailwind =
260
270
  selectedTailwind;
261
- let resolvedDatabase =
271
+ let database =
262
272
  selectedDatabase;
263
- let resolvedAuth =
273
+ let auth =
264
274
  selectedAuth;
265
275
 
276
+ if (acceptDefaults) {
277
+ return {
278
+ tailwind:
279
+ tailwind ??
280
+ true,
281
+ database:
282
+ normalizeDatabase(
283
+ database ??
284
+ "none"
285
+ ),
286
+ auth:
287
+ normalizeAuth(
288
+ auth ??
289
+ "none"
290
+ ),
291
+ prompt:
292
+ null,
293
+ };
294
+ }
295
+
266
296
  const interactive =
267
297
  Boolean(
268
298
  input.isTTY &&
269
299
  output.isTTY
270
300
  );
271
301
 
272
- if (useDefaults) {
302
+ if (!interactive) {
273
303
  return {
274
304
  tailwind:
275
- resolvedTailwind ??
276
- true,
305
+ tailwind ??
306
+ false,
277
307
  database:
278
308
  normalizeDatabase(
279
- resolvedDatabase ??
309
+ database ??
280
310
  "none"
281
311
  ),
282
312
  auth:
283
313
  normalizeAuth(
284
- resolvedAuth ??
314
+ auth ??
285
315
  "none"
286
316
  ),
317
+ prompt:
318
+ null,
287
319
  };
288
320
  }
289
321
 
290
- if (!interactive) {
322
+ const needsPrompt =
323
+ tailwind === undefined ||
324
+ database === undefined ||
325
+ auth === undefined;
326
+
327
+ if (!needsPrompt) {
291
328
  return {
292
329
  tailwind:
293
- resolvedTailwind ??
294
- false,
330
+ Boolean(
331
+ tailwind
332
+ ),
295
333
  database:
296
334
  normalizeDatabase(
297
- resolvedDatabase ??
298
- "none"
335
+ database
299
336
  ),
300
337
  auth:
301
338
  normalizeAuth(
302
- resolvedAuth ??
303
- "none"
339
+ auth
304
340
  ),
341
+ prompt:
342
+ null,
305
343
  };
306
344
  }
307
345
 
@@ -319,10 +357,10 @@ async function resolveInteractiveOptions({
319
357
  console.log("");
320
358
 
321
359
  if (
322
- resolvedTailwind ===
360
+ tailwind ===
323
361
  undefined
324
362
  ) {
325
- resolvedTailwind =
363
+ tailwind =
326
364
  await askYesNo(
327
365
  prompt,
328
366
  "Use Tailwind CSS?",
@@ -331,41 +369,43 @@ async function resolveInteractiveOptions({
331
369
  }
332
370
 
333
371
  if (
334
- resolvedDatabase ===
372
+ database ===
335
373
  undefined
336
374
  ) {
337
- resolvedDatabase =
375
+ database =
338
376
  await askDatabase(
339
377
  prompt
340
378
  );
341
379
  }
342
380
 
343
381
  if (
344
- resolvedAuth ===
382
+ auth ===
345
383
  undefined
346
384
  ) {
347
- resolvedAuth =
385
+ auth =
348
386
  await askAuth(
349
387
  prompt
350
388
  );
351
389
  }
352
- } finally {
390
+ } catch (error) {
353
391
  prompt.close();
392
+ throw error;
354
393
  }
355
394
 
356
395
  return {
357
396
  tailwind:
358
397
  Boolean(
359
- resolvedTailwind
398
+ tailwind
360
399
  ),
361
400
  database:
362
401
  normalizeDatabase(
363
- resolvedDatabase
402
+ database
364
403
  ),
365
404
  auth:
366
405
  normalizeAuth(
367
- resolvedAuth
406
+ auth
368
407
  ),
408
+ prompt,
369
409
  };
370
410
  }
371
411
 
@@ -451,22 +491,13 @@ async function askDatabase(
451
491
  return "none";
452
492
  }
453
493
 
454
- const index =
455
- Number(
456
- answer
457
- ) - 1;
458
-
459
- if (
460
- Number.isInteger(
461
- index
462
- ) &&
463
- index >= 0 &&
464
- index <
465
- DATABASE_CHOICES.length
466
- ) {
467
- return DATABASE_CHOICES[
468
- index
494
+ const choice =
495
+ DATABASE_CHOICES[
496
+ Number(answer) - 1
469
497
  ];
498
+
499
+ if (choice) {
500
+ return choice;
470
501
  }
471
502
 
472
503
  const normalized =
@@ -522,22 +553,13 @@ async function askAuth(
522
553
  return "none";
523
554
  }
524
555
 
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
556
+ const choice =
557
+ AUTH_CHOICES[
558
+ Number(answer) - 1
540
559
  ];
560
+
561
+ if (choice) {
562
+ return choice;
541
563
  }
542
564
 
543
565
  const normalized =
@@ -557,6 +579,52 @@ async function askAuth(
557
579
  }
558
580
  }
559
581
 
582
+ function printSuccess(
583
+ result,
584
+ installed
585
+ ) {
586
+ const relative =
587
+ path.relative(
588
+ process.cwd(),
589
+ result.targetDirectory
590
+ ) || ".";
591
+
592
+ console.log("");
593
+ console.log(
594
+ `Created BCP app: ${result.projectName}`
595
+ );
596
+ console.log(
597
+ `Location: ${result.targetDirectory}`
598
+ );
599
+ console.log(
600
+ `Tailwind CSS: ${result.tailwind ? "Yes" : "No"}`
601
+ );
602
+ console.log(
603
+ `Database: ${formatDatabaseName(result.database)}`
604
+ );
605
+ console.log(
606
+ `Authentication: ${formatAuthName(result.auth)}`
607
+ );
608
+ console.log("");
609
+ console.log(
610
+ "Next steps:"
611
+ );
612
+ console.log(
613
+ ` cd ${quoteIfNeeded(relative)}`
614
+ );
615
+
616
+ if (!installed) {
617
+ console.log(
618
+ " npm install"
619
+ );
620
+ }
621
+
622
+ console.log(
623
+ " npm run dev"
624
+ );
625
+ console.log("");
626
+ }
627
+
560
628
  function formatDatabaseName(
561
629
  value
562
630
  ) {
@@ -613,7 +681,6 @@ Examples:
613
681
  npx create-bcp-app my-app --no-tailwind --database mongodb
614
682
  npx create-bcp-app my-app --yes
615
683
  npx create-bcp-app my-app --no-install
616
- npx create-bcp-app my-app --bcp file:../bcp-0.1.8.tgz
617
684
  `);
618
685
  }
619
686
 
@@ -629,11 +696,19 @@ function quoteIfNeeded(
629
696
  : value;
630
697
  }
631
698
 
632
- function fail(
633
- message
699
+ function reportError(
700
+ error
634
701
  ) {
702
+ const message =
703
+ error instanceof Error
704
+ ? error.message
705
+ : String(
706
+ error
707
+ );
708
+
635
709
  console.error(
636
710
  `create-bcp-app: ${message}`
637
711
  );
638
- process.exit(1);
712
+ process.exitCode =
713
+ 1;
639
714
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bcp-app",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Create a new BCP Framework application.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.d.mts CHANGED
@@ -28,6 +28,20 @@ export interface CreateBcpAppResult {
28
28
  auth: BcpAuthPreset;
29
29
  }
30
30
 
31
+ export interface ResolveNpmInvocationOptions {
32
+ env?: Record<
33
+ string,
34
+ string | undefined
35
+ >;
36
+ platform?: string;
37
+ execPath?: string;
38
+ }
39
+
40
+ export interface NpmInvocation {
41
+ command: string;
42
+ args: string[];
43
+ }
44
+
31
45
  export function createBcpApp(
32
46
  options: CreateBcpAppOptions
33
47
  ): Promise<CreateBcpAppResult>;
@@ -35,3 +49,8 @@ export function createBcpApp(
35
49
  export function validateProjectName(
36
50
  value: unknown
37
51
  ): string;
52
+
53
+ export function resolveNpmInvocation(
54
+ args: string[],
55
+ options?: ResolveNpmInvocationOptions
56
+ ): NpmInvocation;
package/src/index.mjs CHANGED
@@ -359,8 +359,11 @@ function runInstall(
359
359
  {
360
360
  cwd:
361
361
  targetDirectory,
362
- stdio:
362
+ stdio: [
363
+ "ignore",
363
364
  "inherit",
365
+ "inherit",
366
+ ],
364
367
  env:
365
368
  process.env,
366
369
  }
@@ -371,7 +374,7 @@ function runInstall(
371
374
  reject
372
375
  );
373
376
  child.once(
374
- "exit",
377
+ "close",
375
378
  (
376
379
  code,
377
380
  signal
@@ -396,16 +399,28 @@ function runInstall(
396
399
  );
397
400
  }
398
401
 
399
- function resolveNpmInvocation(
400
- args
402
+ export function resolveNpmInvocation(
403
+ args,
404
+ options = {}
401
405
  ) {
406
+ const environment =
407
+ options.env ??
408
+ process.env;
409
+ const platform =
410
+ options.platform ??
411
+ process.platform;
412
+ const execPath =
413
+ options.execPath ??
414
+ process.execPath;
402
415
  const npmExecPath =
403
- process.env.npm_execpath;
416
+ resolveNpmCliPath(
417
+ environment.npm_execpath
418
+ );
404
419
 
405
420
  if (npmExecPath) {
406
421
  return {
407
422
  command:
408
- process.execPath,
423
+ execPath,
409
424
  args: [
410
425
  npmExecPath,
411
426
  ...args,
@@ -414,11 +429,11 @@ function resolveNpmInvocation(
414
429
  }
415
430
 
416
431
  if (
417
- process.platform === "win32"
432
+ platform === "win32"
418
433
  ) {
419
434
  return {
420
435
  command:
421
- process.env.ComSpec ??
436
+ environment.ComSpec ??
422
437
  "cmd.exe",
423
438
  args: [
424
439
  "/d",
@@ -436,3 +451,51 @@ function resolveNpmInvocation(
436
451
  args,
437
452
  };
438
453
  }
454
+
455
+ function resolveNpmCliPath(
456
+ npmExecPath
457
+ ) {
458
+ if (
459
+ typeof npmExecPath !==
460
+ "string" ||
461
+ npmExecPath.trim() ===
462
+ ""
463
+ ) {
464
+ return null;
465
+ }
466
+
467
+ const normalized =
468
+ npmExecPath.trim();
469
+ const baseName =
470
+ path.basename(
471
+ normalized
472
+ ).toLowerCase();
473
+
474
+ if (
475
+ baseName ===
476
+ "npm-cli.js"
477
+ ) {
478
+ return normalized;
479
+ }
480
+
481
+ if (
482
+ baseName ===
483
+ "npx-cli.js"
484
+ ) {
485
+ const npmCliPath =
486
+ path.join(
487
+ path.dirname(
488
+ normalized
489
+ ),
490
+ "npm-cli.js"
491
+ );
492
+
493
+ return fs.existsSync(
494
+ npmCliPath
495
+ )
496
+ ? npmCliPath
497
+ : null;
498
+ }
499
+
500
+ return null;
501
+ }